Skip to content

Commit 5a6eac3

Browse files
authored
[Woo POS] [Barcodes] Play error sound when a barcode scan fails (#15746)
2 parents 222fc6f + 5e43402 commit 5a6eac3

File tree

8 files changed

+209
-42
lines changed

8 files changed

+209
-42
lines changed

WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ protocol PointOfSaleAggregateModelProtocol {
8282
private var startPaymentOnCardReaderConnection: AnyCancellable?
8383
private var cardReaderDisconnection: AnyCancellable?
8484

85+
private let soundPlayer: PointOfSaleSoundPlayerProtocol
86+
8587
private var cancellables: Set<AnyCancellable> = []
8688

8789
// Private storage of the concrete coordinator
@@ -108,6 +110,7 @@ protocol PointOfSaleAggregateModelProtocol {
108110
searchHistoryService: POSSearchHistoryProviding,
109111
popularPurchasableItemsController: PointOfSaleItemsControllerProtocol,
110112
barcodeScanService: PointOfSaleBarcodeScanServiceProtocol,
113+
soundPlayer: PointOfSaleSoundPlayerProtocol = PointOfSaleSoundPlayer(),
111114
paymentState: PointOfSalePaymentState = .card(.idle)) {
112115
self.purchasableItemsController = itemsController
113116
self.purchasableItemsSearchController = purchasableItemsSearchController
@@ -121,6 +124,7 @@ protocol PointOfSaleAggregateModelProtocol {
121124
self.paymentState = paymentState
122125
self.popularPurchasableItemsController = popularPurchasableItemsController
123126
self.barcodeScanService = barcodeScanService
127+
self.soundPlayer = soundPlayer
124128

125129
publishCardReaderConnectionStatus()
126130
publishPaymentMessages()
@@ -190,6 +194,7 @@ extension PointOfSaleAggregateModel {
190194
} catch {
191195
DDLogInfo("Failed to find item by barcode: \(error)")
192196
cart.updateLoadingItem(id: placeholderItemID, with: error)
197+
await soundPlayer.playSound(.barcodeScanFailure)
193198
}
194199
}
195200
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import Foundation
2+
import AVFoundation
3+
4+
struct PointOfSaleSound: Equatable, Hashable {
5+
let name: String
6+
let type: String
7+
8+
static var barcodeScanFailure: PointOfSaleSound {
9+
PointOfSaleSound(name: "pos_scan_failure", type: "mp3")
10+
}
11+
}
12+
13+
protocol PointOfSaleSoundPlayerProtocol {
14+
func playSound(_ sound: PointOfSaleSound) async
15+
}
16+
17+
final class PointOfSaleSoundPlayer: PointOfSaleSoundPlayerProtocol {
18+
private var audioPlayer: AVAudioPlayer?
19+
private var playerCache: [PointOfSaleSound: AVAudioPlayer] = [:]
20+
21+
@MainActor
22+
func playSound(_ sound: PointOfSaleSound) async {
23+
guard let url = Bundle.main.url(forResource: sound.name, withExtension: sound.type) else {
24+
DDLogError("Sound file not found: \(sound.name).\(sound.type)")
25+
return
26+
}
27+
28+
if let cachedPlayer = playerCache[sound] {
29+
if !cachedPlayer.isPlaying {
30+
cachedPlayer.currentTime = 0
31+
cachedPlayer.play()
32+
}
33+
return
34+
}
35+
36+
do {
37+
audioPlayer = try AVAudioPlayer(contentsOf: url)
38+
audioPlayer?.prepareToPlay()
39+
audioPlayer?.play()
40+
playerCache[sound] = audioPlayer
41+
} catch {
42+
DDLogError("Failed to play sound: \(error)")
43+
}
44+
}
45+
}
Binary file not shown.

WooCommerce/Resources/HTML/licenses.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
background-color: #f7f7f7;
1010
}
1111
a {
12-
color: #96588a;
12+
color: #720EEC;
1313
}
1414

1515
@media (prefers-color-scheme: dark) {
@@ -18,7 +18,7 @@
1818
background-color: transparent;
1919
}
2020
a {
21-
color: #B07DD1;
21+
color: #873EFF;
2222
}
2323
}
2424
</style>
@@ -62,6 +62,7 @@ <h4>The following libraries are licensed under <a href="https://opensource.org/l
6262
<h4>Additional credits:</h4>
6363
<ul>
6464
<li><a href="https://freesound.org/people/Zott820/sounds/209578/">Cash register sound</a> ("cha-ching") by CapsLok remixed for extra depth by Zott820</li>
65+
<li><a href="https://pixabay.com/sound-effects/error-message-182475/">Barcode scan error sound</a> by <a href="https://pixabay.com/users/voicebosch-30143949/">VoiceBosch</a> from <a href="https://pixabay.com">Pixabay</a></li>
6566
</ul>
6667
</body>
6768
</html>

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@
8787
01F42C162CE34AB8003D0A5A /* CardPresentModalTapToPaySuccessEmailSent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F42C152CE34AB3003D0A5A /* CardPresentModalTapToPaySuccessEmailSent.swift */; };
8888
01F42C182CE34AD2003D0A5A /* CardPresentModalSuccessEmailSent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F42C172CE34AD1003D0A5A /* CardPresentModalSuccessEmailSent.swift */; };
8989
01F579952C7DE709008BCA28 /* PointOfSaleCardPresentPaymentCaptureErrorMessageViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F579942C7DE709008BCA28 /* PointOfSaleCardPresentPaymentCaptureErrorMessageViewModelTests.swift */; };
90+
01F935532DFC0B9900B50B03 /* PointOfSaleSoundPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F935522DFC0B9700B50B03 /* PointOfSaleSoundPlayer.swift */; };
91+
01F935572DFC0C6400B50B03 /* pos_scan_failure.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 01F935562DFC0C6400B50B03 /* pos_scan_failure.mp3 */; };
92+
01F935592DFC0D4C00B50B03 /* MockPointOfSaleSoundPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F935582DFC0D4800B50B03 /* MockPointOfSaleSoundPlayer.swift */; };
9093
01FB19582C6E901800A44FF0 /* DynamicHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FB19572C6E901800A44FF0 /* DynamicHStack.swift */; };
9194
0202B68D23876BC100F3EBE0 /* ProductsTabProductViewModel+ProductVariation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0202B68C23876BC100F3EBE0 /* ProductsTabProductViewModel+ProductVariation.swift */; };
9295
0202B6922387AB0C00F3EBE0 /* WooTab+Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0202B6912387AB0C00F3EBE0 /* WooTab+Tag.swift */; };
@@ -3348,6 +3351,9 @@
33483351
01F42C152CE34AB3003D0A5A /* CardPresentModalTapToPaySuccessEmailSent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalTapToPaySuccessEmailSent.swift; sourceTree = "<group>"; };
33493352
01F42C172CE34AD1003D0A5A /* CardPresentModalSuccessEmailSent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalSuccessEmailSent.swift; sourceTree = "<group>"; };
33503353
01F579942C7DE709008BCA28 /* PointOfSaleCardPresentPaymentCaptureErrorMessageViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCardPresentPaymentCaptureErrorMessageViewModelTests.swift; sourceTree = "<group>"; };
3354+
01F935522DFC0B9700B50B03 /* PointOfSaleSoundPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleSoundPlayer.swift; sourceTree = "<group>"; };
3355+
01F935562DFC0C6400B50B03 /* pos_scan_failure.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = pos_scan_failure.mp3; sourceTree = "<group>"; };
3356+
01F935582DFC0D4800B50B03 /* MockPointOfSaleSoundPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPointOfSaleSoundPlayer.swift; sourceTree = "<group>"; };
33513357
01FB19572C6E901800A44FF0 /* DynamicHStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicHStack.swift; sourceTree = "<group>"; };
33523358
0202B68C23876BC100F3EBE0 /* ProductsTabProductViewModel+ProductVariation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProductsTabProductViewModel+ProductVariation.swift"; sourceTree = "<group>"; };
33533359
0202B6912387AB0C00F3EBE0 /* WooTab+Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WooTab+Tag.swift"; sourceTree = "<group>"; };
@@ -6634,6 +6640,23 @@
66346640
path = Location;
66356641
sourceTree = "<group>";
66366642
};
6643+
01F935542DFC0C3B00B50B03 /* Audio */ = {
6644+
isa = PBXGroup;
6645+
children = (
6646+
01F935552DFC0C5A00B50B03 /* Resources */,
6647+
01F935522DFC0B9700B50B03 /* PointOfSaleSoundPlayer.swift */,
6648+
);
6649+
path = Audio;
6650+
sourceTree = "<group>";
6651+
};
6652+
01F935552DFC0C5A00B50B03 /* Resources */ = {
6653+
isa = PBXGroup;
6654+
children = (
6655+
01F935562DFC0C6400B50B03 /* pos_scan_failure.mp3 */,
6656+
);
6657+
path = Resources;
6658+
sourceTree = "<group>";
6659+
};
66376660
0202B6932387ACE000F3EBE0 /* TabBar */ = {
66386661
isa = PBXGroup;
66396662
children = (
@@ -7274,6 +7297,7 @@
72747297
02055B132D5DAB6400E51059 /* POSCornerRadiusStyle.swift */,
72757298
020564972D5DC96600E51059 /* POSShadowStyle.swift */,
72767299
02F3884B2D6C38BB00619396 /* POSErrorAndAlertIconSize.swift */,
7300+
01F935542DFC0C3B00B50B03 /* Audio */,
72777301
);
72787302
path = Utils;
72797303
sourceTree = "<group>";
@@ -7847,6 +7871,7 @@
78477871
02CD3BFC2C35D01600E575C4 /* Mocks */ = {
78487872
isa = PBXGroup;
78497873
children = (
7874+
01F935582DFC0D4800B50B03 /* MockPointOfSaleSoundPlayer.swift */,
78507875
01AB2D152DDC8CD600AA67FD /* MockAnalytics.swift */,
78517876
686A71B72DC9EB6D0006E835 /* MockPOSSearchHistoryService.swift */,
78527877
2050D2652DF07BF700C25211 /* MockPointOfSaleBarcodeScanService.swift */,
@@ -14902,6 +14927,7 @@
1490214927
456AB0E8283E610500019CFF /* WCShipInstallTableViewCell.xib in Resources */,
1490314928
02162727237963AF000208D2 /* ProductFormViewController.xib in Resources */,
1490414929
D8EE9693264D328A0033B2F9 /* LegacyReceiptViewController.xib in Resources */,
14930+
01F935572DFC0C6400B50B03 /* pos_scan_failure.mp3 in Resources */,
1490514931
DE1B030E268DD01A00804330 /* ReviewOrderViewController.xib in Resources */,
1490614932
45FDDD66267784AD00ADACE8 /* ShippingLabelSummaryTableViewCell.xib in Resources */,
1490714933
E1D4E84526776AD900256B83 /* HeadlineTableViewCell.xib in Resources */,
@@ -16655,6 +16681,7 @@
1665516681
20A3AFE32B10EF860033AF2D /* CardReaderSettingsFlowPresentingView.swift in Sources */,
1665616682
CE14452E2188C11700A991D8 /* ZendeskManager.swift in Sources */,
1665716683
02577A7F2BFC4BB300B63FE6 /* PaymentMethodsWrapperHostingController.swift in Sources */,
16684+
01F935532DFC0B9900B50B03 /* PointOfSaleSoundPlayer.swift in Sources */,
1665816685
02BA53432A380D7D0069224D /* ProductDescriptionAICoordinator.swift in Sources */,
1665916686
FE28F7122684CA29004465C7 /* RoleErrorViewController.swift in Sources */,
1666016687
B5BE75DB213F1D1E00909A14 /* OverlayMessageView.swift in Sources */,
@@ -17495,6 +17522,7 @@
1749517522
02645D8227BA20A30065DC68 /* InboxViewModelTests.swift in Sources */,
1749617523
57ABE36824EB048A00A64F49 /* MockSwitchStoreUseCase.swift in Sources */,
1749717524
311F827626CD8AB100DF5BAD /* MockCardReaderSettingsAlerts.swift in Sources */,
17525+
01F935592DFC0D4C00B50B03 /* MockPointOfSaleSoundPlayer.swift in Sources */,
1749817526
26A280D62B45F00F00ACEE87 /* OrderNotificationViewModelTests.swift in Sources */,
1749917527
EE45E2C22A42C9D80085F227 /* ProductDescriptionAITooltipUseCaseTests.swift in Sources */,
1750017528
CEEF74282B99F57A00B03948 /* RevenueReportCardViewModelTests.swift in Sources */,

WooCommerce/WooCommerceTests/POS/Mocks/MockPointOfSaleBarcodeScanService.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import Foundation
22
import Yosemite
33

4-
struct MockPointOfSaleBarcodeScanService: PointOfSaleBarcodeScanServiceProtocol {
4+
class MockPointOfSaleBarcodeScanService: PointOfSaleBarcodeScanServiceProtocol {
5+
var errorToThrow: PointOfSaleBarcodeScanError?
6+
57
func getItem(barcode: String) async throws(PointOfSaleBarcodeScanError) -> POSItem {
8+
if let error = errorToThrow {
9+
throw error
10+
}
11+
612
return .simpleProduct(POSSimpleProduct(
713
id: UUID(),
814
name: "Scanned Item",
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import Foundation
2+
@testable import WooCommerce
3+
4+
final class MockPointOfSaleSoundPlayer: PointOfSaleSoundPlayerProtocol {
5+
var onPlaySound: ((PointOfSaleSound) -> Void)?
6+
7+
func playSound(_ sound: PointOfSaleSound) {
8+
onPlaySound?(sound)
9+
}
10+
}

0 commit comments

Comments
 (0)