Merge commit 'f041078fe401812660b1c7be8dc5265e88ba35a8'

This commit is contained in:
Ali 2020-12-13 18:09:44 +04:00
commit 6c94c37ae0
20 changed files with 992 additions and 737 deletions

View File

@ -5962,7 +5962,7 @@ Sorry for the inconvenience.";
"VoiceChat.InvitedPeerText" = "You invited %@ to the voice chat";
"VoiceChat.RemovedPeerText" = "You removed %@ from this group";
"Notification.VoiceChatStarted" = "%@ started a voice chat";
"Notification.VoiceChatStarted" = "%1$@ started a voice chat";
"Notification.VoiceChatEnded" = "Voice chat ended (%@)";
"VoiceChat.Panel.TapToJoin" = "Tap to join";

View File

@ -474,6 +474,8 @@ public protocol ChatController: ViewController {
func updatePresentationMode(_ mode: ChatControllerPresentationMode)
func beginMessageSearch(_ query: String)
func displayPromoAnnouncement(text: String)
var isSendButtonVisible: Bool { get }
}
public protocol ChatMessagePreviewItemNode: class {

View File

@ -19,7 +19,8 @@ typedef enum {
TGCameraControllerPassportIdIntent,
TGCameraControllerPassportMultipleIntent,
TGCameraControllerAvatarIntent,
TGCameraControllerSignupAvatarIntent
TGCameraControllerSignupAvatarIntent,
TGCameraControllerGenericPhotoOnlyIntent
} TGCameraControllerIntent;
@interface TGCameraControllerWindow : TGOverlayControllerWindow

View File

@ -1250,7 +1250,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
}
}];
bool hasCamera = !self.inhibitMultipleCapture && ((_intent == TGCameraControllerGenericIntent && !_shortcut) || (_intent == TGCameraControllerPassportMultipleIntent));
bool hasCamera = !self.inhibitMultipleCapture && (((_intent == TGCameraControllerGenericIntent || _intent == TGCameraControllerGenericPhotoOnlyIntent) && !_shortcut) || (_intent == TGCameraControllerPassportMultipleIntent));
TGMediaPickerGalleryModel *model = [[TGMediaPickerGalleryModel alloc] initWithContext:windowContext items:galleryItems focusItem:focusItem selectionContext:_items.count > 1 ? selectionContext : nil editingContext:editingContext hasCaptions:self.allowCaptions allowCaptionEntities:self.allowCaptionEntities hasTimer:self.hasTimer onlyCrop:_intent == TGCameraControllerPassportIntent || _intent == TGCameraControllerPassportIdIntent || _intent == TGCameraControllerPassportMultipleIntent inhibitDocumentCaptions:self.inhibitDocumentCaptions hasSelectionPanel:true hasCamera:hasCamera recipientName:self.recipientName];
model.inhibitMute = self.inhibitMute;
model.controller = galleryController;

View File

@ -37,6 +37,7 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
case speaker
case airpods
case airpodsPro
case headphones
case accept
case end
}
@ -221,6 +222,8 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallAirpodsButton"), color: imageColor)
case .airpodsPro:
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallAirpodsProButton"), color: imageColor)
case .headphones:
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallHeadphonesButton"), color: imageColor)
case .accept:
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallAcceptButton"), color: imageColor)
case .end:

View File

@ -51,6 +51,7 @@ private enum ButtonDescription: Equatable {
case bluetooth
case airpods
case airpodsPro
case headphones
}
enum EndType {
@ -205,7 +206,7 @@ final class CallControllerButtonsNode: ASDisplayNode {
case .speaker:
soundOutput = .speaker
case .headphones:
soundOutput = .bluetooth
soundOutput = .headphones
case let .bluetooth(type):
switch type {
case .generic:
@ -296,7 +297,7 @@ final class CallControllerButtonsNode: ASDisplayNode {
case .speaker:
soundOutput = .speaker
case .headphones:
soundOutput = .builtin
soundOutput = .headphones
case let .bluetooth(type):
switch type {
case .generic:
@ -467,6 +468,9 @@ final class CallControllerButtonsNode: ASDisplayNode {
case .airpodsPro:
image = .airpodsPro
title = strings.Call_Audio
case .headphones:
image = .headphones
title = strings.Call_Audio
}
buttonContent = CallControllerButtonItemNode.Content(
appearance: .blurred(isFilled: isFilled),

View File

@ -296,7 +296,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
membersText = self.strings.VoiceChat_Panel_Members(Int32(data.participantCount))
}
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }.filter { $0.id != self.context.account.peerId }, animated: false)
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }, animated: false)
self.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: self.theme.chat.inputPanel.secondaryTextColor)
@ -321,7 +321,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
strongSelf.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: strongSelf.theme.chat.inputPanel.secondaryTextColor)
strongSelf.avatarsContent = strongSelf.avatarsContext.update(peers: summaryState.topParticipants.map { $0.peer }.filter { $0.id != strongSelf.context.account.peerId }, animated: false)
strongSelf.avatarsContent = strongSelf.avatarsContext.update(peers: summaryState.topParticipants.map { $0.peer }, animated: false)
if let (size, leftInset, rightInset) = strongSelf.validLayout {
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
@ -400,7 +400,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
self.textNode.attributedText = NSAttributedString(string: membersText, font: Font.regular(13.0), textColor: self.theme.chat.inputPanel.secondaryTextColor)
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }.filter { $0.id != self.context.account.peerId }, animated: false)
self.avatarsContent = self.avatarsContext.update(peers: data.topParticipants.map { $0.peer }, animated: false)
updateAudioLevels = true
}

View File

@ -82,8 +82,8 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|> distinctUntilChanged
}
public var hasActiveGroupCall: Bool {
return self.currentGroupCall != nil
public var hasActiveCall: Bool {
return self.currentCall != nil || self.currentGroupCall != nil
}
private let currentCallPromise = Promise<PresentationCall?>(nil)

View File

@ -20,8 +20,8 @@ private let areaSize = CGSize(width: 440.0, height: 440.0)
private let blobSize = CGSize(width: 244.0, height: 244.0)
final class VoiceChatActionButton: HighlightTrackingButtonNode {
enum State {
enum ActiveState {
enum State: Equatable {
enum ActiveState: Equatable {
case cantSpeak
case muted
case on
@ -31,6 +31,14 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
case active(state: ActiveState)
}
var stateValue: State {
return self.currentParams?.state ?? .connecting
}
var statePromise = ValuePromise<State>()
var state: Signal<State, NoError> {
return self.statePromise.get()
}
private let containerNode: ASDisplayNode
private let backgroundNode: VoiceChatActionButtonBackgroundNode
private let iconNode: VoiceChatMicrophoneNode
@ -167,7 +175,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
let subtitleSize = self.subtitleLabel.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
let totalHeight = titleSize.height + subtitleSize.height + 1.0
self.titleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor(size.height - totalHeight / 2.0) - 110.0), size: titleSize)
self.titleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor(size.height - totalHeight / 2.0) - 112.0), size: titleSize)
self.subtitleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: self.titleLabel.frame.maxY + 1.0), size: subtitleSize)
self.containerNode.frame = CGRect(origin: CGPoint(), size: size)
@ -209,7 +217,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
}
private func applyIconParams() {
guard let (size, _, state, _, small, title, subtitle, snap) = self.currentParams else {
guard let (_, _, state, _, _, _, _, snap) = self.currentParams else {
return
}
@ -250,6 +258,8 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
let previousState = previous?.state
self.currentParams = (size, buttonSize, state, dark, small, title, subtitle, previous?.snap ?? false)
self.statePromise.set(state)
var backgroundState: VoiceChatActionButtonBackgroundNode.State
switch state {
case let .active(state):
@ -392,6 +402,7 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
private var state: State
private var hasState = false
private var transition: State?
var audioLevel: CGFloat = 0.0 {

View File

@ -106,7 +106,7 @@ private final class VoiceChatControllerTitleNode: ASDisplayNode {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: UIColor(rgb: 0xffffff))
self.infoNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.5))
let constrainedSize = CGSize(width: size.width - 80.0, height: size.height)
let constrainedSize = CGSize(width: size.width - 120.0, height: size.height)
let titleSize = self.titleNode.measure(constrainedSize)
let infoSize = self.infoNode.measure(constrainedSize)
let titleInfoSpacing: CGFloat = 0.0
@ -128,7 +128,6 @@ public final class VoiceChatController: ViewController {
let isEmpty: Bool
let crossFade: Bool
let count: Int
let isExpanded: Bool
let animated: Bool
}
@ -363,14 +362,14 @@ public final class VoiceChatController: ViewController {
}
}
private func preparedTransition(from fromEntries: [ListEntry], to toEntries: [ListEntry], isLoading: Bool, isEmpty: Bool, crossFade: Bool, context: AccountContext, presentationData: PresentationData, interaction: Interaction, isExpanded: Bool) -> ListTransition {
private func preparedTransition(from fromEntries: [ListEntry], to toEntries: [ListEntry], isLoading: Bool, isEmpty: Bool, crossFade: Bool, context: AccountContext, presentationData: PresentationData, interaction: Interaction) -> ListTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
return ListTransition(deletions: deletions, insertions: insertions, updates: updates, isLoading: isLoading, isEmpty: isEmpty, crossFade: crossFade, count: toEntries.count, isExpanded: isExpanded, animated: true)
return ListTransition(deletions: deletions, insertions: insertions, updates: updates, isLoading: isLoading, isEmpty: isEmpty, crossFade: crossFade, count: toEntries.count, animated: true)
}
private weak var controller: VoiceChatController?
@ -414,7 +413,6 @@ public final class VoiceChatController: ViewController {
private var currentCallMembers: [GroupCallParticipantsContext.Participant]?
private var currentInvitedPeers: [Peer]?
private var currentSpeakingPeers: Set<PeerId>?
private var currentIsExpanded: Bool = false
private var currentContentOffset: CGFloat?
private var ignoreScrolling = false
private var accountPeer: Peer?
@ -476,6 +474,7 @@ public final class VoiceChatController: ViewController {
self.listNode = ListView()
self.listNode.verticalScrollIndicatorColor = UIColor(white: 1.0, alpha: 0.3)
self.listNode.clipsToBounds = true
self.listNode.scroller.bounces = false
self.topPanelNode = ASDisplayNode()
self.topPanelNode.clipsToBounds = false
@ -886,7 +885,7 @@ public final class VoiceChatController: ViewController {
}
}
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, callMembers: callMembers?.participants ?? [], invitedPeers: invitedPeers, speakingPeers: callMembers?.speakingParticipants ?? [], isExpanded: strongSelf.currentIsExpanded)
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, callMembers: callMembers?.participants ?? [], invitedPeers: invitedPeers, speakingPeers: callMembers?.speakingParticipants ?? [])
let subtitle = strongSelf.presentationData.strings.VoiceChat_Panel_Members(Int32(max(1, callMembers?.totalCount ?? 0)))
strongSelf.currentSubtitle = subtitle
@ -907,7 +906,7 @@ public final class VoiceChatController: ViewController {
}
if !strongSelf.didSetDataReady {
strongSelf.accountPeer = accountPeer
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, callMembers: strongSelf.currentCallMembers ?? [], invitedPeers: strongSelf.currentInvitedPeers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set(), isExpanded: strongSelf.currentIsExpanded)
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, callMembers: strongSelf.currentCallMembers ?? [], invitedPeers: strongSelf.currentInvitedPeers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set())
if let peer = peerViewMainPeer(view), let channel = peer as? TelegramChannel {
let addressName = channel.addressName ?? ""
@ -1100,40 +1099,11 @@ public final class VoiceChatController: ViewController {
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, transition in
if let strongSelf = self {
strongSelf.currentContentOffset = offset
strongSelf.updateFloatingHeaderOffset(offset: offset, transition: transition)
}
}
self.listNode.endedInteractiveDragging = { [weak self] in
guard let strongSelf = self else {
return
}
if strongSelf.ignoreScrolling {
Queue.mainQueue().after(0.5) {
strongSelf.ignoreScrolling = false
if strongSelf.animation == nil && !strongSelf.animatingExpansion {
strongSelf.updateFloatingHeaderOffset(offset: offset, transition: transition)
}
}
}
self.listNode.visibleContentOffsetChanged = { [weak self] offset in
guard let strongSelf = self else {
return
}
switch offset {
case let .known(value):
// strongSelf.updateFloatingHeaderOffset(offset: -value + strongSelf.listNode.insets.top, transition: strongSelf.listNode.isTracking ? .immediate : .animated(duration: 0.4, curve: .linear))
if value > 5, !strongSelf.currentIsExpanded && strongSelf.listNode.isTracking && !strongSelf.ignoreScrolling {
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, callMembers:strongSelf.currentCallMembers ?? [], invitedPeers: strongSelf.currentInvitedPeers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set(), isExpanded: true)
strongSelf.ignoreScrolling = true
} else if value < -5, strongSelf.currentIsExpanded && strongSelf.listNode.isTracking && !strongSelf.ignoreScrolling {
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, callMembers: strongSelf.currentCallMembers ?? [], invitedPeers: strongSelf.currentInvitedPeers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? [], isExpanded: false)
strongSelf.ignoreScrolling = true
}
default:
break
}
}
}
deinit {
@ -1223,7 +1193,7 @@ public final class VoiceChatController: ViewController {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
}
self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? [], invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set(), isExpanded: self.currentIsExpanded)
self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? [], invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set())
}
@objc private func actionButtonPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) {
@ -1268,7 +1238,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
}
self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? [], invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set(), isExpanded: self.currentIsExpanded)
self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? [], invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set())
default:
break
}
@ -1341,7 +1311,7 @@ public final class VoiceChatController: ViewController {
}
}
private func updateFloatingHeaderOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
private func updateFloatingHeaderOffset(offset: CGFloat, transition: ContainedViewLayoutTransition, completion: (() -> Void)? = nil) {
guard let (layout, _) = self.validLayout else {
return
}
@ -1352,9 +1322,22 @@ public final class VoiceChatController: ViewController {
let bottomAreaHeight: CGFloat = 268.0
let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
let listSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
let topInset = self.topInset ?? listSize.height
let topInset: CGFloat
if let (panInitialTopInset, panOffset) = self.panGestureArguments {
if self.isExpanded {
topInset = min(self.topInset ?? listSize.height, panInitialTopInset + max(0.0, panOffset))
} else {
topInset = max(0.0, panInitialTopInset + min(0.0, panOffset))
}
} else if let _ = self.animation {
topInset = self.listNode.frame.minY - listTopInset
} else if let currentTopInset = self.topInset {
topInset = self.isExpanded ? 0.0 : currentTopInset
} else {
topInset = listSize.height
}
var offset = offset + topInset
let offset = offset + topInset
self.floatingHeaderOffset = offset
let rawPanelOffset = offset + listTopInset - topPanelHeight
@ -1374,7 +1357,7 @@ public final class VoiceChatController: ViewController {
if !topPanelFrame.equalTo(previousTopPanelFrame) {
self.topPanelNode.frame = topPanelFrame
let positionDelta = CGPoint(x: 0.0, y: topPanelFrame.minY - previousTopPanelFrame.minY)
transition.animateOffsetAdditive(node: self.topPanelNode, offset: positionDelta.y)
transition.animateOffsetAdditive(layer: self.topPanelNode.layer, offset: positionDelta.y, completion: completion)
self.backgroundNode.frame = backgroundFrame
let backgroundPositionDelta = CGPoint(x: 0.0, y: previousBackgroundFrame.minY - backgroundFrame.minY)
@ -1387,6 +1370,8 @@ public final class VoiceChatController: ViewController {
self.rightBorderNode.frame = rightBorderFrame
let rightBorderPositionDelta = CGPoint(x: 0.0, y: previousRightBorderFrame.minY - rightBorderFrame.minY)
transition.animatePositionAdditive(node: self.rightBorderNode, offset: rightBorderPositionDelta)
} else {
completion?()
}
self.topPanelBackgroundNode.frame = CGRect(x: 0.0, y: topPanelHeight - 24.0, width: layout.size.width, height: 24.0)
@ -1422,19 +1407,17 @@ public final class VoiceChatController: ViewController {
}
var isFullscreen = false
func updateColors(fullscreen: Bool) {
guard self.isFullscreen != fullscreen, let (layout, _) = self.validLayout else {
func updateIsFullscreen(_ isFullscreen: Bool) {
guard self.isFullscreen != isFullscreen, let (layout, _) = self.validLayout else {
return
}
self.isFullscreen = fullscreen
self.controller?.statusBar.statusBarStyle = fullscreen ? .White : .Ignore
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .linear)
self.isFullscreen = isFullscreen
self.controller?.statusBar.statusBarStyle = isFullscreen ? .White : .Ignore
let topPanelHeight: CGFloat = 63.0
let topEdgeFrame: CGRect
if self.isFullscreen {
if isFullscreen {
let offset: CGFloat
if let statusBarHeight = layout.statusBarHeight {
offset = statusBarHeight
@ -1445,16 +1428,17 @@ public final class VoiceChatController: ViewController {
} else {
topEdgeFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: topPanelHeight)
}
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .linear)
transition.updateFrame(node: self.topPanelEdgeNode, frame: topEdgeFrame)
transition.updateCornerRadius(node: self.topPanelEdgeNode, cornerRadius: fullscreen ? layout.deviceMetrics.screenCornerRadius - 0.5 : 12.0)
// transition.updateBackgroundColor(node: self.dimNode, color: fullscreen ? fullscreenBackgroundColor : dimColor)
transition.updateBackgroundColor(node: self.topPanelBackgroundNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.topPanelEdgeNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.backgroundNode, color: fullscreen ? panelBackgroundColor : secondaryPanelBackgroundColor)
transition.updateBackgroundColor(node: self.bottomPanelBackgroundNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.leftBorderNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.rightBorderNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.rightBorderNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateCornerRadius(node: self.topPanelEdgeNode, cornerRadius: isFullscreen ? layout.deviceMetrics.screenCornerRadius - 0.5 : 12.0)
transition.updateBackgroundColor(node: self.topPanelBackgroundNode, color: isFullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.topPanelEdgeNode, color: isFullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.backgroundNode, color: isFullscreen ? panelBackgroundColor : secondaryPanelBackgroundColor)
transition.updateBackgroundColor(node: self.bottomPanelBackgroundNode, color: isFullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.leftBorderNode, color: isFullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.rightBorderNode, color: isFullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.rightBorderNode, color: isFullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
if let snapshotView = self.topCornersNode.view.snapshotContentTree() {
snapshotView.frame = self.topCornersNode.frame
@ -1464,7 +1448,7 @@ public final class VoiceChatController: ViewController {
snapshotView?.removeFromSuperview()
})
}
self.topCornersNode.image = cornersImage(top: true, bottom: false, dark: fullscreen)
self.topCornersNode.image = cornersImage(top: true, bottom: false, dark: isFullscreen)
if let snapshotView = self.bottomCornersNode.view.snapshotContentTree() {
snapshotView.frame = self.bottomCornersNode.bounds
@ -1474,10 +1458,10 @@ public final class VoiceChatController: ViewController {
snapshotView?.removeFromSuperview()
})
}
self.bottomCornersNode.image = cornersImage(top: false, bottom: true, dark: fullscreen)
self.bottomCornersNode.image = cornersImage(top: false, bottom: true, dark: isFullscreen)
self.optionsButton.setImage(optionsButtonImage(dark: fullscreen), animated: transition.isAnimated)
self.closeButton.setImage(closeButtonImage(dark: fullscreen), animated: transition.isAnimated)
self.optionsButton.setImage(optionsButtonImage(dark: isFullscreen), animated: transition.isAnimated)
self.closeButton.setImage(closeButtonImage(dark: isFullscreen), animated: transition.isAnimated)
self.updateTitle(transition: transition)
}
@ -1536,6 +1520,7 @@ public final class VoiceChatController: ViewController {
let soundImage: CallControllerButtonItemNode.Content.Image
var soundAppearance: CallControllerButtonItemNode.Content.Appearance = audioButtonAppearance
var soundTitle: String = self.presentationData.strings.Call_Speaker
switch audioMode {
case .none, .builtin:
soundImage = .speaker
@ -1543,7 +1528,8 @@ public final class VoiceChatController: ViewController {
soundImage = .speaker
soundAppearance = .blurred(isFilled: true)
case .headphones:
soundImage = .bluetooth
soundImage = .headphones
soundTitle = self.presentationData.strings.Call_Audio
case let .bluetooth(type):
switch type {
case .generic:
@ -1553,10 +1539,11 @@ public final class VoiceChatController: ViewController {
case .airpodsPro:
soundImage = .airpodsPro
}
soundTitle = self.presentationData.strings.Call_Audio
}
let sideButtonSize = CGSize(width: 60.0, height: 60.0)
self.audioOutputNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: soundAppearance, image: soundImage), text: self.presentationData.strings.VoiceChat_Audio, transition: .animated(duration: 0.3, curve: .linear))
self.audioOutputNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: soundAppearance, image: soundImage), text: soundTitle, transition: .animated(duration: 0.3, curve: .linear))
self.leaveNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: .color(.custom(0x602522)), image: .end), text: self.presentationData.strings.VoiceChat_Leave, transition: .immediate)
}
@ -1583,25 +1570,43 @@ public final class VoiceChatController: ViewController {
insets.right = layout.safeInsets.right + sideInset
let topPanelHeight: CGFloat = 63.0
let topEdgeFrame: CGRect
if self.isFullscreen {
let offset: CGFloat
if let statusBarHeight = layout.statusBarHeight {
offset = statusBarHeight
} else {
offset = 44.0
}
topEdgeFrame = CGRect(x: 0.0, y: -offset, width: layout.size.width, height: topPanelHeight + offset)
if let _ = self.panGestureArguments {
} else {
topEdgeFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: topPanelHeight)
let topEdgeFrame: CGRect
if self.isFullscreen {
let offset: CGFloat
if let statusBarHeight = layout.statusBarHeight {
offset = statusBarHeight
} else {
offset = 44.0
}
topEdgeFrame = CGRect(x: 0.0, y: -offset, width: layout.size.width, height: topPanelHeight + offset)
} else {
topEdgeFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: topPanelHeight)
}
transition.updateFrame(node: self.topPanelEdgeNode, frame: topEdgeFrame)
}
transition.updateFrame(node: self.topPanelEdgeNode, frame: topEdgeFrame)
let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
let listTopInset = layoutTopInset + topPanelHeight
let listSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(x: 0.0, y: listTopInset + (self.topInset ?? listSize.height)), size: listSize))
let topInset: CGFloat
if let (panInitialTopInset, panOffset) = self.panGestureArguments {
if self.isExpanded {
topInset = min(self.topInset ?? listSize.height, panInitialTopInset + max(0.0, panOffset))
} else {
topInset = max(0.0, panInitialTopInset + min(0.0, panOffset))
}
} else if let currentTopInset = self.topInset {
topInset = self.isExpanded ? 0.0 : currentTopInset
} else {
topInset = listSize.height
}
if self.animation == nil {
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(x: 0.0, y: listTopInset + topInset), size: listSize))
}
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listSize, insets: insets, duration: duration, curve: curve)
@ -1695,7 +1700,7 @@ public final class VoiceChatController: ViewController {
}, completion: { _ in
if self.actionButton.supernode !== self.bottomPanelNode {
self.bottomPanelNode.addSubnode(self.actionButton)
self.containerLayoutUpdated(layout, navigationHeight:navigationHeight, transition: .immediate)
self.containerLayoutUpdated(layout, navigationHeight :navigationHeight, transition: .immediate)
}
self.controller?.currentOverlayController?.dismiss()
@ -1778,24 +1783,24 @@ public final class VoiceChatController: ViewController {
let listTopInset = layoutTopInset + 63.0
let listSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
let previousIsExpanded = self.currentIsExpanded
self.currentIsExpanded = transition.isExpanded
self.topInset = max(0.0, transition.isExpanded ? 0.0 : max(listSize.height - itemsHeight, listSize.height - 46.0 - floor(56.0 * 3.5)))
self.topInset = max(0.0, max(listSize.height - itemsHeight, listSize.height - 46.0 - floor(56.0 * 3.5)))
let frameTransition: ContainedViewLayoutTransition
if previousIsExpanded != self.currentIsExpanded {
frameTransition = .animated(duration: 0.4, curve: .spring)
} else {
frameTransition = .animated(duration: 0.4, curve: .easeInOut)
if !self.isExpanded {
let targetY = listTopInset + (self.topInset ?? listSize.height)
if self.listNode.frame.minY != targetY && !self.animatingExpansion && self.panGestureArguments == nil {
self.animation = ListViewAnimation(from: self.listNode.frame.minY, to: targetY, duration: 0.4, curve: listViewAnimationCurveEaseInOut, beginAt: CACurrentMediaTime(), update: { [weak self] _, currentValue in
if let strongSelf = self {
var frame = strongSelf.listNode.frame
frame.origin.y = currentValue
strongSelf.listNode.frame = frame
strongSelf.updateFloatingHeaderOffset(offset: strongSelf.currentContentOffset ?? 0.0, transition: .immediate)
}
})
self.updateAnimation()
}
}
frameTransition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(x: 0.0, y: listTopInset + (self.topInset ?? listSize.height)), size: listSize))
let (duration, curve) = listViewAnimationDurationAndCurve(transition: frameTransition)
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listSize, insets: insets, duration: duration, curve: curve)
self.updateColors(fullscreen: transition.isExpanded)
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, updateOpaqueState: nil, completion: { [weak self] _ in
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: nil, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
guard let strongSelf = self else {
return
}
@ -1804,12 +1809,42 @@ public final class VoiceChatController: ViewController {
strongSelf.controller?.contentsReady.set(true)
}
})
if previousIsExpanded != self.currentIsExpanded {
self.updateFloatingHeaderOffset(offset: self.currentContentOffset ?? 0.0, transition: frameTransition)
}
private var animator: ConstantDisplayLinkAnimator?
private var animation: ListViewAnimation?
private func updateAnimation() {
var animate = false
let timestamp = CACurrentMediaTime()
if let animation = self.animation {
animation.applyAt(timestamp)
if animation.completeAt(timestamp) {
self.animation = nil
} else {
animate = true
}
}
if animate {
let animator: ConstantDisplayLinkAnimator
if let current = self.animator {
animator = current
} else {
animator = ConstantDisplayLinkAnimator(update: { [weak self] in
self?.updateAnimation()
})
self.animator = animator
}
animator.isPaused = false
} else {
self.animator?.isPaused = true
}
}
private func updateMembers(muteState: GroupCallParticipantsContext.Participant.MuteState?, callMembers: [GroupCallParticipantsContext.Participant], invitedPeers: [Peer], speakingPeers: Set<PeerId>, isExpanded: Bool) {
private func updateMembers(muteState: GroupCallParticipantsContext.Participant.MuteState?, callMembers: [GroupCallParticipantsContext.Participant], invitedPeers: [Peer], speakingPeers: Set<PeerId>) {
self.currentCallMembers = callMembers
self.currentSpeakingPeers = speakingPeers
self.currentInvitedPeers = invitedPeers
@ -1874,7 +1909,7 @@ public final class VoiceChatController: ViewController {
self.currentEntries = entries
let presentationData = self.presentationData.withUpdated(theme: self.darkTheme)
let transition = preparedTransition(from: previousEntries, to: entries, isLoading: false, isEmpty: false, crossFade: false, context: self.context, presentationData: presentationData, interaction: self.itemInteraction!, isExpanded: isExpanded ?? self.currentIsExpanded)
let transition = preparedTransition(from: previousEntries, to: entries, isLoading: false, isEmpty: false, crossFade: false, context: self.context, presentationData: presentationData, interaction: self.itemInteraction!)
self.enqueueTransition(transition)
}
@ -1882,56 +1917,190 @@ public final class VoiceChatController: ViewController {
// if let callState = self.callState, case .connected = callState.networkState, let muteState = callState.muteState, !muteState.canUnmute {
// return false
// }
if let recognizer = gestureRecognizer as? UIPanGestureRecognizer {
let location = recognizer.location(in: self.view)
if let view = super.hitTest(location, with: nil) {
if let gestureRecognizers = view.gestureRecognizers, view != self.view {
for gestureRecognizer in gestureRecognizers {
if let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer, gestureRecognizer.isEnabled {
if panGestureRecognizer.state != .began {
panGestureRecognizer.isEnabled = false
panGestureRecognizer.isEnabled = true
}
}
}
}
}
}
// if let recognizer = gestureRecognizer as? UIPanGestureRecognizer {
// let location = recognizer.location(in: self.view)
// if let view = super.hitTest(location, with: nil) {
// if let gestureRecognizers = view.gestureRecognizers, view != self.view {
// for gestureRecognizer in gestureRecognizers {
// if let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer, gestureRecognizer.isEnabled {
// if panGestureRecognizer.state != .began {
// panGestureRecognizer.isEnabled = false
// panGestureRecognizer.isEnabled = true
// }
// }
// }
// }
// }
// }
return true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer is UIPanGestureRecognizer && otherGestureRecognizer is UIPanGestureRecognizer {
return true
}
return false
}
private var isExpanded = false
private var animatingExpansion = false
private var panGestureArguments: (topInset: CGFloat, offset: CGFloat)?
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
break
let topInset: CGFloat
if self.isExpanded {
topInset = 0.0
} else if let currentTopInset = self.topInset {
topInset = currentTopInset
} else {
topInset = self.listNode.frame.height
}
self.panGestureArguments = (topInset, 0.0)
case .changed:
var translation = recognizer.translation(in: self.contentContainer.view).y
var topInset: CGFloat = 0.0
if let (currentTopInset, currentPanOffset) = self.panGestureArguments {
topInset = currentTopInset
if case let .known(value) = self.listNode.visibleContentOffset(), value > 0 {
translation = currentPanOffset
if self.isExpanded {
recognizer.setTranslation(CGPoint(), in: self.contentContainer.view)
}
}
self.panGestureArguments = (currentTopInset, translation)
}
let currentOffset = topInset + translation
if currentOffset < 20.0 {
self.updateIsFullscreen(true)
} else if currentOffset > 40.0 {
self.updateIsFullscreen(false)
}
if self.isExpanded {
} else {
if currentOffset > 0.0 {
self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller)
}
}
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate)
self.updateFloatingHeaderOffset(offset: self.currentContentOffset ?? 0.0, transition: .immediate)
}
if !self.isExpanded {
var bounds = self.contentContainer.bounds
bounds.origin.y = -translation
bounds.origin.y = min(0.0, bounds.origin.y)
self.contentContainer.bounds = bounds
}
case .ended:
let translation = recognizer.translation(in: self.contentContainer.view)
var velocity = recognizer.velocity(in: self.contentContainer.view)
if case let .known(value) = self.listNode.visibleContentOffset(), value > 0 {
velocity = CGPoint()
}
var bounds = self.contentContainer.bounds
bounds.origin.y = -translation.y
bounds.origin.y = min(0.0, bounds.origin.y)
self.contentContainer.bounds = bounds
case .ended:
let translation = recognizer.translation(in: self.contentContainer.view)
var bounds = self.contentContainer.bounds
bounds.origin.y = -translation.y
let velocity = recognizer.velocity(in: self.contentContainer.view)
if (bounds.minY < -60.0 || velocity.y > 300.0) {
self.controller?.dismiss(closing: false)
let offset: CGFloat
if let (inset, panOffset) = self.panGestureArguments {
offset = inset + panOffset
} else {
var bounds = self.contentContainer.bounds
let previousBounds = bounds
bounds.origin.y = 0.0
self.contentContainer.bounds = bounds
self.contentContainer.layer.animateBounds(from: previousBounds, to: self.contentContainer.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
offset = 0.0
}
let topInset: CGFloat
if let currentTopInset = self.topInset {
topInset = currentTopInset
} else {
topInset = self.listNode.frame.height
}
if self.isExpanded {
self.panGestureArguments = nil
if velocity.y > 300.0 || offset > topInset / 2.0 {
self.isExpanded = false
self.updateIsFullscreen(false)
self.animatingExpansion = true
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
self.updateFloatingHeaderOffset(offset: self.currentContentOffset ?? 0.0, transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
self.animatingExpansion = false
})
} else {
self.updateIsFullscreen(true)
self.animatingExpansion = true
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
self.updateFloatingHeaderOffset(offset: self.currentContentOffset ?? 0.0, transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
self.animatingExpansion = false
})
}
} else {
self.panGestureArguments = nil
var dismissing = false
if bounds.minY < -60 || (bounds.minY < 0.0 && velocity.y > 300.0) {
self.controller?.dismiss(closing: false)
dismissing = true
} else if velocity.y < -300.0 || offset < topInset / 2.0 {
self.isExpanded = true
self.updateIsFullscreen(true)
self.animatingExpansion = true
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
self.updateFloatingHeaderOffset(offset: self.currentContentOffset ?? 0.0, transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
self.animatingExpansion = false
})
} else {
self.updateIsFullscreen(false)
self.animatingExpansion = true
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
self.updateFloatingHeaderOffset(offset: self.currentContentOffset ?? 0.0, transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
self.animatingExpansion = false
})
}
if !dismissing {
var bounds = self.contentContainer.bounds
let previousBounds = bounds
bounds.origin.y = 0.0
self.contentContainer.bounds = bounds
self.contentContainer.layer.animateBounds(from: previousBounds, to: self.contentContainer.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
}
}
case .cancelled:
self.panGestureArguments = nil
let previousBounds = self.contentContainer.bounds
var bounds = self.contentContainer.bounds
bounds.origin.y = 0.0
self.contentContainer.bounds = bounds
self.contentContainer.layer.animateBounds(from: previousBounds, to: self.contentContainer.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
self.updateFloatingHeaderOffset(offset: self.currentContentOffset ?? 0.0, transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
self.animatingExpansion = false
})
default:
break
}
@ -1941,7 +2110,7 @@ public final class VoiceChatController: ViewController {
let result = super.hitTest(point, with: event)
if result === self.topPanelNode.view {
return self.listNode.view
return self.view
}
if result === self.bottomPanelNode.view {
@ -2071,7 +2240,11 @@ public final class VoiceChatController: ViewController {
if let navigationController = self.navigationController as? NavigationController {
let count = navigationController.viewControllers.count
if count == 2 || navigationController.viewControllers[count - 2] is ChatController {
self.detachActionButton()
if case .active(.cantSpeak) = self.controllerNode.actionButton.stateValue {
} else if let chatController = navigationController.viewControllers[count - 2] as? ChatController, chatController.isSendButtonVisible {
} else {
self.detachActionButton()
}
}
}
} else {

View File

@ -53,6 +53,7 @@ public final class VoiceChatOverlayController: ViewController {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring)
if hidden {
if slide {
actionButton.isHidden = false
transition.updateSublayerTransformOffset(layer: actionButton.layer, offset: CGPoint(x: slideOffset, y: 0.0))
} else {
actionButton.layer.removeAllAnimations()
@ -64,10 +65,10 @@ public final class VoiceChatOverlayController: ViewController {
}
} else {
actionButton.isHidden = false
actionButton.layer.removeAllAnimations()
if slide {
transition.updateSublayerTransformOffset(layer: actionButton.layer, offset: CGPoint())
} else {
actionButton.layer.removeAllAnimations()
actionButton.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
}
}
@ -91,10 +92,13 @@ public final class VoiceChatOverlayController: ViewController {
return
}
actionButton.update(snap: true, animated: !self.isSlidOffscreen)
actionButton.update(snap: true, animated: !self.isSlidOffscreen && !self.isButtonHidden)
if self.isSlidOffscreen {
actionButton.layer.sublayerTransform = CATransform3DMakeTranslation(slideOffset, 0.0, 0.0)
return
} else if self.isButtonHidden {
actionButton.isHidden = true
return
}
let targetPosition = actionButton.position
@ -125,12 +129,14 @@ public final class VoiceChatOverlayController: ViewController {
}
private var animating = false
private var dismissed = false
func animateOut(reclaim: Bool, completion: @escaping (Bool) -> Void) {
guard let actionButton = self.controller?.actionButton, let layout = self.validLayout else {
return
}
if reclaim {
self.dismissed = true
let targetPosition = CGPoint(x: layout.size.width / 2.0, y: layout.size.height - layout.intrinsicInsets.bottom - 268.0 / 2.0)
if self.isSlidOffscreen {
self.isSlidOffscreen = false
@ -139,6 +145,13 @@ public final class VoiceChatOverlayController: ViewController {
actionButton.update(snap: false, animated: false)
actionButton.position = CGPoint(x: targetPosition.x, y: 268.0 / 2.0)
completion(true)
} else if self.isButtonHidden {
actionButton.isHidden = false
actionButton.layer.removeAllAnimations()
actionButton.layer.sublayerTransform = CATransform3DIdentity
actionButton.update(snap: false, animated: false)
actionButton.position = CGPoint(x: targetPosition.x, y: 268.0 / 2.0)
completion(true)
} else {
self.animating = true
let sourcePoint = actionButton.position
@ -193,17 +206,18 @@ public final class VoiceChatOverlayController: ViewController {
return nil
}
private var didAnimateIn = false
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.validLayout = layout
if let actionButton = self.controller?.actionButton, !self.animating {
if let actionButton = self.controller?.actionButton, !self.animating && !self.dismissed {
let convertedRect = actionButton.view.convert(actionButton.bounds, to: self.view)
let insets = layout.insets(options: [.input])
transition.updatePosition(node: actionButton, position: CGPoint(x: layout.size.width - layout.safeInsets.right - 21.0, y: layout.size.height - insets.bottom - 22.0))
if actionButton.supernode !== self {
if actionButton.supernode !== self && !self.didAnimateIn {
self.didAnimateIn = true
self.addSubnode(actionButton)
self.animateIn(from: convertedRect)
}
}
@ -224,15 +238,18 @@ public final class VoiceChatOverlayController: ViewController {
super.init(navigationBarPresentationData: nil)
self.statusBar.statusBarStyle = .Ignore
self.additionalSideInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 75.0)
if case .active(.cantSpeak) = actionButton.stateValue {
} else {
self.additionalSideInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 75.0)
}
if let navigationController = navigationController {
let controllers: Signal<[UIViewController], NoError> = .single([])
|> then(navigationController.viewControllersSignal)
let overlayControllers: Signal<[UIViewController], NoError> = .single([])
|> then(navigationController.overlayControllersSignal)
self.disposable = (combineLatest(queue: Queue.mainQueue(), controllers, overlayControllers)).start(next: { [weak self] controllers, overlayControllers in
self.disposable = (combineLatest(queue: Queue.mainQueue(), controllers, overlayControllers, actionButton.state)).start(next: { [weak self] controllers, overlayControllers, state in
if let strongSelf = self {
var hasVoiceChatController = false
var overlayControllersCount = 0
@ -248,19 +265,37 @@ public final class VoiceChatOverlayController: ViewController {
}
}
var slide = true
var hidden = true
var animated = true
if controllers.count == 1 || controllers.last is ChatController {
hidden = false
if let chatController = controllers.last as? ChatController, chatController.isSendButtonVisible {
slide = false
animated = false
} else {
hidden = false
}
}
if overlayControllersCount > 0 {
hidden = true
}
if case .active(.cantSpeak) = state {
hidden = true
}
if hasVoiceChatController {
hidden = false
animated = false
}
strongSelf.controllerNode.update(hidden: hidden, slide: true, animated: animated)
strongSelf.controllerNode.update(hidden: hidden, slide: slide, animated: animated)
let previousInsets = strongSelf.additionalSideInsets
strongSelf.additionalSideInsets = hidden ? UIEdgeInsets() : UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 75.0)
if previousInsets != strongSelf.additionalSideInsets {
navigationController.requestLayout(transition: .animated(duration: 0.3, curve: .easeInOut))
}
}
})
}

View File

@ -405,13 +405,14 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
}
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
case let .groupPhoneCall(_, _, duration):
let titleString: String
if let duration = duration {
titleString = strings.Notification_VoiceChatEnded(callDurationString(strings: strings, value: duration)).0
let titleString = strings.Notification_VoiceChatEnded(callDurationString(strings: strings, value: duration)).0
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
} else {
titleString = strings.Notification_VoiceChatStarted(message.author?.compactDisplayTitle ?? "").0
var attributePeerIds: [(Int, PeerId?)] = [(0, message.author?.id)]
let titleString = strings.Notification_VoiceChatStarted(authorName)
attributedString = addAttributesToStringWithRanges(titleString, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds))
}
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
case let .customText(text, entities):
attributedString = stringWithAppliedEntities(text, entities: entities, baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, blockQuoteFont: titleFont, underlineLinks: false)
case let .botDomainAccessGranted(domain):

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_call_headphones.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -6227,9 +6227,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
let shouldBeActive = combineLatest(self.context.sharedContext.mediaManager.audioSession.isPlaybackActive() |> deliverOnMainQueue, self.chatDisplayNode.historyNode.hasVisiblePlayableItemNodes)
|> mapToSignal { [weak self] isPlaybackActive, hasVisiblePlayableItemNodes -> Signal<Bool, NoError> in
if hasVisiblePlayableItemNodes && !isPlaybackActive {
let hasActiveCalls: Signal<Bool, NoError>
if let callManager = self.context.sharedContext.callManager as? PresentationCallManagerImpl {
hasActiveCalls = callManager.hasActiveCalls
} else {
hasActiveCalls = .single(false)
}
let shouldBeActive = combineLatest(self.context.sharedContext.mediaManager.audioSession.isPlaybackActive() |> deliverOnMainQueue, self.chatDisplayNode.historyNode.hasVisiblePlayableItemNodes, hasActiveCalls)
|> mapToSignal { [weak self] isPlaybackActive, hasVisiblePlayableItemNodes, hasActiveCalls -> Signal<Bool, NoError> in
if hasVisiblePlayableItemNodes && !isPlaybackActive && !hasActiveCalls {
return Signal<Bool, NoError> { [weak self] subscriber in
guard let strongSelf = self else {
subscriber.putCompletion()
@ -7066,7 +7073,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.saveInterfaceState(includeScrollState: false)
}
if let navigationController = self.navigationController as? NavigationController {
if let navigationController = self.navigationController as? NavigationController, self.traceVisibility() && isTopmostChatController(self) {
var voiceChatOverlayController: VoiceChatOverlayController?
for controller in navigationController.globalOverlayControllers {
if let controller = controller as? VoiceChatOverlayController {
@ -7076,11 +7083,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
if let controller = voiceChatOverlayController {
var hidden = false
if self.presentationInterfaceState.interfaceState.editMessage != nil || self.presentationInterfaceState.interfaceState.forwardMessageIds != nil || self.presentationInterfaceState.interfaceState.composeInputState.inputText.string.count > 0 {
hidden = true
}
controller.update(hidden: hidden, slide: false, animated: true)
controller.update(hidden: self.isSendButtonVisible, slide: false, animated: true)
}
}
}
@ -7675,11 +7678,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
}, openCamera: { [weak self] cameraView, menuController in
if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
if let callManager = strongSelf.context.sharedContext.callManager as? PresentationCallManagerImpl, callManager.hasActiveGroupCall {
return
var photoOnly = false
if let callManager = strongSelf.context.sharedContext.callManager as? PresentationCallManagerImpl, callManager.hasActiveCall {
photoOnly = true
}
presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: settings.storeEditedPhotos, mediaGrouping: true, initialCaption: inputText.string, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in
presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: settings.storeEditedPhotos, mediaGrouping: true, initialCaption: inputText.string, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, photoOnly: photoOnly, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in
if let strongSelf = self {
if editMediaOptions != nil {
strongSelf.editMessageMediaWithLegacySignals(signals!)
@ -11313,6 +11317,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
//return self.chatDisplayNode.acceptEmbeddedTitlePeekContent(content: content)
return false
}
public var isSendButtonVisible: Bool {
if self.presentationInterfaceState.interfaceState.editMessage != nil || self.presentationInterfaceState.interfaceState.forwardMessageIds != nil || self.presentationInterfaceState.interfaceState.composeInputState.inputText.string.count > 0 {
return true
} else {
return false
}
}
}
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {

View File

@ -1236,7 +1236,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
let searchLayoutClearButtonSize = CGSize(width: 44.0, height: minimalHeight)
var textFieldInsets = self.textFieldInsets(metrics: metrics)
if additionalSideInsets.right > 0.0 && self.text.isEmpty {
if additionalSideInsets.right > 0.0 {
textFieldInsets.right += additionalSideInsets.right / 3.0
}
self.actionButtons.micButton.isHidden = additionalSideInsets.right > 0.0

View File

@ -11,7 +11,7 @@ import ShareController
import LegacyUI
import LegacyMediaPickerUI
func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocation: ChatLocation, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: String, hasSchedule: Bool, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?) {
func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocation: ChatLocation, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: String, hasSchedule: Bool, photoOnly: Bool, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?) {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait)
@ -23,7 +23,7 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocation: Ch
let controller: TGCameraController
if let cameraView = cameraView, let previewView = cameraView.previewView() {
controller = TGCameraController(context: legacyController.context, saveEditedPhotos: saveCapturedPhotos && !isSecretChat, saveCapturedMedia: saveCapturedPhotos && !isSecretChat, camera: previewView.camera, previewView: previewView, intent: TGCameraControllerGenericIntent)
controller = TGCameraController(context: legacyController.context, saveEditedPhotos: saveCapturedPhotos && !isSecretChat, saveCapturedMedia: saveCapturedPhotos && !isSecretChat, camera: previewView.camera, previewView: previewView, intent: photoOnly ? TGCameraControllerGenericPhotoOnlyIntent : TGCameraControllerGenericIntent)
controller.inhibitMultipleCapture = editingMedia
} else {
controller = TGCameraController()

View File

@ -109,6 +109,7 @@ public final class NotificationContainerController: ViewController {
let toAlpha: CGFloat = value ? 0.0 : 1.0
self.controllerNode.alpha = toAlpha
self.controllerNode.layer.animateAlpha(from: fromAlpha, to: toAlpha, duration: 0.2)
self.controllerNode.isUserInteractionEnabled = !value
}
}
}