[WIP] Conference

This commit is contained in:
Isaac 2025-03-19 18:55:24 +01:00
parent 40b19cfef2
commit 17dd059f13
33 changed files with 2034 additions and 283 deletions

View File

@ -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
) )

View File

@ -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

View File

@ -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",

View File

@ -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 {

View File

@ -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)

View File

@ -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, _, _, _, _, _, _, _, _):
@ -130,6 +143,12 @@ enum CallListNodeEntry: Comparable, Identifiable {
} else { } else {
return false return false
} }
case .createGroupCall:
if case .createGroupCall = rhs {
return true
} else {
return false
}
case let .groupCall(lhsPeer, lhsEditing, lhsIsActive): case let .groupCall(lhsPeer, lhsEditing, lhsIsActive):
if case let .groupCall(rhsPeer, rhsEditing, rhsIsActive) = rhs { if case let .groupCall(rhsPeer, rhsEditing, rhsIsActive) = rhs {
if lhsPeer != rhsPeer { if lhsPeer != rhsPeer {
@ -193,7 +212,7 @@ enum CallListNodeEntry: Comparable, Identifiable {
} }
} }
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))

View File

@ -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 {

View File

@ -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",

View File

@ -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,11 +376,16 @@ public final class InviteLinkInviteController: ViewController {
} }
}))) })))
if case let .groupOrChannel(peerId) = self.mode {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor)
}, action: { _, f in }, action: { [weak self] _, f in
f(.dismissWithoutContent) f(.dismissWithoutContent)
guard let self else {
return
}
if let invite = invite { 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
@ -379,11 +407,15 @@ public final class InviteLinkInviteController: ViewController {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in 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) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { _, f in }, action: { [ weak self] _, f in
f(.dismissWithoutContent) f(.dismissWithoutContent)
guard let self else {
return
}
let _ = (context.account.postbox.loadedPeerWithId(peerId) let _ = (context.account.postbox.loadedPeerWithId(peerId)
|> deliverOnMainQueue).start(next: { peer in |> deliverOnMainQueue).start(next: { [weak self] peer in
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
@ -418,15 +450,25 @@ public final class InviteLinkInviteController: ViewController {
}) })
}))) })))
}
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()
if let completed = self.controller?.completed {
self.controller?.dismiss()
completed(.linkCopied)
} else {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } 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)) 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,14 +538,19 @@ public final class InviteLinkInviteController: ViewController {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
if case let .groupOrChannel(peerId) = strongSelf.mode {
let updatedPresentationData = (strongSelf.presentationData, strongSelf.presentationDataPromise.get()) let updatedPresentationData = (strongSelf.presentationData, strongSelf.presentationDataPromise.get())
let controller = inviteLinkListController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, admin: nil) let controller = inviteLinkListController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, admin: nil)
strongSelf.controller?.parentNavigationController?.pushViewController(controller) strongSelf.controller?.parentNavigationController?.pushViewController(controller)
strongSelf.controller?.dismiss() strongSelf.controller?.dismiss()
}
}) })
let previousEntries = Atomic<[InviteLinkInviteEntry]?>(value: nil) let previousEntries = Atomic<[InviteLinkInviteEntry]?>(value: nil)
switch mode {
case let .groupOrChannel(peerId):
let peerView = context.account.postbox.peerView(id: peerId) let peerView = context.account.postbox.peerView(id: peerId)
let invites: Signal<PeerExportedInvitationsState, NoError> = .single(PeerExportedInvitationsState()) let invites: Signal<PeerExportedInvitationsState, NoError> = .single(PeerExportedInvitationsState())
self.disposable = (combineLatest(self.presentationDataPromise.get(), peerView, mainInvitePromise.get(), invites) self.disposable = (combineLatest(self.presentationDataPromise.get(), peerView, mainInvitePromise.get(), invites)
@ -517,7 +564,7 @@ public final class InviteLinkInviteController: ViewController {
} else { } else {
helpText = presentationData.strings.InviteLink_CreatePrivateLinkHelp helpText = presentationData.strings.InviteLink_CreatePrivateLinkHelp
} }
entries.append(.header(presentationData.theme, presentationData.strings.InviteLink_InviteLink, helpText)) entries.append(.header(title: presentationData.strings.InviteLink_InviteLink, text: helpText))
let mainInvite: ExportedInvitation? let mainInvite: ExportedInvitation?
if let invite = interactiveMainInvite { if let invite = interactiveMainInvite {
@ -530,8 +577,8 @@ public final class InviteLinkInviteController: ViewController {
mainInvite = nil mainInvite = nil
} }
entries.append(.mainLink(presentationData.theme, mainInvite)) entries.append(.mainLink(invitation: mainInvite, isCall: false, isRecentlyCreated: false))
entries.append(.manage(presentationData.theme, presentationData.strings.InviteLink_Manage, true)) entries.append(.manage(text: presentationData.strings.InviteLink_Manage, standalone: true))
let previousEntries = previousEntries.swap(entries) let previousEntries = previousEntries.swap(entries)
@ -539,6 +586,34 @@ public final class InviteLinkInviteController: ViewController {
strongSelf.enqueueTransition(transition) 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] = []
//TODO:localize
let helpText: String = "Anyone on Telegram can join your call by following the link below."
entries.append(.header(title: "Call Link", text: helpText))
let mainInvite: ExportedInvitation = .link(link: link, title: nil, isPermanent: true, requestApproval: false, isRevoked: false, adminId: self.context.account.peerId, date: 0, startDate: nil, expireDate: nil, usageLimit: nil, count: nil, requestedCount: nil, pricing: nil)
entries.append(.mainLink(invitation: mainInvite, isCall: true, isRecentlyCreated: isRecentlyCreated))
let previousEntries = previousEntries.swap(entries)
let transition = preparedTransition(from: previousEntries ?? [], to: entries, isLoading: false, account: context.account, presentationData: presentationData, interaction: self.interaction!)
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)
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.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()
})
}

View File

@ -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)
} }
}) })
} }

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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()

View File

@ -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",

View File

@ -1680,6 +1680,10 @@ public final class PresentationCallImpl: PresentationCall {
} }
} }
} }
func deactivateIncomingAudio() {
self.ongoingContext?.deactivateIncomingAudio()
}
} }
func sampleBufferFromPixelBuffer(pixelBuffer: CVPixelBuffer) -> CMSampleBuffer? { func sampleBufferFromPixelBuffer(pixelBuffer: CVPixelBuffer) -> CMSampleBuffer? {

View File

@ -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()
}
} }
} }

View File

@ -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)

View File

@ -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:

View File

@ -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()

View 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",
],
)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)
}) })
} }

View File

@ -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 {

View File

@ -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;

View File

@ -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

View File

@ -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):