macOS widgets

This commit is contained in:
Hillel Coren 2023-06-29 13:35:02 +03:00
parent d8aefa096e
commit 8f6e27cb77
4 changed files with 121 additions and 119 deletions

View File

@ -16,7 +16,7 @@ class WidgetData {
this.companies, this.companies,
this.companyId, this.companyId,
this.dateRanges, this.dateRanges,
this.fields, this.dashboardFields,
}); });
WidgetData.fromState(AppState state, AppLocalization localization) WidgetData.fromState(AppState state, AppLocalization localization)
@ -30,7 +30,7 @@ class WidgetData {
staticState: state.staticState, staticState: state.staticState,
) )
}, },
fields = Map.fromIterable(<String>[ dashboardFields = Map.fromIterable(<String>[
DashboardUISettings.FIELD_ACTIVE_INVOICES, DashboardUISettings.FIELD_ACTIVE_INVOICES,
DashboardUISettings.FIELD_OUTSTANDING_INVOICES, DashboardUISettings.FIELD_OUTSTANDING_INVOICES,
DashboardUISettings.FIELD_COMPLETED_PAYMENTS, DashboardUISettings.FIELD_COMPLETED_PAYMENTS,
@ -47,21 +47,21 @@ class WidgetData {
companyId = json['company_id'], companyId = json['company_id'],
companies = json['companies'], companies = json['companies'],
dateRanges = json['date_ranges'], dateRanges = json['date_ranges'],
fields = json['fields']; dashboardFields = json['dashboard_fields'];
Map<String, dynamic> toJson() => <String, dynamic>{ Map<String, dynamic> toJson() => <String, dynamic>{
'companies': companies, 'companies': companies,
'company_id': companyId, 'company_id': companyId,
'url': url, 'url': url,
'date_ranges': dateRanges, 'date_ranges': dateRanges,
'fields': fields, 'dashboard_Fields': dashboardFields,
}; };
final String url; final String url;
final String companyId; final String companyId;
final Map<String, WidgetCompany> companies; final Map<String, WidgetCompany> companies;
final Map<String, String> dateRanges; final Map<String, String> dateRanges;
final Map<String, String> fields; final Map<String, String> dashboardFields;
} }
class WidgetCompany { class WidgetCompany {

View File

@ -3,7 +3,11 @@ import Intents
class IntentHandler: INExtension, ConfigurationIntentHandling { class IntentHandler: INExtension, ConfigurationIntentHandling {
private func loadWidgetData() -> WidgetData { private func loadWidgetData() -> WidgetData {
let sharedDefaults = UserDefaults(suiteName: "group.com.invoiceninja.app") let sharedDefaults = UserDefaults(suiteName: "group.com.invoiceninja.app")
var widgetData: WidgetData = WidgetData(url: "", companyId: "", companies: [:], dateRanges: [:], fields: [:]) var widgetData: WidgetData = WidgetData(url: "",
companyId: "",
companies: [:],
dateRanges: [:],
dashboardFields: [:])
if let sharedDefaults = sharedDefaults { if let sharedDefaults = sharedDefaults {
do { do {
@ -71,6 +75,23 @@ class IntentHandler: INExtension, ConfigurationIntentHandling {
return DateRange(identifier: defaultDateRange, display: dateRamge) return DateRange(identifier: defaultDateRange, display: dateRamge)
} }
func provideDashboardFieldOptionsCollection(for intent: ConfigurationIntent) async throws -> INObjectCollection<DashboardField> {
let widgetData = loadWidgetData()
let fields = widgetData.dashboardFields.keys.sorted().map { field in
DashboardField(identifier: field, display: widgetData.dashboardFields[field]!)
}
return INObjectCollection(items: fields)
}
func defaultDashboardField(for intent: ConfigurationIntent) -> DashboardField? {
let widgetData = loadWidgetData()
let defaultField = "total_active_invoices";
let field = widgetData.dashboardFields[defaultField]!;
return DashboardField(identifier: defaultField, display: field)
}
override func handler(for intent: INIntent) -> Any { override func handler(for intent: INIntent) -> Any {
return self return self

View File

@ -3,67 +3,13 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>INEnums</key> <key>INEnums</key>
<array> <array/>
<dict>
<key>INEnumDisplayName</key>
<string>Field</string>
<key>INEnumDisplayNameID</key>
<string>a7k58J</string>
<key>INEnumGeneratesHeader</key>
<true/>
<key>INEnumName</key>
<string>Field</string>
<key>INEnumType</key>
<string>Regular</string>
<key>INEnumValues</key>
<array>
<dict>
<key>INEnumValueDisplayName</key>
<string>unknown</string>
<key>INEnumValueDisplayNameID</key>
<string>lwYuqc</string>
<key>INEnumValueName</key>
<string>unknown</string>
</dict>
<dict>
<key>INEnumValueDisplayName</key>
<string>Active Invoices</string>
<key>INEnumValueDisplayNameID</key>
<string>z5NmCf</string>
<key>INEnumValueIndex</key>
<integer>1</integer>
<key>INEnumValueName</key>
<string>active_invoices</string>
</dict>
<dict>
<key>INEnumValueDisplayName</key>
<string>Outstanding Invoices</string>
<key>INEnumValueDisplayNameID</key>
<string>KMErB0</string>
<key>INEnumValueIndex</key>
<integer>2</integer>
<key>INEnumValueName</key>
<string>outstanding_invoices</string>
</dict>
<dict>
<key>INEnumValueDisplayName</key>
<string>Completed Payments</string>
<key>INEnumValueDisplayNameID</key>
<string>HEngYv</string>
<key>INEnumValueIndex</key>
<integer>3</integer>
<key>INEnumValueName</key>
<string>completed_payments</string>
</dict>
</array>
</dict>
</array>
<key>INIntentDefinitionModelVersion</key> <key>INIntentDefinitionModelVersion</key>
<string>1.2</string> <string>1.2</string>
<key>INIntentDefinitionNamespace</key> <key>INIntentDefinitionNamespace</key>
<string>88xZPY</string> <string>88xZPY</string>
<key>INIntentDefinitionSystemVersion</key> <key>INIntentDefinitionSystemVersion</key>
<string>21G115</string> <string>21G651</string>
<key>INIntentDefinitionToolsBuildVersion</key> <key>INIntentDefinitionToolsBuildVersion</key>
<string>14C18</string> <string>14C18</string>
<key>INIntentDefinitionToolsVersion</key> <key>INIntentDefinitionToolsVersion</key>
@ -80,7 +26,7 @@
<key>INIntentIneligibleForSuggestions</key> <key>INIntentIneligibleForSuggestions</key>
<true/> <true/>
<key>INIntentLastParameterTag</key> <key>INIntentLastParameterTag</key>
<integer>11</integer> <integer>12</integer>
<key>INIntentName</key> <key>INIntentName</key>
<string>Configuration</string> <string>Configuration</string>
<key>INIntentParameters</key> <key>INIntentParameters</key>
@ -168,17 +114,12 @@
<string>t2MezO</string> <string>t2MezO</string>
<key>INIntentParameterDisplayPriority</key> <key>INIntentParameterDisplayPriority</key>
<integer>3</integer> <integer>3</integer>
<key>INIntentParameterEnumType</key>
<string>Field</string>
<key>INIntentParameterEnumTypeNamespace</key>
<string>88xZPY</string>
<key>INIntentParameterMetadata</key>
<dict>
<key>INIntentParameterMetadataDefaultValue</key>
<string>active_invoices</string>
</dict>
<key>INIntentParameterName</key> <key>INIntentParameterName</key>
<string>field</string> <string>dashboardField</string>
<key>INIntentParameterObjectType</key>
<string>DashboardField</string>
<key>INIntentParameterObjectTypeNamespace</key>
<string>88xZPY</string>
<key>INIntentParameterPromptDialogs</key> <key>INIntentParameterPromptDialogs</key>
<array> <array>
<dict> <dict>
@ -197,7 +138,7 @@
<key>INIntentParameterPromptDialogCustom</key> <key>INIntentParameterPromptDialogCustom</key>
<true/> <true/>
<key>INIntentParameterPromptDialogFormatString</key> <key>INIntentParameterPromptDialogFormatString</key>
<string>There are ${count} options matching ${field}.</string> <string>There are ${count} options matching ${dashboardField}.</string>
<key>INIntentParameterPromptDialogFormatStringID</key> <key>INIntentParameterPromptDialogFormatStringID</key>
<string>3UOOUA</string> <string>3UOOUA</string>
<key>INIntentParameterPromptDialogType</key> <key>INIntentParameterPromptDialogType</key>
@ -207,17 +148,19 @@
<key>INIntentParameterPromptDialogCustom</key> <key>INIntentParameterPromptDialogCustom</key>
<true/> <true/>
<key>INIntentParameterPromptDialogFormatString</key> <key>INIntentParameterPromptDialogFormatString</key>
<string>Just to confirm, you wanted ${field}?</string> <string>Just to confirm, you wanted ${dashboardField}?</string>
<key>INIntentParameterPromptDialogFormatStringID</key> <key>INIntentParameterPromptDialogFormatStringID</key>
<string>4sG08t</string> <string>4sG08t</string>
<key>INIntentParameterPromptDialogType</key> <key>INIntentParameterPromptDialogType</key>
<string>Confirmation</string> <string>Confirmation</string>
</dict> </dict>
</array> </array>
<key>INIntentParameterSupportsDynamicEnumeration</key>
<true/>
<key>INIntentParameterTag</key> <key>INIntentParameterTag</key>
<integer>2</integer> <integer>12</integer>
<key>INIntentParameterType</key> <key>INIntentParameterType</key>
<string>Integer</string> <string>Object</string>
</dict> </dict>
<dict> <dict>
<key>INIntentParameterConfigurable</key> <key>INIntentParameterConfigurable</key>
@ -248,26 +191,6 @@
<key>INIntentParameterPromptDialogType</key> <key>INIntentParameterPromptDialogType</key>
<string>Primary</string> <string>Primary</string>
</dict> </dict>
<dict>
<key>INIntentParameterPromptDialogCustom</key>
<true/>
<key>INIntentParameterPromptDialogFormatString</key>
<string>There are ${count} options matching ${dateRange}.</string>
<key>INIntentParameterPromptDialogFormatStringID</key>
<string>M1c9EE</string>
<key>INIntentParameterPromptDialogType</key>
<string>DisambiguationIntroduction</string>
</dict>
<dict>
<key>INIntentParameterPromptDialogCustom</key>
<true/>
<key>INIntentParameterPromptDialogFormatString</key>
<string>Just to confirm, you wanted ${dateRange}?</string>
<key>INIntentParameterPromptDialogFormatStringID</key>
<string>YL2DLe</string>
<key>INIntentParameterPromptDialogType</key>
<string>Confirmation</string>
</dict>
</array> </array>
<key>INIntentParameterSupportsDynamicEnumeration</key> <key>INIntentParameterSupportsDynamicEnumeration</key>
<true/> <true/>
@ -496,6 +419,69 @@
</dict> </dict>
</array> </array>
</dict> </dict>
<dict>
<key>INTypeDisplayName</key>
<string>Field</string>
<key>INTypeDisplayNameID</key>
<string>VFh9Fr</string>
<key>INTypeLastPropertyTag</key>
<integer>99</integer>
<key>INTypeName</key>
<string>DashboardField</string>
<key>INTypeProperties</key>
<array>
<dict>
<key>INTypePropertyDefault</key>
<true/>
<key>INTypePropertyDisplayPriority</key>
<integer>1</integer>
<key>INTypePropertyName</key>
<string>identifier</string>
<key>INTypePropertyTag</key>
<integer>1</integer>
<key>INTypePropertyType</key>
<string>String</string>
</dict>
<dict>
<key>INTypePropertyDefault</key>
<true/>
<key>INTypePropertyDisplayPriority</key>
<integer>2</integer>
<key>INTypePropertyName</key>
<string>displayString</string>
<key>INTypePropertyTag</key>
<integer>2</integer>
<key>INTypePropertyType</key>
<string>String</string>
</dict>
<dict>
<key>INTypePropertyDefault</key>
<true/>
<key>INTypePropertyDisplayPriority</key>
<integer>3</integer>
<key>INTypePropertyName</key>
<string>pronunciationHint</string>
<key>INTypePropertyTag</key>
<integer>3</integer>
<key>INTypePropertyType</key>
<string>String</string>
</dict>
<dict>
<key>INTypePropertyDefault</key>
<true/>
<key>INTypePropertyDisplayPriority</key>
<integer>4</integer>
<key>INTypePropertyName</key>
<string>alternativeSpeakableMatches</string>
<key>INTypePropertySupportsMultipleValues</key>
<true/>
<key>INTypePropertyTag</key>
<integer>4</integer>
<key>INTypePropertyType</key>
<string>SpeakableString</string>
</dict>
</array>
</dict>
</array> </array>
</dict> </dict>
</plist> </plist>

View File

@ -65,7 +65,7 @@ struct Provider: IntentTimelineProvider {
do { do {
return try getWidgetData() return try getWidgetData()
} catch { } catch {
return WidgetData(url: "url", companyId: "", companies: [:], dateRanges: [:], fields: [:]) return WidgetData(url: "url", companyId: "", companies: [:], dateRanges: [:], dashboardFields: [:])
} }
}() }()
@ -74,7 +74,6 @@ struct Provider: IntentTimelineProvider {
SimpleEntry(date: Date(), SimpleEntry(date: Date(),
configuration: ConfigurationIntent(), configuration: ConfigurationIntent(),
widgetData: widgetData, widgetData: widgetData,
field: "Active Invoices",
value: "$100.00", value: "$100.00",
error: "") error: "")
} }
@ -86,7 +85,6 @@ struct Provider: IntentTimelineProvider {
let entry = SimpleEntry(date: Date(), let entry = SimpleEntry(date: Date(),
configuration: configuration, configuration: configuration,
widgetData: widgetData, widgetData: widgetData,
field: "Active Invoices",
value: "$100.00", value: "$100.00",
error: "") error: "")
@ -109,7 +107,7 @@ struct Provider: IntentTimelineProvider {
do { do {
widgetData = try getWidgetData() widgetData = try getWidgetData()
(label, value) = try await getTimelineData(for: configuration, widgetData: widgetData!) value = try await getTimelineData(for: configuration, widgetData: widgetData!)
print("## VALUE: \(value)") print("## VALUE: \(value)")
@ -123,7 +121,6 @@ struct Provider: IntentTimelineProvider {
let entry = SimpleEntry(date: Date(), let entry = SimpleEntry(date: Date(),
configuration: configuration, configuration: configuration,
widgetData: widgetData, widgetData: widgetData,
field: label,
value: value, value: value,
error: message) error: message)
@ -141,11 +138,10 @@ struct Provider: IntentTimelineProvider {
} }
} }
func getTimelineData(for configuration: ConfigurationIntent, widgetData:WidgetData) async throws -> (String, String) { func getTimelineData(for configuration: ConfigurationIntent, widgetData:WidgetData) async throws -> (String) {
var rawValue = 0.0 var rawValue = 0.0
var value = "Error" var value = "Error"
var label = ""
let companyId = configuration.company?.identifier ?? "" let companyId = configuration.company?.identifier ?? ""
let company = widgetData.companies[companyId] let company = widgetData.companies[companyId]
@ -188,22 +184,19 @@ struct Provider: IntentTimelineProvider {
throw "Data not found" throw "Data not found"
} }
switch configuration.field { switch configuration.dashboardField?.identifier {
case .active_invoices: case "total_active_invoices":
if let invoicedAmount = data?.invoices?.invoicedAmount, let value = Double(invoicedAmount) { if let invoicedAmount = data?.invoices?.invoicedAmount, let value = Double(invoicedAmount) {
rawValue = value rawValue = value
} }
label = "Active Invoices" case "total_outstanding_invoices":
case .outstanding_invoices:
if let amount = data?.outstanding?.amount, let value = Double(amount) { if let amount = data?.outstanding?.amount, let value = Double(amount) {
rawValue = value rawValue = value
} }
label = "Outstanding Invoices" case "total_completed_payments":
case .completed_payments:
if let paidToDate = data?.revenue?.paidToDate, let value = Double(paidToDate) { if let paidToDate = data?.revenue?.paidToDate, let value = Double(paidToDate) {
rawValue = value rawValue = value
} }
label = "Completed Payments"
default: default:
break break
} }
@ -214,7 +207,7 @@ struct Provider: IntentTimelineProvider {
formatter.currencyCode = currency?.code ?? "USD" formatter.currencyCode = currency?.code ?? "USD"
value = formatter.string(from: NSNumber(value: rawValue))! value = formatter.string(from: NSNumber(value: rawValue))!
return (label, value) return value
} }
@ -278,14 +271,14 @@ struct WidgetData: Decodable, Hashable {
let companyId: String let companyId: String
let companies: [String: WidgetCompany] let companies: [String: WidgetCompany]
let dateRanges: [String: String] let dateRanges: [String: String]
let fields: [String: String] let dashboardFields: [String: String]
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case url case url
case companyId = "company_id" case companyId = "company_id"
case companies case companies
case dateRanges = "date_ranges" case dateRanges = "date_ranges"
case fields case dashboardFields = "dashboard_fields"
} }
} }
@ -327,7 +320,6 @@ struct SimpleEntry: TimelineEntry {
let date: Date let date: Date
let configuration: ConfigurationIntent let configuration: ConfigurationIntent
let widgetData: WidgetData? let widgetData: WidgetData?
let field: String
let value: String let value: String
let error: String let error: String
} }
@ -358,7 +350,7 @@ struct DashboardWidgetEntryView : View {
HStack { HStack {
VStack { VStack {
Text(entry.field) Text(entry.configuration.dashboardField?.displayString ?? "")
.font(.body) .font(.body)
.bold() .bold()
.lineLimit(2) .lineLimit(2)
@ -413,7 +405,11 @@ struct DashboardWidget_Previews: PreviewProvider {
do { do {
return try getWidgetData() return try getWidgetData()
} catch { } catch {
return WidgetData(url: "url", companyId: "", companies: [:], dateRanges: [:], fields: [:]) return WidgetData(url: "url",
companyId: "",
companies: [:],
dateRanges: [:],
dashboardFields: [:])
} }
}() }()
@ -421,7 +417,6 @@ struct DashboardWidget_Previews: PreviewProvider {
let entry = SimpleEntry(date: Date(), let entry = SimpleEntry(date: Date(),
configuration: ConfigurationIntent(), configuration: ConfigurationIntent(),
widgetData: widgetData, widgetData: widgetData,
field: "Active Invoices",
value: "$100.00", value: "$100.00",
error: "") error: "")