diff --git a/FlinkChallenge/FlinkChallenge.xcodeproj/project.pbxproj b/FlinkChallenge/FlinkChallenge.xcodeproj/project.pbxproj index d59d819..c01df9f 100644 --- a/FlinkChallenge/FlinkChallenge.xcodeproj/project.pbxproj +++ b/FlinkChallenge/FlinkChallenge.xcodeproj/project.pbxproj @@ -24,6 +24,9 @@ BD6A5E7323FCEBA0003B1E4D /* Episode.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD6A5E7223FCEBA0003B1E4D /* Episode.swift */; }; BD6A5E7523FCFD98003B1E4D /* EpisodeModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD6A5E7423FCFD98003B1E4D /* EpisodeModalView.swift */; }; BD6A5E7723FD003A003B1E4D /* SearchBarUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD6A5E7623FD003A003B1E4D /* SearchBarUIView.swift */; }; + BD810CEF23FDB95F00D7853A /* AdvancedFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD810CEE23FDB95F00D7853A /* AdvancedFilterView.swift */; }; + BD810D3723FDD82B00D7853A /* CharacterFilteringController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD810D3623FDD82B00D7853A /* CharacterFilteringController.swift */; }; + BD810D3923FDDC4200D7853A /* CharacterFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD810D3823FDDC4200D7853A /* CharacterFilterView.swift */; }; EB91B40C69510498EB2574BC /* Pods_FlinkChallengeTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DFA73F2EF25F32DFDB17F885 /* Pods_FlinkChallengeTests.framework */; }; /* End PBXBuildFile section */ @@ -63,6 +66,9 @@ BD6A5E7223FCEBA0003B1E4D /* Episode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Episode.swift; sourceTree = ""; }; BD6A5E7423FCFD98003B1E4D /* EpisodeModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeModalView.swift; sourceTree = ""; }; BD6A5E7623FD003A003B1E4D /* SearchBarUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBarUIView.swift; sourceTree = ""; }; + BD810CEE23FDB95F00D7853A /* AdvancedFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedFilterView.swift; sourceTree = ""; }; + BD810D3623FDD82B00D7853A /* CharacterFilteringController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterFilteringController.swift; sourceTree = ""; }; + BD810D3823FDDC4200D7853A /* CharacterFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterFilterView.swift; sourceTree = ""; }; DFA73F2EF25F32DFDB17F885 /* Pods_FlinkChallengeTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FlinkChallengeTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -168,6 +174,8 @@ BD6A5E6D23FCBC01003B1E4D /* CharacterDetailView.swift */, BD6A5E7423FCFD98003B1E4D /* EpisodeModalView.swift */, BD6A5E7623FD003A003B1E4D /* SearchBarUIView.swift */, + BD810CEE23FDB95F00D7853A /* AdvancedFilterView.swift */, + BD810D3823FDDC4200D7853A /* CharacterFilterView.swift */, ); path = Views; sourceTree = ""; @@ -177,6 +185,7 @@ children = ( BD6A5E6523FC855D003B1E4D /* CharacterFeedController.swift */, BD6A5E7023FCEB6C003B1E4D /* EpisodeController.swift */, + BD810D3623FDD82B00D7853A /* CharacterFilteringController.swift */, ); path = Controller; sourceTree = ""; @@ -359,6 +368,8 @@ buildActionMask = 2147483647; files = ( BD6A5E6E23FCBC01003B1E4D /* CharacterDetailView.swift in Sources */, + BD810CEF23FDB95F00D7853A /* AdvancedFilterView.swift in Sources */, + BD810D3723FDD82B00D7853A /* CharacterFilteringController.swift in Sources */, BD6A5E6C23FC9ADF003B1E4D /* CardView.swift in Sources */, BD6A5E6923FC8F02003B1E4D /* CharacterFeedView.swift in Sources */, BD6A5E7523FCFD98003B1E4D /* EpisodeModalView.swift in Sources */, @@ -369,6 +380,7 @@ BD6A5E4323FC7FF7003B1E4D /* SceneDelegate.swift in Sources */, BD6A5E6423FC823D003B1E4D /* APICharacter.swift in Sources */, BD6A5E4523FC7FF7003B1E4D /* ContentView.swift in Sources */, + BD810D3923FDDC4200D7853A /* CharacterFilterView.swift in Sources */, BD6A5E6623FC855D003B1E4D /* CharacterFeedController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/FlinkChallenge/FlinkChallenge/Controller/CharacterFilteringController.swift b/FlinkChallenge/FlinkChallenge/Controller/CharacterFilteringController.swift new file mode 100644 index 0000000..6165036 --- /dev/null +++ b/FlinkChallenge/FlinkChallenge/Controller/CharacterFilteringController.swift @@ -0,0 +1,80 @@ +// +// CharacterFilteringController.swift +// FlinkChallenge +// +// Created by Fernando Martin Garcia Del Angel on 19/02/20. +// Copyright © 2020 Fernando Martin Garcia Del Angel. All rights reserved. +// + +import Foundation + +class CharacterFiltering : ObservableObject, RandomAccessCollection { + typealias Element = APICharacter + @Published var characterListItems = [APICharacter]() + + var startIndex: Int { characterListItems.startIndex } + var endIndex: Int { characterListItems.endIndex } + var loadStatus = LoadStatus.ready(nextPage: 1) + + var baseURL = "https://rickandmortyapi.com/api/character/?" + + init(name : String, status : String, species: String, type : String, gender: String){ + loadFilteredCharacters(params: [name,status,species,type, gender]) + } + + subscript(position: Int) -> APICharacter { + return characterListItems[position] + } + + func loadFilteredCharacters(params : [String]) { + var leadingQueryString : String = "" + let labels = ["name","status","species","type","gender"] + for (index, param) in params.enumerated() { + if !param.isEmpty { + leadingQueryString += "\(labels[index])=\(param.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "")" + } + + if index != params.count { + leadingQueryString += "&" + } + } + let completeURL = "\(baseURL)\(leadingQueryString)" + let url = URL(string: completeURL)! + let task = URLSession.shared.dataTask(with: url,completionHandler: parseCharactersFromResponse(data:response:error:)) + task.resume() + } + + func parseCharactersFromResponse(data: Data?, response : URLResponse?, error : Error?) { + guard error == nil else { + print("Error: \(error?.localizedDescription ?? "Unknown error produced")") + return + } + + guard let data = data else { + print("Error: \(error?.localizedDescription ?? "Unknown error produced")") + return + } + + let apiCharacters = parseCharactersFromData(data: data) + DispatchQueue.main.async { + self.characterListItems.append(contentsOf: apiCharacters) + if apiCharacters.count == 0 { + print("load status done") + } else { + print("load status unknown") + } + } + } + + func parseCharactersFromData(data: Data) -> [APICharacter] { + var response : APICharactersResponse + do { + response = try JSONDecoder().decode(APICharactersResponse.self, from: data) + } catch { + print(error.localizedDescription) + return [] + } + + return response.results ?? [] + } +} diff --git a/FlinkChallenge/FlinkChallenge/Views/AdvancedFilterView.swift b/FlinkChallenge/FlinkChallenge/Views/AdvancedFilterView.swift new file mode 100644 index 0000000..90dba37 --- /dev/null +++ b/FlinkChallenge/FlinkChallenge/Views/AdvancedFilterView.swift @@ -0,0 +1,85 @@ +// +// AdvancedFilterView.swift +// FlinkChallenge +// +// Created by Fernando Martin Garcia Del Angel on 19/02/20. +// Copyright © 2020 Fernando Martin Garcia Del Angel. All rights reserved. +// + +import SwiftUI + +struct RoundedButton : View { + var name : String = "" + var status : String = "" + var species : String = "" + var type: String = "" + var gender : String = "" + + var body: some View { + NavigationLink(destination: + CharacterFilterView(name: name, + status: status, + species: species, + type: type, + gender: gender)) { + Button(action: {}){ + HStack { + Spacer() + Text("Search") + .font(.headline) + Spacer() + } + } + .padding(.vertical, 10.0) + .padding(.horizontal, 50) + } + } +} + +struct AdvancedFilterView: View { + var statuses = ["alive","dead","unknown"] + var genders = ["female","male","genderless","unknown"] + @State private var selectedStatus = 0 + @State private var selectedGender = 0 + @State private var name: String = "" + @State private var status: String = "" + @State private var species : String = "" + @State private var type : String = "" + @State private var gender : String = "" + + var body: some View { + Form { + Section(header: Text("Basic Data")) { + TextField("Name",text: $name) + } + + Section(header: Text("Other data")) { + Picker (selection: $selectedStatus, label: Text("Status")) { + ForEach(0 ..< statuses.count) { + Text(self.statuses[$0].capitalized) + } + }.pickerStyle(SegmentedPickerStyle()) + + Picker (selection: $selectedGender, label: Text("Gender")) { + ForEach(0 ..< genders.count) { + Text(self.genders[$0].capitalized) + } + }.pickerStyle(SegmentedPickerStyle()) + TextField("Species",text: $species) + TextField("Type",text: $type) + + } + RoundedButton(name: name, + status: statuses[selectedStatus], + species: species, + type: type, + gender: genders[selectedGender]) + } + } +} + +struct AdvancedFilterView_Previews: PreviewProvider { + static var previews: some View { + AdvancedFilterView() + } +} diff --git a/FlinkChallenge/FlinkChallenge/Views/CharacterFeedView.swift b/FlinkChallenge/FlinkChallenge/Views/CharacterFeedView.swift index 2c6f305..0c414a6 100644 --- a/FlinkChallenge/FlinkChallenge/Views/CharacterFeedView.swift +++ b/FlinkChallenge/FlinkChallenge/Views/CharacterFeedView.swift @@ -27,12 +27,12 @@ struct CharacterFeedView: View { NavigationLink(destination: CharacterDetail(character: character)) { EmptyView() }.buttonStyle(PlainButtonStyle()) + } } } } } } -} struct CharacterFeedView_Previews: PreviewProvider { static var previews: some View { diff --git a/FlinkChallenge/FlinkChallenge/Views/CharacterFilterView.swift b/FlinkChallenge/FlinkChallenge/Views/CharacterFilterView.swift new file mode 100644 index 0000000..a3ae180 --- /dev/null +++ b/FlinkChallenge/FlinkChallenge/Views/CharacterFilterView.swift @@ -0,0 +1,42 @@ +// +// CharacterFilterView.swift +// FlinkChallenge +// +// Created by Fernando Martin Garcia Del Angel on 19/02/20. +// Copyright © 2020 Fernando Martin Garcia Del Angel. All rights reserved. +// + +import SwiftUI + +struct CharacterFilterView: View { + @ObservedObject var characterFeed : CharacterFiltering + @State private var searchText : String = "" + + init(name : String, status: String, species : String, type: String, gender: String) { + characterFeed = CharacterFiltering(name: name, status: status, species: species, type: type, gender: gender) + } + + var body: some View { + VStack { + SearchBar(text: $searchText, placeholder: "Search for characters") + List { + ForEach(self.characterFeed.filter { + self.searchText.isEmpty ? true : $0.name!.lowercased().contains(self.searchText.lowercased()) + }, id: \.id) { character in + ZStack { + Card(character: character).frame(width: 300, height: 300) + NavigationLink(destination: CharacterDetail(character: character)) { + EmptyView() + }.buttonStyle(PlainButtonStyle()) + } + } + } + }.navigationBarTitle(Text("Search Results")) + } +} + +struct CharacterFilterView_Previews: PreviewProvider { + static var previews: some View { + CharacterFilterView(name: "Rick", status: "", species: "", type: "", gender: "") + } +} diff --git a/FlinkChallenge/FlinkChallenge/Views/ContentView.swift b/FlinkChallenge/FlinkChallenge/Views/ContentView.swift index 2f390cd..36cf434 100644 --- a/FlinkChallenge/FlinkChallenge/Views/ContentView.swift +++ b/FlinkChallenge/FlinkChallenge/Views/ContentView.swift @@ -19,10 +19,12 @@ struct ContentView: View { Text("Characters") } - dummyView() + NavigationView { + AdvancedFilterView().navigationBarTitle(Text("Advanced Search")) + } .tag(1) .tabItem { - Text("Search") + Text("Advanced Search") } } }