Put flip cam button in alt location when alone in group call
This commit is contained in:
parent
c76c6759fa
commit
cd2bb5c90a
@ -2092,6 +2092,7 @@
|
||||
E1E78CAF2B573BD100B6FC2D /* CallMemberCameraOffView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E78CAE2B573BD100B6FC2D /* CallMemberCameraOffView.swift */; };
|
||||
E1E78CB12B573C3100B6FC2D /* CallMemberWaitingAndErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E78CB02B573C3100B6FC2D /* CallMemberWaitingAndErrorView.swift */; };
|
||||
E1E78CB42B575C2700B6FC2D /* CallMemberVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E78CB32B575C2700B6FC2D /* CallMemberVideoView.swift */; };
|
||||
E1F7F1792C65666100F2754E /* SupplementalCallControlsForFullscreenLocalMember.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F7F1782C65666100F2754E /* SupplementalCallControlsForFullscreenLocalMember.swift */; };
|
||||
E44AD4E624E98F440035D7B8 /* PhotoCaptureDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = E44AD4E524E98F430035D7B8 /* PhotoCaptureDismiss.swift */; };
|
||||
E75DD3E02810CDBD00E32C36 /* SubscriptionManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E75DD3DF2810CDBD00E32C36 /* SubscriptionManagerTest.swift */; };
|
||||
E7D7C93F28B580AC003F043B /* Bundle+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7D7C93E28B580AC003F043B /* Bundle+OWS.swift */; };
|
||||
@ -5196,6 +5197,7 @@
|
||||
E1E78CAE2B573BD100B6FC2D /* CallMemberCameraOffView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMemberCameraOffView.swift; sourceTree = "<group>"; };
|
||||
E1E78CB02B573C3100B6FC2D /* CallMemberWaitingAndErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMemberWaitingAndErrorView.swift; sourceTree = "<group>"; };
|
||||
E1E78CB32B575C2700B6FC2D /* CallMemberVideoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMemberVideoView.swift; sourceTree = "<group>"; };
|
||||
E1F7F1782C65666100F2754E /* SupplementalCallControlsForFullscreenLocalMember.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupplementalCallControlsForFullscreenLocalMember.swift; sourceTree = "<group>"; };
|
||||
E44AD4E524E98F430035D7B8 /* PhotoCaptureDismiss.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoCaptureDismiss.swift; sourceTree = "<group>"; };
|
||||
E75DD3DF2810CDBD00E32C36 /* SubscriptionManagerTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionManagerTest.swift; sourceTree = "<group>"; };
|
||||
E7D7C93E28B580AC003F043B /* Bundle+OWS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+OWS.swift"; sourceTree = "<group>"; };
|
||||
@ -9552,6 +9554,7 @@
|
||||
766CE0D52A323DFE00AD609D /* RemoteVideoView.swift */,
|
||||
342FFE7D271EF5B1000AC89F /* ReturnToCallViewController.swift */,
|
||||
88D23D1823CEC0C700B0E74B /* SimulatorCallUIAdaptee.swift */,
|
||||
E1F7F1782C65666100F2754E /* SupplementalCallControlsForFullscreenLocalMember.swift */,
|
||||
);
|
||||
path = UserInterface;
|
||||
sourceTree = "<group>";
|
||||
@ -14036,6 +14039,7 @@
|
||||
884DB94527DD70F700C6A309 /* StoryViewModel.swift in Sources */,
|
||||
88B00D4D28A3346100BC9CA0 /* StoryViewsViewController.swift in Sources */,
|
||||
88863A50280CAE4400977F69 /* StoryZoomAnimator.swift in Sources */,
|
||||
E1F7F1792C65666100F2754E /* SupplementalCallControlsForFullscreenLocalMember.swift in Sources */,
|
||||
6675F64F29261C39007A311E /* SyncPushTokensJob.swift in Sources */,
|
||||
344A761324B36C8C009D69A5 /* TestingViewController.swift in Sources */,
|
||||
506A9252290326A600A2EFC2 /* TextHelper.swift in Sources */,
|
||||
|
||||
@ -83,6 +83,15 @@ class GroupCall: SignalRingRTC.GroupCallDelegate {
|
||||
)
|
||||
}
|
||||
|
||||
public var isJustMe: Bool {
|
||||
switch ringRtcCall.localDeviceState.joinState {
|
||||
case .notJoined, .joining, .pending:
|
||||
return true
|
||||
case .joined:
|
||||
return ringRtcCall.remoteDeviceStates.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Concrete Type
|
||||
|
||||
enum ConcreteType {
|
||||
|
||||
@ -24,6 +24,12 @@ class CallButton: UIButton {
|
||||
var unselectedBackgroundColor = UIColor.ows_whiteAlpha40 { didSet { updateAppearance() } }
|
||||
var selectedBackgroundColor = UIColor.ows_white { didSet { updateAppearance() } }
|
||||
|
||||
// TODO: When `FeatureFlags.groupCallDrawerSupport` is removed, have
|
||||
// these replace [un]selectedBackgroundColor` in this file and make
|
||||
// private and non-static.
|
||||
static private(set) var unselectedBackgroundColorInDrawer = UIColor(rgbHex: 0x4A4A4A).withAlphaComponent(0.63)
|
||||
static private(set) var selectedIconColorInDrawer: UIColor = .ows_black
|
||||
|
||||
var currentBackgroundColor: UIColor {
|
||||
return isSelected ? selectedBackgroundColor : unselectedBackgroundColor
|
||||
}
|
||||
|
||||
@ -280,10 +280,8 @@ class CallControls: UIView {
|
||||
button.setContentHuggingHorizontalHigh()
|
||||
button.setCompressionResistanceHorizontalLow()
|
||||
if useCallDrawerStyling {
|
||||
// TODO: When feature flag is removed, set these colors in `CallButton`.
|
||||
let unselectedBackgroundColor = UIColor(rgbHex: 0x4A4A4A).withAlphaComponent(0.63)
|
||||
button.unselectedBackgroundColor = unselectedBackgroundColor
|
||||
button.selectedIconColor = .ows_black
|
||||
button.unselectedBackgroundColor = CallButton.unselectedBackgroundColorInDrawer
|
||||
button.selectedIconColor = CallButton.selectedIconColorInDrawer
|
||||
} else {
|
||||
button.alpha = 0.9
|
||||
}
|
||||
@ -431,17 +429,9 @@ private class CallControlsViewModel {
|
||||
case .individual(let call):
|
||||
return ![.idle, .dialing, .remoteRinging, .localRinging_Anticipatory, .localRinging_ReadyToAnswer].contains(call.state)
|
||||
case .groupThread(let call as GroupCall), .callLink(let call as GroupCall):
|
||||
// Because joined group calls include the `moreButton`, we're out of
|
||||
// space for the `flipCameraButton`.
|
||||
//
|
||||
// Recall that the flip camera button is in the local pip once someone
|
||||
// else has joined the call. That leaves the issue of where to locate
|
||||
// the flip camera button when the user is fullscreen because they have
|
||||
// joined and are the sole member in the call.
|
||||
//
|
||||
// TODO: Implement the future designs for this. In the meantime, Design
|
||||
// wants us to omit the flip camera button from Call Controls when in
|
||||
// joined fullscreen.
|
||||
if FeatureFlags.groupCallDrawerSupport && call.isJustMe {
|
||||
return true
|
||||
}
|
||||
return call.hasJoinedOrIsWaitingForAdminApproval
|
||||
}
|
||||
}
|
||||
|
||||
@ -250,8 +250,8 @@ class CallMemberChromeOverlayView: UIView, CallMemberComposableView {
|
||||
private var flipCameraButtonWidthConstraint: NSLayoutConstraint?
|
||||
private var flipCameraImageWidthConstraint: NSLayoutConstraint?
|
||||
|
||||
private lazy var flipCameraCircleView: CircleView = {
|
||||
let circleView = CircleView()
|
||||
private lazy var flipCameraCircleView: CircleBlurView = {
|
||||
let circleView = CircleBlurView(effect: UIBlurEffect(style: .systemThinMaterialDark))
|
||||
circleView.isUserInteractionEnabled = false
|
||||
return circleView
|
||||
}()
|
||||
@ -269,7 +269,7 @@ class CallMemberChromeOverlayView: UIView, CallMemberComposableView {
|
||||
}()
|
||||
|
||||
private lazy var flipCameraButton: UIButton = {
|
||||
flipCameraCircleView.addSubview(flipCameraImageView)
|
||||
flipCameraCircleView.contentView.addSubview(flipCameraImageView)
|
||||
flipCameraImageView.autoCenterInSuperview()
|
||||
let button = UIButton()
|
||||
button.addSubview(flipCameraCircleView)
|
||||
|
||||
@ -48,6 +48,15 @@ class GroupCallViewController: UIViewController {
|
||||
sheetPanDelegate: self
|
||||
)
|
||||
}()
|
||||
private lazy var fullscreenLocalMemberAddOnsView: SupplementalCallControlsForFullscreenLocalMember = {
|
||||
let view = SupplementalCallControlsForFullscreenLocalMember(
|
||||
call: call,
|
||||
groupCall: groupCall,
|
||||
callService: callService
|
||||
)
|
||||
view.isHidden = true
|
||||
return view
|
||||
}()
|
||||
private lazy var callControlsConfirmationToastContainerView = UIView()
|
||||
private var callService: CallService { AppEnvironment.shared.callService }
|
||||
private var incomingCallControls: IncomingCallControls?
|
||||
@ -386,6 +395,15 @@ class GroupCallViewController: UIViewController {
|
||||
swipeToastView.autoPinEdge(toSuperviewMargin: .leading, relation: .greaterThanOrEqual)
|
||||
swipeToastView.autoPinEdge(toSuperviewMargin: .trailing, relation: .greaterThanOrEqual)
|
||||
|
||||
view.addSubview(fullscreenLocalMemberAddOnsView)
|
||||
fullscreenLocalMemberAddOnsView.autoPinLeadingToSuperviewMargin()
|
||||
fullscreenLocalMemberAddOnsView.autoPinTrailingToSuperviewMargin()
|
||||
fullscreenLocalMemberAddOnsView.autoPinEdge(
|
||||
.bottom,
|
||||
to: .bottom,
|
||||
of: bottomVStack
|
||||
)
|
||||
|
||||
view.addSubview(callControlsConfirmationToastContainerView)
|
||||
callControlsConfirmationToastContainerView.autoHCenterInSuperview()
|
||||
view.addSubview(callControlsOverflowView)
|
||||
@ -518,7 +536,9 @@ class GroupCallViewController: UIViewController {
|
||||
return
|
||||
}
|
||||
bottomSheet.setBottomSheetMinimizedHeight()
|
||||
present(self.bottomSheet, animated: true)
|
||||
present(self.bottomSheet, animated: true) {
|
||||
self.fullscreenLocalMemberAddOnsView.isHidden = false
|
||||
}
|
||||
}
|
||||
|
||||
private func dismissBottomSheet(animated: Bool = true) {
|
||||
@ -678,7 +698,7 @@ class GroupCallViewController: UIViewController {
|
||||
speakerView.applyChangesToCallMemberViewAndVideoView { view in
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
if self.isJustMe {
|
||||
if groupCall.isJustMe {
|
||||
localMemberView.applyChangesToCallMemberViewAndVideoView { view in
|
||||
speakerPage.addSubview(view)
|
||||
view.frame = CGRect(origin: .zero, size: size)
|
||||
@ -798,7 +818,7 @@ class GroupCallViewController: UIViewController {
|
||||
shouldAnimateViewFrames: Bool = false,
|
||||
bottomSheetChangedStateFrom oldBottomSheetState: BottomSheetState? = nil
|
||||
) {
|
||||
let isFullScreen = self.isJustMe
|
||||
let isFullScreen = groupCall.isJustMe
|
||||
localMemberView.configure(
|
||||
call: call,
|
||||
isFullScreen: isFullScreen
|
||||
@ -1114,6 +1134,8 @@ class GroupCallViewController: UIViewController {
|
||||
return
|
||||
}
|
||||
|
||||
fullscreenLocalMemberAddOnsView.isHidden = true
|
||||
|
||||
bottomSheetStateManager.submitState(.callControls)
|
||||
self.raisedHandsToast.raisedHands.removeAll()
|
||||
|
||||
@ -1154,15 +1176,6 @@ class GroupCallViewController: UIViewController {
|
||||
return true
|
||||
}
|
||||
|
||||
private var isJustMe: Bool {
|
||||
switch ringRtcCall.localDeviceState.joinState {
|
||||
case .notJoined, .joining, .pending:
|
||||
return true
|
||||
case .joined:
|
||||
return ringRtcCall.remoteDeviceStates.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
private var hasAtLeastTwoOthers: Bool {
|
||||
switch ringRtcCall.localDeviceState.joinState {
|
||||
case .notJoined, .joining, .pending:
|
||||
@ -1195,7 +1208,7 @@ class GroupCallViewController: UIViewController {
|
||||
}
|
||||
|
||||
private var bottomSheetMustBeVisible: Bool {
|
||||
return self.isJustMe
|
||||
return groupCall.isJustMe
|
||||
}
|
||||
|
||||
private var sheetTimeoutTimer: Timer?
|
||||
|
||||
@ -0,0 +1,117 @@
|
||||
//
|
||||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import SignalRingRTC
|
||||
import SignalServiceKit
|
||||
import SignalUI
|
||||
import UIKit
|
||||
|
||||
/// Once at least one remote member joins a call, the local member's video
|
||||
/// reduces down to a pip, which includes certain call controls such as
|
||||
/// the flip camera button (with more to come, per future designs). While
|
||||
/// the local member is alone (either because they're in the lobby or
|
||||
/// because they're the only one in the call), they're fullscreen, and
|
||||
/// those call controls need to be somewhere! This view is the place!
|
||||
class SupplementalCallControlsForFullscreenLocalMember: UIView {
|
||||
private lazy var flipCameraCircleView: CircleBlurView = {
|
||||
let circleView = CircleBlurView(effect: UIBlurEffect(style: .systemThinMaterialDark))
|
||||
circleView.backgroundColor = CallButton.unselectedBackgroundColorInDrawer
|
||||
circleView.isUserInteractionEnabled = false
|
||||
return circleView
|
||||
}()
|
||||
|
||||
private lazy var flipCameraImageView = {
|
||||
let imageView = UIImageView(image: UIImage(named: "switch-camera-28"))
|
||||
imageView.tintColor = .ows_white
|
||||
imageView.autoSetDimension(
|
||||
.height,
|
||||
toSize: 24
|
||||
)
|
||||
imageView.autoMatch(.width, to: .height, of: imageView)
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var flipCameraButton: UIButton = {
|
||||
flipCameraCircleView.contentView.addSubview(flipCameraImageView)
|
||||
flipCameraImageView.autoCenterInSuperview()
|
||||
let button = UIButton()
|
||||
button.addSubview(flipCameraCircleView)
|
||||
flipCameraCircleView.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
button.autoSetDimension(
|
||||
.height,
|
||||
toSize: 48
|
||||
)
|
||||
button.autoMatch(.width, to: .height, of: button)
|
||||
button.accessibilityLabel = flipCameraButtonAccessibilityLabel
|
||||
button.addTarget(self, action: #selector(didPressFlipCamera), for: .touchUpInside)
|
||||
return button
|
||||
}()
|
||||
|
||||
@objc
|
||||
private func didPressFlipCamera() {
|
||||
if let isUsingFrontCamera = call.videoCaptureController.isUsingFrontCamera {
|
||||
callService.updateCameraSource(call: call, isUsingFrontCamera: !isUsingFrontCamera)
|
||||
}
|
||||
}
|
||||
|
||||
private var flipCameraButtonAccessibilityLabel: String {
|
||||
return OWSLocalizedString(
|
||||
"CALL_VIEW_SWITCH_CAMERA_DIRECTION",
|
||||
comment: "Accessibility label to toggle front- vs. rear-facing camera"
|
||||
)
|
||||
}
|
||||
|
||||
private let call: SignalCall
|
||||
private let groupCall: GroupCall
|
||||
private let callService: CallService
|
||||
|
||||
init(
|
||||
call: SignalCall,
|
||||
groupCall: GroupCall,
|
||||
callService: CallService
|
||||
) {
|
||||
self.call = call
|
||||
self.groupCall = groupCall
|
||||
self.callService = callService
|
||||
super.init(frame: .zero)
|
||||
self.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
groupCall.addObserver(self)
|
||||
|
||||
if FeatureFlags.groupCallDrawerSupport {
|
||||
flipCameraButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(flipCameraButton)
|
||||
NSLayoutConstraint.activate([
|
||||
flipCameraButton.trailingAnchor.constraint(equalTo: self.trailingAnchor),
|
||||
flipCameraButton.topAnchor.constraint(equalTo: self.topAnchor),
|
||||
flipCameraButton.bottomAnchor.constraint(equalTo: self.bottomAnchor)
|
||||
])
|
||||
updateView()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateView() {
|
||||
if FeatureFlags.groupCallDrawerSupport {
|
||||
flipCameraButton.isHidden = !groupCall.isJustMe || call.isOutgoingVideoMuted
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Required
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
extension SupplementalCallControlsForFullscreenLocalMember: GroupCallObserver {
|
||||
func groupCallLocalDeviceStateChanged(_ call: GroupCall) {
|
||||
updateView()
|
||||
}
|
||||
|
||||
func groupCallRemoteDeviceStatesChanged(_ call: GroupCall) {
|
||||
updateView()
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user