mirror of
https://github.com/home-assistant/iOS.git
synced 2026-02-04 11:42:39 -06:00
<!-- Thank you for submitting a Pull Request and helping to improve Home Assistant. Please complete the following sections to help the processing and review of your changes. Please do not delete anything from this template. --> ## Summary <!-- Provide a brief summary of the changes you have made and most importantly what they aim to achieve --> This PR allows adding supported domain entities to CarPlay, Widgets and Apple watch directly from the entity more info dialog ## Screenshots <!-- If this is a user-facing change not in the frontend, please include screenshots in light and dark mode. --> ## Link to pull request in Documentation repository <!-- Pull requests that add, change or remove functionality must have a corresponding pull request in the Companion App Documentation repository (https://github.com/home-assistant/companion.home-assistant). Please add the number of this pull request after the "#" --> Documentation: home-assistant/companion.home-assistant# ## Any other notes <!-- If there is any other information of note, like if this Pull Request is part of a bigger change, please include it here. --> --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
162 lines
5.0 KiB
Swift
162 lines
5.0 KiB
Swift
import SFSafeSymbols
|
|
import Shared
|
|
import SwiftUI
|
|
|
|
/// A bottom sheet view that allows users to select an existing widget to add an entity to,
|
|
/// or create a new widget.
|
|
struct WidgetSelectionView: View {
|
|
@Environment(\.dismiss) private var dismiss
|
|
@StateObject private var viewModel: WidgetSelectionViewModel
|
|
|
|
/// Called when a widget is selected or a new one should be created
|
|
/// - Parameter widget: The selected widget, or nil if creating a new one
|
|
private let onSelection: (CustomWidget?) -> Void
|
|
|
|
init(
|
|
entityId: String,
|
|
serverId: String,
|
|
onSelection: @escaping (CustomWidget?) -> Void
|
|
) {
|
|
self._viewModel = .init(wrappedValue: WidgetSelectionViewModel(
|
|
entityId: entityId,
|
|
serverId: serverId
|
|
))
|
|
self.onSelection = onSelection
|
|
}
|
|
|
|
var body: some View {
|
|
NavigationView {
|
|
List {
|
|
if viewModel.widgets.isEmpty {
|
|
emptyStateView
|
|
} else {
|
|
widgetsSection
|
|
createNewSection
|
|
}
|
|
}
|
|
.navigationTitle(L10n.Settings.Widgets.Select.title)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .cancellationAction) {
|
|
if #unavailable(iOS 16.0) {
|
|
Button(L10n.cancelLabel) {
|
|
dismiss()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.onAppear {
|
|
viewModel.loadWidgets()
|
|
}
|
|
}
|
|
}
|
|
|
|
private var emptyStateView: some View {
|
|
Section {
|
|
VStack(spacing: DesignSystem.Spaces.two) {
|
|
Image(systemSymbol: {
|
|
if #available(iOS 17.0, *) {
|
|
return .squareBadgePlusFill
|
|
} else {
|
|
return .squareshapeDashedSquareshape
|
|
}
|
|
}())
|
|
.font(.system(size: 50))
|
|
.foregroundStyle(Color.haPrimary)
|
|
|
|
Text(L10n.Settings.Widgets.Select.Empty.title)
|
|
.font(.headline)
|
|
|
|
Text(L10n.Settings.Widgets.Select.Empty.subtitle)
|
|
.font(.subheadline)
|
|
.foregroundStyle(.secondary)
|
|
.multilineTextAlignment(.center)
|
|
|
|
Button {
|
|
dismiss()
|
|
onSelection(nil)
|
|
} label: {
|
|
Label(L10n.Settings.Widgets.Create.title, systemSymbol: .plus)
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
.padding(.top, DesignSystem.Spaces.one)
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.padding(.vertical, DesignSystem.Spaces.four)
|
|
}
|
|
.listRowBackground(Color.clear)
|
|
}
|
|
|
|
private var widgetsSection: some View {
|
|
Section {
|
|
ForEach(viewModel.widgets, id: \.id) { widget in
|
|
Button {
|
|
dismiss()
|
|
onSelection(widget)
|
|
} label: {
|
|
HStack {
|
|
VStack(alignment: .leading, spacing: DesignSystem.Spaces.half) {
|
|
Text(widget.name)
|
|
.font(.body)
|
|
.foregroundStyle(Color.primary)
|
|
|
|
Text(L10n.Settings.Widgets.Select.ItemCount.title(widget.items.count))
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
Image(systemSymbol: .chevronRight)
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
}
|
|
} header: {
|
|
Text(L10n.Settings.Widgets.YourWidgets.title)
|
|
} footer: {
|
|
Text(L10n.Settings.Widgets.Select.Footer.title)
|
|
}
|
|
}
|
|
|
|
private var createNewSection: some View {
|
|
Section {
|
|
Button {
|
|
dismiss()
|
|
onSelection(nil)
|
|
} label: {
|
|
Label(L10n.Settings.Widgets.Create.title, systemSymbol: .plus)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - ViewModel
|
|
|
|
final class WidgetSelectionViewModel: ObservableObject {
|
|
@Published var widgets: [CustomWidget] = []
|
|
|
|
let entityId: String
|
|
let serverId: String
|
|
|
|
init(entityId: String, serverId: String) {
|
|
self.entityId = entityId
|
|
self.serverId = serverId
|
|
}
|
|
|
|
func loadWidgets() {
|
|
do {
|
|
widgets = try CustomWidget.widgets()?.sorted(by: { $0.name < $1.name }) ?? []
|
|
} catch {
|
|
Current.Log.error("Failed to load widgets: \(error)")
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
WidgetSelectionView(entityId: "light.living_room", serverId: "server-1") { widget in
|
|
print("Selected: \(widget?.name ?? "Create new")")
|
|
}
|
|
}
|