├── screenshot.jpg
├── TraktTV
├── Assets.xcassets
│ ├── Contents.json
│ └── App Icon & Top Shelf Image.brandassets
│ │ ├── App Icon.imagestack
│ │ ├── Back.imagestacklayer
│ │ │ ├── Contents.json
│ │ │ └── Content.imageset
│ │ │ │ └── Contents.json
│ │ ├── Front.imagestacklayer
│ │ │ ├── Contents.json
│ │ │ └── Content.imageset
│ │ │ │ └── Contents.json
│ │ ├── Middle.imagestacklayer
│ │ │ ├── Contents.json
│ │ │ └── Content.imageset
│ │ │ │ └── Contents.json
│ │ └── Contents.json
│ │ ├── App Icon - App Store.imagestack
│ │ ├── Back.imagestacklayer
│ │ │ ├── Contents.json
│ │ │ └── Content.imageset
│ │ │ │ └── Contents.json
│ │ ├── Front.imagestacklayer
│ │ │ ├── Contents.json
│ │ │ └── Content.imageset
│ │ │ │ └── Contents.json
│ │ ├── Middle.imagestacklayer
│ │ │ ├── Contents.json
│ │ │ └── Content.imageset
│ │ │ │ └── Contents.json
│ │ └── Contents.json
│ │ ├── Top Shelf Image.imageset
│ │ └── Contents.json
│ │ ├── Top Shelf Image Wide.imageset
│ │ └── Contents.json
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── Features
│ ├── Networking
│ │ ├── Models
│ │ │ ├── Endpoint.swift
│ │ │ ├── RequestModifier.swift
│ │ │ └── Request.swift
│ │ ├── Extensions
│ │ │ ├── URL.swift
│ │ │ └── URLQueryItem.swift
│ │ └── Logic
│ │ │ └── DataLoader.swift
│ └── Search
│ │ ├── Model
│ │ ├── Images.swift
│ │ ├── Endpoint+Images.swift
│ │ ├── Endpoint+Search.swift
│ │ └── Show.swift
│ │ ├── Logic
│ │ ├── ImageService.swift
│ │ ├── SearchService.swift
│ │ └── SearchStore.swift
│ │ └── Views
│ │ ├── PosterView.swift
│ │ └── ShelfView.swift
├── ContentView.swift
├── Info.plist
├── Base.lproj
│ └── LaunchScreen.storyboard
└── AppDelegate.swift
├── README.md
└── TraktTV.xcodeproj
├── project.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── xcuserdata
└── majid.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
└── project.pbxproj
/screenshot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mecid/swiftui-trakttv-app/HEAD/screenshot.jpg
--------------------------------------------------------------------------------
/TraktTV/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # swiftui-trakttv-app
2 | Simple tvOS app that shows trending shows written in SwiftUI
3 |
4 | 
5 |
--------------------------------------------------------------------------------
/TraktTV/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/TraktTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/TraktTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/TraktTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/TraktTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/TraktTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/TraktTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/TraktTV.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/TraktTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/TraktTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/TraktTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/TraktTV.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/TraktTV/Features/Networking/Models/Endpoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Endpoint.swift
3 | // ShowBot
4 | //
5 | // Created by Majid Jabrayilov on 12/20/18.
6 | // Copyright © 2018 aaplab. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct Endpoint: Request {
12 | let path: String
13 | let queryItems: [URLQueryItem]
14 | }
15 |
--------------------------------------------------------------------------------
/TraktTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/TraktTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/TraktTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/TraktTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "layers" : [
7 | {
8 | "filename" : "Front.imagestacklayer"
9 | },
10 | {
11 | "filename" : "Middle.imagestacklayer"
12 | },
13 | {
14 | "filename" : "Back.imagestacklayer"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/TraktTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "layers" : [
7 | {
8 | "filename" : "Front.imagestacklayer"
9 | },
10 | {
11 | "filename" : "Middle.imagestacklayer"
12 | },
13 | {
14 | "filename" : "Back.imagestacklayer"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/TraktTV.xcodeproj/xcuserdata/majid.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | TraktTV.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/TraktTV/Features/Networking/Extensions/URL.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URL.swift
3 | // ShowBot
4 | //
5 | // Created by Majid Jabrayilov on 12/19/18.
6 | // Copyright © 2018 aaplab. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension URL {
12 | init(staticString string: StaticString) {
13 | guard let url = URL(string: "\(string)") else {
14 | preconditionFailure("Invalid URL string: \(string)")
15 | }
16 |
17 | self = url
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/TraktTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "tv-marketing",
13 | "scale" : "1x"
14 | },
15 | {
16 | "idiom" : "tv-marketing",
17 | "scale" : "2x"
18 | }
19 | ],
20 | "info" : {
21 | "author" : "xcode",
22 | "version" : 1
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/TraktTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "tv-marketing",
13 | "scale" : "1x"
14 | },
15 | {
16 | "idiom" : "tv-marketing",
17 | "scale" : "2x"
18 | }
19 | ],
20 | "info" : {
21 | "author" : "xcode",
22 | "version" : 1
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/TraktTV/Features/Search/Model/Images.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Images.swift
3 | // ShowBot
4 | //
5 | // Created by Majid Jabrayilov on 1/3/19.
6 | // Copyright © 2019 aaplab. All rights reserved.
7 | //
8 | import Foundation
9 |
10 | struct Still: Codable {
11 | let filePath: String
12 | let aspectRatio: Double
13 | }
14 |
15 | extension Still {
16 | var url: URL? {
17 | return URL(string: "https://image.tmdb.org/t/p/original\(filePath)")
18 | }
19 | }
20 |
21 | struct TMDBImages: Codable {
22 | let id: Int
23 | let backdrops: [Still]
24 | let posters: [Still]
25 | }
26 |
--------------------------------------------------------------------------------
/TraktTV/Features/Search/Model/Endpoint+Images.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImagesRequest.swift
3 | // ShowBot
4 | //
5 | // Created by Majid Jabrayilov on 1/3/19.
6 | // Copyright © 2019 aaplab. All rights reserved.
7 | //
8 | import Foundation
9 |
10 | struct ImagesRequest: Request {
11 | let show: Int
12 |
13 | var host: String { "api.themoviedb.org" }
14 | var path: String { return "/3/tv/\(show)/images" }
15 | var queryItems: [URLQueryItem] {
16 | [
17 | .init(name: "api_key", value: "26032f00317cb09595066add499cb5b4"),
18 | .init(name: "language", value: "en")
19 | ]
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TraktTV/Features/Search/Model/Endpoint+Search.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Endpoint+Search.swift
3 | // ShowBot
4 | //
5 | // Created by Majid Jabrayilov on 12/21/18.
6 | // Copyright © 2018 aaplab. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum SearchType: String, CaseIterable {
12 | case popular
13 | case trending
14 | case anticipated
15 | }
16 |
17 | extension Endpoint {
18 | static func search(in type: SearchType, limit: Int = 20) -> Endpoint {
19 | return Endpoint(
20 | path: "/shows/\(type.rawValue)",
21 | queryItems: [.extended(), .limit(value: limit)]
22 | )
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/TraktTV/Features/Networking/Extensions/URLQueryItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLQueryItem.swift
3 | // ShowBot
4 | //
5 | // Created by Majid Jabrayilov on 3/17/19.
6 | // Copyright © 2019 aaplab. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension URLQueryItem {
12 | static func limit(value: Int) -> URLQueryItem {
13 | return .init(name: "limit", value: String(value))
14 | }
15 |
16 | static func extended(value: String = "full") -> URLQueryItem {
17 | return .init(name: "extended", value: value)
18 | }
19 |
20 | static func query(value: String) -> URLQueryItem {
21 | return .init(name: "query", value: value)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/TraktTV/Features/Search/Logic/ImageService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageService.swift
3 | // ShowBot
4 | //
5 | // Created by Majid Jabrayilov on 1/3/19.
6 | // Copyright © 2019 aaplab. All rights reserved.
7 | //
8 | import Combine
9 | import Foundation
10 |
11 | final class ImageService {
12 | private let loader: DataLoader
13 | private let decoder: JSONDecoder
14 |
15 | init(loader: DataLoader, decoder: JSONDecoder) {
16 | self.loader = loader
17 | self.decoder = decoder
18 | }
19 |
20 | func fetch(for show: Int) -> AnyPublisher {
21 | return loader
22 | .load(ImagesRequest(show: show))
23 | .decode(type: TMDBImages.self, decoder: decoder)
24 | .eraseToAnyPublisher()
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/TraktTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "assets" : [
3 | {
4 | "filename" : "App Icon - App Store.imagestack",
5 | "idiom" : "tv",
6 | "role" : "primary-app-icon",
7 | "size" : "1280x768"
8 | },
9 | {
10 | "filename" : "App Icon.imagestack",
11 | "idiom" : "tv",
12 | "role" : "primary-app-icon",
13 | "size" : "400x240"
14 | },
15 | {
16 | "filename" : "Top Shelf Image Wide.imageset",
17 | "idiom" : "tv",
18 | "role" : "top-shelf-image-wide",
19 | "size" : "2320x720"
20 | },
21 | {
22 | "filename" : "Top Shelf Image.imageset",
23 | "idiom" : "tv",
24 | "role" : "top-shelf-image",
25 | "size" : "1920x720"
26 | }
27 | ],
28 | "info" : {
29 | "author" : "xcode",
30 | "version" : 1
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/TraktTV/Features/Networking/Logic/DataLoader.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DataLoader.swift
3 | // ShowBot
4 | //
5 | // Created by Majid Jabrayilov on 12/19/18.
6 | // Copyright © 2018 aaplab. All rights reserved.
7 | //
8 | import Combine
9 | import Foundation
10 |
11 | final class DataLoader {
12 | private let session: URLSession
13 | private let modifiers: [RequestModifier]
14 |
15 | init(session: URLSession = .shared, modifiers: [RequestModifier] = []) {
16 | self.session = session
17 | self.modifiers = modifiers
18 | }
19 |
20 | func load(_ request: Request) -> AnyPublisher {
21 | let modifiedRequest = modifiers.reduce(request.build()) { $1.modifyRequest($0) }
22 |
23 | return session
24 | .dataTaskPublisher(for: modifiedRequest)
25 | .mapError { $0 }
26 | .map(\.data)
27 | .eraseToAnyPublisher()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/TraktTV/Features/Networking/Models/RequestModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RequestModifier.swift
3 | // ShowBot
4 | //
5 | // Created by Majid Jabrayilov on 5/26/19.
6 | // Copyright © 2019 aaplab. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol RequestModifier {
12 | func modifyRequest(_ request: URLRequest) -> URLRequest
13 | }
14 |
15 | struct ApiKeyModifier: RequestModifier {
16 | private enum Keys {
17 | static let clientId = "1f481b53997fd687e4956e97747af94f41a502460ce18d0bff8c65640072fc57"
18 | }
19 |
20 | func modifyRequest(_ request: URLRequest) -> URLRequest {
21 | var request = request
22 | request.addValue(Keys.clientId, forHTTPHeaderField: "trakt-api-key")
23 | request.addValue("2", forHTTPHeaderField: "trakt-api-version")
24 | request.addValue("application/json", forHTTPHeaderField: "Content-type")
25 | return request
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/TraktTV/Features/Search/Model/Show.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Show.swift
3 | // ShowBot
4 | //
5 | // Created by Majid Jabrayilov on 12/21/18.
6 | // Copyright © 2018 aaplab. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct Show: Codable, Hashable {
12 | let ids: Ids
13 | let title: String
14 | let year: Int?
15 | let overview: String?
16 | let runtime: Int?
17 | let certification: String?
18 | let network: String?
19 | let country: String?
20 | let trailer: String?
21 | let homepage: String?
22 | let status: String?
23 | let rating: Double?
24 | let votes: Int?
25 | let commentCount: Int?
26 | let updatedAt: Date?
27 | let language: String?
28 | let availableTranslations: [String]?
29 | let genres: [String]?
30 | let airedEpisodes: Int?
31 | }
32 |
33 | struct Ids: Codable, Hashable {
34 | let trakt: Int
35 | let tvdb: Int?
36 | let tmdb: Int?
37 | let slug: String?
38 | let imdb: String?
39 | }
40 |
--------------------------------------------------------------------------------
/TraktTV/Features/Search/Views/PosterView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PosterView.swift
3 | // TraktTV
4 | //
5 | // Created by Majid Jabrayilov on 4/21/20.
6 | // Copyright © 2020 Majid Jabrayilov. All rights reserved.
7 | //
8 | import SwiftUI
9 | import KingfisherSwiftUI
10 |
11 | struct PosterView: View {
12 | let image: URL?
13 | let title: String
14 |
15 | @State private var isFocused = false
16 |
17 | var body: some View {
18 | VStack {
19 | image.map {
20 | KFImage($0)
21 | .resizable()
22 | .background(Color.gray)
23 | .frame(width: 400, height: 235, alignment: .center)
24 | }
25 | .cornerRadius(16)
26 | .shadow(radius: 16)
27 |
28 | if isFocused {
29 | Text(title)
30 | }
31 | }
32 | .scaleEffect(isFocused ? 1.05 : 1.0)
33 | .focusable(true) { self.isFocused = $0 }
34 | .animation(.default)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/TraktTV/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // TraktTV
4 | //
5 | // Created by Majid Jabrayilov on 4/20/20.
6 | // Copyright © 2020 Majid Jabrayilov. All rights reserved.
7 | //
8 | import SwiftUI
9 | import KingfisherSwiftUI
10 |
11 | struct ContentView: View {
12 | @EnvironmentObject var store: SearchStore
13 |
14 | var body: some View {
15 | NavigationView {
16 | ScrollView {
17 | VStack(alignment: .leading) {
18 | ForEach(SearchType.allCases, id: \.self) { type in
19 | ShelfView(
20 | title: type.rawValue.capitalized,
21 | shows: self.store.listOfShows(for: type)
22 | )
23 | }
24 | }
25 | }.onAppear(perform: store.fetch)
26 | }
27 | }
28 | }
29 |
30 | struct ContentView_Previews: PreviewProvider {
31 | static var previews: some View {
32 | ContentView()
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/TraktTV/Features/Search/Logic/SearchService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchService.swift
3 | // ShowBot
4 | //
5 | // Created by Majid Jabrayilov on 3/17/19.
6 | // Copyright © 2019 aaplab. All rights reserved.
7 | //
8 | import Combine
9 | import Foundation
10 |
11 | final class SearchService {
12 | private let loader: DataLoader
13 | private let decoder: JSONDecoder
14 |
15 | init(loader: DataLoader, decoder: JSONDecoder) {
16 | self.loader = loader
17 | self.decoder = decoder
18 | }
19 |
20 | private struct ShowContainer: Decodable {
21 | let show: Show
22 | }
23 |
24 | func fetchShows(in type: SearchType, limit: Int = 20) -> AnyPublisher<[Show], Error> {
25 | if case .popular = type {
26 | return loader.load(Endpoint.search(in: type, limit: limit))
27 | .decode(type: [Show].self, decoder: decoder)
28 | .eraseToAnyPublisher()
29 | } else {
30 | return loader.load(Endpoint.search(in: type, limit: limit))
31 | .decode(type: [ShowContainer].self, decoder: decoder)
32 | .map { $0.map(\.show) }
33 | .eraseToAnyPublisher()
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/TraktTV/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIRequiredDeviceCapabilities
26 |
27 | arm64
28 |
29 | UIUserInterfaceStyle
30 | Automatic
31 | NSAppTransportSecurity
32 |
33 | NSAllowsArbitraryLoads
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/TraktTV/Features/Networking/Models/Request.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | enum HTTPMethod: String {
4 | case put = "PUT"
5 | case post = "POST"
6 | case get = "GET"
7 | case delete = "DELETE"
8 | case head = "HEAD"
9 | }
10 |
11 | protocol Request {
12 | var scheme: String { get }
13 | var method: HTTPMethod { get }
14 | var path: String { get }
15 | var host: String { get }
16 | var queryItems: [URLQueryItem] { get }
17 | var headers: [String: String] { get }
18 | var body: Data? { get }
19 | }
20 |
21 | extension Request {
22 | var method: HTTPMethod { return .get }
23 | var scheme: String { return "https" }
24 | var headers: [String: String] { return [:] }
25 | var host: String { return "api.trakt.tv" }
26 | var body: Data? { return nil }
27 | }
28 |
29 | extension Request {
30 | func build() -> URLRequest {
31 | var components = URLComponents()
32 | components.scheme = scheme
33 | components.host = host
34 | components.path = path
35 | components.queryItems = queryItems
36 |
37 | guard let url = components.url else {
38 | preconditionFailure("Invalid url components")
39 | }
40 |
41 | var request = URLRequest(url: url)
42 | request.allHTTPHeaderFields = headers
43 | request.httpMethod = method.rawValue
44 | request.httpBody = body
45 | return request
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/TraktTV/Features/Search/Views/ShelfView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShelfView.swift
3 | // TraktTV
4 | //
5 | // Created by Majid Jabrayilov on 4/21/20.
6 | // Copyright © 2020 Majid Jabrayilov. All rights reserved.
7 | //
8 | import SwiftUI
9 |
10 | struct ShelfView: View {
11 | @EnvironmentObject var store: SearchStore
12 |
13 | let title: String
14 | let shows: [Show]
15 |
16 | var body: some View {
17 | ScrollView(.horizontal) {
18 | VStack(alignment: .leading) {
19 | Text(title)
20 | .font(.headline)
21 |
22 | HStack {
23 | ForEach(shows, id: \.ids) { show in
24 | PosterView(
25 | image: self.store.posters[show.ids.tmdb ?? 0],
26 | title: show.title
27 | )
28 | }
29 | }
30 | }.padding()
31 | }
32 | }
33 | }
34 |
35 | struct ShelfView_Previews: PreviewProvider {
36 | static var previews: some View {
37 | let loader = DataLoader(session: .shared, modifiers: [])
38 | let search = SearchService(loader: loader, decoder: .init())
39 | let image = ImageService(loader: loader, decoder: .init())
40 | let store = SearchStore(searchService: search, imageService: image)
41 | return ShelfView(title: "Trending", shows: [])
42 | .environmentObject(store)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/TraktTV/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 |
--------------------------------------------------------------------------------
/TraktTV/Features/Search/Logic/SearchStore.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchStore.swift
3 | // TraktTV
4 | //
5 | // Created by Majid Jabrayilov on 4/20/20.
6 | // Copyright © 2020 Majid Jabrayilov. All rights reserved.
7 | //
8 | import Foundation
9 | import Combine
10 |
11 | final class SearchStore: ObservableObject {
12 | @Published private(set) var allShows: [Ids: Show] = [:]
13 | @Published private(set) var posters: [Int: URL] = [:]
14 | @Published private(set) var categories: [SearchType: [Ids]] = [:]
15 |
16 | private var cancellables: Set = []
17 |
18 | private let searchService: SearchService
19 | private let imageService: ImageService
20 |
21 | init(searchService: SearchService, imageService: ImageService) {
22 | self.searchService = searchService
23 | self.imageService = imageService
24 | }
25 |
26 | func fetch() {
27 | fetchShows(for: .popular)
28 | fetchShows(for: .trending)
29 | fetchShows(for: .anticipated)
30 | }
31 |
32 | func listOfShows(for type: SearchType) -> [Show] {
33 | let ids = categories[type] ?? []
34 | return ids.compactMap { allShows[$0] }
35 | }
36 |
37 | private func fetchShows(for type: SearchType) {
38 | searchService
39 | .fetchShows(in: type)
40 | .receive(on: DispatchQueue.main)
41 | .handleEvents(receiveOutput: { [weak self] shows in
42 | shows.forEach { self?.allShows[$0.ids] = $0 }
43 | self?.categories[type] = shows.map { $0.ids }
44 | })
45 | .receive(on: DispatchQueue.global())
46 | .flatMap {
47 | Publishers.Sequence(sequence: $0)
48 | .flatMap { self.imageService.fetch(for: $0.ids.tmdb ?? 0) }
49 | }
50 | .receive(on: DispatchQueue.main)
51 | .sink(
52 | receiveCompletion: { _ in },
53 | receiveValue: { [weak self] in self?.posters[$0.id] = $0.backdrops.first?.url }
54 | )
55 | .store(in: &cancellables)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/TraktTV/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // TraktTV
4 | //
5 | // Created by Majid Jabrayilov on 4/20/20.
6 | // Copyright © 2020 Majid Jabrayilov. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SwiftUI
11 |
12 | @UIApplicationMain
13 | class AppDelegate: UIResponder, UIApplicationDelegate {
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | let iso8601 = DateFormatter()
19 | iso8601.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
20 |
21 | let decoder = JSONDecoder()
22 | decoder.keyDecodingStrategy = .convertFromSnakeCase
23 | decoder.dateDecodingStrategy = .formatted(iso8601)
24 |
25 | let loader = DataLoader(session: .shared, modifiers: [ApiKeyModifier()])
26 | let searchService = SearchService(loader: loader, decoder: decoder)
27 | let imageService = ImageService(loader: loader, decoder: decoder)
28 | let store = SearchStore(searchService: searchService, imageService: imageService)
29 | // Create the SwiftUI view that provides the window contents.
30 | let contentView = ContentView().environmentObject(store)
31 |
32 | // Use a UIHostingController as window root view controller.
33 | let window = UIWindow(frame: UIScreen.main.bounds)
34 | window.rootViewController = UIHostingController(rootView: contentView)
35 | self.window = window
36 | window.makeKeyAndVisible()
37 | return true
38 | }
39 |
40 | func applicationWillResignActive(_ application: UIApplication) {
41 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
42 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
43 | }
44 |
45 | func applicationDidEnterBackground(_ application: UIApplication) {
46 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
47 | }
48 |
49 | func applicationWillEnterForeground(_ application: UIApplication) {
50 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
51 | }
52 |
53 | func applicationDidBecomeActive(_ application: UIApplication) {
54 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
55 | }
56 |
57 |
58 | }
59 |
60 |
--------------------------------------------------------------------------------
/TraktTV.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 52;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1C53B54D244DD38F00B044C0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C53B54C244DD38F00B044C0 /* AppDelegate.swift */; };
11 | 1C53B54F244DD38F00B044C0 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C53B54E244DD38F00B044C0 /* ContentView.swift */; };
12 | 1C53B551244DD39000B044C0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1C53B550244DD39000B044C0 /* Assets.xcassets */; };
13 | 1C53B554244DD39000B044C0 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1C53B553244DD39000B044C0 /* Preview Assets.xcassets */; };
14 | 1C53B557244DD39000B044C0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1C53B555244DD39000B044C0 /* LaunchScreen.storyboard */; };
15 | 1C53B56A244DD3CE00B044C0 /* DataLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C53B562244DD3CE00B044C0 /* DataLoader.swift */; };
16 | 1C53B56B244DD3CE00B044C0 /* RequestModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C53B564244DD3CE00B044C0 /* RequestModifier.swift */; };
17 | 1C53B56C244DD3CE00B044C0 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C53B565244DD3CE00B044C0 /* Request.swift */; };
18 | 1C53B56D244DD3CE00B044C0 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C53B566244DD3CE00B044C0 /* Endpoint.swift */; };
19 | 1C53B56E244DD3CE00B044C0 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C53B568244DD3CE00B044C0 /* URL.swift */; };
20 | 1C53B56F244DD3CE00B044C0 /* URLQueryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C53B569244DD3CE00B044C0 /* URLQueryItem.swift */; };
21 | 1C53B575244DD48A00B044C0 /* SearchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C53B574244DD48A00B044C0 /* SearchService.swift */; };
22 | 1C53B577244DD4A100B044C0 /* Endpoint+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C53B576244DD4A100B044C0 /* Endpoint+Search.swift */; };
23 | 1C53B57B244DD51A00B044C0 /* Show.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C53B57A244DD51A00B044C0 /* Show.swift */; };
24 | 1C53B57D244DD59A00B044C0 /* SearchStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C53B57C244DD59A00B044C0 /* SearchStore.swift */; };
25 | 1C53B583244DD70300B044C0 /* Endpoint+Images.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C53B582244DD70300B044C0 /* Endpoint+Images.swift */; };
26 | 1C53B585244DD71C00B044C0 /* Images.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C53B584244DD71B00B044C0 /* Images.swift */; };
27 | 1C53B588244E0F2E00B044C0 /* KingfisherSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 1C53B587244E0F2E00B044C0 /* KingfisherSwiftUI */; };
28 | 1C7BDF51244F237F003880CE /* ImageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7BDF50244F237F003880CE /* ImageService.swift */; };
29 | 1CCD168B244F1A84004AD4CE /* PosterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CCD168A244F1A84004AD4CE /* PosterView.swift */; };
30 | 1CCD168D244F1CED004AD4CE /* ShelfView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CCD168C244F1CED004AD4CE /* ShelfView.swift */; };
31 | /* End PBXBuildFile section */
32 |
33 | /* Begin PBXFileReference section */
34 | 1C53B549244DD38F00B044C0 /* TraktTV.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TraktTV.app; sourceTree = BUILT_PRODUCTS_DIR; };
35 | 1C53B54C244DD38F00B044C0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
36 | 1C53B54E244DD38F00B044C0 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
37 | 1C53B550244DD39000B044C0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
38 | 1C53B553244DD39000B044C0 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
39 | 1C53B556244DD39000B044C0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
40 | 1C53B558244DD39000B044C0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
41 | 1C53B562244DD3CE00B044C0 /* DataLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataLoader.swift; sourceTree = ""; };
42 | 1C53B564244DD3CE00B044C0 /* RequestModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestModifier.swift; sourceTree = ""; };
43 | 1C53B565244DD3CE00B044C0 /* Request.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = ""; };
44 | 1C53B566244DD3CE00B044C0 /* Endpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Endpoint.swift; sourceTree = ""; };
45 | 1C53B568244DD3CE00B044C0 /* URL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = ""; };
46 | 1C53B569244DD3CE00B044C0 /* URLQueryItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLQueryItem.swift; sourceTree = ""; };
47 | 1C53B574244DD48A00B044C0 /* SearchService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchService.swift; sourceTree = ""; };
48 | 1C53B576244DD4A100B044C0 /* Endpoint+Search.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Endpoint+Search.swift"; sourceTree = ""; };
49 | 1C53B57A244DD51A00B044C0 /* Show.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Show.swift; sourceTree = ""; };
50 | 1C53B57C244DD59A00B044C0 /* SearchStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchStore.swift; sourceTree = ""; };
51 | 1C53B582244DD70300B044C0 /* Endpoint+Images.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Endpoint+Images.swift"; sourceTree = ""; };
52 | 1C53B584244DD71B00B044C0 /* Images.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Images.swift; sourceTree = ""; };
53 | 1C7BDF50244F237F003880CE /* ImageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageService.swift; sourceTree = ""; };
54 | 1CCD168A244F1A84004AD4CE /* PosterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PosterView.swift; sourceTree = ""; };
55 | 1CCD168C244F1CED004AD4CE /* ShelfView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShelfView.swift; sourceTree = ""; };
56 | /* End PBXFileReference section */
57 |
58 | /* Begin PBXFrameworksBuildPhase section */
59 | 1C53B546244DD38F00B044C0 /* Frameworks */ = {
60 | isa = PBXFrameworksBuildPhase;
61 | buildActionMask = 2147483647;
62 | files = (
63 | 1C53B588244E0F2E00B044C0 /* KingfisherSwiftUI in Frameworks */,
64 | );
65 | runOnlyForDeploymentPostprocessing = 0;
66 | };
67 | /* End PBXFrameworksBuildPhase section */
68 |
69 | /* Begin PBXGroup section */
70 | 1C53B540244DD38F00B044C0 = {
71 | isa = PBXGroup;
72 | children = (
73 | 1C53B54B244DD38F00B044C0 /* TraktTV */,
74 | 1C53B54A244DD38F00B044C0 /* Products */,
75 | );
76 | sourceTree = "";
77 | };
78 | 1C53B54A244DD38F00B044C0 /* Products */ = {
79 | isa = PBXGroup;
80 | children = (
81 | 1C53B549244DD38F00B044C0 /* TraktTV.app */,
82 | );
83 | name = Products;
84 | sourceTree = "";
85 | };
86 | 1C53B54B244DD38F00B044C0 /* TraktTV */ = {
87 | isa = PBXGroup;
88 | children = (
89 | 1C53B55F244DD3A600B044C0 /* Features */,
90 | 1C53B54C244DD38F00B044C0 /* AppDelegate.swift */,
91 | 1C53B54E244DD38F00B044C0 /* ContentView.swift */,
92 | 1C53B550244DD39000B044C0 /* Assets.xcassets */,
93 | 1C53B555244DD39000B044C0 /* LaunchScreen.storyboard */,
94 | 1C53B558244DD39000B044C0 /* Info.plist */,
95 | 1C53B552244DD39000B044C0 /* Preview Content */,
96 | );
97 | path = TraktTV;
98 | sourceTree = "";
99 | };
100 | 1C53B552244DD39000B044C0 /* Preview Content */ = {
101 | isa = PBXGroup;
102 | children = (
103 | 1C53B553244DD39000B044C0 /* Preview Assets.xcassets */,
104 | );
105 | path = "Preview Content";
106 | sourceTree = "";
107 | };
108 | 1C53B55F244DD3A600B044C0 /* Features */ = {
109 | isa = PBXGroup;
110 | children = (
111 | 1C53B570244DD45200B044C0 /* Search */,
112 | 1C53B560244DD3CE00B044C0 /* Networking */,
113 | );
114 | path = Features;
115 | sourceTree = "";
116 | };
117 | 1C53B560244DD3CE00B044C0 /* Networking */ = {
118 | isa = PBXGroup;
119 | children = (
120 | 1C53B561244DD3CE00B044C0 /* Logic */,
121 | 1C53B563244DD3CE00B044C0 /* Models */,
122 | 1C53B567244DD3CE00B044C0 /* Extensions */,
123 | );
124 | path = Networking;
125 | sourceTree = "";
126 | };
127 | 1C53B561244DD3CE00B044C0 /* Logic */ = {
128 | isa = PBXGroup;
129 | children = (
130 | 1C53B562244DD3CE00B044C0 /* DataLoader.swift */,
131 | );
132 | path = Logic;
133 | sourceTree = "";
134 | };
135 | 1C53B563244DD3CE00B044C0 /* Models */ = {
136 | isa = PBXGroup;
137 | children = (
138 | 1C53B564244DD3CE00B044C0 /* RequestModifier.swift */,
139 | 1C53B565244DD3CE00B044C0 /* Request.swift */,
140 | 1C53B566244DD3CE00B044C0 /* Endpoint.swift */,
141 | );
142 | path = Models;
143 | sourceTree = "";
144 | };
145 | 1C53B567244DD3CE00B044C0 /* Extensions */ = {
146 | isa = PBXGroup;
147 | children = (
148 | 1C53B568244DD3CE00B044C0 /* URL.swift */,
149 | 1C53B569244DD3CE00B044C0 /* URLQueryItem.swift */,
150 | );
151 | path = Extensions;
152 | sourceTree = "";
153 | };
154 | 1C53B570244DD45200B044C0 /* Search */ = {
155 | isa = PBXGroup;
156 | children = (
157 | 1C53B573244DD46600B044C0 /* Views */,
158 | 1C53B572244DD46000B044C0 /* Model */,
159 | 1C53B571244DD45900B044C0 /* Logic */,
160 | );
161 | path = Search;
162 | sourceTree = "";
163 | };
164 | 1C53B571244DD45900B044C0 /* Logic */ = {
165 | isa = PBXGroup;
166 | children = (
167 | 1C53B574244DD48A00B044C0 /* SearchService.swift */,
168 | 1C53B57C244DD59A00B044C0 /* SearchStore.swift */,
169 | 1C7BDF50244F237F003880CE /* ImageService.swift */,
170 | );
171 | path = Logic;
172 | sourceTree = "";
173 | };
174 | 1C53B572244DD46000B044C0 /* Model */ = {
175 | isa = PBXGroup;
176 | children = (
177 | 1C53B584244DD71B00B044C0 /* Images.swift */,
178 | 1C53B57A244DD51A00B044C0 /* Show.swift */,
179 | 1C53B576244DD4A100B044C0 /* Endpoint+Search.swift */,
180 | 1C53B582244DD70300B044C0 /* Endpoint+Images.swift */,
181 | );
182 | path = Model;
183 | sourceTree = "";
184 | };
185 | 1C53B573244DD46600B044C0 /* Views */ = {
186 | isa = PBXGroup;
187 | children = (
188 | 1CCD168A244F1A84004AD4CE /* PosterView.swift */,
189 | 1CCD168C244F1CED004AD4CE /* ShelfView.swift */,
190 | );
191 | path = Views;
192 | sourceTree = "";
193 | };
194 | /* End PBXGroup section */
195 |
196 | /* Begin PBXNativeTarget section */
197 | 1C53B548244DD38F00B044C0 /* TraktTV */ = {
198 | isa = PBXNativeTarget;
199 | buildConfigurationList = 1C53B55B244DD39000B044C0 /* Build configuration list for PBXNativeTarget "TraktTV" */;
200 | buildPhases = (
201 | 1C53B545244DD38F00B044C0 /* Sources */,
202 | 1C53B546244DD38F00B044C0 /* Frameworks */,
203 | 1C53B547244DD38F00B044C0 /* Resources */,
204 | );
205 | buildRules = (
206 | );
207 | dependencies = (
208 | );
209 | name = TraktTV;
210 | packageProductDependencies = (
211 | 1C53B587244E0F2E00B044C0 /* KingfisherSwiftUI */,
212 | );
213 | productName = TraktTV;
214 | productReference = 1C53B549244DD38F00B044C0 /* TraktTV.app */;
215 | productType = "com.apple.product-type.application";
216 | };
217 | /* End PBXNativeTarget section */
218 |
219 | /* Begin PBXProject section */
220 | 1C53B541244DD38F00B044C0 /* Project object */ = {
221 | isa = PBXProject;
222 | attributes = {
223 | LastSwiftUpdateCheck = 1140;
224 | LastUpgradeCheck = 1140;
225 | ORGANIZATIONNAME = "Majid Jabrayilov";
226 | TargetAttributes = {
227 | 1C53B548244DD38F00B044C0 = {
228 | CreatedOnToolsVersion = 11.4.1;
229 | };
230 | };
231 | };
232 | buildConfigurationList = 1C53B544244DD38F00B044C0 /* Build configuration list for PBXProject "TraktTV" */;
233 | compatibilityVersion = "Xcode 9.3";
234 | developmentRegion = en;
235 | hasScannedForEncodings = 0;
236 | knownRegions = (
237 | en,
238 | Base,
239 | );
240 | mainGroup = 1C53B540244DD38F00B044C0;
241 | packageReferences = (
242 | 1C53B586244E0F2E00B044C0 /* XCRemoteSwiftPackageReference "Kingfisher" */,
243 | );
244 | productRefGroup = 1C53B54A244DD38F00B044C0 /* Products */;
245 | projectDirPath = "";
246 | projectRoot = "";
247 | targets = (
248 | 1C53B548244DD38F00B044C0 /* TraktTV */,
249 | );
250 | };
251 | /* End PBXProject section */
252 |
253 | /* Begin PBXResourcesBuildPhase section */
254 | 1C53B547244DD38F00B044C0 /* Resources */ = {
255 | isa = PBXResourcesBuildPhase;
256 | buildActionMask = 2147483647;
257 | files = (
258 | 1C53B557244DD39000B044C0 /* LaunchScreen.storyboard in Resources */,
259 | 1C53B554244DD39000B044C0 /* Preview Assets.xcassets in Resources */,
260 | 1C53B551244DD39000B044C0 /* Assets.xcassets in Resources */,
261 | );
262 | runOnlyForDeploymentPostprocessing = 0;
263 | };
264 | /* End PBXResourcesBuildPhase section */
265 |
266 | /* Begin PBXSourcesBuildPhase section */
267 | 1C53B545244DD38F00B044C0 /* Sources */ = {
268 | isa = PBXSourcesBuildPhase;
269 | buildActionMask = 2147483647;
270 | files = (
271 | 1C53B577244DD4A100B044C0 /* Endpoint+Search.swift in Sources */,
272 | 1CCD168D244F1CED004AD4CE /* ShelfView.swift in Sources */,
273 | 1C53B575244DD48A00B044C0 /* SearchService.swift in Sources */,
274 | 1C53B585244DD71C00B044C0 /* Images.swift in Sources */,
275 | 1CCD168B244F1A84004AD4CE /* PosterView.swift in Sources */,
276 | 1C53B56B244DD3CE00B044C0 /* RequestModifier.swift in Sources */,
277 | 1C53B57D244DD59A00B044C0 /* SearchStore.swift in Sources */,
278 | 1C53B54F244DD38F00B044C0 /* ContentView.swift in Sources */,
279 | 1C53B56C244DD3CE00B044C0 /* Request.swift in Sources */,
280 | 1C53B57B244DD51A00B044C0 /* Show.swift in Sources */,
281 | 1C53B56E244DD3CE00B044C0 /* URL.swift in Sources */,
282 | 1C53B54D244DD38F00B044C0 /* AppDelegate.swift in Sources */,
283 | 1C53B56F244DD3CE00B044C0 /* URLQueryItem.swift in Sources */,
284 | 1C7BDF51244F237F003880CE /* ImageService.swift in Sources */,
285 | 1C53B56D244DD3CE00B044C0 /* Endpoint.swift in Sources */,
286 | 1C53B583244DD70300B044C0 /* Endpoint+Images.swift in Sources */,
287 | 1C53B56A244DD3CE00B044C0 /* DataLoader.swift in Sources */,
288 | );
289 | runOnlyForDeploymentPostprocessing = 0;
290 | };
291 | /* End PBXSourcesBuildPhase section */
292 |
293 | /* Begin PBXVariantGroup section */
294 | 1C53B555244DD39000B044C0 /* LaunchScreen.storyboard */ = {
295 | isa = PBXVariantGroup;
296 | children = (
297 | 1C53B556244DD39000B044C0 /* Base */,
298 | );
299 | name = LaunchScreen.storyboard;
300 | sourceTree = "";
301 | };
302 | /* End PBXVariantGroup section */
303 |
304 | /* Begin XCBuildConfiguration section */
305 | 1C53B559244DD39000B044C0 /* Debug */ = {
306 | isa = XCBuildConfiguration;
307 | buildSettings = {
308 | ALWAYS_SEARCH_USER_PATHS = NO;
309 | CLANG_ANALYZER_NONNULL = YES;
310 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
311 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
312 | CLANG_CXX_LIBRARY = "libc++";
313 | CLANG_ENABLE_MODULES = YES;
314 | CLANG_ENABLE_OBJC_ARC = YES;
315 | CLANG_ENABLE_OBJC_WEAK = YES;
316 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
317 | CLANG_WARN_BOOL_CONVERSION = YES;
318 | CLANG_WARN_COMMA = YES;
319 | CLANG_WARN_CONSTANT_CONVERSION = YES;
320 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
321 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
322 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
323 | CLANG_WARN_EMPTY_BODY = YES;
324 | CLANG_WARN_ENUM_CONVERSION = YES;
325 | CLANG_WARN_INFINITE_RECURSION = YES;
326 | CLANG_WARN_INT_CONVERSION = YES;
327 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
328 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
329 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
330 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
331 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
332 | CLANG_WARN_STRICT_PROTOTYPES = YES;
333 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
334 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
335 | CLANG_WARN_UNREACHABLE_CODE = YES;
336 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
337 | COPY_PHASE_STRIP = NO;
338 | DEBUG_INFORMATION_FORMAT = dwarf;
339 | ENABLE_STRICT_OBJC_MSGSEND = YES;
340 | ENABLE_TESTABILITY = YES;
341 | GCC_C_LANGUAGE_STANDARD = gnu11;
342 | GCC_DYNAMIC_NO_PIC = NO;
343 | GCC_NO_COMMON_BLOCKS = YES;
344 | GCC_OPTIMIZATION_LEVEL = 0;
345 | GCC_PREPROCESSOR_DEFINITIONS = (
346 | "DEBUG=1",
347 | "$(inherited)",
348 | );
349 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
350 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
351 | GCC_WARN_UNDECLARED_SELECTOR = YES;
352 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
353 | GCC_WARN_UNUSED_FUNCTION = YES;
354 | GCC_WARN_UNUSED_VARIABLE = YES;
355 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
356 | MTL_FAST_MATH = YES;
357 | ONLY_ACTIVE_ARCH = YES;
358 | SDKROOT = appletvos;
359 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
360 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
361 | TVOS_DEPLOYMENT_TARGET = 13.4;
362 | };
363 | name = Debug;
364 | };
365 | 1C53B55A244DD39000B044C0 /* Release */ = {
366 | isa = XCBuildConfiguration;
367 | buildSettings = {
368 | ALWAYS_SEARCH_USER_PATHS = NO;
369 | CLANG_ANALYZER_NONNULL = YES;
370 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
371 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
372 | CLANG_CXX_LIBRARY = "libc++";
373 | CLANG_ENABLE_MODULES = YES;
374 | CLANG_ENABLE_OBJC_ARC = YES;
375 | CLANG_ENABLE_OBJC_WEAK = YES;
376 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
377 | CLANG_WARN_BOOL_CONVERSION = YES;
378 | CLANG_WARN_COMMA = YES;
379 | CLANG_WARN_CONSTANT_CONVERSION = YES;
380 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
381 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
382 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
383 | CLANG_WARN_EMPTY_BODY = YES;
384 | CLANG_WARN_ENUM_CONVERSION = YES;
385 | CLANG_WARN_INFINITE_RECURSION = YES;
386 | CLANG_WARN_INT_CONVERSION = YES;
387 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
388 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
389 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
390 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
391 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
392 | CLANG_WARN_STRICT_PROTOTYPES = YES;
393 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
394 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
395 | CLANG_WARN_UNREACHABLE_CODE = YES;
396 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
397 | COPY_PHASE_STRIP = NO;
398 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
399 | ENABLE_NS_ASSERTIONS = NO;
400 | ENABLE_STRICT_OBJC_MSGSEND = YES;
401 | GCC_C_LANGUAGE_STANDARD = gnu11;
402 | GCC_NO_COMMON_BLOCKS = YES;
403 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
404 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
405 | GCC_WARN_UNDECLARED_SELECTOR = YES;
406 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
407 | GCC_WARN_UNUSED_FUNCTION = YES;
408 | GCC_WARN_UNUSED_VARIABLE = YES;
409 | MTL_ENABLE_DEBUG_INFO = NO;
410 | MTL_FAST_MATH = YES;
411 | SDKROOT = appletvos;
412 | SWIFT_COMPILATION_MODE = wholemodule;
413 | SWIFT_OPTIMIZATION_LEVEL = "-O";
414 | TVOS_DEPLOYMENT_TARGET = 13.4;
415 | VALIDATE_PRODUCT = YES;
416 | };
417 | name = Release;
418 | };
419 | 1C53B55C244DD39000B044C0 /* Debug */ = {
420 | isa = XCBuildConfiguration;
421 | buildSettings = {
422 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
423 | CODE_SIGN_STYLE = Automatic;
424 | DEVELOPMENT_ASSET_PATHS = "\"TraktTV/Preview Content\"";
425 | DEVELOPMENT_TEAM = 2952RH3AR7;
426 | ENABLE_PREVIEWS = YES;
427 | INFOPLIST_FILE = TraktTV/Info.plist;
428 | LD_RUNPATH_SEARCH_PATHS = (
429 | "$(inherited)",
430 | "@executable_path/Frameworks",
431 | );
432 | PRODUCT_BUNDLE_IDENTIFIER = com.aaplab.TraktTV;
433 | PRODUCT_NAME = "$(TARGET_NAME)";
434 | SWIFT_VERSION = 5.0;
435 | TARGETED_DEVICE_FAMILY = 3;
436 | };
437 | name = Debug;
438 | };
439 | 1C53B55D244DD39000B044C0 /* Release */ = {
440 | isa = XCBuildConfiguration;
441 | buildSettings = {
442 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
443 | CODE_SIGN_STYLE = Automatic;
444 | DEVELOPMENT_ASSET_PATHS = "\"TraktTV/Preview Content\"";
445 | DEVELOPMENT_TEAM = 2952RH3AR7;
446 | ENABLE_PREVIEWS = YES;
447 | INFOPLIST_FILE = TraktTV/Info.plist;
448 | LD_RUNPATH_SEARCH_PATHS = (
449 | "$(inherited)",
450 | "@executable_path/Frameworks",
451 | );
452 | PRODUCT_BUNDLE_IDENTIFIER = com.aaplab.TraktTV;
453 | PRODUCT_NAME = "$(TARGET_NAME)";
454 | SWIFT_VERSION = 5.0;
455 | TARGETED_DEVICE_FAMILY = 3;
456 | };
457 | name = Release;
458 | };
459 | /* End XCBuildConfiguration section */
460 |
461 | /* Begin XCConfigurationList section */
462 | 1C53B544244DD38F00B044C0 /* Build configuration list for PBXProject "TraktTV" */ = {
463 | isa = XCConfigurationList;
464 | buildConfigurations = (
465 | 1C53B559244DD39000B044C0 /* Debug */,
466 | 1C53B55A244DD39000B044C0 /* Release */,
467 | );
468 | defaultConfigurationIsVisible = 0;
469 | defaultConfigurationName = Release;
470 | };
471 | 1C53B55B244DD39000B044C0 /* Build configuration list for PBXNativeTarget "TraktTV" */ = {
472 | isa = XCConfigurationList;
473 | buildConfigurations = (
474 | 1C53B55C244DD39000B044C0 /* Debug */,
475 | 1C53B55D244DD39000B044C0 /* Release */,
476 | );
477 | defaultConfigurationIsVisible = 0;
478 | defaultConfigurationName = Release;
479 | };
480 | /* End XCConfigurationList section */
481 |
482 | /* Begin XCRemoteSwiftPackageReference section */
483 | 1C53B586244E0F2E00B044C0 /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
484 | isa = XCRemoteSwiftPackageReference;
485 | repositoryURL = "https://github.com/onevcat/Kingfisher";
486 | requirement = {
487 | kind = upToNextMajorVersion;
488 | minimumVersion = 5.13.4;
489 | };
490 | };
491 | /* End XCRemoteSwiftPackageReference section */
492 |
493 | /* Begin XCSwiftPackageProductDependency section */
494 | 1C53B587244E0F2E00B044C0 /* KingfisherSwiftUI */ = {
495 | isa = XCSwiftPackageProductDependency;
496 | package = 1C53B586244E0F2E00B044C0 /* XCRemoteSwiftPackageReference "Kingfisher" */;
497 | productName = KingfisherSwiftUI;
498 | };
499 | /* End XCSwiftPackageProductDependency section */
500 | };
501 | rootObject = 1C53B541244DD38F00B044C0 /* Project object */;
502 | }
503 |
--------------------------------------------------------------------------------