Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import Shared
import SwiftUI

struct OnboardingServersListView: View {
enum Constants {
static let initialDelayUntilDismissCenterLoader: TimeInterval = 3
static let minimumDelayUntilDismissCenterLoader: TimeInterval = 1.5
static let delayUntilAutoconnect: TimeInterval = 2
}

@Environment(\.dismiss) private var dismiss
@Environment(\.horizontalSizeClass) private var sizeClass

Expand All @@ -13,6 +19,8 @@ struct OnboardingServersListView: View {
@State private var showManualInput = false
@State private var screenLoaded = false
@State private var autoConnectWorkItem: DispatchWorkItem?
@State private var autoConnectInstance: DiscoveredHomeAssistant?
@State private var autoConnectBottomSheetState: AppleLikeBottomSheetViewState?

let prefillURL: URL?

Expand All @@ -26,12 +34,14 @@ struct OnboardingServersListView: View {
var body: some View {
ZStack {
content
.animation(.easeInOut, value: viewModel.discoveredInstances.count)
centerLoader
autoConnectView
}
.navigationBarTitleDisplayMode(.inline)
.safeAreaInset(edge: .bottom, content: {
manualInputButton
if autoConnectInstance == nil {
manualInputButton
}
})
.toolbar(content: {
toolbarItems
Expand All @@ -52,12 +62,14 @@ struct OnboardingServersListView: View {
scheduleAutoConnect()
} else if newValue.count > 1 {
cancelAutoConnect()
// We display the loader a bit after instances are discovered
// if there is just 1 server available we connect to it automatically
// otherwise we display the list of servers
scheduleCenterLoaderDimiss(
amountOfTimeToWaitToDismissCenterLoader: Constants
.minimumDelayUntilDismissCenterLoader
)
}

// We display the loader a bit after instances are discovered
// if there is just 1 server available we connect to it automatically
// otherwise we display the list of servers
scheduleCenterLoaderDimiss()
}
.sheet(isPresented: $viewModel.showError) {
errorView
Expand All @@ -80,25 +92,66 @@ struct OnboardingServersListView: View {
}
}

@ViewBuilder
private var autoConnectView: some View {
if autoConnectInstance != nil {
AppleLikeBottomSheet(
title: autoConnectInstance?.bonjourName ?? autoConnectInstance?.locationName ?? L10n.unknownLabel,
content: {
autoConnectViewContent(instance: autoConnectInstance)
},
state: $autoConnectBottomSheetState,
customDismiss: {
autoConnectInstance = nil
},
willDismiss: {
autoConnectInstance = nil
}
)
}
}

private func autoConnectViewContent(instance: DiscoveredHomeAssistant?) -> some View {
VStack(spacing: DesignSystem.Spaces.three) {
Spacer()
Image(systemSymbol: .externaldriveConnectedToLineBelow)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 240, height: 100)
.foregroundStyle(.haPrimary)
.padding(.bottom, DesignSystem.Spaces.four)
Text(instance?.internalOrExternalURL.absoluteString ?? "--")
.font(DesignSystem.Font.body.weight(.light))
.foregroundStyle(.secondary)
Button {
autoConnectInstance = nil
guard let viewController = hostingProvider.viewController, let instance else { return }
viewModel.selectInstance(instance, controller: viewController)
} label: {
Text(L10n.Onboarding.Servers.AutoConnect.button)
}
.buttonStyle(.primaryButton)
}
}

private func scheduleAutoConnect() {
autoConnectWorkItem?.cancel()
let workItem = DispatchWorkItem { [weak viewModel] in
if viewModel?.discoveredInstances.count == 1 {
// TODO: Display a bottom sheet asking to connect
viewModel?.showCenterLoader = false
autoConnectInstance = viewModel?.discoveredInstances.first
}
}
autoConnectWorkItem = workItem
let amountOfTimeToWaitToAutoConnect: CGFloat = 2
DispatchQueue.main.asyncAfter(deadline: .now() + amountOfTimeToWaitToAutoConnect, execute: workItem)
DispatchQueue.main.asyncAfter(deadline: .now() + Constants.delayUntilAutoconnect, execute: workItem)
}

private func cancelAutoConnect() {
autoConnectWorkItem?.cancel()
autoConnectWorkItem = nil
}

private func scheduleCenterLoaderDimiss() {
let amountOfTimeToWaitToDismissCenterLoader: CGFloat = 2
private func scheduleCenterLoaderDimiss(amountOfTimeToWaitToDismissCenterLoader: CGFloat) {
DispatchQueue.main.asyncAfter(deadline: .now() + amountOfTimeToWaitToDismissCenterLoader) {
viewModel.showCenterLoader = false
}
Expand Down Expand Up @@ -257,21 +310,19 @@ struct OnboardingServersListView: View {
}

private var headerView: some View {
Section {
Text(L10n.Onboarding.Servers.title)
.font(DesignSystem.Font.largeTitle.bold())
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity, alignment: .center)
.padding(.bottom)
}
Text(L10n.Onboarding.Servers.title)
.font(DesignSystem.Font.largeTitle.bold())
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity, alignment: .center)
.padding(.vertical, DesignSystem.Spaces.four)
}

private func serverRow(instance: DiscoveredHomeAssistant) -> some View {
Button(action: {
viewModel.selectInstance(instance, controller: hostingProvider.viewController)
}, label: {
OnboardingScanningInstanceRow(
name: instance.locationName,
name: instance.bonjourName ?? instance.locationName,
internalURLString: instance.internalURL?.absoluteString,
externalURLString: instance.externalURL?.absoluteString,
internalOrExternalURLString: instance.internalOrExternalURL.absoluteString,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,13 @@ final class OnboardingServersListViewModel: ObservableObject {
Current.Log.verbose("Onboarding authentication succeeded")
self?.authenticationSucceeded(server: server)
case let .rejected(error):
self?.error = error
self?.showError = true
if let pmkError = error as? PMKError, pmkError.isCancelled {
/* No action needed, user cancelled flow */
self?.resetFlow()
} else {
self?.error = error
self?.showError = true
}
}
self?.resetSpecificLoaders()
}
Expand Down
3 changes: 2 additions & 1 deletion Sources/App/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1261,4 +1261,5 @@ Home Assistant is free and open source home automation software with a focus on
"widgets.sensors.description" = "Display state of sensors";
"widgets.sensors.not_configured" = "No Sensors Configured";
"widgets.sensors.title" = "Sensors";
"yes_label" = "Yes";
"yes_label" = "Yes";
"onboarding.servers.auto_connect.button" = "Connect";
64 changes: 49 additions & 15 deletions Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,36 @@
case dismiss
}

private enum AppleLikeBottomSheetConstants {
static let closebuttonSize: CGFloat = 30
}

public struct AppleLikeBottomSheet<Content: View>: View {
@Environment(\.dismiss) private var dismiss
/// Used for appear and disappear bottom sheet animation
@State private var displayBottomSheet = false
private let title: String?
private let content: Content
@State private var showCloseButton: Bool
@Binding private var state: AppleLikeBottomSheetViewState?
private let customDismiss: (() -> Void)?
private let willDismiss: (() -> Void)?

private let bottomSheetMinHeight: CGFloat = 400

public init(
title: String? = nil,
@ViewBuilder content: () -> Content,
showCloseButton: Bool = true,
state: Binding<AppleLikeBottomSheetViewState?>,
customDismiss: (() -> Void)? = nil,
willDismiss: (() -> Void)? = nil
) {
self.title = title

Check warning on line 33 in Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift#L33

Added line #L33 was not covered by tests
self.content = content()
self.showCloseButton = showCloseButton
self._state = state
self.customDismiss = customDismiss

Check warning on line 37 in Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift#L37

Added line #L37 was not covered by tests
self.willDismiss = willDismiss
}

Expand All @@ -34,19 +44,19 @@
ZStack(alignment: .top) {
content
.frame(maxWidth: .infinity)
.padding(.horizontal, Spaces.two)
.padding(.vertical, Spaces.six)
.padding(.horizontal, DesignSystem.Spaces.two)
.padding(.vertical, DesignSystem.Spaces.six)

Check warning on line 48 in Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift#L47-L48

Added lines #L47 - L48 were not covered by tests
if showCloseButton {
closeButton
}
}
.padding(.horizontal)
.frame(minHeight: bottomSheetMinHeight)
.frame(maxWidth: maxWidth, alignment: .center)
.background(.regularMaterial)
.background(Color(uiColor: .systemBackground))

Check warning on line 56 in Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift#L56

Added line #L56 was not covered by tests
.clipShape(RoundedRectangle(cornerRadius: perfectCornerRadius))
.shadow(color: .black.opacity(0.2), radius: 20)
.padding(Spaces.one)
.padding(DesignSystem.Spaces.one)

Check warning on line 59 in Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift#L59

Added line #L59 was not covered by tests
.fixedSize(horizontal: false, vertical: true)
.offset(y: displayBottomSheet ? 0 : bottomSheetMinHeight)
.onAppear {
Expand All @@ -72,22 +82,29 @@
withAnimation(.bouncy) {
displayBottomSheet = false
} completion: {
willDismiss?()
dismiss()
performDismiss()

Check warning on line 85 in Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift#L85

Added line #L85 was not covered by tests
}
} else {
withAnimation(.bouncy) {
displayBottomSheet = false
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
willDismiss?()
dismiss()
performDismiss()

Check warning on line 91 in Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift#L91

Added line #L91 was not covered by tests
}
}
}
}
}
}

private func performDismiss() {
willDismiss?()
if let customDismiss {
customDismiss()
} else {
dismiss()

Check warning on line 104 in Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift#L99-L104

Added lines #L99 - L104 were not covered by tests
}
}

private var maxWidth: CGFloat {
if UIDevice.current.userInterfaceIdiom == .phone {
.infinity
Expand All @@ -98,7 +115,7 @@

private var perfectCornerRadius: CGFloat {
if UIDevice.current.userInterfaceIdiom == .phone {
UIScreen.main.displayCornerRadius - Spaces.one
UIScreen.main.displayCornerRadius - DesignSystem.Spaces.one

Check warning on line 118 in Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift#L118

Added line #L118 was not covered by tests
} else {
50
}
Expand All @@ -108,19 +125,36 @@
private var closeButton: some View {
VStack {
HStack {
// Spacer reserved for title to be center properly
Rectangle()
.foregroundStyle(.clear)
.frame(
width: AppleLikeBottomSheetConstants.closebuttonSize,
height: AppleLikeBottomSheetConstants.closebuttonSize
)
Spacer()
if let title {
Text(title)
.font(DesignSystem.Font.title2.bold())

Check warning on line 138 in Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift#L128-L138

Added lines #L128 - L138 were not covered by tests
}
Spacer()
Button(action: {
state = .dismiss
}, label: {
Image(systemName: "xmark.circle.fill")
.font(.system(size: 20))
.foregroundStyle(.gray, Color(uiColor: .tertiarySystemBackground))
Image(systemSymbol: .xmarkCircleFill)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(
width: AppleLikeBottomSheetConstants.closebuttonSize,
height: AppleLikeBottomSheetConstants.closebuttonSize
)
.foregroundStyle(.gray, Color(uiColor: .secondarySystemBackground))

Check warning on line 151 in Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift#L144-L151

Added lines #L144 - L151 were not covered by tests
})
}
Spacer()
}
.padding(.top, Spaces.three)
.padding([.trailing, .bottom], Spaces.one)
.padding(.top, DesignSystem.Spaces.three)
.padding([.trailing, .bottom], DesignSystem.Spaces.one)

Check warning on line 157 in Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Shared/DesignSystem/Components/AppleLikeBottomSheet.swift#L156-L157

Added lines #L156 - L157 were not covered by tests
}
}

Expand All @@ -129,7 +163,7 @@
VStack {}
.background(.blue)
.frame(maxWidth: .infinity, maxHeight: .infinity)
AppleLikeBottomSheet(content: { Text("Hello World") }, state: .constant(.initial)) {}
AppleLikeBottomSheet(content: { Text("Hello World") }, state: .constant(.initial), willDismiss: {})
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
4 changes: 4 additions & 0 deletions Sources/Shared/Resources/Swiftgen/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1920,6 +1920,10 @@ public enum L10n {
public enum Servers {
/// Searching on home network
public static var title: String { return L10n.tr("Localizable", "onboarding.servers.title") }
public enum AutoConnect {
/// Connect
public static var button: String { return L10n.tr("Localizable", "onboarding.servers.auto_connect.button") }
}
public enum Docs {
/// Read documentation
public static var read: String { return L10n.tr("Localizable", "onboarding.servers.docs.read") }
Expand Down
Loading