mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
4c27869b04
@ -5940,6 +5940,7 @@ Sorry for the inconvenience.";
|
||||
"VoiceChat.EndVoiceChat" = "End Voice Chat";
|
||||
|
||||
"VoiceChat.CopyInviteLink" = "Copy Invite Link";
|
||||
"VoiceChat.InviteLinkCopiedText" = "Invite link copied to clipboard";
|
||||
|
||||
"VoiceChat.UnmutePeer" = "Allow to Speak";
|
||||
"VoiceChat.MutePeer" = "Mute";
|
||||
@ -5956,6 +5957,9 @@ Sorry for the inconvenience.";
|
||||
"Notification.VoiceChatInvitation" = "%1$@ invited %2$@ to the voice chat";
|
||||
"Notification.VoiceChatInvitationForYou" = "%1$@ invited you to the voice chat";
|
||||
|
||||
"VoiceChat.InvitedPeerText" = "You invited %@ to the voice chat";
|
||||
"VoiceChat.RemovedPeerText" = "You removed %@ from this group";
|
||||
|
||||
"Notification.VoiceChatStarted" = "Voice chat started";
|
||||
"Notification.VoiceChatEnded" = "Voice chat ended (%@)";
|
||||
|
||||
|
@ -286,6 +286,7 @@ public protocol PresentationGroupCall: class {
|
||||
func updateMuteState(peerId: PeerId, isMuted: Bool)
|
||||
|
||||
func invitePeer(_ peerId: PeerId)
|
||||
func removedPeer(_ peerId: PeerId)
|
||||
var invitedPeers: Signal<[PeerId], NoError> { get }
|
||||
|
||||
var sourcePanel: ASDisplayNode? { get set }
|
||||
|
@ -58,6 +58,7 @@ private let avatarFont = avatarPlaceholderFont(size: 12.0)
|
||||
|
||||
private final class ContentNode: ASDisplayNode {
|
||||
private var audioLevelView: VoiceBlobView?
|
||||
private var audioLevelBlobOverlay: UIImageView?
|
||||
private let unclippedNode: ASImageNode
|
||||
private let clippedNode: ASImageNode
|
||||
|
||||
@ -136,7 +137,7 @@ private final class ContentNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
func updateAudioLevel(color: UIColor, value: Float) {
|
||||
func updateAudioLevel(color: UIColor, backgroundColor: UIColor, value: Float) {
|
||||
if self.audioLevelView == nil, value > 0.0 {
|
||||
let blobFrame = self.unclippedNode.bounds.insetBy(dx: -8.0, dy: -8.0)
|
||||
|
||||
@ -248,12 +249,12 @@ public final class AnimatedAvatarSetNode: ASDisplayNode {
|
||||
return CGSize(width: contentWidth, height: contentHeight)
|
||||
}
|
||||
|
||||
public func updateAudioLevels(color: UIColor, levels: [PeerId: Float]) {
|
||||
public func updateAudioLevels(color: UIColor, backgroundColor: UIColor, levels: [PeerId: Float]) {
|
||||
for (key, itemNode) in self.contentNodes {
|
||||
if let value = levels[key.peerId] {
|
||||
itemNode.updateAudioLevel(color: color, value: value)
|
||||
itemNode.updateAudioLevel(color: color, backgroundColor: backgroundColor, value: value)
|
||||
} else {
|
||||
itemNode.updateAudioLevel(color: color, value: 0.0)
|
||||
itemNode.updateAudioLevel(color: color, backgroundColor: backgroundColor, value: 0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,16 +14,16 @@ public enum ContactListActionItemHighlight {
|
||||
case alpha
|
||||
}
|
||||
|
||||
class ContactListActionItem: ListViewItem, ListViewItemWithHeader {
|
||||
public class ContactListActionItem: ListViewItem, ListViewItemWithHeader {
|
||||
let presentationData: ItemListPresentationData
|
||||
let title: String
|
||||
let icon: ContactListActionItemIcon
|
||||
let highlight: ContactListActionItemHighlight
|
||||
let clearHighlightAutomatically: Bool
|
||||
let action: () -> Void
|
||||
let header: ListViewItemHeader?
|
||||
public let header: ListViewItemHeader?
|
||||
|
||||
init(presentationData: ItemListPresentationData, title: String, icon: ContactListActionItemIcon, highlight: ContactListActionItemHighlight = .cell, clearHighlightAutomatically: Bool = true, header: ListViewItemHeader?, action: @escaping () -> Void) {
|
||||
public init(presentationData: ItemListPresentationData, title: String, icon: ContactListActionItemIcon, highlight: ContactListActionItemHighlight = .cell, clearHighlightAutomatically: Bool = true, header: ListViewItemHeader?, action: @escaping () -> Void) {
|
||||
self.presentationData = presentationData
|
||||
self.title = title
|
||||
self.icon = icon
|
||||
@ -33,7 +33,7 @@ class ContactListActionItem: ListViewItem, ListViewItemWithHeader {
|
||||
self.action = action
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
async {
|
||||
let node = ContactListActionItemNode()
|
||||
let (_, last, firstWithHeader) = ContactListActionItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||
@ -50,7 +50,7 @@ class ContactListActionItem: ListViewItem, ListViewItemWithHeader {
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ContactListActionItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
@ -68,9 +68,9 @@ class ContactListActionItem: ListViewItem, ListViewItemWithHeader {
|
||||
}
|
||||
}
|
||||
|
||||
var selectable: Bool = true
|
||||
public var selectable: Bool = true
|
||||
|
||||
func selected(listView: ListView){
|
||||
public func selected(listView: ListView){
|
||||
self.action()
|
||||
if self.clearHighlightAutomatically {
|
||||
listView.clearHighlightAnimated(true)
|
||||
|
@ -438,6 +438,12 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
modalContainer = NavigationModalContainer(theme: self.theme, isFlat: navigationLayout.modal[i].isFlat, controllerRemoved: { [weak self] controller in
|
||||
self?.controllerRemoved(controller)
|
||||
})
|
||||
modalContainer.container.statusBarStyleUpdated = { [weak self] transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateContainersNonReentrant(transition: transition)
|
||||
}
|
||||
self.modalContainers.append(modalContainer)
|
||||
if !modalContainer.isReady {
|
||||
modalContainer.isReadyUpdated = { [weak self, weak modalContainer] in
|
||||
@ -586,6 +592,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
|
||||
var previousModalContainer: NavigationModalContainer?
|
||||
var topVisibleModalContainerWithStatusBar: NavigationModalContainer?
|
||||
var visibleModalCount = 0
|
||||
var topModalIsFlat = false
|
||||
let isLandscape = layout.orientation == .landscape
|
||||
@ -617,7 +624,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
|
||||
containerTransition.updateFrame(node: modalContainer, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
modalContainer.update(layout: layout, controllers: navigationLayout.modal[i].controllers, coveredByModalTransition: effectiveModalTransition, transition: containerTransition)
|
||||
modalContainer.update(layout: modalContainer.isFlat ? overlayLayout : layout, controllers: navigationLayout.modal[i].controllers, coveredByModalTransition: effectiveModalTransition, transition: containerTransition)
|
||||
|
||||
if modalContainer.supernode == nil && modalContainer.isReady {
|
||||
if let previousModalContainer = previousModalContainer {
|
||||
@ -653,6 +660,23 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
modalContainer.keyboardViewManager = nil
|
||||
modalContainer.canHaveKeyboardFocus = true
|
||||
}
|
||||
|
||||
if modalContainer.isFlat {
|
||||
let controllerStatusBarStyle = modalContainer.container.statusBarStyle
|
||||
switch controllerStatusBarStyle {
|
||||
case .Black, .White, .Hide:
|
||||
if topVisibleModalContainerWithStatusBar == nil {
|
||||
topVisibleModalContainerWithStatusBar = modalContainer
|
||||
}
|
||||
if case .Hide = controllerStatusBarStyle {
|
||||
statusBarHidden = true
|
||||
} else {
|
||||
statusBarHidden = false
|
||||
}
|
||||
case .Ignore:
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
modalContainer.keyboardViewManager = nil
|
||||
modalContainer.canHaveKeyboardFocus = false
|
||||
@ -921,6 +945,10 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
statusBarStyle = topVisibleOverlayContainerWithStatusBar.controller.statusBar.statusBarStyle
|
||||
}
|
||||
|
||||
if let topVisibleModalContainerWithStatusBar = topVisibleModalContainerWithStatusBar {
|
||||
statusBarStyle = topVisibleModalContainerWithStatusBar.container.statusBarStyle
|
||||
}
|
||||
|
||||
if self.currentStatusBarExternalHidden {
|
||||
statusBarHidden = true
|
||||
}
|
||||
|
@ -372,7 +372,14 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
|
||||
topInset += statusBarHeight
|
||||
}
|
||||
|
||||
containerLayout = ContainerViewLayout(size: CGSize(width: layout.size.width, height: layout.size.height - topInset), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: layout.intrinsicInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.intrinsicInsets.right), safeInsets: UIEdgeInsets(top: 0.0, left: layout.safeInsets.left, bottom: layout.safeInsets.bottom, right: layout.safeInsets.right), statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver)
|
||||
let effectiveStatusBarHeight: CGFloat?
|
||||
if self.isFlat {
|
||||
effectiveStatusBarHeight = layout.statusBarHeight
|
||||
} else {
|
||||
effectiveStatusBarHeight = nil
|
||||
}
|
||||
|
||||
containerLayout = ContainerViewLayout(size: CGSize(width: layout.size.width, height: layout.size.height - topInset), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: layout.intrinsicInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.intrinsicInsets.right), safeInsets: UIEdgeInsets(top: 0.0, left: layout.safeInsets.left, bottom: layout.safeInsets.bottom, right: layout.safeInsets.right), statusBarHeight: effectiveStatusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver)
|
||||
let unscaledFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset - coveredByModalTransition * 10.0), size: containerLayout.size)
|
||||
let maxScale: CGFloat = (containerLayout.size.width - 16.0 * 2.0) / containerLayout.size.width
|
||||
containerScale = 1.0 * (1.0 - coveredByModalTransition) + maxScale * coveredByModalTransition
|
||||
@ -410,7 +417,15 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
|
||||
if let inputHeightValue = layout.inputHeight {
|
||||
inputHeight = max(0.0, inputHeightValue - (layout.size.height - containerFrame.maxY))
|
||||
}
|
||||
containerLayout = ContainerViewLayout(size: containerSize, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver)
|
||||
|
||||
let effectiveStatusBarHeight: CGFloat?
|
||||
if self.isFlat {
|
||||
effectiveStatusBarHeight = layout.statusBarHeight
|
||||
} else {
|
||||
effectiveStatusBarHeight = nil
|
||||
}
|
||||
|
||||
containerLayout = ContainerViewLayout(size: containerSize, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: effectiveStatusBarHeight, inputHeight: inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver)
|
||||
}
|
||||
transition.updateFrameAsPositionAndBounds(node: self.container, frame: containerFrame.offsetBy(dx: 0.0, dy: layout.size.height))
|
||||
transition.updateTransformScale(node: self.container, scale: containerScale)
|
||||
|
@ -30,6 +30,8 @@ public final class ChannelMembersSearchController: ViewController {
|
||||
private let filters: [ChannelMembersSearchFilter]
|
||||
private let openPeer: (Peer, RenderedChannelParticipant?) -> Void
|
||||
|
||||
public var copyInviteLink: (() -> Void)?
|
||||
|
||||
private let forceTheme: PresentationTheme?
|
||||
private var presentationData: PresentationData
|
||||
|
||||
@ -93,6 +95,9 @@ public final class ChannelMembersSearchController: ViewController {
|
||||
self.controllerNode.requestOpenPeerFromSearch = { [weak self] peer, participant in
|
||||
self?.openPeer(peer, participant)
|
||||
}
|
||||
self.controllerNode.requestCopyInviteLink = { [weak self] in
|
||||
self?.copyInviteLink?()
|
||||
}
|
||||
self.controllerNode.pushController = { [weak self] c in
|
||||
(self?.navigationController as? NavigationController)?.pushViewController(c)
|
||||
}
|
||||
|
@ -15,63 +15,98 @@ import SearchBarNode
|
||||
import ContactsPeerItem
|
||||
import SearchUI
|
||||
import ItemListUI
|
||||
import ContactListUI
|
||||
import ChatListSearchItemHeader
|
||||
|
||||
private final class ChannelMembersSearchInteraction {
|
||||
let openPeer: (Peer, RenderedChannelParticipant?) -> Void
|
||||
let copyInviteLink: () -> Void
|
||||
|
||||
init(openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void) {
|
||||
init(
|
||||
openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void,
|
||||
copyInviteLink: @escaping () -> Void
|
||||
) {
|
||||
self.openPeer = openPeer
|
||||
self.copyInviteLink = copyInviteLink
|
||||
}
|
||||
}
|
||||
|
||||
private enum ChannelMembersSearchEntryId: Hashable {
|
||||
case copyInviteLink
|
||||
case peer(PeerId)
|
||||
}
|
||||
|
||||
private enum ChannelMembersSearchEntry: Comparable, Identifiable {
|
||||
case copyInviteLink
|
||||
case peer(Int, RenderedChannelParticipant, ContactsPeerItemEditing, String?, Bool)
|
||||
|
||||
var stableId: ChannelMembersSearchEntryId {
|
||||
switch self {
|
||||
case let .peer(peer):
|
||||
return .peer(peer.1.peer.id)
|
||||
case .copyInviteLink:
|
||||
return .copyInviteLink
|
||||
case let .peer(_, participant, _, _, _):
|
||||
return .peer(participant.peer.id)
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: ChannelMembersSearchEntry, rhs: ChannelMembersSearchEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .peer(lhsIndex, lhsParticipant, lhsEditing, lhsLabel, lhsEnabled):
|
||||
if case .peer(lhsIndex, lhsParticipant, lhsEditing, lhsLabel, lhsEnabled) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .copyInviteLink:
|
||||
if case .copyInviteLink = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peer(lhsIndex, lhsParticipant, lhsEditing, lhsLabel, lhsEnabled):
|
||||
if case .peer(lhsIndex, lhsParticipant, lhsEditing, lhsLabel, lhsEnabled) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: ChannelMembersSearchEntry, rhs: ChannelMembersSearchEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .peer(lhsPeer):
|
||||
if case let .peer(rhsPeer) = rhs {
|
||||
return lhsPeer.0 < rhsPeer.0
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .copyInviteLink:
|
||||
if case .copyInviteLink = rhs {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
case let .peer(lhsIndex, _, _, _, _):
|
||||
if case .copyInviteLink = rhs {
|
||||
return false
|
||||
} else if case let .peer(rhsIndex, _, _, _, _) = rhs {
|
||||
return lhsIndex < rhsIndex
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func item(context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, interaction: ChannelMembersSearchInteraction) -> ListViewItem {
|
||||
switch self {
|
||||
case let .peer(_, participant, editing, label, enabled):
|
||||
let status: ContactsPeerItemStatus
|
||||
if let label = label {
|
||||
status = .custom(string: label, multiline: false)
|
||||
} else {
|
||||
status = .none
|
||||
}
|
||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: participant.peer, chatPeer: nil), status: status, enabled: enabled, selection: .none, editing: editing, index: nil, header: nil, action: { _ in
|
||||
interaction.openPeer(participant.peer, participant)
|
||||
})
|
||||
case .copyInviteLink:
|
||||
let icon: ContactListActionItemIcon
|
||||
if let iconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: presentationData.theme.list.itemAccentColor) {
|
||||
icon = .generic(iconImage)
|
||||
} else {
|
||||
icon = .none
|
||||
}
|
||||
return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: presentationData.strings.VoiceChat_CopyInviteLink, icon: icon, clearHighlightAutomatically: true, header: nil, action: {
|
||||
interaction.copyInviteLink()
|
||||
})
|
||||
case let .peer(_, participant, editing, label, enabled):
|
||||
let status: ContactsPeerItemStatus
|
||||
if let label = label {
|
||||
status = .custom(string: label, multiline: false)
|
||||
} else {
|
||||
status = .none
|
||||
}
|
||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: participant.peer, chatPeer: nil), status: status, enabled: enabled, selection: .none, editing: editing, index: nil, header: ChatListSearchItemHeader(type: .members, theme: presentationData.theme, strings: presentationData.strings), action: { _ in
|
||||
interaction.openPeer(participant.peer, participant)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -110,6 +145,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
||||
var requestActivateSearch: (() -> Void)?
|
||||
var requestDeactivateSearch: (() -> Void)?
|
||||
var requestOpenPeerFromSearch: ((Peer, RenderedChannelParticipant?) -> Void)?
|
||||
var requestCopyInviteLink: (() -> Void)?
|
||||
var pushController: ((ViewController) -> Void)?
|
||||
|
||||
private let forceTheme: PresentationTheme?
|
||||
@ -140,10 +176,16 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
||||
|
||||
self.addSubnode(self.listNode)
|
||||
|
||||
let interaction = ChannelMembersSearchInteraction(openPeer: { [weak self] peer, participant in
|
||||
self?.requestOpenPeerFromSearch?(peer, participant)
|
||||
self?.listNode.clearHighlightAnimated(true)
|
||||
})
|
||||
let interaction = ChannelMembersSearchInteraction(
|
||||
openPeer: { [weak self] peer, participant in
|
||||
self?.requestOpenPeerFromSearch?(peer, participant)
|
||||
self?.listNode.clearHighlightAnimated(true)
|
||||
},
|
||||
copyInviteLink: { [weak self] in
|
||||
self?.requestCopyInviteLink?()
|
||||
self?.listNode.clearHighlightAnimated(true)
|
||||
}
|
||||
)
|
||||
|
||||
let previousEntries = Atomic<[ChannelMembersSearchEntry]?>(value: nil)
|
||||
|
||||
@ -274,6 +316,16 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
||||
}
|
||||
var entries: [ChannelMembersSearchEntry] = []
|
||||
|
||||
if case .inviteToCall = mode, !filters.contains(where: { filter in
|
||||
if case .excludeNonMembers = filter {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
entries.append(.copyInviteLink)
|
||||
}
|
||||
|
||||
var index = 0
|
||||
for participant in state.list {
|
||||
if participant.peer.isDeleted {
|
||||
|
@ -26,155 +26,6 @@ public enum LocationBroadcastPanelSource {
|
||||
case peer(PeerId)
|
||||
}
|
||||
|
||||
final class AccountGroupCallContextImpl: AccountGroupCallContext {
|
||||
final class Proxy {
|
||||
let context: AccountGroupCallContextImpl
|
||||
let removed: () -> Void
|
||||
|
||||
init(context: AccountGroupCallContextImpl, removed: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.removed = removed
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.removed()
|
||||
}
|
||||
|
||||
func keep() {
|
||||
}
|
||||
}
|
||||
|
||||
var disposable: Disposable?
|
||||
var participantsContext: GroupCallParticipantsContext?
|
||||
|
||||
private let panelDataPromise = Promise<GroupCallPanelData>()
|
||||
var panelData: Signal<GroupCallPanelData, NoError> {
|
||||
return self.panelDataPromise.get()
|
||||
}
|
||||
|
||||
init(account: Account, peerId: PeerId, call: CachedChannelData.ActiveCall) {
|
||||
self.panelDataPromise.set(.single(GroupCallPanelData(
|
||||
peerId: peerId,
|
||||
info: GroupCallInfo(
|
||||
id: call.id,
|
||||
accessHash: call.accessHash,
|
||||
participantCount: 0,
|
||||
clientParams: nil
|
||||
),
|
||||
topParticipants: [],
|
||||
participantCount: 0,
|
||||
activeSpeakers: Set(),
|
||||
groupCall: nil
|
||||
)))
|
||||
|
||||
self.disposable = (getGroupCallParticipants(account: account, callId: call.id, accessHash: call.accessHash, offset: "", limit: 100)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<GroupCallParticipantsContext.State?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
guard let strongSelf = self, let state = state else {
|
||||
return
|
||||
}
|
||||
let context = GroupCallParticipantsContext(
|
||||
account: account,
|
||||
peerId: peerId,
|
||||
id: call.id,
|
||||
accessHash: call.accessHash,
|
||||
state: state
|
||||
)
|
||||
strongSelf.participantsContext = context
|
||||
strongSelf.panelDataPromise.set(combineLatest(queue: .mainQueue(),
|
||||
context.state,
|
||||
context.activeSpeakers
|
||||
)
|
||||
|> map { state, activeSpeakers -> GroupCallPanelData in
|
||||
var topParticipants: [GroupCallParticipantsContext.Participant] = []
|
||||
for participant in state.participants {
|
||||
if topParticipants.count >= 3 {
|
||||
break
|
||||
}
|
||||
topParticipants.append(participant)
|
||||
}
|
||||
return GroupCallPanelData(
|
||||
peerId: peerId,
|
||||
info: GroupCallInfo(id: call.id, accessHash: call.accessHash, participantCount: state.totalCount, clientParams: nil),
|
||||
topParticipants: topParticipants,
|
||||
participantCount: state.totalCount,
|
||||
activeSpeakers: activeSpeakers,
|
||||
groupCall: nil
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable?.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
public final class AccountGroupCallContextCacheImpl: AccountGroupCallContextCache {
|
||||
class Impl {
|
||||
private class Record {
|
||||
let context: AccountGroupCallContextImpl
|
||||
let subscribers = Bag<Void>()
|
||||
var removeTimer: SwiftSignalKit.Timer?
|
||||
|
||||
init(context: AccountGroupCallContextImpl) {
|
||||
self.context = context
|
||||
}
|
||||
}
|
||||
|
||||
private let queue: Queue
|
||||
private var contexts: [Int64: Record] = [:]
|
||||
|
||||
init(queue: Queue) {
|
||||
self.queue = queue
|
||||
}
|
||||
|
||||
func get(account: Account, peerId: PeerId, call: CachedChannelData.ActiveCall) -> AccountGroupCallContextImpl.Proxy {
|
||||
let result: Record
|
||||
if let current = self.contexts[call.id] {
|
||||
result = current
|
||||
} else {
|
||||
let context = AccountGroupCallContextImpl(account: account, peerId: peerId, call: call)
|
||||
result = Record(context: context)
|
||||
self.contexts[call.id] = result
|
||||
}
|
||||
|
||||
let index = result.subscribers.add(Void())
|
||||
result.removeTimer?.invalidate()
|
||||
result.removeTimer = nil
|
||||
return AccountGroupCallContextImpl.Proxy(context: result.context, removed: { [weak self, weak result] in
|
||||
Queue.mainQueue().async {
|
||||
if let strongResult = result, let strongSelf = self, strongSelf.contexts[call.id] === strongResult {
|
||||
strongResult.subscribers.remove(index)
|
||||
if strongResult.subscribers.isEmpty {
|
||||
let removeTimer = SwiftSignalKit.Timer(timeout: 30, repeat: false, completion: {
|
||||
if let result = result, let strongSelf = self, strongSelf.contexts[call.id] === result, result.subscribers.isEmpty {
|
||||
strongSelf.contexts.removeValue(forKey: call.id)
|
||||
}
|
||||
}, queue: .mainQueue())
|
||||
strongResult.removeTimer = removeTimer
|
||||
removeTimer.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let queue: Queue = .mainQueue()
|
||||
let impl: QueueLocalObject<Impl>
|
||||
|
||||
public init() {
|
||||
let queue = self.queue
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
return Impl(queue: queue)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func presentLiveLocationController(context: AccountContext, peerId: PeerId, controller: ViewController) {
|
||||
let presentImpl: (Message?) -> Void = { [weak controller] message in
|
||||
if let message = message, let strongController = controller {
|
||||
|
@ -426,7 +426,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
if self.audioLevelGenerators.isEmpty {
|
||||
self.audioLevelGeneratorTimer?.invalidate()
|
||||
self.audioLevelGeneratorTimer = nil
|
||||
self.avatarsNode.updateAudioLevels(color: self.theme.chat.inputPanel.actionControlFillColor, levels: [:])
|
||||
self.avatarsNode.updateAudioLevels(color: self.theme.chat.inputPanel.actionControlFillColor, backgroundColor: self.theme.chat.inputPanel.actionControlFillColor, levels: [:])
|
||||
} else if self.audioLevelGeneratorTimer == nil {
|
||||
let audioLevelGeneratorTimer = SwiftSignalKit.Timer(timeout: 1.0 / 30.0, repeat: true, completion: { [weak self] in
|
||||
self?.sampleAudioGenerators()
|
||||
@ -442,7 +442,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
for (peerId, generator) in self.audioLevelGenerators {
|
||||
levels[peerId] = generator.get()
|
||||
}
|
||||
self.avatarsNode.updateAudioLevels(color: self.theme.chat.inputPanel.actionControlFillColor, levels: levels)
|
||||
self.avatarsNode.updateAudioLevels(color: self.theme.chat.inputPanel.actionControlFillColor, backgroundColor: self.theme.chat.inputPanel.actionControlFillColor, levels: levels)
|
||||
}
|
||||
|
||||
public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
|
@ -15,6 +15,155 @@ import DeviceAccess
|
||||
import UniversalMediaPlayer
|
||||
import AccountContext
|
||||
|
||||
public final class AccountGroupCallContextImpl: AccountGroupCallContext {
|
||||
public final class Proxy {
|
||||
public let context: AccountGroupCallContextImpl
|
||||
let removed: () -> Void
|
||||
|
||||
public init(context: AccountGroupCallContextImpl, removed: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.removed = removed
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.removed()
|
||||
}
|
||||
|
||||
public func keep() {
|
||||
}
|
||||
}
|
||||
|
||||
var disposable: Disposable?
|
||||
public var participantsContext: GroupCallParticipantsContext?
|
||||
|
||||
private let panelDataPromise = Promise<GroupCallPanelData>()
|
||||
public var panelData: Signal<GroupCallPanelData, NoError> {
|
||||
return self.panelDataPromise.get()
|
||||
}
|
||||
|
||||
public init(account: Account, peerId: PeerId, call: CachedChannelData.ActiveCall) {
|
||||
self.panelDataPromise.set(.single(GroupCallPanelData(
|
||||
peerId: peerId,
|
||||
info: GroupCallInfo(
|
||||
id: call.id,
|
||||
accessHash: call.accessHash,
|
||||
participantCount: 0,
|
||||
clientParams: nil
|
||||
),
|
||||
topParticipants: [],
|
||||
participantCount: 0,
|
||||
activeSpeakers: Set(),
|
||||
groupCall: nil
|
||||
)))
|
||||
|
||||
self.disposable = (getGroupCallParticipants(account: account, callId: call.id, accessHash: call.accessHash, offset: "", limit: 100)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<GroupCallParticipantsContext.State?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
guard let strongSelf = self, let state = state else {
|
||||
return
|
||||
}
|
||||
let context = GroupCallParticipantsContext(
|
||||
account: account,
|
||||
peerId: peerId,
|
||||
id: call.id,
|
||||
accessHash: call.accessHash,
|
||||
state: state
|
||||
)
|
||||
strongSelf.participantsContext = context
|
||||
strongSelf.panelDataPromise.set(combineLatest(queue: .mainQueue(),
|
||||
context.state,
|
||||
context.activeSpeakers
|
||||
)
|
||||
|> map { state, activeSpeakers -> GroupCallPanelData in
|
||||
var topParticipants: [GroupCallParticipantsContext.Participant] = []
|
||||
for participant in state.participants {
|
||||
if topParticipants.count >= 3 {
|
||||
break
|
||||
}
|
||||
topParticipants.append(participant)
|
||||
}
|
||||
return GroupCallPanelData(
|
||||
peerId: peerId,
|
||||
info: GroupCallInfo(id: call.id, accessHash: call.accessHash, participantCount: state.totalCount, clientParams: nil),
|
||||
topParticipants: topParticipants,
|
||||
participantCount: state.totalCount,
|
||||
activeSpeakers: activeSpeakers,
|
||||
groupCall: nil
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable?.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
public final class AccountGroupCallContextCacheImpl: AccountGroupCallContextCache {
|
||||
public class Impl {
|
||||
private class Record {
|
||||
let context: AccountGroupCallContextImpl
|
||||
let subscribers = Bag<Void>()
|
||||
var removeTimer: SwiftSignalKit.Timer?
|
||||
|
||||
init(context: AccountGroupCallContextImpl) {
|
||||
self.context = context
|
||||
}
|
||||
}
|
||||
|
||||
private let queue: Queue
|
||||
private var contexts: [Int64: Record] = [:]
|
||||
|
||||
init(queue: Queue) {
|
||||
self.queue = queue
|
||||
}
|
||||
|
||||
public func get(account: Account, peerId: PeerId, call: CachedChannelData.ActiveCall) -> AccountGroupCallContextImpl.Proxy {
|
||||
let result: Record
|
||||
if let current = self.contexts[call.id] {
|
||||
result = current
|
||||
} else {
|
||||
let context = AccountGroupCallContextImpl(account: account, peerId: peerId, call: call)
|
||||
result = Record(context: context)
|
||||
self.contexts[call.id] = result
|
||||
}
|
||||
|
||||
let index = result.subscribers.add(Void())
|
||||
result.removeTimer?.invalidate()
|
||||
result.removeTimer = nil
|
||||
return AccountGroupCallContextImpl.Proxy(context: result.context, removed: { [weak self, weak result] in
|
||||
Queue.mainQueue().async {
|
||||
if let strongResult = result, let strongSelf = self, strongSelf.contexts[call.id] === strongResult {
|
||||
strongResult.subscribers.remove(index)
|
||||
if strongResult.subscribers.isEmpty {
|
||||
let removeTimer = SwiftSignalKit.Timer(timeout: 30, repeat: false, completion: {
|
||||
if let result = result, let strongSelf = self, strongSelf.contexts[call.id] === result, result.subscribers.isEmpty {
|
||||
strongSelf.contexts.removeValue(forKey: call.id)
|
||||
}
|
||||
}, queue: .mainQueue())
|
||||
strongResult.removeTimer = removeTimer
|
||||
removeTimer.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let queue: Queue = .mainQueue()
|
||||
public let impl: QueueLocalObject<Impl>
|
||||
|
||||
public init() {
|
||||
let queue = self.queue
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
return Impl(queue: queue)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private extension PresentationGroupCallState {
|
||||
static var initialValue: PresentationGroupCallState {
|
||||
return PresentationGroupCallState(
|
||||
@ -437,6 +586,50 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
)
|
||||
})
|
||||
|
||||
if let initialCall = initialCall, let temporaryParticipantsContext = (self.accountContext.cachedGroupCallContexts as? AccountGroupCallContextCacheImpl)?.impl.syncWith({ impl in
|
||||
impl.get(account: accountContext.account, peerId: peerId, call: CachedChannelData.ActiveCall(id: initialCall.id, accessHash: initialCall.accessHash))
|
||||
}) {
|
||||
if let participantsContext = temporaryParticipantsContext.context.participantsContext {
|
||||
self.participantsContextStateDisposable.set(combineLatest(queue: .mainQueue(),
|
||||
participantsContext.state,
|
||||
participantsContext.activeSpeakers
|
||||
).start(next: { [weak self] state, activeSpeakers in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
var topParticipants: [GroupCallParticipantsContext.Participant] = []
|
||||
|
||||
var members = PresentationGroupCallMembers(
|
||||
participants: [],
|
||||
speakingParticipants: [],
|
||||
totalCount: 0,
|
||||
loadMoreToken: nil
|
||||
)
|
||||
for participant in state.participants {
|
||||
members.participants.append(participant)
|
||||
|
||||
if topParticipants.count < 3 {
|
||||
topParticipants.append(participant)
|
||||
}
|
||||
}
|
||||
|
||||
members.totalCount = state.totalCount
|
||||
members.loadMoreToken = state.nextParticipantsFetchOffset
|
||||
|
||||
strongSelf.membersValue = members
|
||||
|
||||
strongSelf.stateValue.adminIds = state.adminIds
|
||||
|
||||
strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState(
|
||||
participantCount: state.totalCount,
|
||||
topParticipants: topParticipants,
|
||||
activeSpeakers: activeSpeakers
|
||||
)))
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
self.requestCall()
|
||||
}
|
||||
|
||||
@ -922,6 +1115,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
let _ = inviteToGroupCall(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash, peerId: peerId).start()
|
||||
}
|
||||
|
||||
public func removedPeer(_ peerId: PeerId) {
|
||||
var updatedInvitedPeers = self.invitedPeersValue
|
||||
updatedInvitedPeers.removeAll(where: { $0 == peerId})
|
||||
self.invitedPeersValue = updatedInvitedPeers
|
||||
}
|
||||
|
||||
private var currentMyAudioLevel: Float = 0.0
|
||||
private var currentMyAudioLevelTimestamp: Double = 0.0
|
||||
private var isSendingTyping: Bool = false
|
||||
|
@ -556,6 +556,8 @@ public final class VoiceChatController: ViewController {
|
||||
if let participant = participant {
|
||||
strongSelf.call.invitePeer(participant.peer.id)
|
||||
dismissController?()
|
||||
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: participant.peer, text: strongSelf.presentationData.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).0), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
} else {
|
||||
let selfController = strongSelf.controller
|
||||
let inviteDisposable = strongSelf.inviteDisposable
|
||||
@ -586,14 +588,7 @@ public final class VoiceChatController: ViewController {
|
||||
inviteDisposable.set(nil)
|
||||
}
|
||||
|
||||
inviteDisposable.set((inviteSignal |> deliverOnMainQueue).start(next: { _ in
|
||||
guard let strongSelf = self else {
|
||||
dismissController?()
|
||||
return
|
||||
}
|
||||
strongSelf.call.invitePeer(peer.id)
|
||||
dismissController?()
|
||||
}, error: { error in
|
||||
inviteDisposable.set((inviteSignal |> deliverOnMainQueue).start(error: { error in
|
||||
dismissController?()
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -620,9 +615,49 @@ public final class VoiceChatController: ViewController {
|
||||
text = presentationData.strings.Login_UnknownError
|
||||
}
|
||||
strongSelf.controller?.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}, completed: {
|
||||
guard let strongSelf = self else {
|
||||
dismissController?()
|
||||
return
|
||||
}
|
||||
strongSelf.call.invitePeer(peer.id)
|
||||
dismissController?()
|
||||
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: strongSelf.presentationData.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).0), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
}))
|
||||
}
|
||||
})
|
||||
controller.copyInviteLink = {
|
||||
dismissController?()
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> String? in
|
||||
if let peer = transaction.getPeer(call.peerId), let addressName = peer.addressName, !addressName.isEmpty {
|
||||
return "https://t.me/\(addressName)"
|
||||
} else if let cachedData = transaction.getPeerCachedData(peerId: call.peerId) {
|
||||
if let cachedData = cachedData as? CachedChannelData {
|
||||
return cachedData.exportedInvitation?.link
|
||||
} else if let cachedData = cachedData as? CachedGroupData {
|
||||
return cachedData.exportedInvitation?.link
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { link in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if let link = link {
|
||||
UIPasteboard.general.string = link
|
||||
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .linkCopied(text: strongSelf.presentationData.strings.VoiceChat_InviteLinkCopiedText), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
}
|
||||
})
|
||||
}
|
||||
dismissController = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
}
|
||||
@ -682,7 +717,14 @@ public final class VoiceChatController: ViewController {
|
||||
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.VoiceChat_RemovePeerRemove, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = strongSelf.context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: strongSelf.context.account, peerId: strongSelf.call.peerId, memberId: peer.id, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max)).start()
|
||||
strongSelf.call.removedPeer(peer.id)
|
||||
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .banned(text: strongSelf.presentationData.strings.VoiceChat_RemovedPeerText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).0), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
}))
|
||||
|
||||
actionSheet.setItemGroups([
|
||||
|
@ -39,7 +39,7 @@ func cacheStickerPack(transaction: Transaction, info: StickerPackCollectionInfo,
|
||||
public func cachedStickerPack(postbox: Postbox, network: Network, reference: StickerPackReference, forceRemote: Bool) -> Signal<CachedStickerPackResult, NoError> {
|
||||
return postbox.transaction { transaction -> CachedStickerPackResult? in
|
||||
if let (info, items, local) = cachedStickerPack(transaction: transaction, reference: reference) {
|
||||
if local {
|
||||
if local && !forceRemote {
|
||||
return .result(info, items, true)
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -24,6 +24,8 @@ public enum UndoOverlayContent {
|
||||
case messagesUnpinned(title: String, text: String, undo: Bool, isHidden: Bool)
|
||||
case setProximityAlert(title: String, text: String, cancelled: Bool)
|
||||
case invitedToVoiceChat(context: AccountContext, peer: Peer, text: String)
|
||||
case linkCopied(text: String)
|
||||
case banned(text: String)
|
||||
}
|
||||
|
||||
public enum UndoOverlayAction {
|
||||
|
@ -182,6 +182,34 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
displayUndo = true
|
||||
undoText = cancel
|
||||
self.originalRemainingSeconds = 5
|
||||
case let .linkCopied(text):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = nil
|
||||
self.iconCheckNode = nil
|
||||
self.animationNode = AnimationNode(animation: "anim_linkcopied", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
|
||||
self.animatedStickerNode = nil
|
||||
|
||||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
|
||||
self.textNode.attributedText = attributedText
|
||||
self.textNode.maximumNumberOfLines = 2
|
||||
displayUndo = false
|
||||
self.originalRemainingSeconds = 5
|
||||
case let .banned(text):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = nil
|
||||
self.iconCheckNode = nil
|
||||
self.animationNode = AnimationNode(animation: "anim_banned", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
|
||||
self.animatedStickerNode = nil
|
||||
|
||||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
|
||||
self.textNode.attributedText = attributedText
|
||||
self.textNode.maximumNumberOfLines = 2
|
||||
displayUndo = false
|
||||
self.originalRemainingSeconds = 5
|
||||
case let .chatAddedToFolder(chatTitle, folderTitle):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = nil
|
||||
@ -467,7 +495,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
switch content {
|
||||
case .removedChat:
|
||||
self.panelWrapperNode.addSubnode(self.timerTextNode)
|
||||
case .archivedChat, .hidArchive, .revealedArchive, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat:
|
||||
case .archivedChat, .hidArchive, .revealedArchive, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned:
|
||||
break
|
||||
case .dice:
|
||||
self.panelWrapperNode.clipsToBounds = true
|
||||
@ -579,6 +607,12 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
if case .messagesUnpinned = self.content {
|
||||
let factor: CGFloat = 0.5
|
||||
preferredSize = CGSize(width: floor(iconSize.width * factor), height: floor(iconSize.height * factor))
|
||||
} else if case .linkCopied = self.content {
|
||||
let factor: CGFloat = 0.08
|
||||
preferredSize = CGSize(width: floor(iconSize.width * factor), height: floor(iconSize.height * factor))
|
||||
} else if case .banned = self.content {
|
||||
let factor: CGFloat = 0.08
|
||||
preferredSize = CGSize(width: floor(iconSize.width * factor), height: floor(iconSize.height * factor))
|
||||
} else {
|
||||
preferredSize = iconSize
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user