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

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

View File

@ -58,7 +58,7 @@ private protocol ChannelMemberCategoryListContext {
var listStateValue: ChannelMemberListState { get }
var 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] {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -265,7 +265,6 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin
}
}
}
return openChatMessage(account: account, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: strongSelf.navigationController as? NavigationController, dismissInput: {
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 {

View File

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

View File

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

View File

@ -131,7 +131,7 @@ private func mappedInsertEntries(account: Account, nodeInteraction: ChatListNode
case let .PeerEntry(index, presentationData, message, combinedReadState, notificationSettings, embeddedState, peer, summaryInfo, editing, hasActiveRevealControls, inputActivities, isAd):
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?

View File

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

View File

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

View File

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

View File

@ -159,7 +159,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
}
}
}
if !needShareButton, let author = item.message.author as? TelegramUser, let _ = author.botInfo {
if !needShareButton, let author = item.message.author as? TelegramUser, let _ = author.botInfo, !item.message.media.isEmpty {
needShareButton = true
}
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 {

View File

@ -214,7 +214,9 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: strings.Conversation_PinnedMessage, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0)))
let (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 {

View File

@ -524,7 +524,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
wasEditingMedia = !value.isEmpty
}
}
var isMediaEnabled = true
var isEditingMedia = false
if let editMessageState = interfaceState.editMessageState {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -911,8 +911,30 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa
}
}
} else if let channel = view.peers[view.peerId] as? TelegramChannel, let cachedChannelData = view.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount {
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -35,15 +35,18 @@ private func imageFromAJpeg(data: Data) -> (UIImage, UIImage)? {
}
private func chatMessageStickerDatas(account: Account, file: TelegramMediaFile, small: Bool, fetched: Bool, onlyFullSize: Bool) -> Signal<(Data?, Data?, Bool), NoError> {
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)
}
}

View File

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

View File

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