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
@ -168,6 +168,7 @@ private final class EmbeddedBroadcastUploadImpl: BroadcastUploadImpl {
|
|||||||
onMutedSpeechActivityDetected: { _ in },
|
onMutedSpeechActivityDetected: { _ in },
|
||||||
encryptionKey: nil,
|
encryptionKey: nil,
|
||||||
isConference: false,
|
isConference: false,
|
||||||
|
audioIsActiveByDefault: true,
|
||||||
isStream: false,
|
isStream: false,
|
||||||
sharedAudioDevice: nil
|
sharedAudioDevice: nil
|
||||||
)
|
)
|
||||||
|
@ -301,6 +301,7 @@ public enum ResolvedUrl {
|
|||||||
case instantView(TelegramMediaWebpage, String?)
|
case instantView(TelegramMediaWebpage, String?)
|
||||||
case proxy(host: String, port: Int32, username: String?, password: String?, secret: Data?)
|
case proxy(host: String, port: Int32, username: String?, password: String?, secret: Data?)
|
||||||
case join(String)
|
case join(String)
|
||||||
|
case joinCall(String)
|
||||||
case localization(String)
|
case localization(String)
|
||||||
case confirmationCode(Int)
|
case confirmationCode(Int)
|
||||||
case cancelAccountReset(phone: String, hash: String)
|
case cancelAccountReset(phone: String, hash: String)
|
||||||
@ -920,6 +921,61 @@ public enum JoinAffiliateProgramScreenMode {
|
|||||||
case active(Active)
|
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 {
|
public protocol SharedAccountContext: AnyObject {
|
||||||
var sharedContainerPath: String { get }
|
var sharedContainerPath: String { get }
|
||||||
var basePath: 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 makeAffiliateProgramSetupScreenInitialData(context: AccountContext, peerId: EnginePeer.Id, mode: AffiliateProgramSetupScreenMode) -> Signal<AffiliateProgramSetupScreenInitialData, NoError>
|
||||||
func makeAffiliateProgramSetupScreen(context: AccountContext, initialData: AffiliateProgramSetupScreenInitialData) -> ViewController
|
func makeAffiliateProgramSetupScreen(context: AccountContext, initialData: AffiliateProgramSetupScreenInitialData) -> ViewController
|
||||||
|
|
||||||
func makeAffiliateProgramJoinScreen(context: AccountContext, sourcePeer: EnginePeer, commissionPermille: Int32, programDuration: Int32?, revenuePerUser: Double, mode: JoinAffiliateProgramScreenMode) -> 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 makeGalleryController(context: AccountContext, source: GalleryControllerItemSource, streamSingleVideo: Bool, isPreview: Bool) -> ViewController
|
||||||
|
|
||||||
func makeAccountFreezeInfoScreen(context: AccountContext) -> ViewController
|
func makeAccountFreezeInfoScreen(context: AccountContext) -> ViewController
|
||||||
|
@ -30,6 +30,9 @@ swift_library(
|
|||||||
"//submodules/TelegramBaseController:TelegramBaseController",
|
"//submodules/TelegramBaseController:TelegramBaseController",
|
||||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||||
|
"//submodules/ItemListPeerActionItem",
|
||||||
|
"//submodules/InviteLinksUI",
|
||||||
|
"//submodules/UndoUI",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -13,6 +13,8 @@ import AppBundle
|
|||||||
import LocalizedPeerData
|
import LocalizedPeerData
|
||||||
import ContextUI
|
import ContextUI
|
||||||
import TelegramBaseController
|
import TelegramBaseController
|
||||||
|
import InviteLinksUI
|
||||||
|
import UndoUI
|
||||||
|
|
||||||
public enum CallListControllerMode {
|
public enum CallListControllerMode {
|
||||||
case tab
|
case tab
|
||||||
@ -201,7 +203,28 @@ public final class CallListController: TelegramBaseController {
|
|||||||
if self.isNodeLoaded {
|
if self.isNodeLoaded {
|
||||||
self.controllerNode.updateThemeAndStrings(presentationData: self.presentationData)
|
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() {
|
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 {
|
if case .navigation = self.mode {
|
||||||
|
@ -14,6 +14,7 @@ import ChatListSearchItemHeader
|
|||||||
import AnimatedStickerNode
|
import AnimatedStickerNode
|
||||||
import TelegramAnimatedStickerNode
|
import TelegramAnimatedStickerNode
|
||||||
import AppBundle
|
import AppBundle
|
||||||
|
import ItemListPeerActionItem
|
||||||
|
|
||||||
private struct CallListNodeListViewTransition {
|
private struct CallListNodeListViewTransition {
|
||||||
let callListView: CallListNodeView
|
let callListView: CallListNodeView
|
||||||
@ -66,14 +67,16 @@ final class CallListNodeInteraction {
|
|||||||
let delete: ([EngineMessage.Id]) -> Void
|
let delete: ([EngineMessage.Id]) -> Void
|
||||||
let updateShowCallsTab: (Bool) -> Void
|
let updateShowCallsTab: (Bool) -> Void
|
||||||
let openGroupCall: (EnginePeer.Id) -> 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.setMessageIdWithRevealedOptions = setMessageIdWithRevealedOptions
|
||||||
self.call = call
|
self.call = call
|
||||||
self.openInfo = openInfo
|
self.openInfo = openInfo
|
||||||
self.delete = delete
|
self.delete = delete
|
||||||
self.updateShowCallsTab = updateShowCallsTab
|
self.updateShowCallsTab = updateShowCallsTab
|
||||||
self.openGroupCall = openGroupCall
|
self.openGroupCall = openGroupCall
|
||||||
|
self.createGroupCall = createGroupCall
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +125,12 @@ private func mappedInsertEntries(context: AccountContext, presentationData: Item
|
|||||||
}), directionHint: entry.directionHint)
|
}), directionHint: entry.directionHint)
|
||||||
case let .displayTabInfo(_, text):
|
case let .displayTabInfo(_, text):
|
||||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint)
|
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):
|
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)
|
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, _):
|
case let .messageEntry(topMessage, messages, _, _, dateTimeFormat, editing, hasActiveRevealControls, displayHeader, _):
|
||||||
@ -141,6 +150,12 @@ private func mappedUpdateEntries(context: AccountContext, presentationData: Item
|
|||||||
}), directionHint: entry.directionHint)
|
}), directionHint: entry.directionHint)
|
||||||
case let .displayTabInfo(_, text):
|
case let .displayTabInfo(_, text):
|
||||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint)
|
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):
|
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)
|
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, _):
|
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 call: (EnginePeer.Id, Bool) -> Void
|
||||||
private let joinGroupCall: (EnginePeer.Id, EngineGroupCallDescription) -> Void
|
private let joinGroupCall: (EnginePeer.Id, EngineGroupCallDescription) -> Void
|
||||||
|
private let createGroupCall: () -> Void
|
||||||
private let openInfo: (EnginePeer.Id, [EngineMessage]) -> Void
|
private let openInfo: (EnginePeer.Id, [EngineMessage]) -> Void
|
||||||
private let emptyStateUpdated: (Bool) -> Void
|
private let emptyStateUpdated: (Bool) -> Void
|
||||||
|
|
||||||
private let emptyStatePromise = Promise<Bool>()
|
private let emptyStatePromise = Promise<Bool>()
|
||||||
private let emptyStateDisposable = MetaDisposable()
|
private let emptyStateDisposable = MetaDisposable()
|
||||||
|
|
||||||
@ -219,7 +234,7 @@ final class CallListControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
private var previousContentOffset: ListViewVisibleContentOffset?
|
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.controller = controller
|
||||||
self.context = context
|
self.context = context
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
@ -228,7 +243,7 @@ final class CallListControllerNode: ASDisplayNode {
|
|||||||
self.joinGroupCall = joinGroupCall
|
self.joinGroupCall = joinGroupCall
|
||||||
self.openInfo = openInfo
|
self.openInfo = openInfo
|
||||||
self.emptyStateUpdated = emptyStateUpdated
|
self.emptyStateUpdated = emptyStateUpdated
|
||||||
|
self.createGroupCall = createGroupCall
|
||||||
self.currentState = CallListNodeState(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, disableAnimations: true, editing: false, messageIdWithRevealedOptions: nil)
|
self.currentState = CallListNodeState(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, disableAnimations: true, editing: false, messageIdWithRevealedOptions: nil)
|
||||||
self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true)
|
self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true)
|
||||||
|
|
||||||
@ -432,6 +447,11 @@ final class CallListControllerNode: ASDisplayNode {
|
|||||||
strongSelf.joinGroupCall(peerId, activeCall)
|
strongSelf.joinGroupCall(peerId, activeCall)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
}, createGroupCall: { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.createGroupCall()
|
||||||
})
|
})
|
||||||
|
|
||||||
let viewProcessingQueue = self.viewProcessingQueue
|
let viewProcessingQueue = self.viewProcessingQueue
|
||||||
@ -497,17 +517,30 @@ final class CallListControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|> distinctUntilChanged
|
|> 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(
|
let callListNodeViewTransition = combineLatest(
|
||||||
callListViewUpdate,
|
callListViewUpdate,
|
||||||
self.statePromise.get(),
|
self.statePromise.get(),
|
||||||
groupCalls,
|
groupCalls,
|
||||||
showCallsTab,
|
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 (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 previous = previousView.swap(processedView)
|
||||||
let previousType = previousType.swap(type)
|
let previousType = previousType.swap(type)
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ enum CallListNodeEntry: Comparable, Identifiable {
|
|||||||
enum SortIndex: Comparable {
|
enum SortIndex: Comparable {
|
||||||
case displayTab
|
case displayTab
|
||||||
case displayTabInfo
|
case displayTabInfo
|
||||||
|
case createGroupCall
|
||||||
case groupCall(EnginePeer.Id, String)
|
case groupCall(EnginePeer.Id, String)
|
||||||
case message(EngineMessage.Index)
|
case message(EngineMessage.Index)
|
||||||
case hole(EngineMessage.Index)
|
case hole(EngineMessage.Index)
|
||||||
@ -40,10 +41,17 @@ enum CallListNodeEntry: Comparable, Identifiable {
|
|||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .groupCall(lhsPeerId, lhsTitle):
|
case .createGroupCall:
|
||||||
switch rhs {
|
switch rhs {
|
||||||
case .displayTab, .displayTabInfo:
|
case .displayTab, .displayTabInfo:
|
||||||
return false
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case let .groupCall(lhsPeerId, lhsTitle):
|
||||||
|
switch rhs {
|
||||||
|
case .displayTab, .displayTabInfo, .createGroupCall:
|
||||||
|
return false
|
||||||
case let .groupCall(rhsPeerId, rhsTitle):
|
case let .groupCall(rhsPeerId, rhsTitle):
|
||||||
if lhsTitle == rhsTitle {
|
if lhsTitle == rhsTitle {
|
||||||
return lhsPeerId < rhsPeerId
|
return lhsPeerId < rhsPeerId
|
||||||
@ -55,7 +63,7 @@ enum CallListNodeEntry: Comparable, Identifiable {
|
|||||||
}
|
}
|
||||||
case let .hole(lhsIndex):
|
case let .hole(lhsIndex):
|
||||||
switch rhs {
|
switch rhs {
|
||||||
case .displayTab, .displayTabInfo, .groupCall:
|
case .displayTab, .displayTabInfo, .groupCall, .createGroupCall:
|
||||||
return false
|
return false
|
||||||
case let .hole(rhsIndex):
|
case let .hole(rhsIndex):
|
||||||
return lhsIndex < rhsIndex
|
return lhsIndex < rhsIndex
|
||||||
@ -64,7 +72,7 @@ enum CallListNodeEntry: Comparable, Identifiable {
|
|||||||
}
|
}
|
||||||
case let .message(lhsIndex):
|
case let .message(lhsIndex):
|
||||||
switch rhs {
|
switch rhs {
|
||||||
case .displayTab, .displayTabInfo, .groupCall:
|
case .displayTab, .displayTabInfo, .groupCall, .createGroupCall:
|
||||||
return false
|
return false
|
||||||
case let .hole(rhsIndex):
|
case let .hole(rhsIndex):
|
||||||
return lhsIndex < rhsIndex
|
return lhsIndex < rhsIndex
|
||||||
@ -78,6 +86,7 @@ enum CallListNodeEntry: Comparable, Identifiable {
|
|||||||
|
|
||||||
case displayTab(PresentationTheme, String, Bool)
|
case displayTab(PresentationTheme, String, Bool)
|
||||||
case displayTabInfo(PresentationTheme, String)
|
case displayTabInfo(PresentationTheme, String)
|
||||||
|
case createGroupCall
|
||||||
case groupCall(peer: EnginePeer, editing: Bool, isActive: Bool)
|
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 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)
|
case holeEntry(index: EngineMessage.Index, theme: PresentationTheme)
|
||||||
@ -88,6 +97,8 @@ enum CallListNodeEntry: Comparable, Identifiable {
|
|||||||
return .displayTab
|
return .displayTab
|
||||||
case .displayTabInfo:
|
case .displayTabInfo:
|
||||||
return .displayTabInfo
|
return .displayTabInfo
|
||||||
|
case .createGroupCall:
|
||||||
|
return .createGroupCall
|
||||||
case let .groupCall(peer, _, _):
|
case let .groupCall(peer, _, _):
|
||||||
return .groupCall(peer.id, peer.compactDisplayTitle)
|
return .groupCall(peer.id, peer.compactDisplayTitle)
|
||||||
case let .messageEntry(message, _, _, _, _, _, _, _, _):
|
case let .messageEntry(message, _, _, _, _, _, _, _, _):
|
||||||
@ -103,6 +114,8 @@ enum CallListNodeEntry: Comparable, Identifiable {
|
|||||||
return .setting(0)
|
return .setting(0)
|
||||||
case .displayTabInfo:
|
case .displayTabInfo:
|
||||||
return .setting(1)
|
return .setting(1)
|
||||||
|
case .createGroupCall:
|
||||||
|
return .setting(2)
|
||||||
case let .groupCall(peer, _, _):
|
case let .groupCall(peer, _, _):
|
||||||
return .groupCall(peer.id)
|
return .groupCall(peer.id)
|
||||||
case let .messageEntry(message, _, _, _, _, _, _, _, _):
|
case let .messageEntry(message, _, _, _, _, _, _, _, _):
|
||||||
@ -118,82 +131,88 @@ enum CallListNodeEntry: Comparable, Identifiable {
|
|||||||
|
|
||||||
static func ==(lhs: CallListNodeEntry, rhs: CallListNodeEntry) -> Bool {
|
static func ==(lhs: CallListNodeEntry, rhs: CallListNodeEntry) -> Bool {
|
||||||
switch lhs {
|
switch lhs {
|
||||||
case let .displayTab(lhsTheme, lhsText, lhsValue):
|
case let .displayTab(lhsTheme, lhsText, lhsValue):
|
||||||
if case let .displayTab(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
if case let .displayTab(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||||
return true
|
return true
|
||||||
} else {
|
} 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
|
return false
|
||||||
}
|
}
|
||||||
case let .displayTabInfo(lhsTheme, lhsText):
|
if lhsEditing != rhsEditing {
|
||||||
if case let .displayTabInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .groupCall(lhsPeer, lhsEditing, lhsIsActive):
|
if lhsIsActive != rhsIsActive {
|
||||||
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 {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .messageEntry(lhsMessage, lhsMessages, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsEditing, lhsHasRevealControls, lhsDisplayHeader, lhsMissed):
|
return true
|
||||||
if case let .messageEntry(rhsMessage, rhsMessages, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsEditing, rhsHasRevealControls, rhsDisplayHeader, rhsMissed) = rhs {
|
} else {
|
||||||
if lhsTheme !== rhsTheme {
|
return false
|
||||||
return false
|
}
|
||||||
}
|
case let .messageEntry(lhsMessage, lhsMessages, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsEditing, lhsHasRevealControls, lhsDisplayHeader, lhsMissed):
|
||||||
if lhsStrings !== rhsStrings {
|
if case let .messageEntry(rhsMessage, rhsMessages, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsEditing, rhsHasRevealControls, rhsDisplayHeader, rhsMissed) = rhs {
|
||||||
return false
|
if lhsTheme !== rhsTheme {
|
||||||
}
|
|
||||||
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
|
return false
|
||||||
}
|
}
|
||||||
case let .holeEntry(lhsIndex, lhsTheme):
|
if lhsStrings !== rhsStrings {
|
||||||
if case let .holeEntry(rhsIndex, rhsTheme) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
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] = []
|
var result: [CallListNodeEntry] = []
|
||||||
for entry in view.items {
|
for entry in view.items {
|
||||||
switch entry {
|
switch entry {
|
||||||
@ -218,6 +237,10 @@ func callListNodeEntriesForView(view: EngineCallList, groupCalls: [EnginePeer],
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if canCreateGroupCall {
|
||||||
|
result.append(.createGroupCall)
|
||||||
|
}
|
||||||
|
|
||||||
if showSettings {
|
if showSettings {
|
||||||
result.append(.displayTabInfo(state.presentationData.theme, state.presentationData.strings.CallSettings_TabIconDescription))
|
result.append(.displayTabInfo(state.presentationData.theme, state.presentationData.strings.CallSettings_TabIconDescription))
|
||||||
result.append(.displayTab(state.presentationData.theme, state.presentationData.strings.CallSettings_TabIcon, showCallsTab))
|
result.append(.displayTab(state.presentationData.theme, state.presentationData.strings.CallSettings_TabIcon, showCallsTab))
|
||||||
|
@ -82,7 +82,19 @@ private enum Knob {
|
|||||||
case right
|
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 final class RecognizedTextSelectionGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate {
|
||||||
|
private let internalDelegate = InternalGestureRecognizerDelegate()
|
||||||
|
|
||||||
private var longTapTimer: Timer?
|
private var longTapTimer: Timer?
|
||||||
private var movingKnob: (Knob, CGPoint, CGPoint)?
|
private var movingKnob: (Knob, CGPoint, CGPoint)?
|
||||||
private var currentLocation: CGPoint?
|
private var currentLocation: CGPoint?
|
||||||
@ -96,7 +108,7 @@ private final class RecognizedTextSelectionGestureRecognizer: UIGestureRecognize
|
|||||||
override init(target: Any?, action: Selector?) {
|
override init(target: Any?, action: Selector?) {
|
||||||
super.init(target: nil, action: nil)
|
super.init(target: nil, action: nil)
|
||||||
|
|
||||||
self.delegate = self
|
self.delegate = self.internalDelegate
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func reset() {
|
override public func reset() {
|
||||||
@ -179,15 +191,6 @@ private final class RecognizedTextSelectionGestureRecognizer: UIGestureRecognize
|
|||||||
self.state = .ended
|
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 {
|
public final class RecognizedTextSelectionNodeView: UIView {
|
||||||
|
@ -60,6 +60,8 @@ swift_library(
|
|||||||
"//submodules/PromptUI",
|
"//submodules/PromptUI",
|
||||||
"//submodules/TelegramUI/Components/ItemListDatePickerItem:ItemListDatePickerItem",
|
"//submodules/TelegramUI/Components/ItemListDatePickerItem:ItemListDatePickerItem",
|
||||||
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||||
|
"//submodules/ComponentFlow",
|
||||||
|
"//submodules/Components/MultilineTextComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -51,9 +51,9 @@ private enum InviteLinkInviteEntryId: Hashable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
||||||
case header(PresentationTheme, String, String)
|
case header(title: String, text: String)
|
||||||
case mainLink(PresentationTheme, ExportedInvitation?)
|
case mainLink(invitation: ExportedInvitation?, isCall: Bool, isRecentlyCreated: Bool)
|
||||||
case manage(PresentationTheme, String, Bool)
|
case manage(text: String, standalone: Bool)
|
||||||
|
|
||||||
var stableId: InviteLinkInviteEntryId {
|
var stableId: InviteLinkInviteEntryId {
|
||||||
switch self {
|
switch self {
|
||||||
@ -68,20 +68,20 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
|||||||
|
|
||||||
static func ==(lhs: InviteLinkInviteEntry, rhs: InviteLinkInviteEntry) -> Bool {
|
static func ==(lhs: InviteLinkInviteEntry, rhs: InviteLinkInviteEntry) -> Bool {
|
||||||
switch lhs {
|
switch lhs {
|
||||||
case let .header(lhsTheme, lhsTitle, lhsText):
|
case let .header(lhsTitle, lhsText):
|
||||||
if case let .header(rhsTheme, rhsTitle, rhsText) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsText == rhsText {
|
if case let .header(rhsTitle, rhsText) = rhs, lhsTitle == rhsTitle, lhsText == rhsText {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .mainLink(lhsTheme, lhsInvitation):
|
case let .mainLink(lhsInvitation, lhsIsCall, lhsIsRecentlyCreated):
|
||||||
if case let .mainLink(rhsTheme, rhsInvitation) = rhs, lhsTheme === rhsTheme, lhsInvitation == rhsInvitation {
|
if case let .mainLink(rhsInvitation, rhsIsCall, rhsIsRecentlyCreated) = rhs, lhsInvitation == rhsInvitation, lhsIsCall == rhsIsCall, lhsIsRecentlyCreated == rhsIsRecentlyCreated {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .manage(lhsTheme, lhsText, lhsStandalone):
|
case let .manage(lhsText, lhsStandalone):
|
||||||
if case let .manage(rhsTheme, rhsText, rhsStandalone) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStandalone == rhsStandalone {
|
if case let .manage(rhsText, rhsStandalone) = rhs, lhsText == rhsText, lhsStandalone == rhsStandalone {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -117,23 +117,23 @@ private enum InviteLinkInviteEntry: Comparable, Identifiable {
|
|||||||
|
|
||||||
func item(account: Account, presentationData: PresentationData, interaction: InviteLinkInviteInteraction) -> ListViewItem {
|
func item(account: Account, presentationData: PresentationData, interaction: InviteLinkInviteInteraction) -> ListViewItem {
|
||||||
switch self {
|
switch self {
|
||||||
case let .header(theme, title, text):
|
case let .header(title, text):
|
||||||
return InviteLinkInviteHeaderItem(theme: theme, title: title, text: text)
|
return InviteLinkInviteHeaderItem(theme: presentationData.theme, title: title, text: text)
|
||||||
case let .mainLink(_, invite):
|
case let .mainLink(invitation, isCall, isRecentlyCreated):
|
||||||
return ItemListPermanentInviteLinkItem(context: interaction.context, presentationData: ItemListPresentationData(presentationData), invite: invite, count: 0, peers: [], displayButton: true, displayImporters: false, buttonColor: nil, sectionId: 0, style: .plain, copyAction: {
|
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 = invite {
|
if let invite = invitation {
|
||||||
interaction.copyLink(invite)
|
interaction.copyLink(invite)
|
||||||
}
|
}
|
||||||
}, shareAction: {
|
}, shareAction: {
|
||||||
if let invite = invite {
|
if let invite = invitation {
|
||||||
interaction.shareLink(invite)
|
interaction.shareLink(invite)
|
||||||
}
|
}
|
||||||
}, contextAction: { node, gesture in
|
}, contextAction: { node, gesture in
|
||||||
interaction.mainLinkContextAction(invite, node, gesture)
|
interaction.mainLinkContextAction(invitation, node, gesture)
|
||||||
}, viewAction: {
|
}, viewAction: {
|
||||||
})
|
})
|
||||||
case let .manage(theme, text, standalone):
|
case let .manage(text, standalone):
|
||||||
return InviteLinkInviteManageItem(theme: theme, text: text, standalone: standalone, action: {
|
return InviteLinkInviteManageItem(theme: presentationData.theme, text: text, standalone: standalone, action: {
|
||||||
interaction.manageLinks()
|
interaction.manageLinks()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -155,19 +155,31 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
return self.displayNode as! Node
|
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 var animatedIn = false
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let peerId: EnginePeer.Id
|
private let mode: Mode
|
||||||
private weak var parentNavigationController: NavigationController?
|
private weak var parentNavigationController: NavigationController?
|
||||||
|
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private var presentationDataDisposable: Disposable?
|
private var presentationDataDisposable: Disposable?
|
||||||
|
|
||||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: EnginePeer.Id, parentNavigationController: NavigationController?) {
|
fileprivate let completed: ((CompletionResult?) -> Void)?
|
||||||
|
|
||||||
|
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: Mode, parentNavigationController: NavigationController?, completed: ((CompletionResult?) -> Void)? = nil) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.peerId = peerId
|
self.mode = mode
|
||||||
self.parentNavigationController = parentNavigationController
|
self.parentNavigationController = parentNavigationController
|
||||||
|
self.completed = completed
|
||||||
|
|
||||||
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
@ -198,7 +210,7 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override public func loadDisplayNode() {
|
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
|
private var didAppearOnce: Bool = false
|
||||||
@ -251,8 +263,8 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
private weak var controller: InviteLinkInviteController?
|
private weak var controller: InviteLinkInviteController?
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let peerId: EnginePeer.Id
|
private let mode: InviteLinkInviteController.Mode
|
||||||
private let invitesContext: PeerExportedInvitationsContext
|
private let groupOrChannelInvitesContext: PeerExportedInvitationsContext?
|
||||||
|
|
||||||
private var interaction: InviteLinkInviteInteraction?
|
private var interaction: InviteLinkInviteInteraction?
|
||||||
|
|
||||||
@ -267,6 +279,7 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
private let headerBackgroundNode: ASDisplayNode
|
private let headerBackgroundNode: ASDisplayNode
|
||||||
private let titleNode: ImmediateTextNode
|
private let titleNode: ImmediateTextNode
|
||||||
private let doneButton: HighlightableButtonNode
|
private let doneButton: HighlightableButtonNode
|
||||||
|
private let doneButtonIconNode: ASImageNode
|
||||||
private let historyBackgroundNode: ASDisplayNode
|
private let historyBackgroundNode: ASDisplayNode
|
||||||
private let historyBackgroundContentNode: ASDisplayNode
|
private let historyBackgroundContentNode: ASDisplayNode
|
||||||
private var floatingHeaderOffset: CGFloat?
|
private var floatingHeaderOffset: CGFloat?
|
||||||
@ -278,15 +291,19 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
|
|
||||||
private var revokeDisposable = MetaDisposable()
|
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.context = context
|
||||||
self.peerId = peerId
|
self.mode = mode
|
||||||
|
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.presentationDataPromise = Promise(self.presentationData)
|
self.presentationDataPromise = Promise(self.presentationData)
|
||||||
self.controller = controller
|
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 = ASDisplayNode()
|
||||||
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||||
@ -294,11 +311,12 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
self.contentNode = ASDisplayNode()
|
self.contentNode = ASDisplayNode()
|
||||||
|
|
||||||
self.headerNode = ASDisplayNode()
|
self.headerNode = ASDisplayNode()
|
||||||
self.headerNode.clipsToBounds = true
|
self.headerNode.clipsToBounds = false
|
||||||
|
|
||||||
self.headerBackgroundNode = ASDisplayNode()
|
self.headerBackgroundNode = ASDisplayNode()
|
||||||
self.headerBackgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
self.headerBackgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||||
self.headerBackgroundNode.cornerRadius = 16.0
|
self.headerBackgroundNode.cornerRadius = 16.0
|
||||||
|
self.headerBackgroundNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||||
|
|
||||||
self.titleNode = ImmediateTextNode()
|
self.titleNode = ImmediateTextNode()
|
||||||
self.titleNode.maximumNumberOfLines = 1
|
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.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 = 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 = ASDisplayNode()
|
||||||
self.historyBackgroundNode.isLayerBacked = true
|
self.historyBackgroundNode.isLayerBacked = true
|
||||||
@ -332,6 +352,9 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
let mainInvitePromise = ValuePromise<ExportedInvitation?>(nil)
|
let mainInvitePromise = ValuePromise<ExportedInvitation?>(nil)
|
||||||
|
|
||||||
self.interaction = InviteLinkInviteInteraction(context: context, mainLinkContextAction: { [weak self] invite, node, gesture in
|
self.interaction = InviteLinkInviteInteraction(context: context, mainLinkContextAction: { [weak self] invite, node, gesture in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
guard let node = node as? ContextReferenceContentNode else {
|
guard let node = node as? ContextReferenceContentNode else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -340,7 +363,7 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in
|
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)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { _, f in
|
}, action: { [weak self] _, f in
|
||||||
f(.dismissWithoutContent)
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
if let invite = invite {
|
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
|
if case let .groupOrChannel(peerId) = self.mode {
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor)
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
|
||||||
}, action: { _, f in
|
return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor)
|
||||||
f(.dismissWithoutContent)
|
}, 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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
if let invite = invite {
|
|
||||||
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let isGroup: Bool
|
let isGroup: Bool
|
||||||
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||||
isGroup = false
|
isGroup = false
|
||||||
} else {
|
} else {
|
||||||
isGroup = true
|
isGroup = true
|
||||||
}
|
}
|
||||||
let updatedPresentationData = (strongSelf.presentationData, strongSelf.presentationDataPromise.get())
|
let controller = ActionSheetController(presentationData: presentationData)
|
||||||
let controller = QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup))
|
let dismissAction: () -> Void = { [weak controller] in
|
||||||
strongSelf.controller?.present(controller, in: .window(.root))
|
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)
|
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
|
}, copyLink: { [weak self] invite in
|
||||||
UIPasteboard.general.string = invite.link
|
UIPasteboard.general.string = invite.link
|
||||||
|
|
||||||
self?.controller?.dismissAllTooltips()
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.controller?.dismissAllTooltips()
|
||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
if let completed = self.controller?.completed {
|
||||||
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))
|
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
|
}, shareLink: { [weak self] invite in
|
||||||
guard let strongSelf = self, let inviteLink = invite.link else {
|
guard let strongSelf = self, let inviteLink = invite.link else {
|
||||||
return
|
return
|
||||||
@ -496,49 +538,82 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let updatedPresentationData = (strongSelf.presentationData, strongSelf.presentationDataPromise.get())
|
|
||||||
let controller = inviteLinkListController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, admin: nil)
|
if case let .groupOrChannel(peerId) = strongSelf.mode {
|
||||||
strongSelf.controller?.parentNavigationController?.pushViewController(controller)
|
let updatedPresentationData = (strongSelf.presentationData, strongSelf.presentationDataPromise.get())
|
||||||
strongSelf.controller?.dismiss()
|
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 previousEntries = Atomic<[InviteLinkInviteEntry]?>(value: nil)
|
||||||
|
|
||||||
let peerView = context.account.postbox.peerView(id: peerId)
|
switch mode {
|
||||||
let invites: Signal<PeerExportedInvitationsState, NoError> = .single(PeerExportedInvitationsState())
|
case let .groupOrChannel(peerId):
|
||||||
self.disposable = (combineLatest(self.presentationDataPromise.get(), peerView, mainInvitePromise.get(), invites)
|
let peerView = context.account.postbox.peerView(id: peerId)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData, view, interactiveMainInvite, invites in
|
let invites: Signal<PeerExportedInvitationsState, NoError> = .single(PeerExportedInvitationsState())
|
||||||
if let strongSelf = self {
|
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] = []
|
var entries: [InviteLinkInviteEntry] = []
|
||||||
|
|
||||||
let helpText: String
|
//TODO:localize
|
||||||
if let peer = peerViewMainPeer(view) as? TelegramChannel, case .broadcast = peer.info {
|
let helpText: String = "Anyone on Telegram can join your call by following the link below."
|
||||||
helpText = presentationData.strings.InviteLink_CreatePrivateLinkHelpChannel
|
entries.append(.header(title: "Call Link", text: helpText))
|
||||||
} else {
|
|
||||||
helpText = presentationData.strings.InviteLink_CreatePrivateLinkHelp
|
|
||||||
}
|
|
||||||
entries.append(.header(presentationData.theme, presentationData.strings.InviteLink_InviteLink, helpText))
|
|
||||||
|
|
||||||
let mainInvite: ExportedInvitation?
|
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)
|
||||||
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(presentationData.theme, mainInvite))
|
entries.append(.mainLink(invitation: mainInvite, isCall: true, isRecentlyCreated: isRecentlyCreated))
|
||||||
entries.append(.manage(presentationData.theme, presentationData.strings.InviteLink_Manage, true))
|
|
||||||
|
|
||||||
let previousEntries = previousEntries.swap(entries)
|
let previousEntries = previousEntries.swap(entries)
|
||||||
|
|
||||||
let transition = preparedTransition(from: previousEntries ?? [], to: entries, isLoading: false, account: context.account, presentationData: presentationData, interaction: strongSelf.interaction!)
|
let transition = preparedTransition(from: previousEntries ?? [], to: entries, isLoading: false, account: context.account, presentationData: presentationData, interaction: self.interaction!)
|
||||||
strongSelf.enqueueTransition(transition)
|
self.enqueueTransition(transition)
|
||||||
}
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
self.listNode.preloadPages = true
|
self.listNode.preloadPages = true
|
||||||
self.listNode.stackFromBottom = true
|
self.listNode.stackFromBottom = true
|
||||||
@ -556,6 +631,7 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
|
|
||||||
self.headerNode.addSubnode(self.headerBackgroundNode)
|
self.headerNode.addSubnode(self.headerBackgroundNode)
|
||||||
self.headerNode.addSubnode(self.doneButton)
|
self.headerNode.addSubnode(self.doneButton)
|
||||||
|
self.doneButton.addSubnode(self.doneButtonIconNode)
|
||||||
|
|
||||||
self.doneButton.addTarget(self, action: #selector(self.doneButtonPressed), forControlEvents: .touchUpInside)
|
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.historyBackgroundContentNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||||
self.headerBackgroundNode.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.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) {
|
private func enqueueTransition(_ transition: InviteLinkInviteTransaction) {
|
||||||
@ -658,7 +735,10 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
insets.bottom = layout.intrinsicInsets.bottom
|
insets.bottom = layout.intrinsicInsets.bottom
|
||||||
|
|
||||||
let headerHeight: CGFloat = 54.0
|
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)
|
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.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 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)
|
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)
|
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
||||||
|
|
||||||
let doneSize = self.doneButton.measure(CGSize(width: layout.size.width, height: headerHeight))
|
if let image = self.doneButtonIconNode.image {
|
||||||
let doneFrame = CGRect(origin: CGPoint(x: layout.size.width - doneSize.width - 16.0, y: 18.0), size: doneSize)
|
let doneSize = CGSize(width: 62.0, height: 56.0)
|
||||||
transition.updateFrame(node: self.doneButton, frame: doneFrame)
|
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? {
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
let result = super.hitTest(point, with: event)
|
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 {
|
if result === self.headerNode.view {
|
||||||
return self.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)
|
// 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
|
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 titleFont = Font.bold(24.0)
|
||||||
private let textFont = Font.regular(13.0)
|
private let textFont = Font.regular(15.0)
|
||||||
|
|
||||||
class InviteLinkInviteHeaderItemNode: ListViewItemNode {
|
class InviteLinkInviteHeaderItemNode: ListViewItemNode {
|
||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
@ -102,8 +102,8 @@ class InviteLinkInviteHeaderItemNode: ListViewItemNode {
|
|||||||
return { item, params, neighbors in
|
return { item, params, neighbors in
|
||||||
let leftInset: CGFloat = 40.0 + params.leftInset
|
let leftInset: CGFloat = 40.0 + params.leftInset
|
||||||
let topInset: CGFloat = 98.0
|
let topInset: CGFloat = 98.0
|
||||||
let spacing: CGFloat = 8.0
|
let spacing: CGFloat = 10.0
|
||||||
let bottomInset: CGFloat = 24.0
|
let bottomInset: CGFloat = 13.0
|
||||||
|
|
||||||
var updatedTheme: PresentationTheme?
|
var updatedTheme: PresentationTheme?
|
||||||
if currentItem?.theme !== item.theme {
|
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 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 (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 (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)
|
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)
|
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.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
|
strongSelf.iconNode.frame = strongSelf.iconBackgroundNode.frame.insetBy(dx: 8.0, dy: 8.0)
|
||||||
|
|
||||||
let _ = titleApply()
|
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()
|
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.headerNode.clipsToBounds = true
|
||||||
|
|
||||||
self.headerBackgroundNode = ASDisplayNode()
|
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.headerBackgroundNode.cornerRadius = 16.0
|
||||||
|
|
||||||
self.titleNode = ImmediateTextNode()
|
self.titleNode = ImmediateTextNode()
|
||||||
@ -1025,8 +1025,8 @@ public final class InviteLinkViewController: ViewController {
|
|||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.presentationDataPromise.set(.single(presentationData))
|
self.presentationDataPromise.set(.single(presentationData))
|
||||||
|
|
||||||
self.historyBackgroundContentNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
self.historyBackgroundContentNode.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemBackgroundColor
|
||||||
self.headerBackgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
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.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)
|
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 AnimatedAvatarSetNode
|
||||||
import ShimmerEffect
|
import ShimmerEffect
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
|
import Markdown
|
||||||
|
import TextFormat
|
||||||
|
import ComponentFlow
|
||||||
|
import MultilineTextComponent
|
||||||
|
|
||||||
private func actionButtonImage(color: UIColor) -> UIImage? {
|
private func actionButtonImage(color: UIColor) -> UIImage? {
|
||||||
return generateImage(CGSize(width: 24.0, height: 24.0), contextGenerator: { size, context in
|
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 displayButton: Bool
|
||||||
let separateButtons: Bool
|
let separateButtons: Bool
|
||||||
let displayImporters: Bool
|
let displayImporters: Bool
|
||||||
|
let isCall: Bool
|
||||||
let buttonColor: UIColor?
|
let buttonColor: UIColor?
|
||||||
public let sectionId: ItemListSectionId
|
public let sectionId: ItemListSectionId
|
||||||
let style: ItemListStyle
|
let style: ItemListStyle
|
||||||
@ -52,6 +57,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
|
|||||||
displayButton: Bool,
|
displayButton: Bool,
|
||||||
separateButtons: Bool = false,
|
separateButtons: Bool = false,
|
||||||
displayImporters: Bool,
|
displayImporters: Bool,
|
||||||
|
isCall: Bool = false,
|
||||||
buttonColor: UIColor?,
|
buttonColor: UIColor?,
|
||||||
sectionId: ItemListSectionId,
|
sectionId: ItemListSectionId,
|
||||||
style: ItemListStyle,
|
style: ItemListStyle,
|
||||||
@ -69,6 +75,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
|
|||||||
self.displayButton = displayButton
|
self.displayButton = displayButton
|
||||||
self.separateButtons = separateButtons
|
self.separateButtons = separateButtons
|
||||||
self.displayImporters = displayImporters
|
self.displayImporters = displayImporters
|
||||||
|
self.isCall = isCall
|
||||||
self.buttonColor = buttonColor
|
self.buttonColor = buttonColor
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.style = style
|
self.style = style
|
||||||
@ -140,6 +147,11 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
|||||||
private var shimmerNode: ShimmerEffectNode?
|
private var shimmerNode: ShimmerEffectNode?
|
||||||
private var absoluteLocation: (CGRect, CGSize)?
|
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 let activateArea: AccessibilityAreaNode
|
||||||
|
|
||||||
private var item: ItemListPermanentInviteLinkItem?
|
private var item: ItemListPermanentInviteLinkItem?
|
||||||
@ -287,6 +299,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
|||||||
public func asyncLayout() -> (_ item: ItemListPermanentInviteLinkItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
public func asyncLayout() -> (_ item: ItemListPermanentInviteLinkItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
let makeAddressLayout = TextNode.asyncLayout(self.addressNode)
|
let makeAddressLayout = TextNode.asyncLayout(self.addressNode)
|
||||||
let makeInvitedPeersLayout = TextNode.asyncLayout(self.invitedPeersNode)
|
let makeInvitedPeersLayout = TextNode.asyncLayout(self.invitedPeersNode)
|
||||||
|
let makeJustCreatedCallTextNodeLayout = TextNode.asyncLayout(self.justCreatedCallTextNode)
|
||||||
|
|
||||||
let currentItem = self.item
|
let currentItem = self.item
|
||||||
let avatarsContext = self.avatarsContext
|
let avatarsContext = self.avatarsContext
|
||||||
@ -330,15 +343,57 @@ 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()))
|
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 avatarsContent = avatarsContext.update(peers: item.peers, animated: false)
|
||||||
|
|
||||||
let verticalInset: CGFloat = 16.0
|
let verticalInset: CGFloat = 16.0
|
||||||
let fieldHeight: CGFloat = 52.0
|
let fieldHeight: CGFloat = 52.0
|
||||||
let fieldSpacing: CGFloat = 16.0
|
let fieldSpacing: CGFloat = 16.0
|
||||||
let buttonHeight: CGFloat = 50.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
|
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 {
|
switch item.style {
|
||||||
case .plain:
|
case .plain:
|
||||||
itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor
|
itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor
|
||||||
@ -515,6 +570,81 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
|||||||
let _ = shareButtonNode.updateLayout(width: buttonWidth, transition: .immediate)
|
let _ = shareButtonNode.updateLayout(width: buttonWidth, transition: .immediate)
|
||||||
shareButtonNode.frame = CGRect(x: shareButtonOriginX, y: verticalInset + fieldHeight + fieldSpacing, width: buttonWidth, height: buttonHeight)
|
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 totalWidth = invitedPeersLayout.size.width
|
||||||
var leftOrigin: CGFloat = floorToScreenPixels((params.width - invitedPeersLayout.size.width) / 2.0)
|
var leftOrigin: CGFloat = floorToScreenPixels((params.width - invitedPeersLayout.size.width) / 2.0)
|
||||||
let avatarSpacing: CGFloat = 21.0
|
let avatarSpacing: CGFloat = 21.0
|
||||||
|
@ -30,10 +30,11 @@ public class ItemListPeerActionItem: ListViewItem, ItemListItem {
|
|||||||
let editing: Bool
|
let editing: Bool
|
||||||
let height: ItemListPeerActionItemHeight
|
let height: ItemListPeerActionItemHeight
|
||||||
let color: ItemListPeerActionItemColor
|
let color: ItemListPeerActionItemColor
|
||||||
|
let noInsets: Bool
|
||||||
public let sectionId: ItemListSectionId
|
public let sectionId: ItemListSectionId
|
||||||
public let action: (() -> Void)?
|
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.presentationData = presentationData
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.iconSignal = iconSignal
|
self.iconSignal = iconSignal
|
||||||
@ -43,6 +44,7 @@ public class ItemListPeerActionItem: ListViewItem, ItemListItem {
|
|||||||
self.hasSeparator = hasSeparator
|
self.hasSeparator = hasSeparator
|
||||||
self.editing = editing
|
self.editing = editing
|
||||||
self.height = height
|
self.height = height
|
||||||
|
self.noInsets = noInsets
|
||||||
self.color = color
|
self.color = color
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.action = action
|
self.action = action
|
||||||
@ -217,7 +219,11 @@ public final class ItemListPeerActionItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
let separatorHeight = UIScreenPixel
|
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 contentSize = CGSize(width: params.width, height: titleLayout.size.height + verticalInset * 2.0)
|
||||||
|
|
||||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||||
|
@ -11,7 +11,7 @@ import PresentationDataUtils
|
|||||||
import UndoUI
|
import UndoUI
|
||||||
import OldChannelsController
|
import OldChannelsController
|
||||||
|
|
||||||
public final class JoinLinkPreviewController: ViewController {
|
public final class LegacyJoinLinkPreviewController: ViewController {
|
||||||
private var controllerNode: JoinLinkPreviewControllerNode {
|
private var controllerNode: JoinLinkPreviewControllerNode {
|
||||||
return self.displayNode as! 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: {
|
}, inviteViaLink: {
|
||||||
if let controller = getControllerImpl?() {
|
if let controller = getControllerImpl?() {
|
||||||
dismissInputImpl?()
|
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
|
}, updateHideMembers: { value in
|
||||||
let _ = context.engine.peers.updateChannelMembersHidden(peerId: peerId, value: value).start()
|
let _ = context.engine.peers.updateChannelMembersHidden(peerId: peerId, value: value).start()
|
||||||
|
@ -119,6 +119,7 @@ swift_library(
|
|||||||
"//submodules/Components/BlurredBackgroundComponent",
|
"//submodules/Components/BlurredBackgroundComponent",
|
||||||
"//submodules/DirectMediaImageCache",
|
"//submodules/DirectMediaImageCache",
|
||||||
"//submodules/FastBlur",
|
"//submodules/FastBlur",
|
||||||
|
"//submodules/InviteLinksUI",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -1680,6 +1680,10 @@ public final class PresentationCallImpl: PresentationCall {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deactivateIncomingAudio() {
|
||||||
|
self.ongoingContext?.deactivateIncomingAudio()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sampleBufferFromPixelBuffer(pixelBuffer: CVPixelBuffer) -> CMSampleBuffer? {
|
func sampleBufferFromPixelBuffer(pixelBuffer: CVPixelBuffer) -> CMSampleBuffer? {
|
||||||
|
@ -539,6 +539,7 @@ private final class ScreencastInProcessIPCContext: ScreencastIPCContext {
|
|||||||
onMutedSpeechActivityDetected: { _ in },
|
onMutedSpeechActivityDetected: { _ in },
|
||||||
encryptionKey: nil,
|
encryptionKey: nil,
|
||||||
isConference: self.isConference,
|
isConference: self.isConference,
|
||||||
|
audioIsActiveByDefault: true,
|
||||||
isStream: false,
|
isStream: false,
|
||||||
sharedAudioDevice: nil
|
sharedAudioDevice: nil
|
||||||
)
|
)
|
||||||
@ -1938,6 +1939,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
contextAudioSessionActive = self.audioSessionActive.get()
|
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
|
genericCallContext = .call(OngoingGroupCallContext(audioSessionActive: contextAudioSessionActive, video: self.videoCapturer, requestMediaChannelDescriptions: { [weak self] ssrcs, completion in
|
||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
@ -1963,7 +1969,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
}
|
}
|
||||||
self.onMutedSpeechActivityDetected?(value)
|
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
|
self.genericCallContext = genericCallContext
|
||||||
@ -2756,6 +2762,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
private func activateIncomingAudioIfNeeded() {
|
private func activateIncomingAudioIfNeeded() {
|
||||||
if let genericCallContext = self.genericCallContext, case let .call(groupCall) = genericCallContext {
|
if let genericCallContext = self.genericCallContext, case let .call(groupCall) = genericCallContext {
|
||||||
groupCall.activateIncomingAudio()
|
groupCall.activateIncomingAudio()
|
||||||
|
if let pendingDisconnedUpgradedConferenceCall = self.pendingDisconnedUpgradedConferenceCall {
|
||||||
|
pendingDisconnedUpgradedConferenceCall.deactivateIncomingAudio()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,9 @@ import SwiftSignalKit
|
|||||||
import PeerInfoUI
|
import PeerInfoUI
|
||||||
import OverlayStatusController
|
import OverlayStatusController
|
||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
|
import InviteLinksUI
|
||||||
|
import UndoUI
|
||||||
|
import TelegramPresentationData
|
||||||
|
|
||||||
extension VideoChatScreenComponent.View {
|
extension VideoChatScreenComponent.View {
|
||||||
func openInviteMembers() {
|
func openInviteMembers() {
|
||||||
@ -13,6 +16,31 @@ extension VideoChatScreenComponent.View {
|
|||||||
return
|
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 {
|
if groupCall.isConference {
|
||||||
var disablePeerIds: [EnginePeer.Id] = []
|
var disablePeerIds: [EnginePeer.Id] = []
|
||||||
disablePeerIds.append(groupCall.accountContext.account.peerId)
|
disablePeerIds.append(groupCall.accountContext.account.peerId)
|
||||||
|
@ -1326,21 +1326,66 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||||||
navigationController?.pushViewController(controller)
|
navigationController?.pushViewController(controller)
|
||||||
})
|
})
|
||||||
} else {
|
} 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)
|
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:
|
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)
|
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 {
|
} 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)
|
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):
|
case let .localization(identifier):
|
||||||
strongSelf.presentController(LanguageLinkPreviewController(context: strongSelf.context, identifier: identifier), .window(.root), nil)
|
strongSelf.presentController(LanguageLinkPreviewController(context: strongSelf.context, identifier: identifier), .window(.root), nil)
|
||||||
case .proxy, .confirmationCode, .cancelAccountReset, .share:
|
case .proxy, .confirmationCode, .cancelAccountReset, .share:
|
||||||
|
@ -2726,10 +2726,9 @@ private func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor:
|
|||||||
context.setLineCap(.round)
|
context.setLineCap(.round)
|
||||||
context.setStrokeColor(foregroundColor.cgColor)
|
context.setStrokeColor(foregroundColor.cgColor)
|
||||||
|
|
||||||
|
context.beginPath()
|
||||||
context.move(to: CGPoint(x: 10.0, y: 10.0))
|
context.move(to: CGPoint(x: 10.0, y: 10.0))
|
||||||
context.addLine(to: CGPoint(x: 20.0, y: 20.0))
|
context.addLine(to: CGPoint(x: 20.0, y: 20.0))
|
||||||
context.strokePath()
|
|
||||||
|
|
||||||
context.move(to: CGPoint(x: 20.0, y: 10.0))
|
context.move(to: CGPoint(x: 20.0, y: 10.0))
|
||||||
context.addLine(to: CGPoint(x: 10.0, y: 20.0))
|
context.addLine(to: CGPoint(x: 10.0, y: 20.0))
|
||||||
context.strokePath()
|
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
|
createInviteLinkImpl = { [weak contactsController] in
|
||||||
contactsController?.view.window?.endEditing(true)
|
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)
|
parentController?.push(contactsController)
|
||||||
|
@ -235,13 +235,6 @@ private func oldChannelsEntries(presentationData: PresentationData, state: OldCh
|
|||||||
return entries
|
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 {
|
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 initialState = OldChannelsState()
|
||||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||||
|
@ -339,21 +339,65 @@ func openResolvedUrlImpl(
|
|||||||
navigationController?.pushViewController(controller)
|
navigationController?.pushViewController(controller)
|
||||||
})
|
})
|
||||||
} else {
|
} 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))
|
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:
|
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))
|
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 {
|
} 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))
|
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):
|
case let .localization(identifier):
|
||||||
dismissInput()
|
dismissInput()
|
||||||
present(LanguageLinkPreviewController(context: context, identifier: identifier), nil)
|
present(LanguageLinkPreviewController(context: context, identifier: identifier), nil)
|
||||||
|
@ -78,6 +78,8 @@ import AffiliateProgramSetupScreen
|
|||||||
import GalleryUI
|
import GalleryUI
|
||||||
import ShareController
|
import ShareController
|
||||||
import AccountFreezeInfoScreen
|
import AccountFreezeInfoScreen
|
||||||
|
import JoinSubjectScreen
|
||||||
|
import OldChannelsController
|
||||||
|
|
||||||
private final class AccountUserInterfaceInUseContext {
|
private final class AccountUserInterfaceInUseContext {
|
||||||
let subscribers = Bag<(Bool) -> Void>()
|
let subscribers = Bag<(Bool) -> Void>()
|
||||||
@ -473,6 +475,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
|> deliverOnMainQueue).start(next: { sharedData in
|
|> deliverOnMainQueue).start(next: { sharedData in
|
||||||
if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings]?.get(ExperimentalUISettings.self) {
|
if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings]?.get(ExperimentalUISettings.self) {
|
||||||
let _ = immediateExperimentalUISettingsValue.swap(settings)
|
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)
|
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 {
|
public func makeGalleryController(context: AccountContext, source: GalleryControllerItemSource, streamSingleVideo: Bool, isPreview: Bool) -> ViewController {
|
||||||
let controller = GalleryController(context: context, source: source, streamSingleVideo: streamSingleVideo, replaceRootController: { _, _ in
|
let controller = GalleryController(context: context, source: source, streamSingleVideo: streamSingleVideo, replaceRootController: { _, _ in
|
||||||
}, baseNavigationController: nil)
|
}, baseNavigationController: nil)
|
||||||
|
@ -497,6 +497,7 @@ public final class OngoingGroupCallContext {
|
|||||||
onMutedSpeechActivityDetected: @escaping (Bool) -> Void,
|
onMutedSpeechActivityDetected: @escaping (Bool) -> Void,
|
||||||
encryptionKey: Data?,
|
encryptionKey: Data?,
|
||||||
isConference: Bool,
|
isConference: Bool,
|
||||||
|
audioIsActiveByDefault: Bool,
|
||||||
isStream: Bool,
|
isStream: Bool,
|
||||||
sharedAudioDevice: OngoingCallContext.AudioDevice?
|
sharedAudioDevice: OngoingCallContext.AudioDevice?
|
||||||
) {
|
) {
|
||||||
@ -632,7 +633,8 @@ public final class OngoingGroupCallContext {
|
|||||||
},
|
},
|
||||||
audioDevice: audioDevice?.impl,
|
audioDevice: audioDevice?.impl,
|
||||||
encryptionKey: encryptionKey,
|
encryptionKey: encryptionKey,
|
||||||
isConference: isConference
|
isConference: isConference,
|
||||||
|
isActiveByDefault: audioIsActiveByDefault
|
||||||
)
|
)
|
||||||
#else
|
#else
|
||||||
self.context = GroupCallThreadLocalContext(
|
self.context = GroupCallThreadLocalContext(
|
||||||
@ -732,7 +734,8 @@ public final class OngoingGroupCallContext {
|
|||||||
statsLogPath: tempStatsLogPath,
|
statsLogPath: tempStatsLogPath,
|
||||||
audioDevice: nil,
|
audioDevice: nil,
|
||||||
encryptionKey: encryptionKey,
|
encryptionKey: encryptionKey,
|
||||||
isConference: isConference
|
isConference: isConference,
|
||||||
|
isActiveByDefault: true
|
||||||
)
|
)
|
||||||
#endif
|
#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
|
let queue = self.queue
|
||||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
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 nativeGetDerivedState() -> Data
|
||||||
func addExternalAudioData(data: Data)
|
func addExternalAudioData(data: Data)
|
||||||
func nativeSetIsAudioSessionActive(isActive: Bool)
|
func nativeSetIsAudioSessionActive(isActive: Bool)
|
||||||
|
func nativeDeactivateIncomingAudio()
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class OngoingCallThreadLocalContextHolder {
|
private final class OngoingCallThreadLocalContextHolder {
|
||||||
@ -692,6 +693,10 @@ extension OngoingCallThreadLocalContextWebrtc: OngoingCallThreadLocalContextProt
|
|||||||
self.addExternalAudioData(data)
|
self.addExternalAudioData(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nativeDeactivateIncomingAudio() {
|
||||||
|
self.deactivateIncomingAudio()
|
||||||
|
}
|
||||||
|
|
||||||
func nativeSetIsAudioSessionActive(isActive: Bool) {
|
func nativeSetIsAudioSessionActive(isActive: Bool) {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
self.setManualAudioSessionIsActive(isActive)
|
self.setManualAudioSessionIsActive(isActive)
|
||||||
@ -1394,6 +1399,12 @@ public final class OngoingCallContext {
|
|||||||
strongSelf.callSessionManager.sendSignalingData(internalId: strongSelf.internalId, data: data)
|
strongSelf.callSessionManager.sendSignalingData(internalId: strongSelf.internalId, data: data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func deactivateIncomingAudio() {
|
||||||
|
self.withContext { context in
|
||||||
|
context.nativeDeactivateIncomingAudio()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private protocol CallSignalingConnection: AnyObject {
|
private protocol CallSignalingConnection: AnyObject {
|
||||||
|
@ -315,6 +315,7 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
|
|||||||
- (void)switchAudioOutput:(NSString * _Nonnull)deviceId;
|
- (void)switchAudioOutput:(NSString * _Nonnull)deviceId;
|
||||||
- (void)switchAudioInput:(NSString * _Nonnull)deviceId;
|
- (void)switchAudioInput:(NSString * _Nonnull)deviceId;
|
||||||
- (void)addExternalAudioData:(NSData * _Nonnull)data;
|
- (void)addExternalAudioData:(NSData * _Nonnull)data;
|
||||||
|
- (void)deactivateIncomingAudio;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@ -452,7 +453,8 @@ statsLogPath:(NSString * _Nonnull)statsLogPath
|
|||||||
onMutedSpeechActivityDetected:(void (^ _Nullable)(bool))onMutedSpeechActivityDetected
|
onMutedSpeechActivityDetected:(void (^ _Nullable)(bool))onMutedSpeechActivityDetected
|
||||||
audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice
|
audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice
|
||||||
encryptionKey:(NSData * _Nullable)encryptionKey
|
encryptionKey:(NSData * _Nullable)encryptionKey
|
||||||
isConference:(bool)isConference;
|
isConference:(bool)isConference
|
||||||
|
isActiveByDefault:(bool)isActiveByDefault;
|
||||||
|
|
||||||
- (void)stop:(void (^ _Nullable)())completion;
|
- (void)stop:(void (^ _Nullable)())completion;
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ public:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
virtual rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> audioDeviceModule() = 0;
|
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;
|
virtual void start() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -147,14 +147,14 @@ public:
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateAudioCallback(webrtc::AudioTransport *previousAudioCallback, webrtc::AudioTransport *audioCallback) {
|
void UpdateAudioCallback(webrtc::AudioTransport *previousAudioCallback, webrtc::AudioTransport *audioCallback, bool isActive) {
|
||||||
_mutex.Lock();
|
_mutex.Lock();
|
||||||
|
|
||||||
if (audioCallback) {
|
if (audioCallback) {
|
||||||
_audioTransports.push_back(audioCallback);
|
_audioTransports.push_back(std::make_pair(audioCallback, isActive));
|
||||||
} else if (previousAudioCallback) {
|
} else if (previousAudioCallback) {
|
||||||
for (size_t i = 0; i < _audioTransports.size(); i++) {
|
for (size_t i = 0; i < _audioTransports.size(); i++) {
|
||||||
if (_audioTransports[i] == previousAudioCallback) {
|
if (_audioTransports[i].first == previousAudioCallback) {
|
||||||
_audioTransports.erase(_audioTransports.begin() + i);
|
_audioTransports.erase(_audioTransports.begin() + i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -164,6 +164,18 @@ public:
|
|||||||
_mutex.Unlock();
|
_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 {
|
virtual int32_t RegisterAudioCallback(webrtc::AudioTransport *audioCallback) override {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -474,7 +486,7 @@ public:
|
|||||||
_mutex.Lock();
|
_mutex.Lock();
|
||||||
if (!_audioTransports.empty()) {
|
if (!_audioTransports.empty()) {
|
||||||
for (size_t i = 0; i < _audioTransports.size(); i++) {
|
for (size_t i = 0; i < _audioTransports.size(); i++) {
|
||||||
_audioTransports[i]->RecordedDataIsAvailable(
|
_audioTransports[i].first->RecordedDataIsAvailable(
|
||||||
audioSamples,
|
audioSamples,
|
||||||
nSamples,
|
nSamples,
|
||||||
nBytesPerSample,
|
nBytesPerSample,
|
||||||
@ -508,7 +520,7 @@ public:
|
|||||||
_mutex.Lock();
|
_mutex.Lock();
|
||||||
if (!_audioTransports.empty()) {
|
if (!_audioTransports.empty()) {
|
||||||
for (size_t i = 0; i < _audioTransports.size(); i++) {
|
for (size_t i = 0; i < _audioTransports.size(); i++) {
|
||||||
_audioTransports[i]->RecordedDataIsAvailable(
|
_audioTransports[i].first->RecordedDataIsAvailable(
|
||||||
audioSamples,
|
audioSamples,
|
||||||
nSamples,
|
nSamples,
|
||||||
nBytesPerSample,
|
nBytesPerSample,
|
||||||
@ -552,11 +564,14 @@ public:
|
|||||||
int16_t *resultAudioSamples = (int16_t *)audioSamples;
|
int16_t *resultAudioSamples = (int16_t *)audioSamples;
|
||||||
|
|
||||||
for (size_t i = 0; i < _audioTransports.size(); i++) {
|
for (size_t i = 0; i < _audioTransports.size(); i++) {
|
||||||
|
if (!_audioTransports[i].second) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
int64_t localElapsedTimeMs = 0;
|
int64_t localElapsedTimeMs = 0;
|
||||||
int64_t localNtpTimeMs = 0;
|
int64_t localNtpTimeMs = 0;
|
||||||
size_t localNSamplesOut = 0;
|
size_t localNSamplesOut = 0;
|
||||||
|
|
||||||
_audioTransports[i]->NeedMorePlayData(
|
_audioTransports[i].first->NeedMorePlayData(
|
||||||
nSamples,
|
nSamples,
|
||||||
nBytesPerSample,
|
nBytesPerSample,
|
||||||
nChannels,
|
nChannels,
|
||||||
@ -584,7 +599,7 @@ public:
|
|||||||
}
|
}
|
||||||
nSamplesOut = nSamples;
|
nSamplesOut = nSamples;
|
||||||
} else {
|
} else {
|
||||||
result = _audioTransports[_audioTransports.size() - 1]->NeedMorePlayData(
|
result = _audioTransports[_audioTransports.size() - 1].first->NeedMorePlayData(
|
||||||
nSamples,
|
nSamples,
|
||||||
nBytesPerSample,
|
nBytesPerSample,
|
||||||
nChannels,
|
nChannels,
|
||||||
@ -616,7 +631,7 @@ public:
|
|||||||
_mutex.Lock();
|
_mutex.Lock();
|
||||||
|
|
||||||
if (!_audioTransports.empty()) {
|
if (!_audioTransports.empty()) {
|
||||||
_audioTransports[_audioTransports.size() - 1]->PullRenderData(
|
_audioTransports[_audioTransports.size() - 1].first->PullRenderData(
|
||||||
bits_per_sample,
|
bits_per_sample,
|
||||||
sample_rate,
|
sample_rate,
|
||||||
number_of_channels,
|
number_of_channels,
|
||||||
@ -650,6 +665,9 @@ public:
|
|||||||
virtual void Stop() override {
|
virtual void Stop() override {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual void setIsActive(bool isActive) override {
|
||||||
|
}
|
||||||
|
|
||||||
virtual void ActualStop() {
|
virtual void ActualStop() {
|
||||||
if (_isStarted) {
|
if (_isStarted) {
|
||||||
_isStarted = false;
|
_isStarted = false;
|
||||||
@ -661,22 +679,23 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
bool _isStarted = false;
|
bool _isStarted = false;
|
||||||
std::vector<webrtc::AudioTransport *> _audioTransports;
|
std::vector<std::pair<webrtc::AudioTransport *, bool>> _audioTransports;
|
||||||
webrtc::Mutex _mutex;
|
webrtc::Mutex _mutex;
|
||||||
std::vector<int16_t> _mixAudioSamples;
|
std::vector<int16_t> _mixAudioSamples;
|
||||||
};
|
};
|
||||||
|
|
||||||
class WrappedChildAudioDeviceModule : public tgcalls::DefaultWrappedAudioDeviceModule {
|
class WrappedChildAudioDeviceModule : public tgcalls::DefaultWrappedAudioDeviceModule {
|
||||||
public:
|
public:
|
||||||
WrappedChildAudioDeviceModule(webrtc::scoped_refptr<WrappedAudioDeviceModuleIOS> impl) :
|
WrappedChildAudioDeviceModule(webrtc::scoped_refptr<WrappedAudioDeviceModuleIOS> impl, bool isActive) :
|
||||||
tgcalls::DefaultWrappedAudioDeviceModule(impl) {
|
tgcalls::DefaultWrappedAudioDeviceModule(impl),
|
||||||
|
_isActive(isActive) {
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~WrappedChildAudioDeviceModule() {
|
virtual ~WrappedChildAudioDeviceModule() {
|
||||||
if (_audioCallback) {
|
if (_audioCallback) {
|
||||||
auto previousAudioCallback = _audioCallback;
|
auto previousAudioCallback = _audioCallback;
|
||||||
_audioCallback = nullptr;
|
_audioCallback = nullptr;
|
||||||
((WrappedAudioDeviceModuleIOS *)WrappedInstance().get())->UpdateAudioCallback(previousAudioCallback, nullptr);
|
((WrappedAudioDeviceModuleIOS *)WrappedInstance().get())->UpdateAudioCallback(previousAudioCallback, nullptr, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -685,21 +704,20 @@ public:
|
|||||||
_audioCallback = audioCallback;
|
_audioCallback = audioCallback;
|
||||||
|
|
||||||
if (_isActive) {
|
if (_isActive) {
|
||||||
((WrappedAudioDeviceModuleIOS *)WrappedInstance().get())->UpdateAudioCallback(previousAudioCallback, audioCallback);
|
((WrappedAudioDeviceModuleIOS *)WrappedInstance().get())->UpdateAudioCallback(previousAudioCallback, audioCallback, _isActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
void setIsActive(bool isActive) override {
|
||||||
void setIsActive() {
|
if (_isActive == isActive) {
|
||||||
if (_isActive) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_isActive = true;
|
_isActive = isActive;
|
||||||
|
|
||||||
if (_audioCallback) {
|
if (_audioCallback) {
|
||||||
((WrappedAudioDeviceModuleIOS *)WrappedInstance().get())->UpdateAudioCallback(nullptr, _audioCallback);
|
((WrappedAudioDeviceModuleIOS *)WrappedInstance().get())->UpdateAudioCallbackIsActive(_audioCallback, isActive);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -733,8 +751,8 @@ public:
|
|||||||
return _audioDeviceModule;
|
return _audioDeviceModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> makeChildAudioDeviceModule() override {
|
rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> makeChildAudioDeviceModule(bool isActive) override {
|
||||||
return rtc::make_ref_counted<WrappedChildAudioDeviceModule>(_audioDeviceModule);
|
return rtc::make_ref_counted<WrappedChildAudioDeviceModule>(_audioDeviceModule, isActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void start() override {
|
virtual void start() override {
|
||||||
@ -1928,8 +1946,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
|||||||
},
|
},
|
||||||
.createWrappedAudioDeviceModule = [audioDeviceModule](webrtc::TaskQueueFactory *taskQueueFactory) -> rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> {
|
.createWrappedAudioDeviceModule = [audioDeviceModule](webrtc::TaskQueueFactory *taskQueueFactory) -> rtc::scoped_refptr<tgcalls::WrappedAudioDeviceModule> {
|
||||||
if (audioDeviceModule) {
|
if (audioDeviceModule) {
|
||||||
auto result = audioDeviceModule->getSyncAssumingSameThread()->makeChildAudioDeviceModule();
|
auto result = audioDeviceModule->getSyncAssumingSameThread()->makeChildAudioDeviceModule(true);
|
||||||
((WrappedChildAudioDeviceModule *)result.get())->setIsActive();
|
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
return nullptr;
|
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
|
@end
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -2347,7 +2375,8 @@ statsLogPath:(NSString * _Nonnull)statsLogPath
|
|||||||
onMutedSpeechActivityDetected:(void (^ _Nullable)(bool))onMutedSpeechActivityDetected
|
onMutedSpeechActivityDetected:(void (^ _Nullable)(bool))onMutedSpeechActivityDetected
|
||||||
audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice
|
audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice
|
||||||
encryptionKey:(NSData * _Nullable)encryptionKey
|
encryptionKey:(NSData * _Nullable)encryptionKey
|
||||||
isConference:(bool)isConference {
|
isConference:(bool)isConference
|
||||||
|
isActiveByDefault:(bool)isActiveByDefault {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (self != nil) {
|
if (self != nil) {
|
||||||
_queue = queue;
|
_queue = queue;
|
||||||
@ -2636,10 +2665,9 @@ isConference:(bool)isConference {
|
|||||||
return resultModule;
|
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) {
|
if (audioDeviceModule) {
|
||||||
auto result = audioDeviceModule->getSyncAssumingSameThread()->makeChildAudioDeviceModule();
|
auto result = audioDeviceModule->getSyncAssumingSameThread()->makeChildAudioDeviceModule(isActiveByDefault);
|
||||||
((WrappedChildAudioDeviceModule *)result.get())->setIsActive();
|
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -2983,6 +3011,14 @@ isConference:(bool)isConference {
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)activateIncomingAudio {
|
- (void)activateIncomingAudio {
|
||||||
|
if (_currentAudioDeviceModuleThread) {
|
||||||
|
auto currentAudioDeviceModule = _currentAudioDeviceModule;
|
||||||
|
if (currentAudioDeviceModule) {
|
||||||
|
_currentAudioDeviceModuleThread->PostTask([currentAudioDeviceModule]() {
|
||||||
|
((tgcalls::WrappedAudioDeviceModule *)currentAudioDeviceModule.get())->setIsActive(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit bc8334224dbefb4591d669f7569d16f69134c5b6
|
Subproject commit 29862dd6202e5f71db38e9722fecfd0ab3078268
|
@ -91,6 +91,7 @@ public enum ParsedInternalUrl {
|
|||||||
case stickerPack(name: String, type: StickerPackUrlType)
|
case stickerPack(name: String, type: StickerPackUrlType)
|
||||||
case invoice(String)
|
case invoice(String)
|
||||||
case join(String)
|
case join(String)
|
||||||
|
case joinCall(String)
|
||||||
case localization(String)
|
case localization(String)
|
||||||
case proxy(host: String, port: Int32, username: String?, password: String?, secret: Data?)
|
case proxy(host: String, port: Int32, username: String?, password: String?, secret: Data?)
|
||||||
case internalInstantView(url: String)
|
case internalInstantView(url: String)
|
||||||
@ -400,6 +401,12 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, context: Accou
|
|||||||
return .invoice(pathComponents[1])
|
return .invoice(pathComponents[1])
|
||||||
} else if pathComponents[0] == "joinchat" || pathComponents[0] == "joinchannel" {
|
} else if pathComponents[0] == "joinchat" || pathComponents[0] == "joinchannel" {
|
||||||
return .join(pathComponents[1])
|
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" {
|
} else if pathComponents[0] == "setlanguage" {
|
||||||
return .localization(pathComponents[1])
|
return .localization(pathComponents[1])
|
||||||
} else if pathComponents[0] == "login" {
|
} else if pathComponents[0] == "login" {
|
||||||
@ -1036,6 +1043,8 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
|
|||||||
})
|
})
|
||||||
case let .join(link):
|
case let .join(link):
|
||||||
return .single(.result(.join(link)))
|
return .single(.result(.join(link)))
|
||||||
|
case let .joinCall(link):
|
||||||
|
return .single(.result(.joinCall(link)))
|
||||||
case let .localization(identifier):
|
case let .localization(identifier):
|
||||||
return .single(.result(.localization(identifier)))
|
return .single(.result(.localization(identifier)))
|
||||||
case let .proxy(host, port, username, password, secret):
|
case let .proxy(host, port, username, password, secret):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user