Call controls confirmation toasts
This commit is contained in:
parent
f89a56a43e
commit
320d7e2c72
@ -1581,6 +1581,7 @@
|
||||
D9FC835029F71CC300878BC8 /* GroupUpdateInfoMessageInserter+PrecomputedUpdateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9FC834F29F71CC300878BC8 /* GroupUpdateInfoMessageInserter+PrecomputedUpdateType.swift */; };
|
||||
E1368CBE18A1C36B00109378 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9EB5ABC1884C002007CBB57 /* MessageUI.framework */; };
|
||||
E14EDF6E2A71AFDF00F0FD7C /* RecipientContextMenuHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E14EDF6D2A71AFDF00F0FD7C /* RecipientContextMenuHelper.swift */; };
|
||||
E17283DE2B2A713C00302DC7 /* CallControlsConfirmationToast.swift in Sources */ = {isa = PBXBuildFile; fileRef = E17283DD2B2A713C00302DC7 /* CallControlsConfirmationToast.swift */; };
|
||||
E18C4A7729EF2ECC007534D4 /* SignalAccountTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18C4A7629EF2ECC007534D4 /* SignalAccountTest.swift */; };
|
||||
E1A090382A4B909B00F2BE8B /* RecipientHidingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A090372A4B909B00F2BE8B /* RecipientHidingManager.swift */; };
|
||||
E1C2A54B2A8FCB0D00AEC4DA /* DeleteSystemContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C2A54A2A8FCB0D00AEC4DA /* DeleteSystemContactViewController.swift */; };
|
||||
@ -4313,6 +4314,7 @@
|
||||
DE2DD605305BC6EFAD731723 /* Pods-Signal.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Signal.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Signal/Pods-Signal.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
DF728B4B438716EAF95CEC18 /* Pods-Signal.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Signal.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-Signal/Pods-Signal.app store release.xcconfig"; sourceTree = "<group>"; };
|
||||
E14EDF6D2A71AFDF00F0FD7C /* RecipientContextMenuHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipientContextMenuHelper.swift; sourceTree = "<group>"; };
|
||||
E17283DD2B2A713C00302DC7 /* CallControlsConfirmationToast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallControlsConfirmationToast.swift; sourceTree = "<group>"; };
|
||||
E18C4A7629EF2ECC007534D4 /* SignalAccountTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalAccountTest.swift; sourceTree = "<group>"; };
|
||||
E1A090372A4B909B00F2BE8B /* RecipientHidingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipientHidingManager.swift; sourceTree = "<group>"; };
|
||||
E1A0AD8B16E13FDD0071E604 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
|
||||
@ -8062,6 +8064,7 @@
|
||||
88588D27252E5A1F00405414 /* Individual */,
|
||||
88238EA324E9DDE900F28079 /* CallButton.swift */,
|
||||
88588D1A252D3DFF00405414 /* CallControls.swift */,
|
||||
E17283DD2B2A713C00302DC7 /* CallControlsConfirmationToast.swift */,
|
||||
88ABB8B42534070400229EAA /* CallHeader.swift */,
|
||||
88D23D1C23CEC0C700B0E74B /* CallUIAdapter.swift */,
|
||||
1700E33E28B856FB0073D949 /* IncomingCallControls.swift */,
|
||||
@ -12029,6 +12032,7 @@
|
||||
88D23D2C23CEC17400B0E74B /* CallAudioService.swift in Sources */,
|
||||
88238EA424E9DDE900F28079 /* CallButton.swift in Sources */,
|
||||
88588D1B252D3DFF00405414 /* CallControls.swift in Sources */,
|
||||
E17283DE2B2A713C00302DC7 /* CallControlsConfirmationToast.swift in Sources */,
|
||||
88ABB8B52534070400229EAA /* CallHeader.swift in Sources */,
|
||||
88D23D2423CEC0C700B0E74B /* CallKitCallManager.swift in Sources */,
|
||||
88D23D2523CEC0C700B0E74B /* CallKitCallUIAdaptee.swift in Sources */,
|
||||
|
||||
15
Signal/Symbols.xcassets/mic/mic-slash.imageset/Contents.json
vendored
Normal file
15
Signal/Symbols.xcassets/mic/mic-slash.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "mic-slash.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
146
Signal/Symbols.xcassets/mic/mic-slash.imageset/mic-slash.pdf
vendored
Normal file
146
Signal/Symbols.xcassets/mic/mic-slash.imageset/mic-slash.pdf
vendored
Normal file
@ -0,0 +1,146 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 2.625000 10.791534 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
4.869258 9.072965 m
|
||||
6.282362 7.659862 l
|
||||
6.501116 9.171856 7.802404 10.333466 9.375000 10.333466 c
|
||||
11.100889 10.333466 12.500000 8.934355 12.500000 7.208466 c
|
||||
12.500000 1.708466 l
|
||||
12.500000 1.622302 12.496512 1.536952 12.489672 1.452551 c
|
||||
13.942234 -0.000010 l
|
||||
14.141206 0.531660 14.250000 1.107358 14.250000 1.708466 c
|
||||
14.250000 7.208466 l
|
||||
14.250000 9.900854 12.067389 12.083466 9.375000 12.083466 c
|
||||
7.343011 12.083466 5.601388 10.840257 4.869258 9.072965 c
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 2.625000 16.983765 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
10.303020 -9.270542 m
|
||||
8.523958 -7.491481 l
|
||||
7.482240 -7.197309 6.661456 -6.376525 6.367284 -5.334806 c
|
||||
4.500000 -3.467522 l
|
||||
4.500000 -4.483765 l
|
||||
4.500000 -7.176152 6.682612 -9.358765 9.375000 -9.358765 c
|
||||
9.692335 -9.358765 10.002587 -9.328444 10.303020 -9.270542 c
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 2.625000 9.625000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
11.715831 -3.324589 m
|
||||
13.045951 -4.654709 l
|
||||
12.186229 -5.074633 11.243936 -5.351652 10.250000 -5.454840 c
|
||||
10.250000 -6.750000 l
|
||||
13.625000 -6.750000 l
|
||||
14.108249 -6.750000 14.500000 -7.141750 14.500000 -7.625000 c
|
||||
14.500000 -8.108250 14.108249 -8.500000 13.625000 -8.500000 c
|
||||
5.125000 -8.500000 l
|
||||
4.641751 -8.500000 4.250000 -8.108250 4.250000 -7.625000 c
|
||||
4.250000 -7.141750 4.641751 -6.750000 5.125000 -6.750000 c
|
||||
8.500000 -6.750000 l
|
||||
8.500000 -5.454840 l
|
||||
4.285833 -5.017340 1.000000 -1.454895 1.000000 2.875000 c
|
||||
1.000000 3.875000 l
|
||||
1.000000 4.358249 1.391751 4.750000 1.875000 4.750000 c
|
||||
2.358249 4.750000 2.750000 4.358249 2.750000 3.875000 c
|
||||
2.750000 2.875000 l
|
||||
2.750000 -0.783886 5.716114 -3.750000 9.375000 -3.750000 c
|
||||
10.199077 -3.750000 10.988011 -3.599539 11.715831 -3.324589 c
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 2.625000 16.681549 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
16.552219 -8.500011 m
|
||||
15.266595 -7.214387 l
|
||||
15.735333 -6.305653 16.000000 -5.274509 16.000000 -4.181549 c
|
||||
16.000000 -3.181549 l
|
||||
16.000000 -2.698300 16.391750 -2.306549 16.875000 -2.306549 c
|
||||
17.358250 -2.306549 17.750000 -2.698300 17.750000 -3.181549 c
|
||||
17.750000 -4.181549 l
|
||||
17.750000 -5.761374 17.312569 -7.239032 16.552219 -8.500011 c
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 2.625000 3.954163 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
1.493718 17.164557 m
|
||||
1.152010 17.506264 0.597990 17.506264 0.256282 17.164557 c
|
||||
-0.085427 16.822847 -0.085427 16.268827 0.256282 15.927119 c
|
||||
17.256281 -1.072880 l
|
||||
17.597990 -1.414589 18.152010 -1.414589 18.493717 -1.072880 c
|
||||
18.835426 -0.731173 18.835426 -0.177153 18.493717 0.164557 c
|
||||
1.493718 17.164557 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
2677
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000002767 00000 n
|
||||
0000002790 00000 n
|
||||
0000002963 00000 n
|
||||
0000003037 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
3096
|
||||
%%EOF
|
||||
15
Signal/Symbols.xcassets/speaker/speaker-slash.imageset/Contents.json
vendored
Normal file
15
Signal/Symbols.xcassets/speaker/speaker-slash.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "speaker-slash.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
110
Signal/Symbols.xcassets/speaker/speaker-slash.imageset/speaker-slash.pdf
vendored
Normal file
110
Signal/Symbols.xcassets/speaker/speaker-slash.imageset/speaker-slash.pdf
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 3.625000 2.954102 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
1.493718 17.664618 m
|
||||
1.152010 18.006325 0.597990 18.006325 0.256282 17.664618 c
|
||||
-0.085427 17.322908 -0.085427 16.768888 0.256282 16.427179 c
|
||||
16.256281 0.427179 l
|
||||
16.597990 0.085470 17.152010 0.085470 17.493719 0.427179 c
|
||||
17.835428 0.768888 17.835428 1.322906 17.493719 1.664616 c
|
||||
1.493718 17.664618 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 3.625000 7.786987 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
1.000000 6.213000 m
|
||||
1.000000 6.915573 1.252011 7.559285 1.670569 8.058672 c
|
||||
2.920504 6.808736 l
|
||||
2.812453 6.635981 2.750000 6.431784 2.750000 6.213000 c
|
||||
2.750000 2.213000 l
|
||||
2.750000 1.591680 3.253680 1.088000 3.875000 1.088000 c
|
||||
7.737508 1.088000 l
|
||||
11.500001 -2.674492 l
|
||||
11.500001 -1.770761 l
|
||||
13.182144 -3.452904 l
|
||||
12.850535 -4.569654 11.379924 -5.029287 10.475953 -4.125317 c
|
||||
7.012634 -0.662000 l
|
||||
3.875000 -0.662000 l
|
||||
2.287181 -0.662000 1.000000 0.625181 1.000000 2.213000 c
|
||||
1.000000 6.213000 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 3.625000 10.496582 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
8.423059 5.314100 m
|
||||
11.500000 8.391040 l
|
||||
11.500000 2.237160 l
|
||||
13.250001 0.487160 l
|
||||
13.250000 8.692817 l
|
||||
13.250000 10.140533 11.499649 10.865561 10.475953 9.841866 c
|
||||
7.185623 6.551537 l
|
||||
8.423059 5.314100 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1327
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001417 00000 n
|
||||
0000001440 00000 n
|
||||
0000001613 00000 n
|
||||
0000001687 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1746
|
||||
%%EOF
|
||||
@ -102,9 +102,15 @@ class CallControls: UIView {
|
||||
init(
|
||||
call: SignalCall,
|
||||
callService: CallService,
|
||||
confirmationToastManager: CallControlsConfirmationToastManager,
|
||||
delegate: CallControlsDelegate
|
||||
) {
|
||||
let viewModel = CallControlsViewModel(call: call, callService: callService, delegate: delegate)
|
||||
let viewModel = CallControlsViewModel(
|
||||
call: call,
|
||||
callService: callService,
|
||||
confirmationToastManager: confirmationToastManager,
|
||||
delegate: delegate
|
||||
)
|
||||
self.viewModel = viewModel
|
||||
self.delegate = delegate
|
||||
super.init(frame: .zero)
|
||||
@ -247,10 +253,17 @@ private class CallControlsViewModel {
|
||||
private let call: SignalCall
|
||||
private let callService: CallService
|
||||
private weak var delegate: CallControlsDelegate?
|
||||
private let confirmationToastManager: CallControlsConfirmationToastManager
|
||||
fileprivate var refreshView: (() -> Void)?
|
||||
init(call: SignalCall, callService: CallService, delegate: CallControlsDelegate) {
|
||||
init(
|
||||
call: SignalCall,
|
||||
callService: CallService,
|
||||
confirmationToastManager: CallControlsConfirmationToastManager,
|
||||
delegate: CallControlsDelegate
|
||||
) {
|
||||
self.call = call
|
||||
self.callService = callService
|
||||
self.confirmationToastManager = confirmationToastManager
|
||||
self.delegate = delegate
|
||||
call.addObserverAndSyncState(observer: self)
|
||||
callService.audioService.delegate = self
|
||||
@ -541,14 +554,18 @@ extension CallControlsViewModel {
|
||||
if callService.audioService.hasExternalInputs {
|
||||
callService.audioService.presentRoutePicker()
|
||||
} else {
|
||||
callService.audioService.requestSpeakerphone(call: self.call, isEnabled: !audioSourceButtonIsSelected)
|
||||
let shouldEnableSpeakerphone = !audioSourceButtonIsSelected
|
||||
callService.audioService.requestSpeakerphone(call: self.call, isEnabled: shouldEnableSpeakerphone)
|
||||
confirmationToastManager.toastInducingCallControlChangeDidOccur(state: .speakerphone(isOn: shouldEnableSpeakerphone))
|
||||
}
|
||||
refreshView?()
|
||||
}
|
||||
|
||||
@objc
|
||||
func didPressMute() {
|
||||
callService.updateIsLocalAudioMuted(isLocalAudioMuted: !muteButtonIsSelected)
|
||||
let shouldMute = !muteButtonIsSelected
|
||||
callService.updateIsLocalAudioMuted(isLocalAudioMuted: shouldMute)
|
||||
confirmationToastManager.toastInducingCallControlChangeDidOccur(state: .mute(isOn: shouldMute))
|
||||
refreshView?()
|
||||
}
|
||||
|
||||
@ -569,8 +586,10 @@ extension CallControlsViewModel {
|
||||
switch call.groupCallRingState {
|
||||
case .shouldRing:
|
||||
call.groupCallRingState = .doNotRing
|
||||
confirmationToastManager.toastInducingCallControlChangeDidOccur(state: .ring(isOn: false))
|
||||
case .doNotRing:
|
||||
call.groupCallRingState = .shouldRing
|
||||
confirmationToastManager.toastInducingCallControlChangeDidOccur(state: .ring(isOn: true))
|
||||
default:
|
||||
owsFailBeta("Ring button should not have been available to press!")
|
||||
}
|
||||
|
||||
@ -0,0 +1,198 @@
|
||||
//
|
||||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import SignalMessaging
|
||||
|
||||
class CallControlsConfirmationToastView: UIView {
|
||||
enum ControlType {
|
||||
case mute(isOn: Bool)
|
||||
case speakerphone(isOn: Bool)
|
||||
case ring(isOn: Bool)
|
||||
|
||||
var imageName: String {
|
||||
switch self {
|
||||
case .mute(let isOn):
|
||||
if isOn {
|
||||
return "mic-slash"
|
||||
} else {
|
||||
return "mic"
|
||||
}
|
||||
case .speakerphone(let isOn):
|
||||
if isOn {
|
||||
return "speaker"
|
||||
} else {
|
||||
return "speaker-slash"
|
||||
}
|
||||
case .ring(let isOn):
|
||||
if isOn {
|
||||
return "bell"
|
||||
} else {
|
||||
return "bell-slash"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var text: String {
|
||||
switch self {
|
||||
case .mute(let isOn):
|
||||
if isOn {
|
||||
return OWSLocalizedString(
|
||||
"MUTE_CONFIRMATION_TOAST_LABEL",
|
||||
comment: "Text for a toast confirming that the mic has been muted for a call."
|
||||
)
|
||||
} else {
|
||||
return OWSLocalizedString(
|
||||
"UNMUTE_CONFIRMATION_TOAST_LABEL",
|
||||
comment: "Text for a toast confirming that the mic has been unmuted for a call."
|
||||
)
|
||||
}
|
||||
case .speakerphone(let isOn):
|
||||
if isOn {
|
||||
return OWSLocalizedString(
|
||||
"SPEAKERPHONE_ON_CONFIRMATION_TOAST_LABEL",
|
||||
comment: "Text for a toast confirming that the speakerphone has been turned on for a call."
|
||||
)
|
||||
} else {
|
||||
return OWSLocalizedString(
|
||||
"SPEAKERPHONE_OFF_CONFIRMATION_TOAST_LABEL",
|
||||
comment: "Text for a toast confirming that the speakerphone has been turned off for a call."
|
||||
)
|
||||
}
|
||||
case .ring(let isOn):
|
||||
if isOn {
|
||||
return OWSLocalizedString(
|
||||
"RING_ON_CONFIRMATION_TOAST_LABEL",
|
||||
comment: "Text for a toast confirming that ringing has been turned on for a call."
|
||||
)
|
||||
} else {
|
||||
return OWSLocalizedString(
|
||||
"RING_OFF_CONFIRMATION_TOAST_LABEL",
|
||||
comment: "Text for a toast confirming that ringing has been turned off for a call."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum Style {
|
||||
private static let opacity = UIAccessibility.isReduceTransparencyEnabled ? 0.8 : 0.6
|
||||
static let toastBackgroundColor = UIColor(red: 0.29, green: 0.29, blue: 0.29, alpha: opacity)
|
||||
static let textAndImageColor = UIColor(red: 0.91, green: 0.91, blue: 0.91, alpha: 1)
|
||||
static let cornerRadius: CGFloat = 20
|
||||
static let spacing: CGFloat = 8
|
||||
static let horizontalMargin: CGFloat = 12
|
||||
static let verticalMargin: CGFloat = 10
|
||||
static let font: UIFont = .dynamicTypeBody2
|
||||
static let imageDimension: CGFloat = 16
|
||||
}
|
||||
|
||||
init(state: ControlType) {
|
||||
super.init(frame: .zero)
|
||||
|
||||
// Image view
|
||||
let imageView = UIImageView(image: UIImage(named: state.imageName))
|
||||
imageView.tintColor = Style.textAndImageColor
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
imageView.widthAnchor.constraint(equalToConstant: Style.imageDimension),
|
||||
imageView.heightAnchor.constraint(equalToConstant: Style.imageDimension)
|
||||
])
|
||||
// Label
|
||||
let label = UILabel()
|
||||
label.textColor = Style.textAndImageColor
|
||||
label.text = state.text
|
||||
label.font = Style.font
|
||||
// Stack
|
||||
let stackView = UIStackView()
|
||||
stackView.axis = .horizontal
|
||||
stackView.spacing = Style.spacing
|
||||
stackView.alignment = .center
|
||||
stackView.addArrangedSubview(imageView)
|
||||
stackView.addArrangedSubview(label)
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
// Blur view
|
||||
let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterialDark))
|
||||
blurView.translatesAutoresizingMaskIntoConstraints = false
|
||||
// Container view
|
||||
let containerView = UIView()
|
||||
containerView.backgroundColor = Style.toastBackgroundColor
|
||||
containerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
containerView.addSubview(stackView)
|
||||
NSLayoutConstraint.activate([
|
||||
containerView.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: -Style.horizontalMargin),
|
||||
containerView.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: Style.horizontalMargin),
|
||||
containerView.bottomAnchor.constraint(equalTo: stackView.bottomAnchor, constant: Style.verticalMargin),
|
||||
containerView.topAnchor.constraint(equalTo: stackView.topAnchor, constant: -Style.verticalMargin),
|
||||
])
|
||||
// Self
|
||||
self.addSubview(blurView)
|
||||
self.addSubview(containerView)
|
||||
self.layer.cornerRadius = Style.cornerRadius
|
||||
self.clipsToBounds = true
|
||||
blurView.autoPinEdgesToSuperviewEdges()
|
||||
containerView.autoPinEdgesToSuperviewEdges()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
class CallControlsConfirmationToastManager {
|
||||
typealias ControlType = CallControlsConfirmationToastView.ControlType
|
||||
|
||||
private let presentingContainerView: UIView
|
||||
|
||||
init(presentingContainerView: UIView) {
|
||||
self.presentingContainerView = presentingContainerView
|
||||
}
|
||||
|
||||
func toastInducingCallControlChangeDidOccur(state: ControlType) {
|
||||
self.presentToast(from: self.presentingContainerView, state: state)
|
||||
}
|
||||
|
||||
private var toast: UIView?
|
||||
|
||||
private func presentToast(from view: UIView, state: ControlType) {
|
||||
if let oldToast = self.toast {
|
||||
// Handle case where new toast is triggered before old
|
||||
// toast's disappearance animation completes.
|
||||
oldToast.layer.removeAllAnimations()
|
||||
oldToast.removeFromSuperview()
|
||||
}
|
||||
|
||||
let toast = CallControlsConfirmationToastView(state: state)
|
||||
self.toast = toast
|
||||
toast.alpha = 0
|
||||
view.addSubview(toast)
|
||||
toast.transform = .scale(0.8)
|
||||
toast.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
let appearAnimator = UIViewPropertyAnimator(
|
||||
duration: 0.2,
|
||||
springDamping: 0.8,
|
||||
springResponse: 0.2
|
||||
)
|
||||
appearAnimator.addAnimations {
|
||||
toast.alpha = 1
|
||||
toast.transform = .identity
|
||||
}
|
||||
appearAnimator.addCompletion { _ in
|
||||
let disappearAnimator = UIViewPropertyAnimator(
|
||||
duration: 0.2,
|
||||
springDamping: 0.8,
|
||||
springResponse: 0.2
|
||||
)
|
||||
disappearAnimator.addAnimations {
|
||||
toast.alpha = 0
|
||||
}
|
||||
disappearAnimator.addCompletion { _ in
|
||||
toast.removeFromSuperview()
|
||||
}
|
||||
disappearAnimator.startAnimation(afterDelay: 2)
|
||||
}
|
||||
appearAnimator.startAnimation()
|
||||
}
|
||||
}
|
||||
@ -14,7 +14,16 @@ import SignalUI
|
||||
class GroupCallViewController: UIViewController {
|
||||
private let call: SignalCall
|
||||
private var groupCall: GroupCall { call.groupCall }
|
||||
private lazy var callControls = CallControls(call: call, callService: callService, delegate: self)
|
||||
private lazy var callControlsConfirmationToastManager = CallControlsConfirmationToastManager(
|
||||
presentingContainerView: callControlsConfirmationToastContainerView
|
||||
)
|
||||
private lazy var callControls = CallControls(
|
||||
call: call,
|
||||
callService: callService,
|
||||
confirmationToastManager: callControlsConfirmationToastManager,
|
||||
delegate: self
|
||||
)
|
||||
private lazy var callControlsConfirmationToastContainerView = UIView()
|
||||
private lazy var incomingCallControls = IncomingCallControls(video: true, delegate: self)
|
||||
private var incomingCallControlsConstraint: NSLayoutConstraint?
|
||||
private lazy var noVideoIndicatorView: UIStackView = createNoVideoIndicatorView()
|
||||
@ -217,6 +226,15 @@ class GroupCallViewController: UIViewController {
|
||||
swipeToastView.autoPinEdge(toSuperviewMargin: .leading, relation: .greaterThanOrEqual)
|
||||
swipeToastView.autoPinEdge(toSuperviewMargin: .trailing, relation: .greaterThanOrEqual)
|
||||
|
||||
// Confirmation toasts should sit on top of the `localMemberView` and `videoOverflow`,
|
||||
// so this `addSubview` should remain towards the end of the setup.
|
||||
//
|
||||
// TODO: The call controls '...' overflow menu (which will include reactions and raise hand
|
||||
// options) will sit on top of these toasts.
|
||||
view.addSubview(callControlsConfirmationToastContainerView)
|
||||
callControlsConfirmationToastContainerView.autoPinEdge(.bottom, to: .top, of: callControls, withOffset: -30)
|
||||
callControlsConfirmationToastContainerView.autoHCenterInSuperview()
|
||||
|
||||
view.addGestureRecognizer(tapGesture)
|
||||
|
||||
updateCallUI()
|
||||
@ -382,7 +400,7 @@ class GroupCallViewController: UIViewController {
|
||||
speakerPage.addSubview(speakerView)
|
||||
speakerView.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
view.addSubview(localMemberView)
|
||||
view.insertSubview(localMemberView, belowSubview: callControlsConfirmationToastContainerView)
|
||||
|
||||
if groupCall.remoteDeviceStates.count > 1 {
|
||||
let pipWidth = GroupCallVideoOverflow.itemHeight * ReturnToCallViewController.pipSize.aspectRatio
|
||||
|
||||
@ -32,9 +32,14 @@ class IndividualCallViewController: OWSViewController, CallObserver {
|
||||
|
||||
private var callDurationTimer: Timer?
|
||||
|
||||
private lazy var callControlsConfirmationToastManager = CallControlsConfirmationToastManager(
|
||||
presentingContainerView: callControlsConfirmationToastContainerView
|
||||
)
|
||||
private lazy var callControlsConfirmationToastContainerView = UIView()
|
||||
private lazy var callControls = CallControls(
|
||||
call: call,
|
||||
callService: callService,
|
||||
confirmationToastManager: callControlsConfirmationToastManager,
|
||||
delegate: self
|
||||
)
|
||||
|
||||
@ -275,6 +280,13 @@ class IndividualCallViewController: OWSViewController, CallObserver {
|
||||
callControls.autoPinEdge(toSuperviewEdge: .leading)
|
||||
callControls.autoPinEdge(toSuperviewEdge: .trailing)
|
||||
|
||||
// Confirmation toasts should sit on top of the `localVideoView`
|
||||
// and most other UI elements, so this `addSubview` should remain towards
|
||||
// the end of the setup.
|
||||
view.addSubview(callControlsConfirmationToastContainerView)
|
||||
callControlsConfirmationToastContainerView.autoPinEdge(.bottom, to: .top, of: callControls, withOffset: -30)
|
||||
callControlsConfirmationToastContainerView.autoHCenterInSuperview()
|
||||
|
||||
createContactViews()
|
||||
createIncomingCallControls()
|
||||
}
|
||||
@ -512,6 +524,7 @@ class IndividualCallViewController: OWSViewController, CallObserver {
|
||||
guard !localVideoView.isHidden else { return }
|
||||
|
||||
view.bringSubviewToFront(localVideoView)
|
||||
view.bringSubviewToFront(callControlsConfirmationToastContainerView)
|
||||
|
||||
let pipSize = ReturnToCallViewController.pipSize
|
||||
let lastBoundingRect = lastLocalVideoBoundingRect
|
||||
|
||||
@ -4192,6 +4192,9 @@
|
||||
/* A recovery suggestion shown as the body of an alert when try to link a new device & the user is already at the limit. */
|
||||
"MULTIDEVICE_PAIRING_MAX_RECOVERY" = "You have reached the maximum of devices you can currently link with your account. Please remove a device and try again.";
|
||||
|
||||
/* Text for a toast confirming that the mic has been muted for a call. */
|
||||
"MUTE_CONFIRMATION_TOAST_LABEL" = "Mic off";
|
||||
|
||||
/* Badge indicating that the user is muted. */
|
||||
"MUTED_BADGE" = "Muted";
|
||||
|
||||
@ -5689,6 +5692,12 @@
|
||||
/* Button to return to current group call */
|
||||
"RETURN_CALL_PILL_BUTTON" = "Return";
|
||||
|
||||
/* Text for a toast confirming that ringing has been turned off for a call. */
|
||||
"RING_OFF_CONFIRMATION_TOAST_LABEL" = "Ringing off";
|
||||
|
||||
/* Text for a toast confirming that ringing has been turned on for a call. */
|
||||
"RING_ON_CONFIRMATION_TOAST_LABEL" = "Ringing on";
|
||||
|
||||
/* button title to confirm adding a recipient when their safety number has recently changed */
|
||||
"SAFETY_NUMBER_CHANGED_CONFIRM_ADD_MEMBER_ACTION" = "Add Anyway";
|
||||
|
||||
@ -6937,6 +6946,12 @@
|
||||
/* Title for the captcha view controller */
|
||||
"SPAM_CAPTCHA_VIEW_CONTROLLER" = "Complete verification";
|
||||
|
||||
/* Text for a toast confirming that the speakerphone has been turned off for a call. */
|
||||
"SPEAKERPHONE_OFF_CONFIRMATION_TOAST_LABEL" = "Speaker off";
|
||||
|
||||
/* Text for a toast confirming that the speakerphone has been turned on for a call. */
|
||||
"SPEAKERPHONE_ON_CONFIRMATION_TOAST_LABEL" = "Speaker on";
|
||||
|
||||
/* The name for the sticker category 'Featured' */
|
||||
"STICKER_CATEGORY_FEATURED_NAME" = "Featured";
|
||||
|
||||
@ -7588,6 +7603,9 @@
|
||||
/* Alert title when unlinking device fails */
|
||||
"UNLINKING_FAILED_ALERT_TITLE" = "Signal was unable to unlink your device.";
|
||||
|
||||
/* Text for a toast confirming that the mic has been unmuted for a call. */
|
||||
"UNMUTE_CONFIRMATION_TOAST_LABEL" = "Mic on";
|
||||
|
||||
/* Pressing this button un-pins a thread */
|
||||
"UNPIN_ACTION" = "Unpin";
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user