diff --git a/SinuS/SinuS.xcodeproj/project.pbxproj b/SinuS/SinuS.xcodeproj/project.pbxproj index 4b8b2ad..023120e 100644 --- a/SinuS/SinuS.xcodeproj/project.pbxproj +++ b/SinuS/SinuS.xcodeproj/project.pbxproj @@ -30,7 +30,7 @@ 4A32753E298AE46A009D1CC8 /* FirebaseDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = 4A32753D298AE46A009D1CC8 /* FirebaseDatabase */; }; 4A327540298AE46A009D1CC8 /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 4A32753F298AE46A009D1CC8 /* FirebaseMessaging */; }; 4A327542298AE46A009D1CC8 /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 4A327541298AE46A009D1CC8 /* FirebaseStorage */; }; - 4A4C6D002917C2E4001DB4F7 /* AuthenticationStartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A4C6CFF2917C2E4001DB4F7 /* AuthenticationStartView.swift */; }; + 4A327547298EA697009D1CC8 /* PreAuthenticationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A327546298EA697009D1CC8 /* PreAuthenticationView.swift */; }; 4A4C6D032917C2F9001DB4F7 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A4C6D022917C2F9001DB4F7 /* LoginView.swift */; }; 4A4C6D062917C30D001DB4F7 /* RegisterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A4C6D052917C30D001DB4F7 /* RegisterView.swift */; }; 4A4C6D082917CD95001DB4F7 /* AuthenticationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A4C6D072917CD95001DB4F7 /* AuthenticationResult.swift */; }; @@ -63,6 +63,10 @@ CE6A64E629696C1C00DC47E5 /* SwiftKeychainWrapper in Frameworks */ = {isa = PBXBuildFile; productRef = CE6A64E529696C1C00DC47E5 /* SwiftKeychainWrapper */; }; CE89A8762969F93200300C9D /* ManageProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE89A8752969F93200300C9D /* ManageProfileView.swift */; }; CE89A87A296AF2D300300C9D /* LogHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE89A879296AF2D300300C9D /* LogHelper.swift */; }; + CEECA4FD29709E0F00CA4496 /* ForgotPasswordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEECA4FC29709E0F00CA4496 /* ForgotPasswordView.swift */; }; + CEECA4FF2974534700CA4496 /* ResetPasswordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEECA4FE2974534700CA4496 /* ResetPasswordView.swift */; }; + CEECA5012974665800CA4496 /* VerifyEmailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEECA5002974665800CA4496 /* VerifyEmailView.swift */; }; + CEECA50329748DA600CA4496 /* ClickResetPasswordLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEECA50229748DA600CA4496 /* ClickResetPasswordLinkView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -101,8 +105,7 @@ 4A32752829772B0A009D1CC8 /* WaveMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaveMenuView.swift; sourceTree = ""; }; 4A32752A29773236009D1CC8 /* CompareButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompareButtonView.swift; sourceTree = ""; }; 4A32752C297D4B6E009D1CC8 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; - 4A327537298AE0F5009D1CC8 /* SinuS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SinuS.entitlements; sourceTree = ""; }; - 4A4C6CFF2917C2E4001DB4F7 /* AuthenticationStartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartView.swift; sourceTree = ""; }; + 4A327546298EA697009D1CC8 /* PreAuthenticationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreAuthenticationView.swift; sourceTree = ""; }; 4A4C6D022917C2F9001DB4F7 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; 4A4C6D052917C30D001DB4F7 /* RegisterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterView.swift; sourceTree = ""; }; 4A4C6D072917CD95001DB4F7 /* AuthenticationResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationResult.swift; sourceTree = ""; }; @@ -140,6 +143,11 @@ 4AD26FF428C7BFC10065FB01 /* DataGatherer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataGatherer.swift; sourceTree = ""; }; CE89A8752969F93200300C9D /* ManageProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageProfileView.swift; sourceTree = ""; }; CE89A879296AF2D300300C9D /* LogHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogHelper.swift; sourceTree = ""; }; + CEECA4FC29709E0F00CA4496 /* ForgotPasswordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForgotPasswordView.swift; sourceTree = ""; }; + CEECA4FE2974534700CA4496 /* ResetPasswordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetPasswordView.swift; sourceTree = ""; }; + CEECA5002974665800CA4496 /* VerifyEmailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifyEmailView.swift; sourceTree = ""; }; + CEECA50229748DA600CA4496 /* ClickResetPasswordLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClickResetPasswordLinkView.swift; sourceTree = ""; }; + CEECA5042974928C00CA4496 /* SinuSRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SinuSRelease.entitlements; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -207,9 +215,13 @@ 4A4C6CFE2917C2A8001DB4F7 /* Authentication */ = { isa = PBXGroup; children = ( - 4A4C6CFF2917C2E4001DB4F7 /* AuthenticationStartView.swift */, 4A4C6D022917C2F9001DB4F7 /* LoginView.swift */, 4A4C6D052917C30D001DB4F7 /* RegisterView.swift */, + CEECA4FC29709E0F00CA4496 /* ForgotPasswordView.swift */, + CEECA4FE2974534700CA4496 /* ResetPasswordView.swift */, + CEECA5002974665800CA4496 /* VerifyEmailView.swift */, + CEECA50229748DA600CA4496 /* ClickResetPasswordLinkView.swift */, + 4A327546298EA697009D1CC8 /* PreAuthenticationView.swift */, ); path = Authentication; sourceTree = ""; @@ -308,7 +320,7 @@ 4AD26FB828BBA5D40065FB01 /* SinuS */ = { isa = PBXGroup; children = ( - 4A327537298AE0F5009D1CC8 /* SinuS.entitlements */, + CEECA5042974928C00CA4496 /* SinuSRelease.entitlements */, 4AC5B3DD296AD537003D819C /* Style */, 4AD26FE928BBAE930065FB01 /* Data */, 4AD26FE428BBA8C40065FB01 /* Views */, @@ -552,7 +564,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 4A4C6D002917C2E4001DB4F7 /* AuthenticationStartView.swift in Sources */, + CEECA5012974665800CA4496 /* VerifyEmailView.swift in Sources */, + CEECA4FF2974534700CA4496 /* ResetPasswordView.swift in Sources */, 4A32752B29773236009D1CC8 /* CompareButtonView.swift in Sources */, 4AC5B3E5296ADE0D003D819C /* TotalUserData.swift in Sources */, 4AD26FEB28BBAEA40065FB01 /* SinusData.swift in Sources */, @@ -574,6 +587,8 @@ 4A0B397728E06E9600D7EEC9 /* SinusUserData.swift in Sources */, 4AD26FBC28BBA5D40065FB01 /* ContentView.swift in Sources */, 4A0B397428D6044300D7EEC9 /* WaveView.swift in Sources */, + CEECA4FD29709E0F00CA4496 /* ForgotPasswordView.swift in Sources */, + 4A0B397428D6044300D7EEC9 /* WaveView.swift in Sources */, 4AC5B3D529698525003D819C /* PersonalView.swift in Sources */, 4A0B396A28D38DE000D7EEC9 /* MenuView.swift in Sources */, 4A4C6D062917C30D001DB4F7 /* RegisterView.swift in Sources */, @@ -591,9 +606,11 @@ 4A4C6D082917CD95001DB4F7 /* AuthenticationResult.swift in Sources */, 4A4C6D032917C2F9001DB4F7 /* LoginView.swift in Sources */, 4A0B397B28E0846900D7EEC9 /* RelationStatusView.swift in Sources */, + 4A327547298EA697009D1CC8 /* PreAuthenticationView.swift in Sources */, 4AC5B3E3296ADB51003D819C /* UserData.swift in Sources */, 4AD26FBA28BBA5D40065FB01 /* SinuSApp.swift in Sources */, 4AC5B3D3296849DD003D819C /* RestApiHelper.swift in Sources */, + CEECA50329748DA600CA4496 /* ClickResetPasswordLinkView.swift in Sources */, 4AD26FF128C6773E0065FB01 /* FeedWaveView.swift in Sources */, 4AD26FED28C676C10065FB01 /* LineChart.swift in Sources */, 4A0B396E28D3935900D7EEC9 /* NewWaveView.swift in Sources */, @@ -753,11 +770,10 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = SinuS/SinuS.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"SinuS/Preview Content\""; - DEVELOPMENT_TEAM = 479GB76B7L; + DEVELOPMENT_TEAM = 27MSAG6A3N; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = SinuS/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -778,11 +794,11 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = SinuS/SinuS.entitlements; + CODE_SIGN_ENTITLEMENTS = SinuS/SinuSRelease.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"SinuS/Preview Content\""; - DEVELOPMENT_TEAM = 479GB76B7L; + DEVELOPMENT_TEAM = 27MSAG6A3N; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = SinuS/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; diff --git a/SinuS/SinuS/ContentView.swift b/SinuS/SinuS/ContentView.swift index 7cd0501..0458d63 100644 --- a/SinuS/SinuS/ContentView.swift +++ b/SinuS/SinuS/ContentView.swift @@ -14,9 +14,9 @@ struct ContentView: View { var body: some View { NavigationView { - if KeychainWrapper.standard.string(forKey: "bearerToken") == nil { - AuthenticationStartView() - } else { + if KeychainWrapper.standard.string(forKey: "bearerToken") == nil { + PreAuthenticationView() + } else { MenuView() } } @@ -28,3 +28,24 @@ struct ContentView_Previews: PreviewProvider { ContentView() } } + +func application(_ application: UIApplication, + continue userActivity: NSUserActivity, + restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + // Get URL components from the incoming user activity. + guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, + let incomingURL = userActivity.webpageURL, + let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true) else { + return false + } + + // Check for specific URL components that you need. + guard let path = components.path, + let params = components.queryItems else { + return false + } + print("path = \(path)") + + // Dispatch event to trigger another view + return true +} diff --git a/SinuS/SinuS/Data/DataGatherer.swift b/SinuS/SinuS/Data/DataGatherer.swift index fcb8073..541ca01 100644 --- a/SinuS/SinuS/Data/DataGatherer.swift +++ b/SinuS/SinuS/Data/DataGatherer.swift @@ -98,6 +98,67 @@ public class DataManager { return result } + public func forgotPassword(email: String) -> AuthenticationResult? { + let loginUrl = "https://lukassinus2.vanbroeckhuijsenvof.nl/api/forgot-password" + let parameters: [String: Any] = ["email": email] + let decoder = JSONDecoder() + var request = RestApiHelper.createRequest(type: "POST", url: loginUrl, auth: false) + + do { + request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) + } catch let error { + print(error.localizedDescription) + return nil + } + + var result: AuthenticationResult? + let data = RestApiHelper.perfomRestCall(request: request) + + do { + result = try decoder.decode(AuthenticationResult.self, from: data!) + } catch { + let returnedData = (String(bytes: data!, encoding: .utf8) ?? "") + let errMsg = "Unable to process forgot password request: \(returnedData)" + self.logHelper.logMsg(level: "error", message: errMsg) + print(errMsg) + } + + return result + } + + public func resetPassword(token: String, email: String, password: String, confirmPassword: String) -> AuthenticationResult? { + let loginUrl = "https://lukassinus2.vanbroeckhuijsenvof.nl/api/reset-password" + let parameters: [String: Any] = [ + "token": token, + "email": email, + "password": password, + "password_confirmation": confirmPassword + ] + let decoder = JSONDecoder() + var request = RestApiHelper.createRequest(type: "POST", url: loginUrl, auth: false) + + do { + request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) + } catch let error { + print(error.localizedDescription) + return nil + } + + var result: AuthenticationResult? + let data = RestApiHelper.perfomRestCall(request: request) + + do { + result = try decoder.decode(AuthenticationResult.self, from: data!) + } catch { + let returnedData = (String(bytes: data!, encoding: .utf8) ?? "") + let errMsg = "Unable to reset password: \(returnedData)" + self.logHelper.logMsg(level: "error", message: errMsg) + print(errMsg) + } + + return result + } + public func getCurrentUser() -> TotalUserData? { let url = "https://lukassinus2.vanbroeckhuijsenvof.nl/api/user" let decoder = JSONDecoder() diff --git a/SinuS/SinuS/Views/Authentication/AuthenticationStartView.swift b/SinuS/SinuS/Views/Authentication/AuthenticationStartView.swift index e428f84..0b511b7 100644 --- a/SinuS/SinuS/Views/Authentication/AuthenticationStartView.swift +++ b/SinuS/SinuS/Views/Authentication/AuthenticationStartView.swift @@ -8,23 +8,46 @@ import SwiftUI struct AuthenticationStartView: View { - var body: some View { - NavigationView { - VStack { + @State private var selection = Tab.feed - Spacer() + private var feedViewModelExplore: FeedViewModel - // Login Button - NavigationLink(destination: LoginView(), label: { - MenuButton(image: Image(systemName: "person.badge.key.fill"), name: "Login") - }) + private enum Tab: Hashable { + case feed + case login + case register + } - // Register Button - NavigationLink(destination: RegisterView(), label: { - MenuButton(image: Image(systemName: "person.fill.badge.plus"), name: "Register") - }) + init() { + self.feedViewModelExplore = FeedViewModel() + } + + var body: some View { + let manager = DataManager() - Spacer() + VStack { + TabView(selection: self.$selection) { + Group { + FeedView(feedViewModel: self.feedViewModelExplore, gatherer: manager, onlyFollowing: false) + .tabItem { + Label("Explore", systemImage: "network") + } + .tag(Tab.feed) + RegisterView() + .tabItem { + Label("Register", systemImage: "person.fill.badge.plus") + } + .tag(Tab.register) + LoginView() + .tabItem { + Label("Login", systemImage: "person.badge.key.fill") + } + .tag(Tab.login) + } + .toolbar(.visible, for: .tabBar) + .toolbarBackground(Style.AppColor, for: .tabBar) + .toolbarBackground(.visible, for: .tabBar) + .toolbarColorScheme(.dark, for: .tabBar) } } } diff --git a/SinuS/SinuS/Views/Authentication/ClickResetPasswordLinkView.swift b/SinuS/SinuS/Views/Authentication/ClickResetPasswordLinkView.swift new file mode 100644 index 0000000..c46163a --- /dev/null +++ b/SinuS/SinuS/Views/Authentication/ClickResetPasswordLinkView.swift @@ -0,0 +1,48 @@ +// +// ClickResetPasswordLinkView.swift +// SinuS +// +// Created by Patrick van Broeckhuijsen on 15/01/2023. +// + +import SwiftUI + +struct ClickResetPasswordLinkView: View { + var body: some View { + VStack { + Spacer() + + Text("Please click on the link sent to your email") + + Text("Go back") + + Spacer() + } + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(Style.AppColor, for: .navigationBar) + .toolbarBackground(.visible, for: .navigationBar) + .toolbar { + ToolbarItem(placement: .principal) { + VStack { + HStack { + Image(systemName: "water.waves") + .resizable() + .frame(width: 25, height: 25) + .foregroundColor(.white) + .padding(.bottom) + Text("Love Waves") + .foregroundColor(.white) + .font(.system(size: 25)) + .padding(.bottom) + } + } + } + } + } +} + +struct ClickResetPasswordLinkView_Previews: PreviewProvider { + static var previews: some View { + ClickResetPasswordLinkView() + } +} diff --git a/SinuS/SinuS/Views/Authentication/ForgotPasswordView.swift b/SinuS/SinuS/Views/Authentication/ForgotPasswordView.swift new file mode 100644 index 0000000..1ca282e --- /dev/null +++ b/SinuS/SinuS/Views/Authentication/ForgotPasswordView.swift @@ -0,0 +1,79 @@ +// +// ForgotPasswordView.swift +// SinuS +// +// Created by Patrick van Broeckhuijsen on 12/01/2023. +// + +import SwiftUI + +struct ForgotPasswordView: View { + let manager = DataManager() + + @State private var email: String = UserDefaults.standard.string(forKey: "email") ?? "" + @State private var showAlert = false + @State private var nextView: Bool? = false + + var body: some View { + VStack { + // Email + HStack { + Text("Email") + Spacer() + TextField("", text: self.$email) + .disableAutocorrection(true) + .border(Color.white, width: 0.5) + .frame(width: 220) + }.padding(.horizontal).padding(.top) + + NavigationLink(destination: ClickResetPasswordLinkView(), tag: true, selection: self.$nextView) { EmptyView() } + + Button("Submit") { + let authenticationResult = self.manager.forgotPassword(email: self.email) + + if authenticationResult == nil { + self.showAlert.toggle() + } else { + UserDefaults.standard.set(self.email, forKey: "forgotPasswordEmail") + self.nextView = true + } + + } + .alert(isPresented: $showAlert) { + return Alert(title: Text("Error"), message: Text("Failed to request forgot password link for \(self.email)"), dismissButton: .default(Text("OK"))) + } + .padding() + } + .background(Style.AppColor) + .cornerRadius(5) + .shadow(radius: 5) + .padding() + .foregroundColor(.white) + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(Style.AppColor, for: .navigationBar) + .toolbarBackground(.visible, for: .navigationBar) + .toolbar { + ToolbarItem(placement: .principal) { + VStack { + HStack { + Image(systemName: "water.waves") + .resizable() + .frame(width: 25, height: 25) + .foregroundColor(.white) + .padding(.bottom) + Text("Love Waves") + .foregroundColor(.white) + .font(.system(size: 25)) + .padding(.bottom) + } + } + } + } + } +} + +struct ForgotPasswordView_Previews: PreviewProvider { + static var previews: some View { + ForgotPasswordView() + } +} diff --git a/SinuS/SinuS/Views/Authentication/LoginView.swift b/SinuS/SinuS/Views/Authentication/LoginView.swift index 9ee6294..5589d4b 100644 --- a/SinuS/SinuS/Views/Authentication/LoginView.swift +++ b/SinuS/SinuS/Views/Authentication/LoginView.swift @@ -12,69 +12,108 @@ import Firebase struct LoginView: View { let manager = DataManager() - // Tokens never expire so we can keep reusing the saved token - @State private var pushActive = false + @State private var showMenu: Bool? = false @State private var email: String = UserDefaults.standard.string(forKey: "email") ?? "" @State private var password: String = "" @State private var showAlert = false var body: some View { VStack { - // Email - HStack { - Text("Email:") - Spacer() - TextField("", text: self.$email) - .disableAutocorrection(true) - .border(Color.white, width: 0.5) - .frame(width: 220) - }.padding(.horizontal).padding(.top) + Spacer() - // Password - HStack { - Text("Password:") - Spacer() - SecureField("", text: self.$password) - .disableAutocorrection(true) - .border(Color.white, width: 0.5) - .frame(width: 220) - }.padding(.horizontal).padding(.top) + VStack { + // Email + HStack { + Text("Email") + Spacer() + TextField("", text: self.$email) + .disableAutocorrection(true) + .border(Color.white, width: 1) + .frame(width: 220) + }.padding(.horizontal).padding(.top) - // Login Button - Button("Login") { - let res = self.manager.login(email: self.email, password: self.password) - UserDefaults.standard.set(self.email, forKey: "email") + // Password + HStack { + Text("Password") + Spacer() + SecureField("", text: self.$password) + .disableAutocorrection(true) + .border(Color.white, width: 1) + .frame(width: 220) + }.padding(.horizontal).padding(.top) - if res == nil { - self.showAlert.toggle() - } else { - // Set global authentication token. - ContentView.AuthenticationToken = res!.success - let saveSuccessful: Bool = KeychainWrapper.standard.set(ContentView.AuthenticationToken, forKey: "bearerToken") - if !saveSuccessful { - print("Could not save bearerToken") - } + NavigationLink(destination: MenuView(), tag: true, selection: self.$showMenu) { EmptyView() } + + // Login Button + Button("Login") { + let res = self.manager.login(email: self.email, password: self.password) + UserDefaults.standard.set(self.email, forKey: "email") - self.pushActive = true + if res == nil { + self.showAlert.toggle() + } else { + // Set global authentication token. + ContentView.AuthenticationToken = res!.success + let saveSuccessful: Bool = KeychainWrapper.standard.set( + ContentView.AuthenticationToken, + forKey: "bearerToken") + if !saveSuccessful { + print("Could not save bearerToken") + } + + self.showMenu = true + } } + .alert(isPresented: $showAlert) { + return Alert(title: Text("Failed to login"), + message: Text(self.email), + dismissButton: .default(Text("OK"))) + } + .padding() } - .alert(isPresented: $showAlert) { - return Alert(title: Text("Failed to Login"), message: Text("Unable to log user: \(self.email) in"), dismissButton: .default(Text("OK"))) + .background(Style.AppColor) + .cornerRadius(5) + .shadow(radius: 5) + .padding() + .foregroundColor(.white) + + NavigationLink(destination: ForgotPasswordView(), label: { + Text("Forgot password?") + }) + .foregroundColor(.black) + .padding() + + Spacer() + VStack { + NavigationLink(destination: RegisterView(), label: { + MenuButton(image: Image(systemName: "person.fill.badge.plus"), name: "Register") + }) } .padding() } - .background(Style.AppColor) - .cornerRadius(5) - .shadow(radius: 5) - .padding() - .foregroundColor(.white) - - NavigationLink(destination: MenuView(), - isActive: self.$pushActive) { - EmptyView() - }.hidden() + .navigationBarBackButtonHidden(true) + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(Style.AppColor, for: .navigationBar) + .toolbarBackground(.visible, for: .navigationBar) + .toolbar { + ToolbarItem(placement: .principal) { + VStack { + HStack { + Image(systemName: "water.waves") + .resizable() + .frame(width: 25, height: 25) + .foregroundColor(.white) + .padding(.bottom) + Text("Love Waves") + .foregroundColor(.white) + .font(.system(size: 25)) + .padding(.bottom) + } + } + } + } } } diff --git a/SinuS/SinuS/Views/Authentication/PreAuthenticationView.swift b/SinuS/SinuS/Views/Authentication/PreAuthenticationView.swift new file mode 100644 index 0000000..127359c --- /dev/null +++ b/SinuS/SinuS/Views/Authentication/PreAuthenticationView.swift @@ -0,0 +1,75 @@ +// +// PreAuthenticationView.swift +// SinuS +// +// Created by Loe Hendriks on 04/02/2023. +// + +import SwiftUI + +struct PreAuthenticationView: View { + @State private var selection = Tab.feed + + private var feedViewModelExplore: FeedViewModel + + private enum Tab: Hashable { + case feed + case login + } + + init() { + self.feedViewModelExplore = FeedViewModel() + } + + var body: some View { + let manager = DataManager() + + VStack { + TabView(selection: self.$selection) { + Group { + FeedView(feedViewModel: self.feedViewModelExplore, gatherer: manager, onlyFollowing: false) + .tabItem { + Label("Explore", systemImage: "network") + } + .tag(Tab.feed) + LoginView() + .tabItem { + Label("Login", systemImage: "person.badge.key.fill") + } + .tag(Tab.login) + } + .toolbar(.visible, for: .tabBar) + .toolbarBackground(Style.AppColor, for: .tabBar) + .toolbarBackground(.visible, for: .tabBar) + .toolbarColorScheme(.dark, for: .tabBar) + } + } + .navigationBarBackButtonHidden(true) + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(Style.AppColor, for: .navigationBar) + .toolbarBackground(.visible, for: .navigationBar) + .toolbar { + ToolbarItem(placement: .principal) { + VStack { + HStack { + Image(systemName: "water.waves") + .resizable() + .frame(width: 25, height: 25) + .foregroundColor(.white) + .padding(.bottom) + Text("Love Waves") + .foregroundColor(.white) + .font(.system(size: 25)) + .padding(.bottom) + } + } + } + } + } +} + +struct PreAuthenticationView_Previews: PreviewProvider { + static var previews: some View { + PreAuthenticationView() + } +} diff --git a/SinuS/SinuS/Views/Authentication/RegisterView.swift b/SinuS/SinuS/Views/Authentication/RegisterView.swift index 5d716b6..6a32667 100644 --- a/SinuS/SinuS/Views/Authentication/RegisterView.swift +++ b/SinuS/SinuS/Views/Authentication/RegisterView.swift @@ -16,16 +16,14 @@ struct RegisterView: View { @State private var password: String = "" @State private var confirmPassword: String = "" @State private var showAlert = false - - static var isPasswordCorrect: Bool = false - @State private var showButton = false + @State private var showMenu: Bool? = false var body: some View { VStack { // Name HStack { - Text("Name:") + Text("Name") Spacer() TextField("", text: self.$name) .disableAutocorrection(true) @@ -35,7 +33,7 @@ struct RegisterView: View { // Email HStack { - Text("Email:") + Text("Email") Spacer() TextField("", text: self.$email) .disableAutocorrection(true) @@ -45,7 +43,7 @@ struct RegisterView: View { // Password HStack { - Text("Password:") + Text("Password") Spacer() TextField("", text: self.$password) .disableAutocorrection(true) @@ -55,7 +53,7 @@ struct RegisterView: View { // Confirm password HStack { - Text("Confirm password:") + Text("Confirm password") Spacer() TextField("", text: self.$confirmPassword) .disableAutocorrection(true) @@ -63,6 +61,8 @@ struct RegisterView: View { .frame(width: 220) }.padding(.horizontal).padding(.top) + NavigationLink(destination: VerifyEmailView(), tag: true, selection: self.$showMenu) { EmptyView() } + // Register button Button("Register") { let authenticationResult = self.manager.register( @@ -76,11 +76,12 @@ struct RegisterView: View { } else { // Set global authentication token. ContentView.AuthenticationToken = authenticationResult!.success - let saveSuccessful: Bool = KeychainWrapper.standard.set(ContentView.AuthenticationToken, forKey: "bearerToken", isSynchronizable: true) + let saveSuccessful: Bool = KeychainWrapper.standard.set(ContentView.AuthenticationToken, forKey: "bearerToken") if !saveSuccessful { print("Could not save bearerToken") } - self.showButton.toggle() + + self.showMenu = true } } @@ -94,11 +95,25 @@ struct RegisterView: View { .shadow(radius: 5) .padding() .foregroundColor(.white) - - if self.showButton { - NavigationLink(destination: MenuView(), label: { - MenuButton(image: Image(systemName: "lock.open"), name: "Enter") - }) + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(Style.AppColor, for: .navigationBar) + .toolbarBackground(.visible, for: .navigationBar) + .toolbar { + ToolbarItem(placement: .principal) { + VStack { + HStack { + Image(systemName: "water.waves") + .resizable() + .frame(width: 25, height: 25) + .foregroundColor(.white) + .padding(.bottom) + Text("Love Waves") + .foregroundColor(.white) + .font(.system(size: 25)) + .padding(.bottom) + } + } + } } } } diff --git a/SinuS/SinuS/Views/Authentication/ResetPasswordView.swift b/SinuS/SinuS/Views/Authentication/ResetPasswordView.swift new file mode 100644 index 0000000..d3ee468 --- /dev/null +++ b/SinuS/SinuS/Views/Authentication/ResetPasswordView.swift @@ -0,0 +1,118 @@ +// +// ResetPasswordView.swift +// SinuS +// +// Created by Patrick van Broeckhuijsen on 15/01/2023. +// + +import SwiftUI +import SwiftKeychainWrapper + +struct ResetPasswordView: View { + let manager = DataManager() + + @State private var token: String = "" + @State private var email: String = UserDefaults.standard.string(forKey: "forgotPasswordEmail") ?? "" + @State private var password: String = "" + @State private var confirmPassword: String = "" + @State private var showAlert = false + @State private var showLogin: Bool? = false + + var body: some View { + VStack { + if self.email != "" { + // Show email address of which reset password email was sent to + HStack { + Text("Email") + Spacer() + TextField("", text: self.$email) + .disabled(true) + .disableAutocorrection(true) + .border(Color.white, width: 0.5) + .frame(width: 220) + }.padding(.horizontal).padding(.top) + } + + // Password + HStack { + Text("New password") + Spacer() + TextField("", text: self.$password) + .disableAutocorrection(true) + .border(Color.white, width: 0.5) + .frame(width: 220) + }.padding(.horizontal).padding(.top) + + // Confirm password + HStack { + Text("Confirm password") + Spacer() + TextField("", text: self.$confirmPassword) + .disableAutocorrection(true) + .border(Color.white, width: 0.5) + .frame(width: 220) + }.padding(.horizontal).padding(.top) + + NavigationLink(destination: LoginView(), tag: true, selection: self.$showLogin) { EmptyView() } + + Button("Submit") { + let authenticationResult = self.manager.resetPassword( + token: self.token, + email: self.email, + password: self.password, + confirmPassword: self.confirmPassword) + + if authenticationResult == nil { + self.showAlert.toggle() + } else { + UserDefaults.standard.removeObject(forKey: "forgotPasswordEmail") + + // Set global authentication token. + ContentView.AuthenticationToken = authenticationResult!.success + let saveSuccessful: Bool = KeychainWrapper.standard.set(ContentView.AuthenticationToken, forKey: "bearerToken") + if !saveSuccessful { + print("Could not save bearerToken") + } + + self.showLogin = true + } + + } + .alert(isPresented: $showAlert) { + return Alert(title: Text("Failed to reset password"), message: Text("Unable to reset password: \(self.email)"), dismissButton: .default(Text("OK"))) + } + .padding() + } + .background(Style.AppColor) + .cornerRadius(5) + .shadow(radius: 5) + .padding() + .foregroundColor(.white) + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(Style.AppColor, for: .navigationBar) + .toolbarBackground(.visible, for: .navigationBar) + .toolbar { + ToolbarItem(placement: .principal) { + VStack { + HStack { + Image(systemName: "water.waves") + .resizable() + .frame(width: 25, height: 25) + .foregroundColor(.white) + .padding(.bottom) + Text("Love Waves") + .foregroundColor(.white) + .font(.system(size: 25)) + .padding(.bottom) + } + } + } + } + } +} + +struct ResetPasswordView_Previews: PreviewProvider { + static var previews: some View { + ResetPasswordView() + } +} diff --git a/SinuS/SinuS/Views/Authentication/VerifyEmailView.swift b/SinuS/SinuS/Views/Authentication/VerifyEmailView.swift new file mode 100644 index 0000000..5373335 --- /dev/null +++ b/SinuS/SinuS/Views/Authentication/VerifyEmailView.swift @@ -0,0 +1,48 @@ +// +// VerifyEmailView.swift +// SinuS +// +// Created by Patrick van Broeckhuijsen on 15/01/2023. +// + +import SwiftUI + +struct VerifyEmailView: View { + var body: some View { + VStack { + Spacer() + + Text("Please verify your email address by clicking on the link in your email") + + Text("Resend email") + + Spacer() + } + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(Style.AppColor, for: .navigationBar) + .toolbarBackground(.visible, for: .navigationBar) + .toolbar { + ToolbarItem(placement: .principal) { + VStack { + HStack { + Image(systemName: "water.waves") + .resizable() + .frame(width: 25, height: 25) + .foregroundColor(.white) + .padding(.bottom) + Text("Love Waves") + .foregroundColor(.white) + .font(.system(size: 25)) + .padding(.bottom) + } + } + } + } + } +} + +struct VerifyEmailView_Previews: PreviewProvider { + static var previews: some View { + VerifyEmailView() + } +} diff --git a/SinuS/SinuS/Views/MenuView.swift b/SinuS/SinuS/Views/MenuView.swift index f6fb27f..b0bae41 100644 --- a/SinuS/SinuS/Views/MenuView.swift +++ b/SinuS/SinuS/Views/MenuView.swift @@ -28,21 +28,6 @@ struct MenuView: View { var body: some View { let manager = DataManager() VStack { - HStack { - Spacer() - Image(systemName: "water.waves") - .resizable() - .frame(width: 25, height: 25) - .foregroundColor(.white) - .padding(.bottom) - Text("Love Waves") - .foregroundColor(.white) - .font(.system(size: 25)) - .padding(.bottom) - Spacer() - } - .background(Style.AppColor) - TabView(selection: self.$selection) { Group { FeedView(feedViewModel: self.feedViewModelExplore, gatherer: manager, onlyFollowing: false) @@ -65,6 +50,28 @@ struct MenuView: View { .toolbarBackground(Style.AppColor, for: .tabBar) .toolbarBackground(.visible, for: .tabBar) .toolbarColorScheme(.dark, for: .tabBar) + + } + } + .navigationBarBackButtonHidden(true) + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(Style.AppColor, for: .navigationBar) + .toolbarBackground(.visible, for: .navigationBar) + .toolbar { + ToolbarItem(placement: .principal) { + VStack { + HStack { + Image(systemName: "water.waves") + .resizable() + .frame(width: 25, height: 25) + .foregroundColor(.white) + .padding(.bottom) + Text("Love Waves") + .foregroundColor(.white) + .font(.system(size: 25)) + .padding(.bottom) + } + } } } } diff --git a/SinuS/SinuS/Views/Personal/ManageProfileView.swift b/SinuS/SinuS/Views/Personal/ManageProfileView.swift index 2cfb88c..14effa9 100644 --- a/SinuS/SinuS/Views/Personal/ManageProfileView.swift +++ b/SinuS/SinuS/Views/Personal/ManageProfileView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import SwiftKeychainWrapper struct ManageProfileView: View { let manager: DataManager @@ -21,8 +22,10 @@ struct ManageProfileView: View { HStack { Spacer() - NavigationLink(destination: AuthenticationStartView(), label: { + NavigationLink(destination: PreAuthenticationView(), label: { MenuButton(image: Image(systemName: "figure.walk.departure"), name: "Logout") + }).simultaneousGesture(TapGesture().onEnded { + KeychainWrapper.standard.remove(forKey: "bearerToken") }) NavigationLink(destination: EditProfileView(gatherer: self.manager), label: {