Skip to content

Commit

Permalink
- Search View (with new episode cell for search)
Browse files Browse the repository at this point in the history
- Search working!
- Seasons Watched
  • Loading branch information
jsanzdev committed Jan 7, 2023
1 parent 0c4f147 commit f2d944e
Show file tree
Hide file tree
Showing 11 changed files with 267 additions and 19 deletions.
12 changes: 12 additions & 0 deletions BBTGuide.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
097041232947AC7B007910B8 /* SeasonsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097041222947AC7B007910B8 /* SeasonsView.swift */; };
097041252947AD1B007910B8 /* SeasonsDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097041242947AD1B007910B8 /* SeasonsDetailView.swift */; };
09A127A329689C65006502F7 /* AllEpisodesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A127A229689C65006502F7 /* AllEpisodesView.swift */; };
09A127A529697546006502F7 /* FavoriteCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A127A429697546006502F7 /* FavoriteCell.swift */; };
09A127A7296980AC006502F7 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A127A6296980AC006502F7 /* SearchView.swift */; };
09A127AA2969869A006502F7 /* SearchEpisodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A127A92969869A006502F7 /* SearchEpisodeCell.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand All @@ -47,6 +50,9 @@
097041222947AC7B007910B8 /* SeasonsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeasonsView.swift; sourceTree = "<group>"; };
097041242947AD1B007910B8 /* SeasonsDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeasonsDetailView.swift; sourceTree = "<group>"; };
09A127A229689C65006502F7 /* AllEpisodesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllEpisodesView.swift; sourceTree = "<group>"; };
09A127A429697546006502F7 /* FavoriteCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteCell.swift; sourceTree = "<group>"; };
09A127A6296980AC006502F7 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
09A127A92969869A006502F7 /* SearchEpisodeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchEpisodeCell.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -116,6 +122,7 @@
09418CD0293BBCFB000B34EF /* EpisodeDetailView.swift */,
09418CD2293BBD19000B34EF /* TabController.swift */,
09418CD4293BBD25000B34EF /* FavoritesView.swift */,
09A127A6296980AC006502F7 /* SearchView.swift */,
);
name = Views;
sourceTree = "<group>";
Expand All @@ -127,6 +134,8 @@
09418CCE293B7D1D000B34EF /* SeasonCell.swift */,
09395CC92953657100F3EC3D /* RatingView.swift */,
09395CCB295368C000F3EC3D /* RatingViewCell.swift */,
09A127A429697546006502F7 /* FavoriteCell.swift */,
09A127A92969869A006502F7 /* SearchEpisodeCell.swift */,
);
name = ViewComponents;
sourceTree = "<group>";
Expand Down Expand Up @@ -237,6 +246,7 @@
buildActionMask = 2147483647;
files = (
09418CD5293BBD25000B34EF /* FavoritesView.swift in Sources */,
09A127AA2969869A006502F7 /* SearchEpisodeCell.swift in Sources */,
09418CD1293BBCFB000B34EF /* EpisodeDetailView.swift in Sources */,
09395CCA2953657100F3EC3D /* RatingView.swift in Sources */,
09A127A329689C65006502F7 /* AllEpisodesView.swift in Sources */,
Expand All @@ -249,6 +259,8 @@
09418CC9293B6728000B34EF /* ModelPersistence.swift in Sources */,
09418CD3293BBD19000B34EF /* TabController.swift in Sources */,
09418CD7293BBD37000B34EF /* DetailViewModel.swift in Sources */,
09A127A7296980AC006502F7 /* SearchView.swift in Sources */,
09A127A529697546006502F7 /* FavoriteCell.swift in Sources */,
09418CCD293B7ABC000B34EF /* EpisodeCell.swift in Sources */,
09395CCC295368C000F3EC3D /* RatingViewCell.swift in Sources */,
);
Expand Down
15 changes: 8 additions & 7 deletions BBTGuide/AllEpisodesView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,22 @@ struct AllEpisodesView: View {
} label: {
SeasonCell(season: episodes)
}
}
.swipeActions(edge: .leading, allowsFullSwipe: true) {
Button {
} label: {
Image(systemName: "eye.circle.fill")
.swipeActions(edge: .leading, allowsFullSwipe: true) {
Button {
episodesVM.toggleWatched(number: episodes.first!.season)
} label: {
Image(systemName: "eye.circle.fill")
}
}
.tint(episodesVM.seasonWatched(number: episodes.first!.season) ? .red: .green)
}
.tint(.green)

}
.listStyle(.sidebar)
.navigationDestination(for: Episode.self) { episode in
EpisodeDetailView(detailVM: DetailViewModel(episode: episode))
}
.navigationTitle("The Big Bang Theory")
.searchable(text: $episodesVM.search)
}
}
}
Expand Down
54 changes: 44 additions & 10 deletions BBTGuide/EpisodesViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ final class EpisodesViewModel:ObservableObject {

@Published var search = ""

@Published var watchedSeasons:WatchedSeasons {
didSet {
persistence.saveWatched(watched: watchedSeasons)
}
}

var orderedEpisodes:[Episode] {
return episodes.sorted {
$0.number < $1.number
Expand All @@ -28,18 +34,26 @@ final class EpisodesViewModel:ObservableObject {
Dictionary(grouping: episodes) { episode in
episode.season
}.values.sorted {
$0.first!.season < $1.first!.season
}.map { episodes in
episodes.filter { episode in
if search.isEmpty {
return true
} else {
return episode.name.lowercased().hasPrefix(search.lowercased())
}
}.sorted { episode1, episode2 in
return episode1.number < episode2.number
$0.first?.season ?? 0 < $1.first?.season ?? 0
}
}

var searchSection:[Episode] {
episodes.filter { episode in
return searchEpisode(episode: episode.name.lowercased(), searchField: search.lowercased())
}.sorted { episode1, episode2 in
return episode1.number < episode2.number
}
}

var favoriteEpisodes:[Episode] {
var favorites:[Episode] = []
for episode in episodes {
if episode.favorite {
favorites.append(episode)
}
}
return favorites
}

var seasons:[Int] {
Expand All @@ -48,6 +62,7 @@ final class EpisodesViewModel:ObservableObject {

init() {
self.episodes = persistence.loadData()
self.watchedSeasons = persistence.loadWatched()
}

func refresh() {
Expand All @@ -67,4 +82,23 @@ final class EpisodesViewModel:ObservableObject {
func updateView(){
self.objectWillChange.send()
}

func searchEpisode(episode:String, searchField:String) -> Bool {
guard !episode.isEmpty, !searchField.isEmpty, let _ = episode.range(of: searchField) else {
return false
}
return true
}

func seasonWatched(number:Int) -> Bool {
watchedSeasons.watched.contains(where: { $0 == number })
}

func toggleWatched(number:Int) {
if watchedSeasons.watched.contains(where: {$0 == number }) {
watchedSeasons.watched.removeAll(where: {$0 == number })
} else {
watchedSeasons.watched.append(number)
}
}
}
56 changes: 56 additions & 0 deletions BBTGuide/FavoriteCell.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// FavoriteCell.swift
// BBTGuide
//
// Created by Jesus Sanz on 7/1/23.
//

import SwiftUI

struct FavoriteCell: View {
@ObservedObject var detailVM:DetailViewModel

@Environment(\.colorScheme) var colorScheme
var fillColor: Color {
if colorScheme == .dark {
return Color.black
} else {
return Color.white
}
}

var body: some View {
HStack {
ZStack(alignment: .bottom) {
Image(detailVM.episode.image)
.resizable()
.scaledToFit()
HStack (){
Text("Season \(detailVM.episode.season) - Episode \(detailVM.episode.number)")
.bold()
Spacer()
HStack {
RatingViewCell(rating: detailVM.score)
if (detailVM.watched) {
Image(systemName: "eye.circle.fill")
}
}
.bold()
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(10)
.background {
Rectangle()
.fill(fillColor.opacity(0.9))
}
}
.cornerRadius(10)
}
}
}

struct FavoriteCell_Previews: PreviewProvider {
static var previews: some View {
FavoriteCell(detailVM: DetailViewModel(episode: .episodeTest))
}
}
21 changes: 20 additions & 1 deletion BBTGuide/FavoritesView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,26 @@ struct FavoritesView: View {
@EnvironmentObject var episodesVM:EpisodesViewModel

var body: some View {
ScrollView {
if episodesVM.favoriteEpisodes.isEmpty {
Text("Add some Episodes to your Favorites!")
.bold()
} else {
NavigationStack {
ScrollView {
ForEach(episodesVM.favoriteEpisodes, id:\.self) { episode in
NavigationLink(value: episode) {
FavoriteCell(detailVM: DetailViewModel(episode: episode))
}
}
}
.buttonStyle(.plain)
.padding()
.navigationTitle("Favorite Episodes")
.navigationDestination(for: Episode.self) { episode in
EpisodeDetailView(detailVM: DetailViewModel(episode: episode))
}
}

}
}
}
Expand Down
4 changes: 4 additions & 0 deletions BBTGuide/ModelDefinition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ struct TempEpisode:Codable, Identifiable, Hashable {
let summary: String
}

struct WatchedSeasons:Codable {
var watched:[Int]
}

typealias TempEpisodes = [TempEpisode]

typealias Episodes = [Episode]
Expand Down
21 changes: 21 additions & 0 deletions BBTGuide/ModelPersistence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import SwiftUI
extension URL {
static let episodeDataURL = Bundle.main.url(forResource: "BigBang", withExtension: "json")!
static let userDataURL = URL.documentsDirectory.appending(component: "userdata").appendingPathExtension("json")
static let watchedSeasonsURL = URL.documentsDirectory.appending(component: "watchedSeasons").appendingPathExtension("json")
}


Expand Down Expand Up @@ -57,4 +58,24 @@ final class ModelPersistence {
saveData(episodes: episodes)
}

func loadWatched() -> WatchedSeasons {
do {
let data = try Data(contentsOf: .watchedSeasonsURL)
return try JSONDecoder().decode(WatchedSeasons.self, from: data)
} catch {
print("Error en la carga \(error)")
return WatchedSeasons(watched: [])
}
}

func saveWatched(watched:WatchedSeasons) {
do {
let encoder = JSONEncoder()
let data = try encoder.encode(watched)
try data.write(to: .watchedSeasonsURL, options: [.atomic, .completeFileProtection])
} catch {
print("Error saving the watched Seasons data \(error)")
}
}

}
52 changes: 52 additions & 0 deletions BBTGuide/SearchEpisodeCell.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// SearchEpisodeCell.swift
// BBTGuide
//
// Created by Jesus Sanz on 7/1/23.
//

import SwiftUI

struct SearchEpisodeCell: View {
@ObservedObject var detailVM:DetailViewModel

var body: some View {
HStack {
VStack(alignment: .leading) {
HStack {
Text(detailVM.episode.name)
.font(.headline)
}
Text("Season: \(detailVM.episode.season) - Episode \(detailVM.episode.number)")
.font(.caption)
.padding(.bottom, 10)
Text(detailVM.episode.summary)
.lineLimit(3)
.font(.caption)
.padding(.bottom, 10)
HStack {
VStack (alignment: .leading) {
Text("Runtime: \(detailVM.episode.runtime)")
Text("Air date: \(detailVM.episode.airdate)")
}
.font(.caption)
Spacer()
RatingViewCell(rating: detailVM.score)
Spacer()
if (detailVM.watched) {
Image(systemName: "eye.circle.fill")
}
if (detailVM.favorite) {
Image(systemName: "star.circle.fill")
}
}
}
}
}
}

struct SearchEpisodeCell_Previews: PreviewProvider {
static var previews: some View {
SearchEpisodeCell(detailVM: DetailViewModel(episode: .episodeTest))
}
}
43 changes: 43 additions & 0 deletions BBTGuide/SearchView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// SearchVIew.swift
// BBTGuide
//
// Created by Jesus Sanz on 7/1/23.
//

import SwiftUI

struct SearchView: View {
@EnvironmentObject var episodesVM:EpisodesViewModel
@State var path = NavigationPath()

var body: some View {
NavigationStack(path: $path) {
if episodesVM.search.isEmpty {
Text("Search for an Episode.")
.bold()
} else {
List {
ForEach(episodesVM.searchSection, id:\.self) { episode in
NavigationLink(value: episode) {
SearchEpisodeCell(detailVM: DetailViewModel(episode: episode))
}
}
}
.listStyle(.sidebar)
.navigationDestination(for: Episode.self) { episode in
EpisodeDetailView(detailVM: DetailViewModel(episode: episode))
}
.navigationTitle("Search")
}
}
.searchable(text: $episodesVM.search)
}
}

struct SearchView_Previews: PreviewProvider {
static var previews: some View {
SearchView()
.environmentObject(EpisodesViewModel())
}
}
Loading

0 comments on commit f2d944e

Please sign in to comment.