Merge branches 'master' and 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2020-02-12 02:47:49 +04:00
commit adc000ddf6
19 changed files with 4190 additions and 3690 deletions

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@ enum ChannelMembersSearchControllerMode {
case ban
}
enum ChannelMembersSearchFilter {
public enum ChannelMembersSearchFilter {
case exclude([PeerId])
case disable([PeerId])
}

View File

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

View File

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

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_verify_big.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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