Skip to content

Commit

Permalink
Migrate Lecturers and Groups screens to Shared state
Browse files Browse the repository at this point in the history
  • Loading branch information
asiliuk committed Jul 18, 2024
1 parent 090a32d commit 0095189
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 240 deletions.
56 changes: 56 additions & 0 deletions Modules/Sources/Favorites/PersistanceKey+Favorites.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import Foundation
import ComposableArchitecture
import BsuirCore
import ScheduleCore

// MARK: - High Score

extension PersistenceReaderKey where Self == PersistenceKeyDefault<CloudSyncablePersistenceKey<Int>> {
public static var freeLoveHighScore: Self {
Expand All @@ -15,3 +18,56 @@ extension PersistenceReaderKey where Self == PersistenceKeyDefault<CloudSyncable
}
}

// MARK: - Pinned

extension PersistenceReaderKey
where Self == PersistenceKeyDefault<
PersistenceKeyTransform<
CloudSyncablePersistenceKey<[String: Any]>,
CloudSyncableScheduleSource
>
> {
public static var pinnedSchedule: Self {
let syncableDictionary = CloudSyncablePersistenceKey<[String: Any]>.cloudSyncable(
key: "pinned-schedule",
cloudKey: "cloud-pinned-schedule",
shouldSyncInitialLocalValue: true,
isEqual: { $0 as NSDictionary? == $1 as NSDictionary? }
)

let syncableCloudSource = PersistenceKeyTransform(
base: syncableDictionary,
coding: CloudSyncableScheduleSource.self
)

return PersistenceKeyDefault(syncableCloudSource, .nothing)
}
}

// MARK: - Favorites

extension PersistenceReaderKey where Self == PersistenceKeyDefault<CloudSyncablePersistenceKey<[String]>> {
public static var favoriteGroupNames: Self {
PersistenceKeyDefault(
.cloudSyncable(
key: "favorite-group-names",
cloudKey: "cloud-favorite-group-names",
shouldSyncInitialLocalValue: true
),
[]
)
}
}

extension PersistenceReaderKey where Self == PersistenceKeyDefault<CloudSyncablePersistenceKey<[Int]>> {
public static var favoriteLecturerIDs: Self {
PersistenceKeyDefault(
.cloudSyncable(
key: "favorite-lector-ids",
cloudKey: "cloud-favorite-lector-ids",
shouldSyncInitialLocalValue: true
),
[]
)
}
}
20 changes: 5 additions & 15 deletions Modules/Sources/GroupsFeature/GroupsFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ public struct GroupsFeature {
var groupPresentationMode: GroupPresentationMode = .initial

// MARK: Placeholder
var hasPinnedPlaceholder: Bool = false
var favoritesPlaceholderCount: Int = 0
var hasPinnedPlaceholder: Bool { pinnedSchedule.source?.is(\.group) ?? false }
var favoritesPlaceholderCount: Int { favoriteGroupNames.count }
@SharedReader(.favoriteGroupNames) var favoriteGroupNames
@SharedReader(.pinnedSchedule) var pinnedSchedule

// MARK: Groups
var groups: LoadingState<LoadedGroupsFeature.State> = .initial
Expand All @@ -41,7 +43,6 @@ public struct GroupsFeature {
}

case task
case onAppear
case forceAddGroupButtonTapped
case forceAddAlert(PresentationAction<ForceAddAlert.Action>)

Expand All @@ -51,19 +52,12 @@ public struct GroupsFeature {
}

@Dependency(\.apiClient) var apiClient
@Dependency(\.favorites.currentGroupNames) var favoriteGroupNames
@Dependency(\.pinnedScheduleService.currentSchedule) var pinnedSchedule

public init() {}

public var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .onAppear:
state.hasPinnedPlaceholder = pinnedSchedule()?.groupName != nil
state.favoritesPlaceholderCount = favoriteGroupNames.count
return .none

case .task:
// Very important to do in task if done in `onAppear`
// schedule screen is not properly loaded and got stuck in placeholder state
Expand Down Expand Up @@ -99,11 +93,7 @@ public struct GroupsFeature {
}
.load(state: \.groups, action: \.groups) { _, isRefresh in
let groups = try await apiClient.groups(isRefresh)
return LoadedGroupsFeature.State(
groups: groups,
favoritesNames: favoriteGroupNames,
pinnedName: pinnedSchedule()?.groupName
)
return LoadedGroupsFeature.State(groups: groups)
} loaded: {
LoadedGroupsFeature()
}
Expand Down
1 change: 0 additions & 1 deletion Modules/Sources/GroupsFeature/GroupsFeatureView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ public struct GroupsFeatureView: View {
} destination: { store in
EntityScheduleFeatureViewV2(store: store)
}
.onAppear { store.send(.onAppear) }
.task { await store.send(.task).finish() }
.forceAddAlert(store: $store.scope(state: \.forceAddAlert, action: \.forceAddAlert))
}
Expand Down
119 changes: 22 additions & 97 deletions Modules/Sources/GroupsFeature/Loaded/LoadedGroupsFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@ public struct LoadedGroupsFeature {
updateVisibleRows()
}

private mutating func updatePinnedRows() {
fileprivate mutating func updatePinnedRows(groupName: String?? = nil) {
pinnedRows = IdentifiedArray(
uniqueElements: [pinnedName]
uniqueElements: [groupName ?? pinnedSchedule.source?.groupName]
.compacted()
.filter { $0.matches(query: searchQuery) }
.map { groupRows[id: $0].or(GroupsRow.State(groupName: $0)) }
)
}

private mutating func updateFavoriteRows() {
fileprivate mutating func updateFavoriteRows(favoritesNames: [String]? = nil) {
favoriteRows = IdentifiedArray(
uniqueElements: favoritesNames
uniqueElements: (favoritesNames ?? self.favoritesNames)
.filter { $0.matches(query: searchQuery) }
.map { groupRows[id: $0].or(GroupsRow.State(groupName: $0)) }
)
Expand All @@ -58,17 +58,11 @@ public struct LoadedGroupsFeature {
var visibleRows: IdentifiedArrayOf<GroupsRow.State> = []

// MARK: State
fileprivate var favoritesNames: OrderedSet<String>
fileprivate var pinnedName: String?
@SharedReader(.favoriteGroupNames) var favoritesNames
@SharedReader(.pinnedSchedule) var pinnedSchedule
fileprivate var groupRows: IdentifiedArrayOf<GroupsRow.State>

init(
groups: [StudentGroup],
favoritesNames: OrderedSet<String>,
pinnedName: String?
) {
self.favoritesNames = favoritesNames
self.pinnedName = pinnedName
init(groups: [StudentGroup]) {
self.groupRows = IdentifiedArray(
uniqueElements: groups
.sorted(by: { $0.name < $1.name })
Expand All @@ -89,16 +83,13 @@ public struct LoadedGroupsFeature {
case favoriteRows(IdentifiedActionOf<GroupsRow>)
case visibleRows(IdentifiedActionOf<GroupsRow>)

case _favoritesUpdate(OrderedSet<String>)
case _favoritesUpdate([String])
case _pinnedUpdate(String?)

case delegate(Delegate)
case binding(BindingAction<State>)
}

@Dependency(\.favorites.groupNames) var favoriteGroupNames
@Dependency(\.pinnedScheduleService.schedule) var pinnedSchedule

public var body: some ReducerOf<Self> {
BindingReducer()
.onChange(of: \.searchQuery) { _, query in
Expand All @@ -116,16 +107,24 @@ public struct LoadedGroupsFeature {
switch action {
case .task:
return .merge(
listenToFavoriteUpdates(),
listenToPinnedUpdates()
.publisher {
state.$favoritesNames.publisher
.map(Action._favoritesUpdate)
},
.publisher {
state.$pinnedSchedule.publisher
.map { $0.source?.groupName }
.removeDuplicates()
.map(Action._pinnedUpdate)
}
)

case ._favoritesUpdate(let value):
state.favoritesNames = value
case ._favoritesUpdate(let newValue):
state.updateFavoriteRows(favoritesNames: newValue)
return .none

case ._pinnedUpdate(let value):
state.pinnedName = value
case ._pinnedUpdate(let newValue):
state.updatePinnedRows(groupName: newValue)
return .none

case .pinnedRows(.element(_, .mark(.delegate(let action)))),
Expand All @@ -149,80 +148,6 @@ public struct LoadedGroupsFeature {
.forEach(\.visibleRows, action: \.visibleRows) {
GroupsRow()
}
.onChange(of: \.pinnedName) { oldPinned, newPinned in
Reduce { state, _ in
return updatePinned(state: &state, oldPinned: oldPinned, newPinned: newPinned)
}
}
.onChange(of: \.favoritesNames) { oldFavorites, newFavorites in
Reduce { state, _ in
return updateFavorites(state: &state, oldFavorites: oldFavorites, newFavorites: newFavorites)
}
}
}

private func listenToFavoriteUpdates() -> Effect<Action> {
return .run { send in
for await value in favoriteGroupNames.removeDuplicates().values {
await send(._favoritesUpdate(value), animation: .default)
}
}
}

private func listenToPinnedUpdates() -> Effect<Action> {
return .run { send in
for await value in pinnedSchedule().map(\.?.groupName).removeDuplicates().values {
await send(._pinnedUpdate(value), animation: .default)
}
}
}

private func updatePinned(
state: inout State,
oldPinned: String?,
newPinned: String?
) -> Effect<Action> {
if let newPinned {
state.favoriteRows[id: newPinned]?.mark.isPinned = true
state.visibleRows[id: newPinned]?.mark.isPinned = true
state.groupRows[id: newPinned]?.mark.isPinned = true
if newPinned.matches(query: state.searchQuery) {
state.pinnedRows[id: newPinned] = state.groupRows[id: newPinned] ?? GroupsRow.State(groupName: newPinned)
}
}

if let oldPinned {
state.favoriteRows[id: oldPinned]?.mark.isPinned = false
state.visibleRows[id: oldPinned]?.mark.isPinned = false
state.groupRows[id: oldPinned]?.mark.isPinned = false
state.pinnedRows.remove(id: oldPinned)
}

return .none
}

private func updateFavorites(
state: inout State,
oldFavorites: OrderedSet<String>,
newFavorites: OrderedSet<String>
) -> Effect<Action> {
for difference in newFavorites.difference(from: oldFavorites) {
switch difference {
case .insert(_, let groupName, _):
state.pinnedRows[id: groupName]?.mark.isFavorite = true
state.visibleRows[id: groupName]?.mark.isFavorite = true
state.groupRows[id: groupName]?.mark.isFavorite = true
if groupName.matches(query: state.searchQuery) {
state.favoriteRows[id: groupName] = state.groupRows[id: groupName] ?? GroupsRow.State(groupName: groupName)
}
case .remove(_, let groupName, _):
state.pinnedRows[id: groupName]?.mark.isFavorite = false
state.visibleRows[id: groupName]?.mark.isFavorite = false
state.groupRows[id: groupName]?.mark.isFavorite = false
state.favoriteRows.remove(id: groupName)
}
}
return .none
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ struct LoadedGroupsFeatureView: View {
.dismissSearch(store.searchDismiss)
.searchable(text: $store.searchQuery, prompt: "screen.groups.search.placeholder")
.task { await store.send(.task).finish() }
.animation(.default, value: store.pinnedRows.ids)
.animation(.default, value: store.favoriteRows.ids)
.animation(.default, value: store.visibleRows.ids)
}
}
}
Expand Down
21 changes: 5 additions & 16 deletions Modules/Sources/LecturersFeature/LecturersFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ public struct LecturersFeature {
var lectorPresentationMode: LectorPresentationMode = .initial

// MARK: Placeholder
var hasPinnedPlaceholder: Bool = false
var favoritesPlaceholderCount: Int = 0
var hasPinnedPlaceholder: Bool { pinnedSchedule.source?.is(\.lector) ?? false }
var favoritesPlaceholderCount: Int { favoriteLecturerIDs.count }

// MARK: Lecturers
var lecturers: LoadingState<LoadedLecturersFeature.State> = .initial
@SharedReader(.favoriteLecturerIDs) var favoriteLecturerIDs
@SharedReader(.pinnedSchedule) var pinnedSchedule

public init() {}
}
Expand All @@ -37,28 +39,19 @@ public struct LecturersFeature {
case showPremiumClubPinned
}

case onAppear

case path(StackAction<EntityScheduleFeatureV2.State, EntityScheduleFeatureV2.Action>)
case lecturers(LoadingActionOf<LoadedLecturersFeature>)

case delegate(Delegate)
}

@Dependency(\.apiClient) var apiClient
@Dependency(\.favorites.currentLectorIds) var favoriteLectorIds
@Dependency(\.pinnedScheduleService.currentSchedule) var pinnedSchedule

public init() {}

public var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .onAppear:
state.hasPinnedPlaceholder = pinnedSchedule()?.lector != nil
state.favoritesPlaceholderCount = favoriteLectorIds.count
return .none

case .lecturers(.fetchFinished):
state.presentDeferredLectorIfNeeded()
return .none
Expand Down Expand Up @@ -88,11 +81,7 @@ public struct LecturersFeature {
}
.load(state: \.lecturers, action: \.lecturers) { _, isRefresh in
let lecturers = try await apiClient.lecturers(isRefresh)
return LoadedLecturersFeature.State(
lecturers: lecturers,
favoritesIds: favoriteLectorIds,
pinnedId: pinnedSchedule()?.lector?.id
)
return LoadedLecturersFeature.State(lecturers: lecturers)
} loaded: {
LoadedLecturersFeature()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ public struct LecturersFeatureView: View {
} destination: { store in
EntityScheduleFeatureViewV2(store: store)
}
.onAppear { store.send(.onAppear) }
}
}
}
Loading

0 comments on commit 0095189

Please sign in to comment.