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 */
|
/* Begin PBXBuildFile section */
|
||||||
8904DEC8277055E90071E6CA /* OnboardingTrioView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8904DEC7277055E90071E6CA /* OnboardingTrioView.swift */; };
|
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 */; };
|
891A15E127653A7000B26659 /* nirvana_iosApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 891A15E027653A7000B26659 /* nirvana_iosApp.swift */; };
|
||||||
891A15E327653A7000B26659 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 891A15E227653A7000B26659 /* ContentView.swift */; };
|
891A15E327653A7000B26659 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 891A15E227653A7000B26659 /* ContentView.swift */; };
|
||||||
891A15E527653A7100B26659 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 891A15E427653A7100B26659 /* Assets.xcassets */; };
|
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 */; };
|
894E80BE276ED7A700624B72 /* avatars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 894E80BD276ED7A700624B72 /* avatars.swift */; };
|
||||||
894E80C1276EFD0500624B72 /* AudioPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 894E80C0276EFD0400624B72 /* AudioPlayerView.swift */; };
|
894E80C1276EFD0500624B72 /* AudioPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 894E80C0276EFD0400624B72 /* AudioPlayerView.swift */; };
|
||||||
894E80C327702E5900624B72 /* AddBasicInfoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 894E80C227702E5800624B72 /* AddBasicInfoViewModel.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 */; };
|
89AD5573276DD790001658E0 /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89AD5572276DD790001658E0 /* SplashView.swift */; };
|
||||||
89AD5575276DE724001658E0 /* PhoneCodeVerificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89AD5574276DE724001658E0 /* PhoneCodeVerificationView.swift */; };
|
89AD5575276DE724001658E0 /* PhoneCodeVerificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89AD5574276DE724001658E0 /* PhoneCodeVerificationView.swift */; };
|
||||||
89AD5578276DEAB3001658E0 /* LogoHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89AD5577276DEAB3001658E0 /* LogoHeaderView.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 */; };
|
89BDCDEF2769B50C00577829 /* ContactsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89BDCDEE2769B50C00577829 /* ContactsView.swift */; };
|
||||||
89BDCDF12769E64200577829 /* Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89BDCDF02769E64200577829 /* Messages.swift */; };
|
89BDCDF12769E64200577829 /* Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89BDCDF02769E64200577829 /* Messages.swift */; };
|
||||||
89F05B9B276A87920002E9C7 /* ContactsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F05B9A276A87920002E9C7 /* ContactsViewModel.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 */; };
|
89F05BA4276ACC060002E9C7 /* ContactService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F05BA3276ACC060002E9C7 /* ContactService.swift */; };
|
||||||
89F05BA6276ACD830002E9C7 /* ContactsPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F05BA5276ACD830002E9C7 /* ContactsPickerView.swift */; };
|
89F05BA6276ACD830002E9C7 /* ContactsPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F05BA5276ACD830002E9C7 /* ContactsPickerView.swift */; };
|
||||||
89F1386D2767EB19006680ED /* SignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F1386C2767EB19006680ED /* SignInView.swift */; };
|
89F1386D2767EB19006680ED /* SignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F1386C2767EB19006680ED /* SignInView.swift */; };
|
||||||
@@ -74,6 +80,7 @@
|
|||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
8904DEC7277055E90071E6CA /* OnboardingTrioView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingTrioView.swift; sourceTree = "<group>"; };
|
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; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
89F1386C2767EB19006680ED /* SignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInView.swift; sourceTree = "<group>"; };
|
||||||
@@ -205,6 +217,7 @@
|
|||||||
891A15F62765639B00B26659 /* Views */ = {
|
891A15F62765639B00B26659 /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
896CE7032770AE3400DB474E /* Inbox */,
|
||||||
894E80BF276EFCF500624B72 /* AudioPlayer */,
|
894E80BF276EFCF500624B72 /* AudioPlayer */,
|
||||||
89F18AF3276ECF1800115B1E /* AddBasicInfo */,
|
89F18AF3276ECF1800115B1E /* AddBasicInfo */,
|
||||||
89F18AF2276ECD5D00115B1E /* OnBoarding */,
|
89F18AF2276ECD5D00115B1E /* OnBoarding */,
|
||||||
@@ -261,6 +274,7 @@
|
|||||||
89BDCDE62769616600577829 /* UIImage+Extension.swift */,
|
89BDCDE62769616600577829 /* UIImage+Extension.swift */,
|
||||||
89F18AEB276E92BF00115B1E /* CustomExtensions.swift */,
|
89F18AEB276E92BF00115B1E /* CustomExtensions.swift */,
|
||||||
894E80BD276ED7A700624B72 /* avatars.swift */,
|
894E80BD276ED7A700624B72 /* avatars.swift */,
|
||||||
|
896CE70827712CED00DB474E /* StringExtension.swift */,
|
||||||
);
|
);
|
||||||
path = Globals;
|
path = Globals;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -270,7 +284,7 @@
|
|||||||
children = (
|
children = (
|
||||||
89BDCDF02769E64200577829 /* Messages.swift */,
|
89BDCDF02769E64200577829 /* Messages.swift */,
|
||||||
891A160D27657CEA00B26659 /* User.swift */,
|
891A160D27657CEA00B26659 /* User.swift */,
|
||||||
89F05B9E276A8D360002E9C7 /* Contact.swift */,
|
89110713277166D6005797FA /* UserFriends.swift */,
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -301,6 +315,14 @@
|
|||||||
path = AudioPlayer;
|
path = AudioPlayer;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
896CE7032770AE3400DB474E /* Inbox */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
896CE7042770AE5D00DB474E /* InboxView.swift */,
|
||||||
|
);
|
||||||
|
path = Inbox;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
89AD5571276DD77D001658E0 /* SplashView */ = {
|
89AD5571276DD77D001658E0 /* SplashView */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -331,6 +353,9 @@
|
|||||||
children = (
|
children = (
|
||||||
89BCABE4276B3D58009E9B10 /* InnerCircleView.swift */,
|
89BCABE4276B3D58009E9B10 /* InnerCircleView.swift */,
|
||||||
89BCABE6276B3D64009E9B10 /* InnerCircleViewModel.swift */,
|
89BCABE6276B3D64009E9B10 /* InnerCircleViewModel.swift */,
|
||||||
|
896CE6FD27707E6B00DB474E /* CircleNavigationView.swift */,
|
||||||
|
896CE6FF2770847400DB474E /* CircleFooterView.swift */,
|
||||||
|
896CE7012770892F00DB474E /* CircleGridView.swift */,
|
||||||
);
|
);
|
||||||
path = InnerCircle;
|
path = InnerCircle;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -359,6 +384,7 @@
|
|||||||
89BDCDEE2769B50C00577829 /* ContactsView.swift */,
|
89BDCDEE2769B50C00577829 /* ContactsView.swift */,
|
||||||
89F05B9A276A87920002E9C7 /* ContactsViewModel.swift */,
|
89F05B9A276A87920002E9C7 /* ContactsViewModel.swift */,
|
||||||
89F05BA5276ACD830002E9C7 /* ContactsPickerView.swift */,
|
89F05BA5276ACD830002E9C7 /* ContactsPickerView.swift */,
|
||||||
|
896CE70627711BAB00DB474E /* FindFriendsView.swift */,
|
||||||
);
|
);
|
||||||
path = Contacts;
|
path = Contacts;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -502,9 +528,13 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
891A15E327653A7000B26659 /* ContentView.swift in Sources */,
|
891A15E327653A7000B26659 /* ContentView.swift in Sources */,
|
||||||
|
896CE7002770847400DB474E /* CircleFooterView.swift in Sources */,
|
||||||
894E80BE276ED7A700624B72 /* avatars.swift in Sources */,
|
894E80BE276ED7A700624B72 /* avatars.swift in Sources */,
|
||||||
89F05B9F276A8D360002E9C7 /* Contact.swift in Sources */,
|
896CE7052770AE5D00DB474E /* InboxView.swift in Sources */,
|
||||||
891A160327656FC700B26659 /* HomeView.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 */,
|
891A160B2765732700B26659 /* HomeViewModel.swift in Sources */,
|
||||||
89BCABEB276D2A09009E9B10 /* PhoneVerificationView.swift in Sources */,
|
89BCABEB276D2A09009E9B10 /* PhoneVerificationView.swift in Sources */,
|
||||||
892179E62765FECD001E6C60 /* FooterControlsViewModel.swift in Sources */,
|
892179E62765FECD001E6C60 /* FooterControlsViewModel.swift in Sources */,
|
||||||
@@ -520,6 +550,7 @@
|
|||||||
89BDCDE527695DB900577829 /* NirvanaImage.swift in Sources */,
|
89BDCDE527695DB900577829 /* NirvanaImage.swift in Sources */,
|
||||||
891A16132765A44300B26659 /* ChatsCarouselViewModel.swift in Sources */,
|
891A16132765A44300B26659 /* ChatsCarouselViewModel.swift in Sources */,
|
||||||
89F05B9B276A87920002E9C7 /* ContactsViewModel.swift in Sources */,
|
89F05B9B276A87920002E9C7 /* ContactsViewModel.swift in Sources */,
|
||||||
|
896CE6FE27707E6B00DB474E /* CircleNavigationView.swift in Sources */,
|
||||||
89AD5573276DD790001658E0 /* SplashView.swift in Sources */,
|
89AD5573276DD790001658E0 /* SplashView.swift in Sources */,
|
||||||
89F18AEC276E92BF00115B1E /* CustomExtensions.swift in Sources */,
|
89F18AEC276E92BF00115B1E /* CustomExtensions.swift in Sources */,
|
||||||
89F18AF1276ECAA300115B1E /* RouterView.swift in Sources */,
|
89F18AF1276ECAA300115B1E /* RouterView.swift in Sources */,
|
||||||
@@ -528,6 +559,7 @@
|
|||||||
89BCABED276D2B05009E9B10 /* WavesGlassBackgroundView.swift in Sources */,
|
89BCABED276D2B05009E9B10 /* WavesGlassBackgroundView.swift in Sources */,
|
||||||
89F05BA6276ACD830002E9C7 /* ContactsPickerView.swift in Sources */,
|
89F05BA6276ACD830002E9C7 /* ContactsPickerView.swift in Sources */,
|
||||||
8904DEC8277055E90071E6CA /* OnboardingTrioView.swift in Sources */,
|
8904DEC8277055E90071E6CA /* OnboardingTrioView.swift in Sources */,
|
||||||
|
896CE7022770892F00DB474E /* CircleGridView.swift in Sources */,
|
||||||
89BDCDEA27697F9C00577829 /* UserMenu.swift in Sources */,
|
89BDCDEA27697F9C00577829 /* UserMenu.swift in Sources */,
|
||||||
89BCABE7276B3D64009E9B10 /* InnerCircleViewModel.swift in Sources */,
|
89BCABE7276B3D64009E9B10 /* InnerCircleViewModel.swift in Sources */,
|
||||||
89AD557C276DF86A001658E0 /* FirestoreService.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 Foundation
|
||||||
import Firebase
|
import Firebase
|
||||||
|
import FirebaseCore
|
||||||
import FirebaseFirestore
|
import FirebaseFirestore
|
||||||
|
import FirebaseFirestoreSwift
|
||||||
|
|
||||||
class FirestoreService {
|
class FirestoreService {
|
||||||
enum Collection: String {
|
enum Collection: String {
|
||||||
case users = "users"
|
case users = "users"
|
||||||
case messages = "messages"
|
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()
|
private var db = Firestore.firestore()
|
||||||
@@ -51,6 +53,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) {
|
func createUser(user: User) {
|
||||||
do {
|
do {
|
||||||
let _ = try db.collection(Collection.users.rawValue).addDocument(from: user)
|
let _ = try db.collection(Collection.users.rawValue).addDocument(from: user)
|
||||||
@@ -81,4 +103,21 @@ class FirestoreService {
|
|||||||
return FieldValue.serverTimestamp()
|
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() {
|
func setupAuthListen() {
|
||||||
// monitor authentication changes using firebase
|
// 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")
|
print("auth listener activated")
|
||||||
|
|
||||||
guard let self = self else { return }
|
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
|
// 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
|
// if we have a user, create a new user model
|
||||||
print("auth listener: Got user: \(uid)")
|
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
|
// 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
|
self.firestoreService.getUserRealtime(userId: uid) {[weak self] realtimeUpdatedUser in
|
||||||
if realtimeUpdatedUser != nil {
|
DispatchQueue.main.async {
|
||||||
print("up to date user: \(realtimeUpdatedUser)")
|
if realtimeUpdatedUser != nil {
|
||||||
self?.user = realtimeUpdatedUser
|
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 {
|
} else {
|
||||||
// if we don't have a user, set our session to nil
|
// if we don't have a user, set our session to nil
|
||||||
self.user = nil
|
self.user = nil
|
||||||
|
|||||||
@@ -1,78 +1,82 @@
|
|||||||
|
////
|
||||||
|
//// ContactsPickerView.swift
|
||||||
|
//// nirvana-ios
|
||||||
|
////
|
||||||
|
//// Created by Arjun Patel on 12/15/21.
|
||||||
|
////
|
||||||
//
|
//
|
||||||
// ContactsPickerView.swift
|
//import SwiftUI
|
||||||
// nirvana-ios
|
//import Contacts
|
||||||
//
|
//
|
||||||
// Created by Arjun Patel on 12/15/21.
|
//struct ContactsPickerView: View {
|
||||||
|
// @Environment(\.dismiss) var dismiss
|
||||||
//
|
//
|
||||||
|
// // updates the parent view for it to do something with the new contact
|
||||||
import SwiftUI
|
// @State var selectedContact: CNContact?
|
||||||
import Contacts
|
//
|
||||||
|
// var contacts: [String: [CNContact]] = loadContactsGrouped()
|
||||||
struct ContactPickerView: View {
|
//
|
||||||
@Binding var showPicker: Bool
|
// var body: some View {
|
||||||
// updates the parent view for it to do something with the new contact
|
// let keys = (Array(contacts.keys) as [String]).sorted()
|
||||||
@Binding var selectedContact: CNContact?
|
// NavigationView {
|
||||||
|
// List {
|
||||||
var contacts: [String: [CNContact]] = loadContactsGrouped()
|
// ForEach(keys.sorted(), id: \.self) { key in
|
||||||
|
// Section(header: Text(key)) {
|
||||||
var body: some View {
|
// ForEach(contacts[key] ?? [CNContact](), id: \.identifier) { contact in
|
||||||
let keys = (Array(contacts.keys) as [String]).sorted()
|
// ContactRow(contact: contact, selectedContact: self.$selectedContact)
|
||||||
NavigationView {
|
// }
|
||||||
List {
|
// }
|
||||||
ForEach(keys.sorted(), id: \.self) { key in
|
// }
|
||||||
Section(header: Text(key)) {
|
// }
|
||||||
ForEach(contacts[key] ?? [CNContact](), id: \.identifier) { contact in
|
// .listStyle(GroupedListStyle())
|
||||||
ContactRow(contact: contact, showPicker: self.$showPicker, selectedContact: self.$selectedContact)
|
// .navigationTitle("All Contacts".localized)
|
||||||
}
|
// .navigationBarItems(trailing: Button(action: {
|
||||||
}
|
// dismiss()
|
||||||
}
|
//
|
||||||
}
|
// }, label: {
|
||||||
.listStyle(GroupedListStyle())
|
// Text("Cancel".localized)
|
||||||
.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()
|
||||||
static func loadContactsGrouped() -> [String: [CNContact]] {
|
// print(contacts)
|
||||||
let provider = ContactsViewModel()
|
// let group = Dictionary(grouping: contacts) { (contact) -> String in
|
||||||
let contacts = provider.fetchContacts()
|
// return String(contact.familyName.first ?? "?")
|
||||||
|
// }
|
||||||
let group = Dictionary(grouping: contacts) { (contact) -> String in
|
//
|
||||||
return String(contact.familyName.first ?? "?")
|
// print(group)
|
||||||
}
|
//
|
||||||
|
// return group
|
||||||
return group
|
// }
|
||||||
}
|
//}
|
||||||
}
|
//
|
||||||
|
//
|
||||||
|
//struct ContactRow: View {
|
||||||
struct ContactRow: View {
|
// var contact: CNContact
|
||||||
var contact: CNContact
|
// @Binding var selectedContact: CNContact?
|
||||||
@Binding var showPicker: Bool
|
//
|
||||||
@Binding var selectedContact: CNContact?
|
// var body: some View {
|
||||||
|
// Button(action: {
|
||||||
var body: some View {
|
// selectContact()
|
||||||
Button(action: {
|
// }) {
|
||||||
selectContact()
|
// HStack {
|
||||||
}) {
|
// Text("\(contact.familyName)").fontWeight(.bold)
|
||||||
HStack {
|
// Text("\(contact.givenName) \(contact.middleName)")
|
||||||
Text("\(contact.familyName)").fontWeight(.bold)
|
// }
|
||||||
Text("\(contact.givenName) \(contact.middleName)")
|
// .foregroundColor(.black)
|
||||||
}
|
// }
|
||||||
.foregroundColor(.black)
|
// }
|
||||||
}
|
//
|
||||||
}
|
// func selectContact() {
|
||||||
|
// self.selectedContact = self.contact
|
||||||
func selectContact() {
|
// }
|
||||||
self.selectedContact = self.contact
|
//}
|
||||||
self.showPicker = false
|
//
|
||||||
}
|
//extension String {
|
||||||
}
|
// var localized: String {
|
||||||
|
// return NSLocalizedString(self, comment: "")
|
||||||
extension String {
|
// }
|
||||||
var localized: String {
|
//}
|
||||||
return NSLocalizedString(self, comment: "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ struct ContactsView: View {
|
|||||||
.shadow(radius:10)
|
.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 {
|
Button {
|
||||||
self.navigationStack.push(InnerCircleView())
|
self.navigationStack.push(InnerCircleView())
|
||||||
@@ -53,7 +53,7 @@ struct ContactsView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.sheet(isPresented: self.$showPicker) {
|
.sheet(isPresented: self.$showPicker) {
|
||||||
ContactPickerView(showPicker: self.$showPicker, selectedContact: self.$selectedContact)
|
// ContactPickerView(showPicker: self.$showPicker, selectedContact: self.$selectedContact)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.background(NirvanaColor.bgLightGrey)
|
.background(NirvanaColor.bgLightGrey)
|
||||||
|
|||||||
@@ -11,17 +11,18 @@ import SwiftUI
|
|||||||
|
|
||||||
class ContactsViewModel : ObservableObject {
|
class ContactsViewModel : ObservableObject {
|
||||||
@Published var showPermissionAlert = false
|
@Published var showPermissionAlert = false
|
||||||
|
@Published var contacts: [String: ContactsViewModelContact] = [:]
|
||||||
|
|
||||||
let store: CNContactStore
|
private var firestoreService = FirestoreService()
|
||||||
let keys: [CNKeyDescriptor]
|
|
||||||
|
let store: CNContactStore = CNContactStore()
|
||||||
|
let keys: [CNKeyDescriptor] = [CNContactImageDataKey as CNKeyDescriptor,
|
||||||
|
CNContactPhoneNumbersKey as CNKeyDescriptor,
|
||||||
|
CNContactEmailAddressesKey as CNKeyDescriptor,
|
||||||
|
CNContactFormatter.descriptorForRequiredKeys(for: .fullName)]
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
store = CNContactStore()
|
self.fetchContacts()
|
||||||
|
|
||||||
keys = [CNContactImageDataKey as CNKeyDescriptor,
|
|
||||||
CNContactPhoneNumbersKey as CNKeyDescriptor,
|
|
||||||
CNContactEmailAddressesKey as CNKeyDescriptor,
|
|
||||||
CNContactFormatter.descriptorForRequiredKeys(for: .fullName)]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func openSettings() {
|
func openSettings() {
|
||||||
@@ -30,37 +31,86 @@ class ContactsViewModel : ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func requestAccess() {
|
func requestAccess() {
|
||||||
store.requestAccess(for: .contacts) {
|
|
||||||
(granted, error) in
|
store.requestAccess(for: .contacts) {(granted, error) in
|
||||||
|
// TODO: do something useful with completionHandler
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// TODO do something useful with completionHandler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchContacts(sortOrder: CNContactSortOrder = .userDefault) -> [CNContact] {
|
func fetchContacts() {
|
||||||
let authStatus = CNContactStore.authorizationStatus(for: .contacts)
|
let authStatus = CNContactStore.authorizationStatus(for: .contacts)
|
||||||
|
|
||||||
if authStatus == .notDetermined {
|
if authStatus == .notDetermined {
|
||||||
requestAccess()
|
requestAccess()
|
||||||
} else if authStatus == .restricted {
|
} 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
|
self.showPermissionAlert = true
|
||||||
}
|
}
|
||||||
|
|
||||||
let fetchRequest = CNContactFetchRequest(keysToFetch: keys)
|
let fetchRequest = CNContactFetchRequest(keysToFetch: keys)
|
||||||
fetchRequest.sortOrder = sortOrder
|
fetchRequest.sortOrder = .userDefault
|
||||||
|
|
||||||
var fetchedContacts = [CNContact]()
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try store.enumerateContacts(with: fetchRequest) {
|
try store.enumerateContacts(with: fetchRequest) {(contact, stop) in
|
||||||
(contact, stop) in
|
// only checking if american number or not for now
|
||||||
fetchedContacts.append(contact)
|
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 {
|
} catch {
|
||||||
print("Unable to fetch contacts. \(error)")
|
print("Unable to fetch contacts. \(error)")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return fetchedContacts
|
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)
|
||||||
|
|
||||||
|
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 SwiftUI
|
||||||
import NavigationStack
|
import NavigationStack
|
||||||
|
|
||||||
|
enum SheetView : Identifiable {
|
||||||
|
var id: Self { self }
|
||||||
|
case contacts
|
||||||
|
case inbox
|
||||||
|
}
|
||||||
|
|
||||||
struct InnerCircleView: View {
|
struct InnerCircleView: View {
|
||||||
@EnvironmentObject var authSessionStore: AuthSessionStore
|
@EnvironmentObject var authSessionStore: AuthSessionStore
|
||||||
@EnvironmentObject var navigationStack: NavigationStack
|
@EnvironmentObject var navigationStack: NavigationStack
|
||||||
|
|
||||||
let universalSize = UIScreen.main.bounds
|
@State var sheetView: SheetView? = nil
|
||||||
|
|
||||||
// TESTING
|
let universalSize = UIScreen.main.bounds
|
||||||
// users who will have a thumping thing because they sent a message
|
|
||||||
@State var usersWithNewMessage: [Int] = []
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
@@ -24,357 +28,32 @@ struct InnerCircleView: View {
|
|||||||
WavesGlassBackgroundView()
|
WavesGlassBackgroundView()
|
||||||
|
|
||||||
// content
|
// content
|
||||||
gridContent
|
CircleGridView()
|
||||||
|
|
||||||
// header
|
// header
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
header
|
|
||||||
|
|
||||||
bubbleNavigation
|
CircleNavigationView(sheetView: self.$sheetView)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path { path in
|
CircleFooterView()
|
||||||
//
|
}
|
||||||
// //draw the axes
|
.sheet(item: self.$sheetView) {page in
|
||||||
// path.move(to: CGPoint(x:0, y:universalSize.height*0.5 ))
|
switch page {
|
||||||
//
|
case SheetView.contacts:
|
||||||
// path.addLine(to: CGPoint(x:universalSize.width, y: universalSize.height*0.5))
|
FindFriendsView()
|
||||||
//
|
case SheetView.inbox:
|
||||||
// path.move(to: CGPoint(x:universalSize.width*0.5, y:0 ))
|
InboxView()// test one for now
|
||||||
//
|
default:
|
||||||
// path.addLine(to: CGPoint(x:universalSize.width * 0.5, y: universalSize.height))
|
Text("asdf")
|
||||||
//
|
}
|
||||||
//
|
|
||||||
// //TODO: draw the acceptance boxes
|
|
||||||
// }
|
|
||||||
// .stroke(Color.black)
|
|
||||||
// .edgesIgnoringSafeArea(.all)
|
|
||||||
|
|
||||||
footerNavigation
|
|
||||||
}
|
}
|
||||||
.onAppear() {
|
.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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
.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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InnerCircleView_Previews: PreviewProvider {
|
struct InnerCircleView_Previews: PreviewProvider {
|
||||||
|
|||||||
Reference in New Issue
Block a user