decent working showing contacts sheet to user
This commit is contained in:
@@ -40,6 +40,8 @@
|
||||
89D99D2E2766FD6B00237814 /* Liquid in Frameworks */ = {isa = PBXBuildFile; productRef = 89D99D2D2766FD6B00237814 /* Liquid */; };
|
||||
89F05B9B276A87920002E9C7 /* ContactsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F05B9A276A87920002E9C7 /* ContactsViewModel.swift */; };
|
||||
89F05B9F276A8D360002E9C7 /* Contact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F05B9E276A8D360002E9C7 /* Contact.swift */; };
|
||||
89F05BA4276ACC060002E9C7 /* ContactService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F05BA3276ACC060002E9C7 /* ContactService.swift */; };
|
||||
89F05BA6276ACD830002E9C7 /* ContactsPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F05BA5276ACD830002E9C7 /* ContactsPickerView.swift */; };
|
||||
89F1386D2767EB19006680ED /* SignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F1386C2767EB19006680ED /* SignInView.swift */; };
|
||||
89F138702767F55F006680ED /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = 89F1386F2767F55F006680ED /* GoogleSignIn */; };
|
||||
/* End PBXBuildFile section */
|
||||
@@ -75,6 +77,8 @@
|
||||
89BDCDF02769E64200577829 /* Firestore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Firestore.swift; sourceTree = "<group>"; };
|
||||
89F05B9A276A87920002E9C7 /* ContactsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsViewModel.swift; sourceTree = "<group>"; };
|
||||
89F05B9E276A8D360002E9C7 /* Contact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contact.swift; sourceTree = "<group>"; };
|
||||
89F05BA3276ACC060002E9C7 /* ContactService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactService.swift; sourceTree = "<group>"; };
|
||||
89F05BA5276ACD830002E9C7 /* ContactsPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsPickerView.swift; sourceTree = "<group>"; };
|
||||
89F1386C2767EB19006680ED /* SignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInView.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@@ -114,6 +118,7 @@
|
||||
891A15DF27653A7000B26659 /* nirvana-ios */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
89F05BA2276ACBFA0002E9C7 /* Services */,
|
||||
89B46E6F276893C900B74255 /* Auth */,
|
||||
891A160C27657CD600B26659 /* Models */,
|
||||
891A1609276572C600B26659 /* Globals */,
|
||||
@@ -248,10 +253,19 @@
|
||||
children = (
|
||||
89BDCDEE2769B50C00577829 /* ContactsView.swift */,
|
||||
89F05B9A276A87920002E9C7 /* ContactsViewModel.swift */,
|
||||
89F05BA5276ACD830002E9C7 /* ContactsPickerView.swift */,
|
||||
);
|
||||
path = Contacts;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
89F05BA2276ACBFA0002E9C7 /* Services */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
89F05BA3276ACC060002E9C7 /* ContactService.swift */,
|
||||
);
|
||||
path = Services;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
89F1386B2767EB07006680ED /* SignIn */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -353,6 +367,7 @@
|
||||
892179E62765FECD001E6C60 /* FooterControlsViewModel.swift in Sources */,
|
||||
891A15F5276557AB00B26659 /* colors.swift in Sources */,
|
||||
89288ECB27675B9F00E962C4 /* OnboardingTemplateView.swift in Sources */,
|
||||
89F05BA4276ACC060002E9C7 /* ContactService.swift in Sources */,
|
||||
89B46E71276893E200B74255 /* AuthSessionStore.swift in Sources */,
|
||||
891A15F9276563CA00B26659 /* WelcomeView.swift in Sources */,
|
||||
89BDCDEF2769B50C00577829 /* ContactsView.swift in Sources */,
|
||||
@@ -362,6 +377,7 @@
|
||||
89F05B9B276A87920002E9C7 /* ContactsViewModel.swift in Sources */,
|
||||
891A15E127653A7000B26659 /* nirvana_iosApp.swift in Sources */,
|
||||
891A160127656FB200B26659 /* FooterView.swift in Sources */,
|
||||
89F05BA6276ACD830002E9C7 /* ContactsPickerView.swift in Sources */,
|
||||
89BDCDEA27697F9C00577829 /* UserMenu.swift in Sources */,
|
||||
891A15FF27656F9200B26659 /* HeaderView.swift in Sources */,
|
||||
892179E42765FEB8001E6C60 /* FooterControlsView.swift in Sources */,
|
||||
|
||||
@@ -15,18 +15,3 @@ struct Contact: Identifiable, Hashable {
|
||||
var phoneNumbers: [String]
|
||||
var emailAddresses: [String]
|
||||
}
|
||||
|
||||
enum PermissionsError: Identifiable {
|
||||
var id: String { UUID().uuidString }
|
||||
case userError
|
||||
case fetchError(_: Error)
|
||||
var description: String {
|
||||
switch self {
|
||||
case .userError:
|
||||
return "Please change permissions in settings"
|
||||
case .fetchError(let error):
|
||||
return error.localizedDescription
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// ContactService.swift
|
||||
// nirvana-ios
|
||||
//
|
||||
// Created by Arjun Patel on 12/15/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class ContactService {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// ContactsPickerView.swift
|
||||
// nirvana-ios
|
||||
//
|
||||
// Created by Arjun Patel on 12/15/21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Contacts
|
||||
|
||||
struct ContactPickerView: View {
|
||||
@Binding var showPicker: Bool
|
||||
@Binding var selectedContact: CNContact?
|
||||
var contacts: [String: [CNContact]] = loadContactsGrouped()
|
||||
|
||||
var body: some View {
|
||||
let keys = (Array(contacts.keys) as [String]).sorted()
|
||||
NavigationView {
|
||||
List {
|
||||
ForEach(keys.sorted(), id: \.self) { key in
|
||||
Section(header: Text(key)) {
|
||||
ForEach(contacts[key] ?? [CNContact](), id: \.identifier) { contact in
|
||||
ContactRow(contact: contact, showPicker: self.$showPicker, selectedContact: self.$selectedContact)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(GroupedListStyle())
|
||||
.navigationTitle("All Contacts".localized)
|
||||
.navigationBarItems(trailing: Button(action: {self.showPicker = false}, label: {
|
||||
Text("Cancel".localized)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
static func loadContactsGrouped() -> [String: [CNContact]] {
|
||||
let provider = ContactsViewModel()
|
||||
let contacts = provider.fetchContacts()
|
||||
|
||||
let group = Dictionary(grouping: contacts) { (contact) -> String in
|
||||
return String(contact.familyName.first ?? "?")
|
||||
}
|
||||
|
||||
return group
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct ContactRow: View {
|
||||
var contact: CNContact
|
||||
@Binding var showPicker: Bool
|
||||
@Binding var selectedContact: CNContact?
|
||||
|
||||
var body: some View {
|
||||
Button(action: {
|
||||
selectContact()
|
||||
}) {
|
||||
HStack {
|
||||
Text("\(contact.familyName)").fontWeight(.bold)
|
||||
Text("\(contact.givenName) \(contact.middleName)")
|
||||
}
|
||||
.foregroundColor(.black)
|
||||
}
|
||||
}
|
||||
|
||||
func selectContact() {
|
||||
self.selectedContact = self.contact
|
||||
self.showPicker = false
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
var localized: String {
|
||||
return NSLocalizedString(self, comment: "")
|
||||
}
|
||||
}
|
||||
@@ -18,27 +18,36 @@ struct ContactsView: View {
|
||||
TestUser(_profilePic: "rohan", _firstN: "Rohan", _lastN: "Chadha")
|
||||
]
|
||||
|
||||
@ObservedObject var contactsViewModel = ContactsViewModel()
|
||||
@State var showPicker = false
|
||||
@State var selectedContact: CNContact?
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
VStack(alignment: .trailing, spacing: 0) {
|
||||
OnboardingTemplateView(imgName: "undraw_grades_re_j7d6", mainLeadingActText: "Add your ", mainHighlightedActText: "closest folks.", mainTrailingActText: "", subActText: "besties, bf or gf, siblings, parents, etc.")
|
||||
|
||||
Text("Add contacts")
|
||||
.padding()
|
||||
.foregroundColor(NirvanaColor.teal)
|
||||
.overlay(
|
||||
Rectangle()
|
||||
.strokeBorder(style: StrokeStyle(lineWidth: 4, dash: [10]))
|
||||
.foregroundColor(NirvanaColor.teal)
|
||||
)
|
||||
Button(action: {
|
||||
self.showPicker = true
|
||||
}, label: {
|
||||
Label("Add Contacts", systemImage: "plus.circle")
|
||||
.frame(width: 50, height: 50, alignment: .center)
|
||||
.labelStyle(.iconOnly)
|
||||
.font(.largeTitle)
|
||||
.foregroundColor(NirvanaColor.white)
|
||||
.background(NirvanaColor.teal)
|
||||
.padding(.vertical, 20)
|
||||
.clipShape(Circle())
|
||||
.shadow(radius:10)
|
||||
})
|
||||
|
||||
Text(selectedContact != nil ? "Selected: \((selectedContact?.familyName)!) \((selectedContact?.givenName)!)" : "Nothing selected".localized)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.sheet(isPresented: self.$showPicker) {
|
||||
ContactPickerView(showPicker: self.$showPicker, selectedContact: self.$selectedContact)
|
||||
}
|
||||
.padding()
|
||||
.background(NirvanaColor.bgLightGrey)
|
||||
.alert(item: $contactsViewModel.permissionsError) {_ in
|
||||
Alert(title: Text("Permissions Needed"),
|
||||
message: Text(contactsViewModel.permissionsError?.description ?? "unknown error"),
|
||||
dismissButton: .default(Text("Ok"), action: contactsViewModel.openSettings))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,60 +9,58 @@ import Foundation
|
||||
import Contacts
|
||||
import SwiftUI
|
||||
|
||||
final public class ContactsViewModel : ObservableObject {
|
||||
@Published var contacts : [Contact] = []
|
||||
@Published var permissionsError: PermissionsError? = .none
|
||||
class ContactsViewModel : ObservableObject {
|
||||
@Published var showPermissionAlert = false
|
||||
|
||||
let store: CNContactStore
|
||||
let keys: [CNKeyDescriptor]
|
||||
|
||||
init() {
|
||||
self.permissions()
|
||||
store = CNContactStore()
|
||||
|
||||
keys = [CNContactImageDataKey as CNKeyDescriptor,
|
||||
CNContactPhoneNumbersKey as CNKeyDescriptor,
|
||||
CNContactEmailAddressesKey as CNKeyDescriptor,
|
||||
CNContactFormatter.descriptorForRequiredKeys(for: .fullName)]
|
||||
}
|
||||
|
||||
func openSettings() {
|
||||
self.permissionsError = .none
|
||||
guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { return }
|
||||
if UIApplication.shared.canOpenURL(settingsURL) { UIApplication.shared.open(settingsURL)}
|
||||
guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { return }
|
||||
if UIApplication.shared.canOpenURL(settingsURL) { UIApplication.shared.open(settingsURL)}
|
||||
}
|
||||
|
||||
private func fetchContacts() {
|
||||
//remove all current contacts from vm
|
||||
contacts.removeAll()
|
||||
func requestAccess() {
|
||||
store.requestAccess(for: .contacts) {
|
||||
(granted, error) in
|
||||
return
|
||||
}
|
||||
// TODO do something useful with completionHandler
|
||||
}
|
||||
|
||||
func fetchContacts(sortOrder: CNContactSortOrder = .userDefault) -> [CNContact] {
|
||||
let authStatus = CNContactStore.authorizationStatus(for: .contacts)
|
||||
if authStatus == .notDetermined {
|
||||
requestAccess()
|
||||
} else if authStatus == .restricted {
|
||||
// TODO prompt user that app is useless without access to contacts
|
||||
self.showPermissionAlert = true
|
||||
}
|
||||
|
||||
let store = CNContactStore()
|
||||
let fetchRequest = CNContactFetchRequest(keysToFetch: keys)
|
||||
fetchRequest.sortOrder = sortOrder
|
||||
|
||||
var fetchedContacts = [CNContact]()
|
||||
|
||||
do {
|
||||
let predicate = CNContact.predicateForContacts(matchingName: "sarth")
|
||||
//specific contact details: ex: name, email, etc.
|
||||
let keysToFetch = [CNContactGivenNameKey, CNContactFamilyNameKey] as [CNKeyDescriptor]
|
||||
|
||||
let contacts = try store.unifiedContacts(matching: predicate, keysToFetch: keysToFetch)
|
||||
|
||||
print("Fetched contacts: \(contacts)")
|
||||
} catch {
|
||||
print("Failed to fetch contacts, error: \(error)")
|
||||
// Handle the error
|
||||
self.permissionsError = .fetchError(error)
|
||||
}
|
||||
}
|
||||
|
||||
private func permissions() {
|
||||
switch CNContactStore.authorizationStatus(for: .contacts) {
|
||||
case.authorized:
|
||||
print("fetching contacts")
|
||||
fetchContacts()
|
||||
case .notDetermined, .restricted, .denied:
|
||||
CNContactStore().requestAccess(for: .contacts) {granted, error in
|
||||
switch granted {
|
||||
case true:
|
||||
self.fetchContacts()
|
||||
case false:
|
||||
DispatchQueue.main.async {
|
||||
print("unable to get contacts")
|
||||
self.permissionsError = .userError
|
||||
}
|
||||
}
|
||||
try store.enumerateContacts(with: fetchRequest) {
|
||||
(contact, stop) in
|
||||
fetchedContacts.append(contact)
|
||||
}
|
||||
default:
|
||||
fatalError("Unknown Error!")
|
||||
} catch {
|
||||
print("Unable to fetch contacts. \(error)")
|
||||
|
||||
}
|
||||
|
||||
return fetchedContacts
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user