Skip to content
Open
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
38 changes: 38 additions & 0 deletions cards/CardView/CardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,14 @@ struct CardView: View {
return HStack{
Text(heading)
.bold()
.accessibilityLabel(Text(heading))
Spacer()
if !model.isAuthenticated {
SecureField("", text: value)
.multilineTextAlignment(.trailing)
.accessibilityLabel(Text(heading))
.accessibilityHint(Text("Secure field"))
.accessibilityIdentifier("\(heading.lowercased())SecureField")
} else {
TextField("", text: value)
.multilineTextAlignment(.trailing)
Expand All @@ -43,13 +47,18 @@ struct CardView: View {
Image(systemName: "doc.on.doc")
}
})
.accessibilityLabel(Text(heading))
.accessibilityHint(Text(model.isEditing ? "Editable field" : "Read only"))
.accessibilityIdentifier("\(heading.lowercased())TextField")
}
}
.if (!model.isEditing, transform: { view in
view.onTapGesture(count: 2, perform: {
model.copyAction(with: value.wrappedValue)
})
})
.accessibilityElement(children: .combine)
.accessibilityIdentifier("\(heading.lowercased())ItemView")
}

fileprivate func getCardListView() -> some View {
Expand All @@ -72,6 +81,7 @@ struct CardView: View {

if heading == "Number" && !model.isEditing {
view.popoverTip(tip, arrowEdge: .top)
.accessibilityHint(Text("Double tap to copy card number"))
} else if heading == "Expiration" {
view.onChange(of: model.card.expiration) { _ , newValue in
if newValue.count == 2 && !newValue.contains("/") {
Expand All @@ -92,19 +102,27 @@ struct CardView: View {
Picker("Card Network", selection: $model.card.network) {
ForEach(CardNetwork.allCases) { pref in
Text(pref.rawValue)
.accessibilityLabel(Text(pref.rawValue))
}
}

.disabled(!model.isEditing)
.bold()
.accessibilityLabel(Text("Card Network"))
.accessibilityHint(Text(model.isEditing ? "Select card network" : "Card network"))
.accessibilityIdentifier("cardNetworkPicker")
}
Picker("Card Type", selection: $model.card.type) {
ForEach(CardType.allCases) { pref in
Text(pref.rawValue)
.accessibilityLabel(Text(pref.rawValue))
}
}
.disabled(!model.isEditing)
.bold()
.accessibilityLabel(Text("Card Type"))
.accessibilityHint(Text(model.isEditing ? "Select card type" : "Card type"))
.accessibilityIdentifier("cardTypePicker")
}
}

Expand All @@ -117,6 +135,9 @@ struct CardView: View {
view
.blur(radius: 10, opaque: true)
})
.accessibilityLabel(Text("Card image"))
.accessibilityHint(Text(model.isAuthenticated ? "Card image preview" : "Card image is blurred until authenticated"))
.accessibilityIdentifier("cardImage")
}
}

Expand All @@ -127,7 +148,9 @@ struct CardView: View {
VStack(alignment: .leading){
HStack {
Image(systemName: "photo")
.accessibilityHidden(true)
Text(model.cardImage == nil ? "Add Card Image" : "Change Card Image")
.accessibilityLabel(Text(model.cardImage == nil ? "Add Card Image" : "Change Card Image"))
}
.padding(.bottom)

Expand Down Expand Up @@ -163,9 +186,14 @@ struct CardView: View {
} label: {
HStack {
Image(systemName: "trash")
.accessibilityHidden(true)
Text("Remove Image")
.accessibilityLabel(Text("Remove Image"))
}
}
.accessibilityLabel(Text("Remove Image"))
.accessibilityHint(Text("Removes the selected card image"))
.accessibilityIdentifier("removeImageButton")
}
}
}
Expand All @@ -174,6 +202,9 @@ struct CardView: View {
ShareLink(item: model.card.toShareString()) {
Label("Click to share", systemImage: "square.and.arrow.up")
}
.accessibilityLabel(Text("Share card details"))
.accessibilityHint(Text("Shares your card details"))
.accessibilityIdentifier("shareLink")
Button(action: {
model.isEditing.toggle()
// if user is not editing, then he is done editing when button press
Expand All @@ -183,6 +214,9 @@ struct CardView: View {
}) {
Text(model.isEditing ? "Done" : "Edit")
}
.accessibilityLabel(Text(model.isEditing ? "Done editing" : "Edit card"))
.accessibilityHint(Text(model.isEditing ? "Finish editing card details" : "Edit card details"))
.accessibilityIdentifier("editDoneButton")
}
.disabled(!$model.isAuthenticated.wrappedValue)
.toolbar {
Expand All @@ -192,10 +226,14 @@ struct CardView: View {
model.isShowingScanner = true
}, label: {
Image(systemName: "camera.on.rectangle")
.accessibilityHidden(true)
})
.if(!model.isAddNewFlow, transform: { view in
view.hidden()
})
.accessibilityLabel(Text("Scan card"))
.accessibilityHint(Text("Opens camera to scan your card"))
.accessibilityIdentifier("scanCardButton")

// .screen was causing issues with camera session not closing
.fullScreenCover(isPresented: $model.isShowingScanner) {
Expand Down
1 change: 1 addition & 0 deletions cards/CreditCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ struct CreditCard: App {
var body: some Scene {
WindowGroup {
HomeView()
.accessibilityIdentifier("HomeView")
.task {
try? Tips.configure([
.displayFrequency(.immediate),
Expand Down
31 changes: 31 additions & 0 deletions cards/Home/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@ struct HomeView: View {
Section(header: Text("\(type.rawValue)s")){
ForEach(model.cardDataStore.cardsByType[type] ?? [], id: \.id) { card in
getRowforCards(with: card)
.accessibilityIdentifier("CardRow_\(card.id.uuidString)")
}
.onDelete { offsets in
model.deleteCard(at: offsets, inSection: type)
}
Button("Add a new \(type.rawValue)") {
model.showingPopover.toggle()
}
.accessibilityLabel(Text("Add a new \(type.rawValue) card"))
.accessibilityHint(Text("Adds a new \(type.rawValue) card"))
.accessibilityIdentifier("AddCardButton_\(type.rawValue)")
.sheet(isPresented: $model.showingPopover) {
NavigationView {
CardView(model: CardViewModel(
Expand All @@ -50,21 +54,31 @@ struct HomeView: View {
}
}
.navigationTitle("Cards")
.accessibilityLabel(Text("Cards"))
.onAppear {
model.cardDataStore.loadCards()
}
.toolbar{
NavigationLink(destination: SettingsView(model: SettingsViewModel())){
Image(systemName: "gear")
}
.accessibilityLabel(Text("Settings"))
.accessibilityHint(Text("Opens settings"))
.accessibilityIdentifier("SettingsButton")
}
.alert("Enable Biometrics",isPresented: model.$isFirstLaunch, actions: {
Button("Yes", role: .cancel) {
UserSettings.shared.isAuthEnabled = true
}
.accessibilityLabel(Text("Enable biometrics"))
.accessibilityHint(Text("Enables biometric authentication"))
.accessibilityIdentifier("EnableBiometricsYesButton")
Button("No", role: .destructive) {
UserSettings.shared.isAuthEnabled = false
}
.accessibilityLabel(Text("Do not enable biometrics"))
.accessibilityHint(Text("Disables biometric authentication"))
.accessibilityIdentifier("EnableBiometricsNoButton")
})
} detail: {
if let card = model.selectedCard {
Expand All @@ -76,6 +90,8 @@ struct HomeView: View {
}
else {
Text("Tap on a Card to view details")
.accessibilityLabel(Text("No card selected. Tap on a card to view details."))
.accessibilityIdentifier("NoCardSelectedText")
}
}
.whatsNewSheet()
Expand All @@ -88,16 +104,31 @@ struct HomeView: View {
.resizable()
.scaledToFit()
.frame(width: 36,height: 36)
.accessibilityLabel(Text("\(card.network.rawValue) logo"))
.accessibilityIdentifier("CardNetworkImage_\(card.id.uuidString)")

VStack(alignment: .leading){
if card.description != "" {
Text(card.description)
.accessibilityLabel(Text(card.description))
.accessibilityIdentifier("CardDescription_\(card.id.uuidString)")
.minimumScaleFactor(0.8)
} else {
Text(card.name)
.accessibilityLabel(Text(card.name))
.accessibilityIdentifier("CardName_\(card.id.uuidString)")
.minimumScaleFactor(0.8)
}
Text(card.number.toSecureCard())
.accessibilityLabel(Text("Card number ending in \(card.number.suffix(4))"))
.accessibilityIdentifier("CardNumber_\(card.id.uuidString)")
.minimumScaleFactor(0.8)
}
}
.accessibilityElement(children: .combine)
.accessibilityLabel(Text("\(card.description != "" ? card.description : card.name), card ending in \(card.number.suffix(4)), \(card.network.rawValue)"))
.accessibilityHint(Text("Double tap to view card details"))
.accessibilityIdentifier("CardRow_\(card.id.uuidString)")
}
}
}
20 changes: 20 additions & 0 deletions cards/Settings/AppSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,45 @@ struct AppSettingsView: View {
AnyView (
Section {
Toggle("Toggle Biometrics", isOn: UserSettings.shared.$isAuthEnabled)
.accessibilityLabel("Enable biometric authentication")
.accessibilityHint("Double tap to enable or disable biometric authentication for app security")
.accessibilityIdentifier("toggleBiometrics")
.focusable(true)
HStack(alignment: .center){
Text("Timeout (in seconds)")
.accessibilityLabel("Authentication timeout in seconds")
.accessibilityIdentifier("timeoutLabel")
Spacer()
TextField("", value: UserSettings.shared.$authTimeout, format: .number)
.keyboardType(.numberPad)
.fixedSize()
.accessibilityLabel("Timeout value")
.accessibilityHint("Enter the number of seconds before authentication times out")
.accessibilityIdentifier("timeoutTextField")
.focusable(true)
}
.accessibilityElement(children: .combine)
VStack(alignment: .leading){
Text("Number of card digits visible on home (Restart Required)")
.accessibilityLabel("Number of card digits visible on home. Restart required.")
.accessibilityIdentifier("cardDigitsLabel")
Slider(value: UserSettings.shared.$showNumber, in: 1...10,step: 1) {
Text("Steps")
} minimumValueLabel: {
Text("1")
} maximumValueLabel: {
Text("10")
}
.accessibilityLabel("Number of visible card digits")
.accessibilityHint("Adjust to set how many card digits are visible on the home screen. Restart required.")
.accessibilityIdentifier("cardDigitsSlider")
.focusable(true)
}
.accessibilityElement(children: .combine)
} header: {
Text("App Settings")
.accessibilityAddTraits(.isHeader)
.accessibilityIdentifier("appSettingsHeader")
}
)
}
Expand Down