From 3cfaf3406357a82868151bd51dd3770c5a0ab25f Mon Sep 17 00:00:00 2001 From: Oleg991 Date: Tue, 4 Feb 2025 11:17:55 +0300 Subject: [PATCH 1/3] MVP --- SwiftUI-WorkoutApp.xcodeproj/project.pbxproj | 4 ++ .../Screens/Root/RootScreen.swift | 22 +++++++-- .../Services/UnreadCountService.swift | 11 +++++ .../SwiftUI_WorkoutAppApp.swift | 47 ++++++++++++------- 4 files changed, 63 insertions(+), 21 deletions(-) create mode 100644 SwiftUI-WorkoutApp/Services/UnreadCountService.swift diff --git a/SwiftUI-WorkoutApp.xcodeproj/project.pbxproj b/SwiftUI-WorkoutApp.xcodeproj/project.pbxproj index 8e953256..246de035 100644 --- a/SwiftUI-WorkoutApp.xcodeproj/project.pbxproj +++ b/SwiftUI-WorkoutApp.xcodeproj/project.pbxproj @@ -36,6 +36,7 @@ 67627750283A3A54009C203F /* JournalsListScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6762774F283A3A54009C203F /* JournalsListScreen.swift */; }; 67627755283A4C77009C203F /* JournalEntriesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67627754283A4C77009C203F /* JournalEntriesScreen.swift */; }; 6762775B283A87AD009C203F /* JournalCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6762775A283A87AD009C203F /* JournalCell.swift */; }; + 6764D6382D52009F00699007 /* UnreadCountService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6764D6372D52009F00699007 /* UnreadCountService.swift */; }; 6765B2562D451771006164AB /* UIImage+toMediaFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6765B2552D451771006164AB /* UIImage+toMediaFile.swift */; }; 6765B2582D4544C8006164AB /* MainUserProfileScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6765B2572D4544C8006164AB /* MainUserProfileScreen.swift */; }; 6765B25B2D455D5C006164AB /* ProfileViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6765B25A2D455D5C006164AB /* ProfileViews.swift */; }; @@ -123,6 +124,7 @@ 6762774F283A3A54009C203F /* JournalsListScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JournalsListScreen.swift; sourceTree = ""; }; 67627754283A4C77009C203F /* JournalEntriesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JournalEntriesScreen.swift; sourceTree = ""; }; 6762775A283A87AD009C203F /* JournalCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JournalCell.swift; sourceTree = ""; }; + 6764D6372D52009F00699007 /* UnreadCountService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnreadCountService.swift; sourceTree = ""; }; 6765B2552D451771006164AB /* UIImage+toMediaFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+toMediaFile.swift"; sourceTree = ""; }; 6765B2572D4544C8006164AB /* MainUserProfileScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainUserProfileScreen.swift; sourceTree = ""; }; 6765B25A2D455D5C006164AB /* ProfileViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViews.swift; sourceTree = ""; }; @@ -391,6 +393,7 @@ 67551C352AEC338600084A35 /* SWAddress.swift */, 67A4710C2AEED8F8004D341D /* PastEventStorage.swift */, 67BD2D002AF7D21B00F44064 /* ParksManager.swift */, + 6764D6372D52009F00699007 /* UnreadCountService.swift */, ); path = Services; sourceTree = ""; @@ -646,6 +649,7 @@ 6798AA84280C0F7D00DB76F1 /* EditProfileScreen.swift in Sources */, 6798AA73280B43FE00DB76F1 /* LoginScreen.swift in Sources */, 67D9169628396C1E0098D3CB /* SendMessageScreen.swift in Sources */, + 6764D6382D52009F00699007 /* UnreadCountService.swift in Sources */, 6747575928128603002F0A24 /* ParkDetailScreen.swift in Sources */, 675EC6572815433600C2E229 /* UsersListScreen.swift in Sources */, 675EC65F2815532800C2E229 /* EventFormScreen.swift in Sources */, diff --git a/SwiftUI-WorkoutApp/Screens/Root/RootScreen.swift b/SwiftUI-WorkoutApp/Screens/Root/RootScreen.swift index c520de9c..1d14b93c 100644 --- a/SwiftUI-WorkoutApp/Screens/Root/RootScreen.swift +++ b/SwiftUI-WorkoutApp/Screens/Root/RootScreen.swift @@ -4,6 +4,7 @@ import SwiftUI struct RootScreen: View { @Environment(\.userFlags) private var userFlags @Binding var selectedTab: TabViewModel.Tab + let unreadCount: Int var body: some View { TabView(selection: $selectedTab) { @@ -11,6 +12,7 @@ struct RootScreen: View { tab.screen .tabItem { tab.tabItemLabel } .tag(tab) + .badge(tab == .messages ? unreadCount : 0) } } .navigationViewStyle(.stack) @@ -18,9 +20,21 @@ struct RootScreen: View { } #if DEBUG -#Preview { - RootScreen(selectedTab: .constant(.map)) - .environmentObject(ParksManager()) - .environmentObject(DefaultsService()) +#Preview("Есть бейдж для чатов") { + RootScreen( + selectedTab: .constant(.map), + unreadCount: 1 + ) + .environmentObject(ParksManager()) + .environmentObject(DefaultsService()) +} + +#Preview("Нет бейджа") { + RootScreen( + selectedTab: .constant(.map), + unreadCount: 0 + ) + .environmentObject(ParksManager()) + .environmentObject(DefaultsService()) } #endif diff --git a/SwiftUI-WorkoutApp/Services/UnreadCountService.swift b/SwiftUI-WorkoutApp/Services/UnreadCountService.swift new file mode 100644 index 00000000..6d18fc32 --- /dev/null +++ b/SwiftUI-WorkoutApp/Services/UnreadCountService.swift @@ -0,0 +1,11 @@ +import Foundation +import SWNetworkClient + +struct UnreadCountService { + let client: SWClient + + func getUnreadCount() async -> Int? { + guard let dialogs = try? await client.getDialogs() else { return nil } + return dialogs.map(\.unreadMessagesCount).reduce(0, +) + } +} diff --git a/SwiftUI-WorkoutApp/SwiftUI_WorkoutAppApp.swift b/SwiftUI-WorkoutApp/SwiftUI_WorkoutAppApp.swift index c260d98d..64e1b894 100644 --- a/SwiftUI-WorkoutApp/SwiftUI_WorkoutAppApp.swift +++ b/SwiftUI-WorkoutApp/SwiftUI_WorkoutAppApp.swift @@ -30,28 +30,23 @@ struct SwiftUI_WorkoutAppApp: App { var body: some Scene { WindowGroup { - RootScreen(selectedTab: $tabViewModel.selectedTab) - .environmentObject(tabViewModel) - .environmentObject(network) - .environmentObject(defaults) - .environmentObject(parksManager) - .preferredColorScheme(colorScheme) - .environment(\.isNetworkConnected, network.isConnected) - .environment(\.userFlags, defaults.userFlags) + RootScreen( + selectedTab: $tabViewModel.selectedTab, + unreadCount: defaults.unreadMessagesCount + ) + .environmentObject(tabViewModel) + .environmentObject(network) + .environmentObject(defaults) + .environmentObject(parksManager) + .preferredColorScheme(colorScheme) + .environment(\.isNetworkConnected, network.isConnected) + .environment(\.userFlags, defaults.userFlags) } .onChange(of: scenePhase) { phase in switch phase { case .active: updateCountriesIfNeeded() - 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) - } - } + updateSocialInfoIfNeeded() default: [socialUpdateTask, countriesUpdateTask].forEach { $0?.cancel() } defaults.setUserNeedUpdate(true) @@ -68,6 +63,24 @@ struct SwiftUI_WorkoutAppApp: App { } } } + + private func updateSocialInfoIfNeeded() { + guard let mainUserId = defaults.mainUserInfo?.id else { return } + socialUpdateTask = Task { + async let socialUpdatesTask = client.getSocialUpdates(userID: mainUserId) + async let unreadCountTask = UnreadCountService(client: client).getUnreadCount() + let (socialUpdates, unreadCount) = await (socialUpdatesTask, unreadCountTask) + if let socialUpdates { + try? defaults.saveFriendsIds(socialUpdates.friends.map(\.id)) + try? defaults.saveFriendRequests(socialUpdates.friendRequests) + try? defaults.saveBlacklist(socialUpdates.blacklist) + defaults.setUserNeedUpdate(false) + } + if let unreadCount { + defaults.saveUnreadMessagesCount(unreadCount) + } + } + } } private extension SwiftUI_WorkoutAppApp { From c510c33bd091efe12f3f2892e100ba5ae03bc71c Mon Sep 17 00:00:00 2001 From: Oleg991 Date: Tue, 4 Feb 2025 19:45:24 +0300 Subject: [PATCH 2/3] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B2=D1=8C=D1=8E=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D1=8C=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=D0=B0=20=D1=81=20?= =?UTF-8?q?=D0=B4=D0=B8=D0=B0=D0=BB=D0=BE=D0=B3=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SwiftUI-WorkoutApp.xcodeproj/project.pbxproj | 8 +-- .../Screens/Messages/DialogsListScreen.swift | 72 +++++-------------- .../Screens/Messages/DialogsViewModel.swift | 57 +++++++++++++++ .../Services/UnreadCountService.swift | 11 --- 4 files changed, 79 insertions(+), 69 deletions(-) create mode 100644 SwiftUI-WorkoutApp/Screens/Messages/DialogsViewModel.swift delete mode 100644 SwiftUI-WorkoutApp/Services/UnreadCountService.swift diff --git a/SwiftUI-WorkoutApp.xcodeproj/project.pbxproj b/SwiftUI-WorkoutApp.xcodeproj/project.pbxproj index 246de035..c225fc04 100644 --- a/SwiftUI-WorkoutApp.xcodeproj/project.pbxproj +++ b/SwiftUI-WorkoutApp.xcodeproj/project.pbxproj @@ -36,7 +36,7 @@ 67627750283A3A54009C203F /* JournalsListScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6762774F283A3A54009C203F /* JournalsListScreen.swift */; }; 67627755283A4C77009C203F /* JournalEntriesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67627754283A4C77009C203F /* JournalEntriesScreen.swift */; }; 6762775B283A87AD009C203F /* JournalCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6762775A283A87AD009C203F /* JournalCell.swift */; }; - 6764D6382D52009F00699007 /* UnreadCountService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6764D6372D52009F00699007 /* UnreadCountService.swift */; }; + 6764D6382D52009F00699007 /* DialogsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6764D6372D52009F00699007 /* DialogsViewModel.swift */; }; 6765B2562D451771006164AB /* UIImage+toMediaFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6765B2552D451771006164AB /* UIImage+toMediaFile.swift */; }; 6765B2582D4544C8006164AB /* MainUserProfileScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6765B2572D4544C8006164AB /* MainUserProfileScreen.swift */; }; 6765B25B2D455D5C006164AB /* ProfileViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6765B25A2D455D5C006164AB /* ProfileViews.swift */; }; @@ -124,7 +124,7 @@ 6762774F283A3A54009C203F /* JournalsListScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JournalsListScreen.swift; sourceTree = ""; }; 67627754283A4C77009C203F /* JournalEntriesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JournalEntriesScreen.swift; sourceTree = ""; }; 6762775A283A87AD009C203F /* JournalCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JournalCell.swift; sourceTree = ""; }; - 6764D6372D52009F00699007 /* UnreadCountService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnreadCountService.swift; sourceTree = ""; }; + 6764D6372D52009F00699007 /* DialogsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DialogsViewModel.swift; sourceTree = ""; }; 6765B2552D451771006164AB /* UIImage+toMediaFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+toMediaFile.swift"; sourceTree = ""; }; 6765B2572D4544C8006164AB /* MainUserProfileScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainUserProfileScreen.swift; sourceTree = ""; }; 6765B25A2D455D5C006164AB /* ProfileViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViews.swift; sourceTree = ""; }; @@ -229,6 +229,7 @@ 67419AD5282E8E7C004F5339 /* Messages */ = { isa = PBXGroup; children = ( + 6764D6372D52009F00699007 /* DialogsViewModel.swift */, 67D916802838E2460098D3CB /* DialogsListScreen.swift */, 67D916852838F0DD0098D3CB /* DialogScreen.swift */, ); @@ -393,7 +394,6 @@ 67551C352AEC338600084A35 /* SWAddress.swift */, 67A4710C2AEED8F8004D341D /* PastEventStorage.swift */, 67BD2D002AF7D21B00F44064 /* ParksManager.swift */, - 6764D6372D52009F00699007 /* UnreadCountService.swift */, ); path = Services; sourceTree = ""; @@ -649,7 +649,7 @@ 6798AA84280C0F7D00DB76F1 /* EditProfileScreen.swift in Sources */, 6798AA73280B43FE00DB76F1 /* LoginScreen.swift in Sources */, 67D9169628396C1E0098D3CB /* SendMessageScreen.swift in Sources */, - 6764D6382D52009F00699007 /* UnreadCountService.swift in Sources */, + 6764D6382D52009F00699007 /* DialogsViewModel.swift in Sources */, 6747575928128603002F0A24 /* ParkDetailScreen.swift in Sources */, 675EC6572815433600C2E229 /* UsersListScreen.swift in Sources */, 675EC65F2815532800C2E229 /* EventFormScreen.swift in Sources */, diff --git a/SwiftUI-WorkoutApp/Screens/Messages/DialogsListScreen.swift b/SwiftUI-WorkoutApp/Screens/Messages/DialogsListScreen.swift index 3d2bebd0..59f4658a 100644 --- a/SwiftUI-WorkoutApp/Screens/Messages/DialogsListScreen.swift +++ b/SwiftUI-WorkoutApp/Screens/Messages/DialogsListScreen.swift @@ -8,9 +8,8 @@ import SWUtils struct DialogsListScreen: View { @Environment(\.isNetworkConnected) private var isNetworkConnected @EnvironmentObject private var defaults: DefaultsService - @State private var dialogs = [DialogResponse]() + @EnvironmentObject private var viewModel: DialogsViewModel @State private var selectedDialog: DialogResponse? - @State private var isLoading = false @State private var indexToDelete: Int? @State private var openFriendList = false @State private var showDeleteConfirmation = false @@ -42,7 +41,7 @@ private extension DialogsListScreen { var authorizedContentView: some View { dialogList .overlay { emptyContentView } - .loadingOverlay(if: isLoading) + .loadingOverlay(if: viewModel.isLoading) .background(Color.swBackground) .confirmationDialog( .init(Constants.Alert.deleteDialog), @@ -58,9 +57,6 @@ private extension DialogsListScreen { friendListButton } } - .onDisappear { - [refreshTask, deleteDialogTask].forEach { $0?.cancel() } - } } var refreshButton: some View { @@ -71,8 +67,8 @@ private extension DialogsListScreen { } label: { Icons.Regular.refresh.view } - .opacity(showEmptyView || !DeviceOSVersionChecker.iOS16Available ? 1 : 0) - .disabled(isLoading) + .opacity(viewModel.showRefreshButton ? 1 : 0) + .disabled(viewModel.isLoading) } var friendListButton: some View { @@ -86,20 +82,16 @@ private extension DialogsListScreen { Icons.Regular.plus.view .symbolVariant(.circle) } - .opacity(hasFriends || !dialogs.isEmpty ? 1 : 0) + .opacity(hasFriends || viewModel.hasDialogs ? 1 : 0) .disabled(!isNetworkConnected) } var emptyContentView: some View { EmptyContentView( mode: .dialogs, - action: emptyViewAction + action: { openFriendList.toggle() } ) - .opacity(showEmptyView ? 1 : 0) - } - - var showEmptyView: Bool { - dialogs.isEmpty && !isLoading + .opacity(viewModel.showEmptyView ? 1 : 0) } @ViewBuilder @@ -107,7 +99,7 @@ private extension DialogsListScreen { ZStack { Color.swBackground List { - ForEach(dialogs) { model in + ForEach(viewModel.dialogs) { model in dialogListItem(model) .listRowInsets(.init(top: 12, leading: 16, bottom: 12, trailing: 16)) .listRowBackground(Color.swBackground) @@ -116,9 +108,9 @@ private extension DialogsListScreen { .onDelete { initiateDeletion(at: $0) } } .listStyle(.plain) - .opacity(dialogs.isEmpty ? 0 : 1) + .opacity(viewModel.hasDialogs ? 1 : 0) } - .animation(.default, value: dialogs.count) + .animation(.default, value: viewModel.dialogs.count) .background( NavigationLink( destination: lazyDestination, @@ -130,7 +122,12 @@ private extension DialogsListScreen { @ViewBuilder var lazyDestination: some View { if let selectedDialog { - DialogScreen(dialog: selectedDialog) { markAsRead($0) } + DialogScreen( + dialog: selectedDialog, + markedAsReadClbk: { dialog in + viewModel.markAsRead(dialog, defaults: defaults) + } + ) } } @@ -162,39 +159,12 @@ private extension DialogsListScreen { defaults.hasFriends } - func emptyViewAction() { - openFriendList.toggle() - } - - func markAsRead(_ dialog: DialogResponse) { - dialogs = dialogs.map { item in - if item.id == dialog.id { - var updatedDialog = dialog - updatedDialog.unreadMessagesCount = 0 - return updatedDialog - } else { - return item - } - } - guard dialog.unreadMessagesCount > 0, - defaults.unreadMessagesCount >= dialog.unreadMessagesCount - else { return } - let newValue = defaults.unreadMessagesCount - dialog.unreadMessagesCount - defaults.saveUnreadMessagesCount(newValue) - } - func askForDialogs(refresh: Bool = false) async { - guard defaults.isAuthorized else { return } - if isLoading || (!dialogs.isEmpty && !refresh) { return } - if !refresh { isLoading = true } do { - dialogs = try await client.getDialogs() - let unreadMessagesCount = dialogs.map(\.unreadMessagesCount).reduce(0, +) - defaults.saveUnreadMessagesCount(unreadMessagesCount) + try await viewModel.askForDialogs(refresh: refresh, defaults: defaults) } catch { SWAlert.shared.presentDefaultUIKit(message: error.localizedDescription) } - isLoading = false } func initiateDeletion(at indexSet: IndexSet) { @@ -204,17 +174,11 @@ private extension DialogsListScreen { func deleteAction(at index: Int?) { deleteDialogTask = Task { - guard let index, !isLoading else { return } - isLoading = true do { - let dialogID = dialogs[index].id - if try await client.deleteDialog(dialogID) { - dialogs.remove(at: index) - } + try await viewModel.deleteDialog(at: index, defaults: defaults) } catch { SWAlert.shared.presentDefaultUIKit(message: error.localizedDescription) } - isLoading = false } } } diff --git a/SwiftUI-WorkoutApp/Screens/Messages/DialogsViewModel.swift b/SwiftUI-WorkoutApp/Screens/Messages/DialogsViewModel.swift new file mode 100644 index 00000000..ac4ba4d1 --- /dev/null +++ b/SwiftUI-WorkoutApp/Screens/Messages/DialogsViewModel.swift @@ -0,0 +1,57 @@ +import Foundation +import SWModels +import SWNetworkClient +import SWUtils + +final class DialogsViewModel: ObservableObject { + @Published private(set) var dialogs = [DialogResponse]() + @Published private(set) var isLoading = false + var hasDialogs: Bool { !dialogs.isEmpty } + var showEmptyView: Bool { !hasDialogs && !isLoading } + var showRefreshButton: Bool { + showEmptyView || !DeviceOSVersionChecker.iOS16Available + } + + @MainActor + func askForDialogs( + refresh: Bool = false, + defaults: DefaultsService + ) async throws { + guard defaults.isAuthorized else { return } + if isLoading || (!dialogs.isEmpty && !refresh) { return } + if !refresh || dialogs.isEmpty { isLoading = true } + dialogs = try await SWClient(with: defaults).getDialogs() + let unreadMessagesCount = dialogs.map(\.unreadMessagesCount).reduce(0, +) + defaults.saveUnreadMessagesCount(unreadMessagesCount) + isLoading = false + } + + @MainActor + func deleteDialog(at index: Int?, defaults: DefaultsService) async throws { + guard let index, !isLoading else { return } + isLoading = true + let dialogID = dialogs[index].id + if try await SWClient(with: defaults).deleteDialog(dialogID) { + dialogs.remove(at: index) + } + isLoading = false + } + + @MainActor + func markAsRead(_ dialog: DialogResponse, defaults: DefaultsService) { + dialogs = dialogs.map { item in + if item.id == dialog.id { + var updatedDialog = dialog + updatedDialog.unreadMessagesCount = 0 + return updatedDialog + } else { + return item + } + } + guard dialog.unreadMessagesCount > 0, + defaults.unreadMessagesCount >= dialog.unreadMessagesCount + else { return } + let newValue = defaults.unreadMessagesCount - dialog.unreadMessagesCount + defaults.saveUnreadMessagesCount(newValue) + } +} diff --git a/SwiftUI-WorkoutApp/Services/UnreadCountService.swift b/SwiftUI-WorkoutApp/Services/UnreadCountService.swift deleted file mode 100644 index 6d18fc32..00000000 --- a/SwiftUI-WorkoutApp/Services/UnreadCountService.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation -import SWNetworkClient - -struct UnreadCountService { - let client: SWClient - - func getUnreadCount() async -> Int? { - guard let dialogs = try? await client.getDialogs() else { return nil } - return dialogs.map(\.unreadMessagesCount).reduce(0, +) - } -} From 9b31812dc74ab9bb5859f6d9ee78d737ea02c9c0 Mon Sep 17 00:00:00 2001 From: Oleg991 Date: Tue, 4 Feb 2025 22:22:14 +0300 Subject: [PATCH 3/3] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit + доработал цвета в таббаре и навбаре --- SwiftUI-WorkoutApp.xcodeproj/project.pbxproj | 4 +- .../Sources/SWNetworkClient/SWClient.swift | 1 - .../Screens/Messages/DialogsListScreen.swift | 9 +++-- .../Screens/Messages/DialogsViewModel.swift | 9 +++-- .../SwiftUI_WorkoutAppApp.swift | 40 ++++++++++++++----- 5 files changed, 42 insertions(+), 21 deletions(-) diff --git a/SwiftUI-WorkoutApp.xcodeproj/project.pbxproj b/SwiftUI-WorkoutApp.xcodeproj/project.pbxproj index c225fc04..67a24fff 100644 --- a/SwiftUI-WorkoutApp.xcodeproj/project.pbxproj +++ b/SwiftUI-WorkoutApp.xcodeproj/project.pbxproj @@ -856,7 +856,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 7; + CURRENT_PROJECT_VERSION = 8; DEVELOPMENT_ASSET_PATHS = "SwiftUI-WorkoutApp/Preview\\ Content/PreviewContent.swift SwiftUI-WorkoutApp/Preview\\ Content"; DEVELOPMENT_TEAM = CR68PP2Z3F; ENABLE_PREVIEWS = YES; @@ -907,7 +907,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 7; + CURRENT_PROJECT_VERSION = 8; 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 6e97382f..1556b5b9 100644 --- a/SwiftUI-WorkoutApp/Libraries/SWNetworkClient/Sources/SWNetworkClient/SWClient.swift +++ b/SwiftUI-WorkoutApp/Libraries/SWNetworkClient/Sources/SWNetworkClient/SWClient.swift @@ -62,7 +62,6 @@ public struct SWClient: Sendable { /// - Parameters: /// - userID: `id` пользователя /// - Returns: вся информация о пользователе - @discardableResult public func getUserByID(_ userID: Int) async throws -> UserResponse { let endpoint = Endpoint.getUser(id: userID) return try await makeResult(for: endpoint) diff --git a/SwiftUI-WorkoutApp/Screens/Messages/DialogsListScreen.swift b/SwiftUI-WorkoutApp/Screens/Messages/DialogsListScreen.swift index 59f4658a..68851a08 100644 --- a/SwiftUI-WorkoutApp/Screens/Messages/DialogsListScreen.swift +++ b/SwiftUI-WorkoutApp/Screens/Messages/DialogsListScreen.swift @@ -33,7 +33,8 @@ struct DialogsListScreen: View { .navigationTitle("Сообщения") } .navigationViewStyle(.stack) - .task { await askForDialogs() } + .onChange(of: defaults.isAuthorized, perform: viewModel.clearDialogsOnLogout) + .task(id: defaults.isAuthorized) { await askForDialogs() } } } @@ -48,7 +49,6 @@ private extension DialogsListScreen { isPresented: $showDeleteConfirmation, titleVisibility: .visible ) { deleteDialogButton } - .refreshable { await askForDialogs(refresh: true) } .toolbar { ToolbarItem(placement: .topBarLeading) { refreshButton @@ -67,8 +67,8 @@ private extension DialogsListScreen { } label: { Icons.Regular.refresh.view } - .opacity(viewModel.showRefreshButton ? 1 : 0) - .disabled(viewModel.isLoading) + .opacity(viewModel.showEmptyView ? 1 : 0) + .disabled(viewModel.isLoading || !isNetworkConnected) } var friendListButton: some View { @@ -109,6 +109,7 @@ private extension DialogsListScreen { } .listStyle(.plain) .opacity(viewModel.hasDialogs ? 1 : 0) + .refreshable { await askForDialogs(refresh: true) } } .animation(.default, value: viewModel.dialogs.count) .background( diff --git a/SwiftUI-WorkoutApp/Screens/Messages/DialogsViewModel.swift b/SwiftUI-WorkoutApp/Screens/Messages/DialogsViewModel.swift index ac4ba4d1..f64ef1af 100644 --- a/SwiftUI-WorkoutApp/Screens/Messages/DialogsViewModel.swift +++ b/SwiftUI-WorkoutApp/Screens/Messages/DialogsViewModel.swift @@ -8,9 +8,6 @@ final class DialogsViewModel: ObservableObject { @Published private(set) var isLoading = false var hasDialogs: Bool { !dialogs.isEmpty } var showEmptyView: Bool { !hasDialogs && !isLoading } - var showRefreshButton: Bool { - showEmptyView || !DeviceOSVersionChecker.iOS16Available - } @MainActor func askForDialogs( @@ -54,4 +51,10 @@ final class DialogsViewModel: ObservableObject { let newValue = defaults.unreadMessagesCount - dialog.unreadMessagesCount defaults.saveUnreadMessagesCount(newValue) } + + @MainActor + func clearDialogsOnLogout(isAuthorized: Bool) { + guard !isAuthorized else { return } + dialogs.removeAll() + } } diff --git a/SwiftUI-WorkoutApp/SwiftUI_WorkoutAppApp.swift b/SwiftUI-WorkoutApp/SwiftUI_WorkoutAppApp.swift index 64e1b894..2df92457 100644 --- a/SwiftUI-WorkoutApp/SwiftUI_WorkoutAppApp.swift +++ b/SwiftUI-WorkoutApp/SwiftUI_WorkoutAppApp.swift @@ -11,8 +11,10 @@ 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? { @@ -38,6 +40,7 @@ struct SwiftUI_WorkoutAppApp: App { .environmentObject(network) .environmentObject(defaults) .environmentObject(parksManager) + .environmentObject(dialogsViewModel) .preferredColorScheme(colorScheme) .environment(\.isNetworkConnected, network.isConnected) .environment(\.userFlags, defaults.userFlags) @@ -67,18 +70,15 @@ struct SwiftUI_WorkoutAppApp: App { private func updateSocialInfoIfNeeded() { guard let mainUserId = defaults.mainUserInfo?.id else { return } socialUpdateTask = Task { - async let socialUpdatesTask = client.getSocialUpdates(userID: mainUserId) - async let unreadCountTask = UnreadCountService(client: client).getUnreadCount() - let (socialUpdates, unreadCount) = await (socialUpdatesTask, unreadCountTask) - if let socialUpdates { - try? defaults.saveFriendsIds(socialUpdates.friends.map(\.id)) - try? defaults.saveFriendRequests(socialUpdates.friendRequests) - try? defaults.saveBlacklist(socialUpdates.blacklist) + 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) } - if let unreadCount { - defaults.saveUnreadMessagesCount(unreadCount) - } + } + dialogsUpdateTask = Task { + try? await dialogsViewModel.askForDialogs(refresh: true, defaults: defaults) } } } @@ -93,7 +93,13 @@ private extension SwiftUI_WorkoutAppApp { $0.backgroundColor = .init(Color.swBackground) $0.shadowColor = nil } + let tabBarItemAppearance = makeTabBarItemAppearance() + tabBarAppearance.inlineLayoutAppearance = tabBarItemAppearance + tabBarAppearance.stackedLayoutAppearance = tabBarItemAppearance + tabBarAppearance.compactInlineLayoutAppearance = tabBarItemAppearance + UITabBar.appearance().standardAppearance = tabBarAppearance UITabBar.appearance().scrollEdgeAppearance = tabBarAppearance + UINavigationBar.appearance().standardAppearance = navBarAppearance UINavigationBar.appearance().scrollEdgeAppearance = navBarAppearance fixAlertAccentColor() if !DeviceOSVersionChecker.iOS16Available { @@ -104,15 +110,27 @@ private extension SwiftUI_WorkoutAppApp { /// Исправляет баг с accentColor у алертов, [обсуждение](https://developer.apple.com/forums/thread/673147) /// /// Без этой настройки у всех алертов при первом появлении стандартный tintColor (синий), - /// а при нажатии он меняется на `AccentColor` в проекте + /// а при нажатии он меняется на AccentColor в проекте func fixAlertAccentColor() { UIView.appearance().tintColor = .accent } + /// Настройки цветовых параметров для табов в таббаре + func makeTabBarItemAppearance() -> UITabBarItemAppearance { + let tabBarItemAppearance = UITabBarItemAppearance() + tabBarItemAppearance.normal.iconColor = .init(.swSmallElements) + tabBarItemAppearance.normal.titleTextAttributes = [.foregroundColor: UIColor(.swSmallElements)] + tabBarItemAppearance.normal.badgeBackgroundColor = .accent + tabBarItemAppearance.normal.badgeTextAttributes = [.foregroundColor: UIColor(.swBackground)] + return tabBarItemAppearance + } + + #if DEBUG func prepareForUITestIfNeeded() { if ProcessInfo.processInfo.arguments.contains("UITest") { UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!) UIView.setAnimationsEnabled(false) } } + #endif }