Skip to content

Commit 907ec22

Browse files
committed
Auto connect to server while onboarding if no other server is detected
1 parent fa2cbf2 commit 907ec22

File tree

5 files changed

+136
-37
lines changed

5 files changed

+136
-37
lines changed

Sources/App/Onboarding/Steps/Servers/OnboardingServersListView.swift

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ import Shared
33
import SwiftUI
44

55
struct OnboardingServersListView: View {
6+
enum Constants {
7+
static let initialDelayUntilDismissCenterLoader: CGFloat = 3
8+
static let minimumDelayUntilDismissCenterLoader: CGFloat = 1.5
9+
static let delayUntilAutoconnect: CGFloat = 2
10+
}
11+
612
@Environment(\.dismiss) private var dismiss
713
@Environment(\.horizontalSizeClass) private var sizeClass
814

@@ -13,6 +19,8 @@ struct OnboardingServersListView: View {
1319
@State private var showManualInput = false
1420
@State private var screenLoaded = false
1521
@State private var autoConnectWorkItem: DispatchWorkItem?
22+
@State private var autoConnectInstance: DiscoveredHomeAssistant?
23+
@State private var autoConnectBottomSheetState: AppleLikeBottomSheetViewState?
1624

1725
let prefillURL: URL?
1826

@@ -28,10 +36,13 @@ struct OnboardingServersListView: View {
2836
content
2937
.animation(.easeInOut, value: viewModel.discoveredInstances.count)
3038
centerLoader
39+
autoConnectView
3140
}
3241
.navigationBarTitleDisplayMode(.inline)
3342
.safeAreaInset(edge: .bottom, content: {
34-
manualInputButton
43+
if autoConnectInstance == nil {
44+
manualInputButton
45+
}
3546
})
3647
.toolbar(content: {
3748
toolbarItems
@@ -50,14 +61,20 @@ struct OnboardingServersListView: View {
5061
.onChange(of: viewModel.discoveredInstances) { newValue in
5162
if newValue.count == 1 {
5263
scheduleAutoConnect()
64+
scheduleCenterLoaderDimiss(
65+
amountOfTimeToWaitToDismissCenterLoader: Constants
66+
.initialDelayUntilDismissCenterLoader
67+
)
5368
} else if newValue.count > 1 {
5469
cancelAutoConnect()
70+
// We display the loader a bit after instances are discovered
71+
// if there is just 1 server available we connect to it automatically
72+
// otherwise we display the list of servers
73+
scheduleCenterLoaderDimiss(
74+
amountOfTimeToWaitToDismissCenterLoader: Constants
75+
.minimumDelayUntilDismissCenterLoader
76+
)
5577
}
56-
57-
// We display the loader a bit after instances are discovered
58-
// if there is just 1 server available we connect to it automatically
59-
// otherwise we display the list of servers
60-
scheduleCenterLoaderDimiss()
6178
}
6279
.sheet(isPresented: $viewModel.showError) {
6380
errorView
@@ -80,25 +97,65 @@ struct OnboardingServersListView: View {
8097
}
8198
}
8299

100+
@ViewBuilder
101+
private var autoConnectView: some View {
102+
if autoConnectInstance != nil {
103+
AppleLikeBottomSheet(
104+
title: autoConnectInstance?.bonjourName ?? autoConnectInstance?.locationName ?? L10n.unknownLabel,
105+
content: {
106+
autoConnectViewContent(instance: autoConnectInstance)
107+
},
108+
state: $autoConnectBottomSheetState,
109+
customDismiss: {
110+
autoConnectInstance = nil
111+
},
112+
willDismiss: {
113+
autoConnectInstance = nil
114+
}
115+
)
116+
}
117+
}
118+
119+
private func autoConnectViewContent(instance: DiscoveredHomeAssistant?) -> some View {
120+
VStack(spacing: DesignSystem.Spaces.three) {
121+
Spacer()
122+
Image(systemSymbol: .externaldriveConnectedToLineBelow)
123+
.resizable()
124+
.aspectRatio(contentMode: .fit)
125+
.frame(width: 240, height: 100)
126+
.foregroundStyle(.haPrimary)
127+
.padding(.bottom, DesignSystem.Spaces.four)
128+
Text(instance?.internalOrExternalURL.absoluteString ?? "--")
129+
.font(DesignSystem.Font.body.weight(.light))
130+
.foregroundStyle(.secondary)
131+
Button {
132+
autoConnectInstance = nil
133+
guard let viewController = hostingProvider.viewController, let instance else { return }
134+
viewModel.selectInstance(instance, controller: viewController)
135+
} label: {
136+
Text(L10n.Onboarding.Servers.AutoConnect.button)
137+
}
138+
.buttonStyle(.primaryButton)
139+
}
140+
}
141+
83142
private func scheduleAutoConnect() {
84143
autoConnectWorkItem?.cancel()
85144
let workItem = DispatchWorkItem { [weak viewModel] in
86145
if viewModel?.discoveredInstances.count == 1 {
87-
// TODO: Display a bottom sheet asking to connect
146+
autoConnectInstance = viewModel?.discoveredInstances.first
88147
}
89148
}
90149
autoConnectWorkItem = workItem
91-
let amountOfTimeToWaitToAutoConnect: CGFloat = 2
92-
DispatchQueue.main.asyncAfter(deadline: .now() + amountOfTimeToWaitToAutoConnect, execute: workItem)
150+
DispatchQueue.main.asyncAfter(deadline: .now() + Constants.delayUntilAutoconnect, execute: workItem)
93151
}
94152

95153
private func cancelAutoConnect() {
96154
autoConnectWorkItem?.cancel()
97155
autoConnectWorkItem = nil
98156
}
99157

100-
private func scheduleCenterLoaderDimiss() {
101-
let amountOfTimeToWaitToDismissCenterLoader: CGFloat = 2
158+
private func scheduleCenterLoaderDimiss(amountOfTimeToWaitToDismissCenterLoader: CGFloat) {
102159
DispatchQueue.main.asyncAfter(deadline: .now() + amountOfTimeToWaitToDismissCenterLoader) {
103160
viewModel.showCenterLoader = false
104161
}
@@ -257,21 +314,19 @@ struct OnboardingServersListView: View {
257314
}
258315

259316
private var headerView: some View {
260-
Section {
261-
Text(L10n.Onboarding.Servers.title)
262-
.font(DesignSystem.Font.largeTitle.bold())
263-
.multilineTextAlignment(.center)
264-
.frame(maxWidth: .infinity, alignment: .center)
265-
.padding(.bottom)
266-
}
317+
Text(L10n.Onboarding.Servers.title)
318+
.font(DesignSystem.Font.largeTitle.bold())
319+
.multilineTextAlignment(.center)
320+
.frame(maxWidth: .infinity, alignment: .center)
321+
.padding(.vertical, DesignSystem.Spaces.four)
267322
}
268323

269324
private func serverRow(instance: DiscoveredHomeAssistant) -> some View {
270325
Button(action: {
271326
viewModel.selectInstance(instance, controller: hostingProvider.viewController)
272327
}, label: {
273328
OnboardingScanningInstanceRow(
274-
name: instance.locationName,
329+
name: instance.bonjourName ?? instance.locationName,
275330
internalURLString: instance.internalURL?.absoluteString,
276331
externalURLString: instance.externalURL?.absoluteString,
277332
internalOrExternalURLString: instance.internalOrExternalURL.absoluteString,

Sources/App/Onboarding/Steps/Servers/OnboardingServersListViewModel.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,13 @@ final class OnboardingServersListViewModel: ObservableObject {
9696
Current.Log.verbose("Onboarding authentication succeeded")
9797
self?.authenticationSucceeded(server: server)
9898
case let .rejected(error):
99-
self?.error = error
100-
self?.showError = true
99+
if let pmkError = error as? PMKError, pmkError.isCancelled {
100+
/* No action needed, user cancelled flow */
101+
self?.resetFlow()
102+
} else {
103+
self?.error = error
104+
self?.showError = true
105+
}
101106
}
102107
self?.resetSpecificLoaders()
103108
}

Sources/App/Resources/en.lproj/Localizable.strings

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1261,4 +1261,5 @@ Home Assistant is free and open source home automation software with a focus on
12611261
"widgets.sensors.description" = "Display state of sensors";
12621262
"widgets.sensors.not_configured" = "No Sensors Configured";
12631263
"widgets.sensors.title" = "Sensors";
1264-
"yes_label" = "Yes";
1264+
"yes_label" = "Yes";
1265+
"onboarding.servers.auto_connect.button" = "Connect";

Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,36 @@ public enum AppleLikeBottomSheetViewState {
55
case dismiss
66
}
77

8+
private enum AppleLikeBottomSheetConstants {
9+
static let closebuttonSize: CGFloat = 30
10+
}
11+
812
public struct AppleLikeBottomSheet<Content: View>: View {
913
@Environment(\.dismiss) private var dismiss
1014
/// Used for appear and disappear bottom sheet animation
1115
@State private var displayBottomSheet = false
16+
private let title: String?
1217
private let content: Content
1318
@State private var showCloseButton: Bool
1419
@Binding private var state: AppleLikeBottomSheetViewState?
20+
private let customDismiss: (() -> Void)?
1521
private let willDismiss: (() -> Void)?
1622

1723
private let bottomSheetMinHeight: CGFloat = 400
1824

1925
public init(
26+
title: String? = nil,
2027
@ViewBuilder content: () -> Content,
2128
showCloseButton: Bool = true,
2229
state: Binding<AppleLikeBottomSheetViewState?>,
30+
customDismiss: (() -> Void)? = nil,
2331
willDismiss: (() -> Void)? = nil
2432
) {
33+
self.title = title
2534
self.content = content()
2635
self.showCloseButton = showCloseButton
2736
self._state = state
37+
self.customDismiss = customDismiss
2838
self.willDismiss = willDismiss
2939
}
3040

@@ -34,19 +44,19 @@ public struct AppleLikeBottomSheet<Content: View>: View {
3444
ZStack(alignment: .top) {
3545
content
3646
.frame(maxWidth: .infinity)
37-
.padding(.horizontal, Spaces.two)
38-
.padding(.vertical, Spaces.six)
47+
.padding(.horizontal, DesignSystem.Spaces.two)
48+
.padding(.vertical, DesignSystem.Spaces.six)
3949
if showCloseButton {
4050
closeButton
4151
}
4252
}
4353
.padding(.horizontal)
4454
.frame(minHeight: bottomSheetMinHeight)
4555
.frame(maxWidth: maxWidth, alignment: .center)
46-
.background(.regularMaterial)
56+
.background(Color(uiColor: .systemBackground))
4757
.clipShape(RoundedRectangle(cornerRadius: perfectCornerRadius))
4858
.shadow(color: .black.opacity(0.2), radius: 20)
49-
.padding(Spaces.one)
59+
.padding(DesignSystem.Spaces.one)
5060
.fixedSize(horizontal: false, vertical: true)
5161
.offset(y: displayBottomSheet ? 0 : bottomSheetMinHeight)
5262
.onAppear {
@@ -72,22 +82,29 @@ public struct AppleLikeBottomSheet<Content: View>: View {
7282
withAnimation(.bouncy) {
7383
displayBottomSheet = false
7484
} completion: {
75-
willDismiss?()
76-
dismiss()
85+
performDismiss()
7786
}
7887
} else {
7988
withAnimation(.bouncy) {
8089
displayBottomSheet = false
8190
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
82-
willDismiss?()
83-
dismiss()
91+
performDismiss()
8492
}
8593
}
8694
}
8795
}
8896
}
8997
}
9098

99+
private func performDismiss() {
100+
willDismiss?()
101+
if let customDismiss {
102+
customDismiss()
103+
} else {
104+
dismiss()
105+
}
106+
}
107+
91108
private var maxWidth: CGFloat {
92109
if UIDevice.current.userInterfaceIdiom == .phone {
93110
.infinity
@@ -98,7 +115,7 @@ public struct AppleLikeBottomSheet<Content: View>: View {
98115

99116
private var perfectCornerRadius: CGFloat {
100117
if UIDevice.current.userInterfaceIdiom == .phone {
101-
UIScreen.main.displayCornerRadius - Spaces.one
118+
UIScreen.main.displayCornerRadius - DesignSystem.Spaces.one
102119
} else {
103120
50
104121
}
@@ -108,19 +125,36 @@ public struct AppleLikeBottomSheet<Content: View>: View {
108125
private var closeButton: some View {
109126
VStack {
110127
HStack {
128+
// Spacer reserved for title to be center properly
129+
Rectangle()
130+
.foregroundStyle(.clear)
131+
.frame(
132+
width: AppleLikeBottomSheetConstants.closebuttonSize,
133+
height: AppleLikeBottomSheetConstants.closebuttonSize
134+
)
135+
Spacer()
136+
if let title {
137+
Text(title)
138+
.font(DesignSystem.Font.title2.bold())
139+
}
111140
Spacer()
112141
Button(action: {
113142
state = .dismiss
114143
}, label: {
115-
Image(systemName: "xmark.circle.fill")
116-
.font(.system(size: 20))
117-
.foregroundStyle(.gray, Color(uiColor: .tertiarySystemBackground))
144+
Image(systemSymbol: .xmarkCircleFill)
145+
.resizable()
146+
.aspectRatio(contentMode: .fit)
147+
.frame(
148+
width: AppleLikeBottomSheetConstants.closebuttonSize,
149+
height: AppleLikeBottomSheetConstants.closebuttonSize
150+
)
151+
.foregroundStyle(.gray, Color(uiColor: .secondarySystemBackground))
118152
})
119153
}
120154
Spacer()
121155
}
122-
.padding(.top, Spaces.three)
123-
.padding([.trailing, .bottom], Spaces.one)
156+
.padding(.top, DesignSystem.Spaces.three)
157+
.padding([.trailing, .bottom], DesignSystem.Spaces.one)
124158
}
125159
}
126160

@@ -129,7 +163,7 @@ public struct AppleLikeBottomSheet<Content: View>: View {
129163
VStack {}
130164
.background(.blue)
131165
.frame(maxWidth: .infinity, maxHeight: .infinity)
132-
AppleLikeBottomSheet(content: { Text("Hello World") }, state: .constant(.initial)) {}
166+
AppleLikeBottomSheet(content: { Text("Hello World") }, state: .constant(.initial), willDismiss: {})
133167
}
134168
.frame(maxWidth: .infinity, maxHeight: .infinity)
135169
}

Sources/Shared/Resources/Swiftgen/Strings.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1920,6 +1920,10 @@ public enum L10n {
19201920
public enum Servers {
19211921
/// Searching on home network
19221922
public static var title: String { return L10n.tr("Localizable", "onboarding.servers.title") }
1923+
public enum AutoConnect {
1924+
/// Connect
1925+
public static var button: String { return L10n.tr("Localizable", "onboarding.servers.auto_connect.button") }
1926+
}
19231927
public enum Docs {
19241928
/// Read documentation
19251929
public static var read: String { return L10n.tr("Localizable", "onboarding.servers.docs.read") }

0 commit comments

Comments
 (0)