mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various Improvements
This commit is contained in:
parent
1ec1f5ca78
commit
d8d344d2c1
@ -6923,3 +6923,8 @@ Sorry for the inconvenience.";
|
||||
"InviteLink.InviteLinkForwardTooltip.TwoChats.One" = "Invite link forwarded to **%@** and **%@**";
|
||||
"InviteLink.InviteLinkForwardTooltip.ManyChats.One" = "Invite link forwarded to **%@** and %@ others";
|
||||
"InviteLink.InviteLinkForwardTooltip.SavedMessages.One" = "Invite link forwarded to **Saved Messages**";
|
||||
|
||||
"Conversation.RequestToJoinChannel" = "REQUEST TO JOIN";
|
||||
"Conversation.RequestToJoinGroup" = "REQUEST TO JOIN";
|
||||
|
||||
"Channel.AdminLog.JoinedViaRequest" = "%1$@ joined via invite link %2$@, approved by %3$@";
|
||||
|
@ -55,6 +55,7 @@ swift_library(
|
||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||
"//submodules/AvatarNode:AvatarNode",
|
||||
"//submodules/LocalizedPeerData:LocalizedPeerData",
|
||||
"//submodules/PeerInfoAvatarListNode:PeerInfoAvatarListNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -52,6 +52,8 @@ private enum InviteLinkViewEntryId: Hashable {
|
||||
case link
|
||||
case creatorHeader
|
||||
case creator
|
||||
case requestHeader
|
||||
case request(EnginePeer.Id)
|
||||
case importerHeader
|
||||
case importer(EnginePeer.Id)
|
||||
}
|
||||
@ -60,6 +62,8 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
case link(PresentationTheme, ExportedInvitation)
|
||||
case creatorHeader(PresentationTheme, String)
|
||||
case creator(PresentationTheme, PresentationDateTimeFormat, EnginePeer, Int32)
|
||||
case requestHeader(PresentationTheme, String, String, Bool)
|
||||
case request(Int32, PresentationTheme, PresentationDateTimeFormat, EnginePeer, Int32, Bool)
|
||||
case importerHeader(PresentationTheme, String, String, Bool)
|
||||
case importer(Int32, PresentationTheme, PresentationDateTimeFormat, EnginePeer, Int32, Bool)
|
||||
|
||||
@ -71,6 +75,10 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
return .creatorHeader
|
||||
case .creator:
|
||||
return .creator
|
||||
case .requestHeader:
|
||||
return .requestHeader
|
||||
case let .request(_, _, _, peer, _, _):
|
||||
return .request(peer.id)
|
||||
case .importerHeader:
|
||||
return .importerHeader
|
||||
case let .importer(_, _, _, peer, _, _):
|
||||
@ -98,6 +106,18 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .requestHeader(lhsTheme, lhsTitle, lhsSubtitle, lhsExpired):
|
||||
if case let .requestHeader(rhsTheme, rhsTitle, rhsSubtitle, rhsExpired) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsExpired == rhsExpired {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .request(lhsIndex, lhsTheme, lhsDateTimeFormat, lhsPeer, lhsDate, lhsLoading):
|
||||
if case let .request(rhsIndex, rhsTheme, rhsDateTimeFormat, rhsPeer, rhsDate, rhsLoading) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsPeer == rhsPeer, lhsDate == rhsDate, lhsLoading == rhsLoading {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .importerHeader(lhsTheme, lhsTitle, lhsSubtitle, lhsExpired):
|
||||
if case let .importerHeader(rhsTheme, rhsTitle, rhsSubtitle, rhsExpired) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsExpired == rhsExpired {
|
||||
return true
|
||||
@ -119,33 +139,49 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
switch rhs {
|
||||
case .link:
|
||||
return false
|
||||
case .creatorHeader, .creator, .importerHeader, .importer:
|
||||
case .creatorHeader, .creator, .requestHeader, .request, .importerHeader, .importer:
|
||||
return true
|
||||
}
|
||||
case .creatorHeader:
|
||||
switch rhs {
|
||||
case .link, .creatorHeader:
|
||||
return false
|
||||
case .creator, .importerHeader, .importer:
|
||||
case .creator, .requestHeader, .request, .importerHeader, .importer:
|
||||
return true
|
||||
}
|
||||
case .creator:
|
||||
switch rhs {
|
||||
case .link, .creatorHeader, .creator:
|
||||
return false
|
||||
case .importerHeader, .importer:
|
||||
case .requestHeader, .request, .importerHeader, .importer:
|
||||
return true
|
||||
}
|
||||
case .requestHeader:
|
||||
switch rhs {
|
||||
case .link, .creatorHeader, .creator, .requestHeader:
|
||||
return false
|
||||
case .request, .importerHeader, .importer:
|
||||
return true
|
||||
}
|
||||
case let .request(lhsIndex, _, _, _, _, _):
|
||||
switch rhs {
|
||||
case .link, .creatorHeader, .creator, .requestHeader:
|
||||
return false
|
||||
case let .request(rhsIndex, _, _, _, _, _):
|
||||
return lhsIndex < rhsIndex
|
||||
case .importerHeader, .importer:
|
||||
return true
|
||||
}
|
||||
case .importerHeader:
|
||||
switch rhs {
|
||||
case .link, .creatorHeader, .importerHeader:
|
||||
case .link, .creatorHeader, .creator, .requestHeader, .request, .importerHeader:
|
||||
return false
|
||||
case .creator, .importer:
|
||||
case .importer:
|
||||
return true
|
||||
}
|
||||
case let .importer(lhsIndex, _, _, _, _, _):
|
||||
switch rhs {
|
||||
case .link, .creatorHeader, .creator, .importerHeader:
|
||||
case .link, .creatorHeader, .creator, .importerHeader, .request, .requestHeader:
|
||||
return false
|
||||
case let .importer(rhsIndex, _, _, _, _, _):
|
||||
return lhsIndex < rhsIndex
|
||||
@ -175,7 +211,7 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: interaction.context, peer: peer, height: .generic, nameStyle: .distinctBold, presence: nil, text: .text(dateString, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: peer.id != account.peerId, sectionId: 0, action: {
|
||||
interaction.openPeer(peer.id)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, hasTopStripe: false, noInsets: true, tag: nil)
|
||||
case let .importerHeader(_, title, subtitle, expired):
|
||||
case let .importerHeader(_, title, subtitle, expired), let .requestHeader(_, title, subtitle, expired):
|
||||
let additionalText: SectionHeaderAdditionalText
|
||||
if !subtitle.isEmpty {
|
||||
if expired {
|
||||
@ -187,7 +223,7 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
additionalText = .none
|
||||
}
|
||||
return SectionHeaderItem(presentationData: ItemListPresentationData(presentationData), title: title, additionalText: additionalText)
|
||||
case let .importer(_, _, dateTimeFormat, peer, date, loading):
|
||||
case let .importer(_, _, dateTimeFormat, peer, date, loading), let .request(_, _, dateTimeFormat, peer, date, loading):
|
||||
let dateString = stringForFullDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: dateTimeFormat)
|
||||
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: interaction.context, peer: peer, height: .generic, nameStyle: .distinctBold, presence: nil, text: .text(dateString, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: peer.id != account.peerId, sectionId: 0, action: {
|
||||
interaction.openPeer(peer.id)
|
||||
@ -637,14 +673,47 @@ public final class InviteLinkViewController: ViewController {
|
||||
entries.append(.creator(presentationData.theme, presentationData.dateTimeFormat, EnginePeer(creatorPeer), invite.date))
|
||||
|
||||
if !requestsState.importers.isEmpty || (state.isLoadingMore && requestsState.count > 0) {
|
||||
entries.append(.importerHeader(presentationData.theme, presentationData.strings.MemberRequests_PeopleRequested(Int32(requestsState.count)).uppercased(), "", false))
|
||||
entries.append(.requestHeader(presentationData.theme, presentationData.strings.MemberRequests_PeopleRequested(Int32(requestsState.count)).uppercased(), "", false))
|
||||
}
|
||||
|
||||
let count: Int32
|
||||
let loading: Bool
|
||||
|
||||
var count: Int32
|
||||
var loading: Bool
|
||||
var index: Int32 = 0
|
||||
if requestsState.importers.isEmpty && requestsState.isLoadingMore {
|
||||
count = min(4, state.count)
|
||||
loading = true
|
||||
let fakeUser = TelegramUser(id: EnginePeer.Id(namespace: .max, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [])
|
||||
for i in 0 ..< count {
|
||||
entries.append(.request(Int32(i), presentationData.theme, presentationData.dateTimeFormat, EnginePeer.user(fakeUser), 0, true))
|
||||
}
|
||||
} else {
|
||||
count = min(4, Int32(requestsState.importers.count))
|
||||
loading = false
|
||||
for importer in requestsState.importers {
|
||||
if let peer = importer.peer.peer {
|
||||
entries.append(.request(index, presentationData.theme, presentationData.dateTimeFormat, EnginePeer(peer), importer.date, false))
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
if !state.importers.isEmpty || (state.isLoadingMore && state.count > 0) {
|
||||
let subtitle: String
|
||||
let subtitleExpired: Bool
|
||||
if let usageLimit = invite.usageLimit {
|
||||
let remaining = max(0, usageLimit - state.count)
|
||||
subtitle = presentationData.strings.InviteLink_PeopleRemaining(remaining).uppercased()
|
||||
subtitleExpired = remaining <= 0
|
||||
} else {
|
||||
subtitle = ""
|
||||
subtitleExpired = false
|
||||
}
|
||||
|
||||
entries.append(.importerHeader(presentationData.theme, presentationData.strings.InviteLink_PeopleJoined(Int32(state.count)).uppercased(), subtitle, subtitleExpired))
|
||||
}
|
||||
|
||||
index = 0
|
||||
if state.importers.isEmpty && state.isLoadingMore {
|
||||
count = min(4, state.count)
|
||||
loading = true
|
||||
let fakeUser = TelegramUser(id: EnginePeer.Id(namespace: .max, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [])
|
||||
@ -652,9 +721,9 @@ public final class InviteLinkViewController: ViewController {
|
||||
entries.append(.importer(Int32(i), presentationData.theme, presentationData.dateTimeFormat, EnginePeer.user(fakeUser), 0, true))
|
||||
}
|
||||
} else {
|
||||
count = min(4, Int32(requestsState.importers.count))
|
||||
count = min(4, Int32(state.importers.count))
|
||||
loading = false
|
||||
for importer in requestsState.importers {
|
||||
for importer in state.importers {
|
||||
if let peer = importer.peer.peer {
|
||||
entries.append(.importer(index, presentationData.theme, presentationData.dateTimeFormat, EnginePeer(peer), importer.date, false))
|
||||
}
|
||||
@ -662,43 +731,6 @@ public final class InviteLinkViewController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
// if !state.importers.isEmpty || (state.isLoadingMore && state.count > 0) {
|
||||
// let subtitle: String
|
||||
// let subtitleExpired: Bool
|
||||
// if let usageLimit = invite.usageLimit {
|
||||
// let remaining = max(0, usageLimit - state.count)
|
||||
// subtitle = presentationData.strings.InviteLink_PeopleRemaining(remaining).uppercased()
|
||||
// subtitleExpired = remaining <= 0
|
||||
// } else {
|
||||
// subtitle = ""
|
||||
// subtitleExpired = false
|
||||
// }
|
||||
//
|
||||
// entries.append(.importerHeader(presentationData.theme, presentationData.strings.InviteLink_PeopleJoined(Int32(state.count)).uppercased(), subtitle, subtitleExpired))
|
||||
// }
|
||||
|
||||
// let count: Int32
|
||||
// let loading: Bool
|
||||
//
|
||||
// var index: Int32 = 0
|
||||
// if state.importers.isEmpty && state.isLoadingMore {
|
||||
// count = min(4, state.count)
|
||||
// loading = true
|
||||
// let fakeUser = TelegramUser(id: EnginePeer.Id(namespace: .max, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [])
|
||||
// for i in 0 ..< count {
|
||||
// entries.append(.importer(Int32(i), presentationData.theme, presentationData.dateTimeFormat, EnginePeer.user(fakeUser), 0, true))
|
||||
// }
|
||||
// } else {
|
||||
// count = min(4, Int32(state.importers.count))
|
||||
// loading = false
|
||||
// for importer in state.importers {
|
||||
// if let peer = importer.peer.peer {
|
||||
// entries.append(.importer(index, presentationData.theme, presentationData.dateTimeFormat, EnginePeer(peer), importer.date, false))
|
||||
// }
|
||||
// index += 1
|
||||
// }
|
||||
// }
|
||||
|
||||
let previousCount = previousCount.swap(count)
|
||||
let previousLoading = previousLoading.swap(loading)
|
||||
|
||||
|
@ -151,21 +151,29 @@ private func inviteRequestsControllerEntries(presentationData: PresentationData,
|
||||
return entries
|
||||
}
|
||||
|
||||
private struct InviteRequestsControllerState: Equatable {
|
||||
var searchingMembers: Bool
|
||||
}
|
||||
|
||||
public func inviteRequestsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: EnginePeer.Id, existingContext: PeerInvitationImportersContext? = nil) -> ViewController {
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||
var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
|
||||
var navigateToProfileImpl: ((EnginePeer) -> Void)?
|
||||
|
||||
var dismissInputImpl: (() -> Void)?
|
||||
var dismissTooltipsImpl: (() -> Void)?
|
||||
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
let statePromise = ValuePromise(InviteRequestsControllerState(searchingMembers: false), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: InviteRequestsControllerState(searchingMembers: false))
|
||||
let updateState: ((InviteRequestsControllerState) -> InviteRequestsControllerState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
let updateDisposable = MetaDisposable()
|
||||
actionsDisposable.add(updateDisposable)
|
||||
|
||||
var getControllerImpl: (() -> ViewController?)?
|
||||
|
||||
|
||||
let importersContext = existingContext ?? context.engine.peers.peerInvitationImporters(peerId: peerId, subject: .requests(query: nil))
|
||||
|
||||
let arguments = InviteRequestsControllerArguments(context: context, openLinks: {
|
||||
@ -195,7 +203,7 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
|
||||
}, denyRequest: { peer in
|
||||
importersContext.update(peer.id, action: .deny)
|
||||
}, peerContextAction: { peer, node, gesture in
|
||||
guard let node = node as? ContextReferenceContentNode, let controller = getControllerImpl?() else {
|
||||
guard let node = node as? ContextExtractedContentContainingNode else {
|
||||
return
|
||||
}
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
@ -206,11 +214,15 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
|
||||
}, action: { _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
dismissTooltipsImpl?()
|
||||
|
||||
})))
|
||||
|
||||
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(items: items)), gesture: gesture)
|
||||
let dismissPromise = ValuePromise<Bool>(false)
|
||||
let source = InviteRequestsContextExtractedContentSource(sourceNode: node, keepInPlace: false, blurBackground: true, centerVertically: true, shouldBeDismissed: dismissPromise.get())
|
||||
// sourceNode.requestDismiss = {
|
||||
// dismissPromise.set(true)
|
||||
// }
|
||||
|
||||
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(source), items: .single(ContextController.Items(items: items)), gesture: gesture)
|
||||
presentInGlobalOverlayImpl?(contextController)
|
||||
})
|
||||
|
||||
@ -222,9 +234,10 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
|
||||
context.engine.data.subscribe(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
|
||||
),
|
||||
importersContext.state
|
||||
importersContext.state,
|
||||
statePromise.get()
|
||||
)
|
||||
|> map { presentationData, peer, importersState -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
|> map { presentationData, peer, importersState, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
var isGroup = true
|
||||
if case let .channel(channel) = peer, case .broadcast = channel.info {
|
||||
isGroup = false
|
||||
@ -241,9 +254,43 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
|
||||
let crossfade = !previousEntries.isEmpty && entries.isEmpty
|
||||
let animateChanges = (!previousEntries.isEmpty && !entries.isEmpty) && previousEntries.count != entries.count
|
||||
|
||||
let rightNavigationButton: ItemListNavigationButton?
|
||||
if !importersState.importers.isEmpty {
|
||||
rightNavigationButton = ItemListNavigationButton(content: .icon(.search), style: .regular, enabled: true, action: {
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.searchingMembers = true
|
||||
return updatedState
|
||||
}
|
||||
})
|
||||
} else {
|
||||
rightNavigationButton = nil
|
||||
}
|
||||
|
||||
var searchItem: ItemListControllerSearch?
|
||||
if state.searchingMembers && !importersState.importers.isEmpty {
|
||||
searchItem = InviteRequestsSearchItem(context: context, peerId: peerId, cancel: {
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.searchingMembers = false
|
||||
return updatedState
|
||||
}
|
||||
}, openPeer: { peer in
|
||||
arguments.openPeer(peer)
|
||||
}, approveRequest: { peer in
|
||||
arguments.approveRequest(peer)
|
||||
}, denyRequest: { peer in
|
||||
arguments.denyRequest(peer)
|
||||
}, pushController: { c in
|
||||
pushControllerImpl?(c)
|
||||
}, dismissInput: {
|
||||
dismissInputImpl?()
|
||||
})
|
||||
}
|
||||
|
||||
let title: ItemListControllerTitle = .text(presentationData.strings.MemberRequests_Title)
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: crossfade, animateChanges: animateChanges)
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, crossfadeState: crossfade, animateChanges: animateChanges)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
@ -283,8 +330,8 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
|
||||
navigationController.pushViewController(controller)
|
||||
}
|
||||
}
|
||||
getControllerImpl = { [weak controller] in
|
||||
return controller
|
||||
dismissInputImpl = { [weak controller] in
|
||||
controller?.view.endEditing(true)
|
||||
}
|
||||
dismissTooltipsImpl = { [weak controller] in
|
||||
controller?.window?.forEachController({ controller in
|
||||
@ -301,3 +348,31 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
||||
|
||||
private final class InviteRequestsContextExtractedContentSource: ContextExtractedContentSource {
|
||||
var keepInPlace: Bool
|
||||
let ignoreContentTouches: Bool = false
|
||||
let blurBackground: Bool
|
||||
|
||||
private let sourceNode: ContextExtractedContentContainingNode
|
||||
|
||||
var centerVertically: Bool
|
||||
var shouldBeDismissed: Signal<Bool, NoError>
|
||||
|
||||
init(sourceNode: ContextExtractedContentContainingNode, keepInPlace: Bool, blurBackground: Bool, centerVertically: Bool, shouldBeDismissed: Signal<Bool, NoError>) {
|
||||
self.sourceNode = sourceNode
|
||||
self.keepInPlace = keepInPlace
|
||||
self.blurBackground = blurBackground
|
||||
self.centerVertically = centerVertically
|
||||
self.shouldBeDismissed = shouldBeDismissed
|
||||
}
|
||||
|
||||
func takeView() -> ContextControllerTakeViewInfo? {
|
||||
return ContextControllerTakeViewInfo(contentContainingNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
|
||||
func putBack() -> ContextControllerPutBackViewInfo? {
|
||||
return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
}
|
||||
|
@ -70,6 +70,8 @@ final class InviteRequestsEmptyStateItemNode: ItemListControllerEmptyStateItemNo
|
||||
|
||||
super.init()
|
||||
|
||||
self.isUserInteractionEnabled = false
|
||||
|
||||
self.addSubnode(self.animationNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
|
598
submodules/InviteLinksUI/Sources/InviteRequestsSearchItem.swift
Normal file
598
submodules/InviteLinksUI/Sources/InviteRequestsSearchItem.swift
Normal file
@ -0,0 +1,598 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import SearchBarNode
|
||||
import MergeLists
|
||||
import ChatListSearchItemHeader
|
||||
import ItemListUI
|
||||
import SearchUI
|
||||
|
||||
private let searchBarFont = Font.regular(17.0)
|
||||
|
||||
final class SearchNavigationContentNode: NavigationBarContentNode, ItemListControllerSearchNavigationContentNode {
|
||||
private var theme: PresentationTheme
|
||||
private let strings: PresentationStrings
|
||||
|
||||
private let cancel: () -> Void
|
||||
|
||||
private let searchBar: SearchBarNode
|
||||
|
||||
private var queryUpdated: ((String) -> Void)?
|
||||
var activity: Bool = false {
|
||||
didSet {
|
||||
searchBar.activity = activity
|
||||
}
|
||||
}
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, cancel: @escaping () -> Void, updateActivity: @escaping(@escaping(Bool)->Void) -> Void) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
|
||||
self.cancel = cancel
|
||||
|
||||
self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: strings, fieldStyle: .modern, displayBackground: false)
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.searchBar)
|
||||
|
||||
self.searchBar.cancel = { [weak self] in
|
||||
self?.searchBar.deactivate(clear: false)
|
||||
self?.cancel()
|
||||
}
|
||||
|
||||
self.searchBar.textUpdated = { [weak self] query, _ in
|
||||
self?.queryUpdated?(query)
|
||||
}
|
||||
|
||||
updateActivity({ [weak self] value in
|
||||
self?.activity = value
|
||||
})
|
||||
|
||||
self.updatePlaceholder()
|
||||
}
|
||||
|
||||
func setQueryUpdated(_ f: @escaping (String) -> Void) {
|
||||
self.queryUpdated = f
|
||||
}
|
||||
|
||||
func updateTheme(_ theme: PresentationTheme) {
|
||||
self.theme = theme
|
||||
self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: self.theme), strings: self.strings)
|
||||
self.updatePlaceholder()
|
||||
}
|
||||
|
||||
func updatePlaceholder() {
|
||||
self.searchBar.placeholderString = NSAttributedString(string: self.strings.Conversation_SearchByName_Placeholder, font: searchBarFont, textColor: self.theme.rootController.navigationSearchBar.inputPlaceholderTextColor)
|
||||
}
|
||||
|
||||
override var nominalHeight: CGFloat {
|
||||
return 56.0
|
||||
}
|
||||
|
||||
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 56.0))
|
||||
self.searchBar.frame = searchBarFrame
|
||||
self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition)
|
||||
}
|
||||
|
||||
func activate() {
|
||||
self.searchBar.activate()
|
||||
}
|
||||
|
||||
func deactivate() {
|
||||
self.searchBar.deactivate(clear: false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final class InviteRequestsSearchItem: ItemListControllerSearch {
|
||||
let context: AccountContext
|
||||
let peerId: PeerId
|
||||
let cancel: () -> Void
|
||||
let openPeer: (EnginePeer) -> Void
|
||||
let approveRequest: (EnginePeer) -> Void
|
||||
let denyRequest: (EnginePeer) -> Void
|
||||
let pushController: (ViewController) -> Void
|
||||
let dismissInput: () -> Void
|
||||
|
||||
private var updateActivity: ((Bool) -> Void)?
|
||||
private var activity: ValuePromise<Bool> = ValuePromise(ignoreRepeated: false)
|
||||
private let activityDisposable = MetaDisposable()
|
||||
|
||||
init(context: AccountContext, peerId: PeerId, cancel: @escaping () -> Void, openPeer: @escaping (EnginePeer) -> Void, approveRequest: @escaping (EnginePeer) -> Void, denyRequest: @escaping (EnginePeer) -> Void, pushController: @escaping (ViewController) -> Void, dismissInput: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.cancel = cancel
|
||||
self.openPeer = openPeer
|
||||
self.approveRequest = approveRequest
|
||||
self.denyRequest = denyRequest
|
||||
self.pushController = pushController
|
||||
self.dismissInput = dismissInput
|
||||
self.activityDisposable.set((activity.get() |> mapToSignal { value -> Signal<Bool, NoError> in
|
||||
if value {
|
||||
return .single(value) |> delay(0.2, queue: Queue.mainQueue())
|
||||
} else {
|
||||
return .single(value)
|
||||
}
|
||||
}).start(next: { [weak self] value in
|
||||
self?.updateActivity?(value)
|
||||
}))
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.activityDisposable.dispose()
|
||||
}
|
||||
|
||||
func isEqual(to: ItemListControllerSearch) -> Bool {
|
||||
if let to = to as? InviteRequestsSearchItem {
|
||||
if self.context !== to.context {
|
||||
return false
|
||||
}
|
||||
if self.peerId != to.peerId {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> NavigationBarContentNode & ItemListControllerSearchNavigationContentNode {
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
if let current = current as? SearchNavigationContentNode {
|
||||
current.updateTheme(presentationData.theme)
|
||||
return current
|
||||
} else {
|
||||
return SearchNavigationContentNode(theme: presentationData.theme, strings: presentationData.strings, cancel: self.cancel, updateActivity: { [weak self] value in
|
||||
self?.updateActivity = value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func node(current: ItemListControllerSearchNode?, titleContentNode: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> ItemListControllerSearchNode {
|
||||
return InviteRequestsSearchItemNode(context: self.context, peerId: self.peerId, openPeer: self.openPeer, approveRequest: self.approveRequest, denyRequest: self.denyRequest, cancel: self.cancel, updateActivity: { [weak self] value in
|
||||
self?.activity.set(value)
|
||||
}, pushController: { [weak self] c in
|
||||
self?.pushController(c)
|
||||
}, dismissInput: self.dismissInput)
|
||||
}
|
||||
}
|
||||
|
||||
private final class InviteRequestsSearchItemNode: ItemListControllerSearchNode {
|
||||
private let containerNode: InviteRequestsSearchContainerNode
|
||||
|
||||
init(context: AccountContext, peerId: PeerId, openPeer: @escaping (EnginePeer) -> Void, approveRequest: @escaping (EnginePeer) -> Void, denyRequest: @escaping (EnginePeer) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, pushController: @escaping (ViewController) -> Void, dismissInput: @escaping () -> Void) {
|
||||
self.containerNode = InviteRequestsSearchContainerNode(context: context, forceTheme: nil, peerId: peerId, openPeer: { peer in
|
||||
openPeer(peer)
|
||||
}, approveRequest: { peer in
|
||||
approveRequest(peer)
|
||||
}, denyRequest: { peer in
|
||||
denyRequest(peer)
|
||||
}, updateActivity: updateActivity, pushController: pushController)
|
||||
self.containerNode.cancel = {
|
||||
cancel()
|
||||
}
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.containerNode)
|
||||
|
||||
self.containerNode.dismissInput = {
|
||||
dismissInput()
|
||||
}
|
||||
}
|
||||
|
||||
override func queryUpdated(_ query: String) {
|
||||
self.containerNode.searchTextUpdated(text: query)
|
||||
}
|
||||
|
||||
override func scrollToTop() {
|
||||
self.containerNode.scrollToTop()
|
||||
}
|
||||
|
||||
override func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight)))
|
||||
self.containerNode.containerLayoutUpdated(layout.withUpdatedSize(CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight)), navigationBarHeight: 0.0, transition: transition)
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if let result = self.containerNode.hitTest(self.view.convert(point, to: self.containerNode.view), with: event) {
|
||||
return result
|
||||
}
|
||||
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final class InviteRequestsSearchContainerInteraction {
|
||||
let openPeer: (EnginePeer) -> Void
|
||||
let approveRequest: (EnginePeer) -> Void
|
||||
let denyRequest: (EnginePeer) -> Void
|
||||
let peerContextAction: (EnginePeer, ASDisplayNode, ContextGesture?) -> Void
|
||||
|
||||
init(openPeer: @escaping (EnginePeer) -> Void, approveRequest: @escaping (EnginePeer) -> Void, denyRequest: @escaping (EnginePeer) -> Void, peerContextAction: @escaping (EnginePeer, ASDisplayNode, ContextGesture?) -> Void) {
|
||||
self.openPeer = openPeer
|
||||
self.approveRequest = approveRequest
|
||||
self.denyRequest = denyRequest
|
||||
self.peerContextAction = peerContextAction
|
||||
}
|
||||
}
|
||||
|
||||
private enum InviteRequestsSearchEntryId: Hashable {
|
||||
case request(EnginePeer.Id)
|
||||
}
|
||||
|
||||
private final class InviteRequestsSearchEntry: Comparable, Identifiable {
|
||||
let index: Int
|
||||
let request: PeerInvitationImportersState.Importer
|
||||
let dateTimeFormat: PresentationDateTimeFormat
|
||||
let nameDisplayOrder: PresentationPersonNameOrder
|
||||
let isGroup: Bool
|
||||
|
||||
init(index: Int, request: PeerInvitationImportersState.Importer, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, isGroup: Bool) {
|
||||
self.index = index
|
||||
self.request = request
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
self.nameDisplayOrder = nameDisplayOrder
|
||||
self.isGroup = isGroup
|
||||
}
|
||||
|
||||
var stableId: InviteRequestsSearchEntryId {
|
||||
return .request(self.request.peer.peerId)
|
||||
}
|
||||
|
||||
static func ==(lhs: InviteRequestsSearchEntry, rhs: InviteRequestsSearchEntry) -> Bool {
|
||||
return lhs.index == rhs.index && lhs.request == rhs.request && lhs.dateTimeFormat == rhs.dateTimeFormat && lhs.nameDisplayOrder == rhs.nameDisplayOrder && lhs.isGroup == rhs.isGroup
|
||||
}
|
||||
|
||||
static func <(lhs: InviteRequestsSearchEntry, rhs: InviteRequestsSearchEntry) -> Bool {
|
||||
return lhs.index < rhs.index
|
||||
}
|
||||
|
||||
func item(context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, interaction: InviteRequestsSearchContainerInteraction) -> ListViewItem {
|
||||
return ItemListInviteRequestItem(context: context, presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, importer: self.request, isGroup: self.isGroup, sectionId: 0, style: .plain, tapAction: {
|
||||
if let peer = self.request.peer.peer.flatMap({ EnginePeer($0) }) {
|
||||
interaction.openPeer(peer)
|
||||
}
|
||||
}, addAction: {
|
||||
if let peer = self.request.peer.peer.flatMap({ EnginePeer($0) }) {
|
||||
interaction.approveRequest(peer)
|
||||
}
|
||||
}, dismissAction: {
|
||||
if let peer = self.request.peer.peer.flatMap({ EnginePeer($0) }) {
|
||||
interaction.denyRequest(peer)
|
||||
}
|
||||
}, contextAction: { node, gesture in
|
||||
if let peer = self.request.peer.peer.flatMap({ EnginePeer($0) }) {
|
||||
interaction.peerContextAction(peer, node, gesture)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct InviteRequestsSearchContainerTransition {
|
||||
let deletions: [ListViewDeleteItem]
|
||||
let insertions: [ListViewInsertItem]
|
||||
let updates: [ListViewUpdateItem]
|
||||
let isSearching: Bool
|
||||
let isEmpty: Bool
|
||||
let query: String
|
||||
}
|
||||
|
||||
private func InviteRequestsSearchContainerPreparedRecentTransition(from fromEntries: [InviteRequestsSearchEntry], to toEntries: [InviteRequestsSearchEntry], isSearching: Bool, isEmpty: Bool, query: String, context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, interaction: InviteRequestsSearchContainerInteraction) -> InviteRequestsSearchContainerTransition {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||
|
||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, interaction: interaction), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, interaction: interaction), directionHint: nil) }
|
||||
|
||||
return InviteRequestsSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, isSearching: isSearching, isEmpty: isEmpty, query: query)
|
||||
}
|
||||
|
||||
|
||||
public final class InviteRequestsSearchContainerNode: SearchDisplayControllerContentNode {
|
||||
private let context: AccountContext
|
||||
private let openPeer: (EnginePeer) -> Void
|
||||
|
||||
private let dimNode: ASDisplayNode
|
||||
private let listNode: ListView
|
||||
|
||||
private let emptyResultsTitleNode: ImmediateTextNode
|
||||
private let emptyResultsTextNode: ImmediateTextNode
|
||||
|
||||
private var enqueuedTransitions: [(InviteRequestsSearchContainerTransition, Bool)] = []
|
||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
private let searchQuery = Promise<String?>()
|
||||
private let emptyQueryDisposable = MetaDisposable()
|
||||
private let searchDisposable = MetaDisposable()
|
||||
|
||||
private let forceTheme: PresentationTheme?
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
private let presentationDataPromise: Promise<PresentationData>
|
||||
|
||||
private var _hasDim: Bool = false
|
||||
override public var hasDim: Bool {
|
||||
return _hasDim
|
||||
}
|
||||
|
||||
private var processedPeerIdsPromise = ValuePromise<Set<PeerId>>(Set())
|
||||
private var processedPeerIds = Set<PeerId>() {
|
||||
didSet {
|
||||
self.processedPeerIdsPromise.set(self.processedPeerIds)
|
||||
}
|
||||
}
|
||||
|
||||
public init(context: AccountContext, forceTheme: PresentationTheme?, peerId: PeerId, openPeer: @escaping (EnginePeer) -> Void, approveRequest: @escaping (EnginePeer) -> Void, denyRequest: @escaping (EnginePeer) -> Void, updateActivity: @escaping (Bool) -> Void, pushController: @escaping (ViewController) -> Void) {
|
||||
self.context = context
|
||||
self.openPeer = openPeer
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.presentationData = presentationData
|
||||
|
||||
self.forceTheme = forceTheme
|
||||
if let forceTheme = self.forceTheme {
|
||||
self.presentationData = self.presentationData.withUpdated(theme: forceTheme)
|
||||
}
|
||||
self.presentationDataPromise = Promise(self.presentationData)
|
||||
|
||||
self.dimNode = ASDisplayNode()
|
||||
self.dimNode.backgroundColor = UIColor.black.withAlphaComponent(0.5)
|
||||
|
||||
self.listNode = ListView()
|
||||
self.listNode.accessibilityPageScrolledString = { row, count in
|
||||
return presentationData.strings.VoiceOver_ScrollStatus(row, count).string
|
||||
}
|
||||
|
||||
self.emptyResultsTitleNode = ImmediateTextNode()
|
||||
self.emptyResultsTitleNode.displaysAsynchronously = false
|
||||
self.emptyResultsTitleNode.attributedText = NSAttributedString(string: self.presentationData.strings.ChatList_Search_NoResults, font: Font.semibold(17.0), textColor: self.presentationData.theme.list.freeTextColor)
|
||||
self.emptyResultsTitleNode.textAlignment = .center
|
||||
self.emptyResultsTitleNode.isHidden = true
|
||||
|
||||
self.emptyResultsTextNode = ImmediateTextNode()
|
||||
self.emptyResultsTextNode.displaysAsynchronously = false
|
||||
self.emptyResultsTextNode.maximumNumberOfLines = 0
|
||||
self.emptyResultsTextNode.textAlignment = .center
|
||||
self.emptyResultsTextNode.isHidden = true
|
||||
|
||||
super.init()
|
||||
|
||||
self.listNode.backgroundColor = self.presentationData.theme.chatList.backgroundColor
|
||||
self.listNode.isHidden = true
|
||||
|
||||
self._hasDim = true
|
||||
|
||||
self.addSubnode(self.dimNode)
|
||||
self.addSubnode(self.listNode)
|
||||
|
||||
self.addSubnode(self.emptyResultsTitleNode)
|
||||
self.addSubnode(self.emptyResultsTextNode)
|
||||
|
||||
|
||||
let interaction = InviteRequestsSearchContainerInteraction(openPeer: { [weak self] peer in
|
||||
openPeer(peer)
|
||||
self?.listNode.clearHighlightAnimated(true)
|
||||
}, approveRequest: { [weak self] peer in
|
||||
approveRequest(peer)
|
||||
self?.processedPeerIds.insert(peer.id)
|
||||
}, denyRequest: { [weak self] peer in
|
||||
denyRequest(peer)
|
||||
self?.processedPeerIds.insert(peer.id)
|
||||
}, peerContextAction: { _, _, _ in
|
||||
|
||||
})
|
||||
|
||||
let presentationDataPromise = self.presentationDataPromise
|
||||
|
||||
let previousRequestsContext = Atomic<PeerInvitationImportersContext?>(value: nil)
|
||||
|
||||
let processedPeerIds = self.processedPeerIdsPromise
|
||||
let searchQuery = self.searchQuery.get()
|
||||
|> mapToSignal { query -> Signal<String?, NoError> in
|
||||
if let query = query, !query.isEmpty {
|
||||
return (.complete() |> delay(0.6, queue: Queue.mainQueue()))
|
||||
|> then(.single(query))
|
||||
} else {
|
||||
return .single(query)
|
||||
}
|
||||
}
|
||||
|
||||
let foundItems = combineLatest(searchQuery, context.account.postbox.peerView(id: peerId) |> take(1))
|
||||
|> mapToSignal { query, peerView -> Signal<[InviteRequestsSearchEntry]?, NoError> in
|
||||
guard let query = query, !query.isEmpty else {
|
||||
return .single(nil)
|
||||
}
|
||||
updateActivity(true)
|
||||
let requestsContext = context.engine.peers.peerInvitationImporters(peerId: peerId, subject: .requests(query: query))
|
||||
let _ = previousRequestsContext.swap(requestsContext)
|
||||
|
||||
return combineLatest(requestsContext.state, presentationDataPromise.get(), processedPeerIds.get())
|
||||
|> mapToSignal { state, presentationData, processedPeerIds -> Signal<[InviteRequestsSearchEntry]?, NoError> in
|
||||
if !state.hasLoadedOnce {
|
||||
return .complete()
|
||||
}
|
||||
|
||||
var entries: [InviteRequestsSearchEntry] = []
|
||||
var index = 0
|
||||
for importer in state.importers {
|
||||
if processedPeerIds.contains(importer.peer.peerId) {
|
||||
continue
|
||||
}
|
||||
entries.append(InviteRequestsSearchEntry(index: index, request: importer, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, isGroup: false))
|
||||
index += 1
|
||||
}
|
||||
return .single(entries)
|
||||
}
|
||||
}
|
||||
|
||||
let previousSearchItems = Atomic<[InviteRequestsSearchEntry]?>(value: nil)
|
||||
|
||||
self.searchDisposable.set((combineLatest(searchQuery, foundItems, self.presentationDataPromise.get())
|
||||
|> deliverOnMainQueue).start(next: { [weak self] query, entries, presentationData in
|
||||
if let strongSelf = self {
|
||||
let previousEntries = previousSearchItems.swap(entries)
|
||||
updateActivity(false)
|
||||
let firstTime = previousEntries == nil
|
||||
let transition = InviteRequestsSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries ?? [], isSearching: entries != nil, isEmpty: entries?.isEmpty ?? false, query: query ?? "", context: context, presentationData: presentationData, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, interaction: interaction)
|
||||
strongSelf.enqueueTransition(transition, firstTime: firstTime)
|
||||
}
|
||||
}))
|
||||
|
||||
self.presentationDataDisposable = (context.sharedContext.presentationData
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
var presentationData = presentationData
|
||||
|
||||
let previousTheme = strongSelf.presentationData.theme
|
||||
let previousStrings = strongSelf.presentationData.strings
|
||||
|
||||
if let forceTheme = strongSelf.forceTheme {
|
||||
presentationData = presentationData.withUpdated(theme: forceTheme)
|
||||
}
|
||||
|
||||
strongSelf.presentationData = presentationData
|
||||
|
||||
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
||||
strongSelf.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.listNode.beganInteractiveDragging = { [weak self] _ in
|
||||
self?.dismissInput?()
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.searchDisposable.dispose()
|
||||
self.presentationDataDisposable?.dispose()
|
||||
}
|
||||
|
||||
override public func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||
}
|
||||
|
||||
private func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
|
||||
self.listNode.backgroundColor = theme.chatList.backgroundColor
|
||||
}
|
||||
|
||||
override public func searchTextUpdated(text: String) {
|
||||
if text.isEmpty {
|
||||
self.searchQuery.set(.single(nil))
|
||||
} else {
|
||||
self.searchQuery.set(.single(text))
|
||||
}
|
||||
}
|
||||
|
||||
private func enqueueTransition(_ transition: InviteRequestsSearchContainerTransition, firstTime: Bool) {
|
||||
self.enqueuedTransitions.append((transition, firstTime))
|
||||
|
||||
if let _ = self.validLayout {
|
||||
while !self.enqueuedTransitions.isEmpty {
|
||||
self.dequeueTransition()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func dequeueTransition() {
|
||||
if let (transition, _) = self.enqueuedTransitions.first {
|
||||
self.enqueuedTransitions.remove(at: 0)
|
||||
|
||||
var options = ListViewDeleteAndInsertOptions()
|
||||
options.insert(.PreferSynchronousDrawing)
|
||||
options.insert(.PreferSynchronousResourceLoading)
|
||||
|
||||
let isSearching = transition.isSearching
|
||||
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.listNode.isHidden = !isSearching
|
||||
strongSelf.dimNode.isHidden = transition.isSearching
|
||||
|
||||
strongSelf.emptyResultsTextNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.ChatList_Search_NoResultsQueryDescription(transition.query).string, font: Font.regular(15.0), textColor: strongSelf.presentationData.theme.list.freeTextColor)
|
||||
|
||||
let emptyResults = transition.isSearching && transition.isEmpty
|
||||
strongSelf.emptyResultsTitleNode.isHidden = !emptyResults
|
||||
strongSelf.emptyResultsTextNode.isHidden = !emptyResults
|
||||
|
||||
if let (layout, navigationBarHeight) = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
||||
|
||||
let hadValidLayout = self.validLayout == nil
|
||||
self.validLayout = (layout, navigationBarHeight)
|
||||
|
||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||
|
||||
var insets = layout.insets(options: [.input])
|
||||
insets.top += navigationBarHeight
|
||||
insets.left += layout.safeInsets.left
|
||||
insets.right += layout.safeInsets.right
|
||||
|
||||
let topInset = navigationBarHeight
|
||||
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: layout.size.height - topInset)))
|
||||
|
||||
self.listNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
let padding: CGFloat = 16.0
|
||||
let emptyTitleSize = self.emptyResultsTitleNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
let emptyTextSize = self.emptyResultsTextNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
|
||||
let emptyTextSpacing: CGFloat = 8.0
|
||||
let emptyTotalHeight = emptyTitleSize.height + emptyTextSize.height + emptyTextSpacing
|
||||
let emptyTitleY = navigationBarHeight + floorToScreenPixels((layout.size.height - navigationBarHeight - max(insets.bottom, layout.intrinsicInsets.bottom) - emptyTotalHeight) / 2.0)
|
||||
|
||||
transition.updateFrame(node: self.emptyResultsTitleNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + padding + (layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0 - emptyTitleSize.width) / 2.0, y: emptyTitleY), size: emptyTitleSize))
|
||||
transition.updateFrame(node: self.emptyResultsTextNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + padding + (layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0 - emptyTextSize.width) / 2.0, y: emptyTitleY + emptyTitleSize.height + emptyTextSpacing), size: emptyTextSize))
|
||||
|
||||
if !hadValidLayout {
|
||||
while !self.enqueuedTransitions.isEmpty {
|
||||
self.dequeueTransition()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public func scrollToTop() {
|
||||
self.listNode.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 })
|
||||
}
|
||||
|
||||
@objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.cancel?()
|
||||
}
|
||||
}
|
||||
|
||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
guard let result = self.view.hitTest(point, with: event) else {
|
||||
return nil
|
||||
}
|
||||
if result === self.view {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
@ -14,6 +14,10 @@ import LocalizedPeerData
|
||||
import AvatarNode
|
||||
import AccountContext
|
||||
import SolidRoundedButtonNode
|
||||
import PeerInfoAvatarListNode
|
||||
import ContextUI
|
||||
|
||||
private let backgroundCornerRadius: CGFloat = 14.0
|
||||
|
||||
public class ItemListInviteRequestItem: ListViewItem, ItemListItem {
|
||||
let context: AccountContext
|
||||
@ -130,24 +134,30 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let highlightedBackgroundNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
|
||||
private let extractedBackgroundImageNode: ASImageNode
|
||||
|
||||
private let containerNode: ContextControllerSourceNode
|
||||
private let contextSourceNode: ContextExtractedContentContainingNode
|
||||
|
||||
private let extractedBackgroundImageNode: ASImageNode
|
||||
private let offsetContainerNode: ASDisplayNode
|
||||
|
||||
private var extractedRect: CGRect?
|
||||
private var nonExtractedRect: CGRect?
|
||||
|
||||
private let offsetContainerNode: ASDisplayNode
|
||||
private var extractedVerticalOffset: CGFloat?
|
||||
|
||||
fileprivate let avatarNode: AvatarNode
|
||||
private let contentWrapperNode: ASDisplayNode
|
||||
private let titleNode: TextNode
|
||||
private let subtitleNode: TextNode
|
||||
private let expandedSubtitleNode: TextNode
|
||||
private let dateNode: TextNode
|
||||
private let addButton: SolidRoundedButtonNode
|
||||
private let dismissButton: HighlightableButtonNode
|
||||
|
||||
private var avatarTransitionNode: ASImageNode?
|
||||
private var avatarListContainerNode: ASDisplayNode?
|
||||
private var avatarListWrapperNode: PinchSourceContainerNode?
|
||||
private var avatarListNode: PeerInfoAvatarListContainerNode?
|
||||
|
||||
private var placeholderNode: ShimmerEffectNode?
|
||||
private var absoluteLocation: (CGRect, CGSize)?
|
||||
|
||||
@ -155,6 +165,8 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
|
||||
|
||||
public var tag: ItemListItemTag?
|
||||
|
||||
private var isExtracted = false
|
||||
|
||||
public init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
@ -185,6 +197,11 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
|
||||
self.subtitleNode.contentMode = .left
|
||||
self.subtitleNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.expandedSubtitleNode = TextNode()
|
||||
self.expandedSubtitleNode.isUserInteractionEnabled = false
|
||||
self.expandedSubtitleNode.contentMode = .left
|
||||
self.expandedSubtitleNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.dateNode = TextNode()
|
||||
self.dateNode.isUserInteractionEnabled = false
|
||||
self.dateNode.contentMode = .left
|
||||
@ -198,6 +215,8 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
|
||||
|
||||
self.avatarNode = AvatarNode(font: avatarFont)
|
||||
|
||||
self.contentWrapperNode = ASDisplayNode()
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
|
||||
|
||||
self.isAccessibilityElement = true
|
||||
@ -209,12 +228,14 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
|
||||
self.contextSourceNode.contentNode.addSubnode(self.extractedBackgroundImageNode)
|
||||
self.contextSourceNode.contentNode.addSubnode(self.offsetContainerNode)
|
||||
|
||||
self.offsetContainerNode.addSubnode(self.avatarNode)
|
||||
self.offsetContainerNode.addSubnode(self.titleNode)
|
||||
self.offsetContainerNode.addSubnode(self.subtitleNode)
|
||||
self.offsetContainerNode.addSubnode(self.dateNode)
|
||||
self.offsetContainerNode.addSubnode(self.addButton)
|
||||
self.offsetContainerNode.addSubnode(self.dismissButton)
|
||||
self.offsetContainerNode.addSubnode(self.contentWrapperNode)
|
||||
self.contentWrapperNode.addSubnode(self.avatarNode)
|
||||
self.contentWrapperNode.addSubnode(self.titleNode)
|
||||
self.contentWrapperNode.addSubnode(self.subtitleNode)
|
||||
self.contentWrapperNode.addSubnode(self.expandedSubtitleNode)
|
||||
self.contentWrapperNode.addSubnode(self.dateNode)
|
||||
self.contentWrapperNode.addSubnode(self.addButton)
|
||||
self.contentWrapperNode.addSubnode(self.dismissButton)
|
||||
|
||||
self.addButton.pressed = { [weak self] in
|
||||
if let (item, _, _, _, _) = self?.layoutParams {
|
||||
@ -230,33 +251,241 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
|
||||
}
|
||||
contextAction(strongSelf.contextSourceNode, gesture)
|
||||
}
|
||||
|
||||
|
||||
self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in
|
||||
guard let strongSelf = self, let item = strongSelf.layoutParams?.0 else {
|
||||
guard let strongSelf = self, let item = strongSelf.layoutParams?.0, let peer = item.importer?.peer.peer else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.isExtracted = isExtracted
|
||||
|
||||
let inset: CGFloat = 0.0
|
||||
if isExtracted {
|
||||
strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: item.presentationData.theme.list.plainBackgroundColor)
|
||||
}
|
||||
|
||||
if let extractedRect = strongSelf.extractedRect, let nonExtractedRect = strongSelf.nonExtractedRect {
|
||||
let rect = isExtracted ? extractedRect : nonExtractedRect
|
||||
transition.updateFrame(node: strongSelf.extractedBackgroundImageNode, frame: rect)
|
||||
}
|
||||
|
||||
transition.updateSublayerTransformOffset(layer: strongSelf.offsetContainerNode.layer, offset: CGPoint(x: isExtracted ? 12.0 : 0.0, y: 0.0))
|
||||
transition.updateAlpha(node: strongSelf.extractedBackgroundImageNode, alpha: isExtracted ? 1.0 : 0.0, completion: { _ in
|
||||
if !isExtracted {
|
||||
self?.extractedBackgroundImageNode.image = nil
|
||||
strongSelf.contextSourceNode.contentNode.customHitTest = { [weak self] point in
|
||||
if let strongSelf = self {
|
||||
if let avatarListWrapperNode = strongSelf.avatarListWrapperNode, avatarListWrapperNode.frame.contains(point) {
|
||||
return strongSelf.avatarListNode?.view
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
})
|
||||
} else {
|
||||
strongSelf.contextSourceNode.contentNode.customHitTest = nil
|
||||
}
|
||||
|
||||
let extractedVerticalOffset = strongSelf.extractedVerticalOffset ?? 0.0
|
||||
if let extractedRect = strongSelf.extractedRect, let nonExtractedRect = strongSelf.nonExtractedRect {
|
||||
let rect: CGRect
|
||||
if isExtracted {
|
||||
if extractedVerticalOffset > 0.0 {
|
||||
rect = CGRect(x: extractedRect.minX, y: extractedRect.minY + extractedVerticalOffset, width: extractedRect.width, height: extractedRect.height - extractedVerticalOffset)
|
||||
} else {
|
||||
rect = extractedRect
|
||||
}
|
||||
} else {
|
||||
rect = nonExtractedRect
|
||||
}
|
||||
|
||||
let springDuration: Double = isExtracted ? 0.42 : 0.3
|
||||
let springDamping: CGFloat = isExtracted ? 124.0 : 1000.0
|
||||
|
||||
let itemBackgroundColor: UIColor
|
||||
switch item.style {
|
||||
case .plain:
|
||||
itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor
|
||||
case .blocks:
|
||||
itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
|
||||
}
|
||||
|
||||
if !extractedVerticalOffset.isZero {
|
||||
let radiusTransition = ContainedViewLayoutTransition.animated(duration: 0.15, curve: .easeInOut)
|
||||
if isExtracted {
|
||||
strongSelf.extractedBackgroundImageNode.image = generateImage(CGSize(width: backgroundCornerRadius * 2.0, height: backgroundCornerRadius * 2.0), rotatedContext: { (size, context) in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
context.setFillColor(itemBackgroundColor.cgColor)
|
||||
context.fillEllipse(in: bounds)
|
||||
context.fill(CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height / 2.0))
|
||||
})?.stretchableImage(withLeftCapWidth: Int(backgroundCornerRadius), topCapHeight: Int(backgroundCornerRadius))
|
||||
|
||||
strongSelf.avatarNode.transform = CATransform3DIdentity
|
||||
var avatarInitialRect = strongSelf.avatarNode.view.convert(strongSelf.avatarNode.bounds, to: strongSelf.offsetContainerNode.supernode?.view)
|
||||
if strongSelf.avatarTransitionNode == nil {
|
||||
let targetRect = CGRect(x: extractedRect.minX, y: extractedRect.minY, width: extractedRect.width, height: extractedRect.width)
|
||||
let initialScale = avatarInitialRect.width / targetRect.width
|
||||
avatarInitialRect.origin.y += backgroundCornerRadius / 2.0 * initialScale
|
||||
|
||||
let avatarListWrapperNode = PinchSourceContainerNode()
|
||||
avatarListWrapperNode.clipsToBounds = true
|
||||
avatarListWrapperNode.cornerRadius = backgroundCornerRadius
|
||||
avatarListWrapperNode.activate = { [weak self] sourceNode in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.avatarListNode?.controlsContainerNode.alpha = 0.0
|
||||
let pinchController = PinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: {
|
||||
return UIScreen.main.bounds
|
||||
})
|
||||
item.context.sharedContext.mainWindow?.presentInGlobalOverlay(pinchController)
|
||||
}
|
||||
avatarListWrapperNode.deactivated = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.avatarListWrapperNode?.contentNode.layer.animate(from: 0.0 as NSNumber, to: backgroundCornerRadius as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.3, completion: { _ in
|
||||
})
|
||||
}
|
||||
avatarListWrapperNode.update(size: targetRect.size, transition: .immediate)
|
||||
avatarListWrapperNode.frame = CGRect(x: targetRect.minX, y: targetRect.minY, width: targetRect.width, height: targetRect.height + backgroundCornerRadius)
|
||||
avatarListWrapperNode.animatedOut = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.avatarListNode?.controlsContainerNode.alpha = 1.0
|
||||
strongSelf.avatarListNode?.controlsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
}
|
||||
|
||||
let transitionNode = ASImageNode()
|
||||
transitionNode.clipsToBounds = true
|
||||
transitionNode.displaysAsynchronously = false
|
||||
transitionNode.displayWithoutProcessing = true
|
||||
transitionNode.image = strongSelf.avatarNode.unroundedImage
|
||||
transitionNode.frame = CGRect(origin: CGPoint(), size: targetRect.size)
|
||||
transitionNode.cornerRadius = targetRect.width / 2.0
|
||||
radiusTransition.updateCornerRadius(node: transitionNode, cornerRadius: 0.0)
|
||||
|
||||
strongSelf.avatarNode.isHidden = true
|
||||
avatarListWrapperNode.contentNode.addSubnode(transitionNode)
|
||||
|
||||
strongSelf.avatarTransitionNode = transitionNode
|
||||
|
||||
let avatarListContainerNode = ASDisplayNode()
|
||||
avatarListContainerNode.clipsToBounds = true
|
||||
avatarListContainerNode.frame = CGRect(origin: CGPoint(), size: targetRect.size)
|
||||
avatarListContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
avatarListContainerNode.cornerRadius = targetRect.width / 2.0
|
||||
|
||||
avatarListWrapperNode.layer.animateSpring(from: initialScale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
|
||||
avatarListWrapperNode.layer.animateSpring(from: NSValue(cgPoint: avatarInitialRect.center), to: NSValue(cgPoint: avatarListWrapperNode.position), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
|
||||
|
||||
radiusTransition.updateCornerRadius(node: avatarListContainerNode, cornerRadius: 0.0)
|
||||
|
||||
let avatarListNode = PeerInfoAvatarListContainerNode(context: item.context)
|
||||
avatarListWrapperNode.contentNode.clipsToBounds = true
|
||||
avatarListNode.backgroundColor = .clear
|
||||
avatarListNode.peer = peer
|
||||
avatarListNode.firstFullSizeOnly = true
|
||||
avatarListNode.offsetLocation = true
|
||||
avatarListNode.customCenterTapAction = { [weak self] in
|
||||
self?.contextSourceNode.requestDismiss?()
|
||||
}
|
||||
avatarListNode.frame = CGRect(x: targetRect.width / 2.0, y: targetRect.height / 2.0, width: targetRect.width, height: targetRect.height)
|
||||
avatarListNode.controlsClippingNode.frame = CGRect(x: -targetRect.width / 2.0, y: -targetRect.height / 2.0, width: targetRect.width, height: targetRect.height)
|
||||
avatarListNode.controlsClippingOffsetNode.frame = CGRect(origin: CGPoint(x: targetRect.width / 2.0, y: targetRect.height / 2.0), size: CGSize())
|
||||
avatarListNode.stripContainerNode.frame = CGRect(x: 0.0, y: 13.0, width: targetRect.width, height: 2.0)
|
||||
avatarListNode.shadowNode.frame = CGRect(x: 0.0, y: 0.0, width: targetRect.width, height: 44.0)
|
||||
|
||||
avatarListContainerNode.addSubnode(avatarListNode)
|
||||
avatarListContainerNode.addSubnode(avatarListNode.controlsClippingOffsetNode)
|
||||
avatarListWrapperNode.contentNode.addSubnode(avatarListContainerNode)
|
||||
|
||||
avatarListNode.update(size: targetRect.size, peer: peer, customNode: nil, additionalEntry: .single(nil), isExpanded: true, transition: .immediate)
|
||||
strongSelf.offsetContainerNode.supernode?.addSubnode(avatarListWrapperNode)
|
||||
|
||||
strongSelf.avatarListWrapperNode = avatarListWrapperNode
|
||||
strongSelf.avatarListContainerNode = avatarListContainerNode
|
||||
strongSelf.avatarListNode = avatarListNode
|
||||
}
|
||||
} else if let transitionNode = strongSelf.avatarTransitionNode, let avatarListWrapperNode = strongSelf.avatarListWrapperNode, let avatarListContainerNode = strongSelf.avatarListContainerNode {
|
||||
|
||||
var avatarInitialRect = CGRect(origin: strongSelf.avatarNode.frame.origin, size: strongSelf.avatarNode.frame.size)
|
||||
let targetScale = avatarInitialRect.width / avatarListContainerNode.frame.width
|
||||
avatarInitialRect.origin.y += backgroundCornerRadius / 2.0 * targetScale
|
||||
|
||||
strongSelf.avatarTransitionNode = nil
|
||||
strongSelf.avatarListWrapperNode = nil
|
||||
strongSelf.avatarListContainerNode = nil
|
||||
strongSelf.avatarListNode = nil
|
||||
|
||||
avatarListContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak avatarListContainerNode] _ in
|
||||
avatarListContainerNode?.removeFromSupernode()
|
||||
})
|
||||
|
||||
avatarListWrapperNode.layer.animate(from: 1.0 as NSNumber, to: targetScale as NSNumber, keyPath: "transform.scale", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2, removeOnCompletion: false)
|
||||
avatarListWrapperNode.layer.animate(from: NSValue(cgPoint: avatarListWrapperNode.position), to: NSValue(cgPoint: avatarInitialRect.center), keyPath: "position", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2, removeOnCompletion: false, completion: { [weak transitionNode, weak self] _ in
|
||||
transitionNode?.removeFromSupernode()
|
||||
self?.avatarNode.isHidden = false
|
||||
})
|
||||
|
||||
radiusTransition.updateCornerRadius(node: avatarListContainerNode, cornerRadius: avatarListContainerNode.frame.width / 2.0)
|
||||
radiusTransition.updateCornerRadius(node: transitionNode, cornerRadius: avatarListContainerNode.frame.width / 2.0)
|
||||
}
|
||||
|
||||
let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
||||
alphaTransition.updateAlpha(node: strongSelf.subtitleNode, alpha: isExtracted ? 0.0 : 1.0)
|
||||
alphaTransition.updateAlpha(node: strongSelf.expandedSubtitleNode, alpha: isExtracted ? 1.0 : 0.0)
|
||||
alphaTransition.updateAlpha(node: strongSelf.dateNode, alpha: isExtracted ? 0.0 : 1.0)
|
||||
alphaTransition.updateAlpha(node: strongSelf.addButton, alpha: isExtracted ? 0.0 : 1.0, delay: isExtracted ? 0.0 : 0.1)
|
||||
alphaTransition.updateAlpha(node: strongSelf.dismissButton, alpha: isExtracted ? 0.0 : 1.0, delay: isExtracted ? 0.0 : 0.1)
|
||||
|
||||
let offsetInitialSublayerTransform = strongSelf.offsetContainerNode.layer.sublayerTransform
|
||||
strongSelf.offsetContainerNode.layer.sublayerTransform = CATransform3DMakeTranslation(isExtracted ? -43 : 0.0, isExtracted ? extractedVerticalOffset : 0.0, 0.0)
|
||||
|
||||
let initialExtractedBackgroundPosition = strongSelf.extractedBackgroundImageNode.position
|
||||
strongSelf.extractedBackgroundImageNode.layer.position = rect.center
|
||||
let initialExtractedBackgroundBounds = strongSelf.extractedBackgroundImageNode.bounds
|
||||
strongSelf.extractedBackgroundImageNode.layer.bounds = CGRect(origin: CGPoint(), size: rect.size)
|
||||
if isExtracted {
|
||||
strongSelf.offsetContainerNode.layer.animateSpring(from: NSValue(caTransform3D: offsetInitialSublayerTransform), to: NSValue(caTransform3D: strongSelf.offsetContainerNode.layer.sublayerTransform), keyPath: "sublayerTransform", duration: springDuration, delay: 0.0, initialVelocity: 0.0, damping: springDamping)
|
||||
strongSelf.extractedBackgroundImageNode.layer.animateSpring(from: NSValue(cgPoint: initialExtractedBackgroundPosition), to: NSValue(cgPoint: strongSelf.extractedBackgroundImageNode.position), keyPath: "position", duration: springDuration, delay: 0.0, initialVelocity: 0.0, damping: springDamping)
|
||||
strongSelf.extractedBackgroundImageNode.layer.animateSpring(from: NSValue(cgRect: initialExtractedBackgroundBounds), to: NSValue(cgRect: strongSelf.extractedBackgroundImageNode.bounds), keyPath: "bounds", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
|
||||
} else {
|
||||
strongSelf.offsetContainerNode.layer.animate(from: NSValue(caTransform3D: offsetInitialSublayerTransform), to: NSValue(caTransform3D: strongSelf.offsetContainerNode.layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2)
|
||||
strongSelf.extractedBackgroundImageNode.layer.animate(from: NSValue(cgPoint: initialExtractedBackgroundPosition), to: NSValue(cgPoint: strongSelf.extractedBackgroundImageNode.position), keyPath: "position", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2)
|
||||
strongSelf.extractedBackgroundImageNode.layer.animate(from: NSValue(cgRect: initialExtractedBackgroundBounds), to: NSValue(cgRect: strongSelf.extractedBackgroundImageNode.bounds), keyPath: "bounds", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2)
|
||||
}
|
||||
|
||||
if isExtracted {
|
||||
strongSelf.extractedBackgroundImageNode.alpha = 1.0
|
||||
strongSelf.extractedBackgroundImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, delay: 0.1, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
} else {
|
||||
strongSelf.extractedBackgroundImageNode.alpha = 0.0
|
||||
strongSelf.extractedBackgroundImageNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, delay: 0.0, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.extractedBackgroundImageNode.image = nil
|
||||
strongSelf.extractedBackgroundImageNode.layer.removeAllAnimations()
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if isExtracted {
|
||||
strongSelf.extractedBackgroundImageNode.alpha = 1.0
|
||||
strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: backgroundCornerRadius * 2.0, color: item.presentationData.theme.list.itemBlocksBackgroundColor)
|
||||
}
|
||||
|
||||
transition.updateFrame(node: strongSelf.extractedBackgroundImageNode, frame: CGRect(origin: CGPoint(), size: rect.size))
|
||||
|
||||
transition.updateAlpha(node: strongSelf.subtitleNode, alpha: isExtracted ? 0.0 : 1.0)
|
||||
transition.updateAlpha(node: strongSelf.expandedSubtitleNode, alpha: isExtracted ? 1.0 : 0.0)
|
||||
|
||||
transition.updateSublayerTransformOffset(layer: strongSelf.offsetContainerNode.layer, offset: CGPoint(x: isExtracted ? inset : 0.0, y: isExtracted ? extractedVerticalOffset : 0.0))
|
||||
|
||||
// transition.updateAlpha(node: strongSelf.backgroundImageNode, alpha: isExtracted ? 1.0 : 0.0, completion: { _ in
|
||||
// if !isExtracted {
|
||||
// self?.backgroundImageNode.image = nil
|
||||
// self?.extractedBackgroundImageNode.image = nil
|
||||
// }
|
||||
// })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func asyncLayout() -> (_ item: ItemListInviteRequestItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors, _ firstWithHeader: Bool, _ last: Bool) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode)
|
||||
let makeExpandedSubtitleLayout = TextNode.asyncLayout(self.expandedSubtitleNode)
|
||||
let makeDateLayout = TextNode.asyncLayout(self.dateNode)
|
||||
|
||||
let currentItem = self.layoutParams?.0
|
||||
@ -296,6 +525,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
|
||||
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (expandedSubtitleLayout, expandedSubtitleApply) = makeExpandedSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleAttributedString, backgroundColor: nil, maximumNumberOfLines: 5, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (dateLayout, dateApply) = makeDateLayout(TextNodeLayoutArguments(attributedString: dateAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let titleSpacing: CGFloat = 1.0
|
||||
@ -313,7 +543,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
|
||||
itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor
|
||||
itemSeparatorColor = item.presentationData.theme.list.itemPlainSeparatorColor
|
||||
insets = itemListNeighborsPlainInsets(neighbors)
|
||||
insets.top = firstWithHeader ? 29.0 : 0.0
|
||||
insets.top = 0.0
|
||||
insets.bottom = 0.0
|
||||
case .blocks:
|
||||
itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
|
||||
@ -336,11 +566,23 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
|
||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||
strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||
strongSelf.offsetContainerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||
strongSelf.contentWrapperNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||
strongSelf.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||
strongSelf.containerNode.isGestureEnabled = item.contextAction != nil
|
||||
|
||||
let nonExtractedRect = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width - 16.0, height: layout.contentSize.height))
|
||||
let extractedRect = CGRect(origin: CGPoint(), size: layout.contentSize).insetBy(dx: 16.0 + params.leftInset, dy: 0.0)
|
||||
let nonExtractedRect = CGRect(origin: CGPoint(x: 16.0, y: 0.0), size: CGSize(width: layout.contentSize.width - 32.0, height: layout.contentSize.height))
|
||||
var extractedRect = CGRect(origin: CGPoint(), size: layout.contentSize).insetBy(dx: params.leftInset, dy: 0.0)
|
||||
var extractedHeight = extractedRect.height + expandedSubtitleLayout.size.height - subtitleLayout.size.height
|
||||
var extractedVerticalOffset: CGFloat = 0.0
|
||||
if item.importer?.peer.peer?.smallProfileImage != nil {
|
||||
extractedRect.size.width = min(extractedRect.width, params.availableHeight - 20.0)
|
||||
extractedVerticalOffset = extractedRect.width
|
||||
extractedHeight += extractedVerticalOffset
|
||||
}
|
||||
|
||||
extractedRect.size.height = extractedHeight - 48.0
|
||||
|
||||
strongSelf.extractedVerticalOffset = extractedVerticalOffset
|
||||
strongSelf.extractedRect = extractedRect
|
||||
strongSelf.nonExtractedRect = nonExtractedRect
|
||||
|
||||
@ -362,6 +604,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
|
||||
|
||||
let _ = titleApply()
|
||||
let _ = subtitleApply()
|
||||
let _ = expandedSubtitleApply()
|
||||
let _ = dateApply()
|
||||
|
||||
switch item.style {
|
||||
@ -439,6 +682,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode {
|
||||
|
||||
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size))
|
||||
transition.updateFrame(node: strongSelf.subtitleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: verticalInset + titleLayout.size.height + titleSpacing), size: subtitleLayout.size))
|
||||
transition.updateFrame(node: strongSelf.expandedSubtitleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: verticalInset + titleLayout.size.height + titleSpacing), size: expandedSubtitleLayout.size))
|
||||
transition.updateFrame(node: strongSelf.dateNode, frame: CGRect(origin: CGPoint(x: params.width - rightInset - dateLayout.size.width, y: verticalInset + 2.0), size: dateLayout.size))
|
||||
|
||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel + UIScreenPixel))
|
||||
|
@ -66,7 +66,7 @@ public final class JoinLinkPreviewController: ViewController {
|
||||
}
|
||||
self.displayNodeDidLoad()
|
||||
|
||||
let signal: Signal<ExternalJoiningChatState, NoError>
|
||||
let signal: Signal<ExternalJoiningChatState, JoinLinkInfoError>
|
||||
if let resolvedState = self.resolvedState {
|
||||
signal = .single(resolvedState)
|
||||
} else {
|
||||
@ -99,6 +99,16 @@ public final class JoinLinkPreviewController: ViewController {
|
||||
strongSelf.dismiss()
|
||||
}
|
||||
}
|
||||
}, error: { [weak self] error in
|
||||
if let strongSelf = self {
|
||||
switch error {
|
||||
case .flood:
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_FloodError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
default:
|
||||
break
|
||||
}
|
||||
strongSelf.dismiss()
|
||||
}
|
||||
}))
|
||||
self.ready.set(self.controllerNode.ready.get())
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ final class JoinLinkPreviewControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
self.wrappingScrollNode.addSubnode(self.contentContainerNode)
|
||||
self.wrappingScrollNode.addSubnode(self.cancelButton)
|
||||
|
||||
self.transitionToContentNode(ShareLoadingContainerNode(theme: self.presentationData.theme, forceNativeAppearance: false))
|
||||
self.transitionToContentNode(JoinLinkPreviewLoadingContainerNode(theme: self.presentationData.theme))
|
||||
|
||||
self.ready.set(.single(true))
|
||||
self.didSetReady = true
|
||||
@ -184,7 +184,7 @@ final class JoinLinkPreviewControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
}
|
||||
contentNode.layer.add(animation, forKey: "opacity")
|
||||
|
||||
self.animateContentNodeOffsetFromBackgroundOffset = self.contentBackgroundNode.frame.minY
|
||||
self.animateContentNodeOffsetFromBackgroundOffset = self.backgroundNode.frame.minY
|
||||
self.scheduleInteractiveTransition(transition)
|
||||
|
||||
contentNode.activate()
|
||||
@ -394,20 +394,6 @@ final class JoinLinkPreviewControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
|
||||
func transitionToProgress(signal: Signal<Void, NoError>) {
|
||||
self.transitionToContentNode(ShareLoadingContainerNode(theme: self.presentationData.theme, forceNativeAppearance: false), fastOut: true)
|
||||
let timestamp = CACurrentMediaTime()
|
||||
self.disposable.set(signal.start(completed: { [weak self] in
|
||||
let minDelay = 0.6
|
||||
let delay = max(0.0, (timestamp + minDelay) - CACurrentMediaTime())
|
||||
Queue.mainQueue().after(delay, {
|
||||
if let strongSelf = self {
|
||||
strongSelf.cancel?()
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
func setInvitePeer(image: TelegramMediaImageRepresentation?, title: String, memberCount: Int32, members: [EnginePeer], data: JoinLinkPreviewData) {
|
||||
let contentNode = JoinLinkPreviewPeerContentNode(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, content: .invite(isGroup: data.isGroup, image: image, title: title, memberCount: memberCount, members: members))
|
||||
contentNode.join = { [weak self] in
|
||||
|
@ -9,6 +9,7 @@ import AccountContext
|
||||
import SelectablePeerNode
|
||||
import ShareController
|
||||
import SolidRoundedButtonNode
|
||||
import ActivityIndicator
|
||||
|
||||
private let avatarFont = avatarPlaceholderFont(size: 42.0)
|
||||
|
||||
@ -250,3 +251,51 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer
|
||||
func updateSelectedPeers() {
|
||||
}
|
||||
}
|
||||
|
||||
public enum ShareLoadingState {
|
||||
case preparing
|
||||
case progress(Float)
|
||||
case done
|
||||
}
|
||||
|
||||
public final class JoinLinkPreviewLoadingContainerNode: ASDisplayNode, ShareContentContainerNode {
|
||||
private var contentOffsetUpdated: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
|
||||
|
||||
private let theme: PresentationTheme
|
||||
private let activityIndicator: ActivityIndicator
|
||||
|
||||
public init(theme: PresentationTheme) {
|
||||
self.theme = theme
|
||||
self.activityIndicator = ActivityIndicator(type: .custom(theme.actionSheet.controlAccentColor, 22.0, 2.0, false))
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.activityIndicator)
|
||||
}
|
||||
|
||||
public func activate() {
|
||||
}
|
||||
|
||||
public func deactivate() {
|
||||
}
|
||||
|
||||
public func setEnsurePeerVisibleOnLayout(_ peerId: EnginePeer.Id?) {
|
||||
}
|
||||
|
||||
public func setContentOffsetUpdated(_ f: ((CGFloat, ContainedViewLayoutTransition) -> Void)?) {
|
||||
self.contentOffsetUpdated = f
|
||||
}
|
||||
|
||||
public func updateLayout(size: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let nodeHeight: CGFloat = 125.0
|
||||
|
||||
let indicatorSize = self.activityIndicator.calculateSizeThatFits(size)
|
||||
let indicatorFrame = CGRect(origin: CGPoint(x: floor((size.width - indicatorSize.width) / 2.0), y: size.height - nodeHeight + floor((nodeHeight - indicatorSize.height) / 2.0)), size: indicatorSize)
|
||||
transition.updateFrame(node: self.activityIndicator, frame: indicatorFrame)
|
||||
|
||||
self.contentOffsetUpdated?(-size.height + nodeHeight, transition)
|
||||
}
|
||||
|
||||
public func updateSelectedPeers() {
|
||||
}
|
||||
}
|
||||
|
@ -17,19 +17,27 @@ final class LocationInfoListItem: ListViewItem {
|
||||
let location: TelegramMediaMap
|
||||
let address: String?
|
||||
let distance: String?
|
||||
let eta: String?
|
||||
let drivingTime: Double?
|
||||
let transitTime: Double?
|
||||
let walkingTime: Double?
|
||||
let action: () -> Void
|
||||
let getDirections: () -> Void
|
||||
let drivingAction: () -> Void
|
||||
let transitAction: () -> Void
|
||||
let walkingAction: () -> Void
|
||||
|
||||
public init(presentationData: ItemListPresentationData, engine: TelegramEngine, location: TelegramMediaMap, address: String?, distance: String?, eta: String?, action: @escaping () -> Void, getDirections: @escaping () -> Void) {
|
||||
public init(presentationData: ItemListPresentationData, engine: TelegramEngine, location: TelegramMediaMap, address: String?, distance: String?, drivingTime: Double?, transitTime: Double?, walkingTime: Double?, action: @escaping () -> Void, drivingAction: @escaping () -> Void, transitAction: @escaping () -> Void, walkingAction: @escaping () -> Void) {
|
||||
self.presentationData = presentationData
|
||||
self.engine = engine
|
||||
self.location = location
|
||||
self.address = address
|
||||
self.distance = distance
|
||||
self.eta = eta
|
||||
self.drivingTime = drivingTime
|
||||
self.transitTime = transitTime
|
||||
self.walkingTime = walkingTime
|
||||
self.action = action
|
||||
self.getDirections = getDirections
|
||||
self.drivingAction = drivingAction
|
||||
self.transitAction = transitAction
|
||||
self.walkingAction = walkingAction
|
||||
}
|
||||
|
||||
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
@ -71,7 +79,10 @@ final class LocationInfoListItemNode: ListViewItemNode {
|
||||
private var subtitleNode: TextNode?
|
||||
private let venueIconNode: TransformImageNode
|
||||
private let buttonNode: HighlightableButtonNode
|
||||
private var directionsButtonNode: SolidRoundedButtonNode?
|
||||
|
||||
private var drivingButtonNode: SolidRoundedButtonNode?
|
||||
private var transitButtonNode: SolidRoundedButtonNode?
|
||||
private var walkingButtonNode: SolidRoundedButtonNode?
|
||||
|
||||
private var item: LocationInfoListItem?
|
||||
private var layoutParams: ListViewItemLayoutParams?
|
||||
@ -166,7 +177,7 @@ final class LocationInfoListItemNode: ListViewItemNode {
|
||||
|
||||
let titleSpacing: CGFloat = 1.0
|
||||
let bottomInset: CGFloat = 4.0
|
||||
let contentSize = CGSize(width: params.width, height: max(126.0, verticalInset * 2.0 + titleLayout.size.height + titleSpacing + subtitleLayout.size.height + bottomInset))
|
||||
let contentSize = CGSize(width: params.width, height: max(100.0, verticalInset * 2.0 + titleLayout.size.height + titleSpacing + subtitleLayout.size.height + bottomInset))
|
||||
let nodeLayout = ListViewItemNodeLayout(contentSize: contentSize, insets: UIEdgeInsets())
|
||||
|
||||
return (nodeLayout, { [weak self] in
|
||||
@ -187,7 +198,6 @@ final class LocationInfoListItemNode: ListViewItemNode {
|
||||
|
||||
if let _ = updatedTheme {
|
||||
strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.plainBackgroundColor
|
||||
strongSelf.directionsButtonNode?.updateTheme(SolidRoundedButtonTheme(theme: item.presentationData.theme))
|
||||
}
|
||||
|
||||
let arguments = VenueIconArguments(defaultBackgroundColor: item.presentationData.theme.chat.inputPanel.actionControlFillColor, defaultForegroundColor: item.presentationData.theme.chat.inputPanel.actionControlForegroundColor)
|
||||
@ -212,19 +222,50 @@ final class LocationInfoListItemNode: ListViewItemNode {
|
||||
strongSelf.addSubnode(subtitleNode)
|
||||
}
|
||||
|
||||
let directionsButtonNode: SolidRoundedButtonNode
|
||||
if let currentDirectionsButtonNode = strongSelf.directionsButtonNode {
|
||||
directionsButtonNode = currentDirectionsButtonNode
|
||||
} else {
|
||||
directionsButtonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: item.presentationData.theme), height: 50.0, cornerRadius: 10.0)
|
||||
directionsButtonNode.title = item.presentationData.strings.Map_Directions
|
||||
directionsButtonNode.pressed = {
|
||||
item.getDirections()
|
||||
let buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme)
|
||||
if strongSelf.drivingButtonNode == nil {
|
||||
strongSelf.drivingButtonNode = SolidRoundedButtonNode(icon: generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsDriving"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor), theme: buttonTheme, fontSize: 15.0, height: 32.0, cornerRadius: 16.0)
|
||||
strongSelf.drivingButtonNode?.iconSpacing = 5.0
|
||||
strongSelf.drivingButtonNode?.alpha = 0.0
|
||||
strongSelf.drivingButtonNode?.allowsGroupOpacity = true
|
||||
strongSelf.drivingButtonNode?.pressed = { [weak self] in
|
||||
if let item = self?.item {
|
||||
item.drivingAction()
|
||||
}
|
||||
}
|
||||
strongSelf.addSubnode(directionsButtonNode)
|
||||
strongSelf.directionsButtonNode = directionsButtonNode
|
||||
strongSelf.drivingButtonNode.flatMap { strongSelf.addSubnode($0) }
|
||||
|
||||
strongSelf.transitButtonNode = SolidRoundedButtonNode(icon: generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsTransit"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor), theme: buttonTheme, fontSize: 15.0, height: 32.0, cornerRadius: 16.0)
|
||||
strongSelf.transitButtonNode?.iconSpacing = 2.0
|
||||
strongSelf.transitButtonNode?.alpha = 0.0
|
||||
strongSelf.transitButtonNode?.allowsGroupOpacity = true
|
||||
strongSelf.transitButtonNode?.pressed = { [weak self] in
|
||||
if let item = self?.item {
|
||||
item.transitAction()
|
||||
}
|
||||
}
|
||||
strongSelf.transitButtonNode.flatMap { strongSelf.addSubnode($0) }
|
||||
|
||||
strongSelf.walkingButtonNode = SolidRoundedButtonNode(icon: generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsWalking"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor), theme: buttonTheme, fontSize: 15.0, height: 32.0, cornerRadius: 16.0)
|
||||
strongSelf.walkingButtonNode?.iconSpacing = 2.0
|
||||
strongSelf.walkingButtonNode?.alpha = 0.0
|
||||
strongSelf.walkingButtonNode?.allowsGroupOpacity = true
|
||||
strongSelf.walkingButtonNode?.pressed = { [weak self] in
|
||||
if let item = self?.item {
|
||||
item.walkingAction()
|
||||
}
|
||||
}
|
||||
strongSelf.walkingButtonNode.flatMap { strongSelf.addSubnode($0) }
|
||||
} else if let _ = updatedTheme {
|
||||
strongSelf.drivingButtonNode?.updateTheme(buttonTheme)
|
||||
strongSelf.drivingButtonNode?.icon = generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsDriving"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
|
||||
strongSelf.transitButtonNode?.updateTheme(buttonTheme)
|
||||
strongSelf.transitButtonNode?.icon = generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsTransit"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
|
||||
strongSelf.walkingButtonNode?.updateTheme(buttonTheme)
|
||||
strongSelf.walkingButtonNode?.icon = generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsWalking"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
}
|
||||
directionsButtonNode.subtitle = item.eta
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size)
|
||||
titleNode.frame = titleFrame
|
||||
@ -235,9 +276,42 @@ final class LocationInfoListItemNode: ListViewItemNode {
|
||||
let iconNodeFrame = CGRect(origin: CGPoint(x: params.leftInset + inset, y: 10.0), size: CGSize(width: iconSize, height: iconSize))
|
||||
strongSelf.venueIconNode.frame = iconNodeFrame
|
||||
|
||||
let directionsWidth = contentSize.width - inset * 2.0
|
||||
let directionsHeight = directionsButtonNode.updateLayout(width: directionsWidth, transition: .immediate)
|
||||
directionsButtonNode.frame = CGRect(x: inset, y: iconNodeFrame.maxY + 14.0, width: directionsWidth, height: directionsHeight)
|
||||
if let drivingTime = item.drivingTime {
|
||||
strongSelf.drivingButtonNode?.title = stringForEstimatedDuration(strings: item.presentationData.strings, time: drivingTime, format: { $0 })
|
||||
|
||||
if currentItem?.drivingTime == nil {
|
||||
strongSelf.drivingButtonNode?.alpha = 1.0
|
||||
strongSelf.drivingButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
if let transitTime = item.transitTime {
|
||||
strongSelf.transitButtonNode?.title = stringForEstimatedDuration(strings: item.presentationData.strings, time: transitTime, format: { $0 })
|
||||
|
||||
if currentItem?.transitTime == nil {
|
||||
strongSelf.transitButtonNode?.alpha = 1.0
|
||||
strongSelf.transitButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
if let walkingTime = item.walkingTime {
|
||||
strongSelf.walkingButtonNode?.title = stringForEstimatedDuration(strings: item.presentationData.strings, time: walkingTime, format: { $0 })
|
||||
|
||||
if currentItem?.walkingTime == nil {
|
||||
strongSelf.walkingButtonNode?.alpha = 1.0
|
||||
strongSelf.walkingButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
let directionsWidth: CGFloat = 93.0
|
||||
let directionsSpacing: CGFloat = 8.0
|
||||
let drivingHeight = strongSelf.drivingButtonNode?.updateLayout(width: directionsWidth, transition: .immediate) ?? 0.0
|
||||
let transitHeight = strongSelf.transitButtonNode?.updateLayout(width: directionsWidth, transition: .immediate) ?? 0.0
|
||||
let walkingHeight = strongSelf.walkingButtonNode?.updateLayout(width: directionsWidth, transition: .immediate) ?? 0.0
|
||||
|
||||
strongSelf.drivingButtonNode?.frame = CGRect(origin: CGPoint(x: leftInset, y: subtitleFrame.maxY + 12.0), size: CGSize(width: directionsWidth, height: drivingHeight))
|
||||
strongSelf.transitButtonNode?.frame = CGRect(origin: CGPoint(x: leftInset + directionsWidth + directionsSpacing, y: subtitleFrame.maxY + 12.0), size: CGSize(width: directionsWidth, height: transitHeight))
|
||||
strongSelf.walkingButtonNode?.frame = CGRect(origin: CGPoint(x: leftInset + directionsWidth + directionsSpacing + directionsWidth + directionsSpacing, y: subtitleFrame.maxY + 12.0), size: CGSize(width: directionsWidth, height: walkingHeight))
|
||||
|
||||
strongSelf.buttonNode.frame = CGRect(x: 0.0, y: 0.0, width: contentSize.width, height: 72.0)
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentSize.width, height: contentSize.height))
|
||||
|
@ -242,7 +242,7 @@ final class LocationLiveListItemNode: ListViewItemNode {
|
||||
|
||||
let buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme)
|
||||
if strongSelf.drivingButtonNode == nil {
|
||||
strongSelf.drivingButtonNode = SolidRoundedButtonNode(icon: UIImage(bundleImageName: "Location/DirectionsDriving"), theme: buttonTheme, fontSize: 15.0, height: 32.0, cornerRadius: 16.0)
|
||||
strongSelf.drivingButtonNode = SolidRoundedButtonNode(icon: generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsDriving"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor), theme: buttonTheme, fontSize: 15.0, height: 32.0, cornerRadius: 16.0)
|
||||
strongSelf.drivingButtonNode?.alpha = 0.0
|
||||
strongSelf.drivingButtonNode?.allowsGroupOpacity = true
|
||||
strongSelf.drivingButtonNode?.pressed = { [weak self] in
|
||||
@ -252,7 +252,7 @@ final class LocationLiveListItemNode: ListViewItemNode {
|
||||
}
|
||||
strongSelf.drivingButtonNode.flatMap { strongSelf.addSubnode($0) }
|
||||
|
||||
strongSelf.transitButtonNode = SolidRoundedButtonNode(icon: UIImage(bundleImageName: "Location/DirectionsTransit"), theme: buttonTheme, fontSize: 15.0, height: 32.0, cornerRadius: 16.0)
|
||||
strongSelf.transitButtonNode = SolidRoundedButtonNode(icon: generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsTransit"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor), theme: buttonTheme, fontSize: 15.0, height: 32.0, cornerRadius: 16.0)
|
||||
strongSelf.transitButtonNode?.alpha = 0.0
|
||||
strongSelf.transitButtonNode?.allowsGroupOpacity = true
|
||||
strongSelf.transitButtonNode?.pressed = { [weak self] in
|
||||
@ -262,7 +262,7 @@ final class LocationLiveListItemNode: ListViewItemNode {
|
||||
}
|
||||
strongSelf.transitButtonNode.flatMap { strongSelf.addSubnode($0) }
|
||||
|
||||
strongSelf.walkingButtonNode = SolidRoundedButtonNode(icon: UIImage(bundleImageName: "Location/DirectionsWalking"), theme: buttonTheme, fontSize: 15.0, height: 32.0, cornerRadius: 16.0)
|
||||
strongSelf.walkingButtonNode = SolidRoundedButtonNode(icon: generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsWalking"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor), theme: buttonTheme, fontSize: 15.0, height: 32.0, cornerRadius: 16.0)
|
||||
strongSelf.walkingButtonNode?.alpha = 0.0
|
||||
strongSelf.walkingButtonNode?.allowsGroupOpacity = true
|
||||
strongSelf.walkingButtonNode?.pressed = { [weak self] in
|
||||
@ -273,8 +273,13 @@ final class LocationLiveListItemNode: ListViewItemNode {
|
||||
strongSelf.walkingButtonNode.flatMap { strongSelf.addSubnode($0) }
|
||||
} else if let _ = updatedTheme {
|
||||
strongSelf.drivingButtonNode?.updateTheme(buttonTheme)
|
||||
strongSelf.drivingButtonNode?.icon = generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsDriving"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
|
||||
strongSelf.transitButtonNode?.updateTheme(buttonTheme)
|
||||
strongSelf.transitButtonNode?.icon = generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsTransit"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
|
||||
strongSelf.walkingButtonNode?.updateTheme(buttonTheme)
|
||||
strongSelf.walkingButtonNode?.icon = generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsWalking"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
}
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size)
|
||||
|
@ -47,7 +47,7 @@ private enum LocationViewEntryId: Hashable {
|
||||
}
|
||||
|
||||
private enum LocationViewEntry: Comparable, Identifiable {
|
||||
case info(PresentationTheme, TelegramMediaMap, String?, Double?, Double?)
|
||||
case info(PresentationTheme, TelegramMediaMap, String?, Double?, Double?, Double?, Double?)
|
||||
case toggleLiveLocation(PresentationTheme, String, String, Double?, Double?)
|
||||
case liveLocation(PresentationTheme, PresentationDateTimeFormat, PresentationPersonNameOrder, Message, Double?, Double?, Double?, Double?, Int)
|
||||
|
||||
@ -64,8 +64,8 @@ private enum LocationViewEntry: Comparable, Identifiable {
|
||||
|
||||
static func ==(lhs: LocationViewEntry, rhs: LocationViewEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .info(lhsTheme, lhsLocation, lhsAddress, lhsDistance, lhsTime):
|
||||
if case let .info(rhsTheme, rhsLocation, rhsAddress, rhsDistance, rhsTime) = rhs, lhsTheme === rhsTheme, lhsLocation.venue?.id == rhsLocation.venue?.id, lhsAddress == rhsAddress, lhsDistance == rhsDistance, lhsTime == rhsTime {
|
||||
case let .info(lhsTheme, lhsLocation, lhsAddress, lhsDistance, lhsDrivingTime, lhsTransitTime, lhsWalkingTime):
|
||||
if case let .info(rhsTheme, rhsLocation, rhsAddress, rhsDistance, rhsDrivingTime, rhsTransitTime, rhsWalkingTime) = rhs, lhsTheme === rhsTheme, lhsLocation.venue?.id == rhsLocation.venue?.id, lhsAddress == rhsAddress, lhsDistance == rhsDistance, lhsDrivingTime == rhsDrivingTime, lhsTransitTime == rhsTransitTime, lhsWalkingTime == rhsWalkingTime {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -113,7 +113,7 @@ private enum LocationViewEntry: Comparable, Identifiable {
|
||||
|
||||
func item(context: AccountContext, presentationData: PresentationData, interaction: LocationViewInteraction?) -> ListViewItem {
|
||||
switch self {
|
||||
case let .info(_, location, address, distance, time):
|
||||
case let .info(_, location, address, distance, drivingTime, transitTime, walkingTime):
|
||||
let addressString: String?
|
||||
if let address = address {
|
||||
addressString = address
|
||||
@ -126,14 +126,14 @@ private enum LocationViewEntry: Comparable, Identifiable {
|
||||
} else {
|
||||
distanceString = nil
|
||||
}
|
||||
var eta: String?
|
||||
if let time = time, time < 60.0 * 60.0 * 10.0 {
|
||||
eta = stringForEstimatedDuration(strings: presentationData.strings, time: time, format: { presentationData.strings.Map_DirectionsDriveEta($0).string })
|
||||
}
|
||||
return LocationInfoListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, location: location, address: addressString, distance: distanceString, eta: eta, action: {
|
||||
return LocationInfoListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, location: location, address: addressString, distance: distanceString, drivingTime: drivingTime, transitTime: transitTime, walkingTime: walkingTime, action: {
|
||||
interaction?.goToCoordinate(location.coordinate)
|
||||
}, getDirections: {
|
||||
}, drivingAction: {
|
||||
interaction?.requestDirections(location, nil, .driving)
|
||||
}, transitAction: {
|
||||
interaction?.requestDirections(location, nil, .transit)
|
||||
}, walkingAction: {
|
||||
interaction?.requestDirections(location, nil, .walking)
|
||||
})
|
||||
case let .toggleLiveLocation(_, title, subtitle, beginTimstamp, timeout):
|
||||
let beginTimeAndTimeout: (Double, Double)?
|
||||
@ -286,12 +286,12 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
|
||||
throttledUserLocation(self.headerNode.mapNode.userLocation)
|
||||
)
|
||||
|
||||
var eta: Signal<Double?, NoError> = .single(nil)
|
||||
var eta: Signal<(Double?, Double?, Double?)?, NoError> = .single(nil)
|
||||
var address: Signal<String?, NoError> = .single(nil)
|
||||
|
||||
if let location = getLocation(from: subject), location.liveBroadcastingTimeout == nil {
|
||||
eta = .single(nil)
|
||||
|> then(getExpectedTravelTime(coordinate: location.coordinate, transportType: .automobile))
|
||||
|> then(combineLatest(queue: Queue.mainQueue(), getExpectedTravelTime(coordinate: location.coordinate, transportType: .automobile), getExpectedTravelTime(coordinate: location.coordinate, transportType: .transit), getExpectedTravelTime(coordinate: location.coordinate, transportType: .walking)) |> map(Optional.init))
|
||||
|
||||
if let venue = location.venue, let venueAddress = venue.address, !venueAddress.isEmpty {
|
||||
address = .single(venueAddress)
|
||||
@ -359,7 +359,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
|
||||
let subjectLocation = CLLocation(latitude: location.latitude, longitude: location.longitude)
|
||||
let distance = userLocation.flatMap { subjectLocation.distance(from: $0) }
|
||||
|
||||
entries.append(.info(presentationData.theme, location, address, distance, eta))
|
||||
entries.append(.info(presentationData.theme, location, address, distance, eta?.0, eta?.1, eta?.2))
|
||||
|
||||
annotations.append(LocationPinAnnotation(context: context, theme: presentationData.theme, location: location, forcedSelection: true))
|
||||
} else {
|
||||
@ -814,7 +814,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
|
||||
}
|
||||
|
||||
let overlap: CGFloat = 6.0
|
||||
var topInset: CGFloat = layout.size.height - layout.intrinsicInsets.bottom - 126.0 - overlap
|
||||
var topInset: CGFloat = layout.size.height - layout.intrinsicInsets.bottom - 100.0 - overlap
|
||||
if let location = getLocation(from: self.subject), location.liveBroadcastingTimeout != nil {
|
||||
topInset += 66.0
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
|
||||
case publicLinkAvailability(PresentationTheme, String, Bool)
|
||||
case editablePublicLink(PresentationTheme, PresentationStrings, String, String)
|
||||
case privateLinkHeader(PresentationTheme, String)
|
||||
case privateLink(PresentationTheme, ExportedInvitation?, Bool)
|
||||
case privateLink(PresentationTheme, ExportedInvitation?, [EnginePeer], Int32, Bool)
|
||||
case privateLinkInfo(PresentationTheme, String)
|
||||
case privateLinkManage(PresentationTheme, String)
|
||||
case privateLinkManageInfo(PresentationTheme, String)
|
||||
@ -182,8 +182,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .privateLink(lhsTheme, lhsInvite, lhsDisplayImporters):
|
||||
if case let .privateLink(rhsTheme, rhsInvite, rhsDisplayImporters) = rhs, lhsTheme === rhsTheme, lhsInvite == rhsInvite, lhsDisplayImporters == rhsDisplayImporters {
|
||||
case let .privateLink(lhsTheme, lhsInvite, lhsPeers, lhsImportersCount, lhsDisplayImporters):
|
||||
if case let .privateLink(rhsTheme, rhsInvite, rhsPeers, rhsImportersCount, rhsDisplayImporters) = rhs, lhsTheme === rhsTheme, lhsInvite == rhsInvite, lhsPeers == rhsPeers, lhsImportersCount == rhsImportersCount, lhsDisplayImporters == rhsDisplayImporters {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -290,8 +290,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
|
||||
return ItemListActivityTextItem(displayActivity: value, presentationData: presentationData, text: attr, sectionId: self.section)
|
||||
case let .privateLinkHeader(_, title):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
|
||||
case let .privateLink(_, invite, displayImporters):
|
||||
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, count: 0, peers: [], displayButton: true, displayImporters: displayImporters, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
|
||||
case let .privateLink(_, invite, peers, importersCount, displayImporters):
|
||||
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, count: importersCount, peers: peers, displayButton: true, displayImporters: displayImporters, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
|
||||
if let invite = invite {
|
||||
arguments.copyLink(invite)
|
||||
}
|
||||
@ -452,7 +452,7 @@ private struct ChannelVisibilityControllerState: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
private func channelVisibilityControllerEntries(presentationData: PresentationData, mode: ChannelVisibilityControllerMode, view: PeerView, publicChannelsToRevoke: [Peer]?, state: ChannelVisibilityControllerState) -> [ChannelVisibilityEntry] {
|
||||
private func channelVisibilityControllerEntries(presentationData: PresentationData, mode: ChannelVisibilityControllerMode, view: PeerView, publicChannelsToRevoke: [Peer]?, importers: PeerInvitationImportersState?, state: ChannelVisibilityControllerState) -> [ChannelVisibilityEntry] {
|
||||
var entries: [ChannelVisibilityEntry] = []
|
||||
|
||||
if let peer = view.peers[view.peerId] as? TelegramChannel {
|
||||
@ -615,7 +615,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
||||
case .privateChannel:
|
||||
let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation
|
||||
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
|
||||
entries.append(.privateLink(presentationData.theme, invite, mode != .initialSetup))
|
||||
entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, mode != .initialSetup))
|
||||
if isGroup {
|
||||
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp))
|
||||
} else {
|
||||
@ -634,7 +634,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
||||
case .privateLink:
|
||||
let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation
|
||||
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
|
||||
entries.append(.privateLink(presentationData.theme, invite, mode != .initialSetup))
|
||||
entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, mode != .initialSetup))
|
||||
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.GroupInfo_InviteLink_Help))
|
||||
switch mode {
|
||||
case .initialSetup:
|
||||
@ -733,7 +733,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
||||
case .privateChannel:
|
||||
let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation
|
||||
entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased()))
|
||||
entries.append(.privateLink(presentationData.theme, invite, mode != .initialSetup))
|
||||
entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, mode != .initialSetup))
|
||||
entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp))
|
||||
switch mode {
|
||||
case .initialSetup:
|
||||
@ -926,7 +926,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
|
||||
}
|
||||
presentControllerImpl?(shareController, nil)
|
||||
}, linkContextAction: { node, gesture in
|
||||
guard let node = node as? ContextExtractedContentContainingNode, let controller = getControllerImpl?() else {
|
||||
guard let node = node as? ContextReferenceContentNode, let controller = getControllerImpl?() else {
|
||||
return
|
||||
}
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
@ -1050,7 +1050,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
|
||||
})
|
||||
})))
|
||||
|
||||
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .extracted(InviteLinkContextExtractedContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(items: items)), gesture: gesture)
|
||||
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(items: items)), gesture: gesture)
|
||||
presentInGlobalOverlayImpl?(contextController)
|
||||
}, manageInviteLinks: {
|
||||
let controller = inviteLinkListController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, admin: nil)
|
||||
@ -1063,10 +1063,28 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
|
||||
let previousHadNamesToRevoke = Atomic<Bool?>(value: nil)
|
||||
let previousInvitation = Atomic<ExportedInvitation?>(value: nil)
|
||||
|
||||
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
|
||||
let signal = combineLatest(presentationData, statePromise.get() |> deliverOnMainQueue, peerView, peersDisablingAddressNameAssignment.get() |> deliverOnMainQueue)
|
||||
let mainLink = context.engine.data.subscribe(
|
||||
TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: peerId)
|
||||
)
|
||||
|
||||
let importersState = Promise<PeerInvitationImportersState?>(nil)
|
||||
let importersContext: Signal<PeerInvitationImportersContext?, NoError> = mainLink
|
||||
|> distinctUntilChanged
|
||||
|> deliverOnMainQueue
|
||||
|> map { presentationData, state, view, publicChannelsToRevoke -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
|> map { invite -> PeerInvitationImportersContext? in
|
||||
return invite.flatMap { context.engine.peers.peerInvitationImporters(peerId: peerId, subject: .invite(invite: $0, requested: false)) }
|
||||
} |> afterNext { context in
|
||||
if let context = context {
|
||||
importersState.set(context.state |> map(Optional.init))
|
||||
} else {
|
||||
importersState.set(.single(nil))
|
||||
}
|
||||
}
|
||||
|
||||
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
|
||||
let signal = combineLatest(presentationData, statePromise.get() |> deliverOnMainQueue, peerView, peersDisablingAddressNameAssignment.get() |> deliverOnMainQueue, importersContext, importersState.get())
|
||||
|> deliverOnMainQueue
|
||||
|> map { presentationData, state, view, publicChannelsToRevoke, importersContext, importers -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let peer = peerViewMainPeer(view)
|
||||
|
||||
var rightNavigationButton: ItemListNavigationButton?
|
||||
@ -1302,7 +1320,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
|
||||
}
|
||||
}
|
||||
|
||||
let entries = channelVisibilityControllerEntries(presentationData: presentationData, mode: mode, view: view, publicChannelsToRevoke: publicChannelsToRevoke, state: state)
|
||||
let entries = channelVisibilityControllerEntries(presentationData: presentationData, mode: mode, view: view, publicChannelsToRevoke: publicChannelsToRevoke, importers: importers, state: state)
|
||||
|
||||
var focusItemTag: ItemListItemTag?
|
||||
if entries.count > 1, let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
|
||||
@ -1439,26 +1457,16 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
|
||||
return controller
|
||||
}
|
||||
|
||||
private final class InviteLinkContextExtractedContentSource: ContextExtractedContentSource {
|
||||
var keepInPlace: Bool
|
||||
let ignoreContentTouches: Bool = true
|
||||
let blurBackground: Bool
|
||||
|
||||
final class InviteLinkContextReferenceContentSource: ContextReferenceContentSource {
|
||||
private let controller: ViewController
|
||||
private let sourceNode: ContextExtractedContentContainingNode
|
||||
private let sourceNode: ContextReferenceContentNode
|
||||
|
||||
init(controller: ViewController, sourceNode: ContextExtractedContentContainingNode) {
|
||||
init(controller: ViewController, sourceNode: ContextReferenceContentNode) {
|
||||
self.controller = controller
|
||||
self.sourceNode = sourceNode
|
||||
self.keepInPlace = false
|
||||
self.blurBackground = false
|
||||
}
|
||||
|
||||
func takeView() -> ContextControllerTakeViewInfo? {
|
||||
return ContextControllerTakeViewInfo(contentContainingNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
|
||||
func putBack() -> ContextControllerPutBackViewInfo? {
|
||||
return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||
return ContextControllerReferenceViewInfo(referenceNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import Postbox
|
||||
import TelegramPresentationData
|
||||
import ActivityIndicator
|
||||
import RadialStatusNode
|
||||
import RadialStatusNode
|
||||
|
||||
public enum ShareLoadingState {
|
||||
case preparing
|
||||
|
@ -54,6 +54,20 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
public var icon: UIImage? {
|
||||
didSet {
|
||||
self.iconNode.image = icon
|
||||
}
|
||||
}
|
||||
|
||||
public var iconSpacing: CGFloat = 8.0 {
|
||||
didSet {
|
||||
if let width = self.validLayout {
|
||||
_ = self.updateLayout(width: width, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public init(title: String? = nil, icon: UIImage? = nil, theme: SolidRoundedButtonTheme, font: SolidRoundedButtonFont = .bold, fontSize: CGFloat = 17.0, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0, gloss: Bool = false) {
|
||||
self.theme = theme
|
||||
self.font = font
|
||||
@ -66,9 +80,6 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
self.buttonBackgroundNode.clipsToBounds = true
|
||||
self.buttonBackgroundNode.backgroundColor = theme.backgroundColor
|
||||
self.buttonBackgroundNode.cornerRadius = cornerRadius
|
||||
if #available(iOS 13.0, *) {
|
||||
self.buttonBackgroundNode.layer.cornerCurve = .continuous
|
||||
}
|
||||
|
||||
self.buttonGlossNode = SolidRoundedButtonGlossNode(color: theme.foregroundColor, cornerRadius: cornerRadius)
|
||||
|
||||
@ -122,6 +133,14 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
public override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
self.buttonBackgroundNode.layer.cornerCurve = .continuous
|
||||
}
|
||||
}
|
||||
|
||||
public func updateTheme(_ theme: SolidRoundedButtonTheme) {
|
||||
guard theme !== self.theme else {
|
||||
return
|
||||
@ -158,7 +177,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
let iconSize = self.iconNode.image?.size ?? CGSize()
|
||||
let titleSize = self.titleNode.updateLayout(buttonSize)
|
||||
|
||||
let iconSpacing: CGFloat = 8.0
|
||||
let iconSpacing: CGFloat = self.iconSpacing
|
||||
|
||||
var contentWidth: CGFloat = titleSize.width
|
||||
if !iconSize.width.isZero {
|
||||
|
@ -286,6 +286,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-997782967] = { return Api.Update.parse_updateBotStopped($0) }
|
||||
dict[192428418] = { return Api.Update.parse_updateGroupCallConnection($0) }
|
||||
dict[1299263278] = { return Api.Update.parse_updateBotCommands($0) }
|
||||
dict[-82532135] = { return Api.Update.parse_updatePendingJoinRequests($0) }
|
||||
dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) }
|
||||
dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) }
|
||||
dict[-592373577] = { return Api.GroupCallParticipantVideoSourceGroup.parse_groupCallParticipantVideoSourceGroup($0) }
|
||||
@ -469,6 +470,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-384910503] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionExportedInviteEdit($0) }
|
||||
dict[1048537159] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantVolume($0) }
|
||||
dict[1855199800] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeHistoryTTL($0) }
|
||||
dict[-1347021750] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantJoinByRequest($0) }
|
||||
dict[-1271602504] = { return Api.auth.ExportedAuthorization.parse_exportedAuthorization($0) }
|
||||
dict[2103482845] = { return Api.SecurePlainData.parse_securePlainPhone($0) }
|
||||
dict[569137759] = { return Api.SecurePlainData.parse_securePlainEmail($0) }
|
||||
|
@ -4827,6 +4827,7 @@ public extension Api {
|
||||
case updateBotStopped(userId: Int64, date: Int32, stopped: Api.Bool, qts: Int32)
|
||||
case updateGroupCallConnection(flags: Int32, params: Api.DataJSON)
|
||||
case updateBotCommands(peer: Api.Peer, botId: Int64, commands: [Api.BotCommand])
|
||||
case updatePendingJoinRequests(peer: Api.Peer, requestsPending: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
@ -5641,6 +5642,13 @@ public extension Api {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
case .updatePendingJoinRequests(let peer, let requestsPending):
|
||||
if boxed {
|
||||
buffer.appendInt32(-82532135)
|
||||
}
|
||||
peer.serialize(buffer, true)
|
||||
serializeInt32(requestsPending, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -5832,6 +5840,8 @@ public extension Api {
|
||||
return ("updateGroupCallConnection", [("flags", flags), ("params", params)])
|
||||
case .updateBotCommands(let peer, let botId, let commands):
|
||||
return ("updateBotCommands", [("peer", peer), ("botId", botId), ("commands", commands)])
|
||||
case .updatePendingJoinRequests(let peer, let requestsPending):
|
||||
return ("updatePendingJoinRequests", [("peer", peer), ("requestsPending", requestsPending)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -7491,6 +7501,22 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_updatePendingJoinRequests(_ reader: BufferReader) -> Update? {
|
||||
var _1: Api.Peer?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.Peer
|
||||
}
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.Update.updatePendingJoinRequests(peer: _1!, requestsPending: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum PopularContact: TypeConstructorDescription {
|
||||
@ -11428,6 +11454,7 @@ public extension Api {
|
||||
case channelAdminLogEventActionExportedInviteEdit(prevInvite: Api.ExportedChatInvite, newInvite: Api.ExportedChatInvite)
|
||||
case channelAdminLogEventActionParticipantVolume(participant: Api.GroupCallParticipant)
|
||||
case channelAdminLogEventActionChangeHistoryTTL(prevValue: Int32, newValue: Int32)
|
||||
case channelAdminLogEventActionParticipantJoinByRequest(invite: Api.ExportedChatInvite, approvedBy: Int64)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
@ -11637,6 +11664,13 @@ public extension Api {
|
||||
serializeInt32(prevValue, buffer: buffer, boxed: false)
|
||||
serializeInt32(newValue, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .channelAdminLogEventActionParticipantJoinByRequest(let invite, let approvedBy):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1347021750)
|
||||
}
|
||||
invite.serialize(buffer, true)
|
||||
serializeInt64(approvedBy, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -11706,6 +11740,8 @@ public extension Api {
|
||||
return ("channelAdminLogEventActionParticipantVolume", [("participant", participant)])
|
||||
case .channelAdminLogEventActionChangeHistoryTTL(let prevValue, let newValue):
|
||||
return ("channelAdminLogEventActionChangeHistoryTTL", [("prevValue", prevValue), ("newValue", newValue)])
|
||||
case .channelAdminLogEventActionParticipantJoinByRequest(let invite, let approvedBy):
|
||||
return ("channelAdminLogEventActionParticipantJoinByRequest", [("invite", invite), ("approvedBy", approvedBy)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -12151,6 +12187,22 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_channelAdminLogEventActionParticipantJoinByRequest(_ reader: BufferReader) -> ChannelAdminLogEventAction? {
|
||||
var _1: Api.ExportedChatInvite?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite
|
||||
}
|
||||
var _2: Int64?
|
||||
_2 = reader.readInt64()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.ChannelAdminLogEventAction.channelAdminLogEventActionParticipantJoinByRequest(invite: _1!, approvedBy: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum SecurePlainData: TypeConstructorDescription {
|
||||
|
@ -1451,6 +1451,28 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
||||
}
|
||||
return current
|
||||
})
|
||||
case let .updatePendingJoinRequests(peer, requestsPending):
|
||||
updatedState.updateCachedPeerData(peer.peerId, { current in
|
||||
if peer.peerId.namespace == Namespaces.Peer.CloudGroup {
|
||||
let previous: CachedGroupData
|
||||
if let current = current as? CachedGroupData {
|
||||
previous = current
|
||||
} else {
|
||||
previous = CachedGroupData()
|
||||
}
|
||||
return previous.withUpdatedInviteRequestsPending(requestsPending)
|
||||
} else if peer.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
let previous: CachedChannelData
|
||||
if let current = current as? CachedChannelData {
|
||||
previous = current
|
||||
} else {
|
||||
previous = CachedChannelData()
|
||||
}
|
||||
return previous.withUpdatedInviteRequestsPending(requestsPending)
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
})
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ public enum AdminLogEventAction {
|
||||
case participantJoinedViaInvite(ExportedInvitation)
|
||||
case changeHistoryTTL(previousValue: Int32?, updatedValue: Int32?)
|
||||
case changeTheme(previous: String?, updated: String?)
|
||||
case participantJoinByRequest(invitation: ExportedInvitation, approvedBy: PeerId)
|
||||
}
|
||||
|
||||
public enum ChannelAdminLogEventError {
|
||||
@ -250,6 +251,8 @@ func channelAdminLogEvents(postbox: Postbox, network: Network, peerId: PeerId, m
|
||||
action = .groupCallUpdateParticipantVolume(peerId: parsedParticipant.peerId, volume: parsedParticipant.volume ?? 10000)
|
||||
case let .channelAdminLogEventActionChangeHistoryTTL(prevValue, newValue):
|
||||
action = .changeHistoryTTL(previousValue: prevValue, updatedValue: newValue)
|
||||
case let .channelAdminLogEventActionParticipantJoinByRequest(invite, approvedBy):
|
||||
action = .participantJoinByRequest(invitation: ExportedInvitation(apiExportedInvite: invite), approvedBy: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(approvedBy)))
|
||||
}
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||
if let action = action {
|
||||
|
@ -81,7 +81,7 @@ func _internal_createPeerExportedInvitation(account: Account, peerId: PeerId, ex
|
||||
if let _ = usageLimit {
|
||||
flags |= (1 << 1)
|
||||
}
|
||||
if let _ = requestNeeded {
|
||||
if let requestNeeded = requestNeeded, requestNeeded {
|
||||
flags |= (1 << 3)
|
||||
}
|
||||
return account.network.request(Api.functions.messages.exportChatInvite(flags: flags, peer: inputPeer, expireDate: expireDate, usageLimit: usageLimit))
|
||||
|
@ -4,6 +4,11 @@ import TelegramApi
|
||||
import MtProtoKit
|
||||
|
||||
|
||||
public enum JoinLinkInfoError {
|
||||
case generic
|
||||
case flood
|
||||
}
|
||||
|
||||
public enum JoinLinkError {
|
||||
case generic
|
||||
case tooMuchJoined
|
||||
@ -49,7 +54,7 @@ func _internal_joinChatInteractively(with hash: String, account: Account) -> Sig
|
||||
case "INVITE_REQUEST_SENT":
|
||||
return .requestSent
|
||||
default:
|
||||
if error.description.hasPrefix("FLOOD_WAIT") {
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .flood
|
||||
} else {
|
||||
return .generic
|
||||
@ -74,13 +79,17 @@ func _internal_joinChatInteractively(with hash: String, account: Account) -> Sig
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_joinLinkInformation(_ hash: String, account: Account) -> Signal<ExternalJoiningChatState, NoError> {
|
||||
return account.network.request(Api.functions.messages.checkChatInvite(hash: hash))
|
||||
func _internal_joinLinkInformation(_ hash: String, account: Account) -> Signal<ExternalJoiningChatState, JoinLinkInfoError> {
|
||||
return account.network.request(Api.functions.messages.checkChatInvite(hash: hash), automaticFloodWait: false)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.ChatInvite?, NoError> in
|
||||
return .single(nil)
|
||||
|> `catch` { error -> Signal<Api.ChatInvite?, JoinLinkInfoError> in
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
return .fail(.flood)
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
|> mapToSignal { (result) -> Signal<ExternalJoiningChatState, NoError> in
|
||||
|> mapToSignal { result -> Signal<ExternalJoiningChatState, JoinLinkInfoError> in
|
||||
if let result = result {
|
||||
switch result {
|
||||
case let .chatInvite(flags, title, about, invitePhoto, participantsCount, participants):
|
||||
@ -96,6 +105,7 @@ func _internal_joinLinkInformation(_ hash: String, account: Account) -> Signal<E
|
||||
|
||||
return .alreadyJoined(peer.id)
|
||||
})
|
||||
|> castError(JoinLinkInfoError.self)
|
||||
}
|
||||
return .single(.invalidHash)
|
||||
case let .chatInvitePeek(chat, expires):
|
||||
@ -107,6 +117,7 @@ func _internal_joinLinkInformation(_ hash: String, account: Account) -> Signal<E
|
||||
|
||||
return .peek(peer.id, expires)
|
||||
})
|
||||
|> castError(JoinLinkInfoError.self)
|
||||
}
|
||||
return .single(.invalidHash)
|
||||
}
|
||||
|
@ -486,7 +486,7 @@ public extension TelegramEngine {
|
||||
return _internal_joinChatInteractively(with: hash, account: self.account)
|
||||
}
|
||||
|
||||
public func joinLinkInformation(_ hash: String) -> Signal<ExternalJoiningChatState, NoError> {
|
||||
public func joinLinkInformation(_ hash: String) -> Signal<ExternalJoiningChatState, JoinLinkInfoError> {
|
||||
return _internal_joinLinkInformation(hash, account: self.account)
|
||||
}
|
||||
|
||||
|
@ -191,6 +191,7 @@ private struct ApplicationSpecificNoticeKeys {
|
||||
private static let inlineBotLocationRequestNamespace: Int32 = 5
|
||||
private static let psaAcknowledgementNamespace: Int32 = 6
|
||||
private static let botGameNoticeNamespace: Int32 = 7
|
||||
private static let peerInviteRequestsNamespace: Int32 = 8
|
||||
|
||||
static func inlineBotLocationRequestNotice(peerId: PeerId) -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: inlineBotLocationRequestNamespace), key: noticeKey(peerId: peerId, key: 0))
|
||||
@ -319,6 +320,10 @@ private struct ApplicationSpecificNoticeKeys {
|
||||
static func interactiveEmojiSyncTip() -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.interactiveEmojiSyncTip.key)
|
||||
}
|
||||
|
||||
static func dismissedInvitationRequestsNotice(peerId: PeerId) -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: peerInviteRequestsNamespace), key: noticeKey(peerId: peerId, key: 0))
|
||||
}
|
||||
}
|
||||
|
||||
public struct ApplicationSpecificNotice {
|
||||
@ -1016,6 +1021,25 @@ public struct ApplicationSpecificNotice {
|
||||
}
|
||||
}
|
||||
|
||||
public static func dismissedInvitationRequests(accountManager: AccountManager<TelegramAccountManagerTypes>, peerId: PeerId) -> Signal<[Int64]?, NoError> {
|
||||
return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedInvitationRequestsNotice(peerId: peerId))
|
||||
|> map { view -> [Int64]? in
|
||||
if let value = view.value?.get(ApplicationSpecificInt64ArrayNotice.self) {
|
||||
return value.values
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func setDismissedInvitationRequests(accountManager: AccountManager<TelegramAccountManagerTypes>, peerId: PeerId, values: [Int64]) -> Signal<Void, NoError> {
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
if let entry = CodableEntry(ApplicationSpecificInt64ArrayNotice(values: values)) {
|
||||
transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedInvitationRequestsNotice(peerId: peerId), entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func reset(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Void, NoError> {
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
}
|
||||
|
@ -1,17 +1,8 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "car.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"filename" : "car_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB |
121
submodules/TelegramUI/Images.xcassets/Location/DirectionsDriving.imageset/car_24.pdf
vendored
Normal file
121
submodules/TelegramUI/Images.xcassets/Location/DirectionsDriving.imageset/car_24.pdf
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 3.042480 4.871582 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
1.003418 0.000000 m
|
||||
1.896973 0.000000 l
|
||||
2.468262 0.000000 2.900391 0.439453 2.900391 0.996094 c
|
||||
2.900391 2.299805 l
|
||||
4.541016 2.189941 6.979980 2.116699 8.957520 2.116699 c
|
||||
10.935059 2.116699 13.366699 2.189941 15.007324 2.299805 c
|
||||
15.007324 0.996094 l
|
||||
15.007324 0.439453 15.446777 0.000000 16.010742 0.000000 c
|
||||
16.904297 0.000000 l
|
||||
17.475586 0.000000 17.907715 0.439453 17.907715 0.996094 c
|
||||
17.907715 5.427246 l
|
||||
17.907715 6.745605 17.680664 7.485352 16.984863 8.378906 c
|
||||
16.318359 9.206543 l
|
||||
16.047363 10.524902 15.563965 11.901855 15.307617 12.451172 c
|
||||
14.890137 13.337402 14.077148 13.872070 13.044434 14.011230 c
|
||||
12.487793 14.084473 10.942383 14.128418 8.957520 14.128418 c
|
||||
6.972656 14.128418 5.419922 14.077148 4.870605 14.011230 c
|
||||
3.830566 13.879395 3.017578 13.337402 2.600098 12.451172 c
|
||||
2.343750 11.901855 1.860352 10.524902 1.589355 9.206543 c
|
||||
0.922852 8.378906 l
|
||||
0.227051 7.485352 0.000000 6.745605 0.000000 5.427246 c
|
||||
0.000000 0.996094 l
|
||||
0.000000 0.439453 0.439453 0.000000 1.003418 0.000000 c
|
||||
h
|
||||
3.339844 9.916992 m
|
||||
3.493652 10.605469 3.801270 11.520996 3.999023 11.887207 c
|
||||
4.226074 12.304688 4.482422 12.465820 4.973145 12.531738 c
|
||||
5.471191 12.604980 6.730957 12.641602 8.957520 12.641602 c
|
||||
11.176758 12.641602 12.436523 12.612305 12.941895 12.531738 c
|
||||
13.425293 12.465820 13.681641 12.304688 13.908691 11.887207 c
|
||||
14.113770 11.520996 14.406738 10.605469 14.575195 9.916992 c
|
||||
14.655762 9.572754 14.509277 9.404297 14.143066 9.418945 c
|
||||
13.073730 9.477539 11.667480 9.550781 8.957520 9.550781 c
|
||||
6.247559 9.550781 4.833984 9.477539 3.764648 9.418945 c
|
||||
3.398438 9.404297 3.259277 9.572754 3.339844 9.916992 c
|
||||
h
|
||||
3.537598 4.196777 m
|
||||
2.819824 4.196777 2.277832 4.738770 2.277832 5.456543 c
|
||||
2.277832 6.174316 2.819824 6.716309 3.537598 6.716309 c
|
||||
4.255371 6.716309 4.797363 6.174316 4.797363 5.456543 c
|
||||
4.797363 4.738770 4.255371 4.196777 3.537598 4.196777 c
|
||||
h
|
||||
14.370117 4.196777 m
|
||||
13.659668 4.196777 13.117676 4.738770 13.117676 5.456543 c
|
||||
13.117676 6.174316 13.659668 6.716309 14.370117 6.716309 c
|
||||
15.087891 6.716309 15.629883 6.174316 15.629883 5.456543 c
|
||||
15.629883 4.738770 15.087891 4.196777 14.370117 4.196777 c
|
||||
h
|
||||
6.950684 4.519043 m
|
||||
6.416016 4.519043 6.035156 4.892578 6.035156 5.427246 c
|
||||
6.035156 5.961914 6.416016 6.342773 6.950684 6.342773 c
|
||||
10.964355 6.342773 l
|
||||
11.499023 6.342773 11.872559 5.961914 11.872559 5.427246 c
|
||||
11.872559 4.892578 11.499023 4.519043 10.964355 4.519043 c
|
||||
6.950684 4.519043 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
2543
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000002633 00000 n
|
||||
0000002656 00000 n
|
||||
0000002829 00000 n
|
||||
0000002903 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
2962
|
||||
%%EOF
|
@ -1,17 +1,8 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "transit.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"filename" : "train_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
129
submodules/TelegramUI/Images.xcassets/Location/DirectionsTransit.imageset/train_24.pdf
vendored
Normal file
129
submodules/TelegramUI/Images.xcassets/Location/DirectionsTransit.imageset/train_24.pdf
vendored
Normal file
@ -0,0 +1,129 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 5.305664 2.266602 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
1.259766 0.000000 m
|
||||
1.750488 0.000000 l
|
||||
2.006836 0.000000 2.153320 0.080566 2.285156 0.300293 c
|
||||
2.695312 0.952148 l
|
||||
10.620117 0.952148 l
|
||||
11.030273 0.300293 l
|
||||
11.162109 0.080566 11.308594 0.000000 11.564941 0.000000 c
|
||||
11.997070 0.000000 l
|
||||
12.436523 0.000000 12.648926 0.439453 12.414551 0.820312 c
|
||||
10.876465 3.303223 l
|
||||
10.891113 3.303223 l
|
||||
12.465820 3.303223 13.381348 4.321289 13.381348 5.859375 c
|
||||
13.381348 11.359863 l
|
||||
13.381348 13.388672 13.176270 14.934082 12.912598 16.091309 c
|
||||
12.539062 17.819824 11.586914 18.845215 9.880371 19.064941 c
|
||||
9.294434 19.145508 8.107910 19.233398 6.687012 19.233398 c
|
||||
5.266113 19.233398 4.086914 19.145508 3.500977 19.064941 c
|
||||
1.794434 18.845215 0.834961 17.819824 0.468750 16.091309 c
|
||||
0.205078 14.934082 0.000000 13.388672 0.000000 11.359863 c
|
||||
0.000000 5.859375 l
|
||||
0.000000 4.343262 0.893555 3.325195 2.438965 3.303223 c
|
||||
0.864258 0.783691 l
|
||||
0.644531 0.432129 0.827637 0.000000 1.259766 0.000000 c
|
||||
h
|
||||
4.768066 16.530762 m
|
||||
4.438477 16.530762 4.211426 16.765137 4.211426 17.094727 c
|
||||
4.211426 17.424316 4.438477 17.651367 4.768066 17.651367 c
|
||||
8.613281 17.651367 l
|
||||
8.942871 17.651367 9.169922 17.424316 9.169922 17.094727 c
|
||||
9.169922 16.765137 8.942871 16.530762 8.613281 16.530762 c
|
||||
4.768066 16.530762 l
|
||||
h
|
||||
6.687012 8.393555 m
|
||||
4.899902 8.400879 3.398438 8.474121 2.607422 8.569336 c
|
||||
2.124023 8.627930 1.896973 8.957520 1.896973 9.418945 c
|
||||
1.896973 11.250000 l
|
||||
1.896973 12.707520 2.058105 13.967285 2.160645 14.450684 c
|
||||
2.233887 14.824219 2.475586 15.095215 2.849121 15.139160 c
|
||||
3.669434 15.241699 4.760742 15.292969 6.687012 15.307617 c
|
||||
8.605957 15.307617 9.704590 15.241699 10.517578 15.139160 c
|
||||
10.891113 15.095215 11.140137 14.824219 11.220703 14.450684 c
|
||||
11.315918 13.967285 11.484375 12.707520 11.484375 11.250000 c
|
||||
11.484375 9.418945 l
|
||||
11.484375 8.950195 11.257324 8.627930 10.766602 8.569336 c
|
||||
9.704590 8.452148 8.188477 8.393555 6.687012 8.393555 c
|
||||
h
|
||||
10.224609 5.024414 m
|
||||
9.631348 5.024414 9.177246 5.471191 9.177246 6.071777 c
|
||||
9.177246 6.665039 9.631348 7.111816 10.224609 7.111816 c
|
||||
10.817871 7.111816 11.264648 6.665039 11.264648 6.071777 c
|
||||
11.264648 5.471191 10.803223 5.024414 10.224609 5.024414 c
|
||||
h
|
||||
3.149414 5.031738 m
|
||||
2.548828 5.031738 2.116699 5.471191 2.116699 6.071777 c
|
||||
2.116699 6.665039 2.556152 7.111816 3.149414 7.111816 c
|
||||
3.750000 7.111816 4.189453 6.665039 4.189453 6.071777 c
|
||||
4.189453 5.471191 3.742676 5.031738 3.149414 5.031738 c
|
||||
h
|
||||
3.500977 2.263184 m
|
||||
4.152832 3.303223 l
|
||||
9.162598 3.303223 l
|
||||
9.814453 2.263184 l
|
||||
3.500977 2.263184 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
2613
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000002703 00000 n
|
||||
0000002726 00000 n
|
||||
0000002899 00000 n
|
||||
0000002973 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
3032
|
||||
%%EOF
|
Binary file not shown.
Before Width: | Height: | Size: 1.2 KiB |
@ -1,17 +1,8 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "walking.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"filename" : "walk_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
118
submodules/TelegramUI/Images.xcassets/Location/DirectionsWalking.imageset/walk_24.pdf
vendored
Normal file
118
submodules/TelegramUI/Images.xcassets/Location/DirectionsWalking.imageset/walk_24.pdf
vendored
Normal file
@ -0,0 +1,118 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 7.000000 3.275391 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
5.515137 14.157715 m
|
||||
6.503906 14.157715 7.302246 14.978027 7.302246 15.930176 c
|
||||
7.302246 16.918945 6.503906 17.724609 5.515137 17.724609 c
|
||||
4.526367 17.724609 3.742676 16.918945 3.742676 15.930176 c
|
||||
3.742676 14.978027 4.526367 14.157715 5.515137 14.157715 c
|
||||
h
|
||||
8.034668 0.000000 m
|
||||
8.474121 0.000000 8.869629 0.322266 8.869629 0.842285 c
|
||||
8.869629 1.032715 8.811035 1.208496 8.708496 1.413574 c
|
||||
7.104492 4.665527 l
|
||||
6.958008 4.965820 6.796875 5.222168 6.650391 5.419922 c
|
||||
5.690918 6.789551 l
|
||||
5.749512 6.965332 l
|
||||
6.020508 7.829590 6.115723 8.349609 6.174316 9.162598 c
|
||||
6.342773 11.433105 l
|
||||
6.423340 12.568359 5.764160 13.505859 4.562988 13.505859 c
|
||||
3.698730 13.505859 3.046875 13.044434 2.248535 12.253418 c
|
||||
1.025391 11.052246 l
|
||||
0.607910 10.634766 0.476074 10.305176 0.439453 9.785156 c
|
||||
0.285645 7.917480 l
|
||||
0.241699 7.397461 0.541992 7.053223 1.003418 7.023926 c
|
||||
1.457520 6.994629 1.787109 7.258301 1.838379 7.814941 c
|
||||
2.006836 9.858398 l
|
||||
2.724609 10.495605 l
|
||||
2.863770 10.612793 3.054199 10.546875 3.039551 10.349121 c
|
||||
2.907715 8.503418 l
|
||||
2.834473 7.602539 3.244629 6.958008 3.684082 6.401367 c
|
||||
5.273438 4.401855 l
|
||||
5.427246 4.211426 5.449219 4.138184 5.522461 3.991699 c
|
||||
7.221680 0.541992 l
|
||||
7.419434 0.146484 7.719727 0.000000 8.034668 0.000000 c
|
||||
h
|
||||
8.408203 8.774414 m
|
||||
10.231934 8.774414 l
|
||||
10.759277 8.774414 11.081543 9.082031 11.081543 9.543457 c
|
||||
11.088867 10.012207 10.759277 10.327148 10.224609 10.327148 c
|
||||
8.327637 10.327148 l
|
||||
7.016602 11.733398 l
|
||||
6.877441 9.667969 l
|
||||
7.426758 9.118652 l
|
||||
7.697754 8.847656 7.946777 8.774414 8.408203 8.774414 c
|
||||
h
|
||||
0.820312 0.000000 m
|
||||
1.083984 0.000000 1.325684 0.117188 1.494141 0.329590 c
|
||||
3.881836 3.178711 l
|
||||
4.130859 3.471680 4.174805 3.559570 4.270020 3.845215 c
|
||||
4.328613 4.020996 4.372559 4.189453 4.416504 4.357910 c
|
||||
3.024902 6.093750 l
|
||||
2.629395 4.255371 l
|
||||
0.336914 1.545410 l
|
||||
0.131836 1.303711 0.000000 1.098633 0.000000 0.812988 c
|
||||
0.000000 0.344238 0.366211 0.000000 0.820312 0.000000 c
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
2052
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000002142 00000 n
|
||||
0000002165 00000 n
|
||||
0000002338 00000 n
|
||||
0000002412 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
2471
|
||||
%%EOF
|
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB |
@ -4801,60 +4801,75 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
|
||||
if canManageInvitations, let inviteRequestsPending = inviteRequestsPending, inviteRequestsPending >= 0, strongSelf.inviteRequestsContext == nil {
|
||||
let inviteRequestsContext = strongSelf.context.engine.peers.peerInvitationImporters(peerId: peerId, subject: .requests(query: nil))
|
||||
strongSelf.inviteRequestsContext = inviteRequestsContext
|
||||
|
||||
strongSelf.inviteRequestsDisposable.set((inviteRequestsContext.state
|
||||
|> deliverOnMainQueue).start(next: { [weak self] requestsState in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in
|
||||
return state
|
||||
.updatedTitlePanelContext({ context in
|
||||
if requestsState.count > 0 {
|
||||
if canManageInvitations, let inviteRequestsPending = inviteRequestsPending, inviteRequestsPending >= 0 {
|
||||
if strongSelf.inviteRequestsContext == nil {
|
||||
let inviteRequestsContext = strongSelf.context.engine.peers.peerInvitationImporters(peerId: peerId, subject: .requests(query: nil))
|
||||
strongSelf.inviteRequestsContext = inviteRequestsContext
|
||||
|
||||
strongSelf.inviteRequestsDisposable.set((combineLatest(queue: Queue.mainQueue(), inviteRequestsContext.state, ApplicationSpecificNotice.dismissedInvitationRequests(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peerId))).start(next: { [weak self] requestsState, dismissedInvitationRequests in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { state in
|
||||
return state
|
||||
.updatedTitlePanelContext({ context in
|
||||
let peers: [EnginePeer] = Array(requestsState.importers.compactMap({ $0.peer.peer.flatMap({ EnginePeer($0) }) }).prefix(3))
|
||||
if !context.contains(where: {
|
||||
switch $0 {
|
||||
case .inviteRequests(peers, requestsState.count):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
var updatedContexts = context.filter { c in
|
||||
if case .inviteRequests = c {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
|
||||
var peersDismissed = false
|
||||
if let dismissedInvitationRequests = dismissedInvitationRequests, Set(peers.map({ $0.id.toInt64() })) == Set(dismissedInvitationRequests) {
|
||||
peersDismissed = true
|
||||
}
|
||||
|
||||
if requestsState.count > 0 && !peersDismissed {
|
||||
if !context.contains(where: {
|
||||
switch $0 {
|
||||
case .inviteRequests(peers, requestsState.count):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
var updatedContexts = context.filter { c in
|
||||
if case .inviteRequests = c {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
updatedContexts.append(.inviteRequests(peers, requestsState.count))
|
||||
return updatedContexts.sorted()
|
||||
} else {
|
||||
return context
|
||||
}
|
||||
updatedContexts.append(.inviteRequests(peers, requestsState.count))
|
||||
return updatedContexts.sorted()
|
||||
} else {
|
||||
return context
|
||||
}
|
||||
} else {
|
||||
if let index = context.firstIndex(where: {
|
||||
switch $0 {
|
||||
case .inviteRequests:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
if let index = context.firstIndex(where: {
|
||||
switch $0 {
|
||||
case .inviteRequests:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
var updatedContexts = context
|
||||
updatedContexts.remove(at: index)
|
||||
return updatedContexts
|
||||
} else {
|
||||
return context
|
||||
}
|
||||
}) {
|
||||
var updatedContexts = context
|
||||
updatedContexts.remove(at: index)
|
||||
return updatedContexts
|
||||
} else {
|
||||
return context
|
||||
}
|
||||
}
|
||||
})
|
||||
.updatedSlowmodeState(slowmodeState)
|
||||
})
|
||||
.updatedSlowmodeState(slowmodeState)
|
||||
}))
|
||||
} else if let inviteRequestsContext = strongSelf.inviteRequestsContext {
|
||||
let _ = (inviteRequestsContext.state
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak inviteRequestsContext] state in
|
||||
if state.count != inviteRequestsPending {
|
||||
inviteRequestsContext?.loadMore()
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
if strongSelf.presentationInterfaceState.pinnedMessageId != pinnedMessageId || strongSelf.presentationInterfaceState.pinnedMessage != pinnedMessage || strongSelf.presentationInterfaceState.peerIsBlocked != peerIsBlocked || pinnedMessageUpdated || callsDataUpdated || strongSelf.presentationInterfaceState.slowmodeState != slowmodeState || strongSelf.presentationInterfaceState.activeGroupCallInfo != activeGroupCallInfo {
|
||||
@ -5094,7 +5109,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
mappedTransition = (ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: deleteItems, insertItems: insertItems, updateItems: transition.updateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange, initialData: transition.initialData, keyboardButtonsMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData, scrolledToIndex: transition.scrolledToIndex, scrolledToSomeIndex: transition.scrolledToSomeIndex, peerType: transition.peerType, networkType: transition.networkType, animateIn: false, reason: transition.reason, flashIndicators: transition.flashIndicators), updateSizeAndInsets)
|
||||
}, updateExtraNavigationBarBackgroundHeight: { value in
|
||||
}, updateExtraNavigationBarBackgroundHeight: { value, _ in
|
||||
strongSelf.additionalNavigationBarBackgroundHeight = value
|
||||
})
|
||||
|
||||
@ -8247,9 +8262,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
|
||||
var navigationBarTransition = transition
|
||||
self.chatDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition, listViewTransaction: { updateSizeAndInsets, additionalScrollDistance, scrollToTop, completion in
|
||||
self.chatDisplayNode.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: additionalScrollDistance, scrollToTop: scrollToTop, completion: completion)
|
||||
}, updateExtraNavigationBarBackgroundHeight: { value in
|
||||
}, updateExtraNavigationBarBackgroundHeight: { value, extraNavigationTransition in
|
||||
navigationBarTransition = extraNavigationTransition
|
||||
self.additionalNavigationBarBackgroundHeight = value
|
||||
})
|
||||
|
||||
@ -8269,7 +8286,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.suspendNavigationBarLayout = false
|
||||
if let suspendedNavigationBarLayout = self.suspendedNavigationBarLayout {
|
||||
self.suspendedNavigationBarLayout = suspendedNavigationBarLayout
|
||||
self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: transition)
|
||||
self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: navigationBarTransition)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -649,7 +649,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.inputMediaNode?.simulateUpdateLayout(isVisible: isInFocus)
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition protoTransition: ContainedViewLayoutTransition, listViewTransaction: (ListViewUpdateSizeAndInsets, CGFloat, Bool, @escaping () -> Void) -> Void, updateExtraNavigationBarBackgroundHeight: (CGFloat) -> Void) {
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition protoTransition: ContainedViewLayoutTransition, listViewTransaction: (ListViewUpdateSizeAndInsets, CGFloat, Bool, @escaping () -> Void) -> Void, updateExtraNavigationBarBackgroundHeight: (CGFloat, ContainedViewLayoutTransition) -> Void) {
|
||||
let transition: ContainedViewLayoutTransition
|
||||
if let _ = self.scheduledAnimateInAsOverlayFromNode {
|
||||
transition = .immediate
|
||||
@ -797,17 +797,26 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
var immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance = false
|
||||
var titleAccessoryPanelHeight: CGFloat?
|
||||
var titleAccessoryPanelBackgroundHeight: CGFloat?
|
||||
var extraTransition = transition
|
||||
if let titleAccessoryPanelNode = titlePanelForChatPresentationInterfaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.titleAccessoryPanelNode, interfaceInteraction: self.interfaceInteraction) {
|
||||
if self.titleAccessoryPanelNode != titleAccessoryPanelNode {
|
||||
dismissedTitleAccessoryPanelNode = self.titleAccessoryPanelNode
|
||||
self.titleAccessoryPanelNode = titleAccessoryPanelNode
|
||||
immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance = true
|
||||
self.titleAccessoryPanelContainer.addSubnode(titleAccessoryPanelNode)
|
||||
|
||||
titleAccessoryPanelNode.clipsToBounds = true
|
||||
extraTransition = .animated(duration: 0.2, curve: .easeInOut)
|
||||
}
|
||||
|
||||
let layoutResult = titleAccessoryPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState)
|
||||
titleAccessoryPanelHeight = layoutResult.insetHeight
|
||||
titleAccessoryPanelBackgroundHeight = layoutResult.backgroundHeight
|
||||
if immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance {
|
||||
titleAccessoryPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
titleAccessoryPanelNode.subnodeTransform = CATransform3DMakeTranslation(0.0, -layoutResult.backgroundHeight, 0.0)
|
||||
extraTransition.updateSublayerTransformOffset(layer: titleAccessoryPanelNode.layer, offset: CGPoint())
|
||||
}
|
||||
} else if let titleAccessoryPanelNode = self.titleAccessoryPanelNode {
|
||||
dismissedTitleAccessoryPanelNode = titleAccessoryPanelNode
|
||||
self.titleAccessoryPanelNode = nil
|
||||
@ -1009,7 +1018,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
insets.top += panelHeight
|
||||
}
|
||||
|
||||
updateExtraNavigationBarBackgroundHeight(titleAccessoryPanelBackgroundHeight ?? 0.0)
|
||||
updateExtraNavigationBarBackgroundHeight(titleAccessoryPanelBackgroundHeight ?? 0.0, extraTransition)
|
||||
|
||||
var importStatusPanelFrame: CGRect?
|
||||
if let _ = self.chatImportStatusPanel, let panelHeight = importStatusPanelHeight {
|
||||
@ -2241,7 +2250,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
if let (layout, navigationHeight) = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: transition, listViewTransaction: { updateSizeAndInsets, additionalScrollDistance, scrollToTop, completion in
|
||||
self.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: additionalScrollDistance, scrollToTop: scrollToTop, completion: completion)
|
||||
}, updateExtraNavigationBarBackgroundHeight: { _ in
|
||||
}, updateExtraNavigationBarBackgroundHeight: { _, _ in
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -120,14 +120,16 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
|
||||
return panel
|
||||
}
|
||||
case let .inviteRequests(peers, count):
|
||||
if let currentPanel = currentPanel as? ChatInviteRequestsTitlePanelNode {
|
||||
currentPanel.update(peers: peers, count: count)
|
||||
return currentPanel
|
||||
} else {
|
||||
let panel = ChatInviteRequestsTitlePanelNode(context: context)
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
panel.update(peers: peers, count: count)
|
||||
return panel
|
||||
if let peerId = chatPresentationInterfaceState.renderedPeer?.peerId {
|
||||
if let currentPanel = currentPanel as? ChatInviteRequestsTitlePanelNode {
|
||||
currentPanel.update(peerId: peerId, peers: peers, count: count)
|
||||
return currentPanel
|
||||
} else {
|
||||
let panel = ChatInviteRequestsTitlePanelNode(context: context)
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
panel.update(peerId: peerId, peers: peers, count: count)
|
||||
return panel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import LocalizedPeerData
|
||||
import TelegramStringFormatting
|
||||
import TelegramNotices
|
||||
import AnimatedAvatarSetNode
|
||||
import AccountContext
|
||||
|
||||
@ -119,6 +120,10 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
|
||||
private var peerId: PeerId?
|
||||
private var peers: [EnginePeer] = []
|
||||
private var count: Int32 = 0
|
||||
|
||||
init(context: AccountContext) {
|
||||
self.context = context
|
||||
|
||||
@ -142,9 +147,11 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
self.addSubnode(self.avatarsNode)
|
||||
}
|
||||
|
||||
private var requestsCount: Int32 = 0
|
||||
func update(peers: [EnginePeer], count: Int32) {
|
||||
self.requestsCount = count
|
||||
|
||||
func update(peerId: PeerId, peers: [EnginePeer], count: Int32) {
|
||||
self.peerId = peerId
|
||||
self.peers = peers
|
||||
self.count = count
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.avatarsContent = self.avatarsContext.update(peers: peers, animated: false)
|
||||
@ -176,7 +183,7 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
self.button = view
|
||||
}
|
||||
|
||||
self.button?.setTitle(interfaceState.strings.Conversation_RequestsToJoin(self.requestsCount), for: [])
|
||||
self.button?.setTitle(interfaceState.strings.Conversation_RequestsToJoin(self.count), for: [])
|
||||
|
||||
let maxInset = max(contentRightInset, leftInset)
|
||||
let buttonWidth = floor(width - maxInset * 2.0)
|
||||
@ -198,7 +205,12 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
}
|
||||
|
||||
@objc func closePressed() {
|
||||
// self.interfaceInteraction?.dismissReportPeer()
|
||||
guard let peerId = self.peerId else {
|
||||
return
|
||||
}
|
||||
|
||||
let ids = peers.map { $0.id.toInt64() }
|
||||
let _ = ApplicationSpecificNotice.setDismissedInvitationRequests(accountManager: context.sharedContext.accountManager, peerId: peerId, values: ids).start()
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
|
@ -283,6 +283,10 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
} else if let type = webpage.type {
|
||||
switch type {
|
||||
case "telegram_channel_request":
|
||||
actionTitle = item.presentationData.strings.Conversation_RequestToJoinChannel
|
||||
case "telegram_chat_request", "telegram_megagroup_request":
|
||||
actionTitle = item.presentationData.strings.Conversation_RequestToJoinGroup
|
||||
case "telegram_channel":
|
||||
actionTitle = item.presentationData.strings.Conversation_ViewChannel
|
||||
case "telegram_chat", "telegram_megagroup":
|
||||
|
@ -153,7 +153,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
strongSelf.presentController(StickerPackScreen(context: strongSelf.context, mainStickerPack: new, stickerPacks: [new], parentNavigationController: strongSelf.getNavigationController()), .window(.root), nil)
|
||||
return true
|
||||
}
|
||||
case let .editExportedInvitation(_, invite), let .revokeExportedInvitation(invite), let .deleteExportedInvitation(invite), let .participantJoinedViaInvite(invite):
|
||||
case let .editExportedInvitation(_, invite), let .revokeExportedInvitation(invite), let .deleteExportedInvitation(invite), let .participantJoinedViaInvite(invite), let .participantJoinByRequest(invite, _):
|
||||
if !invite.link.hasSuffix("...") {
|
||||
if invite.isPermanent {
|
||||
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
|
||||
@ -248,7 +248,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}
|
||||
return false
|
||||
}, openPeer: { [weak self] peerId, _, message in
|
||||
if let peerId = peerId {
|
||||
if let peerId = peerId, peerId != context.account.peerId {
|
||||
self?.openPeer(peerId: peerId, peer: message?.peers[peerId])
|
||||
}
|
||||
}, openPeerMention: { [weak self] name in
|
||||
|
@ -1404,6 +1404,39 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
|
||||
let action = TelegramMediaActionType.customText(text: text, entities: entities)
|
||||
|
||||
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
|
||||
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes()))
|
||||
case let .participantJoinByRequest(invite, approvedBy):
|
||||
var peers = SimpleDictionary<PeerId, Peer>()
|
||||
var author: Peer?
|
||||
var approver: Peer?
|
||||
if let peer = self.entry.peers[self.entry.event.peerId] {
|
||||
author = peer
|
||||
peers[peer.id] = peer
|
||||
}
|
||||
if let peer = self.entry.peers[approvedBy] {
|
||||
approver = peer
|
||||
peers[approvedBy] = approver
|
||||
}
|
||||
|
||||
var text: String = ""
|
||||
var entities: [MessageTextEntity] = []
|
||||
|
||||
let rawText: PresentationStrings.FormattedString = self.presentationData.strings.Channel_AdminLog_JoinedViaRequest(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", invite.link.replacingOccurrences(of: "https://", with: ""), approver.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "")
|
||||
|
||||
appendAttributedText(text: rawText, generateEntities: { index in
|
||||
if index == 0, let author = author {
|
||||
return [.TextMention(peerId: author.id)]
|
||||
} else if index == 1 {
|
||||
return [.Bold]
|
||||
} else if index == 2, let approver = approver {
|
||||
return [.TextMention(peerId: approver.id)]
|
||||
}
|
||||
return []
|
||||
}, to: &text, entities: &entities)
|
||||
|
||||
let action = TelegramMediaActionType.customText(text: text, entities: entities)
|
||||
|
||||
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
|
||||
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes()))
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user