8000 BIT-348, BIT-351, & BIT-1121: Add, Save, View, & Edit Card Type Cipher by eliot-livefront Β· Pull Request #229 Β· bitwarden/ios Β· GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

BIT-348, BIT-351, & BIT-1121: Add, Save, View, & Edit Card Type Cipher #229

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

Merged
merged 10 commits into from
Jan 2, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,58 @@ extension CipherView {
)
}

static func cardFixture(
attachments: [AttachmentView]? = nil,
card: CardView = CardView.fixture(),
collectionIds: [String] = [],
creationDate: DateTime = Date(year: 2023, month: 11, day: 5, hour: 9, minute: 41),
deletedDate: Date? = nil,
edit: Bool = true,
favorite: Bool = false,
fields: [FieldView]? = nil,
folderId: String? = nil,
id: String = "8675",
key: String? = nil,
localData: LocalDataView? = nil,
name: String = "Bitwarden",
notes: String? = nil,
organizationId: String? = nil,
organizationUseTotp: Bool = false,
passwordHistory: [PasswordHistoryView]? = nil,
reprompt: BitwardenSdk.CipherRepromptType = .none,
revisionDate: Date = Date(year: 2023, month: 11, day: 5, hour: 9, minute: 41),
viewPassword: Bool = true
) -> CipherView {
CipherView(
id: id,
organizationId: organizationId,
folderId: folderId,
collectionIds: collectionIds,
key: key,
name: name,
notes: notes,
type: .card,
login: nil,
identity: nil,
card: card,
secureNote: nil,
favorite: favorite,
reprompt: reprompt,
organizationUseTotp: organizationUseTotp,
edit: edit,
viewPassword: viewPassword,
localData: localData,
attachments: attachments,
fields: fields,
passwordHistory: passwordHistory,
creationDate: creationDate,
deletedDate: deletedDate,
revisionDate: revisionDate
)
}

static func loginFixture(
attachments: [AttachmentView]? = nil,
card: CardView? = nil,
collectionIds: [String] = [],
creationDate: DateTime = Date(year: 2023, month: 11, day: 5, hour: 9, minute: 41),
deletedDate: Date? = nil,
Expand All @@ -127,7 +176,6 @@ extension CipherView {
fields: [FieldView]? = nil,
folderId: String? = nil,
id: String = "8675",
identity: IdentityView? = nil,
key: String? = nil,
localData: LocalDataView? = nil,
login: BitwardenSdk.LoginView = .fixture(),
Expand All @@ -138,8 +186,6 @@ extension CipherView {
passwordHistory: [PasswordHistoryView]? = nil,
reprompt: BitwardenSdk.CipherRepromptType = .none,
revisionDate: Date = Date(year: 2023, month: 11, day: 5, hour: 9, minute: 41),
secureNote: SecureNoteView? = nil,
type: BitwardenSdk.CipherType = .login,
viewPassword: Bool = true
) -> CipherView {
CipherView(
Expand All @@ -150,11 +196,11 @@ extension CipherView {
key: key,
name: name,
notes: notes,
type: type,
type: .login,
login: login,
identity: identity,
card: card,
secureNote: secureNote,
identity: nil,
card: nil,
secureNote: nil,
favorite: favo 10000 rite,
reprompt: reprompt,
organizationUseTotp: organizationUseTotp,
Expand Down Expand Up @@ -191,6 +237,26 @@ extension Collection {
}
}

extension BitwardenSdk.CardView {
static func fixture(
brand: String? = nil,
cardholderName: String? = nil,
code: String? = nil,
expMonth: String? = nil,
expYear: String? = nil,
number: String? = nil
) -> BitwardenSdk.CardView {
BitwardenSdk.CardView(
cardholderName: cardholderName,
expMonth: expMonth,
expYear: expYear,
code: code,
brand: brand,
number: number
)
}
}

extension CollectionView {
static func fixture(
externalId: String = "",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// MARK: AddEditCardItemAction

/// An enum of actions for adding or editing a card Item in its add/edit state.
///
enum AddEditCardItemAction: Equatable {
/// The brand of the card changed.
case brandChanged(DefaultableType<CardComponent.Brand>)

/// The name of the card holder changed.
case cardholderNameChanged(String)

/// The number of the card changed.
case cardNumberChanged(String)

/// The security code of the card changed.
case cardSecurityCodeChanged(String)

/// The expiration month of the card changed.
case expirationMonthChanged(DefaultableType<CardComponent.Month>)

/// The expiration year of the card changed.
case expirationYearChanged(String)

/// Toggle for code visibility changed.
case toggleCodeVisibilityChanged(Bool)

/// Toggle for number visibility changed.
case toggleNumberVisibilityChanged(Bool)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// MARK: AddEditCardItemState

/// A protocol for a sendable type that models a Card Item in it's add/edit state.
///
protocol AddEditCardItemState: Equatable, Sendable {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ€” I'm curious the benefit we get from having this protocol and ViewCardItemState over using CardItemState directly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My goal was to build in some separation from the State to the ViewState and remove the ability to mutate the struct in a view configuration. I build this pattern to match the one used for CipherItemState, AddEditItemState, and ViewVaultItemState

/// The brand of the card.
var brand: DefaultableType<CardComponent.Brand> { get set }

/// The name of the card holder.
var cardholderName: String { get set }

/// The number of the card.
var cardNumber: String { get set }

/// The security code of the card.
var cardSecurityCode: String { get set }

/// The expiration month of the card.
var expirationMonth: DefaultableType<CardComponent.Month> { get set }

/// The expiration year of the card.
var expirationYear: String { get set }

/// The visibility of the security code.
var isCodeVisible: Bool { get set }

/// The visibility of the card number.
var isNumberVisible: Bool { get set }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import SwiftUI

// MARK: - AddEditCardItemView

/// A view that allows the user to add or edit a card item for a vault.
///
struct AddEditCardItemView: View {
// MARK: Properties

/// The `Store` for this view.
@ObservedObject var store: Store<any AddEditCardItemState, AddEditCardItemAction, AddEditItemEffect>

var body: some View {
LazyVStack(spacing: 16.0) {
BitwardenTextField(
title: Localizations.cardholderName,
text: store.binding(
get: \.cardholderName,
send: AddEditCardItemAction.cardholderNameChanged
)
)

BitwardenTextField(
title: Localizations.number,
text: store.binding(
get: \.cardNumber,
send: AddEditCardItemAction.cardNumberChanged
), isPasswordVisible: store.binding(
get: \.isNumberVisible,
send: AddEditCardItemAction.toggleNumberVisibilityChanged
)
)
.textFieldConfiguration(.password)

BitwardenMenuField(
title: Localizations.brand,
options: DefaultableType<CardComponent.Brand>.allCases,
selection: store.binding(
get: \.brand,
send: AddEditCardItemAction.brandChanged
)
)

BitwardenMenuField(
title: Localizations.expirationMonth,
options: DefaultableType<CardComponent.Month>.allCases,
selection: store.binding(
get: \.expirationMonth,
send: AddEditCardItemAction.expirationMonthChanged
)
)

BitwardenTextField(
title: Localizations.expirationYear,
text: store.binding(
get: \.expirationYear,
send: AddEditCardItemAction.expirationYearChanged
)
)

BitwardenTextField(
title: Localizations.securityCode,
text: store.binding(
get: \.cardSecurityCode,
send: AddEditCardItemAction.cardSecurityCodeChanged
), isPasswordVisible: store.binding(
get: \.isCodeVisible,
send: AddEditCardItemAction.toggleCodeVisibilityChanged
)
)
.textFieldConfiguration(.password)
}
}
}

#if DEBUG
struct AddEditCardItemView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
ScrollView {
AddEditCardItemView(
store: Store(
processor: StateProcessor(
state: CardItemState() as (any AddEditCardItemState)
)
)
)
.padding(16)
}
.background(Asset.Colors.backgroundSecondary.swiftUIColor)
.navigationBar(title: "Empty Add Edit State", titleDisplayMode: .inline)
}
.previewDisplayName("Empty Add Edit State")

NavigationView {
ScrollView {
AddEditCardItemView(
store: Store(
processor: StateProcessor(
state: {
var state = CardItemState()
state.brand = .custom(.visa)
state.cardNumber = "4400123456789"
state.cardSecurityCode = "123"
state.cardholderName = "Bitwarden User"
state.expirationMonth = .custom(.aug)
state.expirationYear = "1989"
return state
}() as (any AddEditCardItemState)
)
)
)
.padding(16)
}
.background(Asset.Colors.backgroundSecondary.swiftUIColor)
.navigationBar(title: "Hidden Add Edit State", titleDisplayMode: .inline)
}
.previewDisplayName("Hidden Add Edit State")

NavigationView {
ScrollView {
AddEditCardItemView(
store: Store(
processor: StateProcessor(
state: {
var state = CardItemState()
state.brand = .custom(.visa)
state.cardNumber = "4400123456789"
state.cardSecurityCode = "123"
state.cardholderName = "Bitwarden User"
state.expirationMonth = .custom(.aug)
state.expirationYear = "1989"
state.isCodeVisible = true
state.isNumberVisible = true
return state
}() as (any AddEditCardItemState)
)
)
)
.padding(16)
}
.background(Asset.Colors.backgroundSecondary.swiftUIColor)
.navigationBar(title: "Visible Add Edit State", titleDisplayMode: .inline)
}
.previewDisplayName("Visible Add Edit State")
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import SnapshotTesting
import XCTest

@testable import BitwardenShared

class AddEditCardItemViewTests: BitwardenTestCase {
// MARK: Snapshots

/// Test preview snapshots of the `AddEditCardItemView`.
///
func test_snapshot_addEditCardItemView() {
for preview in AddEditCardItemView_Previews._allPreviews {
assertSnapshots(
matching: preview.content,
as: [
.defaultPortrait,
.defaultPortraitDark,
.tallPortraitAX5(heightMultiple: 1.75),
]
)
}
}
}
Loading
0