Put flip cam button in alt location when alone in group call

This commit is contained in:
Marissa Le Coz 2024-08-16 16:30:25 -04:00 committed by GitHub
parent c76c6759fa
commit cd2bb5c90a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 170 additions and 31 deletions

View File

@ -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 */,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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