Rename screens (#826)

* Rename: AnalyticsPrompt -> AnalyticsPromptScreen

* Rename: AnalyticsSettings -> AnalyticsSettingsScreen

* Rename: Settings -> SettingsScreen

* Rename: SessionVerification -> SessionVerificationScreen

* Rename: BugReport -> BugReportScreen

* Rename: FilePreview -> FilePreviewScreen

* Rename: AnalyticsPrompt -> AnalyticsPromptScreen

* Rename: Invites -> InvitesScreen

* Rename: InviteUsers -> InviteUsersScreen

* Rename: MediaPicker -> MediaPickerScreen

* Delete unused UIActivityViewControllerWrapper

* Rename: ReportContent -> ReportContentScreen

* Rename: RoomDetails -> RoomDetailsScreen

* Rename: RoomMemberDetails -> RoomMemberDetailsScreen

* Rename: RoomMemberList -> RoomMemberListScreen

* Rename: Cleanup

* Renaming missed items

* Low effort attempt at fixing the flakey unit tests
This commit is contained in:
Stefan Ceriu
2023-04-24 18:48:07 +03:00
committed by GitHub
parent 9911ad2b9b
commit 24572c6582
88 changed files with 492 additions and 520 deletions
@@ -0,0 +1,105 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import SwiftUI
enum BugReportScreenCoordinatorResult {
case cancel
case finish
}
struct BugReportScreenCoordinatorParameters {
let bugReportService: BugReportServiceProtocol
let userID: String
let deviceID: String?
weak var userIndicatorController: UserIndicatorControllerProtocol?
let screenshot: UIImage?
let isModallyPresented: Bool
}
final class BugReportScreenCoordinator: CoordinatorProtocol {
private let parameters: BugReportScreenCoordinatorParameters
private var viewModel: BugReportScreenViewModelProtocol
private var cancellables: Set<AnyCancellable> = .init()
var completion: ((BugReportScreenCoordinatorResult) -> Void)?
init(parameters: BugReportScreenCoordinatorParameters) {
self.parameters = parameters
viewModel = BugReportScreenViewModel(bugReportService: parameters.bugReportService,
userID: parameters.userID,
deviceID: parameters.deviceID,
screenshot: parameters.screenshot,
isModallyPresented: parameters.isModallyPresented)
}
// MARK: - Public
func start() {
viewModel
.actions
.sink { [weak self] result in
guard let self else { return }
MXLog.info("BugReportViewModel did complete with result: \(result).")
switch result {
case .cancel:
self.completion?(.cancel)
case let .submitStarted(progressTracker):
self.startLoading(label: L10n.commonSending, progressPublisher: progressTracker)
case .submitFinished:
self.stopLoading()
self.completion?(.finish)
case .submitFailed(let error):
self.stopLoading()
self.showError(label: error.localizedDescription)
}
}
.store(in: &cancellables)
}
func stop() {
stopLoading()
}
func toPresentable() -> AnyView {
AnyView(BugReportScreen(context: viewModel.context))
}
// MARK: - Private
static let loadingIndicatorIdentifier = "BugReportLoading"
private func startLoading(label: String = L10n.commonLoading, progressPublisher: ProgressPublisher) {
parameters.userIndicatorController?.submitIndicator(
UserIndicator(id: Self.loadingIndicatorIdentifier,
type: .modal,
title: label,
persistent: true,
progressPublisher: progressPublisher)
)
}
private func stopLoading() {
parameters.userIndicatorController?.retractIndicatorWithId(Self.loadingIndicatorIdentifier)
}
private func showError(label: String) {
parameters.userIndicatorController?.submitIndicator(UserIndicator(title: label))
}
}
@@ -0,0 +1,43 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import UIKit
enum BugReportScreenViewModelAction {
case cancel
case submitStarted(progressTracker: ProgressTracker)
case submitFinished
case submitFailed(error: Error)
}
struct BugReportScreenViewState: BindableState {
var screenshot: UIImage?
var bindings: BugReportScreenViewStateBindings
let isModallyPresented: Bool
}
struct BugReportScreenViewStateBindings {
var reportText: String
var sendingLogsEnabled: Bool
}
enum BugReportScreenViewAction {
case cancel
case submit
case removeScreenshot
case attachScreenshot(UIImage)
}
@@ -0,0 +1,91 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import SwiftUI
typealias BugReportScreenViewModelType = StateStoreViewModel<BugReportScreenViewState, BugReportScreenViewAction>
class BugReportScreenViewModel: BugReportScreenViewModelType, BugReportScreenViewModelProtocol {
private let bugReportService: BugReportServiceProtocol
private let userID: String
private let deviceID: String?
private let actionsSubject: PassthroughSubject<BugReportScreenViewModelAction, Never> = .init()
var actions: AnyPublisher<BugReportScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(bugReportService: BugReportServiceProtocol,
userID: String,
deviceID: String?,
screenshot: UIImage?,
isModallyPresented: Bool) {
self.bugReportService = bugReportService
self.userID = userID
self.deviceID = deviceID
let bindings = BugReportScreenViewStateBindings(reportText: "", sendingLogsEnabled: true)
super.init(initialViewState: BugReportScreenViewState(screenshot: screenshot,
bindings: bindings,
isModallyPresented: isModallyPresented))
}
// MARK: - Public
override func process(viewAction: BugReportScreenViewAction) {
switch viewAction {
case .cancel:
actionsSubject.send(.cancel)
case .submit:
Task { await submitBugReport() }
case .removeScreenshot:
state.screenshot = nil
case let .attachScreenshot(image):
state.screenshot = image
}
}
// MARK: Private
private func submitBugReport() async {
let progressTracker = ProgressTracker()
actionsSubject.send(.submitStarted(progressTracker: progressTracker))
do {
var files: [URL] = []
if let screenshot = context.viewState.screenshot {
let imageURL = URL.temporaryDirectory.appendingPathComponent("Screenshot.png")
let pngData = screenshot.pngData()
try pngData?.write(to: imageURL)
files.append(imageURL)
}
let bugReport = BugReport(userID: userID,
deviceID: deviceID,
text: context.reportText,
includeLogs: context.sendingLogsEnabled,
includeCrashLog: true,
githubLabels: [],
files: files)
let result = try await bugReportService.submitBugReport(bugReport,
progressListener: progressTracker)
MXLog.info("SubmitBugReport succeeded, result: \(result.reportUrl)")
actionsSubject.send(.submitFinished)
} catch {
MXLog.error("SubmitBugReport failed: \(error)")
actionsSubject.send(.submitFailed(error: error))
}
}
}
@@ -0,0 +1,23 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
@MainActor
protocol BugReportScreenViewModelProtocol {
var actions: AnyPublisher<BugReportScreenViewModelAction, Never> { get }
var context: BugReportScreenViewModelType.Context { get }
}
@@ -0,0 +1,159 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Compound
import PhotosUI
import SwiftUI
struct BugReportScreen: View {
@State private var selectedScreenshot: PhotosPickerItem?
@ObservedObject var context: BugReportScreenViewModel.Context
var body: some View {
Form {
textFieldSection
attachScreenshotSection
sendLogsSection
}
.scrollDismissesKeyboard(.immediately)
.compoundForm()
.navigationTitle(L10n.commonReportABug)
.navigationBarTitleDisplayMode(.inline)
.toolbar { toolbar }
.interactiveDismissDisabled()
.onChange(of: selectedScreenshot) { newItem in
Task {
guard let data = try? await newItem?.loadTransferable(type: Data.self),
let image = UIImage(data: data)
else {
return
}
context.send(viewAction: .attachScreenshot(image))
}
}
}
private var textFieldSection: some View {
Section {
TextField(L10n.screenBugReportEditorPlaceholder,
text: $context.reportText,
prompt: Text(L10n.screenBugReportEditorPlaceholder).compoundFormTextFieldPlaceholder(),
axis: .vertical)
.lineLimit(4, reservesSpace: true)
.textFieldStyle(.compoundForm)
.accessibilityIdentifier(A11yIdentifiers.bugReportScreen.report)
} footer: {
Text(L10n.screenBugReportEditorDescription)
.compoundFormSectionFooter()
}
.compoundFormSection()
}
private var sendLogsSection: some View {
Section {
Toggle(L10n.screenBugReportIncludeLogs, isOn: $context.sendingLogsEnabled)
.toggleStyle(.compoundForm)
.accessibilityIdentifier(A11yIdentifiers.bugReportScreen.sendLogs)
} footer: {
Text(L10n.screenBugReportLogsDescription)
.compoundFormSectionFooter()
}
.compoundFormSection()
}
@ViewBuilder
private var attachScreenshotSection: some View {
Section {
PhotosPicker(selection: $selectedScreenshot,
matching: .screenshots,
photoLibrary: .shared()) {
Label(context.viewState.screenshot == nil ? L10n.screenBugReportAttachScreenshot : L10n.screenBugReportEditScreenshot, systemImage: "camera")
}
.buttonStyle(.compoundForm())
.accessibilityIdentifier(A11yIdentifiers.bugReportScreen.attachScreenshot)
} footer: {
if let screenshot = context.viewState.screenshot {
Image(uiImage: screenshot)
.resizable()
.scaledToFit()
.frame(width: 100)
.cornerRadius(4)
.accessibilityIdentifier(A11yIdentifiers.bugReportScreen.screenshot)
.overlay(alignment: .topTrailing) {
Button { context.send(viewAction: .removeScreenshot) } label: {
Image(Asset.Images.closeCircle.name)
}
.offset(x: 10, y: -10)
.accessibilityIdentifier(A11yIdentifiers.bugReportScreen.removeScreenshot)
}
.padding(.vertical, 16)
.padding(.horizontal, 16)
}
}
.compoundFormSection()
}
@ToolbarContentBuilder
private var toolbar: some ToolbarContent {
if context.viewState.isModallyPresented {
ToolbarItem(placement: .cancellationAction) {
Button(L10n.actionCancel) {
context.send(viewAction: .cancel)
}
}
}
ToolbarItem(placement: .confirmationAction) {
Button(L10n.actionSend) {
context.send(viewAction: .submit)
}
.disabled(context.reportText.count < 5)
}
}
}
// MARK: - Previews
struct BugReport_Previews: PreviewProvider {
static let viewModel = BugReportScreenViewModel(bugReportService: BugReportServiceMock(),
userID: "@mock.client.com",
deviceID: nil,
screenshot: nil,
isModallyPresented: false)
static var previews: some View {
NavigationStack {
BugReportScreen(context: BugReportScreenViewModel(bugReportService: BugReportServiceMock(),
userID: "@mock.client.com",
deviceID: nil,
screenshot: nil,
isModallyPresented: false).context)
.previewDisplayName("Without Screenshot")
}
NavigationStack {
BugReportScreen(context: BugReportScreenViewModel(bugReportService: BugReportServiceMock(),
userID: "@mock.client.com",
deviceID: nil,
screenshot: Asset.Images.appLogo.image,
isModallyPresented: false).context)
.previewDisplayName("With Screenshot")
}
}
}