Skip to content

Commit

Permalink
Final sample app updates
Browse files Browse the repository at this point in the history
  • Loading branch information
tamerbader committed Nov 19, 2024
1 parent 8ef5236 commit 2b28348
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -433,14 +433,15 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "Donut Counter";
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Square uses Bluetooth to connect and communicate with Square readers and compatible accessories.\n";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Square needs to know where transactions take place to reduce risk and minimize payment disputes.\n";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Some Square readers use the microphone to communicate payment card data to your device.\n";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Square uses location to know where transactions take place to reduce risk and minimize payment disputes.\n";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Square’s magstripe reader uses the microphone to communicate payment card data to your device.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen.storyboard";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UIUserInterfaceStyle = Light;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand Down Expand Up @@ -471,14 +472,15 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "Donut Counter";
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "Square uses Bluetooth to connect and communicate with Square readers and compatible accessories.\n";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Square needs to know where transactions take place to reduce risk and minimize payment disputes.\n";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Some Square readers use the microphone to communicate payment card data to your device.\n";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Square uses location to know where transactions take place to reduce risk and minimize payment disputes.\n";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Square’s magstripe reader uses the microphone to communicate payment card data to your device.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen.storyboard";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UIUserInterfaceStyle = Light;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,6 @@ struct AuthorizationButtonStyle: ButtonStyle {
}
}

struct MockReaderButtonStyle: ButtonStyle {
var isPresented: Bool

func makeBody(configuration: Configuration) -> some View {
configuration.label
.frame(maxWidth: .infinity)
.padding(16)
.background(isPresented ? Color.Button.MockReader.hideMockReaderBackground : Color.Button.MockReader.showMockReaderBackground)
.foregroundStyle(Color.Button.MockReader.foreground)
.opacity(configuration.isPressed ? 0.8 : 1.0)
.clipShape(.rect(cornerRadius: 6))
}
}

struct DismissButton: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ extension String {

enum Microphone {
static var microphonePermissionTitle: String = "Microphone"
static var microphonePermissionDescription: String = "Square’s R4 reader uses the microphone jack to communicate payment card data to your device. You should ask for this permission if you are using an R4 reader."
static var microphonePermissionDescription: String = "Square’s magstripe reader uses the microphone to communicate payment card data to your device. You should ask for this permission if you are using a magstripe reader."
}

enum AuthorizationButton {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,31 @@ import MockReaderUI
#endif

struct HomeView: View {

private enum Constants {
static let headerViewBottomPadding: CGFloat = 50
static let donutImageWidth: CGFloat = 248
static let donutImageBottomPadding: CGFloat = 50
static let appNameTextBottomPadding: CGFloat = 32
static let authorizationStatusTextTopPadding: CGFloat = 10
static let iPadPadding: CGFloat = 50
}

@SwiftUI.Environment(\.horizontalSizeClass) private var horizontalSizeClass

@State private var presentingPermissionsView: Bool = false
@State var isMockReaderPresented: Bool = false
@State var viewModel: HomeViewModel
@State private var isMockReaderPresented: Bool = false
@State private var viewModel: HomeViewModel

private let viewHolder: MobilePaymentsSDKViewHolder = MobilePaymentsSDKViewHolder()
private var mobilePaymentsSDK: SDKManager { viewModel.mobilePaymentsSDK }
private var isIPad: Bool {
UIDevice.current.userInterfaceIdiom == .pad
horizontalSizeClass == .regular
}
private var isMockReaderAvailable: Bool {
viewModel.authorizationState == .authorized && mobilePaymentsSDK.settingsManager.sdkSettings.environment == .sandbox
}

private let viewHolder: MobilePaymentsSDKViewHolder = MobilePaymentsSDKViewHolder()

init(viewModel: HomeViewModel) {
self.viewModel = viewModel
Expand All @@ -33,16 +45,13 @@ struct HomeView: View {
VStack {
headerView
contentView
Spacer()
if isMockReaderAvailable {
mockReaderButton
}
Spacer()
}
.alert(isPresented: $viewModel.showPaymentStatusAlert) {
paymentStatusAlert
}
}
.padding([.leading, .trailing], isIPad ? 50 : nil)
.padding([.leading, .trailing], isIPad ? Constants.iPadPadding : nil)
.padding([.top, .bottom])
.background(Color.white)
.onChange(of: viewModel.authorizationState) { oldValue, newValue in
Expand All @@ -61,21 +70,21 @@ struct HomeView: View {
Spacer()
permissionsButton
}
.padding(.bottom, 50)
.padding(.bottom, Constants.headerViewBottomPadding)
}

private var contentView: some View {
VStack {
Image("donut")
.resizable()
.aspectRatio(1.0, contentMode: .fit)
.frame(width: 248)
.padding(.bottom, 54)
.frame(width: Constants.donutImageWidth)
.padding(.bottom, Constants.donutImageBottomPadding)
Text(String.Home.appTitle)
.font(.title)
.fontWeight(.bold)
.foregroundColor(.black)
.padding([.bottom], 32)
.padding([.bottom], Constants.appNameTextBottomPadding)
buyDonutButton
.buttonStyle(BuyButtonStyle())
.font(.body)
Expand All @@ -86,7 +95,7 @@ struct HomeView: View {
.multilineTextAlignment(.leading)
.font(.subheadline)
.foregroundStyle(Color.Text.warning)
.padding(.top, 10)
.padding(.top, Constants.authorizationStatusTextTopPadding)
}
}
}
Expand Down Expand Up @@ -192,30 +201,10 @@ struct HomeView: View {
Text(String.Home.MockReaderButton.showMockReaderTitle)
}
}
.buttonStyle(MockReaderButtonStyle(isPresented: isMockReaderPresented))
.font(.body)
.fontWeight(.semibold)
}

// MARK: - Alerts

private var paymentStatusAlert: Alert {
switch viewModel.lastPaymentStatus {
case .completed(let payment):
Alert(
title: Text(String.Home.PaymentStatusAlert.paymentCompletedTitle),
message: Text("\(payment.paymentDescription?.debugDescription ?? "")")
)
case .failure(let error):
Alert(
title: Text(String.Home.PaymentStatusAlert.paymentFailedTitle),
message: Text("\(error.localizedDescription)")
)
case .canceled, .none:
Alert(title: Text(String.Home.PaymentStatusAlert.paymentCanceledTitle))
}
}

// MARK: - Mock Reader UI

private func presentMockReader() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,7 @@ import MockReaderUI
#endif

@Observable class HomeViewModel: PaymentManagerDelegate {

enum PaymentStatus {
case completed(Payment)
case failure(Error)
case canceled
}

var showPaymentStatusAlert: Bool = false
var lastPaymentStatus: PaymentStatus? = nil
var authorizationState: AuthorizationState

let mobilePaymentsSDK: SDKManager
Expand Down Expand Up @@ -51,9 +43,6 @@ import MockReaderUI
// for the transaction from your backend or generating it locally, prior to calling the
// `startPayment` method.
Config.localSalesID = String(UUID().uuidString.prefix(8))

lastPaymentStatus = .completed(payment)
showPaymentStatusAlert = true
}

func paymentManager(
Expand Down Expand Up @@ -81,9 +70,6 @@ import MockReaderUI
// idempotency key since it has been used, and a new key will be generated when the payment is restarted.
idempotencyKeyStorage.delete(id: Config.localSalesID)
}

lastPaymentStatus = .failure(error)
showPaymentStatusAlert = true
}

func paymentManager(
Expand All @@ -96,9 +82,6 @@ import MockReaderUI
// It is essential to delete the idempotency key associated with this sale, allowing
// a new key to be generated if the transaction is retried using the same custom ID.
idempotencyKeyStorage.delete(id: Config.localSalesID)

lastPaymentStatus = .canceled
showPaymentStatusAlert = true
}

private func refreshAuthorizationState() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,7 @@ struct PermissionsRow: View {
.foregroundColor(Color.Permissions.iconColor)
.padding(.leading, 16)
.onTapGesture {
if !isPermissionGranted {
tapAction?()
}
tapAction?()
}
}
.padding(.bottom, 15)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,37 @@ import SquareMobilePaymentsSDK //add

struct PermissionsView: View {

@State var viewModel: PermissionsViewModel
@Binding var presentingPermissionsView: Bool
private enum Constants {
static let permissionsViewBottomPadding: CGFloat = 16
static let iPadPadding: CGFloat = 50
static let authorizationButtonHeight: CGFloat = 48
static let authorizationStatusTextTopPadding: CGFloat = 10
}

@SwiftUI.Environment(\.horizontalSizeClass) private var horizontalSizeClass
@State private var viewModel: PermissionsViewModel
@Binding private var presentingPermissionsView: Bool

private var mobilePaymentsSDK: SDKManager { viewModel.mobilePaymentsSDK }
private var isIPad: Bool {
UIDevice.current.userInterfaceIdiom == .pad
horizontalSizeClass == .regular
}
private var isAuthorized: Bool {
viewModel.authorizationState == .authorized
}

init(viewModel: PermissionsViewModel, presentingPermissionsView: Binding<Bool>) {
self.viewModel = viewModel
self._presentingPermissionsView = presentingPermissionsView
}

var body: some View {
ZStack {
VStack(alignment: .center) {
headerView
ScrollView {
permissionsView
.padding(.bottom, 16)
.padding(.bottom, Constants.permissionsViewBottomPadding)
authorizationButton
authorizationStatus
Spacer()
Expand All @@ -32,7 +45,7 @@ struct PermissionsView: View {
UIScrollView.appearance().bounces = true
}
}
.padding([.leading, .trailing], isIPad ? 50 : nil)
.padding([.leading, .trailing], isIPad ? Constants.iPadPadding : nil)
.padding([.top, .bottom])
}
.background(.white)
Expand All @@ -41,23 +54,22 @@ struct PermissionsView: View {
// MARK: - Subviews

private var headerView: some View {
HStack {
Button {
presentingPermissionsView = false
} label: {
Image(systemName: "xmark")
.font(.body)
.fontWeight(.medium)
ZStack {
HStack {
Button {
presentingPermissionsView = false
} label: {
Image(systemName: "xmark")
.font(.body)
.fontWeight(.medium)
}
.buttonStyle(DismissButton())
Spacer()
}
.buttonStyle(DismissButton())
Spacer()
Text(String.Permissions.headerTitle)
.font(.title2)
.fontWeight(.bold)
.foregroundStyle(.black)
.padding(.trailing, 45)

Spacer()
}
}

Expand Down Expand Up @@ -111,7 +123,7 @@ struct PermissionsView: View {
}
}
)
.frame(height: 48)
.frame(height: Constants.authorizationButtonHeight)
.buttonStyle(AuthorizationButtonStyle(isAuthorized: isAuthorized))
.font(.headline)
.disabled(viewModel.isLoading)
Expand All @@ -124,7 +136,7 @@ struct PermissionsView: View {
.multilineTextAlignment(.leading)
.font(.subheadline)
.foregroundStyle(authorizationStatusForegroundColor)
.padding(.top, 10)
.padding(.top, Constants.authorizationStatusTextTopPadding)
}

// MARK: - Private Properties
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,15 @@ import SwiftUI
}

func requestMicrophone() {
AVCaptureDevice.requestAccess(for: .audio) { [weak self] _ in
self?.refreshMicrophonePermission()
switch AVAudioApplication.shared.recordPermission {
case .undetermined:
AVCaptureDevice.requestAccess(for: .audio) { [weak self] _ in
self?.refreshMicrophonePermission()
}
case .denied:
self.openAppSettings()
default:
return
}
}

Expand Down Expand Up @@ -83,7 +90,7 @@ import SwiftUI
}

private func refreshMicrophonePermission() {
switch AVAudioSession.sharedInstance().recordPermission {
switch AVAudioApplication.shared.recordPermission {
case .granted:
isMicrophonePermissionGranted = true
default:
Expand Down

0 comments on commit 2b28348

Please sign in to comment.