├── Codegen
├── README.md
├── .gitignore
├── Tests
│ ├── LinuxMain.swift
│ └── CodegenTests
│ │ ├── XCTestManifests.swift
│ │ └── CodegenTests.swift
├── Package.swift
├── Sources
│ └── Codegen
│ │ └── main.swift
└── Package.resolved
├── Screenshots
├── character.png
├── episode.png
├── episodes.png
├── characters.png
├── character_dark.png
├── episode_dark.png
└── characters_dark.png
├── MortyUI
├── Assets.xcassets
│ ├── Contents.json
│ ├── morty-brown.colorset
│ │ └── Contents.json
│ ├── morty-green.colorset
│ │ └── Contents.json
│ ├── morty-pink.colorset
│ │ └── Contents.json
│ ├── morty-skin.colorset
│ │ └── Contents.json
│ ├── morty-yellow.colorset
│ │ └── Contents.json
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── Shared
│ ├── Color.swift
│ └── InfoRowView.swift
├── MortyUIApp.swift
├── GraphQL
│ ├── SingleQuery.swift
│ ├── Network.swift
│ ├── Queries.graphql
│ └── schema.json
├── Features
│ ├── Episodes
│ │ ├── List
│ │ │ ├── EpisodesListRowView.swift
│ │ │ ├── EpisodesListView.swift
│ │ │ └── EpisodesListViewModel.swift
│ │ └── Detail
│ │ │ └── EpisodeDetailView.swift
│ ├── Locations
│ │ ├── List
│ │ │ ├── LocationsListRowView.swift
│ │ │ ├── LocationsListView.swift
│ │ │ └── LocationsListViewModel.swift
│ │ └── Detail
│ │ │ └── LocationDetailView.swift
│ ├── Tabbar
│ │ └── TabbarView.swift
│ ├── Characters
│ │ ├── List
│ │ │ ├── CharactersListView.swift
│ │ │ ├── CharactersListRowView.swift
│ │ │ └── CharactersListViewModel.swift
│ │ └── Detail
│ │ │ └── CharacterDetailView.swift
│ └── Search
│ │ ├── SearchView.swift
│ │ └── SearchViewModel.swift
└── Info.plist
├── MortyUI.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
└── project.pbxproj
├── README.md
├── .gitignore
└── LICENSE
/Codegen/README.md:
--------------------------------------------------------------------------------
1 | # Codegen
2 |
3 | A description of this package.
4 |
--------------------------------------------------------------------------------
/Codegen/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/Screenshots/character.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dimillian/MortyUI/main/Screenshots/character.png
--------------------------------------------------------------------------------
/Screenshots/episode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dimillian/MortyUI/main/Screenshots/episode.png
--------------------------------------------------------------------------------
/Screenshots/episodes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dimillian/MortyUI/main/Screenshots/episodes.png
--------------------------------------------------------------------------------
/Screenshots/characters.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dimillian/MortyUI/main/Screenshots/characters.png
--------------------------------------------------------------------------------
/Screenshots/character_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dimillian/MortyUI/main/Screenshots/character_dark.png
--------------------------------------------------------------------------------
/Screenshots/episode_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dimillian/MortyUI/main/Screenshots/episode_dark.png
--------------------------------------------------------------------------------
/Screenshots/characters_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dimillian/MortyUI/main/Screenshots/characters_dark.png
--------------------------------------------------------------------------------
/MortyUI/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/MortyUI/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/MortyUI/Shared/Color.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Color.swift
3 | // MortyUI
4 | //
5 | // Created by Thomas Ricouard on 21/12/2020.
6 | //
7 |
8 | import Foundation
9 |
--------------------------------------------------------------------------------
/Codegen/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import CodegenTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += CodegenTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/MortyUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Codegen/Tests/CodegenTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(CodegenTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------
/MortyUI/MortyUIApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MortyUIApp.swift
3 | // MortyUI
4 | //
5 | // Created by Thomas Ricouard on 18/12/2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct MortyUIApp: App {
12 | var body: some Scene {
13 | WindowGroup {
14 | TabbarView()
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/MortyUI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/MortyUI/Assets.xcassets/morty-brown.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.114",
9 | "green" : "0.157",
10 | "red" : "0.267"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/MortyUI/Assets.xcassets/morty-green.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x4D",
9 | "green" : "0xCE",
10 | "red" : "0x97"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/MortyUI/Assets.xcassets/morty-pink.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.780",
9 | "green" : "0.604",
10 | "red" : "0.910"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/MortyUI/Assets.xcassets/morty-skin.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x88",
9 | "green" : "0xA7",
10 | "red" : "0xE4"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/MortyUI/Assets.xcassets/morty-yellow.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x4B",
9 | "green" : "0xE1",
10 | "red" : "0xF0"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Codegen/Tests/CodegenTests/CodegenTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import Codegen
3 |
4 | final class CodegenTests: XCTestCase {
5 | func testExample() {
6 | // This is an example of a functional test case.
7 | // Use XCTAssert and related functions to verify your tests produce the correct
8 | // results.
9 | XCTAssertEqual(Codegen().text, "Hello, World!")
10 | }
11 |
12 | static var allTests = [
13 | ("testExample", testExample),
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/MortyUI/Shared/InfoRowView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InfoRowView.swift
3 | // MortyUI
4 | //
5 | // Created by Thomas Ricouard on 21/12/2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct InfoRowView: View {
11 | let label: String
12 | let icon: String
13 | let value: String
14 |
15 | var body: some View {
16 | HStack {
17 | Label(label, systemImage: icon)
18 | Spacer()
19 | Text(value)
20 | .foregroundColor(.accentColor)
21 | .fontWeight(.semibold)
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/MortyUI/GraphQL/SingleQuery.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SingleQuery.swift
3 | // MortyUI
4 | //
5 | // Created by Thomas Ricouard on 22/12/2020.
6 | //
7 |
8 | import Foundation
9 | import Apollo
10 | import SwiftUI
11 |
12 | public class SingleQuery: ObservableObject {
13 | @Published public var data: Query.Data?
14 | @Published public var error: Error?
15 |
16 | public init(query: Query) {
17 | loadData(query: query)
18 | }
19 |
20 | private func loadData(query: Query) {
21 | Network.shared.apollo.fetch(query: query) { [weak self] result in
22 | switch result {
23 | case .success(let result):
24 | self?.data = result.data
25 | case .failure(let error):
26 | self?.error = error
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/MortyUI/Features/Episodes/List/EpisodesListRowView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EpisodesListRowView.swift
3 | // MortyUI
4 | //
5 | // Created by Thomas Ricouard on 21/12/2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct EpisodesListRowView: View {
11 | let episode: EpisodeDetail
12 |
13 | var body: some View {
14 | HStack(alignment: .center) {
15 | VStack(alignment: .leading) {
16 | Text(episode.name ?? "Loading...")
17 | .foregroundColor(.accentColor)
18 | Text(episode.episode ?? "Loading...")
19 | .font(.footnote)
20 | }
21 | Spacer()
22 | Text(episode.airDate ?? "Loading...")
23 | .foregroundColor(.gray)
24 | .font(.footnote)
25 | }.redacted(reason: episode.name == nil ? .placeholder : [])
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/MortyUI/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x1D",
9 | "green" : "0x28",
10 | "red" : "0x44"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x88",
27 | "green" : "0xA7",
28 | "red" : "0xE4"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/MortyUI/Features/Locations/List/LocationsListRowView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocationsListRowView.swift
3 | // MortyUI
4 | //
5 | // Created by Thomas Ricouard on 22/12/2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct LocationsListRowView: View {
11 | let location: LocationDetail
12 |
13 | var body: some View {
14 | HStack(alignment: .center) {
15 | VStack(alignment: .leading) {
16 | Text(location.name ?? "Loading...")
17 | .foregroundColor(.accentColor)
18 | Text("\(location.residents?.count ?? 0) resident(s)")
19 | .font(.footnote)
20 | }
21 | Spacer()
22 | Text(location.dimension ?? "Loading...")
23 | .foregroundColor(.gray)
24 | .font(.footnote)
25 | }.redacted(reason: location.name == nil ? .placeholder : [])
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Codegen/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "Codegen",
8 | dependencies: [
9 | .package(name: "Apollo",
10 | url: "https://github.com/apollographql/apollo-ios.git",
11 | .upToNextMinor(from: "0.39.0")),
12 | .package(url: "https://github.com/apple/swift-argument-parser", from: "0.2.0"),
13 | ],
14 | targets: [
15 | .target(
16 | name: "Codegen",
17 | dependencies: [.product(name: "ApolloCodegenLib", package: "Apollo"),
18 | .product(name: "ArgumentParser", package: "swift-argument-parser")]),
19 | .testTarget(
20 | name: "CodegenTests",
21 | dependencies: ["Codegen"]),
22 | ]
23 | )
24 |
--------------------------------------------------------------------------------
/MortyUI/Features/Tabbar/TabbarView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TabbarView.swift
3 | // MortyUI
4 | //
5 | // Created by Thomas Ricouard on 19/12/2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct TabbarView: View {
11 | var body: some View {
12 | TabView {
13 | CharactersListView()
14 | .tabItem {
15 | Label("Characters", systemImage: "person.crop.square.fill.and.at.rectangle")
16 | }
17 | EpisodesListView()
18 | .tabItem {
19 | Label("Episodes", systemImage: "tv")
20 | }
21 | LocationsListView()
22 | .tabItem {
23 | Label("Locations", systemImage: "map")
24 | }
25 | SearchView()
26 | .tabItem {
27 | Label("Seach", systemImage: "magnifyingglass")
28 | }
29 | }
30 | }
31 | }
32 |
33 | struct TabbarView_Previews: PreviewProvider {
34 | static var previews: some View {
35 | TabbarView()
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/MortyUI/GraphQL/Network.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Network.swift
3 | // MortyUI
4 | //
5 | // Created by Thomas Ricouard on 18/12/2020.
6 | //
7 |
8 | import CoreData
9 | import Apollo
10 | import ApolloSQLite
11 |
12 | class Network {
13 | static let shared = Network()
14 | private(set) lazy var apollo: ApolloClient = {
15 | let documentsPath = NSSearchPathForDirectoriesInDomains(
16 | .documentDirectory,
17 | .userDomainMask,
18 | true).first!
19 | let documentsURL = URL(fileURLWithPath: documentsPath)
20 | let sqliteFileURL = documentsURL.appendingPathComponent("db.sqlite")
21 | do {
22 | let sqliteCache = try SQLiteNormalizedCache(fileURL: sqliteFileURL)
23 | let store = ApolloStore(cache: sqliteCache)
24 | let network = RequestChainNetworkTransport(interceptorProvider: LegacyInterceptorProvider(store: store),
25 | endpointURL: URL(string: "https://rickandmortyapi.com/graphql")!)
26 | return ApolloClient(networkTransport: network, store: store)
27 | } catch {
28 | return ApolloClient(url: URL(string: "https://rickandmortyapi.com/graphql")!)
29 | }
30 | }()
31 | }
32 |
--------------------------------------------------------------------------------
/MortyUI/Features/Episodes/List/EpisodesListView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EpisodesListView.swift
3 | // MortyUI
4 | //
5 | // Created by Thomas Ricouard on 21/12/2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct EpisodesListView: View {
11 | @StateObject private var data = EpisodesListViewModel()
12 |
13 | var body: some View {
14 | NavigationView {
15 | List {
16 | ForEach(data.episodes ?? data.placeholders, id: \.id) { episode in
17 | NavigationLink(
18 | destination: EpisodeDetailView(id: episode.id!),
19 | label: {
20 | EpisodesListRowView(episode: episode)
21 | })
22 | }
23 | if data.shouldDisplayNextPage {
24 | nextPageView
25 | }
26 | }
27 | .navigationTitle("Episodes")
28 | .onAppear {
29 | data.fetchEpisodes()
30 | }
31 | }
32 | }
33 |
34 | private var nextPageView: some View {
35 | HStack {
36 | Spacer()
37 | VStack {
38 | ProgressView()
39 | Text("Loading next page...")
40 | }
41 | Spacer()
42 | }
43 | .onAppear(perform: {
44 | data.currentPage += 1
45 | })
46 | }
47 | }
48 |
49 | struct EpisodesListView_Previews: PreviewProvider {
50 | static var previews: some View {
51 | EpisodesListView()
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/MortyUI/Features/Locations/List/LocationsListView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocationsListView.swift
3 | // MortyUI
4 | //
5 | // Created by Thomas Ricouard on 22/12/2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct LocationsListView: View {
11 | @StateObject private var data = LocationsListViewModel()
12 |
13 | var body: some View {
14 | NavigationView {
15 | List {
16 | ForEach(data.locations ?? data.placeholders, id: \.id) { location in
17 | NavigationLink(
18 | destination: LocationDetailView(id: location.id!),
19 | label: {
20 | LocationsListRowView(location: location)
21 | })
22 | }
23 | if data.shouldDisplayNextPage {
24 | nextPageView
25 | }
26 | }
27 | .navigationTitle("Characters")
28 | .onAppear {
29 | data.fetchLocations()
30 | }
31 | }
32 | }
33 |
34 | private var nextPageView: some View {
35 | HStack {
36 | Spacer()
37 | VStack {
38 | ProgressView()
39 | Text("Loading next page...")
40 | }
41 | Spacer()
42 | }
43 | .onAppear(perform: {
44 | data.currentPage += 1
45 | })
46 | }
47 | }
48 |
49 | struct LocationsListView_Previews: PreviewProvider {
50 | static var previews: some View {
51 | LocationsListView()
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/MortyUI/Features/Characters/List/CharactersListView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CharactersListView.swift
3 | // MortyUI
4 | //
5 | // Created by Thomas Ricouard on 19/12/2020.
6 | //
7 |
8 | import SwiftUI
9 | import KingfisherSwiftUI
10 |
11 | struct CharactersListView: View {
12 | @StateObject private var data = CharacterListViewModel()
13 |
14 | var body: some View {
15 | NavigationView {
16 | List {
17 | ForEach(data.characters ?? data.placeholders, id: \.id) { character in
18 | NavigationLink(
19 | destination: CharacterDetailView(id: character.id!),
20 | label: {
21 | CharactersListRowView(character: character)
22 | })
23 | }
24 | if data.shouldDisplayNextPage {
25 | nextPageView
26 | }
27 | }
28 | .navigationTitle("Characters")
29 | .onAppear {
30 | data.fetchCharacters()
31 | }
32 | }
33 | }
34 |
35 | private var nextPageView: some View {
36 | HStack {
37 | Spacer()
38 | VStack {
39 | ProgressView()
40 | Text("Loading next page...")
41 | }
42 | Spacer()
43 | }
44 | .onAppear(perform: {
45 | data.currentPage += 1
46 | })
47 | }
48 | }
49 |
50 | struct CharactersListView_Previews: PreviewProvider {
51 | static var previews: some View {
52 | CharactersListView()
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/MortyUI/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 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 |
28 | UIApplicationSupportsIndirectInputEvents
29 |
30 | UILaunchScreen
31 |
32 | UIRequiredDeviceCapabilities
33 |
34 | armv7
35 |
36 | UISupportedInterfaceOrientations
37 |
38 | UIInterfaceOrientationPortrait
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UISupportedInterfaceOrientations~ipad
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationPortraitUpsideDown
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/MortyUI/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/MortyUI/GraphQL/Queries.graphql:
--------------------------------------------------------------------------------
1 | query GetCharacters($page: Int) {
2 | characters(page: $page) {
3 | info {
4 | pages, count
5 | }
6 | results {
7 | ...CharacterSmall
8 | }
9 | }
10 | }
11 |
12 | query GetCharacter($id: ID!){
13 | character(id: $id) {
14 | ...CharacterFull
15 | }
16 | }
17 |
18 | query GetEpisodes($page: Int){
19 | episodes(page: $page) {
20 | info {
21 | count, pages
22 | }
23 | results {
24 | ...EpisodeDetail
25 | }
26 | }
27 | }
28 |
29 | query GetEpisode($id: ID!) {
30 | episode(id: $id) {
31 | ...EpisodeDetail
32 | }
33 | }
34 |
35 | query GetLocations($page: Int) {
36 | locations(page: $page) {
37 | info {
38 | count, pages
39 | }
40 | results {
41 | ...LocationDetail
42 | }
43 | }
44 | }
45 |
46 | query GetLocation($id: ID!) {
47 | location(id: $id) {
48 | ...LocationDetail
49 | }
50 | }
51 |
52 | query GetSearch($name: String!) {
53 | characters(page: 1, filter: { name: $name }) {
54 | info {
55 | count
56 | }
57 | results {
58 | ...CharacterSmall
59 | }
60 | }
61 | locations(page: 1, filter: { name: $name }) {
62 | info {
63 | count
64 | }
65 | results {
66 | ...LocationDetail
67 | }
68 | }
69 | episodes(page: 1, filter: { name: $name }) {
70 | info {
71 | count
72 | }
73 | results {
74 | ...EpisodeDetail
75 | }
76 | }
77 | }
78 |
79 | fragment CharacterFull on Character {
80 | id, name, image, status, species, type, gender
81 | episode {
82 | id, name, air_date
83 | }
84 | location {
85 | id, name
86 | }
87 | origin {
88 | id, name
89 | }
90 | }
91 |
92 | fragment CharacterSmall on Character {
93 | id, name, image,
94 | episode {
95 | id, name
96 | }
97 | }
98 |
99 | fragment LocationDetail on Location {
100 | id, name, type, dimension,
101 | residents {
102 | id, name, image
103 | }
104 | }
105 |
106 | fragment EpisodeDetail on Episode {
107 | id, name, created, air_date, episode
108 | characters {
109 | id, name, image
110 | }
111 | }
112 |
113 |
114 |
--------------------------------------------------------------------------------
/MortyUI/Features/Characters/List/CharactersListRowView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CharacterListRowView.swift
3 | // MortyUI
4 | //
5 | // Created by Thomas Ricouard on 21/12/2020.
6 | //
7 |
8 | import SwiftUI
9 | import KingfisherSwiftUI
10 |
11 | struct CharactersListRowView: View {
12 | let character: CharacterSmall
13 |
14 | var body: some View {
15 | HStack {
16 | if let image = character.image,
17 | let url = URL(string: image) {
18 | KFImage(url)
19 | .resizable()
20 | .aspectRatio(contentMode: .fit)
21 | .frame(width: 50, height: 50)
22 | .cornerRadius(25)
23 | } else {
24 | RoundedRectangle(cornerRadius: 25)
25 | .frame(width: 50, height: 50)
26 | .foregroundColor(.gray)
27 | }
28 | VStack(alignment: .leading) {
29 | Text(character.name ?? "Loading...")
30 | .font(.title3)
31 | .foregroundColor(.accentColor)
32 | .redacted(reason: character.name == nil ? .placeholder : [])
33 | Text("\(character.episode?.count ?? 0) episode(s)")
34 | .font(.footnote)
35 | .foregroundColor(.gray)
36 | .redacted(reason: character.episode == nil ? .placeholder : [])
37 | }
38 | }
39 | }
40 | }
41 |
42 | struct CharacterListRowView_Previews: PreviewProvider {
43 | static var previews: some View {
44 | List {
45 | CharactersListRowView(character: .init(id: nil,
46 | name: nil,
47 | image: nil,
48 | episode: nil))
49 | CharactersListRowView(character: .init(id: nil,
50 | name: "preview",
51 | image: nil,
52 | episode: nil))
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/MortyUI/Features/Characters/List/CharactersListViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CharacterListViewModel.swift
3 | // MortyUI
4 | //
5 | // Created by Thomas Ricouard on 19/12/2020.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 | import Apollo
11 |
12 | class CharacterListViewModel: ObservableObject {
13 | @Published public var characters: [CharacterSmall]?
14 | public var placeholders = Array(repeating: CharacterSmall(id: GraphQLID(0),
15 | name: nil,
16 | image: nil,
17 | episode: nil), count: 10)
18 |
19 | public var currentPage = 1 {
20 | didSet {
21 | fetchCharacters()
22 | }
23 | }
24 |
25 | public var shouldDisplayNextPage: Bool {
26 | if characters?.isEmpty == false,
27 | let totalPages = totalPage,
28 | currentPage < totalPages {
29 | return true
30 | }
31 | return false
32 | }
33 |
34 | public private(set) var totalPage: Int?
35 | public private(set) var totalCharacters: Int?
36 |
37 | func fetchCharacters() {
38 | let fetchedPage = currentPage
39 | Network.shared.apollo.fetch(query: GetCharactersQuery(page: currentPage)) { [weak self] result in
40 | switch result {
41 | case .success(let result):
42 | if fetchedPage > 1 {
43 | if let newCharacters = result.data?.characters?.results?.compactMap({ $0?.fragments.characterSmall }) {
44 | self?.characters?.append(contentsOf: newCharacters)
45 | }
46 | } else {
47 | self?.characters = result.data?.characters?.results?.compactMap{ $0?.fragments.characterSmall }
48 | }
49 | self?.totalPage = result.data?.characters?.info?.pages
50 | self?.totalCharacters = result.data?.characters?.info?.count
51 |
52 | case .failure(let error):
53 | print("GraphQL query error: \(error)")
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Codegen/Sources/Codegen/main.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Darwin
3 | import ApolloCodegenLib
4 | import ArgumentParser
5 |
6 | struct GraphQLHelper: ParsableCommand {
7 |
8 | public static let configuration = CommandConfiguration(
9 | abstract: "A Swift command-line tool to manage your Apollo schema.json and codegen")
10 |
11 | @Argument(help: "For now you can use 'download' to download & update the schema.json, or 'codegen' to update the API.swift")
12 | var action: String
13 | func run() throws {
14 | let parentFolderOfScriptFile = FileFinder.findParentFolder()
15 |
16 | let sourceRootURL = parentFolderOfScriptFile
17 | .apollo.parentFolderURL()
18 | .apollo.parentFolderURL()
19 | .apollo.parentFolderURL()
20 |
21 | let targetRootURL = sourceRootURL
22 | .apollo.childFolderURL(folderName: "MortyUI")
23 | let endpoint = URL(string: "https://rickandmortyapi.com/graphql")!
24 |
25 | let graphQLFolder = sourceRootURL
26 | .apollo
27 | .childFolderURL(folderName: "MortyUI/GraphQL")
28 | let schemaDownloadOptions = ApolloSchemaOptions(endpointURL: endpoint,
29 | outputFolderURL: graphQLFolder)
30 |
31 | switch action {
32 | case "download":
33 | do {
34 | try ApolloSchemaDownloader.run(with: graphQLFolder,
35 | options: schemaDownloadOptions)
36 | } catch {
37 | GraphQLHelper.exit(withError: nil)
38 | }
39 |
40 | case "codegen":
41 | let codegenOptions = ApolloCodegenOptions(targetRootURL: graphQLFolder)
42 |
43 | do {
44 | try ApolloCodegen.run(from: targetRootURL,
45 | with: graphQLFolder,
46 | options: codegenOptions)
47 | } catch {
48 | GraphQLHelper.exit(withError: nil)
49 | }
50 | default:
51 | GraphQLHelper.exit(withError: nil)
52 | }
53 | }
54 | }
55 |
56 | GraphQLHelper.main()
57 |
--------------------------------------------------------------------------------
/MortyUI/Features/Episodes/List/EpisodesListViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EpisodesListViewModel.swift
3 | // MortyUI
4 | //
5 | // Created by Thomas Ricouard on 19/12/2020.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 | import Apollo
11 |
12 | class EpisodesListViewModel: ObservableObject {
13 | @Published public var episodes: [EpisodeDetail]?
14 | public var placeholders = Array(repeating: EpisodeDetail(id: GraphQLID(0),
15 | name: nil,
16 | created: nil,
17 | airDate: nil,
18 | characters: nil), count: 10)
19 |
20 | public var currentPage = 1 {
21 | didSet {
22 | fetchEpisodes()
23 | }
24 | }
25 |
26 | public var shouldDisplayNextPage: Bool {
27 | if episodes?.isEmpty == false,
28 | let totalPages = totalPage,
29 | currentPage < totalPages {
30 | return true
31 | }
32 | return false
33 | }
34 |
35 | public private(set) var totalPage: Int?
36 | public private(set) var totalCharacters: Int?
37 |
38 | func fetchEpisodes() {
39 | let fetchedPage = currentPage
40 | Network.shared.apollo.fetch(query: GetEpisodesQuery(page: currentPage)) { [weak self] result in
41 | switch result {
42 | case .success(let result):
43 | if fetchedPage > 1 {
44 | if let newEpisodes = result.data?.episodes?.results?.compactMap({ $0?.fragments.episodeDetail }) {
45 | self?.episodes?.append(contentsOf: newEpisodes)
46 | }
47 | } else {
48 | self?.episodes = result.data?.episodes?.results?.compactMap{ $0?.fragments.episodeDetail }
49 | }
50 | self?.totalPage = result.data?.episodes?.info?.pages
51 | self?.totalCharacters = result.data?.episodes?.info?.count
52 |
53 | case .failure(let error):
54 | print("GraphQL query error: \(error)")
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/MortyUI/Features/Locations/List/LocationsListViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocationsListViewModel.swift
3 | // MortyUI
4 | //
5 | // Created by Thomas Ricouard on 22/12/2020.
6 | //
7 |
8 | import SwiftUI
9 | import Apollo
10 |
11 | class LocationsListViewModel: ObservableObject {
12 | @Published public var locations: [LocationDetail]?
13 | public var placeholders = Array(repeating: LocationDetail(id: GraphQLID(0),
14 | name: nil,
15 | type: nil,
16 | dimension: nil,
17 | residents: nil), count: 10)
18 |
19 | public var currentPage = 1 {
20 | didSet {
21 | fetchLocations()
22 | }
23 | }
24 |
25 | public var shouldDisplayNextPage: Bool {
26 | if locations?.isEmpty == false,
27 | let totalPages = totalPage,
28 | currentPage < totalPages {
29 | return true
30 | }
31 | return false
32 | }
33 |
34 | public private(set) var totalPage: Int?
35 | public private(set) var totalCharacters: Int?
36 |
37 | func fetchLocations() {
38 | let fetchedPage = currentPage
39 | Network.shared.apollo.fetch(query: GetLocationsQuery(page: currentPage)) { [weak self] result in
40 | switch result {
41 | case .success(let result):
42 | if fetchedPage > 1 {
43 | if let newLocations = result.data?.locations?.results?.compactMap({ $0?.fragments.locationDetail }) {
44 | self?.locations?.append(contentsOf: newLocations)
45 | }
46 | } else {
47 | self?.locations = result.data?.locations?.results?.compactMap{ $0?.fragments.locationDetail }
48 | }
49 | self?.totalPage = result.data?.locations?.info?.pages
50 | self?.totalCharacters = result.data?.locations?.info?.count
51 |
52 | case .failure(let error):
53 | print("GraphQL query error: \(error)")
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MortyUI
2 | A very simple Rick & Morty app to demo GraphQL + SwiftUI
3 |
4 |
5 |
6 | | Characters |
7 | Character detail |
8 | Episode |
9 |
10 |
11 |  |
12 |  |
13 |  |
14 |
15 |
16 |  |
17 |  |
18 |  |
19 |
20 |
21 |
22 |
23 | This app have a very simple SwiftUI MVVM architecture.
24 |
25 | Each views have its own view model which consist of running a simple GraphQL query to fetch the necassary data. Once the data is set in the `@Published` property of the view model, the data will be displayed in the view. While it's fetching the data, it'll use the powerful SwiftUI `.redacted placeholder` modifier to display a beautiful loading state.
26 |
27 | All the queries are in `Queries.graphql`. The file `API.swift` is generated at the build time using the `schema.json` of the GraphQL API of this awesome Rick & Morty API [website](https://rickandmortyapi.com/documentation/#episode-schema) and the `Queries.graphql` file using the [Apollo CLI](https://www.apollographql.com/docs/devtools/cli)
28 |
29 | For now you can browse characters, episodes and locations (soon). Search is coming soon.
30 |
31 | To not slow the build process, this project is coming with a Swift package command line tool to generate the swift code using the new Apollo Swift [scripting feature](https://www.apollographql.com/docs/ios/swift-scripting/). It can also download the schema from the Rick & Morty API.
32 |
33 | It's not executed as part of the build process, if you modify the queries and wish to regenerate the API.swift file you'll need to:
34 |
35 | `cd Codegen `
36 |
37 | `swift run Codegen codegen`
38 |
39 | and to download the schema
40 |
41 | `swift run Codegen download`
42 |
43 |
44 | This is all an excercice for me to play a bit with GraphQL + SwiftUI. Nothing very exciting on the UI side. The exiting part is that it's actually very little code (just the UI code + some GraphQL queries) to have the app working.
45 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | # *.xcodeproj
44 | #
45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46 | # hence it is not needed unless you have added a package configuration file to your project
47 | # .swiftpm
48 |
49 | .build/
50 |
51 | # CocoaPods
52 | #
53 | # We recommend against adding the Pods directory to your .gitignore. However
54 | # you should judge for yourself, the pros and cons are mentioned at:
55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56 | #
57 | # Pods/
58 | #
59 | # Add this line if you want to avoid checking in source code from the Xcode workspace
60 | # *.xcworkspace
61 |
62 | # Carthage
63 | #
64 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
65 | # Carthage/Checkouts
66 |
67 | Carthage/Build/
68 |
69 | # Accio dependency management
70 | Dependencies/
71 | .accio/
72 |
73 | # fastlane
74 | #
75 | # It is recommended to not store the screenshots in the git repo.
76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
77 | # For more information about the recommended setup visit:
78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
79 |
80 | fastlane/report.xml
81 | fastlane/Preview.html
82 | fastlane/screenshots/**/*.png
83 | fastlane/test_output
84 |
85 | # Code Injection
86 | #
87 | # After new code Injection tools there's a generated folder /iOSInjectionProject
88 | # https://github.com/johnno1962/injectionforxcode
89 |
90 | iOSInjectionProject/
91 | .DS_Store
92 | MortyUI/GraphQL/apollo/
93 | MortyUI/GraphQL/apollo.tar.gz
94 | MortyUI/GraphQL/operationIDs.json
95 |
--------------------------------------------------------------------------------
/MortyUI/Features/Episodes/Detail/EpisodeDetailView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EpisodeDetailView.swift
3 | // MortyUI
4 | //
5 | // Created by Thomas Ricouard on 21/12/2020.
6 | //
7 |
8 | import SwiftUI
9 | import Apollo
10 | import KingfisherSwiftUI
11 |
12 | struct EpisodeDetailView: View {
13 | @StateObject private var query: SingleQuery
14 |
15 | init(id: GraphQLID) {
16 | _query = StateObject(wrappedValue: SingleQuery(query: GetEpisodeQuery(id: id)))
17 | }
18 |
19 | var episode: EpisodeDetail? {
20 | query.data?.episode?.fragments.episodeDetail
21 | }
22 |
23 | var body: some View {
24 | List {
25 | Section(header: Text("Info")) {
26 | InfoRowView(label: "Name",
27 | icon: "info",
28 | value: episode?.name ?? "loading...")
29 | InfoRowView(label: "Air date",
30 | icon: "calendar",
31 | value: episode?.airDate ?? "loading...")
32 | InfoRowView(label: "Code",
33 | icon: "barcode",
34 | value: episode?.episode ?? "loading...")
35 | }.redacted(reason: episode == nil ? .placeholder : [])
36 |
37 | if let characters = episode?.characters?.compactMap{ $0 } {
38 | Section(header: Text("Characters")) {
39 | ForEach(characters, id: \.id) { character in
40 | NavigationLink(
41 | destination: CharacterDetailView(id: character.id!),
42 | label: {
43 | HStack {
44 | if let image = character.image,
45 | let url = URL(string: image) {
46 | KFImage(url)
47 | .resizable()
48 | .aspectRatio(contentMode: .fit)
49 | .frame(width: 28, height: 28)
50 | .cornerRadius(14)
51 | }
52 | Text(character.name!)
53 | }
54 | })
55 | }
56 | }
57 | }
58 | }
59 | .listStyle(GroupedListStyle())
60 | }
61 | }
62 |
63 | struct EpisodeDetailView_Previews: PreviewProvider {
64 | static var previews: some View {
65 | EpisodeDetailView(id: GraphQLID(0))
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/MortyUI/Features/Locations/Detail/LocationDetailView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocationDetailView.swift
3 | // MortyUI
4 | //
5 | // Created by Thomas Ricouard on 22/12/2020.
6 | //
7 |
8 | import SwiftUI
9 | import Apollo
10 | import KingfisherSwiftUI
11 |
12 | struct LocationDetailView: View {
13 | @StateObject private var query: SingleQuery
14 |
15 | var location: LocationDetail? {
16 | query.data?.location?.fragments.locationDetail
17 | }
18 |
19 | init(id: GraphQLID) {
20 | _query = StateObject(wrappedValue: SingleQuery(query: GetLocationQuery(id: id)))
21 | }
22 |
23 | var body: some View {
24 | List {
25 | Section(header: Text("Info")) {
26 | InfoRowView(label: "Name",
27 | icon: "info",
28 | value: location?.name ?? "loading...")
29 | InfoRowView(label: "Dimension",
30 | icon: "tornado",
31 | value: location?.dimension ?? "loading...")
32 | InfoRowView(label: "Type",
33 | icon: "newspaper",
34 | value: location?.type ?? "loading...")
35 | }.redacted(reason: query.data?.location == nil ? .placeholder : [])
36 |
37 | if let characters = location?.residents?.compactMap{ $0 }.filter{ $0.id != nil } {
38 | Section(header: Text("Residents")) {
39 | ForEach(characters, id: \.id) { character in
40 | NavigationLink(
41 | destination: CharacterDetailView(id: character.id!),
42 | label: {
43 | HStack {
44 | if let image = character.image,
45 | let url = URL(string: image) {
46 | KFImage(url)
47 | .resizable()
48 | .aspectRatio(contentMode: .fit)
49 | .frame(width: 28, height: 28)
50 | .cornerRadius(14)
51 | }
52 | Text(character.name!)
53 | }
54 | })
55 | }
56 | }
57 | }
58 | }
59 | .listStyle(GroupedListStyle())
60 | .navigationTitle(location?.name ?? "")
61 | }
62 | }
63 |
64 | struct LocationDetailView_Previews: PreviewProvider {
65 | static var previews: some View {
66 | LocationDetailView(id: GraphQLID(0))
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Codegen/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "Apollo",
6 | "repositoryURL": "https://github.com/apollographql/apollo-ios.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "ac158ccaad973ff44f8c2a78a50933c29f34bdd9",
10 | "version": "0.39.0"
11 | }
12 | },
13 | {
14 | "package": "InflectorKit",
15 | "repositoryURL": "https://github.com/apollographql/InflectorKit",
16 | "state": {
17 | "branch": null,
18 | "revision": "b1d0099abe36facd198113633f502889842906af",
19 | "version": "0.0.2"
20 | }
21 | },
22 | {
23 | "package": "PathKit",
24 | "repositoryURL": "https://github.com/kylef/PathKit.git",
25 | "state": {
26 | "branch": null,
27 | "revision": "73f8e9dca9b7a3078cb79128217dc8f2e585a511",
28 | "version": "1.0.0"
29 | }
30 | },
31 | {
32 | "package": "Spectre",
33 | "repositoryURL": "https://github.com/kylef/Spectre.git",
34 | "state": {
35 | "branch": null,
36 | "revision": "f79d4ecbf8bc4e1579fbd86c3e1d652fb6876c53",
37 | "version": "0.9.2"
38 | }
39 | },
40 | {
41 | "package": "SQLite.swift",
42 | "repositoryURL": "https://github.com/stephencelis/SQLite.swift.git",
43 | "state": {
44 | "branch": null,
45 | "revision": "0a9893ec030501a3956bee572d6b4fdd3ae158a1",
46 | "version": "0.12.2"
47 | }
48 | },
49 | {
50 | "package": "Starscream",
51 | "repositoryURL": "https://github.com/daltoniam/Starscream",
52 | "state": {
53 | "branch": null,
54 | "revision": "e6b65c6d9077ea48b4a7bdda8994a1d3c6969c8d",
55 | "version": "3.1.1"
56 | }
57 | },
58 | {
59 | "package": "Stencil",
60 | "repositoryURL": "https://github.com/stencilproject/Stencil.git",
61 | "state": {
62 | "branch": null,
63 | "revision": "94197b3adbbf926348ad8765476a158aa4e54f8a",
64 | "version": "0.14.0"
65 | }
66 | },
67 | {
68 | "package": "swift-argument-parser",
69 | "repositoryURL": "https://github.com/apple/swift-argument-parser",
70 | "state": {
71 | "branch": null,
72 | "revision": "92646c0cdbaca076c8d3d0207891785b3379cbff",
73 | "version": "0.3.1"
74 | }
75 | },
76 | {
77 | "package": "swift-nio-zlib-support",
78 | "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git",
79 | "state": {
80 | "branch": null,
81 | "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd",
82 | "version": "1.0.0"
83 | }
84 | }
85 | ]
86 | },
87 | "version": 1
88 | }
89 |
--------------------------------------------------------------------------------
/MortyUI/Features/Search/SearchView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchView.swift
3 | // MortyUI
4 | //
5 | // Created by Thomas Ricouard on 04/01/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SearchView: View {
11 | @ObservedObject private var viewModel = SearchViewModel()
12 |
13 | var body: some View {
14 | NavigationView {
15 | List {
16 | Section {
17 | HStack {
18 | TextField("Search", text: $viewModel.searchText)
19 | if !viewModel.searchText.isEmpty {
20 | Button(action: {
21 | viewModel.searchText = ""
22 | UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder),
23 | to: nil,
24 | from: nil,
25 | for: nil)
26 | }, label: {
27 | Text("Cancel")
28 | .foregroundColor(.blue)
29 | })
30 | }
31 | }
32 | }
33 | if let characters = viewModel.characters {
34 | Section(header: Text("Characters")) {
35 | ForEach(characters, id: \.id) { character in
36 | NavigationLink(
37 | destination: CharacterDetailView(id: character.id!),
38 | label: {
39 | CharactersListRowView(character: character)
40 | })
41 | }
42 | }
43 | }
44 | if let locations = viewModel.locations {
45 | Section(header: Text("Locations")) {
46 | ForEach(locations, id: \.id) { location in
47 | NavigationLink(
48 | destination: LocationDetailView(id: location.id!),
49 | label: {
50 | LocationsListRowView(location: location)
51 | })
52 | }
53 | }
54 | }
55 | if let episodes = viewModel.episodes {
56 | Section(header: Text("Episodes")) {
57 | ForEach(episodes, id: \.id) { episode in
58 | NavigationLink(
59 | destination: EpisodeDetailView(id: episode.id!),
60 | label: {
61 | EpisodesListRowView(episode: episode)
62 | })
63 | }
64 | }
65 | }
66 | }.navigationTitle("Search")
67 | }
68 | }
69 | }
70 |
71 | struct SearchView_Previews: PreviewProvider {
72 | static var previews: some View {
73 | SearchView()
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/MortyUI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "Apollo",
6 | "repositoryURL": "https://github.com/apollographql/apollo-ios.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "ac158ccaad973ff44f8c2a78a50933c29f34bdd9",
10 | "version": "0.39.0"
11 | }
12 | },
13 | {
14 | "package": "InflectorKit",
15 | "repositoryURL": "https://github.com/apollographql/InflectorKit",
16 | "state": {
17 | "branch": null,
18 | "revision": "b1d0099abe36facd198113633f502889842906af",
19 | "version": "0.0.2"
20 | }
21 | },
22 | {
23 | "package": "Kingfisher",
24 | "repositoryURL": "https://github.com/onevcat/Kingfisher",
25 | "state": {
26 | "branch": null,
27 | "revision": "2a10bf41da75599a9f8e872dbd44fe0155a2e00c",
28 | "version": "5.15.8"
29 | }
30 | },
31 | {
32 | "package": "PathKit",
33 | "repositoryURL": "https://github.com/kylef/PathKit.git",
34 | "state": {
35 | "branch": null,
36 | "revision": "73f8e9dca9b7a3078cb79128217dc8f2e585a511",
37 | "version": "1.0.0"
38 | }
39 | },
40 | {
41 | "package": "Spectre",
42 | "repositoryURL": "https://github.com/kylef/Spectre.git",
43 | "state": {
44 | "branch": null,
45 | "revision": "f79d4ecbf8bc4e1579fbd86c3e1d652fb6876c53",
46 | "version": "0.9.2"
47 | }
48 | },
49 | {
50 | "package": "SQLite.swift",
51 | "repositoryURL": "https://github.com/stephencelis/SQLite.swift.git",
52 | "state": {
53 | "branch": null,
54 | "revision": "0a9893ec030501a3956bee572d6b4fdd3ae158a1",
55 | "version": "0.12.2"
56 | }
57 | },
58 | {
59 | "package": "Starscream",
60 | "repositoryURL": "https://github.com/daltoniam/Starscream",
61 | "state": {
62 | "branch": null,
63 | "revision": "e6b65c6d9077ea48b4a7bdda8994a1d3c6969c8d",
64 | "version": "3.1.1"
65 | }
66 | },
67 | {
68 | "package": "Stencil",
69 | "repositoryURL": "https://github.com/stencilproject/Stencil.git",
70 | "state": {
71 | "branch": null,
72 | "revision": "94197b3adbbf926348ad8765476a158aa4e54f8a",
73 | "version": "0.14.0"
74 | }
75 | },
76 | {
77 | "package": "swift-argument-parser",
78 | "repositoryURL": "https://github.com/apple/swift-argument-parser",
79 | "state": {
80 | "branch": null,
81 | "revision": "92646c0cdbaca076c8d3d0207891785b3379cbff",
82 | "version": "0.3.1"
83 | }
84 | },
85 | {
86 | "package": "swift-nio-zlib-support",
87 | "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git",
88 | "state": {
89 | "branch": null,
90 | "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd",
91 | "version": "1.0.0"
92 | }
93 | }
94 | ]
95 | },
96 | "version": 1
97 | }
98 |
--------------------------------------------------------------------------------
/MortyUI/Features/Search/SearchViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchViewModel.swift
3 | // MortyUI
4 | //
5 | // Created by Thomas Ricouard on 04/01/2021.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 | import Combine
11 | import Apollo
12 |
13 | class SearchViewModel: ObservableObject {
14 | @Published var searchText = "" {
15 | willSet {
16 | DispatchQueue.main.async {
17 | self.searchSubject.send(newValue)
18 | }
19 | }
20 | didSet {
21 | guard searchText != "" else {
22 | characters = nil
23 | locations = nil
24 | episodes = nil
25 | return
26 | }
27 | }
28 | }
29 |
30 | private let searchSubject = PassthroughSubject()
31 | private var searchCancellable: AnyCancellable? {
32 | didSet {
33 | oldValue?.cancel()
34 | }
35 | }
36 |
37 | @Published var characters: [CharacterSmall]?
38 | @Published var locations: [LocationDetail]?
39 | @Published var episodes: [EpisodeDetail]?
40 |
41 | init() {
42 | searchCancellable = searchSubject.eraseToAnyPublisher()
43 | .map {
44 | $0
45 | }
46 | .debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
47 | .removeDuplicates()
48 | .filter { !$0.isEmpty }
49 | .sink(receiveValue: { [weak self] searchText in
50 | self?.characters = Array(repeating: CharacterSmall(id: GraphQLID(0), name: nil,
51 | image: nil, episode: nil),
52 | count: 3)
53 | self?.locations = Array(repeating: LocationDetail(id: GraphQLID(0), name: nil,
54 | type: nil, dimension: nil, residents: nil),
55 | count: 3)
56 | self?.episodes = Array(repeating: EpisodeDetail(id: GraphQLID(0), name: nil,
57 | created: nil, airDate: nil, episode: nil,
58 | characters: nil),
59 | count: 3)
60 |
61 | self?.fetchSearch(search: searchText)
62 | })
63 | }
64 |
65 | func fetchSearch(search: String) {
66 | Network.shared.apollo.fetch(query: GetSearchQuery(name: searchText),
67 | cachePolicy: .fetchIgnoringCacheCompletely) { [weak self] result in
68 | switch result {
69 | case .success(let result):
70 | self?.characters = result.data?.characters?.results?.compactMap{ $0?.fragments.characterSmall }.prefix(5).map{ $0}
71 | self?.locations = result.data?.locations?.results?.compactMap{ $0?.fragments.locationDetail }.prefix(5).map{ $0}
72 | self?.episodes = result.data?.episodes?.results?.compactMap{ $0?.fragments.episodeDetail }.prefix(5).map{ $0}
73 |
74 | case .failure(let error):
75 | print("GraphQL query error: \(error)")
76 | }
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/MortyUI/Features/Characters/Detail/CharacterDetailView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CharacterDetailView.swift
3 | // MortyUI
4 | //
5 | // Created by Thomas Ricouard on 19/12/2020.
6 | //
7 |
8 | import SwiftUI
9 | import Apollo
10 | import KingfisherSwiftUI
11 |
12 | struct CharacterDetailView: View {
13 | @StateObject private var query: SingleQuery
14 |
15 | var character: CharacterFull? {
16 | query.data?.character?.fragments.characterFull
17 | }
18 |
19 | init(id: GraphQLID) {
20 | _query = StateObject(wrappedValue: SingleQuery(query: GetCharacterQuery(id: id)))
21 | }
22 |
23 | var body: some View {
24 | List {
25 | Section(header: Text("Mugshot")) {
26 | HStack {
27 | Spacer()
28 | if let image = character?.image,
29 | let url = URL(string: image) {
30 | KFImage(url)
31 | .resizable()
32 | .aspectRatio(contentMode: .fit)
33 | .frame(width: 150, height: 150)
34 | .cornerRadius(25)
35 | } else {
36 | RoundedRectangle(cornerRadius: 25)
37 | .frame(width: 150, height: 150)
38 | .foregroundColor(.gray)
39 | }
40 | Spacer()
41 | }
42 | }
43 |
44 | infoSection
45 | locationSection
46 |
47 | if let episodes = character?.episode?.compactMap{ $0 } {
48 | Section(header: Text("Episodes")) {
49 | ForEach(episodes, id: \.id) { episode in
50 | NavigationLink(
51 | destination: EpisodeDetailView(id: episode.id!),
52 | label: {
53 | HStack {
54 | Text(episode.name!)
55 | Spacer()
56 | Text(episode.airDate!)
57 | .foregroundColor(.gray)
58 | .font(.footnote)
59 | }
60 | })
61 | }
62 | }
63 | }
64 |
65 | }
66 | .listStyle(GroupedListStyle())
67 | .navigationTitle(character?.name ?? "Loading...")
68 | }
69 |
70 | private var infoSection: some View {
71 | Section(header: Text("Info"),
72 | content: {
73 | InfoRowView(label: "Species",
74 | icon: "hare",
75 | value: character?.species ?? "loading...")
76 | InfoRowView(label: "Gender",
77 | icon: "eyes",
78 | value: character?.gender ?? "loading...")
79 | InfoRowView(label: "Status",
80 | icon: "waveform.path.ecg.rectangle",
81 | value: character?.status ?? "loading...")
82 | })
83 | .redacted(reason: character == nil ? .placeholder : [])
84 | }
85 |
86 | private var locationSection: some View {
87 | Section(header: Text("Location")) {
88 | NavigationLink(
89 | destination:
90 | LocationDetailView(id: character?.location?.id ?? GraphQLID(0)),
91 | label: {
92 | InfoRowView(label: "Location",
93 | icon: "map",
94 | value: character?.location?.name ?? "loading...")
95 | })
96 | NavigationLink(
97 | destination:
98 | LocationDetailView(id: character?.origin?.id ?? GraphQLID(0)),
99 | label: {
100 | InfoRowView(label: "Origin",
101 | icon: "paperplane",
102 | value: character?.origin?.name ?? "loading...")
103 | })
104 | }
105 | .redacted(reason: character == nil ? .placeholder : [])
106 | }
107 | }
108 |
109 | struct CharacterDetailView_Previews: PreviewProvider {
110 | static var previews: some View {
111 | NavigationView {
112 | CharacterDetailView(id: GraphQLID(1))
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/MortyUI.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 52;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 6975BDF925A3256F00C72136 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6975BDF825A3256F00C72136 /* SearchView.swift */; };
11 | 6975BDFC25A325DD00C72136 /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6975BDFB25A325DD00C72136 /* SearchViewModel.swift */; };
12 | 69A1201E2590EA920073E76F /* InfoRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69A1201D2590EA920073E76F /* InfoRowView.swift */; };
13 | 69A120222590EC9D0073E76F /* EpisodesListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69A120212590EC9D0073E76F /* EpisodesListView.swift */; };
14 | 69A120262590ECB00073E76F /* EpisodesListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69A120252590ECB00073E76F /* EpisodesListViewModel.swift */; };
15 | 69A120292590ED4B0073E76F /* EpisodesListRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69A120282590ED4B0073E76F /* EpisodesListRowView.swift */; };
16 | 69A1202F2591E00E0073E76F /* SingleQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69A1202E2591E00E0073E76F /* SingleQuery.swift */; };
17 | 69A1203D2591E6500073E76F /* LocationsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69A1203C2591E6500073E76F /* LocationsListView.swift */; };
18 | 69A120552591EF490073E76F /* LocationsListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69A120542591EF490073E76F /* LocationsListViewModel.swift */; };
19 | 69A120582591EFB00073E76F /* LocationsListRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69A120572591EFB00073E76F /* LocationsListRowView.swift */; };
20 | 69A1205B2591F0070073E76F /* LocationDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69A1205A2591F0070073E76F /* LocationDetailView.swift */; };
21 | 69C794562590ADDB008EF633 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69C794552590ADDB008EF633 /* Color.swift */; };
22 | 69C794722590CB7A008EF633 /* CharactersListRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69C794712590CB7A008EF633 /* CharactersListRowView.swift */; };
23 | 69C794772590D541008EF633 /* EpisodeDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69C794762590D541008EF633 /* EpisodeDetailView.swift */; };
24 | 69EF672C25A5B1E00009F5D1 /* ApolloSQLite in Frameworks */ = {isa = PBXBuildFile; productRef = 69EF672B25A5B1E00009F5D1 /* ApolloSQLite */; };
25 | 69F9DCEB258CB21300F99396 /* MortyUIApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69F9DCEA258CB21300F99396 /* MortyUIApp.swift */; };
26 | 69F9DCEF258CB21500F99396 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69F9DCEE258CB21500F99396 /* Assets.xcassets */; };
27 | 69F9DCF2258CB21500F99396 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69F9DCF1258CB21500F99396 /* Preview Assets.xcassets */; };
28 | 69F9DCFC258CB2DF00F99396 /* Apollo in Frameworks */ = {isa = PBXBuildFile; productRef = 69F9DCFB258CB2DF00F99396 /* Apollo */; };
29 | 69F9DCFF258CB3E200F99396 /* schema.json in Resources */ = {isa = PBXBuildFile; fileRef = 69F9DCFE258CB3E200F99396 /* schema.json */; };
30 | 69F9DD02258CB40400F99396 /* Queries.graphql in Resources */ = {isa = PBXBuildFile; fileRef = 69F9DD01258CB40400F99396 /* Queries.graphql */; };
31 | 69F9DD07258CB4B300F99396 /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69F9DD06258CB4B300F99396 /* API.swift */; };
32 | 69F9DD0A258CB58900F99396 /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69F9DD09258CB58900F99396 /* Network.swift */; };
33 | 69F9DD14258E1F1600F99396 /* KingfisherSwiftUIStatic in Frameworks */ = {isa = PBXBuildFile; productRef = 69F9DD13258E1F1600F99396 /* KingfisherSwiftUIStatic */; };
34 | 69F9DD1A258E1F3900F99396 /* TabbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69F9DD19258E1F3900F99396 /* TabbarView.swift */; };
35 | 69F9DD1F258E204F00F99396 /* CharactersListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69F9DD1E258E204F00F99396 /* CharactersListView.swift */; };
36 | 69F9DD23258E206400F99396 /* CharactersListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69F9DD22258E206400F99396 /* CharactersListViewModel.swift */; };
37 | 69F9DD2A258E243000F99396 /* CharacterDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69F9DD29258E243000F99396 /* CharacterDetailView.swift */; };
38 | /* End PBXBuildFile section */
39 |
40 | /* Begin PBXFileReference section */
41 | 6975BDF825A3256F00C72136 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = ""; };
42 | 6975BDFB25A325DD00C72136 /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = ""; };
43 | 69A1201D2590EA920073E76F /* InfoRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoRowView.swift; sourceTree = ""; };
44 | 69A120212590EC9D0073E76F /* EpisodesListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodesListView.swift; sourceTree = ""; };
45 | 69A120252590ECB00073E76F /* EpisodesListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodesListViewModel.swift; sourceTree = ""; };
46 | 69A120282590ED4B0073E76F /* EpisodesListRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodesListRowView.swift; sourceTree = ""; };
47 | 69A1202E2591E00E0073E76F /* SingleQuery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleQuery.swift; sourceTree = ""; };
48 | 69A1203C2591E6500073E76F /* LocationsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationsListView.swift; sourceTree = ""; };
49 | 69A120542591EF490073E76F /* LocationsListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationsListViewModel.swift; sourceTree = ""; };
50 | 69A120572591EFB00073E76F /* LocationsListRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationsListRowView.swift; sourceTree = ""; };
51 | 69A1205A2591F0070073E76F /* LocationDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDetailView.swift; sourceTree = ""; };
52 | 69C794552590ADDB008EF633 /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; };
53 | 69C794712590CB7A008EF633 /* CharactersListRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharactersListRowView.swift; sourceTree = ""; };
54 | 69C794762590D541008EF633 /* EpisodeDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeDetailView.swift; sourceTree = ""; };
55 | 69EF672125A4C8710009F5D1 /* Codegen */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Codegen; sourceTree = ""; };
56 | 69F9DCE7258CB21300F99396 /* MortyUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MortyUI.app; sourceTree = BUILT_PRODUCTS_DIR; };
57 | 69F9DCEA258CB21300F99396 /* MortyUIApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MortyUIApp.swift; sourceTree = ""; };
58 | 69F9DCEE258CB21500F99396 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
59 | 69F9DCF1258CB21500F99396 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
60 | 69F9DCF3258CB21500F99396 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
61 | 69F9DCFE258CB3E200F99396 /* schema.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = schema.json; sourceTree = ""; };
62 | 69F9DD01258CB40400F99396 /* Queries.graphql */ = {isa = PBXFileReference; lastKnownFileType = text; path = Queries.graphql; sourceTree = ""; };
63 | 69F9DD06258CB4B300F99396 /* API.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = API.swift; sourceTree = ""; };
64 | 69F9DD09258CB58900F99396 /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; };
65 | 69F9DD19258E1F3900F99396 /* TabbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabbarView.swift; sourceTree = ""; };
66 | 69F9DD1E258E204F00F99396 /* CharactersListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharactersListView.swift; sourceTree = ""; };
67 | 69F9DD22258E206400F99396 /* CharactersListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharactersListViewModel.swift; sourceTree = ""; };
68 | 69F9DD29258E243000F99396 /* CharacterDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterDetailView.swift; sourceTree = ""; };
69 | /* End PBXFileReference section */
70 |
71 | /* Begin PBXFrameworksBuildPhase section */
72 | 69F9DCE4258CB21300F99396 /* Frameworks */ = {
73 | isa = PBXFrameworksBuildPhase;
74 | buildActionMask = 2147483647;
75 | files = (
76 | 69F9DD14258E1F1600F99396 /* KingfisherSwiftUIStatic in Frameworks */,
77 | 69F9DCFC258CB2DF00F99396 /* Apollo in Frameworks */,
78 | 69EF672C25A5B1E00009F5D1 /* ApolloSQLite in Frameworks */,
79 | );
80 | runOnlyForDeploymentPostprocessing = 0;
81 | };
82 | /* End PBXFrameworksBuildPhase section */
83 |
84 | /* Begin PBXGroup section */
85 | 6975BDF325A3254C00C72136 /* Search */ = {
86 | isa = PBXGroup;
87 | children = (
88 | 6975BDFB25A325DD00C72136 /* SearchViewModel.swift */,
89 | 6975BDF825A3256F00C72136 /* SearchView.swift */,
90 | );
91 | path = Search;
92 | sourceTree = "";
93 | };
94 | 69A120112590E1E80073E76F /* Frameworks */ = {
95 | isa = PBXGroup;
96 | children = (
97 | );
98 | name = Frameworks;
99 | sourceTree = "";
100 | };
101 | 69A120202590EC940073E76F /* List */ = {
102 | isa = PBXGroup;
103 | children = (
104 | 69A120212590EC9D0073E76F /* EpisodesListView.swift */,
105 | 69A120282590ED4B0073E76F /* EpisodesListRowView.swift */,
106 | 69A120252590ECB00073E76F /* EpisodesListViewModel.swift */,
107 | );
108 | path = List;
109 | sourceTree = "";
110 | };
111 | 69A120392591E62C0073E76F /* Locations */ = {
112 | isa = PBXGroup;
113 | children = (
114 | 69A1203A2591E6420073E76F /* List */,
115 | 69A1203B2591E6450073E76F /* Detail */,
116 | );
117 | path = Locations;
118 | sourceTree = "";
119 | };
120 | 69A1203A2591E6420073E76F /* List */ = {
121 | isa = PBXGroup;
122 | children = (
123 | 69A1203C2591E6500073E76F /* LocationsListView.swift */,
124 | 69A120572591EFB00073E76F /* LocationsListRowView.swift */,
125 | 69A120542591EF490073E76F /* LocationsListViewModel.swift */,
126 | );
127 | path = List;
128 | sourceTree = "";
129 | };
130 | 69A1203B2591E6450073E76F /* Detail */ = {
131 | isa = PBXGroup;
132 | children = (
133 | 69A1205A2591F0070073E76F /* LocationDetailView.swift */,
134 | );
135 | path = Detail;
136 | sourceTree = "";
137 | };
138 | 69C794542590ADD1008EF633 /* Shared */ = {
139 | isa = PBXGroup;
140 | children = (
141 | 69C794552590ADDB008EF633 /* Color.swift */,
142 | 69A1201D2590EA920073E76F /* InfoRowView.swift */,
143 | );
144 | path = Shared;
145 | sourceTree = "";
146 | };
147 | 69C794752590D530008EF633 /* Episodes */ = {
148 | isa = PBXGroup;
149 | children = (
150 | 69A120202590EC940073E76F /* List */,
151 | 69C7947A2590D544008EF633 /* Detail */,
152 | );
153 | path = Episodes;
154 | sourceTree = "";
155 | };
156 | 69C7947A2590D544008EF633 /* Detail */ = {
157 | isa = PBXGroup;
158 | children = (
159 | 69C794762590D541008EF633 /* EpisodeDetailView.swift */,
160 | );
161 | path = Detail;
162 | sourceTree = "";
163 | };
164 | 69F9DCDE258CB21300F99396 = {
165 | isa = PBXGroup;
166 | children = (
167 | 69EF672125A4C8710009F5D1 /* Codegen */,
168 | 69F9DCE9258CB21300F99396 /* MortyUI */,
169 | 69F9DCE8258CB21300F99396 /* Products */,
170 | 69A120112590E1E80073E76F /* Frameworks */,
171 | );
172 | sourceTree = "";
173 | };
174 | 69F9DCE8258CB21300F99396 /* Products */ = {
175 | isa = PBXGroup;
176 | children = (
177 | 69F9DCE7258CB21300F99396 /* MortyUI.app */,
178 | );
179 | name = Products;
180 | sourceTree = "";
181 | };
182 | 69F9DCE9258CB21300F99396 /* MortyUI */ = {
183 | isa = PBXGroup;
184 | children = (
185 | 69F9DCEA258CB21300F99396 /* MortyUIApp.swift */,
186 | 69F9DD16258E1F2200F99396 /* Features */,
187 | 69F9DD0C258CB9C100F99396 /* GraphQL */,
188 | 69C794542590ADD1008EF633 /* Shared */,
189 | 69F9DCEE258CB21500F99396 /* Assets.xcassets */,
190 | 69F9DCF3258CB21500F99396 /* Info.plist */,
191 | 69F9DCF0258CB21500F99396 /* Preview Content */,
192 | );
193 | path = MortyUI;
194 | sourceTree = "";
195 | };
196 | 69F9DCF0258CB21500F99396 /* Preview Content */ = {
197 | isa = PBXGroup;
198 | children = (
199 | 69F9DCF1258CB21500F99396 /* Preview Assets.xcassets */,
200 | );
201 | path = "Preview Content";
202 | sourceTree = "";
203 | };
204 | 69F9DD0C258CB9C100F99396 /* GraphQL */ = {
205 | isa = PBXGroup;
206 | children = (
207 | 69F9DD06258CB4B300F99396 /* API.swift */,
208 | 69F9DD09258CB58900F99396 /* Network.swift */,
209 | 69A1202E2591E00E0073E76F /* SingleQuery.swift */,
210 | 69F9DCFE258CB3E200F99396 /* schema.json */,
211 | 69F9DD01258CB40400F99396 /* Queries.graphql */,
212 | );
213 | path = GraphQL;
214 | sourceTree = "";
215 | };
216 | 69F9DD16258E1F2200F99396 /* Features */ = {
217 | isa = PBXGroup;
218 | children = (
219 | 69F9DD18258E1F3100F99396 /* Tabbar */,
220 | 69F9DD1C258E203700F99396 /* Characters */,
221 | 69A120392591E62C0073E76F /* Locations */,
222 | 69C794752590D530008EF633 /* Episodes */,
223 | 6975BDF325A3254C00C72136 /* Search */,
224 | );
225 | path = Features;
226 | sourceTree = "";
227 | };
228 | 69F9DD18258E1F3100F99396 /* Tabbar */ = {
229 | isa = PBXGroup;
230 | children = (
231 | 69F9DD19258E1F3900F99396 /* TabbarView.swift */,
232 | );
233 | path = Tabbar;
234 | sourceTree = "";
235 | };
236 | 69F9DD1C258E203700F99396 /* Characters */ = {
237 | isa = PBXGroup;
238 | children = (
239 | 69F9DD26258E206E00F99396 /* List */,
240 | 69F9DD27258E207400F99396 /* Detail */,
241 | );
242 | path = Characters;
243 | sourceTree = "";
244 | };
245 | 69F9DD26258E206E00F99396 /* List */ = {
246 | isa = PBXGroup;
247 | children = (
248 | 69F9DD1E258E204F00F99396 /* CharactersListView.swift */,
249 | 69C794712590CB7A008EF633 /* CharactersListRowView.swift */,
250 | 69F9DD22258E206400F99396 /* CharactersListViewModel.swift */,
251 | );
252 | path = List;
253 | sourceTree = "";
254 | };
255 | 69F9DD27258E207400F99396 /* Detail */ = {
256 | isa = PBXGroup;
257 | children = (
258 | 69F9DD29258E243000F99396 /* CharacterDetailView.swift */,
259 | );
260 | path = Detail;
261 | sourceTree = "";
262 | };
263 | /* End PBXGroup section */
264 |
265 | /* Begin PBXNativeTarget section */
266 | 69F9DCE6258CB21300F99396 /* MortyUI */ = {
267 | isa = PBXNativeTarget;
268 | buildConfigurationList = 69F9DCF6258CB21500F99396 /* Build configuration list for PBXNativeTarget "MortyUI" */;
269 | buildPhases = (
270 | 69F9DCE3258CB21300F99396 /* Sources */,
271 | 69F9DCE4258CB21300F99396 /* Frameworks */,
272 | 69F9DCE5258CB21300F99396 /* Resources */,
273 | );
274 | buildRules = (
275 | );
276 | dependencies = (
277 | );
278 | name = MortyUI;
279 | packageProductDependencies = (
280 | 69F9DCFB258CB2DF00F99396 /* Apollo */,
281 | 69F9DD13258E1F1600F99396 /* KingfisherSwiftUIStatic */,
282 | 69EF672B25A5B1E00009F5D1 /* ApolloSQLite */,
283 | );
284 | productName = MortyUI;
285 | productReference = 69F9DCE7258CB21300F99396 /* MortyUI.app */;
286 | productType = "com.apple.product-type.application";
287 | };
288 | /* End PBXNativeTarget section */
289 |
290 | /* Begin PBXProject section */
291 | 69F9DCDF258CB21300F99396 /* Project object */ = {
292 | isa = PBXProject;
293 | attributes = {
294 | LastSwiftUpdateCheck = 1230;
295 | LastUpgradeCheck = 1230;
296 | TargetAttributes = {
297 | 69F9DCE6258CB21300F99396 = {
298 | CreatedOnToolsVersion = 12.3;
299 | };
300 | };
301 | };
302 | buildConfigurationList = 69F9DCE2258CB21300F99396 /* Build configuration list for PBXProject "MortyUI" */;
303 | compatibilityVersion = "Xcode 9.3";
304 | developmentRegion = en;
305 | hasScannedForEncodings = 0;
306 | knownRegions = (
307 | en,
308 | Base,
309 | );
310 | mainGroup = 69F9DCDE258CB21300F99396;
311 | packageReferences = (
312 | 69F9DCFA258CB2DF00F99396 /* XCRemoteSwiftPackageReference "apollo-ios" */,
313 | 69F9DD12258E1F1500F99396 /* XCRemoteSwiftPackageReference "Kingfisher" */,
314 | );
315 | productRefGroup = 69F9DCE8258CB21300F99396 /* Products */;
316 | projectDirPath = "";
317 | projectRoot = "";
318 | targets = (
319 | 69F9DCE6258CB21300F99396 /* MortyUI */,
320 | );
321 | };
322 | /* End PBXProject section */
323 |
324 | /* Begin PBXResourcesBuildPhase section */
325 | 69F9DCE5258CB21300F99396 /* Resources */ = {
326 | isa = PBXResourcesBuildPhase;
327 | buildActionMask = 2147483647;
328 | files = (
329 | 69F9DCF2258CB21500F99396 /* Preview Assets.xcassets in Resources */,
330 | 69F9DCFF258CB3E200F99396 /* schema.json in Resources */,
331 | 69F9DD02258CB40400F99396 /* Queries.graphql in Resources */,
332 | 69F9DCEF258CB21500F99396 /* Assets.xcassets in Resources */,
333 | );
334 | runOnlyForDeploymentPostprocessing = 0;
335 | };
336 | /* End PBXResourcesBuildPhase section */
337 |
338 | /* Begin PBXSourcesBuildPhase section */
339 | 69F9DCE3258CB21300F99396 /* Sources */ = {
340 | isa = PBXSourcesBuildPhase;
341 | buildActionMask = 2147483647;
342 | files = (
343 | 6975BDF925A3256F00C72136 /* SearchView.swift in Sources */,
344 | 69A120262590ECB00073E76F /* EpisodesListViewModel.swift in Sources */,
345 | 69F9DD07258CB4B300F99396 /* API.swift in Sources */,
346 | 69A1201E2590EA920073E76F /* InfoRowView.swift in Sources */,
347 | 69F9DD1F258E204F00F99396 /* CharactersListView.swift in Sources */,
348 | 69F9DD23258E206400F99396 /* CharactersListViewModel.swift in Sources */,
349 | 69A1203D2591E6500073E76F /* LocationsListView.swift in Sources */,
350 | 69F9DD0A258CB58900F99396 /* Network.swift in Sources */,
351 | 69A120552591EF490073E76F /* LocationsListViewModel.swift in Sources */,
352 | 69F9DD1A258E1F3900F99396 /* TabbarView.swift in Sources */,
353 | 69A1202F2591E00E0073E76F /* SingleQuery.swift in Sources */,
354 | 69C794722590CB7A008EF633 /* CharactersListRowView.swift in Sources */,
355 | 69F9DCEB258CB21300F99396 /* MortyUIApp.swift in Sources */,
356 | 69C794772590D541008EF633 /* EpisodeDetailView.swift in Sources */,
357 | 69F9DD2A258E243000F99396 /* CharacterDetailView.swift in Sources */,
358 | 6975BDFC25A325DD00C72136 /* SearchViewModel.swift in Sources */,
359 | 69C794562590ADDB008EF633 /* Color.swift in Sources */,
360 | 69A120222590EC9D0073E76F /* EpisodesListView.swift in Sources */,
361 | 69A1205B2591F0070073E76F /* LocationDetailView.swift in Sources */,
362 | 69A120582591EFB00073E76F /* LocationsListRowView.swift in Sources */,
363 | 69A120292590ED4B0073E76F /* EpisodesListRowView.swift in Sources */,
364 | );
365 | runOnlyForDeploymentPostprocessing = 0;
366 | };
367 | /* End PBXSourcesBuildPhase section */
368 |
369 | /* Begin XCBuildConfiguration section */
370 | 69F9DCF4258CB21500F99396 /* Debug */ = {
371 | isa = XCBuildConfiguration;
372 | buildSettings = {
373 | ALWAYS_SEARCH_USER_PATHS = NO;
374 | CLANG_ANALYZER_NONNULL = YES;
375 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
376 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
377 | CLANG_CXX_LIBRARY = "libc++";
378 | CLANG_ENABLE_MODULES = YES;
379 | CLANG_ENABLE_OBJC_ARC = YES;
380 | CLANG_ENABLE_OBJC_WEAK = YES;
381 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
382 | CLANG_WARN_BOOL_CONVERSION = YES;
383 | CLANG_WARN_COMMA = YES;
384 | CLANG_WARN_CONSTANT_CONVERSION = YES;
385 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
386 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
387 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
388 | CLANG_WARN_EMPTY_BODY = YES;
389 | CLANG_WARN_ENUM_CONVERSION = YES;
390 | CLANG_WARN_INFINITE_RECURSION = YES;
391 | CLANG_WARN_INT_CONVERSION = YES;
392 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
393 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
394 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
395 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
396 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
397 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
398 | CLANG_WARN_STRICT_PROTOTYPES = YES;
399 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
400 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
401 | CLANG_WARN_UNREACHABLE_CODE = YES;
402 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
403 | COPY_PHASE_STRIP = NO;
404 | DEBUG_INFORMATION_FORMAT = dwarf;
405 | ENABLE_STRICT_OBJC_MSGSEND = YES;
406 | ENABLE_TESTABILITY = YES;
407 | GCC_C_LANGUAGE_STANDARD = gnu11;
408 | GCC_DYNAMIC_NO_PIC = NO;
409 | GCC_NO_COMMON_BLOCKS = YES;
410 | GCC_OPTIMIZATION_LEVEL = 0;
411 | GCC_PREPROCESSOR_DEFINITIONS = (
412 | "DEBUG=1",
413 | "$(inherited)",
414 | );
415 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
416 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
417 | GCC_WARN_UNDECLARED_SELECTOR = YES;
418 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
419 | GCC_WARN_UNUSED_FUNCTION = YES;
420 | GCC_WARN_UNUSED_VARIABLE = YES;
421 | IPHONEOS_DEPLOYMENT_TARGET = 14.3;
422 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
423 | MTL_FAST_MATH = YES;
424 | ONLY_ACTIVE_ARCH = YES;
425 | SDKROOT = iphoneos;
426 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
427 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
428 | };
429 | name = Debug;
430 | };
431 | 69F9DCF5258CB21500F99396 /* Release */ = {
432 | isa = XCBuildConfiguration;
433 | buildSettings = {
434 | ALWAYS_SEARCH_USER_PATHS = NO;
435 | CLANG_ANALYZER_NONNULL = YES;
436 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
437 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
438 | CLANG_CXX_LIBRARY = "libc++";
439 | CLANG_ENABLE_MODULES = YES;
440 | CLANG_ENABLE_OBJC_ARC = YES;
441 | CLANG_ENABLE_OBJC_WEAK = YES;
442 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
443 | CLANG_WARN_BOOL_CONVERSION = YES;
444 | CLANG_WARN_COMMA = YES;
445 | CLANG_WARN_CONSTANT_CONVERSION = YES;
446 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
447 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
448 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
449 | CLANG_WARN_EMPTY_BODY = YES;
450 | CLANG_WARN_ENUM_CONVERSION = YES;
451 | CLANG_WARN_INFINITE_RECURSION = YES;
452 | CLANG_WARN_INT_CONVERSION = YES;
453 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
454 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
455 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
456 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
457 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
458 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
459 | CLANG_WARN_STRICT_PROTOTYPES = YES;
460 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
461 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
462 | CLANG_WARN_UNREACHABLE_CODE = YES;
463 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
464 | COPY_PHASE_STRIP = NO;
465 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
466 | ENABLE_NS_ASSERTIONS = NO;
467 | ENABLE_STRICT_OBJC_MSGSEND = YES;
468 | GCC_C_LANGUAGE_STANDARD = gnu11;
469 | GCC_NO_COMMON_BLOCKS = YES;
470 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
471 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
472 | GCC_WARN_UNDECLARED_SELECTOR = YES;
473 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
474 | GCC_WARN_UNUSED_FUNCTION = YES;
475 | GCC_WARN_UNUSED_VARIABLE = YES;
476 | IPHONEOS_DEPLOYMENT_TARGET = 14.3;
477 | MTL_ENABLE_DEBUG_INFO = NO;
478 | MTL_FAST_MATH = YES;
479 | SDKROOT = iphoneos;
480 | SWIFT_COMPILATION_MODE = wholemodule;
481 | SWIFT_OPTIMIZATION_LEVEL = "-O";
482 | VALIDATE_PRODUCT = YES;
483 | };
484 | name = Release;
485 | };
486 | 69F9DCF7258CB21500F99396 /* Debug */ = {
487 | isa = XCBuildConfiguration;
488 | buildSettings = {
489 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
490 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
491 | CODE_SIGN_STYLE = Automatic;
492 | DEVELOPMENT_ASSET_PATHS = "\"MortyUI/Preview Content\"";
493 | DEVELOPMENT_TEAM = Z6P74P6T99;
494 | ENABLE_PREVIEWS = YES;
495 | INFOPLIST_FILE = MortyUI/Info.plist;
496 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
497 | LD_RUNPATH_SEARCH_PATHS = (
498 | "$(inherited)",
499 | "@executable_path/Frameworks",
500 | );
501 | PRODUCT_BUNDLE_IDENTIFIER = com.thomasricouard.MortyUI;
502 | PRODUCT_NAME = "$(TARGET_NAME)";
503 | SWIFT_VERSION = 5.0;
504 | TARGETED_DEVICE_FAMILY = "1,2";
505 | };
506 | name = Debug;
507 | };
508 | 69F9DCF8258CB21500F99396 /* Release */ = {
509 | isa = XCBuildConfiguration;
510 | buildSettings = {
511 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
512 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
513 | CODE_SIGN_STYLE = Automatic;
514 | DEVELOPMENT_ASSET_PATHS = "\"MortyUI/Preview Content\"";
515 | DEVELOPMENT_TEAM = Z6P74P6T99;
516 | ENABLE_PREVIEWS = YES;
517 | INFOPLIST_FILE = MortyUI/Info.plist;
518 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
519 | LD_RUNPATH_SEARCH_PATHS = (
520 | "$(inherited)",
521 | "@executable_path/Frameworks",
522 | );
523 | PRODUCT_BUNDLE_IDENTIFIER = com.thomasricouard.MortyUI;
524 | PRODUCT_NAME = "$(TARGET_NAME)";
525 | SWIFT_VERSION = 5.0;
526 | TARGETED_DEVICE_FAMILY = "1,2";
527 | };
528 | name = Release;
529 | };
530 | /* End XCBuildConfiguration section */
531 |
532 | /* Begin XCConfigurationList section */
533 | 69F9DCE2258CB21300F99396 /* Build configuration list for PBXProject "MortyUI" */ = {
534 | isa = XCConfigurationList;
535 | buildConfigurations = (
536 | 69F9DCF4258CB21500F99396 /* Debug */,
537 | 69F9DCF5258CB21500F99396 /* Release */,
538 | );
539 | defaultConfigurationIsVisible = 0;
540 | defaultConfigurationName = Release;
541 | };
542 | 69F9DCF6258CB21500F99396 /* Build configuration list for PBXNativeTarget "MortyUI" */ = {
543 | isa = XCConfigurationList;
544 | buildConfigurations = (
545 | 69F9DCF7258CB21500F99396 /* Debug */,
546 | 69F9DCF8258CB21500F99396 /* Release */,
547 | );
548 | defaultConfigurationIsVisible = 0;
549 | defaultConfigurationName = Release;
550 | };
551 | /* End XCConfigurationList section */
552 |
553 | /* Begin XCRemoteSwiftPackageReference section */
554 | 69F9DCFA258CB2DF00F99396 /* XCRemoteSwiftPackageReference "apollo-ios" */ = {
555 | isa = XCRemoteSwiftPackageReference;
556 | repositoryURL = "https://github.com/apollographql/apollo-ios.git";
557 | requirement = {
558 | kind = upToNextMajorVersion;
559 | minimumVersion = 0.38.3;
560 | };
561 | };
562 | 69F9DD12258E1F1500F99396 /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
563 | isa = XCRemoteSwiftPackageReference;
564 | repositoryURL = "https://github.com/onevcat/Kingfisher";
565 | requirement = {
566 | kind = upToNextMajorVersion;
567 | minimumVersion = 5.15.8;
568 | };
569 | };
570 | /* End XCRemoteSwiftPackageReference section */
571 |
572 | /* Begin XCSwiftPackageProductDependency section */
573 | 69EF672B25A5B1E00009F5D1 /* ApolloSQLite */ = {
574 | isa = XCSwiftPackageProductDependency;
575 | package = 69F9DCFA258CB2DF00F99396 /* XCRemoteSwiftPackageReference "apollo-ios" */;
576 | productName = ApolloSQLite;
577 | };
578 | 69F9DCFB258CB2DF00F99396 /* Apollo */ = {
579 | isa = XCSwiftPackageProductDependency;
580 | package = 69F9DCFA258CB2DF00F99396 /* XCRemoteSwiftPackageReference "apollo-ios" */;
581 | productName = Apollo;
582 | };
583 | 69F9DD13258E1F1600F99396 /* KingfisherSwiftUIStatic */ = {
584 | isa = XCSwiftPackageProductDependency;
585 | package = 69F9DD12258E1F1500F99396 /* XCRemoteSwiftPackageReference "Kingfisher" */;
586 | productName = KingfisherSwiftUIStatic;
587 | };
588 | /* End XCSwiftPackageProductDependency section */
589 | };
590 | rootObject = 69F9DCDF258CB21300F99396 /* Project object */;
591 | }
592 |
--------------------------------------------------------------------------------
/MortyUI/GraphQL/schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "__schema": {
3 | "description": null,
4 | "queryType": {
5 | "name": "Query"
6 | },
7 | "mutationType": null,
8 | "subscriptionType": null,
9 | "types": [
10 | {
11 | "kind": "OBJECT",
12 | "name": "Query",
13 | "description": "",
14 | "fields": [
15 | {
16 | "name": "character",
17 | "description": "Get a specific character by ID",
18 | "args": [
19 | {
20 | "name": "id",
21 | "description": null,
22 | "type": {
23 | "kind": "NON_NULL",
24 | "name": null,
25 | "ofType": {
26 | "kind": "SCALAR",
27 | "name": "ID",
28 | "ofType": null
29 | }
30 | },
31 | "defaultValue": null
32 | }
33 | ],
34 | "type": {
35 | "kind": "OBJECT",
36 | "name": "Character",
37 | "ofType": null
38 | },
39 | "isDeprecated": false,
40 | "deprecationReason": null
41 | },
42 | {
43 | "name": "characters",
44 | "description": "Get the list of all characters",
45 | "args": [
46 | {
47 | "name": "page",
48 | "description": null,
49 | "type": {
50 | "kind": "SCALAR",
51 | "name": "Int",
52 | "ofType": null
53 | },
54 | "defaultValue": null
55 | },
56 | {
57 | "name": "filter",
58 | "description": null,
59 | "type": {
60 | "kind": "INPUT_OBJECT",
61 | "name": "FilterCharacter",
62 | "ofType": null
63 | },
64 | "defaultValue": null
65 | }
66 | ],
67 | "type": {
68 | "kind": "OBJECT",
69 | "name": "Characters",
70 | "ofType": null
71 | },
72 | "isDeprecated": false,
73 | "deprecationReason": null
74 | },
75 | {
76 | "name": "charactersByIds",
77 | "description": "Get a list of characters selected by ids",
78 | "args": [
79 | {
80 | "name": "ids",
81 | "description": null,
82 | "type": {
83 | "kind": "NON_NULL",
84 | "name": null,
85 | "ofType": {
86 | "kind": "LIST",
87 | "name": null,
88 | "ofType": {
89 | "kind": "NON_NULL",
90 | "name": null,
91 | "ofType": {
92 | "kind": "SCALAR",
93 | "name": "ID",
94 | "ofType": null
95 | }
96 | }
97 | }
98 | },
99 | "defaultValue": null
100 | }
101 | ],
102 | "type": {
103 | "kind": "LIST",
104 | "name": null,
105 | "ofType": {
106 | "kind": "OBJECT",
107 | "name": "Character",
108 | "ofType": null
109 | }
110 | },
111 | "isDeprecated": false,
112 | "deprecationReason": null
113 | },
114 | {
115 | "name": "location",
116 | "description": "Get a specific locations by ID",
117 | "args": [
118 | {
119 | "name": "id",
120 | "description": null,
121 | "type": {
122 | "kind": "NON_NULL",
123 | "name": null,
124 | "ofType": {
125 | "kind": "SCALAR",
126 | "name": "ID",
127 | "ofType": null
128 | }
129 | },
130 | "defaultValue": null
131 | }
132 | ],
133 | "type": {
134 | "kind": "OBJECT",
135 | "name": "Location",
136 | "ofType": null
137 | },
138 | "isDeprecated": false,
139 | "deprecationReason": null
140 | },
141 | {
142 | "name": "locations",
143 | "description": "Get the list of all locations",
144 | "args": [
145 | {
146 | "name": "page",
147 | "description": null,
148 | "type": {
149 | "kind": "SCALAR",
150 | "name": "Int",
151 | "ofType": null
152 | },
153 | "defaultValue": null
154 | },
155 | {
156 | "name": "filter",
157 | "description": null,
158 | "type": {
159 | "kind": "INPUT_OBJECT",
160 | "name": "FilterLocation",
161 | "ofType": null
162 | },
163 | "defaultValue": null
164 | }
165 | ],
166 | "type": {
167 | "kind": "OBJECT",
168 | "name": "Locations",
169 | "ofType": null
170 | },
171 | "isDeprecated": false,
172 | "deprecationReason": null
173 | },
174 | {
175 | "name": "locationsByIds",
176 | "description": "Get a list of locations selected by ids",
177 | "args": [
178 | {
179 | "name": "ids",
180 | "description": null,
181 | "type": {
182 | "kind": "NON_NULL",
183 | "name": null,
184 | "ofType": {
185 | "kind": "LIST",
186 | "name": null,
187 | "ofType": {
188 | "kind": "NON_NULL",
189 | "name": null,
190 | "ofType": {
191 | "kind": "SCALAR",
192 | "name": "ID",
193 | "ofType": null
194 | }
195 | }
196 | }
197 | },
198 | "defaultValue": null
199 | }
200 | ],
201 | "type": {
202 | "kind": "LIST",
203 | "name": null,
204 | "ofType": {
205 | "kind": "OBJECT",
206 | "name": "Location",
207 | "ofType": null
208 | }
209 | },
210 | "isDeprecated": false,
211 | "deprecationReason": null
212 | },
213 | {
214 | "name": "episode",
215 | "description": "Get a specific episode by ID",
216 | "args": [
217 | {
218 | "name": "id",
219 | "description": null,
220 | "type": {
221 | "kind": "NON_NULL",
222 | "name": null,
223 | "ofType": {
224 | "kind": "SCALAR",
225 | "name": "ID",
226 | "ofType": null
227 | }
228 | },
229 | "defaultValue": null
230 | }
231 | ],
232 | "type": {
233 | "kind": "OBJECT",
234 | "name": "Episode",
235 | "ofType": null
236 | },
237 | "isDeprecated": false,
238 | "deprecationReason": null
239 | },
240 | {
241 | "name": "episodes",
242 | "description": "Get the list of all episodes",
243 | "args": [
244 | {
245 | "name": "page",
246 | "description": null,
247 | "type": {
248 | "kind": "SCALAR",
249 | "name": "Int",
250 | "ofType": null
251 | },
252 | "defaultValue": null
253 | },
254 | {
255 | "name": "filter",
256 | "description": null,
257 | "type": {
258 | "kind": "INPUT_OBJECT",
259 | "name": "FilterEpisode",
260 | "ofType": null
261 | },
262 | "defaultValue": null
263 | }
264 | ],
265 | "type": {
266 | "kind": "OBJECT",
267 | "name": "Episodes",
268 | "ofType": null
269 | },
270 | "isDeprecated": false,
271 | "deprecationReason": null
272 | },
273 | {
274 | "name": "episodesByIds",
275 | "description": "Get a list of episodes selected by ids",
276 | "args": [
277 | {
278 | "name": "ids",
279 | "description": null,
280 | "type": {
281 | "kind": "NON_NULL",
282 | "name": null,
283 | "ofType": {
284 | "kind": "LIST",
285 | "name": null,
286 | "ofType": {
287 | "kind": "NON_NULL",
288 | "name": null,
289 | "ofType": {
290 | "kind": "SCALAR",
291 | "name": "ID",
292 | "ofType": null
293 | }
294 | }
295 | }
296 | },
297 | "defaultValue": null
298 | }
299 | ],
300 | "type": {
301 | "kind": "LIST",
302 | "name": null,
303 | "ofType": {
304 | "kind": "OBJECT",
305 | "name": "Episode",
306 | "ofType": null
307 | }
308 | },
309 | "isDeprecated": false,
310 | "deprecationReason": null
311 | }
312 | ],
313 | "inputFields": null,
314 | "interfaces": [],
315 | "enumValues": null,
316 | "possibleTypes": null
317 | },
318 | {
319 | "kind": "SCALAR",
320 | "name": "ID",
321 | "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.",
322 | "fields": null,
323 | "inputFields": null,
324 | "interfaces": null,
325 | "enumValues": null,
326 | "possibleTypes": null
327 | },
328 | {
329 | "kind": "SCALAR",
330 | "name": "Int",
331 | "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.",
332 | "fields": null,
333 | "inputFields": null,
334 | "interfaces": null,
335 | "enumValues": null,
336 | "possibleTypes": null
337 | },
338 | {
339 | "kind": "OBJECT",
340 | "name": "Character",
341 | "description": "",
342 | "fields": [
343 | {
344 | "name": "id",
345 | "description": "The id of the character.",
346 | "args": [],
347 | "type": {
348 | "kind": "SCALAR",
349 | "name": "ID",
350 | "ofType": null
351 | },
352 | "isDeprecated": false,
353 | "deprecationReason": null
354 | },
355 | {
356 | "name": "name",
357 | "description": "The name of the character.",
358 | "args": [],
359 | "type": {
360 | "kind": "SCALAR",
361 | "name": "String",
362 | "ofType": null
363 | },
364 | "isDeprecated": false,
365 | "deprecationReason": null
366 | },
367 | {
368 | "name": "status",
369 | "description": "The status of the character ('Alive', 'Dead' or 'unknown').",
370 | "args": [],
371 | "type": {
372 | "kind": "SCALAR",
373 | "name": "String",
374 | "ofType": null
375 | },
376 | "isDeprecated": false,
377 | "deprecationReason": null
378 | },
379 | {
380 | "name": "species",
381 | "description": "The species of the character.",
382 | "args": [],
383 | "type": {
384 | "kind": "SCALAR",
385 | "name": "String",
386 | "ofType": null
387 | },
388 | "isDeprecated": false,
389 | "deprecationReason": null
390 | },
391 | {
392 | "name": "type",
393 | "description": "The type or subspecies of the character.",
394 | "args": [],
395 | "type": {
396 | "kind": "SCALAR",
397 | "name": "String",
398 | "ofType": null
399 | },
400 | "isDeprecated": false,
401 | "deprecationReason": null
402 | },
403 | {
404 | "name": "gender",
405 | "description": "The gender of the character ('Female', 'Male', 'Genderless' or 'unknown').",
406 | "args": [],
407 | "type": {
408 | "kind": "SCALAR",
409 | "name": "String",
410 | "ofType": null
411 | },
412 | "isDeprecated": false,
413 | "deprecationReason": null
414 | },
415 | {
416 | "name": "origin",
417 | "description": "The character's origin location",
418 | "args": [],
419 | "type": {
420 | "kind": "OBJECT",
421 | "name": "Location",
422 | "ofType": null
423 | },
424 | "isDeprecated": false,
425 | "deprecationReason": null
426 | },
427 | {
428 | "name": "location",
429 | "description": "The character's last known location",
430 | "args": [],
431 | "type": {
432 | "kind": "OBJECT",
433 | "name": "Location",
434 | "ofType": null
435 | },
436 | "isDeprecated": false,
437 | "deprecationReason": null
438 | },
439 | {
440 | "name": "image",
441 | "description": "Link to the character's image.\nAll images are 300x300px and most are medium shots or portraits since they are intended to be used as avatars.",
442 | "args": [],
443 | "type": {
444 | "kind": "SCALAR",
445 | "name": "String",
446 | "ofType": null
447 | },
448 | "isDeprecated": false,
449 | "deprecationReason": null
450 | },
451 | {
452 | "name": "episode",
453 | "description": "Episodes in which this character appeared.",
454 | "args": [],
455 | "type": {
456 | "kind": "LIST",
457 | "name": null,
458 | "ofType": {
459 | "kind": "OBJECT",
460 | "name": "Episode",
461 | "ofType": null
462 | }
463 | },
464 | "isDeprecated": false,
465 | "deprecationReason": null
466 | },
467 | {
468 | "name": "created",
469 | "description": "Time at which the character was created in the database.",
470 | "args": [],
471 | "type": {
472 | "kind": "SCALAR",
473 | "name": "String",
474 | "ofType": null
475 | },
476 | "isDeprecated": false,
477 | "deprecationReason": null
478 | }
479 | ],
480 | "inputFields": null,
481 | "interfaces": [],
482 | "enumValues": null,
483 | "possibleTypes": null
484 | },
485 | {
486 | "kind": "SCALAR",
487 | "name": "String",
488 | "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.",
489 | "fields": null,
490 | "inputFields": null,
491 | "interfaces": null,
492 | "enumValues": null,
493 | "possibleTypes": null
494 | },
495 | {
496 | "kind": "OBJECT",
497 | "name": "Location",
498 | "description": "",
499 | "fields": [
500 | {
501 | "name": "id",
502 | "description": "The id of the location.",
503 | "args": [],
504 | "type": {
505 | "kind": "SCALAR",
506 | "name": "ID",
507 | "ofType": null
508 | },
509 | "isDeprecated": false,
510 | "deprecationReason": null
511 | },
512 | {
513 | "name": "name",
514 | "description": "The name of the location.",
515 | "args": [],
516 | "type": {
517 | "kind": "SCALAR",
518 | "name": "String",
519 | "ofType": null
520 | },
521 | "isDeprecated": false,
522 | "deprecationReason": null
523 | },
524 | {
525 | "name": "type",
526 | "description": "The type of the location.",
527 | "args": [],
528 | "type": {
529 | "kind": "SCALAR",
530 | "name": "String",
531 | "ofType": null
532 | },
533 | "isDeprecated": false,
534 | "deprecationReason": null
535 | },
536 | {
537 | "name": "dimension",
538 | "description": "The dimension in which the location is located.",
539 | "args": [],
540 | "type": {
541 | "kind": "SCALAR",
542 | "name": "String",
543 | "ofType": null
544 | },
545 | "isDeprecated": false,
546 | "deprecationReason": null
547 | },
548 | {
549 | "name": "residents",
550 | "description": "List of characters who have been last seen in the location.",
551 | "args": [],
552 | "type": {
553 | "kind": "LIST",
554 | "name": null,
555 | "ofType": {
556 | "kind": "OBJECT",
557 | "name": "Character",
558 | "ofType": null
559 | }
560 | },
561 | "isDeprecated": false,
562 | "deprecationReason": null
563 | },
564 | {
565 | "name": "created",
566 | "description": "Time at which the location was created in the database.",
567 | "args": [],
568 | "type": {
569 | "kind": "SCALAR",
570 | "name": "String",
571 | "ofType": null
572 | },
573 | "isDeprecated": false,
574 | "deprecationReason": null
575 | }
576 | ],
577 | "inputFields": null,
578 | "interfaces": [],
579 | "enumValues": null,
580 | "possibleTypes": null
581 | },
582 | {
583 | "kind": "OBJECT",
584 | "name": "Episode",
585 | "description": "",
586 | "fields": [
587 | {
588 | "name": "id",
589 | "description": "The id of the episode.",
590 | "args": [],
591 | "type": {
592 | "kind": "SCALAR",
593 | "name": "ID",
594 | "ofType": null
595 | },
596 | "isDeprecated": false,
597 | "deprecationReason": null
598 | },
599 | {
600 | "name": "name",
601 | "description": "The name of the episode.",
602 | "args": [],
603 | "type": {
604 | "kind": "SCALAR",
605 | "name": "String",
606 | "ofType": null
607 | },
608 | "isDeprecated": false,
609 | "deprecationReason": null
610 | },
611 | {
612 | "name": "air_date",
613 | "description": "The air date of the episode.",
614 | "args": [],
615 | "type": {
616 | "kind": "SCALAR",
617 | "name": "String",
618 | "ofType": null
619 | },
620 | "isDeprecated": false,
621 | "deprecationReason": null
622 | },
623 | {
624 | "name": "episode",
625 | "description": "The code of the episode.",
626 | "args": [],
627 | "type": {
628 | "kind": "SCALAR",
629 | "name": "String",
630 | "ofType": null
631 | },
632 | "isDeprecated": false,
633 | "deprecationReason": null
634 | },
635 | {
636 | "name": "characters",
637 | "description": "List of characters who have been seen in the episode.",
638 | "args": [],
639 | "type": {
640 | "kind": "LIST",
641 | "name": null,
642 | "ofType": {
643 | "kind": "OBJECT",
644 | "name": "Character",
645 | "ofType": null
646 | }
647 | },
648 | "isDeprecated": false,
649 | "deprecationReason": null
650 | },
651 | {
652 | "name": "created",
653 | "description": "Time at which the episode was created in the database.",
654 | "args": [],
655 | "type": {
656 | "kind": "SCALAR",
657 | "name": "String",
658 | "ofType": null
659 | },
660 | "isDeprecated": false,
661 | "deprecationReason": null
662 | }
663 | ],
664 | "inputFields": null,
665 | "interfaces": [],
666 | "enumValues": null,
667 | "possibleTypes": null
668 | },
669 | {
670 | "kind": "INPUT_OBJECT",
671 | "name": "FilterCharacter",
672 | "description": "",
673 | "fields": null,
674 | "inputFields": [
675 | {
676 | "name": "name",
677 | "description": "",
678 | "type": {
679 | "kind": "SCALAR",
680 | "name": "String",
681 | "ofType": null
682 | },
683 | "defaultValue": null
684 | },
685 | {
686 | "name": "status",
687 | "description": "",
688 | "type": {
689 | "kind": "SCALAR",
690 | "name": "String",
691 | "ofType": null
692 | },
693 | "defaultValue": null
694 | },
695 | {
696 | "name": "species",
697 | "description": "",
698 | "type": {
699 | "kind": "SCALAR",
700 | "name": "String",
701 | "ofType": null
702 | },
703 | "defaultValue": null
704 | },
705 | {
706 | "name": "type",
707 | "description": "",
708 | "type": {
709 | "kind": "SCALAR",
710 | "name": "String",
711 | "ofType": null
712 | },
713 | "defaultValue": null
714 | },
715 | {
716 | "name": "gender",
717 | "description": "",
718 | "type": {
719 | "kind": "SCALAR",
720 | "name": "String",
721 | "ofType": null
722 | },
723 | "defaultValue": null
724 | }
725 | ],
726 | "interfaces": null,
727 | "enumValues": null,
728 | "possibleTypes": null
729 | },
730 | {
731 | "kind": "OBJECT",
732 | "name": "Characters",
733 | "description": "",
734 | "fields": [
735 | {
736 | "name": "info",
737 | "description": "",
738 | "args": [],
739 | "type": {
740 | "kind": "OBJECT",
741 | "name": "Info",
742 | "ofType": null
743 | },
744 | "isDeprecated": false,
745 | "deprecationReason": null
746 | },
747 | {
748 | "name": "results",
749 | "description": "",
750 | "args": [],
751 | "type": {
752 | "kind": "LIST",
753 | "name": null,
754 | "ofType": {
755 | "kind": "OBJECT",
756 | "name": "Character",
757 | "ofType": null
758 | }
759 | },
760 | "isDeprecated": false,
761 | "deprecationReason": null
762 | }
763 | ],
764 | "inputFields": null,
765 | "interfaces": [],
766 | "enumValues": null,
767 | "possibleTypes": null
768 | },
769 | {
770 | "kind": "OBJECT",
771 | "name": "Info",
772 | "description": "",
773 | "fields": [
774 | {
775 | "name": "count",
776 | "description": "The length of the response.",
777 | "args": [],
778 | "type": {
779 | "kind": "SCALAR",
780 | "name": "Int",
781 | "ofType": null
782 | },
783 | "isDeprecated": false,
784 | "deprecationReason": null
785 | },
786 | {
787 | "name": "pages",
788 | "description": "The amount of pages.",
789 | "args": [],
790 | "type": {
791 | "kind": "SCALAR",
792 | "name": "Int",
793 | "ofType": null
794 | },
795 | "isDeprecated": false,
796 | "deprecationReason": null
797 | },
798 | {
799 | "name": "next",
800 | "description": "Number of the next page (if it exists)",
801 | "args": [],
802 | "type": {
803 | "kind": "SCALAR",
804 | "name": "Int",
805 | "ofType": null
806 | },
807 | "isDeprecated": false,
808 | "deprecationReason": null
809 | },
810 | {
811 | "name": "prev",
812 | "description": "Number of the previous page (if it exists)",
813 | "args": [],
814 | "type": {
815 | "kind": "SCALAR",
816 | "name": "Int",
817 | "ofType": null
818 | },
819 | "isDeprecated": false,
820 | "deprecationReason": null
821 | }
822 | ],
823 | "inputFields": null,
824 | "interfaces": [],
825 | "enumValues": null,
826 | "possibleTypes": null
827 | },
828 | {
829 | "kind": "INPUT_OBJECT",
830 | "name": "FilterLocation",
831 | "description": "",
832 | "fields": null,
833 | "inputFields": [
834 | {
835 | "name": "name",
836 | "description": "",
837 | "type": {
838 | "kind": "SCALAR",
839 | "name": "String",
840 | "ofType": null
841 | },
842 | "defaultValue": null
843 | },
844 | {
845 | "name": "type",
846 | "description": "",
847 | "type": {
848 | "kind": "SCALAR",
849 | "name": "String",
850 | "ofType": null
851 | },
852 | "defaultValue": null
853 | },
854 | {
855 | "name": "dimension",
856 | "description": "",
857 | "type": {
858 | "kind": "SCALAR",
859 | "name": "String",
860 | "ofType": null
861 | },
862 | "defaultValue": null
863 | }
864 | ],
865 | "interfaces": null,
866 | "enumValues": null,
867 | "possibleTypes": null
868 | },
869 | {
870 | "kind": "OBJECT",
871 | "name": "Locations",
872 | "description": "",
873 | "fields": [
874 | {
875 | "name": "info",
876 | "description": "",
877 | "args": [],
878 | "type": {
879 | "kind": "OBJECT",
880 | "name": "Info",
881 | "ofType": null
882 | },
883 | "isDeprecated": false,
884 | "deprecationReason": null
885 | },
886 | {
887 | "name": "results",
888 | "description": "",
889 | "args": [],
890 | "type": {
891 | "kind": "LIST",
892 | "name": null,
893 | "ofType": {
894 | "kind": "OBJECT",
895 | "name": "Location",
896 | "ofType": null
897 | }
898 | },
899 | "isDeprecated": false,
900 | "deprecationReason": null
901 | }
902 | ],
903 | "inputFields": null,
904 | "interfaces": [],
905 | "enumValues": null,
906 | "possibleTypes": null
907 | },
908 | {
909 | "kind": "INPUT_OBJECT",
910 | "name": "FilterEpisode",
911 | "description": "",
912 | "fields": null,
913 | "inputFields": [
914 | {
915 | "name": "name",
916 | "description": "",
917 | "type": {
918 | "kind": "SCALAR",
919 | "name": "String",
920 | "ofType": null
921 | },
922 | "defaultValue": null
923 | },
924 | {
925 | "name": "episode",
926 | "description": "",
927 | "type": {
928 | "kind": "SCALAR",
929 | "name": "String",
930 | "ofType": null
931 | },
932 | "defaultValue": null
933 | }
934 | ],
935 | "interfaces": null,
936 | "enumValues": null,
937 | "possibleTypes": null
938 | },
939 | {
940 | "kind": "OBJECT",
941 | "name": "Episodes",
942 | "description": "",
943 | "fields": [
944 | {
945 | "name": "info",
946 | "description": "",
947 | "args": [],
948 | "type": {
949 | "kind": "OBJECT",
950 | "name": "Info",
951 | "ofType": null
952 | },
953 | "isDeprecated": false,
954 | "deprecationReason": null
955 | },
956 | {
957 | "name": "results",
958 | "description": "",
959 | "args": [],
960 | "type": {
961 | "kind": "LIST",
962 | "name": null,
963 | "ofType": {
964 | "kind": "OBJECT",
965 | "name": "Episode",
966 | "ofType": null
967 | }
968 | },
969 | "isDeprecated": false,
970 | "deprecationReason": null
971 | }
972 | ],
973 | "inputFields": null,
974 | "interfaces": [],
975 | "enumValues": null,
976 | "possibleTypes": null
977 | },
978 | {
979 | "kind": "ENUM",
980 | "name": "CacheControlScope",
981 | "description": "",
982 | "fields": null,
983 | "inputFields": null,
984 | "interfaces": null,
985 | "enumValues": [
986 | {
987 | "name": "PUBLIC",
988 | "description": "",
989 | "isDeprecated": false,
990 | "deprecationReason": null
991 | },
992 | {
993 | "name": "PRIVATE",
994 | "description": "",
995 | "isDeprecated": false,
996 | "deprecationReason": null
997 | }
998 | ],
999 | "possibleTypes": null
1000 | },
1001 | {
1002 | "kind": "SCALAR",
1003 | "name": "Upload",
1004 | "description": "The `Upload` scalar type represents a file upload.",
1005 | "fields": null,
1006 | "inputFields": null,
1007 | "interfaces": null,
1008 | "enumValues": null,
1009 | "possibleTypes": null
1010 | },
1011 | {
1012 | "kind": "SCALAR",
1013 | "name": "Boolean",
1014 | "description": "The `Boolean` scalar type represents `true` or `false`.",
1015 | "fields": null,
1016 | "inputFields": null,
1017 | "interfaces": null,
1018 | "enumValues": null,
1019 | "possibleTypes": null
1020 | },
1021 | {
1022 | "kind": "OBJECT",
1023 | "name": "__Schema",
1024 | "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.",
1025 | "fields": [
1026 | {
1027 | "name": "description",
1028 | "description": null,
1029 | "args": [],
1030 | "type": {
1031 | "kind": "SCALAR",
1032 | "name": "String",
1033 | "ofType": null
1034 | },
1035 | "isDeprecated": false,
1036 | "deprecationReason": null
1037 | },
1038 | {
1039 | "name": "types",
1040 | "description": "A list of all types supported by this server.",
1041 | "args": [],
1042 | "type": {
1043 | "kind": "NON_NULL",
1044 | "name": null,
1045 | "ofType": {
1046 | "kind": "LIST",
1047 | "name": null,
1048 | "ofType": {
1049 | "kind": "NON_NULL",
1050 | "name": null,
1051 | "ofType": {
1052 | "kind": "OBJECT",
1053 | "name": "__Type",
1054 | "ofType": null
1055 | }
1056 | }
1057 | }
1058 | },
1059 | "isDeprecated": false,
1060 | "deprecationReason": null
1061 | },
1062 | {
1063 | "name": "queryType",
1064 | "description": "The type that query operations will be rooted at.",
1065 | "args": [],
1066 | "type": {
1067 | "kind": "NON_NULL",
1068 | "name": null,
1069 | "ofType": {
1070 | "kind": "OBJECT",
1071 | "name": "__Type",
1072 | "ofType": null
1073 | }
1074 | },
1075 | "isDeprecated": false,
1076 | "deprecationReason": null
1077 | },
1078 | {
1079 | "name": "mutationType",
1080 | "description": "If this server supports mutation, the type that mutation operations will be rooted at.",
1081 | "args": [],
1082 | "type": {
1083 | "kind": "OBJECT",
1084 | "name": "__Type",
1085 | "ofType": null
1086 | },
1087 | "isDeprecated": false,
1088 | "deprecationReason": null
1089 | },
1090 | {
1091 | "name": "subscriptionType",
1092 | "description": "If this server support subscription, the type that subscription operations will be rooted at.",
1093 | "args": [],
1094 | "type": {
1095 | "kind": "OBJECT",
1096 | "name": "__Type",
1097 | "ofType": null
1098 | },
1099 | "isDeprecated": false,
1100 | "deprecationReason": null
1101 | },
1102 | {
1103 | "name": "directives",
1104 | "description": "A list of all directives supported by this server.",
1105 | "args": [],
1106 | "type": {
1107 | "kind": "NON_NULL",
1108 | "name": null,
1109 | "ofType": {
1110 | "kind": "LIST",
1111 | "name": null,
1112 | "ofType": {
1113 | "kind": "NON_NULL",
1114 | "name": null,
1115 | "ofType": {
1116 | "kind": "OBJECT",
1117 | "name": "__Directive",
1118 | "ofType": null
1119 | }
1120 | }
1121 | }
1122 | },
1123 | "isDeprecated": false,
1124 | "deprecationReason": null
1125 | }
1126 | ],
1127 | "inputFields": null,
1128 | "interfaces": [],
1129 | "enumValues": null,
1130 | "possibleTypes": null
1131 | },
1132 | {
1133 | "kind": "OBJECT",
1134 | "name": "__Type",
1135 | "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name, description and optional `specifiedByUrl`, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.",
1136 | "fields": [
1137 | {
1138 | "name": "kind",
1139 | "description": null,
1140 | "args": [],
1141 | "type": {
1142 | "kind": "NON_NULL",
1143 | "name": null,
1144 | "ofType": {
1145 | "kind": "ENUM",
1146 | "name": "__TypeKind",
1147 | "ofType": null
1148 | }
1149 | },
1150 | "isDeprecated": false,
1151 | "deprecationReason": null
1152 | },
1153 | {
1154 | "name": "name",
1155 | "description": null,
1156 | "args": [],
1157 | "type": {
1158 | "kind": "SCALAR",
1159 | "name": "String",
1160 | "ofType": null
1161 | },
1162 | "isDeprecated": false,
1163 | "deprecationReason": null
1164 | },
1165 | {
1166 | "name": "description",
1167 | "description": null,
1168 | "args": [],
1169 | "type": {
1170 | "kind": "SCALAR",
1171 | "name": "String",
1172 | "ofType": null
1173 | },
1174 | "isDeprecated": false,
1175 | "deprecationReason": null
1176 | },
1177 | {
1178 | "name": "specifiedByUrl",
1179 | "description": null,
1180 | "args": [],
1181 | "type": {
1182 | "kind": "SCALAR",
1183 | "name": "String",
1184 | "ofType": null
1185 | },
1186 | "isDeprecated": false,
1187 | "deprecationReason": null
1188 | },
1189 | {
1190 | "name": "fields",
1191 | "description": null,
1192 | "args": [
1193 | {
1194 | "name": "includeDeprecated",
1195 | "description": null,
1196 | "type": {
1197 | "kind": "SCALAR",
1198 | "name": "Boolean",
1199 | "ofType": null
1200 | },
1201 | "defaultValue": "false"
1202 | }
1203 | ],
1204 | "type": {
1205 | "kind": "LIST",
1206 | "name": null,
1207 | "ofType": {
1208 | "kind": "NON_NULL",
1209 | "name": null,
1210 | "ofType": {
1211 | "kind": "OBJECT",
1212 | "name": "__Field",
1213 | "ofType": null
1214 | }
1215 | }
1216 | },
1217 | "isDeprecated": false,
1218 | "deprecationReason": null
1219 | },
1220 | {
1221 | "name": "interfaces",
1222 | "description": null,
1223 | "args": [],
1224 | "type": {
1225 | "kind": "LIST",
1226 | "name": null,
1227 | "ofType": {
1228 | "kind": "NON_NULL",
1229 | "name": null,
1230 | "ofType": {
1231 | "kind": "OBJECT",
1232 | "name": "__Type",
1233 | "ofType": null
1234 | }
1235 | }
1236 | },
1237 | "isDeprecated": false,
1238 | "deprecationReason": null
1239 | },
1240 | {
1241 | "name": "possibleTypes",
1242 | "description": null,
1243 | "args": [],
1244 | "type": {
1245 | "kind": "LIST",
1246 | "name": null,
1247 | "ofType": {
1248 | "kind": "NON_NULL",
1249 | "name": null,
1250 | "ofType": {
1251 | "kind": "OBJECT",
1252 | "name": "__Type",
1253 | "ofType": null
1254 | }
1255 | }
1256 | },
1257 | "isDeprecated": false,
1258 | "deprecationReason": null
1259 | },
1260 | {
1261 | "name": "enumValues",
1262 | "description": null,
1263 | "args": [
1264 | {
1265 | "name": "includeDeprecated",
1266 | "description": null,
1267 | "type": {
1268 | "kind": "SCALAR",
1269 | "name": "Boolean",
1270 | "ofType": null
1271 | },
1272 | "defaultValue": "false"
1273 | }
1274 | ],
1275 | "type": {
1276 | "kind": "LIST",
1277 | "name": null,
1278 | "ofType": {
1279 | "kind": "NON_NULL",
1280 | "name": null,
1281 | "ofType": {
1282 | "kind": "OBJECT",
1283 | "name": "__EnumValue",
1284 | "ofType": null
1285 | }
1286 | }
1287 | },
1288 | "isDeprecated": false,
1289 | "deprecationReason": null
1290 | },
1291 | {
1292 | "name": "inputFields",
1293 | "description": null,
1294 | "args": [
1295 | {
1296 | "name": "includeDeprecated",
1297 | "description": null,
1298 | "type": {
1299 | "kind": "SCALAR",
1300 | "name": "Boolean",
1301 | "ofType": null
1302 | },
1303 | "defaultValue": "false"
1304 | }
1305 | ],
1306 | "type": {
1307 | "kind": "LIST",
1308 | "name": null,
1309 | "ofType": {
1310 | "kind": "NON_NULL",
1311 | "name": null,
1312 | "ofType": {
1313 | "kind": "OBJECT",
1314 | "name": "__InputValue",
1315 | "ofType": null
1316 | }
1317 | }
1318 | },
1319 | "isDeprecated": false,
1320 | "deprecationReason": null
1321 | },
1322 | {
1323 | "name": "ofType",
1324 | "description": null,
1325 | "args": [],
1326 | "type": {
1327 | "kind": "OBJECT",
1328 | "name": "__Type",
1329 | "ofType": null
1330 | },
1331 | "isDeprecated": false,
1332 | "deprecationReason": null
1333 | }
1334 | ],
1335 | "inputFields": null,
1336 | "interfaces": [],
1337 | "enumValues": null,
1338 | "possibleTypes": null
1339 | },
1340 | {
1341 | "kind": "ENUM",
1342 | "name": "__TypeKind",
1343 | "description": "An enum describing what kind of type a given `__Type` is.",
1344 | "fields": null,
1345 | "inputFields": null,
1346 | "interfaces": null,
1347 | "enumValues": [
1348 | {
1349 | "name": "SCALAR",
1350 | "description": "Indicates this type is a scalar.",
1351 | "isDeprecated": false,
1352 | "deprecationReason": null
1353 | },
1354 | {
1355 | "name": "OBJECT",
1356 | "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.",
1357 | "isDeprecated": false,
1358 | "deprecationReason": null
1359 | },
1360 | {
1361 | "name": "INTERFACE",
1362 | "description": "Indicates this type is an interface. `fields`, `interfaces`, and `possibleTypes` are valid fields.",
1363 | "isDeprecated": false,
1364 | "deprecationReason": null
1365 | },
1366 | {
1367 | "name": "UNION",
1368 | "description": "Indicates this type is a union. `possibleTypes` is a valid field.",
1369 | "isDeprecated": false,
1370 | "deprecationReason": null
1371 | },
1372 | {
1373 | "name": "ENUM",
1374 | "description": "Indicates this type is an enum. `enumValues` is a valid field.",
1375 | "isDeprecated": false,
1376 | "deprecationReason": null
1377 | },
1378 | {
1379 | "name": "INPUT_OBJECT",
1380 | "description": "Indicates this type is an input object. `inputFields` is a valid field.",
1381 | "isDeprecated": false,
1382 | "deprecationReason": null
1383 | },
1384 | {
1385 | "name": "LIST",
1386 | "description": "Indicates this type is a list. `ofType` is a valid field.",
1387 | "isDeprecated": false,
1388 | "deprecationReason": null
1389 | },
1390 | {
1391 | "name": "NON_NULL",
1392 | "description": "Indicates this type is a non-null. `ofType` is a valid field.",
1393 | "isDeprecated": false,
1394 | "deprecationReason": null
1395 | }
1396 | ],
1397 | "possibleTypes": null
1398 | },
1399 | {
1400 | "kind": "OBJECT",
1401 | "name": "__Field",
1402 | "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.",
1403 | "fields": [
1404 | {
1405 | "name": "name",
1406 | "description": null,
1407 | "args": [],
1408 | "type": {
1409 | "kind": "NON_NULL",
1410 | "name": null,
1411 | "ofType": {
1412 | "kind": "SCALAR",
1413 | "name": "String",
1414 | "ofType": null
1415 | }
1416 | },
1417 | "isDeprecated": false,
1418 | "deprecationReason": null
1419 | },
1420 | {
1421 | "name": "description",
1422 | "description": null,
1423 | "args": [],
1424 | "type": {
1425 | "kind": "SCALAR",
1426 | "name": "String",
1427 | "ofType": null
1428 | },
1429 | "isDeprecated": false,
1430 | "deprecationReason": null
1431 | },
1432 | {
1433 | "name": "args",
1434 | "description": null,
1435 | "args": [
1436 | {
1437 | "name": "includeDeprecated",
1438 | "description": null,
1439 | "type": {
1440 | "kind": "SCALAR",
1441 | "name": "Boolean",
1442 | "ofType": null
1443 | },
1444 | "defaultValue": "false"
1445 | }
1446 | ],
1447 | "type": {
1448 | "kind": "NON_NULL",
1449 | "name": null,
1450 | "ofType": {
1451 | "kind": "LIST",
1452 | "name": null,
1453 | "ofType": {
1454 | "kind": "NON_NULL",
1455 | "name": null,
1456 | "ofType": {
1457 | "kind": "OBJECT",
1458 | "name": "__InputValue",
1459 | "ofType": null
1460 | }
1461 | }
1462 | }
1463 | },
1464 | "isDeprecated": false,
1465 | "deprecationReason": null
1466 | },
1467 | {
1468 | "name": "type",
1469 | "description": null,
1470 | "args": [],
1471 | "type": {
1472 | "kind": "NON_NULL",
1473 | "name": null,
1474 | "ofType": {
1475 | "kind": "OBJECT",
1476 | "name": "__Type",
1477 | "ofType": null
1478 | }
1479 | },
1480 | "isDeprecated": false,
1481 | "deprecationReason": null
1482 | },
1483 | {
1484 | "name": "isDeprecated",
1485 | "description": null,
1486 | "args": [],
1487 | "type": {
1488 | "kind": "NON_NULL",
1489 | "name": null,
1490 | "ofType": {
1491 | "kind": "SCALAR",
1492 | "name": "Boolean",
1493 | "ofType": null
1494 | }
1495 | },
1496 | "isDeprecated": false,
1497 | "deprecationReason": null
1498 | },
1499 | {
1500 | "name": "deprecationReason",
1501 | "description": null,
1502 | "args": [],
1503 | "type": {
1504 | "kind": "SCALAR",
1505 | "name": "String",
1506 | "ofType": null
1507 | },
1508 | "isDeprecated": false,
1509 | "deprecationReason": null
1510 | }
1511 | ],
1512 | "inputFields": null,
1513 | "interfaces": [],
1514 | "enumValues": null,
1515 | "possibleTypes": null
1516 | },
1517 | {
1518 | "kind": "OBJECT",
1519 | "name": "__InputValue",
1520 | "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.",
1521 | "fields": [
1522 | {
1523 | "name": "name",
1524 | "description": null,
1525 | "args": [],
1526 | "type": {
1527 | "kind": "NON_NULL",
1528 | "name": null,
1529 | "ofType": {
1530 | "kind": "SCALAR",
1531 | "name": "String",
1532 | "ofType": null
1533 | }
1534 | },
1535 | "isDeprecated": false,
1536 | "deprecationReason": null
1537 | },
1538 | {
1539 | "name": "description",
1540 | "description": null,
1541 | "args": [],
1542 | "type": {
1543 | "kind": "SCALAR",
1544 | "name": "String",
1545 | "ofType": null
1546 | },
1547 | "isDeprecated": false,
1548 | "deprecationReason": null
1549 | },
1550 | {
1551 | "name": "type",
1552 | "description": null,
1553 | "args": [],
1554 | "type": {
1555 | "kind": "NON_NULL",
1556 | "name": null,
1557 | "ofType": {
1558 | "kind": "OBJECT",
1559 | "name": "__Type",
1560 | "ofType": null
1561 | }
1562 | },
1563 | "isDeprecated": false,
1564 | "deprecationReason": null
1565 | },
1566 | {
1567 | "name": "defaultValue",
1568 | "description": "A GraphQL-formatted string representing the default value for this input value.",
1569 | "args": [],
1570 | "type": {
1571 | "kind": "SCALAR",
1572 | "name": "String",
1573 | "ofType": null
1574 | },
1575 | "isDeprecated": false,
1576 | "deprecationReason": null
1577 | },
1578 | {
1579 | "name": "isDeprecated",
1580 | "description": null,
1581 | "args": [],
1582 | "type": {
1583 | "kind": "NON_NULL",
1584 | "name": null,
1585 | "ofType": {
1586 | "kind": "SCALAR",
1587 | "name": "Boolean",
1588 | "ofType": null
1589 | }
1590 | },
1591 | "isDeprecated": false,
1592 | "deprecationReason": null
1593 | },
1594 | {
1595 | "name": "deprecationReason",
1596 | "description": null,
1597 | "args": [],
1598 | "type": {
1599 | "kind": "SCALAR",
1600 | "name": "String",
1601 | "ofType": null
1602 | },
1603 | "isDeprecated": false,
1604 | "deprecationReason": null
1605 | }
1606 | ],
1607 | "inputFields": null,
1608 | "interfaces": [],
1609 | "enumValues": null,
1610 | "possibleTypes": null
1611 | },
1612 | {
1613 | "kind": "OBJECT",
1614 | "name": "__EnumValue",
1615 | "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.",
1616 | "fields": [
1617 | {
1618 | "name": "name",
1619 | "description": null,
1620 | "args": [],
1621 | "type": {
1622 | "kind": "NON_NULL",
1623 | "name": null,
1624 | "ofType": {
1625 | "kind": "SCALAR",
1626 | "name": "String",
1627 | "ofType": null
1628 | }
1629 | },
1630 | "isDeprecated": false,
1631 | "deprecationReason": null
1632 | },
1633 | {
1634 | "name": "description",
1635 | "description": null,
1636 | "args": [],
1637 | "type": {
1638 | "kind": "SCALAR",
1639 | "name": "String",
1640 | "ofType": null
1641 | },
1642 | "isDeprecated": false,
1643 | "deprecationReason": null
1644 | },
1645 | {
1646 | "name": "isDeprecated",
1647 | "description": null,
1648 | "args": [],
1649 | "type": {
1650 | "kind": "NON_NULL",
1651 | "name": null,
1652 | "ofType": {
1653 | "kind": "SCALAR",
1654 | "name": "Boolean",
1655 | "ofType": null
1656 | }
1657 | },
1658 | "isDeprecated": false,
1659 | "deprecationReason": null
1660 | },
1661 | {
1662 | "name": "deprecationReason",
1663 | "description": null,
1664 | "args": [],
1665 | "type": {
1666 | "kind": "SCALAR",
1667 | "name": "String",
1668 | "ofType": null
1669 | },
1670 | "isDeprecated": false,
1671 | "deprecationReason": null
1672 | }
1673 | ],
1674 | "inputFields": null,
1675 | "interfaces": [],
1676 | "enumValues": null,
1677 | "possibleTypes": null
1678 | },
1679 | {
1680 | "kind": "OBJECT",
1681 | "name": "__Directive",
1682 | "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.",
1683 | "fields": [
1684 | {
1685 | "name": "name",
1686 | "description": null,
1687 | "args": [],
1688 | "type": {
1689 | "kind": "NON_NULL",
1690 | "name": null,
1691 | "ofType": {
1692 | "kind": "SCALAR",
1693 | "name": "String",
1694 | "ofType": null
1695 | }
1696 | },
1697 | "isDeprecated": false,
1698 | "deprecationReason": null
1699 | },
1700 | {
1701 | "name": "description",
1702 | "description": null,
1703 | "args": [],
1704 | "type": {
1705 | "kind": "SCALAR",
1706 | "name": "String",
1707 | "ofType": null
1708 | },
1709 | "isDeprecated": false,
1710 | "deprecationReason": null
1711 | },
1712 | {
1713 | "name": "isRepeatable",
1714 | "description": null,
1715 | "args": [],
1716 | "type": {
1717 | "kind": "NON_NULL",
1718 | "name": null,
1719 | "ofType": {
1720 | "kind": "SCALAR",
1721 | "name": "Boolean",
1722 | "ofType": null
1723 | }
1724 | },
1725 | "isDeprecated": false,
1726 | "deprecationReason": null
1727 | },
1728 | {
1729 | "name": "locations",
1730 | "description": null,
1731 | "args": [],
1732 | "type": {
1733 | "kind": "NON_NULL",
1734 | "name": null,
1735 | "ofType": {
1736 | "kind": "LIST",
1737 | "name": null,
1738 | "ofType": {
1739 | "kind": "NON_NULL",
1740 | "name": null,
1741 | "ofType": {
1742 | "kind": "ENUM",
1743 | "name": "__DirectiveLocation",
1744 | "ofType": null
1745 | }
1746 | }
1747 | }
1748 | },
1749 | "isDeprecated": false,
1750 | "deprecationReason": null
1751 | },
1752 | {
1753 | "name": "args",
1754 | "description": null,
1755 | "args": [],
1756 | "type": {
1757 | "kind": "NON_NULL",
1758 | "name": null,
1759 | "ofType": {
1760 | "kind": "LIST",
1761 | "name": null,
1762 | "ofType": {
1763 | "kind": "NON_NULL",
1764 | "name": null,
1765 | "ofType": {
1766 | "kind": "OBJECT",
1767 | "name": "__InputValue",
1768 | "ofType": null
1769 | }
1770 | }
1771 | }
1772 | },
1773 | "isDeprecated": false,
1774 | "deprecationReason": null
1775 | }
1776 | ],
1777 | "inputFields": null,
1778 | "interfaces": [],
1779 | "enumValues": null,
1780 | "possibleTypes": null
1781 | },
1782 | {
1783 | "kind": "ENUM",
1784 | "name": "__DirectiveLocation",
1785 | "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.",
1786 | "fields": null,
1787 | "inputFields": null,
1788 | "interfaces": null,
1789 | "enumValues": [
1790 | {
1791 | "name": "QUERY",
1792 | "description": "Location adjacent to a query operation.",
1793 | "isDeprecated": false,
1794 | "deprecationReason": null
1795 | },
1796 | {
1797 | "name": "MUTATION",
1798 | "description": "Location adjacent to a mutation operation.",
1799 | "isDeprecated": false,
1800 | "deprecationReason": null
1801 | },
1802 | {
1803 | "name": "SUBSCRIPTION",
1804 | "description": "Location adjacent to a subscription operation.",
1805 | "isDeprecated": false,
1806 | "deprecationReason": null
1807 | },
1808 | {
1809 | "name": "FIELD",
1810 | "description": "Location adjacent to a field.",
1811 | "isDeprecated": false,
1812 | "deprecationReason": null
1813 | },
1814 | {
1815 | "name": "FRAGMENT_DEFINITION",
1816 | "description": "Location adjacent to a fragment definition.",
1817 | "isDeprecated": false,
1818 | "deprecationReason": null
1819 | },
1820 | {
1821 | "name": "FRAGMENT_SPREAD",
1822 | "description": "Location adjacent to a fragment spread.",
1823 | "isDeprecated": false,
1824 | "deprecationReason": null
1825 | },
1826 | {
1827 | "name": "INLINE_FRAGMENT",
1828 | "description": "Location adjacent to an inline fragment.",
1829 | "isDeprecated": false,
1830 | "deprecationReason": null
1831 | },
1832 | {
1833 | "name": "VARIABLE_DEFINITION",
1834 | "description": "Location adjacent to a variable definition.",
1835 | "isDeprecated": false,
1836 | "deprecationReason": null
1837 | },
1838 | {
1839 | "name": "SCHEMA",
1840 | "description": "Location adjacent to a schema definition.",
1841 | "isDeprecated": false,
1842 | "deprecationReason": null
1843 | },
1844 | {
1845 | "name": "SCALAR",
1846 | "description": "Location adjacent to a scalar definition.",
1847 | "isDeprecated": false,
1848 | "deprecationReason": null
1849 | },
1850 | {
1851 | "name": "OBJECT",
1852 | "description": "Location adjacent to an object type definition.",
1853 | "isDeprecated": false,
1854 | "deprecationReason": null
1855 | },
1856 | {
1857 | "name": "FIELD_DEFINITION",
1858 | "description": "Location adjacent to a field definition.",
1859 | "isDeprecated": false,
1860 | "deprecationReason": null
1861 | },
1862 | {
1863 | "name": "ARGUMENT_DEFINITION",
1864 | "description": "Location adjacent to an argument definition.",
1865 | "isDeprecated": false,
1866 | "deprecationReason": null
1867 | },
1868 | {
1869 | "name": "INTERFACE",
1870 | "description": "Location adjacent to an interface definition.",
1871 | "isDeprecated": false,
1872 | "deprecationReason": null
1873 | },
1874 | {
1875 | "name": "UNION",
1876 | "description": "Location adjacent to a union definition.",
1877 | "isDeprecated": false,
1878 | "deprecationReason": null
1879 | },
1880 | {
1881 | "name": "ENUM",
1882 | "description": "Location adjacent to an enum definition.",
1883 | "isDeprecated": false,
1884 | "deprecationReason": null
1885 | },
1886 | {
1887 | "name": "ENUM_VALUE",
1888 | "description": "Location adjacent to an enum value definition.",
1889 | "isDeprecated": false,
1890 | "deprecationReason": null
1891 | },
1892 | {
1893 | "name": "INPUT_OBJECT",
1894 | "description": "Location adjacent to an input object type definition.",
1895 | "isDeprecated": false,
1896 | "deprecationReason": null
1897 | },
1898 | {
1899 | "name": "INPUT_FIELD_DEFINITION",
1900 | "description": "Location adjacent to an input object field definition.",
1901 | "isDeprecated": false,
1902 | "deprecationReason": null
1903 | }
1904 | ],
1905 | "possibleTypes": null
1906 | }
1907 | ],
1908 | "directives": [
1909 | {
1910 | "name": "cacheControl",
1911 | "description": "",
1912 | "isRepeatable": false,
1913 | "locations": [
1914 | "FIELD_DEFINITION",
1915 | "OBJECT",
1916 | "INTERFACE"
1917 | ],
1918 | "args": [
1919 | {
1920 | "name": "maxAge",
1921 | "description": null,
1922 | "type": {
1923 | "kind": "SCALAR",
1924 | "name": "Int",
1925 | "ofType": null
1926 | },
1927 | "defaultValue": null
1928 | },
1929 | {
1930 | "name": "scope",
1931 | "description": null,
1932 | "type": {
1933 | "kind": "ENUM",
1934 | "name": "CacheControlScope",
1935 | "ofType": null
1936 | },
1937 | "defaultValue": null
1938 | }
1939 | ]
1940 | },
1941 | {
1942 | "name": "include",
1943 | "description": "Directs the executor to include this field or fragment only when the `if` argument is true.",
1944 | "isRepeatable": false,
1945 | "locations": [
1946 | "FIELD",
1947 | "FRAGMENT_SPREAD",
1948 | "INLINE_FRAGMENT"
1949 | ],
1950 | "args": [
1951 | {
1952 | "name": "if",
1953 | "description": "Included when true.",
1954 | "type": {
1955 | "kind": "NON_NULL",
1956 | "name": null,
1957 | "ofType": {
1958 | "kind": "SCALAR",
1959 | "name": "Boolean",
1960 | "ofType": null
1961 | }
1962 | },
1963 | "defaultValue": null
1964 | }
1965 | ]
1966 | },
1967 | {
1968 | "name": "skip",
1969 | "description": "Directs the executor to skip this field or fragment when the `if` argument is true.",
1970 | "isRepeatable": false,
1971 | "locations": [
1972 | "FIELD",
1973 | "FRAGMENT_SPREAD",
1974 | "INLINE_FRAGMENT"
1975 | ],
1976 | "args": [
1977 | {
1978 | "name": "if",
1979 | "description": "Skipped when true.",
1980 | "type": {
1981 | "kind": "NON_NULL",
1982 | "name": null,
1983 | "ofType": {
1984 | "kind": "SCALAR",
1985 | "name": "Boolean",
1986 | "ofType": null
1987 | }
1988 | },
1989 | "defaultValue": null
1990 | }
1991 | ]
1992 | },
1993 | {
1994 | "name": "deprecated",
1995 | "description": "Marks an element of a GraphQL schema as no longer supported.",
1996 | "isRepeatable": false,
1997 | "locations": [
1998 | "FIELD_DEFINITION",
1999 | "ARGUMENT_DEFINITION",
2000 | "INPUT_FIELD_DEFINITION",
2001 | "ENUM_VALUE"
2002 | ],
2003 | "args": [
2004 | {
2005 | "name": "reason",
2006 | "description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax, as specified by [CommonMark](https://commonmark.org/).",
2007 | "type": {
2008 | "kind": "SCALAR",
2009 | "name": "String",
2010 | "ofType": null
2011 | },
2012 | "defaultValue": "\"No longer supported\""
2013 | }
2014 | ]
2015 | },
2016 | {
2017 | "name": "specifiedBy",
2018 | "description": "Exposes a URL that specifies the behaviour of this scalar.",
2019 | "isRepeatable": false,
2020 | "locations": [
2021 | "SCALAR"
2022 | ],
2023 | "args": [
2024 | {
2025 | "name": "url",
2026 | "description": "The URL that specifies the behaviour of this scalar.",
2027 | "type": {
2028 | "kind": "NON_NULL",
2029 | "name": null,
2030 | "ofType": {
2031 | "kind": "SCALAR",
2032 | "name": "String",
2033 | "ofType": null
2034 | }
2035 | },
2036 | "defaultValue": null
2037 | }
2038 | ]
2039 | },
2040 | {
2041 | "name": "client",
2042 | "description": "Direct the client to resolve this field locally, either from the cache or local resolvers.",
2043 | "isRepeatable": false,
2044 | "locations": [
2045 | "FIELD",
2046 | "FRAGMENT_DEFINITION",
2047 | "INLINE_FRAGMENT"
2048 | ],
2049 | "args": [
2050 | {
2051 | "name": "always",
2052 | "description": "When true, the client will never use the cache for this value. See\nhttps://www.apollographql.com/docs/react/essentials/local-state/#forcing-resolvers-with-clientalways-true",
2053 | "type": {
2054 | "kind": "SCALAR",
2055 | "name": "Boolean",
2056 | "ofType": null
2057 | },
2058 | "defaultValue": null
2059 | }
2060 | ]
2061 | },
2062 | {
2063 | "name": "export",
2064 | "description": "Export this locally resolved field as a variable to be used in the remainder of this query. See\nhttps://www.apollographql.com/docs/react/essentials/local-state/#using-client-fields-as-variables",
2065 | "isRepeatable": false,
2066 | "locations": [
2067 | "FIELD"
2068 | ],
2069 | "args": [
2070 | {
2071 | "name": "as",
2072 | "description": "The variable name to export this field as.",
2073 | "type": {
2074 | "kind": "NON_NULL",
2075 | "name": null,
2076 | "ofType": {
2077 | "kind": "SCALAR",
2078 | "name": "String",
2079 | "ofType": null
2080 | }
2081 | },
2082 | "defaultValue": null
2083 | }
2084 | ]
2085 | },
2086 | {
2087 | "name": "connection",
2088 | "description": "Specify a custom store key for this result. See\nhttps://www.apollographql.com/docs/react/advanced/caching/#the-connection-directive",
2089 | "isRepeatable": false,
2090 | "locations": [
2091 | "FIELD"
2092 | ],
2093 | "args": [
2094 | {
2095 | "name": "key",
2096 | "description": "Specify the store key.",
2097 | "type": {
2098 | "kind": "NON_NULL",
2099 | "name": null,
2100 | "ofType": {
2101 | "kind": "SCALAR",
2102 | "name": "String",
2103 | "ofType": null
2104 | }
2105 | },
2106 | "defaultValue": null
2107 | },
2108 | {
2109 | "name": "filter",
2110 | "description": "An array of query argument names to include in the generated custom store key.",
2111 | "type": {
2112 | "kind": "LIST",
2113 | "name": null,
2114 | "ofType": {
2115 | "kind": "NON_NULL",
2116 | "name": null,
2117 | "ofType": {
2118 | "kind": "SCALAR",
2119 | "name": "String",
2120 | "ofType": null
2121 | }
2122 | }
2123 | },
2124 | "defaultValue": null
2125 | }
2126 | ]
2127 | }
2128 | ]
2129 | }
2130 | }
--------------------------------------------------------------------------------