macOS widgets
This commit is contained in:
parent
cbfc96ee4b
commit
f72614a2f1
|
|
@ -66,112 +66,117 @@ struct Provider: IntentTimelineProvider {
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
|
|
||||||
|
var rawValue = 0.0
|
||||||
|
var value = "Error"
|
||||||
|
var label = ""
|
||||||
|
|
||||||
let sharedDefaults = UserDefaults.init(suiteName: "group.com.invoiceninja.app")
|
let sharedDefaults = UserDefaults.init(suiteName: "group.com.invoiceninja.app")
|
||||||
var widgetData: WidgetData? = nil
|
var widgetData: WidgetData? = nil
|
||||||
|
|
||||||
if sharedDefaults == nil {
|
if sharedDefaults != nil {
|
||||||
return
|
do {
|
||||||
}
|
let shared = sharedDefaults!.string(forKey: "widget_data")
|
||||||
|
if shared != nil {
|
||||||
|
|
||||||
do {
|
//print("## Shared: \(shared!)")
|
||||||
let shared = sharedDefaults!.string(forKey: "widget_data")
|
|
||||||
if shared == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//print("## Shared: \(shared!)")
|
let decoder = JSONDecoder()
|
||||||
|
widgetData = try decoder.decode(WidgetData.self, from: shared!.data(using: .utf8)!)
|
||||||
|
|
||||||
let decoder = JSONDecoder()
|
let companyId = configuration.company?.identifier ?? ""
|
||||||
widgetData = try decoder.decode(WidgetData.self, from: shared!.data(using: .utf8)!)
|
let company = widgetData?.companies[companyId]
|
||||||
|
let currencyId = configuration.currency?.identifier ?? company?.currencyId
|
||||||
|
let currency = company?.currencies[currencyId!]
|
||||||
|
|
||||||
if (widgetData?.url == nil) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let url = (widgetData?.url ?? "") + "/charts/totals_v2";
|
if (widgetData?.url == nil) {
|
||||||
let companyId = configuration.company?.identifier ?? ""
|
return
|
||||||
let company = widgetData?.companies[companyId]
|
|
||||||
var token = company?.token
|
|
||||||
let (startDate, endDate) = getDateRange(dateRange: (configuration.dateRange?.identifier)!,
|
|
||||||
firstMonthOfYear: company!.firstMonthOfYear)
|
|
||||||
|
|
||||||
if (token == "" && !(widgetData?.companies.isEmpty)!) {
|
|
||||||
print("## WARNING: using first token")
|
|
||||||
let company = widgetData?.companies.values.first;
|
|
||||||
token = company?.token ?? ""
|
|
||||||
}
|
|
||||||
|
|
||||||
print("## company.name: \(configuration.company?.displayString ?? "")")
|
|
||||||
print("## company.id: \(configuration.company?.identifier ?? "")")
|
|
||||||
print("## Date Range: \(String(describing: configuration.dateRange?.identifier)) => \(startDate) - \(endDate)")
|
|
||||||
//print("## URL: \(url)")
|
|
||||||
|
|
||||||
if (token == "") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let result = try? await ApiService.post(urlString: url,
|
|
||||||
apiToken: token!,
|
|
||||||
startDate: startDate,
|
|
||||||
endDate: endDate) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let currencyId = configuration.currency?.identifier ?? company?.currencyId
|
|
||||||
let currency = company?.currencies[currencyId!]
|
|
||||||
|
|
||||||
var value = 0.0
|
|
||||||
var label = ""
|
|
||||||
let data = result[currencyId ?? "1"]
|
|
||||||
|
|
||||||
if (data != nil) {
|
|
||||||
if (configuration.field == Field.active_invoices) {
|
|
||||||
if (data?.invoices?.invoicedAmount != nil) {
|
|
||||||
value = Double(data?.invoices?.invoicedAmount ?? "")!
|
|
||||||
}
|
}
|
||||||
label = "Active Invoices"
|
|
||||||
} else if (configuration.field == Field.outstanding_invoices) {
|
let url = (widgetData?.url ?? "") + "/charts/totals_v2";
|
||||||
if (data?.outstanding?.amount != nil) {
|
var token = company?.token
|
||||||
value = Double(data?.outstanding?.amount ?? "")!
|
let (startDate, endDate) = getDateRange(dateRange: (configuration.dateRange?.identifier)!,
|
||||||
|
firstMonthOfYear: company!.firstMonthOfYear)
|
||||||
|
|
||||||
|
if (token == "" && !(widgetData?.companies.isEmpty)!) {
|
||||||
|
print("## WARNING: using first token")
|
||||||
|
let company = widgetData?.companies.values.first;
|
||||||
|
token = company?.token ?? ""
|
||||||
}
|
}
|
||||||
label = "Outstanding Invoices"
|
|
||||||
} else if (configuration.field == Field.completed_payments) {
|
print("## company.name: \(configuration.company?.displayString ?? "")")
|
||||||
if (data?.revenue?.paidToDate != nil) {
|
print("## company.id: \(configuration.company?.identifier ?? "")")
|
||||||
value = Double(data?.revenue?.paidToDate ?? "")!
|
print("## Date Range: \(String(describing: configuration.dateRange?.identifier)) => \(startDate) - \(endDate)")
|
||||||
|
//print("## URL: \(url)")
|
||||||
|
|
||||||
|
if (token == "") {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
label = "Completed Payments"
|
|
||||||
|
|
||||||
|
let result = try await ApiService.post(urlString: url,
|
||||||
|
apiToken: token!,
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate)!
|
||||||
|
|
||||||
|
let data = result[currencyId ?? "1"]
|
||||||
|
|
||||||
|
if (data != nil) {
|
||||||
|
if (configuration.field == Field.active_invoices) {
|
||||||
|
if (data?.invoices?.invoicedAmount != nil) {
|
||||||
|
rawValue = Double(data?.invoices?.invoicedAmount ?? "")!
|
||||||
|
}
|
||||||
|
label = "Active Invoices"
|
||||||
|
} else if (configuration.field == Field.outstanding_invoices) {
|
||||||
|
if (data?.outstanding?.amount != nil) {
|
||||||
|
rawValue = Double(data?.outstanding?.amount ?? "")!
|
||||||
|
}
|
||||||
|
label = "Outstanding Invoices"
|
||||||
|
} else if (configuration.field == Field.completed_payments) {
|
||||||
|
if (data?.revenue?.paidToDate != nil) {
|
||||||
|
rawValue = Double(data?.revenue?.paidToDate ?? "")!
|
||||||
|
}
|
||||||
|
label = "Completed Payments"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let formatter = NumberFormatter()
|
||||||
|
formatter.numberStyle = .currency
|
||||||
|
formatter.currencyCode = currency?.code ?? "USD"
|
||||||
|
value = formatter.string(from: NSNumber(value: rawValue))!
|
||||||
|
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
print("## ERROR: \(error)")
|
||||||
|
//value = "\(error)"
|
||||||
}
|
}
|
||||||
|
|
||||||
let formatter = NumberFormatter()
|
|
||||||
formatter.numberStyle = .currency
|
|
||||||
formatter.currencyCode = currency?.code ?? "USD"
|
|
||||||
|
|
||||||
let entry = SimpleEntry(date: Date(),
|
|
||||||
configuration: configuration,
|
|
||||||
widgetData: widgetData,
|
|
||||||
field: label,
|
|
||||||
value: formatter.string(from: NSNumber(value: value))!)
|
|
||||||
|
|
||||||
let nextUpdate = Calendar.current.date(
|
|
||||||
byAdding: DateComponents(minute: 15),
|
|
||||||
to: Date()
|
|
||||||
)!
|
|
||||||
|
|
||||||
let timeline = Timeline(
|
|
||||||
entries: [entry],
|
|
||||||
policy: .after(nextUpdate)
|
|
||||||
)
|
|
||||||
|
|
||||||
completion(timeline)
|
|
||||||
|
|
||||||
} catch {
|
|
||||||
print(error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print("## VALUE: \(value)")
|
||||||
|
|
||||||
|
let entry = SimpleEntry(date: Date(),
|
||||||
|
configuration: configuration,
|
||||||
|
widgetData: widgetData,
|
||||||
|
field: label,
|
||||||
|
value: value)
|
||||||
|
|
||||||
|
|
||||||
|
let nextUpdate = Calendar.current.date(
|
||||||
|
byAdding: DateComponents(minute: 15),
|
||||||
|
to: Date()
|
||||||
|
)!
|
||||||
|
|
||||||
|
let timeline = Timeline(
|
||||||
|
entries: [entry],
|
||||||
|
policy: .after(nextUpdate)
|
||||||
|
)
|
||||||
|
|
||||||
|
completion(timeline)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func getDateRange(dateRange: String, firstMonthOfYear: Int = 1) -> (start: String, end: String) {
|
func getDateRange(dateRange: String, firstMonthOfYear: Int = 1) -> (start: String, end: String) {
|
||||||
|
|
||||||
let today = Date()
|
let today = Date()
|
||||||
|
|
@ -213,11 +218,11 @@ struct Provider: IntentTimelineProvider {
|
||||||
end = calendar.date(byAdding: .month, value: 3, to: start)!.addingTimeInterval(-1)
|
end = calendar.date(byAdding: .month, value: 3, to: start)!.addingTimeInterval(-1)
|
||||||
} else if (dateRange == "last_quarter") {
|
} else if (dateRange == "last_quarter") {
|
||||||
let monthOffset = (calendar.component(.month, from: today) - 1) % 3 * -1
|
let monthOffset = (calendar.component(.month, from: today) - 1) % 3 * -1
|
||||||
start = calendar.date(byAdding: .month, value: monthOffset - 3, to: firstDayOfMonth)!
|
start = calendar.date(byAdding: .month, value: monthOffset - 3, to: firstDayOfMonth)!
|
||||||
end = calendar.date(byAdding: .month, value: 3, to: start)!.addingTimeInterval(-1)
|
end = calendar.date(byAdding: .month, value: 3, to: start)!.addingTimeInterval(-1)
|
||||||
} else if (dateRange == "this_year") {
|
} else if (dateRange == "this_year") {
|
||||||
start = firstDayOfYear
|
start = firstDayOfYear
|
||||||
end = calendar.date(byAdding: .year, value: 1, to: start)!.addingTimeInterval(-1)
|
end = calendar.date(byAdding: .year, value: 1, to: start)!.addingTimeInterval(-1)
|
||||||
} else if (dateRange == "last_year") {
|
} else if (dateRange == "last_year") {
|
||||||
start = calendar.date(byAdding: .year, value: -1, to: firstDayOfYear)!
|
start = calendar.date(byAdding: .year, value: -1, to: firstDayOfYear)!
|
||||||
end = calendar.date(byAdding: .year, value: 1, to: start)!.addingTimeInterval(-1)
|
end = calendar.date(byAdding: .year, value: 1, to: start)!.addingTimeInterval(-1)
|
||||||
|
|
@ -289,7 +294,7 @@ struct DashboardWidgetEntryView : View {
|
||||||
|
|
||||||
var accentColor: Color {
|
var accentColor: Color {
|
||||||
let companyId = entry.configuration.company?.identifier ?? ""
|
let companyId = entry.configuration.company?.identifier ?? ""
|
||||||
return Color(hex: (entry.widgetData?.companies[companyId]!.accentColor)!)
|
return Color(hex: (entry.widgetData?.companies[companyId]?.accentColor ?? "#0000ff")!)
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
@ -362,7 +367,6 @@ struct DashboardWidget_Previews: PreviewProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct ApiResult: Codable {
|
struct ApiResult: Codable {
|
||||||
let invoices: Invoices?
|
let invoices: Invoices?
|
||||||
let revenue: Revenue?
|
let revenue: Revenue?
|
||||||
|
|
@ -420,6 +424,14 @@ struct Expenses: Codable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ApiResultError: Codable {
|
||||||
|
let message: String
|
||||||
|
let errors: [String: String]
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ApiError: Error {
|
||||||
|
case message(String)
|
||||||
|
}
|
||||||
|
|
||||||
struct ApiService {
|
struct ApiService {
|
||||||
|
|
||||||
|
|
@ -432,18 +444,26 @@ struct ApiService {
|
||||||
request.addValue("macOS Widget", forHTTPHeaderField: "X-CLIENT")
|
request.addValue("macOS Widget", forHTTPHeaderField: "X-CLIENT")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let (data, _) = try await URLSession.shared.data(for: request)
|
let (data, response) = try await URLSession.shared.data(for: request)
|
||||||
|
|
||||||
//print("## Details WAS: \(String(describing: String(data: data, encoding: .utf8)))")
|
if let httpResponse = response as? HTTPURLResponse {
|
||||||
//print("## Details IS: \(String(describing: String(data: try! ApiService.fixData(data: data), encoding: .utf8)))")
|
let statusCode = httpResponse.statusCode
|
||||||
|
if statusCode >= 200 && statusCode < 300 {
|
||||||
|
//print("## Details WAS: \(String(describing: String(data: data, encoding: .utf8)))")
|
||||||
|
//print("## Details IS: \(String(describing: String(data: try! ApiService.fixData(data: data), encoding: .utf8)))")
|
||||||
|
|
||||||
//let result = try JSONDecoder().decode([String: ApiResult].self, from: data)
|
//let result = try JSONDecoder().decode([String: ApiResult].self, from: data)
|
||||||
let result = try JSONDecoder().decode([String: ApiResult].self, from: ApiService.fixData(data: data))
|
let result = try JSONDecoder().decode([String: ApiResult].self, from: ApiService.fixData(data: data))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
} else {
|
||||||
|
let result = try JSONDecoder().decode(ApiResultError.self, from: data)
|
||||||
|
|
||||||
|
throw ApiError.message("\(statusCode): \(result.message)")
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
print("Error: \(error)")
|
throw ApiError.message("\(error)")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue