This commit is contained in:
Isaac 2026-03-27 00:47:05 +08:00
commit e6ce004498
34 changed files with 402 additions and 142 deletions

View file

@ -15982,6 +15982,9 @@ Error: %8$@";
"Notification.PollAddedOption" = "%1$@ added the option \"%2$@\" to the poll";
"Notification.PollAddedOptionYou" = "You added the option \"%1$@\" to the poll";
"Notification.PollDeletedOption" = "%1$@ deleted the option \"%2$@\" from the poll";
"Notification.PollDeletedOptionYou" = "You deleted the option \"%1$@\" to the poll";
"Notification.ManagedBotCreated" = "%@ bot created";
"Chat.PolOptionAddedTimestamp.Date" = "Added by %1$@ %2$@";
@ -15999,3 +16002,6 @@ Error: %8$@";
"MessagePoll.ResultsIn" = "results in %@";
"MessagePoll.ResultsInDays_1" = "results in %@ day";
"MessagePoll.ResultsInDays_any" = "results in %@ days";
"Premium.AiTools" = "AI Tools";
"Premium.AiToolsInfo" = "Transform your messages and entire chats in your preferred style and language.";

View file

@ -44,6 +44,7 @@ public enum PremiumIntroSource {
case messageEffects
case todo
case copyProtection
case aiTools
case auth(String)
case premiumGift(TelegramMediaFile)
}
@ -85,6 +86,7 @@ public enum PremiumDemoSubject {
case messageEffects
case todo
case copyProtection
case aiTools
case businessLocation
case businessHours

View file

@ -3580,8 +3580,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}
interaction.messageContextAction(EngineMessage(message), node, rect, gesture, key, fetchResourceId)
}, toggleMediaPlayback: { _ in
}, toggleMessagesSelection: { messageId, selected in
}, toggleMediaPlayback: nil, toggleMessagesSelection: { messageId, selected in
if let messageId = messageId.first {
interaction.toggleMessageSelection(messageId, selected)
}

View file

@ -155,6 +155,8 @@ class ChatImageGalleryItem: GalleryItem {
for media in self.message.media {
if let poll = media as? TelegramMediaPoll, let image = poll.attachedMedia as? TelegramMediaImage {
node.setImage(userLocation: .peer(self.message.id.peerId), imageReference: .message(message: MessageReference(self.message), media: image))
} else if let poll = media as? TelegramMediaPoll, let file = poll.attachedMedia as? TelegramMediaFile, file.mimeType.hasPrefix("image/") {
node.setFile(context: self.context, userLocation: .peer(self.message.id.peerId), fileReference: .message(message: MessageReference(self.message), media: file))
} else if let paidContent = media as? TelegramMediaPaidContent {
let mediaIndex = self.mediaIndex ?? 0
if case let .full(fullMedia) = paidContent.extendedMedia[Int(mediaIndex)], let image = fullMedia as? TelegramMediaImage {

View file

@ -976,9 +976,11 @@
if (adjustments.paintingData.hasAnimation) {
grouping = false;
}
// if ([editingContext livePhotoModeForItem:asset] != TGMediaLivePhotoModeOff) {
// grouping = false;
// }
TGMediaLivePhotoMode livePhotoMode = [editingContext livePhotoModeForItem:asset];
if (livePhotoMode == TGMediaLivePhotoModeLoop || livePhotoMode == TGMediaLivePhotoModeBounce) {
grouping = false;
}
}
}
@ -1544,9 +1546,10 @@
if (adjustments.paintingData.hasAnimation) {
grouping = false;
}
// if ([editingContext livePhotoModeForItem:asset] != TGMediaLivePhotoModeOff) {
// grouping = false;
// }
TGMediaLivePhotoMode livePhotoMode = [editingContext livePhotoModeForItem:asset];
if (livePhotoMode == TGMediaLivePhotoModeLoop || livePhotoMode == TGMediaLivePhotoModeBounce) {
grouping = false;
}
}
}
@ -1771,21 +1774,6 @@
- (UIBarButtonItem *)rightBarButtonItem
{
return nil;
// if (_intent == TGMediaAssetsControllerSendFileIntent)
// return nil;
// if (self.requestSearchController == nil) {
// return nil;
// }
//
// if (iosMajorVersion() < 7)
// {
// TGModernBarButton *searchButton = [[TGModernBarButton alloc] initWithImage:TGComponentsImageNamed(@"NavigationSearchIcon.png")];
// searchButton.portraitAdjustment = CGPointMake(-7, -5);
// [searchButton addTarget:self action:@selector(searchButtonPressed) forControlEvents:UIControlEventTouchUpInside];
// return [[UIBarButtonItem alloc] initWithCustomView:searchButton];
// }
//
// return [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@selector(searchButtonPressed)];
}
- (void)cancelButtonPressed

View file

@ -754,6 +754,14 @@
hasLivePhotoButton = true;
}
}
} else if ([item isKindOfClass:[TGMediaPickerGalleryPhotoItem class]]) {
TGMediaPickerGalleryPhotoItem *photoGalleryItem = (TGMediaPickerGalleryPhotoItem *)item;
if ([photoGalleryItem.asset isKindOfClass:[TGMediaAsset class]]) {
TGMediaAsset *asset = (TGMediaAsset *)photoGalleryItem.asset;
if (asset.subtypes & TGMediaAssetSubtypePhotoLive) {
hasLivePhotoButton = true;
}
}
}
[_muteButton setImage:muteIcon forState:UIControlStateNormal];
[_muteButton setImage:muteActiveIcon forState:UIControlStateSelected];

View file

@ -338,7 +338,13 @@
backgroundHeight += _keyboardHeight - edgeInsets.bottom;
}
CGRect livePhotoButtonFrame = CGRectMake(edgeInsets.left + 16.0, edgeInsets.top + 80.0 + 32.0 - _keyboardHeight, 160.0, 18.0);
CGFloat livePhotoY = 0.0;
if (frame.size.width > frame.size.height && !TGIsPad()) {
livePhotoY = 48.0;
} else {
livePhotoY = edgeInsets.top + 145.0 - _keyboardHeight;
}
CGRect livePhotoButtonFrame = CGRectMake(_safeAreaInset.left + edgeInsets.left + 16.0, livePhotoY, 160.0, 18.0);
_livePhotoButtonView.frame = livePhotoButtonFrame;
CGRect panelFrame = CGRectMake(edgeInsets.left, panelY, frame.size.width, panelHeight);

View file

@ -13,7 +13,16 @@ import LegacyMediaPickerUI
import Photos
import MediaAssetsContext
private func galleryFetchResultItems(fetchResult: PHFetchResult<PHAsset>, index: Int, reversed: Bool, selectionContext: TGMediaSelectionContext?, editingContext: TGMediaEditingContext, stickersContext: TGPhotoPaintStickersContext, immediateThumbnail: UIImage?) -> ([TGModernGalleryItem], TGModernGalleryItem?) {
private func galleryFetchResultItems(
fetchResult: PHFetchResult<PHAsset>,
index: Int,
reversed: Bool,
selectionContext: TGMediaSelectionContext?,
editingContext: TGMediaEditingContext,
stickersContext: TGPhotoPaintStickersContext,
immediateThumbnail: UIImage?,
asFile: Bool
) -> ([TGModernGalleryItem], TGModernGalleryItem?) {
var focusItem: TGModernGalleryItem?
var galleryItems: [TGModernGalleryItem] = []
@ -24,6 +33,7 @@ private func galleryFetchResultItems(fetchResult: PHFetchResult<PHAsset>, index:
galleryItem.selectionContext = selectionContext
galleryItem.editingContext = editingContext
galleryItem.stickersContext = stickersContext
galleryItem.asFile = asFile
galleryItems.append(galleryItem)
if i == index {
@ -112,6 +122,7 @@ func presentLegacyMediaPickerGallery(
immediateThumbnail: UIImage?,
selectionContext: TGMediaSelectionContext?,
editingContext: TGMediaEditingContext,
asFile: Bool,
hasSilentPosting: Bool,
hasSchedule: Bool,
hasTimer: Bool,
@ -166,10 +177,10 @@ func presentLegacyMediaPickerGallery(
let (items, focusItem): ([TGModernGalleryItem], TGModernGalleryItem?)
switch source {
case let .fetchResult(fetchResult, index, reversed):
(items, focusItem) = galleryFetchResultItems(fetchResult: fetchResult, index: index, reversed: reversed, selectionContext: selectionContext, editingContext: editingContext, stickersContext: paintStickersContext, immediateThumbnail: immediateThumbnail)
case let .selection(item):
(items, focusItem) = gallerySelectionItems(item: item, selectionContext: selectionContext, editingContext: editingContext, stickersContext: paintStickersContext, immediateThumbnail: immediateThumbnail)
case let .fetchResult(fetchResult, index, reversed):
(items, focusItem) = galleryFetchResultItems(fetchResult: fetchResult, index: index, reversed: reversed, selectionContext: selectionContext, editingContext: editingContext, stickersContext: paintStickersContext, immediateThumbnail: immediateThumbnail, asFile: asFile)
case let .selection(item):
(items, focusItem) = gallerySelectionItems(item: item, selectionContext: selectionContext, editingContext: editingContext, stickersContext: paintStickersContext, immediateThumbnail: immediateThumbnail)
}
let recipientName: String?
@ -183,7 +194,23 @@ func presentLegacyMediaPickerGallery(
}
}
let model = TGMediaPickerGalleryModel(context: legacyController.context, items: items, focus: focusItem, selectionContext: selectionContext, editingContext: editingContext, hasCaptions: true, allowCaptionEntities: true, hasTimer: hasTimer, onlyCrop: false, inhibitDocumentCaptions: false, hasSelectionPanel: true, hasCamera: false, recipientName: recipientName, isScheduledMessages: isScheduledMessages, hasCoverButton: hasCoverButton)!
let model = TGMediaPickerGalleryModel(
context: legacyController.context,
items: items,
focus: focusItem,
selectionContext: selectionContext,
editingContext: editingContext,
hasCaptions: true,
allowCaptionEntities: true,
hasTimer: hasTimer,
onlyCrop: false,
inhibitDocumentCaptions: false,
hasSelectionPanel: true,
hasCamera: false,
recipientName: recipientName,
isScheduledMessages: isScheduledMessages,
hasCoverButton: hasCoverButton
)!
model.stickersContext = paintStickersContext
controller.model = model
model.controller = controller

View file

@ -603,7 +603,7 @@ final class MediaPickerGridItemNode: GridItemNode {
}
duration = stringForDuration(Int32(asset.duration))
} else {
if asset.mediaSubtypes.contains(.photoLive) {
if asset.mediaSubtypes.contains(.photoLive) && interaction.displayLivePhotoIcon {
typeIcon = UIImage(bundleImageName: "Chat/Message/LivePhoto")
}
}

View file

@ -47,8 +47,20 @@ final class MediaPickerInteraction {
let selectionState: TGMediaSelectionContext?
let editingState: TGMediaEditingContext
var hiddenMediaId: String?
var displayLivePhotoIcon: Bool = false
init(downloadManager: AssetDownloadManager, openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, openDraft: @escaping (MediaEditorDraft, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Bool, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool, ChatSendMessageActionSheetController.SendParameters?, @escaping () -> Void) -> Void, schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
init(
downloadManager: AssetDownloadManager,
openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void,
openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void,
openDraft: @escaping (MediaEditorDraft, UIImage?) -> Void,
toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Bool,
sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool, ChatSendMessageActionSheetController.SendParameters?, @escaping () -> Void) -> Void,
schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void,
dismissInput: @escaping () -> Void,
selectionState: TGMediaSelectionContext?,
editingState: TGMediaEditingContext
) {
self.downloadManager = downloadManager
self.openMedia = openMedia
self.openSelectedMedia = openSelectedMedia
@ -185,6 +197,18 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
case assets(PHAssetCollection?, AssetsMode)
case media([Media])
var asFile: Bool {
switch self {
case let .assets(_, mode):
if case let .poll(_, asFile) = mode {
return asFile
}
return false
default:
return false
}
}
}
private let context: AccountContext
@ -607,11 +631,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.gridNode.scrollView.alwaysBounceVertical = true
self.gridNode.scrollView.showsVerticalScrollIndicator = false
if case let .assets(_, mode) = controller.subject, [.wallpaper, .story, .addImage, .cover, .createSticker, .createAvatar].contains(mode) {
} else {
self.setupSelectionGesture()
}
self.setupSelectionGesture()
if let controller = self.controller, case let .assets(collection, _) = controller.subject, collection != nil {
self.gridNode.view.interactiveTransitionGestureRecognizerTest = { point -> Bool in
@ -808,10 +828,20 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
}
}
func setupSelectionGesture() {
guard self.selectionGesture == nil else {
func setupSelectionGesture(force: Bool = false) {
guard self.selectionGesture == nil, let controller = self.controller else {
return
}
if !force, case let .assets(_, mode) = controller.subject {
switch mode {
case .default:
break
default:
return
}
}
let selectionGesture = MediaPickerGridSelectionGesture<TGMediaSelectableItem>()
selectionGesture.delegate = self.wrappedGestureRecognizerDelegate
selectionGesture.began = { [weak self] in
@ -1296,7 +1326,6 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.openingMedia = true
let asset = fetchResult[index]
let _ = (checkIfAssetIsLocal(asset)
|> deliverOnMainQueue).start(next: { [weak self] isLocallyAvailable in
guard let self else {
@ -1333,7 +1362,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.openingMedia = true
self.currentGalleryController = presentLegacyMediaPickerGallery(context: controller.context, peer: controller.peer, threadTitle: controller.threadTitle, chatLocation: controller.chatLocation, isScheduledMessages: controller.isScheduledMessages, presentationData: self.presentationData, source: .fetchResult(fetchResult: fetchResult, index: index, reversed: reversed), immediateThumbnail: immediateThumbnail, selectionContext: interaction.selectionState?.selectionLimit == 1 ? nil : interaction.selectionState, editingContext: interaction.editingState, hasSilentPosting: true, hasSchedule: hasSchedule, hasTimer: hasTimer, updateHiddenMedia: { [weak self] id in
self.currentGalleryController = presentLegacyMediaPickerGallery(context: controller.context, peer: controller.peer, threadTitle: controller.threadTitle, chatLocation: controller.chatLocation, isScheduledMessages: controller.isScheduledMessages, presentationData: self.presentationData, source: .fetchResult(fetchResult: fetchResult, index: index, reversed: reversed), immediateThumbnail: immediateThumbnail, selectionContext: interaction.selectionState?.selectionLimit == 1 ? nil : interaction.selectionState, editingContext: interaction.editingState, asFile: controller.subject.asFile, hasSilentPosting: true, hasSchedule: hasSchedule, hasTimer: hasTimer, updateHiddenMedia: { [weak self] id in
self?.hiddenMediaId.set(.single(id))
}, initialLayout: layout, transitionHostView: { [weak self] in
return self?.gridNode.view
@ -1346,7 +1375,11 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
}, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
self?.currentGalleryParentController = c
c.navigationPresentation = .flatModal
self?.controller?.parentController()?.push(c)
if let parentController = self?.controller?.parentController() {
parentController.push(c)
} else {
self?.controller?.push(c)
}
//self?.controller?.present(c, in: .window(.root), with: a)
}, finishedTransitionIn: { [weak self] in
self?.openingMedia = false
@ -1376,7 +1409,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
}
self.openingMedia = true
self.currentGalleryController = presentLegacyMediaPickerGallery(context: controller.context, peer: controller.peer, threadTitle: controller.threadTitle, chatLocation: controller.chatLocation, isScheduledMessages: controller.isScheduledMessages, presentationData: self.presentationData, source: .selection(item: item), immediateThumbnail: immediateThumbnail, selectionContext: interaction.selectionState, editingContext: interaction.editingState, hasSilentPosting: true, hasSchedule: true, hasTimer: hasTimer, updateHiddenMedia: { [weak self] id in
self.currentGalleryController = presentLegacyMediaPickerGallery(context: controller.context, peer: controller.peer, threadTitle: controller.threadTitle, chatLocation: controller.chatLocation, isScheduledMessages: controller.isScheduledMessages, presentationData: self.presentationData, source: .selection(item: item), immediateThumbnail: immediateThumbnail, selectionContext: interaction.selectionState, editingContext: interaction.editingState, asFile: controller.subject.asFile, hasSilentPosting: true, hasSchedule: true, hasTimer: hasTimer, updateHiddenMedia: { [weak self] id in
self?.hiddenMediaId.set(.single(id))
}, initialLayout: layout, transitionHostView: { [weak self] in
return self?.selectionNode?.view
@ -2021,6 +2054,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.secondaryButtonAction = secondaryButtonAction
let selectionContext = selectionContext ?? TGMediaSelectionContext(groupingAllowed: false, selectionLimit: enableMultiselection ? 100 : 1)!
let editingContext = editingContext ?? (subject.asFile ? TGMediaEditingContext.forCaptionsOnly() : TGMediaEditingContext())!
self.titleView = MediaPickerTitleView(theme: self.presentationData.theme, glass: style == .glass, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0)
@ -2260,7 +2294,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
if let currentItem = currentItem {
selectionState.setItem(currentItem, selected: true)
}
strongSelf.controllerNode.send(fromGallery: currentItem != nil, silently: silently, scheduleTime: scheduleTime, animated: animated, parameters: parameters, completion: completion)
strongSelf.controllerNode.send(fromGallery: currentItem != nil, asFile: strongSelf.subject.asFile, silently: silently, scheduleTime: scheduleTime, animated: animated, parameters: parameters, completion: completion)
}
}, schedule: { [weak self] parameters in
if let strongSelf = self {
@ -2272,7 +2306,16 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
if let strongSelf = self {
strongSelf.controllerNode.dismissInput()
}
}, selectionState: selectionContext, editingState: editingContext ?? TGMediaEditingContext())
}, selectionState: selectionContext, editingState: editingContext)
var displayLivePhotoIcon = false
if case let .assets(_, mode) = subject, case .default = mode {
if let peer = self.peer, case .secretChat = peer {
} else {
displayLivePhotoIcon = true
}
}
self.interaction?.displayLivePhotoIcon = displayLivePhotoIcon
let highQualityPhoto = UserDefaults.standard.bool(forKey: "TG_photoHighQuality_v0")
if highQualityPhoto {
@ -2575,13 +2618,8 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
let selectedSize = self.selectedButtonNode.update(count: count)
var safeInset: CGFloat = 0.0
if layout.safeInsets.right > 0.0 {
safeInset += layout.safeInsets.right + 16.0
}
let selectedButtonInset: CGFloat = self._hasGlassStyle ? 68.0 : 54.0
let navigationHeight = navigationLayout(layout: layout).navigationFrame.height
self.selectedButtonNode.frame = CGRect(origin: CGPoint(x: self.view.bounds.width - selectedButtonInset - selectedSize.width - safeInset, y: self._hasGlassStyle ? 16.0 : floorToScreenPixels((navigationHeight - selectedSize.height) / 2.0) + 1.0), size: selectedSize)
self.selectedButtonNode.frame = CGRect(origin: CGPoint(x: 16.0 + 44.0 + 12.0, y: self._hasGlassStyle ? 16.0 : floorToScreenPixels((navigationHeight - selectedSize.height) / 2.0) + 1.0), size: selectedSize)
let isSelectionButtonVisible = count > 0 && self.controllerNode.currentDisplayMode == .all
transition.updateAlpha(node: self.selectedButtonNode, alpha: isSelectionButtonVisible ? 1.0 : 0.0)
@ -2973,7 +3011,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
}
self.explicitMultipleSelection = true
self.controllerNode.setupSelectionGesture()
self.controllerNode.setupSelectionGesture(force: true)
self.requestAttachmentMenuExpansion()
if let state = self.controllerNode.state {
@ -3202,14 +3240,8 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.validLayout = layout
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
var safeInset: CGFloat = 0.0
if layout.safeInsets.right > 0.0 {
safeInset += layout.safeInsets.right + 16.0
}
let navigationHeight = navigationLayout(layout: layout).navigationFrame.height
let selectedButtonInset: CGFloat = self._hasGlassStyle ? 68.0 : 54.0
self.selectedButtonNode.frame = CGRect(origin: CGPoint(x: self.view.bounds.width - selectedButtonInset - self.selectedButtonNode.frame.width - safeInset, y: self._hasGlassStyle ? 16.0 : floorToScreenPixels((navigationHeight - self.selectedButtonNode.frame.height) / 2.0) + 1.0), size: self.selectedButtonNode.frame.size)
self.selectedButtonNode.frame = CGRect(origin: CGPoint(x: 16.0 + 44.0 + 12.0, y: self._hasGlassStyle ? 16.0 : floorToScreenPixels((navigationHeight - self.selectedButtonNode.frame.height) / 2.0) + 1.0), size: self.selectedButtonNode.frame.size)
}
public func dismissAnimated() {

View file

@ -1129,6 +1129,26 @@ private final class DemoSheetContent: CombinedComponent {
)
)
availableItems[.aiTools] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.aiTools,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .top,
model: .island,
videoFile: configuration.videos["ai_compose"],
decoration: .badgeStars
)),
title: strings.Premium_AiTools,
text: strings.Premium_AiToolsInfo,
textColor: textColor
)
)
)
)
let index: Int = 0
var items: [DemoPagerComponent.Item] = []
if let item = availableItems.first(where: { $0.value.content.id == component.subject as AnyHashable }) {
@ -1221,6 +1241,8 @@ private final class DemoSheetContent: CombinedComponent {
text = strings.Premium_TodoInfo
case .copyProtection:
text = strings.Premium_CopyProtectionInfo
case .aiTools:
text = "Transform your messages and entire chats in your preferred style and language."
default:
text = ""
}
@ -1309,6 +1331,8 @@ private final class DemoSheetContent: CombinedComponent {
buttonText = strings.Premium_PaidMessages_Proceed
case .copyProtection:
buttonText = strings.Premium_PaidMessages_Proceed
case .aiTools:
buttonText = strings.Premium_PaidMessages_Proceed
default:
buttonText = strings.Common_OK
}
@ -1498,6 +1522,7 @@ public class PremiumDemoScreen: ViewControllerComponentContainer {
case messageEffects
case todo
case copyProtection
case aiTools
case businessLocation
case businessHours
@ -1560,6 +1585,8 @@ public class PremiumDemoScreen: ViewControllerComponentContainer {
return .todo
case .copyProtection:
return .copyProtection
case .aiTools:
return .aiTools
case .businessLocation:
return .businessLocation
case .businessHours:

View file

@ -443,6 +443,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
UIColor(rgb: 0x676bff),
UIColor(rgb: 0x6172ff),
UIColor(rgb: 0x5b79ff),
UIColor(rgb: 0x5b79ff),
UIColor(rgb: 0x4492ff),
UIColor(rgb: 0x429bd5),
UIColor(rgb: 0x41a6a5),

View file

@ -323,6 +323,12 @@ public enum PremiumSource: Equatable {
} else {
return false
}
case .aiTools:
if case .aiTools = rhs {
return true
} else {
return false
}
case let .auth(lhsPrice):
if case let .auth(rhsPrice) = rhs, lhsPrice == rhsPrice {
return true
@ -384,6 +390,7 @@ public enum PremiumSource: Equatable {
case messageEffects
case todo
case copyProtection
case aiTools
case auth(String)
case premiumGift(TelegramMediaFile)
@ -483,6 +490,8 @@ public enum PremiumSource: Equatable {
return "todo"
case .copyProtection:
return "pm_noforwards"
case .aiTools:
return "ai_compose"
case .auth:
return "auth"
case .premiumGift:
@ -517,6 +526,7 @@ public enum PremiumPerk: CaseIterable {
case messageEffects
case todo
case copyProtection
case aiTools
case businessLocation
case businessHours
@ -553,7 +563,8 @@ public enum PremiumPerk: CaseIterable {
.business,
.messageEffects,
.todo,
.copyProtection
.copyProtection,
.aiTools
]
}
@ -631,6 +642,8 @@ public enum PremiumPerk: CaseIterable {
return "todo"
case .copyProtection:
return "pm_noforwards"
case .aiTools:
return "ai_compose"
case .business:
return "business"
case .businessLocation:
@ -704,6 +717,8 @@ public enum PremiumPerk: CaseIterable {
return strings.Premium_Todo
case .copyProtection:
return strings.Premium_CopyProtection
case .aiTools:
return strings.Premium_AiTools
case .businessLocation:
return strings.Business_Location
case .businessHours:
@ -775,6 +790,8 @@ public enum PremiumPerk: CaseIterable {
return strings.Premium_TodoInfo
case .copyProtection:
return strings.Premium_CopyProtectionInfo
case .aiTools:
return strings.Premium_AiToolsInfo
case .businessLocation:
return strings.Business_LocationInfo
case .businessHours:
@ -846,6 +863,8 @@ public enum PremiumPerk: CaseIterable {
return "Item List/Icons/Checkbox"
case .copyProtection:
return "Item List/Icons/NoForward"
case .aiTools:
return "Item List/Icons/AITools"
case .businessLocation:
return "Item List/Icons/Location"
case .businessHours:
@ -870,6 +889,7 @@ struct PremiumIntroConfiguration {
static var defaultValue: PremiumIntroConfiguration {
return PremiumIntroConfiguration(perks: [
.stories,
.aiTools,
.moreUpload,
.doubleLimits,
.lastSeen,
@ -932,6 +952,12 @@ struct PremiumIntroConfiguration {
if perks.count < 4 {
perks = PremiumIntroConfiguration.defaultValue.perks
}
#if DEBUG
if !perks.contains(.aiTools) {
perks.insert(.aiTools, at: 1)
}
#endif
var businessPerks: [PremiumPerk] = []
if let values = data["business_promo_order"] as? [String] {
@ -1635,7 +1661,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
super.init()
self.newPerks = [PremiumPerk.copyProtection.identifier]
self.newPerks = [PremiumPerk.aiTools.identifier, PremiumPerk.copyProtection.identifier]
let premiumIntroConfiguration: Signal<PremiumIntroConfiguration, NoError>
let accountPeer: Signal<EnginePeer?, NoError>
@ -1970,6 +1996,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
UIColor(rgb: 0x676bff),
UIColor(rgb: 0x6172ff),
UIColor(rgb: 0x5b79ff),
UIColor(rgb: 0x5b79ff),
UIColor(rgb: 0x4492ff),
UIColor(rgb: 0x429bd5),
UIColor(rgb: 0x41a6a5),
@ -2220,6 +2247,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
demoSubject = .todo
case .copyProtection:
demoSubject = .copyProtection
case .aiTools:
demoSubject = .aiTools
case .business:
demoSubject = .business
default:

View file

@ -884,6 +884,26 @@ public class PremiumLimitsListScreen: ViewController {
)
)
availableItems[.aiTools] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.aiTools,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: context,
position: .top,
model: .island,
videoFile: videos["ai_compose"],
decoration: .badgeStars
)),
title: strings.Premium_AiTools,
text: strings.Premium_AiToolsInfo,
textColor: textColor
)
)
)
)
availableItems[.business] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.business,

View file

@ -296,7 +296,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-207944868] = { return Api.FileHash.parse_fileHash($0) }
dict[-11252123] = { return Api.Folder.parse_folder($0) }
dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) }
dict[-838922550] = { return Api.ForumTopic.parse_forumTopic($0) }
dict[-52766699] = { return Api.ForumTopic.parse_forumTopic($0) }
dict[37687451] = { return Api.ForumTopic.parse_forumTopicDeleted($0) }
dict[-394605632] = { return Api.FoundStory.parse_foundStory($0) }
dict[-1107729093] = { return Api.Game.parse_game($0) }
@ -623,6 +623,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-2132731265] = { return Api.MessageAction.parse_messageActionPhoneCall($0) }
dict[-1799538451] = { return Api.MessageAction.parse_messageActionPinMessage($0) }
dict[-1650340500] = { return Api.MessageAction.parse_messageActionPollAppendAnswer($0) }
dict[966161628] = { return Api.MessageAction.parse_messageActionPollDeleteAnswer($0) }
dict[-1341372510] = { return Api.MessageAction.parse_messageActionPrizeStars($0) }
dict[827428507] = { return Api.MessageAction.parse_messageActionRequestedPeer($0) }
dict[-1816979384] = { return Api.MessageAction.parse_messageActionRequestedPeerSentMe($0) }
@ -1174,7 +1175,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-710666460] = { return Api.Update.parse_updateMessageExtendedMedia($0) }
dict[1318109142] = { return Api.Update.parse_updateMessageID($0) }
dict[-699641301] = { return Api.Update.parse_updateMessagePoll($0) }
dict[619974263] = { return Api.Update.parse_updateMessagePollVote($0) }
dict[1989799956] = { return Api.Update.parse_updateMessagePollVote($0) }
dict[506035194] = { return Api.Update.parse_updateMessageReactions($0) }
dict[-1618924792] = { return Api.Update.parse_updateMonoForumNoPaidException($0) }
dict[-2030252155] = { return Api.Update.parse_updateMoveStickerSetToTop($0) }
@ -1258,7 +1259,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-484987010] = { return Api.Updates.parse_updatesTooLong($0) }
dict[1648005024] = { return Api.UrlAuthResult.parse_urlAuthResultAccepted($0) }
dict[-1445536993] = { return Api.UrlAuthResult.parse_urlAuthResultDefault($0) }
dict[-117904610] = { return Api.UrlAuthResult.parse_urlAuthResultRequest($0) }
dict[1020666860] = { return Api.UrlAuthResult.parse_urlAuthResultRequest($0) }
dict[829899656] = { return Api.User.parse_user($0) }
dict[-742634630] = { return Api.User.parse_userEmpty($0) }
dict[114026053] = { return Api.UserFull.parse_userFull($0) }

View file

@ -2080,6 +2080,15 @@ public extension Api {
return ("messageActionPollAppendAnswer", [("answer", self.answer as Any)])
}
}
public class Cons_messageActionPollDeleteAnswer: TypeConstructorDescription {
public var answer: Api.PollAnswer
public init(answer: Api.PollAnswer) {
self.answer = answer
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("messageActionPollDeleteAnswer", [("answer", self.answer as Any)])
}
}
public class Cons_messageActionPrizeStars: TypeConstructorDescription {
public var flags: Int32
public var stars: Int64
@ -2429,6 +2438,7 @@ public extension Api {
case messageActionPhoneCall(Cons_messageActionPhoneCall)
case messageActionPinMessage
case messageActionPollAppendAnswer(Cons_messageActionPollAppendAnswer)
case messageActionPollDeleteAnswer(Cons_messageActionPollDeleteAnswer)
case messageActionPrizeStars(Cons_messageActionPrizeStars)
case messageActionRequestedPeer(Cons_messageActionRequestedPeer)
case messageActionRequestedPeerSentMe(Cons_messageActionRequestedPeerSentMe)
@ -2838,6 +2848,12 @@ public extension Api {
}
_data.answer.serialize(buffer, true)
break
case .messageActionPollDeleteAnswer(let _data):
if boxed {
buffer.appendInt32(966161628)
}
_data.answer.serialize(buffer, true)
break
case .messageActionPrizeStars(let _data):
if boxed {
buffer.appendInt32(-1341372510)
@ -3208,6 +3224,8 @@ public extension Api {
return ("messageActionPinMessage", [])
case .messageActionPollAppendAnswer(let _data):
return ("messageActionPollAppendAnswer", [("answer", _data.answer as Any)])
case .messageActionPollDeleteAnswer(let _data):
return ("messageActionPollDeleteAnswer", [("answer", _data.answer as Any)])
case .messageActionPrizeStars(let _data):
return ("messageActionPrizeStars", [("flags", _data.flags as Any), ("stars", _data.stars as Any), ("transactionId", _data.transactionId as Any), ("boostPeer", _data.boostPeer as Any), ("giveawayMsgId", _data.giveawayMsgId as Any)])
case .messageActionRequestedPeer(let _data):
@ -3973,6 +3991,19 @@ public extension Api {
return nil
}
}
public static func parse_messageActionPollDeleteAnswer(_ reader: BufferReader) -> MessageAction? {
var _1: Api.PollAnswer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.PollAnswer
}
let _c1 = _1 != nil
if _c1 {
return Api.MessageAction.messageActionPollDeleteAnswer(Cons_messageActionPollDeleteAnswer(answer: _1!))
}
else {
return nil
}
}
public static func parse_messageActionPrizeStars(_ reader: BufferReader) -> MessageAction? {
var _1: Int32?
_1 = reader.readInt32()

View file

@ -1690,15 +1690,17 @@ public extension Api {
public var pollId: Int64
public var peer: Api.Peer
public var options: [Buffer]
public var positions: [Int32]
public var qts: Int32
public init(pollId: Int64, peer: Api.Peer, options: [Buffer], qts: Int32) {
public init(pollId: Int64, peer: Api.Peer, options: [Buffer], positions: [Int32], qts: Int32) {
self.pollId = pollId
self.peer = peer
self.options = options
self.positions = positions
self.qts = qts
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("updateMessagePollVote", [("pollId", self.pollId as Any), ("peer", self.peer as Any), ("options", self.options as Any), ("qts", self.qts as Any)])
return ("updateMessagePollVote", [("pollId", self.pollId as Any), ("peer", self.peer as Any), ("options", self.options as Any), ("positions", self.positions as Any), ("qts", self.qts as Any)])
}
}
public class Cons_updateMessageReactions: TypeConstructorDescription {
@ -3440,7 +3442,7 @@ public extension Api {
break
case .updateMessagePollVote(let _data):
if boxed {
buffer.appendInt32(619974263)
buffer.appendInt32(1989799956)
}
serializeInt64(_data.pollId, buffer: buffer, boxed: false)
_data.peer.serialize(buffer, true)
@ -3449,6 +3451,11 @@ public extension Api {
for item in _data.options {
serializeBytes(item, buffer: buffer, boxed: false)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.positions.count))
for item in _data.positions {
serializeInt32(item, buffer: buffer, boxed: false)
}
serializeInt32(_data.qts, buffer: buffer, boxed: false)
break
case .updateMessageReactions(let _data):
@ -4245,7 +4252,7 @@ public extension Api {
case .updateMessagePoll(let _data):
return ("updateMessagePoll", [("flags", _data.flags as Any), ("peer", _data.peer as Any), ("msgId", _data.msgId as Any), ("topMsgId", _data.topMsgId as Any), ("pollId", _data.pollId as Any), ("poll", _data.poll as Any), ("results", _data.results as Any)])
case .updateMessagePollVote(let _data):
return ("updateMessagePollVote", [("pollId", _data.pollId as Any), ("peer", _data.peer as Any), ("options", _data.options as Any), ("qts", _data.qts as Any)])
return ("updateMessagePollVote", [("pollId", _data.pollId as Any), ("peer", _data.peer as Any), ("options", _data.options as Any), ("positions", _data.positions as Any), ("qts", _data.qts as Any)])
case .updateMessageReactions(let _data):
return ("updateMessageReactions", [("flags", _data.flags as Any), ("peer", _data.peer as Any), ("msgId", _data.msgId as Any), ("topMsgId", _data.topMsgId as Any), ("savedPeerId", _data.savedPeerId as Any), ("reactions", _data.reactions as Any)])
case .updateMonoForumNoPaidException(let _data):
@ -6022,14 +6029,19 @@ public extension Api {
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self)
}
var _4: Int32?
_4 = reader.readInt32()
var _4: [Int32]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
}
var _5: Int32?
_5 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.Update.updateMessagePollVote(Cons_updateMessagePollVote(pollId: _1!, peer: _2!, options: _3!, qts: _4!))
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.Update.updateMessagePollVote(Cons_updateMessagePollVote(pollId: _1!, peer: _2!, options: _3!, positions: _4!, qts: _5!))
}
else {
return nil

View file

@ -579,7 +579,8 @@ public extension Api {
public var region: String?
public var matchCodes: [String]?
public var userIdHint: Int64?
public init(flags: Int32, bot: Api.User, domain: String, browser: String?, platform: String?, ip: String?, region: String?, matchCodes: [String]?, userIdHint: Int64?) {
public var verifiedAppName: String?
public init(flags: Int32, bot: Api.User, domain: String, browser: String?, platform: String?, ip: String?, region: String?, matchCodes: [String]?, userIdHint: Int64?, verifiedAppName: String?) {
self.flags = flags
self.bot = bot
self.domain = domain
@ -589,9 +590,10 @@ public extension Api {
self.region = region
self.matchCodes = matchCodes
self.userIdHint = userIdHint
self.verifiedAppName = verifiedAppName
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("urlAuthResultRequest", [("flags", self.flags as Any), ("bot", self.bot as Any), ("domain", self.domain as Any), ("browser", self.browser as Any), ("platform", self.platform as Any), ("ip", self.ip as Any), ("region", self.region as Any), ("matchCodes", self.matchCodes as Any), ("userIdHint", self.userIdHint as Any)])
return ("urlAuthResultRequest", [("flags", self.flags as Any), ("bot", self.bot as Any), ("domain", self.domain as Any), ("browser", self.browser as Any), ("platform", self.platform as Any), ("ip", self.ip as Any), ("region", self.region as Any), ("matchCodes", self.matchCodes as Any), ("userIdHint", self.userIdHint as Any), ("verifiedAppName", self.verifiedAppName as Any)])
}
}
case urlAuthResultAccepted(Cons_urlAuthResultAccepted)
@ -616,7 +618,7 @@ public extension Api {
break
case .urlAuthResultRequest(let _data):
if boxed {
buffer.appendInt32(-117904610)
buffer.appendInt32(1020666860)
}
serializeInt32(_data.flags, buffer: buffer, boxed: false)
_data.bot.serialize(buffer, true)
@ -643,6 +645,9 @@ public extension Api {
if Int(_data.flags) & Int(1 << 4) != 0 {
serializeInt64(_data.userIdHint!, buffer: buffer, boxed: false)
}
if Int(_data.flags) & Int(1 << 7) != 0 {
serializeString(_data.verifiedAppName!, buffer: buffer, boxed: false)
}
break
}
}
@ -654,7 +659,7 @@ public extension Api {
case .urlAuthResultDefault:
return ("urlAuthResultDefault", [])
case .urlAuthResultRequest(let _data):
return ("urlAuthResultRequest", [("flags", _data.flags as Any), ("bot", _data.bot as Any), ("domain", _data.domain as Any), ("browser", _data.browser as Any), ("platform", _data.platform as Any), ("ip", _data.ip as Any), ("region", _data.region as Any), ("matchCodes", _data.matchCodes as Any), ("userIdHint", _data.userIdHint as Any)])
return ("urlAuthResultRequest", [("flags", _data.flags as Any), ("bot", _data.bot as Any), ("domain", _data.domain as Any), ("browser", _data.browser as Any), ("platform", _data.platform as Any), ("ip", _data.ip as Any), ("region", _data.region as Any), ("matchCodes", _data.matchCodes as Any), ("userIdHint", _data.userIdHint as Any), ("verifiedAppName", _data.verifiedAppName as Any)])
}
}
@ -712,6 +717,10 @@ public extension Api {
if Int(_1!) & Int(1 << 4) != 0 {
_9 = reader.readInt64()
}
var _10: String?
if Int(_1!) & Int(1 << 7) != 0 {
_10 = parseString(reader)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
@ -721,8 +730,9 @@ public extension Api {
let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil
let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil
let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
return Api.UrlAuthResult.urlAuthResultRequest(Cons_urlAuthResultRequest(flags: _1!, bot: _2!, domain: _3!, browser: _4, platform: _5, ip: _6, region: _7, matchCodes: _8, userIdHint: _9))
let _c10 = (Int(_1!) & Int(1 << 7) == 0) || _10 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 {
return Api.UrlAuthResult.urlAuthResultRequest(Cons_urlAuthResultRequest(flags: _1!, bot: _2!, domain: _3!, browser: _4, platform: _5, ip: _6, region: _7, matchCodes: _8, userIdHint: _9, verifiedAppName: _10))
}
else {
return nil

View file

@ -1141,10 +1141,11 @@ public extension Api {
public var unreadCount: Int32
public var unreadMentionsCount: Int32
public var unreadReactionsCount: Int32
public var unreadPollVotesCount: Int32
public var fromId: Api.Peer
public var notifySettings: Api.PeerNotifySettings
public var draft: Api.DraftMessage?
public init(flags: Int32, id: Int32, date: Int32, peer: Api.Peer, title: String, iconColor: Int32, iconEmojiId: Int64?, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32, fromId: Api.Peer, notifySettings: Api.PeerNotifySettings, draft: Api.DraftMessage?) {
public init(flags: Int32, id: Int32, date: Int32, peer: Api.Peer, title: String, iconColor: Int32, iconEmojiId: Int64?, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32, unreadPollVotesCount: Int32, fromId: Api.Peer, notifySettings: Api.PeerNotifySettings, draft: Api.DraftMessage?) {
self.flags = flags
self.id = id
self.date = date
@ -1158,12 +1159,13 @@ public extension Api {
self.unreadCount = unreadCount
self.unreadMentionsCount = unreadMentionsCount
self.unreadReactionsCount = unreadReactionsCount
self.unreadPollVotesCount = unreadPollVotesCount
self.fromId = fromId
self.notifySettings = notifySettings
self.draft = draft
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("forumTopic", [("flags", self.flags as Any), ("id", self.id as Any), ("date", self.date as Any), ("peer", self.peer as Any), ("title", self.title as Any), ("iconColor", self.iconColor as Any), ("iconEmojiId", self.iconEmojiId as Any), ("topMessage", self.topMessage as Any), ("readInboxMaxId", self.readInboxMaxId as Any), ("readOutboxMaxId", self.readOutboxMaxId as Any), ("unreadCount", self.unreadCount as Any), ("unreadMentionsCount", self.unreadMentionsCount as Any), ("unreadReactionsCount", self.unreadReactionsCount as Any), ("fromId", self.fromId as Any), ("notifySettings", self.notifySettings as Any), ("draft", self.draft as Any)])
return ("forumTopic", [("flags", self.flags as Any), ("id", self.id as Any), ("date", self.date as Any), ("peer", self.peer as Any), ("title", self.title as Any), ("iconColor", self.iconColor as Any), ("iconEmojiId", self.iconEmojiId as Any), ("topMessage", self.topMessage as Any), ("readInboxMaxId", self.readInboxMaxId as Any), ("readOutboxMaxId", self.readOutboxMaxId as Any), ("unreadCount", self.unreadCount as Any), ("unreadMentionsCount", self.unreadMentionsCount as Any), ("unreadReactionsCount", self.unreadReactionsCount as Any), ("unreadPollVotesCount", self.unreadPollVotesCount as Any), ("fromId", self.fromId as Any), ("notifySettings", self.notifySettings as Any), ("draft", self.draft as Any)])
}
}
public class Cons_forumTopicDeleted: TypeConstructorDescription {
@ -1182,7 +1184,7 @@ public extension Api {
switch self {
case .forumTopic(let _data):
if boxed {
buffer.appendInt32(-838922550)
buffer.appendInt32(-52766699)
}
serializeInt32(_data.flags, buffer: buffer, boxed: false)
serializeInt32(_data.id, buffer: buffer, boxed: false)
@ -1199,6 +1201,7 @@ public extension Api {
serializeInt32(_data.unreadCount, buffer: buffer, boxed: false)
serializeInt32(_data.unreadMentionsCount, buffer: buffer, boxed: false)
serializeInt32(_data.unreadReactionsCount, buffer: buffer, boxed: false)
serializeInt32(_data.unreadPollVotesCount, buffer: buffer, boxed: false)
_data.fromId.serialize(buffer, true)
_data.notifySettings.serialize(buffer, true)
if Int(_data.flags) & Int(1 << 4) != 0 {
@ -1217,7 +1220,7 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .forumTopic(let _data):
return ("forumTopic", [("flags", _data.flags as Any), ("id", _data.id as Any), ("date", _data.date as Any), ("peer", _data.peer as Any), ("title", _data.title as Any), ("iconColor", _data.iconColor as Any), ("iconEmojiId", _data.iconEmojiId as Any), ("topMessage", _data.topMessage as Any), ("readInboxMaxId", _data.readInboxMaxId as Any), ("readOutboxMaxId", _data.readOutboxMaxId as Any), ("unreadCount", _data.unreadCount as Any), ("unreadMentionsCount", _data.unreadMentionsCount as Any), ("unreadReactionsCount", _data.unreadReactionsCount as Any), ("fromId", _data.fromId as Any), ("notifySettings", _data.notifySettings as Any), ("draft", _data.draft as Any)])
return ("forumTopic", [("flags", _data.flags as Any), ("id", _data.id as Any), ("date", _data.date as Any), ("peer", _data.peer as Any), ("title", _data.title as Any), ("iconColor", _data.iconColor as Any), ("iconEmojiId", _data.iconEmojiId as Any), ("topMessage", _data.topMessage as Any), ("readInboxMaxId", _data.readInboxMaxId as Any), ("readOutboxMaxId", _data.readOutboxMaxId as Any), ("unreadCount", _data.unreadCount as Any), ("unreadMentionsCount", _data.unreadMentionsCount as Any), ("unreadReactionsCount", _data.unreadReactionsCount as Any), ("unreadPollVotesCount", _data.unreadPollVotesCount as Any), ("fromId", _data.fromId as Any), ("notifySettings", _data.notifySettings as Any), ("draft", _data.draft as Any)])
case .forumTopicDeleted(let _data):
return ("forumTopicDeleted", [("id", _data.id as Any)])
}
@ -1254,18 +1257,20 @@ public extension Api {
_12 = reader.readInt32()
var _13: Int32?
_13 = reader.readInt32()
var _14: Api.Peer?
var _14: Int32?
_14 = reader.readInt32()
var _15: Api.Peer?
if let signature = reader.readInt32() {
_14 = Api.parse(reader, signature: signature) as? Api.Peer
_15 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _15: Api.PeerNotifySettings?
var _16: Api.PeerNotifySettings?
if let signature = reader.readInt32() {
_15 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings
_16 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings
}
var _16: Api.DraftMessage?
var _17: Api.DraftMessage?
if Int(_1!) & Int(1 << 4) != 0 {
if let signature = reader.readInt32() {
_16 = Api.parse(reader, signature: signature) as? Api.DraftMessage
_17 = Api.parse(reader, signature: signature) as? Api.DraftMessage
}
}
let _c1 = _1 != nil
@ -1283,9 +1288,10 @@ public extension Api {
let _c13 = _13 != nil
let _c14 = _14 != nil
let _c15 = _15 != nil
let _c16 = (Int(_1!) & Int(1 << 4) == 0) || _16 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 {
return Api.ForumTopic.forumTopic(Cons_forumTopic(flags: _1!, id: _2!, date: _3!, peer: _4!, title: _5!, iconColor: _6!, iconEmojiId: _7, topMessage: _8!, readInboxMaxId: _9!, readOutboxMaxId: _10!, unreadCount: _11!, unreadMentionsCount: _12!, unreadReactionsCount: _13!, fromId: _14!, notifySettings: _15!, draft: _16))
let _c16 = _16 != nil
let _c17 = (Int(_1!) & Int(1 << 4) == 0) || _17 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 {
return Api.ForumTopic.forumTopic(Cons_forumTopic(flags: _1!, id: _2!, date: _3!, peer: _4!, title: _5!, iconColor: _6!, iconEmojiId: _7, topMessage: _8!, readInboxMaxId: _9!, readOutboxMaxId: _10!, unreadCount: _11!, unreadMentionsCount: _12!, unreadReactionsCount: _13!, unreadPollVotesCount: _14!, fromId: _15!, notifySettings: _16!, draft: _17))
}
else {
return nil

View file

@ -242,7 +242,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
}
switch action {
case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionGiftStars, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionSetChatWallPaper, .messageActionGiveawayLaunch, .messageActionGiveawayResults, .messageActionBoostApply, .messageActionRequestedPeerSentMe, .messageActionStarGift, .messageActionStarGiftUnique, .messageActionPaidMessagesRefunded, .messageActionPaidMessagesPrice, .messageActionTodoCompletions, .messageActionTodoAppendTasks, .messageActionSuggestedPostApproval, .messageActionGiftTon, .messageActionSuggestedPostSuccess, .messageActionSuggestedPostRefund, .messageActionSuggestBirthday, .messageActionStarGiftPurchaseOffer, .messageActionStarGiftPurchaseOfferDeclined, .messageActionNoForwardsToggle, .messageActionNoForwardsRequest, .messageActionPollAppendAnswer:
case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionGiftStars, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionSetChatWallPaper, .messageActionGiveawayLaunch, .messageActionGiveawayResults, .messageActionBoostApply, .messageActionRequestedPeerSentMe, .messageActionStarGift, .messageActionStarGiftUnique, .messageActionPaidMessagesRefunded, .messageActionPaidMessagesPrice, .messageActionTodoCompletions, .messageActionTodoAppendTasks, .messageActionSuggestedPostApproval, .messageActionGiftTon, .messageActionSuggestedPostSuccess, .messageActionSuggestedPostRefund, .messageActionSuggestBirthday, .messageActionStarGiftPurchaseOffer, .messageActionStarGiftPurchaseOfferDeclined, .messageActionNoForwardsToggle, .messageActionNoForwardsRequest, .messageActionPollAppendAnswer, .messageActionPollDeleteAnswer:
break
case let .messageActionManagedBotCreated(messageActionManagedBotCreated):
let botId = messageActionManagedBotCreated.botId

View file

@ -367,6 +367,8 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
return TelegramMediaAction(action: .managedBotCreated(botId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(messageActionManagedBotCreatedData.botId))))
case let .messageActionPollAppendAnswer(messageActionPollAppendAnswerData):
return TelegramMediaAction(action: .pollOptionAppended(TelegramMediaPollOption(apiOption: messageActionPollAppendAnswerData.answer)))
case let .messageActionPollDeleteAnswer(messageActionPollDeleteAnswerData):
return TelegramMediaAction(action: .pollOptionDeleted(TelegramMediaPollOption(apiOption: messageActionPollDeleteAnswerData.answer)))
}
}

View file

@ -306,6 +306,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case copyProtectionRequest(hasExpired: Bool, previousValue: Bool, newValue: Bool)
case managedBotCreated(botId: PeerId)
case pollOptionAppended(TelegramMediaPollOption)
case pollOptionDeleted(TelegramMediaPollOption)
public init(decoder: PostboxDecoder) {
let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0)
@ -487,6 +488,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
self = .managedBotCreated(botId: PeerId(decoder.decodeInt64ForKey("botId", orElse: 0)))
case 63:
self = .pollOptionAppended(decoder.decodeObjectForKey("option", decoder: { TelegramMediaPollOption(decoder: $0) }) as! TelegramMediaPollOption)
case 64:
self = .pollOptionDeleted(decoder.decodeObjectForKey("option", decoder: { TelegramMediaPollOption(decoder: $0) }) as! TelegramMediaPollOption)
default:
self = .unknown
}
@ -996,6 +999,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case let .pollOptionAppended(option):
encoder.encodeInt32(63, forKey: "_rawValue")
encoder.encodeObject(option, forKey: "option")
case let .pollOptionDeleted(option):
encoder.encodeInt32(64, forKey: "_rawValue")
encoder.encodeObject(option, forKey: "option")
}
}

View file

@ -501,7 +501,7 @@ public func stringForMediaKind(_ kind: MessageContentKind, strings: Presentation
public func descriptionStringForMessage(contentSettings: ContentSettings, message: EngineMessage, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, accountPeerId: EnginePeer.Id) -> (NSAttributedString, Bool, Bool) {
let contentKind = messageContentKind(contentSettings: contentSettings, message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: accountPeerId)
if !message.text.isEmpty && ![.expiredImage, .expiredVideo].contains(contentKind.key) {
if !message.text.isEmpty && ![.expiredImage, .expiredVideo, .poll].contains(contentKind.key) {
return (foldLineBreaks(messageTextWithAttributes(message: message)), false, true)
}
let result = stringForMediaKind(contentKind, strings: strings)

View file

@ -1822,19 +1822,6 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
let attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: peerIds)
attributedString = addAttributesToStringWithRanges(strings.Notification_ManagedBotCreated(peerName)._tuple, body: bodyAttributes, argumentAttributes: attributes)
case let .pollOptionAppended(option):
var optionTitle = "DELETED"
for attribute in message.attributes {
if let attribute = attribute as? ReplyMessageAttribute, let message = message.associatedMessages[attribute.messageId] {
for media in message.media {
if let todo = media as? TelegramMediaTodo {
optionTitle = todo.text
}
}
}
}
if optionTitle.count > 20 {
optionTitle = optionTitle.prefix(20) + ""
}
let optionEntities = option.entities.filter { entity in
switch entity.type {
case .Spoiler, .CustomEmoji:
@ -1872,6 +1859,44 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
}
attributedString = resultAttributedString
}
case let .pollOptionDeleted(option):
let optionEntities = option.entities.filter { entity in
switch entity.type {
case .Spoiler, .CustomEmoji:
return true
default:
return false
}
}
if message.author?.id == accountPeerId {
var optionText = option.text
if optionText.count > 20 {
optionText = optionText.prefix(20) + ""
}
let resultString = strings.Notification_PollDeletedOptionYou(optionText)
let stringWithRanges = resultString._tuple
let resultAttributedString = NSMutableAttributedString(attributedString: addAttributesToStringWithRanges(stringWithRanges, body: bodyAttributes, argumentAttributes: [0: boldAttributes, 1: boldAttributes]))
if let optionRange = serviceMessageArgumentRange(index: 0, value: optionText, in: stringWithRanges) {
addServiceMessageTextEntities(optionEntities, to: resultAttributedString, text: optionText, range: optionRange, associatedMedia: message.associatedMedia)
}
attributedString = resultAttributedString
} else {
let peerName = message.author?.compactDisplayTitle ?? ""
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])
attributes[1] = boldAttributes
var optionText = option.text
if optionText.count > 20 {
optionText = optionText.prefix(20) + ""
}
let resultString = strings.Notification_PollDeletedOption(peerName, optionText)
let stringWithRanges = resultString._tuple
let resultAttributedString = NSMutableAttributedString(attributedString: addAttributesToStringWithRanges(stringWithRanges, body: bodyAttributes, argumentAttributes: attributes))
if let optionRange = serviceMessageArgumentRange(index: 1, value: optionText, in: stringWithRanges) {
addServiceMessageTextEntities(optionEntities, to: resultAttributedString, text: optionText, range: optionRange, associatedMedia: message.associatedMedia)
}
attributedString = resultAttributedString
}
case .unknown:
attributedString = nil
}

View file

@ -631,7 +631,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
var index: Int32 = 0
if let savedMusic, !savedMusic.isEmpty {
entries.append(.header(title: "SAVED MUSIC", section: section))
entries.append(.header(title: "PROFILE MUSIC", section: section))
var savedMusic = savedMusic
var hasShowMore = false
@ -652,7 +652,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
section += 1
if !messages.isEmpty {
entries.append(.header(title: "SHARED AUDIO", section: section))
entries.append(.header(title: "YOUR CHATS", section: section))
var messages = messages
var hasShowMore = false
if messages.count > 4 && !expandedSections.contains(section) {
@ -672,7 +672,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
index = 0
section += 1
entries.append(.header(title: "GLOBAL SEARCH", section: section))
entries.append(.header(title: "PUBLIC CHATS", section: section))
for message in globalMusic {
entries.append(.file(index: index, message: message, section: section))
index += 1

View file

@ -20,8 +20,7 @@ private extension ListMessageItemInteraction {
return controllerInteraction.openMessage(message, OpenMessageParams(mode: mode))
}, openMessageContextMenu: { message, bool, node, rect, gesture in
controllerInteraction.openMessageContextMenu(message, bool, node, rect, gesture, nil)
}, toggleMediaPlayback: { _ in
}, toggleMessagesSelection: { messageId, selected in
}, toggleMediaPlayback: nil, toggleMessagesSelection: { messageId, selected in
controllerInteraction.toggleMessagesSelection(messageId, selected)
}, openUrl: { url, param1, param2, message in
controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: param1, external: param2, message: message))

View file

@ -11,6 +11,7 @@ swift_library(
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Crc32",
"//submodules/Display",
"//submodules/TelegramCore",
"//submodules/Postbox",

View file

@ -1,6 +1,7 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Crc32
import Display
import TelegramCore
import Postbox
@ -3654,11 +3655,6 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
}
private struct ArbitraryRandomNumberGenerator : RandomNumberGenerator {
init(seed: Int) { srand48(seed) }
func next() -> UInt64 { return UInt64(drand48() * Double(UInt64.max)) }
}
private func stringForRemainingTime(_ duration: Int32, strings: PresentationStrings, results: Bool) -> String {
let days = duration / (3600 * 24)
let hours = duration / 3600
@ -3739,26 +3735,31 @@ private func resolvedOptionOrder(for item: ChatMessageBubbleContentItem) -> [(In
return defaultOrderedOptions
}
let currentOpaqueIdentifiers = poll.options.map(\.opaqueIdentifier)
let messageSeed = UInt64(bitPattern: Int64(item.message.stableId))
let peerSeed = UInt64(bitPattern: item.context.account.peerId.toInt64())
let seed = Int(truncatingIfNeeded: (messageSeed &* 6364136223846793005) ^ peerSeed)
let userId = item.context.account.peerId.id._internalGetInt64Value()
let pollId = poll.pollId.id
var resolvedOpaqueIdentifiers = currentOpaqueIdentifiers
var randomNumberGenerator = ArbitraryRandomNumberGenerator(seed: seed)
resolvedOpaqueIdentifiers.shuffle(using: &randomNumberGenerator)
return defaultOrderedOptions
.map { index, option in
var hashValue = Data()
hashValue.append(contentsOf: String(userId).utf8)
hashValue.append(option.opaqueIdentifier)
hashValue.append(contentsOf: String(pollId).utf8)
let indicesByOpaqueIdentifier = Dictionary(uniqueKeysWithValues: poll.options.enumerated().map { ($0.element.opaqueIdentifier, $0.offset) })
let orderedOptions = resolvedOpaqueIdentifiers.compactMap { opaqueIdentifier -> (Int, TelegramMediaPollOption)? in
guard let index = indicesByOpaqueIdentifier[opaqueIdentifier] else {
return nil
let sortValue: UInt32 = hashValue.withUnsafeBytes { bytes in
guard let baseAddress = bytes.baseAddress else {
return 0
}
return Crc32(baseAddress, Int32(bytes.count))
}
return (index, option, sortValue)
}
return (index, poll.options[index])
}
if orderedOptions.count == poll.options.count {
return orderedOptions
} else {
return defaultOrderedOptions
}
.sorted(by: { lhs, rhs in
if lhs.2 != rhs.2 {
return lhs.2 < rhs.2
} else {
return lhs.0 < rhs.0
}
})
.map { ($0.0, $0.1) }
}

View file

@ -3239,7 +3239,7 @@ private final class GiftViewSheetContent: CombinedComponent {
var hasDescriptionButton = false
if let uniqueGift {
titleString = uniqueGift.title + " **#\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: environment.dateTimeFormat))**"
descriptionText = "\(strings.Gift_Unique_Collectible) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: environment.dateTimeFormat))"
descriptionText = "\(strings.Gift_Unique_Collectible)"
for attribute in uniqueGift.attributes {
if case let .model(name, _, _, _) = attribute {
descriptionText = name
@ -3520,7 +3520,7 @@ private final class GiftViewSheetContent: CombinedComponent {
}
var descriptionSize = CGSize()
if state.justUpgraded {
if !"".isEmpty, state.justUpgraded {
var items: [AnyComponentWithIdentity<Empty>] = [
AnyComponentWithIdentity(id: "label", component: AnyComponent(Text(text: "\(strings.Gift_Unique_Collectible) #", font: textFont, color: .white, tintColor: textColor)))
]

View file

@ -1103,9 +1103,13 @@ public final class ListComposePollOptionComponent: Component {
let cornerRadius: CGFloat = 10.0
let makeLayout = imageNode.asyncLayout()
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: cornerRadius), imageSize: imageSize, boundingSize: imageNodeSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))
apply()
Queue.concurrentDefaultQueue().async {
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: cornerRadius), imageSize: imageSize, boundingSize: imageNodeSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))
Queue.mainQueue().async {
apply()
}
}
if self.imageButton.superview == nil {
self.imageButton.addTarget(self, action: #selector(self.imageButtonPressed), for: .touchUpInside)
self.addSubview(self.imageButton)

View file

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

View file

@ -202,8 +202,7 @@ extension ListMessageItemInteraction {
return controllerInteraction.openMessage(message, OpenMessageParams(mode: mode))
}, openMessageContextMenu: { message, bool, node, rect, gesture in
controllerInteraction.openMessageContextMenu(message, bool, node, rect, gesture, nil)
}, toggleMediaPlayback: { _ in
}, toggleMessagesSelection: { messageId, selected in
}, toggleMediaPlayback: nil, toggleMessagesSelection: { messageId, selected in
controllerInteraction.toggleMessagesSelection(messageId, selected)
}, openUrl: { url, param1, param2, message in
controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: param1, external: param2, message: message, progress: Promise()))

View file

@ -2970,6 +2970,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
mappedSource = .todo
case .copyProtection:
mappedSource = .copyProtection
case .aiTools:
mappedSource = .aiTools
case let .auth(price):
mappedSource = .auth(price)
case let .premiumGift(file):
@ -3052,6 +3054,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
mappedSubject = .todo
case .copyProtection:
mappedSubject = .copyProtection
case .aiTools:
mappedSubject = .aiTools
case .business:
mappedSubject = .business
buttonText = presentationData.strings.Chat_EmptyStateIntroFooterPremiumActionButton