diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index f7c21e8505..3494b29489 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -6563,3 +6563,5 @@ Sorry for the inconvenience."; "VoiceChat.VideoPreviewBackCamera" = "Back Camera"; "VoiceChat.VideoPreviewContinue" = "Continue"; "VoiceChat.VideoPreviewShareScreenInfo" = "Everything on your screen\nwill be shared"; + +"VoiceChat.VideoParticipantsLimitExceededExtended" = "The voice chat is over %@ members.\nNew participants only have access to audio stream. "; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraMainView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraMainView.h index c8cc7ecaf4..7e023c677a 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraMainView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraMainView.h @@ -57,6 +57,7 @@ @property (nonatomic, copy) void(^shutterPressed)(bool fromHardwareButton); @property (nonatomic, copy) void(^shutterReleased)(bool fromHardwareButton); +@property (nonatomic, copy) void(^shutterPanGesture)(UIPanGestureRecognizer *gesture); @property (nonatomic, copy) void(^cancelPressed)(void); @property (nonatomic, copy) void(^donePressed)(void); @property (nonatomic, copy) void(^resultPressed)(NSInteger index); @@ -93,6 +94,7 @@ - (void)shutterButtonPressed; - (void)shutterButtonReleased; +- (void)shutterButtonPanGesture:(UIPanGestureRecognizer *)gestureRecognizer; - (void)flipButtonPressed; - (void)cancelButtonPressed; - (void)doneButtonPressed; diff --git a/submodules/LegacyComponents/Sources/TGCameraController.m b/submodules/LegacyComponents/Sources/TGCameraController.m index 34fabf7f92..a7e351ff91 100644 --- a/submodules/LegacyComponents/Sources/TGCameraController.m +++ b/submodules/LegacyComponents/Sources/TGCameraController.m @@ -128,7 +128,6 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus NSTimer *_switchToVideoTimer; NSTimer *_startRecordingTimer; - bool _recordingByShutterHold; bool _stopRecordingOnRelease; bool _shownMicrophoneAlert; @@ -419,6 +418,14 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus [strongSelf shutterReleased]; }; + _interfaceView.shutterPanGesture = ^(UIPanGestureRecognizer *gesture) { + __strong TGCameraController *strongSelf = weakSelf; + if (strongSelf == nil) + return; + + [strongSelf handleRamp:gesture]; + }; + _interfaceView.cancelPressed = ^ { __strong TGCameraController *strongSelf = weakSelf; @@ -1153,8 +1160,12 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus { _stopRecordingOnRelease = false; - _camera.disabled = true; [_camera stopVideoRecording]; + TGDispatchAfter(0.3, dispatch_get_main_queue(), ^{ + [_camera setZoomLevel:1.0]; + [_interfaceView setZoomLevel:1.0 displayNeeded:false]; + _camera.disabled = true; + }); [_buttonHandler ignoreEventsFor:1.0f andDisable:[self willPresentResultController]]; } @@ -2615,6 +2626,37 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus } } +- (void)handleRamp:(UIPanGestureRecognizer *)gestureRecognizer +{ + if (!_stopRecordingOnRelease) { + [gestureRecognizer setTranslation:CGPointZero inView:self.view]; + return; + } + switch (gestureRecognizer.state) + { + case UIGestureRecognizerStateChanged: + { + CGPoint translation = [gestureRecognizer translationInView:self.view]; + + CGFloat value = 1.0; + if (translation.y < 0.0) { + value = MIN(8.0, value + ABS(translation.y) / 60.0); + } + + [_camera setZoomLevel:value]; + [_interfaceView setZoomLevel:value displayNeeded:true]; + } + break; + case UIGestureRecognizerStateEnded: + { + [self shutterReleased]; + } + break; + default: + break; + } +} + - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { if (gestureRecognizer == _panGestureRecognizer) diff --git a/submodules/LegacyComponents/Sources/TGCameraMainPhoneView.m b/submodules/LegacyComponents/Sources/TGCameraMainPhoneView.m index 80adcfa2c6..63f801b571 100644 --- a/submodules/LegacyComponents/Sources/TGCameraMainPhoneView.m +++ b/submodules/LegacyComponents/Sources/TGCameraMainPhoneView.m @@ -281,9 +281,11 @@ [_doneButton addTarget:self action:@selector(doneButtonPressed) forControlEvents:UIControlEventTouchUpInside]; [_bottomPanelView addSubview:_doneButton]; + UIPanGestureRecognizer *shutterPanGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(shutterButtonPanGesture:)]; _shutterButton = [[TGCameraShutterButton alloc] initWithFrame:CGRectMake((frame.size.width - shutterButtonWidth) / 2, 10, shutterButtonWidth, shutterButtonWidth)]; [_shutterButton addTarget:self action:@selector(shutterButtonReleased) forControlEvents:UIControlEventTouchUpInside]; [_shutterButton addTarget:self action:@selector(shutterButtonPressed) forControlEvents:UIControlEventTouchDown]; + [_shutterButton addGestureRecognizer:shutterPanGestureRecognizer]; [_bottomPanelView addSubview:_shutterButton]; _modeControl = [[TGCameraModeControl alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, _modeControlHeight) avatar:avatar]; diff --git a/submodules/LegacyComponents/Sources/TGCameraMainTabletView.m b/submodules/LegacyComponents/Sources/TGCameraMainTabletView.m index 8ebd22c5d0..653a06ebc3 100644 --- a/submodules/LegacyComponents/Sources/TGCameraMainTabletView.m +++ b/submodules/LegacyComponents/Sources/TGCameraMainTabletView.m @@ -100,7 +100,7 @@ const CGFloat TGCameraTabletPanelViewWidth = 102.0f; }; [_panelView addSubview:_timecodeView]; - _flipButton = [[TGCameraFlipButton alloc] initWithFrame:CGRectMake(0, 0, 44, 44)]; + _flipButton = [[TGCameraFlipButton alloc] initWithFrame:CGRectMake(0, 20, 44, 44)]; [_flipButton addTarget:self action:@selector(flipButtonPressed) forControlEvents:UIControlEventTouchUpInside]; [_panelView addSubview:_flipButton]; @@ -277,6 +277,10 @@ const CGFloat TGCameraTabletPanelViewWidth = 102.0f; (_panelView.frame.size.height - _shutterButton.frame.size.height) / 2, _shutterButton.frame.size.width, _shutterButton.frame.size.height); + _flipButton.frame = CGRectMake((_panelView.frame.size.width - _shutterButton.frame.size.width) / 2, + _shutterButton.frame.origin.y + _shutterButton.frame.size.height + 20.0, + _flipButton.frame.size.width, _flipButton.frame.size.height); + CGFloat flipButtonPosition = 0.0f; CGFloat cancelButtonPosition = _panelView.frame.size.height - _cancelButton.frame.size.height - 7; if (!_doneButton.hidden) @@ -284,7 +288,6 @@ const CGFloat TGCameraTabletPanelViewWidth = 102.0f; flipButtonPosition = _panelView.frame.size.height / 8.0f - _flipButton.frame.size.height / 2.0f; cancelButtonPosition = 7.0f; } - _flipButton.frame = CGRectMake((_panelView.frame.size.width - _flipButton.frame.size.width) / 2, flipButtonPosition, _flipButton.frame.size.width, _flipButton.frame.size.height); _doneButton.frame = CGRectMake((_panelView.frame.size.width - _doneButton.frame.size.width) / 2, _panelView.frame.size.height - _doneButton.frame.size.height - 7, _doneButton.frame.size.width, _doneButton.frame.size.height); diff --git a/submodules/LegacyComponents/Sources/TGCameraMainView.m b/submodules/LegacyComponents/Sources/TGCameraMainView.m index 39b7ac246f..bc2cd5d7bb 100644 --- a/submodules/LegacyComponents/Sources/TGCameraMainView.m +++ b/submodules/LegacyComponents/Sources/TGCameraMainView.m @@ -201,6 +201,11 @@ self.shutterReleased(false); } +- (void)shutterButtonPanGesture:(UIPanGestureRecognizer *)gestureRecognizer { + if (self.shutterPanGesture != nil) + self.shutterPanGesture(gestureRecognizer); +} + - (void)cancelButtonPressed { if (self.cancelPressed != nil) @@ -228,6 +233,7 @@ { [_zoomView setZoomLevel:zoomLevel displayNeeded:displayNeeded]; [_zoomModeView setZoomLevel:zoomLevel animated:true]; + [_zoomWheelView setZoomLevel:zoomLevel]; } - (void)zoomChangingEnded diff --git a/submodules/LegacyComponents/Sources/TGCameraZoomView.m b/submodules/LegacyComponents/Sources/TGCameraZoomView.m index ec7b59a5a1..38a31cd3e3 100644 --- a/submodules/LegacyComponents/Sources/TGCameraZoomView.m +++ b/submodules/LegacyComponents/Sources/TGCameraZoomView.m @@ -271,11 +271,9 @@ [_rightItem addTarget:self action:@selector(rightPressed) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:_backgroundView]; - if (hasUltrawideCamera) { - [self addSubview:_leftItem]; - } [self addSubview:_centerItem]; - if (hasTelephotoCamera) { + if (hasTelephotoCamera && hasUltrawideCamera) { + [self addSubview:_leftItem]; [self addSubview:_rightItem]; } @@ -350,8 +348,23 @@ } - (void)centerPressed { - [self setZoomLevel:1.0 animated:true]; - self.zoomChanged(1.0, true, true); + if (!(_hasTelephotoCamera && _hasUltrawideCamera)) { + if (_zoomLevel == 1.0) { + if (_hasUltrawideCamera) { + [self setZoomLevel:0.5 animated:true]; + self.zoomChanged(0.5, true, true); + } else if (_hasTelephotoCamera) { + [self setZoomLevel:2.0 animated:true]; + self.zoomChanged(2.0, true, true); + } + } else { + [self setZoomLevel:1.0 animated:true]; + self.zoomChanged(1.0, true, true); + } + } else { + [self setZoomLevel:1.0 animated:true]; + self.zoomChanged(1.0, true, true); + } } - (void)rightPressed { @@ -828,7 +841,7 @@ [_feedbackGenerator selectionChanged]; } - CGRect valueLabelFrame = CGRectMake(TGScreenPixelFloor((self.frame.size.width - _valueLabel.frame.size.width) / 2.0), 30.0, _valueLabel.frame.size.width, _valueLabel.frame.size.height); + CGRect valueLabelFrame = CGRectMake(TGScreenPixelFloor((self.frame.size.width - _valueLabel.bounds.size.width) / 2.0), 30.0, _valueLabel.bounds.size.width, _valueLabel.bounds.size.height); _valueLabel.bounds = CGRectMake(0, 0, valueLabelFrame.size.width, valueLabelFrame.size.height); _valueLabel.center = CGPointMake(valueLabelFrame.origin.x + valueLabelFrame.size.width / 2.0, valueLabelFrame.origin.y + valueLabelFrame.size.height / 2.0); } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index 68f4f30e93..b25d2b2889 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -379,7 +379,7 @@ public final class VoiceChatController: ViewController { } private enum ListEntry: Comparable, Identifiable { - case tiles([VoiceChatTileItem], VoiceChatTileLayoutMode) + case tiles([VoiceChatTileItem], VoiceChatTileLayoutMode, Int32, Bool) case invite(PresentationTheme, PresentationStrings, String, Bool) case peer(VoiceChatPeerEntry, Int32) @@ -396,8 +396,8 @@ public final class VoiceChatController: ViewController { static func ==(lhs: ListEntry, rhs: ListEntry) -> Bool { switch lhs { - case let .tiles(lhsTiles, lhsLayoutMode): - if case let .tiles(rhsTiles, rhsLayoutMode) = rhs, lhsTiles == rhsTiles, lhsLayoutMode == rhsLayoutMode { + case let .tiles(lhsTiles, lhsLayoutMode, lhsVideoLimit, lhsReachedLimit): + if case let .tiles(rhsTiles, rhsLayoutMode, rhsVideoLimit, rhsReachedLimit) = rhs, lhsTiles == rhsTiles, lhsLayoutMode == rhsLayoutMode, lhsVideoLimit == rhsVideoLimit, lhsReachedLimit == rhsReachedLimit { return true } else { return false @@ -644,8 +644,8 @@ public final class VoiceChatController: ViewController { func item(context: AccountContext, presentationData: PresentationData, interaction: Interaction) -> ListViewItem { switch self { - case let .tiles(tiles, layoutMode): - return VoiceChatTilesGridItem(context: context, tiles: tiles, layoutMode: layoutMode, getIsExpanded: { + case let .tiles(tiles, layoutMode, videoLimit, reachedLimit): + return VoiceChatTilesGridItem(context: context, tiles: tiles, layoutMode: layoutMode, videoLimit: videoLimit, reachedLimit: reachedLimit, getIsExpanded: { return interaction.isExpanded }) case let .invite(_, _, text, isLink): @@ -5019,12 +5019,16 @@ public final class VoiceChatController: ViewController { self.joinedVideo = joinedVideo + let configuration = self.configuration ?? VoiceChatConfiguration.defaultValue + var reachedLimit = false + if !joinedVideo && (!tileItems.isEmpty || !gridTileItems.isEmpty), let peer = self.peer { tileItems.removeAll() gridTileItems.removeAll() - let configuration = self.configuration ?? VoiceChatConfiguration.defaultValue tileItems.append(VoiceChatTileItem(account: self.context.account, peer: peer, videoEndpointId: "", videoReady: false, videoTimeouted: true, isVideoLimit: true, videoLimit: configuration.videoParticipantsMaxCount, isPaused: false, isOwnScreencast: false, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, speaking: false, secondary: false, isTablet: false, icon: .none, text: .none, additionalText: nil, action: {}, contextAction: nil, getVideo: { _ in return nil }, getAudioLevel: nil)) + } else if !tileItems.isEmpty && !(self.callState?.isVideoEnabled ?? false) { + reachedLimit = true } for member in callMembers.0 { @@ -5108,11 +5112,11 @@ public final class VoiceChatController: ViewController { updateLayout = true self.currentTileItems = gridTileItems if displayPanelVideos && !tileItems.isEmpty { - entries.insert(.tiles(tileItems, .pairs), at: 0) + entries.insert(.tiles(tileItems, .pairs, configuration.videoParticipantsMaxCount, reachedLimit), at: 0) } } else { if !tileItems.isEmpty { - entries.insert(.tiles(tileItems, .pairs), at: 0) + entries.insert(.tiles(tileItems, .pairs, configuration.videoParticipantsMaxCount, reachedLimit), at: 0) } } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatTileGridNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatTileGridNode.swift index dca6322e0d..f344a2694d 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatTileGridNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatTileGridNode.swift @@ -181,12 +181,16 @@ final class VoiceChatTilesGridItem: ListViewItem { let context: AccountContext let tiles: [VoiceChatTileItem] let layoutMode: VoiceChatTileLayoutMode + let videoLimit: Int32 + let reachedLimit: Bool let getIsExpanded: () -> Bool - init(context: AccountContext, tiles: [VoiceChatTileItem], layoutMode: VoiceChatTileLayoutMode, getIsExpanded: @escaping () -> Bool) { + init(context: AccountContext, tiles: [VoiceChatTileItem], layoutMode: VoiceChatTileLayoutMode, videoLimit: Int32, reachedLimit: Bool, getIsExpanded: @escaping () -> Bool) { self.context = context self.tiles = tiles self.layoutMode = layoutMode + self.videoLimit = videoLimit + self.reachedLimit = reachedLimit self.getIsExpanded = getIsExpanded } @@ -231,6 +235,8 @@ final class VoiceChatTilesGridItemNode: ListViewItemNode { let backgroundNode: ASDisplayNode let cornersNode: ASImageNode + let limitLabel: TextNode + private var absoluteLocation: (CGRect, CGSize)? var tileNodes: [VoiceChatTileItemNode] { @@ -248,10 +254,14 @@ final class VoiceChatTilesGridItemNode: ListViewItemNode { self.cornersNode.displaysAsynchronously = false self.cornersNode.image = decorationCornersImage(top: true, bottom: false, dark: false) + self.limitLabel = TextNode() + self.limitLabel.alpha = 0.0 + super.init(layerBacked: false, dynamicBounce: false) self.addSubnode(self.backgroundNode) self.addSubnode(self.cornersNode) + self.addSubnode(self.limitLabel) } override func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) { @@ -274,14 +284,24 @@ final class VoiceChatTilesGridItemNode: ListViewItemNode { func asyncLayout() -> (_ item: VoiceChatTilesGridItem, _ params: ListViewItemLayoutParams) -> (ListViewItemNodeLayout, () -> Void) { let currentItem = self.item + let makeLabelLayout = TextNode.asyncLayout(self.limitLabel) + return { item, params in + let presentationData = item.context.sharedContext.currentPresentationData.with { $0 } + let (textLayout, textApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: presentationData.strings.VoiceChat_VideoParticipantsLimitExceededExtended(String(item.videoLimit)).string, font: Font.regular(13.0), textColor: UIColor(rgb: 0x8e8e93), paragraphAlignment: .center), maximumNumberOfLines: 3, truncationType: .end, constrainedSize: CGSize(width: params.width - 32.0, height: CGFloat.greatestFiniteMagnitude), lineSpacing: 0.25)) + let rowCount = ceil(CGFloat(item.tiles.count) / 2.0) - let contentSize = CGSize(width: params.width, height: rowCount * (tileHeight + tileSpacing)) + let gridSize = CGSize(width: params.width, height: rowCount * (tileHeight + tileSpacing)) + var contentSize = gridSize + if item.reachedLimit { + contentSize.height += 10.0 + textLayout.size.height + 10.0 + } let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: UIEdgeInsets()) + return (layout, { [weak self] in if let strongSelf = self { strongSelf.item = item - + let tileGridNode: VoiceChatTileGridNode if let current = strongSelf.tileGridNode { tileGridNode = current @@ -295,22 +315,29 @@ final class VoiceChatTilesGridItemNode: ListViewItemNode { strongSelf.tileGridNode = tileGridNode } - if let (rect, size) = strongSelf.absoluteLocation { tileGridNode.updateAbsoluteRect(rect, within: size) } let transition: ContainedViewLayoutTransition = currentItem == nil ? .immediate : .animated(duration: 0.3, curve: .easeInOut) let tileGridSize = tileGridNode.update(size: CGSize(width: params.width - params.leftInset - params.rightInset, height: params.availableHeight), layoutMode: item.layoutMode, items: item.tiles, transition: transition) + var backgroundSize = tileGridSize + if item.reachedLimit { + backgroundSize.height += 10.0 + textLayout.size.height + 10.0 + } if currentItem == nil { tileGridNode.frame = CGRect(x: params.leftInset, y: 0.0, width: tileGridSize.width, height: tileGridSize.height) - strongSelf.backgroundNode.frame = tileGridNode.frame + strongSelf.backgroundNode.frame = CGRect(origin: tileGridNode.frame.origin, size: backgroundSize) strongSelf.cornersNode.frame = CGRect(x: params.leftInset, y: layout.size.height, width: tileGridSize.width, height: 50.0) } else { transition.updateFrame(node: tileGridNode, frame: CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: tileGridSize)) - transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: tileGridSize)) + transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: tileGridNode.frame.origin, size: backgroundSize)) strongSelf.cornersNode.frame = CGRect(x: params.leftInset, y: layout.size.height, width: tileGridSize.width, height: 50.0) } + + let _ = textApply() + transition.updateFrame(node: strongSelf.limitLabel, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - textLayout.size.width) / 2.0), y: gridSize.height + 10.0), size: textLayout.size)) + transition.updateAlpha(node: strongSelf.limitLabel, alpha: item.reachedLimit ? 1.0 : 0.0) } }) }