Skip to content

Commit

Permalink
Merge branch 'main' into long-press
Browse files Browse the repository at this point in the history
  • Loading branch information
KatherineInCode committed Apr 16, 2024
2 parents 0d4c922 + e8d889d commit 305516d
Show file tree
Hide file tree
Showing 46 changed files with 973 additions and 99 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha }}

- name: Scan with Checkmarx
uses: checkmarx/ast-github-action@749fec53e0db0f6404a97e2e0807c3e80e3583a7 #2.0.23
uses: checkmarx/ast-github-action@8a59a15b86b4e2f35b974222d1f516eedfe2d585 # 2.0.24
env:
INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}"
with:
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import AuthenticatorShared
import FirebaseCore
import FirebaseCrashlytics

/// An `ErrorReporter` that logs non-fatal errors to Crashlytics for investigation.
///
final class CrashlyticsErrorReporter: ErrorReporter {
// MARK: ErrorReporter Properties

var isEnabled: Bool {
get { Crashlytics.crashlytics().isCrashlyticsCollectionEnabled() }
set {
Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(newValue)
}
}

// MARK: Initialization

/// Initialize the `CrashlyticsErrorReporter`.
///
init() {
FirebaseApp.configure()
}

// MARK: ErrorReporter

func log(error: Error) {
// Don't log networking related errors to Crashlytics.
guard !error.isNetworkingError else { return }

Crashlytics.crashlytics().record(error: error)
}
}
9 changes: 9 additions & 0 deletions AuthenticatorShared/Core/Platform/Services/StateService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ protocol StateService: AnyObject {
///
func getShowWebIcons() async -> Bool

/// Whether the user has seen the welcome tutorial.
///
var hasSeenWelcomeTutorial: Bool { get set }

/// Sets the app theme.
///
/// - Parameter appTheme: The new app theme.
Expand Down Expand Up @@ -77,6 +81,11 @@ actor DefaultStateService: StateService {
set { appSettingsStore.appLocale = newValue.value }
}

nonisolated var hasSeenWelcomeTutorial: Bool {
get { appSettingsStore.hasSeenWelcomeTutorial }
set { appSettingsStore.hasSeenWelcomeTutorial = newValue }
}

// MARK: Private Properties

/// The service that persists app settings.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ protocol AppSettingsStore: AnyObject {
/// Whether to disable the website icons.
var disableWebIcons: Bool { get set }

/// Whether the user has seen the welcome tutorial.
var hasSeenWelcomeTutorial: Bool { get set }

/// The user ID for the local user
var localUserId: String { get }

Expand Down Expand Up @@ -162,6 +165,7 @@ extension DefaultAppSettingsStore: AppSettingsStore {
case appTheme
case clearClipboardValue(userId: String)
case disableWebIcons
case hasSeenWelcomeTutorial
case migrationVersion

/// Returns the key used to store the data under for retrieving it later.
Expand All @@ -178,6 +182,8 @@ extension DefaultAppSettingsStore: AppSettingsStore {
key = "clearClipboard_\(userId)"
case .disableWebIcons:
key = "disableFavicon"
case .hasSeenWelcomeTutorial:
key = "hasSeenWelcomeTutorial"
case .migrationVersion:
key = "migrationVersion"
}
Expand Down Expand Up @@ -205,6 +211,11 @@ extension DefaultAppSettingsStore: AppSettingsStore {
set { store(newValue, for: .disableWebIcons) }
}

var hasSeenWelcomeTutorial: Bool {
get { fetch(for: .hasSeenWelcomeTutorial) }
set { store(newValue, for: .hasSeenWelcomeTutorial) }
}

func clearClipboardValue(userId: String) -> ClearClipboardValue {
if let rawValue: Int = fetch(for: .clearClipboardValue(userId: userId)),
let value = ClearClipboardValue(rawValue: rawValue) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ extension ItemListItem {
static func fixture(
id: String = "123",
name: String = "Name",
totp: ItemListTotpItem
totp: ItemListTotpItem = .fixture()
) -> ItemListItem {
ItemListItem(
id: id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ protocol AuthenticatorItemRepository: AnyObject {
/// - Returns: A publisher for the list of a user's items
///
func itemListPublisher() async throws -> AsyncThrowingPublisher<AnyPublisher<[ItemListSection], Error>>

/// A publisher for searching a user's cipher objects based on the specified search text and filter type.
///
/// - Parameters:
/// - searchText: The search text to filter the cipher list.
/// - Returns: A publisher searching for the user's ciphers.
///
func searchItemListPublisher(
searchText: String
) async throws -> AsyncThrowingPublisher<AnyPublisher<[ItemListItem], Error>>
}

// MARK: - DefaultAuthenticatorItemRepository
Expand Down Expand Up @@ -115,6 +125,38 @@ class DefaultAuthenticatorItemRepository {
),
]
}

/// A publisher for searching a user's items based on the specified search text and filter type.
///
/// - Parameters:
/// - searchText: The search text to filter the item list.
/// - Returns: A publisher searching for the user's ciphers.
///
private func searchPublisher(
searchText: String
) async throws -> AnyPublisher<[AuthenticatorItemView], Error> {
let query = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
.lowercased()
.folding(options: .diacriticInsensitive, locale: .current)

return try await authenticatorItemService.authenticatorItemsPublisher()
.asyncTryMap { items -> [AuthenticatorItemView] in
var matchedItems: [AuthenticatorItem] = []

items.forEach { item in
if item.name.lowercased()
.folding(options: .diacriticInsensitive, locale: nil)
.contains(query) {
matchedItems.append(item)
}
}

return try await matchedItems.asyncMap { item in
try await self.cryptographyService.decrypt(item)
}
.sorted { $0.name.localizedStandardCompare($1.name) == .orderedAscending }
}.eraseToAnyPublisher()
}
}

extension DefaultAuthenticatorItemRepository: AuthenticatorItemRepository {
Expand Down Expand Up @@ -147,6 +189,8 @@ extension DefaultAuthenticatorItemRepository: AuthenticatorItemRepository {
try await authenticatorItemService.updateAuthenticatorItem(item)
}

// MARK: Publishers

func authenticatorItemDetailsPublisher(
id: String
) async throws -> AsyncThrowingPublisher<AnyPublisher<AuthenticatorItemView?, Error>> {
Expand All @@ -167,4 +211,16 @@ extension DefaultAuthenticatorItemRepository: AuthenticatorItemRepository {
.eraseToAnyPublisher()
.values
}

func searchItemListPublisher(
searchText: String
) async throws -> AsyncThrowingPublisher<AnyPublisher<[ItemListItem], Error>> {
try await searchPublisher(
searchText: searchText
).asyncTryMap { items in
items.compactMap(ItemListItem.init)
}
.eraseToAnyPublisher()
.values
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class MockAuthenticatorItemRepository: AuthenticatorItemRepository {
var authenticatorItemDetailsSubject = CurrentValueSubject<AuthenticatorItemView?, Error>(nil)
var itemListSubject = CurrentValueSubject<[ItemListSection], Error>([])

var searchItemListSubject = CurrentValueSubject<[ItemListItem], Error>([])

var updateAuthenticatorItemItems = [AuthenticatorItemView]()
var updateAuthenticatorItemResult: Result<Void, Error> = .success(())

Expand Down Expand Up @@ -68,4 +70,10 @@ class MockAuthenticatorItemRepository: AuthenticatorItemRepository {
updateAuthenticatorItemItems.append(authenticatorItem)
try updateAuthenticatorItemResult.get()
}

func searchItemListPublisher(
searchText: String
) async throws -> AsyncThrowingPublisher<AnyPublisher<[AuthenticatorShared.ItemListItem], Error>> {
searchItemListSubject.eraseToAnyPublisher().values
}
}
39 changes: 17 additions & 22 deletions AuthenticatorShared/UI/Platform/Application/AppCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class AppCoordinator: Coordinator, HasRootNavigator {
/// The types of modules used by this coordinator.
typealias Module = ItemListModule
& TabModule
& TutorialModule

// MARK: Private Properties

Expand Down Expand Up @@ -61,14 +62,14 @@ class AppCoordinator: Coordinator, HasRootNavigator {
switch event {
case .didStart:
showTab(route: .itemList(.list))
// showItemList(route: .list)
if (!services.stateService.hasSeenWelcomeTutorial) {
showTutorial()
}
}
}

func navigate(to route: AppRoute, context _: AnyObject?) {
switch route {
case .onboarding:
break
case let .tab(tabRoute):
showTab(route: tabRoute)
}
Expand All @@ -81,25 +82,6 @@ class AppCoordinator: Coordinator, HasRootNavigator {

// MARK: Private Methods

/// Shows the Item List screen.
///
/// - Parameter route: The item list route to show.
///
private func showItemList(route: ItemListRoute) {
if let coordinator = childCoordinator as? AnyCoordinator<ItemListRoute, ItemListEvent> {
coordinator.navigate(to: route)
} else {
let stackNavigator = UINavigationController()
let coordinator = module.makeItemListCoordinator(
stackNavigator: stackNavigator
)
coordinator.start()
coordinator.navigate(to: .list)
childCoordinator = coordinator
rootNavigator?.show(child: stackNavigator)
}
}

/// Shows the tab route.
///
/// - Parameter route: The tab route to show.
Expand All @@ -120,4 +102,17 @@ class AppCoordinator: Coordinator, HasRootNavigator {
childCoordinator = coordinator
}
}

/// Shows the welcome tutorial.
///
private func showTutorial() {
let navigationController = UINavigationController()
let coordinator = module.makeTutorialCoordinator(
stackNavigator: navigationController
)
coordinator.start()

navigationController.modalPresentationStyle = .overFullScreen
rootNavigator?.rootViewController?.present(navigationController, animated: false)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "light.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "recovery-codes.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "unique_codes.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -890,3 +890,13 @@
"Algorithm" = "Algorithm";
"RefreshPeriod" = "Refresh period";
"NumberOfDigits" = "Number of digits";
"BitwardenAuthenticator" = "Bitwarden Authenticator";
"Skip" = "Skip";
"SecureYourAssetsWithBitwardenAuthenticator" = "Secure your accounts with Bitwarden Authenticator";
"GetVerificationCodesForAllYourAccounts" = "Get verification codes for all your accounts using 2-step verification.";
"UseYourDeviceCameraToScanCodes" = "Use your device camera to scan codes";
"ScanTheQRCodeInYourSettings" = "Scan the QR code in your 2-step verification settings for any account.";
"SignInUsingUniqueCodes" = "Sign in using unique codes";
"WhenUsingTwoStepVerification" = "When using 2-step verification, you’ll enter your username and password and a code generated in this app.";
"GetStarted" = "Get started";
"LaunchTutorial" = "Launch tutorial";
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
/// A top level route from the initial screen of the app to anywhere in the app.
///
public enum AppRoute: Equatable {
/// A route to the onboarding experience.
case onboarding

/// A route to the tab interface.
case tab(TabRoute)
}
Expand Down
Loading

0 comments on commit 305516d

Please sign in to comment.