Merge pull request #1 from talksik/connecting-data-to-circle-big-operation
Connecting data to circle big operation
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
8904DEC8277055E90071E6CA /* OnboardingTrioView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8904DEC7277055E90071E6CA /* OnboardingTrioView.swift */; };
|
||||
89110714277166D6005797FA /* UserFriends.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89110713277166D6005797FA /* UserFriends.swift */; };
|
||||
891A15E127653A7000B26659 /* nirvana_iosApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 891A15E027653A7000B26659 /* nirvana_iosApp.swift */; };
|
||||
891A15E327653A7000B26659 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 891A15E227653A7000B26659 /* ContentView.swift */; };
|
||||
891A15E527653A7100B26659 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 891A15E427653A7100B26659 /* Assets.xcassets */; };
|
||||
@@ -29,6 +30,12 @@
|
||||
894E80BE276ED7A700624B72 /* avatars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 894E80BD276ED7A700624B72 /* avatars.swift */; };
|
||||
894E80C1276EFD0500624B72 /* AudioPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 894E80C0276EFD0400624B72 /* AudioPlayerView.swift */; };
|
||||
894E80C327702E5900624B72 /* AddBasicInfoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 894E80C227702E5800624B72 /* AddBasicInfoViewModel.swift */; };
|
||||
896CE6FE27707E6B00DB474E /* CircleNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896CE6FD27707E6B00DB474E /* CircleNavigationView.swift */; };
|
||||
896CE7002770847400DB474E /* CircleFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896CE6FF2770847400DB474E /* CircleFooterView.swift */; };
|
||||
896CE7022770892F00DB474E /* CircleGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896CE7012770892F00DB474E /* CircleGridView.swift */; };
|
||||
896CE7052770AE5D00DB474E /* InboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896CE7042770AE5D00DB474E /* InboxView.swift */; };
|
||||
896CE70727711BAB00DB474E /* FindFriendsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896CE70627711BAB00DB474E /* FindFriendsView.swift */; };
|
||||
896CE70927712CED00DB474E /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896CE70827712CED00DB474E /* StringExtension.swift */; };
|
||||
89AD5573276DD790001658E0 /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89AD5572276DD790001658E0 /* SplashView.swift */; };
|
||||
89AD5575276DE724001658E0 /* PhoneCodeVerificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89AD5574276DE724001658E0 /* PhoneCodeVerificationView.swift */; };
|
||||
89AD5578276DEAB3001658E0 /* LogoHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89AD5577276DEAB3001658E0 /* LogoHeaderView.swift */; };
|
||||
@@ -46,7 +53,6 @@
|
||||
89BDCDEF2769B50C00577829 /* ContactsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89BDCDEE2769B50C00577829 /* ContactsView.swift */; };
|
||||
89BDCDF12769E64200577829 /* Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89BDCDF02769E64200577829 /* Messages.swift */; };
|
||||
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 */; };
|
||||
@@ -74,6 +80,7 @@
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
8904DEC7277055E90071E6CA /* OnboardingTrioView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingTrioView.swift; sourceTree = "<group>"; };
|
||||
89110713277166D6005797FA /* UserFriends.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserFriends.swift; sourceTree = "<group>"; };
|
||||
891A15DD27653A7000B26659 /* nirvana-ios.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "nirvana-ios.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
891A15E027653A7000B26659 /* nirvana_iosApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = nirvana_iosApp.swift; sourceTree = "<group>"; };
|
||||
891A15E227653A7000B26659 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
@@ -97,6 +104,12 @@
|
||||
894E80BD276ED7A700624B72 /* avatars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = avatars.swift; sourceTree = "<group>"; };
|
||||
894E80C0276EFD0400624B72 /* AudioPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerView.swift; sourceTree = "<group>"; };
|
||||
894E80C227702E5800624B72 /* AddBasicInfoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddBasicInfoViewModel.swift; sourceTree = "<group>"; };
|
||||
896CE6FD27707E6B00DB474E /* CircleNavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleNavigationView.swift; sourceTree = "<group>"; };
|
||||
896CE6FF2770847400DB474E /* CircleFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleFooterView.swift; sourceTree = "<group>"; };
|
||||
896CE7012770892F00DB474E /* CircleGridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleGridView.swift; sourceTree = "<group>"; };
|
||||
896CE7042770AE5D00DB474E /* InboxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboxView.swift; sourceTree = "<group>"; };
|
||||
896CE70627711BAB00DB474E /* FindFriendsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindFriendsView.swift; sourceTree = "<group>"; };
|
||||
896CE70827712CED00DB474E /* StringExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = "<group>"; };
|
||||
89AD5572276DD790001658E0 /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = "<group>"; };
|
||||
89AD5574276DE724001658E0 /* PhoneCodeVerificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneCodeVerificationView.swift; sourceTree = "<group>"; };
|
||||
89AD5577276DEAB3001658E0 /* LogoHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoHeaderView.swift; sourceTree = "<group>"; };
|
||||
@@ -113,7 +126,6 @@
|
||||
89BDCDEE2769B50C00577829 /* ContactsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsView.swift; sourceTree = "<group>"; };
|
||||
89BDCDF02769E64200577829 /* Messages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Messages.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>"; };
|
||||
@@ -205,6 +217,7 @@
|
||||
891A15F62765639B00B26659 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
896CE7032770AE3400DB474E /* Inbox */,
|
||||
894E80BF276EFCF500624B72 /* AudioPlayer */,
|
||||
89F18AF3276ECF1800115B1E /* AddBasicInfo */,
|
||||
89F18AF2276ECD5D00115B1E /* OnBoarding */,
|
||||
@@ -261,6 +274,7 @@
|
||||
89BDCDE62769616600577829 /* UIImage+Extension.swift */,
|
||||
89F18AEB276E92BF00115B1E /* CustomExtensions.swift */,
|
||||
894E80BD276ED7A700624B72 /* avatars.swift */,
|
||||
896CE70827712CED00DB474E /* StringExtension.swift */,
|
||||
);
|
||||
path = Globals;
|
||||
sourceTree = "<group>";
|
||||
@@ -270,7 +284,7 @@
|
||||
children = (
|
||||
89BDCDF02769E64200577829 /* Messages.swift */,
|
||||
891A160D27657CEA00B26659 /* User.swift */,
|
||||
89F05B9E276A8D360002E9C7 /* Contact.swift */,
|
||||
89110713277166D6005797FA /* UserFriends.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
@@ -301,6 +315,14 @@
|
||||
path = AudioPlayer;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
896CE7032770AE3400DB474E /* Inbox */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
896CE7042770AE5D00DB474E /* InboxView.swift */,
|
||||
);
|
||||
path = Inbox;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
89AD5571276DD77D001658E0 /* SplashView */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -331,6 +353,9 @@
|
||||
children = (
|
||||
89BCABE4276B3D58009E9B10 /* InnerCircleView.swift */,
|
||||
89BCABE6276B3D64009E9B10 /* InnerCircleViewModel.swift */,
|
||||
896CE6FD27707E6B00DB474E /* CircleNavigationView.swift */,
|
||||
896CE6FF2770847400DB474E /* CircleFooterView.swift */,
|
||||
896CE7012770892F00DB474E /* CircleGridView.swift */,
|
||||
);
|
||||
path = InnerCircle;
|
||||
sourceTree = "<group>";
|
||||
@@ -359,6 +384,7 @@
|
||||
89BDCDEE2769B50C00577829 /* ContactsView.swift */,
|
||||
89F05B9A276A87920002E9C7 /* ContactsViewModel.swift */,
|
||||
89F05BA5276ACD830002E9C7 /* ContactsPickerView.swift */,
|
||||
896CE70627711BAB00DB474E /* FindFriendsView.swift */,
|
||||
);
|
||||
path = Contacts;
|
||||
sourceTree = "<group>";
|
||||
@@ -502,9 +528,13 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
891A15E327653A7000B26659 /* ContentView.swift in Sources */,
|
||||
896CE7002770847400DB474E /* CircleFooterView.swift in Sources */,
|
||||
894E80BE276ED7A700624B72 /* avatars.swift in Sources */,
|
||||
89F05B9F276A8D360002E9C7 /* Contact.swift in Sources */,
|
||||
896CE7052770AE5D00DB474E /* InboxView.swift in Sources */,
|
||||
891A160327656FC700B26659 /* HomeView.swift in Sources */,
|
||||
896CE70927712CED00DB474E /* StringExtension.swift in Sources */,
|
||||
89110714277166D6005797FA /* UserFriends.swift in Sources */,
|
||||
896CE70727711BAB00DB474E /* FindFriendsView.swift in Sources */,
|
||||
891A160B2765732700B26659 /* HomeViewModel.swift in Sources */,
|
||||
89BCABEB276D2A09009E9B10 /* PhoneVerificationView.swift in Sources */,
|
||||
892179E62765FECD001E6C60 /* FooterControlsViewModel.swift in Sources */,
|
||||
@@ -520,6 +550,7 @@
|
||||
89BDCDE527695DB900577829 /* NirvanaImage.swift in Sources */,
|
||||
891A16132765A44300B26659 /* ChatsCarouselViewModel.swift in Sources */,
|
||||
89F05B9B276A87920002E9C7 /* ContactsViewModel.swift in Sources */,
|
||||
896CE6FE27707E6B00DB474E /* CircleNavigationView.swift in Sources */,
|
||||
89AD5573276DD790001658E0 /* SplashView.swift in Sources */,
|
||||
89F18AEC276E92BF00115B1E /* CustomExtensions.swift in Sources */,
|
||||
89F18AF1276ECAA300115B1E /* RouterView.swift in Sources */,
|
||||
@@ -528,6 +559,7 @@
|
||||
89BCABED276D2B05009E9B10 /* WavesGlassBackgroundView.swift in Sources */,
|
||||
89F05BA6276ACD830002E9C7 /* ContactsPickerView.swift in Sources */,
|
||||
8904DEC8277055E90071E6CA /* OnboardingTrioView.swift in Sources */,
|
||||
896CE7022770892F00DB474E /* CircleGridView.swift in Sources */,
|
||||
89BDCDEA27697F9C00577829 /* UserMenu.swift in Sources */,
|
||||
89BCABE7276B3D64009E9B10 /* InnerCircleViewModel.swift in Sources */,
|
||||
89AD557C276DF86A001658E0 /* FirestoreService.swift in Sources */,
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// StringExtension.swift
|
||||
// nirvana-ios
|
||||
//
|
||||
// Created by Arjun Patel on 12/20/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
func matchingStrings(regex: String) -> [[String]] {
|
||||
guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] }
|
||||
let nsString = self as NSString
|
||||
let results = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length))
|
||||
return results.map { result in
|
||||
(0..<result.numberOfRanges).map {
|
||||
result.range(at: $0).location != NSNotFound
|
||||
? nsString.substring(with: result.range(at: $0))
|
||||
: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var digits: String {
|
||||
return components(separatedBy: CharacterSet.decimalDigits.inverted)
|
||||
.joined()
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
//
|
||||
// Contact.swift
|
||||
// nirvana-ios
|
||||
//
|
||||
// Created by Arjun Patel on 12/15/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Contacts
|
||||
|
||||
struct Contact: Identifiable, Hashable {
|
||||
var id = UUID()
|
||||
var firstName: String
|
||||
var lastName: String
|
||||
var phoneNumbers: [String]
|
||||
var emailAddresses: [String]
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// UserFriends.swift
|
||||
// nirvana-ios
|
||||
//
|
||||
// Created by Arjun Patel on 12/20/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Firebase
|
||||
import FirebaseFirestoreSwift
|
||||
|
||||
enum UserFriendsState {
|
||||
case active
|
||||
case inactive
|
||||
}
|
||||
|
||||
struct UserFriends: Identifiable, Codable {
|
||||
@DocumentID var id: String? = UUID().uuidString
|
||||
var userId: String
|
||||
var friendId: String
|
||||
var isActive: Bool
|
||||
|
||||
@ServerTimestamp var lastUpdatedTimestamp: Date?
|
||||
@ServerTimestamp var createdTimestamp: Date?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case userId
|
||||
case friendId
|
||||
case isActive
|
||||
case createdTimestamp
|
||||
case lastUpdatedTimestamp
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,15 @@
|
||||
|
||||
import Foundation
|
||||
import Firebase
|
||||
import FirebaseCore
|
||||
import FirebaseFirestore
|
||||
import FirebaseFirestoreSwift
|
||||
|
||||
class FirestoreService {
|
||||
enum Collection: String {
|
||||
case users = "users"
|
||||
case messages = "messages"
|
||||
case user_friends = "user_friends" // associating a user to
|
||||
case userFriends = "user_friends" // associating a user to
|
||||
}
|
||||
|
||||
private var db = Firestore.firestore()
|
||||
@@ -50,6 +52,26 @@ class FirestoreService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getUserByPhoneNumber(phoneNumber: String, completion: @escaping((_ user: User?) -> ())) {
|
||||
// TODO: validation here as well to verify that phone number string is even valid
|
||||
db.collection(Collection.users.rawValue).whereField("phoneNumber", isEqualTo: phoneNumber)
|
||||
.getDocuments() { (querySnapshot, err) in
|
||||
if let err = err {
|
||||
print("Error getting documents: \(err)")
|
||||
completion(nil)
|
||||
} else {
|
||||
if !(querySnapshot?.isEmpty)! {
|
||||
for document in querySnapshot!.documents {
|
||||
let returnedUser = try? document.data(as: User.self)
|
||||
completion(returnedUser)
|
||||
}
|
||||
} else {
|
||||
completion(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createUser(user: User) {
|
||||
do {
|
||||
@@ -81,4 +103,21 @@ class FirestoreService {
|
||||
return FieldValue.serverTimestamp()
|
||||
}
|
||||
|
||||
|
||||
|
||||
func createOrUpdateUserFriends(userFriend: UserFriends, completion: @escaping((_ state: ServiceState) -> ())) {
|
||||
do {
|
||||
if userFriend.id != nil {
|
||||
let _ = try db.collection(Collection.userFriends.rawValue).addDocument(from: userFriend)
|
||||
completion(ServiceState.success("created/updated userfriend in firestore service"))
|
||||
} else {
|
||||
completion(ServiceState.error(ServiceError(description: "No userfriend id given to firestore service")))
|
||||
}
|
||||
} catch {
|
||||
print("error in creating user friend \(error.localizedDescription)")
|
||||
completion(ServiceState.error(ServiceError(description: error.localizedDescription)))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -121,26 +121,34 @@ final class AuthSessionStore: ObservableObject, SessionStore {
|
||||
|
||||
func setupAuthListen() {
|
||||
// monitor authentication changes using firebase
|
||||
self.handler = Auth.auth().addStateDidChangeListener { [weak self] res, user in
|
||||
self.handler = Auth.auth().addStateDidChangeListener { [weak self] res, authUser in
|
||||
print("auth listener activated")
|
||||
|
||||
guard let self = self else { return }
|
||||
|
||||
self.sessionState = user == nil ? SessionState.isLoggedOut : SessionState.isAuthenticated
|
||||
self.sessionState = authUser == nil ? SessionState.isLoggedOut : SessionState.isAuthenticated
|
||||
|
||||
// get the user from the auth table in firebase auth
|
||||
if let uid = user?.uid {
|
||||
if let uid = authUser?.uid {
|
||||
// if we have a user, create a new user model
|
||||
print("auth listener: Got user: \(uid)")
|
||||
print("auth listener: phone number: \(user?.phoneNumber)")
|
||||
print("auth listener: phone number: \(authUser?.phoneNumber)")
|
||||
|
||||
// when we have a user get signed in, we can activate the listener for fetching the user's data to keep our auth session store always up to date for all of the ui
|
||||
self.firestoreService.getUserRealtime(userId: uid) {[weak self] realtimeUpdatedUser in
|
||||
if realtimeUpdatedUser != nil {
|
||||
print("up to date user: \(realtimeUpdatedUser)")
|
||||
self?.user = realtimeUpdatedUser
|
||||
DispatchQueue.main.async {
|
||||
if realtimeUpdatedUser != nil {
|
||||
print("up to date user: \(realtimeUpdatedUser)")
|
||||
self?.user = realtimeUpdatedUser
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: listener for getting user's sent and received messages
|
||||
|
||||
// TODO: listener for getting user's circle members
|
||||
|
||||
|
||||
} else {
|
||||
// if we don't have a user, set our session to nil
|
||||
self.user = nil
|
||||
|
||||
@@ -102,7 +102,7 @@ struct AddBasicInfoView: View {
|
||||
|
||||
|
||||
Spacer()
|
||||
|
||||
|
||||
// button to save information
|
||||
Button {
|
||||
print("attempting to save information")
|
||||
|
||||
@@ -1,78 +1,82 @@
|
||||
////
|
||||
//// ContactsPickerView.swift
|
||||
//// nirvana-ios
|
||||
////
|
||||
//// Created by Arjun Patel on 12/15/21.
|
||||
////
|
||||
//
|
||||
// ContactsPickerView.swift
|
||||
// nirvana-ios
|
||||
//import SwiftUI
|
||||
//import Contacts
|
||||
//
|
||||
// Created by Arjun Patel on 12/15/21.
|
||||
//struct ContactsPickerView: View {
|
||||
// @Environment(\.dismiss) var dismiss
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Contacts
|
||||
|
||||
struct ContactPickerView: View {
|
||||
@Binding var showPicker: Bool
|
||||
// updates the parent view for it to do something with the new contact
|
||||
@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: "")
|
||||
}
|
||||
}
|
||||
// // updates the parent view for it to do something with the new contact
|
||||
// @State 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, selectedContact: self.$selectedContact)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .listStyle(GroupedListStyle())
|
||||
// .navigationTitle("All Contacts".localized)
|
||||
// .navigationBarItems(trailing: Button(action: {
|
||||
// dismiss()
|
||||
//
|
||||
// }, label: {
|
||||
// Text("Cancel".localized)
|
||||
// }))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// static func loadContactsGrouped() -> [String: [CNContact]] {
|
||||
// let provider = ContactsViewModel()
|
||||
// let contacts = provider.fetchContacts()
|
||||
// print(contacts)
|
||||
// let group = Dictionary(grouping: contacts) { (contact) -> String in
|
||||
// return String(contact.familyName.first ?? "?")
|
||||
// }
|
||||
//
|
||||
// print(group)
|
||||
//
|
||||
// return group
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
//struct ContactRow: View {
|
||||
// var contact: CNContact
|
||||
// @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
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//extension String {
|
||||
// var localized: String {
|
||||
// return NSLocalizedString(self, comment: "")
|
||||
// }
|
||||
//}
|
||||
|
||||
@@ -42,7 +42,7 @@ struct ContactsView: View {
|
||||
.shadow(radius:10)
|
||||
})
|
||||
|
||||
Text(selectedContact != nil ? "Selected: \((selectedContact?.familyName)!) \((selectedContact?.givenName)!)" : "Nothing selected".localized)
|
||||
Text(selectedContact != nil ? "Selected: \((selectedContact?.familyName)!) \((selectedContact?.givenName)!)" : "Nothing selected")
|
||||
|
||||
Button {
|
||||
self.navigationStack.push(InnerCircleView())
|
||||
@@ -53,7 +53,7 @@ struct ContactsView: View {
|
||||
Spacer()
|
||||
}
|
||||
.sheet(isPresented: self.$showPicker) {
|
||||
ContactPickerView(showPicker: self.$showPicker, selectedContact: self.$selectedContact)
|
||||
// ContactPickerView(showPicker: self.$showPicker, selectedContact: self.$selectedContact)
|
||||
}
|
||||
.padding()
|
||||
.background(NirvanaColor.bgLightGrey)
|
||||
|
||||
@@ -11,17 +11,18 @@ import SwiftUI
|
||||
|
||||
class ContactsViewModel : ObservableObject {
|
||||
@Published var showPermissionAlert = false
|
||||
@Published var contacts: [String: ContactsViewModelContact] = [:]
|
||||
|
||||
let store: CNContactStore
|
||||
let keys: [CNKeyDescriptor]
|
||||
private var firestoreService = FirestoreService()
|
||||
|
||||
let store: CNContactStore = CNContactStore()
|
||||
let keys: [CNKeyDescriptor] = [CNContactImageDataKey as CNKeyDescriptor,
|
||||
CNContactPhoneNumbersKey as CNKeyDescriptor,
|
||||
CNContactEmailAddressesKey as CNKeyDescriptor,
|
||||
CNContactFormatter.descriptorForRequiredKeys(for: .fullName)]
|
||||
|
||||
init() {
|
||||
store = CNContactStore()
|
||||
|
||||
keys = [CNContactImageDataKey as CNKeyDescriptor,
|
||||
CNContactPhoneNumbersKey as CNKeyDescriptor,
|
||||
CNContactEmailAddressesKey as CNKeyDescriptor,
|
||||
CNContactFormatter.descriptorForRequiredKeys(for: .fullName)]
|
||||
self.fetchContacts()
|
||||
}
|
||||
|
||||
func openSettings() {
|
||||
@@ -30,37 +31,86 @@ class ContactsViewModel : ObservableObject {
|
||||
}
|
||||
|
||||
func requestAccess() {
|
||||
store.requestAccess(for: .contacts) {
|
||||
(granted, error) in
|
||||
|
||||
store.requestAccess(for: .contacts) {(granted, error) in
|
||||
// TODO: do something useful with completionHandler
|
||||
return
|
||||
}
|
||||
// TODO do something useful with completionHandler
|
||||
}
|
||||
|
||||
func fetchContacts(sortOrder: CNContactSortOrder = .userDefault) -> [CNContact] {
|
||||
func fetchContacts() {
|
||||
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
|
||||
// TODO: prompt user that app is useless without access to contacts
|
||||
self.showPermissionAlert = true
|
||||
}
|
||||
|
||||
let fetchRequest = CNContactFetchRequest(keysToFetch: keys)
|
||||
fetchRequest.sortOrder = sortOrder
|
||||
|
||||
var fetchedContacts = [CNContact]()
|
||||
fetchRequest.sortOrder = .userDefault
|
||||
|
||||
do {
|
||||
try store.enumerateContacts(with: fetchRequest) {
|
||||
(contact, stop) in
|
||||
fetchedContacts.append(contact)
|
||||
try store.enumerateContacts(with: fetchRequest) {(contact, stop) in
|
||||
// only checking if american number or not for now
|
||||
var cnPhoneNumber = contact.phoneNumbers.first?.value.stringValue
|
||||
let contactDisplayName = contact.givenName + " " + contact.familyName
|
||||
// strip out nondigits
|
||||
// add or keep country code
|
||||
|
||||
// TODO: take out hard coding to US country code
|
||||
var formattedNumber = cnPhoneNumber?.digits.suffix(10)
|
||||
|
||||
// only show contacts if they have a phone number
|
||||
if formattedNumber != nil && formattedNumber?.count ?? 0 > 9 {
|
||||
formattedNumber = "+1" + formattedNumber! // adding country code
|
||||
|
||||
let contactNumber = String(formattedNumber!)
|
||||
var contactVm = ContactsViewModelContact(cnName: contactDisplayName, cnPhoneNumber: contactNumber, sortingProp: contactDisplayName)
|
||||
|
||||
// check if the contact is an existing user by phone number
|
||||
self.firestoreService.getUserByPhoneNumber(phoneNumber: contactNumber) {[weak self] returnedUser in
|
||||
DispatchQueue.main.async {
|
||||
if returnedUser == nil {
|
||||
// do nothing, keep contactsVm object user property nil
|
||||
contactVm.isExisting = false
|
||||
contactVm.sortingProp = contactVm.cnName
|
||||
} else {
|
||||
// adding user details if this contact is existing user
|
||||
contactVm.sortingProp = "!" // adding for sorting purposes
|
||||
contactVm.user = returnedUser
|
||||
contactVm.isExisting = true
|
||||
print("existing user: \(contactVm.cnName)")
|
||||
}
|
||||
|
||||
self?.contacts[contactVm.sortingProp] = contactVm
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
print("Unable to fetch contacts. \(error)")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func addOrActivateFriendToCircle(userId: String, friendId: String) {
|
||||
// setting timestamps to nil to make sure that new server timestamp is set
|
||||
var userFriend = UserFriends(userId: userId, friendId: friendId, isActive: true, lastUpdatedTimestamp: nil, createdTimestamp: nil)
|
||||
|
||||
return fetchedContacts
|
||||
self.firestoreService.createOrUpdateUserFriends(userFriend: userFriend) {[weak self] res in
|
||||
print(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct ContactsViewModelContact : Identifiable {
|
||||
var id : String = UUID().uuidString
|
||||
var user: User?
|
||||
var cnName: String
|
||||
var cnPhoneNumber: String
|
||||
var isExisting: Bool = false
|
||||
var sortingProp: String
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
//
|
||||
// FindFriendsView.swift
|
||||
// nirvana-ios
|
||||
//
|
||||
// Created by Arjun Patel on 12/20/21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Contacts
|
||||
import NavigationStack
|
||||
|
||||
|
||||
|
||||
struct FindFriendsView: View {
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
@StateObject var contactsVM = ContactsViewModel()
|
||||
|
||||
@State private var searchQuery: String = ""
|
||||
|
||||
var body: some View {
|
||||
|
||||
NavigationView {
|
||||
// TODO: add search bar + have secondary sort
|
||||
List {
|
||||
ForEach(searchItems, id: \.self) { key in
|
||||
if let currContact = self.contactsVM.contacts[key] {
|
||||
ListContactRow(contactsVM: self.contactsVM, contact: currContact)
|
||||
}
|
||||
}
|
||||
}
|
||||
.searchable(text: self.$searchQuery)
|
||||
.navigationTitle("Find Friends")
|
||||
.navigationBarItems(trailing: Button(action: {
|
||||
dismiss()
|
||||
}, label: {
|
||||
Text("Cancel")
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
var searchItems: [String] {
|
||||
let keys = (Array(self.contactsVM.contacts.keys) as [String]).sorted()
|
||||
|
||||
if self.searchQuery.isEmpty {
|
||||
return keys
|
||||
} else {
|
||||
return keys.filter { $0.contains(self.searchQuery) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
struct FindFriendsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
FindFriendsView().environmentObject(AuthSessionStore())
|
||||
}
|
||||
}
|
||||
|
||||
struct ListContactRow: View {
|
||||
@EnvironmentObject var navigationStack : NavigationStack
|
||||
@EnvironmentObject var authSessionStore: AuthSessionStore
|
||||
|
||||
var contactsVM: ContactsViewModel
|
||||
var contact:ContactsViewModelContact
|
||||
|
||||
@State var showAlert = false
|
||||
@State var alertText = "🌱Confirm Arjun into Your Circle?"
|
||||
@State var alertMessage = "Note: You can have a maximium of 12 people in your circle and you have 5 swaps left!"
|
||||
|
||||
var body: some View {
|
||||
if contact.isExisting { // add to circle
|
||||
// TODO: check if this contact is already in user's circle
|
||||
HStack(alignment: .center, spacing: 0) {
|
||||
Image(contact.user?.avatar ?? Avatars.avatarSystemNames[1])
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.clipShape(Circle())
|
||||
.frame(width: 40, height: 40)
|
||||
.padding(.trailing, 10)
|
||||
|
||||
Text(contact.cnName)
|
||||
.font(.headline)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
print("showing alert now")
|
||||
// TODO: show alert if circle is full
|
||||
self.showAlert.toggle()
|
||||
|
||||
} label: {
|
||||
Label("Add", systemImage: "plus.circle")
|
||||
.font(.title2)
|
||||
.foregroundColor(NirvanaColor.solidTeal)
|
||||
}
|
||||
}
|
||||
.alert(self.alertText, isPresented: self.$showAlert) {
|
||||
Button("Add \(contact.cnName)") {
|
||||
print("adding contact to circle")
|
||||
// call method in vm to get it done, then navigate to the circle
|
||||
self.contactsVM.addOrActivateFriendToCircle(userId: self.authSessionStore.user!.id!, friendId: (contact.user!.id)!)
|
||||
|
||||
self.navigationStack.push(InnerCircleView())
|
||||
}
|
||||
} message: {
|
||||
Text(self.alertMessage)
|
||||
}
|
||||
}
|
||||
else { // invite button to text the person
|
||||
HStack(alignment: .center, spacing: 0) {
|
||||
Image(Avatars.avatarSystemNames[1])
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.clipShape(Circle())
|
||||
.frame(width: 40, height: 40)
|
||||
.blur(radius: 8)
|
||||
.padding(.trailing, 10)
|
||||
|
||||
Text(contact.cnName)
|
||||
.font(.headline)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
print("inviting user now")
|
||||
// text message to him/her
|
||||
|
||||
if contact.cnPhoneNumber != nil {
|
||||
let sms: String = "sms://\(contact.cnPhoneNumber)&body=Join me on Nirvana! I sent you a message there! https://usenirvana.com"
|
||||
let strUrl: String = sms.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
|
||||
|
||||
UIApplication.shared.open(URL(string: strUrl)!, options: [:], completionHandler: nil)
|
||||
} else {
|
||||
print("phone number is nil of contact")
|
||||
}
|
||||
} label: {
|
||||
Label("Invite", systemImage: "paperplane")
|
||||
.font(.title2)
|
||||
.foregroundColor(Color.orange)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// InboxView.swift
|
||||
// nirvana-ios
|
||||
//
|
||||
// Created by Arjun Patel on 12/20/21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct InboxView: View {
|
||||
var body: some View {
|
||||
Text("This is the inbox where you can see a bunch of inboxes")
|
||||
//TODO: add illustration if there is no one in inbox, but have button to open sheet for contacts with the sheet view object
|
||||
}
|
||||
}
|
||||
|
||||
struct InboxView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
InboxView()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// CircleFooterView.swift
|
||||
// nirvana-ios
|
||||
//
|
||||
// Created by Arjun Patel on 12/20/21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import NavigationStack
|
||||
|
||||
struct CircleFooterView: View {
|
||||
@EnvironmentObject var navigationStack: NavigationStack
|
||||
@EnvironmentObject var authSessionStore: AuthSessionStore
|
||||
|
||||
|
||||
var body: some View {
|
||||
// TODO: do cool animations with this footer background fill in while recording or playing a message
|
||||
ZStack(alignment:.bottomTrailing) {
|
||||
VStack(alignment:.center) {
|
||||
Spacer()
|
||||
|
||||
HStack {
|
||||
Image("Artboards_Diversity_Avatars_by_Netguru-1")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.background(NirvanaColor.teal.opacity(0.1))
|
||||
.frame(width: 40, height: 40)
|
||||
.clipShape(Circle())
|
||||
.padding(5)
|
||||
|
||||
|
||||
|
||||
VStack (alignment: .leading) {
|
||||
Text("Sarth Shah")
|
||||
.font(.footnote)
|
||||
.foregroundColor(NirvanaColor.light)
|
||||
Text("sent 20 minutes ago")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Label("Replay", systemImage: "repeat.1.circle.fill")
|
||||
.font(.title2)
|
||||
.labelStyle(.iconOnly)
|
||||
.foregroundColor(Color.orange)
|
||||
|
||||
|
||||
Spacer(minLength: 65)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: 60) // 60 is the height of the footer control big circle
|
||||
.background(Color.white.opacity(0.5))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 30, style: .continuous))
|
||||
.shadow(color: Color.black.opacity(0.25), radius: 30, x: 0, y: 20)
|
||||
}
|
||||
|
||||
FooterControlsView()
|
||||
}
|
||||
.padding()
|
||||
// .offset(
|
||||
// x:0,
|
||||
// y:self.selectedUserIndex == nil ? 150 : 0
|
||||
// )
|
||||
// .animation(Animation.spring(), value: self.selectedUserIndex)
|
||||
}
|
||||
}
|
||||
|
||||
struct CircleFooterView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CircleFooterView()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
//
|
||||
// CircleGridView.swift
|
||||
// nirvana-ios
|
||||
//
|
||||
// Created by Arjun Patel on 12/20/21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import NavigationStack
|
||||
|
||||
struct CircleGridView: View {
|
||||
@EnvironmentObject var authSessionStore: AuthSessionStore
|
||||
@EnvironmentObject var navigationStack: NavigationStack
|
||||
|
||||
let universalSize = UIScreen.main.bounds
|
||||
|
||||
// magic variables for grid
|
||||
// TODO: make the top left person or one horizontal person be in the center of screen...makes the top honeycomb pop
|
||||
private static var numberOfItems: Int = 12
|
||||
private static let size: CGFloat = UIScreen.main.bounds.height*0.15 // scaling with screen size
|
||||
private static let spacingBetweenColumns: CGFloat = 0
|
||||
private static let spacingBetweenRows: CGFloat = 0
|
||||
private static let totalColumns: Int = Int(log2(Double(Self.numberOfItems))) // scaling the circles and calculating column count
|
||||
|
||||
@State private var selectedUserIndex: Int? = nil
|
||||
|
||||
// TODO: change the size to adaptive or something to make outer ring items shrink their overall size and fit better
|
||||
let gridItems: [GridItem] = Array(
|
||||
repeating: GridItem(.fixed(Self.size), spacing: spacingBetweenColumns, alignment: .center),
|
||||
count: totalColumns)
|
||||
|
||||
private let big:CGFloat = 1
|
||||
private let medium:CGFloat = 0.75
|
||||
private let small:CGFloat = 0.5
|
||||
private let supersmall:CGFloat = 0.3
|
||||
|
||||
// TODO: centered honeycomb + side: maybe have two honeycombs: one in the center of 7 people and then everyone else surrounding
|
||||
// TODO: maybe make our "center" offset to a little to the top left to make it more honeycomb from top left
|
||||
private let center: CGPoint = CGPoint(x: UIScreen.main.bounds.width*0.5,y: UIScreen.main.bounds.height*0.5)
|
||||
|
||||
var body: some View {
|
||||
gridContent
|
||||
}
|
||||
|
||||
private var gridContent: some View {
|
||||
// main communication hub
|
||||
// TODO: client side, sort the honeycomb from top left to bottom right
|
||||
// based on most close friends to least
|
||||
ScrollViewReader {scrollReaderValue in
|
||||
ScrollView([.horizontal, .vertical], showsIndicators: false) {
|
||||
LazyVGrid(
|
||||
columns: gridItems,
|
||||
alignment: .center,
|
||||
spacing: Self.spacingBetweenRows
|
||||
) {
|
||||
ForEach(0..<Self.numberOfItems) { value in
|
||||
GeometryReader {gridProxy in
|
||||
let scale = getScale(proxy: gridProxy, itemNumber: value)
|
||||
|
||||
ZStack(alignment: .topTrailing) {
|
||||
Circle()
|
||||
.foregroundColor(self.getBubbleTint(userIndex: value)) // different color for a selected user
|
||||
.blur(radius: 8)
|
||||
.cornerRadius(100)
|
||||
|
||||
Image("Artboards_Diversity_Avatars_by_Netguru-\(value + 1)")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 20)
|
||||
}
|
||||
.scaleEffect(scale)
|
||||
.padding(scale * 5)
|
||||
} // geometry reader
|
||||
.offset(
|
||||
x: honeycombOffSetX(value),
|
||||
y: 0
|
||||
)
|
||||
.id(value) // id for scrollviewreader
|
||||
.frame(height: Self.size)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
// if user had previously selected user, put nil as a toggle
|
||||
if self.selectedUserIndex == value {
|
||||
self.selectedUserIndex = nil
|
||||
} else {
|
||||
self.selectedUserIndex = value
|
||||
}
|
||||
}
|
||||
|
||||
print("the selected item is: \(value)")
|
||||
}
|
||||
.onLongPressGesture { // haptics for recording
|
||||
let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
|
||||
impactHeavy.impactOccurred()
|
||||
}
|
||||
// .animation(Animation.easeInOut(duration: 2).repeatForever(autoreverses: true), value: self.usersWithNewMessage)
|
||||
.animation(Animation.spring())
|
||||
}
|
||||
} // TODO: add padding based on if we are on any cornering item to allow the bubble to enlargen
|
||||
.padding(.trailing, Self.size / 2 + Self.spacingBetweenColumns / 2) // because of the offset of last column
|
||||
.padding(.top, Self.size / 2 + Self.spacingBetweenRows / 2) // because we are going under the nav bar
|
||||
}// scrollview
|
||||
.onAppear {
|
||||
//TODO: was causing problems so commenting out
|
||||
//may not need anymore with bottom nav activation
|
||||
// scrollReaderValue.scrollTo(Self.numberOfItems / 2)
|
||||
}
|
||||
} // scrollview reader
|
||||
}
|
||||
|
||||
private func getBubbleTint(userIndex: Int) -> Color {
|
||||
if (userIndex == self.selectedUserIndex) { // user clicked on this user
|
||||
return NirvanaColor.dimTeal.opacity(0.4)
|
||||
}
|
||||
// else if self.usersWithNewMessage.contains(userIndex) { // this user has a message
|
||||
// return Color.orange.opacity(0.8)
|
||||
// }
|
||||
|
||||
return Color.white.opacity(0.4)
|
||||
}
|
||||
|
||||
// getting the proxy of an individual item
|
||||
// and decoding into a scale that the item should take
|
||||
private func getScale(proxy: GeometryProxy, itemNumber: Int) -> CGFloat {
|
||||
// if this user is selected
|
||||
if itemNumber == self.selectedUserIndex {
|
||||
return big + 0.2
|
||||
}
|
||||
|
||||
// if this user that we are rendering
|
||||
// has a new message for us
|
||||
// TODO: too much memory usage doing this across the board
|
||||
// if self.usersWithNewMessage.contains(itemNumber) {
|
||||
// return big + 0.2
|
||||
// }
|
||||
|
||||
let posRelToGrid = proxy.frame(in: .global) // relative to the entire screen
|
||||
let midX = getRowNumber(itemNumber) % 2 == 0 ? posRelToGrid.midX + (Self.size / 2) + (Self.spacingBetweenColumns / 2) : posRelToGrid.midX
|
||||
let midY = posRelToGrid.midY
|
||||
|
||||
let xdelta = abs(midX - self.center.x)
|
||||
let ydelta = abs(midY - self.center.y)
|
||||
|
||||
let innerCircleXAcceptance = Self.size * 0.75
|
||||
let innerCircleYAcceptance = Self.size * 0.75
|
||||
let outerRingXAcceptance = self.universalSize.width * 0.4
|
||||
let outerRingYAcceptance = self.universalSize.height * 0.25
|
||||
let wayOutRingXAcceptance = self.universalSize.width * 0.425
|
||||
let wayOutRingYAcceptance = self.universalSize.height * 0.375
|
||||
|
||||
|
||||
if xdelta <= innerCircleXAcceptance && ydelta < innerCircleYAcceptance {
|
||||
return big
|
||||
} else if xdelta <= outerRingXAcceptance && ydelta < outerRingYAcceptance {
|
||||
return medium
|
||||
} else if xdelta <= wayOutRingXAcceptance && ydelta < wayOutRingYAcceptance {
|
||||
return small
|
||||
}
|
||||
return supersmall
|
||||
}
|
||||
|
||||
private static func distanceBetweenPoints(p1: CGPoint, p2: CGPoint) -> CGFloat {
|
||||
let xDist = abs(p2.x - p1.x)
|
||||
let yDist = abs(p2.y - p2.y)
|
||||
|
||||
return CGFloat(
|
||||
sqrt(
|
||||
pow(xDist, 2) + pow(yDist, 2)
|
||||
)
|
||||
)
|
||||
}
|
||||
//get row number given a value/item number in the list
|
||||
private func getRowNumber(_ value: Int) -> Int {
|
||||
return value / Self.totalColumns
|
||||
}
|
||||
private func honeycombOffSetX(_ value: Int) -> CGFloat {
|
||||
let rowNumber = self.getRowNumber(value)
|
||||
|
||||
// make every even row have the honeycomb offset
|
||||
if rowNumber % 2 == 0 {
|
||||
return Self.size / 2 + Self.spacingBetweenColumns / 2 // account for the column spacing from before
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
struct CircleGridView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CircleGridView()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
//
|
||||
// NavigationView.swift
|
||||
// nirvana-ios
|
||||
//
|
||||
// Created by Arjun Patel on 12/20/21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import NavigationStack
|
||||
|
||||
struct CircleNavigationView: View {
|
||||
@EnvironmentObject var navigationStack: NavigationStack
|
||||
@EnvironmentObject var authSessionStore: AuthSessionStore
|
||||
|
||||
@Binding var sheetView: SheetView?
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .center) {
|
||||
Menu {
|
||||
Button(role: .destructive) {
|
||||
print("manage inbox")
|
||||
self.sheetView = .inbox
|
||||
} label: {
|
||||
Label("inbox", systemImage: "envelope.badge")
|
||||
.foregroundColor(NirvanaColor.teal)
|
||||
}
|
||||
|
||||
Button {
|
||||
print("going to show contacts now")
|
||||
self.sheetView = .contacts
|
||||
} label: {
|
||||
Label("add peeps", systemImage: "person.2.wave.2")
|
||||
.foregroundColor(NirvanaColor.teal)
|
||||
}
|
||||
|
||||
Button {
|
||||
self.navigationStack.push(AddBasicInfoView())
|
||||
} label: {
|
||||
Label("profile", systemImage: "person.crop.circle")
|
||||
.foregroundColor(NirvanaColor.teal)
|
||||
}
|
||||
|
||||
Button {
|
||||
self.navigationStack.push(OnboardingTrioView())
|
||||
} label: {
|
||||
Label("why?", systemImage: "leaf")
|
||||
.foregroundColor(NirvanaColor.teal)
|
||||
}
|
||||
|
||||
Button {
|
||||
print("navigate to usenirvana.com")
|
||||
if let url = URL(string: "https://usenirvana.com") {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
|
||||
} label: {
|
||||
Label("about us", systemImage: "globe.americas")
|
||||
.foregroundColor(NirvanaColor.teal)
|
||||
}
|
||||
|
||||
Button(role: .destructive) {
|
||||
print("log out button clicked")
|
||||
|
||||
self.authSessionStore.logOut()
|
||||
|
||||
// once logged out, then the listener will listen and router view will take care of us thereafter
|
||||
} label: {
|
||||
Label("log out", systemImage: "rectangle.portrait.and.arrow.right")
|
||||
.foregroundColor(NirvanaColor.teal)
|
||||
}
|
||||
|
||||
} label: {
|
||||
if self.authSessionStore.user?.avatar == nil {
|
||||
Image("Artboards_Diversity_Avatars_by_Netguru-1")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.background(NirvanaColor.teal.opacity(0.1))
|
||||
.blur(radius: 5)
|
||||
.frame(width: 40, height: 40)
|
||||
.clipShape(Circle())
|
||||
.padding(5)
|
||||
} else {
|
||||
Image((self.authSessionStore.user?.avatar)!)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.background(NirvanaColor.teal.opacity(0.5))
|
||||
.frame(width: 40, height: 40)
|
||||
.clipShape(Circle())
|
||||
.padding(5)
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView(.horizontal) {
|
||||
HStack {
|
||||
Button {
|
||||
|
||||
} label: {
|
||||
Label("circle", systemImage: "peacesign")
|
||||
.font(.caption2)
|
||||
.foregroundColor(Color.white)
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 8)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 20, style: .continuous)
|
||||
.fill(Color.teal.opacity(0.5))
|
||||
).overlay(
|
||||
RoundedRectangle(cornerRadius: 20, style: .continuous)
|
||||
.strokeBorder(Color.white.opacity(01), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
|
||||
Button {
|
||||
|
||||
} label: {
|
||||
Label("groups", systemImage: "rectangle.3.group")
|
||||
.font(.caption2)
|
||||
.foregroundColor(Color.gray)
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 8)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 20, style: .continuous)
|
||||
.strokeBorder(Color.white.opacity(1), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
|
||||
Button {
|
||||
|
||||
} label: {
|
||||
Label("work", systemImage: "suitcase")
|
||||
.font(.caption2)
|
||||
.foregroundColor(Color.gray)
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 8)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 20, style: .continuous)
|
||||
.strokeBorder(Color.white.opacity(1), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
|
||||
struct CircleNavigationView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CircleNavigationView(sheetView: .constant(SheetView.contacts)).environmentObject(AuthSessionStore(isPreview: true))
|
||||
}
|
||||
}
|
||||
@@ -8,15 +8,19 @@
|
||||
import SwiftUI
|
||||
import NavigationStack
|
||||
|
||||
enum SheetView : Identifiable {
|
||||
var id: Self { self }
|
||||
case contacts
|
||||
case inbox
|
||||
}
|
||||
|
||||
struct InnerCircleView: View {
|
||||
@EnvironmentObject var authSessionStore: AuthSessionStore
|
||||
@EnvironmentObject var navigationStack: NavigationStack
|
||||
|
||||
let universalSize = UIScreen.main.bounds
|
||||
@State var sheetView: SheetView? = nil
|
||||
|
||||
// TESTING
|
||||
// users who will have a thumping thing because they sent a message
|
||||
@State var usersWithNewMessage: [Int] = []
|
||||
let universalSize = UIScreen.main.bounds
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
@@ -24,356 +28,31 @@ struct InnerCircleView: View {
|
||||
WavesGlassBackgroundView()
|
||||
|
||||
// content
|
||||
gridContent
|
||||
CircleGridView()
|
||||
|
||||
// header
|
||||
VStack(alignment: .leading) {
|
||||
header
|
||||
|
||||
bubbleNavigation
|
||||
CircleNavigationView(sheetView: self.$sheetView)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
// Path { path in
|
||||
//
|
||||
// //draw the axes
|
||||
// path.move(to: CGPoint(x:0, y:universalSize.height*0.5 ))
|
||||
//
|
||||
// path.addLine(to: CGPoint(x:universalSize.width, y: universalSize.height*0.5))
|
||||
//
|
||||
// path.move(to: CGPoint(x:universalSize.width*0.5, y:0 ))
|
||||
//
|
||||
// path.addLine(to: CGPoint(x:universalSize.width * 0.5, y: universalSize.height))
|
||||
//
|
||||
//
|
||||
// //TODO: draw the acceptance boxes
|
||||
// }
|
||||
// .stroke(Color.black)
|
||||
// .edgesIgnoringSafeArea(.all)
|
||||
|
||||
footerNavigation
|
||||
CircleFooterView()
|
||||
}
|
||||
.onAppear() {
|
||||
// TESTING
|
||||
// fake like these users sent a message to view
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
||||
self.usersWithNewMessage = [1, 5, 10]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: do cool animations with this footer background fill in while
|
||||
// recording or playing a message
|
||||
var footerNavigation: some View {
|
||||
ZStack(alignment:.bottomTrailing) {
|
||||
VStack(alignment:.center) {
|
||||
Spacer()
|
||||
|
||||
HStack {
|
||||
if self.selectedUserIndex != nil {
|
||||
Image("Artboards_Diversity_Avatars_by_Netguru-\(self.selectedUserIndex! + 1)")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.background(NirvanaColor.teal.opacity(0.1))
|
||||
.frame(width: 40, height: 40)
|
||||
.clipShape(Circle())
|
||||
.padding(5)
|
||||
}
|
||||
|
||||
|
||||
VStack (alignment: .leading) {
|
||||
Text("Sarth Shah")
|
||||
.font(.footnote)
|
||||
.foregroundColor(NirvanaColor.light)
|
||||
Text("sent 20 minutes ago")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Label("Replay", systemImage: "repeat.1.circle.fill")
|
||||
.font(.title2)
|
||||
.labelStyle(.iconOnly)
|
||||
.foregroundColor(Color.orange)
|
||||
|
||||
|
||||
Spacer(minLength: 65)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: 60) // 60 is the height of the footer control big circle
|
||||
.background(Color.white.opacity(0.5))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 30, style: .continuous))
|
||||
.shadow(color: Color.black.opacity(0.25), radius: 30, x: 0, y: 20)
|
||||
}
|
||||
|
||||
FooterControlsView()
|
||||
}
|
||||
.padding()
|
||||
.offset(
|
||||
x:0,
|
||||
y:self.selectedUserIndex == nil ? 150 : 0
|
||||
)
|
||||
.animation(Animation.spring(), value: self.selectedUserIndex)
|
||||
}
|
||||
|
||||
var bubbleNavigation : some View {
|
||||
HStack(alignment: .center) {
|
||||
Button {
|
||||
|
||||
} label: {
|
||||
Label("circle", systemImage: "peacesign")
|
||||
.font(.caption2)
|
||||
.foregroundColor(Color.white)
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 8)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 20, style: .continuous)
|
||||
.fill(Color.teal.opacity(0.5))
|
||||
).overlay(
|
||||
RoundedRectangle(cornerRadius: 20, style: .continuous)
|
||||
.strokeBorder(Color.white.opacity(01), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
|
||||
Button {
|
||||
|
||||
} label: {
|
||||
Label("groups", systemImage: "rectangle.3.group")
|
||||
.font(.caption2)
|
||||
.foregroundColor(Color.gray)
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 8)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 20, style: .continuous)
|
||||
.strokeBorder(Color.white.opacity(1), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
|
||||
Button {
|
||||
|
||||
} label: {
|
||||
Label("work", systemImage: "suitcase")
|
||||
.font(.caption2)
|
||||
.foregroundColor(Color.gray)
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 8)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 20, style: .continuous)
|
||||
.strokeBorder(Color.white.opacity(1), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
// magic variables for grid
|
||||
// TODO: change the number of columns based on the number of items
|
||||
// TODO: make the top left person or one horizontal person be in the center of screen...makes the top honeycomb pop
|
||||
private static var numberOfItems: Int = 12
|
||||
private static let size: CGFloat = UIScreen.main.bounds.height*0.15 // scaling with screen size
|
||||
private static let spacingBetweenColumns: CGFloat = 0
|
||||
private static let spacingBetweenRows: CGFloat = 0
|
||||
private static let totalColumns: Int = Int(log2(Double(Self.numberOfItems))) // scaling the circles and calculating column count
|
||||
|
||||
@State private var selectedUserIndex: Int? = nil
|
||||
|
||||
// TODO: change the size to adaptive or something to make outer ring items shrink their overall size and fit better
|
||||
let gridItems: [GridItem] = Array(
|
||||
repeating: GridItem(.fixed(Self.size), spacing: spacingBetweenColumns, alignment: .center),
|
||||
count: totalColumns)
|
||||
|
||||
private let big:CGFloat = 1
|
||||
private let medium:CGFloat = 0.75
|
||||
private let small:CGFloat = 0.5
|
||||
private let supersmall:CGFloat = 0.3
|
||||
|
||||
// TODO: centered honeycomb + side: maybe have two honeycombs: one in the center of 7 people and then everyone else surrounding
|
||||
// TODO: maybe make our "center" offset to a little to the top left to make it more honeycomb from top left
|
||||
private let center: CGPoint = CGPoint(x: UIScreen.main.bounds.width*0.5,y: UIScreen.main.bounds.height*0.5)
|
||||
|
||||
private var gridContent: some View {
|
||||
// main communication hub
|
||||
// TODO: client side, sort the honeycomb from top left to bottom right
|
||||
// based on most close friends to least
|
||||
ScrollViewReader {scrollReaderValue in
|
||||
ScrollView([.horizontal, .vertical], showsIndicators: false) {
|
||||
LazyVGrid(
|
||||
columns: gridItems,
|
||||
alignment: .center,
|
||||
spacing: Self.spacingBetweenRows
|
||||
) {
|
||||
ForEach(0..<Self.numberOfItems) { value in
|
||||
GeometryReader {gridProxy in
|
||||
let scale = getScale(proxy: gridProxy, itemNumber: value)
|
||||
|
||||
ZStack(alignment: .topTrailing) {
|
||||
Circle()
|
||||
.foregroundColor(self.getBubbleTint(userIndex: value)) // different color for a selected user
|
||||
.blur(radius: 8)
|
||||
.cornerRadius(100)
|
||||
|
||||
Image("Artboards_Diversity_Avatars_by_Netguru-\(value + 1)")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 20)
|
||||
}
|
||||
.scaleEffect(scale)
|
||||
.padding(scale * 5)
|
||||
} // geometry reader
|
||||
.offset(
|
||||
x: honeycombOffSetX(value),
|
||||
y: 0
|
||||
)
|
||||
.id(value) // id for scrollviewreader
|
||||
.frame(height: Self.size)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
// if user had previously selected user, put nil as a toggle
|
||||
if self.selectedUserIndex == value {
|
||||
self.selectedUserIndex = nil
|
||||
} else {
|
||||
self.selectedUserIndex = value
|
||||
}
|
||||
}
|
||||
|
||||
print("the selected item is: \(value)")
|
||||
}
|
||||
.animation(Animation.easeInOut(duration: 2).repeatForever(autoreverses: true), value: self.usersWithNewMessage)
|
||||
.animation(Animation.spring())
|
||||
}
|
||||
} // lazyvgrid
|
||||
.padding(.trailing, Self.size / 2 + Self.spacingBetweenColumns / 2) // because of the offset of last column
|
||||
.padding(.top, Self.size / 2 + Self.spacingBetweenRows / 2) // because we are going under the nav bar
|
||||
}// scrollview
|
||||
.onAppear {
|
||||
//TODO: was causing problems so commenting out
|
||||
//may not need anymore with bottom nav activation
|
||||
// scrollReaderValue.scrollTo(Self.numberOfItems / 2)
|
||||
}
|
||||
} // scrollview reader
|
||||
}
|
||||
|
||||
private func getBubbleTint(userIndex: Int) -> Color {
|
||||
if (userIndex == self.selectedUserIndex) { // user clicked on this user
|
||||
return NirvanaColor.dimTeal.opacity(0.4)
|
||||
} else if self.usersWithNewMessage.contains(userIndex) { // this user has a message
|
||||
return Color.orange.opacity(0.8)
|
||||
}
|
||||
|
||||
return Color.white.opacity(0.4)
|
||||
}
|
||||
|
||||
// getting the proxy of an individual item
|
||||
// and decoding into a scale that the item should take
|
||||
private func getScale(proxy: GeometryProxy, itemNumber: Int) -> CGFloat {
|
||||
// if this user is selected
|
||||
if itemNumber == self.selectedUserIndex {
|
||||
return big + 0.2
|
||||
}
|
||||
|
||||
// if this user that we are rendering
|
||||
// has a new message for us
|
||||
// TODO: too much memory usage doing this across the board
|
||||
if self.usersWithNewMessage.contains(itemNumber) {
|
||||
return big + 0.2
|
||||
}
|
||||
|
||||
let posRelToGrid = proxy.frame(in: .global) // relative to the entire screen
|
||||
let midX = getRowNumber(itemNumber) % 2 == 0 ? posRelToGrid.midX + (Self.size / 2) + (Self.spacingBetweenColumns / 2) : posRelToGrid.midX
|
||||
let midY = posRelToGrid.midY
|
||||
|
||||
let xdelta = abs(midX - self.center.x)
|
||||
let ydelta = abs(midY - self.center.y)
|
||||
|
||||
let innerCircleXAcceptance = Self.size * 0.75
|
||||
let innerCircleYAcceptance = Self.size * 0.75
|
||||
let outerRingXAcceptance = self.universalSize.width * 0.4
|
||||
let outerRingYAcceptance = self.universalSize.height * 0.25
|
||||
let wayOutRingXAcceptance = self.universalSize.width * 0.425
|
||||
let wayOutRingYAcceptance = self.universalSize.height * 0.375
|
||||
|
||||
|
||||
if xdelta <= innerCircleXAcceptance && ydelta < innerCircleYAcceptance {
|
||||
return big
|
||||
} else if xdelta <= outerRingXAcceptance && ydelta < outerRingYAcceptance {
|
||||
return medium
|
||||
} else if xdelta <= wayOutRingXAcceptance && ydelta < wayOutRingYAcceptance {
|
||||
return small
|
||||
}
|
||||
return supersmall
|
||||
}
|
||||
|
||||
private static func distanceBetweenPoints(p1: CGPoint, p2: CGPoint) -> CGFloat {
|
||||
let xDist = abs(p2.x - p1.x)
|
||||
let yDist = abs(p2.y - p2.y)
|
||||
|
||||
return CGFloat(
|
||||
sqrt(
|
||||
pow(xDist, 2) + pow(yDist, 2)
|
||||
)
|
||||
)
|
||||
}
|
||||
//get row number given a value/item number in the list
|
||||
private func getRowNumber(_ value: Int) -> Int {
|
||||
return value / Self.totalColumns
|
||||
}
|
||||
private func honeycombOffSetX(_ value: Int) -> CGFloat {
|
||||
let rowNumber = self.getRowNumber(value)
|
||||
|
||||
// make every even row have the honeycomb offset
|
||||
if rowNumber % 2 == 0 {
|
||||
return Self.size / 2 + Self.spacingBetweenColumns / 2 // account for the column spacing from before
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
private var header: some View {
|
||||
HStack(alignment: .center) {
|
||||
Image("undraw_handcrafts_leaf")
|
||||
.resizable()
|
||||
.frame(width: 20.0, height: 32.132)
|
||||
.padding(.leading, 20)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "message.and.waveform")
|
||||
.foregroundColor(Color.red.opacity(1))
|
||||
.font(.title2)
|
||||
|
||||
Menu {
|
||||
Button("Edit Profile") {
|
||||
self.navigationStack.push(AddBasicInfoView())
|
||||
}
|
||||
|
||||
Button("Friends") {
|
||||
print("manage friends page")
|
||||
}
|
||||
|
||||
Button("Log out") {
|
||||
print("log out button clicked")
|
||||
|
||||
self.authSessionStore.logOut()
|
||||
|
||||
// once logged out, then the listener will listen and router view will take care of us thereafter
|
||||
}
|
||||
} label: {
|
||||
Image("Artboards_Diversity_Avatars_by_Netguru-51")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.background(NirvanaColor.teal.opacity(0.1))
|
||||
.frame(width: 40, height: 40)
|
||||
.clipShape(Circle())
|
||||
.padding(5)
|
||||
.sheet(item: self.$sheetView) {page in
|
||||
switch page {
|
||||
case SheetView.contacts:
|
||||
FindFriendsView()
|
||||
case SheetView.inbox:
|
||||
InboxView()// test one for now
|
||||
default:
|
||||
Text("asdf")
|
||||
}
|
||||
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.white.opacity(0.5))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 30, style: .continuous))
|
||||
.shadow(color: Color.black.opacity(0.25), radius: 30, x: 0, y: 20)
|
||||
.padding(.horizontal)
|
||||
.onAppear() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user