Skip to content

Commit 27a79c5

Browse files
committed
Simplifty User Service
1 parent 41b8062 commit 27a79c5

File tree

12 files changed

+210
-158
lines changed

12 files changed

+210
-158
lines changed

Modules/Package.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,10 @@ let package = Package(
4646
.package(url: "https://github.com/wordpress-mobile/WordPressKit-iOS", branch: "task/reader-discover"),
4747
.package(url: "https://github.com/zendesk/support_sdk_ios", from: "8.0.3"),
4848
// We can't use wordpress-rs branches nor commits here. Only tags work.
49-
.package(url: "https://github.com/Automattic/wordpress-rs", revision: "alpha-swift-20240813"),
49+
.package(url: "https://github.com/Automattic/wordpress-rs", revision: "alpha-20241116"),
5050
.package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "6cc307e7fc24910697be5f71b7d70f465a9c0f63"),
5151
.package(url: "https://github.com/Automattic/color-studio", branch: "trunk"),
52+
.package(url: "https://github.com/apple/swift-async-algorithms", from: "1.0.0"),
5253
],
5354
targets: XcodeSupport.targets + [
5455
.target(name: "JetpackStatsWidgetsCore"),
@@ -162,6 +163,7 @@ enum XcodeSupport {
162163
.product(name: "ZIPFoundation", package: "ZIPFoundation"),
163164
.product(name: "WordPressAPI", package: "wordpress-rs"),
164165
.product(name: "ColorStudio", package: "color-studio"),
166+
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms"),
165167
]),
166168
.xcodeTarget("XcodeTarget_WordPressTests", dependencies: testDependencies + [
167169
"WordPressShared",

Modules/Sources/WordPressUI/Views/Users/Components/UserListItem.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ struct UserListItem: View {
3232
}
3333
}
3434
}
35-
36-
#Preview {
37-
UserListItem(user: DisplayUser.MockUser, userService: MockUserProvider())
38-
}
35+
//
36+
//#Preview {
37+
// UserListItem(user: DisplayUser.MockUser, userService: MockUserProvider())
38+
//}

Modules/Sources/WordPressUI/Views/Users/UserProvider.swift

+49-60
Original file line numberDiff line numberDiff line change
@@ -2,70 +2,59 @@ import Foundation
22
import Combine
33

44
public protocol UserServiceProtocol: Actor {
5-
var users: [DisplayUser]? { get }
6-
nonisolated var usersUpdates: AsyncStream<[DisplayUser]> { get }
7-
8-
func fetchUsers() async throws -> [DisplayUser]
5+
func fetchPaginatedUsers() -> AsyncThrowingStream<[DisplayUser], Error>
96

107
func isCurrentUserCapableOf(_ capability: String) async throws -> Bool
118

129
func setNewPassword(id: Int32, newPassword: String) async throws
1310

1411
func deleteUser(id: Int32, reassigningPostsTo newUserId: Int32) async throws
1512
}
16-
17-
package actor MockUserProvider: UserServiceProtocol {
18-
19-
enum Scenario {
20-
case infinitLoading
21-
case dummyData
22-
case error
23-
}
24-
25-
var scenario: Scenario
26-
27-
package nonisolated let usersUpdates: AsyncStream<[DisplayUser]>
28-
private let usersUpdatesContinuation: AsyncStream<[DisplayUser]>.Continuation
29-
30-
package private(set) var users: [DisplayUser]? {
31-
didSet {
32-
if let users {
33-
usersUpdatesContinuation.yield(users)
34-
}
35-
}
36-
}
37-
38-
init(scenario: Scenario = .dummyData) {
39-
self.scenario = scenario
40-
(usersUpdates, usersUpdatesContinuation) = AsyncStream<[DisplayUser]>.makeStream()
41-
}
42-
43-
package func fetchUsers() async throws -> [DisplayUser] {
44-
switch scenario {
45-
case .infinitLoading:
46-
// Do nothing
47-
try await Task.sleep(for: .seconds(24 * 60 * 60))
48-
return []
49-
case .dummyData:
50-
let dummyDataUrl = URL(string: "https://my.api.mockaroo.com/users.json?key=067c9730")!
51-
let response = try await URLSession.shared.data(from: dummyDataUrl)
52-
let users = try JSONDecoder().decode([DisplayUser].self, from: response.0)
53-
self.users = users
54-
return users
55-
case .error:
56-
throw URLError(.timedOut)
57-
}
58-
}
59-
60-
package func isCurrentUserCapableOf(_ capability: String) async throws -> Bool {
61-
true
62-
}
63-
64-
package func setNewPassword(id: Int32, newPassword: String) async throws {
65-
// Not used in Preview
66-
}
67-
68-
package func deleteUser(id: Int32, reassigningPostsTo newUserId: Int32) async throws {
69-
// Not used in Preview
70-
}
71-
}
13+
//
14+
//package actor MockUserProvider: UserServiceProtocol {
15+
//
16+
// enum Scenario {
17+
// case infinitLoading
18+
// case dummyData
19+
// case error
20+
// }
21+
//
22+
// var scenario: Scenario
23+
//
24+
// init(scenario: Scenario = .dummyData) {
25+
// self.scenario = scenario
26+
// }
27+
//
28+
// public func fetchUsers() async throws -> any AsyncSequence {
29+
// [DisplayUser]().async
30+
// }
31+
//
32+
//
33+
// package func fetchUsers() async throws -> [DisplayUser] {
34+
// switch scenario {
35+
// case .infinitLoading:
36+
// // Do nothing
37+
// try await Task.sleep(for: .seconds(24 * 60 * 60))
38+
// return []
39+
// case .dummyData:
40+
// let dummyDataUrl = URL(string: "https://my.api.mockaroo.com/users.json?key=067c9730")!
41+
// let response = try await URLSession.shared.data(from: dummyDataUrl)
42+
// let users = try JSONDecoder().decode([DisplayUser].self, from: response.0)
43+
// return users
44+
// case .error:
45+
// throw URLError(.timedOut)
46+
// }
47+
// }
48+
//
49+
// package func isCurrentUserCapableOf(_ capability: String) async throws -> Bool {
50+
// true
51+
// }
52+
//
53+
// package func setNewPassword(id: Int32, newPassword: String) async throws {
54+
// // Not used in Preview
55+
// }
56+
//
57+
// package func deleteUser(id: Int32, reassigningPostsTo newUserId: Int32) async throws {
58+
// // Not used in Preview
59+
// }
60+
//}

Modules/Sources/WordPressUI/Views/Users/ViewModel/UserDeleteViewModel.swift

+5-2
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,11 @@ public class UserDeleteViewModel: ObservableObject {
4949
}
5050

5151
do {
52-
let users = try await userService.fetchUsers()
53-
self.otherUsers = users
52+
let users = await userService.fetchPaginatedUsers()
53+
self.otherUsers = try await users
54+
.reduce(into: [DisplayUser](), { partialResult, users in
55+
partialResult.append(contentsOf: users)
56+
})
5457
.filter { $0.id != self.user.id } // Don't allow re-assigning to yourself
5558
.sorted(using: KeyPathComparator(\.username))
5659
} catch {

Modules/Sources/WordPressUI/Views/Users/ViewModel/UserListViewModel.swift

+29-24
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ class UserListViewModel: ObservableObject {
1212
}
1313

1414
/// The initial set of users fetched by `fetchItems`
15-
private var users: [DisplayUser] = [] {
15+
private var users: [Int32: DisplayUser] = [:] {
1616
didSet {
17-
sortedUsers = self.sortUsers(users)
17+
sortedUsers = self.sortUsers(Array(users.values))
1818
}
1919
}
2020
private var updateUsersTask: Task<Void, Never>?
@@ -36,7 +36,7 @@ class UserListViewModel: ObservableObject {
3636
if searchTerm.trimmingCharacters(in: .whitespacesAndNewlines) == "" {
3737
setSearchResults(sortUsers(users))
3838
} else {
39-
let searchResults = users.search(searchTerm, using: \.searchString)
39+
let searchResults = users.values.search(searchTerm, using: \.searchString)
4040
setSearchResults([Section(role: "Search Results", users: searchResults)])
4141
}
4242
}
@@ -51,41 +51,40 @@ class UserListViewModel: ObservableObject {
5151
}
5252

5353
func onAppear() async {
54-
if updateUsersTask == nil {
55-
updateUsersTask = Task { @MainActor [weak self, usersUpdates = userService.usersUpdates] in
56-
for await users in usersUpdates {
57-
guard let self else { break }
58-
59-
self.users = users
60-
}
61-
}
62-
}
63-
6454
if !initialLoad {
6555
initialLoad = true
6656
await fetchItems()
6757
}
6858
}
6959

7060
private func fetchItems() async {
71-
isLoadingItems = true
72-
defer { isLoadingItems = false }
7361

74-
_ = try? await userService.fetchUsers()
62+
do {
63+
for try await page in await userService.fetchPaginatedUsers() {
64+
for user in page {
65+
self.users[user.id] = user
66+
}
67+
68+
// Show results after the first page has loaded
69+
isLoadingItems = false
70+
}
71+
} catch {
72+
self.error = error
73+
}
7574
}
7675

7776
@Sendable
7877
func refreshItems() async {
79-
_ = try? await userService.fetchUsers()
78+
await fetchItems()
8079
}
8180

82-
func setUsers(_ newValue: [DisplayUser]) {
83-
withAnimation {
84-
self.users = newValue
85-
self.sortedUsers = sortUsers(newValue)
86-
isLoadingItems = false
87-
}
88-
}
81+
// func setUsers(_ newValue: [DisplayUser]) {
82+
// withAnimation {
83+
// self.users = newValue
84+
// self.sortedUsers = sortUsers(newValue)
85+
// isLoadingItems = false
86+
// }
87+
// }
8988

9089
func setSearchResults(_ newValue: [Section]) {
9190
withAnimation {
@@ -98,4 +97,10 @@ class UserListViewModel: ObservableObject {
9897
.map { Section(role: $0.key, users: $0.value.sorted(by: { $0.username < $1.username })) }
9998
.sorted { $0.role < $1.role }
10099
}
100+
101+
private func sortUsers(_ users: [Int32: DisplayUser]) -> [Section] {
102+
Dictionary(grouping: users.values, by: { $0.role })
103+
.map { Section(role: $0.key, users: $0.value.sorted(by: { $0.username < $1.username })) }
104+
.sorted { $0.role < $1.role }
105+
}
101106
}

Modules/Sources/WordPressUI/Views/Users/Views/UserDetailsView.swift

+5-5
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,8 @@ private extension String {
380380
}
381381
}
382382

383-
#Preview {
384-
NavigationStack {
385-
UserDetailsView(user: DisplayUser.MockUser, userService: MockUserProvider())
386-
}
387-
}
383+
//#Preview {
384+
// NavigationStack {
385+
// UserDetailsView(user: DisplayUser.MockUser, userService: MockUserProvider())
386+
// }
387+
//}

Modules/Sources/WordPressUI/Views/Users/Views/UserListView.swift

+18-18
Original file line numberDiff line numberDiff line change
@@ -67,21 +67,21 @@ public struct UserListView: View {
6767
)
6868
}
6969
}
70-
71-
#Preview("Loading") {
72-
NavigationView {
73-
UserListView(userService: MockUserProvider())
74-
}
75-
}
76-
77-
#Preview("Error") {
78-
NavigationView {
79-
UserListView(userService: MockUserProvider(scenario: .error))
80-
}
81-
}
82-
83-
#Preview("List") {
84-
NavigationView {
85-
UserListView(userService: MockUserProvider(scenario: .dummyData))
86-
}
87-
}
70+
//
71+
//#Preview("Loading") {
72+
// NavigationView {
73+
// UserListView(userService: MockUserProvider())
74+
// }
75+
//}
76+
//
77+
//#Preview("Error") {
78+
// NavigationView {
79+
// UserListView(userService: MockUserProvider(scenario: .error))
80+
// }
81+
//}
82+
//
83+
//#Preview("List") {
84+
// NavigationView {
85+
// UserListView(userService: MockUserProvider(scenario: .dummyData))
86+
// }
87+
//}

WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved

+20-2
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,24 @@
313313
"version" : "2.3.1"
314314
}
315315
},
316+
{
317+
"identity" : "swift-async-algorithms",
318+
"kind" : "remoteSourceControl",
319+
"location" : "https://github.com/apple/swift-async-algorithms",
320+
"state" : {
321+
"revision" : "5c8bd186f48c16af0775972700626f0b74588278",
322+
"version" : "1.0.2"
323+
}
324+
},
325+
{
326+
"identity" : "swift-collections",
327+
"kind" : "remoteSourceControl",
328+
"location" : "https://github.com/apple/swift-collections.git",
329+
"state" : {
330+
"revision" : "671108c96644956dddcd89dd59c203dcdb36cec7",
331+
"version" : "1.1.4"
332+
}
333+
},
316334
{
317335
"identity" : "swift-log",
318336
"kind" : "remoteSourceControl",
@@ -372,8 +390,8 @@
372390
"kind" : "remoteSourceControl",
373391
"location" : "https://github.com/Automattic/wordpress-rs",
374392
"state" : {
375-
"branch" : "alpha-swift-20240813",
376-
"revision" : "b51c560d83a61917eb3361441e65779b71fb96ce"
393+
"branch" : "alpha-20241116",
394+
"revision" : "1249ae77fcea2e836b7878a1b1cffdf6c1080256"
377395
}
378396
},
379397
{

WordPress/Classes/Networking/WordPressClient.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ actor WordPressClient {
7878
try await self.api.plugins.create(params: PluginCreateParams(
7979
slug: "InstallJetpack",
8080
status: .active
81-
))
81+
)).data
8282
}
8383
}
8484

WordPress/Classes/Services/ApplicationPasswordService.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import WordPressAPI
1212
}
1313

1414
private func fetchTokens(forUserId userId: Int32) async throws -> [ApplicationPasswordWithEditContext] {
15-
try await apiClient.api.applicationPasswords.listWithEditContext(userId: userId)
15+
try await apiClient.api.applicationPasswords.listWithEditContext(userId: userId).data
1616
}
1717
}
1818

0 commit comments

Comments
 (0)