Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Mikhail Filimonov 2024-02-29 09:04:15 +04:00
commit 26f128a543
75 changed files with 2032 additions and 961 deletions

View File

@ -11316,6 +11316,11 @@ Sorry for the inconvenience.";
"ChannelBoost.Header.Giveaway" = "giveaway";
"ChannelBoost.Header.Features" = "features";
"Premium.FolderTags" = "Folder Tags";
"Premium.FolderTagsInfo" = "Add colorful labels to chats for faster access with Telegram Premium.";
"Premium.FolderTagsStandaloneInfo" = "Add colorful labels to chats for faster access with Telegram Premium.";
"Premium.FolderTags.Proceed" = "About Telegram Premium";
"Premium.Business" = "Telegram Business";
"Premium.BusinessInfo" = "Upgrade your account with business features such as location, opening hours and quick replies.";

View File

@ -80,6 +80,7 @@ public class PeerNameColors: Equatable {
colors: defaultSingleColors,
darkColors: [:],
displayOrder: [5, 3, 1, 0, 2, 4, 6],
chatFolderTagDisplayOrder: [5, 3, 1, 0, 2, 4, 6],
profileColors: [:],
profileDarkColors: [:],
profilePaletteColors: [:],
@ -97,6 +98,8 @@ public class PeerNameColors: Equatable {
public let darkColors: [Int32: Colors]
public let displayOrder: [Int32]
public let chatFolderTagDisplayOrder: [Int32]
public let profileColors: [Int32: Colors]
public let profileDarkColors: [Int32: Colors]
public let profilePaletteColors: [Int32: Colors]
@ -119,6 +122,16 @@ public class PeerNameColors: Equatable {
}
}
public func getChatFolderTag(_ color: PeerNameColor, dark: Bool = false) -> Colors {
if dark, let colors = self.darkColors[color.rawValue] {
return colors
} else if let colors = self.colors[color.rawValue] {
return colors
} else {
return PeerNameColors.defaultSingleColors[5]!
}
}
public func getProfile(_ color: PeerNameColor, dark: Bool = false, subject: Subject = .background) -> Colors {
switch subject {
case .background:
@ -152,6 +165,7 @@ public class PeerNameColors: Equatable {
colors: [Int32: Colors],
darkColors: [Int32: Colors],
displayOrder: [Int32],
chatFolderTagDisplayOrder: [Int32],
profileColors: [Int32: Colors],
profileDarkColors: [Int32: Colors],
profilePaletteColors: [Int32: Colors],
@ -166,6 +180,7 @@ public class PeerNameColors: Equatable {
self.colors = colors
self.darkColors = darkColors
self.displayOrder = displayOrder
self.chatFolderTagDisplayOrder = chatFolderTagDisplayOrder
self.profileColors = profileColors
self.profileDarkColors = profileDarkColors
self.profilePaletteColors = profilePaletteColors
@ -257,6 +272,7 @@ public class PeerNameColors: Equatable {
colors: colors,
darkColors: darkColors,
displayOrder: displayOrder,
chatFolderTagDisplayOrder: PeerNameColors.defaultValue.chatFolderTagDisplayOrder,
profileColors: profileColors,
profileDarkColors: profileDarkColors,
profilePaletteColors: profilePaletteColors,
@ -280,6 +296,9 @@ public class PeerNameColors: Equatable {
if lhs.displayOrder != rhs.displayOrder {
return false
}
if lhs.chatFolderTagDisplayOrder != rhs.chatFolderTagDisplayOrder {
return false
}
if lhs.profileColors != rhs.profileColors {
return false
}

View File

@ -38,6 +38,7 @@ public enum PremiumIntroSource {
case presence
case readTime
case messageTags
case folderTags
}
public enum PremiumGiftSource: Equatable {

View File

@ -618,6 +618,7 @@ public class BrowserScreen: ViewController {
bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom,
right: layout.safeInsets.right
),
additionalInsets: layout.additionalInsets,
inputHeight: layout.inputHeight ?? 0.0,
metrics: layout.metrics,
deviceMetrics: layout.deviceMetrics,

View File

@ -480,7 +480,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
return PeerNameColorItem(
theme: presentationData.theme,
colors: colors,
isProfile: true,
mode: .folderTag,
displayEmptyColor: true,
currentColor: isPremium ? color : nil,
isLocked: !isPremium,
@ -643,7 +643,7 @@ private func chatListFilterPresetControllerEntries(context: AccountContext, pres
}
var resolvedColor: PeerNameColors.Colors?
if let tagColor {
resolvedColor = context.peerNameColors.getProfile(tagColor, dark: presentationData.theme.overallDarkAppearance, subject: .palette)
resolvedColor = context.peerNameColors.getChatFolderTag(tagColor, dark: presentationData.theme.overallDarkAppearance)
}
entries.append(.tagColorHeader(name: state.name, color: resolvedColor, isPremium: isPremium))

View File

@ -190,7 +190,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
if displayTags, case let .filter(_, _, _, data) = preset {
let tagColor = data.color
if let tagColor {
resolvedColor = arguments.context.peerNameColors.getProfile(tagColor, dark: presentationData.theme.overallDarkAppearance, subject: .palette).main
resolvedColor = arguments.context.peerNameColors.getChatFolderTag(tagColor, dark: presentationData.theme.overallDarkAppearance).main
}
}
@ -558,14 +558,15 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
}, updateDisplayTags: { value in
context.engine.peers.updateChatListFiltersDisplayTags(isEnabled: value)
}, updateDisplayTagsLocked: {
//TODO:localize
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_reorder", scale: 0.05, colors: [:], title: nil, text: "Subscribe to **Telegram Premium** to show folder tags.", customUndoText: presentationData.strings.ChatListFolderSettings_SubscribeToMoveAllAction, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in
if case .undo = action {
pushControllerImpl?(PremiumIntroScreen(context: context, source: .folders))
}
return false })
)
var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .folderTags, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .folderTags, forceDark: false, dismissed: nil)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
pushControllerImpl?(controller)
})
let featuredFilters = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFiltersFeaturedState])

View File

@ -411,13 +411,17 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
strongSelf.arrowNode.isHidden = item.isAllChats
if let sharedIconImage = strongSelf.sharedIconNode.image {
strongSelf.sharedIconNode.frame = CGRect(origin: CGPoint(x: strongSelf.arrowNode.frame.minX + 2.0 - sharedIconImage.size.width, y: floorToScreenPixels((layout.contentSize.height - sharedIconImage.size.height) / 2.0) + 1.0), size: sharedIconImage.size)
var sharedIconFrame = CGRect(origin: CGPoint(x: strongSelf.arrowNode.frame.minX + 2.0 - sharedIconImage.size.width, y: floorToScreenPixels((layout.contentSize.height - sharedIconImage.size.height) / 2.0) + 1.0), size: sharedIconImage.size)
if item.tagColor != nil {
sharedIconFrame.origin.x -= 34.0
}
strongSelf.sharedIconNode.frame = sharedIconFrame
}
var isShared = false
if case let .filter(_, _, _, data) = item.preset, data.isShared {
isShared = true
}
strongSelf.sharedIconNode.isHidden = !isShared || item.tagColor != nil
strongSelf.sharedIconNode.isHidden = !isShared
if let tagColor = item.tagColor {
let tagIconView: UIImageView
@ -534,6 +538,9 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
var sharedIconFrame = self.sharedIconNode.frame
sharedIconFrame.origin.x = arrowFrame.minX + 2.0 - sharedIconFrame.width
if self.item?.tagColor != nil {
sharedIconFrame.origin.x -= 34.0
}
transition.updateFrame(node: self.sharedIconNode, frame: sharedIconFrame)
if let tagIconView = self.tagIconView {

View File

@ -328,7 +328,7 @@ private final class ChatListItemTagListComponent: Component {
itemId = tag.id
let tagColor = PeerNameColor(rawValue: tag.colorId)
let resolvedColor = component.context.peerNameColors.getProfile(tagColor, dark: component.theme.overallDarkAppearance, subject: .palette)
let resolvedColor = component.context.peerNameColors.getChatFolderTag(tagColor, dark: component.theme.overallDarkAppearance)
itemTitle = tag.title.uppercased()
itemBackgroundColor = resolvedColor.main.withMultipliedAlpha(0.1)
@ -2270,11 +2270,19 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
}
} else if inlineAuthorPrefix == nil, let draftState = draftState {
hasDraft = true
authorAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Draft, font: textFont, textColor: theme.messageDraftTextColor)
let draftText = stringWithAppliedEntities(draftState.text, entities: draftState.entities, baseColor: theme.messageTextColor, linkColor: theme.messageTextColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, message: nil)
attributedText = foldLineBreaks(draftText)
if !itemTags.isEmpty {
let tempAttributedText = foldLineBreaks(draftText)
let attributedTextWithDraft = NSMutableAttributedString()
attributedTextWithDraft.append(NSAttributedString(string: item.presentationData.strings.DialogList_Draft + ": ", font: textFont, textColor: theme.messageDraftTextColor))
attributedTextWithDraft.append(tempAttributedText)
attributedText = attributedTextWithDraft
} else {
authorAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Draft, font: textFont, textColor: theme.messageDraftTextColor)
attributedText = foldLineBreaks(draftText)
}
} else if let message = messages.first {
var composedString: NSMutableAttributedString
@ -3856,7 +3864,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var badgeFrame: CGRect
if textLayout.numberOfLines > 1 {
badgeFrame = CGRect(origin: CGPoint(x: textLayout.trailingLineWidth, y: textNodeFrame.height - 3.0 - badgeSize.height), size: badgeSize)
badgeFrame = CGRect(origin: CGPoint(x: textLayout.trailingLineWidth + 4.0, y: textNodeFrame.height - 3.0 - badgeSize.height), size: badgeSize)
} else {
let firstLineFrame = textLayout.linesRects().first ?? CGRect(origin: CGPoint(), size: textNodeFrame.size)
badgeFrame = CGRect(origin: CGPoint(x: 0.0, y: firstLineFrame.height + 5.0), size: badgeSize)

View File

@ -350,7 +350,7 @@ public struct ChatListNodeState: Equatable {
}
}
private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, chatListFilters: [ChatListFilter]?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, entries: [ChatListNodeViewTransitionInsertEntry]) -> [ListViewInsertItem] {
private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, isPremium: Bool, filterData: ChatListItemFilterData?, chatListFilters: [ChatListFilter]?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, entries: [ChatListNodeViewTransitionInsertEntry]) -> [ListViewInsertItem] {
return entries.map { entry -> ListViewInsertItem in
switch entry.entry {
case .HeaderEntry:
@ -433,7 +433,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
},
requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging,
displayAsTopicList: peerEntry.displayAsTopicList,
tags: chatListItemTags(location: location, accountPeerId: context.account.peerId, peer: peer.chatMainPeer, isUnread: combinedReadState?.isUnread ?? false, isMuted: isRemovedFromTotalUnreadCount, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: chatListFilters)
tags: chatListItemTags(location: location, accountPeerId: context.account.peerId, isPremium: isPremium, peer: peer.chatMainPeer, isUnread: combinedReadState?.isUnread ?? false, isMuted: isRemovedFromTotalUnreadCount, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: chatListFilters)
)),
editing: editing,
hasActiveRevealControls: hasActiveRevealControls,
@ -751,7 +751,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
}
}
private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, chatListFilters: [ChatListFilter]?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] {
private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, isPremium: Bool, filterData: ChatListItemFilterData?, chatListFilters: [ChatListFilter]?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] {
return entries.map { entry -> ListViewUpdateItem in
switch entry.entry {
case let .PeerEntry(peerEntry):
@ -811,7 +811,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
},
requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging,
displayAsTopicList: peerEntry.displayAsTopicList,
tags: chatListItemTags(location: location, accountPeerId: context.account.peerId, peer: peer.chatMainPeer, isUnread: combinedReadState?.isUnread ?? false, isMuted: isRemovedFromTotalUnreadCount, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: chatListFilters)
tags: chatListItemTags(location: location, accountPeerId: context.account.peerId, isPremium: isPremium, peer: peer.chatMainPeer, isUnread: combinedReadState?.isUnread ?? false, isMuted: isRemovedFromTotalUnreadCount, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: chatListFilters)
)),
editing: editing,
hasActiveRevealControls: hasActiveRevealControls,
@ -1106,8 +1106,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
}
}
private func mappedChatListNodeViewListTransition(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, chatListFilters: [ChatListFilter]?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, transition: ChatListNodeViewTransition) -> ChatListNodeListViewTransition {
return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, nodeInteraction: nodeInteraction, location: location, filterData: filterData, chatListFilters: chatListFilters, mode: mode, isPeerEnabled: isPeerEnabled, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, nodeInteraction: nodeInteraction, location: location, filterData: filterData, chatListFilters: chatListFilters, mode: mode, isPeerEnabled: isPeerEnabled, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, adjustScrollToFirstItem: transition.adjustScrollToFirstItem, animateCrossfade: transition.animateCrossfade)
private func mappedChatListNodeViewListTransition(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, isPremium: Bool, filterData: ChatListItemFilterData?, chatListFilters: [ChatListFilter]?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, transition: ChatListNodeViewTransition) -> ChatListNodeListViewTransition {
return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, nodeInteraction: nodeInteraction, location: location, isPremium: isPremium, filterData: filterData, chatListFilters: chatListFilters, mode: mode, isPeerEnabled: isPeerEnabled, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, nodeInteraction: nodeInteraction, location: location, isPremium: isPremium, filterData: filterData, chatListFilters: chatListFilters, mode: mode, isPeerEnabled: isPeerEnabled, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, adjustScrollToFirstItem: transition.adjustScrollToFirstItem, animateCrossfade: transition.animateCrossfade)
}
private final class ChatListOpaqueTransactionState {
@ -2114,8 +2114,17 @@ public final class ChatListNode: ListView {
}
let previousChatListFilters = Atomic<[ChatListFilter]?>(value: nil)
let chatListNodeViewTransition = combineLatest(
queue: viewProcessingQueue,
let previousAccountIsPremium = Atomic<Bool?>(value: nil)
let accountIsPremium = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)
)
|> map { peer -> Bool in
return peer?.isPremium ?? false
}
|> distinctUntilChanged
let chatListNodeViewTransition = combineLatest(queue: viewProcessingQueue,
hideArchivedFolderByDefault,
displayArchiveIntro,
storageInfo,
@ -2124,9 +2133,10 @@ public final class ChatListNode: ListView {
chatListViewUpdate,
self.statePromise.get(),
contacts,
chatListFilters
chatListFilters,
accountIsPremium
)
|> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestedChatListNotice, savedMessagesPeer, updateAndFilter, state, contacts, chatListFilters) -> Signal<ChatListNodeListViewTransition, NoError> in
|> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestedChatListNotice, savedMessagesPeer, updateAndFilter, state, contacts, chatListFilters, accountIsPremium) -> Signal<ChatListNodeListViewTransition, NoError> in
let (update, filter) = updateAndFilter
let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault)
@ -2587,9 +2597,12 @@ public final class ChatListNode: ListView {
if chatListFilters != previousChatListFiltersValue {
forceAllUpdated = true
}
if accountIsPremium != previousAccountIsPremium.swap(accountIsPremium) {
forceAllUpdated = true
}
return preparedChatListNodeViewTransition(from: previousView, to: processedView, reason: reason, previewing: previewing, disableAnimations: disableAnimations, account: context.account, scrollPosition: updatedScrollPosition, searchMode: searchMode, forceAllUpdated: forceAllUpdated)
|> map({ mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, location: location, filterData: filterData, chatListFilters: chatListFilters, mode: mode, isPeerEnabled: isPeerEnabled, transition: $0) })
|> map({ mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, location: location, isPremium: accountIsPremium, filterData: filterData, chatListFilters: chatListFilters, mode: mode, isPeerEnabled: isPeerEnabled, transition: $0) })
|> runOn(prepareOnMainQueue ? Queue.mainQueue() : viewProcessingQueue)
}
@ -4206,7 +4219,10 @@ func hideChatListContacts(context: AccountContext) {
let _ = ApplicationSpecificNotice.setDisplayChatListContacts(accountManager: context.sharedContext.accountManager).startStandalone()
}
func chatListItemTags(location: ChatListControllerLocation, accountPeerId: EnginePeer.Id, peer: EnginePeer?, isUnread: Bool, isMuted: Bool, isContact: Bool, hasUnseenMentions: Bool, chatListFilters: [ChatListFilter]?) -> [ChatListItemContent.Tag] {
func chatListItemTags(location: ChatListControllerLocation, accountPeerId: EnginePeer.Id, isPremium: Bool, peer: EnginePeer?, isUnread: Bool, isMuted: Bool, isContact: Bool, hasUnseenMentions: Bool, chatListFilters: [ChatListFilter]?) -> [ChatListItemContent.Tag] {
if !isPremium {
return []
}
if case .chatList = location {
} else {
return []
@ -4222,7 +4238,7 @@ func chatListItemTags(location: ChatListControllerLocation, accountPeerId: Engin
for case let .filter(id, title, _, data) in chatListFilters {
if data.color != nil {
let predicate = chatListFilterPredicate(filter: data, accountPeerId: accountPeerId)
if predicate.includes(peer: peer._asPeer(), groupId: .root, isRemovedFromTotalUnreadCount: isMuted, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: hasUnseenMentions) {
if predicate.pinnedPeerIds.contains(peer.id) || predicate.includes(peer: peer._asPeer(), groupId: .root, isRemovedFromTotalUnreadCount: isMuted, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: hasUnseenMentions) {
result.append(ChatListItemContent.Tag(
id: id,
title: title,

View File

@ -46,6 +46,7 @@ open class ViewControllerComponentContainer: ViewController {
public let statusBarHeight: CGFloat
public let navigationHeight: CGFloat
public let safeInsets: UIEdgeInsets
public let additionalInsets: UIEdgeInsets
public let inputHeight: CGFloat
public let metrics: LayoutMetrics
public let deviceMetrics: DeviceMetrics
@ -60,6 +61,7 @@ open class ViewControllerComponentContainer: ViewController {
statusBarHeight: CGFloat,
navigationHeight: CGFloat,
safeInsets: UIEdgeInsets,
additionalInsets: UIEdgeInsets,
inputHeight: CGFloat,
metrics: LayoutMetrics,
deviceMetrics: DeviceMetrics,
@ -73,6 +75,7 @@ open class ViewControllerComponentContainer: ViewController {
self.statusBarHeight = statusBarHeight
self.navigationHeight = navigationHeight
self.safeInsets = safeInsets
self.additionalInsets = additionalInsets
self.inputHeight = inputHeight
self.metrics = metrics
self.deviceMetrics = deviceMetrics
@ -98,6 +101,9 @@ open class ViewControllerComponentContainer: ViewController {
if lhs.safeInsets != rhs.safeInsets {
return false
}
if lhs.additionalInsets != rhs.additionalInsets {
return false
}
if lhs.inputHeight != rhs.inputHeight {
return false
}
@ -167,6 +173,7 @@ open class ViewControllerComponentContainer: ViewController {
statusBarHeight: layout.statusBarHeight ?? 0.0,
navigationHeight: navigationHeight,
safeInsets: UIEdgeInsets(top: layout.intrinsicInsets.top + layout.safeInsets.top, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom, right: layout.safeInsets.right),
additionalInsets: layout.additionalInsets,
inputHeight: layout.inputHeight ?? 0.0,
metrics: layout.metrics,
deviceMetrics: layout.deviceMetrics,

View File

@ -885,7 +885,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
} else if self.autoSelectEntities, gestureRecognizer.numberOfTouches == 1, let viewToSelect = self.entity(at: location) {
self.selectEntity(viewToSelect.entity, animate: false)
self.onInteractionUpdated(true)
} else if gestureRecognizer.numberOfTouches == 2 || self.isStickerEditor, let mediaEntityView = self.subviews.first(where: { $0 is DrawingEntityMediaView }) as? DrawingEntityMediaView {
} else if gestureRecognizer.numberOfTouches == 2 || (self.isStickerEditor && self.autoSelectEntities), let mediaEntityView = self.subviews.first(where: { $0 is DrawingEntityMediaView }) as? DrawingEntityMediaView {
mediaEntityView.handlePan(gestureRecognizer)
}
}

View File

@ -2634,6 +2634,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom,
right: layout.safeInsets.right
),
additionalInsets: layout.additionalInsets,
inputHeight: layout.inputHeight ?? 0.0,
metrics: layout.metrics,
deviceMetrics: layout.deviceMetrics,

View File

@ -236,6 +236,11 @@ public func chatMessageGalleryControllerData(context: AccountContext, chatLocati
}*/
}
var source = source
if standalone {
source = .standaloneMessage(message)
}
if internalDocumentItemSupportsMimeType(file.mimeType, fileName: file.fileName ?? "file") {
let gallery = GalleryController(context: context, source: source ?? .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? .peer(id: message.id.peerId), customTag: chatFilterTag, chatLocationContextHolder: chatLocationContextHolder ?? Atomic<ChatLocationContextHolder?>(value: nil)), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in
navigationController?.replaceTopController(controller, animated: false, ready: ready)

View File

@ -822,7 +822,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
}
var canDelete: Bool
var canShare = !message.containsSecretMedia
var canShare = !message.containsSecretMedia && !Namespaces.Message.allNonRegular.contains(message.id.namespace)
var canFullscreen = false

View File

@ -248,13 +248,18 @@ public func galleryItemForEntry(
if let result = addLocallyGeneratedEntities(text, enabledTypes: [.timecode], entities: entities, mediaDuration: file.duration.flatMap(Double.init)) {
entities = result
}
var originData = GalleryItemOriginData(title: message.effectiveAuthor.flatMap(EnginePeer.init)?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), timestamp: message.timestamp)
if Namespaces.Message.allNonRegular.contains(message.id.namespace) {
originData = GalleryItemOriginData(title: nil, timestamp: nil)
}
let caption = galleryCaptionStringWithAppliedEntities(context: context, text: text, entities: entities, message: message)
return UniversalVideoGalleryItem(
context: context,
presentationData: presentationData,
content: content,
originData: GalleryItemOriginData(title: message.effectiveAuthor.flatMap(EnginePeer.init)?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), timestamp: message.timestamp),
originData: originData,
indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) },
contentInfo: .message(message),
caption: caption,
@ -348,11 +353,17 @@ public func galleryItemForEntry(
}
description = galleryCaptionStringWithAppliedEntities(context: context, text: descriptionText, entities: entities, message: message)
}
var originData = GalleryItemOriginData(title: message.effectiveAuthor.flatMap(EnginePeer.init)?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), timestamp: message.timestamp)
if Namespaces.Message.allNonRegular.contains(message.id.namespace) {
originData = GalleryItemOriginData(title: nil, timestamp: nil)
}
return UniversalVideoGalleryItem(
context: context,
presentationData: presentationData,
content: content,
originData: GalleryItemOriginData(title: message.effectiveAuthor.flatMap(EnginePeer.init)?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), timestamp: message.timestamp),
originData: originData,
indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) },
contentInfo: .message(message),
caption: NSAttributedString(string: ""),

View File

@ -20,12 +20,12 @@ public final class ItemListAddressItem: ListViewItem, ItemListItem {
let style: ItemListStyle
let displayDecorations: Bool
let action: (() -> Void)?
let longTapAction: (() -> Void)?
let longTapAction: ((ASDisplayNode, String) -> Void)?
let linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)?
public let tag: Any?
public init(theme: PresentationTheme, label: String, text: String, imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?, selected: Bool? = nil, sectionId: ItemListSectionId, style: ItemListStyle, displayDecorations: Bool = true, action: (() -> Void)?, longTapAction: (() -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) {
public init(theme: PresentationTheme, label: String, text: String, imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?, selected: Bool? = nil, sectionId: ItemListSectionId, style: ItemListStyle, displayDecorations: Bool = true, action: (() -> Void)?, longTapAction: ((ASDisplayNode, String) -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) {
self.theme = theme
self.label = label
self.text = text
@ -396,7 +396,10 @@ public class ItemListAddressItemNode: ListViewItemNode {
}
override public func longTapped() {
self.item?.longTapAction?()
guard let item = self.item else {
return
}
item.longTapAction?(self, item.text)
}
public var tag: Any? {

View File

@ -222,7 +222,7 @@ public func legacyMediaEditor(context: AccountContext, peer: Peer, threadTitle:
})
}
public func legacyAttachmentMenu(context: AccountContext, peer: Peer, threadTitle: String?, chatLocation: ChatLocation, editMediaOptions: LegacyAttachmentMenuMediaEditing?, saveEditedPhotos: Bool, allowGrouping: Bool, hasSchedule: Bool, canSendPolls: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>), parentController: LegacyController, recentlyUsedInlineBots: [Peer], initialCaption: NSAttributedString, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, presentSelectionLimitExceeded: @escaping () -> Void, presentCantSendMultipleFiles: @escaping () -> Void, presentJpegConversionAlert: @escaping (@escaping (Bool) -> Void) -> Void, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32, ((String) -> UIView?)?, @escaping () -> Void) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, present: @escaping (ViewController, Any?) -> Void) -> TGMenuSheetController {
public func legacyAttachmentMenu(context: AccountContext, peer: Peer?, threadTitle: String?, chatLocation: ChatLocation, editMediaOptions: LegacyAttachmentMenuMediaEditing?, saveEditedPhotos: Bool, allowGrouping: Bool, hasSchedule: Bool, canSendPolls: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>), parentController: LegacyController, recentlyUsedInlineBots: [Peer], initialCaption: NSAttributedString, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, presentSelectionLimitExceeded: @escaping () -> Void, presentCantSendMultipleFiles: @escaping () -> Void, presentJpegConversionAlert: @escaping (@escaping (Bool) -> Void) -> Void, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32, ((String) -> UIView?)?, @escaping () -> Void) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, present: @escaping (ViewController, Any?) -> Void) -> TGMenuSheetController {
let defaultVideoPreset = defaultVideoPresetForContext(context)
UserDefaults.standard.set(defaultVideoPreset.rawValue as NSNumber, forKey: "TG_preferredVideoPreset_v0")
@ -230,18 +230,20 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, threadTitl
let recipientName: String
if let threadTitle {
recipientName = threadTitle
} else {
} else if let peer {
if peer.id == context.account.peerId {
recipientName = presentationData.strings.DialogList_SavedMessages
} else {
recipientName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
}
} else {
recipientName = ""
}
let actionSheetTheme = ActionSheetControllerTheme(presentationData: presentationData)
let fontSize = floor(actionSheetTheme.baseFontSize * 20.0 / 17.0)
let isSecretChat = peer.id.namespace == Namespaces.Peer.SecretChat
let isSecretChat = peer?.id.namespace == Namespaces.Peer.SecretChat
let controller = TGMenuSheetController(context: parentController.context, dark: false)!
controller.dismissesByOutsideTap = true
@ -314,14 +316,14 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, threadTitl
carouselItem.selectionLimitExceeded = {
presentSelectionLimitExceeded()
}
if peer.id != context.account.peerId {
if let peer, peer.id != context.account.peerId {
if peer is TelegramUser {
carouselItem.hasTimer = hasSchedule
}
carouselItem.hasSilentPosting = true
}
carouselItem.hasSchedule = hasSchedule
carouselItem.reminder = peer.id == context.account.peerId
carouselItem.reminder = peer?.id == context.account.peerId
carouselItem.presentScheduleController = { media, done in
presentSchedulePicker(media, { time in
done?(time)
@ -449,7 +451,12 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, threadTitl
navigationController.setNavigationBarHidden(true, animated: false)
legacyController.bind(controller: navigationController)
let recipientName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
let recipientName: String
if let peer {
recipientName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
} else {
recipientName = ""
}
legacyController.enableSizeClassSignal = true
@ -489,12 +496,14 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, threadTitl
itemViews.append(locationItem)
var peerSupportsPolls = false
if peer is TelegramGroup || peer is TelegramChannel {
peerSupportsPolls = true
} else if let user = peer as? TelegramUser, let _ = user.botInfo {
peerSupportsPolls = true
if let peer {
if peer is TelegramGroup || peer is TelegramChannel {
peerSupportsPolls = true
} else if let user = peer as? TelegramUser, let _ = user.botInfo {
peerSupportsPolls = true
}
}
if peerSupportsPolls && canSendMessagesToPeer(peer) && canSendPolls {
if let peer, peerSupportsPolls, canSendMessagesToPeer(peer) && canSendPolls {
let pollItem = TGMenuSheetButtonItemView(title: presentationData.strings.AttachmentMenu_Poll, type: TGMenuSheetButtonTypeDefault, fontSize: fontSize, action: { [weak controller] in
controller?.dismiss(animated: true)
openPoll()

View File

@ -224,7 +224,7 @@ func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?,
})
}
}
if !isScheduledMessages {
if !isScheduledMessages && peer != nil {
model.interfaceView.doneLongPressed = { [weak selectionContext, weak editingContext, weak legacyController, weak model] item in
if let legacyController = legacyController, let item = item as? TGMediaPickerGalleryItem, let model = model, let selectionContext = selectionContext {
var effectiveHasSchedule = hasSchedule
@ -269,8 +269,8 @@ func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?,
}
let _ = (sendWhenOnlineAvailable
|> take(1)
|> deliverOnMainQueue).start(next: { sendWhenOnlineAvailable in
|> take(1)
|> deliverOnMainQueue).start(next: { sendWhenOnlineAvailable in
let legacySheetController = LegacyController(presentation: .custom, theme: presentationData.theme, initialLayout: nil)
let sheetController = TGMediaPickerSendActionSheetController(context: legacyController.context, isDark: true, sendButtonFrame: model.interfaceView.doneButtonFrame, canSendSilently: hasSilentPosting, canSendWhenOnline: sendWhenOnlineAvailable && effectiveHasSchedule, canSchedule: effectiveHasSchedule, reminder: reminder, hasTimer: hasTimer)
let dismissImpl = { [weak model] in

View File

@ -510,7 +510,7 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry {
} else {
arguments.openAddress(value)
}
}, longTapAction: {
}, longTapAction: { _, _ in
if selected == nil {
arguments.displayCopyContextMenu(.info(index), string)
}

View File

@ -1004,7 +1004,7 @@ private final class DemoSheetContent: CombinedComponent {
position: .top,
model: .island,
videoFile: configuration.videos["last_seen"],
decoration: .tag
decoration: .badgeStars
)),
title: strings.Premium_LastSeen,
text: strings.Premium_LastSeenInfo,
@ -1023,7 +1023,7 @@ private final class DemoSheetContent: CombinedComponent {
position: .top,
model: .island,
videoFile: configuration.videos["message_privacy"],
decoration: .tag
decoration: .swirlStars
)),
title: strings.Premium_MessagePrivacy,
text: strings.Premium_MessagePrivacyInfo,
@ -1033,6 +1033,26 @@ private final class DemoSheetContent: CombinedComponent {
)
)
availableItems[.folderTags] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.folderTags,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .top,
model: .island,
videoFile: configuration.videos["folder_tags"],
decoration: .tag
)),
title: strings.Premium_FolderTags,
text: strings.Premium_FolderTagsStandaloneInfo,
textColor: textColor
)
)
)
)
let index: Int = 0
var items: [DemoPagerComponent.Item] = []
if let item = availableItems.first(where: { $0.value.content.id == component.subject as AnyHashable }) {
@ -1136,6 +1156,8 @@ private final class DemoSheetContent: CombinedComponent {
buttonText = strings.Premium_LastSeen_Proceed
case .messagePrivacy:
buttonText = strings.Premium_MessagePrivacy_Proceed
case .folderTags:
buttonText = strings.Premium_FolderTags_Proceed
default:
buttonText = strings.Common_OK
}
@ -1177,6 +1199,8 @@ private final class DemoSheetContent: CombinedComponent {
text = strings.Premium_LastSeenInfo
case .messagePrivacy:
text = strings.Premium_MessagePrivacyInfo
case .folderTags:
text = strings.Premium_FolderTagsStandaloneInfo
default:
text = ""
}

View File

@ -435,6 +435,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
UIColor(rgb: 0x9b4fed),
UIColor(rgb: 0x8958ff),
UIColor(rgb: 0x676bff),
UIColor(rgb: 0x676bff), //replace
UIColor(rgb: 0x6172ff),
UIColor(rgb: 0x5b79ff),
UIColor(rgb: 0x4492ff),

View File

@ -282,6 +282,12 @@ public enum PremiumSource: Equatable {
} else {
return false
}
case .folderTags:
if case .folderTags = rhs {
return true
} else {
return false
}
}
}
@ -326,6 +332,7 @@ public enum PremiumSource: Equatable {
case presence
case readTime
case messageTags
case folderTags
var identifier: String? {
switch self {
@ -413,6 +420,8 @@ public enum PremiumSource: Equatable {
return "read_time"
case .messageTags:
return "saved_tags"
case .folderTags:
return "folder_tags"
}
}
}
@ -448,7 +457,6 @@ public enum PremiumPerk: CaseIterable {
case businessAwayMessage
case businessChatBots
public static var allCases: [PremiumPerk] {
return [
.doubleLimits,
@ -471,12 +479,28 @@ public enum PremiumPerk: CaseIterable {
.messageTags,
.lastSeen,
.messagePrivacy,
.folderTags,
.business
]
}
init?(identifier: String) {
for perk in PremiumPerk.allCases {
public static var allBusinessCases: [PremiumPerk] {
return [
.businessLocation,
.businessHours,
.businessGreetingMessage,
.businessQuickReplies,
.businessAwayMessage,
.businessChatBots,
// .emojiStatus,
// .folderTags,
// .stories,
]
}
init?(identifier: String, business: Bool) {
for perk in business ? PremiumPerk.allBusinessCases : PremiumPerk.allCases {
if perk.identifier == identifier {
self = perk
return
@ -527,10 +551,22 @@ public enum PremiumPerk: CaseIterable {
return "last_seen"
case .messagePrivacy:
return "message_privacy"
case .folderTags:
return "folder_tags"
case .business:
return "business"
default:
return ""
case .businessLocation:
return "location"
case .businessHours:
return "opening_hours"
case .businessQuickReplies:
return "quick_replies"
case .businessGreetingMessage:
return "greeting_messages"
case .businessAwayMessage:
return "away_messages"
case .businessChatBots:
return "chatbots"
}
}
@ -576,10 +612,23 @@ public enum PremiumPerk: CaseIterable {
return strings.Premium_LastSeen
case .messagePrivacy:
return strings.Premium_MessagePrivacy
case .folderTags:
return strings.Premium_FolderTags
case .business:
return strings.Premium_Business
default:
return ""
case .businessLocation:
return strings.Business_Location
case .businessHours:
return strings.Business_OpeningHours
case .businessQuickReplies:
return strings.Business_QuickReplies
case .businessGreetingMessage:
return strings.Business_GreetingMessages
case .businessAwayMessage:
return strings.Business_AwayMessages
case .businessChatBots:
return strings.Business_Chatbots
}
}
@ -625,10 +674,23 @@ public enum PremiumPerk: CaseIterable {
return strings.Premium_LastSeenInfo
case .messagePrivacy:
return strings.Premium_MessagePrivacyInfo
case .folderTags:
return strings.Premium_FolderTagsInfo
case .business:
return strings.Premium_BusinessInfo
default:
return ""
case .businessLocation:
return strings.Business_LocationInfo
case .businessHours:
return strings.Business_OpeningHoursInfo
case .businessQuickReplies:
return strings.Business_QuickRepliesInfo
case .businessGreetingMessage:
return strings.Business_GreetingMessagesInfo
case .businessAwayMessage:
return strings.Business_AwayMessagesInfo
case .businessChatBots:
return strings.Business_ChatbotsInfo
}
}
@ -674,86 +736,22 @@ public enum PremiumPerk: CaseIterable {
return "Premium/Perk/LastSeen"
case .messagePrivacy:
return "Premium/Perk/MessagePrivacy"
case .folderTags:
return "Premium/Perk/MessageTags"
case .business:
return "Premium/Perk/Business"
default:
return ""
}
}
}
private enum BusinessPerk: CaseIterable {
case location
case hours
case quickReplies
case greetings
case awayMessages
case chatbots
var identifier: String {
switch self {
case .location:
return "location"
case .hours:
return "opening_hours"
case .quickReplies:
return "quick_replies"
case .greetings:
return "greeting_messages"
case .awayMessages:
return "away_messages"
case .chatbots:
return "chatbots"
}
}
func title(strings: PresentationStrings) -> String {
switch self {
case .location:
return strings.Business_Location
case .hours:
return strings.Business_OpeningHours
case .quickReplies:
return strings.Business_QuickReplies
case .greetings:
return strings.Business_GreetingMessages
case .awayMessages:
return strings.Business_AwayMessages
case .chatbots:
return strings.Business_Chatbots
}
}
func subtitle(strings: PresentationStrings) -> String {
switch self {
case .location:
return strings.Business_LocationInfo
case .hours:
return strings.Business_OpeningHoursInfo
case .quickReplies:
return strings.Business_QuickRepliesInfo
case .greetings:
return strings.Business_GreetingMessagesInfo
case .awayMessages:
return strings.Business_AwayMessagesInfo
case .chatbots:
return strings.Business_ChatbotsInfo
}
}
var iconName: String {
switch self {
case .location:
case .businessLocation:
return "Premium/BusinessPerk/Location"
case .hours:
case .businessHours:
return "Premium/BusinessPerk/Hours"
case .quickReplies:
case .businessQuickReplies:
return "Premium/BusinessPerk/Replies"
case .greetings:
case .businessGreetingMessage:
return "Premium/BusinessPerk/Greetings"
case .awayMessages:
case .businessAwayMessage:
return "Premium/BusinessPerk/Away"
case .chatbots:
case .businessChatBots:
return "Premium/BusinessPerk/Chatbots"
}
}
@ -783,20 +781,32 @@ struct PremiumIntroConfiguration {
.animatedUserpics,
.premiumStickers,
.business
], businessPerks: [
.businessGreetingMessage,
.businessAwayMessage,
.businessQuickReplies,
.businessChatBots,
.businessHours,
.businessLocation
// .emojiStatus,
// .folderTags,
// .stories
])
}
let perks: [PremiumPerk]
let businessPerks: [PremiumPerk]
fileprivate init(perks: [PremiumPerk]) {
fileprivate init(perks: [PremiumPerk], businessPerks: [PremiumPerk]) {
self.perks = perks
self.businessPerks = businessPerks
}
public static func with(appConfiguration: AppConfiguration) -> PremiumIntroConfiguration {
if let data = appConfiguration.data, let values = data["premium_promo_order"] as? [String] {
var perks: [PremiumPerk] = []
for value in values {
if let perk = PremiumPerk(identifier: value) {
if let perk = PremiumPerk(identifier: value, business: false) {
if !perks.contains(perk) {
perks.append(perk)
} else {
@ -825,7 +835,29 @@ struct PremiumIntroConfiguration {
perks.append(.business)
}
#endif
return PremiumIntroConfiguration(perks: perks)
var businessPerks: [PremiumPerk] = []
if let values = data["business_promo_order"] as? [String] {
for value in values {
if let perk = PremiumPerk(identifier: value, business: true) {
if !businessPerks.contains(perk) {
businessPerks.append(perk)
} else {
businessPerks = []
break
}
} else {
businessPerks = []
break
}
}
}
if businessPerks.count < 4 {
businessPerks = PremiumIntroConfiguration.defaultValue.businessPerks
}
return PremiumIntroConfiguration(perks: perks, businessPerks: businessPerks)
} else {
return .defaultValue
}
@ -1531,8 +1563,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
ApplicationSpecificNotice.dismissedPremiumColorsBadge(accountManager: context.sharedContext.accountManager),
ApplicationSpecificNotice.dismissedMessageTagsBadge(accountManager: context.sharedContext.accountManager),
ApplicationSpecificNotice.dismissedLastSeenBadge(accountManager: context.sharedContext.accountManager),
ApplicationSpecificNotice.dismissedMessagePrivacyBadge(accountManager: context.sharedContext.accountManager)
).startStrict(next: { [weak self] dismissedPremiumAppIconsBadge, dismissedPremiumWallpapersBadge, dismissedPremiumColorsBadge, dismissedMessageTagsBadge, dismissedLastSeenBadge, dismissedMessagePrivacyBadge in
ApplicationSpecificNotice.dismissedMessagePrivacyBadge(accountManager: context.sharedContext.accountManager),
ApplicationSpecificNotice.dismissedBusinessBadge(accountManager: context.sharedContext.accountManager)
).startStrict(next: { [weak self] dismissedPremiumAppIconsBadge, dismissedPremiumWallpapersBadge, dismissedPremiumColorsBadge, dismissedMessageTagsBadge, dismissedLastSeenBadge, dismissedMessagePrivacyBadge, dismissedBusinessBadge in
guard let self else {
return
}
@ -1552,8 +1585,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
if !dismissedMessagePrivacyBadge {
newPerks.append(PremiumPerk.messagePrivacy.identifier)
}
//TODO:
newPerks.append(PremiumPerk.business.identifier)
if !dismissedBusinessBadge {
newPerks.append(PremiumPerk.business.identifier)
}
self.newPerks = newPerks
self.updated()
})
@ -1796,6 +1830,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
UIColor(rgb: 0x9b4fed),
UIColor(rgb: 0x8958ff),
UIColor(rgb: 0x676bff),
UIColor(rgb: 0x676bff), //replace
UIColor(rgb: 0x6172ff),
UIColor(rgb: 0x5b79ff),
UIColor(rgb: 0x4492ff),
@ -1937,17 +1972,31 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
if case .business = context.component.mode, case .business = perk {
continue
}
let isNew = state.newPerks.contains(perk.identifier)
let titleComponent = AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: perk.title(strings: strings),
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: environment.theme.list.itemPrimaryTextColor
)),
maximumNumberOfLines: 0
))
let titleCombinedComponent: AnyComponent<Empty>
if isNew {
titleCombinedComponent = AnyComponent(HStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: titleComponent),
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(BadgeComponent(color: gradientColors[i], text: strings.Premium_New)))
], spacing: 5.0))
} else {
titleCombinedComponent = AnyComponent(HStack([AnyComponentWithIdentity(id: AnyHashable(0), component: titleComponent)], spacing: 0.0))
}
perksItems.append(AnyComponentWithIdentity(id: perksItems.count, component: AnyComponent(ListActionItemComponent(
theme: environment.theme,
title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: perk.title(strings: strings),
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: environment.theme.list.itemPrimaryTextColor
)),
maximumNumberOfLines: 0
))),
AnyComponentWithIdentity(id: AnyHashable(0), component: titleCombinedComponent),
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: perk.subtitle(strings: strings),
@ -2013,6 +2062,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let _ = ApplicationSpecificNotice.setDismissedMessagePrivacyBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
case .business:
demoSubject = .business
let _ = ApplicationSpecificNotice.setDismissedBusinessBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
default:
demoSubject = .doubleLimits
}
@ -2092,7 +2142,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
var i = 0
var perksItems: [AnyComponentWithIdentity<Empty>] = []
for perk in BusinessPerk.allCases {
for perk in state.configuration.businessPerks {
perksItems.append(AnyComponentWithIdentity(id: perksItems.count, component: AnyComponent(ListActionItemComponent(
theme: environment.theme,
title: AnyComponent(VStack([
@ -2123,7 +2173,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let isPremium = state?.isPremium == true
if isPremium {
switch perk {
case .location:
case .businessLocation:
let _ = (accountContext.engine.data.get(
TelegramEngine.EngineData.Item.Peer.BusinessLocation(id: accountContext.account.peerId)
)
@ -2133,7 +2183,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
}
push(accountContext.sharedContext.makeBusinessLocationSetupScreen(context: accountContext, initialValue: businessLocation, completion: { _ in }))
})
case .hours:
case .businessHours:
let _ = (accountContext.engine.data.get(
TelegramEngine.EngineData.Item.Peer.BusinessHours(id: accountContext.account.peerId)
)
@ -2143,7 +2193,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
}
push(accountContext.sharedContext.makeBusinessHoursSetupScreen(context: accountContext, initialValue: businessHours, completion: { _ in }))
})
case .quickReplies:
case .businessQuickReplies:
let _ = (accountContext.sharedContext.makeQuickReplySetupScreenInitialData(context: accountContext)
|> take(1)
|> deliverOnMainQueue).start(next: { [weak accountContext] initialData in
@ -2152,7 +2202,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
}
push(accountContext.sharedContext.makeQuickReplySetupScreen(context: accountContext, initialData: initialData))
})
case .greetings:
case .businessGreetingMessage:
let _ = (accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreenInitialData(context: accountContext)
|> take(1)
|> deliverOnMainQueue).start(next: { [weak accountContext] initialData in
@ -2161,7 +2211,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
}
push(accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreen(context: accountContext, initialData: initialData, isAwayMode: false))
})
case .awayMessages:
case .businessAwayMessage:
let _ = (accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreenInitialData(context: accountContext)
|> take(1)
|> deliverOnMainQueue).start(next: { [weak accountContext] initialData in
@ -2170,7 +2220,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
}
push(accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreen(context: accountContext, initialData: initialData, isAwayMode: true))
})
case .chatbots:
case .businessChatBots:
let _ = (accountContext.sharedContext.makeChatbotSetupScreenInitialData(context: accountContext)
|> take(1)
|> deliverOnMainQueue).start(next: { [weak accountContext] initialData in
@ -2179,25 +2229,29 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
}
push(accountContext.sharedContext.makeChatbotSetupScreen(context: accountContext, initialData: initialData))
})
default:
fatalError()
}
} else {
var demoSubject: PremiumDemoScreen.Subject
switch perk {
case .location:
case .businessLocation:
demoSubject = .businessLocation
case .hours:
case .businessHours:
demoSubject = .businessHours
case .quickReplies:
case .businessQuickReplies:
demoSubject = .businessQuickReplies
case .greetings:
case .businessGreetingMessage:
demoSubject = .businessGreetingMessage
case .awayMessages:
case .businessAwayMessage:
demoSubject = .businessAwayMessage
case .chatbots:
case .businessChatBots:
demoSubject = .businessChatBots
default:
fatalError()
}
var dismissImpl: (() -> Void)?
let controller = PremiumLimitsListScreen(context: accountContext, subject: demoSubject, source: .intro(state?.price), order: [.businessLocation, .businessHours, .businessQuickReplies, .businessGreetingMessage, .businessAwayMessage, .businessChatBots], buttonText: isPremium ? strings.Common_OK : (state?.isAnnual == true ? strings.Premium_SubscribeForAnnual(state?.price ?? "").string : strings.Premium_SubscribeFor(state?.price ?? "").string), isPremium: isPremium, forceDark: forceDark)
let controller = PremiumLimitsListScreen(context: accountContext, subject: demoSubject, source: .intro(state?.price), order: state?.configuration.businessPerks, buttonText: isPremium ? strings.Common_OK : (state?.isAnnual == true ? strings.Premium_SubscribeForAnnual(state?.price ?? "").string : strings.Premium_SubscribeFor(state?.price ?? "").string), isPremium: isPremium, forceDark: forceDark)
controller.action = { [weak state] in
dismissImpl?()
if state?.isPremium == false {
@ -3721,3 +3775,61 @@ private final class EmojiActionIconComponent: Component {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
private final class BadgeComponent: CombinedComponent {
let color: UIColor
let text: String
init(
color: UIColor,
text: String
) {
self.color = color
self.text = text
}
static func ==(lhs: BadgeComponent, rhs: BadgeComponent) -> Bool {
if lhs.color != rhs.color {
return false
}
if lhs.text != rhs.text {
return false
}
return true
}
static var body: Body {
let badgeBackground = Child(RoundedRectangle.self)
let badgeText = Child(MultilineTextComponent.self)
return { context in
let component = context.component
let badgeText = badgeText.update(
component: MultilineTextComponent(text: .plain(NSAttributedString(string: component.text, font: Font.semibold(11.0), textColor: .white))),
availableSize: context.availableSize,
transition: context.transition
)
let badgeSize = CGSize(width: badgeText.size.width + 7.0, height: 16.0)
let badgeBackground = badgeBackground.update(
component: RoundedRectangle(
color: component.color,
cornerRadius: 5.0
),
availableSize: badgeSize,
transition: context.transition
)
context.add(badgeBackground
.position(CGPoint(x: badgeSize.width / 2.0, y: badgeSize.height / 2.0))
)
context.add(badgeText
.position(CGPoint(x: badgeSize.width / 2.0, y: badgeSize.height / 2.0))
)
return badgeSize
}
}
}

View File

@ -384,7 +384,27 @@ public class PremiumLimitsListScreen: ViewController {
let theme = self.presentationData.theme
let strings = self.presentationData.strings
if let stickers = self.stickers, let appIcons = self.appIcons, let configuration = self.promoConfiguration {
let videos: [String: TelegramMediaFile] = self.promoConfiguration?.videos ?? [:]
let stickers = self.stickers ?? []
let appIcons = self.appIcons ?? []
let isReady: Bool
switch controller.subject {
case .premiumStickers:
isReady = !stickers.isEmpty
case .appIcons:
isReady = !appIcons.isEmpty
case .stories:
isReady = true
case .doubleLimits:
isReady = true
case .business:
isReady = true
default:
isReady = !videos.isEmpty
}
if isReady {
let context = controller.context
let textColor = theme.actionSheet.primaryTextColor
@ -482,7 +502,7 @@ public class PremiumLimitsListScreen: ViewController {
content: AnyComponent(PhoneDemoComponent(
context: context,
position: .bottom,
videoFile: configuration.videos["more_upload"],
videoFile: videos["more_upload"],
decoration: .dataRain
)),
title: strings.Premium_UploadSize,
@ -500,7 +520,7 @@ public class PremiumLimitsListScreen: ViewController {
content: AnyComponent(PhoneDemoComponent(
context: context,
position: .top,
videoFile: configuration.videos["faster_download"],
videoFile: videos["faster_download"],
decoration: .fasterStars
)),
title: strings.Premium_FasterSpeed,
@ -518,7 +538,7 @@ public class PremiumLimitsListScreen: ViewController {
content: AnyComponent(PhoneDemoComponent(
context: context,
position: .top,
videoFile: configuration.videos["voice_to_text"],
videoFile: videos["voice_to_text"],
decoration: .badgeStars
)),
title: strings.Premium_VoiceToText,
@ -536,7 +556,7 @@ public class PremiumLimitsListScreen: ViewController {
content: AnyComponent(PhoneDemoComponent(
context: context,
position: .bottom,
videoFile: configuration.videos["no_ads"],
videoFile: videos["no_ads"],
decoration: .swirlStars
)),
title: strings.Premium_NoAds,
@ -554,7 +574,7 @@ public class PremiumLimitsListScreen: ViewController {
content: AnyComponent(PhoneDemoComponent(
context: context,
position: .top,
videoFile: configuration.videos["infinite_reactions"],
videoFile: videos["infinite_reactions"],
decoration: .swirlStars
)),
title: strings.Premium_InfiniteReactions,
@ -593,7 +613,7 @@ public class PremiumLimitsListScreen: ViewController {
content: AnyComponent(PhoneDemoComponent(
context: context,
position: .top,
videoFile: configuration.videos["emoji_status"],
videoFile: videos["emoji_status"],
decoration: .badgeStars
)),
title: strings.Premium_EmojiStatus,
@ -611,7 +631,7 @@ public class PremiumLimitsListScreen: ViewController {
content: AnyComponent(PhoneDemoComponent(
context: context,
position: .top,
videoFile: configuration.videos["advanced_chat_management"],
videoFile: videos["advanced_chat_management"],
decoration: .swirlStars
)),
title: strings.Premium_ChatManagement,
@ -629,7 +649,7 @@ public class PremiumLimitsListScreen: ViewController {
content: AnyComponent(PhoneDemoComponent(
context: context,
position: .top,
videoFile: configuration.videos["profile_badge"],
videoFile: videos["profile_badge"],
decoration: .badgeStars
)),
title: strings.Premium_Badge,
@ -647,7 +667,7 @@ public class PremiumLimitsListScreen: ViewController {
content: AnyComponent(PhoneDemoComponent(
context: context,
position: .top,
videoFile: configuration.videos["animated_userpics"],
videoFile: videos["animated_userpics"],
decoration: .swirlStars
)),
title: strings.Premium_Avatar,
@ -681,7 +701,7 @@ public class PremiumLimitsListScreen: ViewController {
content: AnyComponent(PhoneDemoComponent(
context: context,
position: .bottom,
videoFile: configuration.videos["animated_emoji"],
videoFile: videos["animated_emoji"],
decoration: .emoji
)),
title: strings.Premium_AnimatedEmoji,
@ -700,7 +720,7 @@ public class PremiumLimitsListScreen: ViewController {
context: context,
position: .top,
model: .island,
videoFile: configuration.videos["translations"],
videoFile: videos["translations"],
decoration: .hello
)),
title: strings.Premium_Translation,
@ -718,7 +738,7 @@ public class PremiumLimitsListScreen: ViewController {
content: AnyComponent(PhoneDemoComponent(
context: context,
position: .top,
videoFile: configuration.videos["peer_colors"],
videoFile: videos["peer_colors"],
decoration: .badgeStars
)),
title: strings.Premium_Colors,
@ -737,7 +757,7 @@ public class PremiumLimitsListScreen: ViewController {
context: context,
position: .top,
model: .island,
videoFile: configuration.videos["wallpapers"],
videoFile: videos["wallpapers"],
decoration: .swirlStars
)),
title: strings.Premium_Wallpapers,
@ -756,7 +776,7 @@ public class PremiumLimitsListScreen: ViewController {
context: context,
position: .top,
model: .island,
videoFile: configuration.videos["saved_tags"],
videoFile: videos["saved_tags"],
decoration: .tag
)),
title: strings.Premium_MessageTags,
@ -775,7 +795,7 @@ public class PremiumLimitsListScreen: ViewController {
context: context,
position: .top,
model: .island,
videoFile: configuration.videos["last_seen"],
videoFile: videos["last_seen"],
decoration: .badgeStars
)),
title: strings.Premium_LastSeen,
@ -794,7 +814,7 @@ public class PremiumLimitsListScreen: ViewController {
context: context,
position: .top,
model: .island,
videoFile: configuration.videos["message_privacy"],
videoFile: videos["message_privacy"],
decoration: .swirlStars
)),
title: strings.Premium_MessagePrivacy,
@ -846,7 +866,7 @@ public class PremiumLimitsListScreen: ViewController {
context: context,
position: .top,
model: .island,
videoFile: configuration.videos["business_location"],
videoFile: videos["business_location"],
decoration: .business
)),
title: strings.Business_Location,
@ -866,7 +886,7 @@ public class PremiumLimitsListScreen: ViewController {
context: context,
position: .top,
model: .island,
videoFile: configuration.videos["business_hours"],
videoFile: videos["business_hours"],
decoration: .business
)),
title: strings.Business_OpeningHours,
@ -886,7 +906,7 @@ public class PremiumLimitsListScreen: ViewController {
context: context,
position: .top,
model: .island,
videoFile: configuration.videos["quick_replies"],
videoFile: videos["quick_replies"],
decoration: .business
)),
title: strings.Business_QuickReplies,
@ -906,7 +926,7 @@ public class PremiumLimitsListScreen: ViewController {
context: context,
position: .top,
model: .island,
videoFile: configuration.videos["greeting_message"],
videoFile: videos["greeting_message"],
decoration: .business
)),
title: strings.Business_GreetingMessages,
@ -926,7 +946,7 @@ public class PremiumLimitsListScreen: ViewController {
context: context,
position: .top,
model: .island,
videoFile: configuration.videos["away_message"],
videoFile: videos["away_message"],
decoration: .business
)),
title: strings.Business_AwayMessages,
@ -946,7 +966,7 @@ public class PremiumLimitsListScreen: ViewController {
context: context,
position: .top,
model: .island,
videoFile: configuration.videos["business_bots"],
videoFile: videos["business_bots"],
decoration: .business
)),
title: strings.Business_Chatbots,

View File

@ -477,6 +477,7 @@ public class ReplaceBoostScreen: ViewController {
statusBarHeight: 0.0,
navigationHeight: navigationHeight,
safeInsets: UIEdgeInsets(top: layout.intrinsicInsets.top + layout.safeInsets.top, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom, right: layout.safeInsets.right),
additionalInsets: layout.additionalInsets,
inputHeight: layout.inputHeight ?? 0.0,
metrics: layout.metrics,
deviceMetrics: layout.deviceMetrics,

View File

@ -102,7 +102,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-944407322] = { return Api.BotMenuButton.parse_botMenuButton($0) }
dict[1113113093] = { return Api.BotMenuButton.parse_botMenuButtonCommands($0) }
dict[1966318984] = { return Api.BotMenuButton.parse_botMenuButtonDefault($0) }
dict[467254972] = { return Api.BusinessAwayMessage.parse_businessAwayMessage($0) }
dict[-283809188] = { return Api.BusinessAwayMessage.parse_businessAwayMessage($0) }
dict[-910564679] = { return Api.BusinessAwayMessageSchedule.parse_businessAwayMessageScheduleAlways($0) }
dict[-867328308] = { return Api.BusinessAwayMessageSchedule.parse_businessAwayMessageScheduleCustom($0) }
dict[-1007487743] = { return Api.BusinessAwayMessageSchedule.parse_businessAwayMessageScheduleOutsideWorkHours($0) }
@ -216,8 +216,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1135897376] = { return Api.DefaultHistoryTTL.parse_defaultHistoryTTL($0) }
dict[-712374074] = { return Api.Dialog.parse_dialog($0) }
dict[1908216652] = { return Api.Dialog.parse_dialogFolder($0) }
dict[1949890536] = { return Api.DialogFilter.parse_dialogFilter($0) }
dict[-699792216] = { return Api.DialogFilter.parse_dialogFilterChatlist($0) }
dict[1605718587] = { return Api.DialogFilter.parse_dialogFilter($0) }
dict[-1612542300] = { return Api.DialogFilter.parse_dialogFilterChatlist($0) }
dict[909284270] = { return Api.DialogFilter.parse_dialogFilterDefault($0) }
dict[2004110666] = { return Api.DialogFilterSuggested.parse_dialogFilterSuggested($0) }
dict[-445792507] = { return Api.DialogPeer.parse_dialogPeer($0) }
@ -308,7 +308,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-459324] = { return Api.InputBotInlineResult.parse_inputBotInlineResultDocument($0) }
dict[1336154098] = { return Api.InputBotInlineResult.parse_inputBotInlineResultGame($0) }
dict[-1462213465] = { return Api.InputBotInlineResult.parse_inputBotInlineResultPhoto($0) }
dict[-307493900] = { return Api.InputBusinessAwayMessage.parse_inputBusinessAwayMessage($0) }
dict[-2094959136] = { return Api.InputBusinessAwayMessage.parse_inputBusinessAwayMessage($0) }
dict[26528571] = { return Api.InputBusinessGreetingMessage.parse_inputBusinessGreetingMessage($0) }
dict[1871393450] = { return Api.InputBusinessRecipients.parse_inputBusinessRecipients($0) }
dict[-212145112] = { return Api.InputChannel.parse_inputChannel($0) }
@ -1175,6 +1175,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1571952873] = { return Api.messages.CheckedHistoryImportPeer.parse_checkedHistoryImportPeer($0) }
dict[740433629] = { return Api.messages.DhConfig.parse_dhConfig($0) }
dict[-1058912715] = { return Api.messages.DhConfig.parse_dhConfigNotModified($0) }
dict[718878489] = { return Api.messages.DialogFilters.parse_dialogFilters($0) }
dict[364538944] = { return Api.messages.Dialogs.parse_dialogs($0) }
dict[-253500010] = { return Api.messages.Dialogs.parse_dialogsNotModified($0) }
dict[1910543603] = { return Api.messages.Dialogs.parse_dialogsSlice($0) }
@ -1314,7 +1315,7 @@ public extension Api {
return parser(reader)
}
else {
telegramApiLog("Type constructor \(String(UInt32(bitPattern: signature), radix: 16, uppercase: false)) not found")
telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found")
return nil
}
}
@ -2112,6 +2113,8 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.messages.DhConfig:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.DialogFilters:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.Dialogs:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.DiscussionMessage:

View File

@ -590,14 +590,15 @@ public extension Api {
}
public extension Api {
enum BusinessAwayMessage: TypeConstructorDescription {
case businessAwayMessage(shortcutId: Int32, schedule: Api.BusinessAwayMessageSchedule, recipients: Api.BusinessRecipients)
case businessAwayMessage(flags: Int32, shortcutId: Int32, schedule: Api.BusinessAwayMessageSchedule, recipients: Api.BusinessRecipients)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .businessAwayMessage(let shortcutId, let schedule, let recipients):
case .businessAwayMessage(let flags, let shortcutId, let schedule, let recipients):
if boxed {
buffer.appendInt32(467254972)
buffer.appendInt32(-283809188)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(shortcutId, buffer: buffer, boxed: false)
schedule.serialize(buffer, true)
recipients.serialize(buffer, true)
@ -607,27 +608,30 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .businessAwayMessage(let shortcutId, let schedule, let recipients):
return ("businessAwayMessage", [("shortcutId", shortcutId as Any), ("schedule", schedule as Any), ("recipients", recipients as Any)])
case .businessAwayMessage(let flags, let shortcutId, let schedule, let recipients):
return ("businessAwayMessage", [("flags", flags as Any), ("shortcutId", shortcutId as Any), ("schedule", schedule as Any), ("recipients", recipients as Any)])
}
}
public static func parse_businessAwayMessage(_ reader: BufferReader) -> BusinessAwayMessage? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.BusinessAwayMessageSchedule?
var _2: Int32?
_2 = reader.readInt32()
var _3: Api.BusinessAwayMessageSchedule?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.BusinessAwayMessageSchedule
_3 = Api.parse(reader, signature: signature) as? Api.BusinessAwayMessageSchedule
}
var _3: Api.BusinessRecipients?
var _4: Api.BusinessRecipients?
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.BusinessRecipients
_4 = Api.parse(reader, signature: signature) as? Api.BusinessRecipients
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.BusinessAwayMessage.businessAwayMessage(shortcutId: _1!, schedule: _2!, recipients: _3!)
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.BusinessAwayMessage.businessAwayMessage(flags: _1!, shortcutId: _2!, schedule: _3!, recipients: _4!)
}
else {
return nil

View File

@ -1291,67 +1291,19 @@ public extension Api.messages {
}
}
public extension Api.messages {
enum Dialogs: TypeConstructorDescription {
case dialogs(dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User])
case dialogsNotModified(count: Int32)
case dialogsSlice(count: Int32, dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User])
enum DialogFilters: TypeConstructorDescription {
case dialogFilters(flags: Int32, filters: [Api.DialogFilter])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .dialogs(let dialogs, let messages, let chats, let users):
case .dialogFilters(let flags, let filters):
if boxed {
buffer.appendInt32(364538944)
buffer.appendInt32(718878489)
}
serializeInt32(flags, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(dialogs.count))
for item in dialogs {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(messages.count))
for item in messages {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
case .dialogsNotModified(let count):
if boxed {
buffer.appendInt32(-253500010)
}
serializeInt32(count, buffer: buffer, boxed: false)
break
case .dialogsSlice(let count, let dialogs, let messages, let chats, let users):
if boxed {
buffer.appendInt32(1910543603)
}
serializeInt32(count, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(dialogs.count))
for item in dialogs {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(messages.count))
for item in messages {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
buffer.appendInt32(Int32(filters.count))
for item in filters {
item.serialize(buffer, true)
}
break
@ -1360,80 +1312,22 @@ public extension Api.messages {
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .dialogs(let dialogs, let messages, let chats, let users):
return ("dialogs", [("dialogs", dialogs as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)])
case .dialogsNotModified(let count):
return ("dialogsNotModified", [("count", count as Any)])
case .dialogsSlice(let count, let dialogs, let messages, let chats, let users):
return ("dialogsSlice", [("count", count as Any), ("dialogs", dialogs as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)])
case .dialogFilters(let flags, let filters):
return ("dialogFilters", [("flags", flags as Any), ("filters", filters as Any)])
}
}
public static func parse_dialogs(_ reader: BufferReader) -> Dialogs? {
var _1: [Api.Dialog]?
public static func parse_dialogFilters(_ reader: BufferReader) -> DialogFilters? {
var _1: Int32?
_1 = reader.readInt32()
var _2: [Api.DialogFilter]?
if let _ = reader.readInt32() {
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Dialog.self)
}
var _2: [Api.Message]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
}
var _3: [Api.Chat]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _4: [Api.User]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogFilter.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.messages.Dialogs.dialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!)
}
else {
return nil
}
}
public static func parse_dialogsNotModified(_ reader: BufferReader) -> Dialogs? {
var _1: Int32?
_1 = reader.readInt32()
let _c1 = _1 != nil
if _c1 {
return Api.messages.Dialogs.dialogsNotModified(count: _1!)
}
else {
return nil
}
}
public static func parse_dialogsSlice(_ reader: BufferReader) -> Dialogs? {
var _1: Int32?
_1 = reader.readInt32()
var _2: [Api.Dialog]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Dialog.self)
}
var _3: [Api.Message]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
}
var _4: [Api.Chat]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _5: [Api.User]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.messages.Dialogs.dialogsSlice(count: _1!, dialogs: _2!, messages: _3!, chats: _4!, users: _5!)
if _c1 && _c2 {
return Api.messages.DialogFilters.dialogFilters(flags: _1!, filters: _2!)
}
else {
return nil

View File

@ -1,3 +1,155 @@
public extension Api.messages {
enum Dialogs: TypeConstructorDescription {
case dialogs(dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User])
case dialogsNotModified(count: Int32)
case dialogsSlice(count: Int32, dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .dialogs(let dialogs, let messages, let chats, let users):
if boxed {
buffer.appendInt32(364538944)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(dialogs.count))
for item in dialogs {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(messages.count))
for item in messages {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
case .dialogsNotModified(let count):
if boxed {
buffer.appendInt32(-253500010)
}
serializeInt32(count, buffer: buffer, boxed: false)
break
case .dialogsSlice(let count, let dialogs, let messages, let chats, let users):
if boxed {
buffer.appendInt32(1910543603)
}
serializeInt32(count, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(dialogs.count))
for item in dialogs {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(messages.count))
for item in messages {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .dialogs(let dialogs, let messages, let chats, let users):
return ("dialogs", [("dialogs", dialogs as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)])
case .dialogsNotModified(let count):
return ("dialogsNotModified", [("count", count as Any)])
case .dialogsSlice(let count, let dialogs, let messages, let chats, let users):
return ("dialogsSlice", [("count", count as Any), ("dialogs", dialogs as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)])
}
}
public static func parse_dialogs(_ reader: BufferReader) -> Dialogs? {
var _1: [Api.Dialog]?
if let _ = reader.readInt32() {
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Dialog.self)
}
var _2: [Api.Message]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
}
var _3: [Api.Chat]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _4: [Api.User]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.messages.Dialogs.dialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!)
}
else {
return nil
}
}
public static func parse_dialogsNotModified(_ reader: BufferReader) -> Dialogs? {
var _1: Int32?
_1 = reader.readInt32()
let _c1 = _1 != nil
if _c1 {
return Api.messages.Dialogs.dialogsNotModified(count: _1!)
}
else {
return nil
}
}
public static func parse_dialogsSlice(_ reader: BufferReader) -> Dialogs? {
var _1: Int32?
_1 = reader.readInt32()
var _2: [Api.Dialog]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Dialog.self)
}
var _3: [Api.Message]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
}
var _4: [Api.Chat]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _5: [Api.User]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.messages.Dialogs.dialogsSlice(count: _1!, dialogs: _2!, messages: _3!, chats: _4!, users: _5!)
}
else {
return nil
}
}
}
}
public extension Api.messages {
enum DiscussionMessage: TypeConstructorDescription {
case discussionMessage(flags: Int32, messages: [Api.Message], maxId: Int32?, readInboxMaxId: Int32?, readOutboxMaxId: Int32?, unreadCount: Int32, chats: [Api.Chat], users: [Api.User])
@ -1430,81 +1582,3 @@ public extension Api.messages {
}
}
public extension Api.messages {
enum RecentStickers: TypeConstructorDescription {
case recentStickers(hash: Int64, packs: [Api.StickerPack], stickers: [Api.Document], dates: [Int32])
case recentStickersNotModified
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .recentStickers(let hash, let packs, let stickers, let dates):
if boxed {
buffer.appendInt32(-1999405994)
}
serializeInt64(hash, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(packs.count))
for item in packs {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(stickers.count))
for item in stickers {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(dates.count))
for item in dates {
serializeInt32(item, buffer: buffer, boxed: false)
}
break
case .recentStickersNotModified:
if boxed {
buffer.appendInt32(186120336)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .recentStickers(let hash, let packs, let stickers, let dates):
return ("recentStickers", [("hash", hash as Any), ("packs", packs as Any), ("stickers", stickers as Any), ("dates", dates as Any)])
case .recentStickersNotModified:
return ("recentStickersNotModified", [])
}
}
public static func parse_recentStickers(_ reader: BufferReader) -> RecentStickers? {
var _1: Int64?
_1 = reader.readInt64()
var _2: [Api.StickerPack]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self)
}
var _3: [Api.Document]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
}
var _4: [Int32]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.messages.RecentStickers.recentStickers(hash: _1!, packs: _2!, stickers: _3!, dates: _4!)
}
else {
return nil
}
}
public static func parse_recentStickersNotModified(_ reader: BufferReader) -> RecentStickers? {
return Api.messages.RecentStickers.recentStickersNotModified
}
}
}

View File

@ -1,3 +1,81 @@
public extension Api.messages {
enum RecentStickers: TypeConstructorDescription {
case recentStickers(hash: Int64, packs: [Api.StickerPack], stickers: [Api.Document], dates: [Int32])
case recentStickersNotModified
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .recentStickers(let hash, let packs, let stickers, let dates):
if boxed {
buffer.appendInt32(-1999405994)
}
serializeInt64(hash, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(packs.count))
for item in packs {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(stickers.count))
for item in stickers {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(dates.count))
for item in dates {
serializeInt32(item, buffer: buffer, boxed: false)
}
break
case .recentStickersNotModified:
if boxed {
buffer.appendInt32(186120336)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .recentStickers(let hash, let packs, let stickers, let dates):
return ("recentStickers", [("hash", hash as Any), ("packs", packs as Any), ("stickers", stickers as Any), ("dates", dates as Any)])
case .recentStickersNotModified:
return ("recentStickersNotModified", [])
}
}
public static func parse_recentStickers(_ reader: BufferReader) -> RecentStickers? {
var _1: Int64?
_1 = reader.readInt64()
var _2: [Api.StickerPack]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self)
}
var _3: [Api.Document]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
}
var _4: [Int32]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.messages.RecentStickers.recentStickers(hash: _1!, packs: _2!, stickers: _3!, dates: _4!)
}
else {
return nil
}
}
public static func parse_recentStickersNotModified(_ reader: BufferReader) -> RecentStickers? {
return Api.messages.RecentStickers.recentStickersNotModified
}
}
}
public extension Api.messages {
enum SavedDialogs: TypeConstructorDescription {
case savedDialogs(dialogs: [Api.SavedDialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User])
@ -1386,167 +1464,3 @@ public extension Api.payments {
}
}
public extension Api.payments {
enum PaymentReceipt: TypeConstructorDescription {
case paymentReceipt(flags: Int32, date: Int32, botId: Int64, providerId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, info: Api.PaymentRequestedInfo?, shipping: Api.ShippingOption?, tipAmount: Int64?, currency: String, totalAmount: Int64, credentialsTitle: String, users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .paymentReceipt(let flags, let date, let botId, let providerId, let title, let description, let photo, let invoice, let info, let shipping, let tipAmount, let currency, let totalAmount, let credentialsTitle, let users):
if boxed {
buffer.appendInt32(1891958275)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
serializeInt64(botId, buffer: buffer, boxed: false)
serializeInt64(providerId, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false)
serializeString(description, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)}
invoice.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {info!.serialize(buffer, true)}
if Int(flags) & Int(1 << 1) != 0 {shipping!.serialize(buffer, true)}
if Int(flags) & Int(1 << 3) != 0 {serializeInt64(tipAmount!, buffer: buffer, boxed: false)}
serializeString(currency, buffer: buffer, boxed: false)
serializeInt64(totalAmount, buffer: buffer, boxed: false)
serializeString(credentialsTitle, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .paymentReceipt(let flags, let date, let botId, let providerId, let title, let description, let photo, let invoice, let info, let shipping, let tipAmount, let currency, let totalAmount, let credentialsTitle, let users):
return ("paymentReceipt", [("flags", flags as Any), ("date", date as Any), ("botId", botId as Any), ("providerId", providerId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("info", info as Any), ("shipping", shipping as Any), ("tipAmount", tipAmount as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("credentialsTitle", credentialsTitle as Any), ("users", users as Any)])
}
}
public static func parse_paymentReceipt(_ reader: BufferReader) -> PaymentReceipt? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int64?
_3 = reader.readInt64()
var _4: Int64?
_4 = reader.readInt64()
var _5: String?
_5 = parseString(reader)
var _6: String?
_6 = parseString(reader)
var _7: Api.WebDocument?
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.WebDocument
} }
var _8: Api.Invoice?
if let signature = reader.readInt32() {
_8 = Api.parse(reader, signature: signature) as? Api.Invoice
}
var _9: Api.PaymentRequestedInfo?
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
_9 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo
} }
var _10: Api.ShippingOption?
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
_10 = Api.parse(reader, signature: signature) as? Api.ShippingOption
} }
var _11: Int64?
if Int(_1!) & Int(1 << 3) != 0 {_11 = reader.readInt64() }
var _12: String?
_12 = parseString(reader)
var _13: Int64?
_13 = reader.readInt64()
var _14: String?
_14 = parseString(reader)
var _15: [Api.User]?
if let _ = reader.readInt32() {
_15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil
let _c8 = _8 != nil
let _c9 = (Int(_1!) & Int(1 << 0) == 0) || _9 != nil
let _c10 = (Int(_1!) & Int(1 << 1) == 0) || _10 != nil
let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil
let _c12 = _12 != nil
let _c13 = _13 != nil
let _c14 = _14 != nil
let _c15 = _15 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 {
return Api.payments.PaymentReceipt.paymentReceipt(flags: _1!, date: _2!, botId: _3!, providerId: _4!, title: _5!, description: _6!, photo: _7, invoice: _8!, info: _9, shipping: _10, tipAmount: _11, currency: _12!, totalAmount: _13!, credentialsTitle: _14!, users: _15!)
}
else {
return nil
}
}
}
}
public extension Api.payments {
indirect enum PaymentResult: TypeConstructorDescription {
case paymentResult(updates: Api.Updates)
case paymentVerificationNeeded(url: String)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .paymentResult(let updates):
if boxed {
buffer.appendInt32(1314881805)
}
updates.serialize(buffer, true)
break
case .paymentVerificationNeeded(let url):
if boxed {
buffer.appendInt32(-666824391)
}
serializeString(url, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .paymentResult(let updates):
return ("paymentResult", [("updates", updates as Any)])
case .paymentVerificationNeeded(let url):
return ("paymentVerificationNeeded", [("url", url as Any)])
}
}
public static func parse_paymentResult(_ reader: BufferReader) -> PaymentResult? {
var _1: Api.Updates?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.Updates
}
let _c1 = _1 != nil
if _c1 {
return Api.payments.PaymentResult.paymentResult(updates: _1!)
}
else {
return nil
}
}
public static func parse_paymentVerificationNeeded(_ reader: BufferReader) -> PaymentResult? {
var _1: String?
_1 = parseString(reader)
let _c1 = _1 != nil
if _c1 {
return Api.payments.PaymentResult.paymentVerificationNeeded(url: _1!)
}
else {
return nil
}
}
}
}

View File

@ -1,3 +1,167 @@
public extension Api.payments {
enum PaymentReceipt: TypeConstructorDescription {
case paymentReceipt(flags: Int32, date: Int32, botId: Int64, providerId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, info: Api.PaymentRequestedInfo?, shipping: Api.ShippingOption?, tipAmount: Int64?, currency: String, totalAmount: Int64, credentialsTitle: String, users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .paymentReceipt(let flags, let date, let botId, let providerId, let title, let description, let photo, let invoice, let info, let shipping, let tipAmount, let currency, let totalAmount, let credentialsTitle, let users):
if boxed {
buffer.appendInt32(1891958275)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
serializeInt64(botId, buffer: buffer, boxed: false)
serializeInt64(providerId, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false)
serializeString(description, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)}
invoice.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {info!.serialize(buffer, true)}
if Int(flags) & Int(1 << 1) != 0 {shipping!.serialize(buffer, true)}
if Int(flags) & Int(1 << 3) != 0 {serializeInt64(tipAmount!, buffer: buffer, boxed: false)}
serializeString(currency, buffer: buffer, boxed: false)
serializeInt64(totalAmount, buffer: buffer, boxed: false)
serializeString(credentialsTitle, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .paymentReceipt(let flags, let date, let botId, let providerId, let title, let description, let photo, let invoice, let info, let shipping, let tipAmount, let currency, let totalAmount, let credentialsTitle, let users):
return ("paymentReceipt", [("flags", flags as Any), ("date", date as Any), ("botId", botId as Any), ("providerId", providerId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("info", info as Any), ("shipping", shipping as Any), ("tipAmount", tipAmount as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("credentialsTitle", credentialsTitle as Any), ("users", users as Any)])
}
}
public static func parse_paymentReceipt(_ reader: BufferReader) -> PaymentReceipt? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int64?
_3 = reader.readInt64()
var _4: Int64?
_4 = reader.readInt64()
var _5: String?
_5 = parseString(reader)
var _6: String?
_6 = parseString(reader)
var _7: Api.WebDocument?
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.WebDocument
} }
var _8: Api.Invoice?
if let signature = reader.readInt32() {
_8 = Api.parse(reader, signature: signature) as? Api.Invoice
}
var _9: Api.PaymentRequestedInfo?
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
_9 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo
} }
var _10: Api.ShippingOption?
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
_10 = Api.parse(reader, signature: signature) as? Api.ShippingOption
} }
var _11: Int64?
if Int(_1!) & Int(1 << 3) != 0 {_11 = reader.readInt64() }
var _12: String?
_12 = parseString(reader)
var _13: Int64?
_13 = reader.readInt64()
var _14: String?
_14 = parseString(reader)
var _15: [Api.User]?
if let _ = reader.readInt32() {
_15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil
let _c8 = _8 != nil
let _c9 = (Int(_1!) & Int(1 << 0) == 0) || _9 != nil
let _c10 = (Int(_1!) & Int(1 << 1) == 0) || _10 != nil
let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil
let _c12 = _12 != nil
let _c13 = _13 != nil
let _c14 = _14 != nil
let _c15 = _15 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 {
return Api.payments.PaymentReceipt.paymentReceipt(flags: _1!, date: _2!, botId: _3!, providerId: _4!, title: _5!, description: _6!, photo: _7, invoice: _8!, info: _9, shipping: _10, tipAmount: _11, currency: _12!, totalAmount: _13!, credentialsTitle: _14!, users: _15!)
}
else {
return nil
}
}
}
}
public extension Api.payments {
indirect enum PaymentResult: TypeConstructorDescription {
case paymentResult(updates: Api.Updates)
case paymentVerificationNeeded(url: String)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .paymentResult(let updates):
if boxed {
buffer.appendInt32(1314881805)
}
updates.serialize(buffer, true)
break
case .paymentVerificationNeeded(let url):
if boxed {
buffer.appendInt32(-666824391)
}
serializeString(url, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .paymentResult(let updates):
return ("paymentResult", [("updates", updates as Any)])
case .paymentVerificationNeeded(let url):
return ("paymentVerificationNeeded", [("url", url as Any)])
}
}
public static func parse_paymentResult(_ reader: BufferReader) -> PaymentResult? {
var _1: Api.Updates?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.Updates
}
let _c1 = _1 != nil
if _c1 {
return Api.payments.PaymentResult.paymentResult(updates: _1!)
}
else {
return nil
}
}
public static func parse_paymentVerificationNeeded(_ reader: BufferReader) -> PaymentResult? {
var _1: String?
_1 = parseString(reader)
let _c1 = _1 != nil
if _c1 {
return Api.payments.PaymentResult.paymentVerificationNeeded(url: _1!)
}
else {
return nil
}
}
}
}
public extension Api.payments {
enum SavedInfo: TypeConstructorDescription {
case savedInfo(flags: Int32, savedInfo: Api.PaymentRequestedInfo?)

View File

@ -5325,15 +5325,15 @@ public extension Api.functions.messages {
}
}
public extension Api.functions.messages {
static func getDialogFilters() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.DialogFilter]>) {
static func getDialogFilters() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.DialogFilters>) {
let buffer = Buffer()
buffer.appendInt32(-241247891)
buffer.appendInt32(-271283063)
return (FunctionDescription(name: "messages.getDialogFilters", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.DialogFilter]? in
return (FunctionDescription(name: "messages.getDialogFilters", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.DialogFilters? in
let reader = BufferReader(buffer)
var result: [Api.DialogFilter]?
if let _ = reader.readInt32() {
result = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogFilter.self)
var result: Api.messages.DialogFilters?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.DialogFilters
}
return result
})
@ -7816,6 +7816,21 @@ public extension Api.functions.messages {
})
}
}
public extension Api.functions.messages {
static func toggleDialogFilterTags(enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(-47326647)
enabled.serialize(buffer, true)
return (FunctionDescription(name: "messages.toggleDialogFilterTags", parameters: [("enabled", String(describing: enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
}
public extension Api.functions.messages {
static func toggleDialogPin(flags: Int32, peer: Api.InputDialogPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()

View File

@ -1060,20 +1060,21 @@ public extension Api {
}
public extension Api {
enum DialogFilter: TypeConstructorDescription {
case dialogFilter(flags: Int32, id: Int32, title: String, emoticon: String?, pinnedPeers: [Api.InputPeer], includePeers: [Api.InputPeer], excludePeers: [Api.InputPeer])
case dialogFilterChatlist(flags: Int32, id: Int32, title: String, emoticon: String?, pinnedPeers: [Api.InputPeer], includePeers: [Api.InputPeer])
case dialogFilter(flags: Int32, id: Int32, title: String, emoticon: String?, color: Int32?, pinnedPeers: [Api.InputPeer], includePeers: [Api.InputPeer], excludePeers: [Api.InputPeer])
case dialogFilterChatlist(flags: Int32, id: Int32, title: String, emoticon: String?, color: Int32?, pinnedPeers: [Api.InputPeer], includePeers: [Api.InputPeer])
case dialogFilterDefault
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .dialogFilter(let flags, let id, let title, let emoticon, let pinnedPeers, let includePeers, let excludePeers):
case .dialogFilter(let flags, let id, let title, let emoticon, let color, let pinnedPeers, let includePeers, let excludePeers):
if boxed {
buffer.appendInt32(1949890536)
buffer.appendInt32(1605718587)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(id, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 25) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 27) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(pinnedPeers.count))
for item in pinnedPeers {
@ -1090,14 +1091,15 @@ public extension Api {
item.serialize(buffer, true)
}
break
case .dialogFilterChatlist(let flags, let id, let title, let emoticon, let pinnedPeers, let includePeers):
case .dialogFilterChatlist(let flags, let id, let title, let emoticon, let color, let pinnedPeers, let includePeers):
if boxed {
buffer.appendInt32(-699792216)
buffer.appendInt32(-1612542300)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(id, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 25) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 27) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(pinnedPeers.count))
for item in pinnedPeers {
@ -1120,10 +1122,10 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .dialogFilter(let flags, let id, let title, let emoticon, let pinnedPeers, let includePeers, let excludePeers):
return ("dialogFilter", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("emoticon", emoticon as Any), ("pinnedPeers", pinnedPeers as Any), ("includePeers", includePeers as Any), ("excludePeers", excludePeers as Any)])
case .dialogFilterChatlist(let flags, let id, let title, let emoticon, let pinnedPeers, let includePeers):
return ("dialogFilterChatlist", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("emoticon", emoticon as Any), ("pinnedPeers", pinnedPeers as Any), ("includePeers", includePeers as Any)])
case .dialogFilter(let flags, let id, let title, let emoticon, let color, let pinnedPeers, let includePeers, let excludePeers):
return ("dialogFilter", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("emoticon", emoticon as Any), ("color", color as Any), ("pinnedPeers", pinnedPeers as Any), ("includePeers", includePeers as Any), ("excludePeers", excludePeers as Any)])
case .dialogFilterChatlist(let flags, let id, let title, let emoticon, let color, let pinnedPeers, let includePeers):
return ("dialogFilterChatlist", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("emoticon", emoticon as Any), ("color", color as Any), ("pinnedPeers", pinnedPeers as Any), ("includePeers", includePeers as Any)])
case .dialogFilterDefault:
return ("dialogFilterDefault", [])
}
@ -1138,10 +1140,8 @@ public extension Api {
_3 = parseString(reader)
var _4: String?
if Int(_1!) & Int(1 << 25) != 0 {_4 = parseString(reader) }
var _5: [Api.InputPeer]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self)
}
var _5: Int32?
if Int(_1!) & Int(1 << 27) != 0 {_5 = reader.readInt32() }
var _6: [Api.InputPeer]?
if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self)
@ -1150,15 +1150,20 @@ public extension Api {
if let _ = reader.readInt32() {
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self)
}
var _8: [Api.InputPeer]?
if let _ = reader.readInt32() {
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 25) == 0) || _4 != nil
let _c5 = _5 != nil
let _c5 = (Int(_1!) & Int(1 << 27) == 0) || _5 != nil
let _c6 = _6 != nil
let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.DialogFilter.dialogFilter(flags: _1!, id: _2!, title: _3!, emoticon: _4, pinnedPeers: _5!, includePeers: _6!, excludePeers: _7!)
let _c8 = _8 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
return Api.DialogFilter.dialogFilter(flags: _1!, id: _2!, title: _3!, emoticon: _4, color: _5, pinnedPeers: _6!, includePeers: _7!, excludePeers: _8!)
}
else {
return nil
@ -1173,22 +1178,25 @@ public extension Api {
_3 = parseString(reader)
var _4: String?
if Int(_1!) & Int(1 << 25) != 0 {_4 = parseString(reader) }
var _5: [Api.InputPeer]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self)
}
var _5: Int32?
if Int(_1!) & Int(1 << 27) != 0 {_5 = reader.readInt32() }
var _6: [Api.InputPeer]?
if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self)
}
var _7: [Api.InputPeer]?
if let _ = reader.readInt32() {
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 25) == 0) || _4 != nil
let _c5 = _5 != nil
let _c5 = (Int(_1!) & Int(1 << 27) == 0) || _5 != nil
let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.DialogFilter.dialogFilterChatlist(flags: _1!, id: _2!, title: _3!, emoticon: _4, pinnedPeers: _5!, includePeers: _6!)
let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.DialogFilter.dialogFilterChatlist(flags: _1!, id: _2!, title: _3!, emoticon: _4, color: _5, pinnedPeers: _6!, includePeers: _7!)
}
else {
return nil

View File

@ -264,14 +264,15 @@ public extension Api {
}
public extension Api {
enum InputBusinessAwayMessage: TypeConstructorDescription {
case inputBusinessAwayMessage(shortcutId: Int32, schedule: Api.BusinessAwayMessageSchedule, recipients: Api.InputBusinessRecipients)
case inputBusinessAwayMessage(flags: Int32, shortcutId: Int32, schedule: Api.BusinessAwayMessageSchedule, recipients: Api.InputBusinessRecipients)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputBusinessAwayMessage(let shortcutId, let schedule, let recipients):
case .inputBusinessAwayMessage(let flags, let shortcutId, let schedule, let recipients):
if boxed {
buffer.appendInt32(-307493900)
buffer.appendInt32(-2094959136)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(shortcutId, buffer: buffer, boxed: false)
schedule.serialize(buffer, true)
recipients.serialize(buffer, true)
@ -281,27 +282,30 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputBusinessAwayMessage(let shortcutId, let schedule, let recipients):
return ("inputBusinessAwayMessage", [("shortcutId", shortcutId as Any), ("schedule", schedule as Any), ("recipients", recipients as Any)])
case .inputBusinessAwayMessage(let flags, let shortcutId, let schedule, let recipients):
return ("inputBusinessAwayMessage", [("flags", flags as Any), ("shortcutId", shortcutId as Any), ("schedule", schedule as Any), ("recipients", recipients as Any)])
}
}
public static func parse_inputBusinessAwayMessage(_ reader: BufferReader) -> InputBusinessAwayMessage? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.BusinessAwayMessageSchedule?
var _2: Int32?
_2 = reader.readInt32()
var _3: Api.BusinessAwayMessageSchedule?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.BusinessAwayMessageSchedule
_3 = Api.parse(reader, signature: signature) as? Api.BusinessAwayMessageSchedule
}
var _3: Api.InputBusinessRecipients?
var _4: Api.InputBusinessRecipients?
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.InputBusinessRecipients
_4 = Api.parse(reader, signature: signature) as? Api.InputBusinessRecipients
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.InputBusinessAwayMessage.inputBusinessAwayMessage(shortcutId: _1!, schedule: _2!, recipients: _3!)
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.InputBusinessAwayMessage.inputBusinessAwayMessage(flags: _1!, shortcutId: _2!, schedule: _3!, recipients: _4!)
}
else {
return nil

View File

@ -590,7 +590,8 @@ public final class TelegramBusinessAwayMessage: Codable, Equatable {
extension TelegramBusinessAwayMessage {
convenience init(apiAwayMessage: Api.BusinessAwayMessage) {
switch apiAwayMessage {
case let .businessAwayMessage(shortcutId, schedule, recipients):
case let .businessAwayMessage(flags, shortcutId, schedule, recipients):
let _ = flags
let mappedSchedule: Schedule
switch schedule {
case .businessAwayMessageScheduleAlways:
@ -730,6 +731,7 @@ func _internal_updateBusinessAwayMessage(account: Account, awayMessage: Telegram
}
mappedMessage = .inputBusinessAwayMessage(
flags: 0,
shortcutId: awayMessage.shortcutId,
schedule: mappedSchedule,
recipients: awayMessage.recipients.apiInputValue(additionalPeers: additionalPeers)

View File

@ -306,7 +306,7 @@ extension ChatListFilter {
switch apiFilter {
case .dialogFilterDefault:
self = .allChats
case let .dialogFilter(flags, id, title, emoticon, pinnedPeers, includePeers, excludePeers):
case let .dialogFilter(flags, id, title, emoticon, color, pinnedPeers, includePeers, excludePeers):
self = .filter(
id: id,
title: title,
@ -353,10 +353,10 @@ extension ChatListFilter {
return nil
}
},
color: nil
color: color.flatMap(PeerNameColor.init(rawValue:))
)
)
case let .dialogFilterChatlist(flags, id, title, emoticon, pinnedPeers, includePeers):
case let .dialogFilterChatlist(flags, id, title, emoticon, color, pinnedPeers, includePeers):
self = .filter(
id: id,
title: title,
@ -392,7 +392,7 @@ extension ChatListFilter {
}
}),
excludePeers: [],
color: nil
color: color.flatMap(PeerNameColor.init(rawValue:))
)
)
}
@ -408,7 +408,10 @@ extension ChatListFilter {
if emoticon != nil {
flags |= 1 << 25
}
return .dialogFilterChatlist(flags: flags, id: id, title: title, emoticon: emoticon, pinnedPeers: data.includePeers.pinnedPeers.compactMap { peerId -> Api.InputPeer? in
if data.color != nil {
flags |= 1 << 27
}
return .dialogFilterChatlist(flags: flags, id: id, title: title, emoticon: emoticon, color: data.color?.rawValue, pinnedPeers: data.includePeers.pinnedPeers.compactMap { peerId -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}, includePeers: data.includePeers.peers.compactMap { peerId -> Api.InputPeer? in
if data.includePeers.pinnedPeers.contains(peerId) {
@ -431,7 +434,10 @@ extension ChatListFilter {
if emoticon != nil {
flags |= 1 << 25
}
return .dialogFilter(flags: flags, id: id, title: title, emoticon: emoticon, pinnedPeers: data.includePeers.pinnedPeers.compactMap { peerId -> Api.InputPeer? in
if data.color != nil {
flags |= 1 << 27
}
return .dialogFilter(flags: flags, id: id, title: title, emoticon: emoticon, color: data.color?.rawValue, pinnedPeers: data.includePeers.pinnedPeers.compactMap { peerId -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}, includePeers: data.includePeers.peers.compactMap { peerId -> Api.InputPeer? in
if data.includePeers.pinnedPeers.contains(peerId) {
@ -488,111 +494,116 @@ private enum RequestChatListFiltersError {
case generic
}
private func requestChatListFilters(accountPeerId: PeerId, postbox: Postbox, network: Network) -> Signal<[ChatListFilter], RequestChatListFiltersError> {
private func requestChatListFilters(accountPeerId: PeerId, postbox: Postbox, network: Network) -> Signal<([ChatListFilter], Bool), RequestChatListFiltersError> {
return network.request(Api.functions.messages.getDialogFilters())
|> mapError { _ -> RequestChatListFiltersError in
return .generic
}
|> mapToSignal { result -> Signal<[ChatListFilter], RequestChatListFiltersError> in
return postbox.transaction { transaction -> ([ChatListFilter], [Api.InputPeer], [Api.InputPeer]) in
var filters: [ChatListFilter] = []
var missingPeers: [Api.InputPeer] = []
var missingChats: [Api.InputPeer] = []
var missingPeerIds = Set<PeerId>()
var missingChatIds = Set<PeerId>()
for apiFilter in result {
let filter = ChatListFilter(apiFilter: apiFilter)
filters.append(filter)
switch apiFilter {
case .dialogFilterDefault:
break
case let .dialogFilter(_, _, _, _, pinnedPeers, includePeers, excludePeers):
for peer in pinnedPeers + includePeers + excludePeers {
var peerId: PeerId?
switch peer {
case let .inputPeerUser(userId, _):
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
case let .inputPeerChat(chatId):
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))
case let .inputPeerChannel(channelId, _):
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
default:
break
}
if let peerId = peerId {
if transaction.getPeer(peerId) == nil && !missingPeerIds.contains(peerId) {
missingPeerIds.insert(peerId)
missingPeers.append(peer)
|> mapToSignal { result -> Signal<([ChatListFilter], Bool), RequestChatListFiltersError> in
return postbox.transaction { transaction -> ([ChatListFilter], [Api.InputPeer], [Api.InputPeer], Bool) in
switch result {
case let .dialogFilters(flags, apiFilters):
let tagsEnabled = (flags & (1 << 0)) != 0
var filters: [ChatListFilter] = []
var missingPeers: [Api.InputPeer] = []
var missingChats: [Api.InputPeer] = []
var missingPeerIds = Set<PeerId>()
var missingChatIds = Set<PeerId>()
for apiFilter in apiFilters {
let filter = ChatListFilter(apiFilter: apiFilter)
filters.append(filter)
switch apiFilter {
case .dialogFilterDefault:
break
case let .dialogFilter(_, _, _, _, _, pinnedPeers, includePeers, excludePeers):
for peer in pinnedPeers + includePeers + excludePeers {
var peerId: PeerId?
switch peer {
case let .inputPeerUser(userId, _):
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
case let .inputPeerChat(chatId):
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))
case let .inputPeerChannel(channelId, _):
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
default:
break
}
if let peerId = peerId {
if transaction.getPeer(peerId) == nil && !missingPeerIds.contains(peerId) {
missingPeerIds.insert(peerId)
missingPeers.append(peer)
}
}
}
}
for peer in pinnedPeers {
var peerId: PeerId?
switch peer {
case let .inputPeerUser(userId, _):
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
case let .inputPeerChat(chatId):
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))
case let .inputPeerChannel(channelId, _):
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
default:
break
}
if let peerId = peerId, !missingChatIds.contains(peerId) {
if transaction.getPeerChatListIndex(peerId) == nil {
missingChatIds.insert(peerId)
missingChats.append(peer)
for peer in pinnedPeers {
var peerId: PeerId?
switch peer {
case let .inputPeerUser(userId, _):
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
case let .inputPeerChat(chatId):
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))
case let .inputPeerChannel(channelId, _):
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
default:
break
}
if let peerId = peerId, !missingChatIds.contains(peerId) {
if transaction.getPeerChatListIndex(peerId) == nil {
missingChatIds.insert(peerId)
missingChats.append(peer)
}
}
}
}
case let .dialogFilterChatlist(_, _, _, _, pinnedPeers, includePeers):
for peer in pinnedPeers + includePeers {
var peerId: PeerId?
switch peer {
case let .inputPeerUser(userId, _):
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
case let .inputPeerChat(chatId):
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))
case let .inputPeerChannel(channelId, _):
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
default:
break
}
if let peerId = peerId {
if transaction.getPeer(peerId) == nil && !missingPeerIds.contains(peerId) {
missingPeerIds.insert(peerId)
missingPeers.append(peer)
case let .dialogFilterChatlist(_, _, _, _, _, pinnedPeers, includePeers):
for peer in pinnedPeers + includePeers {
var peerId: PeerId?
switch peer {
case let .inputPeerUser(userId, _):
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
case let .inputPeerChat(chatId):
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))
case let .inputPeerChannel(channelId, _):
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
default:
break
}
if let peerId = peerId {
if transaction.getPeer(peerId) == nil && !missingPeerIds.contains(peerId) {
missingPeerIds.insert(peerId)
missingPeers.append(peer)
}
}
}
}
for peer in pinnedPeers {
var peerId: PeerId?
switch peer {
case let .inputPeerUser(userId, _):
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
case let .inputPeerChat(chatId):
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))
case let .inputPeerChannel(channelId, _):
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
default:
break
}
if let peerId = peerId, !missingChatIds.contains(peerId) {
if transaction.getPeerChatListIndex(peerId) == nil {
missingChatIds.insert(peerId)
missingChats.append(peer)
for peer in pinnedPeers {
var peerId: PeerId?
switch peer {
case let .inputPeerUser(userId, _):
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
case let .inputPeerChat(chatId):
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))
case let .inputPeerChannel(channelId, _):
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
default:
break
}
if let peerId = peerId, !missingChatIds.contains(peerId) {
if transaction.getPeerChatListIndex(peerId) == nil {
missingChatIds.insert(peerId)
missingChats.append(peer)
}
}
}
}
}
return (filters, missingPeers, missingChats, tagsEnabled)
}
return (filters, missingPeers, missingChats)
}
|> castError(RequestChatListFiltersError.self)
|> mapToSignal { filtersAndMissingPeers -> Signal<[ChatListFilter], RequestChatListFiltersError> in
let (filters, missingPeers, missingChats) = filtersAndMissingPeers
|> mapToSignal { filtersAndMissingPeers -> Signal<([ChatListFilter], Bool), RequestChatListFiltersError> in
let (filters, missingPeers, missingChats, tagsEnabled) = filtersAndMissingPeers
var missingUsers: [Api.InputUser] = []
var missingChannels: [Api.InputChannel] = []
@ -700,13 +711,10 @@ private func requestChatListFilters(accountPeerId: PeerId, postbox: Postbox, net
loadMissingChats
)
|> castError(RequestChatListFiltersError.self)
|> mapToSignal { _ -> Signal<[ChatListFilter], RequestChatListFiltersError> in
#if swift(<5.1)
return .complete()
#endif
|> mapToSignal { _ -> Signal<([ChatListFilter], Bool), RequestChatListFiltersError> in
}
|> then(
.single(filters)
Signal<([ChatListFilter], Bool), RequestChatListFiltersError>.single((filters, tagsEnabled))
)
}
}
@ -889,14 +897,16 @@ struct ChatListFiltersState: Codable, Equatable {
var updates: [ChatListFilterUpdates]
var remoteDisplayTags: Bool?
var displayTags: Bool
static var `default` = ChatListFiltersState(filters: [], remoteFilters: nil, updates: [], displayTags: false)
static var `default` = ChatListFiltersState(filters: [], remoteFilters: nil, updates: [], remoteDisplayTags: nil, displayTags: false)
fileprivate init(filters: [ChatListFilter], remoteFilters: [ChatListFilter]?, updates: [ChatListFilterUpdates], displayTags: Bool) {
fileprivate init(filters: [ChatListFilter], remoteFilters: [ChatListFilter]?, updates: [ChatListFilterUpdates], remoteDisplayTags: Bool?, displayTags: Bool) {
self.filters = filters
self.remoteFilters = remoteFilters
self.updates = updates
self.remoteDisplayTags = remoteDisplayTags
self.displayTags = displayTags
}
@ -906,6 +916,7 @@ struct ChatListFiltersState: Codable, Equatable {
self.filters = try container.decode([ChatListFilter].self, forKey: "filters")
self.remoteFilters = try container.decodeIfPresent([ChatListFilter].self, forKey: "remoteFilters")
self.updates = try container.decodeIfPresent([ChatListFilterUpdates].self, forKey: "updates") ?? []
self.remoteDisplayTags = try container.decodeIfPresent(Bool.self, forKey: "remoteDisplayTags")
self.displayTags = try container.decodeIfPresent(Bool.self, forKey: "displayTags") ?? false
}
@ -915,6 +926,7 @@ struct ChatListFiltersState: Codable, Equatable {
try container.encode(self.filters, forKey: "filters")
try container.encodeIfPresent(self.remoteFilters, forKey: "remoteFilters")
try container.encode(self.updates, forKey: "updates")
try container.encodeIfPresent(self.remoteDisplayTags, forKey: "remoteDisplayTags")
try container.encode(self.displayTags, forKey: "displayTags")
}
@ -959,6 +971,27 @@ func _internal_updateChatListFiltersInteractively(postbox: Postbox, _ f: @escapi
}
}
func _internal_updateChatListFiltersDisplayTagsInteractively(postbox: Postbox, displayTags: Bool) -> Signal<Never, NoError> {
return postbox.transaction { transaction -> Void in
var hasUpdates = false
transaction.updatePreferencesEntry(key: PreferencesKeys.chatListFilters, { entry in
var state = entry?.get(ChatListFiltersState.self) ?? ChatListFiltersState.default
if displayTags != state.displayTags {
state.displayTags = displayTags
hasUpdates = true
}
state.normalize()
return PreferencesEntry(state)
})
if hasUpdates {
requestChatListFiltersSync(transaction: transaction)
}
}
|> ignoreValues
}
func _internal_updateChatListFiltersInteractively(transaction: Transaction, _ f: ([ChatListFilter]) -> [ChatListFilter]) {
var hasUpdates = false
transaction.updatePreferencesEntry(key: PreferencesKeys.chatListFilters, { entry in
@ -1366,18 +1399,21 @@ private func synchronizeChatListFilters(transaction: Transaction, accountPeerId:
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters)?.get(ChatListFiltersState.self) ?? ChatListFiltersState.default
let localFilters = settings.filters
let locallyKnownRemoteFilters = settings.remoteFilters ?? []
let localDisplayTags = settings.displayTags
let locallyKnownRemoteDisplayTags = settings.remoteDisplayTags ?? false
return requestChatListFilters(accountPeerId: accountPeerId, postbox: postbox, network: network)
|> `catch` { _ -> Signal<[ChatListFilter], NoError> in
|> `catch` { _ -> Signal<([ChatListFilter], Bool), NoError> in
return .complete()
}
|> mapToSignal { remoteFilters -> Signal<Never, NoError> in
if localFilters == locallyKnownRemoteFilters {
|> mapToSignal { remoteFilters, remoteTagsEnabled -> Signal<Never, NoError> in
if localFilters == locallyKnownRemoteFilters && localDisplayTags == locallyKnownRemoteDisplayTags {
return postbox.transaction { transaction -> Void in
let _ = updateChatListFiltersState(transaction: transaction, { state in
var state = state
state.filters = remoteFilters
state.remoteFilters = state.filters
state.displayTags = remoteTagsEnabled
return state
})
}
@ -1453,6 +1489,17 @@ private func synchronizeChatListFilters(transaction: Transaction, accountPeerId:
reorderFilters = .complete()
}
let updateTagsEnabled: Signal<Never, NoError>
if localDisplayTags != remoteTagsEnabled {
updateTagsEnabled = network.request(Api.functions.messages.toggleDialogFilterTags(enabled: localDisplayTags ? .boolTrue : .boolFalse))
|> ignoreValues
|> `catch` { _ -> Signal<Never, NoError> in
return .complete()
}
} else {
updateTagsEnabled = .complete()
}
return deleteSignals
|> then(
addSignals
@ -1460,12 +1507,16 @@ private func synchronizeChatListFilters(transaction: Transaction, accountPeerId:
|> then(
reorderFilters
)
|> then(
updateTagsEnabled
)
|> then(
postbox.transaction { transaction -> Void in
let _ = updateChatListFiltersState(transaction: transaction, { state in
var state = state
state.filters = mergedFilters
state.remoteFilters = state.filters
state.remoteDisplayTags = state.displayTags
return state
})
}

View File

@ -589,13 +589,7 @@ public extension TelegramEngine {
}
public func updateChatListFiltersDisplayTags(isEnabled: Bool) {
let _ = self.account.postbox.transaction({ transaction in
updateChatListFiltersState(transaction: transaction, { state in
var state = state
state.displayTags = isEnabled
return state
})
}).start()
let _ = _internal_updateChatListFiltersDisplayTagsInteractively(postbox: self.account.postbox, displayTags: isEnabled).startStandalone()
}
public func updatedChatListFilters() -> Signal<[ChatListFilter], NoError> {

View File

@ -257,6 +257,12 @@ public extension TelegramEngine {
return (items.map(\.file), isFinalResult)
}
}
public func addRecentlyUsedSticker(file: TelegramMediaFile) {
let _ = self.account.postbox.transaction({ transaction -> Void in
TelegramCore.addRecentlyUsedSticker(transaction: transaction, fileReference: .standalone(media: file))
}).start()
}
}
}

View File

@ -199,6 +199,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
case savedMessageTagLabelSuggestion = 65
case dismissedLastSeenBadge = 66
case dismissedMessagePrivacyBadge = 67
case dismissedBusinessBadge = 68
var key: ValueBoxKey {
let v = ValueBoxKey(length: 4)
@ -529,6 +530,10 @@ private struct ApplicationSpecificNoticeKeys {
static func dismissedMessagePrivacyBadge() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedMessagePrivacyBadge.key)
}
static func dismissedBusinessBadge() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedBusinessBadge.key)
}
}
public struct ApplicationSpecificNotice {
@ -2223,4 +2228,25 @@ public struct ApplicationSpecificNotice {
}
|> take(1)
}
public static func setDismissedBusinessBadge(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Never, NoError> {
return accountManager.transaction { transaction -> Void in
if let entry = CodableEntry(ApplicationSpecificBoolNotice()) {
transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedBusinessBadge(), entry)
}
}
|> ignoreValues
}
public static func dismissedBusinessBadge(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Bool, NoError> {
return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedBusinessBadge())
|> map { view -> Bool in
if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) {
return true
} else {
return false
}
}
|> take(1)
}
}

View File

@ -2,7 +2,7 @@ import Foundation
import TelegramPresentationData
import TelegramUIPreferences
public func stringForShortTimestamp(hours: Int32, minutes: Int32, dateTimeFormat: PresentationDateTimeFormat) -> String {
public func stringForShortTimestamp(hours: Int32, minutes: Int32, dateTimeFormat: PresentationDateTimeFormat, formatAsPlainText: Bool = false) -> String {
switch dateTimeFormat.timeFormat {
case .regular:
let hourString: String
@ -20,10 +20,18 @@ public func stringForShortTimestamp(hours: Int32, minutes: Int32, dateTimeFormat
} else {
periodString = "AM"
}
if minutes >= 10 {
return "\(hourString):\(minutes)\u{00a0}\(periodString)"
let spaceCharacter: String
if formatAsPlainText {
spaceCharacter = " "
} else {
return "\(hourString):0\(minutes)\u{00a0}\(periodString)"
spaceCharacter = "\u{00a0}"
}
if minutes >= 10 {
return "\(hourString):\(minutes)\(spaceCharacter)\(periodString)"
} else {
return "\(hourString):0\(minutes)\(spaceCharacter)\(periodString)"
}
case .military:
return String(format: "%02d:%02d", arguments: [Int(hours), Int(minutes)])

View File

@ -2384,6 +2384,7 @@ public class CameraScreen: ViewController {
bottom: bottomInset,
right: layout.safeInsets.right
),
additionalInsets: layout.additionalInsets,
inputHeight: layout.inputHeight ?? 0.0,
metrics: layout.metrics,
deviceMetrics: layout.deviceMetrics,

View File

@ -10,7 +10,7 @@ import ShareController
import LegacyUI
import LegacyMediaPickerUI
public func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocation: ChatLocation, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, attachmentController: ViewController? = nil, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: NSAttributedString, hasSchedule: Bool, enablePhoto: Bool, enableVideo: Bool, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, dismissedWithResult: @escaping () -> Void = {}, finishedTransitionIn: @escaping () -> Void = {}) {
public func presentedLegacyCamera(context: AccountContext, peer: Peer?, chatLocation: ChatLocation, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, attachmentController: ViewController? = nil, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: NSAttributedString, hasSchedule: Bool, enablePhoto: Bool, enableVideo: Bool, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, dismissedWithResult: @escaping () -> Void = {}, finishedTransitionIn: @escaping () -> Void = {}) {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait)
@ -18,7 +18,7 @@ public func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocat
legacyController.deferScreenEdgeGestures = [.top]
let isSecretChat = peer.id.namespace == Namespaces.Peer.SecretChat
let isSecretChat = peer?.id.namespace == Namespaces.Peer.SecretChat
let controller: TGCameraController
if let cameraView = cameraView, let previewView = cameraView.previewView() {
@ -87,15 +87,18 @@ public func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocat
controller.allowCaptionEntities = true
controller.allowGrouping = mediaGrouping
controller.inhibitDocumentCaptions = false
controller.recipientName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
if peer.id != context.account.peerId {
if peer is TelegramUser {
controller.hasTimer = hasSchedule
if let peer {
controller.recipientName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
if peer.id != context.account.peerId {
if peer is TelegramUser {
controller.hasTimer = hasSchedule
}
controller.hasSilentPosting = true
}
controller.hasSilentPosting = true
}
controller.hasSchedule = hasSchedule
controller.reminder = peer.id == context.account.peerId
controller.reminder = peer?.id == context.account.peerId
let screenSize = parentController.view.bounds.size
var startFrame = CGRect(x: 0, y: screenSize.height, width: screenSize.width, height: screenSize.height)

View File

@ -3,6 +3,14 @@
using namespace metal;
typedef struct {
float2 dimensions;
float roundness;
float alpha;
float isOpaque;
float empty;
} VideoEncodeParameters;
typedef struct {
float4 pos;
float2 texCoord;
@ -17,11 +25,10 @@ float sdfRoundedRectangle(float2 uv, float2 position, float2 size, float radius)
fragment half4 dualFragmentShader(RasterizerData in [[stage_in]],
texture2d<half, access::sample> texture [[texture(0)]],
constant uint2 &resolution[[buffer(0)]],
constant float &roundness[[buffer(1)]],
constant float &alpha[[buffer(2)]]
texture2d<half, access::sample> mask [[texture(1)]],
constant VideoEncodeParameters& adjustments [[buffer(0)]]
) {
float2 R = float2(resolution.x, resolution.y);
float2 R = float2(adjustments.dimensions.x, adjustments.dimensions.y);
float2 uv = (in.localPos - float2(0.5, 0.5)) * 2.0;
if (R.x > R.y) {
@ -33,10 +40,11 @@ fragment half4 dualFragmentShader(RasterizerData in [[stage_in]],
constexpr sampler samplr(filter::linear, mag_filter::linear, min_filter::linear);
half3 color = texture.sample(samplr, in.texCoord).rgb;
float colorAlpha = min(1.0, adjustments.isOpaque + mask.sample(samplr, in.texCoord).r);
float t = 1.0 / resolution.y;
float t = 1.0 / adjustments.dimensions.y;
float side = 1.0 * aspectRatio;
float distance = smoothstep(t, -t, sdfRoundedRectangle(uv, float2(0.0, 0.0), float2(side, mix(1.0, side, roundness)), side * roundness));
float distance = smoothstep(t, -t, sdfRoundedRectangle(uv, float2(0.0, 0.0), float2(side, mix(1.0, side, adjustments.roundness)), side * adjustments.roundness));
return mix(half4(color, 0.0), half4(color, 1.0 * alpha), distance);
return mix(half4(color, 0.0), half4(color, colorAlpha * adjustments.alpha), distance);
}

View File

@ -55,6 +55,70 @@ public func cutoutStickerImage(from image: UIImage, onlyCheck: Bool = false) ->
}
}
public enum CutoutResult {
case image(UIImage)
case pixelBuffer(CVPixelBuffer)
}
public func cutoutImage(from image: UIImage, atPoint point: CGPoint?, asImage: Bool) -> Signal<CutoutResult?, NoError> {
if #available(iOS 17.0, *) {
guard let cgImage = image.cgImage else {
return .single(nil)
}
return Signal { subscriber in
let ciContext = CIContext(options: nil)
let inputImage = CIImage(cgImage: cgImage)
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
let request = VNGenerateForegroundInstanceMaskRequest { [weak handler] request, error in
guard let handler, let result = request.results?.first as? VNInstanceMaskObservation else {
subscriber.putNext(nil)
subscriber.putCompletion()
return
}
let instances = IndexSet(instances(atPoint: point, inObservation: result).prefix(1))
if let mask = try? result.generateScaledMaskForImage(forInstances: instances, from: handler) {
if asImage {
let filter = CIFilter.blendWithMask()
filter.inputImage = inputImage
filter.backgroundImage = CIImage(color: .clear)
filter.maskImage = CIImage(cvPixelBuffer: mask)
if let output = filter.outputImage, let cgImage = ciContext.createCGImage(output, from: inputImage.extent) {
let image = UIImage(cgImage: cgImage)
subscriber.putNext(.image(image))
subscriber.putCompletion()
return
}
} else {
let filter = CIFilter.blendWithMask()
filter.inputImage = CIImage(color: .white)
filter.backgroundImage = CIImage(color: .black)
filter.maskImage = CIImage(cvPixelBuffer: mask)
if let output = filter.outputImage, let cgImage = ciContext.createCGImage(output, from: inputImage.extent) {
let image = UIImage(cgImage: cgImage)
subscriber.putNext(.image(image))
subscriber.putCompletion()
return
}
// subscriber.putNext(.pixelBuffer(mask))
// subscriber.putCompletion()
}
}
subscriber.putNext(nil)
subscriber.putCompletion()
}
try? handler.perform([request])
return ActionDisposable {
request.cancel()
}
}
|> runOn(queue)
} else {
return .single(nil)
}
}
@available(iOS 17.0, *)
private func instances(atPoint maybePoint: CGPoint?, inObservation observation: VNInstanceMaskObservation) -> IndexSet {
guard let point = maybePoint else {

View File

@ -190,6 +190,7 @@ public final class MediaEditor {
public private(set) var canCutout: Bool = false
public var canCutoutUpdated: (Bool) -> Void = { _ in }
public var isCutoutUpdated: (Bool) -> Void = { _ in }
private var textureCache: CVMetalTextureCache!
@ -1682,6 +1683,51 @@ public final class MediaEditor {
self.renderer.renderFrame()
}
public func getSeparatedImage(point: CGPoint?) -> Signal<UIImage?, NoError> {
guard let textureSource = self.renderer.textureSource as? UniversalTextureSource, let image = textureSource.mainImage else {
return .single(nil)
}
return cutoutImage(from: image, atPoint: point, asImage: true)
|> map { result in
if let result, case let .image(image) = result {
return image
} else {
return nil
}
}
}
public func removeSeparationMask() {
self.isCutoutUpdated(false)
self.renderer.currentMainInputMask = nil
if !self.skipRendering {
self.updateRenderChain()
}
}
public func setSeparationMask(point: CGPoint?) {
guard let renderTarget = self.previewView, let device = renderTarget.mtlDevice else {
return
}
guard let textureSource = self.renderer.textureSource as? UniversalTextureSource, let image = textureSource.mainImage else {
return
}
self.isCutoutUpdated(true)
let _ = (cutoutImage(from: image, atPoint: point, asImage: false)
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let self, let result, case let .image(image) = result else {
return
}
//TODO:replace with pixelbuffer
self.renderer.currentMainInputMask = loadTexture(image: image, device: device)
if !self.skipRendering {
self.updateRenderChain()
}
})
}
private func maybeGeneratePersonSegmentation(_ image: UIImage?) {
if #available(iOS 15.0, *), let cgImage = image?.cgImage {
let faceRequest = VNDetectFaceRectanglesRequest { [weak self] request, _ in

View File

@ -98,6 +98,7 @@ final class MediaEditorRenderer {
}
private var currentMainInput: Input?
var currentMainInputMask: MTLTexture?
private var currentAdditionalInput: Input?
private(set) var resultTexture: MTLTexture?
@ -202,7 +203,7 @@ final class MediaEditorRenderer {
}
if let mainTexture {
return self.videoFinishPass.process(input: mainTexture, secondInput: additionalTexture, timestamp: mainInput.timestamp, device: device, commandBuffer: commandBuffer)
return self.videoFinishPass.process(input: mainTexture, inputMask: self.currentMainInputMask, secondInput: additionalTexture, timestamp: mainInput.timestamp, device: device, commandBuffer: commandBuffer)
} else {
return nil
}

View File

@ -42,6 +42,13 @@ final class UniversalTextureSource: TextureSource {
)
}
var mainImage: UIImage? {
if let mainInput = self.mainInputContext?.input, case let .image(image) = mainInput {
return image
}
return nil
}
func setMainInput(_ input: Input) {
guard let renderTarget = self.renderTarget else {
return

View File

@ -144,6 +144,14 @@ private var transitionDuration = 0.5
private var apperanceDuration = 0.2
private var videoRemovalDuration: Double = 0.2
struct VideoEncodeParameters {
var dimensions: simd_float2
var roundness: simd_float1
var alpha: simd_float1
var isOpaque: simd_float1
var empty: simd_float1
}
final class VideoFinishPass: RenderPass {
private var cachedTexture: MTLTexture?
@ -195,6 +203,7 @@ final class VideoFinishPass: RenderPass {
containerSize: CGSize,
texture: MTLTexture,
textureRotation: TextureRotation,
maskTexture: MTLTexture?,
position: VideoPosition,
roundness: Float,
alpha: Float,
@ -202,6 +211,11 @@ final class VideoFinishPass: RenderPass {
device: MTLDevice
) {
encoder.setFragmentTexture(texture, index: 0)
if let maskTexture {
encoder.setFragmentTexture(maskTexture, index: 1)
} else {
encoder.setFragmentTexture(texture, index: 1)
}
let center = CGPoint(
x: position.position.x - containerSize.width / 2.0,
@ -220,14 +234,25 @@ final class VideoFinishPass: RenderPass {
options: [])
encoder.setVertexBuffer(buffer, offset: 0, index: 0)
var resolution = simd_uint2(UInt32(size.width), UInt32(size.height))
encoder.setFragmentBytes(&resolution, length: MemoryLayout<simd_uint2>.size * 2, index: 0)
var roundness = roundness
encoder.setFragmentBytes(&roundness, length: MemoryLayout<simd_float1>.size, index: 1)
var alpha = alpha
encoder.setFragmentBytes(&alpha, length: MemoryLayout<simd_float1>.size, index: 2)
var parameters = VideoEncodeParameters(
dimensions: simd_float2(Float(size.width), Float(size.height)),
roundness: roundness,
alpha: alpha,
isOpaque: maskTexture == nil ? 1.0 : 0.0,
empty: 0
)
encoder.setFragmentBytes(&parameters, length: MemoryLayout<VideoEncodeParameters>.size, index: 0)
// var resolution = simd_uint2(UInt32(size.width), UInt32(size.height))
// encoder.setFragmentBytes(&resolution, length: MemoryLayout<simd_uint2>.size * 2, index: 0)
//
// var roundness = roundness
// encoder.setFragmentBytes(&roundness, length: MemoryLayout<simd_float1>.size, index: 1)
//
// var alpha = alpha
// encoder.setFragmentBytes(&alpha, length: MemoryLayout<simd_float1>.size, index: 2)
//
// var isOpaque = maskTexture == nil ? 1.0 : 0.0
// encoder.setFragmentBytes(&isOpaque, length: MemoryLayout<simd_float1>.size, index: 3)
encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
}
@ -478,7 +503,14 @@ final class VideoFinishPass: RenderPass {
return (backgroundVideoState, foregroundVideoState, disappearingVideoState)
}
func process(input: MTLTexture, secondInput: MTLTexture?, timestamp: CMTime, device: MTLDevice, commandBuffer: MTLCommandBuffer) -> MTLTexture? {
func process(
input: MTLTexture,
inputMask: MTLTexture?,
secondInput: MTLTexture?,
timestamp: CMTime,
device: MTLDevice,
commandBuffer: MTLCommandBuffer
) -> MTLTexture? {
if !self.isStory {
return input
}
@ -536,7 +568,6 @@ final class VideoFinishPass: RenderPass {
)
if self.gradientColors.topColor.w > 0.0 {
renderCommandEncoder.setRenderPipelineState(self.gradientPipelineState!)
self.encodeGradient(
using: renderCommandEncoder,
containerSize: containerSize,
@ -554,6 +585,7 @@ final class VideoFinishPass: RenderPass {
containerSize: containerSize,
texture: transitionVideoState.texture,
textureRotation: transitionVideoState.textureRotation,
maskTexture: nil,
position: transitionVideoState.position,
roundness: transitionVideoState.roundness,
alpha: transitionVideoState.alpha,
@ -567,6 +599,7 @@ final class VideoFinishPass: RenderPass {
containerSize: containerSize,
texture: mainVideoState.texture,
textureRotation: mainVideoState.textureRotation,
maskTexture: inputMask,
position: mainVideoState.position,
roundness: mainVideoState.roundness,
alpha: mainVideoState.alpha,
@ -580,6 +613,7 @@ final class VideoFinishPass: RenderPass {
containerSize: containerSize,
texture: additionalVideoState.texture,
textureRotation: additionalVideoState.textureRotation,
maskTexture: nil,
position: additionalVideoState.position,
roundness: additionalVideoState.roundness,
alpha: additionalVideoState.alpha,
@ -603,6 +637,7 @@ final class VideoFinishPass: RenderPass {
containerSize: CGSize,
device: MTLDevice
) {
encoder.setRenderPipelineState(self.gradientPipelineState!)
let vertices = verticesDataForRotation(.rotate0Degrees)
let buffer = device.makeBuffer(

View File

@ -51,6 +51,7 @@ swift_library(
"//submodules/TelegramUI/Components/ContextReferenceButtonComponent",
"//submodules/TelegramUI/Components/MediaScrubberComponent",
"//submodules/Components/BlurredBackgroundComponent",
"//submodules/TelegramUI/Components/DustEffect",
],
visibility = [
"//visibility:public",

View File

@ -15,6 +15,7 @@ import MediaEditor
import Photos
import LottieAnimationComponent
import MessageInputPanelComponent
import DustEffect
private final class MediaCutoutScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -40,9 +41,13 @@ private final class MediaCutoutScreenComponent: Component {
public final class View: UIView {
private let buttonsContainerView = UIView()
private let buttonsBackgroundView = UIView()
private let previewContainerView = UIView()
private let cancelButton = ComponentView<Empty>()
private let label = ComponentView<Empty>()
private let doneButton = ComponentView<Empty>()
private let fadeView = UIView()
private let separatedImageView = UIImageView()
private var component: MediaCutoutScreenComponent?
private weak var state: State?
@ -51,18 +56,44 @@ private final class MediaCutoutScreenComponent: Component {
override init(frame: CGRect) {
self.buttonsContainerView.clipsToBounds = true
self.fadeView.alpha = 0.0
self.fadeView.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.6)
self.separatedImageView.contentMode = .scaleAspectFit
super.init(frame: frame)
self.backgroundColor = .clear
self.addSubview(self.buttonsContainerView)
self.buttonsContainerView.addSubview(self.buttonsBackgroundView)
self.addSubview(self.fadeView)
self.addSubview(self.separatedImageView)
self.addSubview(self.previewContainerView)
self.previewContainerView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.previewTap(_:))))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func previewTap(_ gestureRecognizer: UITapGestureRecognizer) {
guard let component = self.component else {
return
}
let location = gestureRecognizer.location(in: gestureRecognizer.view)
let point = CGPoint(
x: location.x / self.previewContainerView.frame.width,
y: location.y / self.previewContainerView.frame.height
)
component.mediaEditor.setSeparationMask(point: point)
self.playDissolveAnimation()
}
func animateInFromEditor() {
self.buttonsBackgroundView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.label.view?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
@ -74,6 +105,7 @@ private final class MediaCutoutScreenComponent: Component {
self.cancelButton.view?.isHidden = true
self.fadeView.layer.animateAlpha(from: self.fadeView.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.buttonsBackgroundView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
completion()
})
@ -82,14 +114,35 @@ private final class MediaCutoutScreenComponent: Component {
self.state?.updated()
}
public func playDissolveAnimation() {
guard let component = self.component, let resultImage = component.mediaEditor.resultImage, let environment = self.environment, let controller = environment.controller() as? MediaCutoutScreen else {
return
}
let previewView = controller.previewView
let dustEffectLayer = DustEffectLayer()
dustEffectLayer.position = previewView.center
dustEffectLayer.bounds = previewView.bounds
previewView.superview?.layer.insertSublayer(dustEffectLayer, below: previewView.layer)
dustEffectLayer.animationSpeed = 2.2
dustEffectLayer.becameEmpty = { [weak dustEffectLayer] in
dustEffectLayer?.removeFromSuperlayer()
}
dustEffectLayer.addItem(frame: previewView.bounds, image: resultImage)
controller.requestDismiss(animated: true)
}
func update(component: MediaCutoutScreenComponent, availableSize: CGSize, state: State, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
let environment = environment[ViewControllerComponentContainer.Environment.self].value
self.environment = environment
let isFirstTime = self.component == nil
self.component = component
self.state = state
// let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let isTablet: Bool
if case .regular = environment.metrics.widthClass {
isTablet = true
@ -97,8 +150,6 @@ private final class MediaCutoutScreenComponent: Component {
isTablet = false
}
// let mediaEditor = (environment.controller() as? MediaCutoutScreen)?.mediaEditor
let buttonSideInset: CGFloat
let buttonBottomInset: CGFloat = 8.0
var controlsBottomInset: CGFloat = 0.0
@ -119,7 +170,7 @@ private final class MediaCutoutScreenComponent: Component {
}
}
// var previewContainerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - previewSize.width) / 2.0), y: environment.safeInsets.top), size: CGSize(width: previewSize.width, height: availableSize.height - environment.safeInsets.top - environment.safeInsets.bottom + controlsBottomInset))
let previewContainerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - previewSize.width) / 2.0), y: environment.safeInsets.top), size: CGSize(width: previewSize.width, height: availableSize.height - environment.safeInsets.top - environment.safeInsets.bottom + controlsBottomInset))
let buttonsContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - environment.safeInsets.bottom + controlsBottomInset), size: CGSize(width: availableSize.width, height: environment.safeInsets.bottom - controlsBottomInset))
let cancelButtonSize = self.cancelButton.update(
@ -140,7 +191,7 @@ private final class MediaCutoutScreenComponent: Component {
guard let controller = environment.controller() as? MediaCutoutScreen else {
return
}
controller.requestDismiss(reset: true, animated: true)
controller.requestDismiss(animated: true)
}
)),
environment: {},
@ -177,6 +228,30 @@ private final class MediaCutoutScreenComponent: Component {
transition.setFrame(view: self.buttonsContainerView, frame: buttonsContainerFrame)
transition.setFrame(view: self.buttonsBackgroundView, frame: CGRect(origin: .zero, size: buttonsContainerFrame.size))
transition.setFrame(view: self.previewContainerView, frame: previewContainerFrame)
transition.setFrame(view: self.separatedImageView, frame: previewContainerFrame)
let frameWidth = floor(previewContainerFrame.width * 0.97)
self.fadeView.frame = CGRect(x: floorToScreenPixels((previewContainerFrame.width - frameWidth) / 2.0), y: previewContainerFrame.minY + floorToScreenPixels((previewContainerFrame.height - frameWidth) / 2.0), width: frameWidth, height: frameWidth)
self.fadeView.layer.cornerRadius = frameWidth / 8.0
if isFirstTime {
let _ = (component.mediaEditor.getSeparatedImage(point: nil)
|> deliverOnMainQueue).start(next: { [weak self] image in
guard let self else {
return
}
self.separatedImageView.image = image
self.state?.updated(transition: .easeInOut(duration: 0.2))
})
} else {
if let _ = self.separatedImageView.image {
transition.setAlpha(view: self.fadeView, alpha: 1.0)
} else {
transition.setAlpha(view: self.fadeView, alpha: 0.0)
}
}
return availableSize
}
}
@ -267,6 +342,7 @@ public final class MediaCutoutScreen: ViewController {
bottom: bottomInset,
right: layout.safeInsets.right
),
additionalInsets: layout.additionalInsets,
inputHeight: layout.inputHeight ?? 0.0,
metrics: layout.metrics,
deviceMetrics: layout.deviceMetrics,
@ -315,14 +391,16 @@ public final class MediaCutoutScreen: ViewController {
fileprivate let context: AccountContext
fileprivate let mediaEditor: MediaEditor
fileprivate let previewView: MediaEditorPreviewView
public var dismissed: () -> Void = {}
private var initialValues: MediaEditorValues
public init(context: AccountContext, mediaEditor: MediaEditor) {
public init(context: AccountContext, mediaEditor: MediaEditor, previewView: MediaEditorPreviewView) {
self.context = context
self.mediaEditor = mediaEditor
self.previewView = previewView
self.initialValues = mediaEditor.values.makeCopy()
super.init(navigationBarPresentationData: nil)
@ -343,11 +421,7 @@ public final class MediaCutoutScreen: ViewController {
super.displayNodeDidLoad()
}
func requestDismiss(reset: Bool, animated: Bool) {
if reset {
self.mediaEditor.values = self.initialValues
}
func requestDismiss(animated: Bool) {
self.dismissed()
self.node.animateOutToEditor(completion: {

View File

@ -154,6 +154,7 @@ final class MediaEditorScreenComponent: Component {
case tools
case done
case cutout
case undo
}
private var cachedImages: [ImageKey: UIImage] = [:]
func image(_ key: ImageKey) -> UIImage {
@ -172,6 +173,8 @@ final class MediaEditorScreenComponent: Component {
image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Tools"), color: .white)!
case .cutout:
image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Cutout"), color: .white)!
case .undo:
image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/CutoutUndo"), color: .white)!
case .done:
image = generateImage(CGSize(width: 33.0, height: 33.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
@ -981,13 +984,14 @@ final class MediaEditorScreenComponent: Component {
}
if controller.node.canCutout {
let isCutout = controller.node.isCutout
let cutoutButtonSize = self.cutoutButton.update(
transition: transition,
component: AnyComponent(PlainButtonComponent(
content: AnyComponent(CutoutButtonContentComponent(
backgroundColor: UIColor(rgb: 0xffffff, alpha: 0.18),
icon: state.image(.cutout),
title: "Cut Out an Object"
icon: state.image(isCutout ? .undo : .cutout),
title: isCutout ? "Undo Cut Out" : "Cut Out an Object"
)),
effectAlignment: .center,
action: {
@ -2161,6 +2165,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
private var isDismissBySwipeSuppressed = false
fileprivate var canCutout = false
fileprivate var isCutout = false
private (set) var hasAnyChanges = false
@ -2513,7 +2518,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.canCutout = canCutout
controller.requestLayout(transition: .animated(duration: 0.25, curve: .easeInOut))
}
mediaEditor.isCutoutUpdated = { [weak self] isCutout in
guard let self else {
return
}
self.isCutout = isCutout
self.requestLayout(forceUpdate: true, transition: .immediate)
}
if case .message = effectiveSubject {
} else {
@ -3998,6 +4009,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
bottom: bottomInset,
right: layout.safeInsets.right
),
additionalInsets: layout.additionalInsets,
inputHeight: layoutInputHeight,
metrics: layout.metrics,
deviceMetrics: layout.deviceMetrics,
@ -4231,14 +4243,25 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.entitiesView.selectEntity(nil)
}
let controller = MediaCutoutScreen(context: self.context, mediaEditor: mediaEditor)
controller.dismissed = { [weak self] in
if let self {
self.animateInFromTool(inPlace: true)
if controller.node.isCutout {
let snapshotView = self.previewView.snapshotView(afterScreenUpdates: false)
if let snapshotView {
self.previewView.superview?.addSubview(snapshotView)
}
self.previewView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, completion: { _ in
snapshotView?.removeFromSuperview()
})
mediaEditor.removeSeparationMask()
} else {
let controller = MediaCutoutScreen(context: self.context, mediaEditor: mediaEditor, previewView: self.previewView)
controller.dismissed = { [weak self] in
if let self {
self.animateInFromTool(inPlace: true)
}
}
self.controller?.present(controller, in: .window(.root))
self.animateOutToTool(inPlace: true)
}
self.controller?.present(controller, in: .window(.root))
self.animateOutToTool(inPlace: true)
}
}
)
@ -5084,35 +5107,46 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let title: String
let save: String
if case .draft = self.node.actualSubject {
title = presentationData.strings.Story_Editor_DraftDiscardDraft
save = presentationData.strings.Story_Editor_DraftKeepDraft
} else {
var title: String
var text: String
var save: String?
switch self.mode {
case .storyEditor:
if case .draft = self.node.actualSubject {
title = presentationData.strings.Story_Editor_DraftDiscardDraft
save = presentationData.strings.Story_Editor_DraftKeepDraft
} else {
title = presentationData.strings.Story_Editor_DraftDiscardMedia
save = presentationData.strings.Story_Editor_DraftKeepMedia
}
text = presentationData.strings.Story_Editor_DraftDiscaedText
case .stickerEditor:
title = presentationData.strings.Story_Editor_DraftDiscardMedia
save = presentationData.strings.Story_Editor_DraftKeepMedia
text = presentationData.strings.Story_Editor_DiscardText
}
var actions: [TextAlertAction] = []
actions.append(TextAlertAction(type: .destructiveAction, title: presentationData.strings.Story_Editor_DraftDiscard, action: { [weak self] in
if let self {
self.requestDismiss(saveDraft: false, animated: true)
}
}))
if let save {
actions.append(TextAlertAction(type: .genericAction, title: save, action: { [weak self] in
if let self {
self.requestDismiss(saveDraft: true, animated: true)
}
}))
}
actions.append(TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
}))
let controller = textAlertController(
context: self.context,
forceTheme: defaultDarkPresentationTheme,
title: title,
text: presentationData.strings.Story_Editor_DraftDiscaedText,
actions: [
TextAlertAction(type: .destructiveAction, title: presentationData.strings.Story_Editor_DraftDiscard, action: { [weak self] in
if let self {
self.requestDismiss(saveDraft: false, animated: true)
}
}),
TextAlertAction(type: .genericAction, title: save, action: { [weak self] in
if let self {
self.requestDismiss(saveDraft: true, animated: true)
}
}),
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
})
],
text: text,
actions: actions,
actionLayout: .vertical
)
self.present(controller, in: .window(.root))

View File

@ -1031,6 +1031,7 @@ public final class MediaToolsScreen: ViewController {
bottom: bottomInset,
right: layout.safeInsets.right
),
additionalInsets: layout.additionalInsets,
inputHeight: layout.inputHeight ?? 0.0,
metrics: layout.metrics,
deviceMetrics: layout.deviceMetrics,

View File

@ -448,6 +448,7 @@ public final class SaveProgressScreen: ViewController {
bottom: topInset,
right: layout.safeInsets.right
),
additionalInsets: layout.additionalInsets,
inputHeight: layout.inputHeight ?? 0.0,
metrics: layout.metrics,
deviceMetrics: layout.deviceMetrics,

View File

@ -13,7 +13,7 @@ final class PeerInfoScreenAddressItem: PeerInfoScreenItem {
let text: String
let imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
let action: (() -> Void)?
let longTapAction: (() -> Void)?
let longTapAction: ((ASDisplayNode, String) -> Void)?
let linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)?
init(
@ -22,7 +22,7 @@ final class PeerInfoScreenAddressItem: PeerInfoScreenItem {
text: String,
imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?,
action: (() -> Void)?,
longTapAction: (() -> Void)? = nil,
longTapAction: ((ASDisplayNode, String) -> Void)? = nil,
linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil
) {
self.id = id
@ -66,6 +66,16 @@ private final class PeerInfoScreenAddressItemNode: PeerInfoScreenItemNode {
self.addSubnode(self.bottomSeparatorNode)
self.addSubnode(self.selectionNode)
self.addSubnode(self.maskNode)
self.view.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(self.longPressGesture(_:))))
}
@objc private func longPressGesture(_ recognizer: UILongPressGestureRecognizer) {
if case .began = recognizer.state {
if let item = self.item {
item.longTapAction?(self, item.text)
}
}
}
override func update(width: CGFloat, safeInsets: UIEdgeInsets, presentationData: PresentationData, item: PeerInfoScreenItem, topItem: PeerInfoScreenItem?, bottomItem: PeerInfoScreenItem?, hasCorners: Bool, transition: ContainedViewLayoutTransition) -> CGFloat {
@ -81,7 +91,7 @@ private final class PeerInfoScreenAddressItemNode: PeerInfoScreenItemNode {
self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
let addressItem = ItemListAddressItem(theme: presentationData.theme, label: item.label, text: item.text, imageSignal: item.imageSignal, sectionId: 0, style: .blocks, displayDecorations: false, action: nil, longTapAction: item.longTapAction, linkItemAction: item.linkItemAction)
let addressItem = ItemListAddressItem(theme: presentationData.theme, label: item.label, text: item.text, imageSignal: item.imageSignal, sectionId: 0, style: .blocks, displayDecorations: false, action: nil, longTapAction: nil, linkItemAction: item.linkItemAction)
let params = ListViewItemLayoutParams(width: width, leftInset: safeInsets.left, rightInset: safeInsets.right, availableHeight: 1000.0)

View File

@ -13,7 +13,7 @@ import MultilineTextComponent
import BundleIconComponent
import PlainButtonComponent
private func dayBusinessHoursText(_ day: TelegramBusinessHours.WeekDay, offsetMinutes: Int) -> String {
private func dayBusinessHoursText(presentationData: PresentationData, day: TelegramBusinessHours.WeekDay, offsetMinutes: Int, formatAsPlainText: Bool = false) -> String {
var businessHoursText: String = ""
switch day {
case .open:
@ -30,14 +30,18 @@ private func dayBusinessHoursText(_ day: TelegramBusinessHours.WeekDay, offsetMi
let range = TelegramBusinessHours.WorkingTimeInterval(startMinute: range.startMinute + offsetMinutes, endMinute: range.endMinute + offsetMinutes)
if !resultText.isEmpty {
resultText.append("\n")
if formatAsPlainText {
resultText.append(", ")
} else {
resultText.append("\n")
}
}
let startHours = clipMinutes(range.startMinute) / 60
let startMinutes = clipMinutes(range.startMinute) % 60
let startText = stringForShortTimestamp(hours: Int32(startHours), minutes: Int32(startMinutes), dateTimeFormat: PresentationDateTimeFormat())
let startText = stringForShortTimestamp(hours: Int32(startHours), minutes: Int32(startMinutes), dateTimeFormat: presentationData.dateTimeFormat, formatAsPlainText: formatAsPlainText)
let endHours = clipMinutes(range.endMinute) / 60
let endMinutes = clipMinutes(range.endMinute) % 60
let endText = stringForShortTimestamp(hours: Int32(endHours), minutes: Int32(endMinutes), dateTimeFormat: PresentationDateTimeFormat())
let endText = stringForShortTimestamp(hours: Int32(endHours), minutes: Int32(endMinutes), dateTimeFormat: presentationData.dateTimeFormat, formatAsPlainText: formatAsPlainText)
resultText.append("\(startText) - \(endText)")
}
businessHoursText += resultText
@ -51,17 +55,20 @@ final class PeerInfoScreenBusinessHoursItem: PeerInfoScreenItem {
let label: String
let businessHours: TelegramBusinessHours
let requestLayout: (Bool) -> Void
let longTapAction: (ASDisplayNode, String) -> Void
init(
id: AnyHashable,
label: String,
businessHours: TelegramBusinessHours,
requestLayout: @escaping (Bool) -> Void
requestLayout: @escaping (Bool) -> Void,
longTapAction: @escaping (ASDisplayNode, String) -> Void
) {
self.id = id
self.label = label
self.businessHours = businessHours
self.requestLayout = requestLayout
self.longTapAction = longTapAction
}
func node() -> PeerInfoScreenItemNode {
@ -92,6 +99,7 @@ private final class PeerInfoScreenBusinessHoursItemNode: PeerInfoScreenItemNode
private let activateArea: AccessibilityAreaNode
private var item: PeerInfoScreenBusinessHoursItem?
private var presentationData: PresentationData?
private var theme: PresentationTheme?
private var currentTimezone: TimeZone
@ -168,8 +176,8 @@ private final class PeerInfoScreenBusinessHoursItemNode: PeerInfoScreenItemNode
super.didLoad()
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
recognizer.tapActionAtPoint = { point in
return .keepWithSingleTap
recognizer.tapActionAtPoint = { _ in
return .waitForSingleTap
}
recognizer.highlight = { [weak self] point in
guard let strongSelf = self else {
@ -185,9 +193,55 @@ private final class PeerInfoScreenBusinessHoursItemNode: PeerInfoScreenItemNode
case .ended:
if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation {
switch gesture {
case .tap, .longTap:
case .tap:
self.isExpanded = !self.isExpanded
self.item?.requestLayout(true)
case .longTap:
if let item = self.item, let presentationData = self.presentationData {
var text = ""
var timezoneOffsetMinutes: Int = 0
if self.displayLocalTimezone {
var currentCalendar = Calendar(identifier: .gregorian)
currentCalendar.timeZone = TimeZone(identifier: item.businessHours.timezoneId) ?? TimeZone.current
timezoneOffsetMinutes = (self.currentTimezone.secondsFromGMT() - currentCalendar.timeZone.secondsFromGMT()) / 60
}
let businessDays: [TelegramBusinessHours.WeekDay] = self.cachedDays
for i in 0 ..< businessDays.count {
let dayTitleValue: String
//TODO:localize
switch i {
case 0:
dayTitleValue = "Monday"
case 1:
dayTitleValue = "Tuesday"
case 2:
dayTitleValue = "Wednesday"
case 3:
dayTitleValue = "Thursday"
case 4:
dayTitleValue = "Friday"
case 5:
dayTitleValue = "Saturday"
case 6:
dayTitleValue = "Sunday"
default:
dayTitleValue = " "
}
let businessHoursText = dayBusinessHoursText(presentationData: presentationData, day: businessDays[i], offsetMinutes: timezoneOffsetMinutes, formatAsPlainText: true)
if !text.isEmpty {
text.append("\n")
}
text.append("\(dayTitleValue): \(businessHoursText)")
}
item.longTapAction(self, text)
}
default:
break
}
@ -212,6 +266,7 @@ private final class PeerInfoScreenBusinessHoursItemNode: PeerInfoScreenItemNode
}
self.item = item
self.presentationData = presentationData
self.theme = presentationData.theme
let sideInset: CGFloat = 16.0 + safeInsets.left
@ -272,7 +327,7 @@ private final class PeerInfoScreenBusinessHoursItemNode: PeerInfoScreenItemNode
//TODO:localize
let openStatusText = isOpen ? "Open" : "Closed"
var currentDayStatusText = currentDayIndex >= 0 && currentDayIndex < businessDays.count ? dayBusinessHoursText(businessDays[currentDayIndex], offsetMinutes: timezoneOffsetMinutes) : " "
var currentDayStatusText = currentDayIndex >= 0 && currentDayIndex < businessDays.count ? dayBusinessHoursText(presentationData: presentationData, day: businessDays[currentDayIndex], offsetMinutes: timezoneOffsetMinutes) : " "
if !isOpen {
for range in self.cachedWeekMinuteSet.rangeView {
@ -465,7 +520,7 @@ private final class PeerInfoScreenBusinessHoursItemNode: PeerInfoScreenItemNode
dayTitleValue = " "
}
let businessHoursText = dayBusinessHoursText(businessDays[i], offsetMinutes: timezoneOffsetMinutes)
let businessHoursText = dayBusinessHoursText(presentationData: presentationData, day: businessDays[i], offsetMinutes: timezoneOffsetMinutes)
let dayTitleSize = dayTitle.update(
transition: .immediate,

View File

@ -481,6 +481,8 @@ private enum PeerInfoContextSubject {
case bio
case phone(String)
case link(customLink: String?)
case businessHours(String)
case businessLocation(String)
}
private enum PeerInfoSettingsSection {
@ -1168,6 +1170,10 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
//TODO:localize
items[.peerInfo]!.append(PeerInfoScreenBusinessHoursItem(id: 300, label: "business hours", businessHours: businessHours, requestLayout: { animated in
interaction.requestLayout(animated)
}, longTapAction: { sourceNode, text in
if !text.isEmpty {
interaction.openPeerInfoContextMenu(.businessHours(text), sourceNode, nil)
}
}))
}
@ -1182,6 +1188,11 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
imageSignal: imageSignal,
action: {
interaction.openLocation()
},
longTapAction: { sourceNode, text in
if !text.isEmpty {
interaction.openPeerInfoContextMenu(.businessLocation(text), sourceNode, nil)
}
}
))
} else {
@ -1190,7 +1201,12 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
label: "location",
text: businessLocation.address,
imageSignal: nil,
action: nil
action: nil,
longTapAction: { sourceNode, text in
if !text.isEmpty {
interaction.openPeerInfoContextMenu(.businessLocation(text), sourceNode, nil)
}
}
))
}
}
@ -7879,6 +7895,27 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
return nil
}
}))
case .businessHours(let text), .businessLocation(let text):
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let actions: [ContextMenuAction] = [ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: presentationData.strings.Conversation_ContextMenuCopy), action: { [weak self] in
UIPasteboard.general.string = text
self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
})]
let contextMenuController = makeContextMenuController(actions: actions)
controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in
if let controller = self?.controller, let sourceNode = sourceNode {
var rect = sourceNode.bounds.insetBy(dx: 0.0, dy: 2.0)
if let sourceRect = sourceRect {
rect = sourceRect.insetBy(dx: 0.0, dy: 2.0)
}
return (sourceNode, rect, controller.displayNode, controller.view.bounds)
} else {
return nil
}
}))
}
}

View File

@ -154,6 +154,7 @@ final class AutomaticBusinessMessageSetupChatContents: ChatCustomContentsProtoco
}
func quickReplyUpdateShortcut(value: String) {
self.shortcut = value
if let shortcutId = self.shortcutId {
self.context.engine.accountData.editMessageShortcut(id: shortcutId, shortcut: value)
}

View File

@ -188,6 +188,53 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
return true
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
if self.isOn {
if self.hasAccessToAllChatsByDefault && self.additionalPeerList.categories.isEmpty && self.additionalPeerList.peers.isEmpty {
//TODO:localize
self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: "No recipients selected. Reset?", actions: [
TextAlertAction(type: .genericAction, title: "Cancel", action: {
}),
TextAlertAction(type: .defaultAction, title: "Reset", action: {
complete()
})
]), in: .window(.root))
return false
}
if case .away = component.mode, case .custom = self.schedule {
//TODO:localize
var errorText: String?
if let customScheduleStart = self.customScheduleStart, let customScheduleEnd = self.customScheduleEnd {
if customScheduleStart >= customScheduleEnd {
errorText = "Custom schedule end time must be larger than start time."
}
} else {
if self.customScheduleStart == nil && self.customScheduleEnd == nil {
errorText = "Custom schedule time is missing."
} else if self.customScheduleStart == nil {
errorText = "Custom schedule start time is missing."
} else {
errorText = "Custom schedule end time is missing."
}
}
if let errorText {
//TODO:localize
self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [
TextAlertAction(type: .genericAction, title: "Cancel", action: {
}),
TextAlertAction(type: .defaultAction, title: "Reset", action: {
complete()
})
]), in: .window(.root))
return false
}
}
}
var mappedCategories: TelegramBusinessRecipients.Categories = []
if self.additionalPeerList.categories.contains(.existingChats) {
mappedCategories.insert(.existingChats)
@ -555,6 +602,21 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
if let awayMessage = component.initialData.awayMessage {
self.isOn = true
initialRecipients = awayMessage.recipients
switch awayMessage.schedule {
case .always:
self.schedule = .always
case let .custom(beginTimestamp, endTimestamp):
self.schedule = .custom
self.customScheduleStart = Date(timeIntervalSince1970: Double(beginTimestamp))
self.customScheduleEnd = Date(timeIntervalSince1970: Double(endTimestamp))
case .outsideWorkingHours:
if component.initialData.businessHours != nil {
self.schedule = .outsideBusinessHours
} else {
self.schedule = .always
}
}
}
}
@ -839,7 +901,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
if case .away = component.mode {
//TODO:localize
var scheduleSectionItems: [AnyComponentWithIdentity<Empty>] = []
for i in 0 ..< 3 {
optionLoop: for i in 0 ..< 3 {
let title: String
let schedule: Schedule
switch i {
@ -847,6 +909,10 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
title = "Always Send"
schedule = .always
case 1:
if component.initialData.businessHours == nil {
continue optionLoop
}
title = "Outside of Business Hours"
schedule = .outsideBusinessHours
default:
@ -1396,19 +1462,22 @@ public final class AutomaticBusinessMessageSetupScreen: ViewControllerComponentC
fileprivate let greetingMessage: TelegramBusinessGreetingMessage?
fileprivate let awayMessage: TelegramBusinessAwayMessage?
fileprivate let additionalPeers: [EnginePeer.Id: AutomaticBusinessMessageSetupScreenComponent.AdditionalPeerList.Peer]
fileprivate let businessHours: TelegramBusinessHours?
fileprivate init(
accountPeer: EnginePeer?,
shortcutMessageList: ShortcutMessageList,
greetingMessage: TelegramBusinessGreetingMessage?,
awayMessage: TelegramBusinessAwayMessage?,
additionalPeers: [EnginePeer.Id: AutomaticBusinessMessageSetupScreenComponent.AdditionalPeerList.Peer]
additionalPeers: [EnginePeer.Id: AutomaticBusinessMessageSetupScreenComponent.AdditionalPeerList.Peer],
businessHours: TelegramBusinessHours?
) {
self.accountPeer = accountPeer
self.shortcutMessageList = shortcutMessageList
self.greetingMessage = greetingMessage
self.awayMessage = awayMessage
self.additionalPeers = additionalPeers
self.businessHours = businessHours
}
}
@ -1468,13 +1537,14 @@ public final class AutomaticBusinessMessageSetupScreen: ViewControllerComponentC
context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId),
TelegramEngine.EngineData.Item.Peer.BusinessGreetingMessage(id: context.account.peerId),
TelegramEngine.EngineData.Item.Peer.BusinessAwayMessage(id: context.account.peerId)
TelegramEngine.EngineData.Item.Peer.BusinessAwayMessage(id: context.account.peerId),
TelegramEngine.EngineData.Item.Peer.BusinessHours(id: context.account.peerId)
),
context.engine.accountData.shortcutMessageList()
|> take(1)
)
|> mapToSignal { data, shortcutMessageList -> Signal<AutomaticBusinessMessageSetupScreenInitialData, NoError> in
let (accountPeer, greetingMessage, awayMessage) = data
let (accountPeer, greetingMessage, awayMessage, businessHours) = data
var additionalPeerIds = Set<EnginePeer.Id>()
if let greetingMessage {
@ -1505,7 +1575,8 @@ public final class AutomaticBusinessMessageSetupScreen: ViewControllerComponentC
shortcutMessageList: shortcutMessageList,
greetingMessage: greetingMessage,
awayMessage: awayMessage,
additionalPeers: additionalPeers
additionalPeers: additionalPeers,
businessHours: businessHours
)
}
}

View File

@ -109,7 +109,7 @@ final class QuickReplyEmptyStateComponent: Component {
transition: .immediate,
component: AnyComponent(LottieComponent(
content: LottieComponent.AppBundleContent(name: "WriteEmoji"),
loop: true
loop: false
)),
environment: {},
containerSize: CGSize(width: 120.0, height: 120.0)
@ -144,9 +144,11 @@ final class QuickReplyEmptyStateComponent: Component {
var centralContentsY: CGFloat = topInset + floor((buttonFrame.minY - topInset - centralContentsHeight) * 0.426)
let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: centralContentsY), size: iconSize)
if let iconView = self.icon.view {
if let iconView = self.icon.view as? LottieComponent.View {
if iconView.superview == nil {
self.addSubview(iconView)
iconView.playOnce()
}
transition.setFrame(view: iconView, frame: iconFrame)
}

View File

@ -590,6 +590,7 @@ final class QuickReplySetupScreenComponent: Component {
return
}
alertController?.view.endEditing(true)
alertController?.dismissAnimated()
self.openQuickReplyChat(shortcut: value, shortcutId: nil)
}
@ -638,6 +639,7 @@ final class QuickReplySetupScreenComponent: Component {
} else {
component.context.engine.accountData.editMessageShortcut(id: id, shortcut: value)
alertController?.view.endEditing(true)
alertController?.dismissAnimated()
}
}
@ -923,7 +925,7 @@ final class QuickReplySetupScreenComponent: Component {
component: AnyComponent(QuickReplyEmptyStateComponent(
theme: environment.theme,
strings: environment.strings,
insets: UIEdgeInsets(top: environment.navigationHeight, left: environment.safeInsets.left, bottom: environment.safeInsets.bottom, right: environment.safeInsets.right),
insets: UIEdgeInsets(top: environment.navigationHeight, left: environment.safeInsets.left, bottom: environment.safeInsets.bottom + environment.additionalInsets.bottom, right: environment.safeInsets.right),
action: { [weak self] in
guard let self else {
return
@ -955,7 +957,7 @@ final class QuickReplySetupScreenComponent: Component {
statusBarHeight = max(statusBarHeight, 1.0)
}
var listBottomInset = environment.safeInsets.bottom
var listBottomInset = environment.safeInsets.bottom + environment.additionalInsets.bottom
let navigationHeight = self.updateNavigationBar(
component: component,
theme: environment.theme,
@ -1090,12 +1092,13 @@ final class QuickReplySetupScreenComponent: Component {
animateScale: false,
animateContents: false
))),
insets: UIEdgeInsets(top: 4.0, left: environment.safeInsets.left, bottom: environment.safeInsets.bottom, right: environment.safeInsets.right)
insets: UIEdgeInsets(top: 4.0, left: environment.safeInsets.left, bottom: environment.safeInsets.bottom + environment.additionalInsets.bottom, right: environment.safeInsets.right)
)),
environment: {},
containerSize: availableSize
)
let selectionPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - selectionPanelSize.height), size: selectionPanelSize)
print("selectionPanelFrame: \(selectionPanelFrame.minY)")
listBottomInset = selectionPanelSize.height
if let selectionPanelView = selectionPanel.view {
var animateIn = false

View File

@ -304,9 +304,7 @@ final class BusinessHoursSetupScreenComponent: Component {
let businessHours = try self.daysState.asBusinessHours()
let _ = component.context.engine.accountData.updateAccountBusinessHours(businessHours: businessHours).startStandalone()
return true
} catch let error {
let _ = error
} catch _ {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
//TODO:localize
self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: "Business hours are intersecting. Reset?", actions: [

View File

@ -233,21 +233,27 @@ private final class PeerNameColorIconItemNode : ASDisplayNode {
}
public final class PeerNameColorItem: ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator {
public enum Mode {
case name
case profile
case folderTag
}
public var sectionId: ItemListSectionId
public let theme: PresentationTheme
public let colors: PeerNameColors
public let isProfile: Bool
public let mode: Mode
public let displayEmptyColor: Bool
public let isLocked: Bool
public let currentColor: PeerNameColor?
public let updated: (PeerNameColor?) -> Void
public let tag: ItemListItemTag?
public init(theme: PresentationTheme, colors: PeerNameColors, isProfile: Bool, displayEmptyColor: Bool = false, currentColor: PeerNameColor?, isLocked: Bool = false, updated: @escaping (PeerNameColor?) -> Void, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId) {
public init(theme: PresentationTheme, colors: PeerNameColors, mode: Mode, displayEmptyColor: Bool = false, currentColor: PeerNameColor?, isLocked: Bool = false, updated: @escaping (PeerNameColor?) -> Void, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId) {
self.theme = theme
self.colors = colors
self.isProfile = isProfile
self.mode = mode
self.displayEmptyColor = displayEmptyColor
self.isLocked = isLocked
self.currentColor = currentColor
@ -300,7 +306,7 @@ public final class PeerNameColorItem: ListViewItem, ItemListItem, ListItemCompon
if lhs.colors != rhs.colors {
return false
}
if lhs.isProfile != rhs.isProfile {
if lhs.mode != rhs.mode {
return false
}
if lhs.currentColor != rhs.currentColor {
@ -363,13 +369,18 @@ public final class PeerNameColorItemNode: ListViewItemNode, ItemListItemNode {
let itemsPerRow: Int
let displayOrder: [Int32]
if item.isProfile {
displayOrder = item.colors.profileDisplayOrder
itemsPerRow = 8
} else {
switch item.mode {
case .name:
displayOrder = item.colors.displayOrder
itemsPerRow = 7
case .profile:
displayOrder = item.colors.profileDisplayOrder
itemsPerRow = 8
case .folderTag:
displayOrder = item.colors.chatFolderTagDisplayOrder
itemsPerRow = 8
}
var numItems = displayOrder.count
if item.displayEmptyColor {
numItems += 1
@ -466,10 +477,13 @@ public final class PeerNameColorItemNode: ListViewItemNode, ItemListItemNode {
for index in displayOrder {
let color = PeerNameColor(rawValue: index)
let colors: PeerNameColors.Colors
if item.isProfile {
colors = item.colors.getProfile(color, dark: item.theme.overallDarkAppearance, subject: .palette)
} else {
switch item.mode {
case .name:
colors = item.colors.get(color, dark: item.theme.overallDarkAppearance)
case .profile:
colors = item.colors.getProfile(color, dark: item.theme.overallDarkAppearance, subject: .palette)
case .folderTag:
colors = item.colors.getChatFolderTag(color, dark: item.theme.overallDarkAppearance)
}
items.append(PeerNameColorIconItem(index: color, colors: colors, isDark: item.theme.overallDarkAppearance, selected: color == item.currentColor, isLocked: item.isLocked, action: action))

View File

@ -1261,7 +1261,7 @@ final class ChannelAppearanceScreenComponent: Component {
itemGenerator: PeerNameColorItem(
theme: environment.theme,
colors: component.context.peerNameColors,
isProfile: true,
mode: .profile,
currentColor: profileColor,
updated: { [weak self] value in
guard let self, let value else {
@ -1578,7 +1578,7 @@ final class ChannelAppearanceScreenComponent: Component {
itemGenerator: PeerNameColorItem(
theme: environment.theme,
colors: component.context.peerNameColors,
isProfile: false,
mode: .name,
currentColor: resolvedState.nameColor,
updated: { [weak self] value in
guard let self, let value else {

View File

@ -212,7 +212,7 @@ private enum PeerNameColorScreenEntry: ItemListNodeEntry {
return PeerNameColorItem(
theme: presentationData.theme,
colors: colors,
isProfile: isProfile,
mode: isProfile ? .profile : .name,
currentColor: currentColor,
updated: { color in
if let color {

View File

@ -225,11 +225,7 @@ public final class QuickReplyNameAlertContentNode: AlertContentNode {
private let hapticFeedback = HapticFeedback()
var complete: (() -> Void)? {
didSet {
self.inputFieldNode.complete = self.complete
}
}
var complete: (() -> Void)?
override public var dismissOnOutsideTap: Bool {
return self.isUserInteractionEnabled
@ -295,6 +291,15 @@ public final class QuickReplyNameAlertContentNode: AlertContentNode {
}
self.updateTheme(theme)
self.inputFieldNode.complete = { [weak self] in
guard let self else {
return
}
if let lastNode = self.actionNodes.last, lastNode.actionEnabled {
self.complete?()
}
}
}
deinit {

View File

@ -345,16 +345,17 @@ public final class AvatarStoryIndicatorComponent: Component {
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
if let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations) {
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
}
}
}
} else {
let lineWidth: CGFloat = component.hasUnseen ? component.activeLineWidth : component.inactiveLineWidth
context.setLineWidth(lineWidth)
if component.isRoundedRect {
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: size.width * 0.5 - diameter * 0.5, y: size.height * 0.5 - diameter * 0.5), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5), cornerRadius: floor(diameter * 0.25)).cgPath)
let path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: size.width * 0.5 - diameter * 0.5, y: size.height * 0.5 - diameter * 0.5), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5), cornerRadius: floor(diameter * 0.27))
context.addPath(path.cgPath)
} else {
context.addEllipse(in: CGRect(origin: CGPoint(x: size.width * 0.5 - diameter * 0.5, y: size.height * 0.5 - diameter * 0.5), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5))
}

View File

@ -260,23 +260,25 @@ private final class VideoMessageCameraScreenComponent: CombinedComponent {
controller.updateCameraState({ $0.updatedRecording(pressing ? .holding : .handsFree).updatedDuration(initialDuration) }, transition: .spring(duration: 0.4))
controller.node.withReadyCamera(isFirstTime: !controller.node.cameraIsActive) {
self.resultDisposable.set((camera.startRecording()
|> deliverOnMainQueue).start(next: { [weak self] recordingData in
let duration = initialDuration + recordingData.duration
if let self, let controller = self.getController() {
controller.updateCameraState({ $0.updatedDuration(duration) }, transition: .easeInOut(duration: 0.1))
if isFirstRecording {
controller.node.setupLiveUpload(filePath: recordingData.filePath)
Queue.mainQueue().after(0.15) {
self.resultDisposable.set((camera.startRecording()
|> deliverOnMainQueue).start(next: { [weak self] recordingData in
let duration = initialDuration + recordingData.duration
if let self, let controller = self.getController() {
controller.updateCameraState({ $0.updatedDuration(duration) }, transition: .easeInOut(duration: 0.1))
if isFirstRecording {
controller.node.setupLiveUpload(filePath: recordingData.filePath)
}
if duration > 59.5 {
controller.onStop()
}
}
if duration > 59.5 {
controller.onStop()
}, error: { [weak self] _ in
if let self, let controller = self.getController() {
controller.completion(nil, nil, nil)
}
}
}, error: { [weak self] _ in
if let self, let controller = self.getController() {
controller.completion(nil, nil, nil)
}
}))
}))
}
}
if initialDuration > 0.0 {
@ -1094,6 +1096,7 @@ public class VideoMessageCameraScreen: ViewController {
bottom: 44.0,
right: layout.safeInsets.right
),
additionalInsets: layout.additionalInsets,
inputHeight: layout.inputHeight ?? 0.0,
metrics: layout.metrics,
deviceMetrics: layout.deviceMetrics,

View File

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

View File

@ -0,0 +1,126 @@
%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 5.000000 4.258789 cm
0.000000 0.000000 0.000000 scn
1.000000 2.571211 m
0.541604 2.571211 0.170000 2.199607 0.170000 1.741211 c
0.170000 1.282815 0.541604 0.911211 1.000000 0.911211 c
1.000000 2.571211 l
h
1.000000 13.571211 m
0.541604 13.571211 0.170000 13.199608 0.170000 12.741211 c
0.170000 12.282814 0.541604 11.911211 1.000000 11.911211 c
1.000000 13.571211 l
h
3.413101 8.154312 m
3.737236 7.830177 4.262764 7.830177 4.586899 8.154312 c
4.911034 8.478448 4.911034 9.003975 4.586899 9.328110 c
3.413101 8.154312 l
h
0.000000 12.741211 m
-0.586899 13.328110 l
-0.911034 13.003975 -0.911034 12.478447 -0.586899 12.154312 c
0.000000 12.741211 l
h
4.586899 16.154312 m
4.911034 16.478447 4.911034 17.003975 4.586899 17.328110 c
4.262764 17.652245 3.737236 17.652245 3.413101 17.328110 c
4.586899 16.154312 l
h
1.000000 0.911211 m
8.500000 0.911211 l
8.500000 2.571211 l
1.000000 2.571211 l
1.000000 0.911211 l
h
8.500000 13.571211 m
1.000000 13.571211 l
1.000000 11.911211 l
8.500000 11.911211 l
8.500000 13.571211 l
h
4.586899 9.328110 m
0.586899 13.328110 l
-0.586899 12.154312 l
3.413101 8.154312 l
4.586899 9.328110 l
h
0.586899 12.154312 m
4.586899 16.154312 l
3.413101 17.328110 l
-0.586899 13.328110 l
0.586899 12.154312 l
h
14.830000 7.241211 m
14.830000 10.737173 11.995962 13.571211 8.500000 13.571211 c
8.500000 11.911211 l
11.079169 11.911211 13.170000 9.820381 13.170000 7.241211 c
14.830000 7.241211 l
h
8.500000 0.911211 m
11.995962 0.911211 14.830000 3.745249 14.830000 7.241211 c
13.170000 7.241211 l
13.170000 4.662042 11.079169 2.571211 8.500000 2.571211 c
8.500000 0.911211 l
h
f
n
Q
endstream
endobj
3 0 obj
1673
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
0000001763 00000 n
0000001786 00000 n
0000001959 00000 n
0000002033 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2092
%%EOF

View File

@ -6144,6 +6144,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
var appliedBoosts: Int32?
var boostsToUnrestrict: Int32?
if let cachedChannelData = peerView.cachedData as? CachedChannelData {
appliedBoosts = cachedChannelData.appliedBoosts
boostsToUnrestrict = cachedChannelData.boostsToUnrestrict
}
strongSelf.updateChatPresentationInterfaceState(animated: animated, interactive: false, {
return $0.updatedPeer { _ in
return renderedPeer
@ -6152,6 +6159,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
.updatedHasSearchTags(hasSearchTags)
.updatedIsPremiumRequiredForMessaging(isPremiumRequiredForMessaging)
.updatedHasSavedChats(hasSavedChats)
.updatedAppliedBoosts(appliedBoosts)
.updatedBoostsToUnrestrict(boostsToUnrestrict)
.updatedInterfaceState { interfaceState in
var interfaceState = interfaceState
@ -9494,15 +9503,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
self.updateChatPresentationInterfaceState(animated: true, interactive: false, {
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))).withUpdatedComposeDisableUrlPreviews([]) }
})
if !self.presentationInterfaceState.isPremium {
let controller = PremiumIntroScreen(context: self.context, source: .settings)
self.push(controller)
return
}
self.updateChatPresentationInterfaceState(animated: true, interactive: false, {
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))).withUpdatedComposeDisableUrlPreviews([]) }
})
self.context.engine.accountData.sendMessageShortcut(peerId: peerId, id: shortcutId)
/*self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in
@ -9906,37 +9916,36 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self else {
return
}
guard let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
return
}
var bannedMediaInput = false
if let channel = peer as? TelegramChannel {
if channel.hasBannedPermission(.banSendVoice) != nil && channel.hasBannedPermission(.banSendInstantVideos) != nil {
bannedMediaInput = true
} else if channel.hasBannedPermission(.banSendVoice) != nil {
if channel.hasBannedPermission(.banSendInstantVideos) == nil {
strongSelf.displayMediaRecordingTooltip()
return
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
if let channel = peer as? TelegramChannel {
if channel.hasBannedPermission(.banSendVoice) != nil && channel.hasBannedPermission(.banSendInstantVideos) != nil {
bannedMediaInput = true
} else if channel.hasBannedPermission(.banSendVoice) != nil {
if channel.hasBannedPermission(.banSendInstantVideos) == nil {
strongSelf.displayMediaRecordingTooltip()
return
}
} else if channel.hasBannedPermission(.banSendInstantVideos) != nil {
if channel.hasBannedPermission(.banSendVoice) == nil {
strongSelf.displayMediaRecordingTooltip()
return
}
}
} else if channel.hasBannedPermission(.banSendInstantVideos) != nil {
if channel.hasBannedPermission(.banSendVoice) == nil {
strongSelf.displayMediaRecordingTooltip()
return
}
}
} else if let group = peer as? TelegramGroup {
if group.hasBannedPermission(.banSendVoice) && group.hasBannedPermission(.banSendInstantVideos) {
bannedMediaInput = true
} else if group.hasBannedPermission(.banSendVoice) {
if !group.hasBannedPermission(.banSendInstantVideos) {
strongSelf.displayMediaRecordingTooltip()
return
}
} else if group.hasBannedPermission(.banSendInstantVideos) {
if !group.hasBannedPermission(.banSendVoice) {
strongSelf.displayMediaRecordingTooltip()
return
} else if let group = peer as? TelegramGroup {
if group.hasBannedPermission(.banSendVoice) && group.hasBannedPermission(.banSendInstantVideos) {
bannedMediaInput = true
} else if group.hasBannedPermission(.banSendVoice) {
if !group.hasBannedPermission(.banSendInstantVideos) {
strongSelf.displayMediaRecordingTooltip()
return
}
} else if group.hasBannedPermission(.banSendInstantVideos) {
if !group.hasBannedPermission(.banSendVoice) {
strongSelf.displayMediaRecordingTooltip()
return
}
}
}
}
@ -14202,10 +14211,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
func requestVideoRecorder() {
guard let peerId = self.chatLocation.peerId else {
return
}
if self.videoRecorderValue == nil {
if let currentInputPanelFrame = self.chatDisplayNode.currentInputPanelFrame() {
if self.recorderFeedback == nil {
@ -14219,6 +14224,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
var isBot = false
var allowLiveUpload = false
var viewOnceAvailable = false
if let peerId = self.chatLocation.peerId {
allowLiveUpload = peerId.namespace != Namespaces.Peer.SecretChat
viewOnceAvailable = !isScheduledMessages && peerId.namespace == Namespaces.Peer.CloudUser && peerId != self.context.account.peerId && !isBot
} else if case .customChatContents = self.chatLocation {
allowLiveUpload = true
}
if let user = self.presentationInterfaceState.renderedPeer?.peer as? TelegramUser, user.botInfo != nil {
isBot = true
}
@ -14226,8 +14241,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let controller = VideoMessageCameraScreen(
context: self.context,
updatedPresentationData: self.updatedPresentationData,
allowLiveUpload: peerId.namespace != Namespaces.Peer.SecretChat,
viewOnceAvailable: !isScheduledMessages && peerId.namespace == Namespaces.Peer.CloudUser && peerId != self.context.account.peerId && !isBot,
allowLiveUpload: allowLiveUpload,
viewOnceAvailable: viewOnceAvailable,
inputPanelFrame: (currentInputPanelFrame, self.chatDisplayNode.inputNode != nil),
chatNode: self.chatDisplayNode.historyNode,
completion: { [weak self] message, silentPosting, scheduleTime in

View File

@ -49,6 +49,7 @@ extension ChatControllerImpl {
}
} else {
self.chatTitleView?.titleContent = .custom("\(value)", nil, false)
alertController?.view.endEditing(true)
alertController?.dismissAnimated()
if case let .customChatContents(customChatContents) = self.subject {

View File

@ -320,16 +320,12 @@ extension ChatControllerImpl {
attachmentController?.dismiss(animated: true)
self?.presentICloudFileGallery()
}, send: { [weak self] mediaReference in
guard let strongSelf = self, let peerId = strongSelf.chatLocation.peerId else {
guard let strongSelf = self else {
return
}
let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: mediaReference, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: strongSelf.transformEnqueueMessages([message]))
|> deliverOnMainQueue).startStandalone(next: { [weak self] _ in
if let strongSelf = self, strongSelf.presentationInterfaceState.subject != .scheduledMessages {
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
}
})
strongSelf.sendMessages([message], media: true)
})
if let controller = controller as? AttachmentFileControllerImpl {
let _ = currentFilesController.swap(controller)
@ -684,26 +680,28 @@ extension ChatControllerImpl {
return entry ?? GeneratedMediaStoreSettings.defaultSettings
}
|> deliverOnMainQueue).startStandalone(next: { [weak self] settings in
guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
guard let strongSelf = self else {
return
}
strongSelf.chatDisplayNode.dismissInput()
var bannedSendMedia: (Int32, Bool)?
var canSendPolls = true
if let channel = peer as? TelegramChannel {
if let value = channel.hasBannedPermission(.banSendMedia) {
bannedSendMedia = value
}
if channel.hasBannedPermission(.banSendPolls) != nil {
canSendPolls = false
}
} else if let group = peer as? TelegramGroup {
if group.hasBannedPermission(.banSendMedia) {
bannedSendMedia = (Int32.max, false)
}
if group.hasBannedPermission(.banSendPolls) {
canSendPolls = false
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
if let channel = peer as? TelegramChannel {
if let value = channel.hasBannedPermission(.banSendMedia) {
bannedSendMedia = value
}
if channel.hasBannedPermission(.banSendPolls) != nil {
canSendPolls = false
}
} else if let group = peer as? TelegramGroup {
if group.hasBannedPermission(.banSendMedia) {
bannedSendMedia = (Int32.max, false)
}
if group.hasBannedPermission(.banSendPolls) {
canSendPolls = false
}
}
}
@ -771,11 +769,15 @@ extension ChatControllerImpl {
}
var slowModeEnabled = false
if let channel = peer as? TelegramChannel, channel.isRestrictedBySlowmode {
slowModeEnabled = true
var hasSchedule = false
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
if let channel = peer as? TelegramChannel, channel.isRestrictedBySlowmode {
slowModeEnabled = true
}
hasSchedule = strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat
}
let controller = legacyAttachmentMenu(context: strongSelf.context, peer: peer, threadTitle: strongSelf.threadInfo?.title, chatLocation: strongSelf.chatLocation, editMediaOptions: menuEditMediaOptions, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, canSendPolls: canSendPolls, updatedPresentationData: strongSelf.updatedPresentationData, parentController: legacyController, recentlyUsedInlineBots: strongSelf.recentlyUsedInlineBotsValue, initialCaption: inputText, openGallery: {
let controller = legacyAttachmentMenu(context: strongSelf.context, peer: strongSelf.presentationInterfaceState.renderedPeer?.peer, threadTitle: strongSelf.threadInfo?.title, chatLocation: strongSelf.chatLocation, editMediaOptions: menuEditMediaOptions, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, hasSchedule: hasSchedule, canSendPolls: canSendPolls, updatedPresentationData: strongSelf.updatedPresentationData, parentController: legacyController, recentlyUsedInlineBots: strongSelf.recentlyUsedInlineBotsValue, initialCaption: inputText, openGallery: {
self?.presentOldMediaPicker(fileMode: false, editingMedia: editMediaOptions != nil, completion: { signals, silentPosting, scheduleTime in
if !inputText.string.isEmpty {
strongSelf.clearInputText()
@ -787,7 +789,7 @@ extension ChatControllerImpl {
}
})
}, openCamera: { [weak self] cameraView, menuController in
if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
if let strongSelf = self {
var enablePhoto = true
var enableVideo = true
@ -798,19 +800,21 @@ extension ChatControllerImpl {
var bannedSendPhotos: (Int32, Bool)?
var bannedSendVideos: (Int32, Bool)?
if let channel = peer as? TelegramChannel {
if let value = channel.hasBannedPermission(.banSendPhotos) {
bannedSendPhotos = value
}
if let value = channel.hasBannedPermission(.banSendVideos) {
bannedSendVideos = value
}
} else if let group = peer as? TelegramGroup {
if group.hasBannedPermission(.banSendPhotos) {
bannedSendPhotos = (Int32.max, false)
}
if group.hasBannedPermission(.banSendVideos) {
bannedSendVideos = (Int32.max, false)
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
if let channel = peer as? TelegramChannel {
if let value = channel.hasBannedPermission(.banSendPhotos) {
bannedSendPhotos = value
}
if let value = channel.hasBannedPermission(.banSendVideos) {
bannedSendVideos = value
}
} else if let group = peer as? TelegramGroup {
if group.hasBannedPermission(.banSendPhotos) {
bannedSendPhotos = (Int32.max, false)
}
if group.hasBannedPermission(.banSendVideos) {
bannedSendVideos = (Int32.max, false)
}
}
}
@ -821,7 +825,15 @@ extension ChatControllerImpl {
enableVideo = false
}
presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: peer.id.namespace != Namespaces.Peer.SecretChat, mediaGrouping: true, initialCaption: inputText, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, enablePhoto: enablePhoto, enableVideo: enableVideo, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in
var storeCapturedPhotos = false
var hasSchedule = false
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
storeCapturedPhotos = peer.id.namespace != Namespaces.Peer.SecretChat
hasSchedule = strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat
}
presentedLegacyCamera(context: strongSelf.context, peer: strongSelf.presentationInterfaceState.renderedPeer?.peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: storeCapturedPhotos, mediaGrouping: true, initialCaption: inputText, hasSchedule: hasSchedule, enablePhoto: enablePhoto, enableVideo: enableVideo, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in
if let strongSelf = self {
if editMediaOptions != nil {
strongSelf.editMessageMediaWithLegacySignals(signals!)
@ -979,7 +991,7 @@ extension ChatControllerImpl {
func presentICloudFileGallery(editingMessage: Bool = false) {
let _ = (self.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId),
TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId),
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false),
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true)
)
@ -1610,17 +1622,12 @@ extension ChatControllerImpl {
}
func openCamera(cameraView: TGAttachmentCameraView? = nil) {
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
return
}
let _ = peer
let _ = (self.context.sharedContext.accountManager.transaction { transaction -> GeneratedMediaStoreSettings in
let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self)
return entry ?? GeneratedMediaStoreSettings.defaultSettings
}
|> deliverOnMainQueue).startStandalone(next: { [weak self] settings in
guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
guard let strongSelf = self else {
return
}
@ -1634,19 +1641,21 @@ extension ChatControllerImpl {
var bannedSendPhotos: (Int32, Bool)?
var bannedSendVideos: (Int32, Bool)?
if let channel = peer as? TelegramChannel {
if let value = channel.hasBannedPermission(.banSendPhotos) {
bannedSendPhotos = value
}
if let value = channel.hasBannedPermission(.banSendVideos) {
bannedSendVideos = value
}
} else if let group = peer as? TelegramGroup {
if group.hasBannedPermission(.banSendPhotos) {
bannedSendPhotos = (Int32.max, false)
}
if group.hasBannedPermission(.banSendVideos) {
bannedSendVideos = (Int32.max, false)
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
if let channel = peer as? TelegramChannel {
if let value = channel.hasBannedPermission(.banSendPhotos) {
bannedSendPhotos = value
}
if let value = channel.hasBannedPermission(.banSendVideos) {
bannedSendVideos = value
}
} else if let group = peer as? TelegramGroup {
if group.hasBannedPermission(.banSendPhotos) {
bannedSendPhotos = (Int32.max, false)
}
if group.hasBannedPermission(.banSendVideos) {
bannedSendVideos = (Int32.max, false)
}
}
}
@ -1657,10 +1666,15 @@ extension ChatControllerImpl {
enableVideo = false
}
let storeCapturedMedia = peer.id.namespace != Namespaces.Peer.SecretChat
var storeCapturedMedia = false
var hasSchedule = false
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
storeCapturedMedia = peer.id.namespace != Namespaces.Peer.SecretChat
hasSchedule = strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat
}
let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText
presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: nil, parentController: strongSelf, attachmentController: self?.attachmentController, editingMedia: false, saveCapturedPhotos: storeCapturedMedia, mediaGrouping: true, initialCaption: inputText, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, enablePhoto: enablePhoto, enableVideo: enableVideo, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in
presentedLegacyCamera(context: strongSelf.context, peer: strongSelf.presentationInterfaceState.renderedPeer?.peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: nil, parentController: strongSelf, attachmentController: self?.attachmentController, editingMedia: false, saveCapturedPhotos: storeCapturedMedia, mediaGrouping: true, initialCaption: inputText, hasSchedule: hasSchedule, enablePhoto: enablePhoto, enableVideo: enableVideo, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in
if let strongSelf = self {
strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil)
if !inputText.string.isEmpty {

View File

@ -243,8 +243,11 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
return false
}
var sortedCommands = filteredCommands.map(ChatInputTextCommand.command)
for shortcut in shortcuts {
sortedCommands.append(.shortcut(shortcut))
if !shortcuts.isEmpty {
sortedCommands.removeAll()
for shortcut in shortcuts {
sortedCommands.append(.shortcut(shortcut))
}
}
return { _ in return .commands(ChatInputQueryCommandsResult(
commands: sortedCommands,

View File

@ -2000,6 +2000,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
mappedSource = .readTime
case .messageTags:
mappedSource = .messageTags
case .folderTags:
mappedSource = .folderTags
}
let controller = PremiumIntroScreen(context: context, source: mappedSource, modal: modal, forceDark: forceDark)
controller.wasDismissed = dismissed
@ -2049,6 +2051,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
mappedSubject = .lastSeen
case .messagePrivacy:
mappedSubject = .messagePrivacy
case .folderTags:
mappedSubject = .folderTags
default:
mappedSubject = .doubleLimits
}

View File

@ -720,6 +720,7 @@ public class TranslateScreen: ViewController {
statusBarHeight: 0.0,
navigationHeight: navigationHeight,
safeInsets: UIEdgeInsets(top: layout.intrinsicInsets.top + layout.safeInsets.top, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom, right: layout.safeInsets.right),
additionalInsets: layout.additionalInsets,
inputHeight: layout.inputHeight ?? 0.0,
metrics: layout.metrics,
deviceMetrics: layout.deviceMetrics,

View File

@ -159,7 +159,7 @@ final class WebAppWebView: WKWebView {
self.interactiveTransitionGestureRecognizerTest = { point -> Bool in
return point.x > 30.0
}
self.allowsBackForwardNavigationGestures = false
self.allowsBackForwardNavigationGestures = true
if #available(iOS 16.4, *) {
self.isInspectable = true
}