diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81e15ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ + +iStore.xcodeproj/project.xcworkspace/xcuserdata/mohamedsayed.xcuserdatad/UserInterfaceState.xcuserstate +*.xcuserstate +*.xcuserstate +*.xcuserstate diff --git a/iStore.xcodeproj/project.pbxproj b/iStore.xcodeproj/project.pbxproj index 01933df..4108378 100644 --- a/iStore.xcodeproj/project.pbxproj +++ b/iStore.xcodeproj/project.pbxproj @@ -35,9 +35,6 @@ 41F8DC102B871D82000582A1 /* UserAddressButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41F8DC0C2B871D82000582A1 /* UserAddressButton.swift */; }; 41F8DC112B871D82000582A1 /* ProductsGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41F8DC0D2B871D82000582A1 /* ProductsGrid.swift */; }; 41F8DC132B871DB0000582A1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 41F8DC122B871DB0000582A1 /* Assets.xcassets */; }; - 41F8DC182B871DCF000582A1 /* CategoriesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41F8DC152B871DCF000582A1 /* CategoriesViewModel.swift */; }; - 41F8DC192B871DCF000582A1 /* CategoriesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41F8DC162B871DCF000582A1 /* CategoriesView.swift */; }; - 41F8DC1A2B871DCF000582A1 /* CategoriesGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41F8DC172B871DCF000582A1 /* CategoriesGrid.swift */; }; 41F8DC1C2B871E09000582A1 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41F8DC1B2B871E09000582A1 /* Constants.swift */; }; 41F8DC202B871E1F000582A1 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41F8DC1E2B871E1F000582A1 /* User.swift */; }; 41F8DC212B871E1F000582A1 /* ProductsData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41F8DC1F2B871E1F000582A1 /* ProductsData.swift */; }; @@ -82,9 +79,6 @@ 41F8DC0C2B871D82000582A1 /* UserAddressButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UserAddressButton.swift; path = Home/UserAddressButton.swift; sourceTree = ""; }; 41F8DC0D2B871D82000582A1 /* ProductsGrid.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProductsGrid.swift; path = Home/ProductsGrid.swift; sourceTree = ""; }; 41F8DC122B871DB0000582A1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 41F8DC152B871DCF000582A1 /* CategoriesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoriesViewModel.swift; sourceTree = ""; }; - 41F8DC162B871DCF000582A1 /* CategoriesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoriesView.swift; sourceTree = ""; }; - 41F8DC172B871DCF000582A1 /* CategoriesGrid.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoriesGrid.swift; sourceTree = ""; }; 41F8DC1B2B871E09000582A1 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 41F8DC1E2B871E1F000582A1 /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 41F8DC1F2B871E1F000582A1 /* ProductsData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductsData.swift; sourceTree = ""; }; @@ -137,7 +131,6 @@ 41F8DBF62B871D45000582A1 /* NetworkLayer */, 419612472BA993F800938360 /* Profile */, 4193243E2B8CCC530015C45D /* Cart */, - 41F8DC142B871DCF000582A1 /* Categories */, 41F8DC322B871EDC000582A1 /* ProductDetails */, 41F8DC092B871D82000582A1 /* Home */, 4161881E2BA1682700A96EB6 /* Login */, @@ -220,16 +213,6 @@ name = Home; sourceTree = ""; }; - 41F8DC142B871DCF000582A1 /* Categories */ = { - isa = PBXGroup; - children = ( - 41F8DC152B871DCF000582A1 /* CategoriesViewModel.swift */, - 41F8DC162B871DCF000582A1 /* CategoriesView.swift */, - 41F8DC172B871DCF000582A1 /* CategoriesGrid.swift */, - ); - path = Categories; - sourceTree = ""; - }; 41F8DC1D2B871E1F000582A1 /* Models */ = { isa = PBXGroup; children = ( @@ -346,7 +329,6 @@ 419612442BA98B2300938360 /* AuthenticatedUser.swift in Sources */, 41F8DC212B871E1F000582A1 /* ProductsData.swift in Sources */, 419324422B8CDBDE0015C45D /* CartData.swift in Sources */, - 41F8DC192B871DCF000582A1 /* CategoriesView.swift in Sources */, 41C96C3E2B9C795C004511E8 /* CheckOutButton.swift in Sources */, 415A0D5D2BA17F1A00ECC38A /* PasswordView.swift in Sources */, 4193243D2B8CCC2F0015C45D /* CartView.swift in Sources */, @@ -354,7 +336,6 @@ 410BCA4D2BA5B1CB0013F568 /* NavigationCoordinator.swift in Sources */, DB208D472BC8479400D3B71D /* LocationView.swift in Sources */, 41F8DC202B871E1F000582A1 /* User.swift in Sources */, - 41F8DC1A2B871DCF000582A1 /* CategoriesGrid.swift in Sources */, 41F8DC1C2B871E09000582A1 /* Constants.swift in Sources */, 419324402B8CDBA30015C45D /* CartNetworkManager.swift in Sources */, 41F8DC112B871D82000582A1 /* ProductsGrid.swift in Sources */, @@ -373,7 +354,6 @@ 41F8DC0F2B871D82000582A1 /* HomeView.swift in Sources */, 41F8DC0E2B871D82000582A1 /* HomeViewModel.swift in Sources */, 41F8DC3E2B871EDD000582A1 /* ProductDetailsView.swift in Sources */, - 41F8DC182B871DCF000582A1 /* CategoriesViewModel.swift in Sources */, 41F8DC102B871D82000582A1 /* UserAddressButton.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/iStore.xcodeproj/project.xcworkspace/xcuserdata/mohamedsayed.xcuserdatad/UserInterfaceState.xcuserstate b/iStore.xcodeproj/project.xcworkspace/xcuserdata/mohamedsayed.xcuserdatad/UserInterfaceState.xcuserstate index bb1b28d..a9a6735 100644 Binary files a/iStore.xcodeproj/project.xcworkspace/xcuserdata/mohamedsayed.xcuserdatad/UserInterfaceState.xcuserstate and b/iStore.xcodeproj/project.xcworkspace/xcuserdata/mohamedsayed.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/iStore/Cart/CartList.swift b/iStore/Cart/CartList.swift index 3c58db4..e2e8c52 100644 --- a/iStore/Cart/CartList.swift +++ b/iStore/Cart/CartList.swift @@ -8,7 +8,6 @@ import SwiftUI struct CartList: View { - private let carts: [CartData] init(carts: [CartData]) { diff --git a/iStore/Cart/CartView.swift b/iStore/Cart/CartView.swift index 28c4e82..1c0bcfc 100644 --- a/iStore/Cart/CartView.swift +++ b/iStore/Cart/CartView.swift @@ -8,16 +8,18 @@ import SwiftUI struct CartView: View { + @StateObject private var cartViewModel = CartViewModel(networkManager: NetworkManager(), cartManager: CartNetworkManager()) + private let id: Int - @StateObject var cartViewModel = CartViewModel(networkManager: NetworkManager(), cartManager: CartNetworkManager()) - - @EnvironmentObject var authenticationViewModel: AuthenticationViewModel + init(id: Int) { + self.id = id + } var body: some View { - NavigationView { + NavigationStack { VStack() { if cartViewModel.carts.isEmpty { - ProgressView() + ContentUnavailableView("No products", systemImage: "cart", description: Text("You don't have any productss yet.")) } else { CartList(carts: cartViewModel.carts) .listStyle(.plain) @@ -36,7 +38,17 @@ struct CartView: View { .navigationBarTitleDisplayMode(.inline) } .task { - cartViewModel.loadUserCarts(withUserId: authenticationViewModel.user.id) + cartViewModel.loadUserCarts(withUserId: id) { result in + switch result { + case .success(let data): + DispatchQueue.main.async { + cartViewModel.carts.removeAll() + cartViewModel.carts.append(contentsOf: data.carts) + } + case .failure(let error): + print(NetworkError.unknownError(error)) + } + } } } } diff --git a/iStore/Cart/CartViewModel.swift b/iStore/Cart/CartViewModel.swift index 7675936..fce5f7d 100644 --- a/iStore/Cart/CartViewModel.swift +++ b/iStore/Cart/CartViewModel.swift @@ -8,7 +8,6 @@ import Foundation final class CartViewModel: ObservableObject { - private let networkManager: NetworkManager private let cartManager: CartNetworkManager @@ -19,19 +18,15 @@ final class CartViewModel: ObservableObject { self.cartManager = cartManager } - func loadUserCarts(withUserId userID: Int) { - + func loadUserCarts(withUserId userID: Int, completion: @escaping (Result) -> Void) { let url = userCartURL + "\(userID)" networkManager.loadData(withURL: url) { (result: Result) in switch result { - case .success(let data): - DispatchQueue.main.async { - self.carts.removeAll() - self.carts.append(contentsOf: data.carts) - } - case .failure(let error): - print(NetworkError.unknownError(error)) + case .success(let data): + completion(.success(data)) + case .failure(let error): + completion(.failure(error)) } } } diff --git a/iStore/Cart/CheckOutButton.swift b/iStore/Cart/CheckOutButton.swift index 7d26255..fdbba5f 100644 --- a/iStore/Cart/CheckOutButton.swift +++ b/iStore/Cart/CheckOutButton.swift @@ -8,7 +8,6 @@ import SwiftUI struct CheckOutButton: View { - private let total: Int private let action: () -> Void diff --git a/iStore/CartNetworkManager.swift b/iStore/CartNetworkManager.swift index 7d34aba..bff9823 100644 --- a/iStore/CartNetworkManager.swift +++ b/iStore/CartNetworkManager.swift @@ -39,7 +39,6 @@ struct CartNetworkManager { request.setValue("application/json", forHTTPHeaderField: "Content-Type") let task = URLSession.shared.uploadTask(with: request, from: uploadData) { data, response , error in - if error != nil { print (CartRequestError.invalidCart) return diff --git a/iStore/Categories/CategoriesGrid.swift b/iStore/Categories/CategoriesGrid.swift deleted file mode 100644 index 6ac2813..0000000 --- a/iStore/Categories/CategoriesGrid.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// CategoriesGrid.swift -// iStore -// -// Created by Mohamed Sayed on 14.02.24. -// - -import SwiftUI - -struct CategoriesGrid: View { - var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) - } -} - -struct CategoriesGrid_Previews: PreviewProvider { - static var previews: some View { - CategoriesGrid() - } -} diff --git a/iStore/Categories/CategoriesView.swift b/iStore/Categories/CategoriesView.swift deleted file mode 100644 index 7385fe4..0000000 --- a/iStore/Categories/CategoriesView.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// Category.swift -// iStore -// -// Created by Mohamed Sayed on 11.02.24. -// - -import SwiftUI - -struct CategoriesView: View { - - @StateObject var categoryViewModel = CategoriesViewModel(networkManager: NetworkManager()) - - @EnvironmentObject var authenticationViewModel: AuthenticationViewModel - - var body: some View { - NavigationView { - VStack { - if categoryViewModel.isloading { - ProgressView() - } else { - ProductsGrid(products: categoryViewModel.products, userID: authenticationViewModel.user.id) - } - } - .navigationTitle(categories) - .navigationBarTitleDisplayMode(.inline) - } - .task { - categoryViewModel.getProducts() - } - } -} diff --git a/iStore/Categories/CategoriesViewModel.swift b/iStore/Categories/CategoriesViewModel.swift deleted file mode 100644 index cc76c42..0000000 --- a/iStore/Categories/CategoriesViewModel.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// CategoryViewModel.swift -// iStore -// -// Created by Mohamed Sayed on 12.02.24. -// - -import Foundation - -final class CategoriesViewModel: ObservableObject { - - private let networkManager: NetworkManager - - @Published var products = [Product]() - @Published var isloading = true - - init(networkManager: NetworkManager) { - self.networkManager = networkManager - } - - func getProducts() { - - let url = allProductsURL - - networkManager.loadData(withURL: url) { [weak self] (result: Result) in - - guard let self = self else { - return - } - - switch result { - case .success(let data): - DispatchQueue.main.async { - self.products.append(contentsOf: data.products) - self.isloading = false - } - - case .failure(let error): - print(NetworkError.unknownError(error)) - } - } - } -} diff --git a/iStore/Constants.swift b/iStore/Constants.swift index 5c972e4..bdf439e 100644 --- a/iStore/Constants.swift +++ b/iStore/Constants.swift @@ -6,13 +6,9 @@ // import Foundation - -// MARK: Colors - // MARK: NavigationBar Titles let iStore = "iStore" -let categories = "Categories" // MARK: Networking Constants @@ -26,4 +22,3 @@ let userURL = "https://dummyjson.com/auth/me" let addToCartURL = "https://dummyjson.com/carts/add" let userCartURL = "https://dummyjson.com/carts/user/" - diff --git a/iStore/ContentView.swift b/iStore/ContentView.swift index 48de92f..11521bf 100644 --- a/iStore/ContentView.swift +++ b/iStore/ContentView.swift @@ -8,23 +8,33 @@ import SwiftUI struct ContentView: View { + @EnvironmentObject private var locationDataManager: LocationDataManager + + private let user: AuthenticatedUser + private let isGuest: Bool + + init(user: AuthenticatedUser, isGuest: Bool) { + self.user = user + self.isGuest = isGuest + } + var body: some View { TabView { - HomeView() + HomeView(id: user.id, homeAddress: user.address.address, homePostalCode: user.address.postalCode, homeCity: user.address.city, homeState: user.address.state, homeLatitude: user.address.coordinates.lat, homeLongitude: user.address.coordinates.lng, currentAddress: locationDataManager.address, currentPostalCode: locationDataManager.postalCode, currentCity: locationDataManager.city, currentState: locationDataManager.state, currentLatitude: locationDataManager.latitude, currentLongitude: locationDataManager.longitude, isCurrentLocation: locationDataManager.isCurrentLocation, isGuest: isGuest, getLocationPermission: locationDataManager.requestUserLocationPermission) .tabItem { Image(systemName: "house.fill") } - CartView() + CartView(id: user.id) .tabItem { - Image(systemName: "bag.fill") + Image(systemName: "cart") } - ProfileView() + ProfileView(username: user.username, firstName: user.firstName, lastName: user.lastName, maidenName: user.maidenName, email: user.email, phone: user.phone, image: user.image, homeAddress: user.address.address, homePostalCode: user.address.postalCode, homeCity: user.address.city, homeState: user.address.state, isGuest: isGuest) .tabItem { Image(systemName: "person.fill") } } - .accentColor(.pink) + .tint(.pink) } } diff --git a/iStore/Home/HomeView.swift b/iStore/Home/HomeView.swift index 917c945..b638d0f 100755 --- a/iStore/Home/HomeView.swift +++ b/iStore/Home/HomeView.swift @@ -8,29 +8,63 @@ import SwiftUI struct HomeView: View { - @StateObject var homeViewModel = HomeViewModel(networkManager: NetworkManager()) - @EnvironmentObject var authenticationViewModel: AuthenticationViewModel - @EnvironmentObject var locationDataManager: LocationDataManager + @StateObject private var homeViewModel = HomeViewModel(networkManager: NetworkManager()) - @State private var isloading = true + private let id: Int + private let homeAddress: String + private let homePostalCode: String + private let homeCity: String + private let homeState: String + private let homeLatitude: Double + private let homeLongitude: Double + + private let currentAddress: String + private let currentPostalCode: String + private let currentCity: String + private let currentState: String + private let currentLatitude: Double + private let currentLongitude: Double + + private let isCurrentLocation: Bool + private let isGuest: Bool + private let getLocationPermission: () -> Void + + init(id: Int, homeAddress: String, homePostalCode: String, homeCity: String, homeState: String, homeLatitude: Double, homeLongitude: Double, currentAddress: String, currentPostalCode: String, currentCity: String, currentState: String, currentLatitude: Double, currentLongitude: Double, isCurrentLocation: Bool, isGuest: Bool, getLocationPermission: @escaping () -> Void) { + self.id = id + self.homeAddress = homeAddress + self.homePostalCode = homePostalCode + self.homeCity = homeCity + self.homeState = homeState + self.homeLatitude = homeLatitude + self.homeLongitude = homeLongitude + self.currentAddress = currentAddress + self.currentPostalCode = currentPostalCode + self.currentCity = currentCity + self.currentState = currentState + self.currentLatitude = currentLatitude + self.currentLongitude = currentLongitude + self.isCurrentLocation = isCurrentLocation + self.isGuest = isGuest + self.getLocationPermission = getLocationPermission + } var body: some View { NavigationStack { VStack() { - if authenticationViewModel.isGuest || locationDataManager.isCurrentLocation { - UserAddressButton(address: locationDataManager.address, postalCode: locationDataManager.postalCode, city: locationDataManager.city, state: locationDataManager.state, latitude: locationDataManager.latitude, longitude: locationDataManager.longitude) + if isGuest || isCurrentLocation { + UserAddressButton(address: currentAddress, postalCode: currentPostalCode, city: currentCity, state: currentState, latitude: currentLatitude, longitude: currentLongitude) } else { - UserAddressButton(address: authenticationViewModel.user.address.address, postalCode: authenticationViewModel.user.address.postalCode, city: authenticationViewModel.user.address.city, state: authenticationViewModel.user.address.state, latitude: authenticationViewModel.user.address.coordinates.lat, longitude: authenticationViewModel.user.address.coordinates.lng) + UserAddressButton(address: homeAddress, postalCode: homePostalCode, city: homeCity, state: homeState, latitude: homeLatitude, longitude: homeLongitude) } - if isloading { + if homeViewModel.isloading { Spacer() ProgressView() Spacer() } else { - ProductsGrid(products: homeViewModel.products, userID: authenticationViewModel.user.id) + ProductsGrid(products: homeViewModel.products, userID: id) } } .padding(.horizontal) @@ -44,18 +78,18 @@ struct HomeView: View { homeViewModel.showProducts { data in DispatchQueue.main.async { homeViewModel.products.append(contentsOf: data.products) - isloading = false + homeViewModel.isloading = false } } } .onAppear() { - locationDataManager.requestUserLocationPermission() + getLocationPermission() } .task { homeViewModel.showProducts { data in DispatchQueue.main.async { homeViewModel.products.append(contentsOf: data.products) - isloading = false + homeViewModel.isloading = false } } } diff --git a/iStore/Home/HomeViewModel.swift b/iStore/Home/HomeViewModel.swift index ca26c12..3ceab39 100755 --- a/iStore/Home/HomeViewModel.swift +++ b/iStore/Home/HomeViewModel.swift @@ -12,6 +12,7 @@ final class HomeViewModel: ObservableObject { @Published var products = [Product]() @Published var searchText = "" + @Published var isloading = true var searchProduct: String { searchText.lowercased() @@ -26,11 +27,11 @@ final class HomeViewModel: ObservableObject { networkManager.loadData(withURL: url) { (result: Result) in switch result { - case .success(let data): - completion(data) - - case .failure(let error): - print(NetworkError.unknownError(error)) + case .success(let data): + completion(data) + + case .failure(let error): + print(NetworkError.unknownError(error)) } } } @@ -40,11 +41,11 @@ final class HomeViewModel: ObservableObject { networkManager.loadData(withURL: url) { (result: Result) in switch result { - case .success(let data): - completion(data) - - case .failure(let error): - print(NetworkError.unknownError(error)) + case .success(let data): + completion(data) + + case .failure(let error): + print(NetworkError.unknownError(error)) } } } diff --git a/iStore/Location/LocationDataManager.swift b/iStore/Location/LocationDataManager.swift index a859c16..2af70bb 100644 --- a/iStore/Location/LocationDataManager.swift +++ b/iStore/Location/LocationDataManager.swift @@ -49,18 +49,18 @@ final class LocationDataManager: NSObject, ObservableObject { func getUserAddress(completion: @escaping (CLPlacemark) -> Void) { guard let Location = self.locationManager.location else { return - } + } - let geocoder = CLGeocoder() - geocoder.reverseGeocodeLocation(Location) { (placemarks, error) in - guard let address = placemarks?.first, error == nil else { - return + let geocoder = CLGeocoder() + geocoder.reverseGeocodeLocation(Location) { (placemarks, error) in + guard let address = placemarks?.first, error == nil else { + return + } + + completion(address) } - - completion(address) } } -} // MARK: CLLocationManagerDelegate diff --git a/iStore/Login/AuthenticationViewModel.swift b/iStore/Login/AuthenticationViewModel.swift index 89d6d7d..758a497 100644 --- a/iStore/Login/AuthenticationViewModel.swift +++ b/iStore/Login/AuthenticationViewModel.swift @@ -11,35 +11,32 @@ final class AuthenticationViewModel: ObservableObject { private let authenticationManager: AuthenticationManager @Published var user: AuthenticatedUser + @Published var userName: String + @Published var password: String @Published var showError = false @Published var showProgress = false - @Published var isGuest = false - init(authenticationManager: AuthenticationManager, user: AuthenticatedUser) { + init(authenticationManager: AuthenticationManager, user: AuthenticatedUser, userName: String, password: String) { self.authenticationManager = authenticationManager self.user = user + self.userName = userName + self.password = password } - func authenticateUser(userName: String, password: String, completion: @escaping () -> Void) { + func authenticateUser(completion: @escaping (Result) -> Void) { authenticationManager.authenticate(userName: userName, password: password) { [weak self] result in - - guard let self = self else { + guard let self else { return } switch result { - case .success(let token): - self.authenticationManager.getCurrentUser(withToken: token) { currentUser in - DispatchQueue.main.async { - self.user = currentUser - completion() - } - } - - case .failure(_): - DispatchQueue.main.async { - self.showError = true - } + case .success(let token): + self.authenticationManager.getCurrentUser(withToken: token) { currentUser in + completion(.success(currentUser)) + } + + case .failure(_): + completion(.failure(AuthenticationError.invalidCurrentUserURL)) } } } diff --git a/iStore/Login/LoginView.swift b/iStore/Login/LoginView.swift index b94429e..4006c20 100644 --- a/iStore/Login/LoginView.swift +++ b/iStore/Login/LoginView.swift @@ -8,16 +8,8 @@ import SwiftUI struct LoginView: View { - @EnvironmentObject var authenticationViewModel: AuthenticationViewModel - @EnvironmentObject var navigationCoordinator: NavigationCoordinator - - @State private var userName: String - @State private var password: String - - init(userName: String, password: String) { - self.userName = userName - self.password = password - } + @EnvironmentObject private var authenticationViewModel: AuthenticationViewModel + @EnvironmentObject private var navigationCoordinator: NavigationCoordinator var body: some View { VStack(alignment: .leading, spacing: 24) { @@ -37,9 +29,9 @@ struct LoginView: View { Spacer() VStack(spacing: 24) { - UserNameView(userName: $userName) + UserNameView(userName: $authenticationViewModel.userName) - PasswordView(password: $password) + PasswordView(password: $authenticationViewModel.password) if authenticationViewModel.showError { Text("Please, Enter a valid UserName and Password!") @@ -55,15 +47,27 @@ struct LoginView: View { authenticationViewModel.showProgress = true authenticationViewModel.showError = false - authenticationViewModel.authenticateUser(userName: userName, password: password) { - navigationCoordinator.switchView = .contentView - authenticationViewModel.showProgress = false + authenticationViewModel.authenticateUser() { result in + switch result { + case .success(let currentUser): + DispatchQueue.main.async { + authenticationViewModel.user = currentUser + navigationCoordinator.user = currentUser + navigationCoordinator.switchView = .contentView + authenticationViewModel.showProgress = false + } + + case .failure(_): + DispatchQueue.main.async { + authenticationViewModel.showError = true + } + } } } Button("Continue as a Guest") { navigationCoordinator.switchView = .contentView - authenticationViewModel.isGuest = true + navigationCoordinator.isGuest = true } } .padding() diff --git a/iStore/Models/CartData.swift b/iStore/Models/CartData.swift index 21e0863..1c73c68 100644 --- a/iStore/Models/CartData.swift +++ b/iStore/Models/CartData.swift @@ -7,7 +7,6 @@ import Foundation - struct Cart: Decodable { let carts: [CartData] } diff --git a/iStore/Navigation/NavigationCoordinator.swift b/iStore/Navigation/NavigationCoordinator.swift index 3925fa4..0a0fe7a 100644 --- a/iStore/Navigation/NavigationCoordinator.swift +++ b/iStore/Navigation/NavigationCoordinator.swift @@ -14,4 +14,10 @@ enum CurrentView { final class NavigationCoordinator: ObservableObject { @Published var switchView = CurrentView.loginView + @Published var user: AuthenticatedUser + @Published var isGuest = false + + init(user: AuthenticatedUser) { + self.user = user + } } diff --git a/iStore/Navigation/ViewManager.swift b/iStore/Navigation/ViewManager.swift index 24caef0..105f18c 100644 --- a/iStore/Navigation/ViewManager.swift +++ b/iStore/Navigation/ViewManager.swift @@ -14,16 +14,16 @@ struct ViewManager: View { var body: some View { switch navigationCoordinator.switchView { - case .loginView: - LoginView(userName: "", password: "") - .environmentObject(navigationCoordinator) - .environmentObject(authenticationViewModel) - - case .contentView: - ContentView() - .environmentObject(navigationCoordinator) - .environmentObject(authenticationViewModel) - .environmentObject(locationDataManager) + case .loginView: + LoginView() + .environmentObject(navigationCoordinator) + .environmentObject(authenticationViewModel) + + case .contentView: + ContentView(user: navigationCoordinator.user, isGuest: navigationCoordinator.isGuest) + .environmentObject(navigationCoordinator) + .environmentObject(authenticationViewModel) + .environmentObject(locationDataManager) } } } diff --git a/iStore/NetworkLayer/NetworkManager.swift b/iStore/NetworkLayer/NetworkManager.swift index 0321b22..fedad4f 100755 --- a/iStore/NetworkLayer/NetworkManager.swift +++ b/iStore/NetworkLayer/NetworkManager.swift @@ -16,8 +16,7 @@ enum NetworkError: Error { } struct NetworkManager { - - func loadData(withURL urlString: String, completion: @ escaping (Result) -> Void) { + func loadData(withURL urlString: String, completion: @escaping (Result) -> Void) { let url = URL(string: urlString) diff --git a/iStore/ProductDetails/BrandView.swift b/iStore/ProductDetails/BrandView.swift index 97b453f..08639f4 100644 --- a/iStore/ProductDetails/BrandView.swift +++ b/iStore/ProductDetails/BrandView.swift @@ -8,8 +8,11 @@ import SwiftUI struct BrandView: View { + private let product: Product - var product: Product + init(product: Product) { + self.product = product + } var body: some View { HStack(alignment: .center, spacing: 2) { diff --git a/iStore/ProductDetails/DescriptionView.swift b/iStore/ProductDetails/DescriptionView.swift index 929cfe2..73fdb8b 100644 --- a/iStore/ProductDetails/DescriptionView.swift +++ b/iStore/ProductDetails/DescriptionView.swift @@ -8,8 +8,11 @@ import SwiftUI struct DescriptionView: View { + private let product: Product - var product: Product + init(product: Product) { + self.product = product + } var body: some View { VStack(alignment: .leading, spacing: 10) { diff --git a/iStore/ProductDetails/ProductDetailsTabView.swift b/iStore/ProductDetails/ProductDetailsTabView.swift index e0261f8..b5d3c89 100644 --- a/iStore/ProductDetails/ProductDetailsTabView.swift +++ b/iStore/ProductDetails/ProductDetailsTabView.swift @@ -8,27 +8,29 @@ import SwiftUI struct ProductDetailsTabView: View { + private let product: Product - var product: Product + init(product: Product) { + self.product = product + } var body: some View { - - TabView { - ForEach(product.images, id:\.self) { url in - AsyncImage(url: url) { image in - image - .resizable() - .scaledToFit() - } placeholder: { - ProgressView() - .tint(.white) - } - .clipShape(RoundedRectangle(cornerRadius: 10)) - .shadow(radius: 10) - .padding(5) + TabView { + ForEach(product.images, id:\.self) { url in + AsyncImage(url: url) { image in + image + .resizable() + .scaledToFit() + } placeholder: { + ProgressView() + .tint(.white) } + .clipShape(RoundedRectangle(cornerRadius: 10)) + .shadow(radius: 10) + .padding(5) } - .tabViewStyle(.page) - .indexViewStyle(.page(backgroundDisplayMode: .never)) + } + .tabViewStyle(.page) + .indexViewStyle(.page(backgroundDisplayMode: .never)) } } diff --git a/iStore/ProductDetails/ProductDetailsView.swift b/iStore/ProductDetails/ProductDetailsView.swift index 8973273..a2d729e 100644 --- a/iStore/ProductDetails/ProductDetailsView.swift +++ b/iStore/ProductDetails/ProductDetailsView.swift @@ -8,7 +8,7 @@ import SwiftUI struct ProductDetailsView: View { - @StateObject var productDetailsViewModel = ProductDetailsViewModel( + @StateObject private let productDetailsViewModel = ProductDetailsViewModel( networkManager: NetworkManager(), cartManager: CartNetworkManager(), product: Product(id: Int(), @@ -26,8 +26,6 @@ struct ProductDetailsView: View { private let productID: Int private let userID: Int - @State private var isFavoriteTapped = false - init(productID: Int, userID: Int) { self.productID = productID self.userID = userID @@ -49,7 +47,6 @@ struct ProductDetailsView: View { ScrollView(.vertical, showsIndicators: false) { VStack(alignment: .leading) { - BrandView(product: productDetailsViewModel.product) Spacer() @@ -82,16 +79,18 @@ struct ProductDetailsView: View { .background(.black) } } - .toolbar() { - Button { - print("favorite tapped") - isFavoriteTapped.toggle() - } label: { - Image(systemName: isFavoriteTapped ? "heart.fill" : "heart") - } - } .task { - productDetailsViewModel.getProduct(with: productID) + productDetailsViewModel.getProduct(with: productID) { result in + switch result { + case .success(let product): + DispatchQueue.main.async { + productDetailsViewModel.product = product + productDetailsViewModel.isloading = false + } + case .failure(let error): + print(NetworkError.unknownError(error)) + } + } } } } diff --git a/iStore/ProductDetails/ProductDetailsViewModel.swift b/iStore/ProductDetails/ProductDetailsViewModel.swift index 964ecf3..e6eba40 100644 --- a/iStore/ProductDetails/ProductDetailsViewModel.swift +++ b/iStore/ProductDetails/ProductDetailsViewModel.swift @@ -8,7 +8,6 @@ import Foundation final class ProductDetailsViewModel: ObservableObject { - private let networkManager: NetworkManager private let cartManager: CartNetworkManager @@ -21,24 +20,16 @@ final class ProductDetailsViewModel: ObservableObject { self.product = product } - func getProduct(with id: Int) { + func getProduct(with id: Int, completion: @escaping (Result) -> Void) { let url = singleProductURL + "\(id)" - networkManager.loadData(withURL: url) { [weak self] (result: Result) in - - guard let self = self else { - return - } - + networkManager.loadData(withURL: url) { (result: Result) in switch result { - case .success(let data): - DispatchQueue.main.async { - self.product = data - self.isloading = false - } - - case .failure(let error): - print(NetworkError.unknownError(error)) + case .success(let data): + completion(.success(data)) + + case .failure(let error): + completion(.failure(error)) } } } diff --git a/iStore/ProductDetails/TotalPriceView.swift b/iStore/ProductDetails/TotalPriceView.swift index 70d4cff..2c7163b 100644 --- a/iStore/ProductDetails/TotalPriceView.swift +++ b/iStore/ProductDetails/TotalPriceView.swift @@ -8,8 +8,11 @@ import SwiftUI struct TotalPriceView: View { + private let product: Product - var product: Product + init(product: Product) { + self.product = product + } var body: some View { HStack() { diff --git a/iStore/Profile/ProfileView.swift b/iStore/Profile/ProfileView.swift index a62ef48..10faf5a 100644 --- a/iStore/Profile/ProfileView.swift +++ b/iStore/Profile/ProfileView.swift @@ -8,16 +8,54 @@ import SwiftUI struct ProfileView: View { - @EnvironmentObject var authenticationViewModel: AuthenticationViewModel + private let username: String + private let firstName: String + private let lastName: String + private let maidenName: String + private let email: String + private let phone: String + private let image: URL + private let homeAddress: String + private let homePostalCode: String + private let homeCity: String + private let homeState: String + + private let isGuest: Bool + + init(username: String, firstName: String, lastName: String, maidenName: String, email: String, phone: String, image: URL, homeAddress: String, homePostalCode: String, homeCity: String, homeState: String, isGuest: Bool) { + self.username = username + self.firstName = firstName + self.lastName = lastName + self.maidenName = maidenName + self.email = email + self.phone = phone + self.image = image + self.homeAddress = homeAddress + self.homePostalCode = homePostalCode + self.homeCity = homeCity + self.homeState = homeState + self.isGuest = isGuest + } var body: some View { - Form { - ProfilePictureView(image: authenticationViewModel.user.image) - - PersonalDetailsView(userName: authenticationViewModel.user.username, firstName: authenticationViewModel.user.firstName, lastName: authenticationViewModel.user.lastName, email: authenticationViewModel.user.email, phone: authenticationViewModel.user.phone) - - Section(header: Text("Home Address")) { - HomeAddressView(address: authenticationViewModel.user.address.address, city: authenticationViewModel.user.address.city, postalCode: authenticationViewModel.user.address.postalCode, state: authenticationViewModel.user.address.state) + NavigationStack { + if isGuest { + ContentUnavailableView("No Profile", systemImage: "person", description: Text("Please Login to Show your Profile.")) + .symbolVariant(.slash) + .navigationTitle("Profile") + .navigationBarTitleDisplayMode(.inline) + } else { + Form { + ProfilePictureView(image: image) + + PersonalDetailsView(userName: username, firstName: firstName, lastName: lastName, email: email, phone: phone) + + Section(header: Text("Home Address")) { + HomeAddressView(address: homeAddress, city: homeCity, postalCode: homePostalCode, state: homeState) + } + } + .navigationTitle("Profile") + .navigationBarTitleDisplayMode(.inline) } } } diff --git a/iStore/iStoreApp.swift b/iStore/iStoreApp.swift index 3515372..41f5c90 100644 --- a/iStore/iStoreApp.swift +++ b/iStore/iStoreApp.swift @@ -11,7 +11,7 @@ import SwiftUI struct iStoreApp: App { var body: some Scene { WindowGroup { - ViewManager(navigationCoordinator: NavigationCoordinator(), authenticationViewModel: AuthenticationViewModel(authenticationManager: AuthenticationManager(), user: AuthenticatedUser(id: Int(), username: String(),firstName: String(), lastName: String(), maidenName: String(), email: String(), phone: String(), image: URL(fileURLWithPath: String()), address: HomeAddress(address: String(), city: String(), postalCode: String(), coordinates: Coordinates(lat: Double(), lng: Double()), state: String()))), locationDataManager: LocationDataManager(address: String(), postalCode: String(), city: String(), state: String(), latitude: Double(), longitude: Double())) + ViewManager(navigationCoordinator: NavigationCoordinator(user: AuthenticatedUser(id: Int(), username: String(),firstName: String(), lastName: String(), maidenName: String(), email: String(), phone: String(), image: URL(fileURLWithPath: String()), address: HomeAddress(address: String(), city: String(), postalCode: String(), coordinates: Coordinates(lat: Double(), lng: Double()), state: String()))), authenticationViewModel: AuthenticationViewModel(authenticationManager: AuthenticationManager(), user: AuthenticatedUser(id: Int(), username: String(),firstName: String(), lastName: String(), maidenName: String(), email: String(), phone: String(), image: URL(fileURLWithPath: String()), address: HomeAddress(address: String(), city: String(), postalCode: String(), coordinates: Coordinates(lat: Double(), lng: Double()), state: String())), userName: "kminchelle", password: "0lelplR"), locationDataManager: LocationDataManager(address: String(), postalCode: String(), city: String(), state: String(), latitude: Double(), longitude: Double())) } } }