diff --git a/SwiftUI-WorkoutApp.xcodeproj/project.pbxproj b/SwiftUI-WorkoutApp.xcodeproj/project.pbxproj index 727a27a8..79f3f65c 100644 --- a/SwiftUI-WorkoutApp.xcodeproj/project.pbxproj +++ b/SwiftUI-WorkoutApp.xcodeproj/project.pbxproj @@ -256,7 +256,6 @@ children = ( 674E704D2B24D382008AE9D0 /* LoggerScreen.swift */, 670CA19D280E8F09003914A3 /* SettingsScreen.swift */, - 6798AA72280B43FE00DB76F1 /* LoginScreen.swift */, ); path = Settings; sourceTree = ""; @@ -424,6 +423,7 @@ isa = PBXGroup; children = ( 67BAF3F72836245100DB40D9 /* CommentsView.swift */, + 6798AA72280B43FE00DB76F1 /* LoginScreen.swift */, 671D7DEB28210D2F0068E728 /* EmptyContentView.swift */, 67515697283FEC0B00501346 /* ImagePicker */, 6798AA65280B232F00DB76F1 /* IncognitoProfileView.swift */, @@ -868,7 +868,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 13; + CURRENT_PROJECT_VERSION = 14; DEVELOPMENT_ASSET_PATHS = "SwiftUI-WorkoutApp/Preview\\ Content/PreviewContent.swift SwiftUI-WorkoutApp/Preview\\ Content"; DEVELOPMENT_TEAM = CR68PP2Z3F; ENABLE_PREVIEWS = YES; @@ -919,7 +919,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 13; + CURRENT_PROJECT_VERSION = 14; DEVELOPMENT_ASSET_PATHS = "SwiftUI-WorkoutApp/Preview\\ Content/PreviewContent.swift SwiftUI-WorkoutApp/Preview\\ Content"; DEVELOPMENT_TEAM = CR68PP2Z3F; ENABLE_PREVIEWS = YES; diff --git a/SwiftUI-WorkoutApp/Libraries/SWNetworkClient/Sources/SWNetworkClient/SWClient.swift b/SwiftUI-WorkoutApp/Libraries/SWNetworkClient/Sources/SWNetworkClient/SWClient.swift index 1556b5b9..ea2d36f2 100644 --- a/SwiftUI-WorkoutApp/Libraries/SWNetworkClient/Sources/SWNetworkClient/SWClient.swift +++ b/SwiftUI-WorkoutApp/Libraries/SWNetworkClient/Sources/SWNetworkClient/SWClient.swift @@ -39,21 +39,23 @@ public struct SWClient: Sendable { return result.userID } - /// Запрашивает обновления списка друзей, заявок в друзья, черного списка + /// Запрашивает обновления для пользователя и его списков: друзья, заявки, черный список /// /// - Вызывается при авторизации и при `scenePhase = active` - /// - Список чатов не обновляет + /// - Список чатов не обновляет (для этого `DialogsViewModel`) /// - Parameter userID: Идентификатор основного пользователя /// - Returns: Список друзей, заявок в друзья и черный список - public func getSocialUpdates(userID: Int) async -> ( + public func getSocialUpdates(userID: Int) async throws -> ( + user: UserResponse, friends: [UserResponse], friendRequests: [UserResponse], blacklist: [UserResponse] - )? { + ) { + async let user = getUserByID(userID) async let friendsForUser = getFriendsForUser(id: userID) async let friendRequests = getFriendRequests() async let blacklist = getBlacklist() - return try? await (friendsForUser, friendRequests, blacklist) + return try await (user, friendsForUser, friendRequests, blacklist) } /// Запрашивает данные пользователя по `id` diff --git a/SwiftUI-WorkoutApp/Screens/Settings/LoginScreen.swift b/SwiftUI-WorkoutApp/Screens/Common/LoginScreen.swift similarity index 91% rename from SwiftUI-WorkoutApp/Screens/Settings/LoginScreen.swift rename to SwiftUI-WorkoutApp/Screens/Common/LoginScreen.swift index 8d8a2659..6d377e4b 100644 --- a/SwiftUI-WorkoutApp/Screens/Settings/LoginScreen.swift +++ b/SwiftUI-WorkoutApp/Screens/Common/LoginScreen.swift @@ -7,6 +7,7 @@ import SWUtils /// Экран для авторизации / восстановления пароля struct LoginScreen: View { @EnvironmentObject private var defaults: DefaultsService + @Environment(\.dismiss) private var dismiss @Environment(\.isNetworkConnected) private var isNetworkConnected @State private var isLoading = false @State private var credentials = LoginCredentials() @@ -100,18 +101,21 @@ private extension LoginScreen { func loginAction() { guard !isLoading else { return } focus = nil - isLoading.toggle() + isLoading = true loginTask = Task { do { let token = AuthData(login: credentials.login, password: credentials.password).token let userId = try await client.logIn(with: token) try defaults.saveAuthData(login: credentials.login, password: credentials.password) - let userInfo = try await client.getUserByID(userId) - try defaults.saveUserInfo(userInfo) + let result = try await client.getSocialUpdates(userID: userId) + try defaults.saveFriendsIds(result.friends.map(\.id)) + try defaults.saveFriendRequests(result.friendRequests) + try defaults.saveBlacklist(result.blacklist) + try defaults.saveUserInfo(result.user) } catch { loginErrorMessage = error.localizedDescription } - isLoading.toggle() + isLoading = false } } diff --git a/SwiftUI-WorkoutApp/Screens/Messages/DialogsListScreen.swift b/SwiftUI-WorkoutApp/Screens/Messages/DialogsListScreen.swift index 1a0a788a..99b952df 100644 --- a/SwiftUI-WorkoutApp/Screens/Messages/DialogsListScreen.swift +++ b/SwiftUI-WorkoutApp/Screens/Messages/DialogsListScreen.swift @@ -6,13 +6,13 @@ import SWUtils /// Экран со списком диалогов struct DialogsListScreen: View { + @Environment(\.scenePhase) private var scenePhase @Environment(\.isNetworkConnected) private var isNetworkConnected @EnvironmentObject private var defaults: DefaultsService - @EnvironmentObject private var viewModel: DialogsViewModel + @StateObject private var viewModel = DialogsViewModel() @State private var selectedDialog: DialogResponse? @State private var indexToDelete: Int? @State private var openFriendList = false - @State private var showDeleteConfirmation = false @State private var refreshTask: Task? @State private var deleteDialogTask: Task? private var client: SWClient { SWClient(with: defaults) } @@ -34,6 +34,11 @@ struct DialogsListScreen: View { } .navigationViewStyle(.stack) .onChange(of: defaults.isAuthorized, perform: viewModel.clearDialogsOnLogout) + .onChange(of: scenePhase) { phase in + if case .active = phase { + refreshTask = Task { await askForDialogs() } + } + } .task(id: defaults.isAuthorized) { await askForDialogs() } } } @@ -46,7 +51,7 @@ private extension DialogsListScreen { .background(Color.swBackground) .confirmationDialog( .init(Constants.Alert.deleteDialog), - isPresented: $showDeleteConfirmation, + isPresented: $indexToDelete.mappedToBool(), titleVisibility: .visible ) { deleteDialogButton } .toolbar { @@ -105,7 +110,7 @@ private extension DialogsListScreen { .listRowBackground(Color.swBackground) .listRowSeparator(.hidden) } - .onDelete { initiateDeletion(at: $0) } + .onDelete { indexToDelete = $0.first } } .listStyle(.plain) .opacity(viewModel.hasDialogs ? 1 : 0) @@ -168,11 +173,6 @@ private extension DialogsListScreen { } } - func initiateDeletion(at indexSet: IndexSet) { - indexToDelete = indexSet.first - showDeleteConfirmation.toggle() - } - func deleteAction(at index: Int?) { deleteDialogTask = Task { do { diff --git a/SwiftUI-WorkoutApp/Screens/Messages/DialogsViewModel.swift b/SwiftUI-WorkoutApp/Screens/Messages/DialogsViewModel.swift index f64ef1af..e7f2d109 100644 --- a/SwiftUI-WorkoutApp/Screens/Messages/DialogsViewModel.swift +++ b/SwiftUI-WorkoutApp/Screens/Messages/DialogsViewModel.swift @@ -15,7 +15,8 @@ final class DialogsViewModel: ObservableObject { defaults: DefaultsService ) async throws { guard defaults.isAuthorized else { return } - if isLoading || (!dialogs.isEmpty && !refresh) { return } + guard !isLoading else { return } + guard dialogs.isEmpty || refresh else { return } if !refresh || dialogs.isEmpty { isLoading = true } dialogs = try await SWClient(with: defaults).getDialogs() let unreadMessagesCount = dialogs.map(\.unreadMessagesCount).reduce(0, +) diff --git a/SwiftUI-WorkoutApp/Screens/Profile/BlackListScreen.swift b/SwiftUI-WorkoutApp/Screens/Profile/BlackListScreen.swift index 76e16ce8..241b763a 100644 --- a/SwiftUI-WorkoutApp/Screens/Profile/BlackListScreen.swift +++ b/SwiftUI-WorkoutApp/Screens/Profile/BlackListScreen.swift @@ -33,10 +33,7 @@ struct BlackListScreen: View { .frame(maxWidth: .infinity) .confirmationDialog( .init(BlacklistOption.remove.dialogTitle), - isPresented: .init( - get: { userToDelete != nil }, - set: { if !$0 { userToDelete = nil } } - ), + isPresented: $userToDelete.mappedToBool(), titleVisibility: .visible ) { Button( diff --git a/SwiftUI-WorkoutApp/Screens/Profile/MainUserProfileScreen.swift b/SwiftUI-WorkoutApp/Screens/Profile/MainUserProfileScreen.swift index a9554b72..918c81a1 100644 --- a/SwiftUI-WorkoutApp/Screens/Profile/MainUserProfileScreen.swift +++ b/SwiftUI-WorkoutApp/Screens/Profile/MainUserProfileScreen.swift @@ -6,8 +6,10 @@ import SWUtils /// Экран с профилем главного пользователя struct MainUserProfileScreen: View { + @Environment(\.scenePhase) private var scenePhase @Environment(\.isNetworkConnected) private var isNetworkConnected @EnvironmentObject private var defaults: DefaultsService + @State private var refreshTask: Task? @State private var isLoading = false @State private var showLogoutDialog = false @State private var showSearchUsersScreen = false @@ -30,6 +32,13 @@ struct MainUserProfileScreen: View { } .navigationViewStyle(.stack) .task { await askForUserInfo() } + .onChange(of: scenePhase) { phase in + if case .active = phase { + refreshTask = Task { + await askForUserInfo(refresh: true) + } + } + } } } @@ -144,30 +153,22 @@ private extension MainUserProfileScreen { } func askForUserInfo(refresh: Bool = false) async { - guard defaults.isAuthorized else { return } + guard let userId = defaults.mainUserInfo?.id else { return } guard !isLoading else { return } if !refresh { isLoading = true } if refresh || defaults.needUpdateUser { - await makeUserInfo() + do { + let result = try await client.getSocialUpdates(userID: userId) + try defaults.saveFriendsIds(result.friends.map(\.id)) + try defaults.saveFriendRequests(result.friendRequests) + try defaults.saveBlacklist(result.blacklist) + try defaults.saveUserInfo(result.user) + } catch { + SWAlert.shared.presentDefaultUIKit(error) + } } isLoading = false } - - func makeUserInfo() async { - guard let mainUserId = defaults.mainUserInfo?.id else { return } - do { - // TODO: вынести обновление заявок/черного списка в отдельную логику - async let getUserInfo = client.getUserByID(mainUserId) - async let getFriendRequests = client.getFriendRequests() - async let getBlacklist = client.getBlacklist() - let (userInfo, friendRequests, blacklist) = try await (getUserInfo, getFriendRequests, getBlacklist) - try defaults.saveUserInfo(userInfo) - try defaults.saveFriendRequests(friendRequests) - try defaults.saveBlacklist(blacklist) - } catch { - SWAlert.shared.presentDefaultUIKit(error) - } - } } #if DEBUG diff --git a/SwiftUI-WorkoutApp/SwiftUI_WorkoutAppApp.swift b/SwiftUI-WorkoutApp/SwiftUI_WorkoutAppApp.swift index 2bf1a004..9b144575 100644 --- a/SwiftUI-WorkoutApp/SwiftUI_WorkoutAppApp.swift +++ b/SwiftUI-WorkoutApp/SwiftUI_WorkoutAppApp.swift @@ -11,10 +11,7 @@ struct SwiftUI_WorkoutAppApp: App { @StateObject private var defaults = DefaultsService() @StateObject private var network = NetworkStatus() @StateObject private var parksManager = ParksManager() - @StateObject private var dialogsViewModel = DialogsViewModel() @State private var countriesUpdateTask: Task? - @State private var socialUpdateTask: Task? - @State private var dialogsUpdateTask: Task? private let countriesStorage = SWAddress() private var client: SWClient { SWClient(with: defaults) } private var colorScheme: ColorScheme? { @@ -39,7 +36,6 @@ struct SwiftUI_WorkoutAppApp: App { .environmentObject(tabViewModel) .environmentObject(defaults) .environmentObject(parksManager) - .environmentObject(dialogsViewModel) .preferredColorScheme(colorScheme) .environment(\.isNetworkConnected, network.isConnected) .environment(\.userFlags, defaults.userFlags) @@ -48,9 +44,8 @@ struct SwiftUI_WorkoutAppApp: App { switch phase { case .active: updateCountriesIfNeeded() - updateSocialInfoIfNeeded() default: - [socialUpdateTask, countriesUpdateTask].forEach { $0?.cancel() } + countriesUpdateTask?.cancel() defaults.setUserNeedUpdate(true) } } @@ -65,21 +60,6 @@ struct SwiftUI_WorkoutAppApp: App { } } } - - private func updateSocialInfoIfNeeded() { - guard let mainUserId = defaults.mainUserInfo?.id else { return } - socialUpdateTask = Task { - if let result = await client.getSocialUpdates(userID: mainUserId) { - try? defaults.saveFriendsIds(result.friends.map(\.id)) - try? defaults.saveFriendRequests(result.friendRequests) - try? defaults.saveBlacklist(result.blacklist) - defaults.setUserNeedUpdate(false) - } - } - dialogsUpdateTask = Task { - try? await dialogsViewModel.askForDialogs(refresh: true, defaults: defaults) - } - } } private extension SwiftUI_WorkoutAppApp {