mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 09:20:08 +00:00
Voice chat UI improvements
This commit is contained in:
parent
8ffe12999f
commit
3e3c9e2005
@ -5940,6 +5940,7 @@ Sorry for the inconvenience.";
|
|||||||
"VoiceChat.EndVoiceChat" = "End Voice Chat";
|
"VoiceChat.EndVoiceChat" = "End Voice Chat";
|
||||||
|
|
||||||
"VoiceChat.CopyInviteLink" = "Copy Invite Link";
|
"VoiceChat.CopyInviteLink" = "Copy Invite Link";
|
||||||
|
"VoiceChat.InviteLinkCopiedText" = "Invite link copied to clipboard";
|
||||||
|
|
||||||
"VoiceChat.UnmutePeer" = "Allow to Speak";
|
"VoiceChat.UnmutePeer" = "Allow to Speak";
|
||||||
"VoiceChat.MutePeer" = "Mute";
|
"VoiceChat.MutePeer" = "Mute";
|
||||||
@ -5956,7 +5957,8 @@ Sorry for the inconvenience.";
|
|||||||
"Notification.VoiceChatInvitation" = "%1$@ invited %2$@ to the voice chat";
|
"Notification.VoiceChatInvitation" = "%1$@ invited %2$@ to the voice chat";
|
||||||
"Notification.VoiceChatInvitationForYou" = "%1$@ invited you to the voice chat";
|
"Notification.VoiceChatInvitationForYou" = "%1$@ invited you to the voice chat";
|
||||||
|
|
||||||
"VoiceChat.InvitedPeerText" = "You have invited %@ 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.VoiceChatStarted" = "Voice chat started";
|
||||||
"Notification.VoiceChatEnded" = "Voice chat ended (%@)";
|
"Notification.VoiceChatEnded" = "Voice chat ended (%@)";
|
||||||
|
|||||||
@ -286,6 +286,7 @@ public protocol PresentationGroupCall: class {
|
|||||||
func updateMuteState(peerId: PeerId, isMuted: Bool)
|
func updateMuteState(peerId: PeerId, isMuted: Bool)
|
||||||
|
|
||||||
func invitePeer(_ peerId: PeerId)
|
func invitePeer(_ peerId: PeerId)
|
||||||
|
func removedPeer(_ peerId: PeerId)
|
||||||
var invitedPeers: Signal<[PeerId], NoError> { get }
|
var invitedPeers: Signal<[PeerId], NoError> { get }
|
||||||
|
|
||||||
var sourcePanel: ASDisplayNode? { get set }
|
var sourcePanel: ASDisplayNode? { get set }
|
||||||
|
|||||||
@ -58,6 +58,7 @@ private let avatarFont = avatarPlaceholderFont(size: 12.0)
|
|||||||
|
|
||||||
private final class ContentNode: ASDisplayNode {
|
private final class ContentNode: ASDisplayNode {
|
||||||
private var audioLevelView: VoiceBlobView?
|
private var audioLevelView: VoiceBlobView?
|
||||||
|
private var audioLevelBlobOverlay: UIImageView?
|
||||||
private let unclippedNode: ASImageNode
|
private let unclippedNode: ASImageNode
|
||||||
private let clippedNode: 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 {
|
if self.audioLevelView == nil, value > 0.0 {
|
||||||
let blobFrame = self.unclippedNode.bounds.insetBy(dx: -8.0, dy: -8.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)
|
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 {
|
for (key, itemNode) in self.contentNodes {
|
||||||
if let value = levels[key.peerId] {
|
if let value = levels[key.peerId] {
|
||||||
itemNode.updateAudioLevel(color: color, value: value)
|
itemNode.updateAudioLevel(color: color, backgroundColor: backgroundColor, value: value)
|
||||||
} else {
|
} 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
|
case alpha
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContactListActionItem: ListViewItem, ListViewItemWithHeader {
|
public class ContactListActionItem: ListViewItem, ListViewItemWithHeader {
|
||||||
let presentationData: ItemListPresentationData
|
let presentationData: ItemListPresentationData
|
||||||
let title: String
|
let title: String
|
||||||
let icon: ContactListActionItemIcon
|
let icon: ContactListActionItemIcon
|
||||||
let highlight: ContactListActionItemHighlight
|
let highlight: ContactListActionItemHighlight
|
||||||
let clearHighlightAutomatically: Bool
|
let clearHighlightAutomatically: Bool
|
||||||
let action: () -> Void
|
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.presentationData = presentationData
|
||||||
self.title = title
|
self.title = title
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
@ -33,7 +33,7 @@ class ContactListActionItem: ListViewItem, ListViewItemWithHeader {
|
|||||||
self.action = action
|
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 {
|
async {
|
||||||
let node = ContactListActionItemNode()
|
let node = ContactListActionItemNode()
|
||||||
let (_, last, firstWithHeader) = ContactListActionItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
|
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 {
|
Queue.mainQueue().async {
|
||||||
if let nodeValue = node() as? ContactListActionItemNode {
|
if let nodeValue = node() as? ContactListActionItemNode {
|
||||||
let makeLayout = nodeValue.asyncLayout()
|
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()
|
self.action()
|
||||||
if self.clearHighlightAutomatically {
|
if self.clearHighlightAutomatically {
|
||||||
listView.clearHighlightAnimated(true)
|
listView.clearHighlightAnimated(true)
|
||||||
|
|||||||
@ -30,6 +30,8 @@ public final class ChannelMembersSearchController: ViewController {
|
|||||||
private let filters: [ChannelMembersSearchFilter]
|
private let filters: [ChannelMembersSearchFilter]
|
||||||
private let openPeer: (Peer, RenderedChannelParticipant?) -> Void
|
private let openPeer: (Peer, RenderedChannelParticipant?) -> Void
|
||||||
|
|
||||||
|
public var copyInviteLink: (() -> Void)?
|
||||||
|
|
||||||
private let forceTheme: PresentationTheme?
|
private let forceTheme: PresentationTheme?
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
|
|
||||||
@ -93,6 +95,9 @@ public final class ChannelMembersSearchController: ViewController {
|
|||||||
self.controllerNode.requestOpenPeerFromSearch = { [weak self] peer, participant in
|
self.controllerNode.requestOpenPeerFromSearch = { [weak self] peer, participant in
|
||||||
self?.openPeer(peer, participant)
|
self?.openPeer(peer, participant)
|
||||||
}
|
}
|
||||||
|
self.controllerNode.requestCopyInviteLink = { [weak self] in
|
||||||
|
self?.copyInviteLink?()
|
||||||
|
}
|
||||||
self.controllerNode.pushController = { [weak self] c in
|
self.controllerNode.pushController = { [weak self] c in
|
||||||
(self?.navigationController as? NavigationController)?.pushViewController(c)
|
(self?.navigationController as? NavigationController)?.pushViewController(c)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,31 +15,48 @@ import SearchBarNode
|
|||||||
import ContactsPeerItem
|
import ContactsPeerItem
|
||||||
import SearchUI
|
import SearchUI
|
||||||
import ItemListUI
|
import ItemListUI
|
||||||
|
import ContactListUI
|
||||||
|
import ChatListSearchItemHeader
|
||||||
|
|
||||||
private final class ChannelMembersSearchInteraction {
|
private final class ChannelMembersSearchInteraction {
|
||||||
let openPeer: (Peer, RenderedChannelParticipant?) -> Void
|
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.openPeer = openPeer
|
||||||
|
self.copyInviteLink = copyInviteLink
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum ChannelMembersSearchEntryId: Hashable {
|
private enum ChannelMembersSearchEntryId: Hashable {
|
||||||
|
case copyInviteLink
|
||||||
case peer(PeerId)
|
case peer(PeerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum ChannelMembersSearchEntry: Comparable, Identifiable {
|
private enum ChannelMembersSearchEntry: Comparable, Identifiable {
|
||||||
|
case copyInviteLink
|
||||||
case peer(Int, RenderedChannelParticipant, ContactsPeerItemEditing, String?, Bool)
|
case peer(Int, RenderedChannelParticipant, ContactsPeerItemEditing, String?, Bool)
|
||||||
|
|
||||||
var stableId: ChannelMembersSearchEntryId {
|
var stableId: ChannelMembersSearchEntryId {
|
||||||
switch self {
|
switch self {
|
||||||
case let .peer(peer):
|
case .copyInviteLink:
|
||||||
return .peer(peer.1.peer.id)
|
return .copyInviteLink
|
||||||
|
case let .peer(_, participant, _, _, _):
|
||||||
|
return .peer(participant.peer.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: ChannelMembersSearchEntry, rhs: ChannelMembersSearchEntry) -> Bool {
|
static func ==(lhs: ChannelMembersSearchEntry, rhs: ChannelMembersSearchEntry) -> Bool {
|
||||||
switch lhs {
|
switch lhs {
|
||||||
|
case .copyInviteLink:
|
||||||
|
if case .copyInviteLink = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
case let .peer(lhsIndex, lhsParticipant, lhsEditing, lhsLabel, lhsEnabled):
|
case let .peer(lhsIndex, lhsParticipant, lhsEditing, lhsLabel, lhsEnabled):
|
||||||
if case .peer(lhsIndex, lhsParticipant, lhsEditing, lhsLabel, lhsEnabled) = rhs {
|
if case .peer(lhsIndex, lhsParticipant, lhsEditing, lhsLabel, lhsEnabled) = rhs {
|
||||||
return true
|
return true
|
||||||
@ -51,9 +68,17 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable {
|
|||||||
|
|
||||||
static func <(lhs: ChannelMembersSearchEntry, rhs: ChannelMembersSearchEntry) -> Bool {
|
static func <(lhs: ChannelMembersSearchEntry, rhs: ChannelMembersSearchEntry) -> Bool {
|
||||||
switch lhs {
|
switch lhs {
|
||||||
case let .peer(lhsPeer):
|
case .copyInviteLink:
|
||||||
if case let .peer(rhsPeer) = rhs {
|
if case .copyInviteLink = rhs {
|
||||||
return lhsPeer.0 < rhsPeer.0
|
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 {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -62,6 +87,16 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable {
|
|||||||
|
|
||||||
func item(context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, interaction: ChannelMembersSearchInteraction) -> ListViewItem {
|
func item(context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, interaction: ChannelMembersSearchInteraction) -> ListViewItem {
|
||||||
switch self {
|
switch self {
|
||||||
|
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):
|
case let .peer(_, participant, editing, label, enabled):
|
||||||
let status: ContactsPeerItemStatus
|
let status: ContactsPeerItemStatus
|
||||||
if let label = label {
|
if let label = label {
|
||||||
@ -69,7 +104,7 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable {
|
|||||||
} else {
|
} else {
|
||||||
status = .none
|
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
|
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)
|
interaction.openPeer(participant.peer, participant)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -110,6 +145,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||||||
var requestActivateSearch: (() -> Void)?
|
var requestActivateSearch: (() -> Void)?
|
||||||
var requestDeactivateSearch: (() -> Void)?
|
var requestDeactivateSearch: (() -> Void)?
|
||||||
var requestOpenPeerFromSearch: ((Peer, RenderedChannelParticipant?) -> Void)?
|
var requestOpenPeerFromSearch: ((Peer, RenderedChannelParticipant?) -> Void)?
|
||||||
|
var requestCopyInviteLink: (() -> Void)?
|
||||||
var pushController: ((ViewController) -> Void)?
|
var pushController: ((ViewController) -> Void)?
|
||||||
|
|
||||||
private let forceTheme: PresentationTheme?
|
private let forceTheme: PresentationTheme?
|
||||||
@ -140,10 +176,16 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.addSubnode(self.listNode)
|
self.addSubnode(self.listNode)
|
||||||
|
|
||||||
let interaction = ChannelMembersSearchInteraction(openPeer: { [weak self] peer, participant in
|
let interaction = ChannelMembersSearchInteraction(
|
||||||
|
openPeer: { [weak self] peer, participant in
|
||||||
self?.requestOpenPeerFromSearch?(peer, participant)
|
self?.requestOpenPeerFromSearch?(peer, participant)
|
||||||
self?.listNode.clearHighlightAnimated(true)
|
self?.listNode.clearHighlightAnimated(true)
|
||||||
})
|
},
|
||||||
|
copyInviteLink: { [weak self] in
|
||||||
|
self?.requestCopyInviteLink?()
|
||||||
|
self?.listNode.clearHighlightAnimated(true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
let previousEntries = Atomic<[ChannelMembersSearchEntry]?>(value: nil)
|
let previousEntries = Atomic<[ChannelMembersSearchEntry]?>(value: nil)
|
||||||
|
|
||||||
@ -274,6 +316,16 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
var entries: [ChannelMembersSearchEntry] = []
|
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
|
var index = 0
|
||||||
for participant in state.list {
|
for participant in state.list {
|
||||||
if participant.peer.isDeleted {
|
if participant.peer.isDeleted {
|
||||||
|
|||||||
@ -26,155 +26,6 @@ public enum LocationBroadcastPanelSource {
|
|||||||
case peer(PeerId)
|
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) {
|
private func presentLiveLocationController(context: AccountContext, peerId: PeerId, controller: ViewController) {
|
||||||
let presentImpl: (Message?) -> Void = { [weak controller] message in
|
let presentImpl: (Message?) -> Void = { [weak controller] message in
|
||||||
if let message = message, let strongController = controller {
|
if let message = message, let strongController = controller {
|
||||||
|
|||||||
@ -426,7 +426,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
|||||||
if self.audioLevelGenerators.isEmpty {
|
if self.audioLevelGenerators.isEmpty {
|
||||||
self.audioLevelGeneratorTimer?.invalidate()
|
self.audioLevelGeneratorTimer?.invalidate()
|
||||||
self.audioLevelGeneratorTimer = nil
|
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 {
|
} else if self.audioLevelGeneratorTimer == nil {
|
||||||
let audioLevelGeneratorTimer = SwiftSignalKit.Timer(timeout: 1.0 / 30.0, repeat: true, completion: { [weak self] in
|
let audioLevelGeneratorTimer = SwiftSignalKit.Timer(timeout: 1.0 / 30.0, repeat: true, completion: { [weak self] in
|
||||||
self?.sampleAudioGenerators()
|
self?.sampleAudioGenerators()
|
||||||
@ -442,7 +442,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
|||||||
for (peerId, generator) in self.audioLevelGenerators {
|
for (peerId, generator) in self.audioLevelGenerators {
|
||||||
levels[peerId] = generator.get()
|
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) {
|
public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
|||||||
@ -15,6 +15,155 @@ import DeviceAccess
|
|||||||
import UniversalMediaPlayer
|
import UniversalMediaPlayer
|
||||||
import AccountContext
|
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 {
|
private extension PresentationGroupCallState {
|
||||||
static var initialValue: PresentationGroupCallState {
|
static var initialValue: PresentationGroupCallState {
|
||||||
return 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()
|
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()
|
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 currentMyAudioLevel: Float = 0.0
|
||||||
private var currentMyAudioLevelTimestamp: Double = 0.0
|
private var currentMyAudioLevelTimestamp: Double = 0.0
|
||||||
private var isSendingTyping: Bool = false
|
private var isSendingTyping: Bool = false
|
||||||
|
|||||||
@ -588,16 +588,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
inviteDisposable.set(nil)
|
inviteDisposable.set(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
inviteDisposable.set((inviteSignal |> deliverOnMainQueue).start(next: { _ in
|
inviteDisposable.set((inviteSignal |> deliverOnMainQueue).start(error: { error in
|
||||||
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)
|
|
||||||
}, error: { error in
|
|
||||||
dismissController?()
|
dismissController?()
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -624,9 +615,49 @@ public final class VoiceChatController: ViewController {
|
|||||||
text = presentationData.strings.Login_UnknownError
|
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))
|
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
|
dismissController = { [weak controller] in
|
||||||
controller?.dismiss()
|
controller?.dismiss()
|
||||||
}
|
}
|
||||||
@ -686,7 +717,14 @@ public final class VoiceChatController: ViewController {
|
|||||||
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.VoiceChat_RemovePeerRemove, color: .destructive, action: { [weak actionSheet] in
|
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.VoiceChat_RemovePeerRemove, color: .destructive, action: { [weak actionSheet] in
|
||||||
actionSheet?.dismissAnimated()
|
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([
|
actionSheet.setItemGroups([
|
||||||
|
|||||||
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 messagesUnpinned(title: String, text: String, undo: Bool, isHidden: Bool)
|
||||||
case setProximityAlert(title: String, text: String, cancelled: Bool)
|
case setProximityAlert(title: String, text: String, cancelled: Bool)
|
||||||
case invitedToVoiceChat(context: AccountContext, peer: Peer, text: String)
|
case invitedToVoiceChat(context: AccountContext, peer: Peer, text: String)
|
||||||
|
case linkCopied(text: String)
|
||||||
|
case banned(text: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum UndoOverlayAction {
|
public enum UndoOverlayAction {
|
||||||
|
|||||||
@ -182,6 +182,34 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
displayUndo = true
|
displayUndo = true
|
||||||
undoText = cancel
|
undoText = cancel
|
||||||
self.originalRemainingSeconds = 5
|
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):
|
case let .chatAddedToFolder(chatTitle, folderTitle):
|
||||||
self.avatarNode = nil
|
self.avatarNode = nil
|
||||||
self.iconNode = nil
|
self.iconNode = nil
|
||||||
@ -467,7 +495,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
switch content {
|
switch content {
|
||||||
case .removedChat:
|
case .removedChat:
|
||||||
self.panelWrapperNode.addSubnode(self.timerTextNode)
|
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
|
break
|
||||||
case .dice:
|
case .dice:
|
||||||
self.panelWrapperNode.clipsToBounds = true
|
self.panelWrapperNode.clipsToBounds = true
|
||||||
@ -579,6 +607,12 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
if case .messagesUnpinned = self.content {
|
if case .messagesUnpinned = self.content {
|
||||||
let factor: CGFloat = 0.5
|
let factor: CGFloat = 0.5
|
||||||
preferredSize = CGSize(width: floor(iconSize.width * factor), height: floor(iconSize.height * factor))
|
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 {
|
} else {
|
||||||
preferredSize = iconSize
|
preferredSize = iconSize
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user