Merge commit '0f66d9847c2ce9c38eea19f4c9c552a0adef4a73' into beta

This commit is contained in:
Ali 2021-02-26 19:35:47 +04:00
commit 4ee5f8487a
108 changed files with 5641 additions and 5076 deletions

View File

@ -6149,3 +6149,12 @@ Sorry for the inconvenience.";
"Channel.Setup.LinkTypePrivate" = "Private";
"VoiceOver.ScrollStatus" = "Row %1$@ of %2$@";
"Conversation.UsersTooMuchError" = "Sorry, this group is full.";
"Conversation.UploadFileTooLarge" = "File could not be sent, because it is larger than 2 GB.\n\nYou can send as many files as you like, but each must be smaller than 2 GB.";
"Channel.AddUserLeftError" = "Sorry, if a person is no longer part of a channel, you need to be in their Telegram contacts in order to add them back.\n\nNote that they can still join via the channel's invite link as long as they are not in the Removed Users list.";
"Message.ScamAccount" = "Scam";
"Message.FakeAccount" = "Fake";

View File

@ -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)

View File

@ -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 }

View File

@ -527,6 +527,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
self.titleView.theme = self.presentationData.theme
self.titleView.strings = self.presentationData.strings
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
@ -2348,7 +2349,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
if chatPeer is TelegramSecretChat {
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.ChatList_DeleteForEveryone(mainPeer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).0, color: .destructive, action: { [weak actionSheet] in
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.ChatList_DeleteForEveryone(mainPeer.compactDisplayTitle).0, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
guard let strongSelf = self else {
return

View File

@ -176,7 +176,7 @@ private final class ChatListShimmerNode: ASDisplayNode {
func update(context: AccountContext, size: CGSize, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
if self.currentParams?.size != size || self.currentParams?.presentationData !== presentationData {
self.currentParams = (size, presentationData)
let chatListPresentationData = ChatListPresentationData(theme: presentationData.theme, fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true)
let peer1 = TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: 1), accessHash: nil, firstName: "FirstName", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [])
@ -368,6 +368,10 @@ private final class ChatListContainerItemNode: ASDisplayNode {
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
self.listNode.accessibilityPageScrolledString = { row, count in
return presentationData.strings.VoiceOver_ScrollStatus(row, count).0
}
self.listNode.updateThemeAndStrings(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations)
self.emptyNode?.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings)

View File

@ -398,7 +398,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
arguments.deleteIncludePeer(id)
})
case let .excludePeer(_, peer, isRevealed):
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer.chatMainPeer!, height: .peerList, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: isRevealed), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: {
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer.chatMainPeer!, height: .peerList, aliasHandling: .threatSelfAsSaved, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: isRevealed), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: {
arguments.deleteExcludePeer(peer.peerId)
})]), switchValue: nil, enabled: true, selectable: false, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in
arguments.setItemIdWithRevealedOptions(lhs.flatMap { .peer($0) }, rhs.flatMap { .peer($0) })

View File

@ -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()

View File

@ -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 {

View File

@ -68,7 +68,11 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl
}
}
var strings: PresentationStrings
var strings: PresentationStrings {
didSet {
self.proxyButton.accessibilityLabel = self.strings.VoiceOver_Navigation_ProxySettings
}
}
init(theme: PresentationTheme, strings: PresentationStrings) {
self.theme = theme

View File

@ -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)

View File

@ -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)
})))
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
})

View File

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

View File

@ -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 {

View File

@ -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 {

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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 {

View File

@ -128,22 +128,27 @@ public final class JoinLinkPreviewController: ViewController {
}
}, error: { [weak self] error in
if let strongSelf = self {
if case .tooMuchJoined = error {
if let parentNavigationController = strongSelf.parentNavigationController {
let context = strongSelf.context
let link = strongSelf.link
let navigateToPeer = strongSelf.navigateToPeer
let resolvedState = strongSelf.resolvedState
parentNavigationController.pushViewController(oldChannelsController(context: strongSelf.context, intent: .join, completed: { [weak parentNavigationController] value in
if value {
(parentNavigationController?.viewControllers.last as? ViewController)?.present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: navigateToPeer, parentNavigationController: parentNavigationController, resolvedState: resolvedState), in: .window(.root))
}
}))
} else {
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Join_ChannelsTooMuch, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
strongSelf.dismiss()
switch error {
case .tooMuchJoined:
if let parentNavigationController = strongSelf.parentNavigationController {
let context = strongSelf.context
let link = strongSelf.link
let navigateToPeer = strongSelf.navigateToPeer
let resolvedState = strongSelf.resolvedState
parentNavigationController.pushViewController(oldChannelsController(context: strongSelf.context, intent: .join, completed: { [weak parentNavigationController] value in
if value {
(parentNavigationController?.viewControllers.last as? ViewController)?.present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: navigateToPeer, parentNavigationController: parentNavigationController, resolvedState: resolvedState), in: .window(.root))
}
}))
} else {
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Join_ChannelsTooMuch, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
case .tooMuchUsers:
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Conversation_UsersTooMuchError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
case .generic:
break
}
strongSelf.dismiss()
}
}))
}

View File

@ -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

View File

@ -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;

View File

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

View File

@ -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;
}
}

View File

@ -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;

View File

@ -40,6 +40,13 @@
self = [super init];
if (self != nil)
{
if (![url.absoluteString.lowercaseString hasSuffix:@".mp4"]) {
NSURL *pathUrl = [NSURL fileURLWithPath:[[[LegacyComponentsGlobals provider] dataStoragePath] stringByAppendingPathComponent:@"videos"]];
[[NSFileManager defaultManager] createDirectoryAtPath:pathUrl.path withIntermediateDirectories:true attributes:nil error:nil];
NSURL *updatedUrl = [pathUrl URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.mp4", [TGStringUtils md5:url.absoluteString]]];
[[NSFileManager defaultManager] createSymbolicLinkAtPath:updatedUrl.path withDestinationPath:url.path error:nil];
url = updatedUrl;
}
_cachedAVAsset = [[AVURLAsset alloc] initWithURL:url options:nil];
_cachedSize = CGSizeZero;
_cachedDuration = 0.0;

View File

@ -1344,8 +1344,6 @@
dict[@"stickers"] = adjustments.paintingData.stickers;
if (timer != nil)
dict[@"timer"] = timer;
else if (groupedId != nil && !hasAnyTimers)
dict[@"groupedId"] = groupedId;
id generatedItem = descriptionGenerator(dict, caption, entities, nil);
return generatedItem;

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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

View File

@ -204,7 +204,9 @@
}
};
[model.interfaceView immediateEditorTransitionIn];
if (paint) {
[model.interfaceView immediateEditorTransitionIn];
}
for (UIView *view in snapshots) {
[galleryController.view addSubview:view];

View File

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

View File

@ -35,11 +35,7 @@ NumberPluralizationForm numberPluralizationForm(unsigned int lc, int n) {
// set4
case 0x6265: // be
case 0x6273: // bs
case 0x6872: // hr
case 0x7275: // ru
case 0x7368: // sh
case 0x7372: // sr
case 0x756b: // uk
if (((n % 10) == 1) && ((n % 100) != 11)) // n mod 10 is 1 and n mod 100 is not 11
return NumberPluralizationFormOne;
@ -48,6 +44,19 @@ NumberPluralizationForm numberPluralizationForm(unsigned int lc, int n) {
if (((n % 10) == 0) || (((n % 10) >= 5 && (n % 10) <= 9)) || (((n % 100) >= 11 && (n % 100) <= 14))) // n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14
return NumberPluralizationFormMany;
break;
// set4 - bugfix
case 0x6273: // bs
case 0x6872: // hr
case 0x7368: // sh
case 0x7372: // sr
if (((n % 10) == 1) && ((n % 100) != 11)) // n mod 10 is 1 and n mod 100 is not 11
return NumberPluralizationFormOne;
if ((((n % 10) >= 2 && (n % 10) <= 4)) && (((n % 100) < 12 || (n % 100) > 14))) // n mod 10 in 2..4 and n mod 100 not in 12..14
return NumberPluralizationFormFew;
if (((n % 10) == 0) || (((n % 10) >= 5 && (n % 10) <= 9)) || (((n % 100) >= 11 && (n % 100) <= 14))) // n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14
return NumberPluralizationFormOther;
break;
// set5
case 0x6b7368: // ksh

View File

@ -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

View File

@ -148,7 +148,7 @@ private enum ChannelDiscussionGroupSetupControllerEntry: ItemListNodeEntry {
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, sectionId: self.section, editing: false, action: {
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()
})

View File

@ -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))

View File

@ -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()
}))
}
}
})

View File

@ -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: {

View File

@ -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)
})
}

View File

@ -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

View File

@ -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)
})

View File

@ -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)
})

View File

@ -469,7 +469,7 @@ public final class ShareController: ViewController {
}
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: title, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}, 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)
}

View File

@ -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()
}
}
}
}

View File

@ -128,7 +128,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
let estimatedSize = TGMediaVideoConverter.estimatedSize(for: preset, duration: finalDuration, hasAudio: true)
let 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 {

View File

@ -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 {

View File

@ -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
}

View File

@ -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 {

View File

@ -718,6 +718,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
jsonParams: nil,
joinTimestamp: strongSelf.temporaryJoinTimestamp,
activityTimestamp: nil,
activityRank: nil,
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
volume: nil
))

View File

@ -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))
}

View File

@ -1202,12 +1202,12 @@ public class Account {
}
}
public func allPeerInputActivities() -> Signal<[PeerActivitySpace: [PeerId: PeerInputActivity]], NoError> {
public func allPeerInputActivities() -> Signal<[PeerActivitySpace: [(PeerId, PeerInputActivity)]], NoError> {
return self.peerInputActivityManager.allActivities()
|> 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
}

View File

@ -192,7 +192,7 @@ public enum ChatHistoryImport {
case invalidChatType
case userBlocked
case limitExceeded
case userIsNotMutualContact
case notMutualContact
}
public static func checkPeerImport(account: Account, peerId: PeerId) -> Signal<CheckPeerImportResult, CheckPeerImportError> {
@ -217,7 +217,7 @@ public enum ChatHistoryImport {
} else if error.errorDescription == "USER_IS_BLOCKED" {
return .userBlocked
} else if error.errorDescription == "USER_NOT_MUTUAL_CONTACT" {
return .userBlocked
return .notMutualContact
} else if error.errorDescription == "FLOOD_WAIT" {
return .limitExceeded
} else {

View File

@ -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

View File

@ -116,6 +116,7 @@ public func getCurrentGroupCall(account: Account, callId: Int64, accessHash: Int
jsonParams: jsonParams,
joinTimestamp: date,
activityTimestamp: activeDate.flatMap(Double.init),
activityRank: nil,
muteState: muteState,
volume: volume
))
@ -264,6 +265,7 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
jsonParams: jsonParams,
joinTimestamp: date,
activityTimestamp: activeDate.flatMap(Double.init),
activityRank: nil,
muteState: muteState,
volume: volume
))
@ -271,6 +273,8 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
}
}
parsedParticipants.sort()
return GroupCallParticipantsContext.State(
participants: parsedParticipants,
nextParticipantsFetchOffset: nextParticipantsFetchOffset,
@ -311,7 +315,6 @@ public func joinGroupCall(account: Account, peerId: PeerId, callId: Int64, acces
return .generic
}
|> mapToSignal { updates -> Signal<JoinGroupCallResult, JoinGroupCallError> in
let admins: Signal<(Set<PeerId>, [Api.User]), JoinGroupCallError>
if peerId.namespace == Namespaces.Peer.CloudChannel {
admins = account.postbox.transaction { transaction -> Api.InputChannel? in
@ -592,6 +595,7 @@ public final class GroupCallParticipantsContext {
public var jsonParams: String?
public var joinTimestamp: Int32
public var activityTimestamp: Double?
public var activityRank: Int?
public var muteState: MuteState?
public var volume: Int32?
@ -601,6 +605,7 @@ public final class GroupCallParticipantsContext {
jsonParams: String?,
joinTimestamp: Int32,
activityTimestamp: Double?,
activityRank: Int?,
muteState: MuteState?,
volume: Int32?
) {
@ -609,6 +614,7 @@ public final class GroupCallParticipantsContext {
self.jsonParams = jsonParams
self.joinTimestamp = joinTimestamp
self.activityTimestamp = activityTimestamp
self.activityRank = activityRank
self.muteState = muteState
self.volume = volume
}
@ -626,6 +632,9 @@ public final class GroupCallParticipantsContext {
if lhs.activityTimestamp != rhs.activityTimestamp {
return false
}
if lhs.activityRank != rhs.activityRank {
return false
}
if lhs.muteState != rhs.muteState {
return false
}
@ -636,6 +645,16 @@ public final class GroupCallParticipantsContext {
}
public static func <(lhs: Participant, rhs: Participant) -> Bool {
if let lhsActivityRank = lhs.activityRank, let rhsActivityRank = rhs.activityRank {
if lhsActivityRank != rhsActivityRank {
return lhsActivityRank < rhsActivityRank
}
} else if lhs.activityRank != nil {
return true
} else if rhs.activityRank != nil {
return false
}
if let lhsActivityTimestamp = lhs.activityTimestamp, let rhsActivityTimestamp = rhs.activityTimestamp {
if lhsActivityTimestamp != rhsActivityTimestamp {
return lhsActivityTimestamp > rhsActivityTimestamp
@ -823,6 +842,9 @@ public final class GroupCallParticipantsContext {
private var shouldResetStateFromServer: Bool = false
private var missingSsrcs = Set<UInt32>()
private var nextActivityRank: Int = 0
private var activityRankResetTimer: SwiftSignalKit.Timer?
private let updateDefaultMuteDisposable = MetaDisposable()
public init(account: Account, peerId: PeerId, id: Int64, accessHash: Int64, state: State) {
@ -912,6 +934,36 @@ public final class GroupCallParticipantsContext {
}
}
})
self.activityRankResetTimer = SwiftSignalKit.Timer(timeout: 10.0, repeat: true, completion: { [weak self] in
guard let strongSelf = self else {
return
}
var updated = false
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
for i in 0 ..< strongSelf.stateValue.state.participants.count {
if strongSelf.stateValue.state.participants[i].activityRank != nil {
var clearRank = false
if let activityTimestamp = strongSelf.stateValue.state.participants[i].activityTimestamp {
if activityTimestamp < timestamp - 60.0 {
clearRank = true
}
} else {
clearRank = true
}
if clearRank {
updated = true
strongSelf.stateValue.state.participants[i].activityRank = nil
}
}
}
if updated {
strongSelf.stateValue.state.participants.sort()
}
}, queue: .mainQueue())
self.activityRankResetTimer?.start()
}
deinit {
@ -919,6 +971,7 @@ public final class GroupCallParticipantsContext {
self.updatesDisposable.dispose()
self.activitiesDisposable?.dispose()
self.updateDefaultMuteDisposable.dispose()
self.activityRankResetTimer?.invalidate()
}
public func addUpdates(updates: [Update]) {
@ -937,6 +990,12 @@ public final class GroupCallParticipantsContext {
}
}
private func takeNextActivityRank() -> Int {
let value = self.nextActivityRank
self.nextActivityRank += 1
return value
}
public func reportSpeakingParticipants(ids: [PeerId: UInt32]) {
if !ids.isEmpty {
self.hasReceivedSpeakingParticipantsReport = true
@ -965,6 +1024,9 @@ public final class GroupCallParticipantsContext {
}
if updateTimestamp {
updatedParticipants[index].activityTimestamp = timestamp
if updatedParticipants[index].activityRank == nil {
updatedParticipants[index].activityRank = self.takeNextActivityRank()
}
updated = true
}
}
@ -1040,19 +1102,7 @@ public final class GroupCallParticipantsContext {
var updatedState = strongSelf.stateValue.state
var existingParticipantIds = Set<PeerId>()
for participant in updatedState.participants {
existingParticipantIds.insert(participant.peer.id)
}
for participant in state.participants {
if existingParticipantIds.contains(participant.peer.id) {
continue
}
existingParticipantIds.insert(participant.peer.id)
updatedState.participants.append(participant)
}
updatedState.participants.sort()
updatedState.participants = mergeAndSortParticipants(current: updatedState.participants, with: state.participants)
updatedState.totalCount = max(updatedState.totalCount, state.totalCount)
updatedState.version = max(updatedState.version, updatedState.version)
@ -1138,8 +1188,10 @@ public final class GroupCallParticipantsContext {
continue
}
var previousActivityTimestamp: Double?
var previousActivityRank: Int?
if let index = updatedParticipants.firstIndex(where: { $0.peer.id == participantUpdate.peerId }) {
previousActivityTimestamp = updatedParticipants[index].activityTimestamp
previousActivityRank = updatedParticipants[index].activityRank
updatedParticipants.remove(at: index)
} else if case .joined = participantUpdate.participationStatusChange {
updatedTotalCount += 1
@ -1159,6 +1211,7 @@ public final class GroupCallParticipantsContext {
jsonParams: participantUpdate.jsonParams,
joinTimestamp: participantUpdate.joinTimestamp,
activityTimestamp: activityTimestamp,
activityRank: previousActivityRank,
muteState: participantUpdate.muteState,
volume: participantUpdate.volume
)
@ -1179,14 +1232,6 @@ public final class GroupCallParticipantsContext {
let defaultParticipantsAreMuted = strongSelf.stateValue.state.defaultParticipantsAreMuted
updatedParticipants.sort()
/*for i in 0 ..< updatedParticipants.count {
if updatedParticipants[i].peer.id == strongSelf.account.peerId {
let member = updatedParticipants[i]
updatedParticipants.remove(at: i)
updatedParticipants.insert(member, at: 0)
break
}
}*/
strongSelf.stateValue = InternalState(
state: State(
@ -1344,17 +1389,7 @@ public final class GroupCallParticipantsContext {
var updatedState = strongSelf.stateValue.state
var existingParticipantIds = Set<PeerId>()
for participant in updatedState.participants {
existingParticipantIds.insert(participant.peer.id)
}
for participant in state.participants {
if existingParticipantIds.contains(participant.peer.id) {
continue
}
existingParticipantIds.insert(participant.peer.id)
updatedState.participants.append(participant)
}
updatedState.participants = mergeAndSortParticipants(current: updatedState.participants, with: state.participants)
updatedState.nextParticipantsFetchOffset = state.nextParticipantsFetchOffset
updatedState.totalCount = max(updatedState.totalCount, state.totalCount)
@ -1513,3 +1548,23 @@ public func updatedCurrentPeerGroupCall(account: Account, peerId: PeerId) -> Sig
}
}
}
private func mergeAndSortParticipants(current currentParticipants: [GroupCallParticipantsContext.Participant], with updatedParticipants: [GroupCallParticipantsContext.Participant]) -> [GroupCallParticipantsContext.Participant] {
var mergedParticipants = currentParticipants
var existingParticipantIndices: [PeerId: Int] = [:]
for i in 0 ..< mergedParticipants.count {
existingParticipantIndices[mergedParticipants[i].peer.id] = i
}
for participant in updatedParticipants {
if let _ = existingParticipantIndices[participant.peer.id] {
} else {
existingParticipantIndices[participant.peer.id] = mergedParticipants.count
mergedParticipants.append(participant)
}
}
mergedParticipants.sort()
return mergedParticipants
}

View File

@ -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
}

View File

@ -2,26 +2,37 @@ import Foundation
import Postbox
import TelegramApi
import SwiftSignalKit
import MtProtoKit
import SyncCore
public enum JoinChannelError {
case generic
case tooMuchJoined
case tooMuchUsers
}
public func joinChannel(account: Account, peerId: PeerId) -> Signal<RenderedChannelParticipant?, JoinChannelError> {
public func joinChannel(account: Account, peerId: PeerId, hash: String?) -> Signal<RenderedChannelParticipant?, JoinChannelError> {
return account.postbox.loadedPeerWithId(peerId)
|> take(1)
|> castError(JoinChannelError.self)
|> mapToSignal { peer -> Signal<RenderedChannelParticipant?, JoinChannelError> in
if let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.joinChannel(channel: inputChannel))
let request: Signal<Api.Updates, MTRpcError>
if let hash = hash {
request = account.network.request(Api.functions.messages.importChatInvite(hash: hash))
} else {
request = account.network.request(Api.functions.channels.joinChannel(channel: inputChannel))
}
return request
|> mapError { error -> JoinChannelError in
if error.errorDescription == "CHANNELS_TOO_MUCH" {
return .tooMuchJoined
} else {
return .generic
switch error.errorDescription {
case "CHANNELS_TOO_MUCH":
return .tooMuchJoined
case "USERS_TOO_MUCH":
return .tooMuchUsers
default:
return .generic
}
}
|> mapToSignal { updates -> Signal<RenderedChannelParticipant?, JoinChannelError> in

View File

@ -8,6 +8,7 @@ import SyncCore
public enum JoinLinkError {
case generic
case tooMuchJoined
case tooMuchUsers
}
func apiUpdatesGroups(_ updates: Api.Updates) -> [Api.Chat] {
@ -31,10 +32,13 @@ public enum ExternalJoiningChatState {
public func joinChatInteractively(with hash: String, account: Account) -> Signal <PeerId?, JoinLinkError> {
return account.network.request(Api.functions.messages.importChatInvite(hash: hash))
|> mapError { error -> JoinLinkError in
if error.errorDescription == "CHANNELS_TOO_MUCH" {
return .tooMuchJoined
} else {
return .generic
switch error.errorDescription {
case "CHANNELS_TOO_MUCH":
return .tooMuchJoined
case "USERS_TOO_MUCH":
return .tooMuchUsers
default:
return .generic
}
}
|> mapToSignal { updates -> Signal<PeerId?, JoinLinkError> in

View File

@ -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())
}
})

View File

@ -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

View File

@ -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" {

View File

@ -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()

View File

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

View File

@ -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

View File

@ -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
}
}
}

View File

@ -212,7 +212,7 @@ public struct PresentationResourcesChatList {
})
}
public static func scamIcon(_ theme: PresentationTheme, type: ScamIconType) -> UIImage? {
public static func scamIcon(_ theme: PresentationTheme, strings: PresentationStrings, type: ScamIconType) -> UIImage? {
let key: PresentationResourceKey
let 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)

View File

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

View File

@ -27,8 +27,8 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
super.init()
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 {

View File

@ -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)

View File

@ -168,7 +168,7 @@ final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
case .join:
self.activityIndicator.isHidden = false
self.activityIndicator.startAnimating()
self.actionDisposable.set((context.peerChannelMemberCategoriesContextsManager.join(account: context.account, peerId: peer.id)
self.actionDisposable.set((context.peerChannelMemberCategoriesContextsManager.join(account: context.account, peerId: peer.id, hash: nil)
|> afterDisposed { [weak self] in
Queue.mainQueue().async {
if let strongSelf = self {
@ -189,7 +189,9 @@ final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
}
}))
return
default:
case .tooMuchUsers:
text = presentationInterfaceState.strings.Conversation_UsersTooMuchError
case .generic:
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
text = presentationInterfaceState.strings.Channel_ErrorAccessDenied
} else {

View File

@ -306,6 +306,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private var automaticMediaDownloadSettings: MediaAutoDownloadSettings
private var automaticMediaDownloadSettingsDisposable: Disposable?
private var disableStickerAnimationsPromise = ValuePromise<Bool>(false)
private var disableStickerAnimationsValue = false
var disableStickerAnimations: Bool {
get {
return self.disableStickerAnimationsValue
} set {
self.disableStickerAnimationsPromise.set(newValue)
}
}
private var stickerSettings: ChatInterfaceStickerSettings
private var stickerSettingsDisposable: Disposable?
@ -1163,124 +1172,122 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
}
}, requestMessageActionUrlAuth: { [weak self] defaultUrl, messageId, buttonId in
}, requestMessageActionUrlAuth: { [weak self] defaultUrl, subject in
if let strongSelf = self {
guard strongSelf.presentationInterfaceState.subject != .scheduledMessages else {
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.ScheduledMessages_BotActionUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
return
}
if let _ = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if !$0.contains(where: {
switch $0 {
case .requestInProgress:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.append(.requestInProgress)
return updatedContexts.sorted()
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if !$0.contains(where: {
switch $0 {
case .requestInProgress:
return true
default:
return false
}
return $0
}) {
var updatedContexts = $0
updatedContexts.append(.requestInProgress)
return updatedContexts.sorted()
}
})
strongSelf.messageActionUrlAuthDisposable.set(((combineLatest(strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.context.account.peerId), requestMessageActionUrlAuth(account: strongSelf.context.account, messageId: messageId, buttonId: buttonId) |> afterDisposed {
Queue.mainQueue().async {
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if let index = $0.firstIndex(where: {
switch $0 {
case .requestInProgress:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.remove(at: index)
return updatedContexts
return $0
}
})
strongSelf.messageActionUrlAuthDisposable.set(((combineLatest(strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.context.account.peerId), requestMessageActionUrlAuth(account: strongSelf.context.account, subject: subject) |> afterDisposed {
Queue.mainQueue().async {
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if let index = $0.firstIndex(where: {
switch $0 {
case .requestInProgress:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.remove(at: index)
return updatedContexts
}
return $0
}
})
}
}
})) |> deliverOnMainQueue).start(next: { peer, result in
if let strongSelf = self {
switch result {
case .default:
strongSelf.openUrl(defaultUrl, concealed: false)
case let .request(domain, bot, requestWriteAccess):
let controller = chatMessageActionUrlAuthController(context: strongSelf.context, defaultUrl: defaultUrl, domain: domain, bot: bot, requestWriteAccess: requestWriteAccess, displayName: peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), open: { [weak self] authorize, allowWriteAccess in
if let strongSelf = self {
if authorize {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if !$0.contains(where: {
switch $0 {
case .requestInProgress:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.append(.requestInProgress)
return updatedContexts.sorted()
}
return $0
}
})
strongSelf.messageActionUrlAuthDisposable.set(((acceptMessageActionUrlAuth(account: strongSelf.context.account, subject: subject, allowWriteAccess: allowWriteAccess) |> afterDisposed {
Queue.mainQueue().async {
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if let index = $0.firstIndex(where: {
switch $0 {
case .requestInProgress:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.remove(at: index)
return updatedContexts
}
return $0
}
})
}
}
}) |> deliverOnMainQueue).start(next: { [weak self] result in
if let strongSelf = self {
switch result {
case let .accepted(url):
strongSelf.openUrl(url, concealed: false)
default:
strongSelf.openUrl(defaultUrl, concealed: false)
}
}
}))
} else {
strongSelf.openUrl(defaultUrl, concealed: false)
}
return $0
}
})
}
strongSelf.chatDisplayNode.dismissInput()
strongSelf.present(controller, in: .window(.root))
case let .accepted(url):
strongSelf.openUrl(url, concealed: false)
}
})) |> deliverOnMainQueue).start(next: { peer, result in
if let strongSelf = self {
switch result {
case .default:
strongSelf.openUrl(defaultUrl, concealed: false)
case let .request(domain, bot, requestWriteAccess):
let controller = chatMessageActionUrlAuthController(context: strongSelf.context, defaultUrl: defaultUrl, domain: domain, bot: bot, requestWriteAccess: requestWriteAccess, displayName: peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), open: { [weak self] authorize, allowWriteAccess in
if let strongSelf = self {
if authorize {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if !$0.contains(where: {
switch $0 {
case .requestInProgress:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.append(.requestInProgress)
return updatedContexts.sorted()
}
return $0
}
})
strongSelf.messageActionUrlAuthDisposable.set(((acceptMessageActionUrlAuth(account: strongSelf.context.account, messageId: messageId, buttonId: buttonId, allowWriteAccess: allowWriteAccess) |> afterDisposed {
Queue.mainQueue().async {
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
return $0.updatedTitlePanelContext {
if let index = $0.firstIndex(where: {
switch $0 {
case .requestInProgress:
return true
default:
return false
}
}) {
var updatedContexts = $0
updatedContexts.remove(at: index)
return updatedContexts
}
return $0
}
})
}
}
}) |> deliverOnMainQueue).start(next: { [weak self] result in
if let strongSelf = self {
switch result {
case let .accepted(url):
strongSelf.openUrl(url, concealed: false)
default:
strongSelf.openUrl(defaultUrl, concealed: false)
}
}
}))
} else {
strongSelf.openUrl(defaultUrl, concealed: false)
}
}
})
strongSelf.chatDisplayNode.dismissInput()
strongSelf.present(controller, in: .window(.root))
case let .accepted(url):
strongSelf.openUrl(url, concealed: false)
}
}
}))
}
}
}))
}
}, activateSwitchInline: { [weak self] peerId, inputString in
guard let strongSelf = self else {
@ -2578,7 +2585,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
self.chatTitleView?.longPressed = { [weak self] in
self?.interfaceInteraction?.beginMessageSearch(.everything, "")
if let strongSelf = self, let peerView = strongSelf.peerView, let peer = peerView.peers[peerView.peerId], peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil && !strongSelf.presentationInterfaceState.isNotAccessible {
strongSelf.interfaceInteraction?.beginMessageSearch(.everything, "")
}
}
let chatInfoButtonItem: UIBarButtonItem
@ -3440,20 +3449,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
})
self.stickerSettingsDisposable = (context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings])
|> deliverOnMainQueue).start(next: { [weak self] sharedData in
self.stickerSettingsDisposable = combineLatest(queue: Queue.mainQueue(), context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]), self.disableStickerAnimationsPromise.get()).start(next: { [weak self] sharedData, disableStickerAnimations in
var stickerSettings = StickerSettings.defaultSettings
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings] as? StickerSettings {
stickerSettings = value
}
let chatStickerSettings = ChatInterfaceStickerSettings(stickerSettings: stickerSettings)
if let strongSelf = self, strongSelf.stickerSettings != chatStickerSettings {
if let strongSelf = self, strongSelf.stickerSettings != chatStickerSettings || strongSelf.disableStickerAnimationsValue != disableStickerAnimations {
strongSelf.stickerSettings = chatStickerSettings
strongSelf.disableStickerAnimationsValue = disableStickerAnimations
strongSelf.controllerInteraction?.stickerSettings = chatStickerSettings
if strongSelf.isNodeLoaded {
strongSelf.chatDisplayNode.updateStickerSettings(chatStickerSettings)
strongSelf.chatDisplayNode.updateStickerSettings(chatStickerSettings, forceStopAnimations: disableStickerAnimations)
}
}
})
@ -7144,6 +7152,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
override public func inFocusUpdated(isInFocus: Bool) {
self.disableStickerAnimationsPromise.set(!isInFocus)
self.chatDisplayNode.inFocusUpdated(isInFocus: isInFocus)
}
@ -8497,6 +8506,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self {
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
for item in results {
if let item = item, item.fileSize > 2000 * 1024 * 1024 {
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Conversation_UploadFileTooLarge, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
return
}
}
var groupingKey: Int64?
var fileTypes: (music: Bool, other: Bool) = (false, false)
if results.count > 1 {
@ -10241,10 +10257,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case .upperBound:
searchLocation = .index(MessageIndex.upperBound(peerId: self.chatLocation.peerId))
}
var historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50, highlight: true), id: 0), context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
#if DEBUG
historyView = historyView |> delay(1.0, queue: .mainQueue())
#endif
let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50, highlight: true), id: 0), context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
let signal = historyView
|> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in
@ -11082,6 +11095,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}, sendFile: nil,
sendSticker: { [weak self] f, sourceNode, sourceRect in
return self?.interfaceInteraction?.sendSticker(f, sourceNode, sourceRect) ?? false
}, requestMessageActionUrlAuth: { [weak self] subject in
if case let .url(url) = subject {
self?.controllerInteraction?.requestMessageActionUrlAuth(url, subject)
}
}, present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a)
}, dismissInput: { [weak self] in

View File

@ -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: {

View File

@ -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)

View File

@ -623,6 +623,14 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
super.init()
self.accessibilityPageScrolledString = { [weak self] row, count in
if let strongSelf = self {
return strongSelf.currentPresentationData.strings.VoiceOver_ScrollStatus(row, count).0
} else {
return ""
}
}
self.dynamicBounceEnabled = !self.currentPresentationData.disableAnimations
self.experimentalSnapScrollToItem = true

View File

@ -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

View File

@ -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
}

View File

@ -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()
}

View File

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

View File

@ -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

View File

@ -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
}

View File

@ -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: {

View File

@ -58,6 +58,12 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode {
self?.interaction.toggleMembersSearch(false)
}
self.searchBar.tokensUpdated = { [weak self] tokens in
if tokens.isEmpty {
self?.interaction.toggleMembersSearch(false)
}
}
if let statuses = interaction.statuses {
self.searchingActivityDisposable = (statuses.searching
|> deliverOnMainQueue).start(next: { [weak self] value in

View File

@ -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
}

View File

@ -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))
}
}))
}

View File

@ -69,7 +69,8 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
init(context: AccountContext, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter]) {
self.context = context
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.presentationData = presentationData
var placeholder: String
var includeChatList = false
@ -88,6 +89,9 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
if case let .chatSelection(_, selectedChats, additionalCategories, chatListFilters) = mode {
placeholder = self.presentationData.strings.ChatListFilter_AddChatsTitle
let chatListNode = ChatListNode(context: context, groupId: .root, previewing: false, fillPreloadItems: false, mode: .peers(filter: [.excludeSecretChats], isSelecting: true, additionalCategories: additionalCategories?.categories ?? [], chatListFilters: chatListFilters), theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
chatListNode.accessibilityPageScrolledString = { row, count in
return presentationData.strings.VoiceOver_ScrollStatus(row, count).0
}
chatListNode.updateState { state in
var state = state
for peerId in selectedChats {

View File

@ -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: {

View File

@ -38,11 +38,15 @@ private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatContr
}
}
func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?) {
func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)? = nil, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?) {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
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))

View File

@ -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()

View File

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

View File

@ -473,7 +473,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
))
case let .user(userPeerId, secretChatId, kind):
let groupsInCommon: GroupsInCommonContext?
if case .user = kind {
if [.user, .bot].contains(kind) {
groupsInCommon = GroupsInCommonContext(account: context.account, peerId: userPeerId)
} else {
groupsInCommon = nil
@ -984,7 +984,7 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
if isOpenedFromChat {
result.append(.search)
}
if isSecretChat && !isContact {
if (isSecretChat && !isContact) || user.flags.contains(.isSupport) {
} else {
result.append(.more)
}
@ -1043,7 +1043,7 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
if channel.isVerified || channel.adminRights != nil || channel.flags.contains(.isCreator) {
canReport = false
}
if !canReport && !canViewStats && displayLeave {
if !canReport && !canViewStats {
displayMore = false
}
if displayMore {

View File

@ -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

View File

@ -563,6 +563,7 @@ private final class PeerInfoInteraction {
let logoutAccount: (AccountRecordId) -> Void
let accountContextMenu: (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void
let updateBio: (String) -> Void
let openDeletePeer: () -> Void
init(
openUsername: @escaping (String) -> Void,
@ -599,7 +600,8 @@ private final class PeerInfoInteraction {
switchToAccount: @escaping (AccountRecordId) -> Void,
logoutAccount: @escaping (AccountRecordId) -> Void,
accountContextMenu: @escaping (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void,
updateBio: @escaping (String) -> Void
updateBio: @escaping (String) -> Void,
openDeletePeer: @escaping () -> Void
) {
self.openUsername = openUsername
self.openPhone = openPhone
@ -636,6 +638,7 @@ private final class PeerInfoInteraction {
self.logoutAccount = logoutAccount
self.accountContextMenu = accountContextMenu
self.updateBio = updateBio
self.openDeletePeer = openDeletePeer
}
}
@ -1140,6 +1143,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
case groupLocation
case peerPublicSettings
case peerSettings
case peerActions
}
var items: [Section: [PeerInfoScreenItem]] = [:]
@ -1147,33 +1151,6 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
items[section] = []
}
// if let data = data, let notificationSettings = data.notificationSettings {
// let notificationsLabel: String
// let soundLabel: String
// if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
// if until < Int32.max - 1 {
// notificationsLabel = stringForRemainingMuteInterval(strings: presentationData.strings, muteInterval: until)
// } else {
// notificationsLabel = presentationData.strings.UserInfo_NotificationsDisabled
// }
// } else {
// notificationsLabel = presentationData.strings.UserInfo_NotificationsEnabled
// }
//
// let globalNotificationSettings: GlobalNotificationSettings = data.globalNotificationSettings ?? GlobalNotificationSettings.defaultSettings
// soundLabel = localizedPeerNotificationSoundString(strings: presentationData.strings, sound: notificationSettings.messageSound, default: globalNotificationSettings.effective.privateChats.sound)
//
// items[.notifications]!.append(PeerInfoScreenDisclosureItem(id: 0, label: .text(notificationsLabel), text: presentationData.strings.GroupInfo_Notifications, action: {
// interaction.editingOpenNotificationSettings()
// }))
// items[.notifications]!.append(PeerInfoScreenDisclosureItem(id: 1, label: .text(soundLabel), text: presentationData.strings.GroupInfo_Sound, action: {
// interaction.editingOpenSoundSettings()
// }))
// items[.notifications]!.append(PeerInfoScreenSwitchItem(id: 2, text: presentationData.strings.Notification_Exceptions_PreviewAlwaysOn, value: notificationSettings.displayPreviews != .hide, toggled: { value in
// interaction.editingToggleShowMessageText(value)
// }))
// }
if let data = data {
if let _ = data.peer as? TelegramUser {
let ItemDelete = 0
@ -1194,8 +1171,8 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
if channel.flags.contains(.isCreator) {
let linkText: String
if let username = channel.username {
linkText = "@\(username)"
if let _ = channel.username {
linkText = presentationData.strings.Channel_Setup_TypePublic
} else {
linkText = presentationData.strings.Channel_Setup_TypePrivate
}
@ -1204,7 +1181,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
}))
}
if (channel.flags.contains(.isCreator) && (channel.username?.isEmpty ?? true)) || (channel.adminRights?.rights.contains(.canInviteUsers) == true) {
if (channel.flags.contains(.isCreator) && (channel.username?.isEmpty ?? true)) || (!channel.flags.contains(.isCreator) && channel.adminRights?.rights.contains(.canInviteUsers) == true) {
let invitesText: String
if let count = data.invitations?.count, count > 0 {
invitesText = "\(count)"
@ -1256,14 +1233,15 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
let ItemLinkedChannel = 103
let ItemPreHistory = 104
let ItemStickerPack = 105
let ItemPermissions = 106
let ItemMembers = 107
let ItemMembers = 106
let ItemPermissions = 107
let ItemAdmins = 108
let ItemRemovedUsers = 109
let ItemLocationHeader = 110
let ItemLocation = 111
let ItemLocationSetup = 112
let ItemAutoremove = 113
let ItemDeleteGroup = 114
let isCreator = channel.flags.contains(.isCreator)
let isPublic = channel.username != nil
@ -1311,7 +1289,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
}
}
if (isCreator && (channel.username?.isEmpty ?? true) && cachedData.peerGeoLocation == nil) || (channel.adminRights?.rights.contains(.canInviteUsers) == true) {
if (isCreator && (channel.username?.isEmpty ?? true) && cachedData.peerGeoLocation == nil) || (!isCreator && channel.adminRights?.rights.contains(.canInviteUsers) == true) {
let invitesText: String
if let count = data.invitations?.count, count > 0 {
invitesText = "\(count)"
@ -1369,25 +1347,28 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
activePermissionCount = count
}
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMembers, label: .text(cachedData.participantsSummary.memberCount.flatMap { "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" } ?? ""), text: presentationData.strings.Group_Info_Members, icon: UIImage(bundleImageName: "Chat/Info/GroupMembersIcon"), action: {
interaction.openParticipantsSection(.members)
}))
if !channel.flags.contains(.isGigagroup) {
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPermissions, label: .text(activePermissionCount.flatMap({ "\($0)/\(allGroupPermissionList.count)" }) ?? ""), text: presentationData.strings.GroupInfo_Permissions, icon: UIImage(bundleImageName: "Settings/MenuIcons/SetPasscode"), action: {
interaction.openPermissions()
}))
} else {
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemMembers, label: .text(cachedData.participantsSummary.memberCount.flatMap { "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" } ?? ""), text: presentationData.strings.Group_Info_Members, icon: UIImage(bundleImageName: "Chat/Info/GroupMembersIcon"), action: {
interaction.openParticipantsSection(.members)
}))
}
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, label: .text(cachedData.participantsSummary.adminCount.flatMap { "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" } ?? ""), text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: {
interaction.openParticipantsSection(.admins)
}))
if channel.flags.contains(.isGigagroup) {
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemRemovedUsers, label: .text(cachedData.participantsSummary.kickedCount.flatMap { $0 > 0 ? "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" : "" } ?? ""), text: presentationData.strings.GroupInfo_Permissions_Removed, icon: UIImage(bundleImageName: "Chat/Info/GroupRemovedIcon"), action: {
interaction.openParticipantsSection(.banned)
}))
}
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemRemovedUsers, label: .text(cachedData.participantsSummary.kickedCount.flatMap { $0 > 0 ? "\(presentationStringsFormattedNumber($0, presentationData.dateTimeFormat.groupingSeparator))" : "" } ?? ""), text: presentationData.strings.GroupInfo_Permissions_Removed, icon: UIImage(bundleImageName: "Chat/Info/GroupRemovedIcon"), action: {
interaction.openParticipantsSection(.banned)
}))
}
if isCreator {
items[.peerActions]!.append(PeerInfoScreenActionItem(id: ItemDeleteGroup, text: presentationData.strings.Group_DeleteGroup, color: .destructive, icon: nil, alignment: .natural, action: {
interaction.openDeletePeer()
}))
}
}
}
@ -1691,6 +1672,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
},
updateBio: { [weak self] bio in
self?.updateBio(bio)
},
openDeletePeer: { [weak self] in
self?.openDeletePeer()
}
)
@ -1987,7 +1971,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}, sendBotContextResultAsGif: { _, _, _, _ in
return false
}, requestMessageActionCallback: { _, _, _, _ in
}, requestMessageActionUrlAuth: { _, _, _ in
}, requestMessageActionUrlAuth: { _, _ in
}, activateSwitchInline: { _, _ in
}, openUrl: { [weak self] url, concealed, external, _ in
guard let strongSelf = self else {
@ -2726,6 +2710,29 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
} else {
screenData = peerInfoScreenData(context: context, peerId: peerId, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, isSettings: self.isSettings, ignoreGroupInCommon: ignoreGroupInCommon)
self.headerNode.displayAvatarContextMenu = { [weak self] node, gesture in
guard let strongSelf = self, let peer = strongSelf.data?.peer else {
return
}
let items: [ContextMenuItem] = [
.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_ReportProfilePhoto, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Report"), color: theme.actionSheet.primaryTextColor)
}, action: { [weak self] c, f in
if let strongSelf = self, let parent = strongSelf.controller {
presentPeerReportOptions(context: context, parent: parent, contextController: c, subject: .profilePhoto(peer.id, 0), completion: { _, _ in })
}
}))
]
let galleryController = AvatarGalleryController(context: strongSelf.context, peer: peer, remoteEntries: nil, replaceRootController: { controller, ready in
}, synchronousLoad: true)
galleryController.setHintWillBePresentedInPreviewingContext(true)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
strongSelf.controller?.presentInGlobalOverlay(contextController)
}
}
self.headerNode.avatarListNode.listContainerNode.currentIndexUpdated = { [weak self] in
@ -3024,10 +3031,10 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}, sendFile: nil,
sendSticker: { [weak self] f, sourceNode, sourceRect in
return false
}, present: { [weak self] c, a in
}, requestMessageActionUrlAuth: nil, present: { [weak self] c, a in
self?.controller?.present(c, in: .window(.root), with: a)
}, dismissInput: { [weak self] in
self?.view.endEditing(true)
}, contentContext: nil)
}
@ -3044,8 +3051,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.controller?.navigationController as? NavigationController, openPeer: { peerId, navigation in
self?.openPeer(peerId: peerId, navigation: navigation)
}, sendFile: nil,
sendSticker: nil,
present: { c, a in
sendSticker: nil,
requestMessageActionUrlAuth: nil,
present: { c, a in
self?.controller?.present(c, in: .window(.root), with: a)
}, dismissInput: {
self?.view.endEditing(true)
@ -3645,11 +3653,18 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
if let navigationController = (strongSelf.controller?.navigationController as? NavigationController) {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId)))
}
}, error: { _ in
}, error: { error in
guard let strongSelf = self else {
return
}
strongSelf.controller?.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
let text: String
switch error {
case .limitExceeded:
text = strongSelf.presentationData.strings.TwoStepAuth_FloodError
default:
text = strongSelf.presentationData.strings.Login_UnknownError
}
strongSelf.controller?.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}))
})]), in: .window(.root))
}
@ -4091,6 +4106,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
context.sharedContext.openResolvedUrl(.groupBotStart(peerId: peerId, payload: ""), context: self.context, urlContext: .generic, navigationController: controller.navigationController as? NavigationController, openPeer: { id, navigation in
}, sendFile: nil,
sendSticker: nil,
requestMessageActionUrlAuth: nil,
present: { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: a)
}, dismissInput: { [weak controller] in
@ -4921,7 +4937,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
resolvedUrl = .instantView(webPage, customAnchor)
}
strongSelf.context.sharedContext.openResolvedUrl(resolvedUrl, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.controller?.navigationController as? NavigationController, openPeer: { peer, navigation in
}, sendFile: nil, sendSticker: nil, present: { [weak self] controller, arguments in
}, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, present: { [weak self] controller, arguments in
self?.controller?.push(controller)
}, dismissInput: {}, contentContext: nil)
}
@ -6564,7 +6580,13 @@ func presentAddMembers(context: AccountContext, parentController: ViewController
case .notMutualContact:
let _ = (context.account.postbox.loadedPeerWithId(memberId)
|> deliverOnMainQueue).start(next: { peer in
parentController?.present(textAlertController(context: context, title: nil, text: presentationData.strings.GroupInfo_AddUserLeftError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
let text: String
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
text = presentationData.strings.Channel_AddUserLeftError
} else {
text = presentationData.strings.GroupInfo_AddUserLeftError
}
parentController?.present(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
})
return .complete()
case .tooManyChannels:
@ -6683,7 +6705,14 @@ func presentAddMembers(context: AccountContext, parentController: ViewController
break
}
} else if peers.count == 1, case .notMutualContact = error {
parentController?.present(textAlertController(context: context, title: nil, text: presentationData.strings.GroupInfo_AddUserLeftError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
let text: String
if let peer = groupPeer as? TelegramChannel, case .broadcast = peer.info {
text = presentationData.strings.Channel_AddUserLeftError
} else {
text = presentationData.strings.GroupInfo_AddUserLeftError
}
parentController?.present(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
} else if case .tooMuchJoined = error {
parentController?.present(textAlertController(context: context, title: nil, text: presentationData.strings.Invite_ChannelsTooMuch, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}

View File

@ -67,7 +67,8 @@ final class PeerSelectionControllerNode: ASDisplayNode {
self.filter = filter
self.hasGlobalSearch = hasGlobalSearch
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.presentationData = presentationData
if hasChatListSelector && hasContactSelector {
self.toolbarBackgroundNode = ASDisplayNode()
@ -107,6 +108,10 @@ final class PeerSelectionControllerNode: ASDisplayNode {
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
self.chatListNode.accessibilityPageScrolledString = { row, count in
return presentationData.strings.VoiceOver_ScrollStatus(row, count).0
}
self.chatListNode.activateSearch = { [weak self] in
self?.requestActivateSearch?()
}

Some files were not shown because too many files have changed in this diff Show More