oficina / meet Public

Clone
git clone https://amadan.net/oficina/meet

Plain git — no account needed to clone.

Download

Download this file

1import SwiftUI
2
3/// The Calls server setting, as one Form section: in Calls → Settings…
4/// on a Mac and in the home screen's settings sheet on iPhone.
5///
6/// The field is a draft until Use this server; changing the server while
7/// signed in asks first, because it signs out.
8@MainActor public struct ServerSettingsSection: View {
9 @ObservedObject private var model: CallsModel
10 @State private var draft: String
11 @State private var problem = ""
12 @State private var confirming: String?
13
14 public init(model: CallsModel) {
15 self.model = model
16 _draft = State(initialValue: model.server)
17 }
18
19 private var changed: Bool {
20 guard case .success(let origin) = CallsServer.validate(draft) else { return true }
21 return origin != model.server
22 }
23
24 public var body: some View {
25 Section {
26 TextField("Server address", text: $draft, prompt: Text(CallsServer.defaultOrigin))
27 .accessibilityIdentifier("serverAddress")
28 #if os(iOS)
29 .keyboardType(.URL).textInputAutocapitalization(.never).autocorrectionDisabled()
30 #endif
31 .onSubmit(apply)
32 .onChange(of: draft) { problem = "" }
33 if !problem.isEmpty {
34 Label(problem, systemImage: "exclamationmark.circle.fill")
35 .foregroundStyle(.red).fixedSize(horizontal: false, vertical: true)
36 .accessibilityLabel("Error: \(problem)")
37 }
38 HStack {
39 Button("Use this server", action: apply)
40 .disabled(!changed || model.joining).accessibilityIdentifier("useServer")
41 Spacer()
42 Button("Reset to \(CallsServer.host(of: CallsServer.defaultOrigin))") {
43 draft = CallsServer.defaultOrigin
44 apply()
45 }
46 .disabled(model.server == CallsServer.defaultOrigin || model.joining)
47 }
48 #if os(iOS)
49 .buttonStyle(.borderless)
50 #endif
51 } header: {
52 Text("Calls server")
53 } footer: {
54 Text(
55 model.joining
56 ? "Leave the current meeting to change the server."
57 : "Sign in goes to this server. If you run your own Calls, enter its address here. Links to meetings on other servers still open, just for that meeting."
58 )
59 }
60 .confirmationDialog(
61 "Change server and sign out?", isPresented: Binding(
62 get: { confirming != nil }, set: { if !$0 { confirming = nil } }),
63 titleVisibility: .visible
64 ) {
65 Button("Change and sign out", role: .destructive) {
66 if let origin = confirming { model.changeServer(to: origin) }
67 confirming = nil
68 }
69 Button("Cancel", role: .cancel) {
70 confirming = nil
71 draft = model.server
72 }
73 } message: {
74 Text(
75 "You’re signed in to \(model.host). Changing the server signs you out there; you can sign in to \(CallsServer.host(of: confirming ?? "")) next."
76 )
77 }
78 .onChange(of: model.server) { draft = model.server }
79 }
80
81 private func apply() {
82 switch CallsServer.validate(draft) {
83 case .failure(let failure):
84 problem = failure.message
85 case .success(let origin):
86 draft = origin
87 guard origin != model.server else { return }
88 if model.changingServerSignsOut {
89 confirming = origin
90 } else {
91 model.changeServer(to: origin)
92 }
93 }
94 }
95}
96
97/// iPhone's settings: there is no Settings scene, so the home screen's
98/// gear opens this.
99@MainActor public struct ServerSettingsSheet: View {
100 @ObservedObject private var model: CallsModel
101 private let done: () -> Void
102 @AppStorage(CallLayoutStyle.key) private var callLayout = CallLayoutStyle.defaultStyle
103
104 public init(model: CallsModel, done: @escaping () -> Void) {
105 self.model = model
106 self.done = done
107 }
108
109 public var body: some View {
110 NavigationStack {
111 Form {
112 Section {
113 Picker("Call layout", selection: $callLayout) {
114 ForEach(CallLayoutStyle.allCases) { style in
115 Text(style.title).tag(style)
116 }
117 }
118 .accessibilityIdentifier("callLayout")
119 } header: {
120 Text("During a call")
121 } footer: {
122 Text(
123 callLayout == .fullWindow
124 ? "The call fills the screen. Tap it to show the controls."
125 : "The call sits above a row of controls that are always showing."
126 )
127 }
128 ServerSettingsSection(model: model)
129 QualityReportsSection()
130 }
131 .formStyle(.grouped)
132 .navigationTitle("Settings")
133 #if os(iOS)
134 .navigationBarTitleDisplayMode(.inline)
135 #endif
136 .toolbar {
137 ToolbarItem(placement: .confirmationAction) { Button("Done", action: done) }
138 }
139 }
140 .tint(CallsAppearance.accent)
141 }
142}
143
144/// The automatic quality reports (spec §15.26): said, and a switch. Off,
145/// no automatic reports go; "Report a problem" still works, because that
146/// one is sent by the person on purpose.
147@MainActor public struct QualityReportsSection: View {
148 @AppStorage(QualityReporting.enabledKey) private var send = true
149
150 public init() {}
151
152 public var body: some View {
153 Section {
154 Toggle("Send anonymous quality reports", isOn: $send)
155 .accessibilityIdentifier("qualityReports")
156 } header: {
157 Text("Call quality")
158 } footer: {
159 Text(
160 QualityReporting.disclosure
161 + " They hold connection numbers only — no names, no audio or video, nothing you type.")
162 }
163 }
164}
165