├── Movie ├── Resources │ ├── Colors.xcassets │ │ ├── Contents.json │ │ ├── bg.colorset │ │ │ └── Contents.json │ │ ├── icon2.colorset │ │ │ └── Contents.json │ │ ├── icons.colorset │ │ │ └── Contents.json │ │ ├── star.colorset │ │ │ └── Contents.json │ │ ├── tabbar.colorset │ │ │ └── Contents.json │ │ ├── title.colorset │ │ │ └── Contents.json │ │ ├── commetsView.colorset │ │ │ └── Contents.json │ │ ├── logoRed.colorset │ │ │ └── Contents.json │ │ └── statusBar.colorset │ │ │ └── Contents.json │ ├── Icons.xcassets │ │ ├── Contents.json │ │ ├── Tabbar │ │ │ ├── Contents.json │ │ │ ├── home.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── home.svg │ │ │ ├── user.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── user.svg │ │ │ ├── bookmark.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── bookmark.svg │ │ │ └── explore.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── explore.svg │ │ ├── Navigation │ │ │ ├── Contents.json │ │ │ ├── filter.imageset │ │ │ │ ├── filter.svg │ │ │ │ └── Contents.json │ │ │ ├── menu.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── menu.svg │ │ │ └── search.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── search.svg │ │ ├── logo.imageset │ │ │ ├── MOVIE LOGO4 1.png │ │ │ └── Contents.json │ │ ├── launchPhoto.imageset │ │ │ ├── launchPhoto.png │ │ │ └── Contents.json │ │ └── gradient.imageset │ │ │ ├── Contents.json │ │ │ └── gradient.svg │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── 100.png │ │ │ ├── 1024.png │ │ │ ├── 114.png │ │ │ ├── 120.png │ │ │ ├── 144.png │ │ │ ├── 152.png │ │ │ ├── 167.png │ │ │ ├── 180.png │ │ │ ├── 20.png │ │ │ ├── 29.png │ │ │ ├── 40.png │ │ │ ├── 50.png │ │ │ ├── 57.png │ │ │ ├── 58.png │ │ │ ├── 60.png │ │ │ ├── 72.png │ │ │ ├── 76.png │ │ │ ├── 80.png │ │ │ ├── 87.png │ │ │ └── Contents.json │ ├── GeneratedColors.swift │ ├── GeneratedIcons.swift │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ └── movies.json ├── Models │ ├── Comments.swift │ └── MovieDTO.swift ├── NetworkService │ ├── Network Components │ │ ├── HTTPMethods.swift │ │ ├── NetworkErrors.swift │ │ └── ProResult.swift │ ├── Base Router │ │ ├── MainScreenRouter.swift │ │ └── BaseRouter.swift │ └── NetworkService.swift ├── AppDelegate.swift ├── Extension │ └── UIKit │ │ ├── UIImage + Extension.swift │ │ ├── UINavigationController + Extension.swift │ │ ├── UICollectionViewCell+Register+extension.swift │ │ └── ViewController + Extension.swift ├── MovieDetailsScreen │ ├── View │ │ ├── SectionDecorationView.swift │ │ ├── CommentsViewFooter.swift │ │ ├── LableImage.swift │ │ ├── NavigationBarBack.swift │ │ └── TopView.swift │ ├── Cells │ │ └── CommentsCell.swift │ └── MovieDetailedScreenViewController.swift ├── SceneDelegate.swift ├── Info.plist ├── MainScreen │ ├── Cells │ │ └── PhotoCell.swift │ ├── View │ │ ├── HeaderView.swift │ │ └── NavigationBar.swift │ ├── MainScreenViewModel.swift │ ├── MainScreenViewController.swift │ └── MainContentView.swift └── Tabbar │ └── UITabBarController.swift ├── Movie.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcuserdata │ │ └── siuzanna.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved ├── xcuserdata │ └── siuzanna.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist └── project.pbxproj ├── .gitignore ├── .swiftlint.yml ├── swiftgen.yml └── README.md /Movie/Resources/Colors.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/Tabbar/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/Navigation/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Movie/Resources/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie/Resources/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /Movie/Resources/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie/Resources/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /Movie/Resources/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie/Resources/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /Movie/Resources/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie/Resources/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /Movie/Resources/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie/Resources/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /Movie/Resources/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie/Resources/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /Movie/Resources/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie/Resources/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /Movie/Resources/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie/Resources/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /Movie/Resources/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie/Resources/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /Movie/Resources/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie/Resources/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /Movie/Resources/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie/Resources/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /Movie/Resources/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie/Resources/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /Movie/Resources/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie/Resources/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /Movie/Resources/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie/Resources/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /Movie/Resources/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie/Resources/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /Movie/Resources/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie/Resources/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /Movie/Resources/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie/Resources/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /Movie/Resources/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie/Resources/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /Movie/Resources/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie/Resources/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/logo.imageset/MOVIE LOGO4 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie/Resources/Icons.xcassets/logo.imageset/MOVIE LOGO4 1.png -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/launchPhoto.imageset/launchPhoto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie/Resources/Icons.xcassets/launchPhoto.imageset/launchPhoto.png -------------------------------------------------------------------------------- /Movie.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Movie.xcodeproj/xcuserdata/siuzanna.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /Movie.xcodeproj/project.xcworkspace/xcuserdata/siuzanna.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siuzanna/Movie-MVVM/HEAD/Movie.xcodeproj/project.xcworkspace/xcuserdata/siuzanna.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Ignore macOS system files 3 | .DS_Store 4 | 5 | # Ignore build products 6 | /build/ 7 | *.xcarchive 8 | 9 | # Ignore Pods folder (for iOS projects using CocoaPods) 10 | /Pods/ 11 | 12 | # Ignore personal Xcode user data 13 | *.xcuserdatad/ -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | excluded: 2 | - Carthage 3 | - Pods 4 | 5 | disabled_rules: 6 | - trailing_whitespace 7 | - force_cast 8 | - switch_case_alignment 9 | - redundant_string_enum_value 10 | - closure_parameter_position 11 | 12 | line_length: 140 13 | 14 | #$ swiftlint autocorrect 15 | -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/Navigation/filter.imageset/filter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Movie/Models/Comments.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Comments.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 19/12/21. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Comments: Codable, Hashable { 11 | let id: Int? 12 | let name: String? 13 | let comment: String? 14 | let picture: String? 15 | } 16 | 17 | -------------------------------------------------------------------------------- /Movie/NetworkService/Network Components/HTTPMethods.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPMethods.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 19/12/21. 6 | // 7 | 8 | import Foundation 9 | 10 | enum HttpMethod: String { 11 | case GET 12 | case POST 13 | case PATCH 14 | case DELETE 15 | case PUT 16 | } 17 | -------------------------------------------------------------------------------- /Movie.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/Navigation/menu.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "menu.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/Tabbar/home.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "home.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/Tabbar/user.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "user.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/Navigation/filter.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "filter.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/Navigation/search.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "search.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/Tabbar/bookmark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "bookmark.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/Tabbar/explore.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "explore.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/launchPhoto.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "launchPhoto.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Movie/NetworkService/Network Components/NetworkErrors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkErrors.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 19/12/21. 6 | // 7 | 8 | import Foundation 9 | 10 | enum NetworkErrors: Error { 11 | case badRequest 12 | case unauthorized 13 | } 14 | 15 | enum StatusCode: Int { 16 | case okey = 200 17 | case badRequest = 400 18 | } 19 | -------------------------------------------------------------------------------- /Movie/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 17/12/21. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | func application( 14 | _ application: UIApplication, 15 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | return true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/gradient.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "gradient.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "MOVIE LOGO4 1.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Movie/Resources/Colors.xcassets/bg.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x29", 9 | "green" : "0x1A", 10 | "red" : "0x1C" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Movie/Resources/Colors.xcassets/icon2.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xDF", 9 | "green" : "0xDD", 10 | "red" : "0xDE" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Movie/Resources/Colors.xcassets/icons.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xA9", 9 | "green" : "0xA3", 10 | "red" : "0xA4" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Movie/Resources/Colors.xcassets/star.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x01", 9 | "green" : "0x96", 10 | "red" : "0xF9" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Movie/Resources/Colors.xcassets/tabbar.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x4B", 9 | "green" : "0x35", 10 | "red" : "0x38" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Movie/Resources/Colors.xcassets/title.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xDF", 9 | "green" : "0xDD", 10 | "red" : "0xDE" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Movie/Resources/Colors.xcassets/commetsView.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x35", 9 | "green" : "0x21", 10 | "red" : "0x24" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Movie/Resources/Colors.xcassets/logoRed.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x26", 9 | "green" : "0x26", 10 | "red" : "0xE8" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Movie/Resources/Colors.xcassets/statusBar.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x24", 9 | "green" : "0x14", 10 | "red" : "0x17" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/gradient.imageset/gradient.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/Navigation/menu.imageset/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Movie/Models/MovieDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Movies.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 19/12/21. 6 | // 7 | 8 | import Foundation 9 | 10 | struct MovieDTO: Codable, Hashable { 11 | let id: Int 12 | let type: Int 13 | let series: Bool 14 | let name: String 15 | let time: Int 16 | let genre: [String] 17 | let rating: String 18 | let votes: String 19 | let photo: String 20 | let miniPhoto: String 21 | let description: String 22 | let trailer: String 23 | let comments: [Comments] 24 | } 25 | -------------------------------------------------------------------------------- /Movie/Extension/UIKit/UIImage + Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage + Extension.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 17/12/21. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIImage { 11 | static func from(color: UIColor) -> UIImage { 12 | let rect = CGRect(x: 0, y: 0, width: 1, height: 1) 13 | UIGraphicsBeginImageContext(rect.size) 14 | let context = UIGraphicsGetCurrentContext() 15 | context!.setFillColor(color.cgColor) 16 | context!.fill(rect) 17 | let img = UIGraphicsGetImageFromCurrentImageContext() 18 | UIGraphicsEndImageContext() 19 | return img! 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Movie/MovieDetailsScreen/View/SectionDecorationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SectionDecorationView.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 20/12/21. 6 | // 7 | 8 | import UIKit 9 | 10 | class SectionBackgroundDecorationView: UICollectionReusableView { 11 | 12 | override init(frame: CGRect) { 13 | super.init(frame: frame) 14 | configure() 15 | } 16 | 17 | required init?(coder: NSCoder) { 18 | fatalError("init(coder:) has not been implemented") 19 | } 20 | 21 | private func configure() { 22 | backgroundColor = Colors.commentsView.color 23 | layer.cornerRadius = 12 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /swiftgen.yml: -------------------------------------------------------------------------------- 1 | 2 | # MARK: - Colors 3 | 4 | # xcassets: 5 | # inputs: Movie/Resources/Colors.xcassets 6 | # outputs: 7 | # templateName: swift5 8 | # params: 9 | # forceProvidesNamespaces: true 10 | # enumName: Colors 11 | # output: Movie/Resources/GeneratedColors.swift 12 | 13 | 14 | 15 | # MARK: - Icons 16 | 17 | # xcassets: 18 | # inputs: Movie/Resources/Icons.xcassets 19 | # outputs: 20 | # templateName: swift5 21 | # params: 22 | # forceProvidesNamespaces: true 23 | # enumName: Icons 24 | # output: Movie/Resources/GeneratedIcons.swift 25 | 26 | 27 | # Pods/SwiftGen/bin/swiftgen -- runs the pod 28 | -------------------------------------------------------------------------------- /Movie/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 17/12/21. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, 15 | options connectionOptions: UIScene.ConnectionOptions) { 16 | guard let windowScene = (scene as? UIWindowScene) else { return } 17 | let window = UIWindow(windowScene: windowScene) 18 | window.backgroundColor = UIColor.white 19 | window.rootViewController = TabBarController() 20 | window.makeKeyAndVisible() 21 | self.window = window 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/Navigation/search.imageset/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Movie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "6b6ce0f12bbd7c3a70ad4db9892c7afaef3ba38abf1df2e0ab744794af7d3b86", 3 | "pins" : [ 4 | { 5 | "identity" : "kingfisher", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/onevcat/Kingfisher.git", 8 | "state" : { 9 | "revision" : "2ef543ee21d63734e1c004ad6c870255e8716c50", 10 | "version" : "7.12.0" 11 | } 12 | }, 13 | { 14 | "identity" : "snapkit", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/SnapKit/SnapKit.git", 17 | "state" : { 18 | "revision" : "2842e6e84e82eb9a8dac0100ca90d9444b0307f4", 19 | "version" : "5.7.1" 20 | } 21 | } 22 | ], 23 | "version" : 3 24 | } 25 | -------------------------------------------------------------------------------- /Movie/NetworkService/Network Components/ProResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProResult.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 19/12/21. 6 | // 7 | 8 | import Foundation 9 | 10 | enum ProResult { 11 | case success(Success) 12 | case badRequest(FailureModel) 13 | case failure(String) 14 | } 15 | 16 | struct FailureModel: Codable { 17 | let statusCode: Int? 18 | let errors: [String]? 19 | 20 | enum CodingKeys: String, CodingKey { 21 | case statusCode 22 | case errors 23 | } 24 | 25 | init(from decoder: Decoder) throws { 26 | let values = try decoder.container(keyedBy: CodingKeys.self) 27 | statusCode = try? values.decodeIfPresent(Int.self, forKey: .statusCode) 28 | errors = try? values.decodeIfPresent([String].self, forKey: .errors) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Movie/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | UIApplicationSceneManifest 11 | 12 | UIApplicationSupportsMultipleScenes 13 | 14 | UISceneConfigurations 15 | 16 | UIWindowSceneSessionRoleApplication 17 | 18 | 19 | UISceneConfigurationName 20 | Default Configuration 21 | UISceneDelegateClassName 22 | $(PRODUCT_MODULE_NAME).SceneDelegate 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/Tabbar/bookmark.imageset/bookmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/Tabbar/user.imageset/user.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Movie/NetworkService/Base Router/MainScreenRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainScreenRouter.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 19/12/21. 6 | // 7 | 8 | import Foundation 9 | 10 | enum MainScreenRouter: BaseRouter { 11 | case getAllPosts 12 | 13 | var path: String { 14 | switch self { 15 | case .getAllPosts: 16 | return "/v3/cbd74aa0-318f-4a38-a4d6-9b1b99a275cf" 17 | } 18 | } 19 | 20 | var queryParameter: [URLQueryItem]? { 21 | switch self { 22 | case .getAllPosts: 23 | return nil 24 | } 25 | } 26 | 27 | var method: HttpMethod { 28 | switch self { 29 | case .getAllPosts: 30 | return .GET 31 | } 32 | } 33 | 34 | var httpBody: Data? { 35 | switch self { 36 | case .getAllPosts: 37 | return nil 38 | } 39 | } 40 | 41 | var httpHeader: [HttpHeader]? { 42 | switch self { 43 | case .getAllPosts: 44 | return nil 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Movie/MovieDetailsScreen/View/CommentsViewFooter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionReusableView.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 20/12/21. 6 | // 7 | 8 | import UIKit 9 | 10 | class CommentsViewFooter: UICollectionReusableView { 11 | 12 | private let buttonLabel: UILabel = { 13 | let text = UILabel() 14 | text.text = "See All >" 15 | text.textColor = UIColor(red: 0.467, green: 0.467, blue: 0.467, alpha: 1) 16 | text.font = UIFont.systemFont(ofSize: 15) 17 | return text 18 | }() 19 | 20 | override init(frame: CGRect) { 21 | super.init(frame: frame) 22 | configure() 23 | } 24 | 25 | required init?(coder: NSCoder) { 26 | fatalError("init(coder:) has not been implemented") 27 | } 28 | 29 | private func configure() { 30 | addSubview(buttonLabel) 31 | buttonLabel.snp.makeConstraints { make in 32 | make.center.equalToSuperview() 33 | } 34 | } 35 | 36 | func setTitle(_ text: String) { 37 | buttonLabel.text = text 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Movie/Extension/UIKit/UINavigationController + Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UINavigationController + Extension.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 17/12/21. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UINavigationController { 11 | 12 | func makeTransparent() -> UINavigationController { 13 | navigationBar.setBackgroundImage(UIImage(), for: .default) 14 | navigationBar.shadowImage = UIImage() 15 | view.backgroundColor = .clear 16 | let barButtonItemAppearance = UIBarButtonItem.appearance() 17 | barButtonItemAppearance.setTitleTextAttributes( 18 | [NSAttributedString.Key.foregroundColor: UIColor.clear], for: .normal) 19 | navigationItem.backButtonTitle = " " 20 | navigationBar.backItem?.title = " " 21 | navigationItem.backBarButtonItem?.title = " " 22 | navigationBar.tintColor = UIColor.black 23 | return self 24 | } 25 | 26 | func removeTitle() -> UINavigationController { 27 | navigationBar.topItem?.title = " " 28 | return self 29 | } 30 | 31 | func changeTintColor(to color: UIColor) -> UINavigationController { 32 | navigationBar.tintColor = color 33 | return self 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Movie/Extension/UIKit/UICollectionViewCell+Register+extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionViewCell+Register+extension.swift 3 | // Neo-Cafe 4 | // 5 | // Created by siuzanna on 11/11/21. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UICollectionView { 11 | func register(cell: T.Type) { 12 | register(T.self, forCellWithReuseIdentifier: T.reuseIdentifier) 13 | } 14 | 15 | func register(header: T.Type) { 16 | register(T.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: T.reuseIdentifier) 17 | } 18 | 19 | func register(footer: T.Type) { 20 | register(T.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: T.reuseIdentifier) 21 | } 22 | 23 | func dequeue(for indexPath: IndexPath) -> T { 24 | return dequeueReusableCell(withReuseIdentifier: T.reuseIdentifier, for: indexPath) as! T 25 | } 26 | 27 | func dequeue(for indexPath: IndexPath, kind: String) -> T { 28 | return dequeueReusableSupplementaryView( ofKind: kind, withReuseIdentifier: T.reuseIdentifier, for: indexPath) as! T 29 | } 30 | } 31 | 32 | extension UIView { 33 | static var reuseIdentifier: String { 34 | return String(describing: Self.self) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Movie/MainScreen/Cells/PhotoCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoCell.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 18/12/21. 6 | // 7 | 8 | import UIKit 9 | import SnapKit 10 | import Kingfisher 11 | 12 | final class PhotoCell: UICollectionViewCell { 13 | 14 | private lazy var imageView: UIImageView = { 15 | let image = UIImageView() 16 | image.layer.masksToBounds = true 17 | image.clipsToBounds = true 18 | image.layer.cornerRadius = 15 19 | image.contentMode = .scaleAspectFill 20 | image.backgroundColor = Colors.commentsView.color 21 | return image 22 | }() 23 | 24 | override init(frame: CGRect) { 25 | super.init(frame: frame) 26 | configure() 27 | } 28 | 29 | public func configureCell(model: MovieDTO?, invertImage: Bool = false) { 30 | let urlString = invertImage ? model?.photo : model?.miniPhoto 31 | if let url = urlString, let finalURL = URL(string: url) { 32 | imageView.kf.setImage(with: finalURL) 33 | } else { 34 | imageView.image = Icons.launchPhoto.image 35 | } 36 | } 37 | 38 | private func configure() { 39 | contentView.addSubview(imageView) 40 | imageView.snp.makeConstraints { make in 41 | make.edges.equalToSuperview() 42 | } 43 | } 44 | 45 | required init?(coder: NSCoder) { 46 | fatalError("init(coder:) has not been implemented") 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Movie/Extension/UIKit/ViewController + Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController + Extension.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 17/12/21. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIViewController { 11 | 12 | func embeddedInNavigationController() -> UINavigationController { 13 | return BaseNavigationController(rootViewController: self) 14 | } 15 | 16 | func setNavigationBarAppearance(style: UIBarStyle) { 17 | navigationController?.navigationBar.barStyle = style 18 | navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) 19 | navigationController?.navigationBar.shadowImage = UIImage() 20 | navigationController?.view.backgroundColor = .clear 21 | } 22 | 23 | func removeBackButtonTitle() { 24 | navigationItem.backButtonTitle = "" 25 | } 26 | 27 | func showAlert(withTitle title: String, message: String, buttonName: String = "OK") { 28 | let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) 29 | alertController.addAction(UIAlertAction(title: buttonName, style: .default)) 30 | present(alertController, animated: true) 31 | } 32 | } 33 | 34 | class BaseNavigationController: UINavigationController { 35 | 36 | override var preferredStatusBarStyle: UIStatusBarStyle { 37 | return .darkContent 38 | } 39 | 40 | override func viewDidLoad() { 41 | super.viewDidLoad() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Movie/MainScreen/View/HeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeaderView.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 18/12/21. 6 | // 7 | 8 | import UIKit 9 | import SnapKit 10 | 11 | final class HeaderView: UICollectionReusableView { 12 | 13 | public let label: UILabel = { 14 | let text = UILabel() 15 | text.textColor = .white 16 | text.font = UIFont.boldSystemFont(ofSize: 21) 17 | text.numberOfLines = 0 18 | return text 19 | }() 20 | 21 | public let buttonLabel: UILabel = { 22 | let text = UILabel() 23 | text.text = "See All >" 24 | text.textColor = UIColor(red: 0.467, green: 0.467, blue: 0.467, alpha: 1) 25 | text.font = UIFont.systemFont(ofSize: 15) 26 | return text 27 | }() 28 | 29 | override init(frame: CGRect) { 30 | super.init(frame: frame) 31 | configure() 32 | } 33 | 34 | required init?(coder: NSCoder) { 35 | fatalError("init(coder:) has not been implemented") 36 | } 37 | 38 | private func configure() { 39 | addSubview(label) 40 | addSubview(buttonLabel) 41 | label.snp.makeConstraints { make in 42 | make.bottom.top.equalToSuperview().inset(8) 43 | make.leading.trailing.equalToSuperview().inset(8) 44 | } 45 | buttonLabel.snp.makeConstraints { make in 46 | make.centerY.equalTo(label.snp.centerY) 47 | make.trailing.equalToSuperview().inset(16) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Movie.xcodeproj/xcuserdata/siuzanna.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Movie.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | SnapKitPlayground (Playground) 1.xcscheme 13 | 14 | isShown 15 | 16 | orderHint 17 | 2 18 | 19 | SnapKitPlayground (Playground) 2.xcscheme 20 | 21 | isShown 22 | 23 | orderHint 24 | 3 25 | 26 | SnapKitPlayground (Playground) 3.xcscheme 27 | 28 | isShown 29 | 30 | orderHint 31 | 4 32 | 33 | SnapKitPlayground (Playground) 4.xcscheme 34 | 35 | isShown 36 | 37 | orderHint 38 | 5 39 | 40 | SnapKitPlayground (Playground) 5.xcscheme 41 | 42 | isShown 43 | 44 | orderHint 45 | 6 46 | 47 | SnapKitPlayground (Playground).xcscheme 48 | 49 | isShown 50 | 51 | orderHint 52 | 1 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Movie/NetworkService/Base Router/BaseRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseRouter.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 19/12/21. 6 | // 7 | 8 | import Foundation 9 | 10 | struct HttpHeader { 11 | var field: String 12 | var value: String 13 | } 14 | 15 | protocol BaseRouter { 16 | var path: String { get } 17 | var queryParameter: [URLQueryItem]? { get } 18 | var method: HttpMethod { get } 19 | var httpBody: Data? { get } 20 | var httpHeader: [HttpHeader]? { get } 21 | } 22 | 23 | extension BaseRouter { 24 | 25 | var scheme: String { 26 | return "https" 27 | } 28 | 29 | var host: String { 30 | return "run.mocky.io" 31 | } 32 | 33 | func createURLRequest() -> URLRequest { 34 | var urlComponents = URLComponents() 35 | urlComponents.scheme = scheme 36 | urlComponents.host = host 37 | urlComponents.path = path 38 | urlComponents.queryItems = queryParameter 39 | guard let url = urlComponents.url else { 40 | fatalError(URLError(.unsupportedURL).localizedDescription) 41 | } 42 | var urlRequest = URLRequest(url: url) 43 | urlRequest.httpMethod = method.rawValue 44 | urlRequest.httpBody = httpBody 45 | httpHeader?.forEach { (header) in 46 | urlRequest.setValue(header.value, forHTTPHeaderField: header.field) 47 | } 48 | urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") 49 | urlRequest.setValue("application/json", forHTTPHeaderField: "Accept") 50 | 51 | return urlRequest 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Movie/MovieDetailsScreen/View/LableImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LableImage.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 19/12/21. 6 | // 7 | 8 | import UIKit 9 | 10 | class LableImage: UIView { 11 | 12 | private lazy var imageView: UIImageView = { 13 | let image = UIImageView() 14 | image.contentMode = .scaleAspectFill 15 | return image 16 | }() 17 | 18 | private lazy var nameLabel: UILabel = { 19 | let text = UILabel() 20 | text.textColor = Colors.title.color 21 | text.font = UIFont.boldSystemFont(ofSize: 13) 22 | text.numberOfLines = 0 23 | return text 24 | }() 25 | 26 | override init(frame: CGRect) { 27 | super.init(frame: frame) 28 | configure() 29 | } 30 | 31 | required init?(coder: NSCoder) { 32 | fatalError("init(coder:) has not been implemented") 33 | } 34 | 35 | private func configure() { 36 | addSubview(imageView) 37 | addSubview(nameLabel) 38 | imageView.snp.makeConstraints { make in 39 | make.leading.equalToSuperview() 40 | make.centerY.equalToSuperview() 41 | make.size.equalTo(16) 42 | } 43 | nameLabel.snp.makeConstraints { make in 44 | make.leading.equalTo(imageView.snp.trailing).inset(-5) 45 | make.centerY.equalToSuperview() 46 | } 47 | } 48 | 49 | func setUp(text: String, image: UIImage? = UIImage(), imageColor: UIColor) { 50 | nameLabel.text = text 51 | imageView.image = image 52 | imageView.tintColor = imageColor 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/Tabbar/home.imageset/home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Movie/Resources/Icons.xcassets/Tabbar/explore.imageset/explore.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Movie/MovieDetailsScreen/View/NavigationBarBack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationBarBack.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 19/12/21. 6 | // 7 | 8 | import UIKit 9 | 10 | class NavigationBarBack: UIView { 11 | 12 | private lazy var backButton: UIButton = { 13 | let button = UIButton() 14 | let config = UIImage.SymbolConfiguration(textStyle: .largeTitle, scale: .medium) 15 | button.setBackgroundImage(UIImage(systemName: "chevron.backward", withConfiguration: config)? 16 | .withTintColor(Colors.title.color, renderingMode: .alwaysOriginal), for: .normal) 17 | button.setTitleColor(.white, for: .normal) 18 | return button 19 | }() 20 | 21 | private lazy var saveButton: UIButton = { 22 | let button = UIButton() 23 | let config = UIImage.SymbolConfiguration(textStyle: .largeTitle, scale: .medium) 24 | button.setBackgroundImage(UIImage( systemName: "bookmark", withConfiguration: config)? 25 | .withTintColor(Colors.title.color, renderingMode: .alwaysOriginal), for: .normal) 26 | return button 27 | }() 28 | 29 | override func layoutSubviews() { 30 | super.layoutSubviews() 31 | setup() 32 | } 33 | 34 | private func setup() { 35 | setupSubviews() 36 | setupContstraints() 37 | } 38 | 39 | private func setupSubviews() { 40 | addSubview(backButton) 41 | addSubview(saveButton) 42 | } 43 | 44 | private func setupContstraints() { 45 | backButton.snp.makeConstraints { make in 46 | make.leading.equalToSuperview() 47 | make.centerY.equalToSuperview() 48 | } 49 | saveButton.snp.makeConstraints { make in 50 | make.trailing.equalToSuperview() 51 | make.centerY.equalToSuperview() 52 | } 53 | } 54 | 55 | func addBackButtonTarget(_ target: Any?, action: Selector, forEvent: UIControl.Event) { 56 | backButton.addTarget(target, action: action, for: forEvent) 57 | } 58 | 59 | func addSaveButtonTarget(_ target: Any?, action: Selector, forEvent: UIControl.Event) { 60 | backButton.addTarget(target, action: action, for: forEvent) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Movie/Tabbar/UITabBarController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITabBarController.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 17/12/21. 6 | // 7 | 8 | import UIKit 9 | 10 | class TabBarController: UITabBarController, UITabBarControllerDelegate { 11 | 12 | override func viewDidLoad() { 13 | viewControllers = TabBar.allCases.map {$0.viewController} 14 | tabBar.barTintColor = .white 15 | addCustomTabBarView() 16 | hideTabBarBorder() 17 | self.selectedIndex = 0 18 | self.delegate = self 19 | self.tabBar.tintColor = Colors.logoRed.color 20 | self.tabBar.unselectedItemTintColor = Colors.icons.color 21 | } 22 | 23 | let customTabBarView: UIView = { 24 | let view = UIView(frame: .zero) 25 | view.backgroundColor = Colors.tabbar.color 26 | view.layer.masksToBounds = true 27 | view.clipsToBounds = true 28 | return view 29 | }() 30 | 31 | override func viewDidLayoutSubviews() { 32 | super.viewDidLayoutSubviews() 33 | customTabBarView.frame = tabBar.frame 34 | } 35 | 36 | func addCustomTabBarView() { 37 | customTabBarView.frame = tabBar.frame 38 | view.addSubview(customTabBarView) 39 | view.bringSubviewToFront(self.tabBar) 40 | } 41 | 42 | func hideTabBarBorder() { 43 | let tabBar = self.tabBar 44 | tabBar.backgroundImage = UIImage.from(color: .clear) 45 | tabBar.shadowImage = UIImage() 46 | tabBar.clipsToBounds = true 47 | tabBar.layer.masksToBounds = false 48 | } 49 | } 50 | 51 | enum TabBar: String, CaseIterable { 52 | 53 | case home 54 | 55 | var viewController: UINavigationController { 56 | var viewController = UINavigationController() 57 | switch self { 58 | case .home: 59 | let viewModel = MainScreenViewModel() 60 | viewController = UINavigationController(rootViewController: MainScreenViewController(viewModel: viewModel)) 61 | } 62 | viewController.setNavigationBarHidden(true, animated: true) 63 | viewController.tabBarItem = tabBarItem 64 | viewController.tabBarItem.imageInsets.top = 5 65 | return viewController 66 | } 67 | 68 | var tabBarItem: UITabBarItem { 69 | switch self { 70 | case .home: 71 | return .init(title: nil, 72 | image: UIImage(named: TabBar.home.rawValue), 73 | selectedImage: UIImage(named: TabBar.home.rawValue)) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Movie/NetworkService/NetworkService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkService.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 19/12/21. 6 | // 7 | 8 | import Foundation 9 | 10 | class NetworkService { 11 | 12 | private let urlSession: URLSession 13 | required init(session: URLSession = URLSession.shared) { 14 | urlSession = session 15 | } 16 | 17 | func sendRequest( 18 | urlRequest: URLRequest, 19 | successModel: SuccessModel.Type, 20 | completion: @escaping (ProResult) -> Void 21 | ) { 22 | urlSession.dataTask(with: urlRequest) { [weak self] data, response, error in 23 | guard let self = self else { 24 | debugPrint("Your class is dead") 25 | return 26 | } 27 | if let error = self.validateErrors(data: data, response: response, error: error) { 28 | if case NetworkErrors.unauthorized = error { 29 | // here you can create an unauthorized error 30 | } else if case NetworkErrors.badRequest = error { 31 | if let model = self.transformFromJSON(data: data, objectType: FailureModel.self) { 32 | completion(.badRequest(model)) 33 | } else { 34 | completion(.failure("Check you JSON MODEL")) 35 | } 36 | } else { 37 | completion(.failure("Check you JSON MODEL")) 38 | } 39 | } else if let model = self.transformFromJSON(data: data, objectType: successModel) { 40 | completion(.success(model)) 41 | } else { 42 | completion(.failure("Some supernatural things happened, check api!!!")) 43 | } 44 | }.resume() 45 | } 46 | 47 | private func validateErrors(data: Data?, response: URLResponse?, error: Error?) -> Error? { 48 | if let error = error { return error } 49 | 50 | guard let statusCode = (response as? HTTPURLResponse)?.statusCode else { 51 | return URLError(.badServerResponse) 52 | } 53 | switch statusCode { 54 | case StatusCode.okey.rawValue: 55 | return nil 56 | default: 57 | return NetworkErrors.badRequest 58 | } 59 | } 60 | 61 | private func transformFromJSON( 62 | data: Data?, 63 | objectType: Model.Type 64 | ) -> Model? { 65 | 66 | guard let data = data else { 67 | print("no Data, no Data, no Data, no Data") 68 | return nil 69 | } 70 | do { 71 | return try JSONDecoder().decode(Model.self, from: data) 72 | } catch let error { 73 | print("Couldn't decode!!! Check you JSON MODEL", error) 74 | } 75 | return nil 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Movie/MainScreen/View/NavigationBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationBar.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 19/12/21. 6 | // 7 | 8 | import UIKit 9 | 10 | final class NavigationBar: UIView { 11 | 12 | private lazy var logoImageView: UIImageView = { 13 | let image = UIImageView() 14 | image.image = Icons.logo.image 15 | return image 16 | }() 17 | 18 | private lazy var searchButton: UIButton = { 19 | let button = UIButton() 20 | button.setBackgroundImage(Icons.Navigation.search.image 21 | .withTintColor(Colors.logoRed.color), for: .normal) 22 | button.setTitleColor(.white, for: .normal) 23 | return button 24 | }() 25 | 26 | private lazy var filterButton: UIButton = { 27 | let button = UIButton() 28 | button.setBackgroundImage(Icons.Navigation.filter.image 29 | .withTintColor(Colors.title.color), for: .normal) 30 | return button 31 | }() 32 | 33 | private lazy var menuButton: UIButton = { 34 | let button = UIButton() 35 | button.setBackgroundImage(Icons.Navigation.menu.image 36 | .withTintColor(Colors.title.color), for: .normal) 37 | return button 38 | }() 39 | 40 | override func layoutSubviews() { 41 | super.layoutSubviews() 42 | setup() 43 | } 44 | 45 | private func setup() { 46 | setupSubviews() 47 | setupContstraints() 48 | } 49 | 50 | private func setupSubviews() { 51 | addSubview(logoImageView) 52 | addSubview(searchButton) 53 | addSubview(filterButton) 54 | addSubview(menuButton) 55 | } 56 | 57 | private func setupContstraints() { 58 | logoImageView.snp.makeConstraints { make in 59 | make.leading.equalToSuperview() 60 | make.centerY.equalToSuperview() 61 | make.width.equalTo(73) 62 | make.height.equalTo(55) 63 | } 64 | menuButton.snp.makeConstraints { make in 65 | make.trailing.equalToSuperview() 66 | make.centerY.equalToSuperview() 67 | make.width.height.equalTo(27) 68 | } 69 | filterButton.snp.makeConstraints { make in 70 | make.trailing.equalTo(menuButton.snp.leading).inset(-20) 71 | make.centerY.equalToSuperview() 72 | make.width.height.equalTo(27) 73 | } 74 | searchButton.snp.makeConstraints { make in 75 | make.trailing.equalTo(filterButton.snp.leading).inset(-20) 76 | make.centerY.equalToSuperview() 77 | make.width.height.equalTo(27) 78 | } 79 | 80 | [menuButton, filterButton, searchButton].forEach { 81 | $0.addTarget(self, action: #selector(tapped(_:)), for: .touchUpInside) 82 | } 83 | } 84 | 85 | @objc private func tapped(_ sender: UIButton) { 86 | if sender == filterButton { 87 | } else if sender == searchButton { 88 | } else if sender == menuButton { 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Movie/Resources/GeneratedColors.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | // Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen 3 | 4 | #if os(macOS) 5 | import AppKit 6 | #elseif os(iOS) 7 | import UIKit 8 | #elseif os(tvOS) || os(watchOS) 9 | import UIKit 10 | #endif 11 | 12 | // Deprecated typealiases 13 | @available(*, deprecated, renamed: "ColorAsset.Color", message: "This typealias will be removed in SwiftGen 7.0") 14 | internal typealias AssetColorTypeAlias = ColorAsset.Color 15 | 16 | // swiftlint:disable superfluous_disable_command file_length implicit_return 17 | 18 | // MARK: - Asset Catalogs 19 | 20 | // swiftlint:disable identifier_name line_length nesting type_body_length type_name 21 | internal enum Colors { 22 | internal static let bg = ColorAsset(name: "bg") 23 | internal static let commentsView = ColorAsset(name: "commetsView") 24 | internal static let icon2 = ColorAsset(name: "icon2") 25 | internal static let icons = ColorAsset(name: "icons") 26 | internal static let logoRed = ColorAsset(name: "logoRed") 27 | internal static let star = ColorAsset(name: "star") 28 | internal static let statusBar = ColorAsset(name: "statusBar") 29 | internal static let tabbar = ColorAsset(name: "tabbar") 30 | internal static let title = ColorAsset(name: "title") 31 | } 32 | // swiftlint:enable identifier_name line_length nesting type_body_length type_name 33 | 34 | // MARK: - Implementation Details 35 | 36 | internal final class ColorAsset { 37 | internal fileprivate(set) var name: String 38 | 39 | #if os(macOS) 40 | internal typealias Color = NSColor 41 | #elseif os(iOS) || os(tvOS) || os(watchOS) 42 | internal typealias Color = UIColor 43 | #endif 44 | 45 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) 46 | internal private(set) lazy var color: Color = { 47 | guard let color = Color(asset: self) else { 48 | fatalError("Unable to load color asset named \(name).") 49 | } 50 | return color 51 | }() 52 | 53 | #if os(iOS) || os(tvOS) 54 | @available(iOS 11.0, tvOS 11.0, *) 55 | internal func color(compatibleWith traitCollection: UITraitCollection) -> Color { 56 | let bundle = BundleToken.bundle 57 | guard let color = Color(named: name, in: bundle, compatibleWith: traitCollection) else { 58 | fatalError("Unable to load color asset named \(name).") 59 | } 60 | return color 61 | } 62 | #endif 63 | 64 | fileprivate init(name: String) { 65 | self.name = name 66 | } 67 | } 68 | 69 | internal extension ColorAsset.Color { 70 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) 71 | convenience init?(asset: ColorAsset) { 72 | let bundle = BundleToken.bundle 73 | #if os(iOS) || os(tvOS) 74 | self.init(named: asset.name, in: bundle, compatibleWith: nil) 75 | #elseif os(macOS) 76 | self.init(named: NSColor.Name(asset.name), bundle: bundle) 77 | #elseif os(watchOS) 78 | self.init(named: asset.name) 79 | #endif 80 | } 81 | } 82 | 83 | // swiftlint:disable convenience_type 84 | private final class BundleToken { 85 | static let bundle: Bundle = { 86 | #if SWIFT_PACKAGE 87 | return Bundle.module 88 | #else 89 | return Bundle(for: BundleToken.self) 90 | #endif 91 | }() 92 | } 93 | // swiftlint:enable convenience_type 94 | -------------------------------------------------------------------------------- /Movie/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | {"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Movie [![wakatime](https://wakatime.com/badge/github/siuzanna/Movie.svg)](https://wakatime.com/badge/github/siuzanna/Movie) 2 | 3 | Buy Me A Coffee 4 | 5 | App shows you collections of TV streaming and other movies. Movie app written in Swift & UIKit using the [Custom API](https://www.mocky.io/v3/3feccf06-6bc1-480d-97af-bbc05d785f86) created on the Mocky website. App built using the MVVM architecture and 100% programmatic UI (No Storyboard). + NSDiffableDataSourceSnapshot, UICollectionViewDiffableDataSource 6 | 7 | ### Screen Shots 8 | ![Group1X](https://user-images.githubusercontent.com/64474454/146820234-84c70414-719a-4471-9513-8cc813a14bbe.png) 9 | --- 10 | 11 | ### Table of Contents 12 | 13 | - [Description](#description) 14 | - [Dependencies](#dependencies) 15 | - [Resources](#resources) 16 | - [How To Use](#how-to-use) 17 | - [Author Info](#author-info) 18 | 19 | --- 20 | 21 | ## Description 22 | 23 | - [X] App shows you collections of TV streaming and other movies. 24 | - [X] Layout created using a UICollectionviewCompositionalLayout. 25 | - [X] Project was completed using 100% programmatic UI (No Storyboard). 26 | - [X] App built using the MVVM architecture. 27 | - [X] This app includes descriptions for each movie as well as trailers and the movie’s rating. 28 | - [X] Movie also contains movies that are from paid apps such as Netflix. 29 | - [X] User can view movie details by tapping on a cell. 30 | - [x] All images are cached uising SDWebImage cocoapod. 31 | - [X] Movie details page contain backdrop and poster image, overview, duration and other relevant information. 32 | - [X] User can view trailer of a particular movie in the youtube app or a web browser. 33 | - [X] It also features the best movies that refresh weekly so you can choose and watch the latest movies that have the best ratings. 34 | 35 | ### Todo 36 | 37 | - [ ] Refresh API data - trailer ulr, description, comments, rating for each movie. 38 | 39 | ## Dependencies 40 | |#|Library|Description| 41 | |-|-|-| 42 | |1|[SwiftLint](https://github.com/realm/SwiftLint)|A tool to enforce Swift style and conventions. SwiftLint enforces the style guide rules that are generally accepted by the Swift community.| 43 | |2|[SwiftGen](https://github.com/SwiftGen/SwiftGen)|SwiftGen is a tool to automatically generate Swift code for resources of your projects (like images, localised strings, etc), to make them type-safe to use.| 44 | |3|[SDWebImage](https://github.com/SDWebImage/SDWebImage)|This library provides an async image downloader with cache support.| 45 | |4|[SnapKit](https://github.com/SnapKit/SnapKit)|SnapKit is a DSL allows building constraints with minimal amounts of code while ensuring they are easy to read and understand.| 46 | 47 | #### Frameworks 48 | 49 | - UIKit 50 | - WebKit 51 | 52 | ## Resources 53 | 54 | - Design - [Figma design](https://www.figma.com/community/file/988093088740037911/VOD-Platform-App) created by [Mehri Fekri](https://www.figma.com/community/file/988093088740037911/VOD-Platform-App) 55 | - API - [Link to Mocky](https://www.mocky.io/v3/3feccf06-6bc1-480d-97af-bbc05d785f86) 56 | 57 | ## How To Use 58 | 59 | - Clone the project and run it on Xcode 12 or above. 60 | - Open a terminal window, and $ cd into your project directory. 61 | - Run $ pod install. 62 | - Open the Movie.xcworkspace. 63 | 64 | [Back To The Top](#Movie) 65 | -------------------------------------------------------------------------------- /Movie/MainScreen/MainScreenViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainScreenViewModel.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 18/12/21. 6 | // 7 | 8 | import Foundation 9 | import UIKit.NSDiffableDataSourceSectionSnapshot 10 | 11 | protocol MainScreenViewModelDelegate: AnyObject { 12 | func updateCollectionView() 13 | func showError(text: String) 14 | } 15 | 16 | protocol MainScreenServiceProtocol { 17 | var delegate: MainScreenViewModelDelegate? { get set } 18 | var dataSource: UICollectionViewDiffableDataSource? { get set } 19 | 20 | func requestMovieList() 21 | func getMoviesBySection(_ indexPath: Int) -> [MovieDTO] 22 | func getMoviesBySection(_ section: Section) -> [MovieDTO] 23 | } 24 | 25 | final class MainScreenViewModel: MainScreenServiceProtocol { 26 | 27 | private let networkService: NetworkService 28 | private var movieList: [MovieDTO] 29 | 30 | public var dataSource: UICollectionViewDiffableDataSource? = nil 31 | weak var delegate: MainScreenViewModelDelegate? 32 | 33 | init(networkService: NetworkService = NetworkService()) { 34 | self.networkService = networkService 35 | self.movieList = [] 36 | } 37 | } 38 | 39 | extension MainScreenViewModel { 40 | 41 | func getByIndexPath(_ indexPath: IndexPath) -> MovieDTO { 42 | return movieList[indexPath.row] 43 | } 44 | 45 | func requestMovieList() { 46 | networkService.sendRequest( 47 | urlRequest: MainScreenRouter.getAllPosts.createURLRequest(), 48 | successModel: [MovieDTO].self 49 | ) { [weak self] (result) in 50 | guard let self else { return } 51 | switch result { 52 | case .success(let model): 53 | self.movieList = model 54 | self.delegate?.updateCollectionView() 55 | case .badRequest(let error): 56 | self.delegate?.showError(text: error.errors?.first ?? "Error occurred") 57 | debugPrint(#function, error) 58 | self.fetchLocalJsonMovies() 59 | case .failure(let error): 60 | self.delegate?.showError(text: error) 61 | debugPrint(#function, error) 62 | self.fetchLocalJsonMovies() 63 | } 64 | } 65 | } 66 | 67 | private func fetchLocalJsonMovies() { 68 | guard let path = Bundle.main.path(forResource: "movies", ofType: "json") else { 69 | debugPrint("Local JSON file 'move.json' not found.") 70 | return 71 | } 72 | 73 | do { 74 | let data = try Data(contentsOf: URL(fileURLWithPath: path)) 75 | let localMovies = try JSONDecoder().decode([MovieDTO].self, from: data) 76 | self.movieList = localMovies 77 | self.delegate?.updateCollectionView() 78 | } catch { 79 | debugPrint("Error decoding local JSON file: \(error)") 80 | self.delegate?.showError(text: "Failed to load data from the local file") 81 | } 82 | } 83 | 84 | func getMoviesBySection(_ indexPath: Int) -> [MovieDTO] { 85 | return movieList.filter { $0.type == indexPath } 86 | } 87 | 88 | func getMoviesBySection(_ section: Section) -> [MovieDTO] { 89 | switch section { 90 | case .topSlide: 91 | getMoviesBySection(0) 92 | case .mostPopular: 93 | getMoviesBySection(1) 94 | case .comingSoon: 95 | getMoviesBySection(2) 96 | case .lastUpdate: 97 | getMoviesBySection(3) 98 | case .bestSeries: 99 | getMoviesBySection(4) 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Movie/Resources/GeneratedIcons.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | // Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen 3 | 4 | #if os(macOS) 5 | import AppKit 6 | #elseif os(iOS) 7 | import UIKit 8 | #elseif os(tvOS) || os(watchOS) 9 | import UIKit 10 | #endif 11 | 12 | // Deprecated typealiases 13 | @available(*, deprecated, renamed: "ImageAsset.Image", message: "This typealias will be removed in SwiftGen 7.0") 14 | internal typealias AssetImageTypeAlias = ImageAsset.Image 15 | 16 | // swiftlint:disable superfluous_disable_command file_length implicit_return 17 | 18 | // MARK: - Asset Catalogs 19 | 20 | // swiftlint:disable identifier_name line_length nesting type_body_length type_name 21 | internal enum Icons { 22 | internal enum Navigation { 23 | internal static let filter = ImageAsset(name: "filter") 24 | internal static let menu = ImageAsset(name: "menu") 25 | internal static let search = ImageAsset(name: "search") 26 | } 27 | internal enum Tabbar { 28 | internal static let bookmark = ImageAsset(name: "bookmark") 29 | internal static let explore = ImageAsset(name: "explore") 30 | internal static let home = ImageAsset(name: "home") 31 | internal static let user = ImageAsset(name: "user") 32 | } 33 | internal static let gradient = ImageAsset(name: "gradient") 34 | internal static let launchPhoto = ImageAsset(name: "launchPhoto") 35 | internal static let logo = ImageAsset(name: "logo") 36 | } 37 | // swiftlint:enable identifier_name line_length nesting type_body_length type_name 38 | 39 | // MARK: - Implementation Details 40 | 41 | internal struct ImageAsset { 42 | internal fileprivate(set) var name: String 43 | 44 | #if os(macOS) 45 | internal typealias Image = NSImage 46 | #elseif os(iOS) || os(tvOS) || os(watchOS) 47 | internal typealias Image = UIImage 48 | #endif 49 | 50 | @available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *) 51 | internal var image: Image { 52 | let bundle = BundleToken.bundle 53 | #if os(iOS) || os(tvOS) 54 | let image = Image(named: name, in: bundle, compatibleWith: nil) 55 | #elseif os(macOS) 56 | let name = NSImage.Name(self.name) 57 | let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name) 58 | #elseif os(watchOS) 59 | let image = Image(named: name) 60 | #endif 61 | guard let result = image else { 62 | fatalError("Unable to load image asset named \(name).") 63 | } 64 | return result 65 | } 66 | 67 | #if os(iOS) || os(tvOS) 68 | @available(iOS 8.0, tvOS 9.0, *) 69 | internal func image(compatibleWith traitCollection: UITraitCollection) -> Image { 70 | let bundle = BundleToken.bundle 71 | guard let result = Image(named: name, in: bundle, compatibleWith: traitCollection) else { 72 | fatalError("Unable to load image asset named \(name).") 73 | } 74 | return result 75 | } 76 | #endif 77 | } 78 | 79 | internal extension ImageAsset.Image { 80 | @available(iOS 8.0, tvOS 9.0, watchOS 2.0, *) 81 | @available(macOS, deprecated, 82 | message: "This initializer is unsafe on macOS, please use the ImageAsset.image property") 83 | convenience init?(asset: ImageAsset) { 84 | #if os(iOS) || os(tvOS) 85 | let bundle = BundleToken.bundle 86 | self.init(named: asset.name, in: bundle, compatibleWith: nil) 87 | #elseif os(macOS) 88 | self.init(named: NSImage.Name(asset.name)) 89 | #elseif os(watchOS) 90 | self.init(named: asset.name) 91 | #endif 92 | } 93 | } 94 | 95 | // swiftlint:disable convenience_type 96 | private final class BundleToken { 97 | static let bundle: Bundle = { 98 | #if SWIFT_PACKAGE 99 | return Bundle.module 100 | #else 101 | return Bundle(for: BundleToken.self) 102 | #endif 103 | }() 104 | } 105 | // swiftlint:enable convenience_type 106 | -------------------------------------------------------------------------------- /Movie/MovieDetailsScreen/Cells/CommentsCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommentsCell.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 20/12/21. 6 | // 7 | 8 | import UIKit 9 | 10 | class CommentsCell: UICollectionViewCell { 11 | 12 | public lazy var pictureView: UIImageView = { 13 | let image = UIImageView() 14 | image.layer.masksToBounds = true 15 | image.clipsToBounds = true 16 | image.image = UIImage(systemName: "person.circle.fill") 17 | image.layer.cornerRadius = image.frame.height/2 18 | image.contentMode = .scaleAspectFill 19 | image.tintColor = Colors.title.color 20 | return image 21 | }() 22 | 23 | private lazy var nameLabel: UILabel = { 24 | let text = UILabel() 25 | text.textColor = Colors.title.color 26 | text.font = UIFont.systemFont(ofSize: 13, weight: .bold) 27 | text.numberOfLines = 1 28 | return text 29 | }() 30 | 31 | private lazy var commentLabel: UILabel = { 32 | let text = UILabel() 33 | text.textColor = Colors.title.color 34 | text.font = UIFont.systemFont(ofSize: 11, weight: .light) 35 | text.numberOfLines = 0 36 | return text 37 | }() 38 | 39 | private lazy var thumbsupButton: UIButton = { 40 | let button = UIButton() 41 | button.titleLabel?.font = UIFont.systemFont(ofSize: 14) 42 | button.tintColor = Colors.title.color 43 | button.setImage(UIImage(systemName: "hand.thumbsup"), for: .normal) 44 | return button 45 | }() 46 | 47 | private lazy var thumbsdownButton: UIButton = { 48 | let button = UIButton() 49 | button.titleLabel?.font = UIFont.systemFont(ofSize: 14) 50 | button.tintColor = Colors.title.color 51 | button.setImage(UIImage(systemName: "hand.thumbsdown"), for: .normal) 52 | return button 53 | }() 54 | 55 | let seperatorView = UIView() 56 | let containerView = UIView() 57 | 58 | var cellViewModel: Comments? { 59 | didSet { 60 | nameLabel.text = cellViewModel?.name 61 | commentLabel.text = cellViewModel?.comment 62 | } 63 | } 64 | 65 | override init(frame: CGRect) { 66 | super.init(frame: frame) 67 | configure() 68 | } 69 | 70 | required init?(coder: NSCoder) { 71 | fatalError("init(coder:) has not been implemented") 72 | } 73 | 74 | func configure() { 75 | containerView.addSubview(nameLabel) 76 | containerView.addSubview(commentLabel) 77 | containerView.addSubview(pictureView) 78 | containerView.addSubview(seperatorView) 79 | containerView.addSubview(thumbsupButton) 80 | containerView.addSubview(thumbsdownButton) 81 | contentView.addSubview(containerView) 82 | seperatorView.backgroundColor = .lightGray 83 | 84 | containerView.snp.makeConstraints { make in 85 | make.height.equalTo(70) 86 | make.top.equalToSuperview().offset(15) 87 | make.leading.trailing.equalToSuperview().inset(15) 88 | } 89 | pictureView.snp.makeConstraints { make in 90 | make.top.equalToSuperview() 91 | make.size.equalTo(25) 92 | make.leading.equalToSuperview().inset(15) 93 | } 94 | nameLabel.snp.makeConstraints { make in 95 | make.leading.equalTo(pictureView.snp.trailing).offset(10) 96 | make.centerY.equalTo(pictureView) 97 | } 98 | thumbsupButton.snp.makeConstraints { make in 99 | make.trailing.equalToSuperview().inset(10) 100 | make.centerY.equalTo(pictureView) 101 | } 102 | thumbsdownButton.snp.makeConstraints { make in 103 | make.trailing.equalTo(thumbsupButton.snp.leading).inset(-10) 104 | make.centerY.equalTo(pictureView) 105 | } 106 | commentLabel.snp.makeConstraints { make in 107 | make.top.equalTo(pictureView.snp.bottom).offset(5) 108 | make.leading.trailing.equalToSuperview().inset(15) 109 | } 110 | seperatorView.snp.makeConstraints { make in 111 | make.height.equalTo(0.5) 112 | make.bottom.equalToSuperview() 113 | make.leading.trailing.equalToSuperview().inset(15) 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Movie/Resources/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Movie/MainScreen/MainScreenViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainScreenViewController.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 17/12/21. 6 | // 7 | 8 | import UIKit 9 | import SnapKit 10 | 11 | enum Section: String, CaseIterable { 12 | case topSlide = "" 13 | case mostPopular = "Most Popular" 14 | case comingSoon = "Coming Soon" 15 | case lastUpdate = "Last Updated" 16 | case bestSeries = "Best Series" 17 | } 18 | 19 | final class MainScreenViewController: UIViewController { 20 | typealias Snapshot = NSDiffableDataSourceSnapshot 21 | 22 | private var viewModel: MainScreenServiceProtocol 23 | private lazy var contentView = Mainview() 24 | 25 | init(viewModel: MainScreenServiceProtocol) { 26 | self.viewModel = viewModel 27 | super.init(nibName: nil, bundle: nil) 28 | } 29 | 30 | override func loadView() { 31 | view = contentView 32 | } 33 | 34 | override func viewDidLoad() { 35 | super.viewDidLoad() 36 | contentView.collectionView.delegate = self 37 | viewModel.delegate = self 38 | 39 | contentView.activityControl.startAnimating() 40 | configureDataSource() 41 | DispatchQueue.global().async { 42 | self.viewModel.requestMovieList() 43 | } 44 | } 45 | 46 | required init?(coder: NSCoder) { 47 | fatalError("init(coder:) has not been implemented") 48 | } 49 | } 50 | 51 | extension MainScreenViewController: MainScreenViewModelDelegate { 52 | 53 | func updateCollectionView() { 54 | DispatchQueue.main.async { 55 | let snapshot = self.snapshot() 56 | self.viewModel.dataSource?.apply(snapshot, animatingDifferences: true) 57 | self.contentView.activityControl.stopAnimating() 58 | } 59 | } 60 | 61 | func showError(text: String) { 62 | DispatchQueue.main.async { 63 | self.showAlert(withTitle: "Error occurred", message: text) 64 | self.contentView.activityControl.stopAnimating() 65 | } 66 | } 67 | } 68 | 69 | extension MainScreenViewController { 70 | 71 | func configureDataSource() { 72 | viewModel.dataSource = UICollectionViewDiffableDataSource(collectionView: contentView.collectionView) { 73 | (collectionView: UICollectionView, indexPath: IndexPath, item: MovieDTO) -> UICollectionViewCell? in 74 | let sectionType = Section.allCases[indexPath.section] 75 | let cell: PhotoCell = collectionView.dequeue(for: indexPath) 76 | switch sectionType { 77 | case .topSlide, .comingSoon: 78 | cell.configureCell(model: item, invertImage: true) 79 | case .mostPopular, .lastUpdate, .bestSeries: 80 | cell.configureCell(model: item) 81 | } 82 | return cell 83 | } 84 | 85 | viewModel.dataSource?.supplementaryViewProvider = { 86 | ( collectionView: UICollectionView, kind: String, indexPath: IndexPath) -> UICollectionReusableView? in 87 | 88 | let sectionType = Section.allCases[indexPath.section] 89 | switch sectionType { 90 | case .topSlide: 91 | let header: UICollectionReusableView = collectionView.dequeue(for: indexPath, kind: kind) 92 | return header 93 | case .comingSoon: 94 | let header: HeaderView = collectionView.dequeue(for: indexPath, kind: kind) 95 | header.label.text = Section.allCases[indexPath.section].rawValue 96 | header.buttonLabel.text = "" 97 | return header 98 | case .mostPopular, .lastUpdate, .bestSeries: 99 | let header: HeaderView = collectionView.dequeue(for: indexPath, kind: kind) 100 | header.label.text = Section.allCases[indexPath.section].rawValue 101 | return header 102 | } 103 | } 104 | 105 | let snapshot = snapshot() 106 | viewModel.dataSource?.apply(snapshot, animatingDifferences: true) 107 | } 108 | 109 | func snapshot() -> Snapshot { 110 | var snapshot = Snapshot() 111 | 112 | let sections: [Section] = [.topSlide, .mostPopular, .comingSoon, .lastUpdate, .bestSeries] 113 | 114 | for section in sections { 115 | let items = viewModel.getMoviesBySection(section) 116 | if !items.isEmpty { 117 | snapshot.appendSections([section]) 118 | if section == .comingSoon { 119 | snapshot.appendItems(items.suffix(1), toSection: section) 120 | } else { 121 | snapshot.appendItems(items, toSection: section) 122 | } 123 | } 124 | } 125 | 126 | return snapshot 127 | } 128 | } 129 | 130 | extension MainScreenViewController: UICollectionViewDelegate { 131 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 132 | let model: MovieDTO = viewModel.getMoviesBySection(indexPath.section)[indexPath.row] 133 | let viewController = MovieDetailedScreenViewController(viewModel: model) 134 | viewController.modalPresentationStyle = .overCurrentContext 135 | present(viewController, animated: true, completion: nil) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Movie/MainScreen/MainContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainContentView.swift 3 | // Movie 4 | // 5 | // Created by Siuzanna Karagulova on 4/3/24. 6 | // 7 | 8 | import UIKit 9 | import SnapKit 10 | 11 | final class Mainview: UIView { 12 | 13 | lazy var navigationBar = NavigationBar() 14 | lazy var collectionView: UICollectionView = configureCollectionView() 15 | lazy var activityControl = UIActivityIndicatorView(style: .large) 16 | 17 | override init(frame: CGRect) { 18 | super.init(frame: frame) 19 | setup() 20 | activityControl.color = .white 21 | backgroundColor = Colors.bg.color 22 | } 23 | 24 | required init?(coder: NSCoder) { 25 | fatalError("init(coder:) has not been implemented") 26 | } 27 | } 28 | 29 | extension Mainview { 30 | private func setup() { 31 | setUpSubviews() 32 | setUpConstraints() 33 | } 34 | 35 | private func setUpSubviews() { 36 | addSubview(navigationBar) 37 | addSubview(activityControl) 38 | addSubview(collectionView) 39 | } 40 | 41 | private func setUpConstraints() { 42 | navigationBar.snp.makeConstraints { make in 43 | make.top.equalTo(safeAreaLayoutGuide.snp.top) 44 | make.leading.trailing.equalToSuperview().inset(16) 45 | make.height.equalTo(92) 46 | } 47 | 48 | activityControl.snp.makeConstraints { make in 49 | make.center.equalToSuperview() 50 | } 51 | 52 | collectionView.snp.makeConstraints { make in 53 | make.top.equalTo(safeAreaLayoutGuide.snp.top).offset(85) 54 | make.leading.trailing.bottom.equalToSuperview() 55 | } 56 | } 57 | } 58 | 59 | extension Mainview { 60 | func configureCollectionView() -> UICollectionView { 61 | let collectionView = UICollectionView(frame: .zero, collectionViewLayout: generateLayout()) 62 | collectionView.autoresizingMask = [.flexibleHeight, .flexibleWidth] 63 | collectionView.backgroundColor = .clear 64 | collectionView.showsVerticalScrollIndicator = false 65 | collectionView.showsHorizontalScrollIndicator = false 66 | collectionView.contentInset.bottom = 20 67 | collectionView.contentInset.top = 20 68 | collectionView.register(cell: PhotoCell.self) 69 | collectionView.register(header: HeaderView.self) 70 | return collectionView 71 | } 72 | 73 | func generateLayout() -> UICollectionViewLayout { 74 | let layout = UICollectionViewCompositionalLayout { 75 | (sectionIndex: Int, _: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in 76 | 77 | let sectionLayoutKind = Section.allCases[sectionIndex] 78 | switch sectionLayoutKind { 79 | case .topSlide: return self.generateTopSlideLayout() 80 | case .mostPopular: return self.generateCustomLayout() 81 | case .comingSoon: return self.generateComingSoonLayout() 82 | case .lastUpdate: return self.generateCustomLayout() 83 | case .bestSeries: return self.generateCustomLayout() 84 | } 85 | } 86 | return layout 87 | } 88 | 89 | func generateTopSlideLayout() -> NSCollectionLayoutSection { 90 | let itemSize = NSCollectionLayoutSize( 91 | widthDimension: .fractionalWidth(1), 92 | heightDimension: .fractionalHeight(1)) 93 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 94 | item.contentInsets = NSDirectionalEdgeInsets( 95 | top: 0, leading: 4, bottom: 0, trailing: 4) 96 | 97 | let groupSize = NSCollectionLayoutSize( 98 | widthDimension: .fractionalWidth(0.78), 99 | heightDimension: .fractionalWidth(0.576)) 100 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) 101 | group.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 4, bottom: 0, trailing: 4) 102 | 103 | let section = NSCollectionLayoutSection(group: group) 104 | section.orthogonalScrollingBehavior = .continuous 105 | section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 12) 106 | section.orthogonalScrollingBehavior = .continuous 107 | 108 | return section 109 | } 110 | 111 | func generateCustomLayout() -> NSCollectionLayoutSection { 112 | let itemSize = NSCollectionLayoutSize( 113 | widthDimension: .fractionalWidth(1), 114 | heightDimension: .fractionalHeight(1)) 115 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 116 | item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 4, bottom: 0, trailing: 4) 117 | 118 | let groupSize = NSCollectionLayoutSize( 119 | widthDimension: .fractionalWidth(0.43), 120 | heightDimension: .fractionalWidth(0.533)) 121 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) 122 | group.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 2, bottom: 0, trailing: 2) 123 | 124 | let headerSize = NSCollectionLayoutSize( 125 | widthDimension: .fractionalWidth(1.0), 126 | heightDimension: .absolute(59)) 127 | let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem( 128 | layoutSize: headerSize, 129 | elementKind: UICollectionView.elementKindSectionHeader, 130 | alignment: .top) 131 | sectionHeader.zIndex = 2 132 | 133 | let section = NSCollectionLayoutSection(group: group) 134 | section.orthogonalScrollingBehavior = .continuous 135 | section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 12) 136 | section.boundarySupplementaryItems = [sectionHeader] 137 | section.orthogonalScrollingBehavior = .continuous 138 | 139 | return section 140 | } 141 | 142 | func generateComingSoonLayout() -> NSCollectionLayoutSection { 143 | let itemSize = NSCollectionLayoutSize( 144 | widthDimension: .fractionalWidth(1), 145 | heightDimension: .fractionalHeight(1)) 146 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 147 | 148 | let groupSize = NSCollectionLayoutSize( 149 | widthDimension: .fractionalWidth(1), 150 | heightDimension: .fractionalWidth(0.426)) 151 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1) 152 | 153 | let headerSize = NSCollectionLayoutSize( 154 | widthDimension: .fractionalWidth(1.0), 155 | heightDimension: .absolute(59)) 156 | let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem( 157 | layoutSize: headerSize, 158 | elementKind: UICollectionView.elementKindSectionHeader, 159 | alignment: .top) 160 | sectionHeader.zIndex = 2 161 | 162 | let section = NSCollectionLayoutSection(group: group) 163 | section.orthogonalScrollingBehavior = .continuous 164 | section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 20) 165 | section.boundarySupplementaryItems = [sectionHeader] 166 | section.orthogonalScrollingBehavior = .none 167 | 168 | return section 169 | } 170 | } 171 | 172 | -------------------------------------------------------------------------------- /Movie/MovieDetailsScreen/View/TopView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TopView.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 19/12/21. 6 | // 7 | 8 | import UIKit 9 | import WebKit 10 | import Kingfisher 11 | import SnapKit 12 | 13 | final class TopView: UICollectionReusableView { 14 | 15 | private lazy var imageView: UIImageView = { 16 | let image = UIImageView() 17 | image.contentMode = .scaleAspectFill 18 | return image 19 | }() 20 | 21 | private lazy var gradientView: UIImageView = { 22 | let image = UIImageView() 23 | image.contentMode = .scaleAspectFill 24 | image.image = Icons.gradient.image 25 | return image 26 | }() 27 | 28 | /// stats View 29 | private lazy var posterView: UIImageView = { 30 | let image = UIImageView() 31 | image.layer.masksToBounds = true 32 | image.clipsToBounds = true 33 | image.layer.cornerRadius = 15 34 | image.contentMode = .scaleAspectFill 35 | return image 36 | }() 37 | 38 | private lazy var statsView: UIStackView = { 39 | let stackView = UIStackView(arrangedSubviews: [nameLabel, timeLabel, ganreLabel, starsLabel, ratingLabel]) 40 | stackView.axis = NSLayoutConstraint.Axis.vertical 41 | stackView.distribution = UIStackView.Distribution.fillEqually 42 | stackView.alignment = UIStackView.Alignment.leading 43 | stackView.spacing = 8 44 | return stackView 45 | }() 46 | 47 | private lazy var nameLabel: UILabel = { 48 | let text = UILabel() 49 | text.textColor = .white 50 | text.font = UIFont.boldSystemFont(ofSize: 21) 51 | text.numberOfLines = 0 52 | return text 53 | }() 54 | 55 | private lazy var timeLabel: UILabel = { 56 | let text = UILabel() 57 | text.textColor = .white 58 | text.font = UIFont.boldSystemFont(ofSize: 13) 59 | text.numberOfLines = 0 60 | return text 61 | }() 62 | 63 | private lazy var ganreLabel: UILabel = { 64 | let text = UILabel() 65 | text.textColor = .white 66 | text.font = UIFont.boldSystemFont(ofSize: 13) 67 | text.numberOfLines = 0 68 | return text 69 | }() 70 | 71 | private lazy var starsLabel: LableImage = { 72 | let text = LableImage() 73 | return text 74 | }() 75 | 76 | private lazy var ratingLabel: LableImage = { 77 | let text = LableImage() 78 | return text 79 | }() 80 | 81 | private lazy var button: UIButton = { 82 | let button = UIButton() 83 | button.layer.cornerRadius = 8 84 | button.titleLabel?.font = UIFont.systemFont(ofSize: 14) 85 | button.setTitleColor(.white, for: .normal) 86 | button.setTitle("Watch Now", for: .normal) 87 | button.backgroundColor = .red 88 | return button 89 | }() 90 | 91 | /// stats View 92 | private lazy var descriptionLabel: UILabel = { 93 | let text = UILabel() 94 | text.textColor = .white 95 | text.font = UIFont.systemFont(ofSize: 13) 96 | text.numberOfLines = 0 97 | return text 98 | }() 99 | 100 | private lazy var trailerLabel: UILabel = { 101 | let text = UILabel() 102 | text.textColor = .white 103 | text.text = "Trailer" 104 | text.font = UIFont.boldSystemFont(ofSize: 21) 105 | text.numberOfLines = 0 106 | return text 107 | }() 108 | 109 | private lazy var trailerView: UIView = { 110 | var view = UIView() 111 | view.backgroundColor = .black 112 | view.layer.cornerRadius = 15 113 | view.clipsToBounds = true 114 | return view 115 | }() 116 | 117 | private lazy var commentsLabel: UILabel = { 118 | let text = UILabel() 119 | text.textColor = .white 120 | text.text = "Comments" 121 | text.font = UIFont.boldSystemFont(ofSize: 21) 122 | text.numberOfLines = 0 123 | return text 124 | }() 125 | 126 | private var webView = WKWebView() 127 | 128 | override init(frame: CGRect) { 129 | super.init(frame: frame) 130 | configure() 131 | configureTrailerView() 132 | } 133 | 134 | required init?(coder: NSCoder) { 135 | fatalError("init(coder:) has not been implemented") 136 | } 137 | 138 | var cellViewModel: MovieDTO? { 139 | didSet { 140 | if let url = cellViewModel?.photo { 141 | self.imageView.kf.setImage(with: URL(string: url)) 142 | } 143 | if let url = cellViewModel?.miniPhoto { 144 | self.posterView.kf.setImage(with: URL(string: url)) 145 | } 146 | if let url = cellViewModel?.trailer { 147 | let webConfiguration = WKWebViewConfiguration() 148 | webConfiguration.allowsInlineMediaPlayback = true 149 | 150 | DispatchQueue.main.asyncAfter(deadline: .now() + 2) { 151 | self.webView = WKWebView(frame: self.trailerView.bounds, configuration: webConfiguration) 152 | self.trailerView.addSubview(self.webView) 153 | guard let videoURL = URL(string: url) else { return } 154 | let request = URLRequest(url: videoURL) 155 | self.webView.load(request) 156 | } 157 | } 158 | if let name = cellViewModel?.name, 159 | let time = cellViewModel?.time, 160 | let description = cellViewModel?.description, 161 | let genres = cellViewModel?.genre, 162 | let stars = cellViewModel?.rating, 163 | let votes = cellViewModel?.votes { 164 | 165 | self.nameLabel.text = name 166 | self.timeLabel.text = "\(time / 60) hour \(time / 360) minute(s)" 167 | self.descriptionLabel.text = description 168 | var string = "" 169 | for genre in genres { 170 | string += "\(genre), " 171 | } 172 | string.removeLast() 173 | string.removeLast() 174 | self.ganreLabel.text = string 175 | self.starsLabel.setUp( 176 | text: stars, 177 | image: UIImage(systemName: "star.fill"), 178 | imageColor: Colors.star.color) 179 | self.ratingLabel.setUp( 180 | text: "\(votes)% from users", 181 | image: UIImage(systemName: "hand.thumbsup"), 182 | imageColor: Colors.title.color) 183 | } 184 | } 185 | } 186 | 187 | func configure() { 188 | addSubview(imageView) 189 | addSubview(gradientView) 190 | addSubview(statsView) 191 | addSubview(button) 192 | addSubview(posterView) 193 | imageView.snp.makeConstraints { make in 194 | make.top.leading.trailing.equalToSuperview() 195 | make.height.equalTo(imageView.snp.width).multipliedBy(0.96) 196 | } 197 | gradientView.snp.makeConstraints { make in 198 | make.top.leading.trailing.equalToSuperview() 199 | make.height.equalTo(imageView.snp.width).multipliedBy(0.96) 200 | } 201 | button.snp.makeConstraints { make in 202 | make.bottom.equalTo(posterView.snp.bottom) 203 | make.height.equalTo(30) 204 | make.width.equalTo(160) 205 | make.leading.equalToSuperview() 206 | } 207 | statsView.snp.makeConstraints { make in 208 | make.leading.equalToSuperview() 209 | make.bottom.equalTo((button.snp.top)).inset(-20) 210 | make.height.equalTo(120) 211 | make.width.equalToSuperview().multipliedBy(0.4) 212 | } 213 | posterView.snp.makeConstraints { make in 214 | make.bottom.equalTo(imageView.snp.bottom).offset(10) 215 | make.height.equalTo(posterView.snp.width).multipliedBy(1.2) 216 | make.width.equalToSuperview().multipliedBy(0.45) 217 | make.trailing.equalToSuperview() 218 | } 219 | } 220 | 221 | func configureTrailerView() { 222 | addSubview(trailerLabel) 223 | addSubview(trailerView) 224 | addSubview(descriptionLabel) 225 | addSubview(commentsLabel) 226 | webView.backgroundColor = .black 227 | descriptionLabel.snp.makeConstraints { make in 228 | make.top.equalTo(imageView.snp.bottom).offset(30) 229 | make.leading.trailing.equalToSuperview() 230 | } 231 | trailerLabel.snp.makeConstraints { make in 232 | make.top.equalTo(descriptionLabel.snp.bottom).offset(20) 233 | make.leading.trailing.equalToSuperview() 234 | } 235 | trailerView.snp.makeConstraints { make in 236 | make.top.equalTo(trailerLabel.snp.bottom).offset(10) 237 | make.leading.trailing.equalToSuperview() 238 | make.height.equalTo(trailerView.snp.width).multipliedBy(0.5) 239 | } 240 | commentsLabel.snp.makeConstraints { make in 241 | make.top.equalTo(trailerView.snp.bottom).offset(20) 242 | make.leading.trailing.equalToSuperview() 243 | make.bottom.equalToSuperview().inset(10) 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /Movie/MovieDetailsScreen/MovieDetailedScreenViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieDetailedScreenViewController.swift 3 | // Movie 4 | // 5 | // Created by siuzanna on 19/12/21. 6 | // 7 | 8 | import UIKit 9 | 10 | class MovieDetailedScreenViewController: UIViewController { 11 | 12 | enum Section: String, CaseIterable { 13 | case comments = "Comments" 14 | case recommend = "" 15 | } 16 | 17 | typealias Snapshot = NSDiffableDataSourceSnapshot 18 | private var dataSource: UICollectionViewDiffableDataSource! = nil 19 | private var viewModel: MovieDTO 20 | 21 | private lazy var navigationBar = NavigationBarBack() 22 | private lazy var collectionView = configureCollectionView() 23 | 24 | init(viewModel: MovieDTO) { 25 | self.viewModel = viewModel 26 | super.init(nibName: nil, bundle: nil) 27 | } 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | view.backgroundColor = Colors.bg.color 32 | navigationBar.addBackButtonTarget(self, action: #selector(dismissView), 33 | forEvent: .touchUpInside) 34 | configureDataSource() 35 | setupNavigationBar() 36 | } 37 | 38 | @objc func dismissView() { 39 | self.dismiss(animated: true, completion: nil) 40 | } 41 | 42 | required init?(coder: NSCoder) { 43 | fatalError("init(coder:) has not been implemented") 44 | } 45 | } 46 | 47 | // MARK: Configure Constraints 48 | extension MovieDetailedScreenViewController { 49 | 50 | private func setupNavigationBar() { 51 | view.addSubview(collectionView) 52 | view.addSubview(navigationBar) 53 | navigationBar.snp.makeConstraints { make in 54 | make.top.equalTo(view.safeAreaLayoutGuide.snp.top) 55 | make.leading.trailing.equalToSuperview().inset(16) 56 | make.height.equalTo(92) 57 | } 58 | collectionView.snp.makeConstraints { make in 59 | make.top.equalTo(view.snp.topMargin) 60 | make.leading.trailing.bottom.equalToSuperview() 61 | } 62 | } 63 | } 64 | 65 | // MARK: Configure UICollectionView 66 | extension MovieDetailedScreenViewController { 67 | 68 | func configureCollectionView() -> UICollectionView { 69 | let collectionView = UICollectionView(frame: .zero, collectionViewLayout: generateLayout()) 70 | collectionView.autoresizingMask = [.flexibleHeight, .flexibleWidth] 71 | collectionView.backgroundColor = .clear 72 | collectionView.showsVerticalScrollIndicator = false 73 | collectionView.showsHorizontalScrollIndicator = false 74 | collectionView.contentInset.bottom = 50 75 | collectionView.bounces = false 76 | collectionView.register(header: TopView.self) 77 | collectionView.register(header: HeaderView.self) 78 | collectionView.register(cell: CommentsCell.self) 79 | collectionView.register(footer: CommentsViewFooter.self) 80 | return collectionView 81 | } 82 | } 83 | 84 | // MARK: UICollectionViewDiffableDataSource 85 | extension MovieDetailedScreenViewController { 86 | 87 | func configureDataSource() { 88 | dataSource = UICollectionViewDiffableDataSource 89 | (collectionView: collectionView) { 90 | (collectionView: UICollectionView, indexPath: IndexPath, item: Comments) -> UICollectionViewCell? in 91 | 92 | let sectionType = Section.allCases[indexPath.section] 93 | switch sectionType { 94 | case .comments: 95 | return UICollectionViewCell() 96 | case .recommend: 97 | let cell: CommentsCell = collectionView.dequeue(for: indexPath) 98 | cell.cellViewModel = item 99 | return cell 100 | } 101 | } 102 | 103 | dataSource.supplementaryViewProvider = { [ weak self ] 104 | ( collectionView: UICollectionView, kind: String, indexPath: IndexPath) -> UICollectionReusableView? in 105 | let sectionType = Section.allCases[indexPath.section] 106 | switch sectionType { 107 | case .comments: 108 | let supplementaryView: TopView = collectionView.dequeue(for: indexPath, kind: kind) 109 | supplementaryView.cellViewModel = self?.viewModel 110 | return supplementaryView 111 | case .recommend: 112 | let supplementaryView: CommentsViewFooter = collectionView.dequeue(for: indexPath, kind: kind) 113 | return supplementaryView 114 | } 115 | } 116 | 117 | let snapshot = snapshot() 118 | dataSource.apply(snapshot, animatingDifferences: true) 119 | } 120 | 121 | func snapshot() -> Snapshot { 122 | var snapshot = Snapshot() 123 | snapshot.appendSections([.comments, .recommend]) 124 | let comments = viewModel.comments 125 | snapshot.appendItems(comments, toSection: .recommend) 126 | snapshot.appendItems(comments.suffix(0), toSection: .comments) 127 | 128 | return snapshot 129 | } 130 | } 131 | 132 | // MARK: UICollectionViewLayout 133 | extension MovieDetailedScreenViewController { 134 | 135 | func generateLayout() -> UICollectionViewLayout { 136 | let layout = UICollectionViewCompositionalLayout { [weak self ] 137 | (sectionIndex: Int, _: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in 138 | 139 | let sectionLayoutKind = Section.allCases[sectionIndex] 140 | switch sectionLayoutKind { 141 | case .comments: return self?.generateEmtyCellLayout() 142 | case .recommend: return self?.generateCommentsLayout() 143 | } 144 | } 145 | layout.register( 146 | SectionBackgroundDecorationView.self, 147 | forDecorationViewOfKind: "NSCollectionLayoutDecorationItem") 148 | return layout 149 | } 150 | 151 | func generateEmtyCellLayout() -> NSCollectionLayoutSection { 152 | let itemSize = NSCollectionLayoutSize( 153 | widthDimension: .fractionalWidth(1), 154 | heightDimension: .fractionalHeight(1)) 155 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 156 | 157 | let groupSize = NSCollectionLayoutSize( 158 | widthDimension: .fractionalWidth(0.43), 159 | heightDimension: .absolute(10)) 160 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) 161 | 162 | let headerSize = NSCollectionLayoutSize( 163 | widthDimension: .fractionalWidth(1.0), 164 | heightDimension: .estimated(685)) 165 | let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem( 166 | layoutSize: headerSize, 167 | elementKind: UICollectionView.elementKindSectionHeader, 168 | alignment: .top) 169 | sectionHeader.zIndex = 2 170 | 171 | let section = NSCollectionLayoutSection(group: group) 172 | section.orthogonalScrollingBehavior = UICollectionLayoutSectionOrthogonalScrollingBehavior.continuous 173 | section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 20) 174 | section.boundarySupplementaryItems = [sectionHeader] 175 | section.orthogonalScrollingBehavior = .continuous 176 | 177 | return section 178 | } 179 | 180 | func generateCommentsLayout() -> NSCollectionLayoutSection { 181 | let itemSize = NSCollectionLayoutSize( 182 | widthDimension: .fractionalWidth(1.0), 183 | heightDimension: .fractionalHeight(1.0)) 184 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 185 | 186 | let groupSize = NSCollectionLayoutSize( 187 | widthDimension: .fractionalWidth(1.0), 188 | heightDimension: .absolute(82)) 189 | let group = NSCollectionLayoutGroup.horizontal( 190 | layoutSize: groupSize, subitems: [item]) 191 | 192 | let footerSize = NSCollectionLayoutSize( 193 | widthDimension: .fractionalWidth(1.0), 194 | heightDimension: .absolute(52)) 195 | let sectionFooter = NSCollectionLayoutBoundarySupplementaryItem( 196 | layoutSize: footerSize, 197 | elementKind: UICollectionView.elementKindSectionFooter, 198 | alignment: .bottom) 199 | sectionFooter.zIndex = 2 200 | 201 | let section = NSCollectionLayoutSection(group: group) 202 | section.interGroupSpacing = 5 203 | 204 | let sectionBackgroundDecoration = NSCollectionLayoutDecorationItem.background( 205 | elementKind: "NSCollectionLayoutDecorationItem" ) 206 | sectionBackgroundDecoration.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 20) 207 | section.decorationItems = [sectionBackgroundDecoration] 208 | 209 | section.orthogonalScrollingBehavior = UICollectionLayoutSectionOrthogonalScrollingBehavior.continuous 210 | section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 20) 211 | section.boundarySupplementaryItems = [sectionFooter] 212 | section.orthogonalScrollingBehavior = .none 213 | 214 | return section 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /Movie/Resources/movies.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 101, 4 | "type": 0, 5 | "series": false, 6 | "name": "Money Heist", 7 | "time": 120, 8 | "genre": ["Crime", "Drama"], 9 | "rating": "8.2", 10 | "votes": "529K", 11 | "photo": "https://i.pinimg.com/originals/cc/3d/c2/cc3dc28c61ecc4e3943ef52a5be72415.jpg", 12 | "miniPhoto": "https://i.pinimg.com/originals/9b/3f/ed/9b3fedaf0922a2538fc8083bbfcb3d2f.jpg", 13 | "description": "An unusual group of robbers attempt to carry out the most perfect robbery in Spanish history - stealing 2.4 billion euros from the Royal Mint of Spain.", 14 | "trailer": "https://www.youtube.com/watch?v=_InqQJRqGW4", 15 | "comments": [ 16 | { 17 | "id": 1, 18 | "name": "John Doe", 19 | "comment": "Great series!", 20 | "picture": "url_to_picture" 21 | }, 22 | { 23 | "id": 2, 24 | "name": "Jane Smith", 25 | "comment": "Loved the storyline.", 26 | "picture": "url_to_picture" 27 | } 28 | ] 29 | }, 30 | { 31 | "id": 102, 32 | "type": 0, 33 | "series": false, 34 | "name": "The Avengers", 35 | "time": 143, 36 | "genre": ["Action", "Adventure"], 37 | "rating": "8.0", 38 | "votes": "1.5M", 39 | "photo": "https://avatars.mds.yandex.net/i?id=9dd4bdd1197e4c470546f18681d944c6c7da6fdf-10555250-images-thumbs&n=13", 40 | "miniPhoto": "http://images6.fanpop.com/image/photos/37300000/Avengers-2-Age-of-Ultron-the-avengers-37328228-900-1185.jpg", 41 | "description": "Earth's mightiest heroes must come together and learn to fight as a team if they are going to stop the mischievous Loki and his alien army.", 42 | "trailer": "https://www.youtube.com/watch?v=6ZfuNTqbHE8", 43 | "comments": [ 44 | { 45 | "id": 1, 46 | "name": "John Doe", 47 | "comment": "Great movie!", 48 | "picture": "url_to_picture" 49 | }, 50 | { 51 | "id": 2, 52 | "name": "Jane Smith", 53 | "comment": "Loved the action scenes.", 54 | "picture": "url_to_picture" 55 | } 56 | ] 57 | }, 58 | { 59 | "id": 201, 60 | "type": 0, 61 | "series": false, 62 | "name": "Fatman", 63 | "time": 100, 64 | "genre": ["Action", "Comedy", "Fantasy"], 65 | "rating": "5.9", 66 | "votes": "30K", 67 | "photo": "https://avatars.dzeninfra.ru/get-zen_doc/3385233/pub_602e2d835f462a3bfd27a7f2_602e2e30ffa2d86ae404fe1c/scale_1200", 68 | "miniPhoto": "https://i.pinimg.com/originals/13/97/ae/1397ae69601ebc7e76e73f643f8a9114.jpg", 69 | "description": "A rowdy, unorthodox Santa Claus is fighting to save his declining business. Meanwhile, Billy, a neglected and precocious 12-year-old, hires a hitman to kill Santa after receiving a lump of coal in his stocking.", 70 | "trailer": "url_to_trailer", 71 | "comments": [ 72 | { 73 | "id": 1, 74 | "name": "John Doe", 75 | "comment": "An unconventional holiday film.", 76 | "picture": "url_to_picture" 77 | }, 78 | { 79 | "id": 2, 80 | "name": "Jane Smith", 81 | "comment": "Unique take on Santa Claus.", 82 | "picture": "url_to_picture" 83 | } 84 | ] 85 | }, 86 | { 87 | "id": 202, 88 | "type": 1, 89 | "series": true, 90 | "name": "30 Coins", 91 | "time": 60, 92 | "genre": ["Drama", "Fantasy", "Horror"], 93 | "rating": "7.1", 94 | "votes": "11K", 95 | "photo": "https://pogd.es/assets/bg/30-Coins.jpg", 96 | "miniPhoto": "https://i.pinimg.com/originals/3e/78/65/3e7865c4646ece782f1d6d1c3971225f.jpg", 97 | "description": "An exiled priest tries to escape his demons while living in a remote village in Spain.", 98 | "trailer": "https://www.youtube.com/watch?v=jOAxchyLZ00", 99 | "comments": [ 100 | { 101 | "id": 1, 102 | "name": "John Doe", 103 | "comment": "Intriguing and suspenseful.", 104 | "picture": "url_to_picture" 105 | }, 106 | { 107 | "id": 2, 108 | "name": "Jane Smith", 109 | "comment": "Captivating storyline.", 110 | "picture": "url_to_picture" 111 | } 112 | ] 113 | }, 114 | { 115 | "id": 301, 116 | "type": 1, 117 | "series": false, 118 | "name": "Inception", 119 | "time": 148, 120 | "genre": ["Action", "Sci-Fi", "Thriller"], 121 | "rating": "8.8", 122 | "votes": "2.3M", 123 | "photo": "https://m.media-amazon.com/images/I/51zUbui+gbL._AC_.jpg", 124 | "miniPhoto": "https://m.media-amazon.com/images/I/51zUbui+gbL._AC_SY679_.jpg", 125 | "description": "A thief who steals corporate secrets through the use of dream-sharing technology is given the inverse task of planting an idea into the mind of a CEO.", 126 | "trailer": "https://www.youtube.com/watch?v=YoHD9XEInc0", 127 | "comments": [ 128 | { 129 | "id": 1, 130 | "name": "Alice Johnson", 131 | "comment": "Mind-bending masterpiece!", 132 | "picture": "url_to_picture" 133 | }, 134 | { 135 | "id": 2, 136 | "name": "Bob Lee", 137 | "comment": "A complex and thrilling experience.", 138 | "picture": "url_to_picture" 139 | } 140 | ] 141 | }, 142 | { 143 | "id": 302, 144 | "type": 1, 145 | "series": false, 146 | "name": "The Grand Budapest Hotel", 147 | "time": 99, 148 | "genre": ["Adventure", "Comedy", "Drama"], 149 | "rating": "8.1", 150 | "votes": "800K", 151 | "photo": "https://static01.nyt.com/images/2014/03/05/multimedia/budapest-anatomy/budapest-anatomy-superJumbo.jpg", 152 | "miniPhoto": "https://m.media-amazon.com/images/M/MV5BOTVjOGNhMmUtMDMzOS00YTljLWEwYWUtNWZjMTgwZmZkNTI0XkEyXkFqcGdeQXVyNTIzOTk5ODM@._V1_.jpg", 153 | "description": "A writer encounters the owner of an aging high-class hotel, who tells him of his early years serving as a lobby boy in the hotel's glorious years under an exceptional concierge.", 154 | "trailer": "https://www.youtube.com/watch?v=1Fg5iWmQjwk", 155 | "comments": [ 156 | { 157 | "id": 1, 158 | "name": "Clara Smith", 159 | "comment": "A whimsical and visually stunning film.", 160 | "picture": "url_to_picture" 161 | }, 162 | { 163 | "id": 2, 164 | "name": "David Brown", 165 | "comment": "Wes Anderson at his best.", 166 | "picture": "url_to_picture" 167 | } 168 | ] 169 | }, 170 | { 171 | "id": 303, 172 | "type": 2, 173 | "series": false, 174 | "name": "Parasite", 175 | "time": 132, 176 | "genre": ["Comedy", "Drama", "Thriller"], 177 | "rating": "8.6", 178 | "votes": "1.1M", 179 | "photo": "https://static.kinoafisha.info/upload/news/391683356050.jpg", 180 | "miniPhoto": "https://m.media-amazon.com/images/M/MV5BYjU3MTEzNGItMDhkOS00YTgyLWIzZGQtMjQ0ZjdiYjE1YmM5XkEyXkFqcGdeQXVyMTA4NjE0NjEy._V1_.jpg", 181 | "description": "Greed and class discrimination threaten the newly formed symbiotic relationship between the wealthy Park family and the destitute Kim clan.", 182 | "trailer": "https://www.youtube.com/watch?v=5xH0HfJHsaY", 183 | "comments": [ 184 | { 185 | "id": 1, 186 | "name": "Eva Green", 187 | "comment": "A brilliant social commentary.", 188 | "picture": "url_to_picture" 189 | }, 190 | { 191 | "id": 2, 192 | "name": "Frank White", 193 | "comment": "Unpredictable and captivating.", 194 | "picture": "url_to_picture" 195 | } 196 | ] 197 | }, 198 | { 199 | "id": 304, 200 | "type": 3, 201 | "series": false, 202 | "name": "The Dark Knight", 203 | "time": 152, 204 | "genre": ["Action", "Crime", "Drama"], 205 | "rating": "9.0", 206 | "votes": "2.5M", 207 | "photo": "https://a.d-cd.net/d4AAAgNbKuA-1920.jpg", 208 | "miniPhoto": "https://images-na.ssl-images-amazon.com/images/I/91ebheNmoUL._RI_.jpg", 209 | "description": "When the menace known as the Joker emerges from his mysterious past, he wreaks havoc and chaos on the people of Gotham.", 210 | "trailer": "https://www.youtube.com/watch?v=EXeTwQWrcwY", 211 | "comments": [ 212 | { 213 | "id": 1, 214 | "name": "George Miller", 215 | "comment": "Heath Ledger's performance is legendary.", 216 | "picture": "url_to_picture" 217 | }, 218 | { 219 | "id": 2, 220 | "name": "Hannah Davis", 221 | "comment": "A masterpiece in the superhero genre.", 222 | "picture": "url_to_picture" 223 | } 224 | ] 225 | }, 226 | { 227 | "id": 305, 228 | "type": 4, 229 | "series": false, 230 | "name": "Pulp Fiction", 231 | "time": 154, 232 | "genre": ["Crime", "Drama"], 233 | "rating": "8.9", 234 | "votes": "1.9M", 235 | "photo": "https://m.media-amazon.com/images/I/71c05lTE03L._AC_SY679_.jpg", 236 | "miniPhoto": "https://m.media-amazon.com/images/I/71c05lTE03L._AC_SY679_.jpg", 237 | "description": "The lives of two mob hitmen, a boxer, a gangster's wife, and a pair of diner bandits intertwine in four tales of violence and redemption.", 238 | "trailer": "https://www.youtube.com/watch?v=s7EdQ4FqbhY", 239 | "comments": [ 240 | { 241 | "id": 1, 242 | "name": "Ian Thompson", 243 | "comment": "Quentin Tarantino's magnum opus.", 244 | "picture": "url_to_picture" 245 | }, 246 | { 247 | "id": 2, 248 | "name": "Julia Roberts", 249 | "comment": "A cult classic with unforgettable dialogue.", 250 | "picture": "url_to_picture" 251 | } 252 | ] 253 | }, 254 | { 255 | "id": 403, 256 | "type": 3, 257 | "series": false, 258 | "name": "The Conjuring", 259 | "time": 112, 260 | "genre": ["Horror", "Mystery", "Thriller"], 261 | "rating": "7.5", 262 | "votes": "528K", 263 | "photo": "https://newcdn.igromania.ru/mnt/news/e/9/e/a/2/2/99139/html/more/fded9944f363f333733fcc3b_1920xH.webp", 264 | "miniPhoto": "https://m.media-amazon.com/images/M/MV5BZTlhZWY0YzAtMDA2Zi00NGVlLThhNzQtYjFjMWI0YjU0Yzg0XkEyXkFqcGdeQXVyNjIzNzM4NzA@._V1_.jpg", 265 | "description": "Paranormal investigators Ed and Lorraine Warren work to help a family terrorized by a dark presence in their farmhouse.", 266 | "trailer": "https://www.youtube.com/watch?v=k10ETZ41q5o", 267 | "comments": [ 268 | { 269 | "id": 1, 270 | "name": "Nathan Brooks", 271 | "comment": "Terrifying and unforgettable.", 272 | "picture": "url_to_picture" 273 | }, 274 | { 275 | "id": 2, 276 | "name": "Olivia Parker", 277 | "comment": "A hauntingly good time.", 278 | "picture": "url_to_picture" 279 | } 280 | ] 281 | }, 282 | { 283 | "id": 404, 284 | "type": 4, 285 | "series": true, 286 | "name": "Game of Thrones", 287 | "time": 57, 288 | "genre": ["Fantasy", "Adventure", "Drama"], 289 | "rating": "9.2", 290 | "votes": "2.0M", 291 | "photo": "https://avatars.mds.yandex.net/get-marketpic/5654221/pic9f3d3ef1bac6f563bcd9f9b17ece84ef/orig", 292 | "miniPhoto": "https://avatars.mds.yandex.net/get-marketpic/5888040/picebb756d60998254792428e8d566183fe/orig", 293 | "description": "Nine noble families fight for control over the lands of Westeros, while an ancient enemy returns after being dormant for millennia.", 294 | "trailer": "https://www.youtube.com/watch?v=BpJYNVhGf1s", 295 | "comments": [ 296 | { 297 | "id": 1, 298 | "name": "Henry Evans", 299 | "comment": "Epic storytelling with rich world-building.", 300 | "picture": "url_to_picture" 301 | }, 302 | { 303 | "id": 2, 304 | "name": "Isabella Garcia", 305 | "comment": "A must-watch for fantasy fans.", 306 | "picture": "url_to_picture" 307 | } 308 | ] 309 | } 310 | ] -------------------------------------------------------------------------------- /Movie.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9605D4272B9504F80084D92F /* MainContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9605D4262B9504F80084D92F /* MainContentView.swift */; }; 11 | 96290EDE276D0F2E00AFD992 /* PhotoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96290EDD276D0F2E00AFD992 /* PhotoCell.swift */; }; 12 | 96290EE0276D117700AFD992 /* UICollectionViewCell+Register+extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96290EDF276D117700AFD992 /* UICollectionViewCell+Register+extension.swift */; }; 13 | 96290EE2276D130A00AFD992 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96290EE1276D130A00AFD992 /* HeaderView.swift */; }; 14 | 96290EE4276D19BA00AFD992 /* MainScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96290EE3276D19BA00AFD992 /* MainScreenViewModel.swift */; }; 15 | 96290EF4276E06D500AFD992 /* MainScreenRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96290EEF276E06D500AFD992 /* MainScreenRouter.swift */; }; 16 | 96290EF5276E06D500AFD992 /* ProResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96290EF2276E06D500AFD992 /* ProResult.swift */; }; 17 | 96290EF6276E06D500AFD992 /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96290EF0276E06D500AFD992 /* NetworkService.swift */; }; 18 | 96290EF7276E06D500AFD992 /* BaseRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96290EED276E06D500AFD992 /* BaseRouter.swift */; }; 19 | 96290EF8276E06D500AFD992 /* NetworkErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96290EF1276E06D500AFD992 /* NetworkErrors.swift */; }; 20 | 96290EFF276E130100AFD992 /* MovieDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96290EFC276E130100AFD992 /* MovieDTO.swift */; }; 21 | 96290F00276E130100AFD992 /* Comments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96290EFD276E130100AFD992 /* Comments.swift */; }; 22 | 96290F05276E5A7C00AFD992 /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96290F04276E5A7C00AFD992 /* NavigationBar.swift */; }; 23 | 96290F09276E656F00AFD992 /* HTTPMethods.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96290F08276E656F00AFD992 /* HTTPMethods.swift */; }; 24 | 96290F0E276E8C7F00AFD992 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = 96290F0D276E8C7F00AFD992 /* .swiftlint.yml */; }; 25 | 96290F11276F384800AFD992 /* MovieDetailedScreenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96290F10276F384800AFD992 /* MovieDetailedScreenViewController.swift */; }; 26 | 96290F15276F397B00AFD992 /* NavigationBarBack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96290F14276F397B00AFD992 /* NavigationBarBack.swift */; }; 27 | 96290F17276F426C00AFD992 /* TopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96290F16276F426C00AFD992 /* TopView.swift */; }; 28 | 96290F1A276F69BC00AFD992 /* LableImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96290F19276F69BC00AFD992 /* LableImage.swift */; }; 29 | 96290F1C276F754F00AFD992 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 96290F1B276F754F00AFD992 /* WebKit.framework */; }; 30 | 9635412A276FCC370036133D /* CommentsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96354129276FCC370036133D /* CommentsCell.swift */; }; 31 | 9635412D276FDFEA0036133D /* SectionDecorationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9635412C276FDFEA0036133D /* SectionDecorationView.swift */; }; 32 | 9635412F276FE0AE0036133D /* CommentsViewFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9635412E276FE0AE0036133D /* CommentsViewFooter.swift */; }; 33 | 963541372770A7FC0036133D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 963541362770A7FC0036133D /* Assets.xcassets */; }; 34 | 963EA3C82D32748300EDF8C7 /* movies.json in Resources */ = {isa = PBXBuildFile; fileRef = 963EA3C72D32748300EDF8C7 /* movies.json */; }; 35 | 964938AD276CA661009C6708 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 964938AC276CA661009C6708 /* AppDelegate.swift */; }; 36 | 964938AF276CA661009C6708 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 964938AE276CA661009C6708 /* SceneDelegate.swift */; }; 37 | 964938B1276CA661009C6708 /* MainScreenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 964938B0276CA661009C6708 /* MainScreenViewController.swift */; }; 38 | 964938B6276CA666009C6708 /* Icons.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 964938B5276CA666009C6708 /* Icons.xcassets */; }; 39 | 964938B9276CA666009C6708 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 964938B7276CA666009C6708 /* LaunchScreen.storyboard */; }; 40 | 964938C3276CA73D009C6708 /* ViewController + Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 964938C2276CA73D009C6708 /* ViewController + Extension.swift */; }; 41 | 964938C5276CA780009C6708 /* UINavigationController + Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 964938C4276CA780009C6708 /* UINavigationController + Extension.swift */; }; 42 | 964938C8276CAD60009C6708 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 964938C7276CAD60009C6708 /* Colors.xcassets */; }; 43 | 964938CA276CAE61009C6708 /* swiftgen.yml in Resources */ = {isa = PBXBuildFile; fileRef = 964938C9276CAE61009C6708 /* swiftgen.yml */; }; 44 | 964938D3276CB5F6009C6708 /* UITabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 964938D2276CB5F6009C6708 /* UITabBarController.swift */; }; 45 | 964938D5276CB7D1009C6708 /* UIImage + Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 964938D4276CB7D1009C6708 /* UIImage + Extension.swift */; }; 46 | 964938D8276CBEA4009C6708 /* GeneratedColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 964938D6276CBEA4009C6708 /* GeneratedColors.swift */; }; 47 | 964938D9276CBEA4009C6708 /* GeneratedIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 964938D7276CBEA4009C6708 /* GeneratedIcons.swift */; }; 48 | 967792682CAEDCCC0031C2F3 /* SnapKit-Dynamic in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 96AADD912B47481100D823D6 /* SnapKit-Dynamic */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 49 | 96AADD922B47481100D823D6 /* SnapKit-Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = 96AADD912B47481100D823D6 /* SnapKit-Dynamic */; }; 50 | 96F59F612B41D1BA004C298A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 96F59F602B41D1BA004C298A /* Kingfisher */; }; 51 | /* End PBXBuildFile section */ 52 | 53 | /* Begin PBXCopyFilesBuildPhase section */ 54 | 967792692CAEDCCC0031C2F3 /* Embed Frameworks */ = { 55 | isa = PBXCopyFilesBuildPhase; 56 | buildActionMask = 2147483647; 57 | dstPath = ""; 58 | dstSubfolderSpec = 10; 59 | files = ( 60 | 967792682CAEDCCC0031C2F3 /* SnapKit-Dynamic in Embed Frameworks */, 61 | ); 62 | name = "Embed Frameworks"; 63 | runOnlyForDeploymentPostprocessing = 0; 64 | }; 65 | /* End PBXCopyFilesBuildPhase section */ 66 | 67 | /* Begin PBXFileReference section */ 68 | 94D69698E83CFDFC9C1B27BC /* Pods_Movie.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Movie.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 69 | 9605D4262B9504F80084D92F /* MainContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainContentView.swift; sourceTree = ""; }; 70 | 96290EDD276D0F2E00AFD992 /* PhotoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoCell.swift; sourceTree = ""; }; 71 | 96290EDF276D117700AFD992 /* UICollectionViewCell+Register+extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UICollectionViewCell+Register+extension.swift"; sourceTree = ""; }; 72 | 96290EE1276D130A00AFD992 /* HeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderView.swift; sourceTree = ""; }; 73 | 96290EE3276D19BA00AFD992 /* MainScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainScreenViewModel.swift; sourceTree = ""; }; 74 | 96290EED276E06D500AFD992 /* BaseRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseRouter.swift; sourceTree = ""; }; 75 | 96290EEF276E06D500AFD992 /* MainScreenRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainScreenRouter.swift; sourceTree = ""; }; 76 | 96290EF0276E06D500AFD992 /* NetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkService.swift; sourceTree = ""; }; 77 | 96290EF1276E06D500AFD992 /* NetworkErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkErrors.swift; sourceTree = ""; }; 78 | 96290EF2276E06D500AFD992 /* ProResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProResult.swift; sourceTree = ""; }; 79 | 96290EFC276E130100AFD992 /* MovieDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieDTO.swift; sourceTree = ""; }; 80 | 96290EFD276E130100AFD992 /* Comments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Comments.swift; sourceTree = ""; }; 81 | 96290F04276E5A7C00AFD992 /* NavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBar.swift; sourceTree = ""; }; 82 | 96290F08276E656F00AFD992 /* HTTPMethods.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPMethods.swift; sourceTree = ""; }; 83 | 96290F0D276E8C7F00AFD992 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; 84 | 96290F10276F384800AFD992 /* MovieDetailedScreenViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieDetailedScreenViewController.swift; sourceTree = ""; }; 85 | 96290F14276F397B00AFD992 /* NavigationBarBack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarBack.swift; sourceTree = ""; }; 86 | 96290F16276F426C00AFD992 /* TopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopView.swift; sourceTree = ""; }; 87 | 96290F19276F69BC00AFD992 /* LableImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LableImage.swift; sourceTree = ""; }; 88 | 96290F1B276F754F00AFD992 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; 89 | 96354129276FCC370036133D /* CommentsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentsCell.swift; sourceTree = ""; }; 90 | 9635412C276FDFEA0036133D /* SectionDecorationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionDecorationView.swift; sourceTree = ""; }; 91 | 9635412E276FE0AE0036133D /* CommentsViewFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentsViewFooter.swift; sourceTree = ""; }; 92 | 963541362770A7FC0036133D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 93 | 963EA3C72D32748300EDF8C7 /* movies.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = movies.json; sourceTree = ""; }; 94 | 964938A9276CA661009C6708 /* Movie.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Movie.app; sourceTree = BUILT_PRODUCTS_DIR; }; 95 | 964938AC276CA661009C6708 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 96 | 964938AE276CA661009C6708 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 97 | 964938B0276CA661009C6708 /* MainScreenViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainScreenViewController.swift; sourceTree = ""; }; 98 | 964938B5276CA666009C6708 /* Icons.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Icons.xcassets; sourceTree = ""; }; 99 | 964938B8276CA666009C6708 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 100 | 964938BA276CA666009C6708 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 101 | 964938C2276CA73D009C6708 /* ViewController + Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ViewController + Extension.swift"; sourceTree = ""; }; 102 | 964938C4276CA780009C6708 /* UINavigationController + Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController + Extension.swift"; sourceTree = ""; }; 103 | 964938C7276CAD60009C6708 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; 104 | 964938C9276CAE61009C6708 /* swiftgen.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = swiftgen.yml; sourceTree = ""; }; 105 | 964938D2276CB5F6009C6708 /* UITabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITabBarController.swift; sourceTree = ""; }; 106 | 964938D4276CB7D1009C6708 /* UIImage + Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage + Extension.swift"; sourceTree = ""; }; 107 | 964938D6276CBEA4009C6708 /* GeneratedColors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedColors.swift; sourceTree = ""; }; 108 | 964938D7276CBEA4009C6708 /* GeneratedIcons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedIcons.swift; sourceTree = ""; }; 109 | /* End PBXFileReference section */ 110 | 111 | /* Begin PBXFrameworksBuildPhase section */ 112 | 964938A6276CA661009C6708 /* Frameworks */ = { 113 | isa = PBXFrameworksBuildPhase; 114 | buildActionMask = 2147483647; 115 | files = ( 116 | 96AADD922B47481100D823D6 /* SnapKit-Dynamic in Frameworks */, 117 | 96F59F612B41D1BA004C298A /* Kingfisher in Frameworks */, 118 | 96290F1C276F754F00AFD992 /* WebKit.framework in Frameworks */, 119 | ); 120 | runOnlyForDeploymentPostprocessing = 0; 121 | }; 122 | /* End PBXFrameworksBuildPhase section */ 123 | 124 | /* Begin PBXGroup section */ 125 | 90660853EFED07B4999132CC /* Frameworks */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 96290F1B276F754F00AFD992 /* WebKit.framework */, 129 | 94D69698E83CFDFC9C1B27BC /* Pods_Movie.framework */, 130 | ); 131 | name = Frameworks; 132 | sourceTree = ""; 133 | }; 134 | 96290ED8276CC02900AFD992 /* MainScreen */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 964938B0276CA661009C6708 /* MainScreenViewController.swift */, 138 | 96290EE3276D19BA00AFD992 /* MainScreenViewModel.swift */, 139 | 9605D4262B9504F80084D92F /* MainContentView.swift */, 140 | 96290EDC276D0F0300AFD992 /* View */, 141 | 96290F06276E64D700AFD992 /* Cells */, 142 | ); 143 | path = MainScreen; 144 | sourceTree = ""; 145 | }; 146 | 96290EDC276D0F0300AFD992 /* View */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | 96290EE1276D130A00AFD992 /* HeaderView.swift */, 150 | 96290F04276E5A7C00AFD992 /* NavigationBar.swift */, 151 | ); 152 | path = View; 153 | sourceTree = ""; 154 | }; 155 | 96290EE5276D19FF00AFD992 /* Models */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | 96290EFD276E130100AFD992 /* Comments.swift */, 159 | 96290EFC276E130100AFD992 /* MovieDTO.swift */, 160 | ); 161 | path = Models; 162 | sourceTree = ""; 163 | }; 164 | 96290EE8276D288900AFD992 /* NetworkService */ = { 165 | isa = PBXGroup; 166 | children = ( 167 | 96290EF0276E06D500AFD992 /* NetworkService.swift */, 168 | 96290F0A276E657C00AFD992 /* Network Components */, 169 | 96290F07276E653800AFD992 /* Base Router */, 170 | ); 171 | path = NetworkService; 172 | sourceTree = ""; 173 | }; 174 | 96290F06276E64D700AFD992 /* Cells */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | 96290EDD276D0F2E00AFD992 /* PhotoCell.swift */, 178 | ); 179 | path = Cells; 180 | sourceTree = ""; 181 | }; 182 | 96290F07276E653800AFD992 /* Base Router */ = { 183 | isa = PBXGroup; 184 | children = ( 185 | 96290EED276E06D500AFD992 /* BaseRouter.swift */, 186 | 96290EEF276E06D500AFD992 /* MainScreenRouter.swift */, 187 | ); 188 | path = "Base Router"; 189 | sourceTree = ""; 190 | }; 191 | 96290F0A276E657C00AFD992 /* Network Components */ = { 192 | isa = PBXGroup; 193 | children = ( 194 | 96290EF1276E06D500AFD992 /* NetworkErrors.swift */, 195 | 96290EF2276E06D500AFD992 /* ProResult.swift */, 196 | 96290F08276E656F00AFD992 /* HTTPMethods.swift */, 197 | ); 198 | path = "Network Components"; 199 | sourceTree = ""; 200 | }; 201 | 96290F0F276F37E100AFD992 /* MovieDetailsScreen */ = { 202 | isa = PBXGroup; 203 | children = ( 204 | 96290F10276F384800AFD992 /* MovieDetailedScreenViewController.swift */, 205 | 9635412B276FDFD00036133D /* Cells */, 206 | 96290F12276F385400AFD992 /* View */, 207 | ); 208 | path = MovieDetailsScreen; 209 | sourceTree = ""; 210 | }; 211 | 96290F12276F385400AFD992 /* View */ = { 212 | isa = PBXGroup; 213 | children = ( 214 | 96290F14276F397B00AFD992 /* NavigationBarBack.swift */, 215 | 96290F16276F426C00AFD992 /* TopView.swift */, 216 | 9635412E276FE0AE0036133D /* CommentsViewFooter.swift */, 217 | 9635412C276FDFEA0036133D /* SectionDecorationView.swift */, 218 | 96290F19276F69BC00AFD992 /* LableImage.swift */, 219 | ); 220 | path = View; 221 | sourceTree = ""; 222 | }; 223 | 9635412B276FDFD00036133D /* Cells */ = { 224 | isa = PBXGroup; 225 | children = ( 226 | 96354129276FCC370036133D /* CommentsCell.swift */, 227 | ); 228 | path = Cells; 229 | sourceTree = ""; 230 | }; 231 | 964938A0276CA661009C6708 = { 232 | isa = PBXGroup; 233 | children = ( 234 | 96290F0D276E8C7F00AFD992 /* .swiftlint.yml */, 235 | 964938C9276CAE61009C6708 /* swiftgen.yml */, 236 | 964938AB276CA661009C6708 /* Movie */, 237 | 964938AA276CA661009C6708 /* Products */, 238 | 90660853EFED07B4999132CC /* Frameworks */, 239 | ); 240 | sourceTree = ""; 241 | }; 242 | 964938AA276CA661009C6708 /* Products */ = { 243 | isa = PBXGroup; 244 | children = ( 245 | 964938A9276CA661009C6708 /* Movie.app */, 246 | ); 247 | name = Products; 248 | sourceTree = ""; 249 | }; 250 | 964938AB276CA661009C6708 /* Movie */ = { 251 | isa = PBXGroup; 252 | children = ( 253 | 964938AC276CA661009C6708 /* AppDelegate.swift */, 254 | 964938AE276CA661009C6708 /* SceneDelegate.swift */, 255 | 96290EE5276D19FF00AFD992 /* Models */, 256 | 96290ED8276CC02900AFD992 /* MainScreen */, 257 | 96290F0F276F37E100AFD992 /* MovieDetailsScreen */, 258 | 964938D1276CB5A2009C6708 /* Tabbar */, 259 | 96290EE8276D288900AFD992 /* NetworkService */, 260 | 964938C0276CA719009C6708 /* Extension */, 261 | 964938C6276CABEF009C6708 /* Resources */, 262 | 964938BA276CA666009C6708 /* Info.plist */, 263 | ); 264 | path = Movie; 265 | sourceTree = ""; 266 | }; 267 | 964938C0276CA719009C6708 /* Extension */ = { 268 | isa = PBXGroup; 269 | children = ( 270 | 964938C1276CA724009C6708 /* UIKit */, 271 | ); 272 | path = Extension; 273 | sourceTree = ""; 274 | }; 275 | 964938C1276CA724009C6708 /* UIKit */ = { 276 | isa = PBXGroup; 277 | children = ( 278 | 96290EDF276D117700AFD992 /* UICollectionViewCell+Register+extension.swift */, 279 | 964938C2276CA73D009C6708 /* ViewController + Extension.swift */, 280 | 964938C4276CA780009C6708 /* UINavigationController + Extension.swift */, 281 | 964938D4276CB7D1009C6708 /* UIImage + Extension.swift */, 282 | ); 283 | path = UIKit; 284 | sourceTree = ""; 285 | }; 286 | 964938C6276CABEF009C6708 /* Resources */ = { 287 | isa = PBXGroup; 288 | children = ( 289 | 963EA3C72D32748300EDF8C7 /* movies.json */, 290 | 964938B7276CA666009C6708 /* LaunchScreen.storyboard */, 291 | 964938D6276CBEA4009C6708 /* GeneratedColors.swift */, 292 | 964938D7276CBEA4009C6708 /* GeneratedIcons.swift */, 293 | 964938B5276CA666009C6708 /* Icons.xcassets */, 294 | 964938C7276CAD60009C6708 /* Colors.xcassets */, 295 | 963541362770A7FC0036133D /* Assets.xcassets */, 296 | ); 297 | path = Resources; 298 | sourceTree = ""; 299 | }; 300 | 964938D1276CB5A2009C6708 /* Tabbar */ = { 301 | isa = PBXGroup; 302 | children = ( 303 | 964938D2276CB5F6009C6708 /* UITabBarController.swift */, 304 | ); 305 | path = Tabbar; 306 | sourceTree = ""; 307 | }; 308 | /* End PBXGroup section */ 309 | 310 | /* Begin PBXNativeTarget section */ 311 | 964938A8276CA661009C6708 /* Movie */ = { 312 | isa = PBXNativeTarget; 313 | buildConfigurationList = 964938BD276CA666009C6708 /* Build configuration list for PBXNativeTarget "Movie" */; 314 | buildPhases = ( 315 | 964938A5276CA661009C6708 /* Sources */, 316 | 964938A6276CA661009C6708 /* Frameworks */, 317 | 964938A7276CA661009C6708 /* Resources */, 318 | 967792692CAEDCCC0031C2F3 /* Embed Frameworks */, 319 | ); 320 | buildRules = ( 321 | ); 322 | dependencies = ( 323 | ); 324 | name = Movie; 325 | packageProductDependencies = ( 326 | 96F59F602B41D1BA004C298A /* Kingfisher */, 327 | 96AADD912B47481100D823D6 /* SnapKit-Dynamic */, 328 | ); 329 | productName = Movie; 330 | productReference = 964938A9276CA661009C6708 /* Movie.app */; 331 | productType = "com.apple.product-type.application"; 332 | }; 333 | /* End PBXNativeTarget section */ 334 | 335 | /* Begin PBXProject section */ 336 | 964938A1276CA661009C6708 /* Project object */ = { 337 | isa = PBXProject; 338 | attributes = { 339 | BuildIndependentTargetsInParallel = 1; 340 | LastSwiftUpdateCheck = 1320; 341 | LastUpgradeCheck = 1320; 342 | TargetAttributes = { 343 | 964938A8276CA661009C6708 = { 344 | CreatedOnToolsVersion = 13.2; 345 | }; 346 | }; 347 | }; 348 | buildConfigurationList = 964938A4276CA661009C6708 /* Build configuration list for PBXProject "Movie" */; 349 | compatibilityVersion = "Xcode 13.0"; 350 | developmentRegion = en; 351 | hasScannedForEncodings = 0; 352 | knownRegions = ( 353 | en, 354 | Base, 355 | ); 356 | mainGroup = 964938A0276CA661009C6708; 357 | packageReferences = ( 358 | 96F59F5F2B41D1BA004C298A /* XCRemoteSwiftPackageReference "Kingfisher" */, 359 | 96AADD8E2B47481100D823D6 /* XCRemoteSwiftPackageReference "SnapKit" */, 360 | ); 361 | productRefGroup = 964938AA276CA661009C6708 /* Products */; 362 | projectDirPath = ""; 363 | projectRoot = ""; 364 | targets = ( 365 | 964938A8276CA661009C6708 /* Movie */, 366 | ); 367 | }; 368 | /* End PBXProject section */ 369 | 370 | /* Begin PBXResourcesBuildPhase section */ 371 | 964938A7276CA661009C6708 /* Resources */ = { 372 | isa = PBXResourcesBuildPhase; 373 | buildActionMask = 2147483647; 374 | files = ( 375 | 96290F0E276E8C7F00AFD992 /* .swiftlint.yml in Resources */, 376 | 964938C8276CAD60009C6708 /* Colors.xcassets in Resources */, 377 | 964938B9276CA666009C6708 /* LaunchScreen.storyboard in Resources */, 378 | 963EA3C82D32748300EDF8C7 /* movies.json in Resources */, 379 | 964938B6276CA666009C6708 /* Icons.xcassets in Resources */, 380 | 963541372770A7FC0036133D /* Assets.xcassets in Resources */, 381 | 964938CA276CAE61009C6708 /* swiftgen.yml in Resources */, 382 | ); 383 | runOnlyForDeploymentPostprocessing = 0; 384 | }; 385 | /* End PBXResourcesBuildPhase section */ 386 | 387 | /* Begin PBXSourcesBuildPhase section */ 388 | 964938A5276CA661009C6708 /* Sources */ = { 389 | isa = PBXSourcesBuildPhase; 390 | buildActionMask = 2147483647; 391 | files = ( 392 | 964938C3276CA73D009C6708 /* ViewController + Extension.swift in Sources */, 393 | 9635412A276FCC370036133D /* CommentsCell.swift in Sources */, 394 | 964938D8276CBEA4009C6708 /* GeneratedColors.swift in Sources */, 395 | 964938D3276CB5F6009C6708 /* UITabBarController.swift in Sources */, 396 | 96290EF4276E06D500AFD992 /* MainScreenRouter.swift in Sources */, 397 | 964938C5276CA780009C6708 /* UINavigationController + Extension.swift in Sources */, 398 | 964938B1276CA661009C6708 /* MainScreenViewController.swift in Sources */, 399 | 964938AD276CA661009C6708 /* AppDelegate.swift in Sources */, 400 | 96290F1A276F69BC00AFD992 /* LableImage.swift in Sources */, 401 | 9605D4272B9504F80084D92F /* MainContentView.swift in Sources */, 402 | 96290F15276F397B00AFD992 /* NavigationBarBack.swift in Sources */, 403 | 964938D5276CB7D1009C6708 /* UIImage + Extension.swift in Sources */, 404 | 96290EF5276E06D500AFD992 /* ProResult.swift in Sources */, 405 | 9635412D276FDFEA0036133D /* SectionDecorationView.swift in Sources */, 406 | 96290EFF276E130100AFD992 /* MovieDTO.swift in Sources */, 407 | 96290F05276E5A7C00AFD992 /* NavigationBar.swift in Sources */, 408 | 96290EF6276E06D500AFD992 /* NetworkService.swift in Sources */, 409 | 9635412F276FE0AE0036133D /* CommentsViewFooter.swift in Sources */, 410 | 96290F17276F426C00AFD992 /* TopView.swift in Sources */, 411 | 964938D9276CBEA4009C6708 /* GeneratedIcons.swift in Sources */, 412 | 964938AF276CA661009C6708 /* SceneDelegate.swift in Sources */, 413 | 96290EE4276D19BA00AFD992 /* MainScreenViewModel.swift in Sources */, 414 | 96290EE0276D117700AFD992 /* UICollectionViewCell+Register+extension.swift in Sources */, 415 | 96290EF7276E06D500AFD992 /* BaseRouter.swift in Sources */, 416 | 96290EDE276D0F2E00AFD992 /* PhotoCell.swift in Sources */, 417 | 96290F09276E656F00AFD992 /* HTTPMethods.swift in Sources */, 418 | 96290F11276F384800AFD992 /* MovieDetailedScreenViewController.swift in Sources */, 419 | 96290EF8276E06D500AFD992 /* NetworkErrors.swift in Sources */, 420 | 96290EE2276D130A00AFD992 /* HeaderView.swift in Sources */, 421 | 96290F00276E130100AFD992 /* Comments.swift in Sources */, 422 | ); 423 | runOnlyForDeploymentPostprocessing = 0; 424 | }; 425 | /* End PBXSourcesBuildPhase section */ 426 | 427 | /* Begin PBXVariantGroup section */ 428 | 964938B7276CA666009C6708 /* LaunchScreen.storyboard */ = { 429 | isa = PBXVariantGroup; 430 | children = ( 431 | 964938B8276CA666009C6708 /* Base */, 432 | ); 433 | name = LaunchScreen.storyboard; 434 | sourceTree = ""; 435 | }; 436 | /* End PBXVariantGroup section */ 437 | 438 | /* Begin XCBuildConfiguration section */ 439 | 964938BB276CA666009C6708 /* Debug */ = { 440 | isa = XCBuildConfiguration; 441 | buildSettings = { 442 | ALWAYS_SEARCH_USER_PATHS = NO; 443 | CLANG_ANALYZER_NONNULL = YES; 444 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 445 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 446 | CLANG_CXX_LIBRARY = "libc++"; 447 | CLANG_ENABLE_MODULES = YES; 448 | CLANG_ENABLE_OBJC_ARC = YES; 449 | CLANG_ENABLE_OBJC_WEAK = YES; 450 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 451 | CLANG_WARN_BOOL_CONVERSION = YES; 452 | CLANG_WARN_COMMA = YES; 453 | CLANG_WARN_CONSTANT_CONVERSION = YES; 454 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 455 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 456 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 457 | CLANG_WARN_EMPTY_BODY = YES; 458 | CLANG_WARN_ENUM_CONVERSION = YES; 459 | CLANG_WARN_INFINITE_RECURSION = YES; 460 | CLANG_WARN_INT_CONVERSION = YES; 461 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 462 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 463 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 464 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 465 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 466 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 467 | CLANG_WARN_STRICT_PROTOTYPES = YES; 468 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 469 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 470 | CLANG_WARN_UNREACHABLE_CODE = YES; 471 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 472 | COPY_PHASE_STRIP = NO; 473 | DEBUG_INFORMATION_FORMAT = dwarf; 474 | ENABLE_STRICT_OBJC_MSGSEND = YES; 475 | ENABLE_TESTABILITY = YES; 476 | GCC_C_LANGUAGE_STANDARD = gnu11; 477 | GCC_DYNAMIC_NO_PIC = NO; 478 | GCC_NO_COMMON_BLOCKS = YES; 479 | GCC_OPTIMIZATION_LEVEL = 0; 480 | GCC_PREPROCESSOR_DEFINITIONS = ( 481 | "DEBUG=1", 482 | "$(inherited)", 483 | ); 484 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 485 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 486 | GCC_WARN_UNDECLARED_SELECTOR = YES; 487 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 488 | GCC_WARN_UNUSED_FUNCTION = YES; 489 | GCC_WARN_UNUSED_VARIABLE = YES; 490 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 491 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 492 | MTL_FAST_MATH = YES; 493 | ONLY_ACTIVE_ARCH = YES; 494 | SDKROOT = iphoneos; 495 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 496 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 497 | }; 498 | name = Debug; 499 | }; 500 | 964938BC276CA666009C6708 /* Release */ = { 501 | isa = XCBuildConfiguration; 502 | buildSettings = { 503 | ALWAYS_SEARCH_USER_PATHS = NO; 504 | CLANG_ANALYZER_NONNULL = YES; 505 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 506 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 507 | CLANG_CXX_LIBRARY = "libc++"; 508 | CLANG_ENABLE_MODULES = YES; 509 | CLANG_ENABLE_OBJC_ARC = YES; 510 | CLANG_ENABLE_OBJC_WEAK = YES; 511 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 512 | CLANG_WARN_BOOL_CONVERSION = YES; 513 | CLANG_WARN_COMMA = YES; 514 | CLANG_WARN_CONSTANT_CONVERSION = YES; 515 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 516 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 517 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 518 | CLANG_WARN_EMPTY_BODY = YES; 519 | CLANG_WARN_ENUM_CONVERSION = YES; 520 | CLANG_WARN_INFINITE_RECURSION = YES; 521 | CLANG_WARN_INT_CONVERSION = YES; 522 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 523 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 524 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 525 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 526 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 527 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 528 | CLANG_WARN_STRICT_PROTOTYPES = YES; 529 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 530 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 531 | CLANG_WARN_UNREACHABLE_CODE = YES; 532 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 533 | COPY_PHASE_STRIP = NO; 534 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 535 | ENABLE_NS_ASSERTIONS = NO; 536 | ENABLE_STRICT_OBJC_MSGSEND = YES; 537 | GCC_C_LANGUAGE_STANDARD = gnu11; 538 | GCC_NO_COMMON_BLOCKS = YES; 539 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 540 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 541 | GCC_WARN_UNDECLARED_SELECTOR = YES; 542 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 543 | GCC_WARN_UNUSED_FUNCTION = YES; 544 | GCC_WARN_UNUSED_VARIABLE = YES; 545 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 546 | MTL_ENABLE_DEBUG_INFO = NO; 547 | MTL_FAST_MATH = YES; 548 | SDKROOT = iphoneos; 549 | SWIFT_COMPILATION_MODE = wholemodule; 550 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 551 | VALIDATE_PRODUCT = YES; 552 | }; 553 | name = Release; 554 | }; 555 | 964938BE276CA666009C6708 /* Debug */ = { 556 | isa = XCBuildConfiguration; 557 | buildSettings = { 558 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 559 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 560 | CODE_SIGN_STYLE = Automatic; 561 | CURRENT_PROJECT_VERSION = 1; 562 | DEVELOPMENT_TEAM = W652FWVRGR; 563 | GENERATE_INFOPLIST_FILE = YES; 564 | INFOPLIST_FILE = Movie/Info.plist; 565 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 566 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 567 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 568 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 569 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 570 | LD_RUNPATH_SEARCH_PATHS = ( 571 | "$(inherited)", 572 | "@executable_path/Frameworks", 573 | ); 574 | MARKETING_VERSION = 1.0; 575 | PRODUCT_BUNDLE_IDENTIFIER = com.SiuzannaKaragulova.Movie; 576 | PRODUCT_NAME = "$(TARGET_NAME)"; 577 | SWIFT_EMIT_LOC_STRINGS = YES; 578 | SWIFT_VERSION = 5.0; 579 | TARGETED_DEVICE_FAMILY = "1,2"; 580 | }; 581 | name = Debug; 582 | }; 583 | 964938BF276CA666009C6708 /* Release */ = { 584 | isa = XCBuildConfiguration; 585 | buildSettings = { 586 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 587 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 588 | CODE_SIGN_STYLE = Automatic; 589 | CURRENT_PROJECT_VERSION = 1; 590 | DEVELOPMENT_TEAM = W652FWVRGR; 591 | GENERATE_INFOPLIST_FILE = YES; 592 | INFOPLIST_FILE = Movie/Info.plist; 593 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 594 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 595 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 596 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 597 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 598 | LD_RUNPATH_SEARCH_PATHS = ( 599 | "$(inherited)", 600 | "@executable_path/Frameworks", 601 | ); 602 | MARKETING_VERSION = 1.0; 603 | PRODUCT_BUNDLE_IDENTIFIER = com.SiuzannaKaragulova.Movie; 604 | PRODUCT_NAME = "$(TARGET_NAME)"; 605 | SWIFT_EMIT_LOC_STRINGS = YES; 606 | SWIFT_VERSION = 5.0; 607 | TARGETED_DEVICE_FAMILY = "1,2"; 608 | }; 609 | name = Release; 610 | }; 611 | /* End XCBuildConfiguration section */ 612 | 613 | /* Begin XCConfigurationList section */ 614 | 964938A4276CA661009C6708 /* Build configuration list for PBXProject "Movie" */ = { 615 | isa = XCConfigurationList; 616 | buildConfigurations = ( 617 | 964938BB276CA666009C6708 /* Debug */, 618 | 964938BC276CA666009C6708 /* Release */, 619 | ); 620 | defaultConfigurationIsVisible = 0; 621 | defaultConfigurationName = Release; 622 | }; 623 | 964938BD276CA666009C6708 /* Build configuration list for PBXNativeTarget "Movie" */ = { 624 | isa = XCConfigurationList; 625 | buildConfigurations = ( 626 | 964938BE276CA666009C6708 /* Debug */, 627 | 964938BF276CA666009C6708 /* Release */, 628 | ); 629 | defaultConfigurationIsVisible = 0; 630 | defaultConfigurationName = Release; 631 | }; 632 | /* End XCConfigurationList section */ 633 | 634 | /* Begin XCRemoteSwiftPackageReference section */ 635 | 96AADD8E2B47481100D823D6 /* XCRemoteSwiftPackageReference "SnapKit" */ = { 636 | isa = XCRemoteSwiftPackageReference; 637 | repositoryURL = "https://github.com/SnapKit/SnapKit.git"; 638 | requirement = { 639 | kind = upToNextMajorVersion; 640 | minimumVersion = 5.7.0; 641 | }; 642 | }; 643 | 96F59F5F2B41D1BA004C298A /* XCRemoteSwiftPackageReference "Kingfisher" */ = { 644 | isa = XCRemoteSwiftPackageReference; 645 | repositoryURL = "https://github.com/onevcat/Kingfisher.git"; 646 | requirement = { 647 | kind = upToNextMajorVersion; 648 | minimumVersion = 7.10.1; 649 | }; 650 | }; 651 | /* End XCRemoteSwiftPackageReference section */ 652 | 653 | /* Begin XCSwiftPackageProductDependency section */ 654 | 96AADD912B47481100D823D6 /* SnapKit-Dynamic */ = { 655 | isa = XCSwiftPackageProductDependency; 656 | package = 96AADD8E2B47481100D823D6 /* XCRemoteSwiftPackageReference "SnapKit" */; 657 | productName = "SnapKit-Dynamic"; 658 | }; 659 | 96F59F602B41D1BA004C298A /* Kingfisher */ = { 660 | isa = XCSwiftPackageProductDependency; 661 | package = 96F59F5F2B41D1BA004C298A /* XCRemoteSwiftPackageReference "Kingfisher" */; 662 | productName = Kingfisher; 663 | }; 664 | /* End XCSwiftPackageProductDependency section */ 665 | }; 666 | rootObject = 964938A1276CA661009C6708 /* Project object */; 667 | } 668 | --------------------------------------------------------------------------------