macOS widgets

This commit is contained in:
Hillel Coren 2023-06-21 16:06:43 +03:00
parent 7b2fb20407
commit 7e1ae1f0d2
4 changed files with 140 additions and 23 deletions

View File

@ -1406,11 +1406,17 @@ void _showAbout(BuildContext context) async {
]); ]);
} else { } else {
await UserDefaults.setString( await UserDefaults.setString(
'widgetData', 'widget_data',
jsonEncode(WidgetData( jsonEncode(WidgetData(
url: formatApiUrl(state.authState.url), url: formatApiUrl(state.authState.url),
tokens: state.apiTokens, 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'); 'group.com.invoiceninja.app');
await WidgetKit.reloadAllTimelines(); await WidgetKit.reloadAllTimelines();

View File

@ -3,9 +3,13 @@ import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/constants.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/main_app.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.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:invoiceninja_flutter/utils/platforms.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:widget_kit_plugin/user_defaults/user_defaults.dart'; import 'package:widget_kit_plugin/user_defaults/user_defaults.dart';
@ -91,17 +95,90 @@ class _WindowManagerState extends State<WindowManager> with WindowListener {
} }
class WidgetData { class WidgetData {
WidgetData({this.url, this.tokens}); WidgetData({this.url, this.companies});
WidgetData.fromJson(Map<String, dynamic> json) WidgetData.fromJson(Map<String, dynamic> json)
: url = json['url'], : url = json['url'],
tokens = json['tokens']; companies = json['companies'];
final String url;
final Map<String, String> tokens;
Map<String, dynamic> toJson() => <String, dynamic>{ Map<String, dynamic> toJson() => <String, dynamic>{
'tokens': tokens, 'companies': companies,
'url': url, 'url': url,
}; };
final String url;
final Map<String, WidgetCompany> 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<String, dynamic> json)
: id = json['id'],
name = json['name'],
token = json['token'],
accentColor = json['accent_color'],
currencies = json['currencies'],
currencyId = json['currency_id'];
Map<String, dynamic> toJson() => <String, dynamic>{
'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<String, WidgetCurrency> currencies;
}
class WidgetCurrency {
WidgetCurrency({this.id, this.name, this.exchangeRate});
WidgetCurrency.fromJson(Map<String, dynamic> json)
: id = json['id'],
name = json['name'],
exchangeRate = json['exchange_rate'];
Map<String, dynamic> toJson() => <String, dynamic>{
'id': id,
'name': name,
'exchange_rate': exchangeRate,
};
final String id;
final String name;
final double exchangeRate;
} }

View File

@ -12,11 +12,14 @@ class IntentHandler: INExtension, ConfigurationIntentHandling {
func provideCompanyOptionsCollection(for intent: ConfigurationIntent) async throws -> INObjectCollection<Company> { func provideCompanyOptionsCollection(for intent: ConfigurationIntent) async throws -> INObjectCollection<Company> {
let sharedDefaults = UserDefaults.init(suiteName: "group.com.invoiceninja.app") let sharedDefaults = UserDefaults.init(suiteName: "group.com.invoiceninja.app")
var exampleData: WidgetData = WidgetData(url: "", tokens:[:]) var exampleData: WidgetData = WidgetData(url: "", companies: [:])
if sharedDefaults != nil { if sharedDefaults != nil {
do { do {
let shared = sharedDefaults!.string(forKey: "widgetData") let shared = sharedDefaults!.string(forKey: "widget_data")
print("## Shared: \(shared!)")
if shared != nil { if shared != nil {
let decoder = JSONDecoder() let decoder = JSONDecoder()
exampleData = try decoder.decode(WidgetData.self, from: shared!.data(using: .utf8)!) 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( let company = Company(
identifier: token, identifier: company.token,
display: exampleData.tokens[token] ?? "" display: company.name
) )
//company.symbol = asset.symbol //company.symbol = asset.symbol
//company.name = asset.name //company.name = asset.name

View File

@ -11,11 +11,11 @@ import Intents
struct Provider: IntentTimelineProvider { struct Provider: IntentTimelineProvider {
func placeholder(in context: Context) -> SimpleEntry { 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) -> ()) { 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) completion(entry)
} }
@ -35,10 +35,10 @@ struct Provider: IntentTimelineProvider {
if sharedDefaults != nil { if sharedDefaults != nil {
do { do {
let shared = sharedDefaults!.string(forKey: "widgetData") let shared = sharedDefaults!.string(forKey: "widget_data")
if shared != nil { if shared != nil {
//print("## Shared: \(shared!)") print("## Shared: \(shared!)")
let decoder = JSONDecoder() let decoder = JSONDecoder()
exampleData = try decoder.decode(WidgetData.self, from: shared!.data(using: .utf8)!) 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"; let url = (exampleData?.url ?? "") + "/charts/totals_v2";
var token = configuration.company?.identifier ?? "" var token = configuration.company?.identifier ?? ""
if (token == "") { if (token == "" && !(exampleData?.companies.isEmpty)!) {
token = exampleData?.tokens.keys.first ?? ""; let company = exampleData?.companies.values.first;
token = company?.token ?? ""
} }
print("## company.name: \(configuration.company?.displayString ?? "")") print("## company.name: \(configuration.company?.displayString ?? "")")
@ -95,7 +96,37 @@ struct Provider: IntentTimelineProvider {
struct WidgetData: Decodable, Hashable { struct WidgetData: Decodable, Hashable {
let url: String 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 { struct SimpleEntry: TimelineEntry {
@ -154,7 +185,7 @@ struct DashboardWidget: Widget {
struct DashboardWidget_Previews: PreviewProvider { struct DashboardWidget_Previews: PreviewProvider {
static var previews: some View { 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)) .previewContext(WidgetPreviewContext(family: .systemSmall))
//.environment(\.sizeCategory, .extraLarge) //.environment(\.sizeCategory, .extraLarge)
//.environment(\.colorScheme, .dark) //.environment(\.colorScheme, .dark)