import SwiftUI struct AddContactOrChannelScreen: View { @EnvironmentObject var store: AppStore enum Field { case account case contact } @FocusState private var focus: Field? @Binding var isPresented: Bool @State private var contactJID: String = "" @State private var ownerAccount: Account? @State private var isShowingLoader = false @State private var isShowingAlert = false @State private var errorMsg = "" var body: some View { ZStack { // Background color Color.Material.Background.light .ignoresSafeArea() // Content VStack(spacing: 0) { // Header SharedNavigationBar( leftButton: .init( image: Image(systemName: "chevron.left"), action: { isPresented = false } ), centerText: .init(text: L10n.Contacts.Add.title), rightButton: .init( image: Image(systemName: "plus.viewfinder"), action: { print("Scan QR-code") } ) ) VStack(spacing: 16) { // Explanation text Text(L10n.Contacts.Add.explanation) .font(.body3) .foregroundColor(.Material.Shape.separator) .multilineTextAlignment(.center) .padding(.top, 16) // Account selector HStack(spacing: 0) { Text("Use account:") .font(.body2) .foregroundColor(.Material.Text.main) .frame(alignment: .leading) Spacer() } UniversalInputCollection.DropDownMenu( prompt: "Use account", elements: store.state.accountsState.accounts, selected: $ownerAccount, focus: $focus, fieldType: .account ) // Contact text input HStack(spacing: 0) { Text("Contact JID:") .font(.body2) .foregroundColor(.Material.Text.main) .frame(alignment: .leading) Spacer() } UniversalInputCollection.TextField( prompt: "Contact or channel JID", text: $contactJID, focus: $focus, fieldType: .contact, contentType: .emailAddress, keyboardType: .emailAddress, submitLabel: .done, action: { focus = .account } ) // Save button Button { save() } label: { Text(L10n.Global.save) } .buttonStyle(PrimaryButtonStyle()) .disabled(!inputValid) .padding(.top) Spacer() } .padding(.horizontal, 32) } } .onAppear { if let exists = store.state.accountsState.accounts.first, exists.isActive { ownerAccount = exists } } .loadingIndicator(isShowingLoader) .alert(isPresented: $isShowingAlert) { Alert( title: Text(L10n.Global.Error.title), message: Text(errorMsg), dismissButton: .default(Text(L10n.Global.ok)) ) } .onChange(of: store.state.rostersState.newAddedRosterJid) { jid in if jid != nil, isShowingLoader { isShowingLoader = false isPresented = false } } .onChange(of: store.state.rostersState.newAddedRosterError) { error in if let error = error, isShowingLoader { isShowingLoader = false errorMsg = error isShowingAlert = true } } } private var inputValid: Bool { ownerAccount != nil && !contactJID.isEmpty && UniversalInputCollection.Validators.isEmail(contactJID) } private func save() { guard let ownerAccount else { return } if let exists = store.state.rostersState.rosters.first(where: { $0.bareJid == ownerAccount.bareJid && $0.contactBareJid == contactJID }), exists.locallyDeleted { store.dispatch(.rostersAction(.unmarkRosterAsLocallyDeleted(ownerJID: ownerAccount.bareJid, contactJID: contactJID))) isPresented = false } else { isShowingLoader = true store.dispatch(.rostersAction(.addRoster(ownerJID: ownerAccount.bareJid, contactJID: contactJID, name: nil, groups: []))) } } }