mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-05 19:28:46 +02:00
Various fixes
This commit is contained in:
parent
532a3ae3e1
commit
1fa0c7991c
22 changed files with 401 additions and 195 deletions
|
|
@ -225,6 +225,7 @@ public struct ResolvedBotAdminRights: OptionSet {
|
|||
public static let manageVideoChats = ResolvedBotAdminRights(rawValue: 512)
|
||||
public static let canBeAnonymous = ResolvedBotAdminRights(rawValue: 1024)
|
||||
public static let manageChat = ResolvedBotAdminRights(rawValue: 2048)
|
||||
public static let manageTopics = ResolvedBotAdminRights(rawValue: 4096)
|
||||
|
||||
public var chatAdminRights: TelegramChatAdminRightsFlags? {
|
||||
var flags = TelegramChatAdminRightsFlags()
|
||||
|
|
@ -259,6 +260,9 @@ public struct ResolvedBotAdminRights: OptionSet {
|
|||
if self.contains(ResolvedBotAdminRights.canBeAnonymous) {
|
||||
flags.insert(.canBeAnonymous)
|
||||
}
|
||||
if self.contains(ResolvedBotAdminRights.manageTopics) {
|
||||
flags.insert(.canManageTopics)
|
||||
}
|
||||
|
||||
if flags.isEmpty && !self.contains(ResolvedBotAdminRights.manageChat) {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1103,6 +1103,27 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
|||
|
||||
self.isTopPanelExpandedUpdated(isExpanded: false, transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
}
|
||||
|
||||
public func revealHiddenPanels() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
guard case .hideOnScroll = component.panelHideBehavior else {
|
||||
return
|
||||
}
|
||||
guard let centralId = self.centralId, let contentView = self.contentViews[centralId] else {
|
||||
return
|
||||
}
|
||||
guard !contentView.wantsExclusiveMode else {
|
||||
return
|
||||
}
|
||||
guard contentView.scrollingPanelOffsetFraction != 0.0 else {
|
||||
return
|
||||
}
|
||||
|
||||
contentView.scrollingPanelOffsetFraction = 0.0
|
||||
self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.25, curve: .easeInOut)))
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
|
|
|
|||
|
|
@ -437,7 +437,7 @@ public class ContactsController: ViewController {
|
|||
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
|
||||
default:
|
||||
let presentationData = strongSelf.presentationData
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
self?.context.sharedContext.applicationBindings.openSettings()
|
||||
})]), in: .window(.root))
|
||||
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
|
||||
|
|
@ -787,7 +787,7 @@ public class ContactsController: ViewController {
|
|||
default:
|
||||
let presentationData = strongSelf.presentationData
|
||||
if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController, let topController = navigationController.topViewController as? ViewController {
|
||||
topController.present(textAlertController(context: strongSelf.context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
topController.present(textAlertController(context: strongSelf.context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
self?.context.sharedContext.applicationBindings.openSettings()
|
||||
})]), in: .window(.root))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -336,7 +336,7 @@ public final class DeviceAccess {
|
|||
case .ageVerification:
|
||||
text = presentationData.strings.AccessDenied_AgeVerificationCamera
|
||||
}
|
||||
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
openSettings()
|
||||
})]), nil)
|
||||
}
|
||||
|
|
@ -363,7 +363,7 @@ public final class DeviceAccess {
|
|||
text = presentationData.strings.AccessDenied_AgeVerificationCamera
|
||||
}
|
||||
}
|
||||
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
openSettings()
|
||||
})]), nil)
|
||||
}
|
||||
|
|
@ -392,7 +392,7 @@ public final class DeviceAccess {
|
|||
case .voiceCall:
|
||||
text = presentationData.strings.AccessDenied_CallMicrophone
|
||||
}
|
||||
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
openSettings()
|
||||
})]), nil)
|
||||
if case .voiceCall = microphoneSubject {
|
||||
|
|
@ -421,7 +421,7 @@ public final class DeviceAccess {
|
|||
case .qrCode:
|
||||
text = presentationData.strings.AccessDenied_QrCode
|
||||
}
|
||||
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
openSettings()
|
||||
})]), nil)
|
||||
}
|
||||
|
|
@ -462,7 +462,7 @@ public final class DeviceAccess {
|
|||
completion(false)
|
||||
if let presentationData = presentationData {
|
||||
let text = presentationData.strings.AccessDenied_LocationPreciseDenied
|
||||
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
openSettings()
|
||||
})]), nil)
|
||||
}
|
||||
|
|
@ -477,7 +477,7 @@ public final class DeviceAccess {
|
|||
completion(false)
|
||||
if let presentationData = presentationData {
|
||||
let text = presentationData.strings.AccessDenied_LocationAlwaysDenied
|
||||
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
openSettings()
|
||||
})]), nil)
|
||||
}
|
||||
|
|
@ -498,7 +498,7 @@ public final class DeviceAccess {
|
|||
} else {
|
||||
text = presentationData.strings.AccessDenied_LocationDisabled
|
||||
}
|
||||
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
openSettings()
|
||||
})]), nil)
|
||||
}
|
||||
|
|
@ -558,7 +558,7 @@ public final class DeviceAccess {
|
|||
}
|
||||
case .cellularData:
|
||||
if let presentationData = presentationData {
|
||||
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.Permissions_CellularDataTitle_v0, text: presentationData.strings.Permissions_CellularDataText_v0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.Permissions_CellularDataTitle_v0, text: presentationData.strings.Permissions_CellularDataText_v0, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
openSettings()
|
||||
})]), nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3923,21 +3923,35 @@ public func avatarMediaPickerController(
|
|||
final class PickerDelegate: NSObject, PHPickerViewControllerDelegate {
|
||||
var completion: ((Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void)?
|
||||
|
||||
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
||||
picker.dismiss(animated: true)
|
||||
|
||||
for item in results {
|
||||
if item.itemProvider.canLoadObject(ofClass: UIImage.self) {
|
||||
item.itemProvider.loadObject(ofClass: UIImage.self) { image, error in
|
||||
if let uiImage = image as? UIImage {
|
||||
Queue.mainQueue().async {
|
||||
self.completion?(uiImage, nil, CGRect(), nil, false, { _ in return nil }, {})
|
||||
}
|
||||
private func resolveResult(_ result: PHPickerResult) {
|
||||
if let assetIdentifier = result.assetIdentifier {
|
||||
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [assetIdentifier], options: nil)
|
||||
if let asset = fetchResult.firstObject {
|
||||
Queue.mainQueue().async {
|
||||
self.completion?(asset, nil, CGRect(), nil, false, { _ in return nil }, {})
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if result.itemProvider.canLoadObject(ofClass: UIImage.self) {
|
||||
result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, _ in
|
||||
if let uiImage = image as? UIImage {
|
||||
Queue.mainQueue().async {
|
||||
self?.completion?(uiImage, nil, CGRect(), nil, false, { _ in return nil }, {})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
||||
picker.dismiss(animated: true)
|
||||
|
||||
if let result = results.first {
|
||||
self.resolveResult(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let holder = PickerDelegate()
|
||||
|
|
@ -3945,7 +3959,7 @@ public func avatarMediaPickerController(
|
|||
|
||||
let openMediaPicker = {
|
||||
var configuration = PHPickerConfiguration(photoLibrary: .shared())
|
||||
configuration.filter = .images
|
||||
configuration.filter = .any(of: [.images, .videos])
|
||||
configuration.selectionLimit = 1
|
||||
|
||||
let picker = PHPickerViewController(configuration: configuration)
|
||||
|
|
|
|||
|
|
@ -673,6 +673,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
|||
.direct(.canManageRanks),
|
||||
.direct(.canPinMessages),
|
||||
.direct(.canManageTopics),
|
||||
.sub(.stories, storiesRelatedFlags),
|
||||
.direct(.canManageCalls),
|
||||
.direct(.canBeAnonymous),
|
||||
.direct(.canAddAdmins)
|
||||
|
|
@ -1553,6 +1554,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
|
|||
|
||||
dismissImpl?()
|
||||
}, completed: {
|
||||
updated(TelegramChatAdminRights(rights: updateFlags))
|
||||
dismissImpl?()
|
||||
}))
|
||||
} else if updateFlags != defaultFlags || updateRank != nil {
|
||||
|
|
|
|||
|
|
@ -478,6 +478,7 @@ public func sentShareItems(accountPeerId: PeerId, postbox: Postbox, network: Net
|
|||
|
||||
var mediaMessageCount = 0
|
||||
var consumedText = false
|
||||
var captionAssigned = false
|
||||
for item in items {
|
||||
switch item {
|
||||
case let .text(text):
|
||||
|
|
@ -493,11 +494,22 @@ public func sentShareItems(accountPeerId: PeerId, postbox: Postbox, network: Net
|
|||
case let .media(media):
|
||||
switch media {
|
||||
case let .media(reference):
|
||||
let captionText: String
|
||||
if !captionAssigned {
|
||||
if let file = reference.media as? TelegramMediaFile, file.isInstantVideo {
|
||||
captionText = ""
|
||||
} else {
|
||||
captionText = additionalText
|
||||
captionAssigned = true
|
||||
}
|
||||
} else {
|
||||
captionText = ""
|
||||
}
|
||||
var message = StandaloneSendEnqueueMessage(
|
||||
content: .arbitraryMedia(
|
||||
media: reference,
|
||||
text: StandaloneSendEnqueueMessage.Text(
|
||||
string: additionalText,
|
||||
string: captionText,
|
||||
entities: []
|
||||
)
|
||||
),
|
||||
|
|
|
|||
|
|
@ -864,7 +864,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
|||
}
|
||||
}
|
||||
|
||||
if threadId == nil, let peer = transaction.getPeer(peerId), (peer is TelegramChannel), peer.isForum {
|
||||
if threadId == nil, let peer = transaction.getPeer(peerId), (peer is TelegramChannel), peer.isForum {
|
||||
threadId = 1
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -217,6 +217,53 @@ public func standaloneSendEnqueueMessages(
|
|||
}
|
||||
}
|
||||
if allDone {
|
||||
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
return postbox.transaction { transaction -> Signal<Never, StandaloneSendMessagesError> in
|
||||
var state = transaction.getPeerChatState(peerId) as? SecretChatState
|
||||
|
||||
for (content, media, attributes) in allResults {
|
||||
var text: String = ""
|
||||
switch content.content {
|
||||
case let .text(textValue):
|
||||
text = textValue
|
||||
case let .media(_, textValue):
|
||||
text = textValue
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if let currentState = state, let updatedState = enqueueSecretChatUploadedMessageContent(
|
||||
transaction: transaction,
|
||||
peerId: peerId,
|
||||
state: currentState,
|
||||
content: content,
|
||||
text: text,
|
||||
attributes: attributes,
|
||||
media: media
|
||||
) {
|
||||
state = updatedState
|
||||
} else {
|
||||
return .fail(StandaloneSendMessagesError(peerId: peerId, reason: .none))
|
||||
}
|
||||
}
|
||||
|
||||
return managedSecretChatOutgoingOperations(
|
||||
auxiliaryMethods: auxiliaryMethods,
|
||||
postbox: postbox,
|
||||
network: network,
|
||||
accountPeerId: accountPeerId,
|
||||
mode: .standaloneComplete(peerId: peerId)
|
||||
)
|
||||
|> castError(StandaloneSendMessagesError.self)
|
||||
|> ignoreValues
|
||||
}
|
||||
|> castError(StandaloneSendMessagesError.self)
|
||||
|> switchToLatest
|
||||
|> map { _ -> StandaloneSendMessageStatus in
|
||||
}
|
||||
|> then(.single(.done))
|
||||
}
|
||||
|
||||
var sendSignals: [Signal<Never, StandaloneSendMessagesError>] = []
|
||||
|
||||
for (content, media, attributes) in allResults {
|
||||
|
|
@ -231,11 +278,10 @@ public func standaloneSendEnqueueMessages(
|
|||
}
|
||||
|
||||
sendSignals.append(sendUploadedMessageContent(
|
||||
auxiliaryMethods: auxiliaryMethods,
|
||||
postbox: postbox,
|
||||
network: network,
|
||||
stateManager: stateManager,
|
||||
accountPeerId: stateManager.accountPeerId,
|
||||
accountPeerId: accountPeerId,
|
||||
peerId: peerId,
|
||||
content: content,
|
||||
text: text,
|
||||
|
|
@ -256,8 +302,52 @@ public func standaloneSendEnqueueMessages(
|
|||
}
|
||||
}
|
||||
|
||||
private func enqueueSecretChatUploadedMessageContent(
|
||||
transaction: Transaction,
|
||||
peerId: PeerId,
|
||||
state: SecretChatState,
|
||||
content: PendingMessageUploadedContentAndReuploadInfo,
|
||||
text: String,
|
||||
attributes: [MessageAttribute],
|
||||
media: [Media]
|
||||
) -> SecretChatState? {
|
||||
var secretFile: SecretChatOutgoingFile?
|
||||
switch content.content {
|
||||
case let .secretMedia(file, size, key):
|
||||
if let fileReference = SecretChatOutgoingFileReference(file) {
|
||||
secretFile = SecretChatOutgoingFile(reference: fileReference, size: size, key: key)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
let layer: SecretChatLayer
|
||||
switch state.embeddedState {
|
||||
case .terminated, .handshake:
|
||||
return nil
|
||||
case .basicLayer:
|
||||
layer = .layer8
|
||||
case let .sequenceBasedLayer(sequenceState):
|
||||
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
|
||||
}
|
||||
|
||||
let messageContents = StandaloneSecretMessageContents(
|
||||
id: Int64.random(in: Int64.min ... Int64.max),
|
||||
text: text,
|
||||
attributes: attributes,
|
||||
media: media.first,
|
||||
file: secretFile
|
||||
)
|
||||
|
||||
let updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: peerId, operation: .sendStandaloneMessage(layer: layer, contents: messageContents), state: state)
|
||||
if updatedState != state {
|
||||
transaction.setPeerChatState(peerId, state: updatedState)
|
||||
}
|
||||
|
||||
return updatedState
|
||||
}
|
||||
|
||||
private func sendUploadedMessageContent(
|
||||
auxiliaryMethods: AccountAuxiliaryMethods,
|
||||
postbox: Postbox,
|
||||
network: Network,
|
||||
stateManager: AccountStateManager,
|
||||
|
|
@ -271,55 +361,7 @@ private func sendUploadedMessageContent(
|
|||
) -> Signal<Never, StandaloneSendMessagesError> {
|
||||
return postbox.transaction { transaction -> Signal<Never, StandaloneSendMessagesError> in
|
||||
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
var secretFile: SecretChatOutgoingFile?
|
||||
switch content.content {
|
||||
case let .secretMedia(file, size, key):
|
||||
if let fileReference = SecretChatOutgoingFileReference(file) {
|
||||
secretFile = SecretChatOutgoingFile(reference: fileReference, size: size, key: key)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
var layer: SecretChatLayer?
|
||||
let state = transaction.getPeerChatState(peerId) as? SecretChatState
|
||||
if let state = state {
|
||||
switch state.embeddedState {
|
||||
case .terminated, .handshake:
|
||||
break
|
||||
case .basicLayer:
|
||||
layer = .layer8
|
||||
case let .sequenceBasedLayer(sequenceState):
|
||||
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
|
||||
}
|
||||
}
|
||||
|
||||
if let state = state, let layer = layer {
|
||||
let messageContents = StandaloneSecretMessageContents(
|
||||
id: Int64.random(in: Int64.min ... Int64.max),
|
||||
text: text,
|
||||
attributes: attributes,
|
||||
media: media.first,
|
||||
file: secretFile
|
||||
)
|
||||
|
||||
let updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: peerId, operation: .sendStandaloneMessage(layer: layer, contents: messageContents), state: state)
|
||||
if updatedState != state {
|
||||
transaction.setPeerChatState(peerId, state: updatedState)
|
||||
}
|
||||
|
||||
return managedSecretChatOutgoingOperations(
|
||||
auxiliaryMethods: auxiliaryMethods,
|
||||
postbox: postbox,
|
||||
network: network,
|
||||
accountPeerId: accountPeerId,
|
||||
mode: .standaloneComplete(peerId: peerId)
|
||||
)
|
||||
|> castError(StandaloneSendMessagesError.self)
|
||||
|> ignoreValues
|
||||
} else {
|
||||
return .fail(StandaloneSendMessagesError(peerId: peerId, reason: .none))
|
||||
}
|
||||
return .fail(StandaloneSendMessagesError(peerId: peerId, reason: .none))
|
||||
} else if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
var uniqueId: Int64 = 0
|
||||
var forwardSourceInfoAttribute: ForwardSourceInfoAttribute?
|
||||
|
|
|
|||
|
|
@ -1038,10 +1038,22 @@ public final class PendingMessageManager {
|
|||
|
||||
var flags: Int32 = 0
|
||||
|
||||
var topMsgId: Int32?
|
||||
var monoforumPeerId: Api.InputPeer?
|
||||
if let threadId = messages[0].0.threadId {
|
||||
if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum) {
|
||||
if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = transaction.getPeer(linkedMonoforumId) as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
|
||||
monoforumPeerId = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer)
|
||||
}
|
||||
} else {
|
||||
topMsgId = Int32(clamping: threadId)
|
||||
}
|
||||
}
|
||||
|
||||
for attribute in messages[0].0.attributes {
|
||||
if let replyAttribute = attribute as? ReplyMessageAttribute {
|
||||
replyMessageId = replyAttribute.messageId.id
|
||||
if peerId != replyAttribute.messageId.peerId {
|
||||
if peerId != replyAttribute.messageId.peerId || (replyAttribute.threadMessageId != nil && replyAttribute.threadMessageId?.id != topMsgId) {
|
||||
replyPeerId = replyAttribute.messageId.peerId
|
||||
}
|
||||
if replyAttribute.isQuote {
|
||||
|
|
@ -1134,19 +1146,10 @@ public final class PendingMessageManager {
|
|||
}
|
||||
}
|
||||
|
||||
var topMsgId: Int32?
|
||||
var monoforumPeerId: Api.InputPeer?
|
||||
if let threadId = messages[0].0.threadId {
|
||||
if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum) {
|
||||
if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = transaction.getPeer(linkedMonoforumId) as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
|
||||
monoforumPeerId = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer)
|
||||
}
|
||||
} else {
|
||||
flags |= Int32(1 << 9)
|
||||
topMsgId = Int32(clamping: threadId)
|
||||
}
|
||||
if topMsgId != nil {
|
||||
flags |= Int32(1 << 9)
|
||||
}
|
||||
|
||||
|
||||
var quickReplyShortcut: Api.InputQuickReplyShortcut?
|
||||
if let quickReply {
|
||||
if let threadId = messages[0].0.threadId {
|
||||
|
|
@ -1233,20 +1236,7 @@ public final class PendingMessageManager {
|
|||
if bubbleUpEmojiOrStickersets {
|
||||
flags |= Int32(1 << 15)
|
||||
}
|
||||
|
||||
var topMsgId: Int32?
|
||||
var monoforumPeerId: Api.InputPeer?
|
||||
if let threadId = messages[0].0.threadId {
|
||||
if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum) {
|
||||
if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = transaction.getPeer(linkedMonoforumId) as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
|
||||
monoforumPeerId = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer)
|
||||
}
|
||||
} else {
|
||||
flags |= Int32(1 << 9)
|
||||
topMsgId = Int32(clamping: threadId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var replyTo: Api.InputReplyTo?
|
||||
if let replyMessageId = replyMessageId {
|
||||
flags |= 1 << 0
|
||||
|
|
@ -1571,7 +1561,7 @@ public final class PendingMessageManager {
|
|||
for attribute in message.attributes {
|
||||
if let replyAttribute = attribute as? ReplyMessageAttribute {
|
||||
replyMessageId = replyAttribute.messageId.id
|
||||
if peer.id != replyAttribute.messageId.peerId {
|
||||
if peer.id != replyAttribute.messageId.peerId || (replyAttribute.threadMessageId != nil && replyAttribute.threadMessageId?.id != topMsgId) {
|
||||
replyPeerId = replyAttribute.messageId.peerId
|
||||
}
|
||||
if replyAttribute.isQuote {
|
||||
|
|
@ -1851,19 +1841,9 @@ public final class PendingMessageManager {
|
|||
sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, effect: messageEffectId, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag)
|
||||
|> map(NetworkRequestResult.result)
|
||||
case let .forward(sourceInfo):
|
||||
var topMsgId: Int32?
|
||||
var monoforumPeerId: Api.InputPeer?
|
||||
if let threadId = message.threadId {
|
||||
if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum) {
|
||||
if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = transaction.getPeer(linkedMonoforumId) as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
|
||||
monoforumPeerId = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer)
|
||||
}
|
||||
} else {
|
||||
flags |= Int32(1 << 9)
|
||||
topMsgId = Int32(clamping: threadId)
|
||||
}
|
||||
if topMsgId != nil {
|
||||
flags |= Int32(1 << 9)
|
||||
}
|
||||
|
||||
var quickReplyShortcut: Api.InputQuickReplyShortcut?
|
||||
if let quickReply {
|
||||
if let threadId = message.threadId {
|
||||
|
|
|
|||
|
|
@ -1803,7 +1803,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
guard let textSelectionNode = self.textSelectionNode else {
|
||||
return nil
|
||||
}
|
||||
guard let range = customRange ?? textSelectionNode.getSelection() else {
|
||||
guard let rawRange = customRange ?? textSelectionNode.getSelection() else {
|
||||
return nil
|
||||
}
|
||||
guard let item = self.item else {
|
||||
|
|
@ -1813,6 +1813,17 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
return nil
|
||||
}
|
||||
|
||||
func normalizedSelectionRange(_ range: NSRange, length: Int) -> NSRange {
|
||||
let location = min(max(range.location, 0), length)
|
||||
let upperBound = min(max(location, range.location + range.length), length)
|
||||
return NSRange(location: location, length: upperBound - location)
|
||||
}
|
||||
|
||||
let range = normalizedSelectionRange(rawRange, length: string.length)
|
||||
guard range.length > 0 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let nsString = string.string as NSString
|
||||
let substring = nsString.substring(with: range)
|
||||
let offset = range.location
|
||||
|
|
|
|||
|
|
@ -794,10 +794,12 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||
if let emojiAttribute = emojiAttribute {
|
||||
AudioServicesPlaySystemSound(0x450)
|
||||
interaction.insertText(NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: emojiAttribute]))
|
||||
(strongSelf.entityKeyboardView.view as? EntityKeyboardComponent.View)?.revealHiddenPanels()
|
||||
}
|
||||
} else if case let .staticEmoji(staticEmoji) = item.content {
|
||||
AudioServicesPlaySystemSound(0x450)
|
||||
interaction.insertText(NSAttributedString(string: staticEmoji, attributes: [:]))
|
||||
(strongSelf.entityKeyboardView.view as? EntityKeyboardComponent.View)?.revealHiddenPanels()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1005,6 +1005,14 @@ public final class EntityKeyboardComponent: Component {
|
|||
pagerView.collapseTopPanel()
|
||||
}
|
||||
|
||||
public func revealHiddenPanels() {
|
||||
guard let pagerView = self.pagerView.findTaggedView(tag: PagerComponentViewTag()) as? PagerComponent<EntityKeyboardChildEnvironment, EntityKeyboardTopContainerPanelEnvironment>.View else {
|
||||
return
|
||||
}
|
||||
|
||||
pagerView.revealHiddenPanels()
|
||||
}
|
||||
|
||||
private func reorderPacks(category: ReorderCategory, items: [EntityKeyboardTopPanelComponent.Item]) {
|
||||
self.component?.reorderItems(category, items)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -666,7 +666,11 @@ extension PeerInfoScreenImpl {
|
|||
|
||||
}
|
||||
if mainController is ActionSheetController {
|
||||
self.present(mainController, in: .window(.root))
|
||||
if let navigationController = self.navigationController, let topController = navigationController.topViewController as? ViewController {
|
||||
topController.present(mainController, in: .window(.root))
|
||||
} else {
|
||||
self.present(mainController, in: .window(.root))
|
||||
}
|
||||
} else {
|
||||
mainController.navigationPresentation = .flatModal
|
||||
mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
|
|
|
|||
|
|
@ -443,35 +443,60 @@ final class AuthorizedApplicationContext {
|
|||
|
||||
return false
|
||||
}
|
||||
|
||||
if let minimizedContainer = strongSelf.rootController.minimizedContainer, minimizedContainer.isExpanded {
|
||||
minimizedContainer.collapse()
|
||||
} else if let topContoller = strongSelf.rootController.topViewController as? AttachmentController {
|
||||
topContoller.minimizeIfNeeded()
|
||||
} else if let topContoller = strongSelf.rootController.topViewController as? BrowserScreen {
|
||||
topContoller.requestMinimize(topEdgeOffset: nil, initialVelocity: nil)
|
||||
|
||||
let proceedAction: (Bool) -> Bool = { allowExpansion in
|
||||
if let minimizedContainer = strongSelf.rootController.minimizedContainer, minimizedContainer.isExpanded {
|
||||
minimizedContainer.collapse()
|
||||
} else if let topContoller = strongSelf.rootController.topViewController as? AttachmentController {
|
||||
topContoller.minimizeIfNeeded()
|
||||
} else if let topContoller = strongSelf.rootController.topViewController as? BrowserScreen {
|
||||
topContoller.requestMinimize(topEdgeOffset: nil, initialVelocity: nil)
|
||||
}
|
||||
|
||||
for controller in strongSelf.rootController.viewControllers {
|
||||
if let controller = controller as? ChatControllerImpl, controller.chatLocation.peerId == chatLocation.peerId, (controller.chatLocation.threadId == nil || controller.chatLocation.threadId == chatLocation.threadId) {
|
||||
if allowExpansion {
|
||||
return true
|
||||
} else {
|
||||
strongSelf.notificationController.removeItemsWithGroupingKey(firstMessage.id.peerId)
|
||||
|
||||
let chatController = ChatControllerImpl(context: strongSelf.context, chatLocation: chatLocation.asChatLocation, mode: .overlay(strongSelf.rootController))
|
||||
let presentationArguments = ChatControllerOverlayPresentationData(expandData: (nil, {}))
|
||||
chatController.presentationArguments = presentationArguments
|
||||
(strongSelf.rootController.viewControllers.last as? ViewController)?.present(chatController, in: .window(.root), with: presentationArguments)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.notificationController.removeItemsWithGroupingKey(firstMessage.id.peerId)
|
||||
|
||||
var processed = false
|
||||
for media in firstMessage.media {
|
||||
if let action = media as? TelegramMediaAction, case .geoProximityReached = action.action {
|
||||
strongSelf.context.sharedContext.openLocationScreen(context: strongSelf.context, messageId: firstMessage.id, navigationController: strongSelf.rootController)
|
||||
processed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !processed {
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: strongSelf.rootController, context: strongSelf.context, chatLocation: chatLocation))
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
for controller in strongSelf.rootController.viewControllers {
|
||||
if let controller = controller as? ChatControllerImpl, controller.chatLocation.peerId == chatLocation.peerId, (controller.chatLocation.threadId == nil || controller.chatLocation.threadId == chatLocation.threadId) {
|
||||
return true
|
||||
|
||||
if let topController = strongSelf.rootController.topViewController as? ChatControllerImpl {
|
||||
let didPresentAlert = topController.presentVoiceMessageDiscardAlert(action: {
|
||||
let _ = proceedAction(false)
|
||||
}, discardIfVideo: true, performAction: false)
|
||||
if didPresentAlert {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.notificationController.removeItemsWithGroupingKey(firstMessage.id.peerId)
|
||||
|
||||
var processed = false
|
||||
for media in firstMessage.media {
|
||||
if let action = media as? TelegramMediaAction, case .geoProximityReached = action.action {
|
||||
strongSelf.context.sharedContext.openLocationScreen(context: strongSelf.context, messageId: firstMessage.id, navigationController: strongSelf.rootController)
|
||||
processed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !processed {
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: strongSelf.rootController, context: strongSelf.context, chatLocation: chatLocation))
|
||||
}
|
||||
return proceedAction(true)
|
||||
}
|
||||
return false
|
||||
}, expandAction: { expandData in
|
||||
|
|
|
|||
|
|
@ -27,6 +27,17 @@ private enum OptionsId: Hashable {
|
|||
case link
|
||||
}
|
||||
|
||||
private func chatLocationMatchesDestination(_ chatLocation: ChatLocation, peerId: EnginePeer.Id, threadId: Int64?) -> Bool {
|
||||
switch chatLocation {
|
||||
case let .peer(id):
|
||||
return id == peerId && threadId == nil
|
||||
case let .replyThread(replyThreadMessage):
|
||||
return replyThreadMessage.peerId == peerId && replyThreadMessage.threadId == threadId
|
||||
case .customChatContents:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private func presentChatInputOptions(selfController: ChatControllerImpl, sourceView: UIView, initialId: OptionsId) {
|
||||
var getContextController: (() -> ContextController?)?
|
||||
|
||||
|
|
@ -623,14 +634,13 @@ func moveReplyMessageToAnotherChat(selfController: ChatControllerImpl, replySubj
|
|||
return
|
||||
}
|
||||
let peerId = peer.id
|
||||
//let accountPeerId = selfController.context.account.peerId
|
||||
|
||||
var isPinnedMessages = false
|
||||
if case .pinnedMessages = selfController.presentationInterfaceState.subject {
|
||||
isPinnedMessages = true
|
||||
}
|
||||
|
||||
if case .peer(peerId) = selfController.chatLocation, selfController.parentController == nil, !isPinnedMessages {
|
||||
if chatLocationMatchesDestination(selfController.chatLocation, peerId: peerId, threadId: threadId), selfController.parentController == nil, !isPinnedMessages {
|
||||
selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(replySubject).withoutSelectionState() }).updatedSearch(nil) })
|
||||
selfController.updateItemNodesSearchTextHighlightStates()
|
||||
selfController.searchResultsController = nil
|
||||
|
|
@ -651,7 +661,7 @@ func moveReplyToChat(selfController: ChatControllerImpl, peerId: EnginePeer.Id,
|
|||
if let navigationController = selfController.effectiveNavigationController {
|
||||
for controller in navigationController.viewControllers {
|
||||
if let maybeChat = controller as? ChatControllerImpl {
|
||||
if case .peer(peerId) = maybeChat.chatLocation {
|
||||
if chatLocationMatchesDestination(maybeChat.chatLocation, peerId: peerId, threadId: threadId) {
|
||||
var isChatPinnedMessages = false
|
||||
if case .pinnedMessages = maybeChat.presentationInterfaceState.subject {
|
||||
isChatPinnedMessages = true
|
||||
|
|
|
|||
|
|
@ -10304,21 +10304,35 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
self.present(controller, in: .window(.root))
|
||||
}
|
||||
|
||||
func presentVoiceMessageDiscardAlert(action: @escaping () -> Void = {}, alertAction: (() -> Void)? = nil, delay: Bool = false, performAction: Bool = true) -> Bool {
|
||||
if let _ = self.presentationInterfaceState.inputTextPanelState.mediaRecordingState {
|
||||
func presentVoiceMessageDiscardAlert(
|
||||
action: @escaping () -> Void = {},
|
||||
alertAction: (() -> Void)? = nil,
|
||||
discardIfVideo: Bool = false,
|
||||
delay: Bool = false,
|
||||
performAction: Bool = true
|
||||
) -> Bool {
|
||||
if let mediaRecordingState = self.presentationInterfaceState.inputTextPanelState.mediaRecordingState {
|
||||
var discard = false
|
||||
if discardIfVideo, case .video = mediaRecordingState {
|
||||
discard = true
|
||||
}
|
||||
alertAction?()
|
||||
Queue.mainQueue().after(delay ? 0.2 : 0.0) {
|
||||
let alertController = textAlertController(
|
||||
context: self.context,
|
||||
updatedPresentationData: self.updatedPresentationData,
|
||||
title: nil,
|
||||
text: self.presentationData.strings.Conversation_StopVoiceMessageDescription,
|
||||
text: discard ? self.presentationData.strings.Conversation_DiscardRecordedVoiceMessageDescription : self.presentationData.strings.Conversation_StopVoiceMessageDescription,
|
||||
actions: [
|
||||
TextAlertAction(
|
||||
type: .defaultAction,
|
||||
title: self.presentationData.strings.Conversation_StopVoiceMessagePauseAction,
|
||||
title: discard ? self.presentationData.strings.Conversation_DiscardRecordedVoiceMessageAction : self.presentationData.strings.Conversation_StopVoiceMessagePauseAction,
|
||||
action: { [weak self] in
|
||||
self?.stopMediaRecorder(pause: true)
|
||||
if discard {
|
||||
self?.dismissMediaRecorder(.dismiss)
|
||||
} else {
|
||||
self?.stopMediaRecorder(pause: true)
|
||||
}
|
||||
Queue.mainQueue().after(0.1) {
|
||||
action()
|
||||
}
|
||||
|
|
@ -10343,26 +10357,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func presentRecordedVoiceMessageDiscardAlert(action: @escaping () -> Void = {}, alertAction: (() -> Void)? = nil, delay: Bool = false, performAction: Bool = true) -> Bool {
|
||||
if let _ = self.presentationInterfaceState.interfaceState.mediaDraftState {
|
||||
alertAction?()
|
||||
Queue.mainQueue().after(delay ? 0.2 : 0.0) {
|
||||
self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.Conversation_DiscardRecordedVoiceMessageDescription, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Conversation_DiscardRecordedVoiceMessageAction, action: { [weak self] in
|
||||
self?.stopMediaRecorder()
|
||||
Queue.mainQueue().after(0.1) {
|
||||
action()
|
||||
}
|
||||
})]), in: .window(.root))
|
||||
}
|
||||
|
||||
return true
|
||||
} else if performAction {
|
||||
action()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
func presentAutoremoveSetup() {
|
||||
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ public class ComposeControllerImpl: ViewController, ComposeController {
|
|||
DeviceAccess.authorizeAccess(to: .contacts)
|
||||
default:
|
||||
let presentationData = strongSelf.presentationData
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
self?.context.sharedContext.applicationBindings.openSettings()
|
||||
})]), in: .window(.root))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ func openAddContactImpl(context: AccountContext, peer: EnginePeer?, firstName: S
|
|||
DeviceAccess.authorizeAccess(to: .contacts)
|
||||
default:
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
present(textAlertController(context: context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
present(textAlertController(context: context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
context.sharedContext.applicationBindings.openSettings()
|
||||
})]), nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,6 +120,15 @@ func openResolvedUrlImpl(
|
|||
}
|
||||
)
|
||||
}
|
||||
let isStartGroup: Bool
|
||||
switch peerType {
|
||||
case .group?:
|
||||
isStartGroup = true
|
||||
default:
|
||||
isStartGroup = false
|
||||
}
|
||||
let shouldForceStartGroupStartFlow = isStartGroup && !payload.isEmpty && adminRights == nil
|
||||
let shouldCheckExistingGroupAdmin = isStartGroup && !payload.isEmpty && adminRights != nil
|
||||
|
||||
var filter: ChatListNodePeersFilter = [.onlyGroupsAndChannels, .onlyManageable, .excludeDisabled, .excludeRecent, .doNotSearchMessages]
|
||||
var title: String = presentationData.strings.Bot_AddToChat_Title
|
||||
|
|
@ -198,42 +207,100 @@ func openResolvedUrlImpl(
|
|||
}
|
||||
}),
|
||||
TextAlertAction(type: .genericAction, title: strings.Common_Cancel, action: {})
|
||||
]
|
||||
],
|
||||
actionLayout: .vertical
|
||||
)
|
||||
present(alertController, nil)
|
||||
}
|
||||
let openAdminControllerImpl: (TelegramChatAdminRightsFlags?) -> Void = { initialAdminRights in
|
||||
let adminController = channelAdminController(context: context, peerId: peerId, adminId: botPeerId, initialParticipant: nil, invite: true, initialAdminRights: initialAdminRights, updated: { _ in
|
||||
if shouldCheckExistingGroupAdmin {
|
||||
Queue.mainQueue().after(0.1) {
|
||||
addMemberImpl()
|
||||
}
|
||||
} else {
|
||||
controller?.dismiss()
|
||||
}
|
||||
}, upgradedToSupergroup: { _, _ in }, transferedOwnership: { _ in })
|
||||
navigationController?.pushViewController(adminController)
|
||||
}
|
||||
let openAdminControllerWithResolvedRightsImpl: (Bool) -> Void = { isGroup in
|
||||
if adminRights == nil {
|
||||
let _ = (defaultAdminRights.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { defaultAdminRights in
|
||||
let initialAdminRights = isGroup ? defaultAdminRights?.group?.rights : defaultAdminRights?.channel?.rights
|
||||
openAdminControllerImpl(initialAdminRights)
|
||||
})
|
||||
} else {
|
||||
openAdminControllerImpl(adminRights?.chatAdminRights)
|
||||
}
|
||||
}
|
||||
|
||||
if case let .channel(peer) = peer {
|
||||
var isGroup = false
|
||||
if case .group = peer.info {
|
||||
isGroup = true
|
||||
}
|
||||
if peer.flags.contains(.isCreator) || peer.adminRights?.rights.contains(.canAddAdmins) == true {
|
||||
let _ = (defaultAdminRights.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { defaultAdminRights in
|
||||
let initialAdminRights = adminRights?.chatAdminRights ?? (isGroup ? defaultAdminRights?.group?.rights : defaultAdminRights?.channel?.rights)
|
||||
let controller = channelAdminController(context: context, peerId: peerId, adminId: botPeerId, initialParticipant: nil, invite: true, initialAdminRights: initialAdminRights, updated: { _ in
|
||||
controller?.dismiss()
|
||||
}, upgradedToSupergroup: { _, _ in }, transferedOwnership: { _ in })
|
||||
navigationController?.pushViewController(controller)
|
||||
})
|
||||
if shouldForceStartGroupStartFlow {
|
||||
addMemberImpl()
|
||||
} else if peer.flags.contains(.isCreator) || peer.adminRights?.rights.contains(.canAddAdmins) == true {
|
||||
if shouldCheckExistingGroupAdmin && isGroup {
|
||||
let _ = (context.engine.peers.fetchChannelParticipant(peerId: peerId, participantId: botPeerId)
|
||||
|> deliverOnMainQueue).start(next: { participant in
|
||||
let isBotAlreadyAdmin: Bool
|
||||
if let participant = participant {
|
||||
switch participant {
|
||||
case .creator:
|
||||
isBotAlreadyAdmin = true
|
||||
case let .member(_, _, adminInfo, _, _, _):
|
||||
isBotAlreadyAdmin = adminInfo != nil
|
||||
}
|
||||
} else {
|
||||
isBotAlreadyAdmin = false
|
||||
}
|
||||
if isBotAlreadyAdmin {
|
||||
addMemberImpl()
|
||||
} else {
|
||||
openAdminControllerWithResolvedRightsImpl(isGroup)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
openAdminControllerWithResolvedRightsImpl(isGroup)
|
||||
}
|
||||
} else {
|
||||
addMemberImpl()
|
||||
}
|
||||
} else if case let .legacyGroup(peer) = peer {
|
||||
if case .member = peer.role {
|
||||
if shouldForceStartGroupStartFlow {
|
||||
addMemberImpl()
|
||||
} else {
|
||||
let _ = (defaultAdminRights.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { defaultAdminRights in
|
||||
let initialAdminRights = adminRights?.chatAdminRights ?? defaultAdminRights?.group?.rights
|
||||
let controller = channelAdminController(context: context, peerId: peerId, adminId: botPeerId, initialParticipant: nil, invite: true, initialAdminRights: initialAdminRights, updated: { _ in
|
||||
controller?.dismiss()
|
||||
}, upgradedToSupergroup: { _, _ in }, transferedOwnership: { _ in })
|
||||
navigationController?.pushViewController(controller)
|
||||
} else if case .member = peer.role {
|
||||
addMemberImpl()
|
||||
} else if shouldCheckExistingGroupAdmin {
|
||||
let _ = (context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peerId)
|
||||
|> mapToSignal { _ in
|
||||
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.LegacyGroupParticipants(id: peerId))
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { participants in
|
||||
let isBotAlreadyAdmin: Bool
|
||||
if let participant = participants.knownValue?.first(where: { $0.peerId == botPeerId }) {
|
||||
switch participant {
|
||||
case .creator, .admin:
|
||||
isBotAlreadyAdmin = true
|
||||
case .member:
|
||||
isBotAlreadyAdmin = false
|
||||
}
|
||||
} else {
|
||||
isBotAlreadyAdmin = false
|
||||
}
|
||||
if isBotAlreadyAdmin {
|
||||
addMemberImpl()
|
||||
} else {
|
||||
openAdminControllerWithResolvedRightsImpl(true)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
openAdminControllerWithResolvedRightsImpl(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -788,7 +855,7 @@ func openResolvedUrlImpl(
|
|||
navigationController?.pushViewController(controller)
|
||||
default:
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
context.sharedContext.applicationBindings.openSettings()
|
||||
})]), nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -531,7 +531,13 @@ public final class TextSelectionNode: ASDisplayNode {
|
|||
})
|
||||
}
|
||||
|
||||
return adjustedRange
|
||||
func normalizedSelectionRange(_ range: NSRange, length: Int) -> NSRange {
|
||||
let location = min(max(range.location, 0), length)
|
||||
let upperBound = min(max(location, range.location + range.length), length)
|
||||
return NSRange(location: location, length: upperBound - location)
|
||||
}
|
||||
|
||||
return normalizedSelectionRange(adjustedRange, length: attributedString.length)
|
||||
}
|
||||
|
||||
private func convertSelectionFromOriginalText(attributedString: NSAttributedString, range: NSRange) -> NSRange {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@ extension ResolvedBotAdminRights {
|
|||
if components.contains("manage_chat") {
|
||||
rawValue |= ResolvedBotAdminRights.manageChat.rawValue
|
||||
}
|
||||
if components.contains("manage_topics") {
|
||||
rawValue |= ResolvedBotAdminRights.manageTopics.rawValue
|
||||
}
|
||||
if components.contains("anonymous") {
|
||||
rawValue |= ResolvedBotAdminRights.canBeAnonymous.rawValue
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue