AzureCommunicationUI/AzureCommunicationUIDemoApp/Sources/Views/SettingsView.swift (410 lines of code) (raw):
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
import SwiftUI
import AzureCommunicationUICalling
import AzureCommunicationCommon
struct SettingsView: View {
@State private var setupSelectedOrientation: String = OrientationOptions.portrait.requestString
@State private var callingSelectedOrientation: String = OrientationOptions.portrait.requestString
private enum ThemeMode: String, CaseIterable, Identifiable {
case osApp = "OS / App"
case light = "Light Mode"
case dark = "Dark Mode"
var id: UIUserInterfaceStyle {
switch self {
case .osApp:
return .unspecified
case .light:
return .light
case .dark:
return .dark
}
}
}
@ObservedObject var envConfigSubject: EnvConfigSubject
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
let avatarChoices: [String] = ["cat", "fox", "koala", "monkey", "mouse", "octopus"]
var body: some View {
NavigationView {
settingsForm
.accessibilityElement(children: .contain)
.navigationTitle("UI Library - Settings")
.toolbar {
dismissButton
}
}
.accessibilityElement(children: .contain)
.onAppear {
GlobalCompositeManager.callComposite?.dismiss()
GlobalCompositeManager.callComposite = nil
}
}
var dismissButton: some View {
Button(
action: { self.presentationMode.wrappedValue.dismiss() },
label: { Image(systemName: "xmark") }
)
.accessibilityIdentifier(AccessibilityId.settingsCloseButtonAccessibilityID.rawValue)
}
var displayLeaveCallConfirmationSettings: some View {
Section(header: Text("Call screen settings")) {
Toggle("Display leave call confirmation", isOn: $envConfigSubject.displayLeaveCallConfirmation)
.onTapGesture {
envConfigSubject.displayLeaveCallConfirmation = !envConfigSubject.displayLeaveCallConfirmation
}
.accessibilityIdentifier(AccessibilityId.leaveCallConfirmationDisplayAccessibilityID.rawValue)
}
}
var callScreenHeaderSettings: some View {
Section(header: Text("Call Screen Title and Subtitle API")) {
TextField("Call Screen Custom title", text: $envConfigSubject.callInformationTitle)
.disableAutocorrection(true)
.autocapitalization(.none)
.textFieldStyle(.roundedBorder)
TextField("Apply Custom title on X number of remote participant join",
value: $envConfigSubject.customTitleApplyOnRemoteJoin, formatter: NumberFormatter())
.keyboardType(.decimalPad)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
TextField("Call Screen Custom subtitle", text: $envConfigSubject.callInformationSubtitle)
.disableAutocorrection(true)
.autocapitalization(.none)
.textFieldStyle(.roundedBorder)
TextField("Apply Custom subtitle on X number of remote participant join",
value: $envConfigSubject.customSubtitleApplyOnRemoteJoin, formatter: NumberFormatter())
.keyboardType(.decimalPad)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}
var setupScreenSettings: some View {
Section(header: Text("Setup screen settings")) {
Toggle("Camera button enabled", isOn: $envConfigSubject.setupScreenOptionsCameraButtonEnabled)
.onTapGesture {
envConfigSubject.setupScreenOptionsCameraButtonEnabled =
!envConfigSubject.setupScreenOptionsCameraButtonEnabled
}
.accessibilityIdentifier(AccessibilityId.setupScreenCameraButtonEnabledAccessibilityID.rawValue)
Toggle("Mic button enabled", isOn: $envConfigSubject.setupScreenOptionsMicButtonEnabled)
.onTapGesture {
envConfigSubject.setupScreenOptionsMicButtonEnabled =
!envConfigSubject.setupScreenOptionsMicButtonEnabled
}
.accessibilityIdentifier(AccessibilityId.setupScreenMicButtonEnabledAccessibilityID.rawValue)
}
}
var settingsForm: some View {
List {
Group {
orientationOptions
captionsSettings
buttonSettins
multitaskingSettings
callScreenHeaderSettings
}
Group {
localizationSettings
skipSetupScreenSettings
micSettings
localParticipantSettings
avatarSettings
useMockCallingSDKHandler
navigationSettings
remoteParticipantsAvatarsSettings
themeSettings
setupScreenSettings
}
displayLeaveCallConfirmationSettings
exitCompositeSettings
callKitSettings
pushNotificationsSettings
deprecatedAPIsSettings
}
}
var orientationOptions: some View {
Group {
callingViewOrientationSettings
setupViewOrientationSettings
}
}
var localParticipantSettings: some View {
Section(header: Text("Local Participant Settings")) {
expiredTokenToggle
audioOnlyModeToggle
}
.accessibilityElement(children: .contain)
}
var expiredTokenToggle: some View {
Toggle("Use expired token", isOn: $envConfigSubject.useExpiredToken)
.accessibilityIdentifier(AccessibilityId.expiredAcsTokenToggleAccessibilityID.rawValue)
}
var useMockCallingSDKHandler: some View {
#if DEBUG
Section(header: Text("Calling SDK Wrapper Handler Mocking")) {
mockCallingSDKToggle
}
.accessibilityElement(children: .contain)
#else
EmptyView()
#endif
}
var mockCallingSDKToggle: some View {
Toggle("Use mock Calling SDK Wrapper Handler",
isOn: $envConfigSubject.useMockCallingSDKHandler)
}
/* <audioVideoMode> */
var audioOnlyModeToggle: some View {
Toggle("Audio only",
isOn: $envConfigSubject.audioOnly)
}
/* </audioVideoMode> */
var relaunchCompositeOnDismissedToggle: some View {
Toggle("Relaunch composite after dismiss api call",
isOn: $envConfigSubject.useRelaunchOnDismissedToggle)
.accessibilityIdentifier(AccessibilityId.useRelaunchOnDismissedToggleToggleAccessibilityID.rawValue)
}
var exitCompositeSettings: some View {
Section(header: Text("Exit API Testing")) {
relaunchCompositeOnDismissedToggle
TextField(
"Exit composite after seconds",
text: $envConfigSubject.exitCompositeAfterDuration
)
.keyboardType(.numberPad)
.disableAutocorrection(true)
.autocapitalization(.none)
.textFieldStyle(.roundedBorder)
}
}
var callKitSettings: some View {
Section(header: Text("Callkit Settings")) {
enableCallKitToggle
enableRemoteHold
enableRemoteInfo
TextField(
"Remote info, default is Group/Teams call",
text: $envConfigSubject.callkitRemoteInfo
)
.keyboardType(.default)
.disableAutocorrection(true)
.autocapitalization(.none)
.textFieldStyle(.roundedBorder)
}
}
var enableCallKitToggle: some View {
Toggle("Enable Callkit",
isOn: $envConfigSubject.enableCallKit)
.accessibilityIdentifier(AccessibilityId.useEnableCalkitToggleToggleAccessibilityID.rawValue)
}
var enableRemoteHold: some View {
Toggle("Enable remote hold",
isOn: $envConfigSubject.enableRemoteHold)
.accessibilityIdentifier(AccessibilityId.useEnableRemoteHoldToggleToggleAccessibilityID.rawValue)
}
var enableRemoteInfo: some View {
Toggle("Enable remote info",
isOn: $envConfigSubject.enableRemoteInfo)
.accessibilityIdentifier(AccessibilityId.useEnableRemoteInfoToggleToggleAccessibilityID.rawValue)
}
var localizationSettings: some View {
Section(header: Text("Localization")) {
LocalePicker(selection: $envConfigSubject.locale)
Toggle("Is Right-to-Left", isOn: $envConfigSubject.isRightToLeft)
TextField(
"Locale identifier (eg. zh-Hant, fr-CA)",
text: $envConfigSubject.localeIdentifier
)
.keyboardType(.default)
.disableAutocorrection(true)
.autocapitalization(.none)
.textFieldStyle(.roundedBorder)
}
}
var captionsSettings: some View {
Section(header: Text("Captions")) {
CaptionsLocaleTextField(selection: $envConfigSubject.spokenLanguage)
Toggle("Start Captions", isOn: $envConfigSubject.captionsOn)
}
}
var buttonSettins: some View {
Section(header: Text("Custom Button")) {
Toggle("Add Custom Buttons", isOn: $envConfigSubject.addCustomButton)
Toggle("Hide All Buttons in More List", isOn: $envConfigSubject.hideAllButtons)
}
}
var pushNotificationsSettings: some View {
Section(header: Text("Push notification")) {
Toggle("Disable internal push for incoming call",
isOn: $envConfigSubject.disableInternalPushForIncomingCall)
.keyboardType(.default)
.disableAutocorrection(true)
.autocapitalization(.none)
.textFieldStyle(.roundedBorder)
}
}
var deprecatedAPIsSettings: some View {
Section(header: Text("Deprecated APIs")) {
Toggle("Use deprecated launch",
isOn: $envConfigSubject.useDeprecatedLaunch)
.keyboardType(.default)
.disableAutocorrection(true)
.autocapitalization(.none)
.textFieldStyle(.roundedBorder)
}
}
var callingViewOrientationSettings: some View {
Section(header: Text("Calling View Orientation")) {
Picker("Orientation", selection: $callingSelectedOrientation) {
ForEach([OrientationOptions.portrait.requestString, OrientationOptions.landscape.requestString,
OrientationOptions.landscapeLeft.requestString,
OrientationOptions.landscapeRight.requestString,
OrientationOptions.allButUpsideDown.requestString], id: \.requestString) { orientationOption in
Text(orientationOption.requestString.capitalized).tag(orientationOption.requestString)
}
}
.pickerStyle(MenuPickerStyle())
.onAppear {
callingSelectedOrientation =
envConfigSubject.callingViewOrientation.requestString
}
.onChange(of: callingSelectedOrientation) { newValue in
switch newValue {
case OrientationOptions.portrait.requestString:
envConfigSubject.callingViewOrientation = .portrait
case OrientationOptions.landscape.requestString:
envConfigSubject.callingViewOrientation = .landscape
case OrientationOptions.landscapeRight.requestString:
envConfigSubject.callingViewOrientation = .landscapeRight
case OrientationOptions.landscapeLeft.requestString:
envConfigSubject.callingViewOrientation = .landscapeLeft
default:
envConfigSubject.callingViewOrientation = .allButUpsideDown
}
}
}
}
var setupViewOrientationSettings: some View {
Section(header: Text("Setup View Orientation")) {
Picker("Orientation", selection: $setupSelectedOrientation) {
ForEach([OrientationOptions.allButUpsideDown.requestString,
OrientationOptions.portrait.requestString, OrientationOptions.landscape.requestString,
OrientationOptions.landscapeLeft.requestString,
OrientationOptions.landscapeRight.requestString], id: \.requestString) { orientationOption in
Text(orientationOption.requestString.capitalized).tag(orientationOption.requestString)
}
}
.pickerStyle(MenuPickerStyle())
.onAppear {
setupSelectedOrientation = envConfigSubject.setupViewOrientation.requestString
}
.onChange(of: setupSelectedOrientation) { newValue in
switch newValue {
case OrientationOptions.portrait.requestString:
envConfigSubject.setupViewOrientation = .portrait
case OrientationOptions.landscape.requestString:
envConfigSubject.setupViewOrientation = .landscape
case OrientationOptions.landscapeLeft.requestString:
envConfigSubject.setupViewOrientation = .landscapeLeft
case OrientationOptions.landscapeRight.requestString:
envConfigSubject.setupViewOrientation = .landscapeRight
case OrientationOptions.allButUpsideDown.requestString:
envConfigSubject.setupViewOrientation = .allButUpsideDown
default:
envConfigSubject.setupViewOrientation = .portrait
}
}
}
}
var micSettings: some View {
Section(header: Text("Mic & Carmera Default Vaule")) {
Toggle("Mic Default", isOn: $envConfigSubject.microphoneOn)
Toggle("Camera Default", isOn: $envConfigSubject.cameraOn)
}
}
var skipSetupScreenSettings: some View {
Section(header: Text("Skip Setup Screen Default Value")) {
Toggle("Skip Setup Screen", isOn: $envConfigSubject.skipSetupScreen)
}
}
var avatarSettings: some View {
Section(header: Text("Local Participant View Data")) {
Picker("Avatar Choices", selection: $envConfigSubject.avatarImageName) {
ForEach(avatarChoices, id: \.self) { avatar in
Image(avatar)
}
}.pickerStyle(.segmented)
TextField("Rendered Display Name", text: $envConfigSubject.renderedDisplayName)
.disableAutocorrection(true)
.autocapitalization(.none)
.textFieldStyle(.roundedBorder)
}
}
var navigationSettings: some View {
Section(header: Text("Setup View Data")) {
TextField("Navigation Title", text: $envConfigSubject.navigationTitle)
.disableAutocorrection(true)
.autocapitalization(.none)
.textFieldStyle(.roundedBorder)
TextField("Navigation SubTitle", text: $envConfigSubject.navigationSubtitle)
.disableAutocorrection(true)
.autocapitalization(.none)
.textFieldStyle(.roundedBorder)
}
}
var remoteParticipantsAvatarsSettings: some View {
Section(header: Text("Remote Participants View Data")) {
Toggle("Inject avatars", isOn: $envConfigSubject.useCustomRemoteParticipantViewData)
}
}
var themeSettings: some View {
Section(header: Text("Theme")) {
Toggle("Use Custom Theme Colors", isOn: $envConfigSubject.useCustomColors)
ColorPicker("Primary Color", selection: $envConfigSubject.primaryColor)
ColorPicker("OnPrimary Color", selection: $envConfigSubject.foregroundOnPrimaryColor)
ColorPicker("Tint 10 Color", selection: $envConfigSubject.tint10)
ColorPicker("Tint 20 Color", selection: $envConfigSubject.tint20)
ColorPicker("Tint 30 Color", selection: $envConfigSubject.tint30)
Picker("Force Theme Mode", selection: $envConfigSubject.colorSchemeOverride) {
ForEach(ThemeMode.allCases) { themeMode in
Text(themeMode.rawValue)
}
}.pickerStyle(.segmented)
}
}
var multitaskingSettings: some View {
Section(header: Text("Multitasking")) {
Toggle("Enable multitasking", isOn: $envConfigSubject.enableMultitasking)
Toggle("Enable Pip", isOn: $envConfigSubject.enablePipWhenMultitasking)
}
}
}
struct LocalePicker: View {
@Binding var selection: Locale
let supportedLanguage: [Locale] = [Locale(identifier: "")] + SupportedLocale.values
var body: some View {
Picker("Language", selection: $selection) {
ForEach(supportedLanguage, id: \.self) {
if $0.identifier == "" {
Text("Detect locale (en, zh-Hant, fr, fr-CA)")
} else {
Text($0.identifier)
}
}
}
}
}
struct CaptionsLocaleTextField: View {
@Binding var selection: String
@State private var inputLanguage: String = ""
var body: some View {
VStack {
TextField("Enter Language Code (e.g., en-US)", text: $inputLanguage)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.onChange(of: inputLanguage) { newValue in
selection = newValue // Directly update the selection
}
Text("Current Selection: \(selection)")
.padding()
}
.onAppear {
inputLanguage = selection
}
}
}