diff --git a/Payan.xcodeproj/project.pbxproj b/Payan.xcodeproj/project.pbxproj index 9216804..82a31c2 100644 --- a/Payan.xcodeproj/project.pbxproj +++ b/Payan.xcodeproj/project.pbxproj @@ -26,6 +26,15 @@ 2F8D512B2800C5AD00ADCC64 /* PYFeedBusinessLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F8D512A2800C5AD00ADCC64 /* PYFeedBusinessLogic.swift */; }; 2F8D512D2800C5E600ADCC64 /* PYFeedDataAccessLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F8D512C2800C5E600ADCC64 /* PYFeedDataAccessLogic.swift */; }; 2F8D51342800C7B900ADCC64 /* PYFeedViewLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F8D51332800C7B900ADCC64 /* PYFeedViewLogic.swift */; }; + 2F908E332842D104003BD450 /* PYPlaceModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F908E322842D104003BD450 /* PYPlaceModule.swift */; }; + 2F908E372842D1B3003BD450 /* PYPlacePageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F908E362842D1B3003BD450 /* PYPlacePageView.swift */; }; + 2F908E3A2842D1DD003BD450 /* PYPlaceViewLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F908E392842D1DD003BD450 /* PYPlaceViewLogic.swift */; }; + 2F908E3D2842D32F003BD450 /* PYPlaceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F908E3C2842D32F003BD450 /* PYPlaceViewModel.swift */; }; + 2F908E402842D33F003BD450 /* PYPlaceInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F908E3F2842D33F003BD450 /* PYPlaceInteractor.swift */; }; + 2F908E422842D34B003BD450 /* PYPlaceBusinessLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F908E412842D34B003BD450 /* PYPlaceBusinessLogic.swift */; }; + 2F908E452843032E003BD450 /* PYPlace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F908E442843032E003BD450 /* PYPlace.swift */; }; + 2F908E4728430396003BD450 /* PYPlaceDataAccessLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F908E4628430396003BD450 /* PYPlaceDataAccessLogic.swift */; }; + 2F908E4A28430458003BD450 /* PYPlaceNetworkWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F908E4928430458003BD450 /* PYPlaceNetworkWorker.swift */; }; 2F9AFADE27FA393C006DC8D6 /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = 2F9AFADD27FA393C006DC8D6 /* FirebaseAnalytics */; }; 2FA03DF7283C51940094976A /* Purace in Frameworks */ = {isa = PBXBuildFile; productRef = 2FA03DF6283C51940094976A /* Purace */; }; 2FA3BB012815E626005BF493 /* PYNetworkRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA3BB002815E626005BF493 /* PYNetworkRequest.swift */; }; @@ -79,6 +88,15 @@ 2F8D512A2800C5AD00ADCC64 /* PYFeedBusinessLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PYFeedBusinessLogic.swift; sourceTree = ""; }; 2F8D512C2800C5E600ADCC64 /* PYFeedDataAccessLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PYFeedDataAccessLogic.swift; sourceTree = ""; }; 2F8D51332800C7B900ADCC64 /* PYFeedViewLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PYFeedViewLogic.swift; sourceTree = ""; }; + 2F908E322842D104003BD450 /* PYPlaceModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PYPlaceModule.swift; sourceTree = ""; }; + 2F908E362842D1B3003BD450 /* PYPlacePageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PYPlacePageView.swift; sourceTree = ""; }; + 2F908E392842D1DD003BD450 /* PYPlaceViewLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PYPlaceViewLogic.swift; sourceTree = ""; }; + 2F908E3C2842D32F003BD450 /* PYPlaceViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PYPlaceViewModel.swift; sourceTree = ""; }; + 2F908E3F2842D33F003BD450 /* PYPlaceInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PYPlaceInteractor.swift; sourceTree = ""; }; + 2F908E412842D34B003BD450 /* PYPlaceBusinessLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PYPlaceBusinessLogic.swift; sourceTree = ""; }; + 2F908E442843032E003BD450 /* PYPlace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PYPlace.swift; sourceTree = ""; }; + 2F908E4628430396003BD450 /* PYPlaceDataAccessLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PYPlaceDataAccessLogic.swift; sourceTree = ""; }; + 2F908E4928430458003BD450 /* PYPlaceNetworkWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PYPlaceNetworkWorker.swift; sourceTree = ""; }; 2F9AFAE027FA3AFF006DC8D6 /* Emogger */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Emogger; path = Modules/Emogger; sourceTree = ""; }; 2FA3BB002815E626005BF493 /* PYNetworkRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PYNetworkRequest.swift; sourceTree = ""; }; 2FA3BB022815E65A005BF493 /* PYNetworkMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PYNetworkMethod.swift; sourceTree = ""; }; @@ -252,6 +270,78 @@ path = Protocols; sourceTree = ""; }; + 2F908E312842D095003BD450 /* Place */ = { + isa = PBXGroup; + children = ( + 2F908E4828430447003BD450 /* Workers */, + 2F908E432842D368003BD450 /* Entities */, + 2F908E3E2842D333003BD450 /* Interactor */, + 2F908E382842D1D1003BD450 /* Protocols */, + 2F908E342842D1A0003BD450 /* UI */, + 2F908E322842D104003BD450 /* PYPlaceModule.swift */, + ); + path = Place; + sourceTree = ""; + }; + 2F908E342842D1A0003BD450 /* UI */ = { + isa = PBXGroup; + children = ( + 2F908E3B2842D325003BD450 /* View Model */, + 2F908E352842D1A4003BD450 /* Page */, + ); + path = UI; + sourceTree = ""; + }; + 2F908E352842D1A4003BD450 /* Page */ = { + isa = PBXGroup; + children = ( + 2F908E362842D1B3003BD450 /* PYPlacePageView.swift */, + ); + path = Page; + sourceTree = ""; + }; + 2F908E382842D1D1003BD450 /* Protocols */ = { + isa = PBXGroup; + children = ( + 2F908E392842D1DD003BD450 /* PYPlaceViewLogic.swift */, + 2F908E412842D34B003BD450 /* PYPlaceBusinessLogic.swift */, + 2F908E4628430396003BD450 /* PYPlaceDataAccessLogic.swift */, + ); + path = Protocols; + sourceTree = ""; + }; + 2F908E3B2842D325003BD450 /* View Model */ = { + isa = PBXGroup; + children = ( + 2F908E3C2842D32F003BD450 /* PYPlaceViewModel.swift */, + ); + path = "View Model"; + sourceTree = ""; + }; + 2F908E3E2842D333003BD450 /* Interactor */ = { + isa = PBXGroup; + children = ( + 2F908E3F2842D33F003BD450 /* PYPlaceInteractor.swift */, + ); + path = Interactor; + sourceTree = ""; + }; + 2F908E432842D368003BD450 /* Entities */ = { + isa = PBXGroup; + children = ( + 2F908E442843032E003BD450 /* PYPlace.swift */, + ); + path = Entities; + sourceTree = ""; + }; + 2F908E4828430447003BD450 /* Workers */ = { + isa = PBXGroup; + children = ( + 2F908E4928430458003BD450 /* PYPlaceNetworkWorker.swift */, + ); + path = Workers; + sourceTree = ""; + }; 2F9AFADF27FA3AFF006DC8D6 /* Packages */ = { isa = PBXGroup; children = ( @@ -423,6 +513,7 @@ 3DF708E426F2A64900FD24CF /* Domain Modules */ = { isa = PBXGroup; children = ( + 2F908E312842D095003BD450 /* Place */, 2F8C80FC28010CBA00A8E7FD /* Collection */, 3D3A3848270A53C700E2EF94 /* Feed */, ); @@ -535,8 +626,12 @@ 2F8789F52842846100C3A40F /* UIViewController+popGesture.swift in Sources */, 2F19514728394F3B007B0E73 /* PYPlaceCategory.swift in Sources */, 6D46485D281D9B21001FACC0 /* PYCollection.swift in Sources */, + 2F908E3A2842D1DD003BD450 /* PYPlaceViewLogic.swift in Sources */, + 2F908E402842D33F003BD450 /* PYPlaceInteractor.swift in Sources */, 3DB9D546277F573F00E4D45A /* FirebaseAppDelegate.swift in Sources */, + 2F908E372842D1B3003BD450 /* PYPlacePageView.swift in Sources */, 2F06927D2801F6C50068AA79 /* PYCollectionModule.swift in Sources */, + 2F908E452843032E003BD450 /* PYPlace.swift in Sources */, 2FA3BB092815F24D005BF493 /* PYServerResponse.swift in Sources */, 3D496F87277E353C009BFF74 /* AnalyticsEngine.swift in Sources */, 2F832F1F283D84A000702E71 /* PYHeroPreview.swift in Sources */, @@ -545,6 +640,7 @@ 2F8CADF128024AF700F166E0 /* PYCollectionNetworkWorker.swift in Sources */, 2F8D51342800C7B900ADCC64 /* PYFeedViewLogic.swift in Sources */, 2F0692752801F5770068AA79 /* PYCollectionBusinessLogic.swift in Sources */, + 2F908E4A28430458003BD450 /* PYPlaceNetworkWorker.swift in Sources */, 6DB6E4F52818A96E0002A77C /* PYModule.swift in Sources */, 2F8D512D2800C5E600ADCC64 /* PYFeedDataAccessLogic.swift in Sources */, 2F63C88227FCD62500857056 /* PYFeedInteractor.swift in Sources */, @@ -552,6 +648,8 @@ 2F19514928394F9A007B0E73 /* PYFeedPage.swift in Sources */, 2FC270EB2837F0AB00770B46 /* PYFeedPageView.swift in Sources */, 2FA3BB052815E707005BF493 /* PYNetworkError.swift in Sources */, + 2F908E332842D104003BD450 /* PYPlaceModule.swift in Sources */, + 2F908E4728430396003BD450 /* PYPlaceDataAccessLogic.swift in Sources */, 3D496F89277E357C009BFF74 /* AnalyticsManager.swift in Sources */, 3DF708BD26F2A4D200FD24CF /* AppDelegate.swift in Sources */, 2F8CADEF28024A4900F166E0 /* PYCollectionDataAccessLogic.swift in Sources */, @@ -561,11 +659,13 @@ 3D496F84277E34F3009BFF74 /* AnalyticsEvent.swift in Sources */, 2F19514C283954FB007B0E73 /* PYFeedViewModel.swift in Sources */, 3D0ECF792782496100847E0D /* Environment.swift in Sources */, + 2F908E422842D34B003BD450 /* PYPlaceBusinessLogic.swift in Sources */, 2FD5786E2840512B007A6EF5 /* PYCollectionPageView.swift in Sources */, 3DF708BF26F2A4D200FD24CF /* SceneDelegate.swift in Sources */, 2F78195128170E820096E342 /* PYNetworkingAppDelegate.swift in Sources */, 2F06927B2801F6360068AA79 /* PYCollectionInteractor.swift in Sources */, 2F2B9FF328408B9200565D3E /* PYCollectionViewModel.swift in Sources */, + 2F908E3D2842D32F003BD450 /* PYPlaceViewModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Payan.xcodeproj/project.xcworkspace/xcuserdata/juandahurt.xcuserdatad/UserInterfaceState.xcuserstate b/Payan.xcodeproj/project.xcworkspace/xcuserdata/juandahurt.xcuserdatad/UserInterfaceState.xcuserstate index 32f0877..9d3fd6e 100644 Binary files a/Payan.xcodeproj/project.xcworkspace/xcuserdata/juandahurt.xcuserdatad/UserInterfaceState.xcuserstate and b/Payan.xcodeproj/project.xcworkspace/xcuserdata/juandahurt.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Payan/App/Domain Modules/Collection/UI/Page/PYCollectionPageView.swift b/Payan/App/Domain Modules/Collection/UI/Page/PYCollectionPageView.swift index b10a5a6..719d555 100644 --- a/Payan/App/Domain Modules/Collection/UI/Page/PYCollectionPageView.swift +++ b/Payan/App/Domain Modules/Collection/UI/Page/PYCollectionPageView.swift @@ -73,11 +73,16 @@ struct PYCollectionPageView: View, PYCollectionViewLogic { .multilineTextAlignment(.center) }.padding() } + .contentShape(Rectangle()) .skeleton(with: viewModel.isLoading) .shape(type: .rectangle) .animation(type: .linear()) .appearance(type: .solid()) .frame(width: UIScreen.main.bounds.width / CGFloat(columns), height: correctHeight) + .onTapGesture { + guard let url = URL(string: element.deepLink) else { return } + PYRoutingManager.shared.open(url: url) + } } } .transition(.slide) diff --git a/Payan/App/Domain Modules/Place/Entities/PYPlace.swift b/Payan/App/Domain Modules/Place/Entities/PYPlace.swift new file mode 100644 index 0000000..2fbed77 --- /dev/null +++ b/Payan/App/Domain Modules/Place/Entities/PYPlace.swift @@ -0,0 +1,19 @@ +// +// PYPlace.swift +// Payan +// +// Created by Juan Hurtado on 28/05/22. +// + +import Foundation + +struct PYPlace: Decodable { + var title: String + var subtitle: String + var image: String + var description: String? +} + +extension PYPlace { + static let empty = PYPlace(title: "", subtitle: "", image: "") +} diff --git a/Payan/App/Domain Modules/Place/Interactor/PYPlaceInteractor.swift b/Payan/App/Domain Modules/Place/Interactor/PYPlaceInteractor.swift new file mode 100644 index 0000000..f1044b2 --- /dev/null +++ b/Payan/App/Domain Modules/Place/Interactor/PYPlaceInteractor.swift @@ -0,0 +1,28 @@ +// +// PYPlaceInteractor.swift +// Payan +// +// Created by Juan Hurtado on 28/05/22. +// + +import Foundation + +class PYPlaceInteractor: PYPlaceBusinessLogic { + var worker: PYPlaceDataAccessLogic + + init(worker: PYPlaceDataAccessLogic = PYPlaceNetworkWorker()) { + self.worker = worker + } + + func getPlace(identifiedBy id: String, completion: @escaping (Result) -> Void) { + worker.fecthPlace(id: id) { res in + DispatchQueue.main.async { + switch res { + case .success(let place): + completion(.success(place)) + case .failure(_): break + } + } + } + } +} diff --git a/Payan/App/Domain Modules/Place/PYPlaceModule.swift b/Payan/App/Domain Modules/Place/PYPlaceModule.swift new file mode 100644 index 0000000..4e276a0 --- /dev/null +++ b/Payan/App/Domain Modules/Place/PYPlaceModule.swift @@ -0,0 +1,18 @@ +// +// PYPlaceModule.swift +// Payan +// +// Created by Juan Hurtado on 28/05/22. +// + +import Foundation +import SwiftUI + +final class PYPlaceModule: PYModule { + var host: String = "place" + + func getViewController(params: [URLQueryItem]) -> UIViewController? { + guard !params.isEmpty, params[0].name == "id", let placeId = params[0].value else { return nil } + return UIHostingController(rootView: PYPlacePageView(placeId: placeId)) + } +} diff --git a/Payan/App/Domain Modules/Place/Protocols/PYPlaceBusinessLogic.swift b/Payan/App/Domain Modules/Place/Protocols/PYPlaceBusinessLogic.swift new file mode 100644 index 0000000..b353f22 --- /dev/null +++ b/Payan/App/Domain Modules/Place/Protocols/PYPlaceBusinessLogic.swift @@ -0,0 +1,14 @@ +// +// PYPlaceBusinessLogic.swift +// Payan +// +// Created by Juan Hurtado on 28/05/22. +// + +import Foundation + +protocol PYPlaceBusinessLogic { + var worker: PYPlaceDataAccessLogic { get } + + func getPlace(identifiedBy id: String, completion: @escaping (Result) -> Void) +} diff --git a/Payan/App/Domain Modules/Place/Protocols/PYPlaceDataAccessLogic.swift b/Payan/App/Domain Modules/Place/Protocols/PYPlaceDataAccessLogic.swift new file mode 100644 index 0000000..2363182 --- /dev/null +++ b/Payan/App/Domain Modules/Place/Protocols/PYPlaceDataAccessLogic.swift @@ -0,0 +1,12 @@ +// +// PYPlaceDataAccessLogic.swift +// Payan +// +// Created by Juan Hurtado on 28/05/22. +// + +import Foundation + +protocol PYPlaceDataAccessLogic { + func fecthPlace(id: String, completion: @escaping (Result) -> Void) +} diff --git a/Payan/App/Domain Modules/Place/Protocols/PYPlaceViewLogic.swift b/Payan/App/Domain Modules/Place/Protocols/PYPlaceViewLogic.swift new file mode 100644 index 0000000..d6df6e8 --- /dev/null +++ b/Payan/App/Domain Modules/Place/Protocols/PYPlaceViewLogic.swift @@ -0,0 +1,12 @@ +// +// PYPlaceViewLogic.swift +// Payan +// +// Created by Juan Hurtado on 28/05/22. +// + +import Foundation + +protocol PYPlaceViewLogic { + var placeId: String { get } +} diff --git a/Payan/App/Domain Modules/Place/UI/Page/PYPlacePageView.swift b/Payan/App/Domain Modules/Place/UI/Page/PYPlacePageView.swift new file mode 100644 index 0000000..e8e3d0c --- /dev/null +++ b/Payan/App/Domain Modules/Place/UI/Page/PYPlacePageView.swift @@ -0,0 +1,78 @@ +// +// PYPlacePageView.swift +// Payan +// +// Created by Juan Hurtado on 28/05/22. +// + +import Foundation +import SwiftUI +import Purace + +struct PYPlacePageView: View, PYPlaceViewLogic { + var placeId: String + + @StateObject var viewModel = PYPlaceViewModel() + + init(placeId: String) { + self.placeId = placeId + } + + var navBar: some View { + HStack(alignment: .center) { + Image(systemName: "chevron.left") + .foregroundColor(.white) + .scaleEffect(1.2) + .onTapGesture { + PYRoutingManager.shared.pop() + } + Spacer() + }.padding() + .frame(height: 50) + } + + var image: some View { + let url = URL(string: viewModel.place.image) + return ZStack { + PuraceImageView(url: url) + .aspectRatio(contentMode: .fill) + .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * 0.35) + .clipped() + LinearGradient(colors: [.black.opacity(0.55), .clear], startPoint: .top, endPoint: .center) + }.frame(height: UIScreen.main.bounds.height * 0.35) + } + + var title: some View { + VStack(spacing: 10) { + PuraceTextView(viewModel.place.title, fontSize: 18, weight: .medium) + PuraceTextView(viewModel.place.subtitle, fontSize: 12, textColor: PuraceStyle.Color.N4) + } + } + + var description: some View { + PuraceTextView(viewModel.place.description ?? "") + } + + var body: some View { + ZStack { + ScrollView { + VStack { + image + title + .padding(.top) + description + .padding() + Spacer() + } + } + VStack { + navBar + Spacer() + } + } + .navigationBarHidden(true) + .onFirstAppear { + viewModel.getPlace(id: placeId) + } + } +} diff --git a/Payan/App/Domain Modules/Place/UI/View Model/PYPlaceViewModel.swift b/Payan/App/Domain Modules/Place/UI/View Model/PYPlaceViewModel.swift new file mode 100644 index 0000000..46cb227 --- /dev/null +++ b/Payan/App/Domain Modules/Place/UI/View Model/PYPlaceViewModel.swift @@ -0,0 +1,28 @@ +// +// PYPlaceViewModel.swift +// Payan +// +// Created by Juan Hurtado on 28/05/22. +// + +import Foundation + +class PYPlaceViewModel: ObservableObject { + @Published var place: PYPlace = .empty + let interactor: PYPlaceBusinessLogic + + init(interactor: PYPlaceBusinessLogic = PYPlaceInteractor()) { + self.interactor = interactor + } + + func getPlace(id: String) { + interactor.getPlace(identifiedBy: id) { [weak self] res in + guard let self = self else { return } + switch res { + case .success(let place): + self.place = place + case .failure(_): break + } + } + } +} diff --git a/Payan/App/Domain Modules/Place/Workers/PYPlaceNetworkWorker.swift b/Payan/App/Domain Modules/Place/Workers/PYPlaceNetworkWorker.swift new file mode 100644 index 0000000..eaedf79 --- /dev/null +++ b/Payan/App/Domain Modules/Place/Workers/PYPlaceNetworkWorker.swift @@ -0,0 +1,30 @@ +// +// PYPlaceNetworkWorker.swift +// Payan +// +// Created by Juan Hurtado on 28/05/22. +// + +import Foundation + +class PYPlaceNetworkWorker: PYPlaceDataAccessLogic { + func fecthPlace(id: String, completion: @escaping (Result) -> Void) { + let request = PYNetworkRequest(endpoint: "place?id=\(id)") + PYNetworkManager.shared.exec(request: request) { result in + switch result { + case .success(let data): + do { + let decoder = JSONDecoder() + let decodedResponse = try decoder.decode(PYServerResponse.self, from: data) + if let place = decodedResponse.data { + completion(.success(place)) + } + } catch { + completion(.failure(error)) + } + case .failure(let error): + completion(.failure(error)) + } + } + } +} diff --git a/Payan/AppDelegate/SceneDelegate.swift b/Payan/AppDelegate/SceneDelegate.swift index 3ac5dd2..3ff378b 100644 --- a/Payan/AppDelegate/SceneDelegate.swift +++ b/Payan/AppDelegate/SceneDelegate.swift @@ -31,7 +31,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { PYRoutingManager.provideNavigationController(navigationController) let modules: [PYModule] = [ PYFeedModule(), - PYCollectionModule() + PYCollectionModule(), + PYPlaceModule() ] modules.forEach { PYRoutingManager.shared.addModule($0) } PYRoutingManager.shared.open(url: URL(string: "payan://feed")!)