Skip to content

Commit 5bb6252

Browse files
authored
Merge pull request #48 from SubvertDev/feature/edit-profile
Profile Edit
2 parents 3bdcde6 + 9f6cad3 commit 5bb6252

File tree

18 files changed

+1049
-49
lines changed

18 files changed

+1049
-49
lines changed

Modules/Sources/APIClient/APIClient.swift

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,11 @@ public struct APIClient: Sendable {
4343

4444
// User
4545
public var getUser: @Sendable (_ userId: Int, _ policy: CachePolicy) async throws -> AsyncThrowingStream<User, any Error>
46+
public var editUserProfile: @Sendable (_ request: UserProfileEditRequest) async throws -> Bool
4647
public var getReputationVotes: @Sendable (_ data: ReputationVotesRequest) async throws -> ReputationVotes
4748
public var changeReputation: @Sendable (_ data: ReputationChangeRequest) async throws -> ReputationChangeResponseType
49+
public var updateUserAvatar: @Sendable (_ userId: Int, _ image: Data) async throws -> UserAvatarResponseType
50+
public var updateUserDevice: @Sendable (_ userId: Int, _ action: UserDeviceAction, _ fullTag: String, _ isPrimary: Bool) async throws -> Bool
4851

4952
// Bookmarks
5053
public var getBookmarksList: @Sendable () async throws -> [Bookmark]
@@ -204,7 +207,22 @@ extension APIClient: DependencyKey {
204207
policy: policy
205208
)
206209
},
207-
210+
editUserProfile: { request in
211+
let command = MemberCommand.profile(
212+
data: MemberProfileRequest(
213+
memberId: request.userId,
214+
city: request.city,
215+
gender: request.transferGender,
216+
status: request.status,
217+
about: request.about,
218+
signature: request.signature,
219+
birthday: request.birthdayDate
220+
)
221+
)
222+
let response = try await api.send(command)
223+
let status = Int(response.getResponseStatus())!
224+
return status == 0
225+
},
208226
getReputationVotes: { request in
209227
let command = MemberCommand.reputationVotes(data: MemberReputationVotesRequest(
210228
memberId: request.userId,
@@ -227,8 +245,30 @@ extension APIClient: DependencyKey {
227245
let status = Int(response.getResponseStatus())!
228246
return ReputationChangeResponseType(rawValue: status)
229247
},
248+
updateUserAvatar: { userId, image in
249+
let command = MemberCommand.avatar(memberId: userId, avatar: image)
250+
let response = try await api.send(command)
251+
return try await parser.parseAvatarUrl(response: response)
252+
},
253+
updateUserDevice: { userId, action, fullTag, isPrimary in
254+
let action: MemberDeviceRequest.Action = switch action {
255+
case .add: .add
256+
case .modify: .modify
257+
case .remove: .remove
258+
}
259+
let command = MemberCommand.device(data: MemberDeviceRequest(
260+
memberId: userId,
261+
action: action,
262+
fullTag: fullTag,
263+
primary: isPrimary
264+
))
265+
let response = try await api.send(command)
266+
let status = Int(response.getResponseStatus())!
267+
return status == 0
268+
},
230269

231270
// MARK: - Bookmarks
271+
232272
getBookmarksList: {
233273
let response = try await api.send(MemberCommand.Bookmarks.list)
234274
return try await parser.parseBookmarksList(response)
@@ -527,12 +567,21 @@ extension APIClient: DependencyKey {
527567
getUser: { _, _ in
528568
AsyncThrowingStream { $0.yield(.mock) }
529569
},
570+
editUserProfile: { _ in
571+
return true
572+
},
530573
getReputationVotes: { _ in
531574
return .mock
532575
},
533576
changeReputation: { _ in
534577
return .success
535578
},
579+
updateUserAvatar: { _, _ in
580+
return .success(URL(string: "https://github.com/SubvertDev/ForPDA/raw/main/Images/logo.png")!)
581+
},
582+
updateUserDevice: { _, _, _, _ in
583+
return true
584+
},
536585
getBookmarksList: {
537586
return [.mockArticle, .mockForum, .mockUser]
538587
},
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//
2+
// UserProfileEditRequest.swift
3+
// ForPDA
4+
//
5+
// Created by Xialtal on 2.09.25.
6+
//
7+
8+
import Foundation
9+
import Models
10+
import PDAPI
11+
12+
public struct UserProfileEditRequest: Sendable {
13+
public let userId: Int
14+
public let city: String
15+
public let about: String
16+
public let gender: User.Gender
17+
public let status: String
18+
public let signature: String
19+
public let birthdayDate: Date?
20+
21+
public init(
22+
userId: Int,
23+
city: String,
24+
about: String,
25+
gender: User.Gender,
26+
status: String,
27+
signature: String,
28+
birthdayDate: Date?
29+
) {
30+
self.userId = userId
31+
self.city = city
32+
self.about = about
33+
self.gender = gender
34+
self.status = status
35+
self.signature = signature
36+
self.birthdayDate = birthdayDate
37+
}
38+
}
39+
40+
extension UserProfileEditRequest {
41+
var transferGender: MemberProfileRequest.Gender {
42+
switch gender {
43+
case .male: return .male
44+
case .female: return .female
45+
case .unknown: return .unknown
46+
}
47+
}
48+
}

Modules/Sources/AnalyticsClient/Events/ProfileEvent.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Foundation
99

1010
public enum ProfileEvent: Event {
1111
case qmsTapped
12+
case editTapped
1213
case settingsTapped
1314
case logoutTapped
1415
case historyTapped

Modules/Sources/Models/Profile/User.swift

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,23 @@
66
//
77

88
import Foundation
9+
import SwiftUI
910

1011
public struct User: Sendable, Hashable, Codable {
1112
public let id: Int
1213
public let nickname: String
13-
public let imageUrl: URL
14-
public let group: Group
15-
public let status: String?
16-
public let signature: String?
17-
public let aboutMe: String?
14+
public var imageUrl: URL
15+
public var group: Group
16+
public var status: String?
17+
public var signature: String?
18+
public var aboutMe: String?
1819
public let registrationDate: Date
1920
public let lastSeenDate: Date
2021
public let birthdate: String?
21-
public let gender: Gender?
22+
public var gender: Gender?
2223
public let userTime: Int?
23-
public let city: String?
24-
public let devDBdevices: [Device]
24+
public var city: String?
25+
public var devDBdevices: [Device]
2526
public let karma: Double
2627
public let posts: Int
2728
public let comments: Int
@@ -33,6 +34,16 @@ public struct User: Sendable, Hashable, Codable {
3334
public let email: String?
3435
public let achievements: [Achievement]
3536

37+
public var birthdayDate: Date? {
38+
if let birthdate {
39+
let dateFormatter = DateFormatter()
40+
dateFormatter.dateFormat = "dd.MM.yyyy"
41+
return dateFormatter.date(from: birthdate)
42+
} else {
43+
return nil
44+
}
45+
}
46+
3647
public var userTimeFormatted: String? {
3748
if let userTime {
3849
let currentDate = Date()
@@ -195,29 +206,20 @@ public extension User {
195206

196207
// MARK: Gender
197208

198-
enum Gender: Int, Codable, Hashable, Sendable {
209+
enum Gender: Int, CaseIterable, Codable, Hashable, Sendable, Identifiable {
199210
case unknown = 0
200211
case male
201212
case female
202213

203-
public var title: String {
204-
switch self {
205-
case .unknown:
206-
"Неизвестно"
207-
case .male:
208-
"Мужчина"
209-
case .female:
210-
"Женщина"
211-
}
212-
}
214+
public var id: Self { self }
213215
}
214216

215217
// MARK: Device
216218

217219
struct Device: Codable, Hashable, Sendable, Identifiable {
218220
public let id: String
219221
public let name: String
220-
public let main: Bool
222+
public var main: Bool
221223

222224
public init(id: String, name: String, main: Bool) {
223225
self.id = id
@@ -274,7 +276,18 @@ public extension User {
274276
gender: .male,
275277
userTime: 10800,
276278
city: "Moscow",
277-
devDBdevices: [],
279+
devDBdevices: [
280+
.init(
281+
id: "ip16pro",
282+
name: "iPhone 16 Pro",
283+
main: true
284+
),
285+
.init(
286+
id: "ip13",
287+
name: "iPhone 13",
288+
main: false
289+
)
290+
],
278291
karma: 1500,
279292
posts: 23,
280293
comments: 173,
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// UserAvatarResponseType.swift
3+
// ForPDA
4+
//
5+
// Created by Xialtal on 29.08.25.
6+
//
7+
8+
import Foundation
9+
10+
public enum UserAvatarResponseType: Sendable {
11+
case error
12+
case success(URL?)
13+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//
2+
// UserDeviceAction.swift
3+
// ForPDA
4+
//
5+
// Created by Xialtal on 29.08.25.
6+
//
7+
8+
public enum UserDeviceAction: Sendable {
9+
case add
10+
case modify
11+
case remove
12+
}

Modules/Sources/ParsingClient/Parsers/ProfileParser.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,26 @@ public struct ProfileParser {
8383
}
8484
}
8585

86+
public static func parseAvatarUrl(from string: String) throws -> UserAvatarResponseType {
87+
if let data = string.data(using: .utf8) {
88+
do {
89+
guard let array = try JSONSerialization.jsonObject(with: data, options: []) as? [Any] else { throw ParsingError.failedToCastDataToAny }
90+
let status = array[1] as! Int
91+
92+
if status == 0 {
93+
let stringUrl = if array.count > 2 { array[2] as! String } else { "" }
94+
return .success(URL(string: stringUrl))
95+
} else {
96+
return .error
97+
}
98+
} catch {
99+
throw ParsingError.failedToSerializeData(error)
100+
}
101+
} else {
102+
throw ParsingError.failedToCreateDataFromString
103+
}
104+
}
105+
86106
private static func parseUserDevDBDevices(_ array: [[Any]]) -> [User.Device] {
87107
return array.map { device in
88108
return User.Device(

Modules/Sources/ParsingClient/ParsingClient.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public struct ParsingClient: Sendable {
2525
// User
2626
public var parseUser: @Sendable (_ response: String) async throws -> User
2727
public var parseReputationVotes: @Sendable ( _ response: String) async throws -> ReputationVotes
28+
public var parseAvatarUrl: @Sendable (_ response: String) async throws -> UserAvatarResponseType
2829

2930
// Bookmarks
3031
public var parseBookmarksList: @Sendable (_ response: String) async throws -> [Bookmark]
@@ -80,6 +81,9 @@ extension ParsingClient: DependencyKey {
8081
parseReputationVotes: { response in
8182
return try ReputationParser.parse(from: response)
8283
},
84+
parseAvatarUrl: { response in
85+
return try ProfileParser.parseAvatarUrl(from: response)
86+
},
8387
parseBookmarksList: { response in
8488
return try BookmarksParser.parse(from: response)
8589
},

Modules/Sources/ProfileFeature/Analytics/ProfileFeature+Analytics.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ extension ProfileFeature {
2929
case .view(.qmsButtonTapped):
3030
analyticsClient.log(ProfileEvent.qmsTapped)
3131

32+
case .view(.editButtonTapped):
33+
analyticsClient.log(ProfileEvent.editTapped)
34+
3235
case .view(.settingsButtonTapped):
3336
analyticsClient.log(ProfileEvent.settingsTapped)
3437

0 commit comments

Comments
 (0)