Skip to content

Commit f786088

Browse files
authored
Доработки (#277)
- Новый пикер фотографий для iOS 16+ - Добавил поддержку диплинков: swparks - Бейдж с количеством непрочитанных сообщений в таббаре - Локализация для текста "Не удалось найти такого пользователя" - При отправке сообщения в чате показываем индикатор загрузки и скрываем клавиатуру - При открытии экрана поиска других пользователей с экрана "Сообщения" убираем лишнюю кнопку "Назад" - Рефактор сетевого слоя: теперь тело создается внутри пакета SWNetwork с уникальным boundary
1 parent 501cec1 commit f786088

File tree

18 files changed

+919
-413
lines changed

18 files changed

+919
-413
lines changed

SwiftUI-WorkoutApp.xcodeproj/project.pbxproj

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
67138D922974851F00BBF450 /* XCUIElement+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67138D912974851F00BBF450 /* XCUIElement+.swift */; };
1414
67138D942974854F00BBF450 /* XCTestCase+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67138D932974854F00BBF450 /* XCTestCase+.swift */; };
1515
6718BCA42AD5327F002846A6 /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6718BCA32AD5327F002846A6 /* SnapshotHelper.swift */; };
16+
671B4AE92D4F623100286996 /* ModernPickedImagesGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671B4AE82D4F623100286996 /* ModernPickedImagesGrid.swift */; };
17+
671B4AEB2D4F683E00286996 /* ImagePickerViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671B4AEA2D4F683E00286996 /* ImagePickerViews.swift */; };
1618
671D7DEC28210D2F0068E728 /* EmptyContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671D7DEB28210D2F0068E728 /* EmptyContentView.swift */; };
1719
67419ACF282E70B9004F5339 /* ParksListScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67419ACE282E70B9004F5339 /* ParksListScreen.swift */; };
1820
6747575628113419002F0A24 /* ChangePasswordScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6747575528113419002F0A24 /* ChangePasswordScreen.swift */; };
@@ -34,6 +36,7 @@
3436
67627750283A3A54009C203F /* JournalsListScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6762774F283A3A54009C203F /* JournalsListScreen.swift */; };
3537
67627755283A4C77009C203F /* JournalEntriesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67627754283A4C77009C203F /* JournalEntriesScreen.swift */; };
3638
6762775B283A87AD009C203F /* JournalCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6762775A283A87AD009C203F /* JournalCell.swift */; };
39+
6764D6382D52009F00699007 /* DialogsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6764D6372D52009F00699007 /* DialogsViewModel.swift */; };
3740
6765B2562D451771006164AB /* UIImage+toMediaFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6765B2552D451771006164AB /* UIImage+toMediaFile.swift */; };
3841
6765B2582D4544C8006164AB /* MainUserProfileScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6765B2572D4544C8006164AB /* MainUserProfileScreen.swift */; };
3942
6765B25B2D455D5C006164AB /* ProfileViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6765B25A2D455D5C006164AB /* ProfileViews.swift */; };
@@ -97,6 +100,8 @@
97100
67138D912974851F00BBF450 /* XCUIElement+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+.swift"; sourceTree = "<group>"; };
98101
67138D932974854F00BBF450 /* XCTestCase+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+.swift"; sourceTree = "<group>"; };
99102
6718BCA32AD5327F002846A6 /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SnapshotHelper.swift; path = fastlane/SnapshotHelper.swift; sourceTree = SOURCE_ROOT; };
103+
671B4AE82D4F623100286996 /* ModernPickedImagesGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModernPickedImagesGrid.swift; sourceTree = "<group>"; };
104+
671B4AEA2D4F683E00286996 /* ImagePickerViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePickerViews.swift; sourceTree = "<group>"; };
100105
671D7DEB28210D2F0068E728 /* EmptyContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyContentView.swift; sourceTree = "<group>"; };
101106
67419ACE282E70B9004F5339 /* ParksListScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParksListScreen.swift; sourceTree = "<group>"; };
102107
6747575528113419002F0A24 /* ChangePasswordScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangePasswordScreen.swift; sourceTree = "<group>"; };
@@ -119,6 +124,7 @@
119124
6762774F283A3A54009C203F /* JournalsListScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JournalsListScreen.swift; sourceTree = "<group>"; };
120125
67627754283A4C77009C203F /* JournalEntriesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JournalEntriesScreen.swift; sourceTree = "<group>"; };
121126
6762775A283A87AD009C203F /* JournalCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JournalCell.swift; sourceTree = "<group>"; };
127+
6764D6372D52009F00699007 /* DialogsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DialogsViewModel.swift; sourceTree = "<group>"; };
122128
6765B2552D451771006164AB /* UIImage+toMediaFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+toMediaFile.swift"; sourceTree = "<group>"; };
123129
6765B2572D4544C8006164AB /* MainUserProfileScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainUserProfileScreen.swift; sourceTree = "<group>"; };
124130
6765B25A2D455D5C006164AB /* ProfileViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViews.swift; sourceTree = "<group>"; };
@@ -223,6 +229,7 @@
223229
67419AD5282E8E7C004F5339 /* Messages */ = {
224230
isa = PBXGroup;
225231
children = (
232+
6764D6372D52009F00699007 /* DialogsViewModel.swift */,
226233
67D916802838E2460098D3CB /* DialogsListScreen.swift */,
227234
67D916852838F0DD0098D3CB /* DialogScreen.swift */,
228235
);
@@ -265,6 +272,8 @@
265272
children = (
266273
67515698283FEC3100501346 /* PickedImagesGrid.swift */,
267274
67A079F12A758E7D005EAF70 /* PickedPhotoView.swift */,
275+
671B4AE82D4F623100286996 /* ModernPickedImagesGrid.swift */,
276+
671B4AEA2D4F683E00286996 /* ImagePickerViews.swift */,
268277
);
269278
path = ImagePicker;
270279
sourceTree = "<group>";
@@ -609,9 +618,11 @@
609618
67419ACF282E70B9004F5339 /* ParksListScreen.swift in Sources */,
610619
67EA685C2A71A99700697C88 /* PhotoDetailScreen.swift in Sources */,
611620
674DF0402B11257D00828016 /* NavigationLink+.swift in Sources */,
621+
671B4AEB2D4F683E00286996 /* ImagePickerViews.swift in Sources */,
612622
67BD2D012AF7D21B00F44064 /* ParksManager.swift in Sources */,
613623
6705E7EE283B703400DABCC8 /* JournalSettingsScreen.swift in Sources */,
614624
6798AA40280AEDC900DB76F1 /* RootScreen.swift in Sources */,
625+
671B4AE92D4F623100286996 /* ModernPickedImagesGrid.swift in Sources */,
615626
675EC64F2814126800C2E229 /* TextEntryScreen.swift in Sources */,
616627
674D0623282A9896007E75C6 /* SearchUsersScreen.swift in Sources */,
617628
67A4710D2AEED8F8004D341D /* PastEventStorage.swift in Sources */,
@@ -638,6 +649,7 @@
638649
6798AA84280C0F7D00DB76F1 /* EditProfileScreen.swift in Sources */,
639650
6798AA73280B43FE00DB76F1 /* LoginScreen.swift in Sources */,
640651
67D9169628396C1E0098D3CB /* SendMessageScreen.swift in Sources */,
652+
6764D6382D52009F00699007 /* DialogsViewModel.swift in Sources */,
641653
6747575928128603002F0A24 /* ParkDetailScreen.swift in Sources */,
642654
675EC6572815433600C2E229 /* UsersListScreen.swift in Sources */,
643655
675EC65F2815532800C2E229 /* EventFormScreen.swift in Sources */,
@@ -844,7 +856,7 @@
844856
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
845857
CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES;
846858
CODE_SIGN_STYLE = Automatic;
847-
CURRENT_PROJECT_VERSION = 6;
859+
CURRENT_PROJECT_VERSION = 9;
848860
DEVELOPMENT_ASSET_PATHS = "SwiftUI-WorkoutApp/Preview\\ Content/PreviewContent.swift SwiftUI-WorkoutApp/Preview\\ Content";
849861
DEVELOPMENT_TEAM = CR68PP2Z3F;
850862
ENABLE_PREVIEWS = YES;
@@ -855,6 +867,7 @@
855867
INFOPLIST_KEY_CFBundleDisplayName = "SW Площадки";
856868
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.healthcare-fitness";
857869
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Для отображения спортивных площадок поблизости";
870+
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Для выбора фото профиля требуется доступ к галерее";
858871
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
859872
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
860873
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -894,7 +907,7 @@
894907
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
895908
CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES;
896909
CODE_SIGN_STYLE = Automatic;
897-
CURRENT_PROJECT_VERSION = 6;
910+
CURRENT_PROJECT_VERSION = 9;
898911
DEVELOPMENT_ASSET_PATHS = "SwiftUI-WorkoutApp/Preview\\ Content/PreviewContent.swift SwiftUI-WorkoutApp/Preview\\ Content";
899912
DEVELOPMENT_TEAM = CR68PP2Z3F;
900913
ENABLE_PREVIEWS = YES;
@@ -905,6 +918,7 @@
905918
INFOPLIST_KEY_CFBundleDisplayName = "SW Площадки";
906919
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.healthcare-fitness";
907920
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Для отображения спортивных площадок поблизости";
921+
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Для выбора фото профиля требуется доступ к галерее";
908922
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
909923
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
910924
INFOPLIST_KEY_UILaunchScreen_Generation = YES;

SwiftUI-WorkoutApp/Libraries/SWNetwork/Sources/SWNetwork/BodyMaker.swift

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import Foundation
22

33
/// Делает `body` для запроса
44
public enum BodyMaker {
5-
public struct Parameter<T: Hashable & RawRepresentable> where T.RawValue == String {
5+
struct Parameter {
66
let key: String
77
let value: String
88

9-
public init(from element: Dictionary<T, String>.Element) {
10-
self.key = element.key.rawValue
9+
init(from element: Dictionary<String, String>.Element) {
10+
self.key = element.key
1111
self.value = element.value
1212
}
1313

@@ -18,8 +18,8 @@ public enum BodyMaker {
1818
}
1919

2020
/// Делает `body` из словаря
21-
public static func makeBody(
22-
with parameters: [Parameter<some Hashable & RawRepresentable>]
21+
static func makeBody(
22+
with parameters: [Parameter]
2323
) -> Data? {
2424
parameters.isEmpty
2525
? nil
@@ -30,11 +30,11 @@ public enum BodyMaker {
3030
}
3131

3232
/// Делает `body` из словаря и медиа-файлов
33-
public static func makeBodyWithMultipartForm(
34-
with parameters: [Parameter<some Hashable & RawRepresentable>],
35-
and media: [MediaFile]?
33+
static func makeBodyWithMultipartForm(
34+
parameters: [Parameter],
35+
media: [MediaFile]?,
36+
boundary: String
3637
) -> Data? {
37-
let boundary = "FFF"
3838
let lineBreak = "\r\n"
3939
var body = Data()
4040
if !parameters.isEmpty {
@@ -56,13 +56,25 @@ public enum BodyMaker {
5656
if !body.isEmpty {
5757
body.append("--\(boundary)--\(lineBreak)")
5858
return body
59-
} else {
60-
return nil
59+
}
60+
return nil
61+
}
62+
}
63+
64+
public extension BodyMaker {
65+
/// Модель для последующего создания тела запроса
66+
struct Parts {
67+
let parameters: [Parameter]
68+
let mediaFiles: [MediaFile]?
69+
70+
public init(_ parameters: [String: String], _ mediaFiles: [MediaFile]?) {
71+
self.parameters = parameters.map(Parameter.init)
72+
self.mediaFiles = mediaFiles
6173
}
6274
}
6375

6476
/// Медиа-файл для отправки на сервер
65-
public struct MediaFile: Codable, Equatable, Sendable {
77+
struct MediaFile: Codable, Equatable, Sendable {
6678
public let key: String
6779
public let filename: String
6880
public let data: Data

SwiftUI-WorkoutApp/Libraries/SWNetwork/Sources/SWNetwork/RequestComponents.swift

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ public struct RequestComponents {
55
let queryItems: [URLQueryItem]
66
let httpMethod: HTTPMethod
77
let hasMultipartFormData: Bool
8-
let body: Data?
8+
let bodyParts: BodyMaker.Parts?
9+
let boundary: String
910
let token: String?
1011

1112
/// Инициализатор
@@ -14,21 +15,24 @@ public struct RequestComponents {
1415
/// - queryItems: Параметры `query`, по умолчанию отсутствуют
1516
/// - httpMethod: Метод запроса
1617
/// - hasMultipartFormData: Есть ли в запросе файлы для отправки (в нашем случае картинки), по умолчанию `false`
17-
/// - body: Тело запроса, по умолчанию `nil`
18+
/// - bodyParts: Данные для тела запроса, по умолчанию `nil`
19+
/// - boundary: `Boundary` для `body`, по умолчанию `UUID().uuidString`
1820
/// - token: Токен для авторизации, по умолчанию `nil`
1921
public init(
2022
path: String,
2123
queryItems: [URLQueryItem] = [],
2224
httpMethod: HTTPMethod,
2325
hasMultipartFormData: Bool = false,
24-
body: Data? = nil,
26+
bodyParts: BodyMaker.Parts? = nil,
27+
boundary: String = UUID().uuidString,
2528
token: String? = nil
2629
) {
2730
self.path = path
2831
self.queryItems = queryItems
2932
self.httpMethod = httpMethod
3033
self.hasMultipartFormData = hasMultipartFormData
31-
self.body = body
34+
self.bodyParts = bodyParts
35+
self.boundary = boundary
3236
self.token = token
3337
}
3438

@@ -50,19 +54,37 @@ extension RequestComponents {
5054
guard let url else { return nil }
5155
var request = URLRequest(url: url)
5256
request.httpMethod = httpMethod.rawValue
53-
request.httpBody = body
57+
5458
var allHeaders = [HTTPHeaderField]()
55-
// TODO: генерировать boundary в одном месте (вместо FFF)
56-
if let body {
57-
allHeaders.append(.init(key: "Content-Length", value: "\(body.count)"))
58-
}
59-
if hasMultipartFormData {
60-
allHeaders.append(.init(key: "Content-Type", value: "multipart/form-data; boundary=FFF"))
59+
var httpBodyData: Data?
60+
61+
if let bodyParts {
62+
let parameters = bodyParts.parameters
63+
if hasMultipartFormData {
64+
httpBodyData = BodyMaker.makeBodyWithMultipartForm(
65+
parameters: parameters,
66+
media: bodyParts.mediaFiles,
67+
boundary: boundary
68+
)
69+
allHeaders.append(.init(
70+
key: "Content-Type",
71+
value: "multipart/form-data; boundary=\(boundary)"
72+
))
73+
} else {
74+
httpBodyData = BodyMaker.makeBody(with: parameters)
75+
}
76+
if let httpBodyData {
77+
allHeaders.append(.init(key: "Content-Length", value: "\(httpBodyData.count)"))
78+
}
6179
}
80+
6281
if let token, !token.isEmpty {
6382
allHeaders.append(.init(key: "Authorization", value: "Basic \(token)"))
6483
}
65-
request.allHTTPHeaderFields = Dictionary(uniqueKeysWithValues: allHeaders.map { ($0.key, $0.value) })
84+
request.allHTTPHeaderFields = Dictionary(
85+
uniqueKeysWithValues: allHeaders.map { ($0.key, $0.value) }
86+
)
87+
request.httpBody = httpBodyData
6688
return request
6789
}
6890
}

0 commit comments

Comments
 (0)