From 7e1ae1f0d2efd32bc3c3aa850d2be201fb932f6c Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 21 Jun 2023 16:06:43 +0300 Subject: [PATCH] macOS widgets --- lib/ui/app/menu_drawer.dart | 14 +++- lib/ui/app/window_manager.dart | 89 +++++++++++++++++++-- macos/CompanyIntent/IntentHandler.swift | 13 +-- macos/DashboardWidget/DashboardWidget.swift | 47 +++++++++-- 4 files changed, 140 insertions(+), 23 deletions(-) diff --git a/lib/ui/app/menu_drawer.dart b/lib/ui/app/menu_drawer.dart index 04d4cb32c..b9a15c876 100644 --- a/lib/ui/app/menu_drawer.dart +++ b/lib/ui/app/menu_drawer.dart @@ -1406,11 +1406,17 @@ void _showAbout(BuildContext context) async { ]); } else { await UserDefaults.setString( - 'widgetData', + 'widget_data', jsonEncode(WidgetData( - url: formatApiUrl(state.authState.url), - tokens: state.apiTokens, - )), + url: formatApiUrl(state.authState.url), + companies: { + for (var userCompany + in state.userCompanyStates.where((state) => state.company.hasName)) + userCompany.company.id: + WidgetCompany.fromUserCompany( + userCompanyState: userCompany, + staticState: state.staticState,) + })), 'group.com.invoiceninja.app'); await WidgetKit.reloadAllTimelines(); diff --git a/lib/ui/app/window_manager.dart b/lib/ui/app/window_manager.dart index 3393c0e60..46d1ce04c 100644 --- a/lib/ui/app/window_manager.dart +++ b/lib/ui/app/window_manager.dart @@ -3,9 +3,13 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:invoiceninja_flutter/constants.dart'; +import 'package:invoiceninja_flutter/data/models/company_model.dart'; import 'package:invoiceninja_flutter/main_app.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart'; +import 'package:invoiceninja_flutter/redux/company/company_selectors.dart'; +import 'package:invoiceninja_flutter/redux/company/company_state.dart'; +import 'package:invoiceninja_flutter/redux/static/static_state.dart'; import 'package:invoiceninja_flutter/utils/platforms.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:widget_kit_plugin/user_defaults/user_defaults.dart'; @@ -91,17 +95,90 @@ class _WindowManagerState extends State with WindowListener { } class WidgetData { - WidgetData({this.url, this.tokens}); + WidgetData({this.url, this.companies}); WidgetData.fromJson(Map json) : url = json['url'], - tokens = json['tokens']; - - final String url; - final Map tokens; + companies = json['companies']; Map toJson() => { - 'tokens': tokens, + 'companies': companies, 'url': url, }; + + final String url; + final Map companies; +} + +class WidgetCompany { + WidgetCompany( + {this.id, + this.name, + this.token, + this.accentColor, + this.currencyId, + this.currencies}); + + WidgetCompany.fromUserCompany( + {UserCompanyState userCompanyState, StaticState staticState}) + : id = userCompanyState.userCompany.company.id, + name = userCompanyState.userCompany.company.displayName, + token = userCompanyState.userCompany.token.token, + accentColor = userCompanyState.userCompany.settings.accentColor, + currencyId = userCompanyState.userCompany.company.currencyId, + currencies = { + for (var currencyId in getCurrencyIds( + userCompanyState.userCompany.company, + userCompanyState.clientState.map, + userCompanyState.groupState.map, + ).where((currencyId) => currencyId != kCurrencyAll)) + currencyId: WidgetCurrency( + id: currencyId, + name: staticState.currencyMap[currencyId].name, + exchangeRate: staticState.currencyMap[currencyId].exchangeRate, + ) + }; + + WidgetCompany.fromJson(Map json) + : id = json['id'], + name = json['name'], + token = json['token'], + accentColor = json['accent_color'], + currencies = json['currencies'], + currencyId = json['currency_id']; + + Map toJson() => { + 'id': id, + 'name': name, + 'token': token, + 'accent_color': accentColor, + 'currencies': currencies, + 'currency_id': currencyId, + }; + + final String id; + final String name; + final String token; + final String accentColor; + final String currencyId; + final Map currencies; +} + +class WidgetCurrency { + WidgetCurrency({this.id, this.name, this.exchangeRate}); + + WidgetCurrency.fromJson(Map json) + : id = json['id'], + name = json['name'], + exchangeRate = json['exchange_rate']; + + Map toJson() => { + 'id': id, + 'name': name, + 'exchange_rate': exchangeRate, + }; + + final String id; + final String name; + final double exchangeRate; } diff --git a/macos/CompanyIntent/IntentHandler.swift b/macos/CompanyIntent/IntentHandler.swift index 843ef2ad8..01542aa83 100644 --- a/macos/CompanyIntent/IntentHandler.swift +++ b/macos/CompanyIntent/IntentHandler.swift @@ -12,11 +12,14 @@ class IntentHandler: INExtension, ConfigurationIntentHandling { func provideCompanyOptionsCollection(for intent: ConfigurationIntent) async throws -> INObjectCollection { let sharedDefaults = UserDefaults.init(suiteName: "group.com.invoiceninja.app") - var exampleData: WidgetData = WidgetData(url: "", tokens:[:]) + var exampleData: WidgetData = WidgetData(url: "", companies: [:]) if sharedDefaults != nil { do { - let shared = sharedDefaults!.string(forKey: "widgetData") + let shared = sharedDefaults!.string(forKey: "widget_data") + + print("## Shared: \(shared!)") + if shared != nil { let decoder = JSONDecoder() exampleData = try decoder.decode(WidgetData.self, from: shared!.data(using: .utf8)!) @@ -26,11 +29,11 @@ class IntentHandler: INExtension, ConfigurationIntentHandling { } } - let companies = exampleData.tokens.keys.map { token in + let companies = exampleData.companies.values.map { company in let company = Company( - identifier: token, - display: exampleData.tokens[token] ?? "" + identifier: company.token, + display: company.name ) //company.symbol = asset.symbol //company.name = asset.name diff --git a/macos/DashboardWidget/DashboardWidget.swift b/macos/DashboardWidget/DashboardWidget.swift index 8f94b74d4..ba82bc448 100644 --- a/macos/DashboardWidget/DashboardWidget.swift +++ b/macos/DashboardWidget/DashboardWidget.swift @@ -11,11 +11,11 @@ import Intents struct Provider: IntentTimelineProvider { func placeholder(in context: Context) -> SimpleEntry { - SimpleEntry(date: Date(), configuration: ConfigurationIntent(), widgetData: WidgetData(url: "url", tokens: ["plk": "ply"]), field: "Invoices", value: 0) + SimpleEntry(date: Date(), configuration: ConfigurationIntent(), widgetData: WidgetData(url: "url", companies: [:]), field: "Invoices", value: 0) } func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) { - let entry = SimpleEntry(date: Date(), configuration: configuration, widgetData: WidgetData(url: "url", tokens: ["sk": "sy"]), field: "Invoices", value: 0) + let entry = SimpleEntry(date: Date(), configuration: configuration, widgetData: WidgetData(url: "url", companies: [:]), field: "Invoices", value: 0) completion(entry) } @@ -35,10 +35,10 @@ struct Provider: IntentTimelineProvider { if sharedDefaults != nil { do { - let shared = sharedDefaults!.string(forKey: "widgetData") + let shared = sharedDefaults!.string(forKey: "widget_data") if shared != nil { - //print("## Shared: \(shared!)") + print("## Shared: \(shared!)") let decoder = JSONDecoder() exampleData = try decoder.decode(WidgetData.self, from: shared!.data(using: .utf8)!) @@ -48,8 +48,9 @@ struct Provider: IntentTimelineProvider { let url = (exampleData?.url ?? "") + "/charts/totals_v2"; var token = configuration.company?.identifier ?? "" - if (token == "") { - token = exampleData?.tokens.keys.first ?? ""; + if (token == "" && !(exampleData?.companies.isEmpty)!) { + let company = exampleData?.companies.values.first; + token = company?.token ?? "" } print("## company.name: \(configuration.company?.displayString ?? "")") @@ -95,7 +96,37 @@ struct Provider: IntentTimelineProvider { struct WidgetData: Decodable, Hashable { let url: String - let tokens: [String: String] + let companies: [String: WidgetCompany] +} + +struct WidgetCompany: Decodable, Hashable { + let id: String + let name: String + let token: String + let accentColor: String + let currencyId: String + let currencies: [String: WidgetCurrency] + + enum CodingKeys: String, CodingKey { + case id + case name + case token + case accentColor = "accent_color" + case currencyId = "currency_id" + case currencies + } +} + +struct WidgetCurrency: Decodable, Hashable { + let id: String + let name: String + let exchangeRate: Double + + enum CodingKeys: String, CodingKey { + case id + case name + case exchangeRate = "exchange_rate" + } } struct SimpleEntry: TimelineEntry { @@ -154,7 +185,7 @@ struct DashboardWidget: Widget { struct DashboardWidget_Previews: PreviewProvider { static var previews: some View { - DashboardWidgetEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent(), widgetData: WidgetData(url: "url", tokens: ["pk": "py"]), field: "Invoices", value: 0)) + DashboardWidgetEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent(), widgetData: WidgetData(url: "url", companies: [:]), field: "Invoices", value: 0)) .previewContext(WidgetPreviewContext(family: .systemSmall)) //.environment(\.sizeCategory, .extraLarge) //.environment(\.colorScheme, .dark)