Merge branch 'master' of github.com:peter-iakovlev/TelegramUI

This commit is contained in:
Ilya Laktyushin 2018-09-07 22:36:43 +03:00
commit ac56816f4c
36 changed files with 480 additions and 232 deletions

View File

@ -58,7 +58,7 @@ private protocol ChannelMemberCategoryListContext {
var listStateValue: ChannelMemberListState { get } var listStateValue: ChannelMemberListState { get }
var listState: Signal<ChannelMemberListState, NoError> { get } var listState: Signal<ChannelMemberListState, NoError> { get }
func loadMore() func loadMore()
func reset() func reset(_ force: Bool)
func replayUpdates(_ updates: [(ChannelParticipant?, RenderedChannelParticipant?)]) func replayUpdates(_ updates: [(ChannelParticipant?, RenderedChannelParticipant?)])
func forceUpdateHead() func forceUpdateHead()
} }
@ -136,12 +136,12 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
})) }))
} }
func reset() { func reset(_ force: Bool) {
if case .loading = self.listStateValue.loadingState, self.listStateValue.list.isEmpty { if case .loading = self.listStateValue.loadingState, self.listStateValue.list.isEmpty {
} else { } else {
var list = self.listStateValue.list var list = self.listStateValue.list
var loadingState: ChannelMemberListLoadingState = .ready(hasMore: false) var loadingState: ChannelMemberListLoadingState = .ready(hasMore: true)
if list.count > Int(initialBatchSize) { if list.count > Int(initialBatchSize) && !force {
list.removeSubrange(Int(initialBatchSize) ..< list.count) list.removeSubrange(Int(initialBatchSize) ..< list.count)
loadingState = .ready(hasMore: true) loadingState = .ready(hasMore: true)
} }
@ -151,6 +151,7 @@ private final class ChannelMemberSingleCategoryListContext: ChannelMemberCategor
} }
} }
private func loadSignal(offset: Int32, count: Int32, hash: Int32) -> Signal<[RenderedChannelParticipant]?, NoError> { private func loadSignal(offset: Int32, count: Int32, hash: Int32) -> Signal<[RenderedChannelParticipant]?, NoError> {
let requestCategory: ChannelMembersCategory let requestCategory: ChannelMembersCategory
var adminQuery: String? = nil var adminQuery: String? = nil
@ -458,9 +459,9 @@ private final class ChannelMemberMultiCategoryListContext: ChannelMemberCategory
} }
} }
func reset() { func reset(_ force: Bool) {
for context in self.contexts { for context in self.contexts {
context.reset() context.reset(force)
} }
} }
@ -508,7 +509,7 @@ private final class PeerChannelMemberContextWithSubscribers {
} }
private func resetAndBeginEmptyTimer() { private func resetAndBeginEmptyTimer() {
self.context.reset() self.context.reset(false)
self.emptyTimer?.invalidate() self.emptyTimer?.invalidate()
let emptyTimer = SwiftSignalKit.Timer(timeout: emptyTimeout, repeat: false, completion: { [weak self] in let emptyTimer = SwiftSignalKit.Timer(timeout: emptyTimeout, repeat: false, completion: { [weak self] in
if let strongSelf = self { if let strongSelf = self {
@ -559,6 +560,15 @@ final class PeerChannelMemberCategoriesContext {
self.becameEmpty = becameEmpty self.becameEmpty = becameEmpty
} }
func reset(_ key: PeerChannelMemberContextKey) {
for (contextKey, context) in contexts {
if contextKey == key {
context.context.reset(true)
context.context.loadMore()
}
}
}
func getContext(key: PeerChannelMemberContextKey, requestUpdate: Bool, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl) { func getContext(key: PeerChannelMemberContextKey, requestUpdate: Bool, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl) {
assert(Queue.mainQueue().isCurrent()) assert(Queue.mainQueue().isCurrent())
if let current = self.contexts[key] { if let current = self.contexts[key] {

View File

@ -319,91 +319,41 @@ public func channelMembersController(account: Account, peerId: PeerId) -> ViewCo
let peersPromise = Promise<[RenderedChannelParticipant]?>(nil) let peersPromise = Promise<[RenderedChannelParticipant]?>(nil)
let arguments = ChannelMembersControllerArguments(account: account, addMember: { let arguments = ChannelMembersControllerArguments(account: account, addMember: {
var confirmationImpl: ((PeerId) -> Signal<Bool, NoError>)?
let contactsController = ContactSelectionController(account: account, title: { $0.GroupInfo_AddParticipantTitle }, confirmation: { peer in
if let confirmationImpl = confirmationImpl, case let .peer(peer, _) = peer {
return confirmationImpl(peer.id)
} else {
return .single(false)
}
})
confirmationImpl = { [weak contactsController] selectedId in
return combineLatest(account.postbox.loadedPeerWithId(selectedId), account.postbox.loadedPeerWithId(peerId))
|> deliverOnMainQueue
|> mapToSignal { peer, channelPeer in
let result = ValuePromise<Bool>()
if let contactsController = contactsController { actionsDisposable.add((peersPromise.get() |> take(1) |> deliverOnMainQueue).start(next: { members in
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let disabledIds = members?.compactMap({$0.peer.id}) ?? []
let confirmationText: String let contactsController = ContactMultiselectionController(account: account, mode: .peerSelection, options: [], filters: [.excludeSelf, .disable(disabledIds)])
if let channel = channelPeer as? TelegramChannel {
switch channel.info { let addMembers: ([ContactListPeerId]) -> Signal<Void, NoError> = { members -> Signal<Void, NoError> in
case .broadcast: let peerIds = members.compactMap { contact -> PeerId? in
confirmationText = presentationData.strings.ChannelInfo_AddParticipantConfirmation(peer.displayTitle).0 switch contact {
case .group: case let .peer(peerId):
confirmationText = presentationData.strings.GroupInfo_AddParticipantConfirmation(peer.displayTitle).0 return peerId
} default:
} else { return nil
confirmationText = presentationData.strings.GroupInfo_AddParticipantConfirmation(peer.displayTitle).0
} }
let alertController = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: confirmationText, actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
result.set(false)
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
result.set(true)
})
])
contactsController.present(alertController, in: .window(.root))
} }
return account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.addMembers(account: account, peerId: peerId, memberIds: peerIds)
return result.get()
} }
}
let addMember = contactsController.result peersPromise.set(contactsController.result
|> mapError { _ -> AddPeerMemberError in return .generic } |> deliverOnMainQueue |> mapToSignal { [weak contactsController] contacts in
|> deliverOnMainQueue contactsController?.displayProgress = true
|> mapToSignal { memberPeer -> Signal<Void, AddPeerMemberError> in
if let memberPeer = memberPeer, case let .peer(selectedPeer, _) = memberPeer { return addMembers(contacts) |> mapToSignal { _ in
let memberId = selectedPeer.id return channelMembers(postbox: account.postbox, network: account.network, peerId: peerId)
let applyMembers: Signal<Void, AddPeerMemberError> = peersPromise.get() } |> deliverOnMainQueue |> afterNext { _ in
|> filter { $0 != nil } contactsController?.dismiss()
|> take(1)
|> mapToSignal { peers -> Signal<Void, NoError> in
return account.postbox.transaction { transaction -> Peer? in
return transaction.getPeer(memberId)
}
|> deliverOnMainQueue
|> mapToSignal { peer -> Signal<Void, NoError> in
if let peer = peer, let peers = peers {
var updatedPeers = peers
var found = false
for i in 0 ..< updatedPeers.count {
if updatedPeers[i].peer.id == memberId {
found = true
break
}
}
if !found {
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
updatedPeers.append(RenderedChannelParticipant(participant: ChannelParticipant.member(id: peer.id, invitedAt: timestamp, adminInfo: nil, banInfo: nil), peer: peer, peers: [:]))
peersPromise.set(.single(updatedPeers))
}
}
return .complete()
}
} }
|> mapError { _ -> AddPeerMemberError in return .generic } })
contactsController.dismissed = {
return addPeerMember(account: account, peerId: peerId, memberId: memberId)
|> then(applyMembers)
} else {
return .complete()
} }
}
presentControllerImpl?(contactsController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) presentControllerImpl?(contactsController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
addMembersDisposable.set(addMember.start()) }))
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
updateState { state in updateState { state in
if (peerId == nil && fromPeerId == state.peerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) { if (peerId == nil && fromPeerId == state.peerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) {

View File

@ -231,6 +231,8 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
switch filter { switch filter {
case let .exclude(ids): case let .exclude(ids):
existingPeerIds = existingPeerIds.union(ids) existingPeerIds = existingPeerIds.union(ids)
case .disable:
break
} }
} }
switch mode { switch mode {

View File

@ -11,6 +11,7 @@ enum ChannelMembersSearchControllerMode {
enum ChannelMembersSearchFilter { enum ChannelMembersSearchFilter {
case exclude([PeerId]) case exclude([PeerId])
case disable([PeerId])
} }
final class ChannelMembersSearchController: ViewController { final class ChannelMembersSearchController: ViewController {

View File

@ -174,6 +174,8 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
if ids.contains(participant.peer.id) { if ids.contains(participant.peer.id) {
continue continue
} }
case .disable:
break
} }
} }
case .promote: case .promote:
@ -186,6 +188,8 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
if ids.contains(participant.peer.id) { if ids.contains(participant.peer.id) {
continue continue
} }
case .disable:
break
} }
} }
if case .creator = participant.participant { if case .creator = participant.participant {

View File

@ -663,7 +663,9 @@ public func channelVisibilityController(account: Account, peerId: PeerId, mode:
let revokeLinkDisposable = MetaDisposable() let revokeLinkDisposable = MetaDisposable()
actionsDisposable.add(revokeLinkDisposable) actionsDisposable.add(revokeLinkDisposable)
actionsDisposable.add(ensuredExistingPeerExportedInvitation(account: account, peerId: peerId).start()) actionsDisposable.add( (account.viewTracker.peerView(peerId) |> filter { $0.cachedData != nil } |> take(1) |> mapToSignal { view -> Signal<Void, NoError> in
return ensuredExistingPeerExportedInvitation(account: account, peerId: peerId)
} ).start())
let arguments = ChannelVisibilityControllerArguments(account: account, updateCurrentType: { type in let arguments = ChannelVisibilityControllerArguments(account: account, updateCurrentType: { type in
updateState { state in updateState { state in
@ -786,6 +788,7 @@ public func channelVisibilityController(account: Account, peerId: PeerId, mode:
}) })
}) })
let peerView = account.viewTracker.peerView(peerId) let peerView = account.viewTracker.peerView(peerId)
|> deliverOnMainQueue |> deliverOnMainQueue
@ -935,7 +938,7 @@ public func channelVisibilityController(account: Account, peerId: PeerId, mode:
nextImpl = { [weak controller] in nextImpl = { [weak controller] in
if let controller = controller { if let controller = controller {
if case .initialSetup = mode { if case .initialSetup = mode {
let selectionController = ContactMultiselectionController(account: account, mode: .channelCreation) let selectionController = ContactMultiselectionController(account: account, mode: .channelCreation, options: [])
(controller.navigationController as? NavigationController)?.replaceAllButRootController(selectionController, animated: true) (controller.navigationController as? NavigationController)?.replaceAllButRootController(selectionController, animated: true)
let _ = (selectionController.result let _ = (selectionController.result
|> deliverOnMainQueue).start(next: { [weak selectionController] peerIds in |> deliverOnMainQueue).start(next: { [weak selectionController] peerIds in

View File

@ -265,7 +265,6 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin
} }
} }
} }
return openChatMessage(account: account, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: strongSelf.navigationController as? NavigationController, dismissInput: { return openChatMessage(account: account, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: strongSelf.navigationController as? NavigationController, dismissInput: {
self?.chatDisplayNode.dismissInput() self?.chatDisplayNode.dismissInput()
}, present: { c, a in }, present: { c, a in
@ -1281,7 +1280,7 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin
if case let .peer(peerId) = self.chatLocation, peerId.namespace == Namespaces.Peer.SecretChat { if case let .peer(peerId) = self.chatLocation, peerId.namespace == Namespaces.Peer.SecretChat {
self.screenCaptureEventsDisposable = screenCaptureEvents().start(next: { [weak self] _ in self.screenCaptureEventsDisposable = screenCaptureEvents().start(next: { [weak self] _ in
if let strongSelf = self, strongSelf.canReadHistoryValue { if let strongSelf = self, strongSelf.canReadHistoryValue, strongSelf.traceVisibility() {
let _ = addSecretChatMessageScreenshot(account: account, peerId: peerId).start() let _ = addSecretChatMessageScreenshot(account: account, peerId: peerId).start()
} }
}) })
@ -3585,7 +3584,10 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin
} }
private func enqueueChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult) { private func enqueueChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult) {
if let message = outgoingMessageWithChatContextResult(results, result), canSendMessagesToChat(self.presentationInterfaceState) { guard case let .peer(peerId) = self.chatLocation else {
return
}
if let message = outgoingMessageWithChatContextResult(to: peerId, results: results, result: result), canSendMessagesToChat(self.presentationInterfaceState) {
let replyMessageId = self.presentationInterfaceState.interfaceState.replyMessageId let replyMessageId = self.presentationInterfaceState.interfaceState.replyMessageId
self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in
if let strongSelf = self { if let strongSelf = self {

View File

@ -641,7 +641,7 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD
previewingContext.sourceRect = sourceRect previewingContext.sourceRect = sourceRect
} }
switch item.content { switch item.content {
case let .peer(_, peer, _, _, _, _, _, _): case let .peer(_, peer, _, _, _, _, _, _, _):
if peer.peerId.namespace != Namespaces.Peer.SecretChat { if peer.peerId.namespace != Namespaces.Peer.SecretChat {
let chatController = ChatController(account: self.account, chatLocation: .peer(peer.peerId), mode: .standard(previewing: true)) let chatController = ChatController(account: self.account, chatLocation: .peer(peer.peerId), mode: .standard(previewing: true))
chatController.canReadHistory.set(false) chatController.canReadHistory.set(false)

View File

@ -7,12 +7,12 @@ import SwiftSignalKit
import TelegramCore import TelegramCore
enum ChatListItemContent { enum ChatListItemContent {
case peer(message: Message?, peer: RenderedPeer, combinedReadState: CombinedPeerReadState?, notificationSettings: PeerNotificationSettings?, summaryInfo: ChatListMessageTagSummaryInfo, embeddedState: PeerChatListEmbeddedInterfaceState?, inputActivities: [(Peer, PeerInputActivity)]?, isAd: Bool) case peer(message: Message?, peer: RenderedPeer, combinedReadState: CombinedPeerReadState?, notificationSettings: PeerNotificationSettings?, summaryInfo: ChatListMessageTagSummaryInfo, embeddedState: PeerChatListEmbeddedInterfaceState?, inputActivities: [(Peer, PeerInputActivity)]?, isAd: Bool, ignoreUnreadBadge: Bool)
case groupReference(groupId: PeerGroupId, message: Message?, topPeers: [Peer], counters: GroupReferenceUnreadCounters) case groupReference(groupId: PeerGroupId, message: Message?, topPeers: [Peer], counters: GroupReferenceUnreadCounters)
var chatLocation: ChatLocation { var chatLocation: ChatLocation {
switch self { switch self {
case let .peer(_, peer, _, _, _, _, _, _): case let .peer(_, peer, _, _, _, _, _, _, _):
return .peer(peer.peerId) return .peer(peer.peerId)
case let .groupReference(groupId, _, _, _): case let .groupReference(groupId, _, _, _):
return .group(groupId) return .group(groupId)
@ -95,7 +95,7 @@ class ChatListItem: ListViewItem {
func selected(listView: ListView) { func selected(listView: ListView) {
switch self.content { switch self.content {
case let .peer(message, peer, _, _, _, _, _, _): case let .peer(message, peer, _, _, _, _, _, _, _):
if let message = message { if let message = message {
self.interaction.messageSelected(message) self.interaction.messageSelected(message)
} else if let peer = peer.peers[peer.peerId] { } else if let peer = peer.peers[peer.peerId] {
@ -321,7 +321,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var peer: Peer? var peer: Peer?
switch item.content { switch item.content {
case let .peer(message, peerValue, _, _, _, _, _, _): case let .peer(message, peerValue, _, _, _, _, _, _, _):
if let message = message { if let message = message {
peer = messageMainPeer(message) peer = messageMainPeer(message)
} else { } else {
@ -420,11 +420,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var multipleAvatarsApply: ((Bool) -> MultipleAvatarsNode)? var multipleAvatarsApply: ((Bool) -> MultipleAvatarsNode)?
switch item.content { switch item.content {
case let .peer(messageValue, peerValue, combinedReadStateValue, notificationSettingsValue, summaryInfoValue, embeddedStateValue, inputActivitiesValue, isAdValue): case let .peer(messageValue, peerValue, combinedReadStateValue, notificationSettingsValue, summaryInfoValue, embeddedStateValue, inputActivitiesValue, isAdValue, ignoreUnreadBadge):
message = messageValue message = messageValue
itemPeer = peerValue itemPeer = peerValue
combinedReadState = combinedReadStateValue combinedReadState = combinedReadStateValue
if let combinedReadState = combinedReadState, !isAdValue { if let combinedReadState = combinedReadState, !isAdValue && !ignoreUnreadBadge {
unreadCount = (combinedReadState.count, combinedReadState.isUnread, notificationSettingsValue?.isRemovedFromTotalUnreadCount ?? false) unreadCount = (combinedReadState.count, combinedReadState.isUnread, notificationSettingsValue?.isRemovedFromTotalUnreadCount ?? false)
} else { } else {
unreadCount = (0, false, false) unreadCount = (0, false, false)

View File

@ -131,7 +131,7 @@ private func mappedInsertEntries(account: Account, nodeInteraction: ChatListNode
case let .PeerEntry(index, presentationData, message, combinedReadState, notificationSettings, embeddedState, peer, summaryInfo, editing, hasActiveRevealControls, inputActivities, isAd): case let .PeerEntry(index, presentationData, message, combinedReadState, notificationSettings, embeddedState, peer, summaryInfo, editing, hasActiveRevealControls, inputActivities, isAd):
switch mode { switch mode {
case .chatList: case .chatList:
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, account: account, peerGroupId: peerGroupId, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd), editing: editing, hasActiveRevealControls: hasActiveRevealControls, header: nil, enableContextActions: true, interaction: nodeInteraction), directionHint: entry.directionHint) return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, account: account, peerGroupId: peerGroupId, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd, ignoreUnreadBadge: false), editing: editing, hasActiveRevealControls: hasActiveRevealControls, header: nil, enableContextActions: true, interaction: nodeInteraction), directionHint: entry.directionHint)
case let .peers(filter): case let .peers(filter):
let itemPeer = peer.chatMainPeer let itemPeer = peer.chatMainPeer
var chatPeer: Peer? var chatPeer: Peer?
@ -192,7 +192,7 @@ private func mappedUpdateEntries(account: Account, nodeInteraction: ChatListNode
case let .PeerEntry(index, presentationData, message, combinedReadState, notificationSettings, embeddedState, peer, summaryInfo, editing, hasActiveRevealControls, inputActivities, isAd): case let .PeerEntry(index, presentationData, message, combinedReadState, notificationSettings, embeddedState, peer, summaryInfo, editing, hasActiveRevealControls, inputActivities, isAd):
switch mode { switch mode {
case .chatList: case .chatList:
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, account: account, peerGroupId: peerGroupId, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd), editing: editing, hasActiveRevealControls: hasActiveRevealControls, header: nil, enableContextActions: true, interaction: nodeInteraction), directionHint: entry.directionHint) return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, account: account, peerGroupId: peerGroupId, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd, ignoreUnreadBadge: false), editing: editing, hasActiveRevealControls: hasActiveRevealControls, header: nil, enableContextActions: true, interaction: nodeInteraction), directionHint: entry.directionHint)
case let .peers(filter): case let .peers(filter):
let itemPeer = peer.chatMainPeer let itemPeer = peer.chatMainPeer
var chatPeer: Peer? var chatPeer: Peer?

View File

@ -397,7 +397,7 @@ enum ChatListSearchEntry: Comparable, Identifiable {
interaction.peerSelected(peer.peer) interaction.peerSelected(peer.peer)
}) })
case let .message(message, readState, presentationData): case let .message(message, readState, presentationData):
return ChatListItem(presentationData: presentationData, account: account, peerGroupId: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(message)), content: .peer(message: message, peer: RenderedPeer(message: message), combinedReadState: readState, notificationSettings: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, isAd: false), editing: false, hasActiveRevealControls: false, header: enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil, enableContextActions: false, interaction: interaction) return ChatListItem(presentationData: presentationData, account: account, peerGroupId: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(message)), content: .peer(message: message, peer: RenderedPeer(message: message), combinedReadState: readState, notificationSettings: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: true), editing: false, hasActiveRevealControls: false, header: enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil, enableContextActions: false, interaction: interaction)
} }
} }
} }
@ -919,7 +919,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode {
return (selectedItemNode.view, peer.id) return (selectedItemNode.view, peer.id)
} else if let selectedItemNode = selectedItemNode as? ChatListItemNode, let item = selectedItemNode.item { } else if let selectedItemNode = selectedItemNode as? ChatListItemNode, let item = selectedItemNode.item {
switch item.content { switch item.content {
case let .peer(message, peer, _, _, _, _, _, _): case let .peer(message, peer, _, _, _, _, _, _, _):
return (selectedItemNode.view, message?.id ?? peer.peerId) return (selectedItemNode.view, message?.id ?? peer.peerId)
case let .groupReference(groupId, _, _, _): case let .groupReference(groupId, _, _, _):
return (selectedItemNode.view, groupId) return (selectedItemNode.view, groupId)

View File

@ -385,7 +385,16 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
} }
} }
} }
if !needShareButton, let author = item.message.author as? TelegramUser, let _ = author.botInfo {
if let info = item.message.forwardInfo {
if let author = info.author as? TelegramUser, let _ = author.botInfo {
needShareButton = true
} else if let author = info.author as? TelegramChannel, case .broadcast = author.info, !(item.message.media.first is TelegramMediaAction) {
needShareButton = true
}
}
if !needShareButton, let author = item.message.author as? TelegramUser, let _ = author.botInfo, !(item.message.media.first is TelegramMediaAction) {
needShareButton = true needShareButton = true
} }
if !needShareButton { if !needShareButton {

View File

@ -285,9 +285,12 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
})) }))
} }
dateAndStatusApply(false) dateAndStatusApply(false)
switch layoutData { switch layoutData {
case let .unconstrained(width): case let .unconstrained(width):
// let dateAndStatusOversized: Bool = videoFrame.maxX + dateAndStatusSize.width > width
strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: min(floor(videoFrame.midX) + 55.0, width - dateAndStatusSize.width - 4.0), y: videoFrame.height - dateAndStatusSize.height), size: dateAndStatusSize) strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: min(floor(videoFrame.midX) + 55.0, width - dateAndStatusSize.width - 4.0), y: videoFrame.height - dateAndStatusSize.height), size: dateAndStatusSize)
case let .constrained(_, right): case let .constrained(_, right):
strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: min(floor(videoFrame.midX) + 55.0, videoFrame.maxX + right - dateAndStatusSize.width - 4.0), y: videoFrame.maxY - dateAndStatusSize.height), size: dateAndStatusSize) strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: min(floor(videoFrame.midX) + 55.0, videoFrame.maxX + right - dateAndStatusSize.width - 4.0), y: videoFrame.maxY - dateAndStatusSize.height), size: dateAndStatusSize)

View File

@ -159,7 +159,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
} }
} }
} }
if !needShareButton, let author = item.message.author as? TelegramUser, let _ = author.botInfo { if !needShareButton, let author = item.message.author as? TelegramUser, let _ = author.botInfo, !item.message.media.isEmpty {
needShareButton = true needShareButton = true
} }
if !needShareButton { if !needShareButton {
@ -311,7 +311,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
strongSelf.replyInfoNode = replyInfoNode strongSelf.replyInfoNode = replyInfoNode
strongSelf.addSubnode(replyInfoNode) strongSelf.addSubnode(replyInfoNode)
} }
let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - replyInfoSize.width - layoutConstants.bubble.edgeInset - 10.0)), y: imageSize.height - replyInfoSize.height - 8.0), size: replyInfoSize) let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - replyInfoSize.width - layoutConstants.bubble.edgeInset - 10.0)), y: 8.0), size: replyInfoSize)
replyInfoNode.frame = replyInfoFrame replyInfoNode.frame = replyInfoFrame
strongSelf.replyBackgroundNode?.frame = CGRect(origin: CGPoint(x: replyInfoFrame.minX - 4.0, y: replyInfoFrame.minY - 2.0), size: CGSize(width: replyInfoFrame.size.width + 8.0, height: replyInfoFrame.size.height + 5.0)) strongSelf.replyBackgroundNode?.frame = CGRect(origin: CGPoint(x: replyInfoFrame.minX - 4.0, y: replyInfoFrame.minY - 2.0), size: CGSize(width: replyInfoFrame.size.width + 8.0, height: replyInfoFrame.size.height + 5.0))
} else if let replyInfoNode = strongSelf.replyInfoNode { } else if let replyInfoNode = strongSelf.replyInfoNode {

View File

@ -214,7 +214,9 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: strings.Conversation_PinnedMessage, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0))) let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: strings.Conversation_PinnedMessage, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0)))
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: descriptionStringForMessage(message, strings: strings, accountPeerId: accountPeerId).0, font: Font.regular(15.0), textColor: !message.media.isEmpty ? theme.chat.inputPanel.secondaryTextColor : theme.chat.inputPanel.primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0)))
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: descriptionStringForMessage(message, strings: strings, accountPeerId: accountPeerId).0, font: Font.regular(15.0), textColor: message.media.isEmpty || message.media.first is TelegramMediaWebpage ? theme.chat.inputPanel.primaryTextColor : theme.chat.inputPanel.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0)))
Queue.mainQueue().async { Queue.mainQueue().async {
if let strongSelf = self { if let strongSelf = self {

View File

@ -100,7 +100,7 @@ public class ComposeController: ViewController {
self.contactsNode.openCreateNewGroup = { [weak self] in self.contactsNode.openCreateNewGroup = { [weak self] in
if let strongSelf = self { if let strongSelf = self {
let controller = ContactMultiselectionController(account: strongSelf.account, mode: .groupCreation) let controller = ContactMultiselectionController(account: strongSelf.account, mode: .groupCreation, options: [])
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller) (strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
strongSelf.createActionDisposable.set((controller.result strongSelf.createActionDisposable.set((controller.result
|> deliverOnMainQueue).start(next: { [weak controller] peerIds in |> deliverOnMainQueue).start(next: { [weak controller] peerIds in

View File

@ -118,7 +118,7 @@ enum ContactListPeer: Equatable {
private enum ContactListNodeEntry: Comparable, Identifiable { private enum ContactListNodeEntry: Comparable, Identifiable {
case search(PresentationTheme, PresentationStrings) case search(PresentationTheme, PresentationStrings)
case option(Int, ContactListAdditionalOption, PresentationTheme, PresentationStrings) case option(Int, ContactListAdditionalOption, PresentationTheme, PresentationStrings)
case peer(Int, ContactListPeer, PeerPresence?, ListViewItemHeader?, ContactsPeerItemSelection, PresentationTheme, PresentationStrings, PresentationTimeFormat) case peer(Int, ContactListPeer, PeerPresence?, ListViewItemHeader?, ContactsPeerItemSelection, PresentationTheme, PresentationStrings, PresentationTimeFormat, Bool)
var stableId: ContactListNodeEntryId { var stableId: ContactListNodeEntryId {
switch self { switch self {
@ -126,7 +126,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
return .search return .search
case let .option(index, _, _, _): case let .option(index, _, _, _):
return .option(index: index) return .option(index: index)
case let .peer(_, peer, _, _, _, _, _, _): case let .peer(_, peer, _, _, _, _, _, _, _):
switch peer { switch peer {
case let .peer(peer, _): case let .peer(peer, _):
return .peerId(peer.id.toInt64()) return .peerId(peer.id.toInt64())
@ -144,7 +144,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
}) })
case let .option(_, option, theme, _): case let .option(_, option, theme, _):
return ContactListActionItem(theme: theme, title: option.title, icon: option.icon, action: option.action) return ContactListActionItem(theme: theme, title: option.title, icon: option.icon, action: option.action)
case let .peer(_, peer, presence, header, selection, theme, strings, timeFormat): case let .peer(_, peer, presence, header, selection, theme, strings, timeFormat, enabled):
let status: ContactsPeerItemStatus let status: ContactsPeerItemStatus
let itemPeer: ContactsPeerItemPeer let itemPeer: ContactsPeerItemPeer
switch peer { switch peer {
@ -161,7 +161,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
status = .none status = .none
itemPeer = .deviceContact(stableId: id, contact: contact) itemPeer = .deviceContact(stableId: id, contact: contact)
} }
return ContactsPeerItem(theme: theme, strings: strings, account: account, peerMode: .peer, peer: itemPeer, status: status, enabled: true, selection: selection, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in return ContactsPeerItem(theme: theme, strings: strings, account: account, peerMode: .peer, peer: itemPeer, status: status, enabled: enabled, selection: selection, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in
interaction.openPeer(peer) interaction.openPeer(peer)
}) })
} }
@ -181,9 +181,9 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
} else { } else {
return false return false
} }
case let .peer(lhsIndex, lhsPeer, lhsPresence, lhsHeader, lhsSelection, lhsTheme, lhsStrings, lhsTimeFormat): case let .peer(lhsIndex, lhsPeer, lhsPresence, lhsHeader, lhsSelection, lhsTheme, lhsStrings, lhsTimeFormat, lhsEnabled):
switch rhs { switch rhs {
case let .peer(rhsIndex, rhsPeer, rhsPresence, rhsHeader, rhsSelection, rhsTheme, rhsStrings, rhsTimeFormat): case let .peer(rhsIndex, rhsPeer, rhsPresence, rhsHeader, rhsSelection, rhsTheme, rhsStrings, rhsTimeFormat, rhsEnabled):
if lhsIndex != rhsIndex { if lhsIndex != rhsIndex {
return false return false
} }
@ -212,6 +212,9 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
if lhsTimeFormat != rhsTimeFormat { if lhsTimeFormat != rhsTimeFormat {
return false return false
} }
if lhsEnabled != rhsEnabled {
return false
}
return true return true
default: default:
return false return false
@ -232,11 +235,11 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
case .peer: case .peer:
return true return true
} }
case let .peer(lhsIndex, _, _, _, _, _, _, _): case let .peer(lhsIndex, _, _, _, _, _, _, _, _):
switch rhs { switch rhs {
case .search, .option: case .search, .option:
return false return false
case let .peer(rhsIndex, _, _, _, _, _, _, _): case let .peer(rhsIndex, _, _, _, _, _, _, _, _):
return lhsIndex < rhsIndex return lhsIndex < rhsIndex
} }
} }
@ -279,7 +282,7 @@ private extension PeerIndexNameRepresentation {
} }
} }
private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer], presences: [PeerId: PeerPresence], presentation: ContactListPresentation, selectionState: ContactListNodeGroupSelectionState?, theme: PresentationTheme, strings: PresentationStrings, timeFormat: PresentationTimeFormat) -> [ContactListNodeEntry] { private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer], presences: [PeerId: PeerPresence], presentation: ContactListPresentation, selectionState: ContactListNodeGroupSelectionState?, theme: PresentationTheme, strings: PresentationStrings, timeFormat: PresentationTimeFormat, disabledPeerIds:Set<PeerId>) -> [ContactListNodeEntry] {
var entries: [ContactListNodeEntry] = [] var entries: [ContactListNodeEntry] = []
var orderedPeers: [ContactListPeer] var orderedPeers: [ContactListPeer]
@ -409,7 +412,14 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer]
if case let .peer(peer, _) = orderedPeers[i] { if case let .peer(peer, _) = orderedPeers[i] {
presence = presences[peer.id] presence = presences[peer.id]
} }
entries.append(.peer(i, orderedPeers[i], presence, header, selection, theme, strings, timeFormat)) let enabled: Bool
switch orderedPeers[i] {
case let .peer(peer, _):
enabled = !disabledPeerIds.contains(peer.id)
default:
enabled = true
}
entries.append(.peer(i, orderedPeers[i], presence, header, selection, theme, strings, timeFormat, enabled))
} }
return entries return entries
} }
@ -474,20 +484,16 @@ struct ContactListNodeGroupSelectionState: Equatable {
} }
} }
struct ContactListFilter: OptionSet { enum ContactListFilter {
public var rawValue: Int32 case excludeSelf
case exclude([PeerId])
public init(rawValue: Int32) { case disable([PeerId])
self.rawValue = rawValue
}
public static let excludeSelf = ContactListFilter(rawValue: 1 << 1)
} }
final class ContactListNode: ASDisplayNode { final class ContactListNode: ASDisplayNode {
private let account: Account private let account: Account
private let presentation: ContactListPresentation private let presentation: ContactListPresentation
private let filter: ContactListFilter private let filters: [ContactListFilter]
let listNode: ListView let listNode: ListView
@ -537,10 +543,10 @@ final class ContactListNode: ASDisplayNode {
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings, PresentationTimeFormat)> private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings, PresentationTimeFormat)>
init(account: Account, presentation: ContactListPresentation, filter: ContactListFilter = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil) { init(account: Account, presentation: ContactListPresentation, filters: [ContactListFilter] = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil) {
self.account = account self.account = account
self.presentation = presentation self.presentation = presentation
self.filter = filter self.filters = filters
self.listNode = ListView() self.listNode = ListView()
@ -592,10 +598,20 @@ final class ContactListNode: ASDisplayNode {
|> mapToQueue { localPeers, remotePeers, deviceContacts, selectionState, themeAndStrings -> Signal<ContactsListNodeTransition, NoError> in |> mapToQueue { localPeers, remotePeers, deviceContacts, selectionState, themeAndStrings -> Signal<ContactsListNodeTransition, NoError> in
let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in
var existingPeerIds = Set<PeerId>() var existingPeerIds = Set<PeerId>()
var disabledPeerIds = Set<PeerId>()
var existingNormalizedPhoneNumbers = Set<DeviceContactNormalizedPhoneNumber>() var existingNormalizedPhoneNumbers = Set<DeviceContactNormalizedPhoneNumber>()
if filter.contains(.excludeSelf) { for filter in filters {
existingPeerIds.insert(account.peerId) switch filter {
case .excludeSelf:
existingPeerIds.insert(account.peerId)
case let .exclude(peerIds):
existingPeerIds = existingPeerIds.union(peerIds)
case let .disable(peerIds):
disabledPeerIds = disabledPeerIds.union(peerIds)
}
} }
var peers: [ContactListPeer] = [] var peers: [ContactListPeer] = []
for peer in localPeers { for peer in localPeers {
if !existingPeerIds.contains(peer.id) { if !existingPeerIds.contains(peer.id) {
@ -639,7 +655,7 @@ final class ContactListNode: ASDisplayNode {
peers.append(.deviceContact(stableId, contact)) peers.append(.deviceContact(stableId, contact))
} }
let entries = contactListNodeEntries(accountPeer: nil, peers: peers, presences: [:], presentation: presentation, selectionState: selectionState, theme: themeAndStrings.0, strings: themeAndStrings.1, timeFormat: themeAndStrings.2) let entries = contactListNodeEntries(accountPeer: nil, peers: peers, presences: [:], presentation: presentation, selectionState: selectionState, theme: themeAndStrings.0, strings: themeAndStrings.1, timeFormat: themeAndStrings.2, disabledPeerIds: disabledPeerIds)
let previous = previousEntries.swap(entries) let previous = previousEntries.swap(entries)
return .single(preparedContactListNodeTransition(account: account, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, animated: false)) return .single(preparedContactListNodeTransition(account: account, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, animated: false))
} }
@ -655,7 +671,31 @@ final class ContactListNode: ASDisplayNode {
transition = (combineLatest(self.contactPeersViewPromise.get(), selectionStateSignal, themeAndStringsPromise.get()) transition = (combineLatest(self.contactPeersViewPromise.get(), selectionStateSignal, themeAndStringsPromise.get())
|> mapToQueue { view, selectionState, themeAndStrings -> Signal<ContactsListNodeTransition, NoError> in |> mapToQueue { view, selectionState, themeAndStrings -> Signal<ContactsListNodeTransition, NoError> in
let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in
let entries = contactListNodeEntries(accountPeer: view.accountPeer, peers: view.peers.map({ ContactListPeer.peer(peer: $0, isGlobal: false) }), presences: view.peerPresences, presentation: presentation, selectionState: selectionState, theme: themeAndStrings.0, strings: themeAndStrings.1, timeFormat: themeAndStrings.2)
var peers = view.peers.map({ ContactListPeer.peer(peer: $0, isGlobal: false) })
var existingPeerIds = Set<PeerId>()
var disabledPeerIds = Set<PeerId>()
for filter in filters {
switch filter {
case .excludeSelf:
existingPeerIds.insert(account.peerId)
case let .exclude(peerIds):
existingPeerIds = existingPeerIds.union(peerIds)
case let .disable(peerIds):
disabledPeerIds = disabledPeerIds.union(peerIds)
}
}
peers = peers.filter { contact in
switch contact {
case let .peer(peer, _):
return !existingPeerIds.contains(peer.id)
default:
return true
}
}
let entries = contactListNodeEntries(accountPeer: view.accountPeer, peers: peers, presences: view.peerPresences, presentation: presentation, selectionState: selectionState, theme: themeAndStrings.0, strings: themeAndStrings.1, timeFormat: themeAndStrings.2, disabledPeerIds: disabledPeerIds)
let previous = previousEntries.swap(entries) let previous = previousEntries.swap(entries)
let animated: Bool let animated: Bool
if let previous = previous { if let previous = previous {

View File

@ -21,6 +21,9 @@ class ContactMultiselectionController: ViewController {
return self.displayNode as! ContactMultiselectionControllerNode return self.displayNode as! ContactMultiselectionControllerNode
} }
var dismissed: (() -> Void)?
private let index: PeerNameIndex = .lastNameFirst private let index: PeerNameIndex = .lastNameFirst
private var _ready = Promise<Bool>() private var _ready = Promise<Bool>()
@ -57,11 +60,13 @@ class ContactMultiselectionController: ViewController {
private var limitsConfiguration: LimitsConfiguration? private var limitsConfiguration: LimitsConfiguration?
private var limitsConfigurationDisposable: Disposable? private var limitsConfigurationDisposable: Disposable?
private let options: [ContactListAdditionalOption]
init(account: Account, mode: ContactMultiselectionControllerMode) { private let filters: [ContactListFilter]
init(account: Account, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter] = [.excludeSelf]) {
self.account = account self.account = account
self.mode = mode self.mode = mode
self.options = options
self.filters = filters
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
self.titleView = CounterContollerTitleView(theme: self.presentationData.theme) self.titleView = CounterContollerTitleView(theme: self.presentationData.theme)
@ -148,7 +153,7 @@ class ContactMultiselectionController: ViewController {
} }
override func loadDisplayNode() { override func loadDisplayNode() {
self.displayNode = ContactMultiselectionControllerNode(account: self.account) self.displayNode = ContactMultiselectionControllerNode(account: self.account, options: self.options, filters: filters)
self._listReady.set(self.contactsNode.contactListNode.ready) self._listReady.set(self.contactsNode.contactListNode.ready)
self.contactsNode.dismiss = { [weak self] in self.contactsNode.dismiss = { [weak self] in
@ -296,7 +301,16 @@ class ContactMultiselectionController: ViewController {
} }
override open func dismiss(completion: (() -> Void)? = nil) { override open func dismiss(completion: (() -> Void)? = nil) {
self.contactsNode.animateOut(completion: completion) if let presentationArguments = self.presentationArguments as? ViewControllerPresentationArguments {
switch presentationArguments.presentationAnimation {
case .modalSheet:
self.dismissed?()
self.contactsNode.animateOut(completion: completion)
case .none:
self.dismissed?()
completion?()
}
}
} }
override func viewDidDisappear(_ animated: Bool) { override func viewDidDisappear(_ animated: Bool) {
@ -313,6 +327,7 @@ class ContactMultiselectionController: ViewController {
@objc func cancelPressed() { @objc func cancelPressed() {
self._result.set(.single([])) self._result.set(.single([]))
self.dismiss()
} }
@objc func rightNavigationButtonPressed() { @objc func rightNavigationButtonPressed() {

View File

@ -39,17 +39,16 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
var editableTokens: [EditableTokenListToken] = [] var editableTokens: [EditableTokenListToken] = []
private let searchResultsReadyDisposable = MetaDisposable() private let searchResultsReadyDisposable = MetaDisposable()
var dismiss: (() -> Void)? var dismiss: (() -> Void)?
private var presentationData: PresentationData private var presentationData: PresentationData
private var presentationDataDisposable: Disposable? private var presentationDataDisposable: Disposable?
init(account: Account) { init(account: Account, options: [ContactListAdditionalOption], filters: [ContactListFilter]) {
self.account = account self.account = account
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
self.contactListNode = ContactListNode(account: account, presentation: .natural(displaySearch: false, options: []), selectionState: ContactListNodeGroupSelectionState()) self.contactListNode = ContactListNode(account: account, presentation: .natural(displaySearch: false, options: options), filters: filters, selectionState: ContactListNodeGroupSelectionState())
self.tokenListNode = EditableTokenListNode(theme: EditableTokenListNodeTheme(backgroundColor: self.presentationData.theme.rootController.navigationBar.backgroundColor, separatorColor: self.presentationData.theme.rootController.navigationBar.separatorColor, placeholderTextColor: self.presentationData.theme.list.itemPlaceholderTextColor, primaryTextColor: self.presentationData.theme.list.itemPrimaryTextColor, selectedTextColor: self.presentationData.theme.list.itemAccentColor, keyboardColor: self.presentationData.theme.chatList.searchBarKeyboardColor), placeholder: self.presentationData.strings.Compose_TokenListPlaceholder) self.tokenListNode = EditableTokenListNode(theme: EditableTokenListNodeTheme(backgroundColor: self.presentationData.theme.rootController.navigationBar.backgroundColor, separatorColor: self.presentationData.theme.rootController.navigationBar.separatorColor, placeholderTextColor: self.presentationData.theme.list.itemPlaceholderTextColor, primaryTextColor: self.presentationData.theme.list.itemPrimaryTextColor, selectedTextColor: self.presentationData.theme.list.itemAccentColor, keyboardColor: self.presentationData.theme.chatList.searchBarKeyboardColor), placeholder: self.presentationData.strings.Compose_TokenListPlaceholder)
super.init() super.init()
@ -88,7 +87,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
selectionState = state selectionState = state
return state return state
} }
let searchResultsNode = ContactListNode(account: account, presentation: .search(signal: searchText.get(), searchDeviceContacts: false), selectionState: selectionState) let searchResultsNode = ContactListNode(account: account, presentation: .search(signal: searchText.get(), searchDeviceContacts: false), filters: filters, selectionState: selectionState)
searchResultsNode.openPeer = { peer in searchResultsNode.openPeer = { peer in
self?.tokenListNode.setText("") self?.tokenListNode.setText("")
self?.openPeer?(peer) self?.openPeer?(peer)

View File

@ -118,7 +118,7 @@ final class ContactsSearchContainerNode: SearchDisplayControllerContentNode {
private var containerViewLayout: (ContainerViewLayout, CGFloat)? private var containerViewLayout: (ContainerViewLayout, CGFloat)?
private var enqueuedTransitions: [ContactListSearchContainerTransition] = [] private var enqueuedTransitions: [ContactListSearchContainerTransition] = []
init(account: Account, onlyWriteable: Bool, categories: ContactsSearchCategories, filter: ContactListFilter = [.excludeSelf], openPeer: @escaping (ContactListPeer) -> Void) { init(account: Account, onlyWriteable: Bool, categories: ContactsSearchCategories, filters: [ContactListFilter] = [.excludeSelf], openPeer: @escaping (ContactListPeer) -> Void) {
self.account = account self.account = account
self.openPeer = openPeer self.openPeer = openPeer
@ -173,8 +173,16 @@ final class ContactsSearchContainerNode: SearchDisplayControllerContentNode {
|> map { localPeers, remotePeers, deviceContacts, themeAndStrings -> [ContactListSearchEntry] in |> map { localPeers, remotePeers, deviceContacts, themeAndStrings -> [ContactListSearchEntry] in
var entries: [ContactListSearchEntry] = [] var entries: [ContactListSearchEntry] = []
var existingPeerIds = Set<PeerId>() var existingPeerIds = Set<PeerId>()
if filter.contains(.excludeSelf) { var disabledPeerIds = Set<PeerId>()
existingPeerIds.insert(account.peerId) for filter in filters {
switch filter {
case .excludeSelf:
existingPeerIds.insert(account.peerId)
case let .exclude(peerIds):
existingPeerIds = existingPeerIds.union(peerIds)
case let .disable(peerIds):
disabledPeerIds = disabledPeerIds.union(peerIds)
}
} }
var existingNormalizedPhoneNumbers = Set<DeviceContactNormalizedPhoneNumber>() var existingNormalizedPhoneNumbers = Set<DeviceContactNormalizedPhoneNumber>()
var index = 0 var index = 0

View File

@ -203,7 +203,12 @@ public func createChannelController(account: Account) -> ViewController {
let arguments = CreateChannelArguments(account: account, updateEditingName: { editingName in let arguments = CreateChannelArguments(account: account, updateEditingName: { editingName in
updateState { current in updateState { current in
var current = current var current = current
current.editingName = editingName switch editingName {
case let .title(title, type):
current.editingName = .title(title: String(title.prefix(255)), type: type)
case let .personName(firstName, lastName):
current.editingName = .personName(firstName: String(firstName.prefix(255)), lastName: String(lastName.prefix(255)))
}
return current return current
} }
}, updateEditingDescriptionText: { text in }, updateEditingDescriptionText: { text in

View File

@ -120,7 +120,7 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr
unreadBadgeActiveTextColor: .white, unreadBadgeActiveTextColor: .white,
unreadBadgeInactiveBackgroundColor: UIColor(rgb: 0xb6b6bb), unreadBadgeInactiveBackgroundColor: UIColor(rgb: 0xb6b6bb),
unreadBadgeInactiveTextColor: .white, unreadBadgeInactiveTextColor: .white,
pinnedBadgeColor: UIColor(rgb: 0x939399), pinnedBadgeColor: UIColor(rgb: 0xb6b6bb),
pinnedSearchBarColor: UIColor(rgb: 0xe5e5e5), pinnedSearchBarColor: UIColor(rgb: 0xe5e5e5),
regularSearchBarColor: UIColor(rgb: 0xe9e9e9), regularSearchBarColor: UIColor(rgb: 0xe9e9e9),
sectionHeaderFillColor: UIColor(rgb: 0xf7f7f7), sectionHeaderFillColor: UIColor(rgb: 0xf7f7f7),
@ -310,7 +310,7 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr
actionControlFillColor: accentColor, actionControlFillColor: accentColor,
actionControlForegroundColor: .white, actionControlForegroundColor: .white,
primaryTextColor: .black, primaryTextColor: .black,
secondaryTextColor: UIColor(rgb: 0x5e5e5e), secondaryTextColor: UIColor(rgb: 0x8e8e93),
mediaRecordingDotColor: UIColor(rgb: 0xed2521), mediaRecordingDotColor: UIColor(rgb: 0xed2521),
keyboardColor: .light, keyboardColor: .light,
mediaRecordingControl: inputPanelMediaRecordingControl mediaRecordingControl: inputPanelMediaRecordingControl

View File

@ -911,8 +911,30 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa
} }
} }
} else if let channel = view.peers[view.peerId] as? TelegramChannel, let cachedChannelData = view.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount { } else if let channel = view.peers[view.peerId] as? TelegramChannel, let cachedChannelData = view.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount {
let updatedParticipants = channelMembers var updatedParticipants = channelMembers
let disabledPeerIds = state.removingParticipantIds let existingParticipantIds = Set(updatedParticipants.map { $0.peer.id })
var peerPresences: [PeerId: PeerPresence] = view.peerPresences
var peers: [PeerId: Peer] = view.peers
var disabledPeerIds = state.removingParticipantIds
if !state.temporaryParticipants.isEmpty {
for participant in state.temporaryParticipants {
if !existingParticipantIds.contains(participant.peer.id) {
updatedParticipants.append(RenderedChannelParticipant(participant: ChannelParticipant.member(id: participant.peer.id, invitedAt: participant.timestamp, adminInfo: nil, banInfo: nil), peer: participant.peer))
if let presence = participant.presence, peerPresences[participant.peer.id] == nil {
peerPresences[participant.peer.id] = presence
}
if peers[participant.peer.id] == nil {
peers[participant.peer.id] = participant.peer
}
//disabledPeerIds.insert(participant.peer.id)
}
}
}
let sortedParticipants: [RenderedChannelParticipant] let sortedParticipants: [RenderedChannelParticipant]
if memberCount < 200 { if memberCount < 200 {
@ -1307,8 +1329,21 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
} }
} }
}, addMember: { }, addMember: {
let _ = (account.postbox.loadedPeerWithId(peerId)
|> deliverOnMainQueue).start(next: { groupPeer in let members: Promise<[PeerId]> = Promise()
if peerId.namespace == Namespaces.Peer.CloudChannel {
var membersDisposable: Disposable?
let (disposable, _) = account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.recent(postbox: account.postbox, network: account.network, peerId: peerId, updated: { listState in
members.set(.single(listState.list.map {$0.peer.id}))
membersDisposable?.dispose()
})
membersDisposable = disposable
} else {
members.set(.single([]))
}
let _ = (combineLatest(account.postbox.loadedPeerWithId(peerId)
|> deliverOnMainQueue, members.get() |> take(1) |> deliverOnMainQueue)).start(next: { groupPeer, recentIds in
var confirmationImpl: ((PeerId) -> Signal<Bool, NoError>)? var confirmationImpl: ((PeerId) -> Signal<Bool, NoError>)?
var options: [ContactListAdditionalOption] = [] var options: [ContactListAdditionalOption] = []
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
@ -1317,13 +1352,22 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
inviteByLinkImpl?() inviteByLinkImpl?()
})) }))
let contactsController = ContactSelectionController(account: account, autoDismiss: false, title: { $0.GroupInfo_AddParticipantTitle }, options: options, confirmation: { peer in let contactsController: ViewController
if let confirmationImpl = confirmationImpl, case let .peer(peer, _) = peer { if peerId.namespace == Namespaces.Peer.CloudGroup {
return confirmationImpl(peer.id) contactsController = ContactSelectionController(account: account, autoDismiss: false, title: { $0.GroupInfo_AddParticipantTitle }, options: options, confirmation: { peer in
} else { if let confirmationImpl = confirmationImpl, case let .peer(peer, _) = peer {
return .single(false) return confirmationImpl(peer.id)
} } else {
}) return .single(false)
}
})
} else {
contactsController = ContactMultiselectionController(account: account, mode: .peerSelection, options: options, filters: [.excludeSelf, .disable(recentIds)])
}
confirmationImpl = { [weak contactsController] peerId in confirmationImpl = { [weak contactsController] peerId in
return account.postbox.loadedPeerWithId(peerId) return account.postbox.loadedPeerWithId(peerId)
|> deliverOnMainQueue |> deliverOnMainQueue
@ -1409,27 +1453,84 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
return .complete() return .complete()
} }
} }
let addMembers: ([ContactListPeerId]) -> Signal<Void, NoError> = { members -> Signal<Void, NoError> in
let memberIds = members.compactMap { contact -> PeerId? in
switch contact {
case let .peer(peerId):
return peerId
default:
return nil
}
}
return account.postbox.multiplePeersView(memberIds)
|> take(1)
|> deliverOnMainQueue
|> mapToSignal { view -> Signal<Void, NoError> in
updateState { state in
var state = state
for (memberId, peer) in view.peers {
var found = false
for participant in state.temporaryParticipants {
if participant.peer.id == memberId {
found = true
break
}
}
if !found {
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
var temporaryParticipants = state.temporaryParticipants
temporaryParticipants.append(TemporaryParticipant(peer: peer, presence: view.presences[memberId], timestamp: timestamp))
state = state.withUpdatedTemporaryParticipants(temporaryParticipants)
}
}
return state
}
return account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.addMembers(account: account, peerId: peerId, memberIds: memberIds)
}
}
inviteByLinkImpl = { [weak contactsController] in inviteByLinkImpl = { [weak contactsController] in
contactsController?.dismiss() contactsController?.dismiss()
presentControllerImpl?(channelVisibilityController(account: account, peerId: peerId, mode: .privateLink), ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet)) presentControllerImpl?(channelVisibilityController(account: account, peerId: peerId, mode: .privateLink), ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet))
} }
selectAddMemberDisposable.set((contactsController.result
|> deliverOnMainQueue).start(next: { [weak contactsController] memberPeer in
guard let memberPeer = memberPeer else {
return
}
contactsController?.displayProgress = true
addMemberDisposable.set((addMember(memberPeer)
|> deliverOnMainQueue).start(completed: {
contactsController?.dismiss()
}))
}))
presentControllerImpl?(contactsController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) presentControllerImpl?(contactsController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
contactsController.dismissed = { if let contactsController = contactsController as? ContactSelectionController {
selectAddMemberDisposable.set(nil) selectAddMemberDisposable.set((contactsController.result
addMemberDisposable.set(nil) |> deliverOnMainQueue).start(next: { [weak contactsController] memberPeer in
guard let memberPeer = memberPeer else {
return
}
contactsController?.displayProgress = true
addMemberDisposable.set((addMember(memberPeer)
|> deliverOnMainQueue).start(completed: {
contactsController?.dismiss()
}))
}))
contactsController.dismissed = {
selectAddMemberDisposable.set(nil)
addMemberDisposable.set(nil)
}
}
if let contactsController = contactsController as? ContactMultiselectionController {
selectAddMemberDisposable.set((contactsController.result
|> deliverOnMainQueue).start(next: { [weak contactsController] peers in
contactsController?.displayProgress = true
addMemberDisposable.set((addMembers(peers)
|> deliverOnMainQueue).start(completed: {
contactsController?.dismiss()
}))
}))
contactsController.dismissed = {
selectAddMemberDisposable.set(nil)
addMemberDisposable.set(nil)
}
} }
}) })
}, promotePeer: { participant in }, promotePeer: { participant in

View File

@ -77,13 +77,13 @@ private let iconTextBackgroundImage = generateStretchableFilledCircleImage(radiu
final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode { final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode {
private let imageNodeBackground: ASDisplayNode private let imageNodeBackground: ASDisplayNode
private let imageNode: TransformImageNode private let imageNode: TransformImageNode
private var statusNode: RadialStatusNode?
private var videoLayer: (SoftwareVideoThumbnailLayer, SoftwareVideoLayerFrameManager, SampleBufferLayer)? private var videoLayer: (SoftwareVideoThumbnailLayer, SoftwareVideoLayerFrameManager, SampleBufferLayer)?
private var currentImageResource: TelegramMediaResource? private var currentImageResource: TelegramMediaResource?
private var currentVideoFile: TelegramMediaFile? private var currentVideoFile: TelegramMediaFile?
private var resourceStatus: MediaResourceStatus? private var resourceStatus: MediaResourceStatus?
private(set) var item: HorizontalListContextResultsChatInputPanelItem? private(set) var item: HorizontalListContextResultsChatInputPanelItem?
private var statusDisposable = MetaDisposable() private var statusDisposable = MetaDisposable()
private let statusNode: RadialStatusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5))
override var visibility: ListViewItemNodeVisibility { override var visibility: ListViewItemNodeVisibility {
@ -354,6 +354,11 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
let progressFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((nodeLayout.contentSize.width - 37) / 2), y: floorToScreenPixels((nodeLayout.contentSize.height - 37) / 2)), size: CGSize(width: 37, height: 37)) let progressFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((nodeLayout.contentSize.width - 37) / 2), y: floorToScreenPixels((nodeLayout.contentSize.height - 37) / 2)), size: CGSize(width: 37, height: 37))
strongSelf.statusNode.removeFromSupernode()
strongSelf.addSubnode(strongSelf.statusNode)
strongSelf.statusNode.frame = progressFrame
if let updatedStatusSignal = updatedStatusSignal { if let updatedStatusSignal = updatedStatusSignal {
strongSelf.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak strongSelf] status in strongSelf.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak strongSelf] status in
@ -361,21 +366,13 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
if let strongSelf = strongSelf { if let strongSelf = strongSelf {
strongSelf.resourceStatus = status strongSelf.resourceStatus = status
if strongSelf.statusNode == nil {
let statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5))
strongSelf.statusNode = statusNode
strongSelf.addSubnode(statusNode)
}
strongSelf.statusNode?.frame = progressFrame
let state: RadialStatusNodeState let state: RadialStatusNodeState
let statusForegroundColor: UIColor = .white let statusForegroundColor: UIColor = .white
switch status { switch status {
case let .Fetching(_, progress): case let .Fetching(_, progress):
state = RadialStatusNodeState.progress(color: statusForegroundColor, lineWidth: nil, value: CGFloat(progress), cancelEnabled: false) state = .progress(color: statusForegroundColor, lineWidth: nil, value: CGFloat(max(progress, 0.2)), cancelEnabled: false)
case .Remote: case .Remote:
state = .download(statusForegroundColor) state = .download(statusForegroundColor)
case .Local: case .Local:
@ -383,19 +380,12 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
} }
if let statusNode = strongSelf.statusNode { strongSelf.statusNode.transitionToState(state, completion: { })
if state == .none {
strongSelf.statusNode = nil
}
statusNode.transitionToState(state, completion: { [weak statusNode] in
if state == .none {
statusNode?.removeFromSupernode()
}
})
}
} }
} }
})) }))
} else {
strongSelf.statusNode.transitionToState(.none, completion: { })
} }
if let (thumbnailLayer, _, layer) = strongSelf.videoLayer { if let (thumbnailLayer, _, layer) = strongSelf.videoLayer {

View File

@ -696,6 +696,8 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNode, Ite
strongSelf.inputSeparator = inputSeparator strongSelf.inputSeparator = inputSeparator
} }
//let title = title.prefix(255)
if strongSelf.inputFirstField == nil { if strongSelf.inputFirstField == nil {
let inputFirstField = TextFieldNodeView() let inputFirstField = TextFieldNodeView()
inputFirstField.font = Font.regular(17.0) inputFirstField.font = Font.regular(17.0)
@ -710,12 +712,12 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNode, Ite
placeholder = item.strings.GroupInfo_ChannelListNamePlaceholder placeholder = item.strings.GroupInfo_ChannelListNamePlaceholder
} }
inputFirstField.attributedPlaceholder = NSAttributedString(string: placeholder, font: Font.regular(19.0), textColor: item.theme.list.itemPlaceholderTextColor) inputFirstField.attributedPlaceholder = NSAttributedString(string: placeholder, font: Font.regular(19.0), textColor: item.theme.list.itemPlaceholderTextColor)
inputFirstField.attributedText = NSAttributedString(string: title, font: Font.regular(19.0), textColor: item.theme.list.itemPrimaryTextColor) inputFirstField.attributedText = NSAttributedString(string: String(title), font: Font.regular(19.0), textColor: item.theme.list.itemPrimaryTextColor)
strongSelf.inputFirstField = inputFirstField strongSelf.inputFirstField = inputFirstField
strongSelf.view.addSubview(inputFirstField) strongSelf.view.addSubview(inputFirstField)
inputFirstField.addTarget(self, action: #selector(strongSelf.textFieldDidChange(_:)), for: .editingChanged) inputFirstField.addTarget(self, action: #selector(strongSelf.textFieldDidChange(_:)), for: .editingChanged)
} else if strongSelf.inputFirstField?.text != title { } else if strongSelf.inputFirstField?.text != String(title) {
strongSelf.inputFirstField?.text = title strongSelf.inputFirstField?.text = String(title)
} }
strongSelf.inputSeparator?.frame = CGRect(origin: CGPoint(x: params.leftInset + 100.0, y: 62.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 100.0, height: separatorHeight)) strongSelf.inputSeparator?.frame = CGRect(origin: CGPoint(x: params.leftInset + 100.0, y: 62.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 100.0, height: separatorHeight))

View File

@ -251,8 +251,6 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) {
unsigned char peerTag[16]; unsigned char peerTag[16];
[connection.peerTag getBytes:peerTag length:16]; [connection.peerTag getBytes:peerTag length:16];
endpoints.push_back(tgvoip::Endpoint(connection.connectionId, (uint16_t)connection.port, address, addressv6, tgvoip::Endpoint::TYPE_UDP_RELAY, peerTag)); endpoints.push_back(tgvoip::Endpoint(connection.connectionId, (uint16_t)connection.port, address, addressv6, tgvoip::Endpoint::TYPE_UDP_RELAY, peerTag));
/*releasable*/
//endpoints.push_back(tgvoip::Endpoint(connection.connectionId, (uint16_t)connection.port, address, addressv6, EP_TYPE_UDP_RELAY, peerTag));
} }
tgvoip::VoIPController::Config config(_callConnectTimeout, _callPacketTimeout, _dataSavingMode, false, true, true); tgvoip::VoIPController::Config config(_callConnectTimeout, _callPacketTimeout, _dataSavingMode, false, true, true);
@ -270,9 +268,9 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) {
} }
- (void)stop { - (void)stop {
if (_controller) { if (_controller != nil) {
char *buffer = (char *)malloc(_controller->GetDebugLogLength()); char *buffer = (char *)malloc(_controller->GetDebugLogLength());
/*releasable*/
_controller->Stop(); _controller->Stop();
_controller->GetDebugLog(buffer); _controller->GetDebugLog(buffer);
NSString *debugLog = [[NSString alloc] initWithUTF8String:buffer]; NSString *debugLog = [[NSString alloc] initWithUTF8String:buffer];
@ -296,7 +294,6 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) {
- (void)controllerStateChanged:(int)state { - (void)controllerStateChanged:(int)state {
OngoingCallState callState = OngoingCallStateInitializing; OngoingCallState callState = OngoingCallStateInitializing;
/*releasable*/
switch (state) { switch (state) {
case tgvoip::STATE_ESTABLISHED: case tgvoip::STATE_ESTABLISHED:
callState = OngoingCallStateConnected; callState = OngoingCallStateConnected;
@ -307,16 +304,6 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) {
default: default:
break; break;
} }
/*switch (state) {
case STATE_ESTABLISHED:
callState = OngoingCallStateConnected;
break;
case STATE_FAILED:
callState = OngoingCallStateFailed;
break;
default:
break;
}*/
if (callState != _state) { if (callState != _state) {
_state = callState; _state = callState;
@ -328,13 +315,17 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) {
} }
- (void)setIsMuted:(bool)isMuted { - (void)setIsMuted:(bool)isMuted {
_controller->SetMicMute(isMuted); if (_controller != nil) {
_controller->SetMicMute(isMuted);
}
} }
- (void)setNetworkType:(OngoingCallNetworkType)networkType { - (void)setNetworkType:(OngoingCallNetworkType)networkType {
if (_networkType != networkType) { if (_networkType != networkType) {
_networkType = networkType; _networkType = networkType;
_controller->SetNetworkType(callControllerNetworkTypeForType(networkType)); if (_controller != nil) {
_controller->SetNetworkType(callControllerNetworkTypeForType(networkType));
}
} }
} }

View File

@ -327,6 +327,7 @@ func openChatMessage(account: Account, message: Message, standalone: Bool, rever
return true return true
} }
case let .chatAvatars(controller, media): case let .chatAvatars(controller, media):
dismissInput()
chatAvatarHiddenMedia(controller.hiddenMedia |> map { value -> MessageId? in chatAvatarHiddenMedia(controller.hiddenMedia |> map { value -> MessageId? in
if value != nil { if value != nil {
return message.id return message.id

View File

@ -8,6 +8,38 @@ enum PeerChannelMemberContextKey: Hashable {
case recentSearch(String) case recentSearch(String)
case admins(String?) case admins(String?)
case restrictedAndBanned(String?) case restrictedAndBanned(String?)
var hashValue: Int {
return 0
}
static func ==(lhs: PeerChannelMemberContextKey, rhs: PeerChannelMemberContextKey) -> Bool {
switch lhs {
case .recent:
if case .recent = rhs {
return true
} else {
return false
}
case let .recentSearch(query):
if case .recentSearch(query) = rhs {
return true
} else {
return false
}
case let .admins(query):
if case .admins(query) = rhs {
return true
} else {
return false
}
case let .restrictedAndBanned(query):
if case .restrictedAndBanned(query) = rhs {
return true
} else {
return false
}
}
}
} }
private final class PeerChannelMemberCategoriesContextsManagerImpl { private final class PeerChannelMemberCategoriesContextsManagerImpl {
@ -161,4 +193,22 @@ final class PeerChannelMemberCategoriesContextsManager {
return .complete() return .complete()
} }
} }
func addMembers(account: Account, peerId: PeerId, memberIds: [PeerId]) -> Signal<Void, NoError> {
return addChannelMembers(account: account, peerId: peerId, memberIds: memberIds) |> deliverOnMainQueue
|> beforeNext { [weak self] result in
if let strongSelf = self {
strongSelf.impl.with { impl in
for (contextPeerId, context) in impl.contexts {
if peerId == contextPeerId {
context.reset(.recent)
}
}
}
}
}
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
}
} }

View File

@ -553,7 +553,7 @@ public class PeerMediaCollectionController: TelegramController {
} }
self.mediaCollectionDisplayNode.selectedMessages = updatedInterfaceState.selectionState?.selectedIds self.mediaCollectionDisplayNode.selectedMessages = updatedInterfaceState.selectionState?.selectedIds
view.disablesInteractiveTransitionGestureRecognizer = updatedInterfaceState.selectionState != nil view.disablesInteractiveTransitionGestureRecognizer = updatedInterfaceState.selectionState != nil && self.mediaCollectionDisplayNode.historyNode is ChatHistoryGridNode
} }
} }
} }

View File

@ -12,6 +12,7 @@ enum PeerReportSubject {
private enum PeerReportOption { private enum PeerReportOption {
case spam case spam
case violence case violence
case copyright
case pornoghraphy case pornoghraphy
case other case other
} }
@ -24,6 +25,7 @@ func peerReportOptionsController(account: Account, subject: PeerReportSubject, p
.spam, .spam,
.violence, .violence,
.pornoghraphy, .pornoghraphy,
.copyright,
.other .other
] ]
@ -37,8 +39,8 @@ func peerReportOptionsController(account: Account, subject: PeerReportSubject, p
title = presentationData.strings.ReportPeer_ReasonViolence title = presentationData.strings.ReportPeer_ReasonViolence
case .pornoghraphy: case .pornoghraphy:
title = presentationData.strings.ReportPeer_ReasonPornography title = presentationData.strings.ReportPeer_ReasonPornography
/*case .copyright: case .copyright:
title = presentationData.strings.ReportPeer_ReasonCopyright*/ title = presentationData.strings.ReportPeer_ReasonCopyright
case .other: case .other:
title = presentationData.strings.ReportPeer_ReasonOther title = presentationData.strings.ReportPeer_ReasonOther
} }
@ -52,6 +54,8 @@ func peerReportOptionsController(account: Account, subject: PeerReportSubject, p
reportReason = .violence reportReason = .violence
case .pornoghraphy: case .pornoghraphy:
reportReason = .porno reportReason = .porno
case .copyright:
reportReason = .copyright
case .other: case .other:
break break
} }
@ -60,12 +64,18 @@ func peerReportOptionsController(account: Account, subject: PeerReportSubject, p
case let .peer(peerId): case let .peer(peerId):
let _ = (reportPeer(account: account, peerId: peerId, reason: reportReason) let _ = (reportPeer(account: account, peerId: peerId, reason: reportReason)
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
present(OverlayStatusController(theme: presentationData.theme, type: .success), nil) let alert = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction.init(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {
})])
present(alert, nil)
}) })
case let .messages(messageIds): case let .messages(messageIds):
let _ = (reportPeerMessages(account: account, messageIds: messageIds, reason: reportReason) let _ = (reportPeerMessages(account: account, messageIds: messageIds, reason: reportReason)
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
present(OverlayStatusController(theme: presentationData.theme, type: .success), nil) let alert = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction.init(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {
})])
present(alert, nil)
}) })
} }
} else { } else {

View File

@ -168,7 +168,7 @@ public final class SecretMediaPreviewController: ViewController {
self.screenCaptureEventsDisposable = (screenCaptureEvents() self.screenCaptureEventsDisposable = (screenCaptureEvents()
|> deliverOnMainQueue).start(next: { [weak self] _ in |> deliverOnMainQueue).start(next: { [weak self] _ in
if let _ = self { if let strongSelf = self, strongSelf.traceVisibility() {
let _ = addSecretChatMessageScreenshot(account: account, peerId: messageId.peerId).start() let _ = addSecretChatMessageScreenshot(account: account, peerId: messageId.peerId).start()
} }
}) })

View File

@ -245,7 +245,7 @@ public func selectivePrivacyPeersController(account: Account, title: String, ini
removePeerDisposable.set(applyPeers.start()) removePeerDisposable.set(applyPeers.start())
}, addPeer: { }, addPeer: {
let controller = ContactMultiselectionController(account: account, mode: .peerSelection) let controller = ContactMultiselectionController(account: account, mode: .peerSelection, options: [])
addPeerDisposable.set((controller.result |> take(1) |> deliverOnMainQueue).start(next: { [weak controller] peerIds in addPeerDisposable.set((controller.result |> take(1) |> deliverOnMainQueue).start(next: { [weak controller] peerIds in
let applyPeers: Signal<Void, NoError> = peersPromise.get() let applyPeers: Signal<Void, NoError> = peersPromise.get()
|> take(1) |> take(1)

View File

@ -35,15 +35,18 @@ private func imageFromAJpeg(data: Data) -> (UIImage, UIImage)? {
} }
private func chatMessageStickerDatas(account: Account, file: TelegramMediaFile, small: Bool, fetched: Bool, onlyFullSize: Bool) -> Signal<(Data?, Data?, Bool), NoError> { private func chatMessageStickerDatas(account: Account, file: TelegramMediaFile, small: Bool, fetched: Bool, onlyFullSize: Bool) -> Signal<(Data?, Data?, Bool), NoError> {
let maybeFetched = account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedStickerAJpegRepresentation(size: small ? CGSize(width: 160.0, height: 160.0) : nil), complete: onlyFullSize) let maybeFetched = account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedStickerAJpegRepresentation(size: small ? CGSize(width: 160.0, height: 160.0) : nil), complete: false)
return maybeFetched |> take(1) |> mapToSignal { maybeData in return maybeFetched
|> take(1)
|> mapToSignal { maybeData in
if maybeData.complete { if maybeData.complete {
let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: [])
return .single((nil, loadedData, true)) return .single((nil, loadedData, true))
} else { } else {
let fullSizeData = account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedStickerAJpegRepresentation(size: small ? CGSize(width: 160.0, height: 160.0) : nil), complete: false) |> map { next in let fullSizeData = account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedStickerAJpegRepresentation(size: small ? CGSize(width: 160.0, height: 160.0) : nil), complete: onlyFullSize)
|> map { next in
return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe), next.complete) return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe), next.complete)
} }
@ -66,7 +69,8 @@ private func chatMessageStickerDatas(account: Account, file: TelegramMediaFile,
} }
} }
} else { } else {
return fullSizeData |> map { (data, complete) -> (Data?, Data?, Bool) in return fullSizeData
|> map { (data, complete) -> (Data?, Data?, Bool) in
return (nil, data, complete) return (nil, data, complete)
} }
} }

View File

@ -117,7 +117,7 @@ public func transformOutgoingMessageMedia(postbox: Postbox, network: Network, me
case let image as TelegramMediaImage: case let image as TelegramMediaImage:
if let representation = largestImageRepresentation(image.representations) { if let representation = largestImageRepresentation(image.representations) {
let signal = Signal<MediaResourceData, NoError> { subscriber in let signal = Signal<MediaResourceData, NoError> { subscriber in
let fetch = postbox.mediaBox.fetchedResource(representation.resource, parameters: nil).start() let fetch = fetchedMediaResource(postbox: postbox, reference: media.resourceReference(representation.resource)).start()
let data = postbox.mediaBox.resourceData(representation.resource, option: .complete(waitUntilFetchStatus: true)).start(next: { next in let data = postbox.mediaBox.resourceData(representation.resource, option: .complete(waitUntilFetchStatus: true)).start(next: { next in
subscriber.putNext(next) subscriber.putNext(next)
if next.complete { if next.complete {

View File

@ -86,6 +86,9 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
private let topSeparatorNode: ASDisplayNode private let topSeparatorNode: ASDisplayNode
private let separatorNode: ASDisplayNode private let separatorNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode
private var statusDisposable = MetaDisposable()
private let statusNode: RadialStatusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5))
private var resourceStatus: MediaResourceStatus?
private var currentIconImageResource: TelegramMediaResource? private var currentIconImageResource: TelegramMediaResource?
@ -123,6 +126,11 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
self.addSubnode(self.iconImageNode) self.addSubnode(self.iconImageNode)
self.addSubnode(self.titleNode) self.addSubnode(self.titleNode)
self.addSubnode(self.textNode) self.addSubnode(self.textNode)
self.addSubnode(self.statusNode)
}
deinit {
statusDisposable.dispose()
} }
override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
@ -155,6 +163,7 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
var iconImageRepresentation: TelegramMediaImageRepresentation? var iconImageRepresentation: TelegramMediaImageRepresentation?
var updateIconImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? var updateIconImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
var updatedStatusSignal: Signal<MediaResourceStatus, NoError>?
if let title = item.result.title { if let title = item.result.title {
titleString = NSAttributedString(string: title, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor) titleString = NSAttributedString(string: title, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor)
@ -206,6 +215,9 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
let imageCorners = ImageCorners(topLeft: .Corner(2.0), topRight: .Corner(2.0), bottomLeft: .Corner(2.0), bottomRight: .Corner(2.0)) let imageCorners = ImageCorners(topLeft: .Corner(2.0), topRight: .Corner(2.0), bottomLeft: .Corner(2.0), bottomRight: .Corner(2.0))
let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: UIEdgeInsets()) let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: UIEdgeInsets())
iconImageApply = iconImageLayout(arguments) iconImageApply = iconImageLayout(arguments)
updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource)
} }
var updatedIconImageResource = false var updatedIconImageResource = false
@ -312,6 +324,40 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset, height: UIScreenPixel)) strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset, height: UIScreenPixel))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel))
let progressFrame = CGRect(origin: CGPoint(x: iconFrame.minX + floorToScreenPixels((iconFrame.width - 37) / 2), y: iconFrame.minY + floorToScreenPixels((iconFrame.height - 37) / 2)), size: CGSize(width: 37, height: 37))
if let updatedStatusSignal = updatedStatusSignal {
strongSelf.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak strongSelf] status in
displayLinkDispatcher.dispatch {
if let strongSelf = strongSelf {
strongSelf.resourceStatus = status
strongSelf.statusNode.frame = progressFrame
let state: RadialStatusNodeState
let statusForegroundColor: UIColor = .white
switch status {
case let .Fetching(_, progress):
state = RadialStatusNodeState.progress(color: statusForegroundColor, lineWidth: nil, value: CGFloat(max(progress, 0.2)), cancelEnabled: false)
case .Remote:
state = .download(statusForegroundColor)
case .Local:
state = .none
}
strongSelf.statusNode.transitionToState(state, completion: { })
}
}
}))
} else {
strongSelf.statusNode.transitionToState(.none, completion: { })
}
} }
}) })
} }