mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP] Conference
This commit is contained in:
parent
40b19cfef2
commit
17dd059f13
Telegram/BroadcastUpload
submodules
AccountContext/Sources
CallListUI
GalleryUI/Sources
InviteLinksUI
BUILD
Sources
ItemListPeerActionItem/Sources
JoinLinkPreviewUI/Sources
PeerInfoUI/Sources
TelegramCallsUI
TelegramUI
Components
Chat
ChatRecentActionsController/Sources
ChatSendStarsScreen/Sources
JoinSubjectScreen
PeerInfo/PeerInfoScreen/Sources
PeerManagement/OldChannelsController/Sources
Sources
TelegramVoip/Sources
TgVoipWebrtc
UrlHandling/Sources
@ -168,6 +168,7 @@ private final class EmbeddedBroadcastUploadImpl: BroadcastUploadImpl {
|
||||
onMutedSpeechActivityDetected: { _ in },
|
||||
encryptionKey: nil,
|
||||
isConference: false,
|
||||
audioIsActiveByDefault: true,
|
||||
isStream: false,
|
||||
sharedAudioDevice: nil
|
||||
)
|
||||
|
@ -301,6 +301,7 @@ public enum ResolvedUrl {
|
||||
case instantView(TelegramMediaWebpage, String?)
|
||||
case proxy(host: String, port: Int32, username: String?, password: String?, secret: Data?)
|
||||
case join(String)
|
||||
case joinCall(String)
|
||||
case localization(String)
|
||||
case confirmationCode(Int)
|
||||
case cancelAccountReset(phone: String, hash: String)
|
||||
@ -920,6 +921,61 @@ public enum JoinAffiliateProgramScreenMode {
|
||||
case active(Active)
|
||||
}
|
||||
|
||||
public enum JoinSubjectScreenMode {
|
||||
public final class Group {
|
||||
public enum VerificationStatus {
|
||||
case fake
|
||||
case scam
|
||||
case verified
|
||||
}
|
||||
|
||||
public let link: String
|
||||
public let isGroup: Bool
|
||||
public let isPublic: Bool
|
||||
public let isRequest: Bool
|
||||
public let verificationStatus: VerificationStatus?
|
||||
public let image: TelegramMediaImageRepresentation?
|
||||
public let title: String
|
||||
public let about: String?
|
||||
public let memberCount: Int32
|
||||
public let members: [EnginePeer]
|
||||
|
||||
public init(link: String, isGroup: Bool, isPublic: Bool, isRequest: Bool, verificationStatus: VerificationStatus?, image: TelegramMediaImageRepresentation?, title: String, about: String?, memberCount: Int32, members: [EnginePeer]) {
|
||||
self.link = link
|
||||
self.isGroup = isGroup
|
||||
self.isPublic = isPublic
|
||||
self.isRequest = isRequest
|
||||
self.verificationStatus = verificationStatus
|
||||
self.image = image
|
||||
self.title = title
|
||||
self.about = about
|
||||
self.memberCount = memberCount
|
||||
self.members = members
|
||||
}
|
||||
}
|
||||
|
||||
public final class GroupCall {
|
||||
public let inviter: EnginePeer?
|
||||
public let members: [EnginePeer]
|
||||
public let totalMemberCount: Int
|
||||
|
||||
public init(inviter: EnginePeer?, members: [EnginePeer], totalMemberCount: Int) {
|
||||
self.inviter = inviter
|
||||
self.members = members
|
||||
self.totalMemberCount = totalMemberCount
|
||||
}
|
||||
}
|
||||
|
||||
case group(Group)
|
||||
case groupCall(GroupCall)
|
||||
}
|
||||
|
||||
public enum OldChannelsControllerIntent {
|
||||
case join
|
||||
case create
|
||||
case upgrade
|
||||
}
|
||||
|
||||
public protocol SharedAccountContext: AnyObject {
|
||||
var sharedContainerPath: String { get }
|
||||
var basePath: String { get }
|
||||
@ -1126,9 +1182,12 @@ public protocol SharedAccountContext: AnyObject {
|
||||
|
||||
func makeAffiliateProgramSetupScreenInitialData(context: AccountContext, peerId: EnginePeer.Id, mode: AffiliateProgramSetupScreenMode) -> Signal<AffiliateProgramSetupScreenInitialData, NoError>
|
||||
func makeAffiliateProgramSetupScreen(context: AccountContext, initialData: AffiliateProgramSetupScreenInitialData) -> ViewController
|
||||
|
||||
func makeAffiliateProgramJoinScreen(context: AccountContext, sourcePeer: EnginePeer, commissionPermille: Int32, programDuration: Int32?, revenuePerUser: Double, mode: JoinAffiliateProgramScreenMode) -> ViewController
|
||||
|
||||
func makeJoinSubjectScreen(context: AccountContext, mode: JoinSubjectScreenMode) -> ViewController
|
||||
|
||||
func makeOldChannelsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, intent: OldChannelsControllerIntent, completed: @escaping (Bool) -> Void) -> ViewController
|
||||
|
||||
func makeGalleryController(context: AccountContext, source: GalleryControllerItemSource, streamSingleVideo: Bool, isPreview: Bool) -> ViewController
|
||||
|
||||
func makeAccountFreezeInfoScreen(context: AccountContext) -> ViewController
|
||||
|
@ -30,6 +30,9 @@ swift_library(
|
||||
"//submodules/TelegramBaseController:TelegramBaseController",
|
||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||
"//submodules/ItemListPeerActionItem",
|
||||
"//submodules/InviteLinksUI",
|
||||
"//submodules/UndoUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -13,6 +13,8 @@ import AppBundle
|
||||
import LocalizedPeerData
|
||||
import ContextUI
|
||||
import TelegramBaseController
|
||||
import InviteLinksUI
|
||||
import UndoUI
|
||||
|
||||
public enum CallListControllerMode {
|
||||
case tab
|
||||
@ -201,7 +203,28 @@ public final class CallListController: TelegramBaseController {
|
||||
if self.isNodeLoaded {
|
||||
self.controllerNode.updateThemeAndStrings(presentationData: self.presentationData)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func createGroupCall() {
|
||||
let controller = InviteLinkInviteController(context: self.context, updatedPresentationData: nil, mode: .groupCall(link: "https://t.me/call/+abbfbffll123", isRecentlyCreated: true), parentNavigationController: self.navigationController as? NavigationController, completed: { [weak self] result in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let result {
|
||||
switch result {
|
||||
case .linkCopied:
|
||||
//TODO:localize
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: "Call link copied.", customUndoText: "View Call", timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in
|
||||
if case .undo = action {
|
||||
//TODO:release
|
||||
}
|
||||
return false
|
||||
}), in: .window(.root))
|
||||
}
|
||||
}
|
||||
})
|
||||
self.present(controller, in: .window(.root), with: nil)
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
@ -275,6 +298,10 @@ public final class CallListController: TelegramBaseController {
|
||||
}
|
||||
}
|
||||
}
|
||||
}, createGroupCall: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.createGroupCall()
|
||||
}
|
||||
})
|
||||
|
||||
if case .navigation = self.mode {
|
||||
|
@ -14,6 +14,7 @@ import ChatListSearchItemHeader
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import AppBundle
|
||||
import ItemListPeerActionItem
|
||||
|
||||
private struct CallListNodeListViewTransition {
|
||||
let callListView: CallListNodeView
|
||||
@ -66,14 +67,16 @@ final class CallListNodeInteraction {
|
||||
let delete: ([EngineMessage.Id]) -> Void
|
||||
let updateShowCallsTab: (Bool) -> Void
|
||||
let openGroupCall: (EnginePeer.Id) -> Void
|
||||
let createGroupCall: () -> Void
|
||||
|
||||
init(setMessageIdWithRevealedOptions: @escaping (EngineMessage.Id?, EngineMessage.Id?) -> Void, call: @escaping (EnginePeer.Id, Bool) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, delete: @escaping ([EngineMessage.Id]) -> Void, updateShowCallsTab: @escaping (Bool) -> Void, openGroupCall: @escaping (EnginePeer.Id) -> Void) {
|
||||
init(setMessageIdWithRevealedOptions: @escaping (EngineMessage.Id?, EngineMessage.Id?) -> Void, call: @escaping (EnginePeer.Id, Bool) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, delete: @escaping ([EngineMessage.Id]) -> Void, updateShowCallsTab: @escaping (Bool) -> Void, openGroupCall: @escaping (EnginePeer.Id) -> Void, createGroupCall: @escaping () -> Void) {
|
||||
self.setMessageIdWithRevealedOptions = setMessageIdWithRevealedOptions
|
||||
self.call = call
|
||||
self.openInfo = openInfo
|
||||
self.delete = delete
|
||||
self.updateShowCallsTab = updateShowCallsTab
|
||||
self.openGroupCall = openGroupCall
|
||||
self.createGroupCall = createGroupCall
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,6 +125,12 @@ private func mappedInsertEntries(context: AccountContext, presentationData: Item
|
||||
}), directionHint: entry.directionHint)
|
||||
case let .displayTabInfo(_, text):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint)
|
||||
case .createGroupCall:
|
||||
//TODO:localize
|
||||
let item = ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.linkIcon(presentationData.theme), title: "New Call Link", hasSeparator: false, sectionId: 1, noInsets: true, editing: false, action: {
|
||||
nodeInteraction.createGroupCall()
|
||||
})
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
|
||||
case let .groupCall(peer, _, isActive):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListGroupCallItem(presentationData: presentationData, context: context, style: showSettings ? .blocks : .plain, peer: peer, isActive: isActive, editing: false, interaction: nodeInteraction), directionHint: entry.directionHint)
|
||||
case let .messageEntry(topMessage, messages, _, _, dateTimeFormat, editing, hasActiveRevealControls, displayHeader, _):
|
||||
@ -141,6 +150,12 @@ private func mappedUpdateEntries(context: AccountContext, presentationData: Item
|
||||
}), directionHint: entry.directionHint)
|
||||
case let .displayTabInfo(_, text):
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint)
|
||||
case .createGroupCall:
|
||||
//TODO:localize
|
||||
let item = ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.linkIcon(presentationData.theme), title: "New Call Link", sectionId: 1, noInsets: true, editing: false, action: {
|
||||
nodeInteraction.createGroupCall()
|
||||
})
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
|
||||
case let .groupCall(peer, _, isActive):
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListGroupCallItem(presentationData: presentationData, context: context, style: showSettings ? .blocks : .plain, peer: peer, isActive: isActive, editing: false, interaction: nodeInteraction), directionHint: entry.directionHint)
|
||||
case let .messageEntry(topMessage, messages, _, _, dateTimeFormat, editing, hasActiveRevealControls, displayHeader, _):
|
||||
@ -209,9 +224,9 @@ final class CallListControllerNode: ASDisplayNode {
|
||||
|
||||
private let call: (EnginePeer.Id, Bool) -> Void
|
||||
private let joinGroupCall: (EnginePeer.Id, EngineGroupCallDescription) -> Void
|
||||
private let createGroupCall: () -> Void
|
||||
private let openInfo: (EnginePeer.Id, [EngineMessage]) -> Void
|
||||
private let emptyStateUpdated: (Bool) -> Void
|
||||
|
||||
private let emptyStatePromise = Promise<Bool>()
|
||||
private let emptyStateDisposable = MetaDisposable()
|
||||
|
||||
@ -219,7 +234,7 @@ final class CallListControllerNode: ASDisplayNode {
|
||||
|
||||
private var previousContentOffset: ListViewVisibleContentOffset?
|
||||
|
||||
init(controller: CallListController, context: AccountContext, mode: CallListControllerMode, presentationData: PresentationData, call: @escaping (EnginePeer.Id, Bool) -> Void, joinGroupCall: @escaping (EnginePeer.Id, EngineGroupCallDescription) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, emptyStateUpdated: @escaping (Bool) -> Void) {
|
||||
init(controller: CallListController, context: AccountContext, mode: CallListControllerMode, presentationData: PresentationData, call: @escaping (EnginePeer.Id, Bool) -> Void, joinGroupCall: @escaping (EnginePeer.Id, EngineGroupCallDescription) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, emptyStateUpdated: @escaping (Bool) -> Void, createGroupCall: @escaping () -> Void) {
|
||||
self.controller = controller
|
||||
self.context = context
|
||||
self.mode = mode
|
||||
@ -228,7 +243,7 @@ final class CallListControllerNode: ASDisplayNode {
|
||||
self.joinGroupCall = joinGroupCall
|
||||
self.openInfo = openInfo
|
||||
self.emptyStateUpdated = emptyStateUpdated
|
||||
|
||||
self.createGroupCall = createGroupCall
|
||||
self.currentState = CallListNodeState(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, disableAnimations: true, editing: false, messageIdWithRevealedOptions: nil)
|
||||
self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true)
|
||||
|
||||
@ -432,6 +447,11 @@ final class CallListControllerNode: ASDisplayNode {
|
||||
strongSelf.joinGroupCall(peerId, activeCall)
|
||||
}
|
||||
}))
|
||||
}, createGroupCall: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.createGroupCall()
|
||||
})
|
||||
|
||||
let viewProcessingQueue = self.viewProcessingQueue
|
||||
@ -496,18 +516,31 @@ final class CallListControllerNode: ASDisplayNode {
|
||||
})
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
let canCreateGroupCall = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.App())
|
||||
|> map { configuration -> Bool in
|
||||
var isConferencePossible = false
|
||||
if context.sharedContext.immediateExperimentalUISettings.conferenceDebug {
|
||||
isConferencePossible = true
|
||||
}
|
||||
if let data = configuration.data, let value = data["ios_enable_conference"] as? Double {
|
||||
isConferencePossible = value != 0.0
|
||||
}
|
||||
return isConferencePossible
|
||||
}
|
||||
|
||||
let callListNodeViewTransition = combineLatest(
|
||||
callListViewUpdate,
|
||||
self.statePromise.get(),
|
||||
groupCalls,
|
||||
showCallsTab,
|
||||
currentGroupCallPeerId
|
||||
currentGroupCallPeerId,
|
||||
canCreateGroupCall
|
||||
)
|
||||
|> mapToQueue { (updateAndType, state, groupCalls, showCallsTab, currentGroupCallPeerId) -> Signal<CallListNodeListViewTransition, NoError> in
|
||||
|> mapToQueue { (updateAndType, state, groupCalls, showCallsTab, currentGroupCallPeerId, canCreateGroupCall) -> Signal<CallListNodeListViewTransition, NoError> in
|
||||
let (update, type) = updateAndType
|
||||
|
||||
let processedView = CallListNodeView(originalView: update.view, filteredEntries: callListNodeEntriesForView(view: update.view, groupCalls: groupCalls, state: state, showSettings: showSettings, showCallsTab: showCallsTab, isRecentCalls: type == .all, currentGroupCallPeerId: currentGroupCallPeerId), presentationData: state.presentationData)
|
||||
let processedView = CallListNodeView(originalView: update.view, filteredEntries: callListNodeEntriesForView(view: update.view, canCreateGroupCall: canCreateGroupCall, groupCalls: groupCalls, state: state, showSettings: showSettings, showCallsTab: showCallsTab, isRecentCalls: type == .all, currentGroupCallPeerId: currentGroupCallPeerId), presentationData: state.presentationData)
|
||||
let previous = previousView.swap(processedView)
|
||||
let previousType = previousType.swap(type)
|
||||
|
||||
|
@ -25,6 +25,7 @@ enum CallListNodeEntry: Comparable, Identifiable {
|
||||
enum SortIndex: Comparable {
|
||||
case displayTab
|
||||
case displayTabInfo
|
||||
case createGroupCall
|
||||
case groupCall(EnginePeer.Id, String)
|
||||
case message(EngineMessage.Index)
|
||||
case hole(EngineMessage.Index)
|
||||
@ -40,10 +41,17 @@ enum CallListNodeEntry: Comparable, Identifiable {
|
||||
default:
|
||||
return false
|
||||
}
|
||||
case let .groupCall(lhsPeerId, lhsTitle):
|
||||
case .createGroupCall:
|
||||
switch rhs {
|
||||
case .displayTab, .displayTabInfo:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
case let .groupCall(lhsPeerId, lhsTitle):
|
||||
switch rhs {
|
||||
case .displayTab, .displayTabInfo, .createGroupCall:
|
||||
return false
|
||||
case let .groupCall(rhsPeerId, rhsTitle):
|
||||
if lhsTitle == rhsTitle {
|
||||
return lhsPeerId < rhsPeerId
|
||||
@ -55,7 +63,7 @@ enum CallListNodeEntry: Comparable, Identifiable {
|
||||
}
|
||||
case let .hole(lhsIndex):
|
||||
switch rhs {
|
||||
case .displayTab, .displayTabInfo, .groupCall:
|
||||
case .displayTab, .displayTabInfo, .groupCall, .createGroupCall:
|
||||
return false
|
||||
case let .hole(rhsIndex):
|
||||
return lhsIndex < rhsIndex
|
||||
@ -64,7 +72,7 @@ enum CallListNodeEntry: Comparable, Identifiable {
|
||||
}
|
||||
case let .message(lhsIndex):
|
||||
switch rhs {
|
||||
case .displayTab, .displayTabInfo, .groupCall:
|
||||
case .displayTab, .displayTabInfo, .groupCall, .createGroupCall:
|
||||
return false
|
||||
case let .hole(rhsIndex):
|
||||
return lhsIndex < rhsIndex
|
||||
@ -78,6 +86,7 @@ enum CallListNodeEntry: Comparable, Identifiable {
|
||||
|
||||
case displayTab(PresentationTheme, String, Bool)
|
||||
case displayTabInfo(PresentationTheme, String)
|
||||
case createGroupCall
|
||||
case groupCall(peer: EnginePeer, editing: Bool, isActive: Bool)
|
||||
case messageEntry(topMessage: EngineMessage, messages: [EngineMessage], theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, editing: Bool, hasActiveRevealControls: Bool, displayHeader: Bool, missed: Bool)
|
||||
case holeEntry(index: EngineMessage.Index, theme: PresentationTheme)
|
||||
@ -88,6 +97,8 @@ enum CallListNodeEntry: Comparable, Identifiable {
|
||||
return .displayTab
|
||||
case .displayTabInfo:
|
||||
return .displayTabInfo
|
||||
case .createGroupCall:
|
||||
return .createGroupCall
|
||||
case let .groupCall(peer, _, _):
|
||||
return .groupCall(peer.id, peer.compactDisplayTitle)
|
||||
case let .messageEntry(message, _, _, _, _, _, _, _, _):
|
||||
@ -103,6 +114,8 @@ enum CallListNodeEntry: Comparable, Identifiable {
|
||||
return .setting(0)
|
||||
case .displayTabInfo:
|
||||
return .setting(1)
|
||||
case .createGroupCall:
|
||||
return .setting(2)
|
||||
case let .groupCall(peer, _, _):
|
||||
return .groupCall(peer.id)
|
||||
case let .messageEntry(message, _, _, _, _, _, _, _, _):
|
||||
@ -118,82 +131,88 @@ enum CallListNodeEntry: Comparable, Identifiable {
|
||||
|
||||
static func ==(lhs: CallListNodeEntry, rhs: CallListNodeEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .displayTab(lhsTheme, lhsText, lhsValue):
|
||||
if case let .displayTab(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
case let .displayTab(lhsTheme, lhsText, lhsValue):
|
||||
if case let .displayTab(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .displayTabInfo(lhsTheme, lhsText):
|
||||
if case let .displayTabInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .createGroupCall:
|
||||
if case .createGroupCall = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .groupCall(lhsPeer, lhsEditing, lhsIsActive):
|
||||
if case let .groupCall(rhsPeer, rhsEditing, rhsIsActive) = rhs {
|
||||
if lhsPeer != rhsPeer {
|
||||
return false
|
||||
}
|
||||
case let .displayTabInfo(lhsTheme, lhsText):
|
||||
if case let .displayTabInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
if lhsEditing != rhsEditing {
|
||||
return false
|
||||
}
|
||||
case let .groupCall(lhsPeer, lhsEditing, lhsIsActive):
|
||||
if case let .groupCall(rhsPeer, rhsEditing, rhsIsActive) = rhs {
|
||||
if lhsPeer != rhsPeer {
|
||||
return false
|
||||
}
|
||||
if lhsEditing != rhsEditing {
|
||||
return false
|
||||
}
|
||||
if lhsIsActive != rhsIsActive {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
if lhsIsActive != rhsIsActive {
|
||||
return false
|
||||
}
|
||||
case let .messageEntry(lhsMessage, lhsMessages, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsEditing, lhsHasRevealControls, lhsDisplayHeader, lhsMissed):
|
||||
if case let .messageEntry(rhsMessage, rhsMessages, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsEditing, rhsHasRevealControls, rhsDisplayHeader, rhsMissed) = rhs {
|
||||
if lhsTheme !== rhsTheme {
|
||||
return false
|
||||
}
|
||||
if lhsStrings !== rhsStrings {
|
||||
return false
|
||||
}
|
||||
if lhsDateTimeFormat != rhsDateTimeFormat {
|
||||
return false
|
||||
}
|
||||
if lhsMissed != rhsMissed {
|
||||
return false
|
||||
}
|
||||
if lhsEditing != rhsEditing {
|
||||
return false
|
||||
}
|
||||
if lhsHasRevealControls != rhsHasRevealControls {
|
||||
return false
|
||||
}
|
||||
if lhsDisplayHeader != rhsDisplayHeader {
|
||||
return false
|
||||
}
|
||||
if !areMessagesEqual(lhsMessage, rhsMessage) {
|
||||
return false
|
||||
}
|
||||
if lhsMessages.count != rhsMessages.count {
|
||||
return false
|
||||
}
|
||||
for i in 0 ..< lhsMessages.count {
|
||||
if !areMessagesEqual(lhsMessages[i], rhsMessages[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .messageEntry(lhsMessage, lhsMessages, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsEditing, lhsHasRevealControls, lhsDisplayHeader, lhsMissed):
|
||||
if case let .messageEntry(rhsMessage, rhsMessages, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsEditing, rhsHasRevealControls, rhsDisplayHeader, rhsMissed) = rhs {
|
||||
if lhsTheme !== rhsTheme {
|
||||
return false
|
||||
}
|
||||
case let .holeEntry(lhsIndex, lhsTheme):
|
||||
if case let .holeEntry(rhsIndex, rhsTheme) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme {
|
||||
return true
|
||||
} else {
|
||||
if lhsStrings !== rhsStrings {
|
||||
return false
|
||||
}
|
||||
if lhsDateTimeFormat != rhsDateTimeFormat {
|
||||
return false
|
||||
}
|
||||
if lhsMissed != rhsMissed {
|
||||
return false
|
||||
}
|
||||
if lhsEditing != rhsEditing {
|
||||
return false
|
||||
}
|
||||
if lhsHasRevealControls != rhsHasRevealControls {
|
||||
return false
|
||||
}
|
||||
if lhsDisplayHeader != rhsDisplayHeader {
|
||||
return false
|
||||
}
|
||||
if !areMessagesEqual(lhsMessage, rhsMessage) {
|
||||
return false
|
||||
}
|
||||
if lhsMessages.count != rhsMessages.count {
|
||||
return false
|
||||
}
|
||||
for i in 0 ..< lhsMessages.count {
|
||||
if !areMessagesEqual(lhsMessages[i], rhsMessages[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .holeEntry(lhsIndex, lhsTheme):
|
||||
if case let .holeEntry(rhsIndex, rhsTheme) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func callListNodeEntriesForView(view: EngineCallList, groupCalls: [EnginePeer], state: CallListNodeState, showSettings: Bool, showCallsTab: Bool, isRecentCalls: Bool, currentGroupCallPeerId: EnginePeer.Id?) -> [CallListNodeEntry] {
|
||||
func callListNodeEntriesForView(view: EngineCallList, canCreateGroupCall: Bool, groupCalls: [EnginePeer], state: CallListNodeState, showSettings: Bool, showCallsTab: Bool, isRecentCalls: Bool, currentGroupCallPeerId: EnginePeer.Id?) -> [CallListNodeEntry] {
|
||||
var result: [CallListNodeEntry] = []
|
||||
for entry in view.items {
|
||||
switch entry {
|
||||
@ -217,6 +236,10 @@ func callListNodeEntriesForView(view: EngineCallList, groupCalls: [EnginePeer],
|
||||
result.append(.groupCall(peer: peer, editing: state.editing, isActive: currentGroupCallPeerId == peer.id))
|
||||
}
|
||||
}
|
||||
|
||||
if canCreateGroupCall {
|
||||
result.append(.createGroupCall)
|
||||
}
|
||||
|
||||
if showSettings {
|
||||
result.append(.displayTabInfo(state.presentationData.theme, state.presentationData.strings.CallSettings_TabIconDescription))
|
||||
|
@ -82,7 +82,19 @@ private enum Knob {
|
||||
case right
|
||||
}
|
||||
|
||||
private final class InternalGestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate {
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive press: UIPress) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private final class RecognizedTextSelectionGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate {
|
||||
private let internalDelegate = InternalGestureRecognizerDelegate()
|
||||
|
||||
private var longTapTimer: Timer?
|
||||
private var movingKnob: (Knob, CGPoint, CGPoint)?
|
||||
private var currentLocation: CGPoint?
|
||||
@ -96,7 +108,7 @@ private final class RecognizedTextSelectionGestureRecognizer: UIGestureRecognize
|
||||
override init(target: Any?, action: Selector?) {
|
||||
super.init(target: nil, action: nil)
|
||||
|
||||
self.delegate = self
|
||||
self.delegate = self.internalDelegate
|
||||
}
|
||||
|
||||
override public func reset() {
|
||||
@ -179,15 +191,6 @@ private final class RecognizedTextSelectionGestureRecognizer: UIGestureRecognize
|
||||
self.state = .ended
|
||||
}
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@available(iOS 9.0, *)
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive press: UIPress) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public final class RecognizedTextSelectionNodeView: UIView {
|
||||
|
@ -60,6 +60,8 @@ swift_library(
|
||||
"//submodules/PromptUI",
|
||||
"//submodules/TelegramUI/Components/ItemListDatePickerItem:ItemListDatePickerItem",
|
||||
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -51,9 +51,9 @@ private enum InviteLinkInviteEntryId: Hashable {
|
||||
}
|
||||
|
||||
private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
||||
case header(PresentationTheme, String, String)
|
||||
case mainLink(PresentationTheme, ExportedInvitation?)
|
||||
case manage(PresentationTheme, String, Bool)
|
||||
case header(title: String, text: String)
|
||||
case mainLink(invitation: ExportedInvitation?, isCall: Bool, isRecentlyCreated: Bool)
|
||||
case manage(text: String, standalone: Bool)
|
||||
|
||||
var stableId: InviteLinkInviteEntryId {
|
||||
switch self {
|
||||
@ -68,20 +68,20 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
||||
|
||||
static func ==(lhs: InviteLinkInviteEntry, rhs: InviteLinkInviteEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .header(lhsTheme, lhsTitle, lhsText):
|
||||
if case let .header(rhsTheme, rhsTitle, rhsText) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsText == rhsText {
|
||||
case let .header(lhsTitle, lhsText):
|
||||
if case let .header(rhsTitle, rhsText) = rhs, lhsTitle == rhsTitle, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .mainLink(lhsTheme, lhsInvitation):
|
||||
if case let .mainLink(rhsTheme, rhsInvitation) = rhs, lhsTheme === rhsTheme, lhsInvitation == rhsInvitation {
|
||||
case let .mainLink(lhsInvitation, lhsIsCall, lhsIsRecentlyCreated):
|
||||
if case let .mainLink(rhsInvitation, rhsIsCall, rhsIsRecentlyCreated) = rhs, lhsInvitation == rhsInvitation, lhsIsCall == rhsIsCall, lhsIsRecentlyCreated == rhsIsRecentlyCreated {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .manage(lhsTheme, lhsText, lhsStandalone):
|
||||
if case let .manage(rhsTheme, rhsText, rhsStandalone) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStandalone == rhsStandalone {
|
||||
case let .manage(lhsText, lhsStandalone):
|
||||
if case let .manage(rhsText, rhsStandalone) = rhs, lhsText == rhsText, lhsStandalone == rhsStandalone {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -117,23 +117,23 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
||||
|
||||
func item(account: Account, presentationData: PresentationData, interaction: InviteLinkInviteInteraction) -> ListViewItem {
|
||||
switch self {
|
||||
case let .header(theme, title, text):
|
||||
return InviteLinkInviteHeaderItem(theme: theme, title: title, text: text)
|
||||
case let .mainLink(_, invite):
|
||||
return ItemListPermanentInviteLinkItem(context: interaction.context, presentationData: ItemListPresentationData(presentationData), invite: invite, count: 0, peers: [], displayButton: true, displayImporters: false, buttonColor: nil, sectionId: 0, style: .plain, copyAction: {
|
||||
if let invite = invite {
|
||||
case let .header(title, text):
|
||||
return InviteLinkInviteHeaderItem(theme: presentationData.theme, title: title, text: text)
|
||||
case let .mainLink(invitation, isCall, isRecentlyCreated):
|
||||
return ItemListPermanentInviteLinkItem(context: interaction.context, presentationData: ItemListPresentationData(presentationData), invite: invitation, count: 0, peers: [], displayButton: true, separateButtons: isCall, displayImporters: false, isCall: isRecentlyCreated, buttonColor: nil, sectionId: 0, style: .plain, copyAction: {
|
||||
if let invite = invitation {
|
||||
interaction.copyLink(invite)
|
||||
}
|
||||
}, shareAction: {
|
||||
if let invite = invite {
|
||||
if let invite = invitation {
|
||||
interaction.shareLink(invite)
|
||||
}
|
||||
}, contextAction: { node, gesture in
|
||||
interaction.mainLinkContextAction(invite, node, gesture)
|
||||
interaction.mainLinkContextAction(invitation, node, gesture)
|
||||
}, viewAction: {
|
||||
})
|
||||
case let .manage(theme, text, standalone):
|
||||
return InviteLinkInviteManageItem(theme: theme, text: text, standalone: standalone, action: {
|
||||
case let .manage(text, standalone):
|
||||
return InviteLinkInviteManageItem(theme: presentationData.theme, text: text, standalone: standalone, action: {
|
||||
interaction.manageLinks()
|
||||
})
|
||||
}
|
||||
@ -155,19 +155,31 @@ public final class InviteLinkInviteController: ViewController {
|
||||
return self.displayNode as! Node
|
||||
}
|
||||
|
||||
public enum Mode {
|
||||
case groupOrChannel(peerId: EnginePeer.Id)
|
||||
case groupCall(link: String, isRecentlyCreated: Bool)
|
||||
}
|
||||
|
||||
public enum CompletionResult {
|
||||
case linkCopied
|
||||
}
|
||||
|
||||
private var animatedIn = false
|
||||
|
||||
private let context: AccountContext
|
||||
private let peerId: EnginePeer.Id
|
||||
private let mode: Mode
|
||||
private weak var parentNavigationController: NavigationController?
|
||||
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
fileprivate let completed: ((CompletionResult?) -> Void)?
|
||||
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: EnginePeer.Id, parentNavigationController: NavigationController?) {
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: Mode, parentNavigationController: NavigationController?, completed: ((CompletionResult?) -> Void)? = nil) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.mode = mode
|
||||
self.parentNavigationController = parentNavigationController
|
||||
self.completed = completed
|
||||
|
||||
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
@ -198,7 +210,7 @@ public final class InviteLinkInviteController: ViewController {
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = Node(context: self.context, presentationData: self.presentationData, peerId: self.peerId, controller: self)
|
||||
self.displayNode = Node(context: self.context, presentationData: self.presentationData, mode: self.mode, controller: self)
|
||||
}
|
||||
|
||||
private var didAppearOnce: Bool = false
|
||||
@ -251,8 +263,8 @@ public final class InviteLinkInviteController: ViewController {
|
||||
private weak var controller: InviteLinkInviteController?
|
||||
|
||||
private let context: AccountContext
|
||||
private let peerId: EnginePeer.Id
|
||||
private let invitesContext: PeerExportedInvitationsContext
|
||||
private let mode: InviteLinkInviteController.Mode
|
||||
private let groupOrChannelInvitesContext: PeerExportedInvitationsContext?
|
||||
|
||||
private var interaction: InviteLinkInviteInteraction?
|
||||
|
||||
@ -267,6 +279,7 @@ public final class InviteLinkInviteController: ViewController {
|
||||
private let headerBackgroundNode: ASDisplayNode
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let doneButton: HighlightableButtonNode
|
||||
private let doneButtonIconNode: ASImageNode
|
||||
private let historyBackgroundNode: ASDisplayNode
|
||||
private let historyBackgroundContentNode: ASDisplayNode
|
||||
private var floatingHeaderOffset: CGFloat?
|
||||
@ -278,15 +291,19 @@ public final class InviteLinkInviteController: ViewController {
|
||||
|
||||
private var revokeDisposable = MetaDisposable()
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, peerId: EnginePeer.Id, controller: InviteLinkInviteController) {
|
||||
init(context: AccountContext, presentationData: PresentationData, mode: InviteLinkInviteController.Mode, controller: InviteLinkInviteController) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.mode = mode
|
||||
|
||||
self.presentationData = presentationData
|
||||
self.presentationDataPromise = Promise(self.presentationData)
|
||||
self.controller = controller
|
||||
|
||||
self.invitesContext = context.engine.peers.peerExportedInvitations(peerId: peerId, adminId: nil, revoked: false, forceUpdate: false)
|
||||
if case let .groupOrChannel(peerId) = mode {
|
||||
self.groupOrChannelInvitesContext = context.engine.peers.peerExportedInvitations(peerId: peerId, adminId: nil, revoked: false, forceUpdate: false)
|
||||
} else {
|
||||
self.groupOrChannelInvitesContext = nil
|
||||
}
|
||||
|
||||
self.dimNode = ASDisplayNode()
|
||||
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||
@ -294,11 +311,12 @@ public final class InviteLinkInviteController: ViewController {
|
||||
self.contentNode = ASDisplayNode()
|
||||
|
||||
self.headerNode = ASDisplayNode()
|
||||
self.headerNode.clipsToBounds = true
|
||||
self.headerNode.clipsToBounds = false
|
||||
|
||||
self.headerBackgroundNode = ASDisplayNode()
|
||||
self.headerBackgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
self.headerBackgroundNode.cornerRadius = 16.0
|
||||
self.headerBackgroundNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.titleNode.maximumNumberOfLines = 1
|
||||
@ -306,7 +324,9 @@ public final class InviteLinkInviteController: ViewController {
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.InviteLink_InviteLink, font: Font.bold(17.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor)
|
||||
|
||||
self.doneButton = HighlightableButtonNode()
|
||||
self.doneButton.setTitle(self.presentationData.strings.Common_Done, with: Font.bold(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
|
||||
|
||||
self.doneButtonIconNode = ASImageNode()
|
||||
self.doneButtonIconNode.image = generateCloseButtonImage(backgroundColor: self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05), foregroundColor: self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.4))!
|
||||
|
||||
self.historyBackgroundNode = ASDisplayNode()
|
||||
self.historyBackgroundNode.isLayerBacked = true
|
||||
@ -332,6 +352,9 @@ public final class InviteLinkInviteController: ViewController {
|
||||
let mainInvitePromise = ValuePromise<ExportedInvitation?>(nil)
|
||||
|
||||
self.interaction = InviteLinkInviteInteraction(context: context, mainLinkContextAction: { [weak self] invite, node, gesture in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
guard let node = node as? ContextReferenceContentNode else {
|
||||
return
|
||||
}
|
||||
@ -340,7 +363,7 @@ public final class InviteLinkInviteController: ViewController {
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { _, f in
|
||||
}, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
if let invite = invite {
|
||||
@ -353,80 +376,99 @@ public final class InviteLinkInviteController: ViewController {
|
||||
}
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { _, f in
|
||||
f(.dismissWithoutContent)
|
||||
if case let .groupOrChannel(peerId) = self.mode {
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
if let invite = invite {
|
||||
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let isGroup: Bool
|
||||
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||
isGroup = false
|
||||
} else {
|
||||
isGroup = true
|
||||
}
|
||||
let updatedPresentationData = (strongSelf.presentationData, strongSelf.presentationDataPromise.get())
|
||||
let controller = QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup))
|
||||
strongSelf.controller?.present(controller, in: .window(.root))
|
||||
})
|
||||
}
|
||||
})))
|
||||
|
||||
if let invite = invite {
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
|
||||
}, action: { [ weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let isGroup: Bool
|
||||
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||
isGroup = false
|
||||
} else {
|
||||
isGroup = true
|
||||
}
|
||||
let updatedPresentationData = (strongSelf.presentationData, strongSelf.presentationDataPromise.get())
|
||||
let controller = QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup))
|
||||
strongSelf.controller?.present(controller, in: .window(.root))
|
||||
let controller = ActionSheetController(presentationData: presentationData)
|
||||
let dismissAction: () -> Void = { [weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
controller.setItemGroups([
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetTextItem(title: isGroup ? presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text : presentationData.strings.ChannelInfo_InviteLink_RevokeAlert_Text),
|
||||
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
|
||||
dismissAction()
|
||||
|
||||
if let inviteLink = invite?.link {
|
||||
let _ = (context.engine.peers.revokePeerExportedInvitation(peerId: peerId, link: inviteLink) |> deliverOnMainQueue).start(next: { result in
|
||||
if let result = result, case let .replace(_, invite) = result {
|
||||
mainInvitePromise.set(invite)
|
||||
}
|
||||
})
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkRevoked(text: presentationData.strings.InviteLink_InviteLinkRevoked), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||
}
|
||||
})
|
||||
]),
|
||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||
])
|
||||
self?.controller?.present(controller, in: .window(.root))
|
||||
})
|
||||
}
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
|
||||
}, action: { _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
let isGroup: Bool
|
||||
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||
isGroup = false
|
||||
} else {
|
||||
isGroup = true
|
||||
}
|
||||
let controller = ActionSheetController(presentationData: presentationData)
|
||||
let dismissAction: () -> Void = { [weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
controller.setItemGroups([
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetTextItem(title: isGroup ? presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text : presentationData.strings.ChannelInfo_InviteLink_RevokeAlert_Text),
|
||||
ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: {
|
||||
dismissAction()
|
||||
|
||||
if let inviteLink = invite?.link {
|
||||
let _ = (context.engine.peers.revokePeerExportedInvitation(peerId: peerId, link: inviteLink) |> deliverOnMainQueue).start(next: { result in
|
||||
if let result = result, case let .replace(_, invite) = result {
|
||||
mainInvitePromise.set(invite)
|
||||
}
|
||||
})
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkRevoked(text: presentationData.strings.InviteLink_InviteLinkRevoked), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||
}
|
||||
})
|
||||
]),
|
||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||
])
|
||||
self?.controller?.present(controller, in: .window(.root))
|
||||
})
|
||||
})))
|
||||
})))
|
||||
|
||||
}
|
||||
|
||||
let contextController = ContextController(presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
||||
self?.controller?.presentInGlobalOverlay(contextController)
|
||||
self.controller?.presentInGlobalOverlay(contextController)
|
||||
}, copyLink: { [weak self] invite in
|
||||
UIPasteboard.general.string = invite.link
|
||||
|
||||
self?.controller?.dismissAllTooltips()
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.controller?.dismissAllTooltips()
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||
if let completed = self.controller?.completed {
|
||||
self.controller?.dismiss()
|
||||
completed(.linkCopied)
|
||||
} else {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||
}
|
||||
}, shareLink: { [weak self] invite in
|
||||
guard let strongSelf = self, let inviteLink = invite.link else {
|
||||
return
|
||||
@ -496,49 +538,82 @@ public final class InviteLinkInviteController: ViewController {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let updatedPresentationData = (strongSelf.presentationData, strongSelf.presentationDataPromise.get())
|
||||
let controller = inviteLinkListController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, admin: nil)
|
||||
strongSelf.controller?.parentNavigationController?.pushViewController(controller)
|
||||
strongSelf.controller?.dismiss()
|
||||
|
||||
if case let .groupOrChannel(peerId) = strongSelf.mode {
|
||||
let updatedPresentationData = (strongSelf.presentationData, strongSelf.presentationDataPromise.get())
|
||||
let controller = inviteLinkListController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, admin: nil)
|
||||
strongSelf.controller?.parentNavigationController?.pushViewController(controller)
|
||||
strongSelf.controller?.dismiss()
|
||||
}
|
||||
})
|
||||
|
||||
let previousEntries = Atomic<[InviteLinkInviteEntry]?>(value: nil)
|
||||
|
||||
let peerView = context.account.postbox.peerView(id: peerId)
|
||||
let invites: Signal<PeerExportedInvitationsState, NoError> = .single(PeerExportedInvitationsState())
|
||||
self.disposable = (combineLatest(self.presentationDataPromise.get(), peerView, mainInvitePromise.get(), invites)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData, view, interactiveMainInvite, invites in
|
||||
if let strongSelf = self {
|
||||
switch mode {
|
||||
case let .groupOrChannel(peerId):
|
||||
let peerView = context.account.postbox.peerView(id: peerId)
|
||||
let invites: Signal<PeerExportedInvitationsState, NoError> = .single(PeerExportedInvitationsState())
|
||||
self.disposable = (combineLatest(self.presentationDataPromise.get(), peerView, mainInvitePromise.get(), invites)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData, view, interactiveMainInvite, invites in
|
||||
if let strongSelf = self {
|
||||
var entries: [InviteLinkInviteEntry] = []
|
||||
|
||||
let helpText: String
|
||||
if let peer = peerViewMainPeer(view) as? TelegramChannel, case .broadcast = peer.info {
|
||||
helpText = presentationData.strings.InviteLink_CreatePrivateLinkHelpChannel
|
||||
} else {
|
||||
helpText = presentationData.strings.InviteLink_CreatePrivateLinkHelp
|
||||
}
|
||||
entries.append(.header(title: presentationData.strings.InviteLink_InviteLink, text: helpText))
|
||||
|
||||
let mainInvite: ExportedInvitation?
|
||||
if let invite = interactiveMainInvite {
|
||||
mainInvite = invite
|
||||
} else if let cachedData = view.cachedData as? CachedGroupData, let invite = cachedData.exportedInvitation {
|
||||
mainInvite = invite
|
||||
} else if let cachedData = view.cachedData as? CachedChannelData, let invite = cachedData.exportedInvitation {
|
||||
mainInvite = invite
|
||||
} else {
|
||||
mainInvite = nil
|
||||
}
|
||||
|
||||
entries.append(.mainLink(invitation: mainInvite, isCall: false, isRecentlyCreated: false))
|
||||
entries.append(.manage(text: presentationData.strings.InviteLink_Manage, standalone: true))
|
||||
|
||||
let previousEntries = previousEntries.swap(entries)
|
||||
|
||||
let transition = preparedTransition(from: previousEntries ?? [], to: entries, isLoading: false, account: context.account, presentationData: presentationData, interaction: strongSelf.interaction!)
|
||||
strongSelf.enqueueTransition(transition)
|
||||
}
|
||||
})
|
||||
case let .groupCall(link, isRecentlyCreated):
|
||||
//TODO:release
|
||||
let tempInfo: Signal<Void, NoError> = .single(Void()) |> delay(0.0, queue: .mainQueue())
|
||||
|
||||
self.disposable = (combineLatest(queue: .mainQueue(),
|
||||
self.presentationDataPromise.get(),
|
||||
tempInfo
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData, _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
var entries: [InviteLinkInviteEntry] = []
|
||||
|
||||
let helpText: String
|
||||
if let peer = peerViewMainPeer(view) as? TelegramChannel, case .broadcast = peer.info {
|
||||
helpText = presentationData.strings.InviteLink_CreatePrivateLinkHelpChannel
|
||||
} else {
|
||||
helpText = presentationData.strings.InviteLink_CreatePrivateLinkHelp
|
||||
}
|
||||
entries.append(.header(presentationData.theme, presentationData.strings.InviteLink_InviteLink, helpText))
|
||||
//TODO:localize
|
||||
let helpText: String = "Anyone on Telegram can join your call by following the link below."
|
||||
entries.append(.header(title: "Call Link", text: helpText))
|
||||
|
||||
let mainInvite: ExportedInvitation?
|
||||
if let invite = interactiveMainInvite {
|
||||
mainInvite = invite
|
||||
} else if let cachedData = view.cachedData as? CachedGroupData, let invite = cachedData.exportedInvitation {
|
||||
mainInvite = invite
|
||||
} else if let cachedData = view.cachedData as? CachedChannelData, let invite = cachedData.exportedInvitation {
|
||||
mainInvite = invite
|
||||
} else {
|
||||
mainInvite = nil
|
||||
}
|
||||
let mainInvite: ExportedInvitation = .link(link: link, title: nil, isPermanent: true, requestApproval: false, isRevoked: false, adminId: self.context.account.peerId, date: 0, startDate: nil, expireDate: nil, usageLimit: nil, count: nil, requestedCount: nil, pricing: nil)
|
||||
|
||||
entries.append(.mainLink(presentationData.theme, mainInvite))
|
||||
entries.append(.manage(presentationData.theme, presentationData.strings.InviteLink_Manage, true))
|
||||
entries.append(.mainLink(invitation: mainInvite, isCall: true, isRecentlyCreated: isRecentlyCreated))
|
||||
|
||||
let previousEntries = previousEntries.swap(entries)
|
||||
|
||||
let transition = preparedTransition(from: previousEntries ?? [], to: entries, isLoading: false, account: context.account, presentationData: presentationData, interaction: strongSelf.interaction!)
|
||||
strongSelf.enqueueTransition(transition)
|
||||
}
|
||||
})
|
||||
let transition = preparedTransition(from: previousEntries ?? [], to: entries, isLoading: false, account: context.account, presentationData: presentationData, interaction: self.interaction!)
|
||||
self.enqueueTransition(transition)
|
||||
})
|
||||
}
|
||||
|
||||
self.listNode.preloadPages = true
|
||||
self.listNode.stackFromBottom = true
|
||||
@ -556,6 +631,7 @@ public final class InviteLinkInviteController: ViewController {
|
||||
|
||||
self.headerNode.addSubnode(self.headerBackgroundNode)
|
||||
self.headerNode.addSubnode(self.doneButton)
|
||||
self.doneButton.addSubnode(self.doneButtonIconNode)
|
||||
|
||||
self.doneButton.addTarget(self, action: #selector(self.doneButtonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
@ -591,7 +667,8 @@ public final class InviteLinkInviteController: ViewController {
|
||||
self.historyBackgroundContentNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
self.headerBackgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.InviteLink_InviteLink, font: Font.bold(17.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor)
|
||||
self.doneButton.setTitle(self.presentationData.strings.Common_Done, with: Font.bold(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
|
||||
|
||||
self.doneButtonIconNode.image = generateCloseButtonImage(backgroundColor: self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05), foregroundColor: self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.4))!
|
||||
}
|
||||
|
||||
private func enqueueTransition(_ transition: InviteLinkInviteTransaction) {
|
||||
@ -658,7 +735,10 @@ public final class InviteLinkInviteController: ViewController {
|
||||
insets.bottom = layout.intrinsicInsets.bottom
|
||||
|
||||
let headerHeight: CGFloat = 54.0
|
||||
let visibleItemsHeight: CGFloat = 409.0
|
||||
var visibleItemsHeight: CGFloat = 409.0
|
||||
if case .groupCall = self.mode {
|
||||
visibleItemsHeight += 80.0
|
||||
}
|
||||
|
||||
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
|
||||
|
||||
@ -673,20 +753,26 @@ public final class InviteLinkInviteController: ViewController {
|
||||
|
||||
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(x: 0.0, y: listTopInset), size: listNodeSize))
|
||||
|
||||
transition.updateFrame(node: self.headerBackgroundNode, frame: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: 68.0))
|
||||
transition.updateFrame(node: self.headerBackgroundNode, frame: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: 36.0))
|
||||
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width, height: headerHeight))
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: 18.0), size: titleSize)
|
||||
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
||||
|
||||
let doneSize = self.doneButton.measure(CGSize(width: layout.size.width, height: headerHeight))
|
||||
let doneFrame = CGRect(origin: CGPoint(x: layout.size.width - doneSize.width - 16.0, y: 18.0), size: doneSize)
|
||||
transition.updateFrame(node: self.doneButton, frame: doneFrame)
|
||||
if let image = self.doneButtonIconNode.image {
|
||||
let doneSize = CGSize(width: 62.0, height: 56.0)
|
||||
let doneFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - doneSize.width, y: 13.0), size: doneSize)
|
||||
transition.updateFrame(node: self.doneButton, frame: doneFrame)
|
||||
transition.updateFrame(node: self.doneButtonIconNode, frame: CGRect(origin: CGPoint(x: floor((doneFrame.width - image.size.width) / 2.0), y: floor((doneFrame.height - image.size.height) / 2.0)), size: image.size))
|
||||
}
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
let result = super.hitTest(point, with: event)
|
||||
|
||||
if let result = result, result === self.doneButton.view.hitTest(self.view.convert(point, to: self.doneButton.view), with: event) {
|
||||
return self.doneButton.view
|
||||
}
|
||||
if result === self.headerNode.view {
|
||||
return self.view
|
||||
}
|
||||
@ -807,7 +893,7 @@ public final class InviteLinkInviteController: ViewController {
|
||||
|
||||
// transition.updateAlpha(node: self.headerNode.separatorNode, alpha: isOverscrolling ? 1.0 : 0.0)
|
||||
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: controlsFrame.maxY), size: CGSize(width: validLayout.size.width, height: validLayout.size.height))
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: controlsFrame.maxY - 10.0), size: CGSize(width: validLayout.size.width, height: validLayout.size.height))
|
||||
|
||||
let previousBackgroundFrame = self.historyBackgroundNode.frame
|
||||
|
||||
@ -822,3 +908,23 @@ public final class InviteLinkInviteController: ViewController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? {
|
||||
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setLineWidth(2.0)
|
||||
context.setLineCap(.round)
|
||||
context.setStrokeColor(foregroundColor.cgColor)
|
||||
|
||||
context.beginPath()
|
||||
context.move(to: CGPoint(x: 10.0, y: 10.0))
|
||||
context.addLine(to: CGPoint(x: 20.0, y: 20.0))
|
||||
context.move(to: CGPoint(x: 20.0, y: 10.0))
|
||||
context.addLine(to: CGPoint(x: 10.0, y: 20.0))
|
||||
context.strokePath()
|
||||
})
|
||||
}
|
||||
|
@ -59,8 +59,8 @@ class InviteLinkInviteHeaderItem: ListViewItem, ItemListItem {
|
||||
}
|
||||
}
|
||||
|
||||
private let titleFont = Font.medium(23.0)
|
||||
private let textFont = Font.regular(13.0)
|
||||
private let titleFont = Font.bold(24.0)
|
||||
private let textFont = Font.regular(15.0)
|
||||
|
||||
class InviteLinkInviteHeaderItemNode: ListViewItemNode {
|
||||
private let titleNode: TextNode
|
||||
@ -102,8 +102,8 @@ class InviteLinkInviteHeaderItemNode: ListViewItemNode {
|
||||
return { item, params, neighbors in
|
||||
let leftInset: CGFloat = 40.0 + params.leftInset
|
||||
let topInset: CGFloat = 98.0
|
||||
let spacing: CGFloat = 8.0
|
||||
let bottomInset: CGFloat = 24.0
|
||||
let spacing: CGFloat = 10.0
|
||||
let bottomInset: CGFloat = 13.0
|
||||
|
||||
var updatedTheme: PresentationTheme?
|
||||
if currentItem?.theme !== item.theme {
|
||||
@ -113,7 +113,7 @@ class InviteLinkInviteHeaderItemNode: ListViewItemNode {
|
||||
let titleAttributedText = NSAttributedString(string: item.title, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor)
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let attributedText = NSAttributedString(string: item.text, font: textFont, textColor: item.theme.list.freeTextColor)
|
||||
let attributedText = NSAttributedString(string: item.text, font: textFont, textColor: item.theme.list.itemPrimaryTextColor)
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let contentSize = CGSize(width: params.width, height: topInset + titleLayout.size.height + spacing + textLayout.size.height + bottomInset)
|
||||
@ -131,14 +131,14 @@ class InviteLinkInviteHeaderItemNode: ListViewItemNode {
|
||||
}
|
||||
|
||||
let iconSize = CGSize(width: 92.0, height: 92.0)
|
||||
strongSelf.iconBackgroundNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: -10.0), size: iconSize)
|
||||
strongSelf.iconNode.frame = strongSelf.iconBackgroundNode.frame
|
||||
strongSelf.iconBackgroundNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: -18.0), size: iconSize)
|
||||
strongSelf.iconNode.frame = strongSelf.iconBackgroundNode.frame.insetBy(dx: 8.0, dy: 8.0)
|
||||
|
||||
let _ = titleApply()
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleLayout.size.width) / 2.0), y: topInset + 8.0), size: titleLayout.size)
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleLayout.size.width) / 2.0), y: topInset + 10.0), size: titleLayout.size)
|
||||
|
||||
let _ = textApply()
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - textLayout.size.width) / 2.0), y: topInset + 8.0 + titleLayout.size.height + spacing), size: textLayout.size)
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - textLayout.size.width) / 2.0), y: topInset + 10.0 + titleLayout.size.height + spacing), size: textLayout.size)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -538,7 +538,7 @@ public final class InviteLinkViewController: ViewController {
|
||||
self.headerNode.clipsToBounds = true
|
||||
|
||||
self.headerBackgroundNode = ASDisplayNode()
|
||||
self.headerBackgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
self.headerBackgroundNode.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemBackgroundColor
|
||||
self.headerBackgroundNode.cornerRadius = 16.0
|
||||
|
||||
self.titleNode = ImmediateTextNode()
|
||||
@ -1025,8 +1025,8 @@ public final class InviteLinkViewController: ViewController {
|
||||
self.presentationData = presentationData
|
||||
self.presentationDataPromise.set(.single(presentationData))
|
||||
|
||||
self.historyBackgroundContentNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
self.headerBackgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
self.historyBackgroundContentNode.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemBackgroundColor
|
||||
self.headerBackgroundNode.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemBackgroundColor
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: titleFont, textColor: self.presentationData.theme.actionSheet.primaryTextColor)
|
||||
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitleNode.attributedText?.string ?? "", font: subtitleFont, textColor: self.presentationData.theme.list.itemSecondaryTextColor)
|
||||
|
||||
|
@ -10,6 +10,10 @@ import SolidRoundedButtonNode
|
||||
import AnimatedAvatarSetNode
|
||||
import ShimmerEffect
|
||||
import TelegramCore
|
||||
import Markdown
|
||||
import TextFormat
|
||||
import ComponentFlow
|
||||
import MultilineTextComponent
|
||||
|
||||
private func actionButtonImage(color: UIColor) -> UIImage? {
|
||||
return generateImage(CGSize(width: 24.0, height: 24.0), contextGenerator: { size, context in
|
||||
@ -34,6 +38,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
|
||||
let displayButton: Bool
|
||||
let separateButtons: Bool
|
||||
let displayImporters: Bool
|
||||
let isCall: Bool
|
||||
let buttonColor: UIColor?
|
||||
public let sectionId: ItemListSectionId
|
||||
let style: ItemListStyle
|
||||
@ -52,6 +57,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
|
||||
displayButton: Bool,
|
||||
separateButtons: Bool = false,
|
||||
displayImporters: Bool,
|
||||
isCall: Bool = false,
|
||||
buttonColor: UIColor?,
|
||||
sectionId: ItemListSectionId,
|
||||
style: ItemListStyle,
|
||||
@ -69,6 +75,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
|
||||
self.displayButton = displayButton
|
||||
self.separateButtons = separateButtons
|
||||
self.displayImporters = displayImporters
|
||||
self.isCall = isCall
|
||||
self.buttonColor = buttonColor
|
||||
self.sectionId = sectionId
|
||||
self.style = style
|
||||
@ -140,6 +147,11 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
||||
private var shimmerNode: ShimmerEffectNode?
|
||||
private var absoluteLocation: (CGRect, CGSize)?
|
||||
|
||||
private var justCreatedCallTextNode: TextNode?
|
||||
private var justCreatedCallLeftSeparatorLayer: SimpleLayer?
|
||||
private var justCreatedCallRightSeparatorLayer: SimpleLayer?
|
||||
private var justCreatedCallSeparatorText: ComponentView<Empty>?
|
||||
|
||||
private let activateArea: AccessibilityAreaNode
|
||||
|
||||
private var item: ItemListPermanentInviteLinkItem?
|
||||
@ -287,6 +299,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
||||
public func asyncLayout() -> (_ item: ItemListPermanentInviteLinkItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let makeAddressLayout = TextNode.asyncLayout(self.addressNode)
|
||||
let makeInvitedPeersLayout = TextNode.asyncLayout(self.invitedPeersNode)
|
||||
let makeJustCreatedCallTextNodeLayout = TextNode.asyncLayout(self.justCreatedCallTextNode)
|
||||
|
||||
let currentItem = self.item
|
||||
let avatarsContext = self.avatarsContext
|
||||
@ -330,14 +343,56 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
||||
|
||||
let (invitedPeersLayout, invitedPeersApply) = makeInvitedPeersLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: subtitle, font: titleFont, textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
var justCreatedCallTextNodeLayout: (TextNodeLayout, () -> TextNode?)?
|
||||
if item.isCall {
|
||||
let chevronImage = generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: item.presentationData.theme.list.itemAccentColor)
|
||||
|
||||
let textFont = Font.regular(15.0)
|
||||
let boldTextFont = Font.semibold(15.0)
|
||||
let textColor = item.presentationData.theme.list.itemPrimaryTextColor
|
||||
let accentColor = item.presentationData.theme.list.itemAccentColor
|
||||
let markdownAttributes = MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
|
||||
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
|
||||
link: MarkdownAttributeSet(font: textFont, textColor: accentColor),
|
||||
linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
}
|
||||
)
|
||||
//TODO:localize
|
||||
let justCreatedCallTextAttributedString = parseMarkdownIntoAttributedString("Be the first to join the call and add people from there. [Open Call >]()", attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString
|
||||
if let range = justCreatedCallTextAttributedString.string.range(of: ">"), let chevronImage {
|
||||
justCreatedCallTextAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: justCreatedCallTextAttributedString.string))
|
||||
}
|
||||
|
||||
justCreatedCallTextNodeLayout = makeJustCreatedCallTextNodeLayout(TextNodeLayoutArguments(
|
||||
attributedString: justCreatedCallTextAttributedString,
|
||||
backgroundColor: nil,
|
||||
maximumNumberOfLines: 0,
|
||||
truncationType: .end,
|
||||
constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude),
|
||||
alignment: .center,
|
||||
lineSpacing: 0.28,
|
||||
cutout: nil,
|
||||
insets: UIEdgeInsets()
|
||||
))
|
||||
}
|
||||
|
||||
let avatarsContent = avatarsContext.update(peers: item.peers, animated: false)
|
||||
|
||||
let verticalInset: CGFloat = 16.0
|
||||
let fieldHeight: CGFloat = 52.0
|
||||
let fieldSpacing: CGFloat = 16.0
|
||||
let buttonHeight: CGFloat = 50.0
|
||||
let justCreatedCallSeparatorSpacing: CGFloat = 16.0
|
||||
let justCreatedCallTextSpacing: CGFloat = 45.0
|
||||
|
||||
var height = verticalInset * 2.0 + fieldHeight + fieldSpacing + buttonHeight + 54.0
|
||||
|
||||
if let justCreatedCallTextNodeLayout {
|
||||
height += justCreatedCallTextSpacing - 2.0
|
||||
height += justCreatedCallTextNodeLayout.0.size.height
|
||||
}
|
||||
|
||||
switch item.style {
|
||||
case .plain:
|
||||
@ -514,6 +569,81 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
||||
|
||||
let _ = shareButtonNode.updateLayout(width: buttonWidth, transition: .immediate)
|
||||
shareButtonNode.frame = CGRect(x: shareButtonOriginX, y: verticalInset + fieldHeight + fieldSpacing, width: buttonWidth, height: buttonHeight)
|
||||
|
||||
if let justCreatedCallTextNodeLayout {
|
||||
if let justCreatedCallTextNode = justCreatedCallTextNodeLayout.1() {
|
||||
if strongSelf.justCreatedCallTextNode !== justCreatedCallTextNode {
|
||||
strongSelf.justCreatedCallTextNode?.removeFromSupernode()
|
||||
strongSelf.justCreatedCallTextNode = justCreatedCallTextNode
|
||||
strongSelf.addSubnode(justCreatedCallTextNode)
|
||||
}
|
||||
let justCreatedCallTextNodeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - justCreatedCallTextNodeLayout.0.size.width) / 2.0), y: shareButtonNode.frame.maxY + justCreatedCallTextSpacing), size: CGSize(width: justCreatedCallTextNodeLayout.0.size.width, height: justCreatedCallTextNodeLayout.0.size.height))
|
||||
justCreatedCallTextNode.frame = justCreatedCallTextNodeFrame
|
||||
|
||||
let justCreatedCallSeparatorText: ComponentView<Empty>
|
||||
if let current = strongSelf.justCreatedCallSeparatorText {
|
||||
justCreatedCallSeparatorText = current
|
||||
} else {
|
||||
justCreatedCallSeparatorText = ComponentView()
|
||||
strongSelf.justCreatedCallSeparatorText = justCreatedCallSeparatorText
|
||||
}
|
||||
|
||||
let justCreatedCallLeftSeparatorLayer: SimpleLayer
|
||||
if let current = strongSelf.justCreatedCallLeftSeparatorLayer {
|
||||
justCreatedCallLeftSeparatorLayer = current
|
||||
} else {
|
||||
justCreatedCallLeftSeparatorLayer = SimpleLayer()
|
||||
strongSelf.justCreatedCallLeftSeparatorLayer = justCreatedCallLeftSeparatorLayer
|
||||
strongSelf.layer.addSublayer(justCreatedCallLeftSeparatorLayer)
|
||||
}
|
||||
|
||||
let justCreatedCallRightSeparatorLayer: SimpleLayer
|
||||
if let current = strongSelf.justCreatedCallRightSeparatorLayer {
|
||||
justCreatedCallRightSeparatorLayer = current
|
||||
} else {
|
||||
justCreatedCallRightSeparatorLayer = SimpleLayer()
|
||||
strongSelf.justCreatedCallRightSeparatorLayer = justCreatedCallRightSeparatorLayer
|
||||
strongSelf.layer.addSublayer(justCreatedCallRightSeparatorLayer)
|
||||
}
|
||||
|
||||
justCreatedCallLeftSeparatorLayer.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor.cgColor
|
||||
justCreatedCallRightSeparatorLayer.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor.cgColor
|
||||
|
||||
let justCreatedCallSeparatorTextSize = justCreatedCallSeparatorText.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: item.presentationData.strings.SendInviteLink_PremiumOrSendSectionSeparator, font: Font.regular(15.0), textColor: item.presentationData.theme.list.itemSecondaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: params.width - leftInset - rightInset, height: 100.0)
|
||||
)
|
||||
let justCreatedCallSeparatorTextFrame = CGRect(origin: CGPoint(x: floor((params.width - justCreatedCallSeparatorTextSize.width) * 0.5), y: shareButtonNode.frame.maxY + justCreatedCallSeparatorSpacing), size: justCreatedCallSeparatorTextSize)
|
||||
if let justCreatedCallSeparatorTextView = justCreatedCallSeparatorText.view {
|
||||
if justCreatedCallSeparatorTextView.superview == nil {
|
||||
strongSelf.view.addSubview(justCreatedCallSeparatorTextView)
|
||||
}
|
||||
justCreatedCallSeparatorTextView.frame = justCreatedCallSeparatorTextFrame
|
||||
}
|
||||
|
||||
let separatorWidth: CGFloat = 72.0
|
||||
let separatorSpacing: CGFloat = 10.0
|
||||
|
||||
justCreatedCallLeftSeparatorLayer.frame = CGRect(origin: CGPoint(x: justCreatedCallSeparatorTextFrame.minX - separatorSpacing - separatorWidth, y: justCreatedCallSeparatorTextFrame.midY + 1.0), size: CGSize(width: separatorWidth, height: UIScreenPixel))
|
||||
justCreatedCallRightSeparatorLayer.frame = CGRect(origin: CGPoint(x: justCreatedCallSeparatorTextFrame.maxX + separatorSpacing, y: justCreatedCallSeparatorTextFrame.midY + 1.0), size: CGSize(width: separatorWidth, height: UIScreenPixel))
|
||||
}
|
||||
} else if let justCreatedCallTextNode = strongSelf.justCreatedCallTextNode {
|
||||
strongSelf.justCreatedCallTextNode = nil
|
||||
justCreatedCallTextNode.removeFromSupernode()
|
||||
|
||||
strongSelf.justCreatedCallLeftSeparatorLayer?.removeFromSuperlayer()
|
||||
strongSelf.justCreatedCallLeftSeparatorLayer = nil
|
||||
|
||||
strongSelf.justCreatedCallRightSeparatorLayer?.removeFromSuperlayer()
|
||||
strongSelf.justCreatedCallRightSeparatorLayer = nil
|
||||
|
||||
strongSelf.justCreatedCallSeparatorText?.view?.removeFromSuperview()
|
||||
strongSelf.justCreatedCallSeparatorText = nil
|
||||
}
|
||||
|
||||
var totalWidth = invitedPeersLayout.size.width
|
||||
var leftOrigin: CGFloat = floorToScreenPixels((params.width - invitedPeersLayout.size.width) / 2.0)
|
||||
|
@ -30,10 +30,11 @@ public class ItemListPeerActionItem: ListViewItem, ItemListItem {
|
||||
let editing: Bool
|
||||
let height: ItemListPeerActionItemHeight
|
||||
let color: ItemListPeerActionItemColor
|
||||
let noInsets: Bool
|
||||
public let sectionId: ItemListSectionId
|
||||
public let action: (() -> Void)?
|
||||
|
||||
public init(presentationData: ItemListPresentationData, icon: UIImage?, iconSignal: Signal<UIImage?, NoError>? = nil, title: String, additionalBadgeIcon: UIImage? = nil, alwaysPlain: Bool = false, hasSeparator: Bool = true, sectionId: ItemListSectionId, height: ItemListPeerActionItemHeight = .peerList, color: ItemListPeerActionItemColor = .accent, editing: Bool = false, action: (() -> Void)?) {
|
||||
public init(presentationData: ItemListPresentationData, icon: UIImage?, iconSignal: Signal<UIImage?, NoError>? = nil, title: String, additionalBadgeIcon: UIImage? = nil, alwaysPlain: Bool = false, hasSeparator: Bool = true, sectionId: ItemListSectionId, height: ItemListPeerActionItemHeight = .peerList, color: ItemListPeerActionItemColor = .accent, noInsets: Bool = false, editing: Bool = false, action: (() -> Void)?) {
|
||||
self.presentationData = presentationData
|
||||
self.icon = icon
|
||||
self.iconSignal = iconSignal
|
||||
@ -43,6 +44,7 @@ public class ItemListPeerActionItem: ListViewItem, ItemListItem {
|
||||
self.hasSeparator = hasSeparator
|
||||
self.editing = editing
|
||||
self.height = height
|
||||
self.noInsets = noInsets
|
||||
self.color = color
|
||||
self.sectionId = sectionId
|
||||
self.action = action
|
||||
@ -217,7 +219,11 @@ public final class ItemListPeerActionItemNode: ListViewItemNode {
|
||||
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
let insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
var insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
if item.noInsets {
|
||||
insets.top = 0.0
|
||||
insets.bottom = 0.0
|
||||
}
|
||||
let contentSize = CGSize(width: params.width, height: titleLayout.size.height + verticalInset * 2.0)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
|
@ -11,7 +11,7 @@ import PresentationDataUtils
|
||||
import UndoUI
|
||||
import OldChannelsController
|
||||
|
||||
public final class JoinLinkPreviewController: ViewController {
|
||||
public final class LegacyJoinLinkPreviewController: ViewController {
|
||||
private var controllerNode: JoinLinkPreviewControllerNode {
|
||||
return self.displayNode as! JoinLinkPreviewControllerNode
|
||||
}
|
||||
@ -184,3 +184,39 @@ public final class JoinLinkPreviewController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
public func JoinLinkPreviewController(
|
||||
context: AccountContext,
|
||||
link: String,
|
||||
navigateToPeer: @escaping (EnginePeer, ChatPeekTimeout?) -> Void,
|
||||
parentNavigationController: NavigationController?,
|
||||
resolvedState: ExternalJoiningChatState? = nil
|
||||
) -> ViewController {
|
||||
if let data = context.currentAppConfiguration.with({ $0 }).data, data["ios_killswitch_legacy_join_link"] != nil {
|
||||
return LegacyJoinLinkPreviewController(context: context, link: link, navigateToPeer: navigateToPeer, parentNavigationController: parentNavigationController, resolvedState: resolvedState)
|
||||
} else if case let .invite(invite) = resolvedState, !invite.flags.requestNeeded, !invite.flags.isBroadcast, !invite.flags.canRefulfillSubscription {
|
||||
//TODO:release
|
||||
|
||||
var verificationStatus: JoinSubjectScreenMode.Group.VerificationStatus?
|
||||
if invite.flags.isFake {
|
||||
verificationStatus = .fake
|
||||
} else if invite.flags.isScam {
|
||||
verificationStatus = .scam
|
||||
} else if invite.flags.isVerified {
|
||||
verificationStatus = .verified
|
||||
}
|
||||
return context.sharedContext.makeJoinSubjectScreen(context: context, mode: .group(JoinSubjectScreenMode.Group(
|
||||
link: link,
|
||||
isGroup: !invite.flags.isChannel,
|
||||
isPublic: invite.flags.isPublic,
|
||||
isRequest: invite.flags.requestNeeded,
|
||||
verificationStatus: verificationStatus,
|
||||
image: invite.photoRepresentation,
|
||||
title: invite.title,
|
||||
about: invite.about,
|
||||
memberCount: invite.participantsCount,
|
||||
members: invite.participants ?? []
|
||||
)))
|
||||
} else {
|
||||
return LegacyJoinLinkPreviewController(context: context, link: link, navigateToPeer: navigateToPeer, parentNavigationController: parentNavigationController, resolvedState: resolvedState)
|
||||
}
|
||||
}
|
||||
|
@ -655,7 +655,7 @@ public func channelMembersController(context: AccountContext, updatedPresentatio
|
||||
}, inviteViaLink: {
|
||||
if let controller = getControllerImpl?() {
|
||||
dismissInputImpl?()
|
||||
presentControllerImpl?(InviteLinkInviteController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, parentNavigationController: controller.navigationController as? NavigationController), nil)
|
||||
presentControllerImpl?(InviteLinkInviteController(context: context, updatedPresentationData: updatedPresentationData, mode: .groupOrChannel(peerId: peerId), parentNavigationController: controller.navigationController as? NavigationController), nil)
|
||||
}
|
||||
}, updateHideMembers: { value in
|
||||
let _ = context.engine.peers.updateChannelMembersHidden(peerId: peerId, value: value).start()
|
||||
|
@ -119,6 +119,7 @@ swift_library(
|
||||
"//submodules/Components/BlurredBackgroundComponent",
|
||||
"//submodules/DirectMediaImageCache",
|
||||
"//submodules/FastBlur",
|
||||
"//submodules/InviteLinksUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -1680,6 +1680,10 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func deactivateIncomingAudio() {
|
||||
self.ongoingContext?.deactivateIncomingAudio()
|
||||
}
|
||||
}
|
||||
|
||||
func sampleBufferFromPixelBuffer(pixelBuffer: CVPixelBuffer) -> CMSampleBuffer? {
|
||||
|
@ -539,6 +539,7 @@ private final class ScreencastInProcessIPCContext: ScreencastIPCContext {
|
||||
onMutedSpeechActivityDetected: { _ in },
|
||||
encryptionKey: nil,
|
||||
isConference: self.isConference,
|
||||
audioIsActiveByDefault: true,
|
||||
isStream: false,
|
||||
sharedAudioDevice: nil
|
||||
)
|
||||
@ -1937,6 +1938,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
} else {
|
||||
contextAudioSessionActive = self.audioSessionActive.get()
|
||||
}
|
||||
|
||||
var audioIsActiveByDefault = true
|
||||
if self.isConference && self.conferenceSourceId != nil {
|
||||
audioIsActiveByDefault = false
|
||||
}
|
||||
|
||||
genericCallContext = .call(OngoingGroupCallContext(audioSessionActive: contextAudioSessionActive, video: self.videoCapturer, requestMediaChannelDescriptions: { [weak self] ssrcs, completion in
|
||||
let disposable = MetaDisposable()
|
||||
@ -1963,7 +1969,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
self.onMutedSpeechActivityDetected?(value)
|
||||
}
|
||||
}, encryptionKey: encryptionKey, isConference: self.isConference, isStream: self.isStream, sharedAudioDevice: self.sharedAudioContext?.audioDevice))
|
||||
}, encryptionKey: encryptionKey, isConference: self.isConference, audioIsActiveByDefault: audioIsActiveByDefault, isStream: self.isStream, sharedAudioDevice: self.sharedAudioContext?.audioDevice))
|
||||
}
|
||||
|
||||
self.genericCallContext = genericCallContext
|
||||
@ -2756,6 +2762,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
private func activateIncomingAudioIfNeeded() {
|
||||
if let genericCallContext = self.genericCallContext, case let .call(groupCall) = genericCallContext {
|
||||
groupCall.activateIncomingAudio()
|
||||
if let pendingDisconnedUpgradedConferenceCall = self.pendingDisconnedUpgradedConferenceCall {
|
||||
pendingDisconnedUpgradedConferenceCall.deactivateIncomingAudio()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,9 @@ import SwiftSignalKit
|
||||
import PeerInfoUI
|
||||
import OverlayStatusController
|
||||
import PresentationDataUtils
|
||||
import InviteLinksUI
|
||||
import UndoUI
|
||||
import TelegramPresentationData
|
||||
|
||||
extension VideoChatScreenComponent.View {
|
||||
func openInviteMembers() {
|
||||
@ -13,6 +16,31 @@ extension VideoChatScreenComponent.View {
|
||||
return
|
||||
}
|
||||
|
||||
if groupCall.accountContext.sharedContext.immediateExperimentalUISettings.conferenceDebug {
|
||||
guard let navigationController = self.environment?.controller()?.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
var presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with { $0 }
|
||||
presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||
let controller = InviteLinkInviteController(context: groupCall.accountContext, updatedPresentationData: (initial: presentationData, signal: .single(presentationData)), mode: .groupCall(link: "https://t.me/call/+abbfbffll123", isRecentlyCreated: false), parentNavigationController: navigationController, completed: { [weak self] result in
|
||||
guard let self, case let .group(groupCall) = self.currentCall else {
|
||||
return
|
||||
}
|
||||
if let result {
|
||||
switch result {
|
||||
case .linkCopied:
|
||||
//TODO:localize
|
||||
let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with { $0 }
|
||||
self.environment?.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: "Call link copied.", customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in
|
||||
return false
|
||||
}), in: .current)
|
||||
}
|
||||
}
|
||||
})
|
||||
self.environment?.controller()?.present(controller, in: .window(.root), with: nil)
|
||||
return
|
||||
}
|
||||
|
||||
if groupCall.isConference {
|
||||
var disablePeerIds: [EnginePeer.Id] = []
|
||||
disablePeerIds.append(groupCall.accountContext.account.peerId)
|
||||
|
@ -1326,21 +1326,66 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
navigationController?.pushViewController(controller)
|
||||
})
|
||||
} else {
|
||||
strongSelf.presentController(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
|
||||
let joinLinkPreviewController = JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
|
||||
openPeer(peer, peekData)
|
||||
}, parentNavigationController: navigationController, resolvedState: resolvedState), .window(.root), nil)
|
||||
}, parentNavigationController: navigationController, resolvedState: resolvedState)
|
||||
if joinLinkPreviewController.navigationPresentation == .flatModal {
|
||||
strongSelf.pushController(joinLinkPreviewController)
|
||||
} else {
|
||||
strongSelf.presentController(joinLinkPreviewController, .window(.root), nil)
|
||||
}
|
||||
}
|
||||
default:
|
||||
strongSelf.presentController(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
|
||||
let joinLinkPreviewController = JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
|
||||
openPeer(peer, peekData)
|
||||
}, parentNavigationController: navigationController, resolvedState: resolvedState), .window(.root), nil)
|
||||
}, parentNavigationController: navigationController, resolvedState: resolvedState)
|
||||
if joinLinkPreviewController.navigationPresentation == .flatModal {
|
||||
strongSelf.pushController(joinLinkPreviewController)
|
||||
} else {
|
||||
strongSelf.presentController(joinLinkPreviewController, .window(.root), nil)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
strongSelf.presentController(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
|
||||
let joinLinkPreviewController = JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
|
||||
openPeer(peer, peekData)
|
||||
}, parentNavigationController: navigationController), .window(.root), nil)
|
||||
}, parentNavigationController: navigationController, resolvedState: nil)
|
||||
if joinLinkPreviewController.navigationPresentation == .flatModal {
|
||||
strongSelf.pushController(joinLinkPreviewController)
|
||||
} else {
|
||||
strongSelf.presentController(joinLinkPreviewController, .window(.root), nil)
|
||||
}
|
||||
}
|
||||
case let .joinCall(link):
|
||||
let context = strongSelf.context
|
||||
let navigationController = strongSelf.getNavigationController()
|
||||
|
||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||
progress?.set(.single(true))
|
||||
return ActionDisposable {
|
||||
Queue.mainQueue().async() {
|
||||
progress?.set(.single(false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
|> delay(0.1, queue: Queue.mainQueue())
|
||||
let progressDisposable = progressSignal.startStrict()
|
||||
|
||||
var signal = context.engine.peers.joinCallLinkInformation(link)
|
||||
signal = signal
|
||||
|> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
progressDisposable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
let _ = (signal
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak navigationController] resolvedCallLink in
|
||||
navigationController?.pushViewController(context.sharedContext.makeJoinSubjectScreen(context: context, mode: JoinSubjectScreenMode.groupCall(JoinSubjectScreenMode.GroupCall(
|
||||
inviter: resolvedCallLink.inviter, members: resolvedCallLink.members, totalMemberCount: resolvedCallLink.totalMemberCount
|
||||
))))
|
||||
})
|
||||
case let .localization(identifier):
|
||||
strongSelf.presentController(LanguageLinkPreviewController(context: strongSelf.context, identifier: identifier), .window(.root), nil)
|
||||
case .proxy, .confirmationCode, .cancelAccountReset, .share:
|
||||
|
@ -2726,10 +2726,9 @@ private func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor:
|
||||
context.setLineCap(.round)
|
||||
context.setStrokeColor(foregroundColor.cgColor)
|
||||
|
||||
context.beginPath()
|
||||
context.move(to: CGPoint(x: 10.0, y: 10.0))
|
||||
context.addLine(to: CGPoint(x: 20.0, y: 20.0))
|
||||
context.strokePath()
|
||||
|
||||
context.move(to: CGPoint(x: 20.0, y: 10.0))
|
||||
context.addLine(to: CGPoint(x: 10.0, y: 20.0))
|
||||
context.strokePath()
|
||||
|
36
submodules/TelegramUI/Components/JoinSubjectScreen/BUILD
Normal file
36
submodules/TelegramUI/Components/JoinSubjectScreen/BUILD
Normal file
@ -0,0 +1,36 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "JoinSubjectScreen",
|
||||
module_name = "JoinSubjectScreen",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/Display",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/ViewControllerComponent",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/Markdown",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/Components/BalancedTextComponent",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/AvatarNode",
|
||||
"//submodules/TelegramStringFormatting",
|
||||
"//submodules/AnimatedAvatarSetNode",
|
||||
"//submodules/UndoUI",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/PresentationDataUtils",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
File diff suppressed because it is too large
Load Diff
@ -14084,7 +14084,7 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa
|
||||
|
||||
createInviteLinkImpl = { [weak contactsController] in
|
||||
contactsController?.view.window?.endEditing(true)
|
||||
contactsController?.present(InviteLinkInviteController(context: context, updatedPresentationData: updatedPresentationData, peerId: groupPeer.id, parentNavigationController: contactsController?.navigationController as? NavigationController), in: .window(.root))
|
||||
contactsController?.present(InviteLinkInviteController(context: context, updatedPresentationData: updatedPresentationData, mode: .groupOrChannel(peerId: groupPeer.id), parentNavigationController: contactsController?.navigationController as? NavigationController), in: .window(.root))
|
||||
}
|
||||
|
||||
parentController?.push(contactsController)
|
||||
|
@ -235,13 +235,6 @@ private func oldChannelsEntries(presentationData: PresentationData, state: OldCh
|
||||
return entries
|
||||
}
|
||||
|
||||
|
||||
public enum OldChannelsControllerIntent {
|
||||
case join
|
||||
case create
|
||||
case upgrade
|
||||
}
|
||||
|
||||
public func oldChannelsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, intent: OldChannelsControllerIntent, completed: @escaping (Bool) -> Void = { _ in }) -> ViewController {
|
||||
let initialState = OldChannelsState()
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
|
@ -339,21 +339,65 @@ func openResolvedUrlImpl(
|
||||
navigationController?.pushViewController(controller)
|
||||
})
|
||||
} else {
|
||||
present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
|
||||
let joinLinkPreviewController = JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
|
||||
openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: peekData))
|
||||
}, parentNavigationController: navigationController, resolvedState: resolvedState), nil)
|
||||
}, parentNavigationController: navigationController, resolvedState: resolvedState)
|
||||
if joinLinkPreviewController.navigationPresentation == .flatModal {
|
||||
navigationController?.pushViewController(joinLinkPreviewController)
|
||||
} else {
|
||||
present(joinLinkPreviewController, nil)
|
||||
}
|
||||
}
|
||||
default:
|
||||
present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
|
||||
let joinLinkPreviewController = JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
|
||||
openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: peekData))
|
||||
}, parentNavigationController: navigationController, resolvedState: resolvedState), nil)
|
||||
}, parentNavigationController: navigationController, resolvedState: resolvedState)
|
||||
if joinLinkPreviewController.navigationPresentation == .flatModal {
|
||||
navigationController?.pushViewController(joinLinkPreviewController)
|
||||
} else {
|
||||
present(joinLinkPreviewController, nil)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
|
||||
let joinLinkPreviewController = JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
|
||||
openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: peekData))
|
||||
}, parentNavigationController: navigationController), nil)
|
||||
}, parentNavigationController: navigationController, resolvedState: nil)
|
||||
if joinLinkPreviewController.navigationPresentation == .flatModal {
|
||||
navigationController?.pushViewController(joinLinkPreviewController)
|
||||
} else {
|
||||
present(joinLinkPreviewController, nil)
|
||||
}
|
||||
}
|
||||
case let .joinCall(link):
|
||||
dismissInput()
|
||||
|
||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||
progress?.set(.single(true))
|
||||
return ActionDisposable {
|
||||
Queue.mainQueue().async() {
|
||||
progress?.set(.single(false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
|> delay(0.1, queue: Queue.mainQueue())
|
||||
let progressDisposable = progressSignal.startStrict()
|
||||
|
||||
var signal = context.engine.peers.joinCallLinkInformation(link)
|
||||
signal = signal
|
||||
|> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
progressDisposable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
let _ = (signal
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak navigationController] resolvedCallLink in
|
||||
navigationController?.pushViewController(context.sharedContext.makeJoinSubjectScreen(context: context, mode: JoinSubjectScreenMode.groupCall(JoinSubjectScreenMode.GroupCall(
|
||||
inviter: resolvedCallLink.inviter, members: resolvedCallLink.members, totalMemberCount: resolvedCallLink.totalMemberCount
|
||||
))))
|
||||
})
|
||||
case let .localization(identifier):
|
||||
dismissInput()
|
||||
present(LanguageLinkPreviewController(context: context, identifier: identifier), nil)
|
||||
|
@ -78,6 +78,8 @@ import AffiliateProgramSetupScreen
|
||||
import GalleryUI
|
||||
import ShareController
|
||||
import AccountFreezeInfoScreen
|
||||
import JoinSubjectScreen
|
||||
import OldChannelsController
|
||||
|
||||
private final class AccountUserInterfaceInUseContext {
|
||||
let subscribers = Bag<(Bool) -> Void>()
|
||||
@ -473,6 +475,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
|> deliverOnMainQueue).start(next: { sharedData in
|
||||
if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings]?.get(ExperimentalUISettings.self) {
|
||||
let _ = immediateExperimentalUISettingsValue.swap(settings)
|
||||
|
||||
flatBuffers_checkedGet = settings.checkSerializedData
|
||||
}
|
||||
})
|
||||
|
||||
@ -3584,6 +3588,14 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
return JoinAffiliateProgramScreen(context: context, sourcePeer: sourcePeer, commissionPermille: commissionPermille, programDuration: programDuration, revenuePerUser: revenuePerUser, mode: mode)
|
||||
}
|
||||
|
||||
public func makeJoinSubjectScreen(context: AccountContext, mode: JoinSubjectScreenMode) -> ViewController {
|
||||
return JoinSubjectScreen(context: context, mode: mode)
|
||||
}
|
||||
|
||||
public func makeOldChannelsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, intent: OldChannelsControllerIntent, completed: @escaping (Bool) -> Void) -> ViewController {
|
||||
return oldChannelsController(context: context, updatedPresentationData: updatedPresentationData, intent: intent, completed: completed)
|
||||
}
|
||||
|
||||
public func makeGalleryController(context: AccountContext, source: GalleryControllerItemSource, streamSingleVideo: Bool, isPreview: Bool) -> ViewController {
|
||||
let controller = GalleryController(context: context, source: source, streamSingleVideo: streamSingleVideo, replaceRootController: { _, _ in
|
||||
}, baseNavigationController: nil)
|
||||
|
@ -497,6 +497,7 @@ public final class OngoingGroupCallContext {
|
||||
onMutedSpeechActivityDetected: @escaping (Bool) -> Void,
|
||||
encryptionKey: Data?,
|
||||
isConference: Bool,
|
||||
audioIsActiveByDefault: Bool,
|
||||
isStream: Bool,
|
||||
sharedAudioDevice: OngoingCallContext.AudioDevice?
|
||||
) {
|
||||
@ -632,7 +633,8 @@ public final class OngoingGroupCallContext {
|
||||
},
|
||||
audioDevice: audioDevice?.impl,
|
||||
encryptionKey: encryptionKey,
|
||||
isConference: isConference
|
||||
isConference: isConference,
|
||||
isActiveByDefault: audioIsActiveByDefault
|
||||
)
|
||||
#else
|
||||
self.context = GroupCallThreadLocalContext(
|
||||
@ -732,7 +734,8 @@ public final class OngoingGroupCallContext {
|
||||
statsLogPath: tempStatsLogPath,
|
||||
audioDevice: nil,
|
||||
encryptionKey: encryptionKey,
|
||||
isConference: isConference
|
||||
isConference: isConference,
|
||||
isActiveByDefault: true
|
||||
)
|
||||
#endif
|
||||
|
||||
@ -1181,10 +1184,10 @@ public final class OngoingGroupCallContext {
|
||||
}
|
||||
}
|
||||
|
||||
public init(inputDeviceId: String = "", outputDeviceId: String = "", audioSessionActive: Signal<Bool, NoError>, video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set<UInt32>, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, disableAudioInput: Bool, enableSystemMute: Bool, preferX264: Bool, logPath: String, onMutedSpeechActivityDetected: @escaping (Bool) -> Void, encryptionKey: Data?, isConference: Bool, isStream: Bool, sharedAudioDevice: OngoingCallContext.AudioDevice?) {
|
||||
public init(inputDeviceId: String = "", outputDeviceId: String = "", audioSessionActive: Signal<Bool, NoError>, video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set<UInt32>, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, disableAudioInput: Bool, enableSystemMute: Bool, preferX264: Bool, logPath: String, onMutedSpeechActivityDetected: @escaping (Bool) -> Void, encryptionKey: Data?, isConference: Bool, audioIsActiveByDefault: Bool, isStream: Bool, sharedAudioDevice: OngoingCallContext.AudioDevice?) {
|
||||
let queue = self.queue
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
return Impl(queue: queue, inputDeviceId: inputDeviceId, outputDeviceId: outputDeviceId, audioSessionActive: audioSessionActive, video: video, requestMediaChannelDescriptions: requestMediaChannelDescriptions, rejoinNeeded: rejoinNeeded, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: videoContentType, enableNoiseSuppression: enableNoiseSuppression, disableAudioInput: disableAudioInput, enableSystemMute: enableSystemMute, preferX264: preferX264, logPath: logPath, onMutedSpeechActivityDetected: onMutedSpeechActivityDetected, encryptionKey: encryptionKey, isConference: isConference, isStream: isStream, sharedAudioDevice: sharedAudioDevice)
|
||||
return Impl(queue: queue, inputDeviceId: inputDeviceId, outputDeviceId: outputDeviceId, audioSessionActive: audioSessionActive, video: video, requestMediaChannelDescriptions: requestMediaChannelDescriptions, rejoinNeeded: rejoinNeeded, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: videoContentType, enableNoiseSuppression: enableNoiseSuppression, disableAudioInput: disableAudioInput, enableSystemMute: enableSystemMute, preferX264: preferX264, logPath: logPath, onMutedSpeechActivityDetected: onMutedSpeechActivityDetected, encryptionKey: encryptionKey, isConference: isConference, audioIsActiveByDefault: audioIsActiveByDefault, isStream: isStream, sharedAudioDevice: sharedAudioDevice)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -286,6 +286,7 @@ private protocol OngoingCallThreadLocalContextProtocol: AnyObject {
|
||||
func nativeGetDerivedState() -> Data
|
||||
func addExternalAudioData(data: Data)
|
||||
func nativeSetIsAudioSessionActive(isActive: Bool)
|
||||
func nativeDeactivateIncomingAudio()
|
||||
}
|
||||
|
||||
private final class OngoingCallThreadLocalContextHolder {
|
||||
@ -692,6 +693,10 @@ extension OngoingCallThreadLocalContextWebrtc: OngoingCallThreadLocalContextProt
|
||||
self.addExternalAudioData(data)
|
||||
}
|
||||
|
||||
func nativeDeactivateIncomingAudio() {
|
||||
self.deactivateIncomingAudio()
|
||||
}
|
||||
|
||||
func nativeSetIsAudioSessionActive(isActive: Bool) {
|
||||
#if os(iOS)
|
||||
self.setManualAudioSessionIsActive(isActive)
|
||||
@ -1394,6 +1399,12 @@ public final class OngoingCallContext {
|
||||
strongSelf.callSessionManager.sendSignalingData(internalId: strongSelf.internalId, data: data)
|
||||
}
|
||||
}
|
||||
|
||||
public func deactivateIncomingAudio() {
|
||||
self.withContext { context in
|
||||
context.nativeDeactivateIncomingAudio()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private protocol CallSignalingConnection: AnyObject {
|
||||
|
@ -315,6 +315,7 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
|
||||
- (void)switchAudioOutput:(NSString * _Nonnull)deviceId;
|
||||
- (void)switchAudioInput:(NSString * _Nonnull)deviceId;
|
||||
- (void)addExternalAudioData:(NSData * _Nonnull)data;
|
||||
- (void)deactivateIncomingAudio;
|
||||
|
||||
@end
|
||||
|
||||
@ -452,7 +453,8 @@ statsLogPath:(NSString * _Nonnull)statsLogPath
|
||||
onMutedSpeechActivityDetected:(void (^ _Nullable)(bool))onMutedSpeechActivityDetected
|
||||
audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice
|
||||
encryptionKey:(NSData * _Nullable)encryptionKey
|
||||
isConference:(bool)isConference;
|
||||
isConference:(bool)isConference
|
||||
isActiveByDefault:(bool)isActiveByDefault;
|
||||
|
||||
- (void)stop:(void (^ _Nullable)())completion;
|
||||
|
||||
|
@ -127,7 +127,7 @@ public:
|
||||
|
||||
public:
|
||||
virtual rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> audioDeviceModule() = 0;
|
||||
virtual rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> makeChildAudioDeviceModule() = 0;
|
||||
virtual rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> makeChildAudioDeviceModule(bool isActive) = 0;
|
||||
virtual void start() = 0;
|
||||
};
|
||||
|
||||
@ -147,14 +147,14 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
void UpdateAudioCallback(webrtc::AudioTransport *previousAudioCallback, webrtc::AudioTransport *audioCallback) {
|
||||
void UpdateAudioCallback(webrtc::AudioTransport *previousAudioCallback, webrtc::AudioTransport *audioCallback, bool isActive) {
|
||||
_mutex.Lock();
|
||||
|
||||
if (audioCallback) {
|
||||
_audioTransports.push_back(audioCallback);
|
||||
_audioTransports.push_back(std::make_pair(audioCallback, isActive));
|
||||
} else if (previousAudioCallback) {
|
||||
for (size_t i = 0; i < _audioTransports.size(); i++) {
|
||||
if (_audioTransports[i] == previousAudioCallback) {
|
||||
if (_audioTransports[i].first == previousAudioCallback) {
|
||||
_audioTransports.erase(_audioTransports.begin() + i);
|
||||
break;
|
||||
}
|
||||
@ -163,6 +163,18 @@ public:
|
||||
|
||||
_mutex.Unlock();
|
||||
}
|
||||
|
||||
void UpdateAudioCallbackIsActive(webrtc::AudioTransport *audioCallback, bool isActive) {
|
||||
_mutex.Lock();
|
||||
|
||||
for (auto &it : _audioTransports) {
|
||||
if (it.first == audioCallback) {
|
||||
it.second = isActive;
|
||||
}
|
||||
}
|
||||
|
||||
_mutex.Unlock();
|
||||
}
|
||||
|
||||
virtual int32_t RegisterAudioCallback(webrtc::AudioTransport *audioCallback) override {
|
||||
return 0;
|
||||
@ -474,7 +486,7 @@ public:
|
||||
_mutex.Lock();
|
||||
if (!_audioTransports.empty()) {
|
||||
for (size_t i = 0; i < _audioTransports.size(); i++) {
|
||||
_audioTransports[i]->RecordedDataIsAvailable(
|
||||
_audioTransports[i].first->RecordedDataIsAvailable(
|
||||
audioSamples,
|
||||
nSamples,
|
||||
nBytesPerSample,
|
||||
@ -508,7 +520,7 @@ public:
|
||||
_mutex.Lock();
|
||||
if (!_audioTransports.empty()) {
|
||||
for (size_t i = 0; i < _audioTransports.size(); i++) {
|
||||
_audioTransports[i]->RecordedDataIsAvailable(
|
||||
_audioTransports[i].first->RecordedDataIsAvailable(
|
||||
audioSamples,
|
||||
nSamples,
|
||||
nBytesPerSample,
|
||||
@ -552,11 +564,14 @@ public:
|
||||
int16_t *resultAudioSamples = (int16_t *)audioSamples;
|
||||
|
||||
for (size_t i = 0; i < _audioTransports.size(); i++) {
|
||||
if (!_audioTransports[i].second) {
|
||||
continue;
|
||||
}
|
||||
int64_t localElapsedTimeMs = 0;
|
||||
int64_t localNtpTimeMs = 0;
|
||||
size_t localNSamplesOut = 0;
|
||||
|
||||
_audioTransports[i]->NeedMorePlayData(
|
||||
_audioTransports[i].first->NeedMorePlayData(
|
||||
nSamples,
|
||||
nBytesPerSample,
|
||||
nChannels,
|
||||
@ -584,7 +599,7 @@ public:
|
||||
}
|
||||
nSamplesOut = nSamples;
|
||||
} else {
|
||||
result = _audioTransports[_audioTransports.size() - 1]->NeedMorePlayData(
|
||||
result = _audioTransports[_audioTransports.size() - 1].first->NeedMorePlayData(
|
||||
nSamples,
|
||||
nBytesPerSample,
|
||||
nChannels,
|
||||
@ -616,7 +631,7 @@ public:
|
||||
_mutex.Lock();
|
||||
|
||||
if (!_audioTransports.empty()) {
|
||||
_audioTransports[_audioTransports.size() - 1]->PullRenderData(
|
||||
_audioTransports[_audioTransports.size() - 1].first->PullRenderData(
|
||||
bits_per_sample,
|
||||
sample_rate,
|
||||
number_of_channels,
|
||||
@ -650,6 +665,9 @@ public:
|
||||
virtual void Stop() override {
|
||||
}
|
||||
|
||||
virtual void setIsActive(bool isActive) override {
|
||||
}
|
||||
|
||||
virtual void ActualStop() {
|
||||
if (_isStarted) {
|
||||
_isStarted = false;
|
||||
@ -661,22 +679,23 @@ public:
|
||||
|
||||
private:
|
||||
bool _isStarted = false;
|
||||
std::vector<webrtc::AudioTransport *> _audioTransports;
|
||||
std::vector<std::pair<webrtc::AudioTransport *, bool>> _audioTransports;
|
||||
webrtc::Mutex _mutex;
|
||||
std::vector<int16_t> _mixAudioSamples;
|
||||
};
|
||||
|
||||
class WrappedChildAudioDeviceModule : public tgcalls::DefaultWrappedAudioDeviceModule {
|
||||
public:
|
||||
WrappedChildAudioDeviceModule(webrtc::scoped_refptr<WrappedAudioDeviceModuleIOS> impl) :
|
||||
tgcalls::DefaultWrappedAudioDeviceModule(impl) {
|
||||
WrappedChildAudioDeviceModule(webrtc::scoped_refptr<WrappedAudioDeviceModuleIOS> impl, bool isActive) :
|
||||
tgcalls::DefaultWrappedAudioDeviceModule(impl),
|
||||
_isActive(isActive) {
|
||||
}
|
||||
|
||||
virtual ~WrappedChildAudioDeviceModule() {
|
||||
if (_audioCallback) {
|
||||
auto previousAudioCallback = _audioCallback;
|
||||
_audioCallback = nullptr;
|
||||
((WrappedAudioDeviceModuleIOS *)WrappedInstance().get())->UpdateAudioCallback(previousAudioCallback, nullptr);
|
||||
((WrappedAudioDeviceModuleIOS *)WrappedInstance().get())->UpdateAudioCallback(previousAudioCallback, nullptr, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -685,21 +704,20 @@ public:
|
||||
_audioCallback = audioCallback;
|
||||
|
||||
if (_isActive) {
|
||||
((WrappedAudioDeviceModuleIOS *)WrappedInstance().get())->UpdateAudioCallback(previousAudioCallback, audioCallback);
|
||||
((WrappedAudioDeviceModuleIOS *)WrappedInstance().get())->UpdateAudioCallback(previousAudioCallback, audioCallback, _isActive);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public:
|
||||
void setIsActive() {
|
||||
if (_isActive) {
|
||||
void setIsActive(bool isActive) override {
|
||||
if (_isActive == isActive) {
|
||||
return;
|
||||
}
|
||||
_isActive = true;
|
||||
_isActive = isActive;
|
||||
|
||||
if (_audioCallback) {
|
||||
((WrappedAudioDeviceModuleIOS *)WrappedInstance().get())->UpdateAudioCallback(nullptr, _audioCallback);
|
||||
((WrappedAudioDeviceModuleIOS *)WrappedInstance().get())->UpdateAudioCallbackIsActive(_audioCallback, isActive);
|
||||
}
|
||||
}
|
||||
|
||||
@ -733,8 +751,8 @@ public:
|
||||
return _audioDeviceModule;
|
||||
}
|
||||
|
||||
rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> makeChildAudioDeviceModule() override {
|
||||
return rtc::make_ref_counted<WrappedChildAudioDeviceModule>(_audioDeviceModule);
|
||||
rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> makeChildAudioDeviceModule(bool isActive) override {
|
||||
return rtc::make_ref_counted<WrappedChildAudioDeviceModule>(_audioDeviceModule, isActive);
|
||||
}
|
||||
|
||||
virtual void start() override {
|
||||
@ -1928,8 +1946,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
},
|
||||
.createWrappedAudioDeviceModule = [audioDeviceModule](webrtc::TaskQueueFactory *taskQueueFactory) -> rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> {
|
||||
if (audioDeviceModule) {
|
||||
auto result = audioDeviceModule->getSyncAssumingSameThread()->makeChildAudioDeviceModule();
|
||||
((WrappedChildAudioDeviceModule *)result.get())->setIsActive();
|
||||
auto result = audioDeviceModule->getSyncAssumingSameThread()->makeChildAudioDeviceModule(true);
|
||||
return result;
|
||||
} else {
|
||||
return nullptr;
|
||||
@ -2260,6 +2277,17 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)deactivateIncomingAudio {
|
||||
if (_currentAudioDeviceModuleThread) {
|
||||
auto currentAudioDeviceModule = _currentAudioDeviceModule;
|
||||
if (currentAudioDeviceModule) {
|
||||
_currentAudioDeviceModuleThread->PostTask([currentAudioDeviceModule]() {
|
||||
((tgcalls::WrappedAudioDeviceModule *)currentAudioDeviceModule.get())->setIsActive(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
namespace {
|
||||
@ -2347,7 +2375,8 @@ statsLogPath:(NSString * _Nonnull)statsLogPath
|
||||
onMutedSpeechActivityDetected:(void (^ _Nullable)(bool))onMutedSpeechActivityDetected
|
||||
audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice
|
||||
encryptionKey:(NSData * _Nullable)encryptionKey
|
||||
isConference:(bool)isConference {
|
||||
isConference:(bool)isConference
|
||||
isActiveByDefault:(bool)isActiveByDefault {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_queue = queue;
|
||||
@ -2636,10 +2665,9 @@ isConference:(bool)isConference {
|
||||
return resultModule;
|
||||
}
|
||||
},
|
||||
.createWrappedAudioDeviceModule = [audioDeviceModule](webrtc::TaskQueueFactory *taskQueueFactory) -> rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> {
|
||||
.createWrappedAudioDeviceModule = [audioDeviceModule, isActiveByDefault](webrtc::TaskQueueFactory *taskQueueFactory) -> rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> {
|
||||
if (audioDeviceModule) {
|
||||
auto result = audioDeviceModule->getSyncAssumingSameThread()->makeChildAudioDeviceModule();
|
||||
((WrappedChildAudioDeviceModule *)result.get())->setIsActive();
|
||||
auto result = audioDeviceModule->getSyncAssumingSameThread()->makeChildAudioDeviceModule(isActiveByDefault);
|
||||
return result;
|
||||
} else {
|
||||
return nullptr;
|
||||
@ -2983,6 +3011,14 @@ isConference:(bool)isConference {
|
||||
}
|
||||
|
||||
- (void)activateIncomingAudio {
|
||||
if (_currentAudioDeviceModuleThread) {
|
||||
auto currentAudioDeviceModule = _currentAudioDeviceModule;
|
||||
if (currentAudioDeviceModule) {
|
||||
_currentAudioDeviceModuleThread->PostTask([currentAudioDeviceModule]() {
|
||||
((tgcalls::WrappedAudioDeviceModule *)currentAudioDeviceModule.get())->setIsActive(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit bc8334224dbefb4591d669f7569d16f69134c5b6
|
||||
Subproject commit 29862dd6202e5f71db38e9722fecfd0ab3078268
|
@ -91,6 +91,7 @@ public enum ParsedInternalUrl {
|
||||
case stickerPack(name: String, type: StickerPackUrlType)
|
||||
case invoice(String)
|
||||
case join(String)
|
||||
case joinCall(String)
|
||||
case localization(String)
|
||||
case proxy(host: String, port: Int32, username: String?, password: String?, secret: Data?)
|
||||
case internalInstantView(url: String)
|
||||
@ -400,6 +401,12 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, context: Accou
|
||||
return .invoice(pathComponents[1])
|
||||
} else if pathComponents[0] == "joinchat" || pathComponents[0] == "joinchannel" {
|
||||
return .join(pathComponents[1])
|
||||
} else if pathComponents[0] == "call" {
|
||||
var callHash = pathComponents[1]
|
||||
if callHash.hasPrefix("+") {
|
||||
callHash = String(callHash.dropFirst())
|
||||
}
|
||||
return .joinCall(callHash)
|
||||
} else if pathComponents[0] == "setlanguage" {
|
||||
return .localization(pathComponents[1])
|
||||
} else if pathComponents[0] == "login" {
|
||||
@ -1036,6 +1043,8 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
|
||||
})
|
||||
case let .join(link):
|
||||
return .single(.result(.join(link)))
|
||||
case let .joinCall(link):
|
||||
return .single(.result(.joinCall(link)))
|
||||
case let .localization(identifier):
|
||||
return .single(.result(.localization(identifier)))
|
||||
case let .proxy(host, port, username, password, secret):
|
||||
|
Loading…
x
Reference in New Issue
Block a user