mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-16 03:09:56 +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 listState: Signal<ChannelMemberListState, NoError> { get }
|
||||
func loadMore()
|
||||
func reset()
|
||||
func reset(_ force: Bool)
|
||||
func replayUpdates(_ updates: [(ChannelParticipant?, RenderedChannelParticipant?)])
|
||||
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 {
|
||||
} else {
|
||||
var list = self.listStateValue.list
|
||||
var loadingState: ChannelMemberListLoadingState = .ready(hasMore: false)
|
||||
if list.count > Int(initialBatchSize) {
|
||||
var loadingState: ChannelMemberListLoadingState = .ready(hasMore: true)
|
||||
if list.count > Int(initialBatchSize) && !force {
|
||||
list.removeSubrange(Int(initialBatchSize) ..< list.count)
|
||||
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> {
|
||||
let requestCategory: ChannelMembersCategory
|
||||
var adminQuery: String? = nil
|
||||
@ -458,9 +459,9 @@ private final class ChannelMemberMultiCategoryListContext: ChannelMemberCategory
|
||||
}
|
||||
}
|
||||
|
||||
func reset() {
|
||||
func reset(_ force: Bool) {
|
||||
for context in self.contexts {
|
||||
context.reset()
|
||||
context.reset(force)
|
||||
}
|
||||
}
|
||||
|
||||
@ -508,7 +509,7 @@ private final class PeerChannelMemberContextWithSubscribers {
|
||||
}
|
||||
|
||||
private func resetAndBeginEmptyTimer() {
|
||||
self.context.reset()
|
||||
self.context.reset(false)
|
||||
self.emptyTimer?.invalidate()
|
||||
let emptyTimer = SwiftSignalKit.Timer(timeout: emptyTimeout, repeat: false, completion: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
@ -559,6 +560,15 @@ final class PeerChannelMemberCategoriesContext {
|
||||
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) {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
if let current = self.contexts[key] {
|
||||
|
||||
@ -319,91 +319,41 @@ public func channelMembersController(account: Account, peerId: PeerId) -> ViewCo
|
||||
let peersPromise = Promise<[RenderedChannelParticipant]?>(nil)
|
||||
|
||||
let arguments = ChannelMembersControllerArguments(account: account, addMember: {
|
||||
var confirmationImpl: ((PeerId) -> Signal<Bool, NoError>)?
|
||||
let contactsController = ContactSelectionController(account: account, title: { $0.GroupInfo_AddParticipantTitle }, confirmation: { peer in
|
||||
if let confirmationImpl = confirmationImpl, case let .peer(peer, _) = peer {
|
||||
return confirmationImpl(peer.id)
|
||||
} else {
|
||||
return .single(false)
|
||||
}
|
||||
})
|
||||
confirmationImpl = { [weak contactsController] selectedId in
|
||||
return combineLatest(account.postbox.loadedPeerWithId(selectedId), account.postbox.loadedPeerWithId(peerId))
|
||||
|> deliverOnMainQueue
|
||||
|> mapToSignal { peer, channelPeer in
|
||||
let result = ValuePromise<Bool>()
|
||||
|
||||
if let contactsController = contactsController {
|
||||
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 }
|
||||
actionsDisposable.add((peersPromise.get() |> take(1) |> deliverOnMainQueue).start(next: { members in
|
||||
let disabledIds = members?.compactMap({$0.peer.id}) ?? []
|
||||
let contactsController = ContactMultiselectionController(account: account, mode: .peerSelection, options: [], filters: [.excludeSelf, .disable(disabledIds)])
|
||||
|
||||
return addPeerMember(account: account, peerId: peerId, memberId: memberId)
|
||||
|> then(applyMembers)
|
||||
} else {
|
||||
return .complete()
|
||||
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)
|
||||
}
|
||||
}
|
||||
presentControllerImpl?(contactsController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
addMembersDisposable.set(addMember.start())
|
||||
|
||||
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()
|
||||
}
|
||||
})
|
||||
|
||||
contactsController.dismissed = {
|
||||
|
||||
}
|
||||
|
||||
presentControllerImpl?(contactsController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}))
|
||||
|
||||
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
|
||||
updateState { state in
|
||||
if (peerId == nil && fromPeerId == state.peerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) {
|
||||
|
||||
@ -231,6 +231,8 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
||||
switch filter {
|
||||
case let .exclude(ids):
|
||||
existingPeerIds = existingPeerIds.union(ids)
|
||||
case .disable:
|
||||
break
|
||||
}
|
||||
}
|
||||
switch mode {
|
||||
|
||||
@ -11,6 +11,7 @@ enum ChannelMembersSearchControllerMode {
|
||||
|
||||
enum ChannelMembersSearchFilter {
|
||||
case exclude([PeerId])
|
||||
case disable([PeerId])
|
||||
}
|
||||
|
||||
final class ChannelMembersSearchController: ViewController {
|
||||
|
||||
@ -174,6 +174,8 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
||||
if ids.contains(participant.peer.id) {
|
||||
continue
|
||||
}
|
||||
case .disable:
|
||||
break
|
||||
}
|
||||
}
|
||||
case .promote:
|
||||
@ -186,6 +188,8 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
||||
if ids.contains(participant.peer.id) {
|
||||
continue
|
||||
}
|
||||
case .disable:
|
||||
break
|
||||
}
|
||||
}
|
||||
if case .creator = participant.participant {
|
||||
|
||||
@ -663,7 +663,9 @@ public func channelVisibilityController(account: Account, peerId: PeerId, mode:
|
||||
let revokeLinkDisposable = MetaDisposable()
|
||||
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
|
||||
updateState { state in
|
||||
@ -786,6 +788,7 @@ public func channelVisibilityController(account: Account, peerId: PeerId, mode:
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
let peerView = account.viewTracker.peerView(peerId)
|
||||
|> deliverOnMainQueue
|
||||
|
||||
@ -935,7 +938,7 @@ public func channelVisibilityController(account: Account, peerId: PeerId, mode:
|
||||
nextImpl = { [weak controller] in
|
||||
if let controller = controller {
|
||||
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)
|
||||
let _ = (selectionController.result
|
||||
|> 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: {
|
||||
self?.chatDisplayNode.dismissInput()
|
||||
}, 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 {
|
||||
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()
|
||||
}
|
||||
})
|
||||
@ -3585,7 +3584,10 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin
|
||||
}
|
||||
|
||||
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
|
||||
self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in
|
||||
if let strongSelf = self {
|
||||
|
||||
@ -641,7 +641,7 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD
|
||||
previewingContext.sourceRect = sourceRect
|
||||
}
|
||||
switch item.content {
|
||||
case let .peer(_, peer, _, _, _, _, _, _):
|
||||
case let .peer(_, peer, _, _, _, _, _, _, _):
|
||||
if peer.peerId.namespace != Namespaces.Peer.SecretChat {
|
||||
let chatController = ChatController(account: self.account, chatLocation: .peer(peer.peerId), mode: .standard(previewing: true))
|
||||
chatController.canReadHistory.set(false)
|
||||
|
||||
@ -7,12 +7,12 @@ import SwiftSignalKit
|
||||
import TelegramCore
|
||||
|
||||
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)
|
||||
|
||||
var chatLocation: ChatLocation {
|
||||
switch self {
|
||||
case let .peer(_, peer, _, _, _, _, _, _):
|
||||
case let .peer(_, peer, _, _, _, _, _, _, _):
|
||||
return .peer(peer.peerId)
|
||||
case let .groupReference(groupId, _, _, _):
|
||||
return .group(groupId)
|
||||
@ -95,7 +95,7 @@ class ChatListItem: ListViewItem {
|
||||
|
||||
func selected(listView: ListView) {
|
||||
switch self.content {
|
||||
case let .peer(message, peer, _, _, _, _, _, _):
|
||||
case let .peer(message, peer, _, _, _, _, _, _, _):
|
||||
if let message = message {
|
||||
self.interaction.messageSelected(message)
|
||||
} else if let peer = peer.peers[peer.peerId] {
|
||||
@ -321,7 +321,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
var peer: Peer?
|
||||
switch item.content {
|
||||
case let .peer(message, peerValue, _, _, _, _, _, _):
|
||||
case let .peer(message, peerValue, _, _, _, _, _, _, _):
|
||||
if let message = message {
|
||||
peer = messageMainPeer(message)
|
||||
} else {
|
||||
@ -420,11 +420,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
var multipleAvatarsApply: ((Bool) -> MultipleAvatarsNode)?
|
||||
|
||||
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
|
||||
itemPeer = peerValue
|
||||
combinedReadState = combinedReadStateValue
|
||||
if let combinedReadState = combinedReadState, !isAdValue {
|
||||
if let combinedReadState = combinedReadState, !isAdValue && !ignoreUnreadBadge {
|
||||
unreadCount = (combinedReadState.count, combinedReadState.isUnread, notificationSettingsValue?.isRemovedFromTotalUnreadCount ?? false)
|
||||
} else {
|
||||
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):
|
||||
switch mode {
|
||||
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):
|
||||
let itemPeer = peer.chatMainPeer
|
||||
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):
|
||||
switch mode {
|
||||
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):
|
||||
let itemPeer = peer.chatMainPeer
|
||||
var chatPeer: Peer?
|
||||
|
||||
@ -397,7 +397,7 @@ enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
interaction.peerSelected(peer.peer)
|
||||
})
|
||||
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)
|
||||
} else if let selectedItemNode = selectedItemNode as? ChatListItemNode, let item = selectedItemNode.item {
|
||||
switch item.content {
|
||||
case let .peer(message, peer, _, _, _, _, _, _):
|
||||
case let .peer(message, peer, _, _, _, _, _, _, _):
|
||||
return (selectedItemNode.view, message?.id ?? peer.peerId)
|
||||
case let .groupReference(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
|
||||
}
|
||||
if !needShareButton {
|
||||
|
||||
@ -285,9 +285,12 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
|
||||
dateAndStatusApply(false)
|
||||
switch layoutData {
|
||||
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)
|
||||
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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
if !needShareButton {
|
||||
@ -311,7 +311,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
strongSelf.replyInfoNode = 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
|
||||
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 {
|
||||
|
||||
@ -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 (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 {
|
||||
if let strongSelf = self {
|
||||
|
||||
@ -524,7 +524,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
wasEditingMedia = !value.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var isMediaEnabled = true
|
||||
var isEditingMedia = false
|
||||
if let editMessageState = interfaceState.editMessageState {
|
||||
|
||||
@ -100,7 +100,7 @@ public class ComposeController: ViewController {
|
||||
|
||||
self.contactsNode.openCreateNewGroup = { [weak self] in
|
||||
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.createActionDisposable.set((controller.result
|
||||
|> deliverOnMainQueue).start(next: { [weak controller] peerIds in
|
||||
|
||||
@ -118,7 +118,7 @@ enum ContactListPeer: Equatable {
|
||||
private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
case search(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 {
|
||||
switch self {
|
||||
@ -126,7 +126,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
return .search
|
||||
case let .option(index, _, _, _):
|
||||
return .option(index: index)
|
||||
case let .peer(_, peer, _, _, _, _, _, _):
|
||||
case let .peer(_, peer, _, _, _, _, _, _, _):
|
||||
switch peer {
|
||||
case let .peer(peer, _):
|
||||
return .peerId(peer.id.toInt64())
|
||||
@ -144,7 +144,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
})
|
||||
case let .option(_, option, theme, _):
|
||||
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 itemPeer: ContactsPeerItemPeer
|
||||
switch peer {
|
||||
@ -161,7 +161,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
status = .none
|
||||
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)
|
||||
})
|
||||
}
|
||||
@ -181,9 +181,9 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
} else {
|
||||
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 {
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
@ -212,6 +212,9 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
if lhsTimeFormat != rhsTimeFormat {
|
||||
return false
|
||||
}
|
||||
if lhsEnabled != rhsEnabled {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@ -232,11 +235,11 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
case .peer:
|
||||
return true
|
||||
}
|
||||
case let .peer(lhsIndex, _, _, _, _, _, _, _):
|
||||
case let .peer(lhsIndex, _, _, _, _, _, _, _, _):
|
||||
switch rhs {
|
||||
case .search, .option:
|
||||
return false
|
||||
case let .peer(rhsIndex, _, _, _, _, _, _, _):
|
||||
case let .peer(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 orderedPeers: [ContactListPeer]
|
||||
@ -409,7 +412,14 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer]
|
||||
if case let .peer(peer, _) = orderedPeers[i] {
|
||||
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
|
||||
}
|
||||
@ -474,20 +484,16 @@ struct ContactListNodeGroupSelectionState: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
struct ContactListFilter: OptionSet {
|
||||
public var rawValue: Int32
|
||||
|
||||
public init(rawValue: Int32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
public static let excludeSelf = ContactListFilter(rawValue: 1 << 1)
|
||||
enum ContactListFilter {
|
||||
case excludeSelf
|
||||
case exclude([PeerId])
|
||||
case disable([PeerId])
|
||||
}
|
||||
|
||||
final class ContactListNode: ASDisplayNode {
|
||||
private let account: Account
|
||||
private let presentation: ContactListPresentation
|
||||
private let filter: ContactListFilter
|
||||
private let filters: [ContactListFilter]
|
||||
|
||||
let listNode: ListView
|
||||
|
||||
@ -537,10 +543,10 @@ final class ContactListNode: ASDisplayNode {
|
||||
private var presentationDataDisposable: Disposable?
|
||||
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.presentation = presentation
|
||||
self.filter = filter
|
||||
self.filters = filters
|
||||
|
||||
self.listNode = ListView()
|
||||
|
||||
@ -592,10 +598,20 @@ final class ContactListNode: ASDisplayNode {
|
||||
|> mapToQueue { localPeers, remotePeers, deviceContacts, selectionState, themeAndStrings -> Signal<ContactsListNodeTransition, NoError> in
|
||||
let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in
|
||||
var existingPeerIds = Set<PeerId>()
|
||||
var disabledPeerIds = Set<PeerId>()
|
||||
|
||||
var existingNormalizedPhoneNumbers = Set<DeviceContactNormalizedPhoneNumber>()
|
||||
if filter.contains(.excludeSelf) {
|
||||
existingPeerIds.insert(account.peerId)
|
||||
for filter in filters {
|
||||
switch filter {
|
||||
case .excludeSelf:
|
||||
existingPeerIds.insert(account.peerId)
|
||||
case let .exclude(peerIds):
|
||||
existingPeerIds = existingPeerIds.union(peerIds)
|
||||
case let .disable(peerIds):
|
||||
disabledPeerIds = disabledPeerIds.union(peerIds)
|
||||
}
|
||||
}
|
||||
|
||||
var peers: [ContactListPeer] = []
|
||||
for peer in localPeers {
|
||||
if !existingPeerIds.contains(peer.id) {
|
||||
@ -639,7 +655,7 @@ final class ContactListNode: ASDisplayNode {
|
||||
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)
|
||||
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())
|
||||
|> mapToQueue { view, selectionState, themeAndStrings -> 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 animated: Bool
|
||||
if let previous = previous {
|
||||
|
||||
@ -21,6 +21,9 @@ class ContactMultiselectionController: ViewController {
|
||||
return self.displayNode as! ContactMultiselectionControllerNode
|
||||
}
|
||||
|
||||
var dismissed: (() -> Void)?
|
||||
|
||||
|
||||
private let index: PeerNameIndex = .lastNameFirst
|
||||
|
||||
private var _ready = Promise<Bool>()
|
||||
@ -57,11 +60,13 @@ class ContactMultiselectionController: ViewController {
|
||||
|
||||
private var limitsConfiguration: LimitsConfiguration?
|
||||
private var limitsConfigurationDisposable: Disposable?
|
||||
|
||||
init(account: Account, mode: ContactMultiselectionControllerMode) {
|
||||
private let options: [ContactListAdditionalOption]
|
||||
private let filters: [ContactListFilter]
|
||||
init(account: Account, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter] = [.excludeSelf]) {
|
||||
self.account = account
|
||||
self.mode = mode
|
||||
|
||||
self.options = options
|
||||
self.filters = filters
|
||||
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||
|
||||
self.titleView = CounterContollerTitleView(theme: self.presentationData.theme)
|
||||
@ -148,7 +153,7 @@ class ContactMultiselectionController: ViewController {
|
||||
}
|
||||
|
||||
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.contactsNode.dismiss = { [weak self] in
|
||||
@ -296,7 +301,16 @@ class ContactMultiselectionController: ViewController {
|
||||
}
|
||||
|
||||
override open func dismiss(completion: (() -> Void)? = nil) {
|
||||
self.contactsNode.animateOut(completion: completion)
|
||||
if let presentationArguments = self.presentationArguments as? ViewControllerPresentationArguments {
|
||||
switch presentationArguments.presentationAnimation {
|
||||
case .modalSheet:
|
||||
self.dismissed?()
|
||||
self.contactsNode.animateOut(completion: completion)
|
||||
case .none:
|
||||
self.dismissed?()
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
@ -313,6 +327,7 @@ class ContactMultiselectionController: ViewController {
|
||||
|
||||
@objc func cancelPressed() {
|
||||
self._result.set(.single([]))
|
||||
self.dismiss()
|
||||
}
|
||||
|
||||
@objc func rightNavigationButtonPressed() {
|
||||
|
||||
@ -39,17 +39,16 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
var editableTokens: [EditableTokenListToken] = []
|
||||
|
||||
private let searchResultsReadyDisposable = MetaDisposable()
|
||||
|
||||
var dismiss: (() -> Void)?
|
||||
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
init(account: Account) {
|
||||
init(account: Account, options: [ContactListAdditionalOption], filters: [ContactListFilter]) {
|
||||
self.account = account
|
||||
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)
|
||||
|
||||
super.init()
|
||||
@ -88,7 +87,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
selectionState = 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
|
||||
self?.tokenListNode.setText("")
|
||||
self?.openPeer?(peer)
|
||||
|
||||
@ -118,7 +118,7 @@ final class ContactsSearchContainerNode: SearchDisplayControllerContentNode {
|
||||
private var containerViewLayout: (ContainerViewLayout, CGFloat)?
|
||||
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.openPeer = openPeer
|
||||
|
||||
@ -173,8 +173,16 @@ final class ContactsSearchContainerNode: SearchDisplayControllerContentNode {
|
||||
|> map { localPeers, remotePeers, deviceContacts, themeAndStrings -> [ContactListSearchEntry] in
|
||||
var entries: [ContactListSearchEntry] = []
|
||||
var existingPeerIds = Set<PeerId>()
|
||||
if filter.contains(.excludeSelf) {
|
||||
existingPeerIds.insert(account.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)
|
||||
}
|
||||
}
|
||||
var existingNormalizedPhoneNumbers = Set<DeviceContactNormalizedPhoneNumber>()
|
||||
var index = 0
|
||||
|
||||
@ -203,7 +203,12 @@ public func createChannelController(account: Account) -> ViewController {
|
||||
let arguments = CreateChannelArguments(account: account, updateEditingName: { editingName in
|
||||
updateState { current in
|
||||
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
|
||||
}
|
||||
}, updateEditingDescriptionText: { text in
|
||||
|
||||
@ -120,7 +120,7 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr
|
||||
unreadBadgeActiveTextColor: .white,
|
||||
unreadBadgeInactiveBackgroundColor: UIColor(rgb: 0xb6b6bb),
|
||||
unreadBadgeInactiveTextColor: .white,
|
||||
pinnedBadgeColor: UIColor(rgb: 0x939399),
|
||||
pinnedBadgeColor: UIColor(rgb: 0xb6b6bb),
|
||||
pinnedSearchBarColor: UIColor(rgb: 0xe5e5e5),
|
||||
regularSearchBarColor: UIColor(rgb: 0xe9e9e9),
|
||||
sectionHeaderFillColor: UIColor(rgb: 0xf7f7f7),
|
||||
@ -310,7 +310,7 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr
|
||||
actionControlFillColor: accentColor,
|
||||
actionControlForegroundColor: .white,
|
||||
primaryTextColor: .black,
|
||||
secondaryTextColor: UIColor(rgb: 0x5e5e5e),
|
||||
secondaryTextColor: UIColor(rgb: 0x8e8e93),
|
||||
mediaRecordingDotColor: UIColor(rgb: 0xed2521),
|
||||
keyboardColor: .light,
|
||||
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 {
|
||||
let updatedParticipants = channelMembers
|
||||
let disabledPeerIds = state.removingParticipantIds
|
||||
var updatedParticipants = channelMembers
|
||||
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]
|
||||
if memberCount < 200 {
|
||||
@ -1307,8 +1329,21 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
|
||||
}
|
||||
}
|
||||
}, 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 options: [ContactListAdditionalOption] = []
|
||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||
@ -1317,13 +1352,22 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
|
||||
inviteByLinkImpl?()
|
||||
}))
|
||||
|
||||
let contactsController = ContactSelectionController(account: account, autoDismiss: false, title: { $0.GroupInfo_AddParticipantTitle }, options: options, confirmation: { peer in
|
||||
if let confirmationImpl = confirmationImpl, case let .peer(peer, _) = peer {
|
||||
return confirmationImpl(peer.id)
|
||||
} else {
|
||||
return .single(false)
|
||||
}
|
||||
})
|
||||
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 {
|
||||
return confirmationImpl(peer.id)
|
||||
} else {
|
||||
return .single(false)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
contactsController = ContactMultiselectionController(account: account, mode: .peerSelection, options: options, filters: [.excludeSelf, .disable(recentIds)])
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
confirmationImpl = { [weak contactsController] peerId in
|
||||
return account.postbox.loadedPeerWithId(peerId)
|
||||
|> deliverOnMainQueue
|
||||
@ -1409,27 +1453,84 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
|
||||
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
|
||||
contactsController?.dismiss()
|
||||
|
||||
presentControllerImpl?(channelVisibilityController(account: account, peerId: peerId, mode: .privateLink), ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet))
|
||||
}
|
||||
selectAddMemberDisposable.set((contactsController.result
|
||||
|> deliverOnMainQueue).start(next: { [weak contactsController] memberPeer in
|
||||
guard let memberPeer = memberPeer else {
|
||||
return
|
||||
}
|
||||
|
||||
contactsController?.displayProgress = true
|
||||
addMemberDisposable.set((addMember(memberPeer)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
contactsController?.dismiss()
|
||||
}))
|
||||
}))
|
||||
|
||||
presentControllerImpl?(contactsController, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
contactsController.dismissed = {
|
||||
selectAddMemberDisposable.set(nil)
|
||||
addMemberDisposable.set(nil)
|
||||
if let contactsController = contactsController as? ContactSelectionController {
|
||||
selectAddMemberDisposable.set((contactsController.result
|
||||
|> deliverOnMainQueue).start(next: { [weak contactsController] memberPeer in
|
||||
guard let memberPeer = memberPeer else {
|
||||
return
|
||||
}
|
||||
|
||||
contactsController?.displayProgress = true
|
||||
addMemberDisposable.set((addMember(memberPeer)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
contactsController?.dismiss()
|
||||
}))
|
||||
}))
|
||||
contactsController.dismissed = {
|
||||
selectAddMemberDisposable.set(nil)
|
||||
addMemberDisposable.set(nil)
|
||||
}
|
||||
}
|
||||
if let contactsController = contactsController as? ContactMultiselectionController {
|
||||
selectAddMemberDisposable.set((contactsController.result
|
||||
|> deliverOnMainQueue).start(next: { [weak contactsController] peers in
|
||||
|
||||
contactsController?.displayProgress = true
|
||||
addMemberDisposable.set((addMembers(peers)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
contactsController?.dismiss()
|
||||
}))
|
||||
}))
|
||||
contactsController.dismissed = {
|
||||
selectAddMemberDisposable.set(nil)
|
||||
addMemberDisposable.set(nil)
|
||||
}
|
||||
}
|
||||
})
|
||||
}, promotePeer: { participant in
|
||||
|
||||
@ -77,14 +77,14 @@ private let iconTextBackgroundImage = generateStretchableFilledCircleImage(radiu
|
||||
final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode {
|
||||
private let imageNodeBackground: ASDisplayNode
|
||||
private let imageNode: TransformImageNode
|
||||
private var statusNode: RadialStatusNode?
|
||||
private var videoLayer: (SoftwareVideoThumbnailLayer, SoftwareVideoLayerFrameManager, SampleBufferLayer)?
|
||||
private var currentImageResource: TelegramMediaResource?
|
||||
private var currentVideoFile: TelegramMediaFile?
|
||||
private var resourceStatus: MediaResourceStatus?
|
||||
private(set) var item: HorizontalListContextResultsChatInputPanelItem?
|
||||
private var statusDisposable = MetaDisposable()
|
||||
|
||||
private let statusNode: RadialStatusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5))
|
||||
|
||||
|
||||
override var visibility: ListViewItemNodeVisibility {
|
||||
didSet {
|
||||
@ -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))
|
||||
|
||||
strongSelf.statusNode.removeFromSupernode()
|
||||
strongSelf.addSubnode(strongSelf.statusNode)
|
||||
|
||||
strongSelf.statusNode.frame = progressFrame
|
||||
|
||||
|
||||
if let updatedStatusSignal = updatedStatusSignal {
|
||||
strongSelf.statusDisposable.set((updatedStatusSignal |> deliverOnMainQueue).start(next: { [weak strongSelf] status in
|
||||
@ -361,21 +366,13 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
|
||||
if let strongSelf = strongSelf {
|
||||
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 statusForegroundColor: UIColor = .white
|
||||
|
||||
switch status {
|
||||
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:
|
||||
state = .download(statusForegroundColor)
|
||||
case .Local:
|
||||
@ -383,19 +380,12 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
|
||||
}
|
||||
|
||||
|
||||
if let statusNode = strongSelf.statusNode {
|
||||
if state == .none {
|
||||
strongSelf.statusNode = nil
|
||||
}
|
||||
statusNode.transitionToState(state, completion: { [weak statusNode] in
|
||||
if state == .none {
|
||||
statusNode?.removeFromSupernode()
|
||||
}
|
||||
})
|
||||
}
|
||||
strongSelf.statusNode.transitionToState(state, completion: { })
|
||||
}
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
strongSelf.statusNode.transitionToState(.none, completion: { })
|
||||
}
|
||||
|
||||
if let (thumbnailLayer, _, layer) = strongSelf.videoLayer {
|
||||
|
||||
@ -696,6 +696,8 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNode, Ite
|
||||
strongSelf.inputSeparator = inputSeparator
|
||||
}
|
||||
|
||||
//let title = title.prefix(255)
|
||||
|
||||
if strongSelf.inputFirstField == nil {
|
||||
let inputFirstField = TextFieldNodeView()
|
||||
inputFirstField.font = Font.regular(17.0)
|
||||
@ -710,12 +712,12 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNode, Ite
|
||||
placeholder = item.strings.GroupInfo_ChannelListNamePlaceholder
|
||||
}
|
||||
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.view.addSubview(inputFirstField)
|
||||
inputFirstField.addTarget(self, action: #selector(strongSelf.textFieldDidChange(_:)), for: .editingChanged)
|
||||
} else if strongSelf.inputFirstField?.text != title {
|
||||
strongSelf.inputFirstField?.text = title
|
||||
} else if strongSelf.inputFirstField?.text != String(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))
|
||||
|
||||
@ -251,8 +251,6 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) {
|
||||
unsigned char peerTag[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));
|
||||
/*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);
|
||||
@ -270,9 +268,9 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) {
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
if (_controller) {
|
||||
if (_controller != nil) {
|
||||
char *buffer = (char *)malloc(_controller->GetDebugLogLength());
|
||||
/*releasable*/
|
||||
|
||||
_controller->Stop();
|
||||
_controller->GetDebugLog(buffer);
|
||||
NSString *debugLog = [[NSString alloc] initWithUTF8String:buffer];
|
||||
@ -296,7 +294,6 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) {
|
||||
|
||||
- (void)controllerStateChanged:(int)state {
|
||||
OngoingCallState callState = OngoingCallStateInitializing;
|
||||
/*releasable*/
|
||||
switch (state) {
|
||||
case tgvoip::STATE_ESTABLISHED:
|
||||
callState = OngoingCallStateConnected;
|
||||
@ -307,16 +304,6 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
/*switch (state) {
|
||||
case STATE_ESTABLISHED:
|
||||
callState = OngoingCallStateConnected;
|
||||
break;
|
||||
case STATE_FAILED:
|
||||
callState = OngoingCallStateFailed;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}*/
|
||||
|
||||
if (callState != _state) {
|
||||
_state = callState;
|
||||
@ -328,13 +315,17 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) {
|
||||
}
|
||||
|
||||
- (void)setIsMuted:(bool)isMuted {
|
||||
_controller->SetMicMute(isMuted);
|
||||
if (_controller != nil) {
|
||||
_controller->SetMicMute(isMuted);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setNetworkType:(OngoingCallNetworkType)networkType {
|
||||
if (_networkType != networkType) {
|
||||
_networkType = networkType;
|
||||
_controller->SetNetworkType(callControllerNetworkTypeForType(networkType));
|
||||
if (_controller != nil) {
|
||||
_controller->SetNetworkType(callControllerNetworkTypeForType(networkType));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -327,6 +327,7 @@ func openChatMessage(account: Account, message: Message, standalone: Bool, rever
|
||||
return true
|
||||
}
|
||||
case let .chatAvatars(controller, media):
|
||||
dismissInput()
|
||||
chatAvatarHiddenMedia(controller.hiddenMedia |> map { value -> MessageId? in
|
||||
if value != nil {
|
||||
return message.id
|
||||
|
||||
@ -8,6 +8,38 @@ enum PeerChannelMemberContextKey: Hashable {
|
||||
case recentSearch(String)
|
||||
case admins(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 {
|
||||
@ -161,4 +193,22 @@ final class PeerChannelMemberCategoriesContextsManager {
|
||||
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
|
||||
view.disablesInteractiveTransitionGestureRecognizer = updatedInterfaceState.selectionState != nil
|
||||
view.disablesInteractiveTransitionGestureRecognizer = updatedInterfaceState.selectionState != nil && self.mediaCollectionDisplayNode.historyNode is ChatHistoryGridNode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ enum PeerReportSubject {
|
||||
private enum PeerReportOption {
|
||||
case spam
|
||||
case violence
|
||||
case copyright
|
||||
case pornoghraphy
|
||||
case other
|
||||
}
|
||||
@ -24,6 +25,7 @@ func peerReportOptionsController(account: Account, subject: PeerReportSubject, p
|
||||
.spam,
|
||||
.violence,
|
||||
.pornoghraphy,
|
||||
.copyright,
|
||||
.other
|
||||
]
|
||||
|
||||
@ -37,8 +39,8 @@ func peerReportOptionsController(account: Account, subject: PeerReportSubject, p
|
||||
title = presentationData.strings.ReportPeer_ReasonViolence
|
||||
case .pornoghraphy:
|
||||
title = presentationData.strings.ReportPeer_ReasonPornography
|
||||
/*case .copyright:
|
||||
title = presentationData.strings.ReportPeer_ReasonCopyright*/
|
||||
case .copyright:
|
||||
title = presentationData.strings.ReportPeer_ReasonCopyright
|
||||
case .other:
|
||||
title = presentationData.strings.ReportPeer_ReasonOther
|
||||
}
|
||||
@ -52,6 +54,8 @@ func peerReportOptionsController(account: Account, subject: PeerReportSubject, p
|
||||
reportReason = .violence
|
||||
case .pornoghraphy:
|
||||
reportReason = .porno
|
||||
case .copyright:
|
||||
reportReason = .copyright
|
||||
case .other:
|
||||
break
|
||||
}
|
||||
@ -60,12 +64,18 @@ func peerReportOptionsController(account: Account, subject: PeerReportSubject, p
|
||||
case let .peer(peerId):
|
||||
let _ = (reportPeer(account: account, peerId: peerId, reason: reportReason)
|
||||
|> 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):
|
||||
let _ = (reportPeerMessages(account: account, messageIds: messageIds, reason: reportReason)
|
||||
|> 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 {
|
||||
|
||||
@ -168,7 +168,7 @@ public final class SecretMediaPreviewController: ViewController {
|
||||
|
||||
self.screenCaptureEventsDisposable = (screenCaptureEvents()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
if let _ = self {
|
||||
if let strongSelf = self, strongSelf.traceVisibility() {
|
||||
let _ = addSecretChatMessageScreenshot(account: account, peerId: messageId.peerId).start()
|
||||
}
|
||||
})
|
||||
|
||||
@ -245,7 +245,7 @@ public func selectivePrivacyPeersController(account: Account, title: String, ini
|
||||
|
||||
removePeerDisposable.set(applyPeers.start())
|
||||
}, 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
|
||||
let applyPeers: Signal<Void, NoError> = peersPromise.get()
|
||||
|> 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> {
|
||||
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 {
|
||||
let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: [])
|
||||
|
||||
return .single((nil, loadedData, true))
|
||||
} 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)
|
||||
}
|
||||
|
||||
@ -66,7 +69,8 @@ private func chatMessageStickerDatas(account: Account, file: TelegramMediaFile,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fullSizeData |> map { (data, complete) -> (Data?, Data?, Bool) in
|
||||
return fullSizeData
|
||||
|> map { (data, complete) -> (Data?, Data?, Bool) in
|
||||
return (nil, data, complete)
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,7 +117,7 @@ public func transformOutgoingMessageMedia(postbox: Postbox, network: Network, me
|
||||
case let image as TelegramMediaImage:
|
||||
if let representation = largestImageRepresentation(image.representations) {
|
||||
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
|
||||
subscriber.putNext(next)
|
||||
if next.complete {
|
||||
|
||||
@ -86,7 +86,10 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
|
||||
private let topSeparatorNode: ASDisplayNode
|
||||
private let separatorNode: 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?
|
||||
|
||||
init() {
|
||||
@ -123,6 +126,11 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
|
||||
self.addSubnode(self.iconImageNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.statusNode)
|
||||
}
|
||||
|
||||
deinit {
|
||||
statusDisposable.dispose()
|
||||
}
|
||||
|
||||
override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
|
||||
@ -155,7 +163,8 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
|
||||
|
||||
var iconImageRepresentation: TelegramMediaImageRepresentation?
|
||||
var updateIconImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
|
||||
|
||||
var updatedStatusSignal: Signal<MediaResourceStatus, NoError>?
|
||||
|
||||
if let title = item.result.title {
|
||||
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 arguments = TransformImageArguments(corners: imageCorners, imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: UIEdgeInsets())
|
||||
iconImageApply = iconImageLayout(arguments)
|
||||
|
||||
updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource)
|
||||
|
||||
}
|
||||
|
||||
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.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