Skip to content

Commit fcfa566

Browse files
authored
Add last update date and beta option to show entity state in widget (#3399)
<!-- 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 --> ## 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. -->
1 parent 3c30ab7 commit fcfa566

File tree

6 files changed

+91
-25
lines changed

6 files changed

+91
-25
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,6 +1120,7 @@ Home Assistant is free and open source home automation software with a focus on
11201120
"widgets.controls.switch.title" = "Switch";
11211121
"widgets.custom.subtitle" = "Create widgets with your own style";
11221122
"widgets.custom.title" = "Custom widgets";
1123+
"widgets.custom.show_update_time.title" = "Last update:";
11231124
"widgets.details.description" = "Display states using from Home Assistant in text";
11241125
"widgets.details.description_with_warning" = "Display states using from Home Assistant in text. ATTENTION: User needs to be admin for templating access";
11251126
"widgets.details.parameters.action" = "Action";

Sources/Extensions/Widgets/Common/WidgetBasicContainerView.swift

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,37 @@ struct WidgetBasicContainerView: View {
99
let emptyViewGenerator: () -> AnyView
1010
let contents: [WidgetBasicViewModel]
1111
let type: WidgetType
12+
let showLastUpdate: Bool
1213

13-
init(emptyViewGenerator: @escaping () -> AnyView, contents: [WidgetBasicViewModel], type: WidgetType) {
14+
init(
15+
emptyViewGenerator: @escaping () -> AnyView,
16+
contents: [WidgetBasicViewModel],
17+
type: WidgetType,
18+
showLastUpdate: Bool = false
19+
) {
1420
self.emptyViewGenerator = emptyViewGenerator
1521
self.contents = contents
1622
self.type = type
23+
self.showLastUpdate = showLastUpdate
1724
}
1825

1926
var body: some View {
20-
Group {
27+
VStack {
2128
if contents.isEmpty {
2229
emptyViewGenerator()
2330
} else {
2431
content(for: contents)
2532
}
33+
if showLastUpdate {
34+
Group {
35+
Text("\(L10n.Widgets.Custom.ShowUpdateTime.title) ") + Text(Date.now, style: .time)
36+
}
37+
.font(.system(size: 10).bold())
38+
.frame(maxWidth: .infinity, alignment: .center)
39+
.multilineTextAlignment(.center)
40+
.padding(.bottom, Spaces.half)
41+
.opacity(0.5)
42+
}
2643
}
2744
// Whenever Apple allow apps to use material backgrounds we should update this
2845
.widgetBackground(Color.asset(Asset.Colors.primaryBackground))

Sources/Extensions/Widgets/Common/WidgetBasicView.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ struct WidgetBasicView: View {
1313
let rows: [[WidgetBasicViewModel]]
1414
let sizeStyle: WidgetBasicSizeStyle
1515

16-
private let opacityWhenDisabled: CGFloat = 0.4
17-
private let blurWhenDisabled: CGFloat = 2
16+
private let opacityWhenDisabled: CGFloat = 0.3
1817

1918
var body: some View {
2019
let spacing = sizeStyle == .compressed ? .zero : Spaces.one
@@ -309,7 +308,6 @@ struct WidgetBasicView: View {
309308
))
310309
}
311310
}
312-
.blur(radius: model.disabled ? blurWhenDisabled : 0)
313311
.opacity(model.disabled ? opacityWhenDisabled : 1)
314312
}
315313

@@ -330,7 +328,6 @@ struct WidgetBasicView: View {
330328
))
331329
}
332330
}
333-
.blur(radius: model.disabled ? blurWhenDisabled : 0)
334331
.opacity(model.disabled ? opacityWhenDisabled : 1)
335332
}
336333

Sources/Extensions/Widgets/Custom/WidgetCustom.swift

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ struct WidgetCustom: Widget {
1717
AnyView(emptyView)
1818
}, contents: modelsForWidget(
1919
widget,
20-
blurItems: timelineEntry.blurItems,
20+
blurItems: timelineEntry.disabledItems,
2121
infoProvider: timelineEntry.magicItemInfoProvider,
22-
states: timelineEntry.entitiesState
23-
), type: .custom)
22+
states: timelineEntry.entitiesState,
23+
showStates: timelineEntry.showStates
24+
), type: .custom, showLastUpdate: timelineEntry.showLastUpdateTime)
2425
} else {
2526
emptyView
2627
.widgetBackground(Color.clear)
@@ -50,7 +51,8 @@ struct WidgetCustom: Widget {
5051
_ widget: CustomWidget?,
5152
blurItems: Bool,
5253
infoProvider: MagicItemProviderProtocol,
53-
states: [MagicItem: WidgetCustomEntry.ItemState]
54+
states: [MagicItem: WidgetCustomEntry.ItemState],
55+
showStates: Bool
5456
) -> [WidgetBasicViewModel] {
5557
guard let widget else { return [] }
5658

@@ -86,7 +88,7 @@ struct WidgetCustom: Widget {
8688
}
8789
}()
8890

89-
if [.light, .switch, .inputBoolean].contains(magicItem.domain) {
91+
if showStates, [.light, .switch, .inputBoolean].contains(magicItem.domain) {
9092
if state?.domainState == Domain.State.off {
9193
return Color.gray
9294
} else {
@@ -234,10 +236,18 @@ enum WidgetCustomSupportedFamilies {
234236
#Preview(as: .systemSmall) {
235237
WidgetCustom()
236238
} timeline: {
237-
WidgetCustomEntry(date: .now, widget: .init(id: "123", name: "My widget", items: [
238-
.init(id: "1", serverId: "1", type: .entity),
239-
.init(id: "2", serverId: "2", type: .entity),
240-
]), magicItemInfoProvider: MockMagicItemProvider(), entitiesState: [:], blurItems: false)
239+
WidgetCustomEntry(
240+
date: .now,
241+
widget: .init(id: "123", name: "My widget", items: [
242+
.init(id: "1", serverId: "1", type: .entity),
243+
.init(id: "2", serverId: "2", type: .entity),
244+
]),
245+
magicItemInfoProvider: MockMagicItemProvider(),
246+
entitiesState: [:],
247+
disabledItems: false,
248+
showLastUpdateTime: true,
249+
showStates: true
250+
)
241251
}
242252

243253
@available(iOS 17, *)
@@ -249,7 +259,9 @@ enum WidgetCustomSupportedFamilies {
249259
widget: nil,
250260
magicItemInfoProvider: MockMagicItemProvider(),
251261
entitiesState: [:],
252-
blurItems: false
262+
disabledItems: false,
263+
showLastUpdateTime: true,
264+
showStates: true
253265
)
254266
}
255267

@@ -262,7 +274,9 @@ enum WidgetCustomSupportedFamilies {
262274
widget: nil,
263275
magicItemInfoProvider: MockMagicItemProvider(),
264276
entitiesState: [:],
265-
blurItems: false
277+
disabledItems: false,
278+
showLastUpdateTime: true,
279+
showStates: true
266280
)
267281
}
268282

Sources/Extensions/Widgets/Custom/WidgetCustomTimelineProvider.swift

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ struct WidgetCustomEntry: TimelineEntry {
99
var magicItemInfoProvider: MagicItemProviderProtocol
1010
var entitiesState: [MagicItem: ItemState]
1111
// True when one of items is pending confirmation
12-
var blurItems: Bool
12+
var disabledItems: Bool
13+
var showLastUpdateTime: Bool
14+
var showStates: Bool
1315

1416
struct ItemState {
1517
let value: String
@@ -23,7 +25,14 @@ struct WidgetCustomTimelineProvider: AppIntentTimelineProvider {
2325
typealias Intent = WidgetCustomAppIntent
2426

2527
func placeholder(in context: Context) -> WidgetCustomEntry {
26-
.init(date: .now, magicItemInfoProvider: Current.magicItemProvider(), entitiesState: [:], blurItems: false)
28+
.init(
29+
date: .now,
30+
magicItemInfoProvider: Current.magicItemProvider(),
31+
entitiesState: [:],
32+
disabledItems: false,
33+
showLastUpdateTime: false,
34+
showStates: false
35+
)
2736
}
2837

2938
func snapshot(for configuration: WidgetCustomAppIntent, in context: Context) async -> WidgetCustomEntry {
@@ -33,13 +42,15 @@ struct WidgetCustomTimelineProvider: AppIntentTimelineProvider {
3342
widget: widget,
3443
magicItemInfoProvider: infoProvider(),
3544
entitiesState: [:],
36-
blurItems: false
45+
disabledItems: false,
46+
showLastUpdateTime: configuration.showLastUpdateTime,
47+
showStates: configuration.showStates
3748
)
3849
}
3950

4051
func timeline(for configuration: WidgetCustomAppIntent, in context: Context) async -> Timeline<WidgetCustomEntry> {
4152
let widget = widget(configuration: configuration, context: context)
42-
let entitiesState = await entitiesState(widget: widget)
53+
let entitiesState = await entitiesState(configuration: configuration, widget: widget)
4354

4455
return await .init(
4556
entries: [
@@ -48,7 +59,9 @@ struct WidgetCustomTimelineProvider: AppIntentTimelineProvider {
4859
widget: widget,
4960
magicItemInfoProvider: infoProvider(),
5061
entitiesState: entitiesState,
51-
blurItems: !(widget?.itemsStates.isEmpty ?? false)
62+
disabledItems: !(widget?.itemsStates.isEmpty ?? false),
63+
showLastUpdateTime: configuration.showLastUpdateTime,
64+
showStates: configuration.showStates
5265
),
5366
], policy: .after(
5467
Current.date()
@@ -94,9 +107,17 @@ struct WidgetCustomTimelineProvider: AppIntentTimelineProvider {
94107
return infoProvider
95108
}
96109

97-
private func entitiesState(widget: CustomWidget?) async -> [MagicItem: WidgetCustomEntry.ItemState] {
110+
private func entitiesState(
111+
configuration: WidgetCustomAppIntent,
112+
widget: CustomWidget?
113+
) async -> [MagicItem: WidgetCustomEntry.ItemState] {
98114
guard let widget else { return [:] }
99115

116+
guard configuration.showStates else {
117+
Current.Log.verbose("States are disabled in widget configuration")
118+
return [:]
119+
}
120+
100121
guard widget.itemsStates.isEmpty else {
101122
Current.Log
102123
.verbose(
@@ -154,12 +175,24 @@ struct WidgetCustomAppIntent: AppIntent, WidgetConfigurationIntent {
154175
)
155176
var widget: CustomWidgetEntity?
156177

178+
@Parameter(
179+
title: "Show last update time",
180+
default: true
181+
)
182+
var showLastUpdateTime: Bool
183+
184+
@Parameter(
185+
title: "Show states (BETA)",
186+
default: false
187+
)
188+
var showStates: Bool
189+
157190
static var parameterSummary: some ParameterSummary {
158191
Summary()
159192
}
160193

161-
func perform() async throws -> some IntentResult & ReturnsValue<Bool> {
162-
.result(value: true)
194+
func perform() async throws -> some IntentResult {
195+
.result()
163196
}
164197
}
165198

Sources/Shared/Resources/Swiftgen/Strings.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3927,6 +3927,10 @@ public enum L10n {
39273927
public static var subtitle: String { return L10n.tr("Localizable", "widgets.custom.subtitle") }
39283928
/// Custom widgets
39293929
public static var title: String { return L10n.tr("Localizable", "widgets.custom.title") }
3930+
public enum ShowUpdateTime {
3931+
/// Last update:
3932+
public static var title: String { return L10n.tr("Localizable", "widgets.custom.show_update_time.title") }
3933+
}
39303934
}
39313935
public enum Details {
39323936
/// Display states using from Home Assistant in text

0 commit comments

Comments
 (0)