Skip to content

Commit 9c10eb8

Browse files
authored
Рефактор экрана со списком пользователей (#280)
* Рефактор экрана со списком пользователей Вынес в отдельные вьюхи списки пользователей, чтобы не было путаницы с логикой для разных режимов: - список друзей и заявок для основного пользователя - список участников мероприятия/тренирующихся на площадке - список друзей для другого пользователя/для начала общения в чате Поправил отступ в нижней части экрана для списков пользователей * Доработал комментарии к ендпоинтам
1 parent da33f9b commit 9c10eb8

File tree

13 files changed

+398
-265
lines changed

13 files changed

+398
-265
lines changed

SwiftUI-WorkoutApp.xcodeproj/project.pbxproj

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
67551C362AEC338600084A35 /* SWAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67551C352AEC338600084A35 /* SWAddress.swift */; };
3131
6758463F2965B7F2000BA5E0 /* PDFViewRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6758463E2965B7F2000BA5E0 /* PDFViewRepresentable.swift */; };
3232
675EC64F2814126800C2E229 /* TextEntryScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 675EC64E2814126800C2E229 /* TextEntryScreen.swift */; };
33-
675EC6572815433600C2E229 /* UsersListScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 675EC6562815433600C2E229 /* UsersListScreen.swift */; };
33+
675EC6572815433600C2E229 /* FriendsListScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 675EC6562815433600C2E229 /* FriendsListScreen.swift */; };
3434
675EC65F2815532800C2E229 /* EventFormScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 675EC65E2815532800C2E229 /* EventFormScreen.swift */; };
3535
675EC6632815AA4A00C2E229 /* MapSnapshotView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 675EC6622815AA4A00C2E229 /* MapSnapshotView.swift */; };
3636
675FB8DB2ADDB87200C9671F /* ParksMapScreen+LocationSettingReminderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 675FB8DA2ADDB87200C9671F /* ParksMapScreen+LocationSettingReminderView.swift */; };
@@ -48,6 +48,8 @@
4848
67795FB22D1C05D90087132F /* SWModels in Frameworks */ = {isa = PBXBuildFile; productRef = 67795FB12D1C05D90087132F /* SWModels */; };
4949
67795FB42D1C05D90087132F /* SWNetworkClient in Frameworks */ = {isa = PBXBuildFile; productRef = 67795FB32D1C05D90087132F /* SWNetworkClient */; };
5050
67891E38283E947B00B10802 /* ParkFormScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67891E37283E947B00B10802 /* ParkFormScreen.swift */; };
51+
679781892D5732D0004D0629 /* ParticipantsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 679781882D5732D0004D0629 /* ParticipantsScreen.swift */; };
52+
6797818B2D574C0E004D0629 /* MainUserFriendsListScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6797818A2D574C0E004D0629 /* MainUserFriendsListScreen.swift */; };
5153
6798AA3E280AEDC900DB76F1 /* SwiftUI_WorkoutAppApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6798AA3D280AEDC900DB76F1 /* SwiftUI_WorkoutAppApp.swift */; };
5254
6798AA40280AEDC900DB76F1 /* RootScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6798AA3F280AEDC900DB76F1 /* RootScreen.swift */; };
5355
6798AA42280AEDCA00DB76F1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6798AA41280AEDCA00DB76F1 /* Assets.xcassets */; };
@@ -119,7 +121,7 @@
119121
6758463E2965B7F2000BA5E0 /* PDFViewRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PDFViewRepresentable.swift; sourceTree = "<group>"; };
120122
675EC64E2814126800C2E229 /* TextEntryScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEntryScreen.swift; sourceTree = "<group>"; };
121123
675EC65528153B8200C2E229 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
122-
675EC6562815433600C2E229 /* UsersListScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsersListScreen.swift; sourceTree = "<group>"; };
124+
675EC6562815433600C2E229 /* FriendsListScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsListScreen.swift; sourceTree = "<group>"; };
123125
675EC65E2815532800C2E229 /* EventFormScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventFormScreen.swift; sourceTree = "<group>"; };
124126
675EC6622815AA4A00C2E229 /* MapSnapshotView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapSnapshotView.swift; sourceTree = "<group>"; };
125127
675FB8DA2ADDB87200C9671F /* ParksMapScreen+LocationSettingReminderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParksMapScreen+LocationSettingReminderView.swift"; sourceTree = "<group>"; };
@@ -137,6 +139,8 @@
137139
677717162B36D87200ED90BD /* SwiftUI-WorkoutApp.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = "SwiftUI-WorkoutApp.xctestplan"; sourceTree = "<group>"; };
138140
67891E37283E947B00B10802 /* ParkFormScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParkFormScreen.swift; sourceTree = "<group>"; };
139141
678CE36A2D1C510900F060C6 /* SWNetwork */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = SWNetwork; sourceTree = "<group>"; };
142+
679781882D5732D0004D0629 /* ParticipantsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantsScreen.swift; sourceTree = "<group>"; };
143+
6797818A2D574C0E004D0629 /* MainUserFriendsListScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainUserFriendsListScreen.swift; sourceTree = "<group>"; };
140144
6798AA3A280AEDC900DB76F1 /* WorkoutApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WorkoutApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
141145
6798AA3D280AEDC900DB76F1 /* SwiftUI_WorkoutAppApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUI_WorkoutAppApp.swift; sourceTree = "<group>"; };
142146
6798AA3F280AEDC900DB76F1 /* RootScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootScreen.swift; sourceTree = "<group>"; };
@@ -411,6 +415,7 @@
411415
674D0622282A9896007E75C6 /* SearchUsersScreen.swift */,
412416
6765B2572D4544C8006164AB /* MainUserProfileScreen.swift */,
413417
6740003F2D55E97900E5CB06 /* BlackListScreen.swift */,
418+
6797818A2D574C0E004D0629 /* MainUserFriendsListScreen.swift */,
414419
);
415420
path = Profile;
416421
sourceTree = "<group>";
@@ -426,7 +431,8 @@
426431
67C87FD4284388EF00D6377D /* PhotoSection */,
427432
67D9169528396C1E0098D3CB /* SendMessageScreen.swift */,
428433
675EC64E2814126800C2E229 /* TextEntryScreen.swift */,
429-
675EC6562815433600C2E229 /* UsersListScreen.swift */,
434+
675EC6562815433600C2E229 /* FriendsListScreen.swift */,
435+
679781882D5732D0004D0629 /* ParticipantsScreen.swift */,
430436
);
431437
path = Common;
432438
sourceTree = "<group>";
@@ -624,6 +630,7 @@
624630
671B4AEB2D4F683E00286996 /* ImagePickerViews.swift in Sources */,
625631
67BD2D012AF7D21B00F44064 /* ParksManager.swift in Sources */,
626632
6705E7EE283B703400DABCC8 /* JournalSettingsScreen.swift in Sources */,
633+
6797818B2D574C0E004D0629 /* MainUserFriendsListScreen.swift in Sources */,
627634
6798AA40280AEDC900DB76F1 /* RootScreen.swift in Sources */,
628635
671B4AE92D4F623100286996 /* ModernPickedImagesGrid.swift in Sources */,
629636
675EC64F2814126800C2E229 /* TextEntryScreen.swift in Sources */,
@@ -637,6 +644,7 @@
637644
676D5A612D48BF0700EE5E9E /* String+localized.swift in Sources */,
638645
6765B2562D451771006164AB /* UIImage+toMediaFile.swift in Sources */,
639646
674DF03E2B11254D00828016 /* Binding+.swift in Sources */,
647+
679781892D5732D0004D0629 /* ParticipantsScreen.swift in Sources */,
640648
6766A036284603CA0033F1E8 /* TabViewModel.swift in Sources */,
641649
67D916862838F0DD0098D3CB /* DialogScreen.swift in Sources */,
642650
67627750283A3A54009C203F /* JournalsListScreen.swift in Sources */,
@@ -655,7 +663,7 @@
655663
67D9169628396C1E0098D3CB /* SendMessageScreen.swift in Sources */,
656664
6764D6382D52009F00699007 /* DialogsViewModel.swift in Sources */,
657665
6747575928128603002F0A24 /* ParkDetailScreen.swift in Sources */,
658-
675EC6572815433600C2E229 /* UsersListScreen.swift in Sources */,
666+
675EC6572815433600C2E229 /* FriendsListScreen.swift in Sources */,
659667
675EC65F2815532800C2E229 /* EventFormScreen.swift in Sources */,
660668
);
661669
runOnlyForDeploymentPostprocessing = 0;
@@ -860,7 +868,7 @@
860868
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
861869
CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES;
862870
CODE_SIGN_STYLE = Automatic;
863-
CURRENT_PROJECT_VERSION = 12;
871+
CURRENT_PROJECT_VERSION = 13;
864872
DEVELOPMENT_ASSET_PATHS = "SwiftUI-WorkoutApp/Preview\\ Content/PreviewContent.swift SwiftUI-WorkoutApp/Preview\\ Content";
865873
DEVELOPMENT_TEAM = CR68PP2Z3F;
866874
ENABLE_PREVIEWS = YES;
@@ -911,7 +919,7 @@
911919
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
912920
CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES;
913921
CODE_SIGN_STYLE = Automatic;
914-
CURRENT_PROJECT_VERSION = 12;
922+
CURRENT_PROJECT_VERSION = 13;
915923
DEVELOPMENT_ASSET_PATHS = "SwiftUI-WorkoutApp/Preview\\ Content/PreviewContent.swift SwiftUI-WorkoutApp/Preview\\ Content";
916924
DEVELOPMENT_TEAM = CR68PP2Z3F;
917925
ENABLE_PREVIEWS = YES;

SwiftUI-WorkoutApp/Libraries/SWNetworkClient/Sources/SWNetworkClient/Endpoint.swift

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ enum Endpoint {
2828
case deleteUser
2929

3030
// MARK: Получить профиль пользователя
31-
/// **GET** ${API}/users/<id>
31+
/// **GET** ${API}/users/<user_id>
3232
/// `id` - идентификатор пользователя, чей профиль нужно получить
3333
case getUser(id: Int)
3434

3535
// MARK: Получить список друзей пользователя
36-
/// **GET** ${API}/users/<id>/friends
36+
/// **GET** ${API}/users/<user_id>/friends
3737
/// `id` - идентификатор пользователя, чьих друзей нужно получить
3838
case getFriendsForUser(id: Int)
3939

@@ -42,7 +42,7 @@ enum Endpoint {
4242
case getFriendRequests
4343

4444
// MARK: Принять заявку на добавление в друзья
45-
/// **POST** ${API}/friends/<id>/accept
45+
/// **POST** ${API}/friends/<user_id>/accept
4646
case acceptFriendRequest(from: Int)
4747

4848
// MARK: Отклонить заявку на добавление в друзья
@@ -70,7 +70,7 @@ enum Endpoint {
7070
case deleteFromBlacklist(_ userID: Int)
7171

7272
// MARK: Найти пользователей по логину
73-
/// **GET** ${API}/users/search?name=<user>
73+
/// **GET** ${API}/users/search?name=<user_login>
7474
case findUsers(with: String)
7575

7676
// MARK: Получить список стран/городов
@@ -88,23 +88,23 @@ enum Endpoint {
8888
case getUpdatedParks(from: String)
8989

9090
// MARK: Получить выбранную площадку:
91-
/// **GET** ${API}/areas/<id>
91+
/// **GET** ${API}/areas/<area_id>
9292
///
9393
/// - Работает и с аутентификацией, и без
9494
/// - Для авторизованного пользователя нужно делать запрос с токеном,
9595
/// чтобы получить корректные данные (тренируется ли на площадке)
9696
case getPark(id: Int)
9797

98-
// MARK: Добавить новую спортплощадку
98+
// MARK: Добавить новую площадку
9999
/// **POST** ${API}/areas
100100
case createPark(form: ParkForm)
101101

102-
// MARK: Изменить выбранную спортплощадку
103-
/// **POST** ${API}/areas/<id>
102+
// MARK: Изменить выбранную площадку
103+
/// **POST** ${API}/areas/<area_id>
104104
case editPark(id: Int, form: ParkForm)
105105

106106
// MARK: Удалить площадку
107-
/// **DELETE** ${API}/areas/<id>
107+
/// **DELETE** ${API}/areas/<area_id>
108108
case deletePark(_ parkID: Int)
109109

110110
// MARK: Добавить комментарий для площадки
@@ -156,7 +156,7 @@ enum Endpoint {
156156
case createEvent(form: EventForm)
157157

158158
// MARK: Изменить существующее мероприятие
159-
/// **POST** ${API}/trainings/<id>
159+
/// **POST** ${API}/trainings/<event_id>
160160
case editEvent(id: Int, form: EventForm)
161161

162162
// MARK: Сообщить, что пользователь пойдет на мероприятие
@@ -176,7 +176,7 @@ enum Endpoint {
176176
case deleteEventComment(_ eventID: Int, commentID: Int)
177177

178178
// MARK: Изменить свой комментарий для мероприятия
179-
/// **POST** ${API}/trainings/<training_id>/comments/<comment_id>
179+
/// **POST** ${API}/trainings/<event_id>/comments/<comment_id>
180180
case editEventComment(eventID: Int, commentID: Int, newComment: String)
181181

182182
// MARK: Удалить мероприятие
@@ -228,11 +228,11 @@ enum Endpoint {
228228
case saveJournalEntry(userID: Int, journalID: Int, message: String)
229229

230230
// MARK: Изменить запись в дневнике пользователя
231-
/// **PUT** ${API}/users/<user_id>/journals/<journal_id>/messages/<id>
231+
/// **PUT** ${API}/users/<user_id>/journals/<journal_id>/messages/<entry_id>
232232
case editEntry(userID: Int, journalID: Int, entryID: Int, newEntryText: String)
233233

234234
// MARK: Удалить запись в дневнике пользователя
235-
/// **DELETE** ${API}/users/<user_id>/journals/<journal_id>/messages/<id>
235+
/// **DELETE** ${API}/users/<user_id>/journals/<journal_id>/messages/<entry_id>
236236
case deleteEntry(userID: Int, journalID: Int, entryID: Int)
237237

238238
// MARK: Удалить дневник пользователя
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import SWDesignSystem
2+
import SwiftUI
3+
import SWModels
4+
import SWNetworkClient
5+
import SWUtils
6+
7+
/// Экран со списком друзей
8+
struct FriendsListScreen: View {
9+
@Environment(\.dismiss) private var dismiss
10+
@EnvironmentObject private var defaults: DefaultsService
11+
@State private var friends = [UserResponse]()
12+
@State private var isLoading = false
13+
@State private var messagingModel = MessagingModel()
14+
@State private var sendMessageTask: Task<Void, Never>?
15+
private var client: SWClient { SWClient(with: defaults) }
16+
let mode: Mode
17+
18+
var body: some View {
19+
ScrollView {
20+
LazyVStack(spacing: 12) {
21+
ForEach(friends) { user in
22+
listItem(for: user)
23+
.disabled(user.id == defaults.mainUserInfo?.id)
24+
}
25+
}
26+
.animation(.default, value: friends)
27+
.padding()
28+
}
29+
.sheet(
30+
item: $messagingModel.recipient,
31+
onDismiss: { endMessaging() },
32+
content: messageSheet
33+
)
34+
.loadingOverlay(if: isLoading)
35+
.background(Color.swBackground)
36+
.task { await askForUsers() }
37+
.refreshable { await askForUsers(refresh: true) }
38+
.navigationTitle("Друзья")
39+
.navigationBarTitleDisplayMode(.inline)
40+
}
41+
}
42+
43+
extension FriendsListScreen {
44+
enum Mode {
45+
/// Друзья пользователя с указанным `id`
46+
///
47+
/// При нажатии на друга откроется его профиль
48+
case user(id: Int)
49+
/// Друзья пользователя с указанным `id` для чата
50+
///
51+
/// При нажатии на друга откроется окно отправки сообщения
52+
case chat(userID: Int)
53+
}
54+
}
55+
56+
private extension FriendsListScreen {
57+
@ViewBuilder
58+
func listItem(for model: UserResponse) -> some View {
59+
switch mode {
60+
case .chat:
61+
Button {
62+
messagingModel.recipient = model
63+
} label: {
64+
userRowView(with: model)
65+
}
66+
case .user:
67+
NavigationLink {
68+
UserDetailsScreen(for: model)
69+
.navigationBarTitleDisplayMode(.inline)
70+
} label: {
71+
userRowView(with: model)
72+
}
73+
}
74+
}
75+
76+
func userRowView(with model: UserResponse) -> some View {
77+
UserRowView(
78+
mode: .regular(
79+
.init(
80+
imageURL: model.avatarURL,
81+
name: model.userName ?? "",
82+
address: SWAddress(model.countryID, model.cityID)?.address ?? ""
83+
)
84+
)
85+
)
86+
}
87+
88+
func messageSheet(for recipient: UserResponse) -> some View {
89+
SendMessageScreen(
90+
header: .init(recipient.messageFor),
91+
text: $messagingModel.message,
92+
isLoading: messagingModel.isLoading,
93+
isSendButtonDisabled: !messagingModel.canSendMessage,
94+
sendAction: { sendMessage(to: recipient.id) }
95+
)
96+
}
97+
98+
func sendMessage(to userID: Int) {
99+
messagingModel.isLoading = true
100+
sendMessageTask = Task {
101+
do {
102+
let isSuccess = try await client.sendMessage(messagingModel.message, to: userID)
103+
endMessaging(isSuccess: isSuccess)
104+
} catch {
105+
SWAlert.shared.presentDefaultUIKit(error)
106+
}
107+
messagingModel.isLoading = false
108+
}
109+
}
110+
111+
func endMessaging(isSuccess: Bool = true) {
112+
if isSuccess {
113+
messagingModel.message = ""
114+
messagingModel.recipient = nil
115+
}
116+
}
117+
118+
func askForUsers(refresh: Bool = false) async {
119+
guard !isLoading else { return }
120+
do {
121+
switch mode {
122+
case let .user(id), let .chat(id):
123+
if !friends.isEmpty, !refresh { return }
124+
if !refresh { isLoading = true }
125+
friends = try await client.getFriendsForUser(id: id)
126+
}
127+
} catch {
128+
SWAlert.shared.presentDefaultUIKit(error)
129+
}
130+
isLoading = false
131+
}
132+
}
133+
134+
#if DEBUG
135+
#Preview {
136+
FriendsListScreen(mode: .user(id: .previewUserID))
137+
.environmentObject(DefaultsService())
138+
}
139+
#endif

0 commit comments

Comments
 (0)