Merge branch 'beta'

This commit is contained in:
Ali 2021-02-26 19:58:15 +04:00
commit 1f69d24691
95 changed files with 5464 additions and 5008 deletions

View File

@ -17,7 +17,7 @@ jobs:
fetch-depth: '0' fetch-depth: '0'
- name: Set active Xcode path - name: Set active Xcode path
run: sudo xcode-select -s /Applications/Xcode_12.3.app/Contents/Developer run: sudo xcode-select -s /Applications/Xcode_12.4.app/Contents/Developer
- name: Create canonical source directory - name: Create canonical source directory
run: | run: |
@ -40,8 +40,8 @@ jobs:
# download bazel # download bazel
mkdir -p $HOME/bazel-dist mkdir -p $HOME/bazel-dist
pushd $HOME/bazel-dist pushd $HOME/bazel-dist
curl -O -L https://github.com/bazelbuild/bazel/releases/download/3.7.0/bazel-3.7.0-darwin-x86_64 curl -O -L https://github.com/bazelbuild/bazel/releases/download/4.0.0/bazel-4.0.0-darwin-x86_64
mv bazel-3.7.0* bazel mv bazel-4.0.0* bazel
chmod +x bazel chmod +x bazel
./bazel --version ./bazel --version
popd popd

View File

@ -6151,3 +6151,10 @@ Sorry for the inconvenience.";
"VoiceOver.ScrollStatus" = "Row %1$@ of %2$@"; "VoiceOver.ScrollStatus" = "Row %1$@ of %2$@";
"Conversation.UsersTooMuchError" = "Sorry, this group is full."; "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";

View File

@ -162,6 +162,7 @@ public enum ResolvedUrlSettingsSection {
public enum ResolvedUrl { public enum ResolvedUrl {
case externalUrl(String) case externalUrl(String)
case urlAuth(String)
case peer(PeerId?, ChatControllerInteractionNavigateToPeer) case peer(PeerId?, ChatControllerInteractionNavigateToPeer)
case inaccessiblePeer case inaccessiblePeer
case botStart(peerId: PeerId, payload: String) 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>) -> Signal<ChatAvailableMessageActions, NoError>
func chatAvailableMessageActions(postbox: Postbox, accountPeerId: PeerId, messageIds: Set<MessageId>, messages: [MessageId: Message], peers: [PeerId: Peer]) -> 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 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 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 openAddPersonContact(context: AccountContext, peerId: PeerId, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void)
func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void) func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void)

View File

@ -258,7 +258,7 @@ private final class ImportManager {
if !pathExtension.isEmpty, let value = TGMimeTypeMap.mimeType(forExtension: pathExtension) { if !pathExtension.isEmpty, let value = TGMimeTypeMap.mimeType(forExtension: pathExtension) {
mimeType = value mimeType = value
} }
return ChatHistoryImport.uploadMedia(account: account, session: session, file: tempFile, fileName: entry.0.path, mimeType: mimeType, type: entry.2) return ChatHistoryImport.uploadMedia(account: account, session: session, file: tempFile, disposeFileAfterDone: true, fileName: entry.0.path, mimeType: mimeType, type: entry.2)
|> mapError { error -> ImportError in |> mapError { error -> ImportError in
switch error { switch error {
case .chatAdminRequired: case .chatAdminRequired:

View File

@ -332,7 +332,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
if case .search = source { if case .search = source {
if let _ = peer as? TelegramChannel { 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 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)? var cancelImpl: (() -> Void)?
let progressSignal = Signal<Never, NoError> { subscriber in let progressSignal = Signal<Never, NoError> { subscriber in
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }

View File

@ -527,6 +527,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
} }
self.titleView.theme = self.presentationData.theme self.titleView.theme = self.presentationData.theme
self.titleView.strings = self.presentationData.strings
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))

View File

@ -167,6 +167,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
// self?.openPeer(peerId: peerId, navigation: navigation) // self?.openPeer(peerId: peerId, navigation: navigation)
}, sendFile: nil, }, sendFile: nil,
sendSticker: nil, sendSticker: nil,
requestMessageActionUrlAuth: nil,
present: { c, a in present: { c, a in
present(c, a) present(c, a)
}, dismissInput: { }, dismissInput: {

View File

@ -511,7 +511,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
peerContextAction(peer.peer, .search(nil), node, gesture) 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 header = ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
let selection: ChatHistoryMessageSelection = selected.flatMap { .selectable(selected: $0) } ?? .none let selection: ChatHistoryMessageSelection = selected.flatMap { .selectable(selected: $0) } ?? .none
if let tagMask = tagMask, tagMask != .photoOrVideo { if let tagMask = tagMask, tagMask != .photoOrVideo {

View File

@ -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) { init(theme: PresentationTheme, strings: PresentationStrings) {
self.theme = theme self.theme = theme

View File

@ -1307,10 +1307,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _): case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _):
if let peer = messages.last?.author { if let peer = messages.last?.author {
if peer.isScam { 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 credibilityIconOffset = 2.0
} else if peer.isFake { } 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 credibilityIconOffset = 2.0
} else if peer.isVerified { } else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
@ -1322,10 +1322,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
} else if case let .chat(itemPeer) = contentPeer, let peer = itemPeer.chatMainPeer { } else if case let .chat(itemPeer) = contentPeer, let peer = itemPeer.chatMainPeer {
if peer.isScam { 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 credibilityIconOffset = 2.0
} else if peer.isFake { } 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 credibilityIconOffset = 2.0
} else if peer.isVerified { } else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)

View File

@ -95,10 +95,17 @@ func contactContextMenuItems(context: AccountContext, peerId: PeerId, contactsCo
if let navigationController = (contactsController?.navigationController as? NavigationController) { if let navigationController = (contactsController?.navigationController as? NavigationController) {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), peekData: nil)) context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId), peekData: nil))
} }
}, error: { _ in }, error: { error in
if let contactsController = contactsController { if let contactsController = contactsController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } 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 { 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 items.append(.action(ContextMenuActionItem(text: strings.ContactList_Context_Call, icon: { theme in
if let contactsController = contactsController { generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Call"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
context.requestCall(peerId: peerId, isVideo: false, completion: {}) 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) f(.default)
}))) })))
} }

View File

@ -132,9 +132,7 @@ public class InviteContactsController: ViewController, MFMessageComposeViewContr
if let strongSelf = self { if let strongSelf = self {
let url = strongSelf.presentationData.strings.InviteText_URL let url = strongSelf.presentationData.strings.InviteText_URL
let body = strongSelf.presentationData.strings.InviteText_SingleContact(url).0 let body = strongSelf.presentationData.strings.InviteText_SingleContact(url).0
presentExternalShare(context: strongSelf.context, text: body, parentController: strongSelf)
let shareController = ShareController(context: strongSelf.context, subject: .text(body), externalShare: true, immediateExternalShare: true)
strongSelf.present(shareController, in: .window(.root))
strongSelf.contactsNode.listNode.clearHighlightAnimated(true) strongSelf.contactsNode.listNode.clearHighlightAnimated(true)
} }

View File

@ -1249,11 +1249,16 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
} }
} }
public var forceHidden = false {
didSet {
self.updateItemNodeVisibilititesAndScrolling()
}
}
private func updateItemNodeVisibilititesAndScrolling() { private func updateItemNodeVisibilititesAndScrolling() {
let visibleRect = self.scrollView.bounds let visibleRect = self.scrollView.bounds
let isScrolling = self.scrollView.isDragging || self.scrollView.isDecelerating let isScrolling = self.scrollView.isDragging || self.scrollView.isDecelerating
for (_, itemNode) in self.itemNodes { for (_, itemNode) in self.itemNodes {
let visible = itemNode.frame.intersects(visibleRect) let visible = itemNode.frame.intersects(visibleRect) && !self.forceHidden
if itemNode.isVisibleInGrid != visible { if itemNode.isVisibleInGrid != visible {
itemNode.isVisibleInGrid = visible itemNode.isVisibleInGrid = visible
} }

View File

@ -29,6 +29,8 @@ public final class PeekController: ViewController {
private let content: PeekControllerContent private let content: PeekControllerContent
var sourceNode: () -> ASDisplayNode? var sourceNode: () -> ASDisplayNode?
public var visibilityUpdated: ((Bool) -> Void)?
private var animatedIn = false private var animatedIn = false
public init(theme: PeekControllerTheme, content: PeekControllerContent, sourceNode: @escaping () -> ASDisplayNode?) { public init(theme: PeekControllerTheme, content: PeekControllerContent, sourceNode: @escaping () -> ASDisplayNode?) {
@ -67,6 +69,8 @@ public final class PeekController: ViewController {
if !self.animatedIn { if !self.animatedIn {
self.animatedIn = true self.animatedIn = true
self.controllerNode.animateIn(from: self.getSourceRect()) 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) { override public func dismiss(completion: (() -> Void)? = nil) {
self.visibilityUpdated?(false)
self.controllerNode.animateOut(to: self.getSourceRect(), completion: { [weak self] in self.controllerNode.animateOut(to: self.getSourceRect(), completion: { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil) self?.presentingViewController?.dismiss(animated: false, completion: nil)
}) })

View File

@ -100,8 +100,9 @@ final class PeekControllerMenuItemNode: HighlightTrackingButtonNode {
} }
@objc func buttonPressed() { @objc func buttonPressed() {
if self.item.action(self, self.bounds) {
self.activatedAction() self.activatedAction()
if self.item.action(self, self.bounds) {
} }
} }
} }

View File

@ -504,6 +504,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
if !displayInfo { if !displayInfo {
authorNameText = "" authorNameText = ""
dateText = "" dateText = ""
canEdit = false
} }
var messageText = NSAttributedString(string: "") var messageText = NSAttributedString(string: "")
@ -910,15 +911,20 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
if let strongSelf = self, !messages.isEmpty { if let strongSelf = self, !messages.isEmpty {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
var generalMessageContentKind: MessageContentKind? var generalMessageContentKind: MessageContentKind?
var beganContentKindScanning = false
var messageContentKinds = Set<MessageContentKindKey>()
for message in messages { 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) 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 { if beganContentKindScanning && currentKind != generalMessageContentKind {
generalMessageContentKind = currentKind
} else {
generalMessageContentKind = nil generalMessageContentKind = nil
break } else if !beganContentKindScanning || currentKind == generalMessageContentKind {
beganContentKindScanning = true
generalMessageContentKind = currentKind
} }
messageContentKinds.insert(currentKind.key)
} }
var preferredAction = ShareControllerPreferredAction.default var preferredAction = ShareControllerPreferredAction.default
if let generalMessageContentKind = generalMessageContentKind { if let generalMessageContentKind = generalMessageContentKind {
switch generalMessageContentKind { switch generalMessageContentKind {
@ -927,6 +933,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
default: default:
break break
} }
} else if messageContentKinds.count == 2 && messageContentKinds.contains(.image) && messageContentKinds.contains(.video) {
preferredAction = .saveToCameraRoll
} }
if messages.count == 1 { if messages.count == 1 {

View File

@ -665,7 +665,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
playing = true playing = true
case let .buffering(_, whilePlaying, _, display): case let .buffering(_, whilePlaying, _, display):
displayProgress = display displayProgress = display
initialBuffering = true initialBuffering = !whilePlaying
isPaused = !whilePlaying isPaused = !whilePlaying
var isStreaming = false var isStreaming = false
if let fetchStatus = strongSelf.fetchStatus { if let fetchStatus = strongSelf.fetchStatus {

View File

@ -164,7 +164,11 @@ open class ZoomableContentGalleryItemNode: GalleryItemNode, UIScrollViewDelegate
self.centerScrollViewContents(transition: transition) self.centerScrollViewContents(transition: transition)
self.ignoreZoom = false self.ignoreZoom = false
let updatedZoomScale = self.scrollNode.view.zoomScale != self.scrollNode.view.minimumZoomScale
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 self.ignoreZoomTransition = nil
} }

View File

@ -1198,6 +1198,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
} }
}, sendFile: nil, }, sendFile: nil,
sendSticker: nil, sendSticker: nil,
requestMessageActionUrlAuth: nil,
present: { c, a in present: { c, a in
self?.present(c, a) self?.present(c, a)
}, dismissInput: { }, dismissInput: {
@ -1275,6 +1276,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
fromPlayingVideo = true fromPlayingVideo = true
entries.append(InstantPageGalleryEntry(index: Int32(media.index), pageId: webPage.webpageId, media: media, caption: media.caption, credit: media.credit, location: nil)) entries.append(InstantPageGalleryEntry(index: Int32(media.index), pageId: webPage.webpageId, media: media, caption: media.caption, credit: media.credit, location: nil))
} else { } else {
fromPlayingVideo = true
var medias: [InstantPageMedia] = mediasFromItems(items) var medias: [InstantPageMedia] = mediasFromItems(items)
medias = medias.filter { medias = medias.filter {
return $0.media is TelegramMediaImage || $0.media is TelegramMediaFile return $0.media is TelegramMediaImage || $0.media is TelegramMediaFile

View File

@ -309,7 +309,7 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
@objc func joinPressed() { @objc func joinPressed() {
if let peer = self.peer, case .notJoined = self.joinState { if let peer = self.peer, case .notJoined = self.joinState {
self.updateJoinState(.inProgress) 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 let strongSelf = self {
if case .inProgress = strongSelf.joinState { if case .inProgress = strongSelf.joinState {
strongSelf.updateJoinState(.notJoined) strongSelf.updateJoinState(.notJoined)

View File

@ -30,11 +30,11 @@ private final class InviteLinkListControllerArguments {
let mainLinkContextAction: (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void let mainLinkContextAction: (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void
let createLink: () -> Void let createLink: () -> Void
let openLink: (ExportedInvitation) -> Void let openLink: (ExportedInvitation) -> Void
let linkContextAction: (ExportedInvitation?, ASDisplayNode, ContextGesture?) -> Void let linkContextAction: (ExportedInvitation?, Bool, ASDisplayNode, ContextGesture?) -> Void
let openAdmin: (ExportedInvitationCreator) -> Void let openAdmin: (ExportedInvitationCreator) -> Void
let deleteAllRevokedLinks: () -> 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.context = context
self.shareMainLink = shareMainLink self.shareMainLink = shareMainLink
self.openMainLink = openMainLink self.openMainLink = openMainLink
@ -65,7 +65,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
case linksHeader(PresentationTheme, String) case linksHeader(PresentationTheme, String)
case linksCreate(PresentationTheme, String) case linksCreate(PresentationTheme, String)
case link(Int32, PresentationTheme, ExportedInvitation?, Int32?) case link(Int32, PresentationTheme, ExportedInvitation?, Bool, Int32?)
case linksInfo(PresentationTheme, String) case linksInfo(PresentationTheme, String)
case revokedLinksHeader(PresentationTheme, String) case revokedLinksHeader(PresentationTheme, String)
@ -104,7 +104,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
return 4 return 4
case .linksCreate: case .linksCreate:
return 5 return 5
case let .link(index, _, _, _): case let .link(index, _, _, _, _):
return 6 + index return 6 + index
case .linksInfo: case .linksInfo:
return 10000 return 10000
@ -159,8 +159,8 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .link(lhsIndex, lhsTheme, lhsLink, lhsTick): case let .link(lhsIndex, lhsTheme, lhsLink, lhsCanEdit, lhsTick):
if case let .link(rhsIndex, rhsTheme, rhsLink, rhsTick) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsLink == rhsLink, lhsTick == rhsTick { if case let .link(rhsIndex, rhsTheme, rhsLink, rhsCanEdit, rhsTick) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsLink == rhsLink, lhsCanEdit == rhsCanEdit, lhsTick == rhsTick {
return true return true
} else { } else {
return false 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: { return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, sectionId: self.section, editing: false, action: {
arguments.createLink() 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 return ItemListInviteLinkItem(presentationData: presentationData, invite: invite, share: false, sectionId: self.section, style: .blocks) { invite in
arguments.openLink(invite) arguments.openLink(invite)
} contextAction: { invite, node, gesture in } contextAction: { invite, node, gesture in
arguments.linkContextAction(invite, node, gesture) arguments.linkContextAction(invite, canEdit, node, gesture)
} }
case let .linksInfo(_, text): case let .linksInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) 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 return ItemListInviteLinkItem(presentationData: presentationData, invite: invite, share: false, sectionId: self.section, style: .blocks) { invite in
arguments.openLink(invite) arguments.openLink(invite)
} contextAction: { invite, node, gesture in } contextAction: { invite, node, gesture in
arguments.linkContextAction(invite, node, gesture) arguments.linkContextAction(invite, false, node, gesture)
} }
case let .adminsHeader(_, text): case let .adminsHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .admin(_, _, creator): 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) arguments.openAdmin(creator)
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, contextAction: nil) }, 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)) 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 { if let additionalInvites = additionalInvites {
var index: Int32 = 0 var index: Int32 = 0
for invite in additionalInvites { 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 index += 1
} }
} else if let admin = admin, admin.count > 1 { } else if let admin = admin, admin.count > 1 {
var index: Int32 = 0 var index: Int32 = 0
for _ in 0 ..< admin.count - 1 { 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 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) let controller = InviteLinkViewController(context: context, peerId: peerId, invite: invite, invitationsContext: invitesContext, revokedInvitationsContext: revokedInvitesContext, importersContext: nil)
pushControllerImpl?(controller) 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 { guard let node = node as? ContextExtractedContentContainingNode, let controller = getControllerImpl?(), let invite = invite else {
return return
} }
@ -615,6 +620,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
}))) })))
} }
if !invite.isPermanent && canEdit {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextEdit, icon: { theme in 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) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
}, action: { _, f in }, action: { _, f in
@ -634,6 +640,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
pushControllerImpl?(controller) pushControllerImpl?(controller)
}))) })))
} }
}
if invite.isRevoked { if invite.isRevoked {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextDelete, textColor: .destructive, icon: { theme in items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextDelete, textColor: .destructive, icon: { theme in
@ -685,8 +692,10 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: { ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
dismissAction() 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) invitesContext.remove(invite)

View File

@ -537,8 +537,10 @@ public final class InviteLinkViewController: ViewController {
dismissAction() dismissAction()
self?.controller?.dismiss() 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) self?.controller?.invitationsContext?.remove(invite)

View File

@ -374,10 +374,10 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo
var credibilityIconOffset: CGFloat = 4.0 var credibilityIconOffset: CGFloat = 4.0
if let peer = item.peer { if let peer = item.peer {
if peer.isScam { 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 credibilityIconOffset = 6.0
} else if peer.isFake { } 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 credibilityIconOffset = 2.0
} else if peer.isVerified { } else if peer.isVerified {
credibilityIconImage = PresentationResourcesItemList.verifiedPeerIcon(item.presentationData.theme) credibilityIconImage = PresentationResourcesItemList.verifiedPeerIcon(item.presentationData.theme)

View File

@ -79,10 +79,36 @@ public struct ItemListToolbarItem {
} }
let actions: [Action] 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 { private struct ItemListNodeTransition {
let theme: PresentationTheme let theme: PresentationTheme
let strings: PresentationStrings
let entries: ItemListNodeEntryTransition let entries: ItemListNodeEntryTransition
let updateStyle: ItemListStyle? let updateStyle: ItemListStyle?
let emptyStateItem: ItemListControllerEmptyStateItem? let emptyStateItem: ItemListControllerEmptyStateItem?
@ -348,7 +374,7 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
scrollToItem = state.initialScrollToItem 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 |> deliverOnMainQueue).start(next: { [weak self] transition in
if let strongSelf = self { if let strongSelf = self {
@ -452,6 +478,10 @@ open class ItemListControllerNode: ASDisplayNode, UIScrollViewDelegate {
searchNode.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, transition: transition) 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 let dequeue = self.validLayout == nil
self.validLayout = (layout, navigationBarHeight) self.validLayout = (layout, navigationBarHeight)
if dequeue { 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 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 let strongSelf = self {
if !strongSelf.didSetReady { if !strongSelf.didSetReady {

View File

@ -4,7 +4,7 @@
#define GPUImageRotationSwapsWidthAndHeight(rotation) ((rotation) == kGPUImageRotateLeft || (rotation) == kGPUImageRotateRight || (rotation) == kGPUImageRotateRightFlipVertical || (rotation) == kGPUImageRotateRightFlipHorizontal) #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 @interface GPUImageContext : NSObject

View File

@ -284,6 +284,13 @@ NSString *const kGPUImagePassthroughFragmentShaderString = SHADER_STRING
0.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) switch(rotationMode)
{ {
case kGPUImageNoRotation: return noRotationTextureCoordinates; case kGPUImageNoRotation: return noRotationTextureCoordinates;
@ -294,6 +301,7 @@ NSString *const kGPUImagePassthroughFragmentShaderString = SHADER_STRING
case kGPUImageRotateRightFlipVertical: return rotateRightVerticalFlipTextureCoordinates; case kGPUImageRotateRightFlipVertical: return rotateRightVerticalFlipTextureCoordinates;
case kGPUImageRotateRightFlipHorizontal: return rotateRightHorizontalFlipTextureCoordinates; case kGPUImageRotateRightFlipHorizontal: return rotateRightHorizontalFlipTextureCoordinates;
case kGPUImageRotate180: return rotate180TextureCoordinates; case kGPUImageRotate180: return rotate180TextureCoordinates;
case kGPUImageRotate180FlipHorizontal: return rotate180HorizontalFlipTextureCoordinates;
} }
} }
@ -642,6 +650,11 @@ NSString *const kGPUImagePassthroughFragmentShaderString = SHADER_STRING
rotatedPoint.x = 1.0f - pointToRotate.x; rotatedPoint.x = 1.0f - pointToRotate.x;
rotatedPoint.y = 1.0f - pointToRotate.y; rotatedPoint.y = 1.0f - pointToRotate.y;
}; break; }; break;
case kGPUImageRotate180FlipHorizontal:
{
rotatedPoint.x = pointToRotate.x;
rotatedPoint.y = 1.0f - pointToRotate.y;
}; break;
} }
return rotatedPoint; return rotatedPoint;

View File

@ -214,7 +214,7 @@
_rotationMode = cropMirrored ? kGPUImageRotateRightFlipHorizontal : kGPUImageRotateRight; _rotationMode = cropMirrored ? kGPUImageRotateRightFlipHorizontal : kGPUImageRotateRight;
break; break;
case UIImageOrientationDown: case UIImageOrientationDown:
_rotationMode = kGPUImageRotate180; _rotationMode = cropMirrored ? kGPUImageRotate180FlipHorizontal : kGPUImageRotate180;
break; break;
case UIImageOrientationUp: case UIImageOrientationUp:
if (cropMirrored) if (cropMirrored)

View File

@ -285,6 +285,13 @@
0.0f, 1.0f, 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) switch(rotationMode)
{ {
case kGPUImageNoRotation: return noRotationTextureCoordinates; case kGPUImageNoRotation: return noRotationTextureCoordinates;
@ -295,6 +302,7 @@
case kGPUImageRotateRightFlipVertical: return rotateRightVerticalFlipTextureCoordinates; case kGPUImageRotateRightFlipVertical: return rotateRightVerticalFlipTextureCoordinates;
case kGPUImageRotateRightFlipHorizontal: return rotateRightHorizontalFlipTextureCoordinates; case kGPUImageRotateRightFlipHorizontal: return rotateRightHorizontalFlipTextureCoordinates;
case kGPUImageRotate180: return rotate180TextureCoordinates; case kGPUImageRotate180: return rotate180TextureCoordinates;
case kGPUImageRotate180FlipHorizontal: return rotate180HorizontalFlipTextureCoordinates;
} }
} }

View File

@ -118,16 +118,20 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN
- (GPUImageRotationMode)rotationForTrack:(AVAsset *)asset { - (GPUImageRotationMode)rotationForTrack:(AVAsset *)asset {
AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; 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; return kGPUImageRotate180;
} else if (trackTransform.a == 1 && trackTransform.d == 1) { } else if (t.a == 1 && t.d == 1) {
return kGPUImageNoRotation; return kGPUImageNoRotation;
} else if (trackTransform.b == -1 && trackTransform.c == 1) { } else if (t.b == -1 && t.c == 1) {
return kGPUImageRotateLeft; return kGPUImageRotateLeft;
} else if (t.a == -1 && t.d == 1) {
return kGPUImageFlipHorizonal;
} else if (t.a == 1 && t.d == -1) {
return kGPUImageRotate180FlipHorizontal;
} else { } else {
if (trackTransform.c == 1) { if (t.c == 1) {
return kGPUImageRotateRightFlipVertical; return kGPUImageRotateRightFlipVertical;
} else { } else {
return kGPUImageRotateRight; return kGPUImageRotateRight;

View File

@ -593,28 +593,30 @@ UIImageOrientation TGVideoOrientationForAsset(AVAsset *asset, bool *mirrored)
{ {
AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] firstObject]; AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] firstObject];
CGAffineTransform t = videoTrack.preferredTransform; CGAffineTransform t = videoTrack.preferredTransform;
double videoRotation = atan2((float)t.b, (float)t.a);
if (mirrored != NULL) if (t.a == -1 && t.d == -1) {
{
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) {
return UIImageOrientationLeft; return UIImageOrientationLeft;
} else if (fabs(videoRotation - M_PI_2) < FLT_EPSILON) { } else if (t.a == 1 && t.d == 1) {
if (t.c == 1 && mirrored != NULL) { 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; *mirrored = true;
} }
return UIImageOrientationUp;
} else if (fabs(videoRotation + M_PI_2) < FLT_EPSILON) {
return UIImageOrientationDown;
} else {
return UIImageOrientationRight; return UIImageOrientationRight;
} else {
if (t.c == 1) {
if (mirrored != NULL) {
*mirrored = true;
}
}
return UIImageOrientationUp;
} }
} }

View File

@ -696,8 +696,8 @@ static void addRoundedRectToPath(CGContextRef context, CGRect rect, CGFloat oval
(id)UIColorRGB(0x80c864).CGColor, //green (id)UIColorRGB(0x80c864).CGColor, //green
(id)UIColorRGB(0xfcde65).CGColor, //yellow (id)UIColorRGB(0xfcde65).CGColor, //yellow
(id)UIColorRGB(0xfc964d).CGColor, //orange (id)UIColorRGB(0xfc964d).CGColor, //orange
(id)[UIColor blackColor].CGColor, //black (id)UIColorRGB(0x000000).CGColor, //black
(id)[UIColor whiteColor].CGColor //white (id)UIColorRGB(0xffffff).CGColor //white
]; ];
}); });
return colors; return colors;

View File

@ -187,7 +187,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
_style = style; _style = style;
switch (_style) { switch (_style) {
case TGPhotoPaintTextEntityStyleRegular: case TGPhotoPaintTextEntityStyleRegular:
_textView.layer.shadowColor = [[UIColor blackColor] CGColor]; _textView.layer.shadowColor = [UIColorRGB(0x000000) CGColor];
_textView.layer.shadowOffset = CGSizeMake(0.0f, 4.0f); _textView.layer.shadowOffset = CGSizeMake(0.0f, 4.0f);
_textView.layer.shadowOpacity = 0.4f; _textView.layer.shadowOpacity = 0.4f;
_textView.layer.shadowRadius = 4.0f; _textView.layer.shadowRadius = 4.0f;
@ -218,7 +218,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
case TGPhotoPaintTextEntityStyleOutlined: case TGPhotoPaintTextEntityStyleOutlined:
{ {
_textView.textColor = [UIColor whiteColor]; _textView.textColor = UIColorRGB(0xffffff);
_textView.strokeColor = _swatch.color; _textView.strokeColor = _swatch.color;
_textView.frameColor = nil; _textView.frameColor = nil;
} }
@ -238,9 +238,9 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
} }
if (lightness > 0.87) { if (lightness > 0.87) {
_textView.textColor = [UIColor blackColor]; _textView.textColor = UIColorRGB(0x000000);
} else { } else {
_textView.textColor = [UIColor whiteColor]; _textView.textColor = UIColorRGB(0xffffff);
} }
_textView.strokeColor = nil; _textView.strokeColor = nil;
_textView.frameColor = _swatch.color; _textView.frameColor = _swatch.color;
@ -484,6 +484,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
@implementation TGPhotoTextView @implementation TGPhotoTextView
{ {
UIFont *_font; UIFont *_font;
UIColor *_forcedTextColor;
} }
- (instancetype)initWithFrame:(CGRect)frame - (instancetype)initWithFrame:(CGRect)frame
@ -564,6 +565,11 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
self.layoutManager.textContainers.firstObject.lineFragmentPadding = floor(font.pointSize * 0.3); self.layoutManager.textContainers.firstObject.lineFragmentPadding = floor(font.pointSize * 0.3);
} }
- (void)setTextColor:(UIColor *)textColor {
_forcedTextColor = textColor;
[super setTextColor:textColor];
}
- (void)insertText:(NSString *)text { - (void)insertText:(NSString *)text {
[self fixTypingAttributes]; [self fixTypingAttributes];
[super insertText:text]; [super insertText:text];
@ -577,9 +583,14 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
} }
- (void)fixTypingAttributes { - (void)fixTypingAttributes {
NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init];
if (_font != nil) { if (_font != nil) {
self.typingAttributes = @{NSFontAttributeName: _font}; attributes[NSFontAttributeName] = _font;
} }
if (_forcedTextColor != nil) {
attributes[NSForegroundColorAttributeName] = _forcedTextColor;
}
self.typingAttributes = attributes;
} }
@end @end

View File

@ -530,7 +530,7 @@ public func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -
} }
} }
} }
if estimatedSize > 5 * 1024 * 1024 { if estimatedSize > 10 * 1024 * 1024 {
fileAttributes.append(.hintFileIsLarge) fileAttributes.append(.hintFileIsLarge)
} }

View File

@ -1049,11 +1049,20 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
case .tooMuchJoined: case .tooMuchJoined:
text = presentationData.strings.Group_ErrorSupergroupConversionNotPossible text = presentationData.strings.Group_ErrorSupergroupConversionNotPossible
case .restricted: case .restricted:
if let peer = adminView.peers[adminView.peerId] { if let admin = adminView.peers[adminView.peerId] {
text = presentationData.strings.Privacy_GroupsAndChannels_InviteToGroupError(peer.compactDisplayTitle, peer.compactDisplayTitle).0 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: case .notMutualContact:
if case .broadcast = channel.info {
text = presentationData.strings.Channel_AddUserLeftError
} else {
text = presentationData.strings.GroupInfo_AddUserLeftError text = presentationData.strings.GroupInfo_AddUserLeftError
}
default: default:
break break
} }
@ -1119,17 +1128,28 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
return current.withUpdatedUpdating(true) 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 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 let .addMemberError(addMemberError) = error, let admin = adminView.peers[adminView.peerId] {
if case .restricted = error { var text = presentationData.strings.Login_UnknownError
var text = presentationData.strings.Privacy_GroupsAndChannels_InviteToChannelError(admin.compactDisplayTitle, admin.compactDisplayTitle).0 switch addMemberError {
if case .group = channel.info { 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 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) case .notMutualContact:
} else if case .tooMuchJoined = error { if case .broadcast = channel.info {
let text = presentationData.strings.Invite_ChannelsTooMuch text = presentationData.strings.Channel_AddUserLeftError
presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) } 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 { } else if case .adminsTooMuch = error {
let text: String let text: String
if case .broadcast = channel.info { 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 updateFlags: TelegramChatAdminRightsFlags?
var updateRank: String? var updateRank: String?
updateState { current in updateState { current in
@ -1163,12 +1183,7 @@ public func channelAdminController(context: AccountContext, peerId: PeerId, admi
} }
let maskRightsFlags: TelegramChatAdminRightsFlags = .groupSpecific let maskRightsFlags: TelegramChatAdminRightsFlags = .groupSpecific
let defaultFlags: TelegramChatAdminRightsFlags let defaultFlags = maskRightsFlags.subtracting([.canBeAnonymous, .canAddAdmins])
if case .creator = group.role {
defaultFlags = maskRightsFlags.subtracting(.canBeAnonymous)
} else {
defaultFlags = maskRightsFlags.subtracting(.canAddAdmins).subtracting(.canBeAnonymous)
}
if updateFlags == nil { if updateFlags == nil {
updateFlags = defaultFlags updateFlags = defaultFlags

View File

@ -148,7 +148,7 @@ private enum ChannelDiscussionGroupSetupControllerEntry: ItemListNodeEntry {
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, sectionId: self.section, editing: false, action: { return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, sectionId: self.section, editing: false, action: {
arguments.createGroup() arguments.createGroup()
}) })
case let .group(_, theme, strings, peer, nameOrder): case let .group(_, _, strings, peer, nameOrder):
let text: String let text: String
if let peer = peer as? TelegramChannel, let addressName = peer.addressName, !addressName.isEmpty { if let peer = peer as? TelegramChannel, let addressName = peer.addressName, !addressName.isEmpty {
text = "@\(addressName)" 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: { 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) arguments.selectGroup(peer.id)
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }) }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
case let .groupsInfo(theme, title): case let .groupsInfo(_, title):
return ItemListTextItem(presentationData: presentationData, text: .plain(title), sectionId: self.section) 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: { return ItemListActionItem(presentationData: presentationData, title: title, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: {
arguments.unlinkGroup() arguments.unlinkGroup()
}) })

View File

@ -370,6 +370,10 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) ->
} }
}).start(error: { [weak contactsController] error in }).start(error: { [weak contactsController] error in
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let _ = (context.account.postbox.transaction { transaction in
return transaction.getPeer(peerId)
}
|> deliverOnMainQueue).start(next: { peer in
let text: String let text: String
switch error { switch error {
case .limitExceeded: case .limitExceeded:
@ -381,16 +385,15 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) ->
case .restricted: case .restricted:
text = presentationData.strings.Channel_ErrorAddBlocked text = presentationData.strings.Channel_ErrorAddBlocked
case .notMutualContact: case .notMutualContact:
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
text = presentationData.strings.Channel_AddUserLeftError
} else {
text = presentationData.strings.GroupInfo_AddUserLeftError 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 case let .bot(memberId):
guard let peer = peer as? TelegramChannel else { 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) presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
contactsController?.dismiss() contactsController?.dismiss()
return return
} }
@ -407,7 +410,6 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) ->
} }
contactsController?.dismiss() contactsController?.dismiss()
})
return return
case .botDoesntSupportGroups: case .botDoesntSupportGroups:
text = presentationData.strings.Channel_BotDoesntSupportGroups text = presentationData.strings.Channel_BotDoesntSupportGroups
@ -416,6 +418,7 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) ->
} }
presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) presentControllerImpl?(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
contactsController?.dismiss() contactsController?.dismiss()
})
})) }))
presentControllerImpl?(contactsController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) presentControllerImpl?(contactsController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))

View File

@ -20,6 +20,7 @@ import Markdown
public enum PeerReportSubject { public enum PeerReportSubject {
case peer(PeerId) case peer(PeerId)
case messages([MessageId]) case messages([MessageId])
case profilePhoto(PeerId, Int64)
} }
public enum PeerReportOption { public enum PeerReportOption {
@ -60,7 +61,7 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
}, action: { [weak parent] _, f in }, action: { [weak parent] _, f in
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var reportReason: ReportReason? let reportReason: ReportReason
switch option { switch option {
case .spam: case .spam:
reportReason = .spam reportReason = .spam
@ -75,30 +76,60 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
case .copyright: case .copyright:
reportReason = .copyright reportReason = .copyright
case .other: case .other:
break reportReason = .custom
} }
if let reportReason = reportReason {
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)
}
}
let action: (String) -> Void = { message in
switch subject { switch subject {
case let .peer(peerId): case let .peer(peerId):
let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason, message: "") let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason, message: "")
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") { displaySuccess()
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) completion(reportReason, true)
}) })
case let .messages(messageIds): case let .messages(messageIds):
let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason, message: "") let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason, message: "")
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") { displaySuccess()
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 .profilePhoto(peerId, photoId):
let _ = (reportPeerPhoto(account: context.account, peerId: peerId, reason: reportReason, message: "")
|> deliverOnMainQueue).start(completed: {
displaySuccess()
completion(reportReason, true) completion(reportReason, true)
}) })
} }
} else {
parent?.push(peerReportController(context: context, subject: subject, completion: completion))
} }
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) f(.dismissWithoutContent)
}))) })))
} }
@ -162,17 +193,21 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
passthrough = false 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 { switch subject {
case let .peer(peerId): case let .peer(peerId):
if passthrough { if passthrough {
completion(reportReason, true) completion(reportReason, true)
} else { } 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: { |> deliverOnMainQueue).start(completed: {
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") { displaySuccess()
present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), nil)
}
completion(nil, false) completion(nil, false)
}) })
} }
@ -180,11 +215,19 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
if passthrough { if passthrough {
completion(reportReason, true) completion(reportReason, true)
} else { } 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: { |> deliverOnMainQueue).start(completed: {
if let path = getAppBundle().path(forResource: "PoliceCar", ofType: "tgs") { displaySuccess()
present(UndoOverlayController(presentationData: presentationData, content: .emoji(path: path, text: presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), nil) 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) 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: { items.append(ActionSheetButtonItem(title: presentationData.strings.Report_Report, color: .accent, font: .bold, enabled: true, action: {
dismissAction() dismissAction()
action() action(message)
})) }))
controller.setItemGroups([ controller.setItemGroups([
@ -214,7 +257,7 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
]) ])
present(controller, nil) present(controller, nil)
} else { } else {
action() action("")
} }
} else { } else {
push(peerReportController(context: context, subject: subject, completion: completion)) push(peerReportController(context: context, subject: subject, completion: completion))
@ -379,6 +422,11 @@ private func peerReportController(context: AccountContext, subject: PeerReportSu
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
completed() completed()
})) }))
case let .profilePhoto(peerId, photoId):
reportDisposable.set((reportPeerPhoto(account: context.account, peerId: peerId, reason: reportReason, message: text)
|> deliverOnMainQueue).start(completed: {
completed()
}))
} }
} }
}) })

View File

@ -1420,10 +1420,17 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
if let navigationController = (controller?.navigationController as? NavigationController) { if let navigationController = (controller?.navigationController as? NavigationController) {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId))) context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peerId)))
} }
}, error: { [weak controller] _ in }, error: { [weak controller] error in
if let controller = controller { if let controller = controller {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } 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)) })]), in: .window(.root))
@ -1438,6 +1445,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe
}, sendFile: nil, }, sendFile: nil,
sendSticker: nil, sendSticker: nil,
requestMessageActionUrlAuth: nil,
present: { c, a in present: { c, a in
presentControllerImpl?(c, a) presentControllerImpl?(c, a)
}, dismissInput: { }, dismissInput: {

View File

@ -531,8 +531,7 @@ public func proxySettingsController(accountManager: AccountManager, context: Acc
result += string result += string
} }
let controller = ShareController(context: context, subject: .text(result), preferredAction: .default, showInChat: nil, externalShare: true, immediateExternalShare: true, switchableAccounts: []) presentExternalShare(context: context, text: result, parentController: strongController)
presentControllerImpl?(controller, nil)
}) })
} }

View File

@ -378,19 +378,15 @@ func proxyServerSettingsController(context: AccountContext? = nil, presentationD
} }
shareImpl = { [weak controller] in shareImpl = { [weak controller] in
let state = stateValue.with { $0 } let state = stateValue.with { $0 }
guard let server = proxyServerSettings(with: state), let strongController = controller else { guard let server = proxyServerSettings(with: state) else {
return return
} }
let link = shareLink(for: server) let link = shareLink(for: server)
controller?.view.endEditing(true) controller?.view.endEditing(true)
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
let controller = ShareProxyServerActionSheetController(presentationData: presentationData, updatedPresentationData: updatedPresentationData, link: link) let controller = ShareProxyServerActionSheetController(presentationData: presentationData, updatedPresentationData: updatedPresentationData, link: link)
presentControllerImpl?(controller, nil) 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)
}
} }
return controller return controller

View File

@ -180,7 +180,7 @@ public func logoutOptionsController(context: AccountContext, navigationControlle
dismissImpl?() dismissImpl?()
context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, openPeer: { peer, navigation 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
pushControllerImpl?(controller) pushControllerImpl?(controller)
}, dismissInput: {}, contentContext: nil) }, dismissInput: {}, contentContext: nil)
}) })

View File

@ -897,7 +897,7 @@ func settingsSearchableItems(context: AccountContext, notificationExceptionsList
let _ = (cachedFaqInstantPage(context: context) let _ = (cachedFaqInstantPage(context: context)
|> deliverOnMainQueue).start(next: { resolvedUrl in |> deliverOnMainQueue).start(next: { resolvedUrl in
context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, openPeer: { peer, navigation 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) present(.push, controller)
}, dismissInput: {}, contentContext: nil) }, dismissInput: {}, contentContext: nil)
}) })

View File

@ -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)) 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) }, 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.controllerNode.dismiss = { [weak self] shared in
self?.presentingViewController?.dismiss(animated: false, completion: nil) self?.presentingViewController?.dismiss(animated: false, completion: nil)
self?.dismissed?(shared) self?.dismissed?(shared)
@ -721,13 +721,18 @@ public final class ShareController: ViewController {
}) })
activities = [shareToInstagram] activities = [shareToInstagram]
} }
let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: activities)
if let window = strongSelf.view.window, let rootViewController = window.rootViewController { 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?.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)) 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) rootViewController.present(activityController, animated: true, completion: nil)
} }
})
} }
return .done return .done
} }
@ -785,14 +790,18 @@ public final class ShareController: ViewController {
super.loadView() super.loadView()
} }
let didAppearPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
override public func viewDidAppear(_ animated: Bool) { override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated) super.viewDidAppear(animated)
if !self.animatedIn { if !self.animatedIn {
self.animatedIn = true self.animatedIn = true
self.didAppearPromise.set(true)
if !self.immediateExternalShare {
self.controllerNode.animateIn() self.controllerNode.animateIn()
} }
} }
}
override public func dismiss(completion: (() -> Void)? = nil) { override public func dismiss(completion: (() -> Void)? = nil) {
self.controllerNode.view.endEditing(true) self.controllerNode.view.endEditing(true)
@ -1080,3 +1089,13 @@ private class ShareToInstagramActivity: UIActivity {
activityDidFinish(true) 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)
}

View File

@ -21,10 +21,6 @@ enum ShareExternalState {
case done case done
} }
func openExternalShare(state: () -> Signal<ShareExternalState, NoError>) {
}
final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate { final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate {
private let sharedContext: SharedAccountContext private let sharedContext: SharedAccountContext
private var context: AccountContext? private var context: AccountContext?
@ -176,6 +172,8 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
super.init() super.init()
self.isHidden = true
self.controllerInteraction = ShareControllerInteraction(togglePeer: { [weak self] peer, search in self.controllerInteraction = ShareControllerInteraction(togglePeer: { [weak self] peer, search in
if let strongSelf = self { if let strongSelf = self {
var added = false var added = false
@ -621,6 +619,11 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
} }
func animateIn() { func animateIn() {
if let completion = self.outCompletion {
self.outCompletion = nil
completion()
return
}
if self.contentNode != nil { if self.contentNode != nil {
self.isHidden = false self.isHidden = false
@ -635,6 +638,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
} }
} }
var outCompletion: (() -> Void)?
func animateOut(shared: Bool, completion: @escaping () -> Void) { func animateOut(shared: Bool, completion: @escaping () -> Void) {
if self.contentNode != nil { if self.contentNode != nil {
var dimCompleted = false var dimCompleted = false
@ -664,9 +668,15 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
internalCompletion() internalCompletion()
}) })
} else { } else {
self.outCompletion = completion
Queue.mainQueue().after(0.2) {
if let completion = self.outCompletion {
self.outCompletion = nil
completion() completion()
} }
} }
}
}
func updatePeers(context: AccountContext, switchableAccounts: [AccountWithInfo], peers: [(RenderedPeer, PeerPresence?)], accountPeer: Peer, defaultAction: ShareControllerAction?) { func updatePeers(context: AccountContext, switchableAccounts: [AccountWithInfo], peers: [(RenderedPeer, PeerPresence?)], accountPeer: Peer, defaultAction: ShareControllerAction?) {
self.context = context self.context = context

View File

@ -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 estimatedSize = TGMediaVideoConverter.estimatedSize(for: preset, duration: finalDuration, hasAudio: true)
let resource = LocalFileVideoMediaResource(randomId: arc4random64(), path: asset.url.path, adjustments: resourceAdjustments) 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 |> mapError { _ -> Void in
return Void() return Void()
} }
@ -192,7 +192,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
mimeType = "animation/gif" mimeType = "animation/gif"
attributes = [.ImageSize(size: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height))), .Animated, .FileName(fileName: fileName ?? "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() } |> mapError { _ -> Void in return Void() }
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in |> mapToSignal { event -> Signal<PreparedShareItem, Void> in
switch event { switch event {
@ -223,7 +223,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
thumbnailData = jpegData 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() } |> mapError { _ -> Void in return Void() }
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in |> mapToSignal { event -> Signal<PreparedShareItem, Void> in
switch event { 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 isVoice = ((value["isVoice"] as? NSNumber)?.boolValue ?? false)
let title = value["title"] as? String let title = value["title"] as? String
let artist = value["artist"] as? String let artist = value["artist"] as? String
let mimeType = value["mimeType"] as? String ?? "audio/ogg"
var waveform: MemoryBuffer? var waveform: MemoryBuffer?
if let waveformData = TGItemProviderSignals.audioWaveform(url) { if let waveformData = TGItemProviderSignals.audioWaveform(url) {
waveform = MemoryBuffer(data: waveformData) 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() } |> mapError { _ -> Void in return Void() }
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in |> mapToSignal { event -> Signal<PreparedShareItem, Void> in
switch event { switch event {

View File

@ -240,6 +240,11 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
let controller = PeekController(theme: PeekControllerTheme(presentationTheme: strongSelf.presentationData.theme), content: content, sourceNode: { let controller = PeekController(theme: PeekControllerTheme(presentationTheme: strongSelf.presentationData.theme), content: content, sourceNode: {
return sourceNode return sourceNode
}) })
controller.visibilityUpdated = { [weak self] visible in
if let strongSelf = self {
strongSelf.contentGridNode.forceHidden = visible
}
}
strongSelf.presentInGlobalOverlay?(controller, nil) strongSelf.presentInGlobalOverlay?(controller, nil)
return controller return controller
} }

View File

@ -140,7 +140,6 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
if self.currentState == nil || self.currentState!.0 !== account || self.currentState!.1 != stickerItem || self.isEmpty != isEmpty { if self.currentState == nil || self.currentState!.0 !== account || self.currentState!.1 != stickerItem || self.isEmpty != isEmpty {
if let stickerItem = stickerItem { if let stickerItem = stickerItem {
if let _ = stickerItem.file.dimensions {
if stickerItem.file.isAnimatedSticker { if stickerItem.file.isAnimatedSticker {
let dimensions = stickerItem.file.dimensions ?? PixelDimensions(width: 512, height: 512) 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)))) self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: account.postbox, file: stickerItem.file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))))
@ -167,7 +166,6 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
self.imageNode.setSignal(chatMessageSticker(account: account, file: stickerItem.file, small: true)) 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()) self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file), resource: chatMessageStickerResource(file: stickerItem.file, small: true)).start())
} }
}
} else { } else {
if let placeholderNode = self.placeholderNode { if let placeholderNode = self.placeholderNode {
if isEmpty { if isEmpty {

View File

@ -887,7 +887,11 @@ public final class VoiceChatController: ViewController {
case .restricted: case .restricted:
text = presentationData.strings.Channel_ErrorAddBlocked text = presentationData.strings.Channel_ErrorAddBlocked
case .notMutualContact: case .notMutualContact:
if case .broadcast = groupPeer.info {
text = presentationData.strings.Channel_AddUserLeftError
} else {
text = presentationData.strings.GroupInfo_AddUserLeftError text = presentationData.strings.GroupInfo_AddUserLeftError
}
case .botDoesntSupportGroups: case .botDoesntSupportGroups:
text = presentationData.strings.Channel_BotDoesntSupportGroups text = presentationData.strings.Channel_BotDoesntSupportGroups
case .tooMuchBots: 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)) 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: case .notMutualContact:
let _ = (strongSelf.context.account.postbox.loadedPeerWithId(peer.id) 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))
|> 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))
})
case .tooManyChannels: case .tooManyChannels:
let _ = (strongSelf.context.account.postbox.loadedPeerWithId(peer.id) 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))
|> 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))
})
case .groupFull, .generic: 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)) 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))
} }

View File

@ -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() return self.peerInputActivityManager.allActivities()
|> map { activities in |> map { activities in
var result: [PeerActivitySpace: [PeerId: PeerInputActivity]] = [:] var result: [PeerActivitySpace: [(PeerId, PeerInputActivity)]] = [:]
for (chatPeerId, chatActivities) in activities { for (chatPeerId, chatActivities) in activities {
result[chatPeerId] = chatActivities.mapValues({ $0.activity }) result[chatPeerId] = chatActivities.map { ($0.0, $0.1.activity) }
} }
return result return result
} }

View File

@ -107,7 +107,7 @@ public enum ChatHistoryImport {
case chatAdminRequired case chatAdminRequired
} }
public static func uploadMedia(account: Account, session: Session, file: TempBoxFile, fileName: String, mimeType: String, type: MediaType) -> Signal<Float, UploadMediaError> { public static func uploadMedia(account: Account, session: Session, file: TempBoxFile, disposeFileAfterDone: Bool, fileName: String, mimeType: String, type: MediaType) -> Signal<Float, UploadMediaError> {
var forceNoBigParts = true var forceNoBigParts = true
guard let size = fileSize(file.path), size != 0 else { guard let size = fileSize(file.path), size != 0 else {
return .single(1.0) return .single(1.0)
@ -160,6 +160,11 @@ public enum ChatHistoryImport {
|> mapToSignal { result -> Signal<Float, UploadMediaError> in |> mapToSignal { result -> Signal<Float, UploadMediaError> in
return .single(1.0) return .single(1.0)
} }
|> afterDisposed {
if disposeFileAfterDone {
TempBox.shared.dispose(file)
}
}
} }
} }
@ -192,7 +197,7 @@ public enum ChatHistoryImport {
case invalidChatType case invalidChatType
case userBlocked case userBlocked
case limitExceeded case limitExceeded
case userIsNotMutualContact case notMutualContact
} }
public static func checkPeerImport(account: Account, peerId: PeerId) -> Signal<CheckPeerImportResult, CheckPeerImportError> { public static func checkPeerImport(account: Account, peerId: PeerId) -> Signal<CheckPeerImportResult, CheckPeerImportError> {
@ -217,7 +222,7 @@ public enum ChatHistoryImport {
} else if error.errorDescription == "USER_IS_BLOCKED" { } else if error.errorDescription == "USER_IS_BLOCKED" {
return .userBlocked return .userBlocked
} else if error.errorDescription == "USER_NOT_MUTUAL_CONTACT" { } else if error.errorDescription == "USER_NOT_MUTUAL_CONTACT" {
return .userBlocked return .notMutualContact
} else if error.errorDescription == "FLOOD_WAIT" { } else if error.errorDescription == "FLOOD_WAIT" {
return .limitExceeded return .limitExceeded
} else { } else {

View File

@ -6,6 +6,7 @@ import MtProtoKit
public enum CreateSecretChatError { public enum CreateSecretChatError {
case generic case generic
case limitExceeded
} }
public func createSecretChat(account: Account, peerId: PeerId) -> Signal<PeerId, CreateSecretChatError> { public func createSecretChat(account: Account, peerId: PeerId) -> Signal<PeerId, CreateSecretChatError> {
@ -29,10 +30,14 @@ public func createSecretChat(account: Account, peerId: PeerId) -> Signal<PeerId,
return .fail(.generic) return .fail(.generic)
} }
return account.network.request(Api.functions.messages.requestEncryption(userId: inputUser, randomId: Int32(bitPattern: arc4random()), gA: Buffer(data: ga))) return account.network.request(Api.functions.messages.requestEncryption(userId: inputUser, randomId: Int32(bitPattern: arc4random()), gA: Buffer(data: ga)), automaticFloodWait: false)
|> mapError { _ -> CreateSecretChatError in |> mapError { error -> CreateSecretChatError in
if error.errorDescription.hasPrefix("FLOOD_WAIT_") {
return .limitExceeded
} else {
return .generic return .generic
} }
}
|> mapToSignal { result -> Signal<PeerId, CreateSecretChatError> in |> mapToSignal { result -> Signal<PeerId, CreateSecretChatError> in
return account.postbox.transaction { transaction -> PeerId in return account.postbox.transaction { transaction -> PeerId in
updateSecretChat(encryptionProvider: account.network.encryptionProvider, accountPeerId: account.peerId, transaction: transaction, mediaBox: account.postbox.mediaBox, chat: result, requestData: SecretChatRequestData(g: config.g, p: config.p, a: a)) updateSecretChat(encryptionProvider: account.network.encryptionProvider, accountPeerId: account.peerId, transaction: transaction, mediaBox: account.postbox.mediaBox, chat: result, requestData: SecretChatRequestData(g: config.g, p: config.p, a: a))

View File

@ -529,7 +529,7 @@ private final class PeerExportedInvitationsContextImpl {
} }
private func updateCache() { private func updateCache() {
guard self.hasLoadedOnce && !self.isLoadingMore else { guard self.isMainList && self.hasLoadedOnce && !self.isLoadingMore else {
return return
} }

View File

@ -2,6 +2,7 @@ import Foundation
import Postbox import Postbox
import TelegramApi import TelegramApi
import SwiftSignalKit import SwiftSignalKit
import MtProtoKit
import SyncCore import SyncCore
@ -11,13 +12,19 @@ public enum JoinChannelError {
case tooMuchUsers 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) return account.postbox.loadedPeerWithId(peerId)
|> take(1) |> take(1)
|> castError(JoinChannelError.self) |> castError(JoinChannelError.self)
|> mapToSignal { peer -> Signal<RenderedChannelParticipant?, JoinChannelError> in |> mapToSignal { peer -> Signal<RenderedChannelParticipant?, JoinChannelError> in
if let inputChannel = apiInputChannel(peer) { 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 |> mapError { error -> JoinChannelError in
switch error.errorDescription { switch error.errorDescription {
case "CHANNELS_TOO_MUCH": case "CHANNELS_TOO_MUCH":

View File

@ -30,13 +30,13 @@ struct PeerInputActivityRecord: Equatable {
private final class ManagedLocalTypingActivitiesContext { private final class ManagedLocalTypingActivitiesContext {
private var disposables: [PeerActivitySpace: (PeerInputActivityRecord, MetaDisposable)] = [:] 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 start: [(PeerActivitySpace, PeerInputActivityRecord?, MetaDisposable)] = []
var dispose: [MetaDisposable] = [] var dispose: [MetaDisposable] = []
var validPeerIds = Set<PeerActivitySpace>() var validPeerIds = Set<PeerActivitySpace>()
for (peerId, record) in activities { for (peerId, record) in activities {
if let activity = record.values.first { if let activity = record.first?.1 {
validPeerIds.insert(peerId) validPeerIds.insert(peerId)
let currentRecord = self.disposables[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 return Signal { subscriber in
let context = Atomic(value: ManagedLocalTypingActivitiesContext()) let context = Atomic(value: ManagedLocalTypingActivitiesContext())
let disposable = activities.start(next: { activities in let disposable = activities.start(next: { activities in

View File

@ -202,6 +202,8 @@ public func updateChannelAdminRights(account: Account, peerId: PeerId, adminId:
} }
|> map { [$0] } |> map { [$0] }
) )
} else if error.errorDescription == "USER_NOT_MUTUAL_CONTACT" {
return .fail(.addMemberError(.notMutualContact))
} else if error.errorDescription == "USER_PRIVACY_RESTRICTED" { } else if error.errorDescription == "USER_PRIVACY_RESTRICTED" {
return .fail(.addMemberError(.restricted)) return .fail(.addMemberError(.restricted))
} else if error.errorDescription == "USER_CHANNELS_TOO_MUCH" { } else if error.errorDescription == "USER_CHANNELS_TOO_MUCH" {

View File

@ -178,9 +178,9 @@ private final class PeerInputActivityContext {
} }
private final class PeerGlobalInputActivityContext { 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) return self.subscribers.add(subscriber)
} }
@ -192,7 +192,7 @@ private final class PeerGlobalInputActivityContext {
return self.subscribers.isEmpty return self.subscribers.isEmpty
} }
func notify(_ activities: [PeerActivitySpace: [PeerId: PeerInputActivityRecord]]) { func notify(_ activities: [PeerActivitySpace: [(PeerId, PeerInputActivityRecord)]]) {
for subscriber in self.subscribers.copyItems() { for subscriber in self.subscribers.copyItems() {
subscriber(activities) 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()) assert(self.queue.isCurrent())
var dict: [PeerActivitySpace: [PeerId: PeerInputActivityRecord]] = [:] var dict: [PeerActivitySpace: [(PeerId, PeerInputActivityRecord)]] = [:]
for (chatPeerId, context) in self.contexts { for (chatPeerId, context) in self.contexts {
var chatDict: [PeerId: PeerInputActivityRecord] = [:] dict[chatPeerId] = context.topActivities()
for (peerId, activity) in context.topActivities() {
chatDict[peerId] = activity
}
dict[chatPeerId] = chatDict
} }
return dict return dict
} }
func allActivities() -> Signal<[PeerActivitySpace: [PeerId: PeerInputActivityRecord]], NoError> { func allActivities() -> Signal<[PeerActivitySpace: [(PeerId, PeerInputActivityRecord)]], NoError> {
let queue = self.queue let queue = self.queue
return Signal { [weak self] subscriber in return Signal { [weak self] subscriber in
let disposable = MetaDisposable() let disposable = MetaDisposable()

View File

@ -623,7 +623,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
hintFileIsLarge = true hintFileIsLarge = true
break loop break loop
default: default:
break loop break
} }
} }

View File

@ -126,6 +126,22 @@ public func reportPeer(account: Account, peerId: PeerId, reason: ReportReason, m
} |> switchToLatest } |> 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> { public func reportPeerMessages(account: Account, messageIds: [MessageId], reason: ReportReason, message: String) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in return account.postbox.transaction { transaction -> Signal<Void, NoError> in
let groupedIds = messagesIdsGroupedByPeerId(messageIds) let groupedIds = messagesIdsGroupedByPeerId(messageIds)
@ -149,22 +165,6 @@ public func reportPeerMessages(account: Account, messageIds: [MessageId], reason
} |> switchToLatest } |> 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> { public func dismissPeerStatusOptions(account: Account, peerId: PeerId) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in return account.postbox.transaction { transaction -> Signal<Void, NoError> in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in

View File

@ -174,13 +174,32 @@ public enum MessageActionUrlAuthResult {
case request(String, Peer, Bool) case request(String, Peer, Bool)
} }
public func requestMessageActionUrlAuth(account: Account, messageId: MessageId, buttonId: Int32) -> Signal<MessageActionUrlAuthResult, NoError> { public enum MessageActionUrlSubject {
return account.postbox.loadedPeerWithId(messageId.peerId) 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) |> take(1)
|> mapToSignal { peer in |> castError(MTRpcError.self)
|> mapToSignal { peer -> Signal<Api.UrlAuthResult?, MTRpcError> in
if let inputPeer = apiInputPeer(peer) { if let inputPeer = apiInputPeer(peer) {
return account.network.request(Api.functions.messages.requestUrlAuth(peer: inputPeer, msgId: messageId.id, buttonId: buttonId)) return account.network.request(Api.functions.messages.requestUrlAuth(peer: inputPeer, msgId: messageId.id, buttonId: buttonId))
|> map(Optional.init) |> 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)
}
return request
|> `catch` { _ -> Signal<Api.UrlAuthResult?, NoError> in |> `catch` { _ -> Signal<Api.UrlAuthResult?, NoError> in
return .single(nil) return .single(nil)
} }
@ -197,23 +216,35 @@ public func requestMessageActionUrlAuth(account: Account, messageId: MessageId,
return .request(domain, TelegramUser(user: bot), (flags & (1 << 0)) != 0) return .request(domain, TelegramUser(user: bot), (flags & (1 << 0)) != 0)
} }
} }
} else {
return .single(.default)
}
}
} }
public func acceptMessageActionUrlAuth(account: Account, messageId: MessageId, buttonId: Int32, allowWriteAccess: Bool) -> Signal<MessageActionUrlAuthResult, NoError> { public func acceptMessageActionUrlAuth(account: Account, subject: MessageActionUrlSubject, 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 var flags: Int32 = 0
if allowWriteAccess { if allowWriteAccess {
flags |= Int32(1 << 0) 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)) return account.network.request(Api.functions.messages.acceptUrlAuth(flags: flags, peer: inputPeer, msgId: messageId.id, buttonId: buttonId))
|> map(Optional.init) |> map(Optional.init)
} else {
return .single(nil)
}
}
case let .url(url):
request = account.network.request(Api.functions.messages.acceptUrlAuth(flags: flags, peer: .inputPeerEmpty, msgId: 0, buttonId: 0))
|> map(Optional.init)
}
return request
|> `catch` { _ -> Signal<Api.UrlAuthResult?, NoError> in |> `catch` { _ -> Signal<Api.UrlAuthResult?, NoError> in
return .single(nil) return .single(nil)
} }
@ -228,8 +259,4 @@ public func acceptMessageActionUrlAuth(account: Account, messageId: MessageId, b
return .default return .default
} }
} }
} else {
return .single(.default)
}
}
} }

View File

@ -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 key: PresentationResourceKey
let color: UIColor let color: UIColor
switch type { switch type {
@ -227,7 +227,10 @@ public struct PresentationResourcesChatList {
color = theme.chat.serviceMessage.components.withDefaultWallpaper.scam color = theme.chat.serviceMessage.components.withDefaultWallpaper.scam
} }
return theme.image(key.rawValue, { theme in 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) let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds) context.clear(bounds)
@ -240,7 +243,6 @@ public struct PresentationResourcesChatList {
let titlePath = CGMutablePath() let titlePath = CGMutablePath()
titlePath.addRect(bounds.offsetBy(dx: 0.0, dy: -2.0 + UIScreenPixel)) 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 titleFramesetter = CTFramesetterCreateWithAttributedString(titleString as CFAttributedString)
let titleFrame = CTFramesetterCreateFrame(titleFramesetter, CFRangeMake(0, titleString.length), titlePath, nil) let titleFrame = CTFramesetterCreateFrame(titleFramesetter, CFRangeMake(0, titleString.length), titlePath, nil)
CTFrameDraw(titleFrame, context) 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 key: PresentationResourceKey
let color: UIColor let color: UIColor
switch type { switch type {
@ -263,7 +265,10 @@ public struct PresentationResourcesChatList {
color = theme.chat.serviceMessage.components.withDefaultWallpaper.scam color = theme.chat.serviceMessage.components.withDefaultWallpaper.scam
} }
return theme.image(key.rawValue, { theme in 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) let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds) context.clear(bounds)
@ -276,7 +281,6 @@ public struct PresentationResourcesChatList {
let titlePath = CGMutablePath() let titlePath = CGMutablePath()
titlePath.addRect(bounds.offsetBy(dx: 0.0, dy: -2.0 + UIScreenPixel)) 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 titleFramesetter = CTFramesetterCreateWithAttributedString(titleString as CFAttributedString)
let titleFrame = CTFramesetterCreateFrame(titleFramesetter, CFRangeMake(0, titleString.length), titlePath, nil) let titleFrame = CTFramesetterCreateFrame(titleFramesetter, CFRangeMake(0, titleString.length), titlePath, nil)
CTFrameDraw(titleFrame, context) CTFrameDraw(titleFrame, context)

View File

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

View File

@ -27,8 +27,8 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
super.init() super.init()
self.containerNode.addSubnode(self.avatarNode)
self.addSubnode(self.containerNode) self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.avatarNode)
self.containerNode.activated = { [weak self] gesture, _ in self.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self else { guard let strongSelf = self else {

View File

@ -209,7 +209,7 @@ final class ChatButtonKeyboardInputNode: ChatInputNode {
break break
case let .urlAuth(url, buttonId): case let .urlAuth(url, buttonId):
if let message = self.message { 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): case let .setupPoll(isQuiz):
self.controllerInteraction.openPollCreation(isQuiz) self.controllerInteraction.openPollCreation(isQuiz)

View File

@ -168,7 +168,7 @@ final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
case .join: case .join:
self.activityIndicator.isHidden = false self.activityIndicator.isHidden = false
self.activityIndicator.startAnimating() 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 |> afterDisposed { [weak self] in
Queue.mainQueue().async { Queue.mainQueue().async {
if let strongSelf = self { if let strongSelf = self {

View File

@ -306,6 +306,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private var automaticMediaDownloadSettings: MediaAutoDownloadSettings private var automaticMediaDownloadSettings: MediaAutoDownloadSettings
private var automaticMediaDownloadSettingsDisposable: Disposable? 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 stickerSettings: ChatInterfaceStickerSettings
private var stickerSettingsDisposable: Disposable? private var stickerSettingsDisposable: Disposable?
@ -1163,13 +1172,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
} }
}, requestMessageActionUrlAuth: { [weak self] defaultUrl, messageId, buttonId in }, requestMessageActionUrlAuth: { [weak self] defaultUrl, subject in
if let strongSelf = self { if let strongSelf = self {
guard strongSelf.presentationInterfaceState.subject != .scheduledMessages else { 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)) 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 return
} }
if let _ = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext { return $0.updatedTitlePanelContext {
if !$0.contains(where: { if !$0.contains(where: {
@ -1187,7 +1195,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return $0 return $0
} }
}) })
strongSelf.messageActionUrlAuthDisposable.set(((combineLatest(strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.context.account.peerId), requestMessageActionUrlAuth(account: strongSelf.context.account, messageId: messageId, buttonId: buttonId) |> afterDisposed { strongSelf.messageActionUrlAuthDisposable.set(((combineLatest(strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.context.account.peerId), requestMessageActionUrlAuth(account: strongSelf.context.account, subject: subject) |> afterDisposed {
Queue.mainQueue().async { Queue.mainQueue().async {
if let strongSelf = self { if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
@ -1236,7 +1244,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
}) })
strongSelf.messageActionUrlAuthDisposable.set(((acceptMessageActionUrlAuth(account: strongSelf.context.account, messageId: messageId, buttonId: buttonId, allowWriteAccess: allowWriteAccess) |> afterDisposed { strongSelf.messageActionUrlAuthDisposable.set(((acceptMessageActionUrlAuth(account: strongSelf.context.account, subject: subject, allowWriteAccess: allowWriteAccess) |> afterDisposed {
Queue.mainQueue().async { Queue.mainQueue().async {
if let strongSelf = self { if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
@ -1281,7 +1289,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
})) }))
} }
}
}, activateSwitchInline: { [weak self] peerId, inputString in }, activateSwitchInline: { [weak self] peerId, inputString in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -2578,7 +2585,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
self.chatTitleView?.longPressed = { [weak self] in 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 let chatInfoButtonItem: UIBarButtonItem
@ -3440,20 +3449,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
}) })
self.stickerSettingsDisposable = (context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]) self.stickerSettingsDisposable = combineLatest(queue: Queue.mainQueue(), context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]), self.disableStickerAnimationsPromise.get()).start(next: { [weak self] sharedData, disableStickerAnimations in
|> deliverOnMainQueue).start(next: { [weak self] sharedData in
var stickerSettings = StickerSettings.defaultSettings var stickerSettings = StickerSettings.defaultSettings
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings] as? StickerSettings { if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings] as? StickerSettings {
stickerSettings = value stickerSettings = value
} }
let chatStickerSettings = ChatInterfaceStickerSettings(stickerSettings: stickerSettings) let chatStickerSettings = ChatInterfaceStickerSettings(stickerSettings: stickerSettings)
if let strongSelf = self, strongSelf.stickerSettings != chatStickerSettings || strongSelf.disableStickerAnimationsValue != disableStickerAnimations {
if let strongSelf = self, strongSelf.stickerSettings != chatStickerSettings {
strongSelf.stickerSettings = chatStickerSettings strongSelf.stickerSettings = chatStickerSettings
strongSelf.disableStickerAnimationsValue = disableStickerAnimations
strongSelf.controllerInteraction?.stickerSettings = chatStickerSettings strongSelf.controllerInteraction?.stickerSettings = chatStickerSettings
if strongSelf.isNodeLoaded { 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) { override public func inFocusUpdated(isInFocus: Bool) {
self.disableStickerAnimationsPromise.set(!isInFocus)
self.chatDisplayNode.inFocusUpdated(isInFocus: isInFocus) self.chatDisplayNode.inFocusUpdated(isInFocus: isInFocus)
} }
@ -8497,6 +8506,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self { if let strongSelf = self {
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId 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 groupingKey: Int64?
var fileTypes: (music: Bool, other: Bool) = (false, false) var fileTypes: (music: Bool, other: Bool) = (false, false)
if results.count > 1 { if results.count > 1 {
@ -11079,6 +11095,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}, sendFile: nil, }, sendFile: nil,
sendSticker: { [weak self] f, sourceNode, sourceRect in sendSticker: { [weak self] f, sourceNode, sourceRect in
return self?.interfaceInteraction?.sendSticker(f, sourceNode, sourceRect) ?? false 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 }, present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a) self?.present(c, in: .window(.root), with: a)
}, dismissInput: { [weak self] in }, dismissInput: { [weak self] in

View File

@ -64,7 +64,7 @@ public final class ChatControllerInteraction {
let sendGif: (FileMediaReference, ASDisplayNode, CGRect) -> Bool let sendGif: (FileMediaReference, ASDisplayNode, CGRect) -> Bool
let sendBotContextResultAsGif: (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool let sendBotContextResultAsGif: (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool
let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool, Bool) -> Void let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool, Bool) -> Void
let requestMessageActionUrlAuth: (String, MessageId, Int32) -> Void let requestMessageActionUrlAuth: (String, MessageActionUrlSubject) -> Void
let activateSwitchInline: (PeerId?, String) -> Void let activateSwitchInline: (PeerId?, String) -> Void
let openUrl: (String, Bool, Bool?, Message?) -> Void let openUrl: (String, Bool, Bool?, Message?) -> Void
let shareCurrentLocation: () -> Void let shareCurrentLocation: () -> Void
@ -154,7 +154,7 @@ public final class ChatControllerInteraction {
sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool,
sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool,
requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool, Bool) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool, Bool) -> Void,
requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageActionUrlSubject) -> Void,
activateSwitchInline: @escaping (PeerId?, String) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void,
openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void,
shareCurrentLocation: @escaping () -> Void, shareCurrentLocation: @escaping () -> Void,
@ -298,7 +298,7 @@ public final class ChatControllerInteraction {
static var `default`: ChatControllerInteraction { static var `default`: ChatControllerInteraction {
return ChatControllerInteraction(openMessage: { _, _ in 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: { }, presentController: { _, _ in }, navigationController: {
return nil return nil
}, chatControllerNode: { }, chatControllerNode: {

View File

@ -964,6 +964,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
} }
if let inputMediaNode = inputNode as? ChatMediaInputNode, self.inputMediaNode == nil { if let inputMediaNode = inputNode as? ChatMediaInputNode, self.inputMediaNode == nil {
self.inputMediaNode = inputMediaNode self.inputMediaNode = inputMediaNode
inputMediaNode.requestDisableStickerAnimations = { [weak self] disabled in
self?.controller?.disableStickerAnimations = disabled
}
} }
if self.inputNode != inputNode { if self.inputNode != inputNode {
dismissedInputNode = self.inputNode dismissedInputNode = self.inputNode
@ -2086,10 +2089,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.historyNode.prefetchManager.updateAutoDownloadSettings(settings) self.historyNode.prefetchManager.updateAutoDownloadSettings(settings)
} }
func updateStickerSettings(_ settings: ChatInterfaceStickerSettings) { func updateStickerSettings(_ settings: ChatInterfaceStickerSettings, forceStopAnimations: Bool) {
self.historyNode.forEachItemNode { itemNode in self.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView { if let itemNode = itemNode as? ChatMessageItemView {
itemNode.updateStickerSettings() itemNode.updateStickerSettings(forceStopAnimations: forceStopAnimations)
} }
} }
} }
@ -2170,6 +2173,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
} }
}) })
inputNode.interfaceInteraction = interfaceInteraction inputNode.interfaceInteraction = interfaceInteraction
inputNode.requestDisableStickerAnimations = { [weak self] disabled in
self?.controller?.disableStickerAnimations = disabled
}
self.inputMediaNode = inputNode self.inputMediaNode = inputNode
if let (validLayout, _) = self.validLayout { 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) 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)

View File

@ -810,6 +810,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}))) })))
} }
var isUnremovableAction = false
if messages.count == 1 { if messages.count == 1 {
let message = messages[0] let message = messages[0]
@ -826,6 +827,14 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
if !hasAutoremove { if !hasAutoremove {
for media in message.media { 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 let file = media as? TelegramMediaFile {
if file.isVideo { if file.isVideo {
if file.isAnimated { if file.isAnimated {
@ -937,7 +946,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
interfaceInteraction.deleteMessages(selectAll ? messages : [message], controller, f) interfaceInteraction.deleteMessages(selectAll ? messages : [message], controller, f)
} }
}), false)) }), false))
} else { } else if !isUnremovableAction {
actions.append(.action(ContextMenuActionItem(text: title, textColor: .destructive, icon: { theme in 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) return generateTintedImage(image: UIImage(bundleImageName: isSending ? "Chat/Context Menu/Clear" : "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { controller, f in }, action: { controller, f in

View File

@ -448,6 +448,8 @@ final class ChatMediaInputNode: ChatInputNode {
private var currentView: ItemCollectionsView? private var currentView: ItemCollectionsView?
private let dismissedPeerSpecificStickerPack = Promise<Bool>() 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 validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, ChatPresentationInterfaceState, DeviceMetrics, Bool)?
private var paneArrangement: ChatMediaInputPaneArrangement private var paneArrangement: ChatMediaInputPaneArrangement
private var initializedArrangement = false private var initializedArrangement = false
@ -1242,6 +1244,10 @@ final class ChatMediaInputNode: ChatInputNode {
let controller = PeekController(theme: PeekControllerTheme(presentationTheme: strongSelf.theme), content: content, sourceNode: { let controller = PeekController(theme: PeekControllerTheme(presentationTheme: strongSelf.theme), content: content, sourceNode: {
return sourceNode return sourceNode
}) })
controller.visibilityUpdated = { [weak self] visible in
self?.requestDisableStickerAnimations?(visible)
self?.simulateUpdateLayout(isVisible: !visible)
}
strongSelf.controllerInteraction.presentGlobalOverlayController(controller, nil) strongSelf.controllerInteraction.presentGlobalOverlayController(controller, nil)
return controller return controller
} }

View File

@ -178,6 +178,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
private var highlightedState: Bool = false private var highlightedState: Bool = false
private var forceStopAnimations = false
private var haptic: EmojiHaptic? private var haptic: EmojiHaptic?
private var mediaPlayer: MediaPlayer? private var mediaPlayer: MediaPlayer?
private let mediaStatusDisposable = MetaDisposable() private let mediaStatusDisposable = MetaDisposable()
@ -489,7 +491,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
} }
if let animationNode = self.animationNode as? AnimatedStickerNode { if let animationNode = self.animationNode as? AnimatedStickerNode {
let isPlaying = self.visibilityStatus let isPlaying = self.visibilityStatus && !self.forceStopAnimations
if self.isPlaying != isPlaying { if self.isPlaying != isPlaying {
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() self.updateVisibility()
} }

View File

@ -1442,9 +1442,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
if case let .peer(peerId) = item.chatLocation, let authorPeerId = item.message.author?.id, authorPeerId == peerId { if case let .peer(peerId) = item.chatLocation, let authorPeerId = item.message.author?.id, authorPeerId == peerId {
} else if effectiveAuthor.isScam { } 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 { } 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)
} }
} }

View File

@ -196,16 +196,16 @@ class ChatMessageForwardInfoNode: ASDisplayNode {
if peer.isFake { if peer.isFake {
switch type { switch type {
case let .bubble(incoming): 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: 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 { } else if peer.isScam {
switch type { switch type {
case let .bubble(incoming): 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: case .standalone:
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(presentationData.theme.theme, type: .service) currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(presentationData.theme.theme, strings: presentationData.strings, type: .service)
} }
} else { } else {
currentCredibilityIconImage = nil currentCredibilityIconImage = nil

View File

@ -758,7 +758,7 @@ public class ChatMessageItemView: ListViewItemNode {
func updateAutomaticMediaDownloadSettings() { func updateAutomaticMediaDownloadSettings() {
} }
func updateStickerSettings() { func updateStickerSettings(forceStopAnimations: Bool) {
} }
func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? { func playMediaWithSound() -> ((Double?) -> Void, Bool, Bool, Bool, ASDisplayNode?)? {
@ -814,7 +814,7 @@ public class ChatMessageItemView: ListViewItemNode {
case .payment: case .payment:
item.controllerInteraction.openCheckoutOrReceipt(item.message.id) item.controllerInteraction.openCheckoutOrReceipt(item.message.id)
case let .urlAuth(url, buttonId): 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: case .setupPoll:
break break
} }

View File

@ -261,7 +261,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
self?.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame) self?.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame)
}, openMessageContextActions: { _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in
}, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ 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) self?.openUrl(url)
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in
if let strongSelf = self, let navigationController = strongSelf.getNavigationController() { if let strongSelf = self, let navigationController = strongSelf.getNavigationController() {
@ -852,6 +852,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
self?.view.endEditing(true) self?.view.endEditing(true)
}) })
} }
case .urlAuth:
break
case let .peer(peerId, _): case let .peer(peerId, _):
if let peerId = peerId { if let peerId = peerId {
strongSelf.openPeer(peerId: peerId, peer: nil) strongSelf.openPeer(peerId: peerId, peer: nil)
@ -890,6 +892,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
} }
}, sendFile: nil, }, sendFile: nil,
sendSticker: nil, sendSticker: nil,
requestMessageActionUrlAuth: nil,
present: { c, a in present: { c, a in
self?.presentController(c, a) self?.presentController(c, a)
}, dismissInput: { }, dismissInput: {

View File

@ -240,13 +240,13 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
if titleFakeIcon != self.titleFakeIcon { if titleFakeIcon != self.titleFakeIcon {
self.titleFakeIcon = 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 updated = true
} }
if titleScamIcon != self.titleScamIcon { if titleScamIcon != self.titleScamIcon {
self.titleScamIcon = 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 updated = true
} }

View File

@ -166,12 +166,18 @@ public class ComposeControllerImpl: ViewController, ComposeController {
controller.displayNavigationActivity = false controller.displayNavigationActivity = false
(controller.navigationController as? NavigationController)?.replaceAllButRootController(ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(peerId)), animated: true) (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 { if let strongSelf = self, let controller = controller {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
controller.displayNavigationActivity = false 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))
} }
})) }))
} }

View File

@ -110,7 +110,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
self.controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in self.controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ 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: { }, presentController: { _, _ in }, navigationController: {
return nil return nil
}, chatControllerNode: { }, chatControllerNode: {

View File

@ -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 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
switch resolvedUrl { switch resolvedUrl {
case let .externalUrl(url): 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) 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): case let .peer(peerId, navigation):
if let peerId = peerId { if let peerId = peerId {
openPeer(peerId, defaultNavigationForPeerId(peerId, navigation: navigation)) openPeer(peerId, defaultNavigationForPeerId(peerId, navigation: navigation))

View File

@ -213,6 +213,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
} }
}, sendFile: nil, }, sendFile: nil,
sendSticker: nil, sendSticker: nil,
requestMessageActionUrlAuth: nil,
present: { c, a in present: { c, a in
context.sharedContext.applicationBindings.dismissNativeController() context.sharedContext.applicationBindings.dismissNativeController()

View File

@ -84,7 +84,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}, sendBotContextResultAsGif: { _, _, _, _ in }, sendBotContextResultAsGif: { _, _, _, _ in
return false return false
}, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionCallback: { _, _, _, _ in
}, requestMessageActionUrlAuth: { _, _, _ in }, requestMessageActionUrlAuth: { _, _ in
}, activateSwitchInline: { _, _ in }, activateSwitchInline: { _, _ in
}, openUrl: { _, _, _, _ in }, openUrl: { _, _, _, _ in
}, shareCurrentLocation: { }, shareCurrentLocation: {

View File

@ -984,7 +984,7 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
if isOpenedFromChat { if isOpenedFromChat {
result.append(.search) result.append(.search)
} }
if isSecretChat && !isContact { if (isSecretChat && !isContact) || user.flags.contains(.isSupport) {
} else { } else {
result.append(.more) result.append(.more)
} }
@ -1043,7 +1043,7 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
if channel.isVerified || channel.adminRights != nil || channel.flags.contains(.isCreator) { if channel.isVerified || channel.adminRights != nil || channel.flags.contains(.isCreator) {
canReport = false canReport = false
} }
if !canReport && !canViewStats && displayLeave { if !canReport && !canViewStats {
displayMore = false displayMore = false
} }
if displayMore { if displayMore {

View File

@ -1255,8 +1255,10 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
let context: AccountContext let context: AccountContext
let avatarNode: AvatarNode
private let containerNode: ContextControllerSourceNode
let avatarNode: AvatarNode
fileprivate var videoNode: UniversalVideoNode? fileprivate var videoNode: UniversalVideoNode?
private var videoContent: NativeVideoContent? private var videoContent: NativeVideoContent?
private var videoStartTimestamp: Double? private var videoStartTimestamp: Double?
@ -1271,6 +1273,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
} }
var tapped: (() -> Void)? var tapped: (() -> Void)?
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
private var isFirstAvatarLoading = true private var isFirstAvatarLoading = true
var item: PeerInfoAvatarListItem? var item: PeerInfoAvatarListItem?
@ -1279,15 +1282,29 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
init(context: AccountContext) { init(context: AccountContext) {
self.context = context self.context = context
self.containerNode = ContextControllerSourceNode()
let avatarFont = avatarPlaceholderFont(size: floor(100.0 * 16.0 / 37.0)) let avatarFont = avatarPlaceholderFont(size: floor(100.0 * 16.0 / 37.0))
self.avatarNode = AvatarNode(font: avatarFont) self.avatarNode = AvatarNode(font: avatarFont)
super.init() super.init()
self.addSubnode(self.avatarNode) self.addSubnode(self.containerNode)
self.avatarNode.frame = CGRect(origin: CGPoint(x: -50.0, y: -50.0), size: CGSize(width: 100.0, height: 100.0)) 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 { 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.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.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)) self.avatarNode.font = avatarPlaceholderFont(size: floor(avatarSize * 16.0 / 37.0))
if let item = item { 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) { 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 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) 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 shape.path = maskPath.cgPath
videoNode.layer.mask = shape videoNode.layer.mask = shape
self.addSubnode(videoNode) self.containerNode.addSubnode(videoNode)
} }
} else if let videoNode = self.videoNode { } else if let videoNode = self.videoNode {
self.videoContent = nil self.videoContent = nil
@ -1424,6 +1444,8 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
self.videoNode = nil self.videoNode = nil
videoNode.removeFromSupernode() videoNode.removeFromSupernode()
self.containerNode.isGestureEnabled = false
} }
if let videoNode = self.videoNode { if let videoNode = self.videoNode {
@ -2568,6 +2590,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
var cancelUpload: (() -> Void)? var cancelUpload: (() -> Void)?
var requestUpdateLayout: (() -> Void)? var requestUpdateLayout: (() -> Void)?
var displayAvatarContextMenu: ((ASDisplayNode, ContextGesture?) -> Void)?
var displayCopyContextMenu: ((ASDisplayNode, Bool, Bool) -> Void)? var displayCopyContextMenu: ((ASDisplayNode, Bool, Bool) -> Void)?
var navigationTransition: PeerInfoHeaderNavigationTransition? var navigationTransition: PeerInfoHeaderNavigationTransition?
@ -2663,6 +2686,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.avatarListNode.avatarContainerNode.tapped = { [weak self] in self.avatarListNode.avatarContainerNode.tapped = { [weak self] in
self?.initiateAvatarExpansion(gallery: false, first: false) 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.editingContentNode.avatarNode.tapped = { [weak self] confirm in
self?.initiateAvatarExpansion(gallery: true, first: true) self?.initiateAvatarExpansion(gallery: true, first: true)
} }
@ -2783,9 +2810,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
if let peer = peer { if let peer = peer {
self.initializedCredibilityIcon = true self.initializedCredibilityIcon = true
if peer.isFake { if peer.isFake {
image = PresentationResourcesChatList.fakeIcon(presentationData.theme, type: .regular) image = PresentationResourcesChatList.fakeIcon(presentationData.theme, strings: presentationData.strings, type: .regular)
} else if peer.isScam { } 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 { } else if peer.isVerified {
if let sourceImage = UIImage(bundleImageName: "Peer Info/VerifiedIcon") { if let sourceImage = UIImage(bundleImageName: "Peer Info/VerifiedIcon") {
image = generateImage(sourceImage.size, contextGenerator: { size, context in image = generateImage(sourceImage.size, contextGenerator: { size, context in

View File

@ -563,6 +563,7 @@ private final class PeerInfoInteraction {
let logoutAccount: (AccountRecordId) -> Void let logoutAccount: (AccountRecordId) -> Void
let accountContextMenu: (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void let accountContextMenu: (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void
let updateBio: (String) -> Void let updateBio: (String) -> Void
let openDeletePeer: () -> Void
init( init(
openUsername: @escaping (String) -> Void, openUsername: @escaping (String) -> Void,
@ -599,7 +600,8 @@ private final class PeerInfoInteraction {
switchToAccount: @escaping (AccountRecordId) -> Void, switchToAccount: @escaping (AccountRecordId) -> Void,
logoutAccount: @escaping (AccountRecordId) -> Void, logoutAccount: @escaping (AccountRecordId) -> Void,
accountContextMenu: @escaping (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void, accountContextMenu: @escaping (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void,
updateBio: @escaping (String) -> Void updateBio: @escaping (String) -> Void,
openDeletePeer: @escaping () -> Void
) { ) {
self.openUsername = openUsername self.openUsername = openUsername
self.openPhone = openPhone self.openPhone = openPhone
@ -636,6 +638,7 @@ private final class PeerInfoInteraction {
self.logoutAccount = logoutAccount self.logoutAccount = logoutAccount
self.accountContextMenu = accountContextMenu self.accountContextMenu = accountContextMenu
self.updateBio = updateBio self.updateBio = updateBio
self.openDeletePeer = openDeletePeer
} }
} }
@ -1140,6 +1143,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
case groupLocation case groupLocation
case peerPublicSettings case peerPublicSettings
case peerSettings case peerSettings
case peerActions
} }
var items: [Section: [PeerInfoScreenItem]] = [:] var items: [Section: [PeerInfoScreenItem]] = [:]
@ -1147,33 +1151,6 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
items[section] = [] 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 = data {
if let _ = data.peer as? TelegramUser { if let _ = data.peer as? TelegramUser {
let ItemDelete = 0 let ItemDelete = 0
@ -1194,8 +1171,8 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
if channel.flags.contains(.isCreator) { if channel.flags.contains(.isCreator) {
let linkText: String let linkText: String
if let username = channel.username { if let _ = channel.username {
linkText = "@\(username)" linkText = presentationData.strings.Channel_Setup_TypePublic
} else { } else {
linkText = presentationData.strings.Channel_Setup_TypePrivate 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 let invitesText: String
if let count = data.invitations?.count, count > 0 { if let count = data.invitations?.count, count > 0 {
invitesText = "\(count)" invitesText = "\(count)"
@ -1256,14 +1233,15 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
let ItemLinkedChannel = 103 let ItemLinkedChannel = 103
let ItemPreHistory = 104 let ItemPreHistory = 104
let ItemStickerPack = 105 let ItemStickerPack = 105
let ItemPermissions = 106 let ItemMembers = 106
let ItemMembers = 107 let ItemPermissions = 107
let ItemAdmins = 108 let ItemAdmins = 108
let ItemRemovedUsers = 109 let ItemRemovedUsers = 109
let ItemLocationHeader = 110 let ItemLocationHeader = 110
let ItemLocation = 111 let ItemLocation = 111
let ItemLocationSetup = 112 let ItemLocationSetup = 112
let ItemAutoremove = 113 let ItemAutoremove = 113
let ItemDeleteGroup = 114
let isCreator = channel.flags.contains(.isCreator) let isCreator = channel.flags.contains(.isCreator)
let isPublic = channel.username != nil let isPublic = channel.username != nil
@ -1369,25 +1347,28 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
activePermissionCount = count 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) { 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: { 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() 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: { 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) 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: { 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) 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 updateBio: { [weak self] bio in
self?.updateBio(bio) self?.updateBio(bio)
},
openDeletePeer: { [weak self] in
self?.openDeletePeer()
} }
) )
@ -1987,7 +1971,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}, sendBotContextResultAsGif: { _, _, _, _ in }, sendBotContextResultAsGif: { _, _, _, _ in
return false return false
}, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionCallback: { _, _, _, _ in
}, requestMessageActionUrlAuth: { _, _, _ in }, requestMessageActionUrlAuth: { _, _ in
}, activateSwitchInline: { _, _ in }, activateSwitchInline: { _, _ in
}, openUrl: { [weak self] url, concealed, external, _ in }, openUrl: { [weak self] url, concealed, external, _ in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -2726,6 +2710,29 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
} }
} else { } else {
screenData = peerInfoScreenData(context: context, peerId: peerId, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, isSettings: self.isSettings, ignoreGroupInCommon: ignoreGroupInCommon) 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 self.headerNode.avatarListNode.listContainerNode.currentIndexUpdated = { [weak self] in
@ -3024,10 +3031,10 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}, sendFile: nil, }, sendFile: nil,
sendSticker: { [weak self] f, sourceNode, sourceRect in sendSticker: { [weak self] f, sourceNode, sourceRect in
return false 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) self?.controller?.present(c, in: .window(.root), with: a)
}, dismissInput: { [weak self] in }, dismissInput: { [weak self] in
self?.view.endEditing(true)
}, contentContext: nil) }, contentContext: nil)
} }
@ -3045,6 +3052,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self?.openPeer(peerId: peerId, navigation: navigation) self?.openPeer(peerId: peerId, navigation: navigation)
}, sendFile: nil, }, sendFile: nil,
sendSticker: nil, sendSticker: nil,
requestMessageActionUrlAuth: nil,
present: { c, a in present: { c, a in
self?.controller?.present(c, in: .window(.root), with: a) self?.controller?.present(c, in: .window(.root), with: a)
}, dismissInput: { }, dismissInput: {
@ -3645,11 +3653,18 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
if let navigationController = (strongSelf.controller?.navigationController as? NavigationController) { if let navigationController = (strongSelf.controller?.navigationController as? NavigationController) {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId))) strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId)))
} }
}, error: { _ in }, error: { error in
guard let strongSelf = self else { guard let strongSelf = self else {
return 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)) })]), 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 context.sharedContext.openResolvedUrl(.groupBotStart(peerId: peerId, payload: ""), context: self.context, urlContext: .generic, navigationController: controller.navigationController as? NavigationController, openPeer: { id, navigation in
}, sendFile: nil, }, sendFile: nil,
sendSticker: nil, sendSticker: nil,
requestMessageActionUrlAuth: nil,
present: { [weak controller] c, a in present: { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: a) controller?.present(c, in: .window(.root), with: a)
}, dismissInput: { [weak controller] in }, dismissInput: { [weak controller] in
@ -4921,7 +4937,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
resolvedUrl = .instantView(webPage, customAnchor) resolvedUrl = .instantView(webPage, customAnchor)
} }
strongSelf.context.sharedContext.openResolvedUrl(resolvedUrl, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.controller?.navigationController as? NavigationController, openPeer: { peer, navigation in 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) self?.controller?.push(controller)
}, dismissInput: {}, contentContext: nil) }, dismissInput: {}, contentContext: nil)
} }
@ -6564,7 +6580,13 @@ func presentAddMembers(context: AccountContext, parentController: ViewController
case .notMutualContact: case .notMutualContact:
let _ = (context.account.postbox.loadedPeerWithId(memberId) let _ = (context.account.postbox.loadedPeerWithId(memberId)
|> deliverOnMainQueue).start(next: { peer in |> 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() return .complete()
case .tooManyChannels: case .tooManyChannels:
@ -6683,7 +6705,14 @@ func presentAddMembers(context: AccountContext, parentController: ViewController
break break
} }
} else if peers.count == 1, case .notMutualContact = error { } 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 { } 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)) 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))
} }

View File

@ -744,7 +744,7 @@ public class ShareRootControllerImpl {
errorText = presentationData.strings.ChatImportActivity_ErrorUserBlocked errorText = presentationData.strings.ChatImportActivity_ErrorUserBlocked
case .limitExceeded: case .limitExceeded:
errorText = presentationData.strings.ChatImportActivity_ErrorLimitExceeded errorText = presentationData.strings.ChatImportActivity_ErrorLimitExceeded
case .userIsNotMutualContact: case .notMutualContact:
errorText = presentationData.strings.ChatImport_UserErrorNotMutual errorText = presentationData.strings.ChatImport_UserErrorNotMutual
} }
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
@ -868,7 +868,7 @@ public class ShareRootControllerImpl {
errorText = presentationData.strings.ChatImportActivity_ErrorUserBlocked errorText = presentationData.strings.ChatImportActivity_ErrorUserBlocked
case .limitExceeded: case .limitExceeded:
errorText = presentationData.strings.ChatImportActivity_ErrorLimitExceeded errorText = presentationData.strings.ChatImportActivity_ErrorLimitExceeded
case .userIsNotMutualContact: case .notMutualContact:
errorText = presentationData.strings.ChatImport_UserErrorNotMutual errorText = presentationData.strings.ChatImport_UserErrorNotMutual
} }
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
@ -989,7 +989,7 @@ public class ShareRootControllerImpl {
errorText = presentationData.strings.ChatImportActivity_ErrorUserBlocked errorText = presentationData.strings.ChatImportActivity_ErrorUserBlocked
case .limitExceeded: case .limitExceeded:
errorText = presentationData.strings.ChatImportActivity_ErrorLimitExceeded errorText = presentationData.strings.ChatImportActivity_ErrorLimitExceeded
case .userIsNotMutualContact: case .notMutualContact:
errorText = presentationData.strings.ChatImport_UserErrorNotMutual errorText = presentationData.strings.ChatImport_UserErrorNotMutual
} }
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {

View File

@ -1142,8 +1142,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return resolveUrlImpl(account: account, url: url) return resolveUrlImpl(account: account, url: url)
} }
public 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?) { public 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?) {
openResolvedUrlImpl(resolvedUrl, context: context, urlContext: urlContext, navigationController: navigationController, openPeer: openPeer, sendFile: sendFile, sendSticker: sendSticker, present: present, dismissInput: dismissInput, contentContext: contentContext) openResolvedUrlImpl(resolvedUrl, context: context, urlContext: urlContext, navigationController: navigationController, openPeer: openPeer, sendFile: sendFile, sendSticker: sendSticker, requestMessageActionUrlAuth: requestMessageActionUrlAuth, present: present, dismissInput: dismissInput, contentContext: contentContext)
} }
public func makeDeviceContactInfoController(context: AccountContext, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController { public func makeDeviceContactInfoController(context: AccountContext, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController {
@ -1221,7 +1221,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
clickThroughMessage?() clickThroughMessage?()
}, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _ in
return false 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 }, 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: { }, presentController: { _, _ in }, navigationController: {
return nil return nil
}, chatControllerNode: { }, chatControllerNode: {

View File

@ -49,7 +49,7 @@ func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Message, da
if let author = message.author as? TelegramUser { if let author = message.author as? TelegramUser {
if let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { if let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
authorTitle = author.displayTitle(strings: strings, displayOrder: nameDisplayOrder) authorTitle = author.displayTitle(strings: strings, displayOrder: nameDisplayOrder)
} else if let forwardInfo = message.forwardInfo { } else if let forwardInfo = message.forwardInfo, forwardInfo.sourceMessageId?.peerId.namespace == Namespaces.Peer.CloudChannel {
authorTitle = forwardInfo.authorSignature authorTitle = forwardInfo.authorSignature
} }
} else { } else {

View File

@ -44,6 +44,7 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate
} }
}, sendFile: nil, }, sendFile: nil,
sendSticker: nil, sendSticker: nil,
requestMessageActionUrlAuth: nil,
present: presentImpl, dismissInput: {}, contentContext: nil) present: presentImpl, dismissInput: {}, contentContext: nil)
} }

View File

@ -418,8 +418,8 @@ public final class PeerChannelMemberCategoriesContextsManager {
} }
} }
public func join(account: Account, peerId: PeerId) -> Signal<Never, JoinChannelError> { public func join(account: Account, peerId: PeerId, hash: String?) -> Signal<Never, JoinChannelError> {
return joinChannel(account: account, peerId: peerId) return joinChannel(account: account, peerId: peerId, hash: hash)
|> deliverOnMainQueue |> deliverOnMainQueue
|> beforeNext { [weak self] result in |> beforeNext { [weak self] result in
if let strongSelf = self, let updated = result { if let strongSelf = self, let updated = result {

View File

@ -494,20 +494,22 @@ public func parseWallpaperUrl(_ url: String) -> WallpaperUrlParameter? {
private struct UrlHandlingConfiguration { private struct UrlHandlingConfiguration {
static var defaultValue: UrlHandlingConfiguration { static var defaultValue: UrlHandlingConfiguration {
return UrlHandlingConfiguration(token: nil, domains: []) return UrlHandlingConfiguration(token: nil, domains: [], urlAuthDomains: [])
} }
public let token: String? public let token: String?
public let domains: [String] public let domains: [String]
public let urlAuthDomains: [String]
fileprivate init(token: String?, domains: [String]) { fileprivate init(token: String?, domains: [String], urlAuthDomains: [String]) {
self.token = token self.token = token
self.domains = domains self.domains = domains
self.urlAuthDomains = urlAuthDomains
} }
static func with(appConfiguration: AppConfiguration) -> UrlHandlingConfiguration { static func with(appConfiguration: AppConfiguration) -> UrlHandlingConfiguration {
if let data = appConfiguration.data, let token = data["autologin_token"] as? String, let domains = data["autologin_domains"] as? [String] { if let data = appConfiguration.data, let token = data["autologin_token"] as? String, let domains = data["autologin_domains"] as? [String] {
return UrlHandlingConfiguration(token: token, domains: domains) return UrlHandlingConfiguration(token: token, domains: domains, urlAuthDomains: [])
} else { } else {
return .defaultValue return .defaultValue
} }
@ -522,15 +524,21 @@ public func resolveUrlImpl(account: Account, url: String) -> Signal<ResolvedUrl,
let urlHandlingConfiguration = UrlHandlingConfiguration.with(appConfiguration: appConfiguration) let urlHandlingConfiguration = UrlHandlingConfiguration.with(appConfiguration: appConfiguration)
var url = url var url = url
if !url.contains("://") && !url.hasPrefix("tel:") && !url.hasPrefix("mailto:") && !url.hasPrefix("calshow:") {
if !(url.hasPrefix("http") || url.hasPrefix("https")) { if !(url.hasPrefix("http") || url.hasPrefix("https")) {
url = "http://\(url)" url = "http://\(url)"
} }
if let urlValue = URL(string: url), let host = urlValue.host, urlHandlingConfiguration.domains.contains(host.lowercased()), var components = URLComponents(string: url) { }
if let urlValue = URL(string: url), let host = urlValue.host?.lowercased() {
if urlHandlingConfiguration.domains.contains(host), var components = URLComponents(string: url) {
components.scheme = "https" components.scheme = "https"
var queryItems = components.queryItems ?? [] var queryItems = components.queryItems ?? []
queryItems.append(URLQueryItem(name: "autologin_token", value: urlHandlingConfiguration.token)) queryItems.append(URLQueryItem(name: "autologin_token", value: urlHandlingConfiguration.token))
components.queryItems = queryItems components.queryItems = queryItems
url = components.url?.absoluteString ?? url url = components.url?.absoluteString ?? url
} else if urlHandlingConfiguration.urlAuthDomains.contains(host) {
return .single(.urlAuth(url))
}
} }
for basePath in baseTelegramMePaths { for basePath in baseTelegramMePaths {

View File

@ -1,5 +1,5 @@
{ {
"app": "7.5", "app": "7.5.1",
"bazel": "4.0.0", "bazel": "4.0.0",
"xcode": "12.4" "xcode": "12.4"
} }