11import WidgetKit
2+ import WooFoundation
23import KeychainAccess
34
45/// Type that represents the all the possible Widget states.
@@ -53,16 +54,20 @@ final class StoreInfoProvider: TimelineProvider {
5354 ///
5455 private var networkService : StoreInfoDataService ?
5556
57+ /// Desired data reload interval provided to system = 30 minutes.
58+ ///
59+ private let reloadInterval : TimeInterval = 30 * 60
60+
5661 /// Redacted entry with sample data.
5762 ///
5863 func placeholder( in context: Context ) -> StoreInfoEntry {
5964 let dependencies = Self . fetchDependencies ( )
6065 return StoreInfoEntry . data ( . init( range: Localization . today,
6166 name: dependencies? . storeName ?? Localization . myShop,
62- revenue: " $ 132.234" ,
67+ revenue: Self . formattedAmountString ( for : 132.234 , with : dependencies ? . storeCurrencySettings ) ,
6368 visitors: " 67 " ,
6469 orders: " 23 " ,
65- conversion: " 34% " ) )
70+ conversion: Self . formattedConversionString ( for : 23 / 67 ) ) )
6671 }
6772
6873 /// Quick Snapshot. Required when previewing the widget.
@@ -72,7 +77,6 @@ final class StoreInfoProvider: TimelineProvider {
7277 }
7378
7479 /// Real data widget.
75- /// TODO: Update with real data.
7680 ///
7781 func getTimeline( in context: Context , completion: @escaping ( Timeline < StoreInfoEntry > ) -> Void ) {
7882 guard let dependencies = Self . fetchDependencies ( ) else {
@@ -85,15 +89,14 @@ final class StoreInfoProvider: TimelineProvider {
8589 do {
8690 let todayStats = try await strongService. fetchTodayStats ( for: dependencies. storeID)
8791
88- // TODO: Use proper store formatting.
8992 let entry = StoreInfoEntry . data ( . init( range: Localization . today,
9093 name: dependencies. storeName,
91- revenue: " $ \( todayStats. revenue) " ,
94+ revenue: Self . formattedAmountString ( for : todayStats. revenue, with : dependencies . storeCurrencySettings ) ,
9295 visitors: " \( todayStats. totalVisitors) " ,
9396 orders: " \( todayStats. totalOrders) " ,
94- conversion: " \( todayStats. conversion) % " ) )
97+ conversion: Self . formattedConversionString ( for : todayStats. conversion) ) )
9598
96- let reloadDate = Date ( timeIntervalSinceNow: 30 * 60 ) // Ask for a 15 minutes reload.
99+ let reloadDate = Date ( timeIntervalSinceNow: reloadInterval )
97100 let timeline = Timeline < StoreInfoEntry > ( entries: [ entry] , policy: . after( reloadDate) )
98101 completion ( timeline)
99102
@@ -102,7 +105,7 @@ final class StoreInfoProvider: TimelineProvider {
102105 // WooFoundation does not expose `DDLOG` types. Should we include them?
103106 print ( " ⛔️ Error fetching today's widget stats: \( error) " )
104107
105- let reloadDate = Date ( timeIntervalSinceNow: 30 * 60 ) // Ask for a 30 minutes reload.
108+ let reloadDate = Date ( timeIntervalSinceNow: reloadInterval )
106109 let timeline = Timeline < StoreInfoEntry > ( entries: [ . error] , policy: . after( reloadDate) )
107110 completion ( timeline)
108111 }
@@ -118,6 +121,7 @@ private extension StoreInfoProvider {
118121 let authToken : String
119122 let storeID : Int64
120123 let storeName : String
124+ let storeCurrencySettings : CurrencySettings
121125 }
122126
123127 /// Fetches the required dependencies from the keychain and the shared users default.
@@ -126,14 +130,40 @@ private extension StoreInfoProvider {
126130 let keychain = Keychain ( service: WooConstants . keychainServiceName)
127131 guard let authToken = keychain [ WooConstants . authToken] ,
128132 let storeID = UserDefaults . group ? [ . defaultStoreID] as? Int64 ,
129- let storeName = UserDefaults . group ? [ . defaultStoreName] as? String else {
133+ let storeName = UserDefaults . group ? [ . defaultStoreName] as? String ,
134+ let storeCurrencySettingsData = UserDefaults . group ? [ . defaultStoreCurrencySettings] as? Data ,
135+ let storeCurrencySettings = try ? JSONDecoder ( ) . decode ( CurrencySettings . self, from: storeCurrencySettingsData) else {
130136 return nil
131137 }
132- return Dependencies ( authToken: authToken, storeID: storeID, storeName: storeName)
138+ return Dependencies ( authToken: authToken,
139+ storeID: storeID,
140+ storeName: storeName,
141+ storeCurrencySettings: storeCurrencySettings)
133142 }
134143}
135144
136145private extension StoreInfoProvider {
146+
147+ static func formattedAmountString( for amountValue: Decimal , with currencySettings: CurrencySettings ? ) -> String {
148+ let currencyFormatter = CurrencyFormatter ( currencySettings: currencySettings ?? CurrencySettings ( ) )
149+ return currencyFormatter. formatAmount ( amountValue) ?? Constants . valuePlaceholderText
150+ }
151+
152+ static func formattedConversionString( for conversionRate: Double ) -> String {
153+ let numberFormatter = NumberFormatter ( )
154+ numberFormatter. numberStyle = . percent
155+ numberFormatter. minimumFractionDigits = 1
156+
157+ // do not add 0 fraction digit if the percentage is round
158+ let minimumFractionDigits = floor ( conversionRate * 100.0 ) == conversionRate * 100.0 ? 0 : 1
159+ numberFormatter. minimumFractionDigits = minimumFractionDigits
160+ return numberFormatter. string ( from: conversionRate as NSNumber ) ?? Constants . valuePlaceholderText
161+ }
162+
163+ enum Constants {
164+ static let valuePlaceholderText = " - "
165+ }
166+
137167 enum Localization {
138168 static let myShop = AppLocalizedString (
139169 " storeWidgets.infoProvider.myShop " ,
0 commit comments