diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/Rethread/.DS_Store b/Rethread/.DS_Store new file mode 100644 index 0000000..6d556de Binary files /dev/null and b/Rethread/.DS_Store differ diff --git a/Rethread/Rethread.xcodeproj/project.pbxproj b/Rethread/Rethread.xcodeproj/project.pbxproj index 3152e62..6b8cfca 100644 --- a/Rethread/Rethread.xcodeproj/project.pbxproj +++ b/Rethread/Rethread.xcodeproj/project.pbxproj @@ -7,13 +7,27 @@ objects = { /* Begin PBXBuildFile section */ - 983286B52B05698E00851B19 /* RethreadApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 983286B42B05698E00851B19 /* RethreadApp.swift */; }; - 983286B72B05698E00851B19 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 983286B62B05698E00851B19 /* ContentView.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 */; }; 983286D02B05698F00851B19 /* RethreadUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 983286CF2B05698F00851B19 /* RethreadUITests.swift */; }; 983286D22B05698F00851B19 /* RethreadUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 983286D12B05698F00851B19 /* RethreadUITestsLaunchTests.swift */; }; + 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 */; }; + 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 */ @@ -34,9 +48,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 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; }; - 983286B42B05698E00851B19 /* RethreadApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RethreadApp.swift; sourceTree = ""; }; - 983286B62B05698E00851B19 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 983286B82B05698F00851B19 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 983286BB2B05698F00851B19 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 983286C12B05698F00851B19 /* RethreadTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RethreadTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -44,6 +57,20 @@ 983286CB2B05698F00851B19 /* RethreadUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RethreadUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 983286CF2B05698F00851B19 /* RethreadUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RethreadUITests.swift; sourceTree = ""; }; 983286D12B05698F00851B19 /* RethreadUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RethreadUITestsLaunchTests.swift; sourceTree = ""; }; + 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 = ""; }; + 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 */ @@ -51,6 +78,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 983014C22B451A3E004A6A07 /* NavigationTransitions in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -71,6 +99,31 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 7C9AA2CC2B0EC02600EF6F36 /* Models */ = { + isa = PBXGroup; + children = ( + 988A0F982B3C112300ACDC90 /* DatePickerModel.swift */, + 98F525CD2B46A96F00CCAD78 /* TermsAndConditions.swift */, + ); + path = Models; + sourceTree = ""; + }; + 7C9AA2D12B0EC3BC00EF6F36 /* Extensions */ = { + isa = PBXGroup; + children = ( + ); + path = Extensions; + sourceTree = ""; + }; + 7C9AA2D42B0ECADF00EF6F36 /* Styles */ = { + isa = PBXGroup; + children = ( + 9868A7DD2B1FAE780063D9BA /* ButtonStyles.swift */, + 98A1838B2B38D32A001E324A /* ColorPalette.swift */, + ); + path = Styles; + sourceTree = ""; + }; 983286A82B05698E00851B19 = { isa = PBXGroup; children = ( @@ -94,8 +147,19 @@ 983286B32B05698E00851B19 /* Rethread */ = { isa = PBXGroup; children = ( - 983286B42B05698E00851B19 /* RethreadApp.swift */, - 983286B62B05698E00851B19 /* ContentView.swift */, + 7C9AA2D12B0EC3BC00EF6F36 /* Extensions */, + 7C9AA2D42B0ECADF00EF6F36 /* Styles */, + 9897C7712B3E6BF600EDE9D9 /* Components */, + 98F525CC2B46A8E900CCAD78 /* VideoPlayer */, + 7C9AA2CC2B0EC02600EF6F36 /* Models */, + 98AE7AB82B1FAADB0029C2F7 /* OnboardingView.swift */, + 98A183852B38273B001E324A /* MainView.swift */, + 98A183872B383C0B001E324A /* SignInView.swift */, + 988A0F942B3BFB4C00ACDC90 /* SignUpView.swift */, + 98A183892B383C47001E324A /* VerificationView.swift */, + 9868A7D92B1FADA70063D9BA /* QuestionView.swift */, + 9868A7D72B1FAD280063D9BA /* WelcomeView.swift */, + 7C3A04B42B0AD9DF00E36883 /* OnboardingApp.swift */, 983286B82B05698F00851B19 /* Assets.xcassets */, 983286BA2B05698F00851B19 /* Preview Content */, ); @@ -127,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 */ @@ -143,6 +224,9 @@ dependencies = ( ); name = Rethread; + packageProductDependencies = ( + 983014C12B451A3E004A6A07 /* NavigationTransitions */, + ); productName = Rethread; productReference = 983286B12B05698E00851B19 /* Rethread.app */; productType = "com.apple.product-type.application"; @@ -195,6 +279,7 @@ TargetAttributes = { 983286B02B05698E00851B19 = { CreatedOnToolsVersion = 15.0.1; + LastSwiftMigration = 1500; }; 983286C02B05698F00851B19 = { CreatedOnToolsVersion = 15.0.1; @@ -215,6 +300,9 @@ Base, ); mainGroup = 983286A82B05698E00851B19; + packageReferences = ( + 983014C02B451A3E004A6A07 /* XCRemoteSwiftPackageReference "swiftui-navigation-transitions" */, + ); productRefGroup = 983286B22B05698E00851B19 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -257,8 +345,21 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 983286B72B05698E00851B19 /* ContentView.swift in Sources */, - 983286B52B05698E00851B19 /* RethreadApp.swift in Sources */, + 9868A7DA2B1FADA70063D9BA /* QuestionView.swift in Sources */, + 7C3A04B82B0AD9DF00E36883 /* OnboardingApp.swift in Sources */, + 9868A7DE2B1FAE780063D9BA /* ButtonStyles.swift in Sources */, + 98AE7AB92B1FAADB0029C2F7 /* OnboardingView.swift in Sources */, + 988A0F952B3BFB4C00ACDC90 /* SignUpView.swift in Sources */, + 98A1838C2B38D32A001E324A /* ColorPalette.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; }; @@ -419,9 +520,11 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Rethread/Preview Content\""; + DEVELOPMENT_TEAM = 4GW9NWA7H9; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; @@ -437,6 +540,7 @@ PRODUCT_BUNDLE_IDENTIFIER = techstart.Rethread; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -447,9 +551,11 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Rethread/Preview Content\""; + DEVELOPMENT_TEAM = 4GW9NWA7H9; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; @@ -582,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/mortezafaraji.xcuserdatad/UserInterfaceState.xcuserstate b/Rethread/Rethread.xcodeproj/project.xcworkspace/xcuserdata/mortezafaraji.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..b5e369f Binary files /dev/null and b/Rethread/Rethread.xcodeproj/project.xcworkspace/xcuserdata/mortezafaraji.xcuserdatad/UserInterfaceState.xcuserstate differ 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 7c8dec0..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/mortezafaraji.xcuserdatad/xcschemes/xcschememanagement.plist b/Rethread/Rethread.xcodeproj/xcuserdata/mortezafaraji.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..c636354 --- /dev/null +++ b/Rethread/Rethread.xcodeproj/xcuserdata/mortezafaraji.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + Rethread.xcscheme_^#shared#^_ + + orderHint + 0 + + + + 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/ContentView.swift b/Rethread/Rethread/ContentView.swift deleted file mode 100644 index adf068e..0000000 --- a/Rethread/Rethread/ContentView.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// ContentView.swift -// Rethread -// -// Created by Parsa Kargari on 2023-11-15. -// - -import SwiftUI - -struct ContentView: View { - var body: some View { - VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") - } - .padding() - } -} - -#Preview { - ContentView() -} 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 new file mode 100644 index 0000000..05a2d43 --- /dev/null +++ b/Rethread/Rethread/OnboardingApp.swift @@ -0,0 +1,12 @@ +// OnboardingApp.swift + +import SwiftUI + +@main +struct OnboardingApp: App { + var body: some Scene { + WindowGroup { + MainView() + } + } +} diff --git a/Rethread/Rethread/OnboardingView.swift b/Rethread/Rethread/OnboardingView.swift new file mode 100644 index 0000000..58a5b17 --- /dev/null +++ b/Rethread/Rethread/OnboardingView.swift @@ -0,0 +1,182 @@ +import SwiftUI + +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 (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 currentStep > 0 { + currentStep -= 1 + } else { + // Navigate to the previous screen + // Close the presentation + presentationMode.wrappedValue.dismiss() + } + } + } + + func nextStep() { + withAnimation { + if currentStep < onboardingStepsCount - 1 { + currentStep += 1 + } + } + } + + + func isCurrentStepValid() -> Bool { + !selectedOptions[currentStep].isEmpty + } + + 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 new file mode 100644 index 0000000..0c60716 --- /dev/null +++ b/Rethread/Rethread/QuestionView.swift @@ -0,0 +1,84 @@ +import SwiftUI + +struct QuestionView: View { + var question: String + var options: [String] + var isLastPage: Bool + var action: () -> Void + var previousAction: () -> Void + + @Binding var selectedAnswers: [String] // Directly using the binding to manage state + + private func currentButtonStyle() -> AnyButtonStyle { + if isLastPage { + return AnyButtonStyle(LetStartButtonStyle(isEnabled: !selectedAnswers.isEmpty)) + } else { + return AnyButtonStyle(NextButtonStyle(isEnabled: !selectedAnswers.isEmpty)) + } + } + + var body: some View { + VStack { + Text(question) + .font(.body) + .foregroundColor(.gray) + .padding(.top, 100) + .padding(.bottom, 40) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 20.0) + + ForEach(options, id: \.self) { option in + OptionRow(option: option, isSelected: selectedAnswers.contains(option)) { + if let index = selectedAnswers.firstIndex(of: option) { + selectedAnswers.remove(at: index) // Deselect the option + } else { + selectedAnswers.append(option) // Select the option + } + } + } + .padding([.leading, .trailing], 35) + + Spacer() + + // Navigation buttons + HStack { + Button(action: previousAction) { + Image(systemName: "chevron.left") + .font(.title) + } + .buttonStyle(PreviousButtonStyle()) + + Spacer() + + Button(action: action) { + if isLastPage { + Text("Let's Start!") + .font(.body) + .foregroundColor(.white) + } else { + Image(systemName: "chevron.right") + .font(.title) + } + } + .buttonStyle(currentButtonStyle()) + .disabled(selectedAnswers.isEmpty) + } + .padding(.bottom, 70) + + } + .background(Color(UIColor.systemGray6)) // Use the system background color + .edgesIgnoringSafeArea(.all) + } +} + +struct AnyButtonStyle: ButtonStyle { + private let _makeBody: (Configuration) -> AnyView + + init(_ style: Style) { + self._makeBody = { configuration in AnyView(style.makeBody(configuration: configuration)) } + } + + func makeBody(configuration: Configuration) -> some View { + self._makeBody(configuration) + } +} diff --git a/Rethread/Rethread/RethreadApp.swift b/Rethread/Rethread/RethreadApp.swift deleted file mode 100644 index 7c94105..0000000 --- a/Rethread/Rethread/RethreadApp.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// RethreadApp.swift -// Rethread -// -// Created by Parsa Kargari on 2023-11-15. -// - -import SwiftUI - -@main -struct RethreadApp: App { - var body: some Scene { - WindowGroup { - ContentView() - } - } -} diff --git a/Rethread/Rethread/SignInView.swift b/Rethread/Rethread/SignInView.swift new file mode 100644 index 0000000..d19329a --- /dev/null +++ b/Rethread/Rethread/SignInView.swift @@ -0,0 +1,144 @@ +import SwiftUI + + + +struct SignInView: View { + @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(\.dismiss) private var dismiss + + enum FieldToFocus { + case secureField, textField + } + + var body: some View { + VStack { + // Top content, including the back button and text fields + 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("Welcome back!") + .font(.largeTitle) + .fontWeight(.bold) + .padding(.horizontal, 25) + .padding(.bottom, 1) + .foregroundColor(Color.primaryColor) + + HStack (spacing: 0) { + Text("Login below or ") + .fontWeight(.medium) + .foregroundColor(Color.primaryColor) + Button(action: { + path.removeLast() + path.append("SignUpView") + }) { + Text("create an account") + .foregroundColor(Color.primaryColor) + .fontWeight(.bold) + .underline() // Underlined text + } + Spacer() + } + .padding(.horizontal, 25) + + + VStack(alignment: .leading, spacing: 0) { + Text("Email") + .foregroundColor(Color.primaryColor) + .padding(.horizontal, 25) + .padding(.top, 25) + + CustomTextField(placeholder: "Email", text: $email) + .padding(.horizontal, 25) + .padding(.top, 9) + + Text("Password") + .foregroundColor(Color.primaryColor) + .padding(.horizontal, 25) + .padding(.top, 20) + .padding(.bottom, 9) + + HStack { + if isPasswordVisible { + TextField("Password", text: $password) + .disableAutocorrection(true) + .autocapitalization(/*@START_MENU_TOKEN@*/.none/*@END_MENU_TOKEN@*/) + .focused($isFieldFocus, equals: .textField) + } else { + SecureField("Password", text: $password) + .disableAutocorrection(true) + .autocapitalization(/*@START_MENU_TOKEN@*/.none/*@END_MENU_TOKEN@*/) + .focused($isFieldFocus, equals: .secureField) + } + + Button(action: { + self.isPasswordVisible.toggle() + }) { + Image(systemName: self.isPasswordVisible ? "eye.slash.fill" : "eye.fill") + .foregroundColor(Color.gray) + } + } + .padding() + .overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.gray, lineWidth: 1)) + .padding(.horizontal, 25) + .onChange(of: isPasswordVisible) { + isFieldFocus = isPasswordVisible ? .textField : .secureField + } + } + + Spacer() + } + + // Bottom content, including the sign-in button + VStack (spacing: 16) { + Button("Sign In") { + // Handle sign in action + self.isShowingVerification = true + } + .buttonStyle(PrimaryButtonStyle(width: 300)) + + + + Button(action: { + // Handle forgot password action + }) { + Text("Forgot Password") + .foregroundColor(Color.primaryColor) + .fontWeight(.semibold) + .underline() // Underlined text + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom) // Aligns buttons to the bottom + } + .fullScreenCover(isPresented: $isShowingVerification) { + VerificationView(isSignIn: true, path: $path) + } + .gesture(TapGesture().onEnded{ + self.hideKeyboard() + }) + .ignoresSafeArea(.keyboard) + .navigationBarBackButtonHidden(true) + } +} + +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 new file mode 100644 index 0000000..a2050bb --- /dev/null +++ b/Rethread/Rethread/Styles/ButtonStyles.swift @@ -0,0 +1,153 @@ +// ButtonStyles.swift + +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 || isDisabled ? Color(red: 170/255, green: 177/255, blue: 187/255) + : Color.primaryColor) + .cornerRadius(9) + .foregroundColor(.white) + .scaleEffect(configuration.isPressed ? 0.95 : 1) + } +} + +struct SecondaryButtonStyle: ButtonStyle { + // Button background color #EEF1F4 + // Rounded corners + // Take in width and height as parameters? + var width: CGFloat = 100 + var height: CGFloat = 20 + + 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: 214/255, green: 216/255, blue: 219/255) + : Color(red: 238/255, green: 241/255, blue: 244/255)) + .cornerRadius(9) + .foregroundColor(Color(red: 84/255, green: 95/255, blue: 113/255)) + .scaleEffect(configuration.isPressed ? 0.95 : 1) + } +} + +struct PreviousButtonStyle: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .foregroundColor(.white) + .frame(width: 70, height: 60) + .background(UnevenRoundedRectangle(cornerRadii: .init( + topLeading: 0.0, + bottomLeading: 0.0, + bottomTrailing: 25.0, + topTrailing: 6.0), + style: .continuous) + .fill(configuration.isPressed ? Color(red: 170/255, green: 177/255, blue: 187/255) + : Color.primaryColor)) + } +} + +struct NextButtonStyle: ButtonStyle { + var isEnabled: Bool + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .foregroundColor(.white) + .frame(width: 70, height: 60) + .background(UnevenRoundedRectangle(cornerRadii: .init( + topLeading: 6.0, + bottomLeading: 25.0, + bottomTrailing: 0.0, + topTrailing: 0.0), + style: .continuous) + .fill(isEnabled && !configuration.isPressed ? Color.primaryColor + : Color(red: 170/255, green: 177/255, blue: 187/255))) // Disabled color + } +} + +struct LetStartButtonStyle: ButtonStyle { + var isEnabled: Bool + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .foregroundColor(.white) + .frame(width: 140, height: 60) + .background(UnevenRoundedRectangle(cornerRadii: .init( + topLeading: 6.0, + bottomLeading: 25.0, + bottomTrailing: 0.0, + topTrailing: 0.0), + style: .continuous) + .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 new file mode 100644 index 0000000..4cc4bca --- /dev/null +++ b/Rethread/Rethread/Styles/ColorPalette.swift @@ -0,0 +1,6 @@ +import SwiftUI + +extension Color { + static let primaryColor = Color(red: 102/255, green: 112/255, blue: 128/255) + 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 new file mode 100644 index 0000000..015b28a --- /dev/null +++ b/Rethread/Rethread/VerificationView.swift @@ -0,0 +1,125 @@ +import SwiftUI + +struct VerificationView: View { + // 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? + + enum Field: Int, Hashable { + case field1 = 0, field2, field3, field4 + + var next: Field? { + return Field(rawValue: self.rawValue + 1) + } + } + + + var body: some View { + VStack { + 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("Verify your account") + .font(.largeTitle) + .fontWeight(.bold) + .padding(.horizontal, 25) + .padding(.bottom, 1) + .foregroundColor(Color.primaryColor) + + HStack (spacing: 0) { + Text("Enter the 4-digit PIN code sent to your email address xxx@example.com.") + .fontWeight(.medium) + .foregroundColor(Color.primaryColor) + Spacer() + } + .padding(.horizontal, 25) + + VStack(alignment: .leading, spacing: 0) { + HStack(spacing: 10) { + ForEach(0..<4, id: \.self) { index in + TextField("", text: $code[index]) + .frame(width: 45, height: 45) + .overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.primary, lineWidth: 1)) + .multilineTextAlignment(.center) + .keyboardType(.numberPad) + .focused($focusedField, equals: Field(rawValue: index)) + .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 { + // Last field - perhaps perform some action or unfocus + focusedField = nil + } + } 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) + .padding(.top, 30) + + } + Spacer() + } + + // Bottom content, including the sign-in button + VStack (spacing: 16) { + Button("Verify") { + // 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 + }) { + Text("Request new code") + .foregroundColor(Color.primaryColor) + .fontWeight(.semibold) + .underline() // Underlined text + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom) // Aligns buttons to the bottom + } + .onTapGesture { + self.hideKeyboard() + } + + } +} + + 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 new file mode 100644 index 0000000..6692b20 --- /dev/null +++ b/Rethread/Rethread/WelcomeView.swift @@ -0,0 +1,70 @@ +import SwiftUI +import AVKit + +struct WelcomeView: View { + @Binding var path: [String] + @State private var showOnboarding = false + @State private var player = AVPlayer() + + var body: some View { + ZStack(alignment: .bottom) { + PlayerView() + .edgesIgnoringSafeArea(.all) + .padding(.bottom, 290) + + + Spacer() + + VStack(alignment: .center, spacing: 40.0) { + + Text("Hi, Parsa!") + .font(.title) + .fontWeight(.bold) + .foregroundColor(.gray) + .padding(.top, 20) + + Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit purus sit amet luctus venenatis, lectus magna fringilla urna, porttitor.") + .font(.body) + .foregroundColor(.gray) + .multilineTextAlignment(.center) + .padding(.horizontal) + + + Button("Get Started", action: { + withAnimation { + showOnboarding.toggle() + } + }) + .buttonStyle(PrimaryButtonStyle(width: 200, height: 20)) + .padding(.bottom, 40.0) + .padding(.top) + + } + .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) // 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