Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  AddContactView.swift   Sprache: JAVA

 

import Foundation
import SwiftUI
import ThreemaEssentials
import ThreemaMacros

struct AddContactView: View {
    
    @Environment(\.dismiss) private var dismiss
    
    @StateObject var viewModel = AddContactViewModel()
    
    @FocusState private var isInputFocused: Bool
    @State private var showAlert = false
    @State private var alertData: AddContactError?
    private let onSaveDisplayMode: OnSaveDisplayMode

    init(onSaveDisplayMode: OnSaveDisplayMode = .showDetails) {
        self.onSaveDisplayMode = onSaveDisplayMode
    }
    
    var body: some View {
        NavigationView {
            Form {
                Section {
                    TextField(#localize("add_contact_ID_placeholder"), text: $viewModel.identity)
                        .font(Font(Configuration().nameFont))
                        .padding(.vertical, 3)
                        .focused($isInputFocused)
                        .textInputAutocapitalization(.characters)
                        .disableAutocorrection(true)
                        .keyboardType(.asciiCapable)
                        .accessibilityIdentifier("AddContactViewControllerCTextField")
                        .submitLabel(.done)
                        .onChange(of: viewModel.identity) { [weak viewModel] _, newValue in
                            guard let viewModel else {
                                return
                            }
                            Task { @MainActor [weak viewModel] in
                                guard let viewModel else {
                                    return
                                }
                                let uppercased = newValue.uppercased()
                                if newValue != uppercased {
                                    viewModel.identity = uppercased
                                }
                            }
                        }
                        .onSubmit {
                            add()
                        }
                }
                Section {
                    buildRow(#localize("add_contact_scan"), "qrcode.viewfinder", action: showScanner)
                        .disabled(viewModel.isScanningDisabled)
                }
                Section {
                    buildShareLink()
                }
            }
            .navigationTitle(#localize("add_contact_title"))
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    CancelButton {
                        dismiss()
                    }
                }
                ToolbarItem(placement: .confirmationAction) {
                    if #available(iOS 26.0, *) {
                        Button(role: .confirm) {
                            add()
                        }
                        .accessibilityIdentifier("AddContactModalAddButton")
                        .disabled(!viewModel.canAdd)
                    }
                    else {
                        Button(#localize("add_button")) {
                            add()
                        }
                        .accessibilityIdentifier("AddContactModalAddButton")
                        .disabled(!viewModel.canAdd)
                    }
                }
            }
            .alert(alertData?.title ?? "", isPresented: $showAlert, presenting: alertData) { _ in
                Button(#localize("ok"), role: .cancel) {
                    alertData = nil
                }
            } message: { alert in
                Text(alert.message)
            }
        }
        .onAppear {
            Task { @MainActor in
                isInputFocused = true
            }
        }
        // Force navigation style to stack so it doesn't show as split view on iPadOS 17
        // Can be removed after migrating from NavigationView to NavigationStack
        .navigationViewStyle(.stack)
        .loadingOverlay(viewModel.isLoading)
        .interactiveDismissDisabled(viewModel.isInteractiveDismissDisabled)
    }
    
    @ViewBuilder
    private func buildShareLink() -> some View {
        let shareText = String.localizedStringWithFormat(
            #localize("invite_sms_body"),
            TargetManager.appName,
            TargetManager.localizedAppName,
            BusinessInjector.ui.profileStore.profile.myIdentity.rawValue
        )
        
        ShareLink(item: shareText) {
            HStack {
                Label(#localize("add_contact_invite_friend"), systemImage: "person.2.wave.2.fill")
                    .labelStyle(.titleAndIcon)
                
                Spacer()
                
                Image(systemName: "chevron.right")
                    .foregroundColor(Color(uiColor: .tertiaryLabel))
            }
        }
    }
    
    @ViewBuilder
    private func buildRow(_ title: String, _ icon: String, action: @escaping () -> Void) -> some View {
        Button(action: action) {
            HStack {
                Label(title, systemImage: icon)
                    .labelStyle(.titleAndIcon)
                
                Spacer()
                
                Image(systemName: "chevron.right")
                    .foregroundColor(Color(uiColor: .tertiaryLabel))
            }
        }
    }

    private func showScanner() {
        let model = QRCodeScannerViewModel(
            mode: .identity,
            audioSessionManager: BusinessInjector.ui.audioSessionManager,
            systemFeedbackManager: SystemFeedbackManager(
                deviceCapabilitiesManager: DeviceCapabilitiesManager(),
                settingsStore: BusinessInjector.ui.settingsStore
            ),
            systemPermissionsManager: SystemPermissionsManager()
        )
        model.onCompletion = { result in
            handleScannerResult(result)
        }
        model.onCancel = {
            topViewController?.dismiss(animated: true) // dismiss only the scanner
        }
        let rootView = QRCodeScannerView(model: model)
        let viewController = UIHostingController(rootView: rootView)
        let nav = PortraitNavigationController(rootViewController: viewController)
        topViewController?.present(nav, animated: true)
    }

    private func handleScannerResult(_ result: QRCodeScannerViewModel.QRCodeResult) {
        switch result {
        case let .identityContact(identity: id, publicKey: key, expirationDate: date):
            handleIdentityContact(id: id, key: key, date: date)

        case let .identityLink(url: url):
            dismissScannerViewAndAddContactView {
                URLHandler().handle(url, hideAppChooser: true)
            }

        default:
            break
        }
    }

    private func handleIdentityContact(id: ThreemaIdentity, key: Data, date: Date?) {
        let contactVM = ContactIdentityProcessingViewModel(
            expectedIdentity: nil,
            scannedIdentity: id,
            scannedPublicKey: key,
            scannedExpirationDate: date,
            systemFeedbackManager: SystemFeedbackManager(
                deviceCapabilitiesManager: DeviceCapabilitiesManager(),
                settingsStore: BusinessInjector.ui.settingsStore
            )
        )
        let rootView = ContactIdentityProcessingView(model: contactVM)
        let viewController = UIHostingController(rootView: rootView)
        (topViewController as? UINavigationController)?.pushViewController(viewController, animated: true)
        contactVM.onCompletion = { verifiedContact in
            dismissScannerViewAndAddContactView {
                if let verifiedContact {
                    let name = Notification.Name(kNotificationShowContact)
                    let userInfo = [kKeyContact: verifiedContact]
                    NotificationCenter.default.post(name: name, object: nil, userInfo: userInfo)
                }
            }
        }
    }

    private func dismissScannerViewAndAddContactView(completion: (() -> Void)? = nil) {
        topViewController? // PortraitNavigationController
            .presentingViewController? // AddContactView
            .presentingViewController? // MainTabBarController
            .dismiss(animated: true, completion: completion)
    }

    private func add() {
        isInputFocused = false
        
        guard viewModel.canAdd else {
            return
        }

        Task {
            do {
                try await viewModel.addContact(onSaveDisplayMode: onSaveDisplayMode)
                await MainActor.run {
                    dismiss()
                }
            }
            catch let error as AddContactValidationError {
                showErrorAlert(error)
            }
            catch {
                assertionFailure("All errors should be wrapped by AddContactValidationError")
                showErrorAlert(AddContactValidationError.unknown(error))
            }
        }
    }

    private func showErrorAlert(_ error: AddContactValidationError) {
        showAlert(
            title: error.errorTitle,
            message: error.localizedDescription
        )
    }
    
    private func showAlert(title: String, message: String) {
        alertData = AddContactError(title: title, message: message)
        showAlert = true
    }
}

struct AddContactView_Previews: PreviewProvider {
    static var previews: some View {
        AddContactView()
    }
}

private struct Configuration: DetailsConfiguration { }

private struct AddContactError: Identifiable {
    let id = UUID()
    let title: String
    let message: String
}

Messung
CHZG
859710094

¤ Dauer der Verarbeitung: 0.1 Sekunden  (vorverarbeitet am  2026-11-04) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104852
#Domains=417171