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 manageVideoChats = ResolvedBotAdminRights(rawValue: 512)
|
||||||
public static let canBeAnonymous = ResolvedBotAdminRights(rawValue: 1024)
|
public static let canBeAnonymous = ResolvedBotAdminRights(rawValue: 1024)
|
||||||
public static let manageChat = ResolvedBotAdminRights(rawValue: 2048)
|
public static let manageChat = ResolvedBotAdminRights(rawValue: 2048)
|
||||||
|
public static let manageTopics = ResolvedBotAdminRights(rawValue: 4096)
|
||||||
|
|
||||||
public var chatAdminRights: TelegramChatAdminRightsFlags? {
|
public var chatAdminRights: TelegramChatAdminRightsFlags? {
|
||||||
var flags = TelegramChatAdminRightsFlags()
|
var flags = TelegramChatAdminRightsFlags()
|
||||||
|
|
@ -259,6 +260,9 @@ public struct ResolvedBotAdminRights: OptionSet {
|
||||||
if self.contains(ResolvedBotAdminRights.canBeAnonymous) {
|
if self.contains(ResolvedBotAdminRights.canBeAnonymous) {
|
||||||
flags.insert(.canBeAnonymous)
|
flags.insert(.canBeAnonymous)
|
||||||
}
|
}
|
||||||
|
if self.contains(ResolvedBotAdminRights.manageTopics) {
|
||||||
|
flags.insert(.canManageTopics)
|
||||||
|
}
|
||||||
|
|
||||||
if flags.isEmpty && !self.contains(ResolvedBotAdminRights.manageChat) {
|
if flags.isEmpty && !self.contains(ResolvedBotAdminRights.manageChat) {
|
||||||
return nil
|
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)))
|
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 {
|
public func makeView() -> View {
|
||||||
|
|
|
||||||
|
|
@ -437,7 +437,7 @@ public class ContactsController: ViewController {
|
||||||
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
|
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
|
||||||
default:
|
default:
|
||||||
let presentationData = strongSelf.presentationData
|
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()
|
self?.context.sharedContext.applicationBindings.openSettings()
|
||||||
})]), in: .window(.root))
|
})]), in: .window(.root))
|
||||||
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
|
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
|
||||||
|
|
@ -787,7 +787,7 @@ public class ContactsController: ViewController {
|
||||||
default:
|
default:
|
||||||
let presentationData = strongSelf.presentationData
|
let presentationData = strongSelf.presentationData
|
||||||
if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController, let topController = navigationController.topViewController as? ViewController {
|
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()
|
self?.context.sharedContext.applicationBindings.openSettings()
|
||||||
})]), in: .window(.root))
|
})]), in: .window(.root))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -336,7 +336,7 @@ public final class DeviceAccess {
|
||||||
case .ageVerification:
|
case .ageVerification:
|
||||||
text = presentationData.strings.AccessDenied_AgeVerificationCamera
|
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()
|
openSettings()
|
||||||
})]), nil)
|
})]), nil)
|
||||||
}
|
}
|
||||||
|
|
@ -363,7 +363,7 @@ public final class DeviceAccess {
|
||||||
text = presentationData.strings.AccessDenied_AgeVerificationCamera
|
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()
|
openSettings()
|
||||||
})]), nil)
|
})]), nil)
|
||||||
}
|
}
|
||||||
|
|
@ -392,7 +392,7 @@ public final class DeviceAccess {
|
||||||
case .voiceCall:
|
case .voiceCall:
|
||||||
text = presentationData.strings.AccessDenied_CallMicrophone
|
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()
|
openSettings()
|
||||||
})]), nil)
|
})]), nil)
|
||||||
if case .voiceCall = microphoneSubject {
|
if case .voiceCall = microphoneSubject {
|
||||||
|
|
@ -421,7 +421,7 @@ public final class DeviceAccess {
|
||||||
case .qrCode:
|
case .qrCode:
|
||||||
text = presentationData.strings.AccessDenied_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()
|
openSettings()
|
||||||
})]), nil)
|
})]), nil)
|
||||||
}
|
}
|
||||||
|
|
@ -462,7 +462,7 @@ public final class DeviceAccess {
|
||||||
completion(false)
|
completion(false)
|
||||||
if let presentationData = presentationData {
|
if let presentationData = presentationData {
|
||||||
let text = presentationData.strings.AccessDenied_LocationPreciseDenied
|
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()
|
openSettings()
|
||||||
})]), nil)
|
})]), nil)
|
||||||
}
|
}
|
||||||
|
|
@ -477,7 +477,7 @@ public final class DeviceAccess {
|
||||||
completion(false)
|
completion(false)
|
||||||
if let presentationData = presentationData {
|
if let presentationData = presentationData {
|
||||||
let text = presentationData.strings.AccessDenied_LocationAlwaysDenied
|
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()
|
openSettings()
|
||||||
})]), nil)
|
})]), nil)
|
||||||
}
|
}
|
||||||
|
|
@ -498,7 +498,7 @@ public final class DeviceAccess {
|
||||||
} else {
|
} else {
|
||||||
text = presentationData.strings.AccessDenied_LocationDisabled
|
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()
|
openSettings()
|
||||||
})]), nil)
|
})]), nil)
|
||||||
}
|
}
|
||||||
|
|
@ -558,7 +558,7 @@ public final class DeviceAccess {
|
||||||
}
|
}
|
||||||
case .cellularData:
|
case .cellularData:
|
||||||
if let presentationData = presentationData {
|
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()
|
openSettings()
|
||||||
})]), nil)
|
})]), nil)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3923,21 +3923,35 @@ public func avatarMediaPickerController(
|
||||||
final class PickerDelegate: NSObject, PHPickerViewControllerDelegate {
|
final class PickerDelegate: NSObject, PHPickerViewControllerDelegate {
|
||||||
var completion: ((Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void)?
|
var completion: ((Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void)?
|
||||||
|
|
||||||
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
private func resolveResult(_ result: PHPickerResult) {
|
||||||
picker.dismiss(animated: true)
|
if let assetIdentifier = result.assetIdentifier {
|
||||||
|
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [assetIdentifier], options: nil)
|
||||||
for item in results {
|
if let asset = fetchResult.firstObject {
|
||||||
if item.itemProvider.canLoadObject(ofClass: UIImage.self) {
|
Queue.mainQueue().async {
|
||||||
item.itemProvider.loadObject(ofClass: UIImage.self) { image, error in
|
self.completion?(asset, nil, CGRect(), nil, false, { _ in return nil }, {})
|
||||||
if let uiImage = image as? UIImage {
|
}
|
||||||
Queue.mainQueue().async {
|
return
|
||||||
self.completion?(uiImage, nil, CGRect(), nil, false, { _ in return nil }, {})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
let holder = PickerDelegate()
|
||||||
|
|
@ -3945,7 +3959,7 @@ public func avatarMediaPickerController(
|
||||||
|
|
||||||
let openMediaPicker = {
|
let openMediaPicker = {
|
||||||
var configuration = PHPickerConfiguration(photoLibrary: .shared())
|
var configuration = PHPickerConfiguration(photoLibrary: .shared())
|
||||||
configuration.filter = .images
|
configuration.filter = .any(of: [.images, .videos])
|
||||||
configuration.selectionLimit = 1
|
configuration.selectionLimit = 1
|
||||||
|
|
||||||
let picker = PHPickerViewController(configuration: configuration)
|
let picker = PHPickerViewController(configuration: configuration)
|
||||||
|
|
|
||||||
|
|
@ -673,6 +673,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
|
||||||
.direct(.canManageRanks),
|
.direct(.canManageRanks),
|
||||||
.direct(.canPinMessages),
|
.direct(.canPinMessages),
|
||||||
.direct(.canManageTopics),
|
.direct(.canManageTopics),
|
||||||
|
.sub(.stories, storiesRelatedFlags),
|
||||||
.direct(.canManageCalls),
|
.direct(.canManageCalls),
|
||||||
.direct(.canBeAnonymous),
|
.direct(.canBeAnonymous),
|
||||||
.direct(.canAddAdmins)
|
.direct(.canAddAdmins)
|
||||||
|
|
@ -1553,6 +1554,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
|
||||||
|
|
||||||
dismissImpl?()
|
dismissImpl?()
|
||||||
}, completed: {
|
}, completed: {
|
||||||
|
updated(TelegramChatAdminRights(rights: updateFlags))
|
||||||
dismissImpl?()
|
dismissImpl?()
|
||||||
}))
|
}))
|
||||||
} else if updateFlags != defaultFlags || updateRank != nil {
|
} else if updateFlags != defaultFlags || updateRank != nil {
|
||||||
|
|
|
||||||
|
|
@ -478,6 +478,7 @@ public func sentShareItems(accountPeerId: PeerId, postbox: Postbox, network: Net
|
||||||
|
|
||||||
var mediaMessageCount = 0
|
var mediaMessageCount = 0
|
||||||
var consumedText = false
|
var consumedText = false
|
||||||
|
var captionAssigned = false
|
||||||
for item in items {
|
for item in items {
|
||||||
switch item {
|
switch item {
|
||||||
case let .text(text):
|
case let .text(text):
|
||||||
|
|
@ -493,11 +494,22 @@ public func sentShareItems(accountPeerId: PeerId, postbox: Postbox, network: Net
|
||||||
case let .media(media):
|
case let .media(media):
|
||||||
switch media {
|
switch media {
|
||||||
case let .media(reference):
|
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(
|
var message = StandaloneSendEnqueueMessage(
|
||||||
content: .arbitraryMedia(
|
content: .arbitraryMedia(
|
||||||
media: reference,
|
media: reference,
|
||||||
text: StandaloneSendEnqueueMessage.Text(
|
text: StandaloneSendEnqueueMessage.Text(
|
||||||
string: additionalText,
|
string: captionText,
|
||||||
entities: []
|
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
|
threadId = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,53 @@ public func standaloneSendEnqueueMessages(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if allDone {
|
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>] = []
|
var sendSignals: [Signal<Never, StandaloneSendMessagesError>] = []
|
||||||
|
|
||||||
for (content, media, attributes) in allResults {
|
for (content, media, attributes) in allResults {
|
||||||
|
|
@ -231,11 +278,10 @@ public func standaloneSendEnqueueMessages(
|
||||||
}
|
}
|
||||||
|
|
||||||
sendSignals.append(sendUploadedMessageContent(
|
sendSignals.append(sendUploadedMessageContent(
|
||||||
auxiliaryMethods: auxiliaryMethods,
|
|
||||||
postbox: postbox,
|
postbox: postbox,
|
||||||
network: network,
|
network: network,
|
||||||
stateManager: stateManager,
|
stateManager: stateManager,
|
||||||
accountPeerId: stateManager.accountPeerId,
|
accountPeerId: accountPeerId,
|
||||||
peerId: peerId,
|
peerId: peerId,
|
||||||
content: content,
|
content: content,
|
||||||
text: text,
|
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(
|
private func sendUploadedMessageContent(
|
||||||
auxiliaryMethods: AccountAuxiliaryMethods,
|
|
||||||
postbox: Postbox,
|
postbox: Postbox,
|
||||||
network: Network,
|
network: Network,
|
||||||
stateManager: AccountStateManager,
|
stateManager: AccountStateManager,
|
||||||
|
|
@ -271,55 +361,7 @@ private func sendUploadedMessageContent(
|
||||||
) -> Signal<Never, StandaloneSendMessagesError> {
|
) -> Signal<Never, StandaloneSendMessagesError> {
|
||||||
return postbox.transaction { transaction -> Signal<Never, StandaloneSendMessagesError> in
|
return postbox.transaction { transaction -> Signal<Never, StandaloneSendMessagesError> in
|
||||||
if peerId.namespace == Namespaces.Peer.SecretChat {
|
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||||
var secretFile: SecretChatOutgoingFile?
|
return .fail(StandaloneSendMessagesError(peerId: peerId, reason: .none))
|
||||||
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))
|
|
||||||
}
|
|
||||||
} else if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
} else if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||||
var uniqueId: Int64 = 0
|
var uniqueId: Int64 = 0
|
||||||
var forwardSourceInfoAttribute: ForwardSourceInfoAttribute?
|
var forwardSourceInfoAttribute: ForwardSourceInfoAttribute?
|
||||||
|
|
|
||||||
|
|
@ -1038,10 +1038,22 @@ public final class PendingMessageManager {
|
||||||
|
|
||||||
var flags: Int32 = 0
|
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 {
|
for attribute in messages[0].0.attributes {
|
||||||
if let replyAttribute = attribute as? ReplyMessageAttribute {
|
if let replyAttribute = attribute as? ReplyMessageAttribute {
|
||||||
replyMessageId = replyAttribute.messageId.id
|
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
|
replyPeerId = replyAttribute.messageId.peerId
|
||||||
}
|
}
|
||||||
if replyAttribute.isQuote {
|
if replyAttribute.isQuote {
|
||||||
|
|
@ -1134,19 +1146,10 @@ public final class PendingMessageManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var topMsgId: Int32?
|
if topMsgId != nil {
|
||||||
var monoforumPeerId: Api.InputPeer?
|
flags |= Int32(1 << 9)
|
||||||
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 quickReplyShortcut: Api.InputQuickReplyShortcut?
|
var quickReplyShortcut: Api.InputQuickReplyShortcut?
|
||||||
if let quickReply {
|
if let quickReply {
|
||||||
if let threadId = messages[0].0.threadId {
|
if let threadId = messages[0].0.threadId {
|
||||||
|
|
@ -1233,20 +1236,7 @@ public final class PendingMessageManager {
|
||||||
if bubbleUpEmojiOrStickersets {
|
if bubbleUpEmojiOrStickersets {
|
||||||
flags |= Int32(1 << 15)
|
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?
|
var replyTo: Api.InputReplyTo?
|
||||||
if let replyMessageId = replyMessageId {
|
if let replyMessageId = replyMessageId {
|
||||||
flags |= 1 << 0
|
flags |= 1 << 0
|
||||||
|
|
@ -1571,7 +1561,7 @@ public final class PendingMessageManager {
|
||||||
for attribute in message.attributes {
|
for attribute in message.attributes {
|
||||||
if let replyAttribute = attribute as? ReplyMessageAttribute {
|
if let replyAttribute = attribute as? ReplyMessageAttribute {
|
||||||
replyMessageId = replyAttribute.messageId.id
|
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
|
replyPeerId = replyAttribute.messageId.peerId
|
||||||
}
|
}
|
||||||
if replyAttribute.isQuote {
|
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)
|
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)
|
|> map(NetworkRequestResult.result)
|
||||||
case let .forward(sourceInfo):
|
case let .forward(sourceInfo):
|
||||||
var topMsgId: Int32?
|
if topMsgId != nil {
|
||||||
var monoforumPeerId: Api.InputPeer?
|
flags |= Int32(1 << 9)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var quickReplyShortcut: Api.InputQuickReplyShortcut?
|
var quickReplyShortcut: Api.InputQuickReplyShortcut?
|
||||||
if let quickReply {
|
if let quickReply {
|
||||||
if let threadId = message.threadId {
|
if let threadId = message.threadId {
|
||||||
|
|
|
||||||
|
|
@ -1803,7 +1803,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||||
guard let textSelectionNode = self.textSelectionNode else {
|
guard let textSelectionNode = self.textSelectionNode else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
guard let range = customRange ?? textSelectionNode.getSelection() else {
|
guard let rawRange = customRange ?? textSelectionNode.getSelection() else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
guard let item = self.item else {
|
guard let item = self.item else {
|
||||||
|
|
@ -1813,6 +1813,17 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||||
return nil
|
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 nsString = string.string as NSString
|
||||||
let substring = nsString.substring(with: range)
|
let substring = nsString.substring(with: range)
|
||||||
let offset = range.location
|
let offset = range.location
|
||||||
|
|
|
||||||
|
|
@ -794,10 +794,12 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||||
if let emojiAttribute = emojiAttribute {
|
if let emojiAttribute = emojiAttribute {
|
||||||
AudioServicesPlaySystemSound(0x450)
|
AudioServicesPlaySystemSound(0x450)
|
||||||
interaction.insertText(NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: emojiAttribute]))
|
interaction.insertText(NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: emojiAttribute]))
|
||||||
|
(strongSelf.entityKeyboardView.view as? EntityKeyboardComponent.View)?.revealHiddenPanels()
|
||||||
}
|
}
|
||||||
} else if case let .staticEmoji(staticEmoji) = item.content {
|
} else if case let .staticEmoji(staticEmoji) = item.content {
|
||||||
AudioServicesPlaySystemSound(0x450)
|
AudioServicesPlaySystemSound(0x450)
|
||||||
interaction.insertText(NSAttributedString(string: staticEmoji, attributes: [:]))
|
interaction.insertText(NSAttributedString(string: staticEmoji, attributes: [:]))
|
||||||
|
(strongSelf.entityKeyboardView.view as? EntityKeyboardComponent.View)?.revealHiddenPanels()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1005,6 +1005,14 @@ public final class EntityKeyboardComponent: Component {
|
||||||
pagerView.collapseTopPanel()
|
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]) {
|
private func reorderPacks(category: ReorderCategory, items: [EntityKeyboardTopPanelComponent.Item]) {
|
||||||
self.component?.reorderItems(category, items)
|
self.component?.reorderItems(category, items)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -666,7 +666,11 @@ extension PeerInfoScreenImpl {
|
||||||
|
|
||||||
}
|
}
|
||||||
if mainController is ActionSheetController {
|
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 {
|
} else {
|
||||||
mainController.navigationPresentation = .flatModal
|
mainController.navigationPresentation = .flatModal
|
||||||
mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||||
|
|
|
||||||
|
|
@ -443,35 +443,60 @@ final class AuthorizedApplicationContext {
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if let minimizedContainer = strongSelf.rootController.minimizedContainer, minimizedContainer.isExpanded {
|
let proceedAction: (Bool) -> Bool = { allowExpansion in
|
||||||
minimizedContainer.collapse()
|
if let minimizedContainer = strongSelf.rootController.minimizedContainer, minimizedContainer.isExpanded {
|
||||||
} else if let topContoller = strongSelf.rootController.topViewController as? AttachmentController {
|
minimizedContainer.collapse()
|
||||||
topContoller.minimizeIfNeeded()
|
} else if let topContoller = strongSelf.rootController.topViewController as? AttachmentController {
|
||||||
} else if let topContoller = strongSelf.rootController.topViewController as? BrowserScreen {
|
topContoller.minimizeIfNeeded()
|
||||||
topContoller.requestMinimize(topEdgeOffset: nil, initialVelocity: nil)
|
} 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 topController = strongSelf.rootController.topViewController as? ChatControllerImpl {
|
||||||
if let controller = controller as? ChatControllerImpl, controller.chatLocation.peerId == chatLocation.peerId, (controller.chatLocation.threadId == nil || controller.chatLocation.threadId == chatLocation.threadId) {
|
let didPresentAlert = topController.presentVoiceMessageDiscardAlert(action: {
|
||||||
return true
|
let _ = proceedAction(false)
|
||||||
|
}, discardIfVideo: true, performAction: false)
|
||||||
|
if didPresentAlert {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.notificationController.removeItemsWithGroupingKey(firstMessage.id.peerId)
|
return proceedAction(true)
|
||||||
|
|
||||||
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
|
return false
|
||||||
}, expandAction: { expandData in
|
}, expandAction: { expandData in
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,17 @@ private enum OptionsId: Hashable {
|
||||||
case link
|
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) {
|
private func presentChatInputOptions(selfController: ChatControllerImpl, sourceView: UIView, initialId: OptionsId) {
|
||||||
var getContextController: (() -> ContextController?)?
|
var getContextController: (() -> ContextController?)?
|
||||||
|
|
||||||
|
|
@ -623,14 +634,13 @@ func moveReplyMessageToAnotherChat(selfController: ChatControllerImpl, replySubj
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let peerId = peer.id
|
let peerId = peer.id
|
||||||
//let accountPeerId = selfController.context.account.peerId
|
|
||||||
|
|
||||||
var isPinnedMessages = false
|
var isPinnedMessages = false
|
||||||
if case .pinnedMessages = selfController.presentationInterfaceState.subject {
|
if case .pinnedMessages = selfController.presentationInterfaceState.subject {
|
||||||
isPinnedMessages = true
|
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.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(replySubject).withoutSelectionState() }).updatedSearch(nil) })
|
||||||
selfController.updateItemNodesSearchTextHighlightStates()
|
selfController.updateItemNodesSearchTextHighlightStates()
|
||||||
selfController.searchResultsController = nil
|
selfController.searchResultsController = nil
|
||||||
|
|
@ -651,7 +661,7 @@ func moveReplyToChat(selfController: ChatControllerImpl, peerId: EnginePeer.Id,
|
||||||
if let navigationController = selfController.effectiveNavigationController {
|
if let navigationController = selfController.effectiveNavigationController {
|
||||||
for controller in navigationController.viewControllers {
|
for controller in navigationController.viewControllers {
|
||||||
if let maybeChat = controller as? ChatControllerImpl {
|
if let maybeChat = controller as? ChatControllerImpl {
|
||||||
if case .peer(peerId) = maybeChat.chatLocation {
|
if chatLocationMatchesDestination(maybeChat.chatLocation, peerId: peerId, threadId: threadId) {
|
||||||
var isChatPinnedMessages = false
|
var isChatPinnedMessages = false
|
||||||
if case .pinnedMessages = maybeChat.presentationInterfaceState.subject {
|
if case .pinnedMessages = maybeChat.presentationInterfaceState.subject {
|
||||||
isChatPinnedMessages = true
|
isChatPinnedMessages = true
|
||||||
|
|
|
||||||
|
|
@ -10304,21 +10304,35 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||||
self.present(controller, in: .window(.root))
|
self.present(controller, in: .window(.root))
|
||||||
}
|
}
|
||||||
|
|
||||||
func presentVoiceMessageDiscardAlert(action: @escaping () -> Void = {}, alertAction: (() -> Void)? = nil, delay: Bool = false, performAction: Bool = true) -> Bool {
|
func presentVoiceMessageDiscardAlert(
|
||||||
if let _ = self.presentationInterfaceState.inputTextPanelState.mediaRecordingState {
|
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?()
|
alertAction?()
|
||||||
Queue.mainQueue().after(delay ? 0.2 : 0.0) {
|
Queue.mainQueue().after(delay ? 0.2 : 0.0) {
|
||||||
let alertController = textAlertController(
|
let alertController = textAlertController(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
updatedPresentationData: self.updatedPresentationData,
|
updatedPresentationData: self.updatedPresentationData,
|
||||||
title: nil,
|
title: nil,
|
||||||
text: self.presentationData.strings.Conversation_StopVoiceMessageDescription,
|
text: discard ? self.presentationData.strings.Conversation_DiscardRecordedVoiceMessageDescription : self.presentationData.strings.Conversation_StopVoiceMessageDescription,
|
||||||
actions: [
|
actions: [
|
||||||
TextAlertAction(
|
TextAlertAction(
|
||||||
type: .defaultAction,
|
type: .defaultAction,
|
||||||
title: self.presentationData.strings.Conversation_StopVoiceMessagePauseAction,
|
title: discard ? self.presentationData.strings.Conversation_DiscardRecordedVoiceMessageAction : self.presentationData.strings.Conversation_StopVoiceMessagePauseAction,
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
self?.stopMediaRecorder(pause: true)
|
if discard {
|
||||||
|
self?.dismissMediaRecorder(.dismiss)
|
||||||
|
} else {
|
||||||
|
self?.stopMediaRecorder(pause: true)
|
||||||
|
}
|
||||||
Queue.mainQueue().after(0.1) {
|
Queue.mainQueue().after(0.1) {
|
||||||
action()
|
action()
|
||||||
}
|
}
|
||||||
|
|
@ -10343,26 +10357,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||||
}
|
}
|
||||||
return false
|
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() {
|
func presentAutoremoveSetup() {
|
||||||
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,7 @@ public class ComposeControllerImpl: ViewController, ComposeController {
|
||||||
DeviceAccess.authorizeAccess(to: .contacts)
|
DeviceAccess.authorizeAccess(to: .contacts)
|
||||||
default:
|
default:
|
||||||
let presentationData = strongSelf.presentationData
|
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()
|
self?.context.sharedContext.applicationBindings.openSettings()
|
||||||
})]), in: .window(.root))
|
})]), in: .window(.root))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ func openAddContactImpl(context: AccountContext, peer: EnginePeer?, firstName: S
|
||||||
DeviceAccess.authorizeAccess(to: .contacts)
|
DeviceAccess.authorizeAccess(to: .contacts)
|
||||||
default:
|
default:
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
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()
|
context.sharedContext.applicationBindings.openSettings()
|
||||||
})]), nil)
|
})]), 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 filter: ChatListNodePeersFilter = [.onlyGroupsAndChannels, .onlyManageable, .excludeDisabled, .excludeRecent, .doNotSearchMessages]
|
||||||
var title: String = presentationData.strings.Bot_AddToChat_Title
|
var title: String = presentationData.strings.Bot_AddToChat_Title
|
||||||
|
|
@ -198,42 +207,100 @@ func openResolvedUrlImpl(
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
TextAlertAction(type: .genericAction, title: strings.Common_Cancel, action: {})
|
TextAlertAction(type: .genericAction, title: strings.Common_Cancel, action: {})
|
||||||
]
|
],
|
||||||
|
actionLayout: .vertical
|
||||||
)
|
)
|
||||||
present(alertController, nil)
|
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 {
|
if case let .channel(peer) = peer {
|
||||||
var isGroup = false
|
var isGroup = false
|
||||||
if case .group = peer.info {
|
if case .group = peer.info {
|
||||||
isGroup = true
|
isGroup = true
|
||||||
}
|
}
|
||||||
if peer.flags.contains(.isCreator) || peer.adminRights?.rights.contains(.canAddAdmins) == true {
|
if shouldForceStartGroupStartFlow {
|
||||||
let _ = (defaultAdminRights.get()
|
addMemberImpl()
|
||||||
|> take(1)
|
} else if peer.flags.contains(.isCreator) || peer.adminRights?.rights.contains(.canAddAdmins) == true {
|
||||||
|> deliverOnMainQueue).start(next: { defaultAdminRights in
|
if shouldCheckExistingGroupAdmin && isGroup {
|
||||||
let initialAdminRights = adminRights?.chatAdminRights ?? (isGroup ? defaultAdminRights?.group?.rights : defaultAdminRights?.channel?.rights)
|
let _ = (context.engine.peers.fetchChannelParticipant(peerId: peerId, participantId: botPeerId)
|
||||||
let controller = channelAdminController(context: context, peerId: peerId, adminId: botPeerId, initialParticipant: nil, invite: true, initialAdminRights: initialAdminRights, updated: { _ in
|
|> deliverOnMainQueue).start(next: { participant in
|
||||||
controller?.dismiss()
|
let isBotAlreadyAdmin: Bool
|
||||||
}, upgradedToSupergroup: { _, _ in }, transferedOwnership: { _ in })
|
if let participant = participant {
|
||||||
navigationController?.pushViewController(controller)
|
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 {
|
} else {
|
||||||
addMemberImpl()
|
addMemberImpl()
|
||||||
}
|
}
|
||||||
} else if case let .legacyGroup(peer) = peer {
|
} else if case let .legacyGroup(peer) = peer {
|
||||||
if case .member = peer.role {
|
if shouldForceStartGroupStartFlow {
|
||||||
addMemberImpl()
|
addMemberImpl()
|
||||||
} else {
|
} else if case .member = peer.role {
|
||||||
let _ = (defaultAdminRights.get()
|
addMemberImpl()
|
||||||
|> take(1)
|
} else if shouldCheckExistingGroupAdmin {
|
||||||
|> deliverOnMainQueue).start(next: { defaultAdminRights in
|
let _ = (context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peerId)
|
||||||
let initialAdminRights = adminRights?.chatAdminRights ?? defaultAdminRights?.group?.rights
|
|> mapToSignal { _ in
|
||||||
let controller = channelAdminController(context: context, peerId: peerId, adminId: botPeerId, initialParticipant: nil, invite: true, initialAdminRights: initialAdminRights, updated: { _ in
|
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.LegacyGroupParticipants(id: peerId))
|
||||||
controller?.dismiss()
|
}
|
||||||
}, upgradedToSupergroup: { _, _ in }, transferedOwnership: { _ in })
|
|> deliverOnMainQueue).start(next: { participants in
|
||||||
navigationController?.pushViewController(controller)
|
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)
|
navigationController?.pushViewController(controller)
|
||||||
default:
|
default:
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
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()
|
context.sharedContext.applicationBindings.openSettings()
|
||||||
})]), nil)
|
})]), 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 {
|
private func convertSelectionFromOriginalText(attributedString: NSAttributedString, range: NSRange) -> NSRange {
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,9 @@ extension ResolvedBotAdminRights {
|
||||||
if components.contains("manage_chat") {
|
if components.contains("manage_chat") {
|
||||||
rawValue |= ResolvedBotAdminRights.manageChat.rawValue
|
rawValue |= ResolvedBotAdminRights.manageChat.rawValue
|
||||||
}
|
}
|
||||||
|
if components.contains("manage_topics") {
|
||||||
|
rawValue |= ResolvedBotAdminRights.manageTopics.rawValue
|
||||||
|
}
|
||||||
if components.contains("anonymous") {
|
if components.contains("anonymous") {
|
||||||
rawValue |= ResolvedBotAdminRights.canBeAnonymous.rawValue
|
rawValue |= ResolvedBotAdminRights.canBeAnonymous.rawValue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue