mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-17 03:40:18 +00:00
Merge branch 'master' of github.com:peter-iakovlev/TelegramUI
This commit is contained in:
commit
ac56816f4c
@ -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] {
|
||||||
|
|||||||
@ -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
|
actionsDisposable.add((peersPromise.get() |> take(1) |> deliverOnMainQueue).start(next: { members in
|
||||||
if let confirmationImpl = confirmationImpl, case let .peer(peer, _) = peer {
|
let disabledIds = members?.compactMap({$0.peer.id}) ?? []
|
||||||
return confirmationImpl(peer.id)
|
let contactsController = ContactMultiselectionController(account: account, mode: .peerSelection, options: [], filters: [.excludeSelf, .disable(disabledIds)])
|
||||||
} else {
|
|
||||||
return .single(false)
|
let addMembers: ([ContactListPeerId]) -> Signal<Void, NoError> = { members -> Signal<Void, NoError> in
|
||||||
|
let peerIds = members.compactMap { contact -> PeerId? in
|
||||||
|
switch contact {
|
||||||
|
case let .peer(peerId):
|
||||||
|
return peerId
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return account.telegramApplicationContext.peerChannelMemberCategoriesContextsManager.addMembers(account: account, peerId: peerId, memberIds: peerIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
peersPromise.set(contactsController.result
|
||||||
|
|> deliverOnMainQueue |> mapToSignal { [weak contactsController] contacts in
|
||||||
|
contactsController?.displayProgress = true
|
||||||
|
|
||||||
|
return addMembers(contacts) |> mapToSignal { _ in
|
||||||
|
return channelMembers(postbox: account.postbox, network: account.network, peerId: peerId)
|
||||||
|
} |> deliverOnMainQueue |> afterNext { _ in
|
||||||
|
contactsController?.dismiss()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
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 {
|
contactsController.dismissed = {
|
||||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
|
||||||
let confirmationText: String
|
|
||||||
if let channel = channelPeer as? TelegramChannel {
|
|
||||||
switch channel.info {
|
|
||||||
case .broadcast:
|
|
||||||
confirmationText = presentationData.strings.ChannelInfo_AddParticipantConfirmation(peer.displayTitle).0
|
|
||||||
case .group:
|
|
||||||
confirmationText = presentationData.strings.GroupInfo_AddParticipantConfirmation(peer.displayTitle).0
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
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 result.get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let addMember = contactsController.result
|
|
||||||
|> mapError { _ -> AddPeerMemberError in return .generic }
|
|
||||||
|> deliverOnMainQueue
|
|
||||||
|> mapToSignal { memberPeer -> Signal<Void, AddPeerMemberError> in
|
|
||||||
if let memberPeer = memberPeer, case let .peer(selectedPeer, _) = memberPeer {
|
|
||||||
let memberId = selectedPeer.id
|
|
||||||
let applyMembers: Signal<Void, AddPeerMemberError> = peersPromise.get()
|
|
||||||
|> filter { $0 != nil }
|
|
||||||
|> 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 }
|
|
||||||
|
|
||||||
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) {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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?
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 {
|
||||||
|
switch filter {
|
||||||
|
case .excludeSelf:
|
||||||
existingPeerIds.insert(account.peerId)
|
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 {
|
||||||
|
|||||||
@ -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) {
|
||||||
|
if let presentationArguments = self.presentationArguments as? ViewControllerPresentationArguments {
|
||||||
|
switch presentationArguments.presentationAnimation {
|
||||||
|
case .modalSheet:
|
||||||
|
self.dismissed?()
|
||||||
self.contactsNode.animateOut(completion: completion)
|
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() {
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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>()
|
||||||
|
for filter in filters {
|
||||||
|
switch filter {
|
||||||
|
case .excludeSelf:
|
||||||
existingPeerIds.insert(account.peerId)
|
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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 peerId.namespace == Namespaces.Peer.CloudGroup {
|
||||||
|
contactsController = ContactSelectionController(account: account, autoDismiss: false, title: { $0.GroupInfo_AddParticipantTitle }, options: options, confirmation: { peer in
|
||||||
if let confirmationImpl = confirmationImpl, case let .peer(peer, _) = peer {
|
if let confirmationImpl = confirmationImpl, case let .peer(peer, _) = peer {
|
||||||
return confirmationImpl(peer.id)
|
return confirmationImpl(peer.id)
|
||||||
} else {
|
} else {
|
||||||
return .single(false)
|
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,11 +1453,53 @@ 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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
presentControllerImpl?(contactsController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
|
if let contactsController = contactsController as? ContactSelectionController {
|
||||||
selectAddMemberDisposable.set((contactsController.result
|
selectAddMemberDisposable.set((contactsController.result
|
||||||
|> deliverOnMainQueue).start(next: { [weak contactsController] memberPeer in
|
|> deliverOnMainQueue).start(next: { [weak contactsController] memberPeer in
|
||||||
guard let memberPeer = memberPeer else {
|
guard let memberPeer = memberPeer else {
|
||||||
@ -1426,11 +1512,26 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
|
|||||||
contactsController?.dismiss()
|
contactsController?.dismiss()
|
||||||
}))
|
}))
|
||||||
}))
|
}))
|
||||||
presentControllerImpl?(contactsController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
||||||
contactsController.dismissed = {
|
contactsController.dismissed = {
|
||||||
selectAddMemberDisposable.set(nil)
|
selectAddMemberDisposable.set(nil)
|
||||||
addMemberDisposable.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
|
||||||
presentControllerImpl?(channelAdminController(account: account, peerId: peerId, adminId: participant.peer.id, initialParticipant: participant.participant, updated: { _ in
|
presentControllerImpl?(channelAdminController(account: account, peerId: peerId, adminId: participant.peer.id, initialParticipant: participant.participant, updated: { _ in
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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))
|
||||||
|
|||||||
@ -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,14 +315,18 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)setIsMuted:(bool)isMuted {
|
- (void)setIsMuted:(bool)isMuted {
|
||||||
|
if (_controller != nil) {
|
||||||
_controller->SetMicMute(isMuted);
|
_controller->SetMicMute(isMuted);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (void)setNetworkType:(OngoingCallNetworkType)networkType {
|
- (void)setNetworkType:(OngoingCallNetworkType)networkType {
|
||||||
if (_networkType != networkType) {
|
if (_networkType != networkType) {
|
||||||
_networkType = networkType;
|
_networkType = networkType;
|
||||||
|
if (_controller != nil) {
|
||||||
_controller->SetNetworkType(callControllerNetworkTypeForType(networkType));
|
_controller->SetNetworkType(callControllerNetworkTypeForType(networkType));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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: { })
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user