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