diff --git a/Rethread/Rethread.xcodeproj/project.pbxproj b/Rethread/Rethread.xcodeproj/project.pbxproj index 0f1951a..6b8cfca 100644 --- a/Rethread/Rethread.xcodeproj/project.pbxproj +++ b/Rethread/Rethread.xcodeproj/project.pbxproj @@ -7,8 +7,8 @@ objects = { /* Begin PBXBuildFile section */ - 7C3A04B62B0AD9DF00E36883 /* IntroView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C3A04B22B0AD9DF00E36883 /* IntroView.swift */; }; 7C3A04B82B0AD9DF00E36883 /* OnboardingApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C3A04B42B0AD9DF00E36883 /* OnboardingApp.swift */; }; + 983014C22B451A3E004A6A07 /* NavigationTransitions in Frameworks */ = {isa = PBXBuildFile; productRef = 983014C12B451A3E004A6A07 /* NavigationTransitions */; }; 983286B92B05698F00851B19 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 983286B82B05698F00851B19 /* Assets.xcassets */; }; 983286BC2B05698F00851B19 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 983286BB2B05698F00851B19 /* Preview Assets.xcassets */; }; 983286C62B05698F00851B19 /* RethreadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 983286C52B05698F00851B19 /* RethreadTests.swift */; }; @@ -17,11 +17,17 @@ 9868A7D82B1FAD280063D9BA /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9868A7D72B1FAD280063D9BA /* WelcomeView.swift */; }; 9868A7DA2B1FADA70063D9BA /* QuestionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9868A7D92B1FADA70063D9BA /* QuestionView.swift */; }; 9868A7DE2B1FAE780063D9BA /* ButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9868A7DD2B1FAE780063D9BA /* ButtonStyles.swift */; }; - 98A183862B38273B001E324A /* LoginSignupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A183852B38273B001E324A /* LoginSignupView.swift */; }; + 988A0F952B3BFB4C00ACDC90 /* SignUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 988A0F942B3BFB4C00ACDC90 /* SignUpView.swift */; }; + 988A0F992B3C112300ACDC90 /* DatePickerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 988A0F982B3C112300ACDC90 /* DatePickerModel.swift */; }; + 9897C7732B3E6C4000EDE9D9 /* CustomDropdownMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9897C7722B3E6C4000EDE9D9 /* CustomDropdownMenu.swift */; }; + 98A183862B38273B001E324A /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A183852B38273B001E324A /* MainView.swift */; }; 98A183882B383C0B001E324A /* SignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A183872B383C0B001E324A /* SignInView.swift */; }; 98A1838A2B383C47001E324A /* VerificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A183892B383C47001E324A /* VerificationView.swift */; }; 98A1838C2B38D32A001E324A /* ColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98A1838B2B38D32A001E324A /* ColorPalette.swift */; }; 98AE7AB92B1FAADB0029C2F7 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98AE7AB82B1FAADB0029C2F7 /* OnboardingView.swift */; }; + 98F525C92B469BBD00CCAD78 /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F525C82B469BBD00CCAD78 /* PlayerView.swift */; }; + 98F525CB2B469BEB00CCAD78 /* UIPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F525CA2B469BEB00CCAD78 /* UIPlayerView.swift */; }; + 98F525CE2B46A96F00CCAD78 /* TermsAndConditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F525CD2B46A96F00CCAD78 /* TermsAndConditions.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -42,7 +48,6 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 7C3A04B22B0AD9DF00E36883 /* IntroView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntroView.swift; sourceTree = ""; }; 7C3A04B42B0AD9DF00E36883 /* OnboardingApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingApp.swift; sourceTree = ""; }; 983286B12B05698E00851B19 /* Rethread.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Rethread.app; sourceTree = BUILT_PRODUCTS_DIR; }; 983286B82B05698F00851B19 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -55,11 +60,17 @@ 9868A7D72B1FAD280063D9BA /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; }; 9868A7D92B1FADA70063D9BA /* QuestionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionView.swift; sourceTree = ""; }; 9868A7DD2B1FAE780063D9BA /* ButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonStyles.swift; sourceTree = ""; }; - 98A183852B38273B001E324A /* LoginSignupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginSignupView.swift; sourceTree = ""; }; + 988A0F942B3BFB4C00ACDC90 /* SignUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpView.swift; sourceTree = ""; }; + 988A0F982B3C112300ACDC90 /* DatePickerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerModel.swift; sourceTree = ""; }; + 9897C7722B3E6C4000EDE9D9 /* CustomDropdownMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDropdownMenu.swift; sourceTree = ""; }; + 98A183852B38273B001E324A /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; 98A183872B383C0B001E324A /* SignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInView.swift; sourceTree = ""; }; 98A183892B383C47001E324A /* VerificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationView.swift; sourceTree = ""; }; 98A1838B2B38D32A001E324A /* ColorPalette.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPalette.swift; sourceTree = ""; }; 98AE7AB82B1FAADB0029C2F7 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; + 98F525C82B469BBD00CCAD78 /* PlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = ""; }; + 98F525CA2B469BEB00CCAD78 /* UIPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIPlayerView.swift; sourceTree = ""; }; + 98F525CD2B46A96F00CCAD78 /* TermsAndConditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsAndConditions.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -67,6 +78,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 983014C22B451A3E004A6A07 /* NavigationTransitions in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -90,6 +102,8 @@ 7C9AA2CC2B0EC02600EF6F36 /* Models */ = { isa = PBXGroup; children = ( + 988A0F982B3C112300ACDC90 /* DatePickerModel.swift */, + 98F525CD2B46A96F00CCAD78 /* TermsAndConditions.swift */, ); path = Models; sourceTree = ""; @@ -133,13 +147,15 @@ 983286B32B05698E00851B19 /* Rethread */ = { isa = PBXGroup; children = ( - 7C9AA2D42B0ECADF00EF6F36 /* Styles */, 7C9AA2D12B0EC3BC00EF6F36 /* Extensions */, + 7C9AA2D42B0ECADF00EF6F36 /* Styles */, + 9897C7712B3E6BF600EDE9D9 /* Components */, + 98F525CC2B46A8E900CCAD78 /* VideoPlayer */, 7C9AA2CC2B0EC02600EF6F36 /* Models */, - 7C3A04B22B0AD9DF00E36883 /* IntroView.swift */, 98AE7AB82B1FAADB0029C2F7 /* OnboardingView.swift */, - 98A183852B38273B001E324A /* LoginSignupView.swift */, + 98A183852B38273B001E324A /* MainView.swift */, 98A183872B383C0B001E324A /* SignInView.swift */, + 988A0F942B3BFB4C00ACDC90 /* SignUpView.swift */, 98A183892B383C47001E324A /* VerificationView.swift */, 9868A7D92B1FADA70063D9BA /* QuestionView.swift */, 9868A7D72B1FAD280063D9BA /* WelcomeView.swift */, @@ -175,6 +191,23 @@ path = RethreadUITests; sourceTree = ""; }; + 9897C7712B3E6BF600EDE9D9 /* Components */ = { + isa = PBXGroup; + children = ( + 9897C7722B3E6C4000EDE9D9 /* CustomDropdownMenu.swift */, + ); + path = Components; + sourceTree = ""; + }; + 98F525CC2B46A8E900CCAD78 /* VideoPlayer */ = { + isa = PBXGroup; + children = ( + 98F525C82B469BBD00CCAD78 /* PlayerView.swift */, + 98F525CA2B469BEB00CCAD78 /* UIPlayerView.swift */, + ); + path = VideoPlayer; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -191,6 +224,9 @@ dependencies = ( ); name = Rethread; + packageProductDependencies = ( + 983014C12B451A3E004A6A07 /* NavigationTransitions */, + ); productName = Rethread; productReference = 983286B12B05698E00851B19 /* Rethread.app */; productType = "com.apple.product-type.application"; @@ -264,6 +300,9 @@ Base, ); mainGroup = 983286A82B05698E00851B19; + packageReferences = ( + 983014C02B451A3E004A6A07 /* XCRemoteSwiftPackageReference "swiftui-navigation-transitions" */, + ); productRefGroup = 983286B22B05698E00851B19 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -308,13 +347,18 @@ files = ( 9868A7DA2B1FADA70063D9BA /* QuestionView.swift in Sources */, 7C3A04B82B0AD9DF00E36883 /* OnboardingApp.swift in Sources */, - 7C3A04B62B0AD9DF00E36883 /* IntroView.swift in Sources */, 9868A7DE2B1FAE780063D9BA /* ButtonStyles.swift in Sources */, 98AE7AB92B1FAADB0029C2F7 /* OnboardingView.swift in Sources */, + 988A0F952B3BFB4C00ACDC90 /* SignUpView.swift in Sources */, 98A1838C2B38D32A001E324A /* ColorPalette.swift in Sources */, - 98A183862B38273B001E324A /* LoginSignupView.swift in Sources */, + 98F525CE2B46A96F00CCAD78 /* TermsAndConditions.swift in Sources */, + 98A183862B38273B001E324A /* MainView.swift in Sources */, + 98F525C92B469BBD00CCAD78 /* PlayerView.swift in Sources */, 98A1838A2B383C47001E324A /* VerificationView.swift in Sources */, + 98F525CB2B469BEB00CCAD78 /* UIPlayerView.swift in Sources */, 98A183882B383C0B001E324A /* SignInView.swift in Sources */, + 988A0F992B3C112300ACDC90 /* DatePickerModel.swift in Sources */, + 9897C7732B3E6C4000EDE9D9 /* CustomDropdownMenu.swift in Sources */, 9868A7D82B1FAD280063D9BA /* WelcomeView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -644,6 +688,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 983014C02B451A3E004A6A07 /* XCRemoteSwiftPackageReference "swiftui-navigation-transitions" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/davdroman/swiftui-navigation-transitions.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.13.3; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 983014C12B451A3E004A6A07 /* NavigationTransitions */ = { + isa = XCSwiftPackageProductDependency; + package = 983014C02B451A3E004A6A07 /* XCRemoteSwiftPackageReference "swiftui-navigation-transitions" */; + productName = NavigationTransitions; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 983286A92B05698E00851B19 /* Project object */; } diff --git a/Rethread/Rethread.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Rethread/Rethread.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..81d08fe --- /dev/null +++ b/Rethread/Rethread.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,23 @@ +{ + "pins" : [ + { + "identity" : "swiftui-introspect", + "kind" : "remoteSourceControl", + "location" : "https://github.com/siteline/swiftui-introspect", + "state" : { + "revision" : "9e1cc02a65b22e09a8251261cccbccce02731fc5", + "version" : "1.1.1" + } + }, + { + "identity" : "swiftui-navigation-transitions", + "kind" : "remoteSourceControl", + "location" : "https://github.com/davdroman/swiftui-navigation-transitions.git", + "state" : { + "revision" : "a6a3c70ad5d771bd1e927fb55897231cd9592024", + "version" : "0.13.3" + } + } + ], + "version" : 2 +} diff --git a/Rethread/Rethread.xcodeproj/project.xcworkspace/xcuserdata/parsakargari.xcuserdatad/UserInterfaceState.xcuserstate b/Rethread/Rethread.xcodeproj/project.xcworkspace/xcuserdata/parsakargari.xcuserdatad/UserInterfaceState.xcuserstate index 5540672..1381d7f 100644 Binary files a/Rethread/Rethread.xcodeproj/project.xcworkspace/xcuserdata/parsakargari.xcuserdatad/UserInterfaceState.xcuserstate and b/Rethread/Rethread.xcodeproj/project.xcworkspace/xcuserdata/parsakargari.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Rethread/Rethread.xcodeproj/xcuserdata/parsakargari.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Rethread/Rethread.xcodeproj/xcuserdata/parsakargari.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..a9d4ab5 --- /dev/null +++ b/Rethread/Rethread.xcodeproj/xcuserdata/parsakargari.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/Rethread/Rethread/.DS_Store b/Rethread/Rethread/.DS_Store new file mode 100644 index 0000000..628e65e Binary files /dev/null and b/Rethread/Rethread/.DS_Store differ diff --git a/Rethread/Rethread/Assets.xcassets/starSky.imageset/Contents.json b/Rethread/Rethread/Assets.xcassets/starSky.imageset/Contents.json new file mode 100644 index 0000000..4aa7228 --- /dev/null +++ b/Rethread/Rethread/Assets.xcassets/starSky.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "starSky.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Rethread/Rethread/Assets.xcassets/starSky.imageset/starSky.jpg b/Rethread/Rethread/Assets.xcassets/starSky.imageset/starSky.jpg new file mode 100644 index 0000000..9a64bd5 Binary files /dev/null and b/Rethread/Rethread/Assets.xcassets/starSky.imageset/starSky.jpg differ diff --git a/Rethread/Rethread/Components/CustomDropdownMenu.swift b/Rethread/Rethread/Components/CustomDropdownMenu.swift new file mode 100644 index 0000000..6020c5f --- /dev/null +++ b/Rethread/Rethread/Components/CustomDropdownMenu.swift @@ -0,0 +1,130 @@ +import SwiftUI + + +struct CustomDropdownMenu: View { + @State var isSelecting = false + @State var selectionTitle = "" + @State var selectedRowId = 0 + let items: [DropdownItem] + + + var body: some View { + GeometryReader { _ in + VStack { + HStack { + Text(selectionTitle) + .font(.system(size: 16, weight: .semibold, design: .rounded)) + .animation(.none) + Spacer() + Image(systemName: "chevron.down") + .font(.system(size: 16, weight: .semibold)) + .rotationEffect(.degrees( isSelecting ? -180 : 0)) + + } + .padding(.horizontal) + .padding(.vertical, 1.5) + .foregroundColor(Color.primaryTextColor) + + if isSelecting { + Divider() + .background(.white) + .padding(.horizontal) + + VStack(spacing: 5) { + dropDownItemsList() + } + } + + } + .frame(maxWidth: .infinity) + .padding(.vertical) + .background( + RoundedRectangle(cornerRadius: 10) + .strokeBorder(Color.gray, lineWidth: 1) + .background(Color.white) + .cornerRadius(10) + ) + .cornerRadius(5) + .onTapGesture { + isSelecting.toggle() + } + .onAppear { + selectedRowId = items[0].id + selectionTitle = items[0].title + } + .animation(.easeInOut(duration: 0.3)) + } + } + + private func dropDownItemsList() -> some View { + ForEach(items) { item in + DropdownMenuItemView(isSelecting: $isSelecting, selectionId: $selectedRowId, selectiontitle: $selectionTitle, item: item) + } + } +} + +struct DropdownItem: Identifiable { + let id: Int + let title: String + let onSelect: () -> Void +} + +struct DropdownMenuItemView: View { + @Binding var isSelecting: Bool + @Binding var selectionId: Int + @Binding var selectiontitle: String + + let item: DropdownItem + + var body: some View { + Button(action: { + isSelecting = false + DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { + selectionId = item.id + } + selectiontitle = item.title + item.onSelect() + }) { + HStack { + Text(item.title) + .font(.system(size: 16, weight: .regular, design: .rounded)) + + Spacer() + Image(systemName: "checkmark") + .font(.system(size: 12, weight: .bold)) + .opacity(selectionId == item.id ? 1 : 0) + } + .padding(.horizontal) + .padding(.vertical, 5) + .foregroundColor(.gray) + + } + } +} + +struct CustomDropdownMenu_Previews: PreviewProvider { + static var previews: some View { + CustomDropdownMenu(items: [ + DropdownItem(id: 1, title: "Messages", onSelect: {}), + DropdownItem(id: 2, title: "Archived", onSelect: {}), + DropdownItem(id: 3, title: "Trash", onSelect: {}) + ]) + .padding(.horizontal) + } +} + +extension View { + func customDropdownMenu(items: [DropdownItem]) -> some View { + ZStack { + VStack { + CustomDropdownMenu(items: items) + .padding(.horizontal) + Spacer() + } + .zIndex(10) + self + .offset(y: 60) + .zIndex(1) + } + } +} diff --git a/Rethread/Rethread/IntroView.swift b/Rethread/Rethread/IntroView.swift deleted file mode 100644 index 11b265c..0000000 --- a/Rethread/Rethread/IntroView.swift +++ /dev/null @@ -1,28 +0,0 @@ -import SwiftUI - -struct IntroView: View { - @AppStorage("signed_in") var currentUserSignedIn: Bool = false - -// init() { -// UserDefaults.standard.set(false, forKey: "signed_in") -// print("Current user signed in: \(currentUserSignedIn)") -// } - - var body: some View { - ZStack { - if currentUserSignedIn { - // Display main content - // Make Onboarding conditional later - OnboardingView() - } else { - // Display the LoginSignupView - LoginSignupView(currentUserSignedIn: $currentUserSignedIn) - } - } - } -} - - -#Preview { - IntroView() -} diff --git a/Rethread/Rethread/LoginSignupView.swift b/Rethread/Rethread/LoginSignupView.swift deleted file mode 100644 index 3d63a2e..0000000 --- a/Rethread/Rethread/LoginSignupView.swift +++ /dev/null @@ -1,63 +0,0 @@ -import SwiftUI - -struct LoginSignupView: View { - @Binding var currentUserSignedIn: Bool - @State private var showingSignIn = false - // ... other properties and logic - - var body: some View { - VStack { - Spacer() - - Image(systemName: "photo") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 30, height: 30) - .foregroundColor(.gray) - .padding(.bottom,20) - .frame(maxHeight: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/) - - // Description text - Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit purus sit amet luctus venenatis, lectus magna fringilla urna.") - .font(.body) - .foregroundColor(.gray) - .multilineTextAlignment(.center) - .padding(.horizontal, 25) - .padding(.bottom, 55) - - - HStack(spacing: 30) { - // Log In Button - Button(action: { - showingSignIn = true - }) { - HStack { - Text("Log In") - Image(systemName: "arrow.right.circle") - } - .frame(maxWidth: .infinity) - } - .buttonStyle(PrimaryButtonStyle()) - - // Sign Up Button - Button(action: { - // Sign up - }) { - HStack { - Text("Join Us") - Image(systemName: "person.crop.circle.badge.plus") - } - .frame(maxWidth: .infinity) - } - .buttonStyle(SecondaryButtonStyle()) - } - .padding(.horizontal) - .padding(.bottom, 15) - - } - .frame(maxHeight: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/) - .fullScreenCover(isPresented: $showingSignIn) { - SignInView(currentUserSignedIn: $currentUserSignedIn) - } - } -} diff --git a/Rethread/Rethread/MainView.swift b/Rethread/Rethread/MainView.swift new file mode 100644 index 0000000..c9a7703 --- /dev/null +++ b/Rethread/Rethread/MainView.swift @@ -0,0 +1,129 @@ +import SwiftUI +import NavigationTransitions + +#if canImport(UIKit) +extension View { + func hideKeyboard() { + UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } +} +#endif + +struct MainView: View { + @AppStorage("isLoggedIn") var isLoggedIn = false + @State private var path: [String] = [] + + var body: some View { + NavigationStack (path: $path) { + ZStack (alignment: .bottom) { + // Add Image + Image("starSky") + .resizable() + .overlay(Color.black.opacity(0.01)) + .clipped() + .edgesIgnoringSafeArea(.all) + .padding(.bottom, 210) + + // Description text + VStack { + Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit purus sit amet luctus venenatis, lectus magna fringilla urna.") + .font(.body) + .foregroundColor(.gray) + .multilineTextAlignment(.center) + .padding(.horizontal, 25) + .padding(.bottom, 55) + .padding(.top, 35) + + + + HStack(spacing: 30) { + // Log In Button + Button(action: { + path.append("SignInView") + }) { + HStack { + Text("Log In") + Image(systemName: "arrow.right.circle") + } + .frame(maxWidth: .infinity) + } + .buttonStyle(PrimaryButtonStyle()) + + // Sign Up Button + Button(action: { + path.append("SignUpView") + }) { + HStack { + Text("Join Us") + Image(systemName: "person.crop.circle.badge.plus") + } + .frame(maxWidth: .infinity) + } + .buttonStyle(SecondaryButtonStyle()) + } + .padding(.horizontal) + .padding(.bottom, 15) + .navigationDestination(for: String.self) { view in + if view == "SignInView" { + SignInView(path: $path) + } else if view == "SignUpView" { + SignUpView(path: $path) + } else if view == "WelcomeView" { + WelcomeView(path: $path) + } + } + } + .frame(maxWidth: .infinity) + .background(Color.white) // Background for text readability + .cornerRadius(20) + .edgesIgnoringSafeArea(.bottom) + } + .frame(maxHeight: .infinity) + .onAppear() { + if isLoggedIn { + + // Get user data from database and store in userDefaults + // We want to check if the user has onboarding completed or not + // If not, we want to show the onboarding view + + // If logged in, and onboarding is not completed, show onboarding + } + } + } + .navigationTransition(.slide(axis: .vertical)) + // Safe area for iPhone X and above + .edgesIgnoringSafeArea(.all) // This will extend the content to the edges + .background(Color(UIColor.systemGray6)) + .overlay( + GeometryReader { geometry in + Color.white.opacity(0.65) // Adjust the opacity here for semi-transparency + .frame(width: geometry.size.width, height: geometry.safeAreaInsets.top) + .edgesIgnoringSafeArea(.top) + }, alignment: .top + ) + + } +} + +struct CustomTextField: View { + var placeholder: String + @Binding var text: String + + var body: some View { + TextField(placeholder, text: $text) + .foregroundColor(Color.primaryTextColor) + .padding() + .overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.gray, lineWidth: 1)) + .zIndex(-1) + } + +} + +#if DEBUG +struct MainView_Previews: PreviewProvider { + static var previews: some View { + MainView() + } +} +#endif + diff --git a/Rethread/Rethread/Models/DatePickerModel.swift b/Rethread/Rethread/Models/DatePickerModel.swift new file mode 100644 index 0000000..f14fdc7 --- /dev/null +++ b/Rethread/Rethread/Models/DatePickerModel.swift @@ -0,0 +1,273 @@ +import SwiftUI + +func formatDate(_ string: String) -> String { + // Remove any non-numeric characters + let numericString = string.filter { "0123456789".contains($0) } + + // Break the string down into components + var components = numericString.map { String($0) } + + // Add slashes at the appropriate places + if components.count > 2 { + components.insert("/", at: 2) + } + if components.count > 5 { + components.insert("/", at: 5) + } + + // Combine the components into a single string + let formattedString = components.joined() + + // Ensure the string is not longer than the max length (10 including slashes) + let endIndex = formattedString.index(formattedString.startIndex, offsetBy: min(10, formattedString.count)) + return String(formattedString[..some View{ + Text("\(value.day)") + .font(.body) + .frame(maxWidth: .infinity) + .frame(height: 40) + .foregroundColor(isSameDay(date1: selectedDate, date2: value.date) ? Color.white : Color.primaryTextColor) + .fontWeight(isSameDay(date1: selectedDate, date2: value.date) ? .bold : .none) + } + + // String to date + func stringToDate(date: String) -> Date { + let formatter = DateFormatter() + formatter.dateFormat = "MM/dd/yyyy" + return formatter.date(from: date) ?? Date() + } + + // Date to string + func dateToString(date: Date) -> String { + let formatter = DateFormatter() + formatter.dateFormat = "MM/dd/yyyy" + return formatter.string(from: date) + } + + func isSameDay(date1: Date, date2: Date) -> Bool { + let calendar = Calendar.current + // Print the dates if same day + return calendar.isDate(date1, inSameDayAs: date2) + } + + func extraData() -> [String] { + let formatter = DateFormatter() + formatter.dateFormat = "MMMM YYYY" + + + let date = formatter.string(from: currentDate) + return date.components(separatedBy: " ") + } + + func getCurrentMonth() -> Date { + let calendar = Calendar.current + + guard let currentMonth = calendar.date(byAdding: .month, value: currentMonth, to: stringToDate(date: chosenDate)) else { return Date() } + return currentMonth + } + func extractDate()->[DateValue]{ + let calendar = Calendar.current + + let currentMonth = getCurrentMonth() + + var days = currentMonth.getAllDates().compactMap { date -> DateValue in + let day = calendar.component(.day, from: date) + + return DateValue(day: day, date: date) + } + let firstWeekDay = calendar.component(.weekday, from: days.first?.date ?? Date()) + + for _ in 0.. [Date]{ + let calender = Calendar.current + + let startDate = calender.date(from: calender.dateComponents([.year, .month], from: self))! + + let range = calender.range(of: .day, in: .month, for: self)! + + // getting date + return range.compactMap { day -> Date in + return calender.date(byAdding: .day, value: day - 1 , to: startDate)! + } + } +} + +// Date value modal +struct DateValue: Identifiable { + var id = UUID().uuidString + var day: Int + var date: Date +} + +// Present the date picker modal view +struct DatePickerModalView_Previews: PreviewProvider { + static var previews: some View { + DatePickerModalView(showModal: .constant(true), chosenDate: .constant("09/05/1992")) + .previewLayout(.sizeThatFits) + } +} diff --git a/Rethread/Rethread/Models/TermsAndConditions.swift b/Rethread/Rethread/Models/TermsAndConditions.swift new file mode 100644 index 0000000..5594532 --- /dev/null +++ b/Rethread/Rethread/Models/TermsAndConditions.swift @@ -0,0 +1,61 @@ +import SwiftUI + +struct TermsAndConditions: View { + var body: some View { + VStack { + Text("Terms & Conditions") + .font(.title) + .padding() + + ScrollView { + Text(""" + These Terms & Conditions govern your use of the ReThread mobile application ("App") provided by TechStart ("Company"). By downloading, installing, or using the App, you agree to comply with and be bound by these Terms. If you do not agree with these Terms, please do not use the App. + + 1. Acceptance of Terms + By using the App, you acknowledge that you have read, understood, and agree to these Terms. If you are using the App on behalf of a company or organization, you represent and warrant that you have the authority to bind that entity to these Terms. + + 2. App License + The Company grants you a limited, non-exclusive, non-transferable, revocable license to use the App for your personal or internal business purposes, subject to these Terms. + + 3. User Registration + You may be required to create an account or provide certain information to access certain features of the App. You agree that all information you provide is accurate and up-to-date, and you will keep your account credentials secure. + + 4. Privacy + Your use of the App is also governed by our Privacy Policy, which can be found [Insert Link to Privacy Policy]. By using the App, you consent to the collection, use, and sharing of your information as described in the Privacy Policy. + + 5. Prohibited Activities + You agree not to: + - Use the App for any illegal or unauthorized purpose. + - Modify, adapt, or hack the App. + - Attempt to gain unauthorized access to the App's systems or networks. + - Engage in any activity that may interfere with the proper functioning of the App. + + 6. Termination + The Company reserves the right to terminate your access to the App at any time, for any reason, without notice. + + 7. Disclaimer of Warranties + The App is provided "as is" and "as available" without warranties of any kind, either express or implied, including, but not limited to, the implied warranties of merchantability, fitness for a particular purpose, or non-infringement. + + 8. Limitation of Liability + In no event shall the Company be liable for any indirect, incidental, special, consequential, or punitive damages, or any loss of profits or revenues, whether incurred directly or indirectly, or any loss of data, use, goodwill, or other intangible losses, resulting from your use of the App. + + 9. Changes to Terms + The Company reserves the right to update or modify these Terms at any time. Any changes will be effective immediately upon posting on the App. Your continued use of the App after any such changes constitute your acceptance of the revised Terms. + + 10. Governing Law + These Terms shall be governed by and construed in accordance with the laws of [Insert Jurisdiction]. Any disputes arising under or in connection with these Terms shall be subject to the exclusive jurisdiction of the courts of [Insert Jurisdiction]. + + 11. Contact Us + If you have any questions or concerns about these Terms, please contact us at [Insert Contact Information]. + """) + .padding() + } + } + } +} + +struct TermsAndConditions_Previews: PreviewProvider { + static var previews: some View { + TermsAndConditions() + } +} diff --git a/Rethread/Rethread/OnboardingApp.swift b/Rethread/Rethread/OnboardingApp.swift index df6ec78..05a2d43 100644 --- a/Rethread/Rethread/OnboardingApp.swift +++ b/Rethread/Rethread/OnboardingApp.swift @@ -6,7 +6,7 @@ import SwiftUI struct OnboardingApp: App { var body: some Scene { WindowGroup { - IntroView() + MainView() } } } diff --git a/Rethread/Rethread/OnboardingView.swift b/Rethread/Rethread/OnboardingView.swift index 50ec177..58a5b17 100644 --- a/Rethread/Rethread/OnboardingView.swift +++ b/Rethread/Rethread/OnboardingView.swift @@ -1,75 +1,182 @@ import SwiftUI -struct OnboardingView: View { - // # Of pages - var numberOfPages = 4 - @State private var currentScreenIndex = 0 - @State private var answers: [[String]] = Array(repeating: [], count: 3) +struct OnboardingStep { + let question: String + let options: [String] + let maxSelections: Int +} + +let onboardingSteps = [ + OnboardingStep(question: "What is your favorite color?", + options: ["Red", "Green", "Blue", "Purple"], + maxSelections: 1), + + OnboardingStep(question: "What is your favorite animal?", + options: ["Dog", "Cat", "Bird"], + maxSelections: 2), + + OnboardingStep(question: "What is your favorite food?", + options: ["Pizza", "Pasta", "Burgers"], + maxSelections: 1) +] +struct OnboardingView: View { + @State private var currentStep = 0 + @State private var selectedOptions = [[String]](repeating: [], count: onboardingSteps.count) + let onboardingStepsCount = onboardingSteps.count + @Environment(\.presentationMode) var presentationMode + + var body: some View { - VStack { - if currentScreenIndex == 0 { - WelcomeView(action: goToNextScreen) - } else if currentScreenIndex == 1 { - QuestionView( - question: "Who penned this timeless wisdom: 'You were born with wings, why prefer to crawl through life?'", - options: ["Kahlil Gibran", "Rumi", "Hafez", "Shakespeare"], - isLastPage: false, - action: goToNextScreen, - previousAction: goToPreviousScreen, - selectedAnswers: $answers[currentScreenIndex - 1] - ) - } else if currentScreenIndex == 2 { - QuestionView( - question: "Identify the author of this quote: 'The only impossible journey is the one you never begin.'", - options: ["Tony Robbins", "Neil Armstrong", "Mark Twain", "J.R.R. Tolkien"], - isLastPage: false, - action: goToNextScreen, - previousAction: goToPreviousScreen, - selectedAnswers: $answers[currentScreenIndex - 1] - ) - } else { - QuestionView( - question: "Determine who said: 'Out beyond ideas of wrongdoing and rightdoing, there is a field. I'll meet you there.'", - options: ["Rumi", "Oscar Wilde", "Emily Dickinson", "Pablo Neruda"], - isLastPage: true, - action: goToNextScreen, - previousAction: goToPreviousScreen, - selectedAnswers: $answers[currentScreenIndex - 1] - ) + VStack (spacing: 0) { + TabView(selection: $currentStep ) { + ForEach(0.. 0 && selectedOptions[newValue - 1].isEmpty { + currentStep -= 1 + } + } + + Spacer() + + HStack (spacing: 0) { + Button(action: previousStep) { + Image(systemName: "chevron.left") + .font(.title) + } + .buttonStyle(PreviousButtonStyle()) + + Spacer() + + + HStack { + ForEach(0.. Bool { + currentStep == onboardingStepsCount - 1 + } + + func previousStep() { withAnimation { - if currentScreenIndex > 0 { - currentScreenIndex -= 1 + if currentStep > 0 { + currentStep -= 1 + } else { + // Navigate to the previous screen + // Close the presentation + presentationMode.wrappedValue.dismiss() } } } - - func goToNextScreen() { + + func nextStep() { withAnimation { - if currentScreenIndex < numberOfPages - 1 { - currentScreenIndex += 1 - } else { - finishOnboarding() + if currentStep < onboardingStepsCount - 1 { + currentStep += 1 } } } - - func finishOnboarding() { - sendAnswersToBackend(answers) - answers.removeAll() - - // Navigate somewhere. Possibly home screen? + + + func isCurrentStepValid() -> Bool { + !selectedOptions[currentStep].isEmpty } - - func sendAnswersToBackend(_ answers: [[String]]) { - // Implement the network request logic here - print("Sending answers to backend:", answers) + + func finishOnboarding() { + // MARK: Backend + // Navigate to the main screen, call backend } } + +struct OptionRow: View { + var option: String + var isSelected: Bool + var toggleSelection: () -> Void + + var body: some View { + HStack { + Image(systemName: isSelected ? "checkmark.square.fill" : "square") + .resizable() + .frame(width: 22, height: 22) + .foregroundColor(Color(red: 102/255, green: 112/255, blue: 128/255)) + .onTapGesture(perform: toggleSelection) + + Text(option) + .foregroundColor(.black) + .padding(.leading) + .onTapGesture(perform: toggleSelection) + + Spacer() + } + .padding(.vertical, 15) + } +} + + diff --git a/Rethread/Rethread/QuestionView.swift b/Rethread/Rethread/QuestionView.swift index 73991a0..0c60716 100644 --- a/Rethread/Rethread/QuestionView.swift +++ b/Rethread/Rethread/QuestionView.swift @@ -71,30 +71,6 @@ struct QuestionView: View { } } -struct OptionRow: View { - var option: String - var isSelected: Bool - var toggleSelection: () -> Void - - var body: some View { - HStack { - Image(systemName: isSelected ? "checkmark.square.fill" : "square") - .resizable() - .frame(width: 22, height: 22) - .foregroundColor(Color(red: 102/255, green: 112/255, blue: 128/255)) - .onTapGesture(perform: toggleSelection) - - Text(option) - .foregroundColor(.black) - .padding(.leading) - .onTapGesture(perform: toggleSelection) - - Spacer() - } - .padding(.vertical, 15) - } -} - struct AnyButtonStyle: ButtonStyle { private let _makeBody: (Configuration) -> AnyView diff --git a/Rethread/Rethread/SignInView.swift b/Rethread/Rethread/SignInView.swift index bbfe911..d19329a 100644 --- a/Rethread/Rethread/SignInView.swift +++ b/Rethread/Rethread/SignInView.swift @@ -1,22 +1,15 @@ import SwiftUI -#if canImport(UIKit) -extension View { - func hideKeyboard() { - UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) - } -} -#endif struct SignInView: View { - @Binding var currentUserSignedIn: Bool + @Binding var path: [String] @State private var email: String = "" @State private var password: String = "" @State private var isShowingVerification: Bool = false @State private var isPasswordVisible: Bool = false @FocusState var isFieldFocus: FieldToFocus? - @Environment(\.presentationMode) var presentationMode + @Environment(\.dismiss) private var dismiss enum FieldToFocus { case secureField, textField @@ -28,7 +21,7 @@ struct SignInView: View { VStack(alignment: .leading) { HStack { Button(action: { - presentationMode.wrappedValue.dismiss() + dismiss() }) { Image(systemName: "chevron.left") } @@ -52,7 +45,8 @@ struct SignInView: View { .fontWeight(.medium) .foregroundColor(Color.primaryColor) Button(action: { - // Handle forgot password action + path.removeLast() + path.append("SignUpView") }) { Text("create an account") .foregroundColor(Color.primaryColor) @@ -63,6 +57,7 @@ struct SignInView: View { } .padding(.horizontal, 25) + VStack(alignment: .leading, spacing: 0) { Text("Email") .foregroundColor(Color.primaryColor) @@ -132,22 +127,18 @@ struct SignInView: View { .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom) // Aligns buttons to the bottom } .fullScreenCover(isPresented: $isShowingVerification) { - VerificationView(currentUserSignedIn: $currentUserSignedIn) + VerificationView(isSignIn: true, path: $path) } .gesture(TapGesture().onEnded{ self.hideKeyboard() }) .ignoresSafeArea(.keyboard) + .navigationBarBackButtonHidden(true) } } -struct CustomTextField: View { - var placeholder: String - @Binding var text: String - - var body: some View { - TextField(placeholder, text: $text) - .padding() - .overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.gray, lineWidth: 1)) +struct MainView2_Previews: PreviewProvider { + static var previews: some View { + MainView() } } diff --git a/Rethread/Rethread/SignUpView.swift b/Rethread/Rethread/SignUpView.swift new file mode 100644 index 0000000..6d8a71e --- /dev/null +++ b/Rethread/Rethread/SignUpView.swift @@ -0,0 +1,264 @@ +import SwiftUI + +struct SignUpView: View { + @Binding var path: [String] + @State private var isShowingVerification: Bool = false + struct SignUpFormData { + var firstName: String = "" // First name text field + var lastName: String = "" // Last name text field + var email: String = "" // Email text field + var password: String = "" // Password text field + var confirmPassword: String = "" // Confirm password text field + var phoneNumber: String = "" // Phone number text field + var postalCode: String = "" // Zip code text field + var dateOfBirth: String = "" // Date of birth text field + var gender: String = "Male" // Gender text field + } + @State private var formData = SignUpFormData() + @State private var isPasswordVisible = false + @State private var showingDatePicker = false + @State private var showingTermsAndConditions = false + @State private var showingGenderPicker = false + @State private var genderOptions = ["Male", "Female", "Other"] + @State private var areTermsAccepted = false + @Environment(\.dismiss) private var dismiss + + func toggleDatePicker() { + showingDatePicker.toggle() + } + + + var body: some View { + ZStack { + VStack { + // Top content, including the back button and text fields + ScrollView { + VStack(alignment: .leading) { + HStack { + Button(action: { + dismiss() + }) { + Image(systemName: "chevron.left") + } + .buttonStyle(SecondaryButtonStyle(width: 15, height: 15)) + .clipShape(Circle()) + Spacer() + } + .padding(.horizontal) + .padding(.top, 40) + .padding(.bottom, 20) + + Text("Create an account") + .font(.largeTitle) + .fontWeight(.bold) + .padding(.horizontal, 25) + .padding(.bottom, 1) + .foregroundColor(Color.primaryColor) + + HStack (spacing: 0) { + Text("Enter your account details below or ") + .fontWeight(.medium) + .foregroundColor(Color.primaryColor) + Button(action: { + path.removeLast() + path.append("SignInView") + }) { + Text("log in") + .foregroundColor(Color.primaryColor) + .fontWeight(.bold) + .underline() // Underlined text + } + Spacer() + } + .padding(.horizontal, 25) + .padding(.bottom, 10) + } + + VStack(alignment: .leading) { + VStack(alignment: .leading, spacing: 0) { + HStack { + VStack(alignment: .leading, spacing: 0) { + Text("First Name") + .foregroundColor(Color.primaryColor) + .padding(.leading, 25) + .padding(.top, 20) + + CustomTextField(placeholder: "First Name", text: $formData.firstName) + .padding(.leading, 25) + .padding(.top, 9) + } + + VStack(alignment: .leading, spacing: 0) { + Text("Last Name") + .foregroundColor(Color.primaryColor) + .padding(.trailing, 25) + .padding(.top, 20) + + CustomTextField(placeholder: "Last Name", text: $formData.lastName) + .padding(.trailing, 25) + .padding(.top, 9) + } + } + + Text("Email") + .foregroundColor(Color.primaryColor) + .padding(.horizontal, 25) + .padding(.top, 20) + + CustomTextField(placeholder: "Email", text: $formData.email) + .padding(.horizontal, 25) + .padding(.top, 9) + HStack { + VStack (alignment: .leading, spacing: 0){ + Text("Date of Birth") + .foregroundColor(Color.primaryColor) + .padding(.top, 20) + + HStack { + TextField("MM / DD / YYYY", text: $formData.dateOfBirth ) + .foregroundColor(Color.primaryTextColor) + .keyboardType(.numberPad) + .onChange(of: formData.dateOfBirth) { newValue, oldValue in + formData.dateOfBirth = formatDate(newValue) + } + + + Button(action: { + // Actions to present a date picker + toggleDatePicker() + }) { + Image(systemName: "calendar") + .foregroundColor(.gray) + } + } + .padding() + .overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.gray, lineWidth: 1)) + .padding(.top, 9) + } + + + VStack (alignment: .leading, spacing: 0) { + Text("Gender") + .foregroundColor(Color.primaryColor) + .padding(.trailing, 25) + .padding(.top, 20) + + CustomDropdownMenu(items: [ + DropdownItem(id: 1, title: "Male", onSelect: {formData.gender = "Male"}), + DropdownItem(id: 2, title: "Female", onSelect: {formData.gender = "Female"}), + DropdownItem(id: 3, title: "Other", onSelect: {formData.gender = "Other"}) + ]) + .frame(width:130) + .padding(.top, 9) + + } + } + .padding(.horizontal, 25) + + Text("Phone Number") + .foregroundColor(Color.primaryColor) + .padding(.horizontal, 25) + .padding(.top, 20) + + CustomTextField(placeholder: "Phone Number", text: $formData.phoneNumber) + .padding(.horizontal, 25) + .padding(.top, 9) + + Text("Password") + .foregroundColor(Color.primaryColor) + .padding(.horizontal, 25) + .padding(.top, 20) + .padding(.bottom, 9) + + SecureField("Password", text: $formData.password) + .disableAutocorrection(true) + .autocapitalization(.none) + .padding() + .overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.gray, lineWidth: 1)) + .padding(.horizontal, 25) + + Text("Confirm Password") + .foregroundColor(Color.primaryColor) + .padding(.horizontal, 25) + .padding(.top, 20) + .padding(.bottom, 9) + + SecureField("Confirm Password", text: $formData.confirmPassword) + .disableAutocorrection(true) + .autocapitalization(.none) + .padding() + .overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.gray, lineWidth: 1)) + .padding(.horizontal, 25) + + Text("Postal Code") + .foregroundColor(Color.primaryColor) + .padding(.horizontal, 25) + .padding(.top, 20) + + CustomTextField(placeholder: "Postal Code", text: $formData.postalCode) + .padding(.horizontal, 25) + .padding(.top, 9) + + } + } + + Spacer() + + + // Bottom content, including the sign-in button + VStack (spacing: 16) { + Button("Join Us") { + // Handle sign up + print(formData) + self.isShowingVerification = true + } + .buttonStyle(PrimaryButtonStyle(width: 300, isDisabled: !areTermsAccepted)) + .disabled(!areTermsAccepted) + + HStack { + Image(systemName: areTermsAccepted ? "checkmark.square.fill" : "square") + .foregroundColor(Color.primaryColor) + .onTapGesture { + areTermsAccepted.toggle() + } + .offset(y: 2) + .font(.system(size: 20)) + + + Button(action: { + showingTermsAndConditions.toggle() + }) { + Text("Terms and Conditions") + .foregroundColor(Color.primaryColor) + .fontWeight(.semibold) + .underline() // Underlined text + } + } + } + .frame(maxWidth: .infinity) // Aligns buttons to the bottom + .padding(.vertical, 10) + } + } + .fullScreenCover(isPresented: $isShowingVerification) { + VerificationView(isSignIn: false, path: $path) + } + .gesture(TapGesture().onEnded{ + self.hideKeyboard() + }) + } + // Date picker modal + .sheet(isPresented: $showingDatePicker) { + DatePickerModalView(showModal: $showingDatePicker, chosenDate: $formData.dateOfBirth) + .presentationDetents([.fraction(0.7)]) + .presentationDragIndicator(.visible) + + } + .sheet(isPresented: $showingTermsAndConditions){ + TermsAndConditions() + .presentationDetents([.medium, .large]) + .padding(.horizontal, 15) + .padding(.top, 15) + } + .navigationBarBackButtonHidden(true) + } +} diff --git a/Rethread/Rethread/Styles/ButtonStyles.swift b/Rethread/Rethread/Styles/ButtonStyles.swift index 1bdcd59..a2050bb 100644 --- a/Rethread/Rethread/Styles/ButtonStyles.swift +++ b/Rethread/Rethread/Styles/ButtonStyles.swift @@ -2,18 +2,39 @@ import SwiftUI +struct ConfigurableButtonStyle: ButtonStyle { + var applyStyle: (Configuration) -> AnyView + + func makeBody(configuration: Configuration) -> some View { + applyStyle(configuration) + } +} + +extension ButtonStyle where Self == ConfigurableButtonStyle { + static func configurable(_ isLastStep: Bool, isEnabled: Bool) -> Self { + ConfigurableButtonStyle { configuration in + if isLastStep { + return AnyView(LetStartButtonStyle(isEnabled: isEnabled).makeBody(configuration: configuration)) + } else { + return AnyView(NextButtonStyle(isEnabled: isEnabled).makeBody(configuration: configuration)) + } + } + } +} + struct PrimaryButtonStyle: ButtonStyle { // Button background color #667080 // Rounded corners // Take in width and height as parameters? var width: CGFloat = 100 var height: CGFloat = 20 + var isDisabled: Bool = false func makeBody(configuration: Self.Configuration) -> some View { configuration.label .frame(width: width, height: height) // Use optional binding in case width and height are not provided .padding() - .background(configuration.isPressed ? Color(red: 170/255, green: 177/255, blue: 187/255) + .background(configuration.isPressed || isDisabled ? Color(red: 170/255, green: 177/255, blue: 187/255) : Color.primaryColor) .cornerRadius(9) .foregroundColor(.white) @@ -69,7 +90,7 @@ struct NextButtonStyle: ButtonStyle { bottomTrailing: 0.0, topTrailing: 0.0), style: .continuous) - .fill(isEnabled ? Color.primaryColor + .fill(isEnabled && !configuration.isPressed ? Color.primaryColor : Color(red: 170/255, green: 177/255, blue: 187/255))) // Disabled color } } @@ -87,10 +108,46 @@ struct LetStartButtonStyle: ButtonStyle { bottomTrailing: 0.0, topTrailing: 0.0), style: .continuous) - .fill(isEnabled ? Color.primaryColor + .fill(isEnabled && !configuration.isPressed ? Color.primaryColor : Color(red: 170/255, green: 177/255, blue: 187/255))) // Disabled color } } +struct RoundedCorners: View { + var color: Color = .white + var tl: CGFloat = 0.0 + var tr: CGFloat = 0.0 + var bl: CGFloat = 0.0 + var br: CGFloat = 0.0 + + var body: some View { + GeometryReader { geometry in + Path { path in + + let w = geometry.size.width + let h = geometry.size.height + + // Make sure we do not exceed the size of the rectangle + let tr = min(min(self.tr, h/2), w/2) + let tl = min(min(self.tl, h/2), w/2) + let bl = min(min(self.bl, h/2), w/2) + let br = min(min(self.br, h/2), w/2) + + path.move(to: CGPoint(x: w / 2.0, y: 0)) + path.addLine(to: CGPoint(x: w - tr, y: 0)) + path.addArc(center: CGPoint(x: w - tr, y: tr), radius: tr, startAngle: Angle(degrees: -90), endAngle: Angle(degrees: 0), clockwise: false) + path.addLine(to: CGPoint(x: w, y: h - br)) + path.addArc(center: CGPoint(x: w - br, y: h - br), radius: br, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 90), clockwise: false) + path.addLine(to: CGPoint(x: bl, y: h)) + path.addArc(center: CGPoint(x: bl, y: h - bl), radius: bl, startAngle: Angle(degrees: 90), endAngle: Angle(degrees: 180), clockwise: false) + path.addLine(to: CGPoint(x: 0, y: tl)) + path.addArc(center: CGPoint(x: tl, y: tl), radius: tl, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false) + path.closeSubpath() + } + .fill(self.color) + } + } +} + diff --git a/Rethread/Rethread/Styles/ColorPalette.swift b/Rethread/Rethread/Styles/ColorPalette.swift index b1fee0f..4cc4bca 100644 --- a/Rethread/Rethread/Styles/ColorPalette.swift +++ b/Rethread/Rethread/Styles/ColorPalette.swift @@ -2,5 +2,5 @@ import SwiftUI extension Color { static let primaryColor = Color(red: 102/255, green: 112/255, blue: 128/255) - static let secondaryColor = Color(red: 0.75, green: 0.22, blue: 0.17) + static let primaryTextColor = Color(red: 84/255, green: 95/255, blue: 113/255) } diff --git a/Rethread/Rethread/VerificationView.swift b/Rethread/Rethread/VerificationView.swift index a2aa4a7..015b28a 100644 --- a/Rethread/Rethread/VerificationView.swift +++ b/Rethread/Rethread/VerificationView.swift @@ -1,8 +1,10 @@ import SwiftUI struct VerificationView: View { - @Binding var currentUserSignedIn: Bool - @Environment(\.presentationMode) var presentationMode + // Sign in or Sign up verification? + @State var isSignIn: Bool + @Binding var path: [String] + @Environment(\.dismiss) private var dismiss @State private var code: [String] = ["", "", "", ""] @FocusState private var focusedField: Field? @@ -20,7 +22,7 @@ struct VerificationView: View { VStack(alignment: .leading) { HStack { Button(action: { - presentationMode.wrappedValue.dismiss() + dismiss() }) { Image(systemName: "chevron.left") } @@ -56,24 +58,25 @@ struct VerificationView: View { .multilineTextAlignment(.center) .keyboardType(.numberPad) .focused($focusedField, equals: Field(rawValue: index)) - .onChange(of: code[index]) { newValue, oldValue in - if newValue.count > 0 { // If a character is entered - if let nextField = focusedField?.next, index < code.count - 1 { - // If it's not the last field, move to the next field - self.focusedField = nextField + .onChange(of: code[index]) { newValue in + if newValue.count == 1 { + // A character is entered in the current field + if index < code.count - 1 { + // Move to the next field only if it's not the last one + focusedField = Field(rawValue: index + 1) } else { - // If it's the last field, unfocus + // Last field - perhaps perform some action or unfocus focusedField = nil } - } else { // If backspace is pressed - if let previousField = focusedField?.rawValue, index > 0 { - // If it's not the first field, move to the previous field - self.focusedField = Field(rawValue: previousField - 1) - } + } else if newValue.isEmpty && index > 0 { + // Backspace is pressed, and it's not the first field + // Move to the previous field + focusedField = Field(rawValue: index - 1) } } + } } .padding(.horizontal, 25) @@ -82,18 +85,25 @@ struct VerificationView: View { } Spacer() } - // Bottom content, including the sign-in button VStack (spacing: 16) { Button("Verify") { - // Go to the home screen, set currentUserSignedIn to true - currentUserSignedIn = true + // Handle sign in + if isSignIn { + // Sign in + print("Sign in") + } else { + withAnimation { + dismiss() + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + path.append("WelcomeView") + } + } + } } .buttonStyle(PrimaryButtonStyle(width: 300)) - - Button(action: { // Handle request new code }) { @@ -112,11 +122,4 @@ struct VerificationView: View { } } -#if DEBUG -struct Verification_Preview: PreviewProvider { - static var previews: some View { - VerificationView(currentUserSignedIn: .constant(false)) - } -} -#endif diff --git a/Rethread/Rethread/VideoPlayer/PlayerView.swift b/Rethread/Rethread/VideoPlayer/PlayerView.swift new file mode 100644 index 0000000..ac6546f --- /dev/null +++ b/Rethread/Rethread/VideoPlayer/PlayerView.swift @@ -0,0 +1,18 @@ +import SwiftUI + +struct PlayerView: UIViewRepresentable { + + func makeUIView(context: Context) -> UIVideoPlayer { + return UIVideoPlayer() + } + + func updateUIView(_ uiView: UIVideoPlayer, context: Context) { + + } +} + +struct PlayerView_Previews: PreviewProvider { + static var previews: some View { + PlayerView() + } +} diff --git a/Rethread/Rethread/VideoPlayer/UIPlayerView.swift b/Rethread/Rethread/VideoPlayer/UIPlayerView.swift new file mode 100644 index 0000000..10ddba6 --- /dev/null +++ b/Rethread/Rethread/VideoPlayer/UIPlayerView.swift @@ -0,0 +1,51 @@ +import Foundation +import SwiftUI +import AVKit + +class UIVideoPlayer: UIView { + + private var playerLayer = AVPlayerLayer() + private var player: AVPlayer? + + override init(frame: CGRect) { + super.init(frame: frame) + + guard let url = URL(string: "https://github.com/bbmvicomte/videoOnboardingScreen/blob/master/bounce.mp4?raw=true") else { return } + + player = AVPlayer(url: url) + player?.isMuted = true + + playerLayer.player = player + playerLayer.videoGravity = .resizeAspectFill + + layer.addSublayer(playerLayer) + + // Add observer + NotificationCenter.default.addObserver(self, + selector: #selector(playerItemDidReachEnd(notification:)), + name: .AVPlayerItemDidPlayToEndTime, + object: player?.currentItem) + + player?.play() + } + + override func layoutSubviews() { + super.layoutSubviews() + playerLayer.frame = bounds + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc func playerItemDidReachEnd(notification: Notification) { + // Restart video from the beginning + player?.seek(to: CMTime.zero) + player?.play() + } + + deinit { + // Remove observer + NotificationCenter.default.removeObserver(self) + } +} diff --git a/Rethread/Rethread/WelcomeView.swift b/Rethread/Rethread/WelcomeView.swift index 56cdf27..6692b20 100644 --- a/Rethread/Rethread/WelcomeView.swift +++ b/Rethread/Rethread/WelcomeView.swift @@ -1,21 +1,17 @@ import SwiftUI +import AVKit struct WelcomeView: View { - var action: () -> Void + @Binding var path: [String] + @State private var showOnboarding = false + @State private var player = AVPlayer() var body: some View { - VStack { - // Video placeholder - Rectangle() - .foregroundColor(.black.opacity(0.1)) - .overlay( - Image(systemName: "video.fill") // Video icon placeholder - .resizable() - .scaledToFit() - .frame(width: 30, height: 30) - .foregroundColor(.white) - .opacity(1) - ) + ZStack(alignment: .bottom) { + PlayerView() + .edgesIgnoringSafeArea(.all) + .padding(.bottom, 290) + Spacer() @@ -25,7 +21,7 @@ struct WelcomeView: View { .font(.title) .fontWeight(.bold) .foregroundColor(.gray) - .padding(.top, 30) + .padding(.top, 20) Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit purus sit amet luctus venenatis, lectus magna fringilla urna, porttitor.") .font(.body) @@ -34,14 +30,41 @@ struct WelcomeView: View { .padding(.horizontal) - Button("Get Started", action: action) + Button("Get Started", action: { + withAnimation { + showOnboarding.toggle() + } + }) .buttonStyle(PrimaryButtonStyle(width: 200, height: 20)) - .padding(.bottom, 55.0) + .padding(.bottom, 40.0) .padding(.top) + } - .frame(maxWidth: .infinity) // Ensure VStack takes full width + .frame(maxWidth: .infinity) + .background(Color.white) // Apply background and corner radius + .cornerRadius(15) // Adjust the corner radius as needed + + } + .fullScreenCover(isPresented: $showOnboarding) { + OnboardingView() } - .edgesIgnoringSafeArea(.all) + .edgesIgnoringSafeArea(.all) // This will extend the content to the edges .background(Color(UIColor.systemGray6)) + .navigationBarBackButtonHidden(true) + .overlay( + GeometryReader { geometry in + Color.white.opacity(0.65) // Adjust the opacity here for semi-transparency + .frame(width: geometry.size.width, height: geometry.safeAreaInsets.top) + .edgesIgnoringSafeArea(.top) + }, alignment: .top + ) + } +} + +#if DEBUG +struct MainView23_Previews: PreviewProvider { + static var previews: some View { + WelcomeView(path: .constant(["WelcomeView"])) } } +#endif