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.Main.backgroundLight .ignoresSafeArea() // Content VStack(spacing: 0) { // Header AddContactsScreenHeader(isPresented: $isPresented) VStack(spacing: 16) { // Explanation text Text(L10n.Contacts.Add.explanation) .font(.body3) .foregroundColor(.Main.gray) .multilineTextAlignment(.center) .padding(.top, 16) // Account selector HStack(spacing: 0) { Text("Use account:") .font(.body2) .foregroundColor(.Main.black) .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(.Main.black) .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 let _ = jid, 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: []))) } } } private struct AddContactsScreenHeader: View { @Binding var isPresented: Bool var body: some View { ZStack { // bg Color.Main.backgroundDark .ignoresSafeArea() // title Text(L10n.Contacts.Add.title) .font(.head2) .foregroundColor(Color.Main.black) HStack { Image(systemName: "chevron.left") .foregroundColor(Color.Material.greenDark500) .tappablePadding(.symmetric(12)) { isPresented = false } Spacer() Image(systemName: "plus.viewfinder") .foregroundColor(Color.Material.greenDark500) .tappablePadding(.symmetric(12)) { print("Scan QR-code") } } .padding(.horizontal, 16) } .frame(height: 44) } }