mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit '0f66d9847c2ce9c38eea19f4c9c552a0adef4a73' into beta
This commit is contained in:
commit
4ee5f8487a
@ -6149,3 +6149,12 @@ Sorry for the inconvenience.";
|
||||
"Channel.Setup.LinkTypePrivate" = "Private";
|
||||
|
||||
"VoiceOver.ScrollStatus" = "Row %1$@ of %2$@";
|
||||
|
||||
"Conversation.UsersTooMuchError" = "Sorry, this group is full.";
|
||||
|
||||
"Conversation.UploadFileTooLarge" = "File could not be sent, because it is larger than 2 GB.\n\nYou can send as many files as you like, but each must be smaller than 2 GB.";
|
||||
|
||||
"Channel.AddUserLeftError" = "Sorry, if a person is no longer part of a channel, you need to be in their Telegram contacts in order to add them back.\n\nNote that they can still join via the channel's invite link as long as they are not in the Removed Users list.";
|
||||
|
||||
"Message.ScamAccount" = "Scam";
|
||||
"Message.FakeAccount" = "Fake";
|
||||
|
@ -162,6 +162,7 @@ public enum ResolvedUrlSettingsSection {
|
||||
|
||||
public enum ResolvedUrl {
|
||||
case externalUrl(String)
|
||||
case urlAuth(String)
|
||||
case peer(PeerId?, ChatControllerInteractionNavigateToPeer)
|
||||
case inaccessiblePeer
|
||||
case botStart(peerId: PeerId, payload: String)
|
||||
@ -603,7 +604,7 @@ public protocol SharedAccountContext: class {
|
||||
func chatAvailableMessageActions(postbox: Postbox, accountPeerId: PeerId, messageIds: Set<MessageId>) -> Signal<ChatAvailableMessageActions, NoError>
|
||||
func chatAvailableMessageActions(postbox: Postbox, accountPeerId: PeerId, messageIds: Set<MessageId>, messages: [MessageId: Message], peers: [PeerId: Peer]) -> Signal<ChatAvailableMessageActions, NoError>
|
||||
func resolveUrl(account: Account, url: String) -> Signal<ResolvedUrl, NoError>
|
||||
func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?)
|
||||
func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?)
|
||||
func openAddContact(context: AccountContext, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void)
|
||||
func openAddPersonContact(context: AccountContext, peerId: PeerId, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void)
|
||||
func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void)
|
||||
|
@ -332,7 +332,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
||||
if case .search = source {
|
||||
if let _ = peer as? TelegramChannel {
|
||||
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_JoinChannel, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
var createSignal = context.peerChannelMemberCategoriesContextsManager.join(account: context.account, peerId: peerId)
|
||||
var createSignal = context.peerChannelMemberCategoriesContextsManager.join(account: context.account, peerId: peerId, hash: nil)
|
||||
var cancelImpl: (() -> Void)?
|
||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
@ -527,6 +527,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
}
|
||||
|
||||
self.titleView.theme = self.presentationData.theme
|
||||
self.titleView.strings = self.presentationData.strings
|
||||
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
|
||||
@ -2348,7 +2349,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
}
|
||||
|
||||
if chatPeer is TelegramSecretChat {
|
||||
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.ChatList_DeleteForEveryone(mainPeer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).0, color: .destructive, action: { [weak actionSheet] in
|
||||
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.ChatList_DeleteForEveryone(mainPeer.compactDisplayTitle).0, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
@ -176,7 +176,7 @@ private final class ChatListShimmerNode: ASDisplayNode {
|
||||
func update(context: AccountContext, size: CGSize, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
|
||||
if self.currentParams?.size != size || self.currentParams?.presentationData !== presentationData {
|
||||
self.currentParams = (size, presentationData)
|
||||
|
||||
|
||||
let chatListPresentationData = ChatListPresentationData(theme: presentationData.theme, fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true)
|
||||
|
||||
let peer1 = TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: 1), accessHash: nil, firstName: "FirstName", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [])
|
||||
@ -368,6 +368,10 @@ private final class ChatListContainerItemNode: ASDisplayNode {
|
||||
func updatePresentationData(_ presentationData: PresentationData) {
|
||||
self.presentationData = presentationData
|
||||
|
||||
self.listNode.accessibilityPageScrolledString = { row, count in
|
||||
return presentationData.strings.VoiceOver_ScrollStatus(row, count).0
|
||||
}
|
||||
|
||||
self.listNode.updateThemeAndStrings(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations)
|
||||
|
||||
self.emptyNode?.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings)
|
||||
|
@ -398,7 +398,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||
arguments.deleteIncludePeer(id)
|
||||
})
|
||||
case let .excludePeer(_, peer, isRevealed):
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer.chatMainPeer!, height: .peerList, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: isRevealed), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: {
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer.chatMainPeer!, height: .peerList, aliasHandling: .threatSelfAsSaved, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: isRevealed), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: {
|
||||
arguments.deleteExcludePeer(peer.peerId)
|
||||
})]), switchValue: nil, enabled: true, selectable: false, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in
|
||||
arguments.setItemIdWithRevealedOptions(lhs.flatMap { .peer($0) }, rhs.flatMap { .peer($0) })
|
||||
|
@ -166,8 +166,9 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
context.sharedContext.openResolvedUrl(resolved, context: context, urlContext: .generic, navigationController: navigationController, openPeer: { peerId, navigation in
|
||||
// self?.openPeer(peerId: peerId, navigation: navigation)
|
||||
}, sendFile: nil,
|
||||
sendSticker: nil,
|
||||
present: { c, a in
|
||||
sendSticker: nil,
|
||||
requestMessageActionUrlAuth: nil,
|
||||
present: { c, a in
|
||||
present(c, a)
|
||||
}, dismissInput: {
|
||||
self?.dismissInput()
|
||||
|
@ -511,7 +511,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
peerContextAction(peer.peer, .search(nil), node, gesture)
|
||||
}
|
||||
})
|
||||
case let .message(message, peer, readState, presentationData, totalCount, selected, displayCustomHeader):
|
||||
case let .message(message, peer, readState, presentationData, _, selected, displayCustomHeader):
|
||||
let header = ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
|
||||
let selection: ChatHistoryMessageSelection = selected.flatMap { .selectable(selected: $0) } ?? .none
|
||||
if let tagMask = tagMask, tagMask != .photoOrVideo {
|
||||
|
@ -68,7 +68,11 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl
|
||||
}
|
||||
}
|
||||
|
||||
var strings: PresentationStrings
|
||||
var strings: PresentationStrings {
|
||||
didSet {
|
||||
self.proxyButton.accessibilityLabel = self.strings.VoiceOver_Navigation_ProxySettings
|
||||
}
|
||||
}
|
||||
|
||||
init(theme: PresentationTheme, strings: PresentationStrings) {
|
||||
self.theme = theme
|
||||
|
@ -1307,10 +1307,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _):
|
||||
if let peer = messages.last?.author {
|
||||
if peer.isScam {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||
credibilityIconOffset = 2.0
|
||||
} else if peer.isFake {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, type: .regular)
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||
credibilityIconOffset = 2.0
|
||||
} else if peer.isVerified {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
||||
@ -1322,10 +1322,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
} else if case let .chat(itemPeer) = contentPeer, let peer = itemPeer.chatMainPeer {
|
||||
if peer.isScam {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||
credibilityIconOffset = 2.0
|
||||
} else if peer.isFake {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, type: .regular)
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||
credibilityIconOffset = 2.0
|
||||
} else if peer.isVerified {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
||||
|
@ -95,10 +95,17 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
|
||||
if let navigationController = (contactsController?.navigationController as? NavigationController) {
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), peekData: nil))
|
||||
}
|
||||
}, error: { _ in
|
||||
}, error: { error in
|
||||
if let contactsController = contactsController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
contactsController.present(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
let text: String
|
||||
switch error {
|
||||
case .limitExceeded:
|
||||
text = presentationData.strings.TwoStepAuth_FloodError
|
||||
default:
|
||||
text = presentationData.strings.Login_UnknownError
|
||||
}
|
||||
contactsController.present(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}
|
||||
}))
|
||||
}
|
||||
@ -121,10 +128,17 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
|
||||
}
|
||||
|
||||
if canCall {
|
||||
items.append(.action(ContextMenuActionItem(text: strings.ContactList_Context_Call, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Call"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
if let contactsController = contactsController {
|
||||
context.requestCall(peerId: peerId, isVideo: false, completion: {})
|
||||
}
|
||||
items.append(.action(ContextMenuActionItem(text: strings.ContactList_Context_Call, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Call"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { _, f in
|
||||
context.requestCall(peerId: peerId, isVideo: false, completion: {})
|
||||
f(.default)
|
||||
})))
|
||||
}
|
||||
if canVideoCall {
|
||||
items.append(.action(ContextMenuActionItem(text: strings.ContactList_Context_VideoCall, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/VideoCall"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { _, f in
|
||||
context.requestCall(peerId: peerId, isVideo: true, completion: {})
|
||||
f(.default)
|
||||
})))
|
||||
}
|
||||
|
@ -132,9 +132,7 @@ public class InviteContactsController: ViewController, MFMessageComposeViewContr
|
||||
if let strongSelf = self {
|
||||
let url = strongSelf.presentationData.strings.InviteText_URL
|
||||
let body = strongSelf.presentationData.strings.InviteText_SingleContact(url).0
|
||||
|
||||
let shareController = ShareController(context: strongSelf.context, subject: .text(body), externalShare: true, immediateExternalShare: true)
|
||||
strongSelf.present(shareController, in: .window(.root))
|
||||
presentExternalShare(context: strongSelf.context, text: body, parentController: strongSelf)
|
||||
|
||||
strongSelf.contactsNode.listNode.clearHighlightAnimated(true)
|
||||
}
|
||||
|
@ -1249,11 +1249,16 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
public var forceHidden = false {
|
||||
didSet {
|
||||
self.updateItemNodeVisibilititesAndScrolling()
|
||||
}
|
||||
}
|
||||
private func updateItemNodeVisibilititesAndScrolling() {
|
||||
let visibleRect = self.scrollView.bounds
|
||||
let isScrolling = self.scrollView.isDragging || self.scrollView.isDecelerating
|
||||
for (_, itemNode) in self.itemNodes {
|
||||
let visible = itemNode.frame.intersects(visibleRect)
|
||||
let visible = itemNode.frame.intersects(visibleRect) && !self.forceHidden
|
||||
if itemNode.isVisibleInGrid != visible {
|
||||
itemNode.isVisibleInGrid = visible
|
||||
}
|
||||
|
@ -29,6 +29,8 @@ public final class PeekController: ViewController {
|
||||
private let content: PeekControllerContent
|
||||
var sourceNode: () -> ASDisplayNode?
|
||||
|
||||
public var visibilityUpdated: ((Bool) -> Void)?
|
||||
|
||||
private var animatedIn = false
|
||||
|
||||
public init(theme: PeekControllerTheme, content: PeekControllerContent, sourceNode: @escaping () -> ASDisplayNode?) {
|
||||
@ -67,6 +69,8 @@ public final class PeekController: ViewController {
|
||||
if !self.animatedIn {
|
||||
self.animatedIn = true
|
||||
self.controllerNode.animateIn(from: self.getSourceRect())
|
||||
|
||||
self.visibilityUpdated?(true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,6 +81,7 @@ public final class PeekController: ViewController {
|
||||
}
|
||||
|
||||
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||
self.visibilityUpdated?(false)
|
||||
self.controllerNode.animateOut(to: self.getSourceRect(), completion: { [weak self] in
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
})
|
||||
|
@ -100,8 +100,9 @@ final class PeekControllerMenuItemNode: HighlightTrackingButtonNode {
|
||||
}
|
||||
|
||||
@objc func buttonPressed() {
|
||||
self.activatedAction()
|
||||
if self.item.action(self, self.bounds) {
|
||||
self.activatedAction()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -504,6 +504,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
if !displayInfo {
|
||||
authorNameText = ""
|
||||
dateText = ""
|
||||
canEdit = false
|
||||
}
|
||||
|
||||
var messageText = NSAttributedString(string: "")
|
||||
@ -910,15 +911,20 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
if let strongSelf = self, !messages.isEmpty {
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
var generalMessageContentKind: MessageContentKind?
|
||||
var beganContentKindScanning = false
|
||||
var messageContentKinds = Set<MessageContentKindKey>()
|
||||
|
||||
for message in messages {
|
||||
let currentKind = messageContentKind(contentSettings: strongSelf.context.currentContentSettings.with { $0 }, message: message, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, accountPeerId: strongSelf.context.account.peerId)
|
||||
if generalMessageContentKind == nil || generalMessageContentKind == currentKind {
|
||||
generalMessageContentKind = currentKind
|
||||
} else {
|
||||
if beganContentKindScanning && currentKind != generalMessageContentKind {
|
||||
generalMessageContentKind = nil
|
||||
break
|
||||
} else if !beganContentKindScanning || currentKind == generalMessageContentKind {
|
||||
beganContentKindScanning = true
|
||||
generalMessageContentKind = currentKind
|
||||
}
|
||||
messageContentKinds.insert(currentKind.key)
|
||||
}
|
||||
|
||||
var preferredAction = ShareControllerPreferredAction.default
|
||||
if let generalMessageContentKind = generalMessageContentKind {
|
||||
switch generalMessageContentKind {
|
||||
@ -927,6 +933,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
default:
|
||||
break
|
||||
}
|
||||
} else if messageContentKinds.count == 2 && messageContentKinds.contains(.image) && messageContentKinds.contains(.video) {
|
||||
preferredAction = .saveToCameraRoll
|
||||
}
|
||||
|
||||
if messages.count == 1 {
|
||||
|
@ -665,7 +665,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
playing = true
|
||||
case let .buffering(_, whilePlaying, _, display):
|
||||
displayProgress = display
|
||||
initialBuffering = true
|
||||
initialBuffering = !whilePlaying
|
||||
isPaused = !whilePlaying
|
||||
var isStreaming = false
|
||||
if let fetchStatus = strongSelf.fetchStatus {
|
||||
|
@ -164,7 +164,11 @@ open class ZoomableContentGalleryItemNode: GalleryItemNode, UIScrollViewDelegate
|
||||
self.centerScrollViewContents(transition: transition)
|
||||
self.ignoreZoom = false
|
||||
|
||||
let updatedZoomScale = self.scrollNode.view.zoomScale != self.scrollNode.view.minimumZoomScale
|
||||
self.scrollNode.view.zoomScale = self.scrollNode.view.minimumZoomScale
|
||||
if !updatedZoomScale {
|
||||
self.scrollViewDidZoom(self.scrollNode.view)
|
||||
}
|
||||
self.ignoreZoomTransition = nil
|
||||
}
|
||||
|
||||
|
@ -1198,6 +1198,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}, sendFile: nil,
|
||||
sendSticker: nil,
|
||||
requestMessageActionUrlAuth: nil,
|
||||
present: { c, a in
|
||||
self?.present(c, a)
|
||||
}, dismissInput: {
|
||||
@ -1275,6 +1276,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
fromPlayingVideo = true
|
||||
entries.append(InstantPageGalleryEntry(index: Int32(media.index), pageId: webPage.webpageId, media: media, caption: media.caption, credit: media.credit, location: nil))
|
||||
} else {
|
||||
fromPlayingVideo = true
|
||||
var medias: [InstantPageMedia] = mediasFromItems(items)
|
||||
medias = medias.filter {
|
||||
return $0.media is TelegramMediaImage || $0.media is TelegramMediaFile
|
||||
|
@ -309,7 +309,7 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
|
||||
@objc func joinPressed() {
|
||||
if let peer = self.peer, case .notJoined = self.joinState {
|
||||
self.updateJoinState(.inProgress)
|
||||
self.joinDisposable.set((joinChannel(account: self.context.account, peerId: peer.id) |> deliverOnMainQueue).start(error: { [weak self] _ in
|
||||
self.joinDisposable.set((joinChannel(account: self.context.account, peerId: peer.id, hash: nil) |> deliverOnMainQueue).start(error: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
if case .inProgress = strongSelf.joinState {
|
||||
strongSelf.updateJoinState(.notJoined)
|
||||
|
@ -30,11 +30,11 @@ private final class InviteLinkListControllerArguments {
|
||||
let mainLinkContextAction: (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void
|
||||
let createLink: () -> Void
|
||||
let openLink: (ExportedInvitation) -> Void
|
||||
let linkContextAction: (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void
|
||||
let linkContextAction: (ExportedInvitation?, Bool, ASDisplayNode, ContextGesture?) -> Void
|
||||
let openAdmin: (ExportedInvitationCreator) -> Void
|
||||
let deleteAllRevokedLinks: () -> Void
|
||||
|
||||
init(context: AccountContext, shareMainLink: @escaping (ExportedInvitation) -> Void, openMainLink: @escaping (ExportedInvitation) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, mainLinkContextAction: @escaping (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void, createLink: @escaping () -> Void, openLink: @escaping (ExportedInvitation?) -> Void, linkContextAction: @escaping (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void, openAdmin: @escaping (ExportedInvitationCreator) -> Void, deleteAllRevokedLinks: @escaping () -> Void) {
|
||||
init(context: AccountContext, shareMainLink: @escaping (ExportedInvitation) -> Void, openMainLink: @escaping (ExportedInvitation) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, mainLinkContextAction: @escaping (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void, createLink: @escaping () -> Void, openLink: @escaping (ExportedInvitation?) -> Void, linkContextAction: @escaping (ExportedInvitation?, Bool, ASDisplayNode, ContextGesture?) -> Void, openAdmin: @escaping (ExportedInvitationCreator) -> Void, deleteAllRevokedLinks: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.shareMainLink = shareMainLink
|
||||
self.openMainLink = openMainLink
|
||||
@ -65,7 +65,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
|
||||
case linksHeader(PresentationTheme, String)
|
||||
case linksCreate(PresentationTheme, String)
|
||||
case link(Int32, PresentationTheme, ExportedInvitation?, Int32?)
|
||||
case link(Int32, PresentationTheme, ExportedInvitation?, Bool, Int32?)
|
||||
case linksInfo(PresentationTheme, String)
|
||||
|
||||
case revokedLinksHeader(PresentationTheme, String)
|
||||
@ -104,7 +104,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
return 4
|
||||
case .linksCreate:
|
||||
return 5
|
||||
case let .link(index, _, _, _):
|
||||
case let .link(index, _, _, _, _):
|
||||
return 6 + index
|
||||
case .linksInfo:
|
||||
return 10000
|
||||
@ -159,8 +159,8 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .link(lhsIndex, lhsTheme, lhsLink, lhsTick):
|
||||
if case let .link(rhsIndex, rhsTheme, rhsLink, rhsTick) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsLink == rhsLink, lhsTick == rhsTick {
|
||||
case let .link(lhsIndex, lhsTheme, lhsLink, lhsCanEdit, lhsTick):
|
||||
if case let .link(rhsIndex, rhsTheme, rhsLink, rhsCanEdit, rhsTick) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsLink == rhsLink, lhsCanEdit == rhsCanEdit, lhsTick == rhsTick {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -239,11 +239,11 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, sectionId: self.section, editing: false, action: {
|
||||
arguments.createLink()
|
||||
})
|
||||
case let .link(_, _, invite, _):
|
||||
case let .link(_, _, invite, canEdit, _):
|
||||
return ItemListInviteLinkItem(presentationData: presentationData, invite: invite, share: false, sectionId: self.section, style: .blocks) { invite in
|
||||
arguments.openLink(invite)
|
||||
} contextAction: { invite, node, gesture in
|
||||
arguments.linkContextAction(invite, node, gesture)
|
||||
arguments.linkContextAction(invite, canEdit, node, gesture)
|
||||
}
|
||||
case let .linksInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
@ -257,12 +257,12 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
return ItemListInviteLinkItem(presentationData: presentationData, invite: invite, share: false, sectionId: self.section, style: .blocks) { invite in
|
||||
arguments.openLink(invite)
|
||||
} contextAction: { invite, node, gesture in
|
||||
arguments.linkContextAction(invite, node, gesture)
|
||||
arguments.linkContextAction(invite, false, node, gesture)
|
||||
}
|
||||
case let .adminsHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .admin(_, _, creator):
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: .firstLast, context: arguments.context, peer: creator.peer.peer!, height: .peerList, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .none, label: .disclosure("\(creator.count)"), editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: nil), revealOptions: nil, switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: {
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: .firstLast, context: arguments.context, peer: creator.peer.peer!, height: .peerList, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .none, label: creator.count > 1 ? .disclosure("\(creator.count)") : .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: nil), revealOptions: nil, switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: {
|
||||
arguments.openAdmin(creator)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, contextAction: nil)
|
||||
}
|
||||
@ -333,16 +333,21 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
|
||||
entries.append(.linksCreate(presentationData.theme, presentationData.strings.InviteLink_Create))
|
||||
}
|
||||
|
||||
var canEditLinks = true
|
||||
if let peer = admin?.peer.peer as? TelegramUser, peer.botInfo != nil {
|
||||
canEditLinks = false
|
||||
}
|
||||
|
||||
if let additionalInvites = additionalInvites {
|
||||
var index: Int32 = 0
|
||||
for invite in additionalInvites {
|
||||
entries.append(.link(index, presentationData.theme, invite, invite.expireDate != nil ? tick : nil))
|
||||
entries.append(.link(index, presentationData.theme, invite, canEditLinks, invite.expireDate != nil ? tick : nil))
|
||||
index += 1
|
||||
}
|
||||
} else if let admin = admin, admin.count > 1 {
|
||||
var index: Int32 = 0
|
||||
for _ in 0 ..< admin.count - 1 {
|
||||
entries.append(.link(index, presentationData.theme, nil, nil))
|
||||
entries.append(.link(index, presentationData.theme, nil, false, nil))
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
@ -558,7 +563,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
|
||||
let controller = InviteLinkViewController(context: context, peerId: peerId, invite: invite, invitationsContext: invitesContext, revokedInvitationsContext: revokedInvitesContext, importersContext: nil)
|
||||
pushControllerImpl?(controller)
|
||||
}
|
||||
}, linkContextAction: { invite, node, gesture in
|
||||
}, linkContextAction: { invite, canEdit, node, gesture in
|
||||
guard let node = node as? ContextExtractedContentContainingNode, let controller = getControllerImpl?(), let invite = invite else {
|
||||
return
|
||||
}
|
||||
@ -615,24 +620,26 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
|
||||
})))
|
||||
}
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextEdit, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { _, f in
|
||||
f(.default)
|
||||
|
||||
let controller = inviteLinkEditController(context: context, peerId: peerId, invite: invite, completion: { invite in
|
||||
if let invite = invite {
|
||||
if invite.isRevoked {
|
||||
invitesContext.remove(invite)
|
||||
revokedInvitesContext.add(invite.withUpdated(isRevoked: true))
|
||||
} else {
|
||||
invitesContext.update(invite)
|
||||
if !invite.isPermanent && canEdit {
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextEdit, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { _, f in
|
||||
f(.default)
|
||||
|
||||
let controller = inviteLinkEditController(context: context, peerId: peerId, invite: invite, completion: { invite in
|
||||
if let invite = invite {
|
||||
if invite.isRevoked {
|
||||
invitesContext.remove(invite)
|
||||
revokedInvitesContext.add(invite.withUpdated(isRevoked: true))
|
||||
} else {
|
||||
invitesContext.update(invite)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
controller.navigationPresentation = .modal
|
||||
pushControllerImpl?(controller)
|
||||
})))
|
||||
})
|
||||
controller.navigationPresentation = .modal
|
||||
pushControllerImpl?(controller)
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
if invite.isRevoked {
|
||||
@ -685,8 +692,10 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
|
||||
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
|
||||
dismissAction()
|
||||
|
||||
revokeLinkDisposable.set((revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
|
||||
|
||||
revokeLinkDisposable.set((revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(next: { result in
|
||||
if case let .replace(_, newInvite) = result {
|
||||
invitesContext.add(newInvite)
|
||||
}
|
||||
}))
|
||||
|
||||
invitesContext.remove(invite)
|
||||
|
@ -537,8 +537,10 @@ public final class InviteLinkViewController: ViewController {
|
||||
dismissAction()
|
||||
self?.controller?.dismiss()
|
||||
|
||||
let _ = (revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(completed: {
|
||||
|
||||
let _ = (revokePeerExportedInvitation(account: context.account, peerId: peerId, link: invite.link) |> deliverOnMainQueue).start(next: { result in
|
||||
if case let .replace(_, newInvite) = result {
|
||||
self?.controller?.invitationsContext?.add(newInvite)
|
||||
}
|
||||
})
|
||||
|
||||
self?.controller?.invitationsContext?.remove(invite)
|
||||
|
@ -374,10 +374,10 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
|
||||
var credibilityIconOffset: CGFloat = 4.0
|
||||
if let peer = item.peer {
|
||||
if peer.isScam {
|
||||
credibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
|
||||
credibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||
credibilityIconOffset = 6.0
|
||||
} else if peer.isFake {
|
||||
credibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, type: .regular)
|
||||
credibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||
credibilityIconOffset = 2.0
|
||||
} else if peer.isVerified {
|
||||
credibilityIconImage = PresentationResourcesItemList.verifiedPeerIcon(item.presentationData.theme)
|
||||
|
@ -79,10 +79,36 @@ public struct ItemListToolbarItem {
|
||||
}
|
||||
|
||||
let actions: [Action]
|
||||
|
||||
var toolbar: Toolbar {
|
||||
var leftAction: ToolbarAction?
|
||||
var middleAction: ToolbarAction?
|
||||
var rightAction: ToolbarAction?
|
||||
|
||||
if self.actions.count == 1 {
|
||||
if let action = self.actions.first {
|
||||
middleAction = ToolbarAction(title: action.title, isEnabled: action.isEnabled)
|
||||
}
|
||||
} else if actions.count == 2 {
|
||||
if let action = self.actions.first {
|
||||
leftAction = ToolbarAction(title: action.title, isEnabled: action.isEnabled)
|
||||
}
|
||||
if let action = self.actions.last {
|
||||
rightAction = ToolbarAction(title: action.title, isEnabled: action.isEnabled)
|
||||
}
|
||||
} else if actions.count == 3 {
|
||||
leftAction = ToolbarAction(title: self.actions[0].title, isEnabled: self.actions[0].isEnabled)
|
||||
middleAction = ToolbarAction(title: self.actions[1].title, isEnabled: self.actions[1].isEnabled)
|
||||
rightAction = ToolbarAction(title: self.actions[2].title, isEnabled: self.actions[2].isEnabled)
|
||||
}
|
||||
return Toolbar(leftAction: leftAction, rightAction: rightAction, middleAction: middleAction)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private struct ItemListNodeTransition {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let entries: ItemListNodeEntryTransition
|
||||
let updateStyle: ItemListStyle?
|
||||
let emptyStateItem: ItemListControllerEmptyStateItem?
|
||||
@ -348,7 +374,7 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
scrollToItem = state.initialScrollToItem
|
||||
}
|
||||
|
||||
return ItemListNodeTransition(theme: presentationData.theme, entries: transition, updateStyle: updatedStyle, emptyStateItem: state.emptyStateItem, searchItem: state.searchItem, toolbarItem: state.toolbarItem, focusItemTag: state.focusItemTag, ensureVisibleItemTag: state.ensureVisibleItemTag, scrollToItem: scrollToItem, firstTime: previous == nil, animated: previous != nil && state.animateChanges, animateAlpha: previous != nil && state.animateChanges, crossfade: state.crossfadeState, mergedEntries: state.entries, scrollEnabled: state.scrollEnabled)
|
||||
return ItemListNodeTransition(theme: presentationData.theme, strings: presentationData.strings, entries: transition, updateStyle: updatedStyle, emptyStateItem: state.emptyStateItem, searchItem: state.searchItem, toolbarItem: state.toolbarItem, focusItemTag: state.focusItemTag, ensureVisibleItemTag: state.ensureVisibleItemTag, scrollToItem: scrollToItem, firstTime: previous == nil, animated: previous != nil && state.animateChanges, animateAlpha: previous != nil && state.animateChanges, crossfade: state.crossfadeState, mergedEntries: state.entries, scrollEnabled: state.scrollEnabled)
|
||||
})
|
||||
|> deliverOnMainQueue).start(next: { [weak self] transition in
|
||||
if let strongSelf = self {
|
||||
@ -452,6 +478,10 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
searchNode.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
||||
}
|
||||
|
||||
if let toolbarNode = self.toolbarNode {
|
||||
// toolbarNode.updateLayout(size: layout.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: UIEdgeInsets(), bottomInset: 0.0, toolbar: <#T##Toolbar#>, transition: transition)
|
||||
}
|
||||
|
||||
let dequeue = self.validLayout == nil
|
||||
self.validLayout = (layout, navigationBarHeight)
|
||||
if dequeue {
|
||||
@ -610,6 +640,47 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
self.listNode.accessibilityPageScrolledString = { row, count in
|
||||
return transition.strings.VoiceOver_ScrollStatus(row, count).0
|
||||
}
|
||||
|
||||
let toolbarFrame = CGRect()
|
||||
let layoutTransition: ContainedViewLayoutTransition = .immediate
|
||||
if let toolbarItem = transition.toolbarItem, let (layout, _) = self.validLayout {
|
||||
if let toolbarNode = self.toolbarNode {
|
||||
layoutTransition.updateFrame(node: toolbarNode, frame: toolbarFrame)
|
||||
toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: layout.intrinsicInsets.bottom, toolbar: toolbarItem.toolbar, transition: layoutTransition)
|
||||
} else {
|
||||
let toolbarNode = ToolbarNode(theme: TabBarControllerTheme(rootControllerTheme: transition.theme), left: {
|
||||
toolbarItem.actions[0].action()
|
||||
}, right: {
|
||||
if toolbarItem.actions.count == 2 {
|
||||
toolbarItem.actions[1].action()
|
||||
} else if toolbarItem.actions.count == 3 {
|
||||
toolbarItem.actions[2].action()
|
||||
}
|
||||
}, middle: {
|
||||
if toolbarItem.actions.count == 1 {
|
||||
toolbarItem.actions[0].action()
|
||||
} else if toolbarItem.actions.count == 3 {
|
||||
toolbarItem.actions[1].action()
|
||||
}
|
||||
})
|
||||
toolbarNode.frame = toolbarFrame
|
||||
toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: layout.intrinsicInsets.bottom, toolbar: toolbarItem.toolbar, transition: .immediate)
|
||||
self.addSubnode(toolbarNode)
|
||||
self.toolbarNode = toolbarNode
|
||||
if transition.animated {
|
||||
toolbarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
} else if let toolbarNode = self.toolbarNode {
|
||||
self.toolbarNode = nil
|
||||
layoutTransition.updateAlpha(node: toolbarNode, alpha: 0.0, completion: { [weak toolbarNode] _ in
|
||||
toolbarNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
|
||||
self.listNode.transaction(deleteIndices: transition.entries.deletions, insertIndicesAndItems: transition.entries.insertions, updateIndicesAndItems: transition.entries.updates, options: options, scrollToItem: scrollToItem, updateOpaqueState: ItemListNodeOpaqueState(mergedEntries: transition.mergedEntries), completion: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
if !strongSelf.didSetReady {
|
||||
|
@ -128,22 +128,27 @@ public final class JoinLinkPreviewController: ViewController {
|
||||
}
|
||||
}, error: { [weak self] error in
|
||||
if let strongSelf = self {
|
||||
if case .tooMuchJoined = error {
|
||||
if let parentNavigationController = strongSelf.parentNavigationController {
|
||||
let context = strongSelf.context
|
||||
let link = strongSelf.link
|
||||
let navigateToPeer = strongSelf.navigateToPeer
|
||||
let resolvedState = strongSelf.resolvedState
|
||||
parentNavigationController.pushViewController(oldChannelsController(context: strongSelf.context, intent: .join, completed: { [weak parentNavigationController] value in
|
||||
if value {
|
||||
(parentNavigationController?.viewControllers.last as? ViewController)?.present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: navigateToPeer, parentNavigationController: parentNavigationController, resolvedState: resolvedState), in: .window(.root))
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Join_ChannelsTooMuch, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}
|
||||
strongSelf.dismiss()
|
||||
switch error {
|
||||
case .tooMuchJoined:
|
||||
if let parentNavigationController = strongSelf.parentNavigationController {
|
||||
let context = strongSelf.context
|
||||
let link = strongSelf.link
|
||||
let navigateToPeer = strongSelf.navigateToPeer
|
||||
let resolvedState = strongSelf.resolvedState
|
||||
parentNavigationController.pushViewController(oldChannelsController(context: strongSelf.context, intent: .join, completed: { [weak parentNavigationController] value in
|
||||
if value {
|
||||
(parentNavigationController?.viewControllers.last as? ViewController)?.present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: navigateToPeer, parentNavigationController: parentNavigationController, resolvedState: resolvedState), in: .window(.root))
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Join_ChannelsTooMuch, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}
|
||||
case .tooMuchUsers:
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Conversation_UsersTooMuchError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
case .generic:
|
||||
break
|
||||
}
|
||||
strongSelf.dismiss()
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
#define GPUImageRotationSwapsWidthAndHeight(rotation) ((rotation) == kGPUImageRotateLeft || (rotation) == kGPUImageRotateRight || (rotation) == kGPUImageRotateRightFlipVertical || (rotation) == kGPUImageRotateRightFlipHorizontal)
|
||||
|
||||
typedef enum { kGPUImageNoRotation, kGPUImageRotateLeft, kGPUImageRotateRight, kGPUImageFlipVertical, kGPUImageFlipHorizonal, kGPUImageRotateRightFlipVertical, kGPUImageRotateRightFlipHorizontal, kGPUImageRotate180 } GPUImageRotationMode;
|
||||
typedef enum { kGPUImageNoRotation, kGPUImageRotateLeft, kGPUImageRotateRight, kGPUImageFlipVertical, kGPUImageFlipHorizonal, kGPUImageRotateRightFlipVertical, kGPUImageRotateRightFlipHorizontal, kGPUImageRotate180, kGPUImageRotate180FlipHorizontal } GPUImageRotationMode;
|
||||
|
||||
@interface GPUImageContext : NSObject
|
||||
|
||||
|
@ -283,6 +283,13 @@ NSString *const kGPUImagePassthroughFragmentShaderString = SHADER_STRING
|
||||
1.0f, 0.0f,
|
||||
0.0f, 0.0f,
|
||||
};
|
||||
|
||||
static const GLfloat rotate180HorizontalFlipTextureCoordinates[] = {
|
||||
0.0f, 1.0f,
|
||||
1.0f, 1.0f,
|
||||
0.0f, 0.0f,
|
||||
1.0f, 0.0f,
|
||||
};
|
||||
|
||||
switch(rotationMode)
|
||||
{
|
||||
@ -294,6 +301,7 @@ NSString *const kGPUImagePassthroughFragmentShaderString = SHADER_STRING
|
||||
case kGPUImageRotateRightFlipVertical: return rotateRightVerticalFlipTextureCoordinates;
|
||||
case kGPUImageRotateRightFlipHorizontal: return rotateRightHorizontalFlipTextureCoordinates;
|
||||
case kGPUImageRotate180: return rotate180TextureCoordinates;
|
||||
case kGPUImageRotate180FlipHorizontal: return rotate180HorizontalFlipTextureCoordinates;
|
||||
}
|
||||
}
|
||||
|
||||
@ -642,6 +650,11 @@ NSString *const kGPUImagePassthroughFragmentShaderString = SHADER_STRING
|
||||
rotatedPoint.x = 1.0f - pointToRotate.x;
|
||||
rotatedPoint.y = 1.0f - pointToRotate.y;
|
||||
}; break;
|
||||
case kGPUImageRotate180FlipHorizontal:
|
||||
{
|
||||
rotatedPoint.x = pointToRotate.x;
|
||||
rotatedPoint.y = 1.0f - pointToRotate.y;
|
||||
}; break;
|
||||
}
|
||||
|
||||
return rotatedPoint;
|
||||
|
@ -214,7 +214,7 @@
|
||||
_rotationMode = cropMirrored ? kGPUImageRotateRightFlipHorizontal : kGPUImageRotateRight;
|
||||
break;
|
||||
case UIImageOrientationDown:
|
||||
_rotationMode = kGPUImageRotate180;
|
||||
_rotationMode = cropMirrored ? kGPUImageRotate180FlipHorizontal : kGPUImageRotate180;
|
||||
break;
|
||||
case UIImageOrientationUp:
|
||||
if (cropMirrored)
|
||||
|
@ -285,6 +285,13 @@
|
||||
0.0f, 1.0f,
|
||||
};
|
||||
|
||||
static const GLfloat rotate180HorizontalFlipTextureCoordinates[] = {
|
||||
0.0f, 1.0f,
|
||||
1.0f, 1.0f,
|
||||
0.0f, 0.0f,
|
||||
1.0f, 0.0f,
|
||||
};
|
||||
|
||||
switch(rotationMode)
|
||||
{
|
||||
case kGPUImageNoRotation: return noRotationTextureCoordinates;
|
||||
@ -295,6 +302,7 @@
|
||||
case kGPUImageRotateRightFlipVertical: return rotateRightVerticalFlipTextureCoordinates;
|
||||
case kGPUImageRotateRightFlipHorizontal: return rotateRightHorizontalFlipTextureCoordinates;
|
||||
case kGPUImageRotate180: return rotate180TextureCoordinates;
|
||||
case kGPUImageRotate180FlipHorizontal: return rotate180HorizontalFlipTextureCoordinates;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,16 +118,20 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN
|
||||
|
||||
- (GPUImageRotationMode)rotationForTrack:(AVAsset *)asset {
|
||||
AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
|
||||
CGAffineTransform trackTransform = [videoTrack preferredTransform];
|
||||
CGAffineTransform t = [videoTrack preferredTransform];
|
||||
|
||||
if (trackTransform.a == -1 && trackTransform.d == -1) {
|
||||
if (t.a == -1 && t.d == -1) {
|
||||
return kGPUImageRotate180;
|
||||
} else if (trackTransform.a == 1 && trackTransform.d == 1) {
|
||||
} else if (t.a == 1 && t.d == 1) {
|
||||
return kGPUImageNoRotation;
|
||||
} else if (trackTransform.b == -1 && trackTransform.c == 1) {
|
||||
} else if (t.b == -1 && t.c == 1) {
|
||||
return kGPUImageRotateLeft;
|
||||
} else if (t.a == -1 && t.d == 1) {
|
||||
return kGPUImageFlipHorizonal;
|
||||
} else if (t.a == 1 && t.d == -1) {
|
||||
return kGPUImageRotate180FlipHorizontal;
|
||||
} else {
|
||||
if (trackTransform.c == 1) {
|
||||
if (t.c == 1) {
|
||||
return kGPUImageRotateRightFlipVertical;
|
||||
} else {
|
||||
return kGPUImageRotateRight;
|
||||
|
@ -40,6 +40,13 @@
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
if (![url.absoluteString.lowercaseString hasSuffix:@".mp4"]) {
|
||||
NSURL *pathUrl = [NSURL fileURLWithPath:[[[LegacyComponentsGlobals provider] dataStoragePath] stringByAppendingPathComponent:@"videos"]];
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:pathUrl.path withIntermediateDirectories:true attributes:nil error:nil];
|
||||
NSURL *updatedUrl = [pathUrl URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.mp4", [TGStringUtils md5:url.absoluteString]]];
|
||||
[[NSFileManager defaultManager] createSymbolicLinkAtPath:updatedUrl.path withDestinationPath:url.path error:nil];
|
||||
url = updatedUrl;
|
||||
}
|
||||
_cachedAVAsset = [[AVURLAsset alloc] initWithURL:url options:nil];
|
||||
_cachedSize = CGSizeZero;
|
||||
_cachedDuration = 0.0;
|
||||
|
@ -1344,8 +1344,6 @@
|
||||
dict[@"stickers"] = adjustments.paintingData.stickers;
|
||||
if (timer != nil)
|
||||
dict[@"timer"] = timer;
|
||||
else if (groupedId != nil && !hasAnyTimers)
|
||||
dict[@"groupedId"] = groupedId;
|
||||
|
||||
id generatedItem = descriptionGenerator(dict, caption, entities, nil);
|
||||
return generatedItem;
|
||||
|
@ -593,28 +593,30 @@ UIImageOrientation TGVideoOrientationForAsset(AVAsset *asset, bool *mirrored)
|
||||
{
|
||||
AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] firstObject];
|
||||
CGAffineTransform t = videoTrack.preferredTransform;
|
||||
double videoRotation = atan2((float)t.b, (float)t.a);
|
||||
|
||||
if (mirrored != NULL)
|
||||
{
|
||||
CGFloat scaleX = sqrt(t.a * t.a + t.c * t.c);
|
||||
CGFloat scaleY = sqrt(t.b * t.b + t.d * t.d);
|
||||
CGSize scale = CGSizeMake(scaleX, scaleY);
|
||||
|
||||
*mirrored = (scale.width < 0);
|
||||
}
|
||||
|
||||
if (fabs(videoRotation - M_PI) < FLT_EPSILON) {
|
||||
if (t.a == -1 && t.d == -1) {
|
||||
return UIImageOrientationLeft;
|
||||
} else if (fabs(videoRotation - M_PI_2) < FLT_EPSILON) {
|
||||
if (t.c == 1 && mirrored != NULL) {
|
||||
} else if (t.a == 1 && t.d == 1) {
|
||||
return UIImageOrientationRight;
|
||||
} else if (t.b == -1 && t.c == 1) {
|
||||
return UIImageOrientationDown;
|
||||
} else if (t.a == -1 && t.d == 1) {
|
||||
if (mirrored != NULL) {
|
||||
*mirrored = true;
|
||||
}
|
||||
return UIImageOrientationLeft;
|
||||
} else if (t.a == 1 && t.d == -1) {
|
||||
if (mirrored != NULL) {
|
||||
*mirrored = true;
|
||||
}
|
||||
return UIImageOrientationUp;
|
||||
} else if (fabs(videoRotation + M_PI_2) < FLT_EPSILON) {
|
||||
return UIImageOrientationDown;
|
||||
} else {
|
||||
return UIImageOrientationRight;
|
||||
} else {
|
||||
if (t.c == 1) {
|
||||
if (mirrored != NULL) {
|
||||
*mirrored = true;
|
||||
}
|
||||
}
|
||||
return UIImageOrientationUp;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -696,8 +696,8 @@ static void addRoundedRectToPath(CGContextRef context, CGRect rect, CGFloat oval
|
||||
(id)UIColorRGB(0x80c864).CGColor, //green
|
||||
(id)UIColorRGB(0xfcde65).CGColor, //yellow
|
||||
(id)UIColorRGB(0xfc964d).CGColor, //orange
|
||||
(id)[UIColor blackColor].CGColor, //black
|
||||
(id)[UIColor whiteColor].CGColor //white
|
||||
(id)UIColorRGB(0x000000).CGColor, //black
|
||||
(id)UIColorRGB(0xffffff).CGColor //white
|
||||
];
|
||||
});
|
||||
return colors;
|
||||
|
@ -187,7 +187,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
|
||||
_style = style;
|
||||
switch (_style) {
|
||||
case TGPhotoPaintTextEntityStyleRegular:
|
||||
_textView.layer.shadowColor = [[UIColor blackColor] CGColor];
|
||||
_textView.layer.shadowColor = [UIColorRGB(0x000000) CGColor];
|
||||
_textView.layer.shadowOffset = CGSizeMake(0.0f, 4.0f);
|
||||
_textView.layer.shadowOpacity = 0.4f;
|
||||
_textView.layer.shadowRadius = 4.0f;
|
||||
@ -218,7 +218,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
|
||||
|
||||
case TGPhotoPaintTextEntityStyleOutlined:
|
||||
{
|
||||
_textView.textColor = [UIColor whiteColor];
|
||||
_textView.textColor = UIColorRGB(0xffffff);
|
||||
_textView.strokeColor = _swatch.color;
|
||||
_textView.frameColor = nil;
|
||||
}
|
||||
@ -238,9 +238,9 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
|
||||
}
|
||||
|
||||
if (lightness > 0.87) {
|
||||
_textView.textColor = [UIColor blackColor];
|
||||
_textView.textColor = UIColorRGB(0x000000);
|
||||
} else {
|
||||
_textView.textColor = [UIColor whiteColor];
|
||||
_textView.textColor = UIColorRGB(0xffffff);
|
||||
}
|
||||
_textView.strokeColor = nil;
|
||||
_textView.frameColor = _swatch.color;
|
||||
@ -484,6 +484,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
|
||||
@implementation TGPhotoTextView
|
||||
{
|
||||
UIFont *_font;
|
||||
UIColor *_forcedTextColor;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
@ -564,6 +565,11 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
|
||||
self.layoutManager.textContainers.firstObject.lineFragmentPadding = floor(font.pointSize * 0.3);
|
||||
}
|
||||
|
||||
- (void)setTextColor:(UIColor *)textColor {
|
||||
_forcedTextColor = textColor;
|
||||
[super setTextColor:textColor];
|
||||
}
|
||||
|
||||
- (void)insertText:(NSString *)text {
|
||||
[self fixTypingAttributes];
|
||||
[super insertText:text];
|
||||
@ -577,9 +583,14 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
|
||||
}
|
||||
|
||||
- (void)fixTypingAttributes {
|
||||
NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init];
|
||||
if (_font != nil) {
|
||||
self.typingAttributes = @{NSFontAttributeName: _font};
|
||||
attributes[NSFontAttributeName] = _font;
|
||||
}
|
||||
if (_forcedTextColor != nil) {
|
||||
attributes[NSForegroundColorAttributeName] = _forcedTextColor;
|
||||
}
|
||||
self.typingAttributes = attributes;
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -204,7 +204,9 @@
|
||||
}
|
||||
};
|
||||
|
||||
[model.interfaceView immediateEditorTransitionIn];
|
||||
if (paint) {
|
||||
[model.interfaceView immediateEditorTransitionIn];
|
||||
}
|
||||
|
||||
for (UIView *view in snapshots) {
|
||||
[galleryController.view addSubview:view];
|
||||
|
@ -530,7 +530,7 @@ public func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -
|
||||
}
|
||||
}
|
||||
}
|
||||
if estimatedSize > 5 * 1024 * 1024 {
|
||||
if estimatedSize > 10 * 1024 * 1024 {
|
||||
fileAttributes.append(.hintFileIsLarge)
|
||||
}
|
||||
|
||||
|
@ -35,11 +35,7 @@ NumberPluralizationForm numberPluralizationForm(unsigned int lc, int n) {
|
||||
|
||||
// set4
|
||||
case 0x6265: // be
|
||||
case 0x6273: // bs
|
||||
case 0x6872: // hr
|
||||
case 0x7275: // ru
|
||||
case 0x7368: // sh
|
||||
case 0x7372: // sr
|
||||
case 0x756b: // uk
|
||||
if (((n % 10) == 1) && ((n % 100) != 11)) // n mod 10 is 1 and n mod 100 is not 11
|
||||
return NumberPluralizationFormOne;
|
||||
@ -48,6 +44,19 @@ NumberPluralizationForm numberPluralizationForm(unsigned int lc, int n) {
|
||||
if (((n % 10) == 0) || (((n % 10) >= 5 && (n % 10) <= 9)) || (((n % 100) >= 11 && (n % 100) <= 14))) // n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14
|
||||
return NumberPluralizationFormMany;
|
||||
break;
|
||||
|
||||
// set4 - bugfix
|
||||
case 0x6273: // bs
|
||||
case 0x6872: // hr
|
||||
case 0x7368: // sh
|
||||
case 0x7372: // sr
|
||||
if (((n % 10) == 1) && ((n % 100) != 11)) // n mod 10 is 1 and n mod 100 is not 11
|
||||
return NumberPluralizationFormOne;
|
||||
if ((((n % 10) >= 2 && (n % 10) <= 4)) && (((n % 100) < 12 || (n % 100) > 14))) // n mod 10 in 2..4 and n mod 100 not in 12..14
|
||||
return NumberPluralizationFormFew;
|
||||
if (((n % 10) == 0) || (((n % 10) >= 5 && (n % 10) <= 9)) || (((n % 100) >= 11 && (n % 100) <= 14))) // n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14
|
||||
return NumberPluralizationFormOther;
|
||||
break;
|
||||
|
||||
// set5
|
||||
case 0x6b7368: // ksh
|
||||
|
@ -1049,11 +1049,20 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
|
||||
case .tooMuchJoined:
|
||||
text = presentationData.strings.Group_ErrorSupergroupConversionNotPossible
|
||||
case .restricted:
|
||||
if let peer = adminView.peers[adminView.peerId] {
|
||||
text = presentationData.strings.Privacy_GroupsAndChannels_InviteToGroupError(peer.compactDisplayTitle, peer.compactDisplayTitle).0
|
||||
if let admin = adminView.peers[adminView.peerId] {
|
||||
switch channel.info {
|
||||
case .broadcast:
|
||||
text = presentationData.strings.Privacy_GroupsAndChannels_InviteToChannelError(admin.compactDisplayTitle, admin.compactDisplayTitle).0
|
||||
case .group:
|
||||
text = presentationData.strings.Privacy_GroupsAndChannels_InviteToGroupError(admin.compactDisplayTitle, admin.compactDisplayTitle).0
|
||||
}
|
||||
}
|
||||
case .notMutualContact:
|
||||
text = presentationData.strings.GroupInfo_AddUserLeftError
|
||||
if case .broadcast = channel.info {
|
||||
text = presentationData.strings.Channel_AddUserLeftError
|
||||
} else {
|
||||
text = presentationData.strings.GroupInfo_AddUserLeftError
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -1119,17 +1128,28 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
|
||||
return current.withUpdatedUpdating(true)
|
||||
}
|
||||
updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(account: context.account, peerId: peerId, memberId: adminId, adminRights: TelegramChatAdminRights(rights: updateFlags), rank: updateRank) |> deliverOnMainQueue).start(error: { error in
|
||||
if case let .addMemberError(error) = error, let admin = adminView.peers[adminView.peerId] {
|
||||
if case .restricted = error {
|
||||
var text = presentationData.strings.Privacy_GroupsAndChannels_InviteToChannelError(admin.compactDisplayTitle, admin.compactDisplayTitle).0
|
||||
if case .group = channel.info {
|
||||
text = presentationData.strings.Privacy_GroupsAndChannels_InviteToGroupError(admin.compactDisplayTitle, admin.compactDisplayTitle).0
|
||||
}
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
} else if case .tooMuchJoined = error {
|
||||
let text = presentationData.strings.Invite_ChannelsTooMuch
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
if case let .addMemberError(addMemberError) = error, let admin = adminView.peers[adminView.peerId] {
|
||||
var text = presentationData.strings.Login_UnknownError
|
||||
switch addMemberError {
|
||||
case .tooMuchJoined:
|
||||
text = presentationData.strings.Group_ErrorSupergroupConversionNotPossible
|
||||
case .restricted:
|
||||
switch channel.info {
|
||||
case .broadcast:
|
||||
text = presentationData.strings.Privacy_GroupsAndChannels_InviteToChannelError(admin.compactDisplayTitle, admin.compactDisplayTitle).0
|
||||
case .group:
|
||||
text = presentationData.strings.Privacy_GroupsAndChannels_InviteToGroupError(admin.compactDisplayTitle, admin.compactDisplayTitle).0
|
||||
}
|
||||
case .notMutualContact:
|
||||
if case .broadcast = channel.info {
|
||||
text = presentationData.strings.Channel_AddUserLeftError
|
||||
} else {
|
||||
text = presentationData.strings.GroupInfo_AddUserLeftError
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
} else if case .adminsTooMuch = error {
|
||||
let text: String
|
||||
if case .broadcast = channel.info {
|
||||
@ -1146,7 +1166,7 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
|
||||
}))
|
||||
}
|
||||
}
|
||||
} else if let group = channelView.peers[channelView.peerId] as? TelegramGroup {
|
||||
} else if let _ = channelView.peers[channelView.peerId] as? TelegramGroup {
|
||||
var updateFlags: TelegramChatAdminRightsFlags?
|
||||
var updateRank: String?
|
||||
updateState { current in
|
||||
@ -1163,12 +1183,7 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
|
||||
}
|
||||
|
||||
let maskRightsFlags: TelegramChatAdminRightsFlags = .groupSpecific
|
||||
let defaultFlags: TelegramChatAdminRightsFlags
|
||||
if case .creator = group.role {
|
||||
defaultFlags = maskRightsFlags.subtracting(.canBeAnonymous)
|
||||
} else {
|
||||
defaultFlags = maskRightsFlags.subtracting(.canAddAdmins).subtracting(.canBeAnonymous)
|
||||
}
|
||||
let defaultFlags = maskRightsFlags.subtracting([.canBeAnonymous, .canAddAdmins])
|
||||
|
||||
if updateFlags == nil {
|
||||
updateFlags = defaultFlags
|
||||
|
@ -148,7 +148,7 @@ private enum ChannelDiscussionGroupSetupControllerEntry: ItemListNodeEntry {
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, sectionId: self.section, editing: false, action: {
|
||||
arguments.createGroup()
|
||||
})
|
||||
case let .group(_, theme, strings, peer, nameOrder):
|
||||
case let .group(_, _, strings, peer, nameOrder):
|
||||
let text: String
|
||||
if let peer = peer as? TelegramChannel, let addressName = peer.addressName, !addressName.isEmpty {
|
||||
text = "@\(addressName)"
|
||||
@ -160,9 +160,9 @@ private enum ChannelDiscussionGroupSetupControllerEntry: ItemListNodeEntry {
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: nameOrder, context: arguments.context, peer: peer, aliasHandling: .standard, nameStyle: .plain, presence: nil, text: .text(text, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: {
|
||||
arguments.selectGroup(peer.id)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
|
||||
case let .groupsInfo(theme, title):
|
||||
case let .groupsInfo(_, title):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(title), sectionId: self.section)
|
||||
case let .unlink(theme, title):
|
||||
case let .unlink(_, title):
|
||||
return ItemListActionItem(presentationData: presentationData, title: title, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.unlinkGroup()
|
||||
})
|
||||
|
@ -370,27 +370,30 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) ->
|
||||
}
|
||||
}).start(error: { [weak contactsController] error in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let text: String
|
||||
switch error {
|
||||
case .limitExceeded:
|
||||
text = presentationData.strings.Channel_ErrorAddTooMuch
|
||||
case .tooMuchJoined:
|
||||
text = presentationData.strings.Invite_ChannelsTooMuch
|
||||
case .generic:
|
||||
text = presentationData.strings.Login_UnknownError
|
||||
case .restricted:
|
||||
text = presentationData.strings.Channel_ErrorAddBlocked
|
||||
case .notMutualContact:
|
||||
text = presentationData.strings.GroupInfo_AddUserLeftError
|
||||
case let .bot(memberId):
|
||||
let _ = (context.account.postbox.transaction { transaction in
|
||||
return transaction.getPeer(peerId)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
let _ = (context.account.postbox.transaction { transaction in
|
||||
return transaction.getPeer(peerId)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
let text: String
|
||||
switch error {
|
||||
case .limitExceeded:
|
||||
text = presentationData.strings.Channel_ErrorAddTooMuch
|
||||
case .tooMuchJoined:
|
||||
text = presentationData.strings.Invite_ChannelsTooMuch
|
||||
case .generic:
|
||||
text = presentationData.strings.Login_UnknownError
|
||||
case .restricted:
|
||||
text = presentationData.strings.Channel_ErrorAddBlocked
|
||||
case .notMutualContact:
|
||||
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||
text = presentationData.strings.Channel_AddUserLeftError
|
||||
} else {
|
||||
text = presentationData.strings.GroupInfo_AddUserLeftError
|
||||
}
|
||||
case let .bot(memberId):
|
||||
guard let peer = peer as? TelegramChannel else {
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
contactsController?.dismiss()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@ -407,15 +410,15 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) ->
|
||||
}
|
||||
|
||||
contactsController?.dismiss()
|
||||
})
|
||||
return
|
||||
case .botDoesntSupportGroups:
|
||||
text = presentationData.strings.Channel_BotDoesntSupportGroups
|
||||
case .tooMuchBots:
|
||||
text = presentationData.strings.Channel_TooMuchBots
|
||||
}
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
contactsController?.dismiss()
|
||||
return
|
||||
case .botDoesntSupportGroups:
|
||||
text = presentationData.strings.Channel_BotDoesntSupportGroups
|
||||
case .tooMuchBots:
|
||||
text = presentationData.strings.Channel_TooMuchBots
|
||||
}
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
contactsController?.dismiss()
|
||||
})
|
||||
}))
|
||||
|
||||
presentControllerImpl?(contactsController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
|
@ -20,6 +20,7 @@ import Markdown
|
||||
public enum PeerReportSubject {
|
||||
case peer(PeerId)
|
||||
case messages([MessageId])
|
||||
case profilePhoto(PeerId, Int64)
|
||||
}
|
||||
|
||||
public enum PeerReportOption {
|
||||
@ -60,7 +61,7 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
|
||||
}, action: { [weak parent] _, f in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var reportReason: ReportReason?
|
||||
let reportReason: ReportReason
|
||||
switch option {
|
||||
case .spam:
|
||||
reportReason = .spam
|
||||
@ -75,30 +76,60 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
|
||||
case .copyright:
|
||||
reportReason = .copyright
|
||||
case .other:
|
||||
break
|
||||
reportReason = .custom
|
||||
}
|
||||
if let reportReason = reportReason {
|
||||
switch subject {
|
||||
case let .peer(peerId):
|
||||
let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason, message: "")
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") {
|
||||
parent?.present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
}
|
||||
completion(reportReason, true)
|
||||
})
|
||||
case let .messages(messageIds):
|
||||
let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason, message: "")
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") {
|
||||
parent?.present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
}
|
||||
completion(reportReason, true)
|
||||
})
|
||||
|
||||
let displaySuccess = {
|
||||
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") {
|
||||
parent?.present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
}
|
||||
} else {
|
||||
parent?.push(peerReportController(context: context, subject: subject, completion: completion))
|
||||
}
|
||||
|
||||
let action: (String) -> Void = { message in
|
||||
switch subject {
|
||||
case let .peer(peerId):
|
||||
let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason, message: "")
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
displaySuccess()
|
||||
completion(reportReason, true)
|
||||
})
|
||||
case let .messages(messageIds):
|
||||
let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason, message: "")
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
displaySuccess()
|
||||
completion(reportReason, true)
|
||||
})
|
||||
case let .profilePhoto(peerId, photoId):
|
||||
let _ = (reportPeerPhoto(account: context.account, peerId: peerId, reason: reportReason, message: "")
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
displaySuccess()
|
||||
completion(reportReason, true)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let controller = ActionSheetController(presentationData: presentationData, allowInputInset: true)
|
||||
let dismissAction: () -> Void = { [weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
var message = ""
|
||||
var items: [ActionSheetItem] = []
|
||||
items.append(ReportPeerHeaderActionSheetItem(context: context, text: presentationData.strings.Report_AdditionalDetailsText))
|
||||
items.append(ReportPeerDetailsActionSheetItem(context: context, placeholderText: presentationData.strings.Report_AdditionalDetailsPlaceholder, textUpdated: { text in
|
||||
message = text
|
||||
}))
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Report_Report, color: .accent, font: .bold, enabled: true, action: {
|
||||
dismissAction()
|
||||
|
||||
action(message)
|
||||
}))
|
||||
|
||||
controller.setItemGroups([
|
||||
ActionSheetItemGroup(items: items),
|
||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||
])
|
||||
parent?.present(controller, in: .window(.root))
|
||||
|
||||
f(.dismissWithoutContent)
|
||||
})))
|
||||
}
|
||||
@ -162,17 +193,21 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
|
||||
passthrough = false
|
||||
}
|
||||
|
||||
let action = {
|
||||
let displaySuccess = {
|
||||
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") {
|
||||
present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), nil)
|
||||
}
|
||||
}
|
||||
|
||||
let action: (String) -> Void = { message in
|
||||
switch subject {
|
||||
case let .peer(peerId):
|
||||
if passthrough {
|
||||
completion(reportReason, true)
|
||||
} else {
|
||||
let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason, message: "")
|
||||
let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason, message: message)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") {
|
||||
present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), nil)
|
||||
}
|
||||
displaySuccess()
|
||||
completion(nil, false)
|
||||
})
|
||||
}
|
||||
@ -180,11 +215,19 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
|
||||
if passthrough {
|
||||
completion(reportReason, true)
|
||||
} else {
|
||||
let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason, message: "")
|
||||
let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason, message: message)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") {
|
||||
present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), nil)
|
||||
}
|
||||
displaySuccess()
|
||||
completion(nil, false)
|
||||
})
|
||||
}
|
||||
case let .profilePhoto(peerId, photoId):
|
||||
if passthrough {
|
||||
completion(reportReason, true)
|
||||
} else {
|
||||
let _ = (reportPeerPhoto(account: context.account, peerId: peerId, reason: reportReason, message: message)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
displaySuccess()
|
||||
completion(nil, false)
|
||||
})
|
||||
}
|
||||
@ -205,7 +248,7 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Report_Report, color: .accent, font: .bold, enabled: true, action: {
|
||||
dismissAction()
|
||||
|
||||
action()
|
||||
action(message)
|
||||
}))
|
||||
|
||||
controller.setItemGroups([
|
||||
@ -214,7 +257,7 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
|
||||
])
|
||||
present(controller, nil)
|
||||
} else {
|
||||
action()
|
||||
action("")
|
||||
}
|
||||
} else {
|
||||
push(peerReportController(context: context, subject: subject, completion: completion))
|
||||
@ -369,16 +412,21 @@ private func peerReportController(context: AccountContext, subject: PeerReportSu
|
||||
dismissImpl?()
|
||||
}
|
||||
switch subject {
|
||||
case let .peer(peerId):
|
||||
reportDisposable.set((reportPeer(account: context.account, peerId: peerId, reason: reportReason, message: text)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
completed()
|
||||
}))
|
||||
case let .messages(messageIds):
|
||||
reportDisposable.set((reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason, message: text)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
completed()
|
||||
}))
|
||||
case let .peer(peerId):
|
||||
reportDisposable.set((reportPeer(account: context.account, peerId: peerId, reason: reportReason, message: text)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
completed()
|
||||
}))
|
||||
case let .messages(messageIds):
|
||||
reportDisposable.set((reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason, message: text)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
completed()
|
||||
}))
|
||||
case let .profilePhoto(peerId, photoId):
|
||||
reportDisposable.set((reportPeerPhoto(account: context.account, peerId: peerId, reason: reportReason, message: text)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
completed()
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -1420,10 +1420,17 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
|
||||
if let navigationController = (controller?.navigationController as? NavigationController) {
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId)))
|
||||
}
|
||||
}, error: { [weak controller] _ in
|
||||
}, error: { [weak controller] error in
|
||||
if let controller = controller {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
controller.present(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
let text: String
|
||||
switch error {
|
||||
case .limitExceeded:
|
||||
text = presentationData.strings.TwoStepAuth_FloodError
|
||||
default:
|
||||
text = presentationData.strings.Login_UnknownError
|
||||
}
|
||||
controller.present(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}
|
||||
}))
|
||||
})]), in: .window(.root))
|
||||
@ -1438,6 +1445,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
|
||||
|
||||
}, sendFile: nil,
|
||||
sendSticker: nil,
|
||||
requestMessageActionUrlAuth: nil,
|
||||
present: { c, a in
|
||||
presentControllerImpl?(c, a)
|
||||
}, dismissInput: {
|
||||
|
@ -531,8 +531,7 @@ public func proxySettingsController(accountManager: AccountManager, context: Acc
|
||||
result += string
|
||||
}
|
||||
|
||||
let controller = ShareController(context: context, subject: .text(result), preferredAction: .default, showInChat: nil, externalShare: true, immediateExternalShare: true, switchableAccounts: [])
|
||||
presentControllerImpl?(controller, nil)
|
||||
presentExternalShare(context: context, text: result, parentController: strongController)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -378,19 +378,15 @@ func proxyServerSettingsController(context: AccountContext? = nil, presentationD
|
||||
}
|
||||
shareImpl = { [weak controller] in
|
||||
let state = stateValue.with { $0 }
|
||||
guard let server = proxyServerSettings(with: state), let strongController = controller else {
|
||||
guard let server = proxyServerSettings(with: state) else {
|
||||
return
|
||||
}
|
||||
|
||||
let link = shareLink(for: server)
|
||||
controller?.view.endEditing(true)
|
||||
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
||||
let controller = ShareProxyServerActionSheetController(presentationData: presentationData, updatedPresentationData: updatedPresentationData, link: link)
|
||||
presentControllerImpl?(controller, nil)
|
||||
} else if let context = context {
|
||||
let controller = ShareController(context: context, subject: .url(link), preferredAction: .default, showInChat: nil, externalShare: true, immediateExternalShare: true, switchableAccounts: [])
|
||||
presentControllerImpl?(controller, nil)
|
||||
}
|
||||
|
||||
let controller = ShareProxyServerActionSheetController(presentationData: presentationData, updatedPresentationData: updatedPresentationData, link: link)
|
||||
presentControllerImpl?(controller, nil)
|
||||
}
|
||||
|
||||
return controller
|
||||
|
@ -180,7 +180,7 @@ public func logoutOptionsController(context: AccountContext, navigationControlle
|
||||
dismissImpl?()
|
||||
|
||||
context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, openPeer: { peer, navigation in
|
||||
}, sendFile: nil, sendSticker: nil, present: { controller, arguments in
|
||||
}, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, present: { controller, arguments in
|
||||
pushControllerImpl?(controller)
|
||||
}, dismissInput: {}, contentContext: nil)
|
||||
})
|
||||
|
@ -897,7 +897,7 @@ func settingsSearchableItems(context: AccountContext, notificationExceptionsList
|
||||
let _ = (cachedFaqInstantPage(context: context)
|
||||
|> deliverOnMainQueue).start(next: { resolvedUrl in
|
||||
context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, openPeer: { peer, navigation in
|
||||
}, sendFile: nil, sendSticker: nil, present: { controller, arguments in
|
||||
}, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, present: { controller, arguments in
|
||||
present(.push, controller)
|
||||
}, dismissInput: {}, contentContext: nil)
|
||||
})
|
||||
|
@ -469,7 +469,7 @@ public final class ShareController: ViewController {
|
||||
}
|
||||
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: title, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}, externalShare: self.externalShare, immediateExternalShare: self.immediateExternalShare, immediatePeerId: self.immediatePeerId, shares: self.shares, fromForeignApp: self.fromForeignApp, forcedTheme: self.forcedTheme)
|
||||
self.controllerNode.completed = completed
|
||||
self.controllerNode.completed = self.completed
|
||||
self.controllerNode.dismiss = { [weak self] shared in
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
self?.dismissed?(shared)
|
||||
@ -721,13 +721,18 @@ public final class ShareController: ViewController {
|
||||
})
|
||||
activities = [shareToInstagram]
|
||||
}
|
||||
let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: activities)
|
||||
|
||||
if let window = strongSelf.view.window, let rootViewController = window.rootViewController {
|
||||
activityController.popoverPresentationController?.sourceView = window
|
||||
activityController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
|
||||
rootViewController.present(activityController, animated: true, completion: nil)
|
||||
}
|
||||
let _ = (strongSelf.didAppearPromise.get()
|
||||
|> filter { $0 }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: activities)
|
||||
if let strongSelf = self, let window = strongSelf.view.window, let rootViewController = window.rootViewController {
|
||||
activityController.popoverPresentationController?.sourceView = window
|
||||
activityController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
|
||||
rootViewController.present(activityController, animated: true, completion: nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
return .done
|
||||
}
|
||||
@ -785,12 +790,16 @@ public final class ShareController: ViewController {
|
||||
super.loadView()
|
||||
}
|
||||
|
||||
let didAppearPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if !self.animatedIn {
|
||||
self.animatedIn = true
|
||||
self.controllerNode.animateIn()
|
||||
self.didAppearPromise.set(true)
|
||||
if !self.immediateExternalShare {
|
||||
self.controllerNode.animateIn()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1080,3 +1089,13 @@ private class ShareToInstagramActivity: UIActivity {
|
||||
activityDidFinish(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func presentExternalShare(context: AccountContext, text: String, parentController: ViewController) {
|
||||
let activityController = UIActivityViewController(activityItems: [text], applicationActivities: nil)
|
||||
if let window = parentController.view.window {
|
||||
activityController.popoverPresentationController?.sourceView = window
|
||||
activityController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
|
||||
}
|
||||
context.sharedContext.applicationBindings.presentNativeController(activityController)
|
||||
}
|
||||
|
@ -21,10 +21,6 @@ enum ShareExternalState {
|
||||
case done
|
||||
}
|
||||
|
||||
func openExternalShare(state: () -> Signal<ShareExternalState, NoError>) {
|
||||
|
||||
}
|
||||
|
||||
final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate {
|
||||
private let sharedContext: SharedAccountContext
|
||||
private var context: AccountContext?
|
||||
@ -175,6 +171,8 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
}
|
||||
|
||||
super.init()
|
||||
|
||||
self.isHidden = true
|
||||
|
||||
self.controllerInteraction = ShareControllerInteraction(togglePeer: { [weak self] peer, search in
|
||||
if let strongSelf = self {
|
||||
@ -621,6 +619,11 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
if let completion = self.outCompletion {
|
||||
self.outCompletion = nil
|
||||
completion()
|
||||
return
|
||||
}
|
||||
if self.contentNode != nil {
|
||||
self.isHidden = false
|
||||
|
||||
@ -635,6 +638,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
}
|
||||
}
|
||||
|
||||
var outCompletion: (() -> Void)?
|
||||
func animateOut(shared: Bool, completion: @escaping () -> Void) {
|
||||
if self.contentNode != nil {
|
||||
var dimCompleted = false
|
||||
@ -664,7 +668,13 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
internalCompletion()
|
||||
})
|
||||
} else {
|
||||
completion()
|
||||
self.outCompletion = completion
|
||||
Queue.mainQueue().after(0.2) {
|
||||
if let completion = self.outCompletion {
|
||||
self.outCompletion = nil
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,7 +128,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
||||
let estimatedSize = TGMediaVideoConverter.estimatedSize(for: preset, duration: finalDuration, hasAudio: true)
|
||||
|
||||
let resource = LocalFileVideoMediaResource(randomId: arc4random64(), path: asset.url.path, adjustments: resourceAdjustments)
|
||||
return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .resource(.standalone(resource: resource)), mimeType: "video/mp4", attributes: [.Video(duration: Int(finalDuration), size: PixelDimensions(width: Int32(finalDimensions.width), height: Int32(finalDimensions.height)), flags: flags)], hintFileIsLarge: estimatedSize > 5 * 1024 * 1024)
|
||||
return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .resource(.standalone(resource: resource)), mimeType: "video/mp4", attributes: [.Video(duration: Int(finalDuration), size: PixelDimensions(width: Int32(finalDimensions.width), height: Int32(finalDimensions.height)), flags: flags)], hintFileIsLarge: estimatedSize > 10 * 1024 * 1024)
|
||||
|> mapError { _ -> Void in
|
||||
return Void()
|
||||
}
|
||||
@ -192,7 +192,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
||||
mimeType = "animation/gif"
|
||||
attributes = [.ImageSize(size: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height))), .Animated, .FileName(fileName: fileName ?? "animation.gif")]
|
||||
}
|
||||
return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(data), mimeType: mimeType, attributes: attributes, hintFileIsLarge: data.count > 5 * 1024 * 1024)
|
||||
return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(data), mimeType: mimeType, attributes: attributes, hintFileIsLarge: data.count > 10 * 1024 * 1024)
|
||||
|> mapError { _ -> Void in return Void() }
|
||||
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in
|
||||
switch event {
|
||||
@ -223,7 +223,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
||||
thumbnailData = jpegData
|
||||
}
|
||||
|
||||
return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(data), thumbnailData: thumbnailData, mimeType: mimeType, attributes: [.FileName(fileName: fileName ?? "file")], hintFileIsLarge: data.count > 5 * 1024 * 1024)
|
||||
return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(data), thumbnailData: thumbnailData, mimeType: mimeType, attributes: [.FileName(fileName: fileName ?? "file")], hintFileIsLarge: data.count > 10 * 1024 * 1024)
|
||||
|> mapError { _ -> Void in return Void() }
|
||||
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in
|
||||
switch event {
|
||||
@ -241,13 +241,14 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
||||
let isVoice = ((value["isVoice"] as? NSNumber)?.boolValue ?? false)
|
||||
let title = value["title"] as? String
|
||||
let artist = value["artist"] as? String
|
||||
let mimeType = value["mimeType"] as? String ?? "audio/ogg"
|
||||
|
||||
var waveform: MemoryBuffer?
|
||||
if let waveformData = TGItemProviderSignals.audioWaveform(url) {
|
||||
waveform = MemoryBuffer(data: waveformData)
|
||||
}
|
||||
|
||||
return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(audioData), mimeType: "audio/ogg", attributes: [.Audio(isVoice: isVoice, duration: Int(duration), title: title, performer: artist, waveform: waveform), .FileName(fileName: fileName)], hintFileIsLarge: audioData.count > 5 * 1024 * 1024)
|
||||
return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(audioData), mimeType: mimeType, attributes: [.Audio(isVoice: isVoice, duration: Int(duration), title: title, performer: artist, waveform: waveform), .FileName(fileName: fileName)], hintFileIsLarge: audioData.count > 10 * 1024 * 1024)
|
||||
|> mapError { _ -> Void in return Void() }
|
||||
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in
|
||||
switch event {
|
||||
|
@ -43,7 +43,7 @@ public final class StickerPackPreviewController: ViewController, StandalonePrese
|
||||
private let openMentionDisposable = MetaDisposable()
|
||||
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
|
||||
public var sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? {
|
||||
didSet {
|
||||
if self.isNodeLoaded {
|
||||
|
@ -240,6 +240,11 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
|
||||
let controller = PeekController(theme: PeekControllerTheme(presentationTheme: strongSelf.presentationData.theme), content: content, sourceNode: {
|
||||
return sourceNode
|
||||
})
|
||||
controller.visibilityUpdated = { [weak self] visible in
|
||||
if let strongSelf = self {
|
||||
strongSelf.contentGridNode.forceHidden = visible
|
||||
}
|
||||
}
|
||||
strongSelf.presentInGlobalOverlay?(controller, nil)
|
||||
return controller
|
||||
}
|
||||
|
@ -140,33 +140,31 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
|
||||
|
||||
if self.currentState == nil || self.currentState!.0 !== account || self.currentState!.1 != stickerItem || self.isEmpty != isEmpty {
|
||||
if let stickerItem = stickerItem {
|
||||
if let _ = stickerItem.file.dimensions {
|
||||
if stickerItem.file.isAnimatedSticker {
|
||||
let dimensions = stickerItem.file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: account.postbox, file: stickerItem.file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))))
|
||||
|
||||
if self.animationNode == nil {
|
||||
let animationNode = AnimatedStickerNode()
|
||||
self.animationNode = animationNode
|
||||
self.addSubnode(animationNode)
|
||||
animationNode.started = { [weak self] in
|
||||
self?.imageNode.isHidden = true
|
||||
self?.removePlaceholder(animated: false)
|
||||
}
|
||||
if stickerItem.file.isAnimatedSticker {
|
||||
let dimensions = stickerItem.file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: account.postbox, file: stickerItem.file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))))
|
||||
|
||||
if self.animationNode == nil {
|
||||
let animationNode = AnimatedStickerNode()
|
||||
self.animationNode = animationNode
|
||||
self.addSubnode(animationNode)
|
||||
animationNode.started = { [weak self] in
|
||||
self?.imageNode.isHidden = true
|
||||
self?.removePlaceholder(animated: false)
|
||||
}
|
||||
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))
|
||||
self.animationNode?.setup(source: AnimatedStickerResourceSource(account: account, resource: stickerItem.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached)
|
||||
self.animationNode?.visibility = self.isVisibleInGrid && self.interaction?.playAnimatedStickers ?? true
|
||||
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file), resource: stickerItem.file.resource).start())
|
||||
} else {
|
||||
if let animationNode = self.animationNode {
|
||||
animationNode.visibility = false
|
||||
self.animationNode = nil
|
||||
animationNode.removeFromSupernode()
|
||||
}
|
||||
self.imageNode.setSignal(chatMessageSticker(account: account, file: stickerItem.file, small: true))
|
||||
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file), resource: chatMessageStickerResource(file: stickerItem.file, small: true)).start())
|
||||
}
|
||||
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))
|
||||
self.animationNode?.setup(source: AnimatedStickerResourceSource(account: account, resource: stickerItem.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached)
|
||||
self.animationNode?.visibility = self.isVisibleInGrid && self.interaction?.playAnimatedStickers ?? true
|
||||
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file), resource: stickerItem.file.resource).start())
|
||||
} else {
|
||||
if let animationNode = self.animationNode {
|
||||
animationNode.visibility = false
|
||||
self.animationNode = nil
|
||||
animationNode.removeFromSupernode()
|
||||
}
|
||||
self.imageNode.setSignal(chatMessageSticker(account: account, file: stickerItem.file, small: true))
|
||||
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file), resource: chatMessageStickerResource(file: stickerItem.file, small: true)).start())
|
||||
}
|
||||
} else {
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
|
@ -718,6 +718,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
jsonParams: nil,
|
||||
joinTimestamp: strongSelf.temporaryJoinTimestamp,
|
||||
activityTimestamp: nil,
|
||||
activityRank: nil,
|
||||
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
|
||||
volume: nil
|
||||
))
|
||||
|
@ -887,7 +887,11 @@ public final class VoiceChatController: ViewController {
|
||||
case .restricted:
|
||||
text = presentationData.strings.Channel_ErrorAddBlocked
|
||||
case .notMutualContact:
|
||||
text = presentationData.strings.GroupInfo_AddUserLeftError
|
||||
if case .broadcast = groupPeer.info {
|
||||
text = presentationData.strings.Channel_AddUserLeftError
|
||||
} else {
|
||||
text = presentationData.strings.GroupInfo_AddUserLeftError
|
||||
}
|
||||
case .botDoesntSupportGroups:
|
||||
text = presentationData.strings.Channel_BotDoesntSupportGroups
|
||||
case .tooMuchBots:
|
||||
@ -952,15 +956,9 @@ public final class VoiceChatController: ViewController {
|
||||
self?.controller?.present(textAlertController(context: context, title: nil, text: presentationData.strings.Privacy_GroupsAndChannels_InviteToGroupError(peer.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
})
|
||||
case .notMutualContact:
|
||||
let _ = (strongSelf.context.account.postbox.loadedPeerWithId(peer.id)
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
self?.controller?.present(textAlertController(context: context, title: nil, text: presentationData.strings.GroupInfo_AddUserLeftError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
})
|
||||
strongSelf.controller?.present(textAlertController(context: context, title: nil, text: presentationData.strings.GroupInfo_AddUserLeftError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
case .tooManyChannels:
|
||||
let _ = (strongSelf.context.account.postbox.loadedPeerWithId(peer.id)
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
self?.controller?.present(textAlertController(context: context, title: nil, text: presentationData.strings.Invite_ChannelsTooMuch, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
})
|
||||
strongSelf.controller?.present(textAlertController(context: context, title: nil, text: presentationData.strings.Invite_ChannelsTooMuch, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
case .groupFull, .generic:
|
||||
strongSelf.controller?.present(textAlertController(context: strongSelf.context, forceTheme: strongSelf.darkTheme, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}
|
||||
|
@ -1202,12 +1202,12 @@ public class Account {
|
||||
}
|
||||
}
|
||||
|
||||
public func allPeerInputActivities() -> Signal<[PeerActivitySpace: [PeerId: PeerInputActivity]], NoError> {
|
||||
public func allPeerInputActivities() -> Signal<[PeerActivitySpace: [(PeerId, PeerInputActivity)]], NoError> {
|
||||
return self.peerInputActivityManager.allActivities()
|
||||
|> map { activities in
|
||||
var result: [PeerActivitySpace: [PeerId: PeerInputActivity]] = [:]
|
||||
var result: [PeerActivitySpace: [(PeerId, PeerInputActivity)]] = [:]
|
||||
for (chatPeerId, chatActivities) in activities {
|
||||
result[chatPeerId] = chatActivities.mapValues({ $0.activity })
|
||||
result[chatPeerId] = chatActivities.map { ($0.0, $0.1.activity) }
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
@ -192,7 +192,7 @@ public enum ChatHistoryImport {
|
||||
case invalidChatType
|
||||
case userBlocked
|
||||
case limitExceeded
|
||||
case userIsNotMutualContact
|
||||
case notMutualContact
|
||||
}
|
||||
|
||||
public static func checkPeerImport(account: Account, peerId: PeerId) -> Signal<CheckPeerImportResult, CheckPeerImportError> {
|
||||
@ -217,7 +217,7 @@ public enum ChatHistoryImport {
|
||||
} else if error.errorDescription == "USER_IS_BLOCKED" {
|
||||
return .userBlocked
|
||||
} else if error.errorDescription == "USER_NOT_MUTUAL_CONTACT" {
|
||||
return .userBlocked
|
||||
return .notMutualContact
|
||||
} else if error.errorDescription == "FLOOD_WAIT" {
|
||||
return .limitExceeded
|
||||
} else {
|
||||
|
@ -6,6 +6,7 @@ import MtProtoKit
|
||||
|
||||
public enum CreateSecretChatError {
|
||||
case generic
|
||||
case limitExceeded
|
||||
}
|
||||
|
||||
public func createSecretChat(account: Account, peerId: PeerId) -> Signal<PeerId, CreateSecretChatError> {
|
||||
@ -29,9 +30,13 @@ public func createSecretChat(account: Account, peerId: PeerId) -> Signal<PeerId,
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
return account.network.request(Api.functions.messages.requestEncryption(userId: inputUser, randomId: Int32(bitPattern: arc4random()), gA: Buffer(data: ga)))
|
||||
|> mapError { _ -> CreateSecretChatError in
|
||||
return .generic
|
||||
return account.network.request(Api.functions.messages.requestEncryption(userId: inputUser, randomId: Int32(bitPattern: arc4random()), gA: Buffer(data: ga)), automaticFloodWait: false)
|
||||
|> mapError { error -> CreateSecretChatError in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT_") {
|
||||
return .limitExceeded
|
||||
} else {
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
|> mapToSignal { result -> Signal<PeerId, CreateSecretChatError> in
|
||||
return account.postbox.transaction { transaction -> PeerId in
|
||||
|
@ -116,6 +116,7 @@ public func getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int
|
||||
jsonParams: jsonParams,
|
||||
joinTimestamp: date,
|
||||
activityTimestamp: activeDate.flatMap(Double.init),
|
||||
activityRank: nil,
|
||||
muteState: muteState,
|
||||
volume: volume
|
||||
))
|
||||
@ -264,6 +265,7 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
|
||||
jsonParams: jsonParams,
|
||||
joinTimestamp: date,
|
||||
activityTimestamp: activeDate.flatMap(Double.init),
|
||||
activityRank: nil,
|
||||
muteState: muteState,
|
||||
volume: volume
|
||||
))
|
||||
@ -271,6 +273,8 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
|
||||
}
|
||||
}
|
||||
|
||||
parsedParticipants.sort()
|
||||
|
||||
return GroupCallParticipantsContext.State(
|
||||
participants: parsedParticipants,
|
||||
nextParticipantsFetchOffset: nextParticipantsFetchOffset,
|
||||
@ -311,7 +315,6 @@ public func joinGroupCall(account: Account, peerId: PeerId, callId: Int64, acces
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<JoinGroupCallResult, JoinGroupCallError> in
|
||||
|
||||
let admins: Signal<(Set<PeerId>, [Api.User]), JoinGroupCallError>
|
||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
admins = account.postbox.transaction { transaction -> Api.InputChannel? in
|
||||
@ -592,6 +595,7 @@ public final class GroupCallParticipantsContext {
|
||||
public var jsonParams: String?
|
||||
public var joinTimestamp: Int32
|
||||
public var activityTimestamp: Double?
|
||||
public var activityRank: Int?
|
||||
public var muteState: MuteState?
|
||||
public var volume: Int32?
|
||||
|
||||
@ -601,6 +605,7 @@ public final class GroupCallParticipantsContext {
|
||||
jsonParams: String?,
|
||||
joinTimestamp: Int32,
|
||||
activityTimestamp: Double?,
|
||||
activityRank: Int?,
|
||||
muteState: MuteState?,
|
||||
volume: Int32?
|
||||
) {
|
||||
@ -609,6 +614,7 @@ public final class GroupCallParticipantsContext {
|
||||
self.jsonParams = jsonParams
|
||||
self.joinTimestamp = joinTimestamp
|
||||
self.activityTimestamp = activityTimestamp
|
||||
self.activityRank = activityRank
|
||||
self.muteState = muteState
|
||||
self.volume = volume
|
||||
}
|
||||
@ -626,6 +632,9 @@ public final class GroupCallParticipantsContext {
|
||||
if lhs.activityTimestamp != rhs.activityTimestamp {
|
||||
return false
|
||||
}
|
||||
if lhs.activityRank != rhs.activityRank {
|
||||
return false
|
||||
}
|
||||
if lhs.muteState != rhs.muteState {
|
||||
return false
|
||||
}
|
||||
@ -636,6 +645,16 @@ public final class GroupCallParticipantsContext {
|
||||
}
|
||||
|
||||
public static func <(lhs: Participant, rhs: Participant) -> Bool {
|
||||
if let lhsActivityRank = lhs.activityRank, let rhsActivityRank = rhs.activityRank {
|
||||
if lhsActivityRank != rhsActivityRank {
|
||||
return lhsActivityRank < rhsActivityRank
|
||||
}
|
||||
} else if lhs.activityRank != nil {
|
||||
return true
|
||||
} else if rhs.activityRank != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if let lhsActivityTimestamp = lhs.activityTimestamp, let rhsActivityTimestamp = rhs.activityTimestamp {
|
||||
if lhsActivityTimestamp != rhsActivityTimestamp {
|
||||
return lhsActivityTimestamp > rhsActivityTimestamp
|
||||
@ -823,6 +842,9 @@ public final class GroupCallParticipantsContext {
|
||||
private var shouldResetStateFromServer: Bool = false
|
||||
private var missingSsrcs = Set<UInt32>()
|
||||
|
||||
private var nextActivityRank: Int = 0
|
||||
private var activityRankResetTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private let updateDefaultMuteDisposable = MetaDisposable()
|
||||
|
||||
public init(account: Account, peerId: PeerId, id: Int64, accessHash: Int64, state: State) {
|
||||
@ -912,6 +934,36 @@ public final class GroupCallParticipantsContext {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.activityRankResetTimer = SwiftSignalKit.Timer(timeout: 10.0, repeat: true, completion: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
var updated = false
|
||||
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
||||
|
||||
for i in 0 ..< strongSelf.stateValue.state.participants.count {
|
||||
if strongSelf.stateValue.state.participants[i].activityRank != nil {
|
||||
var clearRank = false
|
||||
if let activityTimestamp = strongSelf.stateValue.state.participants[i].activityTimestamp {
|
||||
if activityTimestamp < timestamp - 60.0 {
|
||||
clearRank = true
|
||||
}
|
||||
} else {
|
||||
clearRank = true
|
||||
}
|
||||
if clearRank {
|
||||
updated = true
|
||||
strongSelf.stateValue.state.participants[i].activityRank = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if updated {
|
||||
strongSelf.stateValue.state.participants.sort()
|
||||
}
|
||||
}, queue: .mainQueue())
|
||||
self.activityRankResetTimer?.start()
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -919,6 +971,7 @@ public final class GroupCallParticipantsContext {
|
||||
self.updatesDisposable.dispose()
|
||||
self.activitiesDisposable?.dispose()
|
||||
self.updateDefaultMuteDisposable.dispose()
|
||||
self.activityRankResetTimer?.invalidate()
|
||||
}
|
||||
|
||||
public func addUpdates(updates: [Update]) {
|
||||
@ -937,6 +990,12 @@ public final class GroupCallParticipantsContext {
|
||||
}
|
||||
}
|
||||
|
||||
private func takeNextActivityRank() -> Int {
|
||||
let value = self.nextActivityRank
|
||||
self.nextActivityRank += 1
|
||||
return value
|
||||
}
|
||||
|
||||
public func reportSpeakingParticipants(ids: [PeerId: UInt32]) {
|
||||
if !ids.isEmpty {
|
||||
self.hasReceivedSpeakingParticipantsReport = true
|
||||
@ -965,6 +1024,9 @@ public final class GroupCallParticipantsContext {
|
||||
}
|
||||
if updateTimestamp {
|
||||
updatedParticipants[index].activityTimestamp = timestamp
|
||||
if updatedParticipants[index].activityRank == nil {
|
||||
updatedParticipants[index].activityRank = self.takeNextActivityRank()
|
||||
}
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
@ -1040,19 +1102,7 @@ public final class GroupCallParticipantsContext {
|
||||
|
||||
var updatedState = strongSelf.stateValue.state
|
||||
|
||||
var existingParticipantIds = Set<PeerId>()
|
||||
for participant in updatedState.participants {
|
||||
existingParticipantIds.insert(participant.peer.id)
|
||||
}
|
||||
for participant in state.participants {
|
||||
if existingParticipantIds.contains(participant.peer.id) {
|
||||
continue
|
||||
}
|
||||
existingParticipantIds.insert(participant.peer.id)
|
||||
updatedState.participants.append(participant)
|
||||
}
|
||||
|
||||
updatedState.participants.sort()
|
||||
updatedState.participants = mergeAndSortParticipants(current: updatedState.participants, with: state.participants)
|
||||
|
||||
updatedState.totalCount = max(updatedState.totalCount, state.totalCount)
|
||||
updatedState.version = max(updatedState.version, updatedState.version)
|
||||
@ -1138,8 +1188,10 @@ public final class GroupCallParticipantsContext {
|
||||
continue
|
||||
}
|
||||
var previousActivityTimestamp: Double?
|
||||
var previousActivityRank: Int?
|
||||
if let index = updatedParticipants.firstIndex(where: { $0.peer.id == participantUpdate.peerId }) {
|
||||
previousActivityTimestamp = updatedParticipants[index].activityTimestamp
|
||||
previousActivityRank = updatedParticipants[index].activityRank
|
||||
updatedParticipants.remove(at: index)
|
||||
} else if case .joined = participantUpdate.participationStatusChange {
|
||||
updatedTotalCount += 1
|
||||
@ -1159,6 +1211,7 @@ public final class GroupCallParticipantsContext {
|
||||
jsonParams: participantUpdate.jsonParams,
|
||||
joinTimestamp: participantUpdate.joinTimestamp,
|
||||
activityTimestamp: activityTimestamp,
|
||||
activityRank: previousActivityRank,
|
||||
muteState: participantUpdate.muteState,
|
||||
volume: participantUpdate.volume
|
||||
)
|
||||
@ -1179,14 +1232,6 @@ public final class GroupCallParticipantsContext {
|
||||
let defaultParticipantsAreMuted = strongSelf.stateValue.state.defaultParticipantsAreMuted
|
||||
|
||||
updatedParticipants.sort()
|
||||
/*for i in 0 ..< updatedParticipants.count {
|
||||
if updatedParticipants[i].peer.id == strongSelf.account.peerId {
|
||||
let member = updatedParticipants[i]
|
||||
updatedParticipants.remove(at: i)
|
||||
updatedParticipants.insert(member, at: 0)
|
||||
break
|
||||
}
|
||||
}*/
|
||||
|
||||
strongSelf.stateValue = InternalState(
|
||||
state: State(
|
||||
@ -1344,17 +1389,7 @@ public final class GroupCallParticipantsContext {
|
||||
|
||||
var updatedState = strongSelf.stateValue.state
|
||||
|
||||
var existingParticipantIds = Set<PeerId>()
|
||||
for participant in updatedState.participants {
|
||||
existingParticipantIds.insert(participant.peer.id)
|
||||
}
|
||||
for participant in state.participants {
|
||||
if existingParticipantIds.contains(participant.peer.id) {
|
||||
continue
|
||||
}
|
||||
existingParticipantIds.insert(participant.peer.id)
|
||||
updatedState.participants.append(participant)
|
||||
}
|
||||
updatedState.participants = mergeAndSortParticipants(current: updatedState.participants, with: state.participants)
|
||||
|
||||
updatedState.nextParticipantsFetchOffset = state.nextParticipantsFetchOffset
|
||||
updatedState.totalCount = max(updatedState.totalCount, state.totalCount)
|
||||
@ -1513,3 +1548,23 @@ public func updatedCurrentPeerGroupCall(account: Account, peerId: PeerId) -> Sig
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func mergeAndSortParticipants(current currentParticipants: [GroupCallParticipantsContext.Participant], with updatedParticipants: [GroupCallParticipantsContext.Participant]) -> [GroupCallParticipantsContext.Participant] {
|
||||
var mergedParticipants = currentParticipants
|
||||
|
||||
var existingParticipantIndices: [PeerId: Int] = [:]
|
||||
for i in 0 ..< mergedParticipants.count {
|
||||
existingParticipantIndices[mergedParticipants[i].peer.id] = i
|
||||
}
|
||||
for participant in updatedParticipants {
|
||||
if let _ = existingParticipantIndices[participant.peer.id] {
|
||||
} else {
|
||||
existingParticipantIndices[participant.peer.id] = mergedParticipants.count
|
||||
mergedParticipants.append(participant)
|
||||
}
|
||||
}
|
||||
|
||||
mergedParticipants.sort()
|
||||
|
||||
return mergedParticipants
|
||||
}
|
||||
|
@ -529,7 +529,7 @@ private final class PeerExportedInvitationsContextImpl {
|
||||
}
|
||||
|
||||
private func updateCache() {
|
||||
guard self.hasLoadedOnce && !self.isLoadingMore else {
|
||||
guard self.isMainList && self.hasLoadedOnce && !self.isLoadingMore else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -2,26 +2,37 @@ import Foundation
|
||||
import Postbox
|
||||
import TelegramApi
|
||||
import SwiftSignalKit
|
||||
import MtProtoKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
public enum JoinChannelError {
|
||||
case generic
|
||||
case tooMuchJoined
|
||||
case tooMuchUsers
|
||||
}
|
||||
|
||||
public func joinChannel(account: Account, peerId: PeerId) -> Signal<RenderedChannelParticipant?, JoinChannelError> {
|
||||
public func joinChannel(account: Account, peerId: PeerId, hash: String?) -> Signal<RenderedChannelParticipant?, JoinChannelError> {
|
||||
return account.postbox.loadedPeerWithId(peerId)
|
||||
|> take(1)
|
||||
|> castError(JoinChannelError.self)
|
||||
|> mapToSignal { peer -> Signal<RenderedChannelParticipant?, JoinChannelError> in
|
||||
if let inputChannel = apiInputChannel(peer) {
|
||||
return account.network.request(Api.functions.channels.joinChannel(channel: inputChannel))
|
||||
let request: Signal<Api.Updates, MTRpcError>
|
||||
if let hash = hash {
|
||||
request = account.network.request(Api.functions.messages.importChatInvite(hash: hash))
|
||||
} else {
|
||||
request = account.network.request(Api.functions.channels.joinChannel(channel: inputChannel))
|
||||
}
|
||||
return request
|
||||
|> mapError { error -> JoinChannelError in
|
||||
if error.errorDescription == "CHANNELS_TOO_MUCH" {
|
||||
return .tooMuchJoined
|
||||
} else {
|
||||
return .generic
|
||||
switch error.errorDescription {
|
||||
case "CHANNELS_TOO_MUCH":
|
||||
return .tooMuchJoined
|
||||
case "USERS_TOO_MUCH":
|
||||
return .tooMuchUsers
|
||||
default:
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<RenderedChannelParticipant?, JoinChannelError> in
|
||||
|
@ -8,6 +8,7 @@ import SyncCore
|
||||
public enum JoinLinkError {
|
||||
case generic
|
||||
case tooMuchJoined
|
||||
case tooMuchUsers
|
||||
}
|
||||
|
||||
func apiUpdatesGroups(_ updates: Api.Updates) -> [Api.Chat] {
|
||||
@ -31,10 +32,13 @@ public enum ExternalJoiningChatState {
|
||||
public func joinChatInteractively(with hash: String, account: Account) -> Signal <PeerId?, JoinLinkError> {
|
||||
return account.network.request(Api.functions.messages.importChatInvite(hash: hash))
|
||||
|> mapError { error -> JoinLinkError in
|
||||
if error.errorDescription == "CHANNELS_TOO_MUCH" {
|
||||
return .tooMuchJoined
|
||||
} else {
|
||||
return .generic
|
||||
switch error.errorDescription {
|
||||
case "CHANNELS_TOO_MUCH":
|
||||
return .tooMuchJoined
|
||||
case "USERS_TOO_MUCH":
|
||||
return .tooMuchUsers
|
||||
default:
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<PeerId?, JoinLinkError> in
|
||||
|
@ -133,9 +133,9 @@ func managedConsumePersonalMessagesActions(postbox: Postbox, network: Network, s
|
||||
|
||||
for (entry, disposable) in beginValidateOperations {
|
||||
let signal = synchronizeUnseenPersonalMentionsTag(postbox: postbox, network: network, entry: entry)
|
||||
|> then(postbox.transaction { transaction -> Void in
|
||||
transaction.removeInvalidatedMessageHistoryTagsSummaryEntry(entry)
|
||||
})
|
||||
|> then(postbox.transaction { transaction -> Void in
|
||||
transaction.removeInvalidatedMessageHistoryTagsSummaryEntry(entry)
|
||||
})
|
||||
disposable.set(signal.start())
|
||||
}
|
||||
})
|
||||
|
@ -30,13 +30,13 @@ struct PeerInputActivityRecord: Equatable {
|
||||
private final class ManagedLocalTypingActivitiesContext {
|
||||
private var disposables: [PeerActivitySpace: (PeerInputActivityRecord, MetaDisposable)] = [:]
|
||||
|
||||
func update(activities: [PeerActivitySpace: [PeerId: PeerInputActivityRecord]]) -> (start: [(PeerActivitySpace, PeerInputActivityRecord?, MetaDisposable)], dispose: [MetaDisposable]) {
|
||||
func update(activities: [PeerActivitySpace: [(PeerId, PeerInputActivityRecord)]]) -> (start: [(PeerActivitySpace, PeerInputActivityRecord?, MetaDisposable)], dispose: [MetaDisposable]) {
|
||||
var start: [(PeerActivitySpace, PeerInputActivityRecord?, MetaDisposable)] = []
|
||||
var dispose: [MetaDisposable] = []
|
||||
|
||||
var validPeerIds = Set<PeerActivitySpace>()
|
||||
for (peerId, record) in activities {
|
||||
if let activity = record.values.first {
|
||||
if let activity = record.first?.1 {
|
||||
validPeerIds.insert(peerId)
|
||||
|
||||
let currentRecord = self.disposables[peerId]
|
||||
@ -76,7 +76,7 @@ private final class ManagedLocalTypingActivitiesContext {
|
||||
}
|
||||
}
|
||||
|
||||
func managedLocalTypingActivities(activities: Signal<[PeerActivitySpace: [PeerId: PeerInputActivityRecord]], NoError>, postbox: Postbox, network: Network, accountPeerId: PeerId) -> Signal<Void, NoError> {
|
||||
func managedLocalTypingActivities(activities: Signal<[PeerActivitySpace: [(PeerId, PeerInputActivityRecord)]], NoError>, postbox: Postbox, network: Network, accountPeerId: PeerId) -> Signal<Void, NoError> {
|
||||
return Signal { subscriber in
|
||||
let context = Atomic(value: ManagedLocalTypingActivitiesContext())
|
||||
let disposable = activities.start(next: { activities in
|
||||
|
@ -202,6 +202,8 @@ public func updateChannelAdminRights(account: Account, peerId: PeerId, adminId:
|
||||
}
|
||||
|> map { [$0] }
|
||||
)
|
||||
} else if error.errorDescription == "USER_NOT_MUTUAL_CONTACT" {
|
||||
return .fail(.addMemberError(.notMutualContact))
|
||||
} else if error.errorDescription == "USER_PRIVACY_RESTRICTED" {
|
||||
return .fail(.addMemberError(.restricted))
|
||||
} else if error.errorDescription == "USER_CHANNELS_TOO_MUCH" {
|
||||
|
@ -178,9 +178,9 @@ private final class PeerInputActivityContext {
|
||||
}
|
||||
|
||||
private final class PeerGlobalInputActivityContext {
|
||||
private let subscribers = Bag<([PeerActivitySpace: [PeerId: PeerInputActivityRecord]]) -> Void>()
|
||||
private let subscribers = Bag<([PeerActivitySpace: [(PeerId, PeerInputActivityRecord)]]) -> Void>()
|
||||
|
||||
func addSubscriber(_ subscriber: @escaping ([PeerActivitySpace: [PeerId: PeerInputActivityRecord]]) -> Void) -> Int {
|
||||
func addSubscriber(_ subscriber: @escaping ([PeerActivitySpace: [(PeerId, PeerInputActivityRecord)]]) -> Void) -> Int {
|
||||
return self.subscribers.add(subscriber)
|
||||
}
|
||||
|
||||
@ -192,7 +192,7 @@ private final class PeerGlobalInputActivityContext {
|
||||
return self.subscribers.isEmpty
|
||||
}
|
||||
|
||||
func notify(_ activities: [PeerActivitySpace: [PeerId: PeerInputActivityRecord]]) {
|
||||
func notify(_ activities: [PeerActivitySpace: [(PeerId, PeerInputActivityRecord)]]) {
|
||||
for subscriber in self.subscribers.copyItems() {
|
||||
subscriber(activities)
|
||||
}
|
||||
@ -256,21 +256,17 @@ final class PeerInputActivityManager {
|
||||
}
|
||||
}
|
||||
|
||||
private func collectActivities() -> [PeerActivitySpace: [PeerId: PeerInputActivityRecord]] {
|
||||
private func collectActivities() -> [PeerActivitySpace: [(PeerId, PeerInputActivityRecord)]] {
|
||||
assert(self.queue.isCurrent())
|
||||
|
||||
var dict: [PeerActivitySpace: [PeerId: PeerInputActivityRecord]] = [:]
|
||||
var dict: [PeerActivitySpace: [(PeerId, PeerInputActivityRecord)]] = [:]
|
||||
for (chatPeerId, context) in self.contexts {
|
||||
var chatDict: [PeerId: PeerInputActivityRecord] = [:]
|
||||
for (peerId, activity) in context.topActivities() {
|
||||
chatDict[peerId] = activity
|
||||
}
|
||||
dict[chatPeerId] = chatDict
|
||||
dict[chatPeerId] = context.topActivities()
|
||||
}
|
||||
return dict
|
||||
}
|
||||
|
||||
func allActivities() -> Signal<[PeerActivitySpace: [PeerId: PeerInputActivityRecord]], NoError> {
|
||||
func allActivities() -> Signal<[PeerActivitySpace: [(PeerId, PeerInputActivityRecord)]], NoError> {
|
||||
let queue = self.queue
|
||||
return Signal { [weak self] subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
|
@ -623,7 +623,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|
||||
hintFileIsLarge = true
|
||||
break loop
|
||||
default:
|
||||
break loop
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,6 +126,22 @@ public func reportPeer(account: Account, peerId: PeerId, reason: ReportReason, m
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
public func reportPeerPhoto(account: Account, peerId: PeerId, reason: ReportReason, message: String) -> Signal<Void, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
return account.network.request(Api.functions.account.reportProfilePhoto(peer: inputPeer, photoId: .inputPhotoEmpty, reason: reason.apiReason, message: message))
|
||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||
return .single(.boolFalse)
|
||||
}
|
||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
public func reportPeerMessages(account: Account, messageIds: [MessageId], reason: ReportReason, message: String) -> Signal<Void, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
let groupedIds = messagesIdsGroupedByPeerId(messageIds)
|
||||
@ -149,22 +165,6 @@ public func reportPeerMessages(account: Account, messageIds: [MessageId], reason
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
public func reportSupergroupPeer(account: Account, peerId: PeerId, memberId: PeerId, messageIds: [MessageId]) -> Signal<Void, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputChannel(peer), let memberPeer = transaction.getPeer(memberId), let inputMember = apiInputUser(memberPeer) {
|
||||
return account.network.request(Api.functions.channels.reportSpam(channel: inputPeer, userId: inputMember, id: messageIds.map({$0.id})))
|
||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||
return .single(.boolFalse)
|
||||
}
|
||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
public func dismissPeerStatusOptions(account: Account, peerId: PeerId) -> Signal<Void, NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||
|
@ -174,62 +174,89 @@ public enum MessageActionUrlAuthResult {
|
||||
case request(String, Peer, Bool)
|
||||
}
|
||||
|
||||
public func requestMessageActionUrlAuth(account: Account, messageId: MessageId, buttonId: Int32) -> Signal<MessageActionUrlAuthResult, NoError> {
|
||||
return account.postbox.loadedPeerWithId(messageId.peerId)
|
||||
|> take(1)
|
||||
|> mapToSignal { peer in
|
||||
if let inputPeer = apiInputPeer(peer) {
|
||||
return account.network.request(Api.functions.messages.requestUrlAuth(peer: inputPeer, msgId: messageId.id, buttonId: buttonId))
|
||||
public enum MessageActionUrlSubject {
|
||||
case message(id: MessageId, buttonId: Int32)
|
||||
case url(String)
|
||||
}
|
||||
|
||||
public func requestMessageActionUrlAuth(account: Account, subject: MessageActionUrlSubject) -> Signal<MessageActionUrlAuthResult, NoError> {
|
||||
let request: Signal<Api.UrlAuthResult?, MTRpcError>
|
||||
switch subject {
|
||||
case let .message(messageId, buttonId):
|
||||
request = account.postbox.loadedPeerWithId(messageId.peerId)
|
||||
|> take(1)
|
||||
|> castError(MTRpcError.self)
|
||||
|> mapToSignal { peer -> Signal<Api.UrlAuthResult?, MTRpcError> in
|
||||
if let inputPeer = apiInputPeer(peer) {
|
||||
return account.network.request(Api.functions.messages.requestUrlAuth(peer: inputPeer, msgId: messageId.id, buttonId: buttonId))
|
||||
|> map(Optional.init)
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
case let .url(url):
|
||||
request = account.network.request(Api.functions.messages.requestUrlAuth(peer: .inputPeerEmpty, msgId: 0, buttonId: 0))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.UrlAuthResult?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> map { result -> MessageActionUrlAuthResult in
|
||||
guard let result = result else {
|
||||
return .default
|
||||
}
|
||||
switch result {
|
||||
case .urlAuthResultDefault:
|
||||
return .default
|
||||
case let .urlAuthResultAccepted(url):
|
||||
return .accepted(url)
|
||||
case let .urlAuthResultRequest(flags, bot, domain):
|
||||
return .request(domain, TelegramUser(user: bot), (flags & (1 << 0)) != 0)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(.default)
|
||||
}
|
||||
|
||||
return request
|
||||
|> `catch` { _ -> Signal<Api.UrlAuthResult?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> map { result -> MessageActionUrlAuthResult in
|
||||
guard let result = result else {
|
||||
return .default
|
||||
}
|
||||
switch result {
|
||||
case .urlAuthResultDefault:
|
||||
return .default
|
||||
case let .urlAuthResultAccepted(url):
|
||||
return .accepted(url)
|
||||
case let .urlAuthResultRequest(flags, bot, domain):
|
||||
return .request(domain, TelegramUser(user: bot), (flags & (1 << 0)) != 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func acceptMessageActionUrlAuth(account: Account, messageId: MessageId, buttonId: Int32, allowWriteAccess: Bool) -> Signal<MessageActionUrlAuthResult, NoError> {
|
||||
return account.postbox.loadedPeerWithId(messageId.peerId)
|
||||
|> take(1)
|
||||
|> mapToSignal { peer in
|
||||
if let inputPeer = apiInputPeer(peer) {
|
||||
var flags: Int32 = 0
|
||||
if allowWriteAccess {
|
||||
flags |= Int32(1 << 0)
|
||||
public func acceptMessageActionUrlAuth(account: Account, subject: MessageActionUrlSubject, allowWriteAccess: Bool) -> Signal<MessageActionUrlAuthResult, NoError> {
|
||||
var flags: Int32 = 0
|
||||
if allowWriteAccess {
|
||||
flags |= Int32(1 << 0)
|
||||
}
|
||||
|
||||
let request: Signal<Api.UrlAuthResult?, MTRpcError>
|
||||
switch subject {
|
||||
case let .message(messageId, buttonId):
|
||||
request = account.postbox.loadedPeerWithId(messageId.peerId)
|
||||
|> take(1)
|
||||
|> castError(MTRpcError.self)
|
||||
|> mapToSignal { peer -> Signal<Api.UrlAuthResult?, MTRpcError> in
|
||||
if let inputPeer = apiInputPeer(peer) {
|
||||
return account.network.request(Api.functions.messages.acceptUrlAuth(flags: flags, peer: inputPeer, msgId: messageId.id, buttonId: buttonId))
|
||||
|> map(Optional.init)
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
return account.network.request(Api.functions.messages.acceptUrlAuth(flags: flags, peer: inputPeer, msgId: messageId.id, buttonId: buttonId))
|
||||
case let .url(url):
|
||||
request = account.network.request(Api.functions.messages.acceptUrlAuth(flags: flags, peer: .inputPeerEmpty, msgId: 0, buttonId: 0))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.UrlAuthResult?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> map { result -> MessageActionUrlAuthResult in
|
||||
guard let result = result else {
|
||||
return .default
|
||||
}
|
||||
switch result {
|
||||
case let .urlAuthResultAccepted(url):
|
||||
return .accepted(url)
|
||||
default:
|
||||
return .default
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(.default)
|
||||
}
|
||||
|
||||
|
||||
return request
|
||||
|> `catch` { _ -> Signal<Api.UrlAuthResult?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> map { result -> MessageActionUrlAuthResult in
|
||||
guard let result = result else {
|
||||
return .default
|
||||
}
|
||||
switch result {
|
||||
case let .urlAuthResultAccepted(url):
|
||||
return .accepted(url)
|
||||
default:
|
||||
return .default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -212,7 +212,7 @@ public struct PresentationResourcesChatList {
|
||||
})
|
||||
}
|
||||
|
||||
public static func scamIcon(_ theme: PresentationTheme, type: ScamIconType) -> UIImage? {
|
||||
public static func scamIcon(_ theme: PresentationTheme, strings: PresentationStrings, type: ScamIconType) -> UIImage? {
|
||||
let key: PresentationResourceKey
|
||||
let color: UIColor
|
||||
switch type {
|
||||
@ -227,7 +227,10 @@ public struct PresentationResourcesChatList {
|
||||
color = theme.chat.serviceMessage.components.withDefaultWallpaper.scam
|
||||
}
|
||||
return theme.image(key.rawValue, { theme in
|
||||
return generateImage(CGSize(width: 37.0, height: 16.0), contextGenerator: { size, context in
|
||||
let titleString = NSAttributedString(string: strings.Message_ScamAccount.uppercased(), font: Font.bold(10.0), textColor: color, paragraphAlignment: .center)
|
||||
let stringRect = titleString.boundingRect(with: CGSize(width: 100.0, height: 16.0), options: .usesLineFragmentOrigin, context: nil)
|
||||
|
||||
return generateImage(CGSize(width: floor(stringRect.width) + 11.0, height: 16.0), contextGenerator: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
@ -240,7 +243,6 @@ public struct PresentationResourcesChatList {
|
||||
|
||||
let titlePath = CGMutablePath()
|
||||
titlePath.addRect(bounds.offsetBy(dx: 0.0, dy: -2.0 + UIScreenPixel))
|
||||
let titleString = NSAttributedString(string: "SCAM", font: Font.bold(10.0), textColor: color, paragraphAlignment: .center)
|
||||
let titleFramesetter = CTFramesetterCreateWithAttributedString(titleString as CFAttributedString)
|
||||
let titleFrame = CTFramesetterCreateFrame(titleFramesetter, CFRangeMake(0, titleString.length), titlePath, nil)
|
||||
CTFrameDraw(titleFrame, context)
|
||||
@ -248,7 +250,7 @@ public struct PresentationResourcesChatList {
|
||||
})
|
||||
}
|
||||
|
||||
public static func fakeIcon(_ theme: PresentationTheme, type: ScamIconType) -> UIImage? {
|
||||
public static func fakeIcon(_ theme: PresentationTheme, strings: PresentationStrings, type: ScamIconType) -> UIImage? {
|
||||
let key: PresentationResourceKey
|
||||
let color: UIColor
|
||||
switch type {
|
||||
@ -263,7 +265,10 @@ public struct PresentationResourcesChatList {
|
||||
color = theme.chat.serviceMessage.components.withDefaultWallpaper.scam
|
||||
}
|
||||
return theme.image(key.rawValue, { theme in
|
||||
return generateImage(CGSize(width: 37.0, height: 16.0), contextGenerator: { size, context in
|
||||
let titleString = NSAttributedString(string: strings.Message_FakeAccount.uppercased(), font: Font.bold(10.0), textColor: color, paragraphAlignment: .center)
|
||||
let stringRect = titleString.boundingRect(with: CGSize(width: 100.0, height: 16.0), options: .usesLineFragmentOrigin, context: nil)
|
||||
|
||||
return generateImage(CGSize(width: floor(stringRect.width) + 11.0, height: 16.0), contextGenerator: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
@ -276,7 +281,6 @@ public struct PresentationResourcesChatList {
|
||||
|
||||
let titlePath = CGMutablePath()
|
||||
titlePath.addRect(bounds.offsetBy(dx: 0.0, dy: -2.0 + UIScreenPixel))
|
||||
let titleString = NSAttributedString(string: "FAKE", font: Font.bold(10.0), textColor: color, paragraphAlignment: .center)
|
||||
let titleFramesetter = CTFramesetterCreateWithAttributedString(titleString as CFAttributedString)
|
||||
let titleFrame = CTFramesetterCreateFrame(titleFramesetter, CFRangeMake(0, titleString.length), titlePath, nil)
|
||||
CTFrameDraw(titleFrame, context)
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/VideoCall.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/VideoCall.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_menuvideocall.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/VideoCall.imageset/ic_menuvideocall.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/VideoCall.imageset/ic_menuvideocall.pdf
vendored
Normal file
Binary file not shown.
Binary file not shown.
@ -27,8 +27,8 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.containerNode.addSubnode(self.avatarNode)
|
||||
self.addSubnode(self.containerNode)
|
||||
self.containerNode.addSubnode(self.avatarNode)
|
||||
|
||||
self.containerNode.activated = { [weak self] gesture, _ in
|
||||
guard let strongSelf = self else {
|
||||
|
@ -209,7 +209,7 @@ final class ChatButtonKeyboardInputNode: ChatInputNode {
|
||||
break
|
||||
case let .urlAuth(url, buttonId):
|
||||
if let message = self.message {
|
||||
self.controllerInteraction.requestMessageActionUrlAuth(url, message.id, buttonId)
|
||||
self.controllerInteraction.requestMessageActionUrlAuth(url, .message(id: message.id, buttonId: buttonId))
|
||||
}
|
||||
case let .setupPoll(isQuiz):
|
||||
self.controllerInteraction.openPollCreation(isQuiz)
|
||||
|
@ -168,7 +168,7 @@ final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
||||
case .join:
|
||||
self.activityIndicator.isHidden = false
|
||||
self.activityIndicator.startAnimating()
|
||||
self.actionDisposable.set((context.peerChannelMemberCategoriesContextsManager.join(account: context.account, peerId: peer.id)
|
||||
self.actionDisposable.set((context.peerChannelMemberCategoriesContextsManager.join(account: context.account, peerId: peer.id, hash: nil)
|
||||
|> afterDisposed { [weak self] in
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self {
|
||||
@ -189,7 +189,9 @@ final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
||||
}
|
||||
}))
|
||||
return
|
||||
default:
|
||||
case .tooMuchUsers:
|
||||
text = presentationInterfaceState.strings.Conversation_UsersTooMuchError
|
||||
case .generic:
|
||||
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
text = presentationInterfaceState.strings.Channel_ErrorAccessDenied
|
||||
} else {
|
||||
|
@ -306,6 +306,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
private var automaticMediaDownloadSettings: MediaAutoDownloadSettings
|
||||
private var automaticMediaDownloadSettingsDisposable: Disposable?
|
||||
|
||||
private var disableStickerAnimationsPromise = ValuePromise<Bool>(false)
|
||||
private var disableStickerAnimationsValue = false
|
||||
var disableStickerAnimations: Bool {
|
||||
get {
|
||||
return self.disableStickerAnimationsValue
|
||||
} set {
|
||||
self.disableStickerAnimationsPromise.set(newValue)
|
||||
}
|
||||
}
|
||||
private var stickerSettings: ChatInterfaceStickerSettings
|
||||
private var stickerSettingsDisposable: Disposable?
|
||||
|
||||
@ -1163,124 +1172,122 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
}
|
||||
}, requestMessageActionUrlAuth: { [weak self] defaultUrl, messageId, buttonId in
|
||||
}, requestMessageActionUrlAuth: { [weak self] defaultUrl, subject in
|
||||
if let strongSelf = self {
|
||||
guard strongSelf.presentationInterfaceState.subject != .scheduledMessages else {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.ScheduledMessages_BotActionUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
return
|
||||
}
|
||||
if let _ = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
return $0.updatedTitlePanelContext {
|
||||
if !$0.contains(where: {
|
||||
switch $0 {
|
||||
case .requestInProgress:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
var updatedContexts = $0
|
||||
updatedContexts.append(.requestInProgress)
|
||||
return updatedContexts.sorted()
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
return $0.updatedTitlePanelContext {
|
||||
if !$0.contains(where: {
|
||||
switch $0 {
|
||||
case .requestInProgress:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return $0
|
||||
}) {
|
||||
var updatedContexts = $0
|
||||
updatedContexts.append(.requestInProgress)
|
||||
return updatedContexts.sorted()
|
||||
}
|
||||
})
|
||||
strongSelf.messageActionUrlAuthDisposable.set(((combineLatest(strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.context.account.peerId), requestMessageActionUrlAuth(account: strongSelf.context.account, messageId: messageId, buttonId: buttonId) |> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
return $0.updatedTitlePanelContext {
|
||||
if let index = $0.firstIndex(where: {
|
||||
switch $0 {
|
||||
case .requestInProgress:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
var updatedContexts = $0
|
||||
updatedContexts.remove(at: index)
|
||||
return updatedContexts
|
||||
return $0
|
||||
}
|
||||
})
|
||||
strongSelf.messageActionUrlAuthDisposable.set(((combineLatest(strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.context.account.peerId), requestMessageActionUrlAuth(account: strongSelf.context.account, subject: subject) |> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
return $0.updatedTitlePanelContext {
|
||||
if let index = $0.firstIndex(where: {
|
||||
switch $0 {
|
||||
case .requestInProgress:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
var updatedContexts = $0
|
||||
updatedContexts.remove(at: index)
|
||||
return updatedContexts
|
||||
}
|
||||
return $0
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})) |> deliverOnMainQueue).start(next: { peer, result in
|
||||
if let strongSelf = self {
|
||||
switch result {
|
||||
case .default:
|
||||
strongSelf.openUrl(defaultUrl, concealed: false)
|
||||
case let .request(domain, bot, requestWriteAccess):
|
||||
let controller = chatMessageActionUrlAuthController(context: strongSelf.context, defaultUrl: defaultUrl, domain: domain, bot: bot, requestWriteAccess: requestWriteAccess, displayName: peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), open: { [weak self] authorize, allowWriteAccess in
|
||||
if let strongSelf = self {
|
||||
if authorize {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
return $0.updatedTitlePanelContext {
|
||||
if !$0.contains(where: {
|
||||
switch $0 {
|
||||
case .requestInProgress:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
var updatedContexts = $0
|
||||
updatedContexts.append(.requestInProgress)
|
||||
return updatedContexts.sorted()
|
||||
}
|
||||
return $0
|
||||
}
|
||||
})
|
||||
|
||||
strongSelf.messageActionUrlAuthDisposable.set(((acceptMessageActionUrlAuth(account: strongSelf.context.account, subject: subject, allowWriteAccess: allowWriteAccess) |> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
return $0.updatedTitlePanelContext {
|
||||
if let index = $0.firstIndex(where: {
|
||||
switch $0 {
|
||||
case .requestInProgress:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
var updatedContexts = $0
|
||||
updatedContexts.remove(at: index)
|
||||
return updatedContexts
|
||||
}
|
||||
return $0
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}) |> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
if let strongSelf = self {
|
||||
switch result {
|
||||
case let .accepted(url):
|
||||
strongSelf.openUrl(url, concealed: false)
|
||||
default:
|
||||
strongSelf.openUrl(defaultUrl, concealed: false)
|
||||
}
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
strongSelf.openUrl(defaultUrl, concealed: false)
|
||||
}
|
||||
return $0
|
||||
}
|
||||
})
|
||||
}
|
||||
strongSelf.chatDisplayNode.dismissInput()
|
||||
strongSelf.present(controller, in: .window(.root))
|
||||
case let .accepted(url):
|
||||
strongSelf.openUrl(url, concealed: false)
|
||||
}
|
||||
})) |> deliverOnMainQueue).start(next: { peer, result in
|
||||
if let strongSelf = self {
|
||||
switch result {
|
||||
case .default:
|
||||
strongSelf.openUrl(defaultUrl, concealed: false)
|
||||
case let .request(domain, bot, requestWriteAccess):
|
||||
let controller = chatMessageActionUrlAuthController(context: strongSelf.context, defaultUrl: defaultUrl, domain: domain, bot: bot, requestWriteAccess: requestWriteAccess, displayName: peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), open: { [weak self] authorize, allowWriteAccess in
|
||||
if let strongSelf = self {
|
||||
if authorize {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
return $0.updatedTitlePanelContext {
|
||||
if !$0.contains(where: {
|
||||
switch $0 {
|
||||
case .requestInProgress:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
var updatedContexts = $0
|
||||
updatedContexts.append(.requestInProgress)
|
||||
return updatedContexts.sorted()
|
||||
}
|
||||
return $0
|
||||
}
|
||||
})
|
||||
|
||||
strongSelf.messageActionUrlAuthDisposable.set(((acceptMessageActionUrlAuth(account: strongSelf.context.account, messageId: messageId, buttonId: buttonId, allowWriteAccess: allowWriteAccess) |> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
return $0.updatedTitlePanelContext {
|
||||
if let index = $0.firstIndex(where: {
|
||||
switch $0 {
|
||||
case .requestInProgress:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
var updatedContexts = $0
|
||||
updatedContexts.remove(at: index)
|
||||
return updatedContexts
|
||||
}
|
||||
return $0
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}) |> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
if let strongSelf = self {
|
||||
switch result {
|
||||
case let .accepted(url):
|
||||
strongSelf.openUrl(url, concealed: false)
|
||||
default:
|
||||
strongSelf.openUrl(defaultUrl, concealed: false)
|
||||
}
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
strongSelf.openUrl(defaultUrl, concealed: false)
|
||||
}
|
||||
}
|
||||
})
|
||||
strongSelf.chatDisplayNode.dismissInput()
|
||||
strongSelf.present(controller, in: .window(.root))
|
||||
case let .accepted(url):
|
||||
strongSelf.openUrl(url, concealed: false)
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}, activateSwitchInline: { [weak self] peerId, inputString in
|
||||
guard let strongSelf = self else {
|
||||
@ -2578,7 +2585,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
self.chatTitleView?.longPressed = { [weak self] in
|
||||
self?.interfaceInteraction?.beginMessageSearch(.everything, "")
|
||||
if let strongSelf = self, let peerView = strongSelf.peerView, let peer = peerView.peers[peerView.peerId], peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil && !strongSelf.presentationInterfaceState.isNotAccessible {
|
||||
strongSelf.interfaceInteraction?.beginMessageSearch(.everything, "")
|
||||
}
|
||||
}
|
||||
|
||||
let chatInfoButtonItem: UIBarButtonItem
|
||||
@ -3440,20 +3449,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
})
|
||||
|
||||
self.stickerSettingsDisposable = (context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings])
|
||||
|> deliverOnMainQueue).start(next: { [weak self] sharedData in
|
||||
self.stickerSettingsDisposable = combineLatest(queue: Queue.mainQueue(), context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]), self.disableStickerAnimationsPromise.get()).start(next: { [weak self] sharedData, disableStickerAnimations in
|
||||
var stickerSettings = StickerSettings.defaultSettings
|
||||
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings] as? StickerSettings {
|
||||
stickerSettings = value
|
||||
}
|
||||
|
||||
let chatStickerSettings = ChatInterfaceStickerSettings(stickerSettings: stickerSettings)
|
||||
|
||||
if let strongSelf = self, strongSelf.stickerSettings != chatStickerSettings {
|
||||
if let strongSelf = self, strongSelf.stickerSettings != chatStickerSettings || strongSelf.disableStickerAnimationsValue != disableStickerAnimations {
|
||||
strongSelf.stickerSettings = chatStickerSettings
|
||||
strongSelf.disableStickerAnimationsValue = disableStickerAnimations
|
||||
strongSelf.controllerInteraction?.stickerSettings = chatStickerSettings
|
||||
if strongSelf.isNodeLoaded {
|
||||
strongSelf.chatDisplayNode.updateStickerSettings(chatStickerSettings)
|
||||
strongSelf.chatDisplayNode.updateStickerSettings(chatStickerSettings, forceStopAnimations: disableStickerAnimations)
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -7144,6 +7152,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
override public func inFocusUpdated(isInFocus: Bool) {
|
||||
self.disableStickerAnimationsPromise.set(!isInFocus)
|
||||
self.chatDisplayNode.inFocusUpdated(isInFocus: isInFocus)
|
||||
}
|
||||
|
||||
@ -8497,6 +8506,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let strongSelf = self {
|
||||
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
||||
|
||||
for item in results {
|
||||
if let item = item, item.fileSize > 2000 * 1024 * 1024 {
|
||||
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Conversation_UploadFileTooLarge, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var groupingKey: Int64?
|
||||
var fileTypes: (music: Bool, other: Bool) = (false, false)
|
||||
if results.count > 1 {
|
||||
@ -10241,10 +10257,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
case .upperBound:
|
||||
searchLocation = .index(MessageIndex.upperBound(peerId: self.chatLocation.peerId))
|
||||
}
|
||||
var historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50, highlight: true), id: 0), context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
|
||||
#if DEBUG
|
||||
historyView = historyView |> delay(1.0, queue: .mainQueue())
|
||||
#endif
|
||||
let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50, highlight: true), id: 0), context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
|
||||
|
||||
let signal = historyView
|
||||
|> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in
|
||||
@ -11082,6 +11095,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}, sendFile: nil,
|
||||
sendSticker: { [weak self] f, sourceNode, sourceRect in
|
||||
return self?.interfaceInteraction?.sendSticker(f, sourceNode, sourceRect) ?? false
|
||||
}, requestMessageActionUrlAuth: { [weak self] subject in
|
||||
if case let .url(url) = subject {
|
||||
self?.controllerInteraction?.requestMessageActionUrlAuth(url, subject)
|
||||
}
|
||||
}, present: { [weak self] c, a in
|
||||
self?.present(c, in: .window(.root), with: a)
|
||||
}, dismissInput: { [weak self] in
|
||||
|
@ -64,7 +64,7 @@ public final class ChatControllerInteraction {
|
||||
let sendGif: (FileMediaReference, ASDisplayNode, CGRect) -> Bool
|
||||
let sendBotContextResultAsGif: (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool
|
||||
let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool, Bool) -> Void
|
||||
let requestMessageActionUrlAuth: (String, MessageId, Int32) -> Void
|
||||
let requestMessageActionUrlAuth: (String, MessageActionUrlSubject) -> Void
|
||||
let activateSwitchInline: (PeerId?, String) -> Void
|
||||
let openUrl: (String, Bool, Bool?, Message?) -> Void
|
||||
let shareCurrentLocation: () -> Void
|
||||
@ -154,7 +154,7 @@ public final class ChatControllerInteraction {
|
||||
sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool,
|
||||
sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool,
|
||||
requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool, Bool) -> Void,
|
||||
requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void,
|
||||
requestMessageActionUrlAuth: @escaping (String, MessageActionUrlSubject) -> Void,
|
||||
activateSwitchInline: @escaping (PeerId?, String) -> Void,
|
||||
openUrl: @escaping (String, Bool, Bool?, Message?) -> Void,
|
||||
shareCurrentLocation: @escaping () -> Void,
|
||||
@ -298,7 +298,7 @@ public final class ChatControllerInteraction {
|
||||
|
||||
static var `default`: ChatControllerInteraction {
|
||||
return ChatControllerInteraction(openMessage: { _, _ in
|
||||
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
||||
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
||||
}, presentController: { _, _ in }, navigationController: {
|
||||
return nil
|
||||
}, chatControllerNode: {
|
||||
|
@ -964,6 +964,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
if let inputMediaNode = inputNode as? ChatMediaInputNode, self.inputMediaNode == nil {
|
||||
self.inputMediaNode = inputMediaNode
|
||||
inputMediaNode.requestDisableStickerAnimations = { [weak self] disabled in
|
||||
self?.controller?.disableStickerAnimations = disabled
|
||||
}
|
||||
}
|
||||
if self.inputNode != inputNode {
|
||||
dismissedInputNode = self.inputNode
|
||||
@ -2086,10 +2089,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.historyNode.prefetchManager.updateAutoDownloadSettings(settings)
|
||||
}
|
||||
|
||||
func updateStickerSettings(_ settings: ChatInterfaceStickerSettings) {
|
||||
func updateStickerSettings(_ settings: ChatInterfaceStickerSettings, forceStopAnimations: Bool) {
|
||||
self.historyNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ChatMessageItemView {
|
||||
itemNode.updateStickerSettings()
|
||||
itemNode.updateStickerSettings(forceStopAnimations: forceStopAnimations)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2170,6 +2173,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
})
|
||||
inputNode.interfaceInteraction = interfaceInteraction
|
||||
inputNode.requestDisableStickerAnimations = { [weak self] disabled in
|
||||
self?.controller?.disableStickerAnimations = disabled
|
||||
}
|
||||
self.inputMediaNode = inputNode
|
||||
if let (validLayout, _) = self.validLayout {
|
||||
let _ = inputNode.updateLayout(width: validLayout.size.width, leftInset: validLayout.safeInsets.left, rightInset: validLayout.safeInsets.right, bottomInset: validLayout.intrinsicInsets.bottom, standardInputHeight: validLayout.standardInputHeight, inputHeight: validLayout.inputHeight ?? 0.0, maximumHeight: validLayout.standardInputHeight, inputPanelHeight: 44.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, deviceMetrics: validLayout.deviceMetrics, isVisible: false)
|
||||
|
@ -623,6 +623,14 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.accessibilityPageScrolledString = { [weak self] row, count in
|
||||
if let strongSelf = self {
|
||||
return strongSelf.currentPresentationData.strings.VoiceOver_ScrollStatus(row, count).0
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
self.dynamicBounceEnabled = !self.currentPresentationData.disableAnimations
|
||||
self.experimentalSnapScrollToItem = true
|
||||
|
||||
|
@ -810,6 +810,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
})))
|
||||
}
|
||||
|
||||
var isUnremovableAction = false
|
||||
if messages.count == 1 {
|
||||
let message = messages[0]
|
||||
|
||||
@ -826,6 +827,14 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
|
||||
if !hasAutoremove {
|
||||
for media in message.media {
|
||||
if media is TelegramMediaAction {
|
||||
if let channel = message.peers[message.id.peerId] as? TelegramChannel {
|
||||
if channel.flags.contains(.isCreator) || (channel.adminRights?.rights.contains(.canDeleteMessages) == true) {
|
||||
} else {
|
||||
isUnremovableAction = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if let file = media as? TelegramMediaFile {
|
||||
if file.isVideo {
|
||||
if file.isAnimated {
|
||||
@ -937,7 +946,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
interfaceInteraction.deleteMessages(selectAll ? messages : [message], controller, f)
|
||||
}
|
||||
}), false))
|
||||
} else {
|
||||
} else if !isUnremovableAction {
|
||||
actions.append(.action(ContextMenuActionItem(text: title, textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: isSending ? "Chat/Context Menu/Clear" : "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
|
||||
}, action: { controller, f in
|
||||
|
@ -448,6 +448,8 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
private var currentView: ItemCollectionsView?
|
||||
private let dismissedPeerSpecificStickerPack = Promise<Bool>()
|
||||
|
||||
var requestDisableStickerAnimations: ((Bool) -> Void)?
|
||||
|
||||
private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, ChatPresentationInterfaceState, DeviceMetrics, Bool)?
|
||||
private var paneArrangement: ChatMediaInputPaneArrangement
|
||||
private var initializedArrangement = false
|
||||
@ -1242,6 +1244,10 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
let controller = PeekController(theme: PeekControllerTheme(presentationTheme: strongSelf.theme), content: content, sourceNode: {
|
||||
return sourceNode
|
||||
})
|
||||
controller.visibilityUpdated = { [weak self] visible in
|
||||
self?.requestDisableStickerAnimations?(visible)
|
||||
self?.simulateUpdateLayout(isVisible: !visible)
|
||||
}
|
||||
strongSelf.controllerInteraction.presentGlobalOverlayController(controller, nil)
|
||||
return controller
|
||||
}
|
||||
|
@ -178,6 +178,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
|
||||
private var highlightedState: Bool = false
|
||||
|
||||
private var forceStopAnimations = false
|
||||
|
||||
private var haptic: EmojiHaptic?
|
||||
private var mediaPlayer: MediaPlayer?
|
||||
private let mediaStatusDisposable = MetaDisposable()
|
||||
@ -489,7 +491,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
|
||||
if let animationNode = self.animationNode as? AnimatedStickerNode {
|
||||
let isPlaying = self.visibilityStatus
|
||||
let isPlaying = self.visibilityStatus && !self.forceStopAnimations
|
||||
if self.isPlaying != isPlaying {
|
||||
self.isPlaying = isPlaying
|
||||
|
||||
@ -550,7 +552,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
}
|
||||
|
||||
override func updateStickerSettings() {
|
||||
override func updateStickerSettings(forceStopAnimations: Bool) {
|
||||
self.forceStopAnimations = forceStopAnimations
|
||||
self.updateVisibility()
|
||||
}
|
||||
|
||||
|
@ -1442,9 +1442,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
if case let .peer(peerId) = item.chatLocation, let authorPeerId = item.message.author?.id, authorPeerId == peerId {
|
||||
|
||||
} else if effectiveAuthor.isScam {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme.theme, type: incoming ? .regular : .outgoing)
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme.theme, strings: item.presentationData.strings, type: incoming ? .regular : .outgoing)
|
||||
} else if effectiveAuthor.isFake {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme.theme, type: incoming ? .regular : .outgoing)
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme.theme, strings: item.presentationData.strings, type: incoming ? .regular : .outgoing)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -196,16 +196,16 @@ class ChatMessageForwardInfoNode: ASDisplayNode {
|
||||
if peer.isFake {
|
||||
switch type {
|
||||
case let .bubble(incoming):
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(presentationData.theme.theme, type: incoming ? .regular : .outgoing)
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(presentationData.theme.theme, strings: presentationData.strings, type: incoming ? .regular : .outgoing)
|
||||
case .standalone:
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(presentationData.theme.theme, type: .service)
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(presentationData.theme.theme, strings: presentationData.strings, type: .service)
|
||||
}
|
||||
} else if peer.isScam {
|
||||
switch type {
|
||||
case let .bubble(incoming):
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(presentationData.theme.theme, type: incoming ? .regular : .outgoing)
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(presentationData.theme.theme, strings: presentationData.strings, type: incoming ? .regular : .outgoing)
|
||||
case .standalone:
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(presentationData.theme.theme, type: .service)
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(presentationData.theme.theme, strings: presentationData.strings, type: .service)
|
||||
}
|
||||
} else {
|
||||
currentCredibilityIconImage = nil
|
||||
|
@ -758,7 +758,7 @@ public class ChatMessageItemView: ListViewItemNode {
|
||||
func updateAutomaticMediaDownloadSettings() {
|
||||
}
|
||||
|
||||
func updateStickerSettings() {
|
||||
func updateStickerSettings(forceStopAnimations: Bool) {
|
||||
}
|
||||
|
||||
func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? {
|
||||
@ -814,7 +814,7 @@ public class ChatMessageItemView: ListViewItemNode {
|
||||
case .payment:
|
||||
item.controllerInteraction.openCheckoutOrReceipt(item.message.id)
|
||||
case let .urlAuth(url, buttonId):
|
||||
item.controllerInteraction.requestMessageActionUrlAuth(url, item.message.id, buttonId)
|
||||
item.controllerInteraction.requestMessageActionUrlAuth(url, .message(id: item.message.id, buttonId: buttonId))
|
||||
case .setupPoll:
|
||||
break
|
||||
}
|
||||
|
@ -261,7 +261,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
self?.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame)
|
||||
}, openMessageContextActions: { _, _, _, _ in
|
||||
}, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
|
||||
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _, _ in
|
||||
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _, _ in
|
||||
self?.openUrl(url)
|
||||
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in
|
||||
if let strongSelf = self, let navigationController = strongSelf.getNavigationController() {
|
||||
@ -852,6 +852,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
self?.view.endEditing(true)
|
||||
})
|
||||
}
|
||||
case .urlAuth:
|
||||
break
|
||||
case let .peer(peerId, _):
|
||||
if let peerId = peerId {
|
||||
strongSelf.openPeer(peerId: peerId, peer: nil)
|
||||
@ -890,6 +892,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}
|
||||
}, sendFile: nil,
|
||||
sendSticker: nil,
|
||||
requestMessageActionUrlAuth: nil,
|
||||
present: { c, a in
|
||||
self?.presentController(c, a)
|
||||
}, dismissInput: {
|
||||
|
@ -58,6 +58,12 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode {
|
||||
self?.interaction.toggleMembersSearch(false)
|
||||
}
|
||||
|
||||
self.searchBar.tokensUpdated = { [weak self] tokens in
|
||||
if tokens.isEmpty {
|
||||
self?.interaction.toggleMembersSearch(false)
|
||||
}
|
||||
}
|
||||
|
||||
if let statuses = interaction.statuses {
|
||||
self.searchingActivityDisposable = (statuses.searching
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||
|
@ -240,13 +240,13 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
|
||||
if titleFakeIcon != self.titleFakeIcon {
|
||||
self.titleFakeIcon = titleFakeIcon
|
||||
self.titleCredibilityIconNode.image = titleFakeIcon ? PresentationResourcesChatList.fakeIcon(titleTheme, type: .regular) : nil
|
||||
self.titleCredibilityIconNode.image = titleFakeIcon ? PresentationResourcesChatList.fakeIcon(titleTheme, strings: self.strings, type: .regular) : nil
|
||||
updated = true
|
||||
}
|
||||
|
||||
if titleScamIcon != self.titleScamIcon {
|
||||
self.titleScamIcon = titleScamIcon
|
||||
self.titleCredibilityIconNode.image = titleScamIcon ? PresentationResourcesChatList.scamIcon(titleTheme, type: .regular) : nil
|
||||
self.titleCredibilityIconNode.image = titleScamIcon ? PresentationResourcesChatList.scamIcon(titleTheme, strings: self.strings, type: .regular) : nil
|
||||
updated = true
|
||||
}
|
||||
|
||||
|
@ -166,12 +166,18 @@ public class ComposeControllerImpl: ViewController, ComposeController {
|
||||
controller.displayNavigationActivity = false
|
||||
(controller.navigationController as? NavigationController)?.replaceAllButRootController(ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(peerId)), animated: true)
|
||||
}
|
||||
}, error: { _ in
|
||||
}, error: { error in
|
||||
if let strongSelf = self, let controller = controller {
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
controller.displayNavigationActivity = false
|
||||
controller.present(textAlertController(context: strongSelf.context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
let text: String
|
||||
switch error {
|
||||
case .limitExceeded:
|
||||
text = presentationData.strings.TwoStepAuth_FloodError
|
||||
default:
|
||||
text = presentationData.strings.Login_UnknownError
|
||||
}
|
||||
controller.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
@ -69,7 +69,8 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
|
||||
init(context: AccountContext, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter]) {
|
||||
self.context = context
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.presentationData = presentationData
|
||||
|
||||
var placeholder: String
|
||||
var includeChatList = false
|
||||
@ -88,6 +89,9 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
if case let .chatSelection(_, selectedChats, additionalCategories, chatListFilters) = mode {
|
||||
placeholder = self.presentationData.strings.ChatListFilter_AddChatsTitle
|
||||
let chatListNode = ChatListNode(context: context, groupId: .root, previewing: false, fillPreloadItems: false, mode: .peers(filter: [.excludeSecretChats], isSelecting: true, additionalCategories: additionalCategories?.categories ?? [], chatListFilters: chatListFilters), theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
|
||||
chatListNode.accessibilityPageScrolledString = { row, count in
|
||||
return presentationData.strings.VoiceOver_ScrollStatus(row, count).0
|
||||
}
|
||||
chatListNode.updateState { state in
|
||||
var state = state
|
||||
for peerId in selectedChats {
|
||||
|
@ -110,7 +110,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
||||
|
||||
self.controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in
|
||||
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
|
||||
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { fileReference, _, _, node, rect in return selectStickerImpl?(fileReference, node, rect) ?? false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
||||
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { fileReference, _, _, node, rect in return selectStickerImpl?(fileReference, node, rect) ?? false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
||||
}, presentController: { _, _ in }, navigationController: {
|
||||
return nil
|
||||
}, chatControllerNode: {
|
||||
|
@ -38,11 +38,15 @@ private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatContr
|
||||
}
|
||||
}
|
||||
|
||||
func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?) {
|
||||
func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)? = nil, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?) {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
switch resolvedUrl {
|
||||
case let .externalUrl(url):
|
||||
context.sharedContext.openExternalUrl(context: context, urlContext: urlContext, url: url, forceExternal: false, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: navigationController, dismissInput: dismissInput)
|
||||
case let .urlAuth(url):
|
||||
requestMessageActionUrlAuth?(.url(url))
|
||||
dismissInput()
|
||||
break
|
||||
case let .peer(peerId, navigation):
|
||||
if let peerId = peerId {
|
||||
openPeer(peerId, defaultNavigationForPeerId(peerId, navigation: navigation))
|
||||
|
@ -213,6 +213,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
||||
}
|
||||
}, sendFile: nil,
|
||||
sendSticker: nil,
|
||||
requestMessageActionUrlAuth: nil,
|
||||
present: { c, a in
|
||||
context.sharedContext.applicationBindings.dismissNativeController()
|
||||
|
||||
|
@ -84,7 +84,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
||||
}, sendBotContextResultAsGif: { _, _, _, _ in
|
||||
return false
|
||||
}, requestMessageActionCallback: { _, _, _, _ in
|
||||
}, requestMessageActionUrlAuth: { _, _, _ in
|
||||
}, requestMessageActionUrlAuth: { _, _ in
|
||||
}, activateSwitchInline: { _, _ in
|
||||
}, openUrl: { _, _, _, _ in
|
||||
}, shareCurrentLocation: {
|
||||
|
@ -473,7 +473,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
))
|
||||
case let .user(userPeerId, secretChatId, kind):
|
||||
let groupsInCommon: GroupsInCommonContext?
|
||||
if case .user = kind {
|
||||
if [.user, .bot].contains(kind) {
|
||||
groupsInCommon = GroupsInCommonContext(account: context.account, peerId: userPeerId)
|
||||
} else {
|
||||
groupsInCommon = nil
|
||||
@ -984,7 +984,7 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
|
||||
if isOpenedFromChat {
|
||||
result.append(.search)
|
||||
}
|
||||
if isSecretChat && !isContact {
|
||||
if (isSecretChat && !isContact) || user.flags.contains(.isSupport) {
|
||||
} else {
|
||||
result.append(.more)
|
||||
}
|
||||
@ -1043,7 +1043,7 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
|
||||
if channel.isVerified || channel.adminRights != nil || channel.flags.contains(.isCreator) {
|
||||
canReport = false
|
||||
}
|
||||
if !canReport && !canViewStats && displayLeave {
|
||||
if !canReport && !canViewStats {
|
||||
displayMore = false
|
||||
}
|
||||
if displayMore {
|
||||
|
@ -1255,8 +1255,10 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
|
||||
|
||||
final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
||||
let context: AccountContext
|
||||
let avatarNode: AvatarNode
|
||||
|
||||
private let containerNode: ContextControllerSourceNode
|
||||
|
||||
let avatarNode: AvatarNode
|
||||
fileprivate var videoNode: UniversalVideoNode?
|
||||
private var videoContent: NativeVideoContent?
|
||||
private var videoStartTimestamp: Double?
|
||||
@ -1271,6 +1273,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
var tapped: (() -> Void)?
|
||||
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||
|
||||
private var isFirstAvatarLoading = true
|
||||
var item: PeerInfoAvatarListItem?
|
||||
@ -1279,15 +1282,29 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
||||
|
||||
init(context: AccountContext) {
|
||||
self.context = context
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
|
||||
let avatarFont = avatarPlaceholderFont(size: floor(100.0 * 16.0 / 37.0))
|
||||
self.avatarNode = AvatarNode(font: avatarFont)
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.avatarNode)
|
||||
self.avatarNode.frame = CGRect(origin: CGPoint(x: -50.0, y: -50.0), size: CGSize(width: 100.0, height: 100.0))
|
||||
self.addSubnode(self.containerNode)
|
||||
self.containerNode.addSubnode(self.avatarNode)
|
||||
self.containerNode.frame = CGRect(origin: CGPoint(x: -50.0, y: -50.0), size: CGSize(width: 100.0, height: 100.0))
|
||||
self.avatarNode.frame = self.containerNode.bounds
|
||||
|
||||
self.avatarNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
|
||||
self.avatarNode.view.addGestureRecognizer(tapGestureRecognizer)
|
||||
|
||||
self.containerNode.activated = { [weak self] gesture, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
tapGestureRecognizer.isEnabled = false
|
||||
tapGestureRecognizer.isEnabled = true
|
||||
strongSelf.contextAction?(strongSelf.containerNode, gesture)
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -1337,7 +1354,8 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
||||
self.avatarNode.setPeer(context: self.context, theme: theme, peer: peer, overrideImage: overrideImage, synchronousLoad: self.isFirstAvatarLoading, displayDimensions: CGSize(width: avatarSize, height: avatarSize), storeUnrounded: true)
|
||||
self.isFirstAvatarLoading = false
|
||||
|
||||
self.avatarNode.frame = CGRect(origin: CGPoint(x: -avatarSize / 2.0, y: -avatarSize / 2.0), size: CGSize(width: avatarSize, height: avatarSize))
|
||||
self.containerNode.frame = CGRect(origin: CGPoint(x: -avatarSize / 2.0, y: -avatarSize / 2.0), size: CGSize(width: avatarSize, height: avatarSize))
|
||||
self.avatarNode.frame = self.containerNode.bounds
|
||||
self.avatarNode.font = avatarPlaceholderFont(size: floor(avatarSize * 16.0 / 37.0))
|
||||
|
||||
if let item = item {
|
||||
@ -1365,6 +1383,8 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
self.containerNode.isGestureEnabled = true
|
||||
|
||||
if let video = videoRepresentations.last, let peerReference = PeerReference(peer) {
|
||||
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])]))
|
||||
let videoContent = NativeVideoContent(id: .profileVideo(id, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear)
|
||||
@ -1411,7 +1431,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
||||
shape.path = maskPath.cgPath
|
||||
videoNode.layer.mask = shape
|
||||
|
||||
self.addSubnode(videoNode)
|
||||
self.containerNode.addSubnode(videoNode)
|
||||
}
|
||||
} else if let videoNode = self.videoNode {
|
||||
self.videoContent = nil
|
||||
@ -1424,6 +1444,8 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
||||
self.videoNode = nil
|
||||
|
||||
videoNode.removeFromSupernode()
|
||||
|
||||
self.containerNode.isGestureEnabled = false
|
||||
}
|
||||
|
||||
if let videoNode = self.videoNode {
|
||||
@ -2568,6 +2590,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
var cancelUpload: (() -> Void)?
|
||||
var requestUpdateLayout: (() -> Void)?
|
||||
|
||||
var displayAvatarContextMenu: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||
var displayCopyContextMenu: ((ASDisplayNode, Bool, Bool) -> Void)?
|
||||
|
||||
var navigationTransition: PeerInfoHeaderNavigationTransition?
|
||||
@ -2663,6 +2686,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
self.avatarListNode.avatarContainerNode.tapped = { [weak self] in
|
||||
self?.initiateAvatarExpansion(gallery: false, first: false)
|
||||
}
|
||||
self.avatarListNode.avatarContainerNode.contextAction = { [weak self] node, gesture in
|
||||
self?.displayAvatarContextMenu?(node, gesture)
|
||||
}
|
||||
|
||||
self.editingContentNode.avatarNode.tapped = { [weak self] confirm in
|
||||
self?.initiateAvatarExpansion(gallery: true, first: true)
|
||||
}
|
||||
@ -2783,9 +2810,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
if let peer = peer {
|
||||
self.initializedCredibilityIcon = true
|
||||
if peer.isFake {
|
||||
image = PresentationResourcesChatList.fakeIcon(presentationData.theme, type: .regular)
|
||||
image = PresentationResourcesChatList.fakeIcon(presentationData.theme, strings: presentationData.strings, type: .regular)
|
||||
} else if peer.isScam {
|
||||
image = PresentationResourcesChatList.scamIcon(presentationData.theme, type: .regular)
|
||||
image = PresentationResourcesChatList.scamIcon(presentationData.theme, strings: presentationData.strings, type: .regular)
|
||||
} else if peer.isVerified {
|
||||
if let sourceImage = UIImage(bundleImageName: "Peer Info/VerifiedIcon") {
|
||||
image = generateImage(sourceImage.size, contextGenerator: { size, context in
|
||||
|
@ -563,6 +563,7 @@ private final class PeerInfoInteraction {
|
||||
let logoutAccount: (AccountRecordId) -> Void
|
||||
let accountContextMenu: (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void
|
||||
let updateBio: (String) -> Void
|
||||
let openDeletePeer: () -> Void
|
||||
|
||||
init(
|
||||
openUsername: @escaping (String) -> Void,
|
||||
@ -599,7 +600,8 @@ private final class PeerInfoInteraction {
|
||||
switchToAccount: @escaping (AccountRecordId) -> Void,
|
||||
logoutAccount: @escaping (AccountRecordId) -> Void,
|
||||
accountContextMenu: @escaping (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void,
|
||||
updateBio: @escaping (String) -> Void
|
||||
updateBio: @escaping (String) -> Void,
|
||||
openDeletePeer: @escaping () -> Void
|
||||
) {
|
||||
self.openUsername = openUsername
|
||||
self.openPhone = openPhone
|
||||
@ -636,6 +638,7 @@ private final class PeerInfoInteraction {
|
||||
self.logoutAccount = logoutAccount
|
||||
self.accountContextMenu = accountContextMenu
|
||||
self.updateBio = updateBio
|
||||
self.openDeletePeer = openDeletePeer
|
||||
}
|
||||
}
|
||||
|
||||
@ -1140,6 +1143,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
||||
case groupLocation
|
||||
case peerPublicSettings
|
||||
case peerSettings
|
||||
case peerActions
|
||||
}
|
||||
|
||||
var items: [Section: [PeerInfoScreenItem]] = [:]
|
||||
@ -1147,33 +1151,6 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
||||
items[section] = []
|
||||
}
|
||||
|
||||
// if let data = data, let notificationSettings = data.notificationSettings {
|
||||
// let notificationsLabel: String
|
||||
// let soundLabel: String
|
||||
// if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
|
||||
// if until < Int32.max - 1 {
|
||||
// notificationsLabel = stringForRemainingMuteInterval(strings: presentationData.strings, muteInterval: until)
|
||||
// } else {
|
||||
// notificationsLabel = presentationData.strings.UserInfo_NotificationsDisabled
|
||||
// }
|
||||
// } else {
|
||||
// notificationsLabel = presentationData.strings.UserInfo_NotificationsEnabled
|
||||
// }
|
||||
//
|
||||
// let globalNotificationSettings: GlobalNotificationSettings = data.globalNotificationSettings ?? GlobalNotificationSettings.defaultSettings
|
||||
// soundLabel = localizedPeerNotificationSoundString(strings: presentationData.strings, sound: notificationSettings.messageSound, default: globalNotificationSettings.effective.privateChats.sound)
|
||||
//
|
||||
// items[.notifications]!.append(PeerInfoScreenDisclosureItem(id: 0, label: .text(notificationsLabel), text: presentationData.strings.GroupInfo_Notifications, action: {
|
||||
// interaction.editingOpenNotificationSettings()
|
||||
// }))
|
||||
// items[.notifications]!.append(PeerInfoScreenDisclosureItem(id: 1, label: .text(soundLabel), text: presentationData.strings.GroupInfo_Sound, action: {
|
||||
// interaction.editingOpenSoundSettings()
|
||||
// }))
|
||||
// items[.notifications]!.append(PeerInfoScreenSwitchItem(id: 2, text: presentationData.strings.Notification_Exceptions_PreviewAlwaysOn, value: notificationSettings.displayPreviews != .hide, toggled: { value in
|
||||
// interaction.editingToggleShowMessageText(value)
|
||||
// }))
|
||||
// }
|
||||
|
||||
if let data = data {
|
||||
if let _ = data.peer as? TelegramUser {
|
||||
let ItemDelete = 0
|
||||
@ -1194,8 +1171,8 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
||||
|
||||
if channel.flags.contains(.isCreator) {
|
||||
let linkText: String
|
||||
if let username = channel.username {
|
||||
linkText = "@\(username)"
|
||||
if let _ = channel.username {
|
||||
linkText = presentationData.strings.Channel_Setup_TypePublic
|
||||
} else {
|
||||
linkText = presentationData.strings.Channel_Setup_TypePrivate
|
||||
}
|
||||
@ -1204,7 +1181,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
||||
}))
|
||||
}
|
||||
|
||||
if (channel.flags.contains(.isCreator) && (channel.username?.isEmpty ?? true)) || (channel.adminRights?.rights.contains(.canInviteUsers) == true) {
|
||||
if (channel.flags.contains(.isCreator) && (channel.username?.isEmpty ?? true)) || (!channel.flags.contains(.isCreator) && channel.adminRights?.rights.contains(.canInviteUsers) == true) {
|
||||
let invitesText: String
|
||||
if let count = data.invitations?.count, count > 0 {
|
||||
invitesText = "\(count)"
|
||||
@ -1256,14 +1233,15 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
||||
let ItemLinkedChannel = 103
|
||||
let ItemPreHistory = 104
|
||||
let ItemStickerPack = 105
|
||||
let ItemPermissions = 106
|
||||
let ItemMembers = 107
|
||||
let ItemMembers = 106
|
||||
let ItemPermissions = 107
|
||||
let ItemAdmins = 108
|
||||
let ItemRemovedUsers = 109
|
||||
let ItemLocationHeader = 110
|
||||
let ItemLocation = 111
|
||||
let ItemLocationSetup = 112
|
||||
let ItemAutoremove = 113
|
||||
let ItemDeleteGroup = 114
|
||||
|
||||
let isCreator = channel.flags.contains(.isCreator)
|
||||
let isPublic = channel.username != nil
|
||||
@ -1311,7 +1289,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
||||
}
|
||||
}
|
||||
|
||||
if (isCreator && (channel.username?.isEmpty ?? true) && cachedData.peerGeoLocation == nil) || (channel.adminRights?.rights.contains(.canInviteUsers) == true) {
|
||||
if (isCreator && (channel.username?.isEmpty ?? true) && cachedData.peerGeoLocation == nil) || (!isCreator && channel.adminRights?.rights.contains(.canInviteUsers) == true) {
|
||||
let invitesText: String
|
||||
if let count = data.invitations?.count, count > 0 {
|
||||
invitesText = "\(count)"
|
||||
@ -1369,25 +1347,28 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
||||
activePermissionCount = count
|
||||
}
|
||||
|
||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMembers, label: .text(cachedData.participantsSummary.memberCount.flatMap { "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" } ?? ""), text: presentationData.strings.Group_Info_Members, icon: UIImage(bundleImageName: "Chat/Info/GroupMembersIcon"), action: {
|
||||
interaction.openParticipantsSection(.members)
|
||||
}))
|
||||
if !channel.flags.contains(.isGigagroup) {
|
||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPermissions, label: .text(activePermissionCount.flatMap({ "\($0)/\(allGroupPermissionList.count)" }) ?? ""), text: presentationData.strings.GroupInfo_Permissions, icon: UIImage(bundleImageName: "Settings/MenuIcons/SetPasscode"), action: {
|
||||
interaction.openPermissions()
|
||||
}))
|
||||
} else {
|
||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMembers, label: .text(cachedData.participantsSummary.memberCount.flatMap { "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" } ?? ""), text: presentationData.strings.Group_Info_Members, icon: UIImage(bundleImageName: "Chat/Info/GroupMembersIcon"), action: {
|
||||
interaction.openParticipantsSection(.members)
|
||||
}))
|
||||
}
|
||||
|
||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, label: .text(cachedData.participantsSummary.adminCount.flatMap { "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" } ?? ""), text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: {
|
||||
interaction.openParticipantsSection(.admins)
|
||||
}))
|
||||
|
||||
if channel.flags.contains(.isGigagroup) {
|
||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemRemovedUsers, label: .text(cachedData.participantsSummary.kickedCount.flatMap { $0 > 0 ? "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" : "" } ?? ""), text: presentationData.strings.GroupInfo_Permissions_Removed, icon: UIImage(bundleImageName: "Chat/Info/GroupRemovedIcon"), action: {
|
||||
interaction.openParticipantsSection(.banned)
|
||||
}))
|
||||
}
|
||||
|
||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemRemovedUsers, label: .text(cachedData.participantsSummary.kickedCount.flatMap { $0 > 0 ? "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" : "" } ?? ""), text: presentationData.strings.GroupInfo_Permissions_Removed, icon: UIImage(bundleImageName: "Chat/Info/GroupRemovedIcon"), action: {
|
||||
interaction.openParticipantsSection(.banned)
|
||||
}))
|
||||
}
|
||||
|
||||
if isCreator {
|
||||
items[.peerActions]!.append(PeerInfoScreenActionItem(id: ItemDeleteGroup, text: presentationData.strings.Group_DeleteGroup, color: .destructive, icon: nil, alignment: .natural, action: {
|
||||
interaction.openDeletePeer()
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1691,6 +1672,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
},
|
||||
updateBio: { [weak self] bio in
|
||||
self?.updateBio(bio)
|
||||
},
|
||||
openDeletePeer: { [weak self] in
|
||||
self?.openDeletePeer()
|
||||
}
|
||||
)
|
||||
|
||||
@ -1987,7 +1971,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}, sendBotContextResultAsGif: { _, _, _, _ in
|
||||
return false
|
||||
}, requestMessageActionCallback: { _, _, _, _ in
|
||||
}, requestMessageActionUrlAuth: { _, _, _ in
|
||||
}, requestMessageActionUrlAuth: { _, _ in
|
||||
}, activateSwitchInline: { _, _ in
|
||||
}, openUrl: { [weak self] url, concealed, external, _ in
|
||||
guard let strongSelf = self else {
|
||||
@ -2726,6 +2710,29 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
} else {
|
||||
screenData = peerInfoScreenData(context: context, peerId: peerId, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, isSettings: self.isSettings, ignoreGroupInCommon: ignoreGroupInCommon)
|
||||
|
||||
self.headerNode.displayAvatarContextMenu = { [weak self] node, gesture in
|
||||
guard let strongSelf = self, let peer = strongSelf.data?.peer else {
|
||||
return
|
||||
}
|
||||
|
||||
let items: [ContextMenuItem] = [
|
||||
.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_ReportProfilePhoto, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Report"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { [weak self] c, f in
|
||||
if let strongSelf = self, let parent = strongSelf.controller {
|
||||
presentPeerReportOptions(context: context, parent: parent, contextController: c, subject: .profilePhoto(peer.id, 0), completion: { _, _ in })
|
||||
}
|
||||
}))
|
||||
]
|
||||
|
||||
let galleryController = AvatarGalleryController(context: strongSelf.context, peer: peer, remoteEntries: nil, replaceRootController: { controller, ready in
|
||||
}, synchronousLoad: true)
|
||||
galleryController.setHintWillBePresentedInPreviewingContext(true)
|
||||
|
||||
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
|
||||
strongSelf.controller?.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
}
|
||||
|
||||
self.headerNode.avatarListNode.listContainerNode.currentIndexUpdated = { [weak self] in
|
||||
@ -3024,10 +3031,10 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}, sendFile: nil,
|
||||
sendSticker: { [weak self] f, sourceNode, sourceRect in
|
||||
return false
|
||||
}, present: { [weak self] c, a in
|
||||
}, requestMessageActionUrlAuth: nil, present: { [weak self] c, a in
|
||||
self?.controller?.present(c, in: .window(.root), with: a)
|
||||
}, dismissInput: { [weak self] in
|
||||
|
||||
self?.view.endEditing(true)
|
||||
}, contentContext: nil)
|
||||
}
|
||||
|
||||
@ -3044,8 +3051,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.controller?.navigationController as? NavigationController, openPeer: { peerId, navigation in
|
||||
self?.openPeer(peerId: peerId, navigation: navigation)
|
||||
}, sendFile: nil,
|
||||
sendSticker: nil,
|
||||
present: { c, a in
|
||||
sendSticker: nil,
|
||||
requestMessageActionUrlAuth: nil,
|
||||
present: { c, a in
|
||||
self?.controller?.present(c, in: .window(.root), with: a)
|
||||
}, dismissInput: {
|
||||
self?.view.endEditing(true)
|
||||
@ -3645,11 +3653,18 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
if let navigationController = (strongSelf.controller?.navigationController as? NavigationController) {
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId)))
|
||||
}
|
||||
}, error: { _ in
|
||||
}, error: { error in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.controller?.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
let text: String
|
||||
switch error {
|
||||
case .limitExceeded:
|
||||
text = strongSelf.presentationData.strings.TwoStepAuth_FloodError
|
||||
default:
|
||||
text = strongSelf.presentationData.strings.Login_UnknownError
|
||||
}
|
||||
strongSelf.controller?.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}))
|
||||
})]), in: .window(.root))
|
||||
}
|
||||
@ -4091,6 +4106,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
context.sharedContext.openResolvedUrl(.groupBotStart(peerId: peerId, payload: ""), context: self.context, urlContext: .generic, navigationController: controller.navigationController as? NavigationController, openPeer: { id, navigation in
|
||||
}, sendFile: nil,
|
||||
sendSticker: nil,
|
||||
requestMessageActionUrlAuth: nil,
|
||||
present: { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
}, dismissInput: { [weak controller] in
|
||||
@ -4921,7 +4937,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
resolvedUrl = .instantView(webPage, customAnchor)
|
||||
}
|
||||
strongSelf.context.sharedContext.openResolvedUrl(resolvedUrl, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.controller?.navigationController as? NavigationController, openPeer: { peer, navigation in
|
||||
}, sendFile: nil, sendSticker: nil, present: { [weak self] controller, arguments in
|
||||
}, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, present: { [weak self] controller, arguments in
|
||||
self?.controller?.push(controller)
|
||||
}, dismissInput: {}, contentContext: nil)
|
||||
}
|
||||
@ -6564,7 +6580,13 @@ func presentAddMembers(context: AccountContext, parentController: ViewController
|
||||
case .notMutualContact:
|
||||
let _ = (context.account.postbox.loadedPeerWithId(memberId)
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
parentController?.present(textAlertController(context: context, title: nil, text: presentationData.strings.GroupInfo_AddUserLeftError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
let text: String
|
||||
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||
text = presentationData.strings.Channel_AddUserLeftError
|
||||
} else {
|
||||
text = presentationData.strings.GroupInfo_AddUserLeftError
|
||||
}
|
||||
parentController?.present(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
})
|
||||
return .complete()
|
||||
case .tooManyChannels:
|
||||
@ -6683,7 +6705,14 @@ func presentAddMembers(context: AccountContext, parentController: ViewController
|
||||
break
|
||||
}
|
||||
} else if peers.count == 1, case .notMutualContact = error {
|
||||
parentController?.present(textAlertController(context: context, title: nil, text: presentationData.strings.GroupInfo_AddUserLeftError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
let text: String
|
||||
if let peer = groupPeer as? TelegramChannel, case .broadcast = peer.info {
|
||||
text = presentationData.strings.Channel_AddUserLeftError
|
||||
} else {
|
||||
text = presentationData.strings.GroupInfo_AddUserLeftError
|
||||
}
|
||||
|
||||
parentController?.present(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
} else if case .tooMuchJoined = error {
|
||||
parentController?.present(textAlertController(context: context, title: nil, text: presentationData.strings.Invite_ChannelsTooMuch, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}
|
||||
|
@ -67,7 +67,8 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
self.filter = filter
|
||||
self.hasGlobalSearch = hasGlobalSearch
|
||||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.presentationData = presentationData
|
||||
|
||||
if hasChatListSelector && hasContactSelector {
|
||||
self.toolbarBackgroundNode = ASDisplayNode()
|
||||
@ -107,6 +108,10 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
|
||||
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
|
||||
|
||||
self.chatListNode.accessibilityPageScrolledString = { row, count in
|
||||
return presentationData.strings.VoiceOver_ScrollStatus(row, count).0
|
||||
}
|
||||
|
||||
self.chatListNode.activateSearch = { [weak self] in
|
||||
self?.requestActivateSearch?()
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user