diff --git a/SOPTving/SOPTving.xcodeproj/project.pbxproj b/SOPTving/SOPTving.xcodeproj/project.pbxproj index 164da0f..1c7701d 100644 --- a/SOPTving/SOPTving.xcodeproj/project.pbxproj +++ b/SOPTving/SOPTving.xcodeproj/project.pbxproj @@ -19,7 +19,7 @@ B596496D29FD414C000E1AE6 /* MainSectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596496C29FD414C000E1AE6 /* MainSectionHeaderView.swift */; }; B596496F29FD45A3000E1AE6 /* Content.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596496E29FD45A3000E1AE6 /* Content.swift */; }; B596497129FD870F000E1AE6 /* PopularChannelCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596497029FD870F000E1AE6 /* PopularChannelCollectionViewCell.swift */; }; - B596497329FD8EB4000E1AE6 /* MainHomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596497229FD8EB4000E1AE6 /* MainHomeViewModel.swift */; }; + B596497329FD8EB4000E1AE6 /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596497229FD8EB4000E1AE6 /* HomeViewModel.swift */; }; B598C0AB29E8070E00FDEF04 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B598C0AA29E8070E00FDEF04 /* AppDelegate.swift */; }; B598C0AD29E8070E00FDEF04 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B598C0AC29E8070E00FDEF04 /* SceneDelegate.swift */; }; B598C0AF29E8070E00FDEF04 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B598C0AE29E8070E00FDEF04 /* ViewController.swift */; }; @@ -30,9 +30,7 @@ B598C0D629E8112500FDEF04 /* Pretendard-Black.otf in Resources */ = {isa = PBXBuildFile; fileRef = B598C0CD29E8112500FDEF04 /* Pretendard-Black.otf */; }; B598C0D729E8112500FDEF04 /* Pretendard-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = B598C0CE29E8112500FDEF04 /* Pretendard-Bold.otf */; }; B598C0D829E8112500FDEF04 /* Pretendard-ExtraLight.otf in Resources */ = {isa = PBXBuildFile; fileRef = B598C0CF29E8112500FDEF04 /* Pretendard-ExtraLight.otf */; }; - B598C0D929E8112500FDEF04 /* Pretendard-Medium.otf in Resources */ = {isa = PBXBuildFile; fileRef = B598C0D029E8112500FDEF04 /* Pretendard-Medium.otf */; }; B598C0DA29E8112500FDEF04 /* Pretendard-Light.otf in Resources */ = {isa = PBXBuildFile; fileRef = B598C0D129E8112500FDEF04 /* Pretendard-Light.otf */; }; - B598C0DB29E8112500FDEF04 /* Pretendard-SemiBold.otf in Resources */ = {isa = PBXBuildFile; fileRef = B598C0D229E8112500FDEF04 /* Pretendard-SemiBold.otf */; }; B598C0DC29E8112500FDEF04 /* Pretendard-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = B598C0D329E8112500FDEF04 /* Pretendard-Regular.otf */; }; B598C0E129E82B6B00FDEF04 /* ImageLiterals.swift in Sources */ = {isa = PBXBuildFile; fileRef = B598C0E029E82B6B00FDEF04 /* ImageLiterals.swift */; }; B598C0E329E82D3A00FDEF04 /* UIFont+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B598C0E229E82D3A00FDEF04 /* UIFont+.swift */; }; @@ -74,6 +72,24 @@ B5DAE64D29F6FA7E0090F25C /* MembershipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DAE64C29F6FA7E0090F25C /* MembershipView.swift */; }; B5DAE64F29F713E20090F25C /* LabelButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DAE64E29F713E20090F25C /* LabelButtonView.swift */; }; B5DAE65329F7AAA50090F25C /* UIViewController+.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DAE65229F7AAA50090F25C /* UIViewController+.swift */; }; + B5F22E382A0D31B5006F5552 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F22E372A0D31B5006F5552 /* Config.swift */; }; + B5F22E3A2A0D3265006F5552 /* HTTPHeaderField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F22E392A0D3265006F5552 /* HTTPHeaderField.swift */; }; + B5F22E3D2A0D327E006F5552 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = B5F22E3C2A0D327E006F5552 /* Alamofire */; }; + B5F22E3F2A0D32DA006F5552 /* NetworkResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F22E3E2A0D32DA006F5552 /* NetworkResult.swift */; }; + B5F22E412A0D338C006F5552 /* TargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F22E402A0D338C006F5552 /* TargetType.swift */; }; + B5F22E432A0D33CB006F5552 /* APIEventLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F22E422A0D33CB006F5552 /* APIEventLogger.swift */; }; + B5F22E452A0D34E6006F5552 /* BaseService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F22E442A0D34E6006F5552 /* BaseService.swift */; }; + B5F22E492A0D3659006F5552 /* MovieTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F22E482A0D3659006F5552 /* MovieTarget.swift */; }; + B5F22E4D2A0D36C5006F5552 /* PopularMovieEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F22E4C2A0D36C5006F5552 /* PopularMovieEntity.swift */; }; + B5F22E512A0D3A7D006F5552 /* PopularMovieRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F22E502A0D3A7D006F5552 /* PopularMovieRequestDTO.swift */; }; + B5F22E612A0D5AFE006F5552 /* MovieService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F22E602A0D5AFE006F5552 /* MovieService.swift */; }; + B5F22E642A0D5E09006F5552 /* MovieRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F22E632A0D5E09006F5552 /* MovieRepository.swift */; }; + B5F22E672A0D70E5006F5552 /* MovieUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F22E662A0D70E5006F5552 /* MovieUseCase.swift */; }; + B5F22E6A2A0D737E006F5552 /* MovieModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F22E692A0D737E006F5552 /* MovieModel.swift */; }; + B5F22E6D2A0D753B006F5552 /* Pretendard-Medium.otf in Resources */ = {isa = PBXBuildFile; fileRef = B5F22E6B2A0D753B006F5552 /* Pretendard-Medium.otf */; }; + B5F22E6E2A0D753B006F5552 /* Pretendard-SemiBold.otf in Resources */ = {isa = PBXBuildFile; fileRef = B5F22E6C2A0D753B006F5552 /* Pretendard-SemiBold.otf */; }; + B5F22E712A0D7A70006F5552 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = B5F22E702A0D7A70006F5552 /* Kingfisher */; }; + B5F22E732A0D7A90006F5552 /* downloadImageFromURLString.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F22E722A0D7A90006F5552 /* downloadImageFromURLString.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -89,7 +105,7 @@ B596496C29FD414C000E1AE6 /* MainSectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSectionHeaderView.swift; sourceTree = ""; }; B596496E29FD45A3000E1AE6 /* Content.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Content.swift; sourceTree = ""; }; B596497029FD870F000E1AE6 /* PopularChannelCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopularChannelCollectionViewCell.swift; sourceTree = ""; }; - B596497229FD8EB4000E1AE6 /* MainHomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeViewModel.swift; sourceTree = ""; }; + B596497229FD8EB4000E1AE6 /* HomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; B598C0A729E8070E00FDEF04 /* SOPTving.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SOPTving.app; sourceTree = BUILT_PRODUCTS_DIR; }; B598C0AA29E8070E00FDEF04 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; B598C0AC29E8070E00FDEF04 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -102,9 +118,7 @@ B598C0CD29E8112500FDEF04 /* Pretendard-Black.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Black.otf"; sourceTree = ""; }; B598C0CE29E8112500FDEF04 /* Pretendard-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Bold.otf"; sourceTree = ""; }; B598C0CF29E8112500FDEF04 /* Pretendard-ExtraLight.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-ExtraLight.otf"; sourceTree = ""; }; - B598C0D029E8112500FDEF04 /* Pretendard-Medium.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Medium.otf"; sourceTree = ""; }; B598C0D129E8112500FDEF04 /* Pretendard-Light.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Light.otf"; sourceTree = ""; }; - B598C0D229E8112500FDEF04 /* Pretendard-SemiBold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-SemiBold.otf"; sourceTree = ""; }; B598C0D329E8112500FDEF04 /* Pretendard-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Regular.otf"; sourceTree = ""; }; B598C0E029E82B6B00FDEF04 /* ImageLiterals.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageLiterals.swift; sourceTree = ""; }; B598C0E229E82D3A00FDEF04 /* UIFont+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+.swift"; sourceTree = ""; }; @@ -144,6 +158,22 @@ B5DAE64C29F6FA7E0090F25C /* MembershipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MembershipView.swift; sourceTree = ""; }; B5DAE64E29F713E20090F25C /* LabelButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelButtonView.swift; sourceTree = ""; }; B5DAE65229F7AAA50090F25C /* UIViewController+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+.swift"; sourceTree = ""; }; + B5F22E372A0D31B5006F5552 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; + B5F22E392A0D3265006F5552 /* HTTPHeaderField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPHeaderField.swift; sourceTree = ""; }; + B5F22E3E2A0D32DA006F5552 /* NetworkResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkResult.swift; sourceTree = ""; }; + B5F22E402A0D338C006F5552 /* TargetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TargetType.swift; sourceTree = ""; }; + B5F22E422A0D33CB006F5552 /* APIEventLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIEventLogger.swift; sourceTree = ""; }; + B5F22E442A0D34E6006F5552 /* BaseService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseService.swift; sourceTree = ""; }; + B5F22E482A0D3659006F5552 /* MovieTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieTarget.swift; sourceTree = ""; }; + B5F22E4C2A0D36C5006F5552 /* PopularMovieEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopularMovieEntity.swift; sourceTree = ""; }; + B5F22E502A0D3A7D006F5552 /* PopularMovieRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopularMovieRequestDTO.swift; sourceTree = ""; }; + B5F22E602A0D5AFE006F5552 /* MovieService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieService.swift; sourceTree = ""; }; + B5F22E632A0D5E09006F5552 /* MovieRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieRepository.swift; sourceTree = ""; }; + B5F22E662A0D70E5006F5552 /* MovieUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieUseCase.swift; sourceTree = ""; }; + B5F22E692A0D737E006F5552 /* MovieModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieModel.swift; sourceTree = ""; }; + B5F22E6B2A0D753B006F5552 /* Pretendard-Medium.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Medium.otf"; sourceTree = ""; }; + B5F22E6C2A0D753B006F5552 /* Pretendard-SemiBold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-SemiBold.otf"; sourceTree = ""; }; + B5F22E722A0D7A90006F5552 /* downloadImageFromURLString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = downloadImageFromURLString.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -151,6 +181,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B5F22E712A0D7A70006F5552 /* Kingfisher in Frameworks */, + B5F22E3D2A0D327E006F5552 /* Alamofire in Frameworks */, B598C0E829E83FBA00FDEF04 /* SnapKit in Frameworks */, B5DAE62829F685000090F25C /* Then in Frameworks */, ); @@ -167,7 +199,7 @@ B596496829FD3D4F000E1AE6 /* Cells */, B52FA8EC29FAF4D400AC384B /* HomeViewController.swift */, B596496E29FD45A3000E1AE6 /* Content.swift */, - B596497229FD8EB4000E1AE6 /* MainHomeViewModel.swift */, + B596497229FD8EB4000E1AE6 /* HomeViewModel.swift */, B5D0CAD829FD9D1700D1F271 /* MainHomeViewController.swift */, ); path = Home; @@ -214,6 +246,8 @@ B598C0A929E8070E00FDEF04 /* SOPTving */ = { isa = PBXGroup; children = ( + B5F22E352A0D31A9006F5552 /* Data */, + B5F22E652A0D70D7006F5552 /* Domain */, B598C0F729E9BCDF00FDEF04 /* Presentation */, B598C0DE29E82B2D00FDEF04 /* Global */, B598C0B829E8070F00FDEF04 /* Info.plist */, @@ -233,14 +267,14 @@ B598C0C129E810DA00FDEF04 /* Fonts */ = { isa = PBXGroup; children = ( + B5F22E6B2A0D753B006F5552 /* Pretendard-Medium.otf */, + B5F22E6C2A0D753B006F5552 /* Pretendard-SemiBold.otf */, B598C0CD29E8112500FDEF04 /* Pretendard-Black.otf */, B598C0CE29E8112500FDEF04 /* Pretendard-Bold.otf */, B598C0CC29E8112500FDEF04 /* Pretendard-ExtraBold.otf */, B598C0CF29E8112500FDEF04 /* Pretendard-ExtraLight.otf */, B598C0D129E8112500FDEF04 /* Pretendard-Light.otf */, - B598C0D029E8112500FDEF04 /* Pretendard-Medium.otf */, B598C0D329E8112500FDEF04 /* Pretendard-Regular.otf */, - B598C0D229E8112500FDEF04 /* Pretendard-SemiBold.otf */, B598C0CB29E8112500FDEF04 /* Pretendard-Thin.otf */, ); path = Fonts; @@ -313,10 +347,10 @@ B598C0F729E9BCDF00FDEF04 /* Presentation */ = { isa = PBXGroup; children = ( + B5F22E762A0D919C006F5552 /* Welcome */, + B5F22E752A0D9194006F5552 /* Start */, B52FA8EB29FAF4BB00AC384B /* Home */, B5DAE62029F684160090F25C /* MyPage */, - B598C0F329E96CFD00FDEF04 /* WelcomeViewController.swift */, - B598C0AE29E8070E00FDEF04 /* ViewController.swift */, B598C0F829E9BD2F00FDEF04 /* Login */, ); path = Presentation; @@ -327,7 +361,6 @@ children = ( B598C0E929E842B400FDEF04 /* LoginViewController.swift */, B598C0F929E9BD4400FDEF04 /* LoginViewModel.swift */, - B598C0FB29E9BE3A00FDEF04 /* LoginUseCase.swift */, ); path = Login; sourceTree = ""; @@ -336,6 +369,7 @@ isa = PBXGroup; children = ( B5D0CADD29FDAC8000D1F271 /* ScreenUtils.swift */, + B5F22E722A0D7A90006F5552 /* downloadImageFromURLString.swift */, ); path = Utils; sourceTree = ""; @@ -392,6 +426,146 @@ path = Views; sourceTree = ""; }; + B5F22E352A0D31A9006F5552 /* Data */ = { + isa = PBXGroup; + children = ( + B5F22E622A0D5DF7006F5552 /* Repository */, + B5F22E4A2A0D368B006F5552 /* Entity */, + B5F22E362A0D31AD006F5552 /* Network */, + ); + path = Data; + sourceTree = ""; + }; + B5F22E362A0D31AD006F5552 /* Network */ = { + isa = PBXGroup; + children = ( + B5F22E4E2A0D3A4A006F5552 /* Model */, + B5F22E472A0D3643006F5552 /* Router */, + B5F22E462A0D3564006F5552 /* Service */, + B5F22E372A0D31B5006F5552 /* Config.swift */, + B5F22E392A0D3265006F5552 /* HTTPHeaderField.swift */, + B5F22E3E2A0D32DA006F5552 /* NetworkResult.swift */, + B5F22E402A0D338C006F5552 /* TargetType.swift */, + B5F22E422A0D33CB006F5552 /* APIEventLogger.swift */, + ); + path = Network; + sourceTree = ""; + }; + B5F22E462A0D3564006F5552 /* Service */ = { + isa = PBXGroup; + children = ( + B5F22E602A0D5AFE006F5552 /* MovieService.swift */, + B5F22E442A0D34E6006F5552 /* BaseService.swift */, + ); + path = Service; + sourceTree = ""; + }; + B5F22E472A0D3643006F5552 /* Router */ = { + isa = PBXGroup; + children = ( + B5F22E482A0D3659006F5552 /* MovieTarget.swift */, + ); + path = Router; + sourceTree = ""; + }; + B5F22E4A2A0D368B006F5552 /* Entity */ = { + isa = PBXGroup; + children = ( + B5F22E4B2A0D3694006F5552 /* Main */, + ); + path = Entity; + sourceTree = ""; + }; + B5F22E4B2A0D3694006F5552 /* Main */ = { + isa = PBXGroup; + children = ( + B5F22E4C2A0D36C5006F5552 /* PopularMovieEntity.swift */, + ); + path = Main; + sourceTree = ""; + }; + B5F22E4E2A0D3A4A006F5552 /* Model */ = { + isa = PBXGroup; + children = ( + B5F22E4F2A0D3A5B006F5552 /* Main */, + ); + path = Model; + sourceTree = ""; + }; + B5F22E4F2A0D3A5B006F5552 /* Main */ = { + isa = PBXGroup; + children = ( + B5F22E502A0D3A7D006F5552 /* PopularMovieRequestDTO.swift */, + ); + path = Main; + sourceTree = ""; + }; + B5F22E622A0D5DF7006F5552 /* Repository */ = { + isa = PBXGroup; + children = ( + B5F22E632A0D5E09006F5552 /* MovieRepository.swift */, + ); + path = Repository; + sourceTree = ""; + }; + B5F22E652A0D70D7006F5552 /* Domain */ = { + isa = PBXGroup; + children = ( + B5F22E742A0D7DDF006F5552 /* UseCase */, + B5F22E682A0D736E006F5552 /* Model */, + ); + path = Domain; + sourceTree = ""; + }; + B5F22E682A0D736E006F5552 /* Model */ = { + isa = PBXGroup; + children = ( + B5F22E692A0D737E006F5552 /* MovieModel.swift */, + ); + path = Model; + sourceTree = ""; + }; + B5F22E742A0D7DDF006F5552 /* UseCase */ = { + isa = PBXGroup; + children = ( + B5F22E782A0D91C2006F5552 /* Login */, + B5F22E772A0D91BB006F5552 /* Main */, + ); + path = UseCase; + sourceTree = ""; + }; + B5F22E752A0D9194006F5552 /* Start */ = { + isa = PBXGroup; + children = ( + B598C0AE29E8070E00FDEF04 /* ViewController.swift */, + ); + path = Start; + sourceTree = ""; + }; + B5F22E762A0D919C006F5552 /* Welcome */ = { + isa = PBXGroup; + children = ( + B598C0F329E96CFD00FDEF04 /* WelcomeViewController.swift */, + ); + path = Welcome; + sourceTree = ""; + }; + B5F22E772A0D91BB006F5552 /* Main */ = { + isa = PBXGroup; + children = ( + B5F22E662A0D70E5006F5552 /* MovieUseCase.swift */, + ); + path = Main; + sourceTree = ""; + }; + B5F22E782A0D91C2006F5552 /* Login */ = { + isa = PBXGroup; + children = ( + B598C0FB29E9BE3A00FDEF04 /* LoginUseCase.swift */, + ); + path = Login; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -411,6 +585,8 @@ packageProductDependencies = ( B598C0E729E83FBA00FDEF04 /* SnapKit */, B5DAE62729F685000090F25C /* Then */, + B5F22E3C2A0D327E006F5552 /* Alamofire */, + B5F22E702A0D7A70006F5552 /* Kingfisher */, ); productName = SOPTving; productReference = B598C0A729E8070E00FDEF04 /* SOPTving.app */; @@ -443,6 +619,8 @@ packageReferences = ( B598C0E629E83FBA00FDEF04 /* XCRemoteSwiftPackageReference "SnapKit" */, B5DAE62629F685000090F25C /* XCRemoteSwiftPackageReference "Then" */, + B5F22E3B2A0D327E006F5552 /* XCRemoteSwiftPackageReference "Alamofire" */, + B5F22E6F2A0D7A70006F5552 /* XCRemoteSwiftPackageReference "Kingfisher" */, ); productRefGroup = B598C0A829E8070E00FDEF04 /* Products */; projectDirPath = ""; @@ -458,8 +636,9 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - B598C0DB29E8112500FDEF04 /* Pretendard-SemiBold.otf in Resources */, + B5F22E6E2A0D753B006F5552 /* Pretendard-SemiBold.otf in Resources */, B598C0DC29E8112500FDEF04 /* Pretendard-Regular.otf in Resources */, + B5F22E6D2A0D753B006F5552 /* Pretendard-Medium.otf in Resources */, B598C0B729E8070F00FDEF04 /* LaunchScreen.storyboard in Resources */, B598C0D829E8112500FDEF04 /* Pretendard-ExtraLight.otf in Resources */, B598C0DA29E8112500FDEF04 /* Pretendard-Light.otf in Resources */, @@ -468,7 +647,6 @@ B598C0B429E8070F00FDEF04 /* Assets.xcassets in Resources */, B598C0D629E8112500FDEF04 /* Pretendard-Black.otf in Resources */, B598C0D429E8112500FDEF04 /* Pretendard-Thin.otf in Resources */, - B598C0D929E8112500FDEF04 /* Pretendard-Medium.otf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -487,6 +665,7 @@ B598C0F229E8975200FDEF04 /* UITextField+.swift in Sources */, B5D0CB3529FE8A8F00D1F271 /* TVProgramViewController.swift in Sources */, B5D0CADE29FDAC8000D1F271 /* ScreenUtils.swift in Sources */, + B5F22E452A0D34E6006F5552 /* BaseService.swift in Sources */, B57F37192A01A3FC007FC3A0 /* ContainerCarouselCollectionViewCell.swift in Sources */, B598C0F429E96CFD00FDEF04 /* WelcomeViewController.swift in Sources */, B5D0CAD929FD9D1700D1F271 /* MainHomeViewController.swift in Sources */, @@ -495,27 +674,36 @@ B598C0AF29E8070E00FDEF04 /* ViewController.swift in Sources */, B596496A29FD3D66000E1AE6 /* MovieDramaCollectionViewCell.swift in Sources */, B57F37112A014CE0007FC3A0 /* Array+.swift in Sources */, - B596497329FD8EB4000E1AE6 /* MainHomeViewModel.swift in Sources */, + B596497329FD8EB4000E1AE6 /* HomeViewModel.swift in Sources */, B5DAE61F29F682290090F25C /* NSObject+.swift in Sources */, + B5F22E3F2A0D32DA006F5552 /* NetworkResult.swift in Sources */, B57506B02A03FB46009FC995 /* UIControl+.swift in Sources */, B5D0CB2029FE7F2700D1F271 /* TopTabBarCollectionViewCell.swift in Sources */, B57506B22A04022E009FC995 /* UICollectionView+.swift in Sources */, + B5F22E3A2A0D3265006F5552 /* HTTPHeaderField.swift in Sources */, B5DAE61B29F681F90090F25C /* BaseViewController.swift in Sources */, B598C0E529E83D3100FDEF04 /* Color+.swift in Sources */, B57F37132A017665007FC3A0 /* Const.swift in Sources */, B5DAE62D29F68B2D0090F25C /* MyPageTableViewCell.swift in Sources */, B598C0E129E82B6B00FDEF04 /* ImageLiterals.swift in Sources */, + B5F22E6A2A0D737E006F5552 /* MovieModel.swift in Sources */, + B5F22E492A0D3659006F5552 /* MovieTarget.swift in Sources */, + B5F22E512A0D3A7D006F5552 /* PopularMovieRequestDTO.swift in Sources */, B598C0FE29E9C58700FDEF04 /* String+.swift in Sources */, B596496F29FD45A3000E1AE6 /* Content.swift in Sources */, B598C0F629E9BA0700FDEF04 /* UIButton+.swift in Sources */, + B5F22E732A0D7A90006F5552 /* downloadImageFromURLString.swift in Sources */, B598C0FA29E9BD4400FDEF04 /* LoginViewModel.swift in Sources */, + B5F22E412A0D338C006F5552 /* TargetType.swift in Sources */, B598C0AB29E8070E00FDEF04 /* AppDelegate.swift in Sources */, B596497129FD870F000E1AE6 /* PopularChannelCollectionViewCell.swift in Sources */, B52FA8EF29FAF51300AC384B /* NavigationBarView.swift in Sources */, B5D0CB3B29FEF06200D1F271 /* ParamountViewController.swift in Sources */, + B5F22E672A0D70E5006F5552 /* MovieUseCase.swift in Sources */, B596496D29FD414C000E1AE6 /* MainSectionHeaderView.swift in Sources */, B5D0CB3D29FEF06A00D1F271 /* KidsViewController.swift in Sources */, B598C0F029E8970300FDEF04 /* UIView+.swift in Sources */, + B5F22E382A0D31B5006F5552 /* Config.swift in Sources */, B5DAE64629F6F0070090F25C /* MyPageViewModel.swift in Sources */, B5DAE64D29F6FA7E0090F25C /* MembershipView.swift in Sources */, B598C10229E9D60100FDEF04 /* BottomSheetViewController.swift in Sources */, @@ -524,14 +712,18 @@ B598C0FC29E9BE3A00FDEF04 /* LoginUseCase.swift in Sources */, B5DAE64929F6F5390090F25C /* SectionSeperatorView.swift in Sources */, B5D0CB3729FEF05500D1F271 /* LiveChannelViewController.swift in Sources */, + B5F22E432A0D33CB006F5552 /* APIEventLogger.swift in Sources */, B5D0CADB29FDA91100D1F271 /* ImageBannerCollectionViewCell.swift in Sources */, B5D0CB1E29FE7CBA00D1F271 /* TopTabBarView.swift in Sources */, B5DAE61D29F682020090F25C /* BaseView.swift in Sources */, + B5F22E4D2A0D36C5006F5552 /* PopularMovieEntity.swift in Sources */, B5DAE64B29F6F7800090F25C /* LogoutFooterView.swift in Sources */, B5DAE64F29F713E20090F25C /* LabelButtonView.swift in Sources */, B5D0CAE029FDAF4600D1F271 /* CarouselCollectionViewCell.swift in Sources */, B598C0E329E82D3A00FDEF04 /* UIFont+.swift in Sources */, B598C0AD29E8070E00FDEF04 /* SceneDelegate.swift in Sources */, + B5F22E612A0D5AFE006F5552 /* MovieService.swift in Sources */, + B5F22E642A0D5E09006F5552 /* MovieRepository.swift in Sources */, B5D0CB3929FEF05C00D1F271 /* MovieViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -554,6 +746,8 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + API_KEY = 22062d671c2fd9afd57c5eec23ed8741; + BASE_URL = "https://api.themoviedb.org/3/movie"; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -614,6 +808,8 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + API_KEY = 22062d671c2fd9afd57c5eec23ed8741; + BASE_URL = "https://api.themoviedb.org/3/movie"; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -667,8 +863,10 @@ B598C0BC29E8070F00FDEF04 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + API_KEY = 22062d671c2fd9afd57c5eec23ed8741; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + BASE_URL = "https://api.themoviedb.org/3/movie"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = AXN37CP9B9; @@ -694,8 +892,10 @@ B598C0BD29E8070F00FDEF04 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + API_KEY = 22062d671c2fd9afd57c5eec23ed8741; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + BASE_URL = "https://api.themoviedb.org/3/movie"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = AXN37CP9B9; @@ -758,6 +958,22 @@ kind = branch; }; }; + B5F22E3B2A0D327E006F5552 /* XCRemoteSwiftPackageReference "Alamofire" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Alamofire/Alamofire.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.0.0; + }; + }; + B5F22E6F2A0D7A70006F5552 /* XCRemoteSwiftPackageReference "Kingfisher" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/onevcat/Kingfisher.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 7.0.0; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -771,6 +987,16 @@ package = B5DAE62629F685000090F25C /* XCRemoteSwiftPackageReference "Then" */; productName = Then; }; + B5F22E3C2A0D327E006F5552 /* Alamofire */ = { + isa = XCSwiftPackageProductDependency; + package = B5F22E3B2A0D327E006F5552 /* XCRemoteSwiftPackageReference "Alamofire" */; + productName = Alamofire; + }; + B5F22E702A0D7A70006F5552 /* Kingfisher */ = { + isa = XCSwiftPackageProductDependency; + package = B5F22E6F2A0D7A70006F5552 /* XCRemoteSwiftPackageReference "Kingfisher" */; + productName = Kingfisher; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = B598C09F29E8070E00FDEF04 /* Project object */; diff --git a/SOPTving/SOPTving.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SOPTving/SOPTving.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3934ec8..72070d6 100644 --- a/SOPTving/SOPTving.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/SOPTving/SOPTving.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,23 @@ { "pins" : [ + { + "identity" : "alamofire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/Alamofire.git", + "state" : { + "revision" : "bc268c28fb170f494de9e9927c371b8342979ece", + "version" : "5.7.1" + } + }, + { + "identity" : "kingfisher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/onevcat/Kingfisher.git", + "state" : { + "revision" : "af4be924ad984cf4d16f4ae4df424e79a443d435", + "version" : "7.6.2" + } + }, { "identity" : "snapkit", "kind" : "remoteSourceControl", diff --git a/SOPTving/SOPTving/Data/Entity/Main/PopularMovieEntity.swift b/SOPTving/SOPTving/Data/Entity/Main/PopularMovieEntity.swift new file mode 100644 index 0000000..fb1293c --- /dev/null +++ b/SOPTving/SOPTving/Data/Entity/Main/PopularMovieEntity.swift @@ -0,0 +1,69 @@ +// +// PopularMovieEntity.swift +// SOPTving +// +// Created by 김민재 on 2023/05/11. +// + +import UIKit + +struct PopularMovieEntity: Decodable { + let page: Int + let results: [Result] + let totalPages: Int + let totalResults: Int + + enum CodingKeys: String, CodingKey { + case page, results + case totalPages = "total_pages" + case totalResults = "total_results" + } +} + +// MARK: - Result +struct Result: Decodable { + let adult: Bool + let backdropPath: String + let genreIDS: [Int] + let id: Int + let originalLanguage: OriginalLanguage + let originalTitle, overview: String + let popularity: Double + let posterPath: String + let releaseDate: String + let title: String + let video: Bool + let voteAverage: Double + let voteCount: Int + + enum CodingKeys: String, CodingKey { + case adult + case backdropPath = "backdrop_path" + case genreIDS = "genre_ids" + case id + case originalLanguage = "original_language" + case originalTitle = "original_title" + case overview, popularity + case posterPath = "poster_path" + case releaseDate = "release_date" + case title, video + case voteAverage = "vote_average" + case voteCount = "vote_count" + } +} + +enum OriginalLanguage: String, Decodable { + case en = "en" + case fr = "fr" +} + +// MARK: Entity -> Model + +extension PopularMovieEntity { + func toDomain() -> [Movie] { + return self.results.map { + let url = TMDBImageURL.posterURL + $0.posterPath + return Movie(title: $0.title, posterImagePath: url) + } + } +} diff --git a/SOPTving/SOPTving/Data/Network/APIEventLogger.swift b/SOPTving/SOPTving/Data/Network/APIEventLogger.swift new file mode 100644 index 0000000..0de940b --- /dev/null +++ b/SOPTving/SOPTving/Data/Network/APIEventLogger.swift @@ -0,0 +1,45 @@ +// +// APIEventLogger.swift +// SOPTving +// +// Created by 김민재 on 2023/05/11. +// + +import Foundation + +import Alamofire + +final class APIEventLogger: EventMonitor { + func requestDidFinish(_ request: Request) { + + print("🛰 NETWORK Reqeust LOG") + print(request.description) + + print( + "URL: " + (request.request?.url?.absoluteString ?? "") + "\n" + + "Method: " + (request.request?.httpMethod ?? "") + "\n" + + "Headers: " + "\(request.request?.allHTTPHeaderFields ?? [:])" + "\n" + ) + print("Authorization: " + (request.request?.headers["Authorization"] ?? "")) + print("Body: " + (request.request?.httpBody?.toPrettyPrintedString ?? "")) + } + + func request(_ request: DataRequest, didParseResponse response: DataResponse) { + print("🛰 NETWORK Response LOG") + print( + "URL: " + (request.request?.url?.absoluteString ?? "") + "\n" + + "Result: " + "\(response.result)" + "\n" + + "StatusCode: " + "\(response.response?.statusCode ?? 0)" + "\n" + + "Data: \(response.data?.toPrettyPrintedString ?? "")" + ) + } +} + +extension Data { + var toPrettyPrintedString: String? { + guard let object = try? JSONSerialization.jsonObject(with: self, options: []), + let data = try? JSONSerialization.data(withJSONObject: object, options: [.prettyPrinted]), + let prettyPrintedString = NSString(data: data, encoding: String.Encoding.utf8.rawValue) else { return nil } + return prettyPrintedString as String + } +} diff --git a/SOPTving/SOPTving/Data/Network/Config.swift b/SOPTving/SOPTving/Data/Network/Config.swift new file mode 100644 index 0000000..fc8a53e --- /dev/null +++ b/SOPTving/SOPTving/Data/Network/Config.swift @@ -0,0 +1,41 @@ +// +// Config.swift +// SOPTving +// +// Created by 김민재 on 2023/05/11. +// + +import Foundation + +enum Config { + enum Keys { + enum Plist { + static let baseURL = "BASE_URL" + static let apiKey = "API_KEY" + } + } + + private static let infoDictionary: [String: Any] = { + guard let dict = Bundle.main.infoDictionary else { + fatalError("plist cannot found !") + } + return dict + }() +} + + +extension Config { + static let baseURL: String = { + guard let key = Config.infoDictionary[Keys.Plist.baseURL] as? String else { + fatalError("Base URL is not set in plist for this configuration") + } + return key + }() + + static let apiKey: String = { + guard let key = Config.infoDictionary[Keys.Plist.apiKey] as? String else { + fatalError("Base URL is not set in plist for this configuration") + } + return key + }() +} diff --git a/SOPTving/SOPTving/Data/Network/HTTPHeaderField.swift b/SOPTving/SOPTving/Data/Network/HTTPHeaderField.swift new file mode 100644 index 0000000..18c5676 --- /dev/null +++ b/SOPTving/SOPTving/Data/Network/HTTPHeaderField.swift @@ -0,0 +1,20 @@ +// +// HTTPHeaderField.swift +// SOPTving +// +// Created by 김민재 on 2023/05/11. +// + +import Foundation + +import Alamofire + +enum HTTPHeaderField: String { + case authentication = "Authorization" + case contentType = "Content-Type" + case acceptType = "Accept" +} + +enum ContentType: String { + case json = "Application/json" +} diff --git a/SOPTving/SOPTving/Data/Network/Model/Main/PopularMovieRequestDTO.swift b/SOPTving/SOPTving/Data/Network/Model/Main/PopularMovieRequestDTO.swift new file mode 100644 index 0000000..c9556c5 --- /dev/null +++ b/SOPTving/SOPTving/Data/Network/Model/Main/PopularMovieRequestDTO.swift @@ -0,0 +1,23 @@ +// +// PopularMovieRequestDTO.swift +// SOPTving +// +// Created by 김민재 on 2023/05/12. +// + +import Foundation + +struct PopularMovieRequestDTO: Encodable { + let apiKey: String + let page: Int? + let language: String? + + enum CodingKeys: String, CodingKey { + case apiKey = "api_key" + case page, language + } +} + +enum Language { + static let english = "en-US" +} diff --git a/SOPTving/SOPTving/Data/Network/NetworkResult.swift b/SOPTving/SOPTving/Data/Network/NetworkResult.swift new file mode 100644 index 0000000..22bc046 --- /dev/null +++ b/SOPTving/SOPTving/Data/Network/NetworkResult.swift @@ -0,0 +1,16 @@ +// +// NetworkResult.swift +// SOPTving +// +// Created by 김민재 on 2023/05/11. +// + +import Foundation + +enum NetworkResult { + case success(T) // 서버 통신 성공 + case requestErr(T) // 요청에러 발생 + case pathErr // 경로 에러 + case serverErr // 서버 내부 에러 + case networkErr // 네트워크 연결 실패 +} diff --git a/SOPTving/SOPTving/Data/Network/Router/MovieTarget.swift b/SOPTving/SOPTving/Data/Network/Router/MovieTarget.swift new file mode 100644 index 0000000..1c322df --- /dev/null +++ b/SOPTving/SOPTving/Data/Network/Router/MovieTarget.swift @@ -0,0 +1,39 @@ +// +// MovieTarget.swift +// SOPTving +// +// Created by 김민재 on 2023/05/11. +// + +import Foundation + +import Alamofire + +enum MovieTarget { + case getPopularMovie(_ dto: PopularMovieRequestDTO) +} + +extension MovieTarget: TargetType { + + var method: HTTPMethod { + switch self { + case .getPopularMovie: + return .get + } + } + + var path: String { + switch self { + case .getPopularMovie(_): + return "/popular" + } + } + + var parameters: RequestParams { + switch self { + case .getPopularMovie(let dto): + return .query(dto) + } + } + +} diff --git a/SOPTving/SOPTving/Data/Network/Service/BaseService.swift b/SOPTving/SOPTving/Data/Network/Service/BaseService.swift new file mode 100644 index 0000000..c899faf --- /dev/null +++ b/SOPTving/SOPTving/Data/Network/Service/BaseService.swift @@ -0,0 +1,54 @@ +// +// BaseService.swift +// SOPTving +// +// Created by 김민재 on 2023/05/11. +// + +import Foundation + +import Alamofire + + +class BaseService { + + static let shared = BaseService() + private init() {} + + func requestObject(_ target: TargetType, completion: @escaping (NetworkResult) -> Void) { + let dataRequest = AF.request(target) + dataRequest.responseData { response in + switch response.result { + case .success: + guard let statusCode = response.response?.statusCode else { return } + guard let value = response.value else { return } + let networkResult = self.judgeStatus(by: statusCode, value, type: T.self) + completion(networkResult) + case .failure: + completion(.networkErr) + } + + } + + } + + private func judgeStatus(by statusCode: Int, _ data: Data, type: T.Type) -> NetworkResult { + switch statusCode { + case 200: return isValidData(data: data, type: T.self) + case 401...409: return isValidData(data: data, type: T.self) + case 500: return .serverErr + default: return .networkErr + } + } + + private func isValidData(data: Data, type: T.Type) -> NetworkResult { + let decoder = JSONDecoder() + guard let decodedData = try? decoder.decode(T.self, from: data) else { + print("json decoded failed !") + return .pathErr + } + + return .success(decodedData) + } + +} diff --git a/SOPTving/SOPTving/Data/Network/Service/MovieService.swift b/SOPTving/SOPTving/Data/Network/Service/MovieService.swift new file mode 100644 index 0000000..170b5cc --- /dev/null +++ b/SOPTving/SOPTving/Data/Network/Service/MovieService.swift @@ -0,0 +1,21 @@ +// +// MovieService.swift +// SOPTving +// +// Created by 김민재 on 2023/05/12. +// + +import Foundation + +import Alamofire + + +protocol MovieService { + func getPopularMovies(queryDTO: PopularMovieRequestDTO, type: T.Type, completion: @escaping (NetworkResult) -> Void) +} + +extension BaseService: MovieService { + func getPopularMovies(queryDTO: PopularMovieRequestDTO, type: T.Type, completion: @escaping (NetworkResult) -> Void) { + requestObject(MovieTarget.getPopularMovie(queryDTO), completion: completion) + } +} diff --git a/SOPTving/SOPTving/Data/Network/TargetType.swift b/SOPTving/SOPTving/Data/Network/TargetType.swift new file mode 100644 index 0000000..88e397d --- /dev/null +++ b/SOPTving/SOPTving/Data/Network/TargetType.swift @@ -0,0 +1,72 @@ +// +// TargetType.swift +// SOPTving +// +// Created by 김민재 on 2023/05/11. +// + +import Foundation + +import Alamofire + +protocol TargetType: URLRequestConvertible { + var baseURL: String { get } + var method: HTTPMethod { get } + var path: String { get } + var parameters: RequestParams { get } +} + +extension TargetType { + var baseURL: String { + return Config.baseURL + } +} + + +extension TargetType { + func asURLRequest() throws -> URLRequest { + let url = try baseURL.asURL() + var urlRequest = try URLRequest( + url: url.appendingPathComponent(path), + method: method + ) + + urlRequest.setValue(ContentType.json.rawValue, forHTTPHeaderField: HTTPHeaderField.contentType.rawValue) + + switch parameters { + case .body(let request): + let params = request?.toDictionary() ?? [:] + + urlRequest.httpBody = try JSONSerialization.data(withJSONObject: params) + case .query(let request): + let params = request?.toDictionary() + let queryParams = params?.map { + URLQueryItem(name: $0.key, value: "\($0.value)") + } + var components = URLComponents( + string: url.appendingPathComponent(path).absoluteString) + components?.queryItems = queryParams + urlRequest.url = components?.url + case .requestPlain: + break + } + return urlRequest + } +} + +enum RequestParams { + case requestPlain + case query(_ parameter: Encodable?) + case body(_ paramter: Encodable?) +} + +extension Encodable { + func toDictionary() -> [String: Any] { + guard let data = try? JSONEncoder().encode(self), + let jsonData = try? JSONSerialization.jsonObject(with: data), + let dictionaryData = jsonData as? [String: Any] + else { return [:] } + + return dictionaryData + } +} diff --git a/SOPTving/SOPTving/Data/Repository/MovieRepository.swift b/SOPTving/SOPTving/Data/Repository/MovieRepository.swift new file mode 100644 index 0000000..21b8fcc --- /dev/null +++ b/SOPTving/SOPTving/Data/Repository/MovieRepository.swift @@ -0,0 +1,43 @@ +// +// MovieRepository.swift +// SOPTving +// +// Created by 김민재 on 2023/05/12. +// + +import Foundation + +protocol MovieRepository { + func getPopularMovie(page: Int?, completion: @escaping (PopularMovieEntity?) -> Void) +} + + +final class DefaultMovieRespository { + + private let networkService: MovieService + + init(networkService: MovieService) { + self.networkService = networkService + } +} + +extension DefaultMovieRespository: MovieRepository { + + func getPopularMovie(page: Int?,completion: @escaping (PopularMovieEntity?) -> Void) { + let queryDTO = PopularMovieRequestDTO( + apiKey: Config.apiKey, + page: page, + language: Language.english + ) + + networkService.getPopularMovies(queryDTO: queryDTO, type: PopularMovieEntity.self) { response in + switch response { + case .success(let entity): + completion(entity) + default: + completion(nil) + } + } + } + +} diff --git a/SOPTving/SOPTving/Domain/Model/MovieModel.swift b/SOPTving/SOPTving/Domain/Model/MovieModel.swift new file mode 100644 index 0000000..31abaa9 --- /dev/null +++ b/SOPTving/SOPTving/Domain/Model/MovieModel.swift @@ -0,0 +1,14 @@ +// +// MovieModel.swift +// SOPTving +// +// Created by 김민재 on 2023/05/12. +// + +import UIKit + + +struct Movie { + let title: String + let posterImagePath: String +} diff --git a/SOPTving/SOPTving/Presentation/Login/LoginUseCase.swift b/SOPTving/SOPTving/Domain/UseCase/Login/LoginUseCase.swift similarity index 100% rename from SOPTving/SOPTving/Presentation/Login/LoginUseCase.swift rename to SOPTving/SOPTving/Domain/UseCase/Login/LoginUseCase.swift diff --git a/SOPTving/SOPTving/Domain/UseCase/Main/MovieUseCase.swift b/SOPTving/SOPTving/Domain/UseCase/Main/MovieUseCase.swift new file mode 100644 index 0000000..ac2bf6f --- /dev/null +++ b/SOPTving/SOPTving/Domain/UseCase/Main/MovieUseCase.swift @@ -0,0 +1,32 @@ +// +// MovieUseCase.swift +// SOPTving +// +// Created by 김민재 on 2023/05/12. +// + +import Foundation + +protocol MovieUseCase { + func getPopularMovie(page: Int?, completion: @escaping ([Movie]?) -> Void) +} + + +final class DefaultMovieUseCase { + + private let repository: MovieRepository + + init(repository: MovieRepository) { + self.repository = repository + } +} + +extension DefaultMovieUseCase: MovieUseCase { + func getPopularMovie(page: Int?, completion: @escaping ([Movie]?) -> Void) { + repository.getPopularMovie(page: page) { movieEntity in + let movies = movieEntity?.toDomain() + completion(movies) + } + } + +} diff --git a/SOPTving/SOPTving/Global/Literal/Const.swift b/SOPTving/SOPTving/Global/Literal/Const.swift index feeea01..ddedf99 100644 --- a/SOPTving/SOPTving/Global/Literal/Const.swift +++ b/SOPTving/SOPTving/Global/Literal/Const.swift @@ -12,3 +12,17 @@ enum Size { static let screenWidth = UIScreen.main.bounds.width static let screenHeight = UIScreen.main.bounds.height } + +enum TMDBImageURL { + static let posterURL = "https://image.tmdb.org/t/p/original" +} + +enum Headers { + static let empty = "" + static let mustsee = "티빙에서 꼭 봐야하는 컨텐츠" + static let popularLiveChannel = "인기 LIVE 채널" + static let paramount = "파라마운트+의 따끈한 신작" + static let recommend = "김민재님이 좋아할만한 영화" + static let animation = "놓칠 수 없는 티빙 명작 애니메이션" + static let collection = "황금연휴를 책임져 줄 프로그램 컬렉션" +} diff --git a/SOPTving/SOPTving/Global/ModuleFactory.swift b/SOPTving/SOPTving/Global/ModuleFactory.swift index a592fa8..a5711aa 100644 --- a/SOPTving/SOPTving/Global/ModuleFactory.swift +++ b/SOPTving/SOPTving/Global/ModuleFactory.swift @@ -11,6 +11,7 @@ protocol ModuleFactoryProtocol { func makeLoginViewController() -> UIViewController func makeWelcomeViewController() -> UIViewController func makeMyPageViewController() -> UIViewController + func makeMainHomeViewController() -> UIViewController func makeHomeViewController() -> UIViewController } @@ -36,9 +37,16 @@ final class ModuleFactory: ModuleFactoryProtocol { return viewController } + func makeMainHomeViewController() -> UIViewController { + let viewController = MainHomeViewController() + return viewController + } + func makeHomeViewController() -> UIViewController { - let viewModel = MainHomeViewModel() - let viewController = MainHomeViewController(viewModel: viewModel) + let repository = DefaultMovieRespository(networkService: BaseService.shared) + let useCase = DefaultMovieUseCase(repository: repository) + let viewModel = DefaultMainHomeViewModel(useCase: useCase) + let viewController = HomeViewController(viewModel: viewModel) return viewController } } diff --git a/SOPTving/SOPTving/Global/Supprots/SceneDelegate.swift b/SOPTving/SOPTving/Global/Supprots/SceneDelegate.swift index d760e28..ef566ac 100644 --- a/SOPTving/SOPTving/Global/Supprots/SceneDelegate.swift +++ b/SOPTving/SOPTving/Global/Supprots/SceneDelegate.swift @@ -18,7 +18,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). guard let windowScene = (scene as? UIWindowScene) else { return } self.window = UIWindow(windowScene: windowScene) - let viewController = ModuleFactory.shared.makeHomeViewController() + let viewController = ModuleFactory.shared.makeMainHomeViewController() let navigationController = UINavigationController(rootViewController: viewController) self.window?.rootViewController = navigationController self.window?.makeKeyAndVisible() diff --git a/SOPTving/SOPTving/Global/Utils/downloadImageFromURLString.swift b/SOPTving/SOPTving/Global/Utils/downloadImageFromURLString.swift new file mode 100644 index 0000000..3b87345 --- /dev/null +++ b/SOPTving/SOPTving/Global/Utils/downloadImageFromURLString.swift @@ -0,0 +1,31 @@ +// +// downloadImageFromURL.swift +// SOPTving +// +// Created by 김민재 on 2023/05/12. +// + +import UIKit + +import Kingfisher + +extension String { + func downloadImageWithURLString(completion: @escaping (UIImage?) -> Void) { + guard let url = URL(string: self) else { + return completion(nil) + } + + let imgResource = ImageResource(downloadURL: url) + + KingfisherManager.shared.retrieveImage(with: imgResource) { result in + switch result { + case .success(let imageResult): + completion(imageResult.image) + case .failure: + completion(nil) + } + } + + } + +} diff --git a/SOPTving/SOPTving/Info.plist b/SOPTving/SOPTving/Info.plist index 54a3c5e..ce39fa9 100644 --- a/SOPTving/SOPTving/Info.plist +++ b/SOPTving/SOPTving/Info.plist @@ -2,6 +2,10 @@ + API_KEY + $(API_KEY) + BASE_URL + $(BASE_URL) UIAppFonts Pretendard-Black.otf diff --git a/SOPTving/SOPTving/Presentation/Home/Cells/MovieDramaCollectionViewCell.swift b/SOPTving/SOPTving/Presentation/Home/Cells/MovieDramaCollectionViewCell.swift index f2f140b..b6536a6 100644 --- a/SOPTving/SOPTving/Presentation/Home/Cells/MovieDramaCollectionViewCell.swift +++ b/SOPTving/SOPTving/Presentation/Home/Cells/MovieDramaCollectionViewCell.swift @@ -29,6 +29,10 @@ final class MovieDramaCollectionViewCell: UICollectionViewCell { fatalError("init(coder:) has not been implemented") } + override func prepareForReuse() { + self.posterImageView.image = nil + } + private func setStyle() { contentView.backgroundColor = .tvingBlack } @@ -42,14 +46,16 @@ final class MovieDramaCollectionViewCell: UICollectionViewCell { } titleLabel.snp.makeConstraints { make in - make.leading.equalTo(posterImageView) + make.leading.trailing.equalTo(posterImageView) make.top.equalTo(posterImageView.snp.bottom).offset(3) } } - func configureCell(content: Content) { - posterImageView.image = content.image - titleLabel.text = content.title + func configureCell(movie: Movie) { + movie.posterImagePath.downloadImageWithURLString(completion: { image in + self.posterImageView.image = image + }) + titleLabel.text = movie.title } } diff --git a/SOPTving/SOPTving/Presentation/Home/Cells/PopularChannelCollectionViewCell.swift b/SOPTving/SOPTving/Presentation/Home/Cells/PopularChannelCollectionViewCell.swift index e6b2044..8b5a59d 100644 --- a/SOPTving/SOPTving/Presentation/Home/Cells/PopularChannelCollectionViewCell.swift +++ b/SOPTving/SOPTving/Presentation/Home/Cells/PopularChannelCollectionViewCell.swift @@ -72,15 +72,16 @@ final class PopularChannelCollectionViewCell: UICollectionViewCell { stackView.snp.makeConstraints { make in make.top.equalTo(rankingLabel) make.leading.equalTo(rankingLabel.snp.trailing).offset(4) + make.trailing.equalToSuperview() // make.bottom.equalToSuperview() } } - func configureCell(rank: Int, channel: Channel) { + func configureCell(rank: Int, channel: Movie) { rankingLabel.text = "\(rank)" - channelNameLabel.text = channel.channelName - descriptionLabel.text = channel.descriptionName - ratingLabel.text = "\(channel.rating)%" + channelNameLabel.text = channel.title + descriptionLabel.text = "테스트" + ratingLabel.text = "37.3%" } } diff --git a/SOPTving/SOPTving/Presentation/Home/HomeViewController.swift b/SOPTving/SOPTving/Presentation/Home/HomeViewController.swift index 61b6f48..0b8cd2d 100644 --- a/SOPTving/SOPTving/Presentation/Home/HomeViewController.swift +++ b/SOPTving/SOPTving/Presentation/Home/HomeViewController.swift @@ -16,7 +16,15 @@ final class HomeViewController: BaseViewController { // MARK: - Properties - private var viewModel: MainHomeViewModel + private var viewModel: HomeViewModel + + private var posterCarouselImages = Poster.dummy() + + private var movies: [Movie] = [] { + didSet { + self.collectionView.reloadData() + } + } weak var delegate: HomeViewControllerProtocol? @@ -50,11 +58,22 @@ final class HomeViewController: BaseViewController { return 38 } } + + var headerText: String { + switch self { + case .liveChannel: + return Headers.popularLiveChannel + case .movieAndDrama, .paramountNew, .favoriteMovies, .programCollection, .animations: + return Headers.mustsee + case .imageBanner, .carousel: + return Headers.empty + } + } } // MARK: - View Life Cycle - init(viewModel: MainHomeViewModel) { + init(viewModel: HomeViewModel) { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) } @@ -63,6 +82,12 @@ final class HomeViewController: BaseViewController { super.viewDidLoad() setDelegate() makeArrayForCarouselView() + bind() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + viewModel.viewWillAppear() } private func setDelegate() { @@ -99,7 +124,16 @@ final class HomeViewController: BaseViewController { extension HomeViewController { func makeArrayForCarouselView() { - self.viewModel.posterCarouselImages.insertElementsBackAndForward() + self.posterCarouselImages.insertElementsBackAndForward() + } + + private func bind() { + viewModel.moviesFetched = { movies in + if let movies { + self.movies = movies + } + + } } } @@ -245,7 +279,8 @@ extension HomeViewController: UICollectionViewDataSource { let header = collectionView.dequeueHeaderView(type: MainSectionHeaderView.self, forIndexPath: indexPath) - header.configHeaderView(text: viewModel.headerText[indexPath.section]) + guard let sectionType = Section(rawValue: indexPath.section) else { return UICollectionReusableView() } + header.configHeaderView(text: sectionType.headerText) return header } @@ -255,7 +290,7 @@ extension HomeViewController: UICollectionViewDataSource { if sectionType == .imageBanner || sectionType == .carousel { return 1 } - return viewModel.contentDummy.count + return movies.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { @@ -267,18 +302,18 @@ extension HomeViewController: UICollectionViewDataSource { switch sectionType { case .carousel: let cell = collectionView.dequeueReusableCell(type: ContainerCarouselCollectionViewCell.self, forIndexPath: indexPath) - cell.initCell(posters: viewModel.posterCarouselImages) + cell.initCell(posters: posterCarouselImages) return cell case .imageBanner: let cell = collectionView.dequeueReusableCell(type: ImageBannerCollectionViewCell.self, forIndexPath: indexPath) return cell case .liveChannel: let cell = collectionView.dequeueReusableCell(type: PopularChannelCollectionViewCell.self, forIndexPath: indexPath) - cell.configureCell(rank: indexPath.item + 1, channel: viewModel.channelDummy[indexPath.item]) + cell.configureCell(rank: indexPath.item + 1, channel: movies[indexPath.item]) return cell case .movieAndDrama, .paramountNew, .programCollection, .favoriteMovies, .animations: let cell = collectionView.dequeueReusableCell(type: MovieDramaCollectionViewCell.self, forIndexPath: indexPath) - cell.configureCell(content: viewModel.contentDummy[indexPath.item]) + cell.configureCell(movie: movies[indexPath.item]) return cell } } diff --git a/SOPTving/SOPTving/Presentation/Home/HomeViewModel.swift b/SOPTving/SOPTving/Presentation/Home/HomeViewModel.swift new file mode 100644 index 0000000..a268fa1 --- /dev/null +++ b/SOPTving/SOPTving/Presentation/Home/HomeViewModel.swift @@ -0,0 +1,51 @@ +// +// MainHomeViewModel.swift +// SOPTving +// +// Created by 김민재 on 2023/04/30. +// + +import Foundation + +protocol HomeViewModelInput { + func viewWillAppear() +} + +protocol HomeViewModelOutput { + var moviesFetched: (([Movie]?) -> Void)? { get set } +} + +protocol HomeViewModel: HomeViewModelInput, HomeViewModelOutput {} + + +final class DefaultMainHomeViewModel: HomeViewModel { + + // MARK: - Output + + var moviesFetched: (([Movie]?) -> Void)? + + var movies: [Movie]? { + didSet { + self.moviesFetched?(movies) + } + } + + // MARK: - Properties + + private let useCase: MovieUseCase + + init(useCase: MovieUseCase) { + self.useCase = useCase + } +} + +// MARK: - Input + +extension DefaultMainHomeViewModel { + func viewWillAppear() { + useCase.getPopularMovie(page: 1) { movies in + self.movies = movies + } + + } +} diff --git a/SOPTving/SOPTving/Presentation/Home/MainHomeViewController.swift b/SOPTving/SOPTving/Presentation/Home/MainHomeViewController.swift index 4c3aa97..ab2293f 100644 --- a/SOPTving/SOPTving/Presentation/Home/MainHomeViewController.swift +++ b/SOPTving/SOPTving/Presentation/Home/MainHomeViewController.swift @@ -11,8 +11,6 @@ final class MainHomeViewController: BaseViewController { // MARK: - Properties - private let viewModel: MainHomeViewModel - private var currentPageIndex = 0 { didSet { setFirstVCinPageViewController(currIndex: oldValue, targetIndex: currentPageIndex) @@ -39,7 +37,7 @@ final class MainHomeViewController: BaseViewController { transitionStyle: .scroll, navigationOrientation: .horizontal) - private lazy var controllers: [UIViewController] = TopTab.allCases.map({ $0.viewController }) + private lazy var controllers: [UIViewController] = TopTabBarView.TopTab.allCases.map({ $0.viewController }) // MARK: - View Life Cycle @@ -47,15 +45,6 @@ final class MainHomeViewController: BaseViewController { return .lightContent } - init(viewModel: MainHomeViewModel) { - self.viewModel = viewModel - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - override func viewDidLoad() { super.viewDidLoad() setDelegate() diff --git a/SOPTving/SOPTving/Presentation/Home/MainHomeViewModel.swift b/SOPTving/SOPTving/Presentation/Home/MainHomeViewModel.swift deleted file mode 100644 index 0d5a02f..0000000 --- a/SOPTving/SOPTving/Presentation/Home/MainHomeViewModel.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// MainHomeViewModel.swift -// SOPTving -// -// Created by 김민재 on 2023/04/30. -// - -import Foundation - - -final class MainHomeViewModel { - let headerText: [String] = [ - "", - "인기 LIVE 채널", - "티빙에서 꼭 봐야하는 컨텐츠", - "", - "파라마운트+의 따끈한 신작", - "김민재님이 좋아할만한 영화", - "황금연휴를 책임져 줄 프로그램 컬렉션", - "놓칠 수 없는 티빙 명작 애니메이션" - ] - - let contentDummy = Content.dummy() - let channelDummy = Channel.dummy() - var posterCarouselImages: [Poster] = Poster.dummy() -} diff --git a/SOPTving/SOPTving/Presentation/Home/Views/TopTabBarView.swift b/SOPTving/SOPTving/Presentation/Home/Views/TopTabBarView.swift index 9bdce87..7a25843 100644 --- a/SOPTving/SOPTving/Presentation/Home/Views/TopTabBarView.swift +++ b/SOPTving/SOPTving/Presentation/Home/Views/TopTabBarView.swift @@ -11,34 +11,34 @@ protocol TopTabBarViewProtocol: AnyObject { func moveToTargetViewController(index: Int) } -@frozen -enum TopTab: String, CaseIterable { - case home = "홈" - case live = "실시간" - case tvProgram = "TV프로그램" - case movie = "영화" - case paramount = "파라마운트+" - case kids = "키즈" - - var viewController: UIViewController { - switch self { - case .home: - return HomeViewController(viewModel: MainHomeViewModel()) - case .live: - return LiveChannelViewController() - case .tvProgram: - return TVProgramViewController() - case .movie: - return MovieViewController() - case .paramount: - return ParamountViewController() - case .kids: - return KidsViewController() +final class TopTabBarView: BaseView { + + @frozen + enum TopTab: String, CaseIterable { + case home = "홈" + case live = "실시간" + case tvProgram = "TV프로그램" + case movie = "영화" + case paramount = "파라마운트+" + case kids = "키즈" + + var viewController: UIViewController { + switch self { + case .home: + return ModuleFactory.shared.makeHomeViewController() + case .live: + return LiveChannelViewController() + case .tvProgram: + return TVProgramViewController() + case .movie: + return MovieViewController() + case .paramount: + return ParamountViewController() + case .kids: + return KidsViewController() + } } } -} - -final class TopTabBarView: BaseView { private var targetIndex: Int = 0 { didSet { diff --git a/SOPTving/SOPTving/Presentation/ViewController.swift b/SOPTving/SOPTving/Presentation/Start/ViewController.swift similarity index 100% rename from SOPTving/SOPTving/Presentation/ViewController.swift rename to SOPTving/SOPTving/Presentation/Start/ViewController.swift diff --git a/SOPTving/SOPTving/Presentation/WelcomeViewController.swift b/SOPTving/SOPTving/Presentation/Welcome/WelcomeViewController.swift similarity index 100% rename from SOPTving/SOPTving/Presentation/WelcomeViewController.swift rename to SOPTving/SOPTving/Presentation/Welcome/WelcomeViewController.swift