This commit is contained in:
Isaac 2026-02-25 10:47:22 +01:00
commit 99b46bd53a
28 changed files with 790 additions and 187 deletions

View file

@ -1223,6 +1223,10 @@ private final class NotificationServiceHandler {
content.userInfo["peerId"] = "\(peerId.toInt64())"
content.userInfo["accountId"] = "\(recordId.int64)"
if let dataUrl = payloadJson["data_url"] as? String {
content.userInfo["data_url"] = dataUrl
}
if let silentString = payloadJson["silent"] as? String {
if let silentValue = Int(silentString), silentValue != 0 {
content.silent = true

View file

@ -15752,6 +15752,7 @@ Error: %8$@";
"AuthConfirmation.LoginFail.Title" = "Login Failed";
"AuthConfirmation.LoginFail.Text" = "Please try logging in to [%@]() again.";
"AuthConfirmation.LoginFail.TextUnknown" = "Please try logging in again.";
"Notification.GroupCreatorChangePending" = "%2$@ will become the new main admin in 7 days if %1$@ does not return.";
"Notification.GroupCreatorChangeApplied" = "%1$@ has transferred ownership of the group to %2$@.";
@ -15909,7 +15910,7 @@ Error: %8$@";
"RankInfo.Owner.Title" = "Owner Tag";
"RankInfo.Owner.Text" = "This purple tag %1$@ is **%2$@'s** owner tag. **%3$@** is the owner of **%4$@**.";
"RankInfo.ChangeInfo" = "Only admins can assign member tags in this group.";
"RankInfo.MemberTag" = "Admin Tag";
"RankInfo.MemberTag" = "Member Tag";
"RankInfo.AdminTag" = "Admin Tag";
"RankInfo.OwnerTag" = "Owner Tag";
"RankInfo.SetMyTag" = "Set My Tag";
@ -15927,7 +15928,20 @@ Error: %8$@";
"BackgroundTasks.UploadingStories_any" = "Uploading %d Stories";
"BackgroundTasks.StoryOpenAppToContinue" = "Open app to continue";
"BackgroundTasks.StorySubtitle" = "Running...";
"BackgroundTasks.StoryFinished" = "Finished";
"BackgroundTasks.UploadingMedia_1" = "Sending Message";
"BackgroundTasks.UploadingMedia_any" = "Sending %d Messages";
"BackgroundTasks.MediaSubtitle" = "Running...";
"BackgroundTasks.MediaFinished" = "Finished";
"DisableSharing.Title" = "Disable Sharing";
"DisableSharing.Screenshot.Title" = "No Screenshots";
"DisableSharing.Screenshot.Text" = "Disable screenshots and screen recordings in this chat.";
"DisableSharing.Forwarding.Title" = "No Forwarding";
"DisableSharing.Forwarding.Text" = "Disable message forwarding to other chats.";
"DisableSharing.Saving.Title" = "No Saving";
"DisableSharing.Saving.Text" = "Disable copying text and saving photos and videos to Photos.";
"DisableSharing.Confirm" = "Disable Sharing";

View file

@ -441,7 +441,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
return
}
var dismissImpl: (() -> Void)?
let controller = AuthConfirmationScreen(context: self.context, subject: result, completion: { [weak self] accountContext, accountPeer, authResult in
let controller = AuthConfirmationScreen(context: self.context, requestSubject: subject, subject: result, completion: { [weak self] accountContext, accountPeer, authResult in
guard let self else {
return
}
@ -503,6 +503,19 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
case .decline:
let _ = self.context.engine.messages.declineUrlAuth(url: url).start()
self.webView.sendEvent(name: "oauth_result_failed", data: nil)
case .failed:
guard case let .request(domain, _, _, _, _, _) = result else {
return
}
let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: presentationData.strings.AuthConfirmation_LoginFail_Title, text: presentationData.strings.AuthConfirmation_LoginFail_Text(domain).string, cancel: nil, destructive: false), action: { _ in return true })
if let navigationController = self.getNavigationController() {
(navigationController.topViewController as? ViewController)?.present(controller, in: .window(.root))
}
let _ = self.context.engine.messages.declineUrlAuth(url: url).start()
self.webView.sendEvent(name: "oauth_result_failed", data: nil)
HapticFeedback().error()
}
})
dismissImpl = { [weak controller] in

View file

@ -194,7 +194,7 @@ public class ItemListAvatarAndNameInfoItem: ListViewItem, ItemListItem, ListItem
if case .settings = mode {
self.selectable = true
} else {
self.selectable = false
self.selectable = action != nil
}
}

View file

@ -41,6 +41,7 @@ typedef enum
- (bool)trimApplied;
- (bool)isCropAndRotationEqualWith:(id<TGMediaEditAdjustments>)adjustments;
- (bool)isDefaultValuesForGif;
- (NSDictionary *)dictionary;

View file

@ -429,6 +429,11 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5;
return ![self cropAppliedForAvatar:forAvatar] && ![self toolsApplied] && ![self hasPainting] && !_sendAsGif && _preset == TGMediaVideoConversionPresetCompressedDefault;
}
- (bool)isDefaultValuesForGif
{
return ![self cropAppliedForAvatar:false] && ![self toolsApplied] && ![self hasPainting];
}
- (bool)isCropEqualWith:(id<TGMediaEditAdjustments>)adjusments
{
return (_CGRectEqualToRectWithEpsilon(self.cropRect, adjusments.cropRect, [self _cropRectEpsilon]));

View file

@ -157,7 +157,19 @@ public func legacyStoryMediaEditor(context: AccountContext, item: TGMediaEditabl
})
}
public func legacyMediaEditor(context: AccountContext, peer: Peer, threadTitle: String?, media: AnyMediaReference, mode: LegacyMediaEditorMode, initialCaption: NSAttributedString, snapshots: [UIView], transitionCompletion: (() -> Void)?, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32, Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) {
public func legacyMediaEditor(
context: AccountContext,
peer: Peer,
threadTitle: String?,
media: AnyMediaReference,
mode: LegacyMediaEditorMode,
initialCaption: NSAttributedString,
snapshots: [UIView],
transitionCompletion: (() -> Void)?,
getCaptionPanelView: @escaping () -> TGCaptionPanelView?,
sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32, Bool) -> Void,
present: @escaping (ViewController, Any?) -> Void
) {
let _ = (fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .other, mediaReference: media)
|> deliverOnMainQueue).start(next: { (value, isImage) in
guard case let .data(data) = value, data.complete else {

View file

@ -374,7 +374,7 @@ public struct LegacyAssetPickerEnqueueMessage {
public var isFile: Bool
}
public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: Account, signals: [Any]) -> Signal<[LegacyAssetPickerEnqueueMessage], Void> {
public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: Account, signals: [Any], originalMediaReference: AnyMediaReference? = nil) -> Signal<[LegacyAssetPickerEnqueueMessage], Void> {
return Signal { subscriber in
let disposable = SSignal.combineSignals(signals).start(next: { anyValues in
var messages: [LegacyAssetPickerEnqueueMessage] = []
@ -934,8 +934,16 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
fileAttributes.append(.HasLinkedStickers)
}
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], videoCover: videoCover, immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: fileAttributes, alternativeRepresentations: [])
let media: Media
let mediaReference: AnyMediaReference
if let adjustments, adjustments.isDefaultValuesForGif(), let originalMediaReference {
media = originalMediaReference.media
mediaReference = originalMediaReference
} else {
media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], videoCover: videoCover, immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: fileAttributes, alternativeRepresentations: [])
mediaReference = .standalone(media: media)
}
if let timer = item.timer, timer > 0 && (timer <= 60 || timer == viewOnceTimeout) {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil))
}
@ -987,7 +995,7 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
)
}
} else {
messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: item.groupedId, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets), uniqueId: item.uniqueId, isFile: asFile))
messages.append(LegacyAssetPickerEnqueueMessage(message: .message(text: text.string, attributes: attributes, inlineStickers: [:], mediaReference: mediaReference, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: item.groupedId, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets), uniqueId: item.uniqueId, isFile: asFile))
}
}
}

View file

@ -102,8 +102,8 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope
if !skipSafari {
options.append(OpenInOption(identifier: "safari", application: .safari, action: {
var url = url
if url.hasPrefix("http://") || url.hasPrefix("https://") {
url = url.replacingOccurrences(of: "https://", with: "x-safari-https")
if url.hasPrefix("https://") {
url = url.replacingOccurrences(of: "https://", with: "x-safari-https://")
}
return .openUrl(url: url)
}))

View file

@ -31,8 +31,9 @@ private final class ChannelAdminControllerArguments {
let dismissInput: () -> Void
let animateError: () -> Void
let toggleIsOptionExpanded: (RightsItem.Sub) -> Void
let openPeer: () -> Void
init(context: AccountContext, updateAdminRights: @escaping (Bool) -> Void, toggleRight: @escaping (RightsItem, TelegramChatAdminRightsFlags, Bool) -> Void, toggleRightWhileDisabled: @escaping (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void, transferOwnership: @escaping () -> Void, updateRank: @escaping (String, String) -> Void, updateFocusedOnRank: @escaping (Bool) -> Void, dismissAdmin: @escaping () -> Void, dismissInput: @escaping () -> Void, animateError: @escaping () -> Void, toggleIsOptionExpanded: @escaping (RightsItem.Sub) -> Void) {
init(context: AccountContext, updateAdminRights: @escaping (Bool) -> Void, toggleRight: @escaping (RightsItem, TelegramChatAdminRightsFlags, Bool) -> Void, toggleRightWhileDisabled: @escaping (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void, transferOwnership: @escaping () -> Void, updateRank: @escaping (String, String) -> Void, updateFocusedOnRank: @escaping (Bool) -> Void, dismissAdmin: @escaping () -> Void, dismissInput: @escaping () -> Void, animateError: @escaping () -> Void, toggleIsOptionExpanded: @escaping (RightsItem.Sub) -> Void, openPeer: @escaping () -> Void) {
self.context = context
self.updateAdminRights = updateAdminRights
self.toggleRight = toggleRight
@ -44,6 +45,7 @@ private final class ChannelAdminControllerArguments {
self.dismissInput = dismissInput
self.animateError = animateError
self.toggleIsOptionExpanded = toggleIsOptionExpanded
self.openPeer = openPeer
}
}
@ -367,6 +369,8 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
case let .info(_, _, dateTimeFormat, peer, presence):
return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, mode: .generic, peer: peer, presence: presence, memberCount: nil, state: ItemListAvatarAndNameInfoItemState(), sectionId: self.section, style: .blocks(withTopInset: true, withExtendedBottomInset: false), editingNameUpdated: { _ in
}, avatarTapped: {
}, action: {
arguments.openPeer()
})
case let .rankTitle(_, text, count, limit):
var accessoryText: ItemListSectionHeaderAccessoryText?
@ -1219,6 +1223,16 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
return state
}
}, openPeer: {
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: adminId))
|> deliverOnMainQueue).start(next: { peer in
guard let peer else {
return
}
if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: updatedPresentationData, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
pushControllerImpl?(controller)
}
})
})
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
@ -1683,7 +1697,17 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
})
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let rightNavigationButton: ItemListNavigationButton?
if state.focusedOnRank {
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
rightButtonActionImpl()
})
footerItem = nil
} else {
rightNavigationButton = nil
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelAdminControllerEntries(presentationData: presentationData, state: state, accountPeerId: context.account.peerId, channelPeer: channelPeer, adminPeer: adminPeer, adminPresence: adminPresence, initialParticipant: initialParticipant, invite: invite, canEdit: canEdit), style: .blocks, focusItemTag: nil, ensureVisibleItemTag: nil, emptyStateItem: nil, footerItem: footerItem, animateChanges: true)

View file

@ -15,6 +15,8 @@ import PresentationDataUtils
import ItemListAvatarAndNameInfoItem
import OldChannelsController
private let rankMaxLength: Int32 = 16
private final class ChannelBannedMemberControllerArguments {
let context: AccountContext
let toggleRight: (TelegramChatBannedRightsFlags, Bool) -> Void
@ -22,14 +24,24 @@ private final class ChannelBannedMemberControllerArguments {
let toggleIsOptionExpanded: (TelegramChatBannedRightsFlags) -> Void
let openTimeout: () -> Void
let delete: () -> Void
let openPeer: () -> Void
let updateRank: (String, String) -> Void
let updateFocusedOnRank: (Bool) -> Void
let dismissInput: () -> Void
let animateError: () -> Void
init(context: AccountContext, toggleRight: @escaping (TelegramChatBannedRightsFlags, Bool) -> Void, toggleRightWhileDisabled: @escaping (TelegramChatBannedRightsFlags) -> Void, toggleIsOptionExpanded: @escaping (TelegramChatBannedRightsFlags) -> Void, openTimeout: @escaping () -> Void, delete: @escaping () -> Void) {
init(context: AccountContext, toggleRight: @escaping (TelegramChatBannedRightsFlags, Bool) -> Void, toggleRightWhileDisabled: @escaping (TelegramChatBannedRightsFlags) -> Void, toggleIsOptionExpanded: @escaping (TelegramChatBannedRightsFlags) -> Void, openTimeout: @escaping () -> Void, delete: @escaping () -> Void, openPeer: @escaping () -> Void, updateRank: @escaping (String, String) -> Void, updateFocusedOnRank: @escaping (Bool) -> Void, dismissInput: @escaping () -> Void, animateError: @escaping () -> Void) {
self.context = context
self.toggleRight = toggleRight
self.toggleRightWhileDisabled = toggleRightWhileDisabled
self.toggleIsOptionExpanded = toggleIsOptionExpanded
self.openTimeout = openTimeout
self.delete = delete
self.openPeer = openPeer
self.updateRank = updateRank
self.updateFocusedOnRank = updateFocusedOnRank
self.dismissInput = dismissInput
self.animateError = animateError
}
}
@ -38,6 +50,19 @@ private enum ChannelBannedMemberSection: Int32 {
case rights
case timeout
case delete
case rank
}
private enum ChannelBannedMemberEntryTag: ItemListItemTag {
case rank
func isEqual(to other: ItemListItemTag) -> Bool {
if let other = other as? ChannelBannedMemberEntryTag, self == other {
return true
} else {
return false
}
}
}
private enum ChannelBannedMemberEntryStableId: Hashable {
@ -47,6 +72,10 @@ private enum ChannelBannedMemberEntryStableId: Hashable {
case timeout
case exceptionInfo
case delete
case rankTitle
case rankPreview
case rank
case rankInfo
}
private enum ChannelBannedMemberEntry: ItemListNodeEntry {
@ -56,6 +85,10 @@ private enum ChannelBannedMemberEntry: ItemListNodeEntry {
case timeout(PresentationTheme, String, String)
case exceptionInfo(PresentationTheme, String)
case delete(PresentationTheme, String)
case rankTitle(PresentationTheme, String, Int32?, Int32)
case rankPreview(PresentationTheme, PresentationStrings, EnginePeer, String, Bool)
case rank(PresentationTheme, PresentationStrings, String, String, Bool)
case rankInfo(PresentationTheme, String, Bool)
var section: ItemListSectionId {
switch self {
@ -67,6 +100,8 @@ private enum ChannelBannedMemberEntry: ItemListNodeEntry {
return ChannelBannedMemberSection.timeout.rawValue
case .delete:
return ChannelBannedMemberSection.delete.rawValue
case .rankTitle, .rankPreview, .rank, .rankInfo:
return ChannelBannedMemberSection.rank.rawValue
}
}
@ -84,6 +119,14 @@ private enum ChannelBannedMemberEntry: ItemListNodeEntry {
return .exceptionInfo
case .delete:
return .delete
case .rankTitle:
return .rankTitle
case .rankPreview:
return .rankPreview
case .rank:
return .rank
case .rankInfo:
return .rankInfo
}
}
@ -165,50 +208,109 @@ private enum ChannelBannedMemberEntry: ItemListNodeEntry {
} else {
return false
}
case let .rankTitle(lhsTheme, lhsText, lhsCount, lhsLimit):
if case let .rankTitle(rhsTheme, rhsText, rhsCount, rhsLimit) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsCount == rhsCount, lhsLimit == rhsLimit {
return true
} else {
return false
}
case let .rankPreview(lhsTheme, lhsStrings, lhsPeer, lhsRank, lhsIsOwner):
if case let .rankPreview(rhsTheme, rhsStrings, rhsPeer, rhsRank, rhsIsOwner) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPeer == rhsPeer, lhsRank == rhsRank, lhsIsOwner == rhsIsOwner {
return true
} else {
return false
}
case let .rank(lhsTheme, lhsStrings, lhsPlaceholder, lhsValue, lhsEnabled):
if case let .rank(rhsTheme, rhsStrings, rhsPlaceholder, rhsValue, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPlaceholder == rhsPlaceholder, lhsValue == rhsValue, lhsEnabled == rhsEnabled {
return true
} else {
return false
}
case let .rankInfo(lhsTheme, lhsText, lhsTrimBottomInset):
if case let .rankInfo(rhsTheme, rhsText, rhsTrimBottomInset) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsTrimBottomInset == rhsTrimBottomInset {
return true
} else {
return false
}
}
}
static func <(lhs: ChannelBannedMemberEntry, rhs: ChannelBannedMemberEntry) -> Bool {
switch lhs {
case .info:
switch rhs {
case .info:
switch rhs {
case .info:
return false
default:
return true
}
case .rightsHeader:
switch rhs {
case .info, .rightsHeader:
return false
default:
return true
}
case let .rightItem(_, lhsIndex, _, _, _, _, _, _):
switch rhs {
case .info, .rightsHeader:
return false
case let .rightItem(_, rhsIndex, _, _, _, _, _, _):
return lhsIndex < rhsIndex
default:
return true
}
case .timeout:
switch rhs {
case .delete, .exceptionInfo:
return true
default:
return false
}
case .exceptionInfo:
switch rhs {
case .delete:
return true
default:
return false
}
case .delete:
return false
default:
return true
}
case .rightsHeader:
switch rhs {
case .info, .rightsHeader:
return false
default:
return true
}
case let .rightItem(_, lhsIndex, _, _, _, _, _, _):
switch rhs {
case .info, .rightsHeader:
return false
case let .rightItem(_, rhsIndex, _, _, _, _, _, _):
return lhsIndex < rhsIndex
default:
return true
}
case .timeout:
switch rhs {
case .info, .rightsHeader, .rightItem, .timeout:
return false
default:
return true
}
case .exceptionInfo:
switch rhs {
case .info, .rightsHeader, .rightItem, .timeout, .exceptionInfo:
return false
default:
return true
}
case .delete:
switch rhs {
case .info, .rightsHeader, .rightItem, .timeout, .exceptionInfo, .delete:
return false
default:
return true
}
case .rankTitle:
switch rhs {
case .info, .rightsHeader, .rightItem, .timeout, .delete, .rankTitle:
return false
default:
return true
}
case .rankPreview:
switch rhs {
case .info, .rightsHeader, .rightItem, .timeout, .delete, .rankTitle, .rankPreview:
return false
default:
return true
}
case .rank:
switch rhs {
case .info, .rightsHeader, .rightItem, .timeout, .delete, .rankTitle, .rankPreview, .rank:
return false
default:
return true
}
case .rankInfo:
switch rhs {
case .info, .rightsHeader, .rightItem, .timeout, .delete, .rankTitle, .rankPreview, .rank, .rankInfo:
return false
default:
return true
}
}
}
@ -218,6 +320,8 @@ private enum ChannelBannedMemberEntry: ItemListNodeEntry {
case let .info(_, _, dateTimeFormat, peer, presence):
return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, mode: .generic, peer: peer, presence: presence, memberCount: nil, state: ItemListAvatarAndNameInfoItemState(), sectionId: self.section, style: .blocks(withTopInset: true, withExtendedBottomInset: false), editingNameUpdated: { _ in
}, avatarTapped: {
}, action: {
arguments.openPeer()
})
case let .rightsHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
@ -265,6 +369,31 @@ private enum ChannelBannedMemberEntry: ItemListNodeEntry {
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: {
arguments.delete()
})
case let .rankTitle(_, text, count, limit):
var accessoryText: ItemListSectionHeaderAccessoryText?
if let count = count {
accessoryText = ItemListSectionHeaderAccessoryText(value: "\(limit - count)", color: count > limit ? .destructive : .generic)
}
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: accessoryText, sectionId: self.section)
case let .rankPreview(_, _, peer, rank, _):
let globalPresentationData = arguments.context.sharedContext.currentPresentationData.with { $0 }
return arguments.context.sharedContext.makeChatRankPreviewItem(context: arguments.context, peer: peer, rank: rank, rankRole: .member, theme: presentationData.theme, strings: presentationData.strings, wallpaper: globalPresentationData.chatWallpaper, fontSize: globalPresentationData.chatFontSize, chatBubbleCorners: globalPresentationData.chatBubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, sectionId: self.section)
case let .rank(_, _, placeholder, text, enabled):
return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: "", textColor: .black), text: text, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: true), spacing: 0.0, clearType: enabled ? .always : .none, enabled: enabled, tag: ChannelBannedMemberEntryTag.rank, sectionId: self.section, textUpdated: { updatedText in
arguments.updateRank(text, updatedText)
}, shouldUpdateText: { text in
if text.containsEmoji {
arguments.animateError()
return false
}
return true
}, updatedFocus: { focus in
arguments.updateFocusedOnRank(focus)
}, action: {
arguments.dismissInput()
})
case let .rankInfo(_, text, trimBottomInset):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section, additionalOuterInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: trimBottomInset ? -44.0 : 0.0, right: 0.0))
}
}
}
@ -275,6 +404,8 @@ private struct ChannelBannedMemberControllerState: Equatable {
var updatedTimeout: Int32?
var updating: Bool = false
var expandedPermissions = Set<TelegramChatBannedRightsFlags>()
var updatedRank: String?
var focusedOnRank: Bool
}
func completeRights(_ flags: TelegramChatBannedRightsFlags) -> TelegramChatBannedRightsFlags {
@ -349,7 +480,22 @@ private func channelBannedMemberControllerEntries(presentationData: Presentation
index += 1
}
if !editMember {
if editMember {
let currentRank: String?
if let updatedRank = state.updatedRank {
currentRank = updatedRank
} else if let initialParticipant = initialParticipant {
currentRank = initialParticipant.rank
} else {
currentRank = nil
}
let rankEnabled = !state.updating
entries.append(.rankTitle(presentationData.theme, presentationData.strings.Group_EditAdmin_MemberTagTitle.uppercased(), rankEnabled && state.focusedOnRank ? Int32(currentRank?.count ?? 0) : nil, rankMaxLength))
entries.append(.rankPreview(presentationData.theme, presentationData.strings, member, currentRank ?? "0", false))
entries.append(.rank(presentationData.theme, presentationData.strings, presentationData.strings.EditRank_Placeholder, currentRank ?? "", rankEnabled))
entries.append(.rankInfo(presentationData.theme, presentationData.strings.Group_EditAdmin_MemberTagInfo(member.compactDisplayTitle).string, false))
} else {
entries.append(.timeout(presentationData.theme, presentationData.strings.GroupPermission_Duration, currentTimeoutString))
if let initialParticipant = initialParticipant, case let .member(_, _, _, banInfo?, _, _) = initialParticipant, let initialBannedBy = initialBannedBy {
@ -411,7 +557,23 @@ private func channelBannedMemberControllerEntries(presentationData: Presentation
index += 1
}
if !editMember {
if editMember {
let currentRank: String?
if let updatedRank = state.updatedRank {
currentRank = updatedRank
} else if let initialParticipant = initialParticipant {
currentRank = initialParticipant.rank
} else {
currentRank = nil
}
let rankEnabled = !state.updating
entries.append(.rankTitle(presentationData.theme, presentationData.strings.Group_EditAdmin_MemberTagTitle.uppercased(), rankEnabled && state.focusedOnRank ? Int32(currentRank?.count ?? 0) : nil, rankMaxLength))
entries.append(.rankPreview(presentationData.theme, presentationData.strings, member, currentRank ?? "0", false))
entries.append(.rank(presentationData.theme, presentationData.strings, presentationData.strings.EditRank_Placeholder, currentRank ?? "", rankEnabled))
entries.append(.rankInfo(presentationData.theme, presentationData.strings.Group_EditAdmin_MemberTagInfo(member.compactDisplayTitle).string, false))
} else {
entries.append(.timeout(presentationData.theme, presentationData.strings.GroupPermission_Duration, currentTimeoutString))
if let initialParticipant = initialParticipant, case let .member(_, _, _, banInfo?, _, _) = initialParticipant, let initialBannedBy = initialBannedBy {
@ -425,7 +587,7 @@ private func channelBannedMemberControllerEntries(presentationData: Presentation
}
public func channelBannedMemberController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: PeerId, memberId: PeerId, editMember: Bool = false, initialParticipant: ChannelParticipant?, updated: @escaping (TelegramChatBannedRights?) -> Void, upgradedToSupergroup: @escaping (PeerId, @escaping () -> Void) -> Void) -> ViewController {
let initialState = ChannelBannedMemberControllerState(referenceTimestamp: Int32(Date().timeIntervalSince1970), updatedFlags: nil, updatedTimeout: nil, updating: false)
let initialState = ChannelBannedMemberControllerState(referenceTimestamp: Int32(Date().timeIntervalSince1970), updatedFlags: nil, updatedTimeout: nil, updating: false, updatedRank: nil, focusedOnRank: false)
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let stateValue = Atomic(value: initialState)
let updateState: ((ChannelBannedMemberControllerState) -> ChannelBannedMemberControllerState) -> Void = { f in
@ -437,9 +599,15 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
let updateRightsDisposable = MetaDisposable()
actionsDisposable.add(updateRightsDisposable)
let updateRankDisposable = MetaDisposable()
actionsDisposable.add(updateRankDisposable)
var dismissImpl: (() -> Void)?
var presentControllerImpl: ((ViewController, Any?) -> Void)?
var pushControllerImpl: ((ViewController) -> Void)?
var dismissInputImpl: (() -> Void)?
var errorImpl: (() -> Void)?
var scrollToRankImpl: (() -> Void)?
let peerView = Promise<PeerView>()
peerView.set(context.account.viewTracker.peerView(peerId))
@ -471,8 +639,7 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
} else {
effectiveRightsFlags = defaultBannedRightsFlags
}
if rights == .banSendMedia {
if value {
effectiveRightsFlags.remove(rights)
@ -609,6 +776,38 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
})
])])
presentControllerImpl?(actionSheet, nil)
}, openPeer: {
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: memberId))
|> deliverOnMainQueue).start(next: { peer in
guard let peer else {
return
}
if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: updatedPresentationData, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
pushControllerImpl?(controller)
}
})
}, updateRank: { previousRank, updatedRank in
if updatedRank != previousRank {
updateState { state in
var state = state
state.updatedRank = updatedRank
return state
}
}
}, updateFocusedOnRank: { focusedOnRank in
updateState { state in
var state = state
state.focusedOnRank = focusedOnRank
return state
}
if focusedOnRank {
scrollToRankImpl?()
}
}, dismissInput: {
dismissInputImpl?()
}, animateError: {
errorImpl?()
})
var peerDataItems: [TelegramEngine.EngineData.Item.Peer.Peer] = []
@ -656,6 +855,16 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
return
}
var updateRank: String?
updateState { current in
updateRank = current.updatedRank?.trimmingCharacters(in: .whitespacesAndNewlines)
return current
}
if let updateRank = updateRank, updateRank.count > rankMaxLength || updateRank.containsEmoji {
errorImpl?()
return
}
var resolvedRights: TelegramChatBannedRights?
if let initialParticipant = initialParticipant {
var updateFlags: TelegramChatBannedRightsFlags?
@ -724,12 +933,27 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
previousRights = banInfo?.rights
}
let updateRankSignal: (PeerId) -> Signal<Void, NoError>
if let updateRank {
updateRankSignal = { peerId in
return context.peerChannelMemberCategoriesContextsManager.updateMemberRank(engine: context.engine, peerId: peerId, memberId: memberId, rank: updateRank)
|> `catch` { _ -> Signal<Void, NoError> in
return .single(Void())
}
}
} else {
updateRankSignal = { _ in return .complete() }
}
if let resolvedRights = resolvedRights, previousRights != resolvedRights {
let cleanResolvedRightsFlags = resolvedRights.flags.union(defaultBannedRightsFlags)
let cleanResolvedRights = TelegramChatBannedRights(flags: cleanResolvedRightsFlags, untilDate: resolvedRights.untilDate)
if cleanResolvedRights.flags.isEmpty && previousRights == nil {
dismissImpl?()
updateRankDisposable.set((updateRankSignal(peerId)
|> deliverOnMainQueue).start(completed: {
dismissImpl?()
}))
} else {
let applyRights: () -> Void = {
updateState { state in
@ -756,9 +980,15 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
guard let upgradedPeerId = upgradedPeerId else {
return .single(nil)
}
let rankSignal = updateRankSignal(upgradedPeerId)
return context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(engine: context.engine, peerId: upgradedPeerId, memberId: memberId, bannedRights: cleanResolvedRights)
|> mapToSignal { _ -> Signal<PeerId?, NoError> in
return .complete()
return rankSignal
|> mapToSignal { _ -> Signal<PeerId?, NoError> in
return .complete()
}
}
|> then(.single(upgradedPeerId))
}
@ -785,36 +1015,42 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
}))
} else {
updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(engine: context.engine, peerId: peerId, memberId: memberId, bannedRights: cleanResolvedRights)
|> deliverOnMainQueue).start(error: { _ in
}, completed: {
if previousRights == nil {
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.GroupPermission_AddSuccess, false)), nil)
}
updated(cleanResolvedRights.flags.isEmpty ? nil : cleanResolvedRights)
dismissImpl?()
}))
|> deliverOnMainQueue).start(error: { _ in
}, completed: {
if previousRights == nil {
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.GroupPermission_AddSuccess, false)), nil)
}
updated(cleanResolvedRights.flags.isEmpty ? nil : cleanResolvedRights)
dismissImpl?()
}))
updateRankDisposable.set(updateRankSignal(peerId).start())
}
}
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
let actionSheet = ActionSheetController(presentationData: presentationData)
var items: [ActionSheetItem] = []
items.append(ActionSheetTextItem(title: presentationData.strings.GroupPermission_ApplyAlertText(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string))
items.append(ActionSheetButtonItem(title: presentationData.strings.GroupPermission_ApplyAlertAction, color: .accent, font: .default, enabled: true, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
applyRights()
}))
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])])
presentControllerImpl?(actionSheet, nil)
applyRights()
// let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
// let actionSheet = ActionSheetController(presentationData: presentationData)
// var items: [ActionSheetItem] = []
// items.append(ActionSheetTextItem(title: presentationData.strings.GroupPermission_ApplyAlertText(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string))
// items.append(ActionSheetButtonItem(title: presentationData.strings.GroupPermission_ApplyAlertAction, color: .accent, font: .default, enabled: true, action: { [weak actionSheet] in
// actionSheet?.dismissAnimated()
// applyRights()
// }))
// actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
// ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
// actionSheet?.dismissAnimated()
// })
// ])])
// presentControllerImpl?(actionSheet, nil)
}
} else {
dismissImpl?()
updateRankDisposable.set((updateRankSignal(peerId)
|> deliverOnMainQueue).start(completed: {
dismissImpl?()
}))
}
})
}
@ -831,11 +1067,21 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
}
}
let footerItem = ChannelParticipantFooterItem(theme: presentationData.theme, title: footerButtonTitle, displayProgress: state.updating, action: {
rightButtonActionImpl()
})
let rightNavigationButton: ItemListNavigationButton?
let footerItem: ItemListControllerFooterItem?
if state.focusedOnRank {
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
rightButtonActionImpl()
})
footerItem = nil
} else {
rightNavigationButton = nil
footerItem = ChannelParticipantFooterItem(theme: presentationData.theme, title: footerButtonTitle, displayProgress: state.updating, action: {
rightButtonActionImpl()
})
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelBannedMemberControllerEntries(presentationData: presentationData, state: state, accountPeerId: context.account.peerId, channelPeer: channelPeer, memberPeer: memberPeer, memberPresence: memberPresence, initialParticipant: initialParticipant, initialBannedBy: initialBannedByPeer, editMember: editMember), style: .blocks, emptyStateItem: nil, footerItem: footerItem, animateChanges: true)
@ -856,5 +1102,41 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
pushControllerImpl = { [weak controller] c in
controller?.push(c)
}
let hapticFeedback = HapticFeedback()
errorImpl = { [weak controller] in
hapticFeedback.error()
controller?.forEachItemNode { itemNode in
if let itemNode = itemNode as? ItemListSingleLineInputItemNode {
itemNode.animateError()
}
}
}
scrollToRankImpl = { [weak controller] in
controller?.afterLayout({
guard let controller = controller else {
return
}
var resultItemNode: ListViewItemNode?
let _ = controller.frameForItemNode({ itemNode in
if let itemNode = itemNode as? ItemListSingleLineInputItemNode {
if let tag = itemNode.tag as? ChannelBannedMemberEntryTag, tag == .rank {
resultItemNode = itemNode
return true
}
}
return false
})
if let resultItemNode = resultItemNode {
Queue.mainQueue().after(0.1) {
controller.ensureItemNodeVisible(resultItemNode, atTop: true)
}
}
})
}
dismissInputImpl = { [weak controller] in
controller?.view.endEditing(true)
}
return controller
}

View file

@ -685,9 +685,15 @@ public func channelMembersController(context: AccountContext, updatedPresentatio
}))
}, openParticipant: { participant, isGroup in
if isGroup {
let controller = channelBannedMemberController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, memberId: participant.peer.id, editMember: true, initialParticipant: participant.participant, updated: { rights in
}, upgradedToSupergroup: { _, _ in })
pushControllerImpl?(controller)
if let _ = participant.participant.adminInfo {
let controller = channelAdminController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, adminId: participant.participant.peerId, initialParticipant: participant.participant, updated: { _ in
}, upgradedToSupergroup: { _, _ in }, transferedOwnership: { _ in })
pushControllerImpl?(controller)
} else {
let controller = channelBannedMemberController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, memberId: participant.peer.id, editMember: true, initialParticipant: participant.participant, updated: { rights in
}, upgradedToSupergroup: { _, _ in })
pushControllerImpl?(controller)
}
} else {
if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: participant.peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
pushControllerImpl?(infoController)

View file

@ -5322,6 +5322,22 @@ public extension Api.functions.messages {
})
}
}
public extension Api.functions.messages {
static func checkUrlAuthMatchCode(url: String, matchCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(-911967477)
serializeString(url, buffer: buffer, boxed: false)
serializeString(matchCode, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.checkUrlAuthMatchCode", parameters: [("url", String(describing: url)), ("matchCode", String(describing: matchCode))]), 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 clearAllDrafts() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()

View file

@ -4329,6 +4329,8 @@ func replayFinalState(
if !message.flags.contains(.Incoming), message.forwardInfo == nil {
if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(message.id.peerId.namespace), let peer = transaction.getPeer(message.id.peerId), peer.isCopyProtectionEnabled {
} else if message.id.peerId.namespace == Namespaces.Peer.CloudUser, let cachedUserData = transaction.getPeerCachedData(peerId: message.id.peerId) as? CachedUserData, cachedUserData.flags.contains(.copyProtectionEnabled) || cachedUserData.flags.contains(.myCopyProtectionEnabled) {
} else {
inner: for media in message.media {
if let file = media as? TelegramMediaFile {

View file

@ -178,6 +178,7 @@ public enum MessageActionUrlAuthResult {
public static let requestWriteAccess = Flags(rawValue: 1 << 0)
public static let requestPhoneNumber = Flags(rawValue: 1 << 1)
public static let showMatchCodesFirst = Flags(rawValue: 1 << 2)
}
public struct ClientData : Equatable {
@ -244,25 +245,29 @@ func _internal_requestMessageActionUrlAuth(account: Account, subject: MessageAct
return .default
}
switch result {
case .urlAuthResultDefault:
return .default
case let .urlAuthResultAccepted(urlAuthResultAcceptedData):
let url = urlAuthResultAcceptedData.url
return .accepted(url: url)
case let .urlAuthResultRequest(urlAuthResultRequestData):
let (apiFlags, bot, domain) = (urlAuthResultRequestData.flags, urlAuthResultRequestData.bot, urlAuthResultRequestData.domain)
var clientData: MessageActionUrlAuthResult.ClientData?
if let browser = urlAuthResultRequestData.browser, let platform = urlAuthResultRequestData.platform, let ip = urlAuthResultRequestData.ip, let region = urlAuthResultRequestData.region {
clientData = MessageActionUrlAuthResult.ClientData(browser: browser, platform: platform, ip: ip, region: region)
}
var flags: MessageActionUrlAuthResult.Flags = []
if (apiFlags & (1 << 0)) != 0 {
flags.insert(.requestWriteAccess)
}
if (apiFlags & (1 << 1)) != 0 {
flags.insert(.requestPhoneNumber)
}
return .request(domain: domain, bot: TelegramUser(user: bot), clientData: clientData, flags: flags, matchCodes: urlAuthResultRequestData.matchCodes, userIdHint: urlAuthResultRequestData.userIdHint.flatMap { EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) })
case .urlAuthResultDefault:
return .default
case let .urlAuthResultAccepted(urlAuthResultAcceptedData):
let url = urlAuthResultAcceptedData.url
return .accepted(url: url)
case let .urlAuthResultRequest(urlAuthResultRequestData):
let (apiFlags, bot, domain) = (urlAuthResultRequestData.flags, urlAuthResultRequestData.bot, urlAuthResultRequestData.domain)
var clientData: MessageActionUrlAuthResult.ClientData?
if let browser = urlAuthResultRequestData.browser, let platform = urlAuthResultRequestData.platform, let ip = urlAuthResultRequestData.ip, let region = urlAuthResultRequestData.region {
clientData = MessageActionUrlAuthResult.ClientData(browser: browser, platform: platform, ip: ip, region: region)
}
var flags: MessageActionUrlAuthResult.Flags = []
if (apiFlags & (1 << 0)) != 0 {
flags.insert(.requestWriteAccess)
}
if (apiFlags & (1 << 1)) != 0 {
flags.insert(.requestPhoneNumber)
}
if (apiFlags & (1 << 5)) != 0 {
flags.insert(.showMatchCodesFirst)
}
return .request(domain: domain, bot: TelegramUser(user: bot), clientData: clientData, flags: flags, matchCodes: urlAuthResultRequestData.matchCodes, userIdHint: urlAuthResultRequestData.userIdHint.flatMap { EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) })
}
}
}
@ -327,3 +332,13 @@ func _internal_acceptMessageActionUrlAuth(account: Account, subject: MessageActi
}
}
}
func _internal_checkUrlAuthMatchCode(account: Account, url: String, matchCode: String) -> Signal<Bool, NoError> {
return account.network.request(Api.functions.messages.checkUrlAuthMatchCode(url: url, matchCode: matchCode))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> map { result in
return result == .boolTrue
}
}

View file

@ -106,6 +106,10 @@ public extension TelegramEngine {
public func declineUrlAuth(url: String) -> Signal<Never, NoError> {
return _internal_declineUrlAuth(account: self.account, url: url)
}
public func checkUrlAuthMatchCode(url: String, matchCode: String) -> Signal<Bool, NoError> {
return _internal_checkUrlAuthMatchCode(account: self.account, url: url, matchCode: matchCode)
}
public func searchMessages(location: SearchMessagesLocation, query: String, state: SearchMessagesState?, centerId: MessageId? = nil, limit: Int32 = 100) -> Signal<(SearchMessagesResult, SearchMessagesState), NoError> {
return _internal_searchMessages(account: self.account, location: location, query: query, state: state, centerId: centerId, limit: limit)

View file

@ -45,7 +45,7 @@ public struct PresentationResourcesRootController {
public static func navigationShareIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationShareIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: theme.rootController.navigationBar.accentTextColor)
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: theme.rootController.navigationBar.buttonColor)
})
// return theme.image(PresentationResourceKey.navigationShareIcon.rawValue, generateShareButtonImage)
}
@ -113,7 +113,7 @@ public struct PresentationResourcesRootController {
public static func navigationMoreCircledIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationMoreCircledIcon.rawValue, { theme in
generateTintedImage(image: UIImage(bundleImageName: "Chat List/NavigationMore"), color: theme.rootController.navigationBar.accentTextColor)
generateTintedImage(image: UIImage(bundleImageName: "Chat List/NavigationMore"), color: theme.rootController.navigationBar.buttonColor)
})
}
@ -125,7 +125,7 @@ public struct PresentationResourcesRootController {
public static func navigationAddIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationAddIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/AddIcon"), color: theme.rootController.navigationBar.accentTextColor)
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/AddIcon"), color: theme.rootController.navigationBar.buttonColor)
})
}

View file

@ -36,6 +36,8 @@ swift_library(
"//submodules/AccountUtils",
"//submodules/ActivityIndicator",
"//submodules/TelegramUI/Components/PeerInfo/AccountPeerContextItem",
"//submodules/TelegramUI/Components/LottieComponent",
"//submodules/TelegramUI/Components/LottieComponentResourceContent",
],
visibility = [
"//visibility:public",

View file

@ -25,22 +25,27 @@ import AccountUtils
import GlassBackgroundComponent
import AccountPeerContextItem
import ActivityIndicator
import LottieComponent
import LottieComponentResourceContent
private final class AuthConfirmationSheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let requestSubject: MessageActionUrlSubject
let subject: MessageActionUrlAuthResult
let completion: (AccountContext, EnginePeer, AuthConfirmationScreen.Result) -> Void
let cancel: (Bool) -> Void
init(
context: AccountContext,
requestSubject: MessageActionUrlSubject,
subject: MessageActionUrlAuthResult,
completion: @escaping (AccountContext, EnginePeer, AuthConfirmationScreen.Result) -> Void,
cancel: @escaping (Bool) -> Void
) {
self.context = context
self.requestSubject = requestSubject
self.subject = subject
self.completion = completion
self.cancel = cancel
@ -55,7 +60,11 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
final class State: ComponentState {
private let context: AccountContext
private let requestSubject: MessageActionUrlSubject
private let subject: MessageActionUrlAuthResult
private let completion: (AccountContext, EnginePeer, AuthConfirmationScreen.Result) -> Void
private let disposables = DisposableSet()
var peer: EnginePeer?
var forcedAccount: (AccountContext, EnginePeer)?
@ -68,9 +77,11 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
var matchCodes: [String]?
var selectedMatchCode: String?
init(context: AccountContext, subject: MessageActionUrlAuthResult) {
init(context: AccountContext, requestSubject: MessageActionUrlSubject, subject: MessageActionUrlAuthResult, completion: @escaping (AccountContext, EnginePeer, AuthConfirmationScreen.Result) -> Void) {
self.context = context
self.requestSubject = requestSubject
self.subject = subject
self.completion = completion
super.init()
@ -82,6 +93,81 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
self.peer = peer
self.updated()
})
if case let .request(_, _, _, flags, matchCodes, _) = self.subject, let matchCodes {
if flags.contains(.showMatchCodesFirst) {
self.displayEmoji = true
self.matchCodes = matchCodes.shuffled()
} else {
for code in matchCodes {
var file: TelegramMediaFile?
if let item = context.animatedEmojiStickersValue[code] {
file = item.first?.file._parse()
} else if let item = context.animatedEmojiStickersValue[code.strippedEmoji] {
file = item.first?.file._parse()
}
if let file {
self.disposables.add(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: file), resource: file.resource).start())
}
}
}
}
if case let .request(_, _, _, _, _, userIdHint) = self.subject, let userIdHint, userIdHint != context.account.peerId {
let _ = (activeAccountsAndPeers(context: self.context, includePrimary: true)
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] primary, other in
guard let self else {
return
}
for (accountContext, peer, _) in other {
if peer.id == userIdHint {
self.forcedAccount = (accountContext, peer)
self.updated()
accountContext.account.shouldBeServiceTaskMaster.set(.single(.now))
let _ = accountContext.engine.messages.requestMessageActionUrlAuth(subject: requestSubject).start()
break
}
}
})
}
}
deinit {
self.disposables.dispose()
if !self.inProgress {
if let (context, _) = self.forcedAccount {
context.account.shouldBeServiceTaskMaster.set(.single(.never))
}
}
}
func checkMatchCode(_ matchCode: String) {
guard case let .url(url, _) = self.requestSubject else {
return
}
self.selectedMatchCode = matchCode
self.updated(transition: .easeInOut(duration: 0.2))
let _ = (self.context.engine.messages.checkUrlAuthMatchCode(url: url, matchCode: matchCode)
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let self else {
return
}
if result {
self.displayEmoji = false
self.updated(transition: .spring(duration: 0.4))
} else {
let accountContext = self.forcedAccount?.0 ?? self.context
guard let accountPeer = self.forcedAccount?.1 ?? self.peer else {
return
}
self.completion(accountContext, accountPeer, .failed)
}
})
}
func displayPhoneNumberConfirmation(commit: @escaping (Bool) -> Void) {
@ -132,6 +218,10 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
guard let self else {
return
}
if let (context, _) = self.forcedAccount {
context.account.shouldBeServiceTaskMaster.set(.single(.never))
}
self.forcedAccount = nil
self.updated()
}), true))
@ -147,6 +237,12 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
guard let self else {
return
}
if let (context, _) = self.forcedAccount {
context.account.shouldBeServiceTaskMaster.set(.single(.never))
}
accountContext.account.shouldBeServiceTaskMaster.set(.single(.now))
self.forcedAccount = (accountContext, peer)
self.updated()
}), true))
@ -161,7 +257,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
}
func makeState() -> State {
return State(context: self.context, subject: self.subject)
return State(context: self.context, requestSubject: self.requestSubject, subject: self.subject, completion: self.completion)
}
static var body: Body {
@ -191,13 +287,10 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
}
let presentationData = context.component.context.sharedContext.currentPresentationData.with { $0 }
let _ = strings
guard case let .request(domain, bot, clientData, flags, matchCodes, userIdHint) = component.subject else {
guard case let .request(domain, bot, clientData, flags, matchCodes, _) = component.subject else {
fatalError()
}
let _ = userIdHint
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
@ -241,7 +334,6 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
)
context.add(accountButton
.position(CGPoint(x: context.availableSize.width - 16.0 - accountButton.size.width / 2.0, y: 16.0 + accountButton.size.height / 2.0))
.scale(state.displayEmoji ? 0.0 : 1.0)
)
}
@ -338,12 +430,28 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
))
)
}
items.append(
AnyComponentWithIdentity(id: "icon", component: AnyComponent(
Text(text: code, font: Font.regular(32.0), color: .black)
))
)
var file: TelegramMediaFile?
if let item = component.context.animatedEmojiStickersValue[code] {
file = item.first?.file._parse()
} else if let item = component.context.animatedEmojiStickersValue[code.strippedEmoji] {
file = item.first?.file._parse()
}
if let file {
items.append(
AnyComponentWithIdentity(id: "animatedIcon", component: AnyComponent(
LottieComponent(content: LottieComponent.ResourceContent(context: component.context, file: file, attemptSynchronously: true, providesPlaceholder: true), placeholderColor: theme.list.mediaPlaceholderColor, startingPosition: .begin, size: CGSize(width: 32.0, height: 32.0), loop: true, playOnce: nil)
))
)
} else {
items.append(
AnyComponentWithIdentity(id: "staticIcon", component: AnyComponent(
Text(text: code, font: Font.regular(32.0), color: .black)
))
)
}
let subject = component.subject
let emoji = emojis[code].update(
component: AnyComponent(
PlainButtonComponent(
@ -351,9 +459,17 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
ZStack(items)
),
minSize: emojiSize,
action: {
complete(code)
action: { [weak state] in
guard let state else {
return
}
if case let .request(_, _, _, flags, _, _) = subject, flags.contains(.showMatchCodesFirst) {
state.checkMatchCode(code)
} else {
complete(code)
}
},
isEnabled: state.selectedMatchCode == nil,
animateAlpha: false,
animateScale: true
)
@ -364,6 +480,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
)
context.add(emoji
.position(CGPoint(x: emojiOriginX + emojiSize.width / 2.0, y: contentHeight + emojiSize.height / 2.0))
.opacity(state.selectedMatchCode != nil && state.selectedMatchCode != code ? 0.6 : 1.0)
.appear(ComponentTransition.Appear({ _, view, transition in
if !transition.animation.isImmediate {
transition.animateAlpha(view: view, from: 0.0, to: 1.0, delay: emojiDelay)
@ -631,17 +748,12 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
guard let state else {
return
}
if state.displayEmoji {
state.displayEmoji = false
state.updated(transition: .spring(duration: 0.4))
} else {
let accountContext = state.forcedAccount?.0 ?? component.context
guard let accountPeer = state.forcedAccount?.1 ?? state.peer else {
return
}
component.completion(accountContext, accountPeer, .decline)
component.cancel(true)
let accountContext = state.forcedAccount?.0 ?? component.context
guard let accountPeer = state.forcedAccount?.1 ?? state.peer else {
return
}
component.completion(accountContext, accountPeer, .decline)
component.cancel(true)
}
),
availableSize: CGSize(width: buttonWidth, height: 52.0),
@ -669,12 +781,12 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
guard let state else {
return
}
if let matchCodes, !matchCodes.isEmpty {
if !flags.contains(.showMatchCodesFirst), let matchCodes, !matchCodes.isEmpty {
state.displayEmoji = true
state.matchCodes = matchCodes.shuffled()
state.updated(transition: .spring(duration: 0.4))
} else {
complete(nil)
complete(state.selectedMatchCode)
}
}
),
@ -699,15 +811,18 @@ private final class AuthConfirmationSheetComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let requestSubject: MessageActionUrlSubject
let subject: MessageActionUrlAuthResult
let completion: (AccountContext, EnginePeer, AuthConfirmationScreen.Result) -> Void
init(
context: AccountContext,
requestSubject: MessageActionUrlSubject,
subject: MessageActionUrlAuthResult,
completion: @escaping (AccountContext, EnginePeer, AuthConfirmationScreen.Result) -> Void
) {
self.context = context
self.requestSubject = requestSubject
self.subject = subject
self.completion = completion
}
@ -731,6 +846,7 @@ private final class AuthConfirmationSheetComponent: CombinedComponent {
component: SheetComponent<EnvironmentType>(
content: AnyComponent<EnvironmentType>(AuthConfirmationSheetContent(
context: context.component.context,
requestSubject: context.component.requestSubject,
subject: context.component.subject,
completion: context.component.completion,
cancel: { animate in
@ -792,18 +908,22 @@ public class AuthConfirmationScreen: ViewControllerComponentContainer {
public enum Result {
case accept(allowWriteAccess: Bool, sharePhoneNumber: Bool, matchCode: String?)
case decline
case failed
}
private let context: AccountContext
private let requestSubject: MessageActionUrlSubject
private let subject: MessageActionUrlAuthResult
fileprivate let completion: (AccountContext, EnginePeer, AuthConfirmationScreen.Result) -> Void
public init(
context: AccountContext,
requestSubject: MessageActionUrlSubject,
subject: MessageActionUrlAuthResult,
completion: @escaping (AccountContext, EnginePeer, AuthConfirmationScreen.Result) -> Void
) {
self.context = context
self.requestSubject = requestSubject
self.subject = subject
self.completion = completion
@ -811,6 +931,7 @@ public class AuthConfirmationScreen: ViewControllerComponentContainer {
context: context,
component: AuthConfirmationSheetComponent(
context: context,
requestSubject: requestSubject,
subject: subject,
completion: completion
),

View file

@ -1054,7 +1054,7 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat
}
public func updatePeer(peer: Peer) {
if let previousPeer = self.peer, previousPeer.nameColor != peer.nameColor || previousPeer.smallProfileImage != peer.smallProfileImage {
if let previousPeer = self.peer, previousPeer.nameColor != peer.nameColor || previousPeer.smallProfileImage != peer.smallProfileImage || previousPeer.displayLetters != peer.displayLetters {
self.peer = peer
if peer.smallProfileImage != nil {
self.avatarNode.setPeerV2(context: self.context, theme: self.presentationData.theme.theme, peer: EnginePeer(peer), authorOfMessage: self.messageReference, overrideImage: nil, emptyColor: .black, synchronousLoad: false, displayDimensions: CGSize(width: avatarHeaderSize(), height: avatarHeaderSize()))

View file

@ -2343,7 +2343,13 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
return result
}, to: &text, entities: &entities)
} else {
appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_MessageRankNew(newRank), generateEntities: { index in
var result: [MessageTextEntityType] = []
if index == 0 {
result.append(.Bold)
}
return result
}, to: &text, entities: &entities)
}
var attributes: [MessageAttribute] = []
if !entities.isEmpty {

View file

@ -100,7 +100,6 @@ private final class PeerCopyProtectionInfoSheetContent: CombinedComponent {
let theme = environment.theme
let strings = environment.strings
let _ = strings
let sideInset: CGFloat = 30.0 + environment.safeInsets.left
let textSideInset: CGFloat = 30.0 + environment.safeInsets.left
@ -131,7 +130,7 @@ private final class PeerCopyProtectionInfoSheetContent: CombinedComponent {
let title = title.update(
component: BalancedTextComponent(
text: .plain(NSAttributedString(string: "Disable Sharing", font: titleFont, textColor: textColor)),
text: .plain(NSAttributedString(string: strings.DisableSharing_Title, font: titleFont, textColor: textColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.1
@ -150,9 +149,9 @@ private final class PeerCopyProtectionInfoSheetContent: CombinedComponent {
AnyComponentWithIdentity(
id: "screenshot",
component: AnyComponent(InfoParagraphComponent(
title: "No Screenshots",
title: strings.DisableSharing_Screenshot_Title,
titleColor: textColor,
text: "Disable screenshots and screen recordings in this chat.",
text: strings.DisableSharing_Screenshot_Text,
textColor: secondaryTextColor,
accentColor: linkColor,
iconName: "Premium/CopyProtection/NoScreenshot",
@ -164,9 +163,9 @@ private final class PeerCopyProtectionInfoSheetContent: CombinedComponent {
AnyComponentWithIdentity(
id: "forward",
component: AnyComponent(InfoParagraphComponent(
title: "No Forwarding",
title: strings.DisableSharing_Forwarding_Title,
titleColor: textColor,
text: "Disable message forwarding to other chats.",
text: strings.DisableSharing_Forwarding_Text,
textColor: secondaryTextColor,
accentColor: linkColor,
iconName: "Premium/CopyProtection/NoForward",
@ -178,9 +177,9 @@ private final class PeerCopyProtectionInfoSheetContent: CombinedComponent {
AnyComponentWithIdentity(
id: "save",
component: AnyComponent(InfoParagraphComponent(
title: "No Saving",
title: strings.DisableSharing_Saving_Title,
titleColor: textColor,
text: "Disable copying text and saving photos and videos to Photos.",
text: strings.DisableSharing_Saving_Text,
textColor: secondaryTextColor,
accentColor: linkColor,
iconName: "Premium/CopyProtection/NoDownload",
@ -227,7 +226,7 @@ private final class PeerCopyProtectionInfoSheetContent: CombinedComponent {
if component.context.isPremium {
buttonContent = AnyComponentWithIdentity(id: "disable", component: AnyComponent(
ButtonTextContentComponent(
text: "Disable Sharing",
text: strings.DisableSharing_Confirm,
badge: 0,
textColor: theme.list.itemCheckColors.foregroundColor,
badgeBackground: theme.list.itemCheckColors.foregroundColor,

View file

@ -232,11 +232,14 @@ final class RankChatPreviewItemNode: ListViewItemNode {
var items: [ListViewItem] = []
for messageItem in item.messageItems.reversed() {
let authorPeerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(0))
var userPeer = messageItem.peer._asPeer()
let updatedId = PeerId.Id._internalFromInt64Value(userPeer.id.id._internalGetInt64Value() - 7)
let authorPeerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: updatedId)
let groupPeerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(0))
let groupPeer = TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(0)), accessHash: nil, title: "", username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil, verificationIconFileId: nil, sendPaidMessageStars: nil, linkedMonoforumId: nil)
var userPeer = messageItem.peer._asPeer()
if let user = userPeer as? TelegramUser {
userPeer = TelegramUser(id: authorPeerId, accessHash: user.accessHash, firstName: user.firstName, lastName: user.lastName, username: "", phone: user.phone, photo: user.photo, botInfo: user.botInfo, restrictionInfo: user.restrictionInfo, flags: user.flags, emojiStatus: user.emojiStatus, usernames: user.usernames, storiesHidden: user.storiesHidden, nameColor: user.nameColor, backgroundEmojiId: user.backgroundEmojiId, profileColor: user.profileColor, profileBackgroundEmojiId: user.profileBackgroundEmojiId, subscriberCount: user.subscriberCount, verificationIconFileId: user.verificationIconFileId)
}

View file

@ -2811,13 +2811,19 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
let _ = (accountIdFromNotification(response.notification, sharedContext: self.sharedContextPromise.get())
|> deliverOnMainQueue).start(next: { accountId in
if response.actionIdentifier == UNNotificationDefaultActionIdentifier {
if let (peerId, threadId) = peerIdFromNotification(response.notification) {
var messageId: MessageId? = nil
if response.notification.request.content.categoryIdentifier == "c" || response.notification.request.content.categoryIdentifier == "t" {
messageId = messageIdFromNotification(peerId: peerId, notification: response.notification)
if let dataUrl = response.notification.request.content.userInfo["data_url"] as? String {
if let url = URL(string: dataUrl) {
self.openUrlWhenReady(url: url)
}
} else {
if let (peerId, threadId) = peerIdFromNotification(response.notification) {
var messageId: MessageId? = nil
if response.notification.request.content.categoryIdentifier == "c" || response.notification.request.content.categoryIdentifier == "t" {
messageId = messageIdFromNotification(peerId: peerId, notification: response.notification)
}
let storyId = storyIdFromNotification(peerId: peerId, notification: response.notification)
self.openChatWhenReady(accountId: accountId, peerId: peerId, threadId: threadId, messageId: messageId, storyId: storyId)
}
let storyId = storyIdFromNotification(peerId: peerId, notification: response.notification)
self.openChatWhenReady(accountId: accountId, peerId: peerId, threadId: threadId, messageId: messageId, storyId: storyId)
}
completionHandler()
} else if response.actionIdentifier == "reply", let (peerId, threadId) = peerIdFromNotification(response.notification), let accountId = accountId {

View file

@ -31,7 +31,17 @@ extension ChatControllerImpl {
guard let self else {
return
}
self.enqueueMediaMessages(fromGallery: false, signals: signals, silentPosting: false, scheduleTime: nil, replyToSubject: nil, parameters: nil, getAnimatedTransitionSource: nil, completion: {})
self.enqueueMediaMessages(
fromGallery: false,
signals: signals,
originalMediaReference: file.abstract,
silentPosting: false,
scheduleTime: nil,
replyToSubject: nil,
parameters: nil,
getAnimatedTransitionSource: nil,
completion: {}
)
},
present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a)

View file

@ -8452,18 +8452,38 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
}
func enqueueMediaMessages(fromGallery: Bool = false, signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, replyToSubject: ChatInterfaceState.ReplyMessageSubject? = nil, parameters: ChatSendMessageActionSheetController.SendParameters? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) {
func enqueueMediaMessages(
fromGallery: Bool = false,
signals: [Any]?,
originalMediaReference: AnyMediaReference? = nil,
silentPosting: Bool,
scheduleTime: Int32? = nil,
replyToSubject: ChatInterfaceState.ReplyMessageSubject? = nil,
parameters: ChatSendMessageActionSheetController.SendParameters? = nil,
getAnimatedTransitionSource: ((String) -> UIView?)? = nil,
completion: @escaping () -> Void = {}
) {
if let _ = self.presentationInterfaceState.sendPaidMessageStars {
self.presentPaidMessageAlertIfNeeded(count: Int32(signals?.count ?? 1), forceDark: fromGallery, completion: { [weak self] postpone in
self?.commitEnqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, postpone: postpone, parameters: parameters, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
})
} else {
self.commitEnqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, replyToSubject: replyToSubject, parameters: parameters, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
self.commitEnqueueMediaMessages(signals: signals, originalMediaReference: originalMediaReference, silentPosting: silentPosting, scheduleTime: scheduleTime, replyToSubject: replyToSubject, parameters: parameters, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
}
}
private func commitEnqueueMediaMessages(signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, postpone: Bool = false, replyToSubject: ChatInterfaceState.ReplyMessageSubject? = nil, parameters: ChatSendMessageActionSheetController.SendParameters? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) {
self.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(context: self.context, account: self.context.account, signals: signals!)
private func commitEnqueueMediaMessages(
signals: [Any]?,
originalMediaReference: AnyMediaReference? = nil,
silentPosting: Bool,
scheduleTime: Int32? = nil,
postpone: Bool = false,
replyToSubject: ChatInterfaceState.ReplyMessageSubject? = nil,
parameters: ChatSendMessageActionSheetController.SendParameters? = nil,
getAnimatedTransitionSource: ((String) -> UIView?)? = nil,
completion: @escaping () -> Void = {}
) {
self.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(context: self.context, account: self.context.account, signals: signals!, originalMediaReference: originalMediaReference)
|> deliverOnMainQueue).startStrict(next: { [weak self] items in
guard let strongSelf = self else {
return

View file

@ -1822,19 +1822,12 @@ func openResolvedUrlImpl(
|> deliverOnMainQueue).start(next: { result in
if case .request = result {
var dismissImpl: (() -> Void)?
let controller = AuthConfirmationScreen(context: context, subject: result, completion: { accountContext, accountPeer, authResult in
let controller = AuthConfirmationScreen(context: context, requestSubject: subject, subject: result, completion: { accountContext, accountPeer, authResult in
switch authResult {
case let .accept(allowWriteAccess, sharePhoneNumber, matchCode):
let signal: Signal<MessageActionUrlAuthResult, MessageActionUrlAuthError>
if accountContext === context {
signal = accountContext.engine.messages.acceptMessageActionUrlAuth(subject: subject, allowWriteAccess: allowWriteAccess, sharePhoneNumber: sharePhoneNumber, matchCode: matchCode)
} else {
accountContext.account.shouldBeServiceTaskMaster.set(.single(.now))
signal = accountContext.engine.messages.requestMessageActionUrlAuth(subject: subject)
|> castError(MessageActionUrlAuthError.self)
|> mapToSignal { result in
return accountContext.engine.messages.acceptMessageActionUrlAuth(subject: subject, allowWriteAccess: allowWriteAccess, sharePhoneNumber: sharePhoneNumber, matchCode: matchCode)
} |> afterDisposed {
let signal = accountContext.engine.messages.acceptMessageActionUrlAuth(subject: subject, allowWriteAccess: allowWriteAccess, sharePhoneNumber: sharePhoneNumber, matchCode: matchCode)
|> afterDisposed {
if accountContext !== context {
accountContext.account.shouldBeServiceTaskMaster.set(.single(.never))
}
}
@ -1851,8 +1844,13 @@ func openResolvedUrlImpl(
} else {
text = presentationData.strings.AuthConfirmation_LoginSuccess_Text(domain).string
}
let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: presentationData.strings.AuthConfirmation_LoginSuccess_Title, text: text, cancel: nil, destructive: false), action: { _ in return true })
(navigationController?.topViewController as? ViewController)?.present(controller, in: .window(.root))
if var topViewController = navigationController?.topViewController as? ViewController {
if let tabBarController = topViewController as? TabBarController, let controller = tabBarController.currentController {
topViewController = controller
}
let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: presentationData.strings.AuthConfirmation_LoginSuccess_Title, text: text, cancel: nil, destructive: false), action: { _ in return true })
topViewController.present(controller, in: .current)
}
}
}
@ -1882,8 +1880,9 @@ func openResolvedUrlImpl(
}
}
let isWebUrl = url.hasPrefix("http:") || url.hasPrefix("https:")
let openInOptions = availableOpenInOptions(context: context, item: .url(url: url))
if let match = openInOptions.first(where: { $0.identifier == browserIdentifier }), case let .openUrl(openUrl) = match.action() {
if isWebUrl, let match = openInOptions.first(where: { $0.identifier == browserIdentifier }), case let .openUrl(openUrl) = match.action() {
context.sharedContext.openExternalUrl(context: context, urlContext: .external, url: openUrl, forceExternal: true, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
} else {
context.sharedContext.openExternalUrl(context: context, urlContext: .external, url: url, forceExternal: true, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
@ -1892,21 +1891,52 @@ func openResolvedUrlImpl(
}, error: { _ in
dismissImpl?()
if case let .request(domain, _, _, _, _, _) = result {
let controller = UndoOverlayController(presentationData: presentationData, content: .info(title: presentationData.strings.AuthConfirmation_LoginFail_Title, text: presentationData.strings.AuthConfirmation_LoginFail_Text(domain).string, timeout: nil, customUndoText: nil), action: { _ in return true })
(navigationController?.topViewController as? ViewController)?.present(controller, in: .window(.root))
Queue.mainQueue().after(0.3) {
if case let .request(domain, _, _, _, _, _) = result {
if var topViewController = navigationController?.topViewController as? ViewController {
if let tabBarController = topViewController as? TabBarController, let controller = tabBarController.currentController {
topViewController = controller
}
let controller = UndoOverlayController(presentationData: presentationData, content: .info(title: presentationData.strings.AuthConfirmation_LoginFail_Title, text: presentationData.strings.AuthConfirmation_LoginFail_Text(domain).string, timeout: nil, customUndoText: nil), action: { _ in return true })
topViewController.present(controller, in: .current)
}
}
HapticFeedback().error()
}
HapticFeedback().error()
})
case .decline:
let _ = context.engine.messages.declineUrlAuth(url: url).start()
case .failed:
dismissImpl?()
Queue.mainQueue().after(0.3) {
if case let .request(domain, _, _, _, _, _) = result {
if var topViewController = navigationController?.topViewController as? ViewController {
if let tabBarController = topViewController as? TabBarController, let controller = tabBarController.currentController {
topViewController = controller
}
let controller = UndoOverlayController(presentationData: presentationData, content: .info(title: presentationData.strings.AuthConfirmation_LoginFail_Title, text: presentationData.strings.AuthConfirmation_LoginFail_Text(domain).string, timeout: nil, customUndoText: nil), action: { _ in return true })
topViewController.present(controller, in: .current)
}
}
HapticFeedback().error()
}
}
})
navigationController?.pushViewController(controller)
dismissImpl = { [weak controller] in
controller?.dismissAnimated()
}
} else {
if var topViewController = navigationController?.topViewController as? ViewController {
if let tabBarController = topViewController as? TabBarController, let controller = tabBarController.currentController {
topViewController = controller
}
let controller = UndoOverlayController(presentationData: presentationData, content: .info(title: presentationData.strings.AuthConfirmation_LoginFail_Title, text: presentationData.strings.AuthConfirmation_LoginFail_TextUnknown, timeout: nil, customUndoText: nil), action: { _ in return true })
topViewController.present(controller, in: .current)
}
}
})
}

View file

@ -514,7 +514,7 @@ public final class SharedWakeupManager {
return
}
guard let self else {
task.updateTitle(task.title, subtitle: "Finished")
task.updateTitle(task.title, subtitle: presentationData.strings.BackgroundTasks_MediaFinished)
task.setTaskCompleted(success: true)
return
}
@ -562,7 +562,7 @@ public final class SharedWakeupManager {
Task { @MainActor [weak self] in
guard let self else {
task.updateTitle(task.title, subtitle: "Finished")
task.updateTitle(task.title, subtitle: presentationData.strings.BackgroundTasks_MediaFinished)
task.setTaskCompleted(success: true)
return
}
@ -576,7 +576,7 @@ public final class SharedWakeupManager {
if self.backgroundProcessingTaskId != task.identifier || self.pendingMediaUploadsByKey.isEmpty {
self.backgroundProcessingTaskProgressByKey = [:]
task.updateTitle(task.title, subtitle: "Finished")
task.updateTitle(task.title, subtitle: presentationData.strings.BackgroundTasks_MediaFinished)
task.setTaskCompleted(success: true)
if self.backgroundProcessingTaskId == task.identifier {
self.backgroundProcessingTaskId = nil
@ -690,7 +690,7 @@ public final class SharedWakeupManager {
return
}
guard let self else {
task.updateTitle(task.title, subtitle: "Finished")
task.updateTitle(task.title, subtitle: presentationData.strings.BackgroundTasks_StoryFinished)
task.setTaskCompleted(success: true)
return
}
@ -738,7 +738,7 @@ public final class SharedWakeupManager {
Task { @MainActor [weak self] in
guard let self else {
task.updateTitle(task.title, subtitle: "Finished")
task.updateTitle(task.title, subtitle: presentationData.strings.BackgroundTasks_StoryFinished)
task.setTaskCompleted(success: true)
return
}
@ -754,7 +754,7 @@ public final class SharedWakeupManager {
if self.backgroundStoryProcessingTaskId != task.identifier || self.pendingStoryUploadStatusesByKey.isEmpty {
self.backgroundStoryProcessingTaskProgressByKey = [:]
task.updateTitle(task.title, subtitle: "Finished")
task.updateTitle(task.title, subtitle: presentationData.strings.BackgroundTasks_StoryFinished)
task.setTaskCompleted(success: true)
if self.backgroundStoryProcessingTaskId == task.identifier {
self.backgroundStoryProcessingTaskId = nil