Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wip] POS History #12753

Draft
wants to merge 5 commits into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions WooCommerce/Classes/POS/Models/POSHistoryItem.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Foundation

public struct HistoryItem: Identifiable {
public let createdAt: Date
public let id: UUID = UUID()
public let amountInCents: Int
// TODO: add data like products, amount, is it a purchase or a refund etc.
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ struct PointOfSaleDashboardView: View {
@Environment(\.presentationMode) var presentationMode

@ObservedObject private var viewModel: PointOfSaleDashboardViewModel
@ObservedObject private var historyViewModel: PointOfSaleHistoryViewModel

init(viewModel: PointOfSaleDashboardViewModel) {
@State private var showHistory: Bool = false

init(viewModel: PointOfSaleDashboardViewModel, historyViewModel: PointOfSaleHistoryViewModel) {
self.viewModel = viewModel
self.historyViewModel = historyViewModel
}

var body: some View {
Expand Down Expand Up @@ -37,18 +41,22 @@ struct PointOfSaleDashboardView: View {
}
})
ToolbarItem(placement: .primaryAction, content: {
Button("History") {
debugPrint("Not implemented")
Button(historyViewModel.items.isEmpty ? "History" : "History (\(historyViewModel.items.count))") {
showHistory = true
}
})
}
.sheet(isPresented: $viewModel.showsCardReaderSheet, content: {
CardReaderConnectionView(viewModel: viewModel.cardReaderConnectionViewModel)
})
.sheet(isPresented: $showHistory) {
PointOfSaleHistoryView(viewModel: historyViewModel)
}
}
}

#Preview {
PointOfSaleDashboardView(viewModel: PointOfSaleDashboardViewModel(products: POSProductFactory.makeFakeProducts(),
cardReaderConnectionViewModel: .init(state: .connectingToReader)))
cardReaderConnectionViewModel: .init(state: .connectingToReader)),
historyViewModel: PointOfSaleHistoryViewModel(items: []))
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,20 @@ public struct PointOfSaleEntryPointView: View {
return viewModel
}()

@ObservedObject var historyViewModel: PointOfSaleHistoryViewModel

private let hideAppTabBar: ((Bool) -> Void)

// Necessary to expose the View's entry point to WooCommerce
// Otherwise the current switch on `HubMenu` where this View is created
// will error with "No exact matches in reference to static method 'buildExpression'"
public init(hideAppTabBar: @escaping ((Bool) -> Void)) {
public init(historyViewModel: PointOfSaleHistoryViewModel, hideAppTabBar: @escaping ((Bool) -> Void)) {
self.historyViewModel = historyViewModel
self.hideAppTabBar = hideAppTabBar
}

public var body: some View {
PointOfSaleDashboardView(viewModel: viewModel)
PointOfSaleDashboardView(viewModel: viewModel, historyViewModel: historyViewModel)
.onAppear {
hideAppTabBar(true)
}
Expand All @@ -31,5 +34,5 @@ public struct PointOfSaleEntryPointView: View {
}

#Preview {
PointOfSaleEntryPointView(hideAppTabBar: { _ in })
PointOfSaleEntryPointView(historyViewModel: PointOfSaleHistoryViewModel.makeFakeHistory(), hideAppTabBar: { _ in })
}
174 changes: 174 additions & 0 deletions WooCommerce/Classes/POS/Presentation/PointOfSaleHistoryView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import SwiftUI

struct PointOfSaleHistoryView: View {
@Environment(\.presentationMode) var presentationMode

@ObservedObject private var viewModel: PointOfSaleHistoryViewModel

init(viewModel: PointOfSaleHistoryViewModel) {
self.viewModel = viewModel
}

private var titleView: some View {
HStack {
Text("History")
.font(.title)
.bold()
.foregroundColor(Color.primaryText)
Spacer()
Button("Close") {
presentationMode.wrappedValue.dismiss()
}
.tint(Color.primaryText)
}
}

private let dateFormatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.day, .hour, .minute, .second]
formatter.unitsStyle = .abbreviated
return formatter
}()

private let currencyFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
return formatter
}()

@ViewBuilder
private var headerView: some View {
HStack {
Text("This session:")
Spacer()
if let sessionStart = viewModel.sessionStart,
let duration = dateFormatter.string(from: sessionStart, to: Date()) {
Text(duration)
Spacer()
}
else {
Text("\(viewModel.sessionStart!.formatted())")
Spacer()
}
if viewModel.items.count == 1 {
Text("1 transaction")
}
else {
Text("\(viewModel.items.count) transactions")
}
Spacer()
transactionsAmountView
}
.foregroundColor(Color.primaryText)
}

@ViewBuilder
private var transactionsAmountView: some View {
let totalAmount = Double(viewModel.itemsAmount) / 100.0
if let amount = currencyFormatter.string(from: NSNumber(value: totalAmount)) {
Text(amount)
}
else {
EmptyView()
}
}

@State private var searchText: String = ""

private var searchView: some View {
HStack {
HStack {
TextField("Search", text: $searchText)
.placeholder(when: searchText.isEmpty) {
Text("Search").foregroundColor(Color.primaryText)
}
.foregroundColor(Color.primaryText)
.padding()
}
.background(Color.tertiaryBackground)
.cornerRadius(12.0)
Spacer()
Button {
} label: {
Text("Filter")
.fontWeight(.medium)
.padding()
.foregroundColor(Color.primaryText)
.background(
RoundedRectangle(
cornerRadius: 20,
style: .continuous
)
.stroke(Color.primaryText, lineWidth: 2)
)
}
}
}

private var itemsView: some View {
ScrollView {
LazyVStack {
ForEach(viewModel.items) { item in
HistoryItemView(item: item)
}
}
}
}

var body: some View {
VStack {
VStack {
titleView
headerView
.padding(.vertical)
searchView
itemsView
}
.padding()
}
.background(Color.secondaryBackground)
}
}

struct HistoryItemView: View {
let item: HistoryItem

private let currencyFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
return formatter
}()

var body: some View {
HStack {
Text(item.createdAt.formatted())
.foregroundColor(Color.primaryText)
.padding()
Spacer()
let amountValue = Double(item.amountInCents) / 100.0
if let amount = currencyFormatter.string(from: NSNumber(value: amountValue)) {
Text(amount)
.foregroundColor(Color.primaryText)
.bold()
.padding()
}
else {
EmptyView()
}
}
.background(Color.tertiaryBackground)
}
}

extension View {
func placeholder<Content: View>(
when shouldShow: Bool,
alignment: Alignment = .leading,
@ViewBuilder placeholder: () -> Content) -> some View {

ZStack(alignment: alignment) {
placeholder().opacity(shouldShow ? 1 : 0)
self
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import SwiftUI

public final class PointOfSaleHistoryViewModel: ObservableObject {
@Published var items: [HistoryItem]
@Published var sessionStart: Date? = Date()

init(items: [HistoryItem]) {
self.items = items
}

static func makeFakeHistory() -> PointOfSaleHistoryViewModel {
return PointOfSaleHistoryViewModel(items: [
HistoryItem(createdAt: Date(), amountInCents: 299),
HistoryItem(createdAt: Date() - 1000, amountInCents: 399)
])
}

func addItem(_ item: HistoryItem) {
self.items.append(item)
}

var itemsAmount: Int {
let amount = items.reduce(0) { $0 + $1.amountInCents }
return amount
}
}
4 changes: 3 additions & 1 deletion WooCommerce/Classes/ViewRelated/Hub Menu/HubMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ struct HubMenu: View {
@ObservedObject private var iO = Inject.observer

@ObservedObject private var viewModel: HubMenuViewModel

@StateObject private var historyViewModel = PointOfSaleHistoryViewModel.makeFakeHistory()

init(viewModel: HubMenuViewModel) {
self.viewModel = viewModel
Expand Down Expand Up @@ -167,7 +169,7 @@ private extension HubMenu {
case HubMenuViewModel.Customers.id:
CustomersListView(viewModel: .init(siteID: viewModel.siteID))
case HubMenuViewModel.PointOfSaleEntryPoint.id:
PointOfSaleEntryPointView(hideAppTabBar: { isHidden in
PointOfSaleEntryPointView(historyViewModel: historyViewModel, hideAppTabBar: { isHidden in
AppDelegate.shared.setShouldHideTabBar(isHidden)
})
default:
Expand Down
12 changes: 12 additions & 0 deletions WooCommerce/WooCommerce.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2232,6 +2232,9 @@
D8EE9698264D3CCB0033B2F9 /* LegacyReceiptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8EE9697264D3CCB0033B2F9 /* LegacyReceiptViewModel.swift */; };
D8F01DD325DEDC1C00CE70BE /* StripeCardReaderIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F01DD225DEDC1C00CE70BE /* StripeCardReaderIntegrationTests.swift */; };
D8F82AC522AF903700B67E4B /* IconsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F82AC422AF903700B67E4B /* IconsTests.swift */; };
DAE11FA42BF5F0A400AAE72F /* POSHistoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE11FA32BF5F0A400AAE72F /* POSHistoryItem.swift */; };
DAE11FA62BF5F0D100AAE72F /* PointOfSaleHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE11FA52BF5F0D100AAE72F /* PointOfSaleHistoryView.swift */; };
DAE11FA82BF5F16000AAE72F /* PointOfSaleHistoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE11FA72BF5F16000AAE72F /* PointOfSaleHistoryViewModel.swift */; };
DE001323279A793A00EB0350 /* CouponWooTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE001322279A793A00EB0350 /* CouponWooTests.swift */; };
DE0134152A2EED52000A6F54 /* ProductSharingMessageGenerationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE0134142A2EED52000A6F54 /* ProductSharingMessageGenerationView.swift */; };
DE0134172A30364B000A6F54 /* ProductSharingMessageGenerationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE0134162A30364B000A6F54 /* ProductSharingMessageGenerationViewModel.swift */; };
Expand Down Expand Up @@ -5012,6 +5015,9 @@
D8F82AC422AF903700B67E4B /* IconsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = IconsTests.swift; path = WooCommerceTests/Extensions/IconsTests.swift; sourceTree = SOURCE_ROOT; };
D8FBFF1622D4CC2F006E3336 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; name = docs; path = ../docs; sourceTree = "<group>"; };
DA1AE99A10B90748C7676E95 /* Pods-StoreWidgetsExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StoreWidgetsExtension.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-StoreWidgetsExtension/Pods-StoreWidgetsExtension.debug.xcconfig"; sourceTree = "<group>"; };
DAE11FA32BF5F0A400AAE72F /* POSHistoryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSHistoryItem.swift; sourceTree = "<group>"; };
DAE11FA52BF5F0D100AAE72F /* PointOfSaleHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleHistoryView.swift; sourceTree = "<group>"; };
DAE11FA72BF5F16000AAE72F /* PointOfSaleHistoryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleHistoryViewModel.swift; sourceTree = "<group>"; };
DE001322279A793A00EB0350 /* CouponWooTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CouponWooTests.swift; sourceTree = "<group>"; };
DE0134142A2EED52000A6F54 /* ProductSharingMessageGenerationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductSharingMessageGenerationView.swift; sourceTree = "<group>"; };
DE0134162A30364B000A6F54 /* ProductSharingMessageGenerationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductSharingMessageGenerationViewModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -6241,6 +6247,7 @@
isa = PBXGroup;
children = (
026826922BF59D830036F959 /* PointOfSaleDashboardViewModel.swift */,
DAE11FA72BF5F16000AAE72F /* PointOfSaleHistoryViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
Expand All @@ -6266,6 +6273,7 @@
isa = PBXGroup;
children = (
0268269E2BF59DE10036F959 /* POSProduct.swift */,
DAE11FA32BF5F0A400AAE72F /* POSHistoryItem.swift */,
0268269D2BF59DE10036F959 /* CartProduct.swift */,
);
path = Models;
Expand All @@ -6277,6 +6285,7 @@
026826B02BF59E170036F959 /* CardReaderConnection */,
026826A72BF59DF70036F959 /* PointOfSaleEntryPointView.swift */,
026826A52BF59DF60036F959 /* PointOfSaleDashboardView.swift */,
DAE11FA52BF5F0D100AAE72F /* PointOfSaleHistoryView.swift */,
026826A82BF59DF70036F959 /* ProductGridView.swift */,
026826A32BF59DF60036F959 /* CartView.swift */,
026826A22BF59DF60036F959 /* ProductRowView.swift */,
Expand Down Expand Up @@ -14061,6 +14070,7 @@
311D21ED264AF0E700102316 /* CardReaderSettingsAlerts.swift in Sources */,
867EA0AF2B8F15740064BCA7 /* CustomerFilter+Analytics.swift in Sources */,
02C0CD2A23B5BB1C00F880B1 /* ImageService.swift in Sources */,
DAE11FA82BF5F16000AAE72F /* PointOfSaleHistoryViewModel.swift in Sources */,
26ABCE532518EAF300721CB0 /* IssueRefundTableViewCell.swift in Sources */,
0304E35E28BDC86D00A80191 /* LearnMoreViewModel.swift in Sources */,
02DF980B2A15FD920009E2EA /* StoreCreationStatusChecker.swift in Sources */,
Expand Down Expand Up @@ -14328,6 +14338,7 @@
DE36E0982A8634FF00B98496 /* StoreNameSetupView.swift in Sources */,
2688641B25D3202B00821BA5 /* EditAttributesViewController.swift in Sources */,
7E6A01972725B811001668D5 /* FilterProductCategoryListViewController.swift in Sources */,
DAE11FA62BF5F0D100AAE72F /* PointOfSaleHistoryView.swift in Sources */,
E10DFC7A2673595A0083AFF2 /* ShareSheet.swift in Sources */,
03EF250628C75838006A033E /* PurchaseCardReaderWebViewViewModel.swift in Sources */,
027CB043295D7B6B00F98F7C /* StoreCreationCountryQuestionView.swift in Sources */,
Expand Down Expand Up @@ -14579,6 +14590,7 @@
209AD3D02AC1EDDA00825D76 /* WooPaymentsDepositsCurrencyOverviewViewModel.swift in Sources */,
AE3AA88B290C30B900BE422D /* WebViewControllerConfiguration.swift in Sources */,
26E1BECA251BE5390096D0A1 /* RefundItemTableViewCell.swift in Sources */,
DAE11FA42BF5F0A400AAE72F /* POSHistoryItem.swift in Sources */,
DE7B479527A38B8F0018742E /* CouponDetailsViewModel.swift in Sources */,
E16058F9285876E600E471D4 /* LeftImageTitleSubtitleTableViewCell.swift in Sources */,
03E471C2293A1F6B001A58AD /* BluetoothReaderConnectionAlertsProvider.swift in Sources */,
Expand Down