Voice Chat UI improvements

This commit is contained in:
Ilya Laktyushin 2020-12-13 17:11:55 +04:00
parent dc1006617b
commit 49df81422b
20 changed files with 767 additions and 510 deletions

View File

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

View File

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

View File

@ -19,7 +19,8 @@ typedef enum {
TGCameraControllerPassportIdIntent, TGCameraControllerPassportIdIntent,
TGCameraControllerPassportMultipleIntent, TGCameraControllerPassportMultipleIntent,
TGCameraControllerAvatarIntent, TGCameraControllerAvatarIntent,
TGCameraControllerSignupAvatarIntent TGCameraControllerSignupAvatarIntent,
TGCameraControllerGenericPhotoOnlyIntent
} TGCameraControllerIntent; } TGCameraControllerIntent;
@interface TGCameraControllerWindow : TGOverlayControllerWindow @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]; 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.inhibitMute = self.inhibitMute;
model.controller = galleryController; model.controller = galleryController;

View File

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

View File

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

View File

@ -296,7 +296,7 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
membersText = self.strings.VoiceChat_Panel_Members(Int32(data.participantCount)) 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) 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.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 { if let (size, leftInset, rightInset) = strongSelf.validLayout {
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate) 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.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 updateAudioLevels = true
} }

View File

@ -82,8 +82,8 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|> distinctUntilChanged |> distinctUntilChanged
} }
public var hasActiveGroupCall: Bool { public var hasActiveCall: Bool {
return self.currentGroupCall != nil return self.currentCall != nil || self.currentGroupCall != nil
} }
private let currentCallPromise = Promise<PresentationCall?>(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) private let blobSize = CGSize(width: 244.0, height: 244.0)
final class VoiceChatActionButton: HighlightTrackingButtonNode { final class VoiceChatActionButton: HighlightTrackingButtonNode {
enum State { enum State: Equatable {
enum ActiveState { enum ActiveState: Equatable {
case cantSpeak case cantSpeak
case muted case muted
case on case on
@ -31,6 +31,14 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
case active(state: ActiveState) 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 containerNode: ASDisplayNode
private let backgroundNode: VoiceChatActionButtonBackgroundNode private let backgroundNode: VoiceChatActionButtonBackgroundNode
private let iconNode: VoiceChatMicrophoneNode private let iconNode: VoiceChatMicrophoneNode
@ -167,7 +175,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
let subtitleSize = self.subtitleLabel.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude)) let subtitleSize = self.subtitleLabel.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
let totalHeight = titleSize.height + subtitleSize.height + 1.0 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.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) self.containerNode.frame = CGRect(origin: CGPoint(), size: size)
@ -209,7 +217,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
} }
private func applyIconParams() { private func applyIconParams() {
guard let (size, _, state, _, small, title, subtitle, snap) = self.currentParams else { guard let (_, _, state, _, _, _, _, snap) = self.currentParams else {
return return
} }
@ -250,6 +258,8 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
let previousState = previous?.state let previousState = previous?.state
self.currentParams = (size, buttonSize, state, dark, small, title, subtitle, previous?.snap ?? false) self.currentParams = (size, buttonSize, state, dark, small, title, subtitle, previous?.snap ?? false)
self.statePromise.set(state)
var backgroundState: VoiceChatActionButtonBackgroundNode.State var backgroundState: VoiceChatActionButtonBackgroundNode.State
switch state { switch state {
case let .active(state): case let .active(state):
@ -392,6 +402,7 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
private var state: State private var state: State
private var hasState = false private var hasState = false
private var transition: State? private var transition: State?
var audioLevel: CGFloat = 0.0 { 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.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)) 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 titleSize = self.titleNode.measure(constrainedSize)
let infoSize = self.infoNode.measure(constrainedSize) let infoSize = self.infoNode.measure(constrainedSize)
let titleInfoSpacing: CGFloat = 0.0 let titleInfoSpacing: CGFloat = 0.0
@ -128,7 +128,6 @@ public final class VoiceChatController: ViewController {
let isEmpty: Bool let isEmpty: Bool
let crossFade: Bool let crossFade: Bool
let count: Int let count: Int
let isExpanded: Bool
let animated: 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 (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } 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 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) } 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? private weak var controller: VoiceChatController?
@ -414,7 +413,6 @@ public final class VoiceChatController: ViewController {
private var currentCallMembers: [GroupCallParticipantsContext.Participant]? private var currentCallMembers: [GroupCallParticipantsContext.Participant]?
private var currentInvitedPeers: [Peer]? private var currentInvitedPeers: [Peer]?
private var currentSpeakingPeers: Set<PeerId>? private var currentSpeakingPeers: Set<PeerId>?
private var currentIsExpanded: Bool = false
private var currentContentOffset: CGFloat? private var currentContentOffset: CGFloat?
private var ignoreScrolling = false private var ignoreScrolling = false
private var accountPeer: Peer? private var accountPeer: Peer?
@ -476,6 +474,7 @@ public final class VoiceChatController: ViewController {
self.listNode = ListView() self.listNode = ListView()
self.listNode.verticalScrollIndicatorColor = UIColor(white: 1.0, alpha: 0.3) self.listNode.verticalScrollIndicatorColor = UIColor(white: 1.0, alpha: 0.3)
self.listNode.clipsToBounds = true self.listNode.clipsToBounds = true
self.listNode.scroller.bounces = false
self.topPanelNode = ASDisplayNode() self.topPanelNode = ASDisplayNode()
self.topPanelNode.clipsToBounds = false 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))) let subtitle = strongSelf.presentationData.strings.VoiceChat_Panel_Members(Int32(max(1, callMembers?.totalCount ?? 0)))
strongSelf.currentSubtitle = subtitle strongSelf.currentSubtitle = subtitle
@ -907,7 +906,7 @@ public final class VoiceChatController: ViewController {
} }
if !strongSelf.didSetDataReady { if !strongSelf.didSetDataReady {
strongSelf.accountPeer = accountPeer 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 { if let peer = peerViewMainPeer(view), let channel = peer as? TelegramChannel {
let addressName = channel.addressName ?? "" let addressName = channel.addressName ?? ""
@ -1100,39 +1099,10 @@ public final class VoiceChatController: ViewController {
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, transition in self.listNode.updateFloatingHeaderOffset = { [weak self] offset, transition in
if let strongSelf = self { if let strongSelf = self {
strongSelf.currentContentOffset = offset strongSelf.currentContentOffset = offset
if strongSelf.animation == nil && !strongSelf.animatingExpansion {
strongSelf.updateFloatingHeaderOffset(offset: offset, transition: transition) 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
}
}
}
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
}
} }
} }
@ -1223,7 +1193,7 @@ public final class VoiceChatController: ViewController {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring)) 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) { @objc private func actionButtonPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) {
@ -1268,7 +1238,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = self.validLayout { if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring)) 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: default:
break 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 { guard let (layout, _) = self.validLayout else {
return return
} }
@ -1352,9 +1322,22 @@ public final class VoiceChatController: ViewController {
let bottomAreaHeight: CGFloat = 268.0 let bottomAreaHeight: CGFloat = 268.0
let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
let listSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset - bottomPanelHeight) 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 self.floatingHeaderOffset = offset
let rawPanelOffset = offset + listTopInset - topPanelHeight let rawPanelOffset = offset + listTopInset - topPanelHeight
@ -1374,7 +1357,7 @@ public final class VoiceChatController: ViewController {
if !topPanelFrame.equalTo(previousTopPanelFrame) { if !topPanelFrame.equalTo(previousTopPanelFrame) {
self.topPanelNode.frame = topPanelFrame self.topPanelNode.frame = topPanelFrame
let positionDelta = CGPoint(x: 0.0, y: topPanelFrame.minY - previousTopPanelFrame.minY) 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 self.backgroundNode.frame = backgroundFrame
let backgroundPositionDelta = CGPoint(x: 0.0, y: previousBackgroundFrame.minY - backgroundFrame.minY) let backgroundPositionDelta = CGPoint(x: 0.0, y: previousBackgroundFrame.minY - backgroundFrame.minY)
@ -1387,6 +1370,8 @@ public final class VoiceChatController: ViewController {
self.rightBorderNode.frame = rightBorderFrame self.rightBorderNode.frame = rightBorderFrame
let rightBorderPositionDelta = CGPoint(x: 0.0, y: previousRightBorderFrame.minY - rightBorderFrame.minY) let rightBorderPositionDelta = CGPoint(x: 0.0, y: previousRightBorderFrame.minY - rightBorderFrame.minY)
transition.animatePositionAdditive(node: self.rightBorderNode, offset: rightBorderPositionDelta) 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) 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 var isFullscreen = false
func updateColors(fullscreen: Bool) { func updateIsFullscreen(_ isFullscreen: Bool) {
guard self.isFullscreen != fullscreen, let (layout, _) = self.validLayout else { guard self.isFullscreen != isFullscreen, let (layout, _) = self.validLayout else {
return return
} }
self.isFullscreen = fullscreen self.isFullscreen = isFullscreen
self.controller?.statusBar.statusBarStyle = fullscreen ? .White : .Ignore self.controller?.statusBar.statusBarStyle = isFullscreen ? .White : .Ignore
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .linear)
let topPanelHeight: CGFloat = 63.0 let topPanelHeight: CGFloat = 63.0
let topEdgeFrame: CGRect let topEdgeFrame: CGRect
if self.isFullscreen { if isFullscreen {
let offset: CGFloat let offset: CGFloat
if let statusBarHeight = layout.statusBarHeight { if let statusBarHeight = layout.statusBarHeight {
offset = statusBarHeight offset = statusBarHeight
@ -1445,16 +1428,17 @@ public final class VoiceChatController: ViewController {
} else { } else {
topEdgeFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: topPanelHeight) 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.updateFrame(node: self.topPanelEdgeNode, frame: topEdgeFrame)
transition.updateCornerRadius(node: self.topPanelEdgeNode, cornerRadius: fullscreen ? layout.deviceMetrics.screenCornerRadius - 0.5 : 12.0) transition.updateCornerRadius(node: self.topPanelEdgeNode, cornerRadius: isFullscreen ? layout.deviceMetrics.screenCornerRadius - 0.5 : 12.0)
// transition.updateBackgroundColor(node: self.dimNode, color: fullscreen ? fullscreenBackgroundColor : dimColor) transition.updateBackgroundColor(node: self.topPanelBackgroundNode, color: isFullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.topPanelBackgroundNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor) transition.updateBackgroundColor(node: self.topPanelEdgeNode, color: isFullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.topPanelEdgeNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor) transition.updateBackgroundColor(node: self.backgroundNode, color: isFullscreen ? panelBackgroundColor : secondaryPanelBackgroundColor)
transition.updateBackgroundColor(node: self.backgroundNode, color: fullscreen ? panelBackgroundColor : secondaryPanelBackgroundColor) transition.updateBackgroundColor(node: self.bottomPanelBackgroundNode, color: isFullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.bottomPanelBackgroundNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor) transition.updateBackgroundColor(node: self.leftBorderNode, color: isFullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.leftBorderNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor) transition.updateBackgroundColor(node: self.rightBorderNode, color: isFullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.rightBorderNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor) transition.updateBackgroundColor(node: self.rightBorderNode, color: isFullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.rightBorderNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
if let snapshotView = self.topCornersNode.view.snapshotContentTree() { if let snapshotView = self.topCornersNode.view.snapshotContentTree() {
snapshotView.frame = self.topCornersNode.frame snapshotView.frame = self.topCornersNode.frame
@ -1464,7 +1448,7 @@ public final class VoiceChatController: ViewController {
snapshotView?.removeFromSuperview() 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() { if let snapshotView = self.bottomCornersNode.view.snapshotContentTree() {
snapshotView.frame = self.bottomCornersNode.bounds snapshotView.frame = self.bottomCornersNode.bounds
@ -1474,10 +1458,10 @@ public final class VoiceChatController: ViewController {
snapshotView?.removeFromSuperview() 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.optionsButton.setImage(optionsButtonImage(dark: isFullscreen), animated: transition.isAnimated)
self.closeButton.setImage(closeButtonImage(dark: fullscreen), animated: transition.isAnimated) self.closeButton.setImage(closeButtonImage(dark: isFullscreen), animated: transition.isAnimated)
self.updateTitle(transition: transition) self.updateTitle(transition: transition)
} }
@ -1536,6 +1520,7 @@ public final class VoiceChatController: ViewController {
let soundImage: CallControllerButtonItemNode.Content.Image let soundImage: CallControllerButtonItemNode.Content.Image
var soundAppearance: CallControllerButtonItemNode.Content.Appearance = audioButtonAppearance var soundAppearance: CallControllerButtonItemNode.Content.Appearance = audioButtonAppearance
var soundTitle: String = self.presentationData.strings.Call_Speaker
switch audioMode { switch audioMode {
case .none, .builtin: case .none, .builtin:
soundImage = .speaker soundImage = .speaker
@ -1543,7 +1528,8 @@ public final class VoiceChatController: ViewController {
soundImage = .speaker soundImage = .speaker
soundAppearance = .blurred(isFilled: true) soundAppearance = .blurred(isFilled: true)
case .headphones: case .headphones:
soundImage = .bluetooth soundImage = .headphones
soundTitle = self.presentationData.strings.Call_Audio
case let .bluetooth(type): case let .bluetooth(type):
switch type { switch type {
case .generic: case .generic:
@ -1553,10 +1539,11 @@ public final class VoiceChatController: ViewController {
case .airpodsPro: case .airpodsPro:
soundImage = .airpodsPro soundImage = .airpodsPro
} }
soundTitle = self.presentationData.strings.Call_Audio
} }
let sideButtonSize = CGSize(width: 60.0, height: 60.0) 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) self.leaveNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: .color(.custom(0x602522)), image: .end), text: self.presentationData.strings.VoiceChat_Leave, transition: .immediate)
} }
@ -1583,6 +1570,8 @@ public final class VoiceChatController: ViewController {
insets.right = layout.safeInsets.right + sideInset insets.right = layout.safeInsets.right + sideInset
let topPanelHeight: CGFloat = 63.0 let topPanelHeight: CGFloat = 63.0
if let _ = self.panGestureArguments {
} else {
let topEdgeFrame: CGRect let topEdgeFrame: CGRect
if self.isFullscreen { if self.isFullscreen {
let offset: CGFloat let offset: CGFloat
@ -1596,12 +1585,28 @@ public final class VoiceChatController: ViewController {
topEdgeFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: topPanelHeight) 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 bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
let listTopInset = layoutTopInset + topPanelHeight let listTopInset = layoutTopInset + topPanelHeight
let listSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset - bottomPanelHeight) 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 (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listSize, insets: insets, duration: duration, curve: curve) let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listSize, insets: insets, duration: duration, curve: curve)
@ -1778,24 +1783,24 @@ public final class VoiceChatController: ViewController {
let listTopInset = layoutTopInset + 63.0 let listTopInset = layoutTopInset + 63.0
let listSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset - bottomPanelHeight) let listSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
let previousIsExpanded = self.currentIsExpanded self.topInset = max(0.0, max(listSize.height - itemsHeight, listSize.height - 46.0 - floor(56.0 * 3.5)))
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)))
let frameTransition: ContainedViewLayoutTransition if !self.isExpanded {
if previousIsExpanded != self.currentIsExpanded { let targetY = listTopInset + (self.topInset ?? listSize.height)
frameTransition = .animated(duration: 0.4, curve: .spring) if self.listNode.frame.minY != targetY && !self.animatingExpansion && self.panGestureArguments == nil {
} else { self.animation = ListViewAnimation(from: self.listNode.frame.minY, to: targetY, duration: 0.4, curve: listViewAnimationCurveEaseInOut, beginAt: CACurrentMediaTime(), update: { [weak self] _, currentValue in
frameTransition = .animated(duration: 0.4, curve: .easeInOut) 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) self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: nil, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
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
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -1804,12 +1809,42 @@ public final class VoiceChatController: ViewController {
strongSelf.controller?.contentsReady.set(true) 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
} }
} }
private func updateMembers(muteState: GroupCallParticipantsContext.Participant.MuteState?, callMembers: [GroupCallParticipantsContext.Participant], invitedPeers: [Peer], speakingPeers: Set<PeerId>, isExpanded: Bool) { 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>) {
self.currentCallMembers = callMembers self.currentCallMembers = callMembers
self.currentSpeakingPeers = speakingPeers self.currentSpeakingPeers = speakingPeers
self.currentInvitedPeers = invitedPeers self.currentInvitedPeers = invitedPeers
@ -1874,7 +1909,7 @@ public final class VoiceChatController: ViewController {
self.currentEntries = entries self.currentEntries = entries
let presentationData = self.presentationData.withUpdated(theme: self.darkTheme) 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) 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 { // if let callState = self.callState, case .connected = callState.networkState, let muteState = callState.muteState, !muteState.canUnmute {
// return false // return false
// } // }
if let recognizer = gestureRecognizer as? UIPanGestureRecognizer { // if let recognizer = gestureRecognizer as? UIPanGestureRecognizer {
let location = recognizer.location(in: self.view) // let location = recognizer.location(in: self.view)
if let view = super.hitTest(location, with: nil) { // if let view = super.hitTest(location, with: nil) {
if let gestureRecognizers = view.gestureRecognizers, view != self.view { // if let gestureRecognizers = view.gestureRecognizers, view != self.view {
for gestureRecognizer in gestureRecognizers { // for gestureRecognizer in gestureRecognizers {
if let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer, gestureRecognizer.isEnabled { // if let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer, gestureRecognizer.isEnabled {
if panGestureRecognizer.state != .began { // if panGestureRecognizer.state != .began {
panGestureRecognizer.isEnabled = false // panGestureRecognizer.isEnabled = false
panGestureRecognizer.isEnabled = true // panGestureRecognizer.isEnabled = true
} // }
} // }
} // }
} // }
} // }
} // }
return 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) { @objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state { switch recognizer.state {
case .began: 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: 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) 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 var bounds = self.contentContainer.bounds
bounds.origin.y = -translation.y bounds.origin.y = -translation.y
bounds.origin.y = min(0.0, bounds.origin.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) let offset: CGFloat
if let (inset, panOffset) = self.panGestureArguments {
if (bounds.minY < -60.0 || velocity.y > 300.0) { offset = inset + panOffset
self.controller?.dismiss(closing: false)
} else { } else {
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 var bounds = self.contentContainer.bounds
let previousBounds = bounds let previousBounds = bounds
bounds.origin.y = 0.0 bounds.origin.y = 0.0
self.contentContainer.bounds = bounds self.contentContainer.bounds = bounds
self.contentContainer.layer.animateBounds(from: previousBounds, to: self.contentContainer.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) self.contentContainer.layer.animateBounds(from: previousBounds, to: self.contentContainer.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
} }
}
case .cancelled: case .cancelled:
self.panGestureArguments = nil
let previousBounds = self.contentContainer.bounds let previousBounds = self.contentContainer.bounds
var bounds = self.contentContainer.bounds var bounds = self.contentContainer.bounds
bounds.origin.y = 0.0 bounds.origin.y = 0.0
self.contentContainer.bounds = bounds self.contentContainer.bounds = bounds
self.contentContainer.layer.animateBounds(from: previousBounds, to: self.contentContainer.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) 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: default:
break break
} }
@ -1941,7 +2110,7 @@ public final class VoiceChatController: ViewController {
let result = super.hitTest(point, with: event) let result = super.hitTest(point, with: event)
if result === self.topPanelNode.view { if result === self.topPanelNode.view {
return self.listNode.view return self.view
} }
if result === self.bottomPanelNode.view { if result === self.bottomPanelNode.view {
@ -2071,9 +2240,13 @@ public final class VoiceChatController: ViewController {
if let navigationController = self.navigationController as? NavigationController { if let navigationController = self.navigationController as? NavigationController {
let count = navigationController.viewControllers.count let count = navigationController.viewControllers.count
if count == 2 || navigationController.viewControllers[count - 2] is ChatController { if count == 2 || navigationController.viewControllers[count - 2] is ChatController {
if case .active(.cantSpeak) = self.controllerNode.actionButton.stateValue {
} else if let chatController = navigationController.viewControllers[count - 2] as? ChatController, chatController.isSendButtonVisible {
} else {
self.detachActionButton() self.detachActionButton()
} }
} }
}
} else { } else {
self.isDisconnected = true self.isDisconnected = true
} }

View File

@ -53,6 +53,7 @@ public final class VoiceChatOverlayController: ViewController {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring)
if hidden { if hidden {
if slide { if slide {
actionButton.isHidden = false
transition.updateSublayerTransformOffset(layer: actionButton.layer, offset: CGPoint(x: slideOffset, y: 0.0)) transition.updateSublayerTransformOffset(layer: actionButton.layer, offset: CGPoint(x: slideOffset, y: 0.0))
} else { } else {
actionButton.layer.removeAllAnimations() actionButton.layer.removeAllAnimations()
@ -64,10 +65,10 @@ public final class VoiceChatOverlayController: ViewController {
} }
} else { } else {
actionButton.isHidden = false actionButton.isHidden = false
actionButton.layer.removeAllAnimations()
if slide { if slide {
transition.updateSublayerTransformOffset(layer: actionButton.layer, offset: CGPoint()) transition.updateSublayerTransformOffset(layer: actionButton.layer, offset: CGPoint())
} else { } else {
actionButton.layer.removeAllAnimations()
actionButton.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4) 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 return
} }
actionButton.update(snap: true, animated: !self.isSlidOffscreen) actionButton.update(snap: true, animated: !self.isSlidOffscreen && !self.isButtonHidden)
if self.isSlidOffscreen { if self.isSlidOffscreen {
actionButton.layer.sublayerTransform = CATransform3DMakeTranslation(slideOffset, 0.0, 0.0) actionButton.layer.sublayerTransform = CATransform3DMakeTranslation(slideOffset, 0.0, 0.0)
return return
} else if self.isButtonHidden {
actionButton.isHidden = true
return
} }
let targetPosition = actionButton.position let targetPosition = actionButton.position
@ -125,12 +129,14 @@ public final class VoiceChatOverlayController: ViewController {
} }
private var animating = false private var animating = false
private var dismissed = false
func animateOut(reclaim: Bool, completion: @escaping (Bool) -> Void) { func animateOut(reclaim: Bool, completion: @escaping (Bool) -> Void) {
guard let actionButton = self.controller?.actionButton, let layout = self.validLayout else { guard let actionButton = self.controller?.actionButton, let layout = self.validLayout else {
return return
} }
if reclaim { 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) let targetPosition = CGPoint(x: layout.size.width / 2.0, y: layout.size.height - layout.intrinsicInsets.bottom - 268.0 / 2.0)
if self.isSlidOffscreen { if self.isSlidOffscreen {
self.isSlidOffscreen = false self.isSlidOffscreen = false
@ -139,6 +145,13 @@ public final class VoiceChatOverlayController: ViewController {
actionButton.update(snap: false, animated: false) actionButton.update(snap: false, animated: false)
actionButton.position = CGPoint(x: targetPosition.x, y: 268.0 / 2.0) actionButton.position = CGPoint(x: targetPosition.x, y: 268.0 / 2.0)
completion(true) 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 { } else {
self.animating = true self.animating = true
let sourcePoint = actionButton.position let sourcePoint = actionButton.position
@ -193,17 +206,18 @@ public final class VoiceChatOverlayController: ViewController {
return nil return nil
} }
private var didAnimateIn = false
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.validLayout = layout 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 convertedRect = actionButton.view.convert(actionButton.bounds, to: self.view)
let insets = layout.insets(options: [.input]) 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)) 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.addSubnode(actionButton)
self.animateIn(from: convertedRect) self.animateIn(from: convertedRect)
} }
} }
@ -224,15 +238,18 @@ public final class VoiceChatOverlayController: ViewController {
super.init(navigationBarPresentationData: nil) super.init(navigationBarPresentationData: nil)
self.statusBar.statusBarStyle = .Ignore 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 { if let navigationController = navigationController {
let controllers: Signal<[UIViewController], NoError> = .single([]) let controllers: Signal<[UIViewController], NoError> = .single([])
|> then(navigationController.viewControllersSignal) |> then(navigationController.viewControllersSignal)
let overlayControllers: Signal<[UIViewController], NoError> = .single([]) let overlayControllers: Signal<[UIViewController], NoError> = .single([])
|> then(navigationController.overlayControllersSignal) |> 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 { if let strongSelf = self {
var hasVoiceChatController = false var hasVoiceChatController = false
var overlayControllersCount = 0 var overlayControllersCount = 0
@ -248,19 +265,37 @@ public final class VoiceChatOverlayController: ViewController {
} }
} }
var slide = true
var hidden = true var hidden = true
var animated = true var animated = true
if controllers.count == 1 || controllers.last is ChatController { if controllers.count == 1 || controllers.last is ChatController {
if let chatController = controllers.last as? ChatController, chatController.isSendButtonVisible {
slide = false
animated = false
} else {
hidden = false hidden = false
} }
}
if overlayControllersCount > 0 { if overlayControllersCount > 0 {
hidden = true hidden = true
} }
if case .active(.cantSpeak) = state {
hidden = true
}
if hasVoiceChatController { if hasVoiceChatController {
hidden = false hidden = false
animated = 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) attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
case let .groupPhoneCall(_, _, duration): case let .groupPhoneCall(_, _, duration):
let titleString: String
if let duration = duration { 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
} else {
titleString = strings.Notification_VoiceChatStarted
}
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
} else {
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))
}
case let .customText(text, entities): 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) 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): 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

@ -6221,9 +6221,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
let shouldBeActive = combineLatest(self.context.sharedContext.mediaManager.audioSession.isPlaybackActive() |> deliverOnMainQueue, self.chatDisplayNode.historyNode.hasVisiblePlayableItemNodes) let hasActiveCalls: Signal<Bool, NoError>
|> mapToSignal { [weak self] isPlaybackActive, hasVisiblePlayableItemNodes -> Signal<Bool, NoError> in if let callManager = self.context.sharedContext.callManager as? PresentationCallManagerImpl {
if hasVisiblePlayableItemNodes && !isPlaybackActive { 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 return Signal<Bool, NoError> { [weak self] subscriber in
guard let strongSelf = self else { guard let strongSelf = self else {
subscriber.putCompletion() subscriber.putCompletion()
@ -7060,7 +7067,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.saveInterfaceState(includeScrollState: false) 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? var voiceChatOverlayController: VoiceChatOverlayController?
for controller in navigationController.globalOverlayControllers { for controller in navigationController.globalOverlayControllers {
if let controller = controller as? VoiceChatOverlayController { if let controller = controller as? VoiceChatOverlayController {
@ -7070,11 +7077,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
if let controller = voiceChatOverlayController { if let controller = voiceChatOverlayController {
var hidden = false controller.update(hidden: self.isSendButtonVisible, slide: false, animated: true)
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)
} }
} }
} }
@ -7669,11 +7672,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}) })
}, openCamera: { [weak self] cameraView, menuController in }, openCamera: { [weak self] cameraView, menuController in
if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
if let callManager = strongSelf.context.sharedContext.callManager as? PresentationCallManagerImpl, callManager.hasActiveGroupCall { var photoOnly = false
return 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 let strongSelf = self {
if editMediaOptions != nil { if editMediaOptions != nil {
strongSelf.editMessageMediaWithLegacySignals(signals!) strongSelf.editMessageMediaWithLegacySignals(signals!)
@ -11307,6 +11311,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
//return self.chatDisplayNode.acceptEmbeddedTitlePeekContent(content: content) //return self.chatDisplayNode.acceptEmbeddedTitlePeekContent(content: content)
return false 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 { private final class ContextControllerContentSourceImpl: ContextControllerContentSource {

View File

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

View File

@ -11,7 +11,7 @@ import ShareController
import LegacyUI import LegacyUI
import LegacyMediaPickerUI 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 presentationData = context.sharedContext.currentPresentationData.with { $0 }
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait) legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait)
@ -23,7 +23,7 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocation: Ch
let controller: TGCameraController let controller: TGCameraController
if let cameraView = cameraView, let previewView = cameraView.previewView() { 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 controller.inhibitMultipleCapture = editingMedia
} else { } else {
controller = TGCameraController() controller = TGCameraController()

View File

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