mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-29 09:01:05 +00:00
Merge branches 'master' and 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
adc000ddf6
@ -5322,3 +5322,11 @@ Any member of this group will be able to see messages in the channel.";
|
||||
"PeopleNearby.DiscoverDescription" = "Exchange contact info with people nearby\nand find new friends.";
|
||||
|
||||
"Time.TomorrowAt" = "tomorrow at %@";
|
||||
|
||||
"PeerInfo.ButtonMessage" = "Message";
|
||||
"PeerInfo.ButtonDiscuss" = "Discuss";
|
||||
"PeerInfo.ButtonCall" = "Call";
|
||||
"PeerInfo.ButtonMute" = "Mute";
|
||||
"PeerInfo.ButtonUnmute" = "Unmute";
|
||||
"PeerInfo.ButtonMore" = "More";
|
||||
"PeerInfo.ButtonAddMember" = "Add Members";
|
||||
|
@ -68,7 +68,7 @@ private final class NavigationButtonItemNode: ASTextNode {
|
||||
imageNode.displayWithoutProcessing = true
|
||||
imageNode.displaysAsynchronously = false
|
||||
self.imageNode = imageNode
|
||||
if value.size == CGSize(width: 30.0, height: 30.0) {
|
||||
if false, value.size == CGSize(width: 30.0, height: 30.0) {
|
||||
if self.imageRippleNode.supernode == nil {
|
||||
self.addSubnode(self.imageRippleNode)
|
||||
self.imageRippleNode.image = generateFilledCircleImage(diameter: 30.0, color: self.rippleColor)
|
||||
|
@ -17,7 +17,7 @@ import ContactsPeerItem
|
||||
import ChatListSearchItemHeader
|
||||
import ItemListUI
|
||||
|
||||
enum ChannelMembersSearchMode {
|
||||
public enum ChannelMembersSearchMode {
|
||||
case searchMembers
|
||||
case searchAdmins
|
||||
case searchBanned
|
||||
@ -239,10 +239,10 @@ private struct GroupMembersSearchContextState {
|
||||
var members: [RenderedChannelParticipant] = []
|
||||
}
|
||||
|
||||
final class GroupMembersSearchContext {
|
||||
public final class GroupMembersSearchContext {
|
||||
fileprivate let state = Promise<GroupMembersSearchContextState>()
|
||||
|
||||
init(context: AccountContext, peerId: PeerId) {
|
||||
public init(context: AccountContext, peerId: PeerId) {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
|
||||
let combinedSignal = combineLatest(queue: .mainQueue(), categorySignal(context: context, peerId: peerId, category: .contacts), categorySignal(context: context, peerId: peerId, category: .bots), categorySignal(context: context, peerId: peerId, category: .admins), categorySignal(context: context, peerId: peerId, category: .members))
|
||||
@ -275,7 +275,7 @@ private struct ChannelMembersSearchContainerState: Equatable {
|
||||
var removingParticipantIds = Set<PeerId>()
|
||||
}
|
||||
|
||||
final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNode {
|
||||
public final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNode {
|
||||
private let context: AccountContext
|
||||
private let openPeer: (Peer, RenderedChannelParticipant?) -> Void
|
||||
private let mode: ChannelMembersSearchMode
|
||||
@ -298,7 +298,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
||||
|
||||
private let presentationDataPromise: Promise<PresentationData>
|
||||
|
||||
init(context: AccountContext, peerId: PeerId, mode: ChannelMembersSearchMode, filters: [ChannelMembersSearchFilter], searchContext: GroupMembersSearchContext?, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, updateActivity: @escaping (Bool) -> Void, pushController: @escaping (ViewController) -> Void) {
|
||||
public init(context: AccountContext, peerId: PeerId, mode: ChannelMembersSearchMode, filters: [ChannelMembersSearchFilter], searchContext: GroupMembersSearchContext?, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, updateActivity: @escaping (Bool) -> Void, pushController: @escaping (ViewController) -> Void) {
|
||||
self.context = context
|
||||
self.openPeer = openPeer
|
||||
self.mode = mode
|
||||
@ -1170,7 +1170,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
||||
self.removeMemberDisposable.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
override public func didLoad() {
|
||||
super.didLoad()
|
||||
}
|
||||
|
||||
@ -1179,7 +1179,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
||||
self.listNode.backgroundColor = theme.chatList.backgroundColor
|
||||
}
|
||||
|
||||
override func searchTextUpdated(text: String) {
|
||||
override public func searchTextUpdated(text: String) {
|
||||
if text.isEmpty {
|
||||
self.searchQuery.set(.single(nil))
|
||||
} else {
|
||||
@ -1244,7 +1244,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
||||
}
|
||||
}
|
||||
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
||||
|
||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||
@ -1268,7 +1268,7 @@ final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNod
|
||||
}
|
||||
}
|
||||
|
||||
override func scrollToTop() {
|
||||
override public func scrollToTop() {
|
||||
if self.listNode.isHidden {
|
||||
self.emptyQueryListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
} else {
|
||||
|
@ -14,7 +14,7 @@ enum ChannelMembersSearchControllerMode {
|
||||
case ban
|
||||
}
|
||||
|
||||
enum ChannelMembersSearchFilter {
|
||||
public enum ChannelMembersSearchFilter {
|
||||
case exclude([PeerId])
|
||||
case disable([PeerId])
|
||||
}
|
||||
|
@ -813,7 +813,7 @@ public enum ChannelVisibilityControllerMode {
|
||||
case privateLink
|
||||
}
|
||||
|
||||
public func channelVisibilityController(context: AccountContext, peerId: PeerId, mode: ChannelVisibilityControllerMode, upgradedToSupergroup: @escaping (PeerId, @escaping () -> Void) -> Void) -> ViewController {
|
||||
public func channelVisibilityController(context: AccountContext, peerId: PeerId, mode: ChannelVisibilityControllerMode, upgradedToSupergroup: @escaping (PeerId, @escaping () -> Void) -> Void, onDismissRemoveController: ViewController? = nil) -> ViewController {
|
||||
let statePromise = ValuePromise(ChannelVisibilityControllerState(), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: ChannelVisibilityControllerState())
|
||||
let updateState: ((ChannelVisibilityControllerState) -> ChannelVisibilityControllerState) -> Void = { f in
|
||||
@ -1231,9 +1231,22 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
|
||||
}
|
||||
|
||||
let controller = ItemListController(context: context, state: signal)
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.view.endEditing(true)
|
||||
controller?.dismiss()
|
||||
dismissImpl = { [weak controller, weak onDismissRemoveController] in
|
||||
guard let controller = controller else {
|
||||
return
|
||||
}
|
||||
controller.view.endEditing(true)
|
||||
if let onDismissRemoveController = onDismissRemoveController, let navigationController = controller.navigationController {
|
||||
navigationController.setViewControllers(navigationController.viewControllers.filter { c in
|
||||
if c === controller || c === onDismissRemoveController {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}, animated: true)
|
||||
} else {
|
||||
controller.dismiss()
|
||||
}
|
||||
}
|
||||
dismissInputImpl = { [weak controller] in
|
||||
controller?.view.endEditing(true)
|
||||
|
@ -3,7 +3,156 @@ import Postbox
|
||||
import TelegramApi
|
||||
import SwiftSignalKit
|
||||
|
||||
public func groupsInCommon(account:Account, peerId:PeerId) -> Signal<[Peer], NoError> {
|
||||
public enum GroupsInCommonDataState: Equatable {
|
||||
case loading
|
||||
case ready(canLoadMore: Bool)
|
||||
}
|
||||
|
||||
public struct GroupsInCommonState: Equatable {
|
||||
public var peers: [RenderedPeer]
|
||||
public var count: Int?
|
||||
public var dataState: GroupsInCommonDataState
|
||||
}
|
||||
|
||||
private final class GroupsInCommonContextImpl {
|
||||
private let queue: Queue
|
||||
private let account: Account
|
||||
private let peerId: PeerId
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
|
||||
private var peers: [RenderedPeer] = []
|
||||
private var count: Int?
|
||||
private var dataState: GroupsInCommonDataState = .ready(canLoadMore: true)
|
||||
|
||||
private let stateValue = Promise<GroupsInCommonState>()
|
||||
var state: Signal<GroupsInCommonState, NoError> {
|
||||
return self.stateValue.get()
|
||||
}
|
||||
|
||||
init(queue: Queue, account: Account, peerId: PeerId) {
|
||||
self.queue = queue
|
||||
self.account = account
|
||||
self.peerId = peerId
|
||||
|
||||
self.loadMore(limit: 32)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
func loadMore(limit: Int32) {
|
||||
if case .ready(true) = self.dataState {
|
||||
self.dataState = .loading
|
||||
self.pushState()
|
||||
|
||||
let maxId = self.peers.last?.peerId.id
|
||||
let peerId = self.peerId
|
||||
let network = self.account.network
|
||||
let postbox = self.account.postbox
|
||||
let signal: Signal<([Peer], Int), NoError> = self.account.postbox.transaction { transaction -> Api.InputUser? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputUser)
|
||||
}
|
||||
|> mapToSignal { inputUser -> Signal<([Peer], Int), NoError> in
|
||||
guard let inputUser = inputUser else {
|
||||
return .single(([], 0))
|
||||
}
|
||||
return network.request(Api.functions.messages.getCommonChats(userId: inputUser, maxId: maxId ?? 0, limit: limit))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.messages.Chats?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<([Peer], Int), NoError> in
|
||||
let chats: [Api.Chat]
|
||||
let count: Int?
|
||||
switch result {
|
||||
case .none:
|
||||
chats = []
|
||||
count = nil
|
||||
case let .chats(apiChats):
|
||||
chats = apiChats
|
||||
count = nil
|
||||
case let .chatsSlice(apiCount, apiChats):
|
||||
chats = apiChats
|
||||
count = Int(apiCount)
|
||||
}
|
||||
|
||||
return postbox.transaction { transaction -> ([Peer], Int) in
|
||||
var peers: [Peer] = []
|
||||
for chat in chats {
|
||||
if let peer = parseTelegramGroupOrChannel(chat: chat) {
|
||||
peers.append(peer)
|
||||
}
|
||||
}
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer? in
|
||||
return updated
|
||||
})
|
||||
|
||||
return (peers, count ?? 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.disposable.set((signal
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] (peers, count) in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var existingPeers = Set(strongSelf.peers.map { $0.peerId })
|
||||
for peer in peers {
|
||||
if !existingPeers.contains(peer.id) {
|
||||
existingPeers.insert(peer.id)
|
||||
strongSelf.peers.append(RenderedPeer(peer: peer))
|
||||
}
|
||||
}
|
||||
|
||||
let updatedCount = max(strongSelf.peers.count, count)
|
||||
strongSelf.count = updatedCount
|
||||
strongSelf.dataState = .ready(canLoadMore: count != 0 && updatedCount > strongSelf.peers.count)
|
||||
strongSelf.pushState()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
private func pushState() {
|
||||
self.stateValue.set(.single(GroupsInCommonState(peers: self.peers, count: self.count, dataState: self.dataState)))
|
||||
}
|
||||
}
|
||||
|
||||
public final class GroupsInCommonContext {
|
||||
private let queue: Queue = .mainQueue()
|
||||
private let impl: QueueLocalObject<GroupsInCommonContextImpl>
|
||||
|
||||
public var state: Signal<GroupsInCommonState, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
self.impl.with { impl in
|
||||
disposable.set(impl.state.start(next: { value in
|
||||
subscriber.putNext(value)
|
||||
}))
|
||||
}
|
||||
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
public init(account: Account, peerId: PeerId) {
|
||||
let queue = self.queue
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
return GroupsInCommonContextImpl(queue: queue, account: account, peerId: peerId)
|
||||
})
|
||||
}
|
||||
|
||||
public func loadMore() {
|
||||
self.impl.with { impl in
|
||||
impl.loadMore(limit: 32)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func groupsInCommon(account: Account, peerId: PeerId) -> Signal<[Peer], NoError> {
|
||||
return account.postbox.transaction { transaction -> Signal<[Peer], NoError> in
|
||||
if let peer = transaction.getPeer(peerId), let inputUser = apiInputUser(peer) {
|
||||
return account.network.request(Api.functions.messages.getCommonChats(userId: inputUser, maxId: 0, limit: 100))
|
||||
@ -18,7 +167,7 @@ public func groupsInCommon(account:Account, peerId:PeerId) -> Signal<[Peer], NoE
|
||||
}
|
||||
|
||||
return account.postbox.transaction { transaction -> [Peer] in
|
||||
var peers:[Peer] = []
|
||||
var peers: [Peer] = []
|
||||
for chat in chats {
|
||||
if let peer = parseTelegramGroupOrChannel(chat: chat) {
|
||||
peers.append(peer)
|
||||
|
File diff suppressed because it is too large
Load Diff
12
submodules/TelegramUI/Images.xcassets/Peer Info/VerifiedIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Peer Info/VerifiedIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ic_verify_big.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/VerifiedIcon.imageset/ic_verify_big.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/VerifiedIcon.imageset/ic_verify_big.pdf
vendored
Normal file
Binary file not shown.
@ -62,9 +62,10 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
private let peerId: PeerId
|
||||
private let chatControllerInteraction: ChatControllerInteraction
|
||||
private let openPeerContextAction: (Peer, ASDisplayNode, ContextGesture?) -> Void
|
||||
private let groupsInCommonContext: GroupsInCommonContext
|
||||
|
||||
private let listNode: ListView
|
||||
private var peers: [Peer] = []
|
||||
private var state: GroupsInCommonState?
|
||||
private var currentEntries: [GroupsInCommonListEntry] = []
|
||||
private var enqueuedTransactions: [GroupsInCommonListTransaction] = []
|
||||
|
||||
@ -76,11 +77,14 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
return self.ready.get()
|
||||
}
|
||||
|
||||
init(context: AccountContext, peerId: PeerId, chatControllerInteraction: ChatControllerInteraction, openPeerContextAction: @escaping (Peer, ASDisplayNode, ContextGesture?) -> Void, peers: [Peer]) {
|
||||
private var disposable: Disposable?
|
||||
|
||||
init(context: AccountContext, peerId: PeerId, chatControllerInteraction: ChatControllerInteraction, openPeerContextAction: @escaping (Peer, ASDisplayNode, ContextGesture?) -> Void, groupsInCommonContext: GroupsInCommonContext) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.chatControllerInteraction = chatControllerInteraction
|
||||
self.openPeerContextAction = openPeerContextAction
|
||||
self.groupsInCommonContext = groupsInCommonContext
|
||||
|
||||
self.listNode = ListView()
|
||||
|
||||
@ -89,10 +93,29 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
self.listNode.preloadPages = true
|
||||
self.addSubnode(self.listNode)
|
||||
|
||||
self.peers = peers
|
||||
self.disposable = (groupsInCommonContext.state
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.state = state
|
||||
if let (_, _, presentationData) = strongSelf.currentParams {
|
||||
strongSelf.updatePeers(state: state, presentationData: presentationData)
|
||||
}
|
||||
})
|
||||
|
||||
self.listNode.visibleBottomContentOffsetChanged = { [weak self] offset in
|
||||
guard let strongSelf = self, let state = strongSelf.state, case .ready(true) = state.dataState else {
|
||||
return
|
||||
}
|
||||
if case let .known(value) = offset, value < 100.0, case .ready(true) = state.dataState {
|
||||
strongSelf.groupsInCommonContext.loadMore()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable?.dispose()
|
||||
}
|
||||
|
||||
func scrollToTop() -> Bool {
|
||||
@ -115,15 +138,17 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
|
||||
self.listNode.scrollEnabled = !isScrollingLockedAtTop
|
||||
|
||||
if isFirstLayout {
|
||||
self.updatePeers(peers: self.peers, presentationData: presentationData)
|
||||
if isFirstLayout, let state = self.state {
|
||||
self.updatePeers(state: state, presentationData: presentationData)
|
||||
}
|
||||
}
|
||||
|
||||
private func updatePeers(peers: [Peer], presentationData: PresentationData) {
|
||||
private func updatePeers(state: GroupsInCommonState, presentationData: PresentationData) {
|
||||
var entries: [GroupsInCommonListEntry] = []
|
||||
for peer in peers {
|
||||
entries.append(GroupsInCommonListEntry(index: entries.count, peer: peer))
|
||||
for peer in state.peers {
|
||||
if let peer = peer.peer {
|
||||
entries.append(GroupsInCommonListEntry(index: entries.count, peer: peer))
|
||||
}
|
||||
}
|
||||
let transaction = preparedTransition(from: self.currentEntries, to: entries, context: self.context, presentationData: presentationData, openPeer: { [weak self] peer in
|
||||
self?.chatControllerInteraction.openPeer(peer.id, .default, nil)
|
||||
|
@ -17,6 +17,7 @@ private struct PeerMembersListTransaction {
|
||||
let deletions: [ListViewDeleteItem]
|
||||
let insertions: [ListViewInsertItem]
|
||||
let updates: [ListViewUpdateItem]
|
||||
let animated: Bool
|
||||
}
|
||||
|
||||
enum PeerMembersListAction {
|
||||
@ -94,7 +95,7 @@ private func preparedTransition(from fromEntries: [PeerMembersListEntry], to toE
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enclosingPeer: enclosingPeer, action: action), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enclosingPeer: enclosingPeer, action: action), directionHint: nil) }
|
||||
|
||||
return PeerMembersListTransaction(deletions: deletions, insertions: insertions, updates: updates)
|
||||
return PeerMembersListTransaction(deletions: deletions, insertions: insertions, updates: updates, animated: toEntries.count < fromEntries.count)
|
||||
}
|
||||
|
||||
final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
@ -207,7 +208,11 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
self.enqueuedTransactions.remove(at: 0)
|
||||
|
||||
var options = ListViewDeleteAndInsertOptions()
|
||||
options.insert(.Synchronous)
|
||||
if transaction.animated {
|
||||
options.insert(.AnimateInsertion)
|
||||
} else {
|
||||
options.insert(.Synchronous)
|
||||
}
|
||||
|
||||
self.listNode.transaction(deleteIndices: transaction.deletions, insertIndicesAndItems: transaction.insertions, updateIndicesAndItems: transaction.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
|
@ -87,7 +87,11 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.view.addGestureRecognizer(TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
|
||||
recognizer.tapActionAtPoint = { _ in
|
||||
return .waitForSingleTap
|
||||
}
|
||||
self.imageNode.view.addGestureRecognizer(recognizer)
|
||||
}
|
||||
|
||||
@objc func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||
@ -237,6 +241,8 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
|
||||
func updateSelectionState(animated: Bool) {
|
||||
if let (item, media, _, mediaDimensions) = self.item, let theme = self.theme {
|
||||
self.containerNode.isGestureEnabled = self.interaction.selectedMessageIds == nil
|
||||
|
||||
if let selectedIds = self.interaction.selectedMessageIds {
|
||||
let selected = selectedIds.contains(item.message.id)
|
||||
|
||||
@ -373,6 +379,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
self.itemInteraction.selectedMessageIds = chatControllerInteraction.selectionState.flatMap { $0.selectedIds }
|
||||
|
||||
self.scrollNode.view.delaysContentTouches = false
|
||||
self.scrollNode.view.canCancelContentTouches = true
|
||||
self.scrollNode.view.showsVerticalScrollIndicator = false
|
||||
if #available(iOS 11.0, *) {
|
||||
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
|
||||
|
@ -63,7 +63,7 @@ final class PeerInfoScreenData {
|
||||
let globalNotificationSettings: GlobalNotificationSettings?
|
||||
let isContact: Bool
|
||||
let availablePanes: [PeerInfoPaneKey]
|
||||
let groupsInCommon: [Peer]?
|
||||
let groupsInCommon: GroupsInCommonContext?
|
||||
let linkedDiscussionPeer: Peer?
|
||||
let members: PeerInfoMembersData?
|
||||
|
||||
@ -75,7 +75,7 @@ final class PeerInfoScreenData {
|
||||
globalNotificationSettings: GlobalNotificationSettings?,
|
||||
isContact: Bool,
|
||||
availablePanes: [PeerInfoPaneKey],
|
||||
groupsInCommon: [Peer]?,
|
||||
groupsInCommon: GroupsInCommonContext?,
|
||||
linkedDiscussionPeer: Peer?,
|
||||
members: PeerInfoMembersData?
|
||||
) {
|
||||
@ -105,7 +105,7 @@ private enum PeerInfoScreenInputData: Equatable {
|
||||
case group(groupId: PeerId)
|
||||
}
|
||||
|
||||
func peerInfoAvailableMediaPanes(context: AccountContext, peerId: PeerId) -> Signal<[PeerInfoPaneKey], NoError> {
|
||||
private func peerInfoAvailableMediaPanes(context: AccountContext, peerId: PeerId) -> Signal<[PeerInfoPaneKey]?, NoError> {
|
||||
let tags: [(MessageTags, PeerInfoPaneKey)] = [
|
||||
(.photoOrVideo, .media),
|
||||
(.file, .files),
|
||||
@ -113,34 +113,51 @@ func peerInfoAvailableMediaPanes(context: AccountContext, peerId: PeerId) -> Sig
|
||||
//(.voiceOrInstantVideo, .voice),
|
||||
(.webPage, .links)
|
||||
]
|
||||
return combineLatest(tags.map { tagAndKey -> Signal<PeerInfoPaneKey?, NoError> in
|
||||
enum PaneState {
|
||||
case loading
|
||||
case empty
|
||||
case present
|
||||
}
|
||||
let loadedOnce = Atomic<Bool>(value: false)
|
||||
return combineLatest(queue: .mainQueue(), tags.map { tagAndKey -> Signal<(PeerInfoPaneKey, PaneState), NoError> in
|
||||
let (tag, key) = tagAndKey
|
||||
return context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId), index: .upperBound, anchorIndex: .upperBound, count: 20, clipHoles: false, fixedCombinedReadStates: nil, tagMask: tag)
|
||||
|> map { (view, _, _) -> PeerInfoPaneKey? in
|
||||
|> map { (view, _, _) -> (PeerInfoPaneKey, PaneState) in
|
||||
if view.entries.isEmpty {
|
||||
return nil
|
||||
if view.isLoading {
|
||||
return (key, .loading)
|
||||
} else {
|
||||
return (key, .empty)
|
||||
}
|
||||
} else {
|
||||
return key
|
||||
return (key, .present)
|
||||
}
|
||||
}
|
||||
})
|
||||
|> map { keys -> [PeerInfoPaneKey] in
|
||||
return keys.compactMap { $0 }
|
||||
|> map { keysAndStates -> [PeerInfoPaneKey]? in
|
||||
let loadedOnceValue = loadedOnce.with { $0 }
|
||||
var result: [PeerInfoPaneKey] = []
|
||||
var hasNonLoaded = false
|
||||
for (key, state) in keysAndStates {
|
||||
switch state {
|
||||
case .present:
|
||||
result.append(key)
|
||||
case .empty:
|
||||
break
|
||||
case .loading:
|
||||
hasNonLoaded = true
|
||||
}
|
||||
}
|
||||
if !hasNonLoaded || loadedOnceValue {
|
||||
if !loadedOnceValue {
|
||||
let _ = loadedOnce.swap(true)
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
/*return context.account.postbox.combinedView(keys: tags.map { (tag, _) -> PostboxViewKey in
|
||||
return .historyTagInfo(peerId: peerId, tag: tag)
|
||||
})
|
||||
|> map { view -> [PeerInfoPaneKey] in
|
||||
return tags.compactMap { (tag, key) -> PeerInfoPaneKey? in
|
||||
if let info = view.views[.historyTagInfo(peerId: peerId, tag: tag)] as? HistoryTagInfoView, !info.isEmpty {
|
||||
return key
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged*/
|
||||
}
|
||||
|
||||
struct PeerInfoStatusData: Equatable {
|
||||
@ -258,17 +275,13 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
members: nil
|
||||
))
|
||||
case let .user(peerId, secretChatId, kind):
|
||||
let groupsInCommonSignal: Signal<[Peer]?, NoError>
|
||||
switch kind {
|
||||
case .user:
|
||||
groupsInCommonSignal = .single(nil)
|
||||
|> then(
|
||||
groupsInCommon(account: context.account, peerId: peerId)
|
||||
|> map(Optional.init)
|
||||
)
|
||||
default:
|
||||
groupsInCommonSignal = .single([])
|
||||
let groupsInCommon: GroupsInCommonContext?
|
||||
if case .user = kind {
|
||||
groupsInCommon = GroupsInCommonContext(account: context.account, peerId: peerId)
|
||||
} else {
|
||||
groupsInCommon = nil
|
||||
}
|
||||
|
||||
enum StatusInputData: Equatable {
|
||||
case none
|
||||
case presence(TelegramUserPresence)
|
||||
@ -359,10 +372,9 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
context.account.viewTracker.peerView(peerId, updateData: true),
|
||||
peerInfoAvailableMediaPanes(context: context, peerId: peerId),
|
||||
context.account.postbox.combinedView(keys: combinedKeys),
|
||||
status,
|
||||
groupsInCommonSignal
|
||||
status
|
||||
)
|
||||
|> map { peerView, availablePanes, combinedView, status, groupsInCommon -> PeerInfoScreenData in
|
||||
|> map { peerView, availablePanes, combinedView, status -> PeerInfoScreenData in
|
||||
var globalNotificationSettings: GlobalNotificationSettings = .defaultSettings
|
||||
if let preferencesView = combinedView.views[globalNotificationsKey] as? PreferencesView {
|
||||
if let settings = preferencesView.values[PreferencesKeys.globalNotifications] as? GlobalNotificationSettings {
|
||||
@ -371,13 +383,9 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
}
|
||||
|
||||
var availablePanes = availablePanes
|
||||
if let groupsInCommon = groupsInCommon {
|
||||
if !groupsInCommon.isEmpty {
|
||||
availablePanes.append(.groupsInCommon)
|
||||
}
|
||||
} else if let cachedData = peerView.cachedData as? CachedUserData {
|
||||
if availablePanes != nil, groupsInCommon != nil, let cachedData = peerView.cachedData as? CachedUserData {
|
||||
if cachedData.commonGroupCount != 0 {
|
||||
availablePanes.append(.groupsInCommon)
|
||||
availablePanes?.append(.groupsInCommon)
|
||||
}
|
||||
}
|
||||
|
||||
@ -388,7 +396,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
notificationSettings: peerView.notificationSettings as? TelegramPeerNotificationSettings,
|
||||
globalNotificationSettings: globalNotificationSettings,
|
||||
isContact: peerView.peerIsContact,
|
||||
availablePanes: availablePanes,
|
||||
availablePanes: availablePanes ?? [],
|
||||
groupsInCommon: groupsInCommon,
|
||||
linkedDiscussionPeer: nil,
|
||||
members: nil
|
||||
@ -437,8 +445,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
notificationSettings: peerView.notificationSettings as? TelegramPeerNotificationSettings,
|
||||
globalNotificationSettings: globalNotificationSettings,
|
||||
isContact: peerView.peerIsContact,
|
||||
availablePanes: availablePanes,
|
||||
groupsInCommon: [],
|
||||
availablePanes: availablePanes ?? [],
|
||||
groupsInCommon: nil,
|
||||
linkedDiscussionPeer: discussionPeer,
|
||||
members: nil
|
||||
)
|
||||
@ -447,7 +455,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
let status = context.account.viewTracker.peerView(groupId, updateData: false)
|
||||
|> map { peerView -> PeerInfoStatusData? in
|
||||
guard let channel = peerView.peers[groupId] as? TelegramChannel else {
|
||||
return PeerInfoStatusData(text: strings.Channel_Status, isActivity: false)
|
||||
return PeerInfoStatusData(text: strings.Group_Status, isActivity: false)
|
||||
}
|
||||
if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount, memberCount != 0 {
|
||||
return PeerInfoStatusData(text: strings.Conversation_StatusMembers(memberCount), isActivity: false)
|
||||
@ -494,7 +502,11 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
|
||||
var availablePanes = availablePanes
|
||||
if let membersData = membersData, case .longList = membersData {
|
||||
availablePanes.insert(.members, at: 0)
|
||||
if availablePanes != nil {
|
||||
availablePanes?.insert(.members, at: 0)
|
||||
} else {
|
||||
availablePanes = [.members]
|
||||
}
|
||||
}
|
||||
|
||||
return PeerInfoScreenData(
|
||||
@ -504,8 +516,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
notificationSettings: peerView.notificationSettings as? TelegramPeerNotificationSettings,
|
||||
globalNotificationSettings: globalNotificationSettings,
|
||||
isContact: peerView.peerIsContact,
|
||||
availablePanes: availablePanes,
|
||||
groupsInCommon: [],
|
||||
availablePanes: availablePanes ?? [],
|
||||
groupsInCommon: nil,
|
||||
linkedDiscussionPeer: discussionPeer,
|
||||
members: membersData
|
||||
)
|
||||
@ -612,8 +624,11 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?) -> [PeerInf
|
||||
if let user = peer as? TelegramUser {
|
||||
result.append(.message)
|
||||
var callsAvailable = false
|
||||
if !user.isDeleted, user.botInfo == nil, !user.flags.contains(.isSupport), let cachedUserData = cachedData as? CachedUserData {
|
||||
callsAvailable = cachedUserData.callsAvailable
|
||||
if !user.isDeleted, user.botInfo == nil, !user.flags.contains(.isSupport) {
|
||||
if let cachedUserData = cachedData as? CachedUserData {
|
||||
callsAvailable = cachedUserData.callsAvailable
|
||||
}
|
||||
callsAvailable = true
|
||||
}
|
||||
if callsAvailable {
|
||||
result.append(.call)
|
||||
|
@ -375,7 +375,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
|
||||
if strongSelf.items.count > 1 {
|
||||
highlightedSide = false
|
||||
}
|
||||
} else if point.x > size.width * 4.0 / 5.0 {
|
||||
} else {
|
||||
if strongSelf.items.count > 1 {
|
||||
highlightedSide = true
|
||||
}
|
||||
@ -423,7 +423,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
|
||||
self.currentIndex = self.items.count - 1
|
||||
self.updateItems(size: size, transition: .immediate, synchronous: true)
|
||||
}
|
||||
} else if location.x > size.width * 4.0 / 5.0 {
|
||||
} else {
|
||||
if self.currentIndex < self.items.count - 1 {
|
||||
self.currentIndex += 1
|
||||
self.updateItems(size: size, transition: .immediate)
|
||||
@ -986,13 +986,15 @@ final class PeerInfoHeaderNavigationButtonContainerNode: ASDisplayNode {
|
||||
} else {
|
||||
nextRegularButtonOrigin = nextButtonOrigin
|
||||
}
|
||||
let alphaFactor: CGFloat = spec.isForExpandedView ? expandFraction : (1.0 - expandFraction)
|
||||
if wasAdded {
|
||||
buttonNode.frame = buttonFrame
|
||||
buttonNode.alpha = 0.0
|
||||
transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor)
|
||||
} else {
|
||||
transition.updateFrameAdditiveToCenter(node: buttonNode, frame: buttonFrame)
|
||||
transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor)
|
||||
}
|
||||
let alphaFactor: CGFloat = spec.isForExpandedView ? expandFraction : (1.0 - expandFraction)
|
||||
transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor)
|
||||
}
|
||||
var removeKeys: [PeerInfoHeaderNavigationButtonKey] = []
|
||||
for (key, _) in self.buttonNodes {
|
||||
@ -1097,30 +1099,135 @@ final class PeerInfoHeaderSingleLineTextFieldNode: ASDisplayNode, PeerInfoHeader
|
||||
}
|
||||
}
|
||||
|
||||
/*final class PeerInfoHeaderMultiLineTextFieldNode: ASDisplayNode, PeerInfoHeaderTextFieldNode {
|
||||
private let textNode: TextFieldNode
|
||||
final class PeerInfoHeaderMultiLineTextFieldNode: ASDisplayNode, PeerInfoHeaderTextFieldNode, ASEditableTextNodeDelegate {
|
||||
private let textNode: EditableTextNode
|
||||
private let textNodeContainer: ASDisplayNode
|
||||
private let measureTextNode: ImmediateTextNode
|
||||
private let topSeparator: ASDisplayNode
|
||||
|
||||
override init() {
|
||||
self.textNode = TextFieldNode()
|
||||
private let requestUpdateHeight: () -> Void
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
private var currentParams: (width: CGFloat, safeInset: CGFloat)?
|
||||
private var currentMeasuredHeight: CGFloat?
|
||||
|
||||
var text: String {
|
||||
return self.textNode.attributedText?.string ?? ""
|
||||
}
|
||||
|
||||
init(requestUpdateHeight: @escaping () -> Void) {
|
||||
self.requestUpdateHeight = requestUpdateHeight
|
||||
|
||||
self.textNode = EditableTextNode()
|
||||
self.textNodeContainer = ASDisplayNode()
|
||||
self.measureTextNode = ImmediateTextNode()
|
||||
self.measureTextNode.maximumNumberOfLines = 0
|
||||
self.topSeparator = ASDisplayNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.textNodeContainer.addSubnode(self.textNode)
|
||||
self.addSubnode(self.textNodeContainer)
|
||||
self.addSubnode(self.topSeparator)
|
||||
}
|
||||
|
||||
func update(width: CGFloat, safeInset: CGFloat) -> CGFloat {
|
||||
return 44.0
|
||||
func update(width: CGFloat, safeInset: CGFloat, hasPrevious: Bool, placeholder: String, isEnabled: Bool, presentationData: PresentationData, updateText: String?) -> CGFloat {
|
||||
self.currentParams = (width, safeInset)
|
||||
|
||||
if self.theme !== presentationData.theme {
|
||||
self.theme = presentationData.theme
|
||||
let textColor = presentationData.theme.list.itemPrimaryTextColor
|
||||
self.textNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: textColor]
|
||||
|
||||
self.textNode.clipsToBounds = true
|
||||
self.textNode.delegate = self
|
||||
self.textNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
|
||||
}
|
||||
|
||||
self.topSeparator.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
|
||||
self.topSeparator.frame = CGRect(origin: CGPoint(x: safeInset + (hasPrevious ? 16.0 : 0.0), y: 0.0), size: CGSize(width: width, height: UIScreenPixel))
|
||||
|
||||
let attributedPlaceholderText = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: presentationData.theme.list.itemPlaceholderTextColor)
|
||||
if self.textNode.attributedPlaceholderText == nil || !self.textNode.attributedPlaceholderText!.isEqual(to: attributedPlaceholderText) {
|
||||
self.textNode.attributedPlaceholderText = attributedPlaceholderText
|
||||
}
|
||||
|
||||
if let updateText = updateText {
|
||||
let attributedText = NSAttributedString(string: updateText, font: Font.regular(17.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
|
||||
self.textNode.attributedText = attributedText
|
||||
}
|
||||
|
||||
var measureText = self.textNode.attributedText?.string ?? ""
|
||||
if measureText.hasSuffix("\n") || measureText.isEmpty {
|
||||
measureText += "|"
|
||||
}
|
||||
let attributedMeasureText = NSAttributedString(string: measureText, font: Font.regular(17.0), textColor: .black)
|
||||
self.measureTextNode.attributedText = attributedMeasureText
|
||||
let measureTextSize = self.measureTextNode.updateLayout(CGSize(width: width - safeInset * 2.0 - 16 * 2.0, height: .greatestFiniteMagnitude))
|
||||
self.currentMeasuredHeight = measureTextSize.height
|
||||
|
||||
let height = measureTextSize.height + 22.0
|
||||
|
||||
let textNodeFrame = CGRect(origin: CGPoint(x: safeInset + 16.0, y: 10.0), size: CGSize(width: width - safeInset * 2.0 - 16.0 * 2.0, height: max(height, 1000.0)))
|
||||
self.textNodeContainer.frame = textNodeFrame
|
||||
self.textNode.frame = CGRect(origin: CGPoint(), size: textNodeFrame.size)
|
||||
|
||||
return height
|
||||
}
|
||||
}*/
|
||||
|
||||
func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||
guard let theme = self.theme else {
|
||||
return true
|
||||
}
|
||||
let updatedText = (editableTextNode.textView.text as NSString).replacingCharacters(in: range, with: text)
|
||||
if updatedText.count > 255 {
|
||||
let attributedText = NSAttributedString(string: String(updatedText[updatedText.startIndex..<updatedText.index(updatedText.startIndex, offsetBy: 255)]), font: Font.regular(17.0), textColor: theme.list.itemPrimaryTextColor)
|
||||
self.textNode.attributedText = attributedText
|
||||
self.requestUpdateHeight()
|
||||
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
|
||||
if let (width, safeInset) = self.currentParams {
|
||||
var measureText = self.textNode.attributedText?.string ?? ""
|
||||
if measureText.hasSuffix("\n") || measureText.isEmpty {
|
||||
measureText += "|"
|
||||
}
|
||||
let attributedMeasureText = NSAttributedString(string: measureText, font: Font.regular(17.0), textColor: .black)
|
||||
self.measureTextNode.attributedText = attributedMeasureText
|
||||
let measureTextSize = self.measureTextNode.updateLayout(CGSize(width: width - safeInset * 2.0 - 16 * 2.0, height: .greatestFiniteMagnitude))
|
||||
if let currentMeasuredHeight = self.currentMeasuredHeight, abs(measureTextSize.height - currentMeasuredHeight) > 0.1 {
|
||||
self.requestUpdateHeight()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func editableTextNodeShouldPaste(_ editableTextNode: ASEditableTextNode) -> Bool {
|
||||
let text: String? = UIPasteboard.general.string
|
||||
if let _ = text {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
|
||||
private let context: AccountContext
|
||||
private let requestUpdateLayout: () -> Void
|
||||
|
||||
let avatarNode: PeerInfoEditingAvatarNode
|
||||
|
||||
var itemNodes: [PeerInfoHeaderTextFieldNodeKey: PeerInfoHeaderTextFieldNode] = [:]
|
||||
|
||||
init(context: AccountContext) {
|
||||
init(context: AccountContext, requestUpdateLayout: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.requestUpdateLayout = requestUpdateLayout
|
||||
|
||||
self.avatarNode = PeerInfoEditingAvatarNode(context: context)
|
||||
|
||||
super.init()
|
||||
@ -1165,6 +1272,7 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
|
||||
if let current = self.itemNodes[key] {
|
||||
itemNode = current
|
||||
} else {
|
||||
var isMultiline = false
|
||||
switch key {
|
||||
case .firstName:
|
||||
updateText = (peer as? TelegramUser)?.firstName ?? ""
|
||||
@ -1173,6 +1281,7 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
|
||||
case .title:
|
||||
updateText = peer?.debugDisplayTitle ?? ""
|
||||
case .description:
|
||||
isMultiline = true
|
||||
if let cachedData = cachedData as? CachedChannelData {
|
||||
updateText = cachedData.about ?? ""
|
||||
} else if let cachedData = cachedData as? CachedGroupData {
|
||||
@ -1181,7 +1290,13 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
|
||||
updateText = ""
|
||||
}
|
||||
}
|
||||
itemNode = PeerInfoHeaderSingleLineTextFieldNode()
|
||||
if isMultiline {
|
||||
itemNode = PeerInfoHeaderMultiLineTextFieldNode(requestUpdateHeight: { [weak self] in
|
||||
self?.requestUpdateLayout()
|
||||
})
|
||||
} else {
|
||||
itemNode = PeerInfoHeaderSingleLineTextFieldNode()
|
||||
}
|
||||
self.itemNodes[key] = itemNode
|
||||
self.addSubnode(itemNode)
|
||||
}
|
||||
@ -1189,16 +1304,20 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
|
||||
var isEnabled = true
|
||||
switch key {
|
||||
case .firstName:
|
||||
placeholder = "First Name"
|
||||
placeholder = presentationData.strings.UserInfo_FirstNamePlaceholder
|
||||
isEnabled = isContact
|
||||
case .lastName:
|
||||
placeholder = "Last Name"
|
||||
placeholder = presentationData.strings.UserInfo_LastNamePlaceholder
|
||||
isEnabled = isContact
|
||||
case .title:
|
||||
placeholder = "Title"
|
||||
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
placeholder = presentationData.strings.GroupInfo_ChannelListNamePlaceholder
|
||||
} else {
|
||||
placeholder = presentationData.strings.GroupInfo_GroupNamePlaceholder
|
||||
}
|
||||
isEnabled = canEditPeerInfo(peer: peer)
|
||||
case .description:
|
||||
placeholder = "Description"
|
||||
placeholder = presentationData.strings.Channel_Edit_AboutItem
|
||||
isEnabled = canEditPeerInfo(peer: peer)
|
||||
}
|
||||
let itemHeight = itemNode.update(width: width, safeInset: safeInset, hasPrevious: hasPrevious, placeholder: placeholder, isEnabled: isEnabled, presentationData: presentationData, updateText: updateText)
|
||||
@ -1250,6 +1369,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
var performButtonAction: ((PeerInfoHeaderButtonKey) -> Void)?
|
||||
var requestAvatarExpansion: (([AvatarGalleryEntry], (ASDisplayNode, CGRect, () -> (UIView?, UIView?))) -> Void)?
|
||||
var requestOpenAvatarForEditing: (() -> Void)?
|
||||
var requestUpdateLayout: (() -> Void)?
|
||||
|
||||
var navigationTransition: PeerInfoHeaderNavigationTransition?
|
||||
|
||||
@ -1276,7 +1396,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
self.subtitleNode.displaysAsynchronously = false
|
||||
|
||||
self.regularContentNode = PeerInfoHeaderRegularContentNode()
|
||||
self.editingContentNode = PeerInfoHeaderEditingContentNode(context: context)
|
||||
var requestUpdateLayoutImpl: (() -> Void)?
|
||||
self.editingContentNode = PeerInfoHeaderEditingContentNode(context: context, requestUpdateLayout: {
|
||||
requestUpdateLayoutImpl?()
|
||||
})
|
||||
self.editingContentNode.alpha = 0.0
|
||||
|
||||
self.navigationButtonContainer = PeerInfoHeaderNavigationButtonContainerNode()
|
||||
@ -1291,6 +1414,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
|
||||
super.init()
|
||||
|
||||
requestUpdateLayoutImpl = { [weak self] in
|
||||
self?.requestUpdateLayout?()
|
||||
}
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.expandedBackgroundNode)
|
||||
self.addSubnode(self.separatorNode)
|
||||
@ -1330,7 +1457,17 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
self.presentationData = presentationData
|
||||
|
||||
if themeUpdated {
|
||||
self.titleCredibilityIconNode.image = PresentationResourcesItemList.verifiedPeerIcon(presentationData.theme)
|
||||
if let sourceImage = UIImage(bundleImageName: "Peer Info/VerifiedIcon") {
|
||||
self.titleCredibilityIconNode.image = generateImage(sourceImage.size, contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(presentationData.theme.list.itemCheckColors.foregroundColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: 7.0, dy: 7.0))
|
||||
context.setFillColor(presentationData.theme.list.itemCheckColors.fillColor.cgColor)
|
||||
context.clip(to: CGRect(origin: CGPoint(), size: size), mask: sourceImage.cgImage!)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
})
|
||||
//self.titleCredibilityIconNode.image = PresentationResourcesItemList.verifiedPeerIcon(presentationData.theme)
|
||||
}
|
||||
}
|
||||
|
||||
self.regularContentNode.alpha = state.isEditing ? 0.0 : 1.0
|
||||
@ -1392,7 +1529,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
self.subtitleNode.attributedText = subtitleString
|
||||
}
|
||||
|
||||
let textSideInset: CGFloat = 16.0
|
||||
let textSideInset: CGFloat = 44.0
|
||||
let expandedAvatarControlsHeight: CGFloat = 64.0
|
||||
let expandedAvatarHeight: CGFloat = expandedAvatarListSize.height + expandedAvatarControlsHeight
|
||||
|
||||
@ -1634,7 +1771,14 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
let buttonsAlpha: CGFloat
|
||||
let apparentButtonSize: CGFloat
|
||||
let buttonsVerticalOffset: CGFloat
|
||||
|
||||
var buttonsAlphaTransition = transition
|
||||
|
||||
if self.navigationTransition != nil {
|
||||
if case let .animated(duration, curve) = transition, transitionFraction >= 1.0 - CGFloat.ulpOfOne {
|
||||
buttonsAlphaTransition = .animated(duration: duration * 0.6, curve: curve)
|
||||
}
|
||||
|
||||
if self.isAvatarExpanded {
|
||||
apparentButtonSize = expandedButtonSize
|
||||
} else {
|
||||
@ -1691,46 +1835,49 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
let buttonIcon: PeerInfoHeaderButtonIcon
|
||||
switch buttonKey {
|
||||
case .message:
|
||||
buttonText = "Message"
|
||||
buttonText = presentationData.strings.PeerInfo_ButtonMessage
|
||||
buttonIcon = .message
|
||||
case .discussion:
|
||||
buttonText = "Discuss"
|
||||
buttonText = presentationData.strings.PeerInfo_ButtonDiscuss
|
||||
buttonIcon = .message
|
||||
case .call:
|
||||
buttonText = "Call"
|
||||
buttonText = presentationData.strings.PeerInfo_ButtonCall
|
||||
buttonIcon = .call
|
||||
case .mute:
|
||||
if let notificationSettings = notificationSettings, case .muted = notificationSettings.muteState {
|
||||
buttonText = "Unmute"
|
||||
buttonText = presentationData.strings.PeerInfo_ButtonUnmute
|
||||
buttonIcon = .unmute
|
||||
} else {
|
||||
buttonText = "Mute"
|
||||
buttonText = presentationData.strings.PeerInfo_ButtonMute
|
||||
buttonIcon = .mute
|
||||
}
|
||||
case .more:
|
||||
buttonText = "More"
|
||||
buttonText = presentationData.strings.PeerInfo_ButtonMore
|
||||
buttonIcon = .more
|
||||
case .addMember:
|
||||
buttonText = "Add Member"
|
||||
buttonText = presentationData.strings.PeerInfo_ButtonAddMember
|
||||
buttonIcon = .addMember
|
||||
}
|
||||
buttonNode.update(size: buttonFrame.size, text: buttonText, icon: buttonIcon, isExpanded: self.isAvatarExpanded, presentationData: presentationData, transition: buttonTransition)
|
||||
transition.updateSublayerTransformScaleAdditive(node: buttonNode, scale: buttonsScale)
|
||||
|
||||
transition.updateAlpha(node: buttonNode, alpha: buttonsAlpha)
|
||||
if wasAdded {
|
||||
buttonNode.alpha = 0.0
|
||||
}
|
||||
buttonsAlphaTransition.updateAlpha(node: buttonNode, alpha: buttonsAlpha)
|
||||
|
||||
let hiddenWhileExpanded: Bool
|
||||
switch self.keepExpandedButtons {
|
||||
case .message:
|
||||
switch buttonKey {
|
||||
case .mute, .addMember:
|
||||
case .mute:
|
||||
hiddenWhileExpanded = true
|
||||
default:
|
||||
hiddenWhileExpanded = false
|
||||
}
|
||||
case .mute:
|
||||
switch buttonKey {
|
||||
case .message, .addMember:
|
||||
case .message:
|
||||
hiddenWhileExpanded = true
|
||||
default:
|
||||
hiddenWhileExpanded = false
|
||||
|
@ -174,7 +174,6 @@ final class PeerInfoPaneTabsContainerNode: ASDisplayNode {
|
||||
self?.paneSelected(specifier.key)
|
||||
})
|
||||
self.paneNodes[specifier.key] = paneNode
|
||||
self.scrollNode.addSubnode(paneNode)
|
||||
}
|
||||
paneNode.updateText(specifier.title, isSelected: selectedPane == specifier.key, presentationData: presentationData)
|
||||
}
|
||||
@ -191,7 +190,7 @@ final class PeerInfoPaneTabsContainerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
var tabSizes: [(CGSize, PeerInfoPaneTabsContainerPaneNode)] = []
|
||||
var tabSizes: [(CGSize, PeerInfoPaneTabsContainerPaneNode, Bool)] = []
|
||||
var totalRawTabSize: CGFloat = 0.0
|
||||
|
||||
var selectedFrame: CGRect?
|
||||
@ -199,20 +198,30 @@ final class PeerInfoPaneTabsContainerNode: ASDisplayNode {
|
||||
guard let paneNode = self.paneNodes[specifier.key] else {
|
||||
continue
|
||||
}
|
||||
let wasAdded = paneNode.supernode == nil
|
||||
if wasAdded {
|
||||
self.scrollNode.addSubnode(paneNode)
|
||||
}
|
||||
let paneNodeWidth = paneNode.updateLayout(height: size.height)
|
||||
let paneNodeSize = CGSize(width: paneNodeWidth, height: size.height)
|
||||
tabSizes.append((paneNodeSize, paneNode))
|
||||
tabSizes.append((paneNodeSize, paneNode, wasAdded))
|
||||
totalRawTabSize += paneNodeSize.width
|
||||
}
|
||||
|
||||
let spacing: CGFloat = 32.0
|
||||
if tabSizes.count == 1 {
|
||||
for i in 0 ..< tabSizes.count {
|
||||
let (paneNodeSize, paneNode) = tabSizes[i]
|
||||
let (paneNodeSize, paneNode, wasAdded) = tabSizes[i]
|
||||
let leftOffset: CGFloat = 16.0
|
||||
|
||||
let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0)), size: paneNodeSize)
|
||||
paneNode.frame = paneFrame
|
||||
if wasAdded {
|
||||
paneNode.frame = paneFrame
|
||||
paneNode.alpha = 0.0
|
||||
transition.updateAlpha(node: paneNode, alpha: 1.0)
|
||||
} else {
|
||||
transition.updateFrameAdditiveToCenter(node: paneNode, frame: paneFrame)
|
||||
}
|
||||
let areaSideInset: CGFloat = 16.0
|
||||
paneNode.updateArea(size: paneFrame.size, sideInset: areaSideInset)
|
||||
paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -areaSideInset, bottom: 0.0, right: -areaSideInset)
|
||||
@ -229,10 +238,16 @@ final class PeerInfoPaneTabsContainerNode: ASDisplayNode {
|
||||
|
||||
var leftOffset = perTabSpacing
|
||||
for i in 0 ..< tabSizes.count {
|
||||
let (paneNodeSize, paneNode) = tabSizes[i]
|
||||
let (paneNodeSize, paneNode, wasAdded) = tabSizes[i]
|
||||
|
||||
let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0)), size: paneNodeSize)
|
||||
paneNode.frame = paneFrame
|
||||
if wasAdded {
|
||||
paneNode.frame = paneFrame
|
||||
paneNode.alpha = 0.0
|
||||
transition.updateAlpha(node: paneNode, alpha: 1.0)
|
||||
} else {
|
||||
transition.updateFrameAdditiveToCenter(node: paneNode, frame: paneFrame)
|
||||
}
|
||||
let areaSideInset = floor(perTabSpacing / 2.0)
|
||||
paneNode.updateArea(size: paneFrame.size, sideInset: areaSideInset)
|
||||
paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -areaSideInset, bottom: 0.0, right: -areaSideInset)
|
||||
@ -248,9 +263,15 @@ final class PeerInfoPaneTabsContainerNode: ASDisplayNode {
|
||||
let sideInset: CGFloat = 16.0
|
||||
var leftOffset: CGFloat = sideInset
|
||||
for i in 0 ..< tabSizes.count {
|
||||
let (paneNodeSize, paneNode) = tabSizes[i]
|
||||
let (paneNodeSize, paneNode, wasAdded) = tabSizes[i]
|
||||
let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0)), size: paneNodeSize)
|
||||
paneNode.frame = paneFrame
|
||||
if wasAdded {
|
||||
paneNode.frame = paneFrame
|
||||
paneNode.alpha = 0.0
|
||||
transition.updateAlpha(node: paneNode, alpha: 1.0)
|
||||
} else {
|
||||
transition.updateFrameAdditiveToCenter(node: paneNode, frame: paneFrame)
|
||||
}
|
||||
paneNode.updateArea(size: paneFrame.size, sideInset: spacing)
|
||||
paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -spacing, bottom: 0.0, right: -spacing)
|
||||
if paneList[i].key == selectedPane {
|
||||
@ -262,13 +283,21 @@ final class PeerInfoPaneTabsContainerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
if let selectedFrame = selectedFrame {
|
||||
let wasAdded = self.selectedLineNode.isHidden
|
||||
self.selectedLineNode.isHidden = false
|
||||
transition.updateFrame(node: self.selectedLineNode, frame: CGRect(origin: CGPoint(x: selectedFrame.minX, y: size.height - 4.0), size: CGSize(width: selectedFrame.width, height: 4.0)))
|
||||
let lineFrame = CGRect(origin: CGPoint(x: selectedFrame.minX, y: size.height - 4.0), size: CGSize(width: selectedFrame.width, height: 4.0))
|
||||
if wasAdded {
|
||||
self.selectedLineNode.frame = lineFrame
|
||||
self.selectedLineNode.alpha = 0.0
|
||||
transition.updateAlpha(node: self.selectedLineNode, alpha: 1.0)
|
||||
} else {
|
||||
transition.updateFrame(node: self.selectedLineNode, frame: lineFrame)
|
||||
}
|
||||
if focusOnSelectedPane {
|
||||
if selectedPane == paneList.first?.key {
|
||||
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size))
|
||||
} else if selectedPane == paneList.last?.key {
|
||||
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width, y: 0.0), size: self.scrollNode.bounds.size))
|
||||
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: max(0.0, self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width), y: 0.0), size: self.scrollNode.bounds.size))
|
||||
} else {
|
||||
let contentOffsetX = max(0.0, min(self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width, floor(selectedFrame.midX - self.scrollNode.bounds.width / 2.0)))
|
||||
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: contentOffsetX, y: 0.0), size: self.scrollNode.bounds.size))
|
||||
@ -457,7 +486,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode {
|
||||
case .music:
|
||||
paneNode = PeerInfoListPaneNode(context: self.context, chatControllerInteraction: self.chatControllerInteraction!, peerId: self.peerId, tagMask: .music)
|
||||
case .groupsInCommon:
|
||||
paneNode = PeerInfoGroupsInCommonPaneNode(context: self.context, peerId: self.peerId, chatControllerInteraction: self.chatControllerInteraction!, openPeerContextAction: self.openPeerContextAction!, peers: data?.groupsInCommon ?? [])
|
||||
paneNode = PeerInfoGroupsInCommonPaneNode(context: self.context, peerId: self.peerId, chatControllerInteraction: self.chatControllerInteraction!, openPeerContextAction: self.openPeerContextAction!, groupsInCommonContext: data!.groupsInCommon!)
|
||||
case .members:
|
||||
if case let .longList(membersContext) = data?.members {
|
||||
paneNode = PeerInfoMembersPaneNode(context: self.context, peerId: self.peerId, membersContext: membersContext, action: { [weak self] member, action in
|
||||
|
@ -575,14 +575,14 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
||||
if let user = data.peer as? TelegramUser {
|
||||
if let phone = user.phone {
|
||||
let formattedPhone = formatPhoneNumber(phone)
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 2, label: "mobile", text: formattedPhone, textColor: .accent, action: {
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 2, label: presentationData.strings.ContactInfo_PhoneLabelMobile, text: formattedPhone, textColor: .accent, action: {
|
||||
interaction.openPhone(phone)
|
||||
}, longTapAction: { sourceNode in
|
||||
interaction.openPeerInfoContextMenu(.phone(formattedPhone), sourceNode)
|
||||
}))
|
||||
}
|
||||
if let username = user.username {
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 1, label: "username", text: "@\(username)", textColor: .accent, action: {
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 1, label: presentationData.strings.Profile_Username, text: "@\(username)", textColor: .accent, action: {
|
||||
interaction.openUsername(username)
|
||||
}, longTapAction: { sourceNode in
|
||||
interaction.openPeerInfoContextMenu(.link, sourceNode)
|
||||
@ -590,36 +590,37 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
||||
}
|
||||
if let cachedData = data.cachedData as? CachedUserData {
|
||||
if user.isScam {
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Profile_About, text: user.botInfo != nil ? presentationData.strings.UserInfo_ScamBotWarning : presentationData.strings.UserInfo_ScamUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledBioEntities : []), action: nil))
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Channel_AboutItem, text: user.botInfo != nil ? presentationData.strings.UserInfo_ScamBotWarning : presentationData.strings.UserInfo_ScamUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledBioEntities : []), action: nil))
|
||||
} else if let about = cachedData.about, !about.isEmpty {
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Profile_About, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction))
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Channel_AboutItem, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction))
|
||||
}
|
||||
}
|
||||
if !data.isContact {
|
||||
if user.botInfo == nil {
|
||||
items[.peerInfo]!.append(PeerInfoScreenActionItem(id: 3, text: "Add Contact", action: {
|
||||
items[.peerInfo]!.append(PeerInfoScreenActionItem(id: 3, text: presentationData.strings.UserInfo_AddContact, action: {
|
||||
interaction.openAddContact()
|
||||
}))
|
||||
}
|
||||
if let cachedData = data.cachedData as? CachedUserData {
|
||||
if cachedData.isBlocked {
|
||||
items[.peerInfo]!.append(PeerInfoScreenActionItem(id: 4, text: user.botInfo != nil ? "Restart Bot" : "Unblock", action: {
|
||||
interaction.updateBlocked(false)
|
||||
}))
|
||||
}
|
||||
if let cachedData = data.cachedData as? CachedUserData {
|
||||
if cachedData.isBlocked {
|
||||
items[.peerInfo]!.append(PeerInfoScreenActionItem(id: 4, text: user.botInfo != nil ? presentationData.strings.Bot_Unblock : presentationData.strings.Conversation_Unblock, action: {
|
||||
interaction.updateBlocked(false)
|
||||
}))
|
||||
} else {
|
||||
if user.flags.contains(.isSupport) {
|
||||
} else {
|
||||
if user.flags.contains(.isSupport) {
|
||||
} else {
|
||||
items[.peerInfo]!.append(PeerInfoScreenActionItem(id: 4, text: user.botInfo != nil ? "Stop Bot" : "Block User", color: .destructive, action: {
|
||||
interaction.updateBlocked(true)
|
||||
}))
|
||||
}
|
||||
items[.peerInfo]!.append(PeerInfoScreenActionItem(id: 4, text: user.botInfo != nil ? presentationData.strings.Bot_Stop : presentationData.strings.Conversation_BlockUser, color: .destructive, action: {
|
||||
interaction.updateBlocked(true)
|
||||
}))
|
||||
}
|
||||
}
|
||||
if user.botInfo != nil, !user.isVerified {
|
||||
items[.peerInfo]!.append(PeerInfoScreenActionItem(id: 5, text: presentationData.strings.ReportPeer_Report, action: {
|
||||
interaction.openReport()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
if user.botInfo != nil, !user.isVerified {
|
||||
items[.peerInfo]!.append(PeerInfoScreenActionItem(id: 5, text: presentationData.strings.ReportPeer_Report, action: {
|
||||
interaction.openReport()
|
||||
}))
|
||||
}
|
||||
} else if let channel = data.peer as? TelegramChannel {
|
||||
let ItemUsername = 1
|
||||
@ -655,9 +656,9 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
||||
}
|
||||
if let cachedData = data.cachedData as? CachedChannelData {
|
||||
if channel.isScam {
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Profile_About, text: presentationData.strings.GroupInfo_ScamGroupWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledBioEntities), action: nil))
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Channel_AboutItem, text: presentationData.strings.GroupInfo_ScamGroupWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledBioEntities), action: nil))
|
||||
} else if let about = cachedData.about, !about.isEmpty {
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Profile_About, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction))
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Channel_AboutItem, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction))
|
||||
}
|
||||
|
||||
if case .broadcast = channel.info {
|
||||
@ -687,9 +688,9 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
||||
} else if let group = data.peer as? TelegramGroup {
|
||||
if let cachedData = data.cachedData as? CachedGroupData {
|
||||
if group.isScam {
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Profile_About, text: presentationData.strings.GroupInfo_ScamGroupWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledBioEntities), action: nil))
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Channel_AboutItem, text: presentationData.strings.GroupInfo_ScamGroupWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledBioEntities), action: nil))
|
||||
} else if let about = cachedData.about, !about.isEmpty {
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Profile_About, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction))
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Channel_AboutItem, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -755,13 +756,13 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
||||
let globalNotificationSettings: GlobalNotificationSettings = data.globalNotificationSettings ?? GlobalNotificationSettings.defaultSettings
|
||||
soundLabel = localizedPeerNotificationSoundString(strings: presentationData.strings, sound: notificationSettings.messageSound, default: globalNotificationSettings.effective.privateChats.sound)
|
||||
|
||||
items[.notifications]!.append(PeerInfoScreenDisclosureItem(id: 0, label: notificationsLabel, text: "Notifications", action: {
|
||||
items[.notifications]!.append(PeerInfoScreenDisclosureItem(id: 0, label: notificationsLabel, text: presentationData.strings.GroupInfo_Notifications, action: {
|
||||
interaction.editingOpenNotificationSettings()
|
||||
}))
|
||||
items[.notifications]!.append(PeerInfoScreenDisclosureItem(id: 1, label: soundLabel, text: "Sound", action: {
|
||||
items[.notifications]!.append(PeerInfoScreenDisclosureItem(id: 1, label: soundLabel, text: presentationData.strings.GroupInfo_Sound, action: {
|
||||
interaction.editingOpenSoundSettings()
|
||||
}))
|
||||
items[.notifications]!.append(PeerInfoScreenSwitchItem(id: 2, text: "Show Message Text", value: notificationSettings.displayPreviews != .hide, toggled: { value in
|
||||
items[.notifications]!.append(PeerInfoScreenSwitchItem(id: 2, text: presentationData.strings.Notification_Exceptions_PreviewAlwaysOn, value: notificationSettings.displayPreviews != .hide, toggled: { value in
|
||||
interaction.editingToggleShowMessageText(value)
|
||||
}))
|
||||
}
|
||||
@ -1170,6 +1171,15 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
}))
|
||||
}
|
||||
if strongSelf.searchDisplayController == nil {
|
||||
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_ContextMenuMore, color: .accent, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
if let strongSelf = self {
|
||||
strongSelf.chatInterfaceInteraction.toggleMessagesSelection([message.id], true)
|
||||
strongSelf.expandTabs()
|
||||
}
|
||||
}))
|
||||
}
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
@ -1274,6 +1284,18 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
})))
|
||||
}
|
||||
|
||||
items.append(.separator)
|
||||
items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuMore, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.chatInterfaceInteraction.toggleMessagesSelection([message.id], true)
|
||||
strongSelf.expandTabs()
|
||||
f(.default)
|
||||
})))
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
@ -1555,6 +1577,15 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
self?.openAvatarForEditing()
|
||||
}
|
||||
|
||||
self.headerNode.requestUpdateLayout = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let (layout, navigationHeight) = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
|
||||
}
|
||||
}
|
||||
|
||||
self.headerNode.navigationButtonContainer.performAction = { [weak self] key in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -1626,7 +1657,36 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
} else if let group = data.peer as? TelegramGroup, canEditPeerInfo(peer: group) {
|
||||
let title = strongSelf.headerNode.editingContentNode.editingTextForKey(.title) ?? ""
|
||||
let description = strongSelf.headerNode.editingContentNode.editingTextForKey(.description) ?? ""
|
||||
|
||||
var updateDataSignals: [Signal<Never, Void>] = []
|
||||
|
||||
if title != group.title {
|
||||
updateDataSignals.append(
|
||||
updatePeerTitle(account: strongSelf.context.account, peerId: group.id, title: title)
|
||||
|> ignoreValues
|
||||
|> mapError { _ in return Void() }
|
||||
)
|
||||
}
|
||||
if description != (data.cachedData as? CachedGroupData)?.about {
|
||||
updateDataSignals.append(
|
||||
updatePeerDescription(account: strongSelf.context.account, peerId: group.id, description: description.isEmpty ? nil : description)
|
||||
|> ignoreValues
|
||||
|> mapError { _ in return Void() }
|
||||
)
|
||||
}
|
||||
strongSelf.activeActionDisposable.set((combineLatest(updateDataSignals)
|
||||
|> deliverOnMainQueue).start(error: { _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
|
||||
}, completed: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
|
||||
}))
|
||||
} else if let channel = data.peer as? TelegramChannel, canEditPeerInfo(peer: channel) {
|
||||
let title = strongSelf.headerNode.editingContentNode.editingTextForKey(.title) ?? ""
|
||||
let description = strongSelf.headerNode.editingContentNode.editingTextForKey(.description) ?? ""
|
||||
@ -1717,9 +1777,27 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
|
||||
private func updateData(_ data: PeerInfoScreenData) {
|
||||
var previousMemberCount: Int?
|
||||
if let data = self.data {
|
||||
if let members = data.members, case let .shortList(_, memberList) = members {
|
||||
previousMemberCount = memberList.count
|
||||
}
|
||||
}
|
||||
self.data = data
|
||||
if let (layout, navigationHeight) = self.validLayout {
|
||||
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: self.didSetReady ? .animated(duration: 0.3, curve: .spring) : .immediate)
|
||||
var updatedMemberCount: Int?
|
||||
if let data = self.data {
|
||||
if let members = data.members, case let .shortList(_, memberList) = members {
|
||||
updatedMemberCount = memberList.count
|
||||
}
|
||||
}
|
||||
|
||||
var membersUpdated = false
|
||||
if let previousMemberCount = previousMemberCount, let updatedMemberCount = updatedMemberCount, previousMemberCount > updatedMemberCount {
|
||||
membersUpdated = true
|
||||
}
|
||||
|
||||
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: self.didSetReady && membersUpdated ? .animated(duration: 0.3, curve: .spring) : .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1729,6 +1807,16 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
}
|
||||
|
||||
private func expandTabs() {
|
||||
if let (layout, navigationHeight) = self.validLayout {
|
||||
let contentOffset = self.scrollNode.view.contentOffset
|
||||
let paneAreaExpansionFinalPoint: CGFloat = self.paneContainerNode.frame.minY - navigationHeight
|
||||
if contentOffset.y < paneAreaExpansionFinalPoint - CGFloat.ulpOfOne {
|
||||
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: paneAreaExpansionFinalPoint), animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func editingCancelPressed() {
|
||||
self.headerNode.navigationButtonContainer.performAction?(.cancel)
|
||||
}
|
||||
@ -1855,41 +1943,13 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
case .call:
|
||||
self.requestCall()
|
||||
case .mute:
|
||||
let peerId = self.peerId
|
||||
let _ = (self.context.account.postbox.transaction { transaction -> (TelegramPeerNotificationSettings, GlobalNotificationSettings) in
|
||||
let peerSettings: TelegramPeerNotificationSettings = (transaction.getPeerNotificationSettings(peerId) as? TelegramPeerNotificationSettings) ?? TelegramPeerNotificationSettings.defaultSettings
|
||||
let globalSettings: GlobalNotificationSettings = (transaction.getPreferencesEntry(key: PreferencesKeys.globalNotifications) as? GlobalNotificationSettings) ?? GlobalNotificationSettings.defaultSettings
|
||||
return (peerSettings, globalSettings)
|
||||
let muteInterval: Int32?
|
||||
if let notificationSettings = self.data?.notificationSettings, case .muted = notificationSettings.muteState {
|
||||
muteInterval = nil
|
||||
} else {
|
||||
muteInterval = Int32.max
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peerSettings, globalSettings in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let soundSettings: NotificationSoundSettings?
|
||||
if case .default = peerSettings.messageSound {
|
||||
soundSettings = NotificationSoundSettings(value: nil)
|
||||
} else {
|
||||
soundSettings = NotificationSoundSettings(value: peerSettings.messageSound)
|
||||
}
|
||||
let muteSettingsController = notificationMuteSettingsController(presentationData: strongSelf.presentationData, notificationSettings: globalSettings.effective.groupChats, soundSettings: soundSettings, openSoundSettings: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let soundController = notificationSoundSelectionController(context: strongSelf.context, isModal: true, currentSound: peerSettings.messageSound, defaultSound: globalSettings.effective.groupChats.sound, completion: { sound in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = updatePeerNotificationSoundInteractive(account: strongSelf.context.account, peerId: strongSelf.peerId, sound: sound).start()
|
||||
})
|
||||
strongSelf.controller?.present(soundController, in: .window(.root))
|
||||
}, updateSettings: { value in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = updatePeerMuteSetting(account: strongSelf.context.account, peerId: strongSelf.peerId, muteInterval: value).start()
|
||||
})
|
||||
strongSelf.controller?.present(muteSettingsController, in: .window(.root))
|
||||
})
|
||||
let _ = updatePeerMuteSetting(account: self.context.account, peerId: self.peerId, muteInterval: muteInterval).start()
|
||||
case .more:
|
||||
guard let data = self.data, let peer = data.peer else {
|
||||
return
|
||||
@ -2212,6 +2272,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
let _ = updatePeerNotificationSoundInteractive(account: strongSelf.context.account, peerId: strongSelf.peerId, sound: sound).start()
|
||||
})
|
||||
soundController.navigationPresentation = .modal
|
||||
strongSelf.controller?.push(soundController)
|
||||
}, updateSettings: { value in
|
||||
guard let strongSelf = self else {
|
||||
@ -3085,17 +3146,19 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
} else {
|
||||
mode = .privateLink
|
||||
}
|
||||
let visibilityController = channelVisibilityController(context: strongSelf.context, peerId: groupPeer.id, mode: mode, upgradedToSupergroup: { _, f in f() })
|
||||
visibilityController.navigationPresentation = .modal
|
||||
let visibilityController = channelVisibilityController(context: strongSelf.context, peerId: groupPeer.id, mode: mode, upgradedToSupergroup: { _, f in f() }, onDismissRemoveController: contactsController)
|
||||
//visibilityController.navigationPresentation = .modal
|
||||
|
||||
if let navigationController = strongSelf.controller?.navigationController as? NavigationController {
|
||||
contactsController?.push(visibilityController)
|
||||
|
||||
/*if let navigationController = strongSelf.controller?.navigationController as? NavigationController {
|
||||
var controllers = navigationController.viewControllers
|
||||
if let contactsController = contactsController {
|
||||
controllers.removeAll(where: { $0 === contactsController })
|
||||
}
|
||||
controllers.append(visibilityController)
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
strongSelf.controller?.push(contactsController)
|
||||
@ -3291,21 +3354,33 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
return
|
||||
}
|
||||
|
||||
var tagMask: MessageTags = .file
|
||||
if let currentPaneKey = self.paneContainerNode.currentPaneKey {
|
||||
switch currentPaneKey {
|
||||
case .links:
|
||||
tagMask = .webPage
|
||||
case .music:
|
||||
tagMask = .music
|
||||
default:
|
||||
break
|
||||
if let currentPaneKey = self.paneContainerNode.currentPaneKey, case .members = currentPaneKey {
|
||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, placeholder: self.presentationData.strings.Common_Search, contentNode: ChannelMembersSearchContainerNode(context: self.context, peerId: self.peerId, mode: .searchMembers, filters: [], searchContext: nil, openPeer: { [weak self] peer, participant in
|
||||
self?.openPeer(peerId: peer.id, navigation: .info)
|
||||
}, updateActivity: { _ in
|
||||
}, pushController: { [weak self] c in
|
||||
self?.controller?.push(c)
|
||||
}), cancel: { [weak self] in
|
||||
self?.deactivateSearch()
|
||||
})
|
||||
} else {
|
||||
var tagMask: MessageTags = .file
|
||||
if let currentPaneKey = self.paneContainerNode.currentPaneKey {
|
||||
switch currentPaneKey {
|
||||
case .links:
|
||||
tagMask = .webPage
|
||||
case .music:
|
||||
tagMask = .music
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, placeholder: self.presentationData.strings.Common_Search, contentNode: ChatHistorySearchContainerNode(context: self.context, peerId: self.peerId, tagMask: tagMask, interfaceInteraction: self.chatInterfaceInteraction), cancel: { [weak self] in
|
||||
self?.deactivateSearch()
|
||||
})
|
||||
}
|
||||
|
||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, placeholder: self.presentationData.strings.Common_Search, contentNode: ChatHistorySearchContainerNode(context: self.context, peerId: self.peerId, tagMask: tagMask, interfaceInteraction: self.chatInterfaceInteraction), cancel: { [weak self] in
|
||||
self?.deactivateSearch()
|
||||
})
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
|
||||
if let navigationBar = self.controller?.navigationBar {
|
||||
transition.updateAlpha(node: navigationBar, alpha: 0.0)
|
||||
@ -3419,10 +3494,12 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
for (sectionId, sectionItems) in editingItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction) {
|
||||
validEditingSections.append(sectionId)
|
||||
|
||||
var wasAdded = false
|
||||
let sectionNode: PeerInfoScreenItemSectionContainerNode
|
||||
if let current = self.editingSections[sectionId] {
|
||||
sectionNode = current
|
||||
} else {
|
||||
wasAdded = true
|
||||
sectionNode = PeerInfoScreenItemSectionContainerNode()
|
||||
self.editingSections[sectionId] = sectionNode
|
||||
self.scrollNode.addSubnode(sectionNode)
|
||||
@ -3430,13 +3507,18 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
|
||||
let sectionHeight = sectionNode.update(width: layout.size.width, presentationData: self.presentationData, items: sectionItems, transition: transition)
|
||||
let sectionFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: layout.size.width, height: sectionHeight))
|
||||
if additive {
|
||||
transition.updateFrameAdditive(node: sectionNode, frame: sectionFrame)
|
||||
} else {
|
||||
transition.updateFrame(node: sectionNode, frame: sectionFrame)
|
||||
}
|
||||
|
||||
transition.updateAlpha(node: sectionNode, alpha: self.state.isEditing ? 1.0 : 0.0)
|
||||
if wasAdded {
|
||||
sectionNode.frame = sectionFrame
|
||||
sectionNode.alpha = self.state.isEditing ? 1.0 : 0.0
|
||||
} else {
|
||||
if additive {
|
||||
transition.updateFrameAdditive(node: sectionNode, frame: sectionFrame)
|
||||
} else {
|
||||
transition.updateFrame(node: sectionNode, frame: sectionFrame)
|
||||
}
|
||||
transition.updateAlpha(node: sectionNode, alpha: self.state.isEditing ? 1.0 : 0.0)
|
||||
}
|
||||
if !sectionHeight.isZero && self.state.isEditing {
|
||||
contentHeight += sectionHeight
|
||||
contentHeight += sectionSpacing
|
||||
@ -3625,14 +3707,15 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
if self.state.selectedMessageIds == nil {
|
||||
if let currentPaneKey = self.paneContainerNode.currentPaneKey {
|
||||
switch currentPaneKey {
|
||||
case .files, .music, .links:
|
||||
case .files, .music, .links, .members:
|
||||
navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true))
|
||||
default:
|
||||
break
|
||||
}
|
||||
switch currentPaneKey {
|
||||
case .media, .files, .music, .links, .voice:
|
||||
navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .select, isForExpandedView: true))
|
||||
//navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .select, isForExpandedView: true))
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
Binary file not shown.
Binary file not shown.
@ -448,12 +448,12 @@ public final class WalletStrings: Equatable {
|
||||
public var Wallet_SecureStorageReset_Title: String { return self._s[218]! }
|
||||
public var Wallet_Receive_CommentHeader: String { return self._s[219]! }
|
||||
public var Wallet_Info_ReceiveGrams: String { return self._s[220]! }
|
||||
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
|
||||
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
|
||||
let form = getPluralizationForm(self.lc, value)
|
||||
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
|
||||
return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue)
|
||||
}
|
||||
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
|
||||
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
|
||||
let form = getPluralizationForm(self.lc, value)
|
||||
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
|
||||
return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue)
|
||||
|
Loading…
x
Reference in New Issue
Block a user