diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubView.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubView.swift index 3ca9a9caebb..9b6c8032f52 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubView.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubView.swift @@ -58,6 +58,16 @@ struct AnalyticsHubView: View { Divider() } + VStack(spacing: Layout.dividerSpacing) { + Divider() + + AnalyticsProductCard(viewModel: viewModel.productCard) + .padding(.horizontal, insets: safeAreaInsets) + .background(Color(uiColor: .listForeground)) + + Divider() + } + Spacer() } } diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubViewModel.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubViewModel.swift index 690f7d105a1..9c4c12145a2 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsHubViewModel.swift @@ -53,6 +53,12 @@ final class AnalyticsHubViewModel: ObservableObject { currentRangeSubtitle: "Jan 1 - Nov 23, 2022", previousRangeSubtitle: "Jan 1 - Nov 23, 2021") + /// Products Card ViewModel + /// + @Published var productCard = AnalyticsProductCardViewModel(itemsSold: "3,234", + delta: "+43%", + deltaBackgroundColor: .withColorStudio(.green, shade: .shade50)) + // MARK: Private data /// Order stats for the current selected time period diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsProductCard.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsProductCard.swift new file mode 100644 index 00000000000..77e1b1038c7 --- /dev/null +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsProductCard.swift @@ -0,0 +1,62 @@ +import SwiftUI + +/// Products Card on the Analytics Hub +/// +struct AnalyticsProductCard: View { + + /// Items sold quantity. Needs to be formatted. + /// + let itemsSold: String + + /// Delta Tag Value. Needs to be formatted + let delta: String + + /// Delta Tag background color. + let deltaBackgroundColor: UIColor + + var body: some View { + VStack(alignment: .leading) { + + Text(Localization.title) + .foregroundColor(Color(.text)) + .footnoteStyle() + + Text(Localization.itemsSold) + .headlineStyle() + .padding(.top, Layout.titleSpacing) + .padding(.bottom, Layout.columnSpacing) + + HStack { + Text(itemsSold) + .titleStyle() + .frame(maxWidth: .infinity, alignment: .leading) + + DeltaTag(value: delta, backgroundColor: deltaBackgroundColor) + } + } + .padding(Layout.cardPadding) + } +} + +// MARK: Constants +private extension AnalyticsProductCard { + enum Localization { + static let title = NSLocalizedString("Products", comment: "Title for the products card on the analytics hub screen.").localizedUppercase + static let itemsSold = NSLocalizedString("Items Sold", comment: "Title for the items sold column on the products card on the analytics hub screen.") + } + + enum Layout { + static let titleSpacing: CGFloat = 24 + static let cardPadding: CGFloat = 16 + static let columnSpacing: CGFloat = 10 + } +} + + +// MARK: Previews +struct AnalyticsProductCardPreviews: PreviewProvider { + static var previews: some View { + AnalyticsProductCard(itemsSold: "2,234", delta: "+23%", deltaBackgroundColor: .withColorStudio(.green, shade: .shade50)) + .previewLayout(.sizeThatFits) + } +} diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsProductCardViewModel.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsProductCardViewModel.swift new file mode 100644 index 00000000000..ba80b8ff30f --- /dev/null +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsProductCardViewModel.swift @@ -0,0 +1,29 @@ +import Foundation +import class UIKit.UIColor + +/// Analytics Hub Product Card ViewModel. +/// Used to transmit analytics products data. +/// +struct AnalyticsProductCardViewModel { + /// Items Sold Value + /// + let itemsSold: String + + /// Items Sold Delta + /// + let delta: String + + /// Delta background color. + /// + let deltaBackgroundColor: UIColor +} + +/// Convenience extension to create an `AnalyticsReportCard` from a view model. +/// +extension AnalyticsProductCard { + init(viewModel: AnalyticsProductCardViewModel) { + self.itemsSold = viewModel.itemsSold + self.delta = viewModel.delta + self.deltaBackgroundColor = viewModel.deltaBackgroundColor + } +} diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsReportCard.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsReportCard.swift index a617f0d32e9..574e8243f68 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsReportCard.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Analytics Hub/AnalyticsReportCard.swift @@ -56,29 +56,12 @@ struct AnalyticsReportCard: View { } } -private struct DeltaTag: View { - - let value: String - let backgroundColor: UIColor - - var body: some View { - Text(value) - .padding(AnalyticsReportCard.Layout.deltaBackgroundPadding) - .foregroundColor(Color(.textInverted)) - .captionStyle() - .background(Color(backgroundColor)) - .cornerRadius(AnalyticsReportCard.Layout.deltaCornerRadius) - } -} - // MARK: Constants private extension AnalyticsReportCard { enum Layout { static let titleSpacing: CGFloat = 24 static let cardPadding: CGFloat = 16 static let columnSpacing: CGFloat = 10 - static let deltaBackgroundPadding = EdgeInsets(top: 2, leading: 8, bottom: 2, trailing: 8) - static let deltaCornerRadius: CGFloat = 4.0 } } diff --git a/WooCommerce/Classes/ViewRelated/ReusableViews/SwiftUI Components/DeltaTag.swift b/WooCommerce/Classes/ViewRelated/ReusableViews/SwiftUI Components/DeltaTag.swift new file mode 100644 index 00000000000..2af63d31539 --- /dev/null +++ b/WooCommerce/Classes/ViewRelated/ReusableViews/SwiftUI Components/DeltaTag.swift @@ -0,0 +1,44 @@ +import SwiftUI + +/// Reusable tag view. +/// Useful to indicate growth rates. +/// +struct DeltaTag: View { + + /// Value to display. Needs to be already formatted + /// + let value: String + + /// Tag color. + /// + let backgroundColor: UIColor + + var body: some View { + Text(value) + .padding(Layout.backgroundPadding) + .foregroundColor(Color(.textInverted)) + .captionStyle() + .background(Color(backgroundColor)) + .cornerRadius(Layout.cornerRadius) + } +} + +// MARK: Constants +private extension DeltaTag { + enum Layout { + static let backgroundPadding = EdgeInsets(top: 2, leading: 8, bottom: 2, trailing: 8) + static let cornerRadius: CGFloat = 4.0 + } +} + +// MARK: Peviews +struct DeltaTagPreviews: PreviewProvider { + static var previews: some View { + VStack { + DeltaTag(value: "+3.23%", backgroundColor: .systemGreen) + + DeltaTag(value: "-3.23%", backgroundColor: .systemRed) + } + .previewLayout(.sizeThatFits) + } +} diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 264132c7199..d7af6a916f0 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -646,6 +646,9 @@ 26E1BECE251CD9F80096D0A1 /* RefundItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26E1BECD251CD9F80096D0A1 /* RefundItemViewModel.swift */; }; 26E7EE6A292D688900793045 /* AnalyticsHubViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26E7EE69292D688900793045 /* AnalyticsHubViewModel.swift */; }; 26E7EE6C292D894100793045 /* AnalyticsReportCardViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26E7EE6B292D894100793045 /* AnalyticsReportCardViewModel.swift */; }; + 26E7EE6E29300E8100793045 /* AnalyticsProductCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26E7EE6D29300E8100793045 /* AnalyticsProductCard.swift */; }; + 26E7EE7029300F6200793045 /* DeltaTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26E7EE6F29300F6200793045 /* DeltaTag.swift */; }; + 26E7EE7229301EBC00793045 /* AnalyticsProductCardViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26E7EE7129301EBC00793045 /* AnalyticsProductCardViewModel.swift */; }; 26ED9660274328BC00FA00A1 /* SimplePaymentsSummaryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26ED965F274328BC00FA00A1 /* SimplePaymentsSummaryViewModel.swift */; }; 26F65C9825DEDAF0008FAE29 /* GenerateVariationUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26F65C9725DEDAF0008FAE29 /* GenerateVariationUseCase.swift */; }; 26F65C9E25DEDE67008FAE29 /* GenerateVariationUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26F65C9D25DEDE67008FAE29 /* GenerateVariationUseCaseTests.swift */; }; @@ -2623,6 +2626,9 @@ 26E1BECD251CD9F80096D0A1 /* RefundItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefundItemViewModel.swift; sourceTree = ""; }; 26E7EE69292D688900793045 /* AnalyticsHubViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsHubViewModel.swift; sourceTree = ""; }; 26E7EE6B292D894100793045 /* AnalyticsReportCardViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsReportCardViewModel.swift; sourceTree = ""; }; + 26E7EE6D29300E8100793045 /* AnalyticsProductCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsProductCard.swift; sourceTree = ""; }; + 26E7EE6F29300F6200793045 /* DeltaTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeltaTag.swift; sourceTree = ""; }; + 26E7EE7129301EBC00793045 /* AnalyticsProductCardViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsProductCardViewModel.swift; sourceTree = ""; }; 26ED965F274328BC00FA00A1 /* SimplePaymentsSummaryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplePaymentsSummaryViewModel.swift; sourceTree = ""; }; 26F65C9725DEDAF0008FAE29 /* GenerateVariationUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateVariationUseCase.swift; sourceTree = ""; }; 26F65C9D25DEDE67008FAE29 /* GenerateVariationUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateVariationUseCaseTests.swift; sourceTree = ""; }; @@ -5277,6 +5283,8 @@ 26E7EE6B292D894100793045 /* AnalyticsReportCardViewModel.swift */, B60B5025292D308A00178C26 /* AnalyticsTimeRangeCard.swift */, B6A10E9B292E5DEE00790797 /* AnalyticsTimeRangeCardViewModel.swift */, + 26E7EE6D29300E8100793045 /* AnalyticsProductCard.swift */, + 26E7EE7129301EBC00793045 /* AnalyticsProductCardViewModel.swift */, ); path = "Analytics Hub"; sourceTree = ""; @@ -6119,6 +6127,7 @@ 02EAA4C92911004B00918DAB /* TextFieldStyles.swift */, 036CA6F029229C9E00E4DF4F /* IndefiniteCircularProgressViewStyle.swift */, DE2FE5872925DD950018040A /* JetpackInstallHeaderView.swift */, + 26E7EE6F29300F6200793045 /* DeltaTag.swift */, ); path = "SwiftUI Components"; sourceTree = ""; @@ -9789,6 +9798,7 @@ 03EF24FC28BF996F006A033E /* InPersonPaymentsCashOnDeliveryPaymentGatewayHelpers.swift in Sources */, 57896D6625362B0C000E8C4D /* TitleAndEditableValueTableViewCellViewModel.swift in Sources */, 0205021E27C8B6C600FB1C6B /* InboxEligibilityUseCase.swift in Sources */, + 26E7EE6E29300E8100793045 /* AnalyticsProductCard.swift in Sources */, 26E1BECE251CD9F80096D0A1 /* RefundItemViewModel.swift in Sources */, 26E7EE6C292D894100793045 /* AnalyticsReportCardViewModel.swift in Sources */, 6827141128A5410D00E6E3F6 /* NewSimplePaymentsLocationNoticeViewModel.swift in Sources */, @@ -10333,6 +10343,7 @@ E1E649E92846188C0070B194 /* BetaFeature.swift in Sources */, 0286B27B23C7051F003D784B /* ProductImagesCollectionViewController.swift in Sources */, E107FCE126C12B2700BAF51B /* InPersonPaymentsCountryNotSupported.swift in Sources */, + 26E7EE7229301EBC00793045 /* AnalyticsProductCardViewModel.swift in Sources */, 027A2E142513124E00DA6ACB /* Keychain+Entries.swift in Sources */, 268EC45F26CEA50C00716F5C /* EditCustomerNote.swift in Sources */, 4535EE7E281BE04A004212B4 /* CouponAmountInputFormatter.swift in Sources */, @@ -10445,6 +10456,7 @@ 020886572499E643001D784E /* ProductExternalLinkViewController.swift in Sources */, DEC2962526C122DF005A056B /* ShippingLabelCustomsFormInputViewModel.swift in Sources */, 02F4F50F237AFC1E00E13A9C /* ImageAndTitleAndTextTableViewCell.swift in Sources */, + 26E7EE7029300F6200793045 /* DeltaTag.swift in Sources */, 021E2A1C23AA0DD100B1DE07 /* ProductBackordersSettingListSelectorCommand.swift in Sources */, 26F94E21267A41BE00DB6CCF /* ProductAddOnsListViewModel.swift in Sources */, 45F5A3C123DF206B007D40E5 /* ShippingInputFormatter.swift in Sources */,