From 73bac3edb26f3cf858f9992d9ba7d91e8f1a91f4 Mon Sep 17 00:00:00 2001 From: Anton Siliuk Date: Fri, 7 Aug 2020 17:35:54 +0300 Subject: [PATCH] Implement favorite groups --- .../AppState/AllGroupsScreen.swift | 51 +++++++++++++++++-- .../AppState/AllLecturersScreen.swift | 8 +-- BsuirScheduleApp/AppState/AppState.swift | 5 +- BsuirScheduleApp/AppState/GroupScreen.swift | 7 ++- .../AppState/LecturerScreen.swift | 4 +- .../AppState/ScheduleScreen.swift | 14 ++++- .../Views/Schedule/ScheduleView.swift | 16 +++++- 7 files changed, 90 insertions(+), 15 deletions(-) diff --git a/BsuirScheduleApp/AppState/AllGroupsScreen.swift b/BsuirScheduleApp/AppState/AllGroupsScreen.swift index fb3d6915..2c2f9a19 100644 --- a/BsuirScheduleApp/AppState/AllGroupsScreen.swift +++ b/BsuirScheduleApp/AppState/AllGroupsScreen.swift @@ -10,14 +10,39 @@ import BsuirApi import Combine import Foundation +final class FavoritesContainer { + @Published private(set) var favorites: Set { + didSet { + storage.set(favorites.sorted(), forKey: key) + } + } + + init(storage: UserDefaults) { + self.storage = storage + self.favorites = Set(storage.object(forKey: key) as? [Int] ?? []) + } + + func toggleFavorite(id: Int) { + if favorites.contains(id) { + favorites.remove(id) + } else { + favorites.insert(id) + } + } + + private let key = "favorite-group-ids" + private let storage: UserDefaults +} + final class AllGroupsScreen: ObservableObject { @Published var searchQuery: String = "" let groups: LoadableContent<[AllGroupsScreenGroupSection]> let requestManager: RequestsManager - init(requestManager: RequestsManager) { + init(requestManager: RequestsManager, favorites: FavoritesContainer) { self.requestManager = requestManager + self.favorites = favorites self.groups = LoadableContent( requestManager .request(BsuirTargets.Groups()) @@ -26,22 +51,32 @@ final class AllGroupsScreen: ObservableObject { guard !query.isEmpty else { return groups } return groups.filter { $0.name.starts(with: query) } } - .map { .init($0) } + .combineLatest(favorites.$favorites.setFailureType(to: RequestsManager.RequestError.self)) + .map { .init(favorites: $1, groups: $0) } .eraseToLoading() ) } func screen(for group: AllGroupsScreenGroup) -> ScheduleScreen { - .group(group.group, requestManager: requestManager) + .group(group.group, favorites: favorites, requestManager: requestManager) } + private let favorites: FavoritesContainer private var cancellables = Set() } extension Array where Element == AllGroupsScreenGroupSection { - init(_ groups: [Group]) { + init(favorites: Set, groups: [Group]) { + let favoritesGroup = AllGroupsScreenGroupSection( + title: "⭐️ Избранные", + groups: groups + .filter { favorites.contains($0.id) } + .sorted { $0.name < $1.name } + .map(AllGroupsScreenGroup.init) + ) + let groupedGroups = Dictionary(grouping: groups, by: { $0.name.prefix(3) }) - self = groupedGroups + let rest = groupedGroups .sorted(by: { $0.key < $1.key }) .map { title, groups in AllGroupsScreenGroupSection( @@ -51,6 +86,12 @@ extension Array where Element == AllGroupsScreenGroupSection { .map(AllGroupsScreenGroup.init) ) } + + if favoritesGroup.groups.isEmpty { + self = rest + } else { + self = [favoritesGroup] + rest + } } } diff --git a/BsuirScheduleApp/AppState/AllLecturersScreen.swift b/BsuirScheduleApp/AppState/AllLecturersScreen.swift index 459457ef..1d29f3ae 100644 --- a/BsuirScheduleApp/AppState/AllLecturersScreen.swift +++ b/BsuirScheduleApp/AppState/AllLecturersScreen.swift @@ -26,8 +26,9 @@ final class AllLecturersScreen: ObservableObject { @Published var searchQuery: String = "" let lecturers: LoadableContent<[AllLecturersScreenLecturer]> - init(requestManager: RequestsManager) { + init(requestManager: RequestsManager, favorites: FavoritesContainer) { self.requestManager = requestManager + self.favorites = favorites self.lecturers = LoadableContent( requestManager.request(BsuirTargets.Employees()) .map { $0.map(AllLecturersScreenLecturer.init) } @@ -40,9 +41,10 @@ final class AllLecturersScreen: ObservableObject { } func screen(for lecturer: AllLecturersScreenLecturer) -> ScheduleScreen { - .lecturer(lecturer.employee, requestManager: requestManager) + .lecturer(lecturer.employee, favorites: favorites, requestManager: requestManager) } - + + private let favorites: FavoritesContainer private let requestManager: RequestsManager } diff --git a/BsuirScheduleApp/AppState/AppState.swift b/BsuirScheduleApp/AppState/AppState.swift index 086c0849..2b156fa1 100644 --- a/BsuirScheduleApp/AppState/AppState.swift +++ b/BsuirScheduleApp/AppState/AppState.swift @@ -26,6 +26,7 @@ final class AppState: ObservableObject { let requestManager: RequestsManager init(requestManager: RequestsManager) { self.requestManager = requestManager } - private(set) lazy var allGroups = AllGroupsScreen(requestManager: requestManager) - private(set) lazy var allLecturers = AllLecturersScreen(requestManager: requestManager) + private lazy var favorites = FavoritesContainer(storage: .standard) + private(set) lazy var allGroups = AllGroupsScreen(requestManager: requestManager, favorites: favorites) + private(set) lazy var allLecturers = AllLecturersScreen(requestManager: requestManager, favorites: favorites) } diff --git a/BsuirScheduleApp/AppState/GroupScreen.swift b/BsuirScheduleApp/AppState/GroupScreen.swift index 7100b18e..23d8cdc4 100644 --- a/BsuirScheduleApp/AppState/GroupScreen.swift +++ b/BsuirScheduleApp/AppState/GroupScreen.swift @@ -11,9 +11,14 @@ import Foundation extension ScheduleScreen { - static func group(_ group: Group, requestManager: RequestsManager) -> Self { + static func group(_ group: Group, favorites: FavoritesContainer, requestManager: RequestsManager) -> Self { Self( name: group.name, + isFavorite: favorites.$favorites + .map { $0.contains(group.id) } + .removeDuplicates() + .eraseToAnyPublisher(), + toggleFavorite: { favorites.toggleFavorite(id: group.id) }, request: requestManager .request(BsuirTargets.Schedule(agent: .groupID(group.id))) .map { ($0.schedules, $0.examSchedules) } diff --git a/BsuirScheduleApp/AppState/LecturerScreen.swift b/BsuirScheduleApp/AppState/LecturerScreen.swift index 5af14baa..e3ea5d37 100644 --- a/BsuirScheduleApp/AppState/LecturerScreen.swift +++ b/BsuirScheduleApp/AppState/LecturerScreen.swift @@ -12,9 +12,11 @@ import Foundation extension ScheduleScreen { - static func lecturer(_ employee: Employee, requestManager: RequestsManager) -> Self { + static func lecturer(_ employee: Employee, favorites: FavoritesContainer, requestManager: RequestsManager) -> Self { Self( name: employee.fio, + isFavorite: Just(false).eraseToAnyPublisher(), + toggleFavorite: {}, request: requestManager .request(BsuirTargets.EmployeeSchedule(id: employee.id)) .map { ($0.schedules ?? [], $0.examSchedules ?? []) } diff --git a/BsuirScheduleApp/AppState/ScheduleScreen.swift b/BsuirScheduleApp/AppState/ScheduleScreen.swift index 340e304a..f5d38e65 100644 --- a/BsuirScheduleApp/AppState/ScheduleScreen.swift +++ b/BsuirScheduleApp/AppState/ScheduleScreen.swift @@ -7,14 +7,24 @@ final class ScheduleScreen: ObservableObject { let name: String let schedule: LoadableContent - - init(name: String, request: AnyPublisher<(schedule: [DaySchedule], exams: [DaySchedule]), RequestsManager.RequestError>) { + @Published private(set) var isFavorite: Bool = false + let toggleFavorite: () -> Void + + init( + name: String, + isFavorite: AnyPublisher, + toggleFavorite: @escaping () -> Void, + request: AnyPublisher<(schedule: [DaySchedule], exams: [DaySchedule]), RequestsManager.RequestError> + ) { self.name = name self.schedule = LoadableContent( request .map(Schedule.init) .eraseToLoading() ) + + self.toggleFavorite = toggleFavorite + isFavorite.assign(to: &self.$isFavorite) } } diff --git a/BsuirScheduleApp/Views/Schedule/ScheduleView.swift b/BsuirScheduleApp/Views/Schedule/ScheduleView.swift index dd7000ea..ab09086c 100644 --- a/BsuirScheduleApp/Views/Schedule/ScheduleView.swift +++ b/BsuirScheduleApp/Views/Schedule/ScheduleView.swift @@ -23,7 +23,20 @@ struct ScheduleView: View { schedule .onAppear(perform: screen.schedule.load) .navigationBarTitle(Text(screen.name), displayMode: .inline) - .navigationBarItems(trailing: picker) + .navigationBarItems(trailing: HStack { favorite; picker }) + } + + private var favorite: some View { + Button(action: screen.toggleFavorite) { + Image(systemName: screen.isFavorite ? "star.fill" : "star") + .accentColor(.yellow) + .padding(.horizontal, 4) + } + .accessibility( + label: screen.isFavorite + ? Text("Добавить в избранное") + : Text("Убрать из избранного") + ) } private var picker: some View { @@ -35,6 +48,7 @@ struct ScheduleView: View { } } label: { Image(systemName: "calendar") + .padding(.horizontal, 4) } .accessibility(label: Text("Тип расписания")) }