Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add favorited only filter #5

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions MyLibrary/Sources/ScheduleFeature/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@
}
}
},
"All" : {
"localizations" : {
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "全て"
}
}
}
},
"As most people tend to use cloud sync services to store relevant content, we will explore File Provider framework on both iOS and macOS and all the related features: Finder and Files app integration, remote synchronisation with upload and downloads. So let’s explore how it works on both iOS and macOS and how you can sync, upload and download files on these platforms." : {
"extractionState" : "manual",
"localizations" : {
Expand Down Expand Up @@ -326,6 +336,26 @@
}
}
},
"Favorite" : {
"localizations" : {
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "お気に入り"
}
}
}
},
"Filter" : {
"localizations" : {
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "フィルター"
}
}
}
},
"Getting started with controlling LEGO using Swift" : {
"extractionState" : "manual",
"localizations" : {
Expand Down Expand Up @@ -733,6 +763,16 @@
}
}
},
"No items to show." : {
"localizations" : {
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "表示できるものがありません。"
}
}
}
},
"Office hour %@" : {
"extractionState" : "manual",
"localizations" : {
Expand Down
134 changes: 109 additions & 25 deletions MyLibrary/Sources/ScheduleFeature/Schedule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,41 @@ public struct Schedule {
var day1: Conference?
var day2: Conference?
var workshop: Conference?
var selectedFilter: FilterItem = .all
var favorites: Favorites = [:]
@Presents var destination: Destination.State?

var day1ToShow: Conference? {
filteredConference(of: .day1)
}
var day2ToShow: Conference? {
filteredConference(of: .day2)
}
var workshopToShow: Conference? {
filteredConference(of: .day3)
}

public init() {}

func filteredConference(of day: Days) -> Conference? {
let conference =
switch selectedDay {
case .day1:
day1
case .day2:
day2
case .day3:
workshop
}
guard let conference = conference else { return nil }
switch selectedFilter {
case .all:
return conference
case .favorite:
let schedules = conference.schedules.filtered(using: favorites, in: conference)
return Conference(id: conference.id, title: conference.title, date: conference.date, schedules: schedules)
}
}
}

public enum Action: BindableAction, ViewAction {
Expand All @@ -54,6 +85,11 @@ public struct Schedule {
}
}

public enum FilterItem: LocalizedStringKey, CaseIterable {
case all = "All"
case favorite = "Favorite"
}

@Reducer(state: .equatable)
public enum Path {
case detail(ScheduleDetail)
Expand Down Expand Up @@ -103,7 +139,7 @@ public struct Schedule {
)
return .none
case let .view(.favoriteIconTapped(session)):
let day = switch state.selectedDay {
let conference = switch state.selectedDay {
case .day1:
state.day1!
case .day2:
Expand All @@ -112,10 +148,10 @@ public struct Schedule {
state.workshop!
}
var favorites = state.favorites
favorites.updateFavoriteState(of: session, in: day)
favorites.updateFavoriteState(of: session, in: conference)
return .run { [favorites = favorites] send in
try? fileClient.saveFavorites(favorites)
await send(.savedFavorites(session, day))
await send(.savedFavorites(session, conference))
}
case let .savedFavorites(session, day):
state.favorites.updateFavoriteState(of: session, in: day)
Expand Down Expand Up @@ -164,6 +200,21 @@ private extension Favorites {
}
}

extension [SharedModels.Schedule] {
func filtered(using favorites: Favorites, in conference: Conference) -> Self {
self
.map {
SharedModels.Schedule(time: $0.time, sessions: $0.sessions.filter {
guard let favorites = favorites[conference.title] else {
return false
}
return favorites.contains($0)
})
}
.filter { $0.sessions.count > 0 }
}
}

@ViewAction(for: Schedule.self)
public struct ScheduleView: View {

Expand Down Expand Up @@ -198,25 +249,42 @@ public struct ScheduleView: View {
.padding(.horizontal)
switch store.selectedDay {
case .day1:
if let day1 = store.day1 {
if let day1 = store.day1ToShow {
conferenceList(conference: day1)
} else {
Text("")
}
case .day2:
if let day2 = store.day2 {
if let day2 = store.day2ToShow {
conferenceList(conference: day2)
} else {
Text("")
}
case .day3:
if let workshop = store.workshop {
if let workshop = store.workshopToShow {
conferenceList(conference: workshop)
} else {
Text("")
}
}
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Menu {
Picker(String(localized: "Filter", bundle: .module), selection: $store.selectedFilter, content: {
ForEach(Schedule.FilterItem.allCases, id:\.self) { item in
Text(item.rawValue, bundle: .module)
.tag(item)
}
})
} label: {
HStack {
Text("Filter", bundle: .module)
Image(systemName: "line.horizontal.3.decrease")
}
}
}
}
.onAppear(perform: {
send(.onAppear)
})
Expand All @@ -229,37 +297,53 @@ public struct ScheduleView: View {
VStack(alignment: .leading, spacing: 8) {
Text(conference.date, style: .date)
.font(.title2)
ForEach(conference.schedules, id: \.self) { schedule in
VStack(alignment: .leading, spacing: 4) {
Text(schedule.time, style: .time)
.font(.subheadline.bold())
ForEach(schedule.sessions, id: \.self) { session in
if session.description != nil {
Button {
send(.disclosureTapped(session))
} label: {
listRow(session: session)
.padding()
}
.background(
Color(uiColor: .secondarySystemBackground)
.clipShape(RoundedRectangle(cornerRadius: 8))
)
} else {
listRow(session: session)
.padding()

if conference.schedules.count > 0 {
ForEach(conference.schedules, id: \.self) { schedule in
VStack(alignment: .leading, spacing: 4) {
Text(schedule.time, style: .time)
.font(.subheadline.bold())
ForEach(schedule.sessions, id: \.self) { session in
if session.description != nil {
Button {
send(.disclosureTapped(session))
} label: {
listRow(session: session)
.padding()
}
.background(
Color(uiColor: .secondarySystemBackground)
.clipShape(RoundedRectangle(cornerRadius: 8))
)
} else {
listRow(session: session)
.padding()
.background(
Color(uiColor: .secondarySystemBackground)
.clipShape(RoundedRectangle(cornerRadius: 8))
)
}
}
}
}
} else {
noItemsToShowMessage()
}
}
.padding()
}

@ViewBuilder
func noItemsToShowMessage() -> some View {
Text("No items to show.", bundle: .module)
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(
Color(uiColor: .secondarySystemBackground)
.clipShape(RoundedRectangle(cornerRadius: 8))
)
}

@ViewBuilder
func listRow(session: Session) -> some View {
HStack(spacing: 8) {
Expand Down
2 changes: 2 additions & 0 deletions MyLibrary/Sources/SharedModels/Conference.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import Foundation

public struct Conference: Codable, Equatable, Hashable, Sendable {
public var id: Int
public var title: String
public var date: Date
public var schedules: [Schedule]

public init(id: Int, title: String, date: Date, schedules: [Schedule]) {
self.id = id
self.title = title
self.date = date
self.schedules = schedules
Expand Down
20 changes: 20 additions & 0 deletions MyLibrary/Tests/ScheduleFeatureTests/ScheduleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,24 @@ final class ScheduleTests: XCTestCase {
initialState.favorites = [initialState.day1!.title: [firstSession]]
return initialState
}()

@MainActor
func testSchedulesFilteredFavoritesOnly() async {
let initialState: ScheduleFeature.Schedule.State = ScheduleTests.selectingDay1ScheduleWithOneFavorite
let store = TestStore(initialState: initialState) {
Schedule()
}
let schedulesMock1Only = [Schedule(time: Date(timeIntervalSince1970: 10_000), sessions: [.mock1])]
let expected = Conference(
id: 1,
title: "conference1",
date: Date(timeIntervalSince1970: 1_000),
schedules: schedulesMock1Only
)

await store.send(.binding(.set(\.selectedFilter, Schedule.FilterItem.favorite))) {
$0.selectedFilter = .favorite
XCTAssertEqual($0.day1ToShow, expected)
}
}
}