Allow reporting a problem from the onboarding screen

This commit is contained in:
Stefan Ceriu
2024-01-20 17:10:12 +02:00
parent a0aecf415a
commit 2f9d2cf457
16 changed files with 71 additions and 23 deletions

View File

@@ -398,11 +398,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
encryptionKeyProvider: EncryptionKeyProvider(), encryptionKeyProvider: EncryptionKeyProvider(),
appSettings: appSettings) appSettings: appSettings)
authenticationCoordinator = AuthenticationCoordinator(authenticationService: authenticationService, authenticationCoordinator = AuthenticationCoordinator(authenticationService: authenticationService,
appLockService: appLockFlowCoordinator.appLockService,
bugReportService: ServiceLocator.shared.bugReportService,
navigationStackCoordinator: authenticationNavigationStackCoordinator, navigationStackCoordinator: authenticationNavigationStackCoordinator,
appSettings: appSettings, appSettings: appSettings,
analytics: ServiceLocator.shared.analytics, analytics: ServiceLocator.shared.analytics,
userIndicatorController: ServiceLocator.shared.userIndicatorController, userIndicatorController: ServiceLocator.shared.userIndicatorController)
appLockService: appLockFlowCoordinator.appLockService)
authenticationCoordinator?.delegate = self authenticationCoordinator?.delegate = self
authenticationCoordinator?.start() authenticationCoordinator?.start()

View File

@@ -24,11 +24,12 @@ protocol AuthenticationCoordinatorDelegate: AnyObject {
class AuthenticationCoordinator: CoordinatorProtocol { class AuthenticationCoordinator: CoordinatorProtocol {
private let authenticationService: AuthenticationServiceProxyProtocol private let authenticationService: AuthenticationServiceProxyProtocol
private let appLockService: AppLockServiceProtocol
private let bugReportService: BugReportServiceProtocol
private let navigationStackCoordinator: NavigationStackCoordinator private let navigationStackCoordinator: NavigationStackCoordinator
private let appSettings: AppSettings private let appSettings: AppSettings
private let analytics: AnalyticsService private let analytics: AnalyticsService
private let userIndicatorController: UserIndicatorControllerProtocol private let userIndicatorController: UserIndicatorControllerProtocol
private let appLockService: AppLockServiceProtocol
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
@@ -39,17 +40,19 @@ class AuthenticationCoordinator: CoordinatorProtocol {
weak var delegate: AuthenticationCoordinatorDelegate? weak var delegate: AuthenticationCoordinatorDelegate?
init(authenticationService: AuthenticationServiceProxyProtocol, init(authenticationService: AuthenticationServiceProxyProtocol,
appLockService: AppLockServiceProtocol,
bugReportService: BugReportServiceProtocol,
navigationStackCoordinator: NavigationStackCoordinator, navigationStackCoordinator: NavigationStackCoordinator,
appSettings: AppSettings, appSettings: AppSettings,
analytics: AnalyticsService, analytics: AnalyticsService,
userIndicatorController: UserIndicatorControllerProtocol, userIndicatorController: UserIndicatorControllerProtocol) {
appLockService: AppLockServiceProtocol) {
self.authenticationService = authenticationService self.authenticationService = authenticationService
self.appLockService = appLockService
self.bugReportService = bugReportService
self.navigationStackCoordinator = navigationStackCoordinator self.navigationStackCoordinator = navigationStackCoordinator
self.appSettings = appSettings self.appSettings = appSettings
self.analytics = analytics self.analytics = analytics
self.userIndicatorController = userIndicatorController self.userIndicatorController = userIndicatorController
self.appLockService = appLockService
} }
func start() { func start() {
@@ -81,6 +84,8 @@ class AuthenticationCoordinator: CoordinatorProtocol {
switch action { switch action {
case .login: case .login:
Task { await self.startAuthentication() } Task { await self.startAuthentication() }
case .reportProblem:
showReportProblemScreen()
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
@@ -88,6 +93,27 @@ class AuthenticationCoordinator: CoordinatorProtocol {
navigationStackCoordinator.setRootCoordinator(coordinator) navigationStackCoordinator.setRootCoordinator(coordinator)
} }
private func showReportProblemScreen() {
let feedbackNavigationStackCoordinator = NavigationStackCoordinator()
let parameters = BugReportScreenCoordinatorParameters(bugReportService: bugReportService,
userID: nil,
deviceID: nil,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
screenshot: nil,
isModallyPresented: true)
let coordinator = BugReportScreenCoordinator(parameters: parameters)
coordinator.completion = { [weak self] _ in
self?.navigationStackCoordinator.setSheetCoordinator(nil)
}
feedbackNavigationStackCoordinator.setRootCoordinator(coordinator)
navigationStackCoordinator.setSheetCoordinator(feedbackNavigationStackCoordinator, animated: true)
}
private func startAuthentication() async { private func startAuthentication() async {
startLoading() startLoading()

View File

@@ -24,7 +24,7 @@ enum BugReportScreenCoordinatorResult {
struct BugReportScreenCoordinatorParameters { struct BugReportScreenCoordinatorParameters {
let bugReportService: BugReportServiceProtocol let bugReportService: BugReportServiceProtocol
let userID: String let userID: String?
let deviceID: String? let deviceID: String?
let userIndicatorController: UserIndicatorControllerProtocol? let userIndicatorController: UserIndicatorControllerProtocol?

View File

@@ -21,7 +21,7 @@ typealias BugReportScreenViewModelType = StateStoreViewModel<BugReportScreenView
class BugReportScreenViewModel: BugReportScreenViewModelType, BugReportScreenViewModelProtocol { class BugReportScreenViewModel: BugReportScreenViewModelType, BugReportScreenViewModelProtocol {
private let bugReportService: BugReportServiceProtocol private let bugReportService: BugReportServiceProtocol
private let userID: String private let userID: String?
private let deviceID: String? private let deviceID: String?
private let actionsSubject: PassthroughSubject<BugReportScreenViewModelAction, Never> = .init() private let actionsSubject: PassthroughSubject<BugReportScreenViewModelAction, Never> = .init()
// periphery:ignore - when set to nil this is automatically cancelled // periphery:ignore - when set to nil this is automatically cancelled
@@ -32,7 +32,7 @@ class BugReportScreenViewModel: BugReportScreenViewModelType, BugReportScreenVie
} }
init(bugReportService: BugReportServiceProtocol, init(bugReportService: BugReportServiceProtocol,
userID: String, userID: String?,
deviceID: String?, deviceID: String?,
screenshot: UIImage?, screenshot: UIImage?,
isModallyPresented: Bool) { isModallyPresented: Bool) {

View File

@@ -40,6 +40,8 @@ final class OnboardingScreenCoordinator: CoordinatorProtocol {
switch action { switch action {
case .login: case .login:
actionsSubject.send(.login) actionsSubject.send(.login)
case .reportProblem:
actionsSubject.send(.reportProblem)
} }
} }
.store(in: &cancellables) .store(in: &cancellables)

View File

@@ -20,14 +20,17 @@ import SwiftUI
enum OnboardingScreenCoordinatorAction { enum OnboardingScreenCoordinatorAction {
case login case login
case reportProblem
} }
enum OnboardingScreenViewModelAction { enum OnboardingScreenViewModelAction {
case login case login
case reportProblem
} }
struct OnboardingScreenViewState: BindableState { } struct OnboardingScreenViewState: BindableState { }
enum OnboardingScreenViewAction { enum OnboardingScreenViewAction {
case login case login
case reportProblem
} }

View File

@@ -34,6 +34,8 @@ class OnboardingScreenViewModel: OnboardingScreenViewModelType, OnboardingScreen
switch viewAction { switch viewAction {
case .login: case .login:
actionsSubject.send(.login) actionsSubject.send(.login)
case .reportProblem:
actionsSubject.send(.reportProblem)
} }
} }
} }

View File

@@ -42,6 +42,16 @@ struct OnboardingScreen: View {
.frame(height: UIConstants.spacerHeight(in: geometry)) .frame(height: UIConstants.spacerHeight(in: geometry))
} }
.frame(maxHeight: .infinity) .frame(maxHeight: .infinity)
.safeAreaInset(edge: .bottom) {
Button {
context.send(viewAction: .reportProblem)
} label: {
Text(L10n.commonReportAProblem)
.font(.compound.bodySM)
.foregroundColor(.compound.textSecondary)
}
.frame(width: geometry.size.width)
}
} }
.navigationBarHidden(true) .navigationBarHidden(true)
.background { .background {

View File

@@ -119,11 +119,14 @@ class BugReportService: NSObject, BugReportServiceProtocol {
func submitBugReport(_ bugReport: BugReport, func submitBugReport(_ bugReport: BugReport,
progressListener: CurrentValueSubject<Double, Never>) async -> Result<SubmitBugReportResponse, BugReportServiceError> { progressListener: CurrentValueSubject<Double, Never>) async -> Result<SubmitBugReportResponse, BugReportServiceError> {
var params = [ var params = [
MultipartFormData(key: "user_id", type: .text(value: bugReport.userID)),
MultipartFormData(key: "text", type: .text(value: bugReport.text)), MultipartFormData(key: "text", type: .text(value: bugReport.text)),
MultipartFormData(key: "can_contact", type: .text(value: "\(bugReport.canContact)")) MultipartFormData(key: "can_contact", type: .text(value: "\(bugReport.canContact)"))
] ]
if let userID = bugReport.userID {
params.append(.init(key: "user_id", type: .text(value: userID)))
}
if let deviceID = bugReport.deviceID { if let deviceID = bugReport.deviceID {
params.append(.init(key: "device_id", type: .text(value: deviceID))) params.append(.init(key: "device_id", type: .text(value: deviceID)))
} }

View File

@@ -19,7 +19,7 @@ import Foundation
import UIKit import UIKit
struct BugReport: Equatable { struct BugReport: Equatable {
let userID: String let userID: String?
let deviceID: String? let deviceID: String?
let text: String let text: String
let includeLogs: Bool let includeLogs: Bool

View File

@@ -137,11 +137,12 @@ class MockScreen: Identifiable {
case .authenticationFlow: case .authenticationFlow:
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = AuthenticationCoordinator(authenticationService: MockAuthenticationServiceProxy(), let coordinator = AuthenticationCoordinator(authenticationService: MockAuthenticationServiceProxy(),
appLockService: AppLockServiceMock(),
bugReportService: BugReportServiceMock(),
navigationStackCoordinator: navigationStackCoordinator, navigationStackCoordinator: navigationStackCoordinator,
appSettings: ServiceLocator.shared.settings, appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics, analytics: ServiceLocator.shared.analytics,
userIndicatorController: ServiceLocator.shared.userIndicatorController, userIndicatorController: ServiceLocator.shared.userIndicatorController)
appLockService: AppLockServiceMock())
retainedState.append(coordinator) retainedState.append(coordinator)
navigationStackCoordinator.setRootCoordinator(coordinator) navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator return navigationStackCoordinator

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ce9979e0f1ad3ae45fac19da8d790a4b92de31ad56d07d7735b88407c354e48e oid sha256:b0df6a86517b6c8c5e45d7a77ad323c7730178fdc6523f52068748b5d81eca3b
size 1276288 size 1276372

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:392d7264ceb22146a4a066ccd46a75a37c06789966bc49f23fb2fe0c23358a2b oid sha256:279e06e9fbfb8670fecd2ad88f6486402067e5eb1788a5db2f7420a88adaf2d4
size 990811 size 1000272

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:3480e47e56d9487354e552fd2dc8236d48781d6f9f8dfed2b7b63c5cbe2a9448 oid sha256:d2de5fced9093eeef584c2c1ee3b2296e9201cb0c8132672bce778389bbae036
size 1296599 size 1298519

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:1e8c2594242d3ea72a46202703f84c0920d41491c36f12c85aeb9d54fd3f9625 oid sha256:b829d6b34f1bb1600e5ccefbf4f7c04fba4b1fad73671cf648ab72ae49e012de
size 1013586 size 1025995

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:5f932bf95aeacda33b053111d5db67dacc992eb8cf3daedd193bc54c3edfc658 oid sha256:2413b7707aa285dd909981b20f7d5dbcb421ab469c217de1c1facb9bdd5b517b
size 828166 size 832592