diff --git a/Core/Sources/Core/Models/DomainConvertible/SellerInfo.swift b/Core/Sources/Core/Models/DomainConvertible/SellerInfo.swift new file mode 100644 index 00000000..cfbe4fb4 --- /dev/null +++ b/Core/Sources/Core/Models/DomainConvertible/SellerInfo.swift @@ -0,0 +1,29 @@ +// +// File.swift +// +// +// Created by raniazeid on 30/08/2022. +// + +import Domain +import Networking + +extension Networking.SellerInfo: DomainConvertible { + + func toDomain() -> Domain.SellerInfo { + return Domain.SellerInfo(sellerName: sellerName, + reviews: reviews, + location: location, + workTime: workTime, + followers: followers, + productsCount: productsCount, + createdAt: createdAt, + products: products.map { $0.toDomain() }) + } +} + +extension Networking.Products: DomainConvertible { + func toDomain() -> Domain.Products { + return Domain.Products(title: title, image: image, price: price, review: review, totalRevieews: totalReviews) + } +} diff --git a/Core/Sources/Core/Repository/SellerInfoRepository.swift b/Core/Sources/Core/Repository/SellerInfoRepository.swift new file mode 100644 index 00000000..cf768f34 --- /dev/null +++ b/Core/Sources/Core/Repository/SellerInfoRepository.swift @@ -0,0 +1,34 @@ +// +// File.swift +// +// +// Created by raniazeid on 30/08/2022. +// + +import Domain +import Foundation +import Networking + +/// Implementation of `Domain.SellerInfoRepository` +/// +struct SellerInfoRepository: Domain.SellerInfoRepository { + + let remote: SellerInfoProtocol + + init(remote: SellerInfoProtocol) { + self.remote = remote + } + + func loadSellerInfo(completion: @escaping (Result) -> Void) { + remote.getAllSellerInfo(completion: { + result in + switch result { + case let .success(list): + let domainList = list.toDomain() + completion(.success(domainList)) + case let .failure(error): + completion(.failure(error)) + } + }) + } +} diff --git a/Core/Sources/Core/Tools/RepositoryProvider.swift b/Core/Sources/Core/Tools/RepositoryProvider.swift index a41fb7d0..991023cc 100644 --- a/Core/Sources/Core/Tools/RepositoryProvider.swift +++ b/Core/Sources/Core/Tools/RepositoryProvider.swift @@ -22,6 +22,10 @@ public struct DefaultRepositoryProvider { // MARK: - DefaultRepositoryProvider extension DefaultRepositoryProvider: RepositoryProvider { + public func makeSellerInfoRepository() -> Domain.SellerInfoRepository { + let remote = SellerInfoRemote(network: network) + return SellerInfoRepository(remote: remote) + } public func makeProductRepository() -> Domain.ProductRepository { let remote = ProductsRemote(network: network) diff --git a/Dokan/Dokan.xcodeproj/project.pbxproj b/Dokan/Dokan.xcodeproj/project.pbxproj index a0b8128c..dd311d1b 100644 --- a/Dokan/Dokan.xcodeproj/project.pbxproj +++ b/Dokan/Dokan.xcodeproj/project.pbxproj @@ -24,9 +24,11 @@ 437D1B1E2870F2C400B972BC /* Core in Frameworks */ = {isa = PBXBuildFile; productRef = 437D1B1D2870F2C400B972BC /* Core */; }; 43D4BF0928918F9A0023190F /* FeaturedProductTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D4BF0828918F9A0023190F /* FeaturedProductTests.swift */; }; 43D4BF0B2891A5C50023190F /* FeaturedProductCollectionViewCellTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D4BF0A2891A5C50023190F /* FeaturedProductCollectionViewCellTests.swift */; }; - 51A04FAD289335FF00900EFF /* ReviewerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A04FAB289335FF00900EFF /* ReviewerTableViewCell.swift */; }; + 51934C8F28AD91B60078FD3C /* SellerInfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934C8D28AD91B60078FD3C /* SellerInfoCell.swift */; }; + 51934C9028AD91B60078FD3C /* SellerInfoCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51934C8E28AD91B60078FD3C /* SellerInfoCell.xib */; }; 519D380428A05AD90029B722 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 519D380328A05AD90029B722 /* Kingfisher */; }; 519D380728A05B860029B722 /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519D380628A05B860029B722 /* UIImage+Extension.swift */; }; + 51A04FAD289335FF00900EFF /* ReviewerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A04FAB289335FF00900EFF /* ReviewerTableViewCell.swift */; }; 856E36AF2889229B00735EF2 /* InfoSellerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 856E36AE2889229B00735EF2 /* InfoSellerView.swift */; }; 856E36B1288922EF00735EF2 /* InfoSellerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 856E36B0288922EF00735EF2 /* InfoSellerView.xib */; }; 856E36B928893C9A00735EF2 /* UIDokan in Frameworks */ = {isa = PBXBuildFile; productRef = AAE1A1C0285E1A3D00A4EBB0 /* UIDokan */; }; @@ -36,9 +38,14 @@ 85B31DBD2870BEDE00B1C9E8 /* LoginViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 85B31DB92870BEDE00B1C9E8 /* LoginViewController.xib */; }; 85DA676428928F1500DCABD2 /* InfoSellerViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85DA676328928F1500DCABD2 /* InfoSellerViewDelegate.swift */; }; 85F6B6DA28919C87007730AB /* ProductDetailsNavigationBarBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F6B6D928919C87007730AB /* ProductDetailsNavigationBarBehavior.swift */; }; + 9900241E28B15F9E00D1D722 /* InfoSellerViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9900241A28B15F9E00D1D722 /* InfoSellerViewModelType.swift */; }; + 9900241F28B15F9E00D1D722 /* InfoSellerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9900241B28B15F9E00D1D722 /* InfoSellerViewController.swift */; }; + 9900242028B15F9E00D1D722 /* InfoSellerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9900241C28B15F9E00D1D722 /* InfoSellerViewModel.swift */; }; + 9900242128B15F9E00D1D722 /* InfoSellerViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9900241D28B15F9E00D1D722 /* InfoSellerViewController.xib */; }; 99091F1828895D96000FA9C9 /* ProductTitleQuantityView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 99091F1728895D96000FA9C9 /* ProductTitleQuantityView.xib */; }; 99091F1A28895DC1000FA9C9 /* ProductTitleQuantityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99091F1928895DC1000FA9C9 /* ProductTitleQuantityView.swift */; }; 99CC7308288E2FE400503D86 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 99CC7307288E2FE400503D86 /* Colors.xcassets */; }; + 99E2C0A528C2A7DE0063874E /* InfoSellerViewController+CollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E2C0A428C2A7DE0063874E /* InfoSellerViewController+CollectionView.swift */; }; AA21B43628537E0400E4F217 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA21B43528537E0400E4F217 /* AppDelegate.swift */; }; AA21B43828537E0400E4F217 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA21B43728537E0400E4F217 /* SceneDelegate.swift */; }; AA21B43A28537E0400E4F217 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA21B43928537E0400E4F217 /* ViewController.swift */; }; @@ -95,9 +102,11 @@ 437B99AB289082BC00F59B06 /* FeaturedProductCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FeaturedProductCollectionViewCell.xib; sourceTree = ""; }; 43D4BF0828918F9A0023190F /* FeaturedProductTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedProductTests.swift; sourceTree = ""; }; 43D4BF0A2891A5C50023190F /* FeaturedProductCollectionViewCellTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedProductCollectionViewCellTests.swift; sourceTree = ""; }; + 51934C8D28AD91B60078FD3C /* SellerInfoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SellerInfoCell.swift; sourceTree = ""; }; + 51934C8E28AD91B60078FD3C /* SellerInfoCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SellerInfoCell.xib; sourceTree = ""; }; + 519D380628A05B860029B722 /* UIImage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = ""; }; 51A04FAB289335FF00900EFF /* ReviewerTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewerTableViewCell.swift; sourceTree = ""; }; 51A04FAC289335FF00900EFF /* ReviewerTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ReviewerTableViewCell.xib; sourceTree = ""; }; - 519D380628A05B860029B722 /* UIImage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = ""; }; 856E36AE2889229B00735EF2 /* InfoSellerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoSellerView.swift; sourceTree = ""; }; 856E36B0288922EF00735EF2 /* InfoSellerView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InfoSellerView.xib; sourceTree = ""; }; 85B31DB62870BEDE00B1C9E8 /* LoginViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModelType.swift; sourceTree = ""; }; @@ -106,9 +115,14 @@ 85B31DB92870BEDE00B1C9E8 /* LoginViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LoginViewController.xib; sourceTree = ""; }; 85DA676328928F1500DCABD2 /* InfoSellerViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoSellerViewDelegate.swift; sourceTree = ""; }; 85F6B6D928919C87007730AB /* ProductDetailsNavigationBarBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductDetailsNavigationBarBehavior.swift; sourceTree = ""; }; + 9900241A28B15F9E00D1D722 /* InfoSellerViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoSellerViewModelType.swift; sourceTree = ""; }; + 9900241B28B15F9E00D1D722 /* InfoSellerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoSellerViewController.swift; sourceTree = ""; }; + 9900241C28B15F9E00D1D722 /* InfoSellerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoSellerViewModel.swift; sourceTree = ""; }; + 9900241D28B15F9E00D1D722 /* InfoSellerViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InfoSellerViewController.xib; sourceTree = ""; }; 99091F1728895D96000FA9C9 /* ProductTitleQuantityView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ProductTitleQuantityView.xib; sourceTree = ""; }; 99091F1928895DC1000FA9C9 /* ProductTitleQuantityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductTitleQuantityView.swift; sourceTree = ""; }; 99CC7307288E2FE400503D86 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; + 99E2C0A428C2A7DE0063874E /* InfoSellerViewController+CollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "InfoSellerViewController+CollectionView.swift"; sourceTree = ""; }; AA21B43228537E0400E4F217 /* Dokan.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Dokan.app; sourceTree = BUILT_PRODUCTS_DIR; }; AA21B43528537E0400E4F217 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; AA21B43728537E0400E4F217 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -247,11 +261,19 @@ path = FeaturedProduct; sourceTree = ""; }; - 51A04FAA289334A800900EFF /* Cell */ = { + 51934C8728AD911A0078FD3C /* SellerInfo */ = { isa = PBXGroup; children = ( - 51A04FAB289335FF00900EFF /* ReviewerTableViewCell.swift */, - 51A04FAC289335FF00900EFF /* ReviewerTableViewCell.xib */, + 51934C8828AD91320078FD3C /* Cell */, + ); + path = SellerInfo; + sourceTree = ""; + }; + 51934C8828AD91320078FD3C /* Cell */ = { + isa = PBXGroup; + children = ( + 51934C8D28AD91B60078FD3C /* SellerInfoCell.swift */, + 51934C8E28AD91B60078FD3C /* SellerInfoCell.xib */, ); path = Cell; sourceTree = ""; @@ -264,6 +286,15 @@ path = Extension; sourceTree = ""; }; + 51A04FAA289334A800900EFF /* Cell */ = { + isa = PBXGroup; + children = ( + 51A04FAB289335FF00900EFF /* ReviewerTableViewCell.swift */, + 51A04FAC289335FF00900EFF /* ReviewerTableViewCell.xib */, + ); + path = Cell; + sourceTree = ""; + }; 856E36AD2889224B00735EF2 /* InfoSellerView */ = { isa = PBXGroup; children = ( @@ -285,6 +316,18 @@ path = Login; sourceTree = ""; }; + 9900241928B15F5A00D1D722 /* InfoSeller */ = { + isa = PBXGroup; + children = ( + 99E2C0A328C2A7BD0063874E /* CollectionView */, + 9900241A28B15F9E00D1D722 /* InfoSellerViewModelType.swift */, + 9900241B28B15F9E00D1D722 /* InfoSellerViewController.swift */, + 9900241C28B15F9E00D1D722 /* InfoSellerViewModel.swift */, + 9900241D28B15F9E00D1D722 /* InfoSellerViewController.xib */, + ); + path = InfoSeller; + sourceTree = ""; + }; 99091F1628895CFF000FA9C9 /* TitleQuantity */ = { isa = PBXGroup; children = ( @@ -294,15 +337,12 @@ path = TitleQuantity; sourceTree = ""; }; - 276889D92874162B0091ACEB /* Registration */ = { + 99E2C0A328C2A7BD0063874E /* CollectionView */ = { isa = PBXGroup; children = ( - 276889DA287417730091ACEB /* RegisterViewController.swift */, - 276889DB287417730091ACEB /* RegisterViewController.xib */, - 276889DE28741F750091ACEB /* RegisterViewModel.swift */, - 276889E0287420980091ACEB /* RegisterViewModelType.swift */, + 99E2C0A428C2A7DE0063874E /* InfoSellerViewController+CollectionView.swift */, ); - path = Registration; + path = CollectionView; sourceTree = ""; }; AA21B42928537E0400E4F217 = { @@ -357,6 +397,7 @@ AA57CB322881B22000FE62C8 /* ProductDetails */ = { isa = PBXGroup; children = ( + 9900241928B15F5A00D1D722 /* InfoSeller */, 51A04FAA289334A800900EFF /* Cell */, 2768DEEB28A42292004C6A5B /* SliderView */, 99091F1628895CFF000FA9C9 /* TitleQuantity */, @@ -375,8 +416,8 @@ AA57CB332881B50500FE62C8 /* Views */ = { isa = PBXGroup; children = ( + 51934C8728AD911A0078FD3C /* SellerInfo */, E2DADBAA288A53D9005E483A /* ButtonsView */, - AAC39E13289DD9CA0069822D /* ReviewerTableViewCell */, 856E36AD2889224B00735EF2 /* InfoSellerView */, 437B99A428907E6C00F59B06 /* FeaturedProduct */, 85F6B6D928919C87007730AB /* ProductDetailsNavigationBarBehavior.swift */, @@ -566,12 +607,13 @@ files = ( 437B99AD289082BC00F59B06 /* FeaturedProductCollectionViewCell.xib in Resources */, 99CC7308288E2FE400503D86 /* Colors.xcassets in Resources */, + 51934C9028AD91B60078FD3C /* SellerInfoCell.xib in Resources */, 2768DEF428A422F3004C6A5B /* SliderView.xib in Resources */, + 9900242128B15F9E00D1D722 /* InfoSellerViewController.xib in Resources */, 85B31DBD2870BEDE00B1C9E8 /* LoginViewController.xib in Resources */, E251D05028784A050065C852 /* VerificationViewController.xib in Resources */, AA21B44228537E0500E4F217 /* LaunchScreen.storyboard in Resources */, 276889DD287417730091ACEB /* RegisterViewController.xib in Resources */, - AAC39E17289DD9DE0069822D /* ReviewerTableViewCell.xib in Resources */, 2768DEEF28A422D2004C6A5B /* SliderCollectionViewCell.xib in Resources */, 432EF5D4288F078E0073FB1D /* Localizable.strings in Resources */, AAA2E17D285CBB9100EAD189 /* GoogleService-Info.plist in Resources */, @@ -678,6 +720,7 @@ files = ( E2CC398828A4A933008AE48B /* LoadingButton.swift in Sources */, 85B31DBB2870BEDE00B1C9E8 /* LoginViewController.swift in Sources */, + 9900242028B15F9E00D1D722 /* InfoSellerViewModel.swift in Sources */, 85B31DBA2870BEDE00B1C9E8 /* LoginViewModelType.swift in Sources */, 856E36AF2889229B00735EF2 /* InfoSellerView.swift in Sources */, E251D04E28784A050065C852 /* VerificationViewController.swift in Sources */, @@ -686,8 +729,11 @@ B82D6D4F2892FF290035D30C /* UIModels.swift in Sources */, 51A04FAD289335FF00900EFF /* ReviewerTableViewCell.swift in Sources */, AA21B43A28537E0400E4F217 /* ViewController.swift in Sources */, + 99E2C0A528C2A7DE0063874E /* InfoSellerViewController+CollectionView.swift in Sources */, + 51934C8F28AD91B60078FD3C /* SellerInfoCell.swift in Sources */, 276889E1287420980091ACEB /* RegisterViewModelType.swift in Sources */, 99091F1A28895DC1000FA9C9 /* ProductTitleQuantityView.swift in Sources */, + 9900241F28B15F9E00D1D722 /* InfoSellerViewController.swift in Sources */, 276889DF28741F750091ACEB /* RegisterViewModel.swift in Sources */, 276889DC287417730091ACEB /* RegisterViewController.swift in Sources */, 437B99AC289082BC00F59B06 /* FeaturedProductCollectionViewCell.swift in Sources */, @@ -708,6 +754,7 @@ AA98C0762867EAC0004C5967 /* Logging.swift in Sources */, 85B31DBC2870BEDE00B1C9E8 /* LoginViewModel.swift in Sources */, 437B99A828907F2A00F59B06 /* FeaturedProductView.swift in Sources */, + 9900241E28B15F9E00D1D722 /* InfoSellerViewModelType.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Dokan/Dokan/Assets.xcassets/vector.imageset/Contents.json b/Dokan/Dokan/Assets.xcassets/arrowRight.imageset/Contents.json similarity index 100% rename from Dokan/Dokan/Assets.xcassets/vector.imageset/Contents.json rename to Dokan/Dokan/Assets.xcassets/arrowRight.imageset/Contents.json index bb3f9442..b2bfa33a 100644 --- a/Dokan/Dokan/Assets.xcassets/vector.imageset/Contents.json +++ b/Dokan/Dokan/Assets.xcassets/arrowRight.imageset/Contents.json @@ -1,11 +1,11 @@ { "images" : [ { - "filename" : "vector.png", "idiom" : "universal", "scale" : "1x" }, { + "filename" : "vector.png", "idiom" : "universal", "scale" : "2x" }, diff --git a/Dokan/Dokan/Assets.xcassets/arrowRight.imageset/vector.png b/Dokan/Dokan/Assets.xcassets/arrowRight.imageset/vector.png new file mode 100644 index 00000000..d33e1d63 Binary files /dev/null and b/Dokan/Dokan/Assets.xcassets/arrowRight.imageset/vector.png differ diff --git a/Dokan/Dokan/Assets.xcassets/marker-time.imageset/Contents.json b/Dokan/Dokan/Assets.xcassets/marker-time.imageset/Contents.json new file mode 100644 index 00000000..2c4c528c --- /dev/null +++ b/Dokan/Dokan/Assets.xcassets/marker-time.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "marker-time.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Dokan/Dokan/Assets.xcassets/marker-time.imageset/marker-time.svg b/Dokan/Dokan/Assets.xcassets/marker-time.imageset/marker-time.svg new file mode 100644 index 00000000..4ed915ac --- /dev/null +++ b/Dokan/Dokan/Assets.xcassets/marker-time.imageset/marker-time.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Dokan/Dokan/Assets.xcassets/shopImage.imageset/Contents.json b/Dokan/Dokan/Assets.xcassets/shopImage.imageset/Contents.json new file mode 100644 index 00000000..783fefdd --- /dev/null +++ b/Dokan/Dokan/Assets.xcassets/shopImage.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "image 7.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Dokan/Dokan/Assets.xcassets/shopImage.imageset/image 7.svg b/Dokan/Dokan/Assets.xcassets/shopImage.imageset/image 7.svg new file mode 100644 index 00000000..3e3fa1cf --- /dev/null +++ b/Dokan/Dokan/Assets.xcassets/shopImage.imageset/image 7.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/Dokan/Dokan/Assets.xcassets/vector.imageset/vector.png b/Dokan/Dokan/Assets.xcassets/vector.imageset/vector.png deleted file mode 100644 index 4fe83860..00000000 Binary files a/Dokan/Dokan/Assets.xcassets/vector.imageset/vector.png and /dev/null differ diff --git a/Dokan/Dokan/SceneDelegate.swift b/Dokan/Dokan/SceneDelegate.swift index f57c3445..fcd7169d 100644 --- a/Dokan/Dokan/SceneDelegate.swift +++ b/Dokan/Dokan/SceneDelegate.swift @@ -18,7 +18,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). guard let _ = (scene as? UIWindowScene) else { return } - window?.rootViewController = ProductDetailsViewController(viewModel: ProductDetailsViewModel()) + window?.rootViewController = InfoSellerViewController(viewModel: InfoSellerViewModel()) window?.makeKeyAndVisible() } diff --git a/Dokan/Dokan/Scenes/ProductDetails/InfoSeller/CollectionView/InfoSellerViewController+CollectionView.swift b/Dokan/Dokan/Scenes/ProductDetails/InfoSeller/CollectionView/InfoSellerViewController+CollectionView.swift new file mode 100644 index 00000000..18ba63a9 --- /dev/null +++ b/Dokan/Dokan/Scenes/ProductDetails/InfoSeller/CollectionView/InfoSellerViewController+CollectionView.swift @@ -0,0 +1,43 @@ +// +// InfoSellerViewController+CollectionView.swift +// Dokan +// +// Created by ziad on 02/09/2022. +// + +import UIKit + +// MARK: Regsteration + +// +extension InfoSellerViewController { + + func registerSellerCollectionView() { + let nib = UINib(nibName: "SellerInfoCell", bundle: nil) + infoSellerCollectionView.register(nib, forCellWithReuseIdentifier: "SellerInfoCell") + infoSellerCollectionView.delegate = self + infoSellerCollectionView.dataSource = self + } +} + +// MARK: - CollectionView + +// +extension InfoSellerViewController: UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return viewModel.numberOfCells + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let infoCell = collectionView.dequeueReusableCell(withReuseIdentifier: "SellerInfoCell", for: indexPath) as! SellerInfoCell + let infoSellerCellData = viewModel.getInfoSellerProducts(at: indexPath) + infoCell.configureCell(model: infoSellerCellData) + return infoCell + } +} + +extension InfoSellerViewController: UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return CGSize(width: infoSellerCollectionView.frame.width / 2.5, height: 242) + } +} diff --git a/Dokan/Dokan/Scenes/ProductDetails/InfoSeller/InfoSellerViewController.swift b/Dokan/Dokan/Scenes/ProductDetails/InfoSeller/InfoSellerViewController.swift new file mode 100644 index 00000000..d5d2a306 --- /dev/null +++ b/Dokan/Dokan/Scenes/ProductDetails/InfoSeller/InfoSellerViewController.swift @@ -0,0 +1,97 @@ +// +// InfoSellerViewController.swift +// Dokan +// +// Created by ziad on 20/08/2022. +// + +import UIDokan +import UIKit + +class InfoSellerViewController: UIViewController { + + // MARK: Outlets + + @IBOutlet private weak var shopImage: UIImageView! + @IBOutlet private weak var sortingButton: UIButton! + @IBOutlet private weak var followButton: UIButton! + @IBOutlet private weak var name: UILabel! + @IBOutlet private weak var locationAndTime: UILabel! + @IBOutlet private weak var followersCount: UILabel! + @IBOutlet private weak var productsCount: UILabel! + @IBOutlet private weak var createdAt: UILabel! + @IBOutlet weak var viewOfCollection: UIView! + @IBOutlet weak var infoSellerCollectionView: UICollectionView! + + // MARK: Properties + + var viewModel: InfoSellerViewModelType + + // MARK: Init + + init(viewModel: InfoSellerViewModelType) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + style() + registerSellerCollectionView() + callInfoSellerApi() + viewOfCollection.startSkeletonView() + } +} + +// MARK: - Actions + +// +extension InfoSellerViewController {} + +// MARK: - Configurations + +extension InfoSellerViewController { + + private func style() { + shopImage.layer.cornerRadius = 25 + sortingButton.applyButtonStyle(.border) + followButton.applyButtonStyle(.filledBlue) + } + + private func presentApiData() { + let infoSellerData = viewModel.getInfoSellerData() + + name.text = infoSellerData?.sellerName + locationAndTime.text = (infoSellerData?.location ?? "") + " " + (infoSellerData?.workTime ?? "") + followersCount.text = String(infoSellerData?.followers ?? 0) + productsCount.text = String(infoSellerData?.productsCount ?? 0) + createdAt.text = infoSellerData?.createdAt + } +} + +// MARK: - Private Handlers + +// +private extension InfoSellerViewController { + + private func callInfoSellerApi() { + + viewModel.loadSellerInfo() + reloadInfoSellerCollectionViewData() + } + + private func reloadInfoSellerCollectionViewData() { + viewModel.onReloadData = { + self.infoSellerCollectionView.reloadData() + self.presentApiData() + self.viewOfCollection.stopSkeletonView() + } + } +} diff --git a/Dokan/Dokan/Scenes/ProductDetails/InfoSeller/InfoSellerViewController.xib b/Dokan/Dokan/Scenes/ProductDetails/InfoSeller/InfoSellerViewController.xib new file mode 100644 index 00000000..2e754544 --- /dev/null +++ b/Dokan/Dokan/Scenes/ProductDetails/InfoSeller/InfoSellerViewController.xib @@ -0,0 +1,316 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Dokan/Dokan/Scenes/ProductDetails/InfoSeller/InfoSellerViewModel.swift b/Dokan/Dokan/Scenes/ProductDetails/InfoSeller/InfoSellerViewModel.swift new file mode 100644 index 00000000..d199e9ab --- /dev/null +++ b/Dokan/Dokan/Scenes/ProductDetails/InfoSeller/InfoSellerViewModel.swift @@ -0,0 +1,83 @@ +// +// InfoSellerViewModel.swift +// Dokan +// +// Created by ziad on 20/08/2022. +// + +import Domain +import Foundation + +// MARK: InfoSellerViewModel + +// +class InfoSellerViewModel { + + private let repository: SellerInfoRepository + private var sellerInfoData: Domain.SellerInfo? + private var cellViewModel: [SellerInfoCell.ViewModel] = [] + + var onReloadData: () -> Void = {} + var showAlert: () -> Void = {} + var onShowAlert: (String) -> Void = { _ in } + var numberOfCells: Int { + return cellViewModel.count + } + + init(repository: SellerInfoRepository = ServiceLocator.provider.makeSellerInfoRepository()) { + self.repository = repository + } +} + +// MARK: InfoSellerViewModel + +extension InfoSellerViewModel: InfoSellerViewModelInput { + + func loadSellerInfo() { + repository.loadSellerInfo { [weak self] result in + guard let self = self else { return } + switch result { + + case let .failure(error): + self.onShowAlert(error.localizedDescription) + case let .success(list): + self.appendSellerInfoDataToCell(sellerInfo: list) + self.onReloadData() + } + } + } +} + +// MARK: InfoSellerViewModelOutput + +// +extension InfoSellerViewModel: InfoSellerViewModelOutput { + + func getInfoSellerData() -> Domain.SellerInfo? { + return sellerInfoData + } + + func getInfoSellerProducts(at indexPath: IndexPath) -> SellerInfoCell.ViewModel { + return cellViewModel[indexPath.row] + } +} + +// MARK: Private Handlers + +// +private extension InfoSellerViewModel { + + private func createCellViewModel(sellerInfoProduct: Domain.Products) -> SellerInfoCell.ViewModel { + return SellerInfoCell.ViewModel(title: sellerInfoProduct.title, + image: sellerInfoProduct.image, + price: sellerInfoProduct.price, + review: sellerInfoProduct.review, + totalReviews: sellerInfoProduct.totalReviews) + } + + private func appendSellerInfoDataToCell(sellerInfo: Domain.SellerInfo) { + sellerInfoData = sellerInfo + let sellerInfoProductList = sellerInfoData?.products ?? [] + cellViewModel = sellerInfoProductList.map { createCellViewModel(sellerInfoProduct: $0) } + } +} diff --git a/Dokan/Dokan/Scenes/ProductDetails/InfoSeller/InfoSellerViewModelType.swift b/Dokan/Dokan/Scenes/ProductDetails/InfoSeller/InfoSellerViewModelType.swift new file mode 100644 index 00000000..2b9ea343 --- /dev/null +++ b/Dokan/Dokan/Scenes/ProductDetails/InfoSeller/InfoSellerViewModelType.swift @@ -0,0 +1,29 @@ +// +// InfoSellerViewModelType.swift +// Dokan +// +// Created by ziad on 20/08/2022. +// + +import Domain +import Foundation + +/// InfoSeller Input & Output +/// +typealias InfoSellerViewModelType = InfoSellerViewModelInput & InfoSellerViewModelOutput + +/// InfoSeller ViewModel Input +/// +protocol InfoSellerViewModelInput { + func loadSellerInfo() +} + +/// InfoSeller ViewModel Output +/// +protocol InfoSellerViewModelOutput { + var onReloadData: () -> Void { get set } + var onShowAlert: (String) -> Void { get set } + var numberOfCells: Int { get } + func getInfoSellerProducts(at indexPath: IndexPath) -> SellerInfoCell.ViewModel + func getInfoSellerData() -> Domain.SellerInfo? +} diff --git a/Dokan/Dokan/Scenes/ProductDetails/ProductDetailsViewController.swift b/Dokan/Dokan/Scenes/ProductDetails/ProductDetailsViewController.swift index b37c324b..7e262a65 100644 --- a/Dokan/Dokan/Scenes/ProductDetails/ProductDetailsViewController.swift +++ b/Dokan/Dokan/Scenes/ProductDetails/ProductDetailsViewController.swift @@ -11,17 +11,17 @@ import UIKit class ProductDetailsViewController: UIViewController { // MARK: Outlets - + @IBOutlet private weak var buttonsView: ButtonsView! @IBOutlet private weak var InfoSellerView: InfoSellerView! @IBOutlet private weak var descriptionTextView: ReadMoreTextView! // MARK: Properties - + private let viewModel: ProductDetailsViewModelType private var addedToCart = false private var addedToFavorite = false - + private var navigationBarBehavior: ProductDetailsNavigationBarBehavior? // MARK: Init @@ -35,7 +35,7 @@ class ProductDetailsViewController: UIViewController { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + // MARK: Lifecycle override func viewDidLoad() { @@ -52,14 +52,15 @@ class ProductDetailsViewController: UIViewController { extension ProductDetailsViewController {} // MARK: - Configurations + // private extension ProductDetailsViewController { - + func configureButtonsView() { buttonsView.addToCartButton.addTarget(self, action: #selector(addToCartButtonTapped), for: .touchUpInside) buttonsView.addToFavoriteButton.addTarget(self, action: #selector(addToFavoriteButtonTapped), for: .touchUpInside) } - + func configureDescriptionTextView() { descriptionTextView.shouldTrim = true descriptionTextView.maximumNumberOfLines = 3 @@ -79,6 +80,7 @@ private extension ProductDetailsViewController { } // MARK: - Actions + // private extension ProductDetailsViewController { @objc func addToCartButtonTapped() { @@ -86,9 +88,9 @@ private extension ProductDetailsViewController { #warning("Implement tapping the add to cart button") } } - + @objc func addToFavoriteButtonTapped() { - + buttonsView.addToFavoriteButton.buttonTapped { #warning("Implement tapping the add to favorite button") } diff --git a/Dokan/Dokan/Scenes/ProductDetails/Views/SellerInfo/Cell/SellerInfoCell.swift b/Dokan/Dokan/Scenes/ProductDetails/Views/SellerInfo/Cell/SellerInfoCell.swift new file mode 100644 index 00000000..aa797c26 --- /dev/null +++ b/Dokan/Dokan/Scenes/ProductDetails/Views/SellerInfo/Cell/SellerInfoCell.swift @@ -0,0 +1,46 @@ +// +// SellerInfoCell.swift +// Dokan +// +// Created by raniazeid on 17/08/2022. +// + +import UIKit + +class SellerInfoCell: UICollectionViewCell { + // MARK: - Outlets.. + + @IBOutlet private weak var sellerProductImage: UIImageView! + @IBOutlet private weak var productName: UILabel! + @IBOutlet private weak var productPrice: UILabel! + @IBOutlet private weak var productReviewRate: UILabel! + @IBOutlet private weak var productReviewerCount: UILabel! + + // MARK: - Life cycle... + + override func awakeFromNib() { + super.awakeFromNib() + } + + // MARK: - func to configure cell with data + + func configureCell(model: ViewModel) { + sellerProductImage.setImage(with: model.image, placeholderImage: UIImage()) + productName.text = model.title + productPrice.text = String(model.price) + productReviewRate.text = String(model.review) + productReviewerCount.text = String(model.totalReviews) + } +} + +// MARK: - Implement cell view model + +extension SellerInfoCell { + struct ViewModel { + var title: String + var image: String + var price: Int + var review: Double + var totalReviews: Int + } +} diff --git a/Dokan/Dokan/Scenes/ProductDetails/Views/SellerInfo/Cell/SellerInfoCell.xib b/Dokan/Dokan/Scenes/ProductDetails/Views/SellerInfo/Cell/SellerInfoCell.xib new file mode 100644 index 00000000..efb28c61 --- /dev/null +++ b/Dokan/Dokan/Scenes/ProductDetails/Views/SellerInfo/Cell/SellerInfoCell.xib @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Domain/Sources/Domain/Model/SellerInfo.swift b/Domain/Sources/Domain/Model/SellerInfo.swift new file mode 100644 index 00000000..1704aa0d --- /dev/null +++ b/Domain/Sources/Domain/Model/SellerInfo.swift @@ -0,0 +1,49 @@ +// +// File.swift +// +// +// Created by raniazeid on 30/08/2022. +// + +import Foundation + +public struct SellerInfo { + public let sellerName: String + public let reviews: Double + public let location, workTime: String + public let followers, productsCount: Int + public let createdAt: String + public let products: [Products] + + public init(sellerName: String, reviews: Double, + location: String, workTime: String, + followers: Int, productsCount: Int, + createdAt: String, products: [Products]) { + self.sellerName = sellerName + self.reviews = reviews + self.location = location + self.workTime = workTime + self.followers = followers + self.productsCount = productsCount + self.createdAt = createdAt + self.products = products + } +} + +// MARK: - Seller Product + +public struct Products { + public let title: String + public let image: String + public let price: Int + public let review: Double + public let totalReviews: Int + + public init(title: String, image: String, price: Int, review: Double, totalRevieews: Int) { + self.title = title + self.image = image + self.price = price + self.review = review + totalReviews = totalRevieews + } +} diff --git a/Domain/Sources/Domain/Provider/RepositoryProvider.swift b/Domain/Sources/Domain/Provider/RepositoryProvider.swift index c547dfef..c7a96beb 100644 --- a/Domain/Sources/Domain/Provider/RepositoryProvider.swift +++ b/Domain/Sources/Domain/Provider/RepositoryProvider.swift @@ -14,4 +14,7 @@ public protocol RepositoryProvider { /// Creates new instance of `ProductRepository` /// func makeProductRepository() -> ProductRepository + + /// Create new instance Of 'Seller Info Repository' + func makeSellerInfoRepository() -> SellerInfoRepository } diff --git a/Domain/Sources/Domain/Repository/SellerInfoRepository.swift b/Domain/Sources/Domain/Repository/SellerInfoRepository.swift new file mode 100644 index 00000000..176a2cad --- /dev/null +++ b/Domain/Sources/Domain/Repository/SellerInfoRepository.swift @@ -0,0 +1,16 @@ +// +// File.swift +// +// +// Created by raniazeid on 30/08/2022. +// + +import Foundation +/// All Seller Info related use cases +/// +public protocol SellerInfoRepository { + + /// Load all seller info at a page. Completes with products list or error if any. + /// + func loadSellerInfo(completion: @escaping (Result) -> Void) +} diff --git a/Networking/Sources/Networking/Models/SellerInfo.swift b/Networking/Sources/Networking/Models/SellerInfo.swift new file mode 100644 index 00000000..fe5b80a0 --- /dev/null +++ b/Networking/Sources/Networking/Models/SellerInfo.swift @@ -0,0 +1,29 @@ +// +// File.swift +// +// +// Created by raniazeid on 30/08/2022. +// + +import Foundation + +// MARK: - Seller + +public struct SellerInfo: Decodable { + public let sellerName: String + public let reviews: Double + public let location, workTime: String + public let followers, productsCount: Int + public let createdAt: String + public let products: [Products] +} + +// MARK: - Seller Product + +public struct Products: Decodable { + public let title: String + public let image: String + public let price: Int + public let review: Double + public let totalReviews: Int +} diff --git a/Networking/Sources/Networking/Remote/SellerInfoRemote.swift b/Networking/Sources/Networking/Remote/SellerInfoRemote.swift new file mode 100644 index 00000000..b6db478f --- /dev/null +++ b/Networking/Sources/Networking/Remote/SellerInfoRemote.swift @@ -0,0 +1,36 @@ +// +// File.swift +// +// +// Created by raniazeid on 30/08/2022. +// + +import Domain +import Foundation +/// Protocol for `AllCategoryRemote` mainly used for mocking. + +/// + +public protocol SellerInfoProtocol { + + func getAllSellerInfo(completion: @escaping (Result) -> Void) +} + +/// Products: Remote Endpoints + +/// + +public class SellerInfoRemote: Remote, SellerInfoProtocol { + + /// Loads all available category + + /// + + public func getAllSellerInfo(completion: @escaping (Result) -> Void) { + + let path = "v3/fd437aa0-d818-447b-809f-09ff5413bd97" + let request = MockApiRequest(method: .get, path: path) + + enqueue(request, completion: completion) + } +} diff --git a/Networking/Sources/Networking/Requests/MockyApiRequest.swift b/Networking/Sources/Networking/Requests/MockyApiRequest.swift new file mode 100644 index 00000000..a214bd8e --- /dev/null +++ b/Networking/Sources/Networking/Requests/MockyApiRequest.swift @@ -0,0 +1,61 @@ +// +// File.swift +// +// +// Created by raniazeid on 30/08/2022. +// + +import Foundation + +import Alamofire + +/// Represents Fakestore.com Endpoint +/// +struct MockApiRequest: URLRequestConvertible { + + /// HTTP Request Method + /// + let method: HTTPMethod + + /// URL Path + /// + let path: String + + /// Parameters + /// + let parameters: [String: Any] + + /// Designated Initializer. + /// + /// - Parameters: + /// - method: HTTP Method we should use. + /// - path: RPC that should be called. + /// - parameters: Collection of Key/Value parameters, to be forwarded to the Jetpack Connected site. + /// + init(method: HTTPMethod, path: String, parameters: [String: Any]? = nil) { + self.method = method + self.path = path + self.parameters = parameters ?? [:] + } + + /// Returns a URLRequest instance reprensenting the current FakeStore Request. + /// + func asURLRequest() throws -> URLRequest { + let url = URL(string: Settings.mocyApiBaseURL + path)! + let request = try URLRequest(url: url, method: method, headers: nil) + + return try encoder.encode(request, with: parameters) + } +} + +// MARK: - MockApiRequest Request: Internal + +// +private extension MockApiRequest { + + /// Returns the Parameters Encoder + /// + var encoder: ParameterEncoding { + return method == .get ? URLEncoding.queryString : URLEncoding.httpBody + } +} diff --git a/Networking/Sources/Networking/Settings.swift b/Networking/Sources/Networking/Settings.swift index 7cd40603..87d002ab 100644 --- a/Networking/Sources/Networking/Settings.swift +++ b/Networking/Sources/Networking/Settings.swift @@ -14,4 +14,5 @@ enum Settings { /// Base API URL /// static let storeApiBaseURL = "https://fakestoreapi.com/" + static let mocyApiBaseURL = "https://run.mocky.io/" } diff --git a/UIDokan/Sources/UIDokan/Generated/UIColors.Generated.swift b/UIDokan/Sources/UIDokan/Generated/UIColors.Generated.swift index 4033f0f4..f430bdf7 100644 --- a/UIDokan/Sources/UIDokan/Generated/UIColors.Generated.swift +++ b/UIDokan/Sources/UIDokan/Generated/UIColors.Generated.swift @@ -1,7 +1,7 @@ import UIKit.UIColor // this is automatic generated file please don't edit it 🔫 + // MARK: - Colors -extension UIColor { - } +extension UIColor {} diff --git a/UIDokan/Sources/UIDokan/Generated/UIImage.Generated.swift b/UIDokan/Sources/UIDokan/Generated/UIImage.Generated.swift index 30af1896..bdb91ad2 100644 --- a/UIDokan/Sources/UIDokan/Generated/UIImage.Generated.swift +++ b/UIDokan/Sources/UIDokan/Generated/UIImage.Generated.swift @@ -10,6 +10,10 @@ extension UIImage { UIImage(named: "AccentColor")! } + static var arrowRight: UIImage { + UIImage(named: "arrowRight")! + } + static var bellBadge: UIImage { UIImage(named: "bell-badge")! } @@ -18,6 +22,10 @@ extension UIImage { UIImage(named: "bell")! } + static var markerTime: UIImage { + UIImage(named: "marker-time")! + } + static var redo: UIImage { UIImage(named: "redo")! } @@ -26,6 +34,10 @@ extension UIImage { UIImage(named: "search")! } + static var shopImage: UIImage { + UIImage(named: "shopImage")! + } + static var shoppingCartBadge: UIImage { UIImage(named: "shopping-cart-badge")! } @@ -38,10 +50,6 @@ extension UIImage { UIImage(named: "star")! } - static var vector: UIImage { - UIImage(named: "vector")! - } - static var verified: UIImage { UIImage(named: "verified")! }