From d091e45219406233085aadc273a62fffe9a3baa8 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 8 Sep 2024 14:22:58 +0900 Subject: [PATCH 01/16] update: update gitignore --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index edad1c0..a9b1c32 100644 --- a/.gitignore +++ b/.gitignore @@ -65,4 +65,7 @@ playground.xcworkspace # fastlane/report.xml # fastlane/Preview.html # fastlane/screenshots/**/*.png -# fastlane/test_output \ No newline at end of file +# fastlane/test_output + +# macOS +.DS_Store From 7bc544d4e2e34c194616be358db83aed1dc777c0 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 8 Sep 2024 14:41:05 +0900 Subject: [PATCH 02/16] update: add swiftlint settings at project file --- Harmonie.xcodeproj/project.pbxproj | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Harmonie.xcodeproj/project.pbxproj b/Harmonie.xcodeproj/project.pbxproj index 7370720..d991fdf 100644 --- a/Harmonie.xcodeproj/project.pbxproj +++ b/Harmonie.xcodeproj/project.pbxproj @@ -130,6 +130,7 @@ 20395D8D2B945CD700003921 /* Sources */, 20395D8E2B945CD700003921 /* Frameworks */, 20395D8F2B945CD700003921 /* Resources */, + 20A26A662C8D6DF0004310E6 /* Swiftlint */, ); buildRules = ( ); @@ -276,6 +277,28 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 20A26A662C8D6DF0004310E6 /* Swiftlint */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = Swiftlint; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nSWIFT_PACKAGE_DIR=\"${BUILD_DIR%Build/*}SourcePackages/artifacts\"\nSWIFTLINT_CMD=$(ls \"$SWIFT_PACKAGE_DIR\"/swiftlintplugins/SwiftLintBinary/SwiftLintBinary.artifactbundle/swiftlint-*/bin/swiftlint | head -n 1)\n\nif test -f \"$SWIFTLINT_CMD\" 2>&1\nthen\n \"$SWIFTLINT_CMD\"\nelse\n echo \"warning: `swiftlint` command not found - See https://github.com/realm/SwiftLint#installation for installation instructions.\"\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 20395D8D2B945CD700003921 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -445,6 +468,7 @@ DEVELOPMENT_ASSET_PATHS = "\"harmonie/Preview Content\""; DEVELOPMENT_TEAM = CJHK4FP72G; ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Harmonie/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Harmonie; @@ -479,6 +503,7 @@ DEVELOPMENT_ASSET_PATHS = "\"harmonie/Preview Content\""; DEVELOPMENT_TEAM = CJHK4FP72G; ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Harmonie/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Harmonie; From ab96b5510bea9cb6948b8ea0bf6e560734a339b0 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 8 Sep 2024 14:51:11 +0900 Subject: [PATCH 03/16] feat: implement clearing filter button in FriendsView --- harmonie/ViewModels/FriendViewModel+Filters.swift | 5 +++++ harmonie/Views/Friend/FriendsView+Filters.swift | 3 +++ 2 files changed, 8 insertions(+) diff --git a/harmonie/ViewModels/FriendViewModel+Filters.swift b/harmonie/ViewModels/FriendViewModel+Filters.swift index ef4950d..6ba8c8f 100644 --- a/harmonie/ViewModels/FriendViewModel+Filters.swift +++ b/harmonie/ViewModels/FriendViewModel+Filters.swift @@ -27,6 +27,11 @@ extension FriendViewModel { case asc, desc } + func clearFilters() { + filterUserStatus = [] + filterFavoriteGroups = [] + } + /// Filters the list of friends based on the specified list type. /// - Parameter favoriteFriends: Favorite friends information. /// - Returns: A filtered list of friends whose display names meet the criteria defined by `isIncluded`. diff --git a/harmonie/Views/Friend/FriendsView+Filters.swift b/harmonie/Views/Friend/FriendsView+Filters.swift index a5add00..15c7e4d 100644 --- a/harmonie/Views/Friend/FriendsView+Filters.swift +++ b/harmonie/Views/Friend/FriendsView+Filters.swift @@ -15,6 +15,9 @@ extension FriendsView { var filterMenu: some View { Menu { + Button("Clear") { + friendVM.clearFilters() + } filterUserStatusMenu filterFavoriteGroupsMenu } label: { From e5be226a45bb44e23ceea57ca07937596d94230d Mon Sep 17 00:00:00 2001 From: kiripoipoi Date: Sun, 8 Sep 2024 15:18:54 +0900 Subject: [PATCH 04/16] feat: implement list favorite world feature --- harmonie/ViewModels/FavoriteViewModel.swift | 6 ++++ harmonie/Views/Favorite/FavoritesView.swift | 40 +++++++++++++++++++-- harmonie/Views/MainTabView.swift | 9 +++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/harmonie/ViewModels/FavoriteViewModel.swift b/harmonie/ViewModels/FavoriteViewModel.swift index e98f492..3e0928c 100644 --- a/harmonie/ViewModels/FavoriteViewModel.swift +++ b/harmonie/ViewModels/FavoriteViewModel.swift @@ -15,6 +15,7 @@ class FavoriteViewModel { typealias FavoriteFriend = (favoriteGroupId: String, friends: [Friend]) var favoriteGroups: [FavoriteGroup] = [] var favoriteFriends: [FavoriteFriend] = [] + var favoriteWorlds: [World] = [] var segment: Segment = .friend @ObservationIgnored let service: any FavoriteServiceProtocol @@ -128,6 +129,11 @@ class FavoriteViewModel { addFriendToFavoriteGroup(friend: friend, groupId: targetGroup.id) } } + + func fetchFavoritedWorlds(service: any WorldServiceProtocol) async throws { + let favoriteWorldsWrapper = try await service.fetchFavoritedWorlds() + favoriteWorlds = favoriteWorldsWrapper.worlds + } } extension FavoriteViewModel.Segment: Identifiable { diff --git a/harmonie/Views/Favorite/FavoritesView.swift b/harmonie/Views/Favorite/FavoritesView.swift index fc23fc4..30df7b9 100644 --- a/harmonie/Views/Favorite/FavoritesView.swift +++ b/harmonie/Views/Favorite/FavoritesView.swift @@ -18,7 +18,7 @@ struct FavoritesView: View { if favoriteVM.segment == .friend { listView } else if favoriteVM.segment == .world { - Text("Work in progress!") + listWorldView } } .navigationDestination(item: $selected) { selected in @@ -92,4 +92,40 @@ struct FavoritesView: View { .contentShape(Rectangle()) } } -} + + var listWorldView: some View { + List { + ForEach(groupedWorlds.keys.sorted(), id: \.self) { group in + if let worlds = groupedWorlds[group] { + Section(header: Text(group)) { + ForEach(worlds) { world in + rowWorldView(world) + } + } + } + } + } + } + + var groupedWorlds: [String: [World]] { + let grouped = Dictionary(grouping: favoriteVM.favoriteWorlds, by: { String($0.name.prefix(1)) }) + return grouped + } + + func rowWorldView(_ world: World) -> some View { + Button { + selected = Selected(id: world.id) + } label: { + VStack(alignment: .leading) { + Text(world.name) + .font(.headline) + Text(world.description) + .font(.subheadline) + .foregroundColor(.gray) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding() + } + .contentShape(Rectangle()) + } + } diff --git a/harmonie/Views/MainTabView.swift b/harmonie/Views/MainTabView.swift index 699bd4c..aa42ecc 100644 --- a/harmonie/Views/MainTabView.swift +++ b/harmonie/Views/MainTabView.swift @@ -37,6 +37,15 @@ struct MainTabView: View { appVM.handleError(error) } } + .task { + do { + try await favoriteVM.fetchFavoritedWorlds( + service: WorldService(client: appVM.client) + ) + } catch { + appVM.handleError(error) + } + } } } From 4cf18e3dcd52371c48e8bb185cdc0a3566ebfa04 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 8 Sep 2024 16:28:30 +0900 Subject: [PATCH 05/16] update: package dependencies --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c227a02..988cfc3 100644 --- a/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -61,7 +61,7 @@ "location" : "https://github.com/makinosp/vrckit", "state" : { "branch" : "develop", - "revision" : "2371372b625c197e928215e4c9ae912de24e5835" + "revision" : "feb0aba0193cb2ca7f4dadb065a11656defdb091" } } ], From edb647608928865454a394e78f9bc77fc9a2a1fe Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 8 Sep 2024 21:16:22 +0900 Subject: [PATCH 06/16] fix: storing favoriteWorlds --- harmonie/ViewModels/FavoriteViewModel.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/harmonie/ViewModels/FavoriteViewModel.swift b/harmonie/ViewModels/FavoriteViewModel.swift index 3e0928c..50b49d1 100644 --- a/harmonie/ViewModels/FavoriteViewModel.swift +++ b/harmonie/ViewModels/FavoriteViewModel.swift @@ -131,8 +131,7 @@ class FavoriteViewModel { } func fetchFavoritedWorlds(service: any WorldServiceProtocol) async throws { - let favoriteWorldsWrapper = try await service.fetchFavoritedWorlds() - favoriteWorlds = favoriteWorldsWrapper.worlds + favoriteWorlds = try await service.fetchFavoritedWorlds() } } From 1e75113b88c9e169f33bc3517552d74778acfa07 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 8 Sep 2024 21:32:27 +0900 Subject: [PATCH 07/16] fix: behavior of friend search --- harmonie/Views/Friend/FriendsView.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/harmonie/Views/Friend/FriendsView.swift b/harmonie/Views/Friend/FriendsView.swift index 7737722..6a06cd4 100644 --- a/harmonie/Views/Friend/FriendsView.swift +++ b/harmonie/Views/Friend/FriendsView.swift @@ -12,6 +12,7 @@ struct FriendsView: View { @Environment(AppViewModel.self) var appVM: AppViewModel @Environment(FriendViewModel.self) var friendVM: FriendViewModel @Environment(FavoriteViewModel.self) var favoriteVM: FavoriteViewModel + @State private var searchText = "" @State var selected: Selected? var body: some View { @@ -83,12 +84,15 @@ struct FriendsView: View { } } .navigationTitle("Friends") - .searchable(text: bindFilterText) .toolbar { toolbarContent } .navigationDestination(item: $selected) { selected in UserDetailPresentationView(id: selected.id) .id(selected.id) } + .searchable(text: $searchText) + .onSubmit(of: .search) { + friendVM.filterText = searchText + } } } From 9dc361c7b8bd2e56a956b977a6fe9e0c97b65d0e Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 8 Sep 2024 21:35:05 +0900 Subject: [PATCH 08/16] refact: comment --- harmonie/ViewModels/AppViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/harmonie/ViewModels/AppViewModel.swift b/harmonie/ViewModels/AppViewModel.swift index d63bd78..e92fe5d 100644 --- a/harmonie/ViewModels/AppViewModel.swift +++ b/harmonie/ViewModels/AppViewModel.swift @@ -1,5 +1,5 @@ // -// UserData.swift +// AppViewModel.swift // Harmonie // // Created by makinosp on 2024/03/09. From f442304c807720ad75efb37eef0c8d19c3855e1e Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 8 Sep 2024 22:38:19 +0900 Subject: [PATCH 09/16] update: package dependencies --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 988cfc3..07c1a8c 100644 --- a/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -61,7 +61,7 @@ "location" : "https://github.com/makinosp/vrckit", "state" : { "branch" : "develop", - "revision" : "feb0aba0193cb2ca7f4dadb065a11656defdb091" + "revision" : "bf2facec1f9c338ee67c6469c5098b7e5c2ebd09" } } ], From ecc97e8f0d0a25356cf0d812faca6c5bf646b7e3 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 8 Sep 2024 22:44:52 +0900 Subject: [PATCH 10/16] feat: handle required re-authentication error --- harmonie/ViewModels/AppViewModel.swift | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/harmonie/ViewModels/AppViewModel.swift b/harmonie/ViewModels/AppViewModel.swift index e92fe5d..0299268 100644 --- a/harmonie/ViewModels/AppViewModel.swift +++ b/harmonie/ViewModels/AppViewModel.swift @@ -124,13 +124,19 @@ class AppViewModel { func handleError(_ error: Error) { if let error = error as? VRCKitError { - vrckError = error - } else if (error as NSError?)?.isCancelled ?? false { - return - } else { - vrckError = .unexpectedError + if error == .unauthorized { + step = .loggingIn + } else { + vrckError = error + } + } else if !isCancelled(error) { + vrckError = .unexpected } - isPresentedAlert = true + isPresentedAlert = vrckError != nil + } + + func isCancelled(_ error: Error) -> Bool { + (error as NSError?)?.isCancelled ?? false } } From 44d1d1f5cdeb565fc99f57f47c2769775909b4aa Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 8 Sep 2024 23:08:18 +0900 Subject: [PATCH 11/16] feat: implement AuthenticationServicePresentable protocol --- .../AuthenticationServicePresentable.swift | 21 ++++++++++++++ harmonie/ViewModels/AppViewModel.swift | 29 +++++++++---------- .../AuthenticationView+LoginView.swift | 1 + .../AuthenticationView+OtpView.swift | 6 +++- .../Authentication/AuthenticationView.swift | 2 +- harmonie/Views/ContentView.swift | 6 ++-- harmonie/Views/Setting/SettingsView.swift | 4 +-- 7 files changed, 47 insertions(+), 22 deletions(-) create mode 100644 harmonie/Protocols/AuthenticationServicePresentable.swift diff --git a/harmonie/Protocols/AuthenticationServicePresentable.swift b/harmonie/Protocols/AuthenticationServicePresentable.swift new file mode 100644 index 0000000..b95ceb6 --- /dev/null +++ b/harmonie/Protocols/AuthenticationServicePresentable.swift @@ -0,0 +1,21 @@ +// +// AuthenticationServicePresentable.swift +// Harmonie +// +// Created by makinosp on 2024/09/08. +// + +import VRCKit + +protocol AuthenticationServicePresentable { + var appVM: AppViewModel { get } +} + +extension AuthenticationServicePresentable { + @MainActor + var authenticationService: AuthenticationServiceProtocol { + appVM.isDemoMode + ? AuthenticationPreviewService(client: appVM.client) + : AuthenticationService(client: appVM.client) + } +} diff --git a/harmonie/ViewModels/AppViewModel.swift b/harmonie/ViewModels/AppViewModel.swift index 0299268..abf70e6 100644 --- a/harmonie/ViewModels/AppViewModel.swift +++ b/harmonie/ViewModels/AppViewModel.swift @@ -17,21 +17,11 @@ class AppViewModel { var vrckError: VRCKitError? var isDemoMode = false @ObservationIgnored var client = APIClient() - @ObservationIgnored var service: any AuthenticationServiceProtocol - - init() { - self.service = AuthenticationService(client: client) - } enum Step: Equatable { case initializing, loggingIn, done(User) } - func setDemoMode() { - isDemoMode = true - service = AuthenticationPreviewService(client: client) - } - func reset() { user = nil step = .initializing @@ -41,7 +31,7 @@ class AppViewModel { /// Check the authentication status of the user, /// fetch the user information, and perform the initialization process. /// - Returns: Depending on the status, either `loggingIn` or `done` is returned. - func setup() async -> Step { + func setup(service: any AuthenticationServiceProtocol) async -> Step { // check local data guard !client.cookieManager.cookies.isEmpty else { return .loggingIn @@ -60,9 +50,14 @@ class AppViewModel { } } - func login(username: String, password: String, isSavedOnKeyChain: Bool) async -> VerifyType? { + func login( + service: any AuthenticationServiceProtocol, + username: String, + password: String, + isSavedOnKeyChain: Bool + ) async -> VerifyType? { if username == "demo" && password == "demo" { - setDemoMode() + isDemoMode = true } else { client.setCledentials(username: username, password: password) } @@ -84,7 +79,11 @@ class AppViewModel { return nil } - func verifyTwoFA(_ verifyType: VerifyType?, _ code: String) async { + func verifyTwoFA( + service: any AuthenticationServiceProtocol, + verifyType: VerifyType?, + code: String + ) async { do { defer { reset() @@ -113,7 +112,7 @@ class AppViewModel { return FavoriteViewModel(service: service) } - func logout() async { + func logout(service: any AuthenticationServiceProtocol) async { do { try await service.logout() reset() diff --git a/harmonie/Views/Authentication/AuthenticationView+LoginView.swift b/harmonie/Views/Authentication/AuthenticationView+LoginView.swift index e1a6f9f..2a676f7 100644 --- a/harmonie/Views/Authentication/AuthenticationView+LoginView.swift +++ b/harmonie/Views/Authentication/AuthenticationView+LoginView.swift @@ -13,6 +13,7 @@ extension AuthenticationView { loginFields loginButton("Login") { verifyType = await appVM.login( + service: authenticationService, username: username, password: password, isSavedOnKeyChain: isSavedOnKeyChain diff --git a/harmonie/Views/Authentication/AuthenticationView+OtpView.swift b/harmonie/Views/Authentication/AuthenticationView+OtpView.swift index 105a386..6272351 100644 --- a/harmonie/Views/Authentication/AuthenticationView+OtpView.swift +++ b/harmonie/Views/Authentication/AuthenticationView+OtpView.swift @@ -15,7 +15,11 @@ extension AuthenticationView { .textFieldStyle(.roundedBorder) .padding(.horizontal, 8) loginButton("Continue") { - await appVM.verifyTwoFA(verifyType, code) + await appVM.verifyTwoFA( + service: authenticationService, + verifyType: verifyType, + code: code + ) } } .padding(32) diff --git a/harmonie/Views/Authentication/AuthenticationView.swift b/harmonie/Views/Authentication/AuthenticationView.swift index 053d307..a23c807 100644 --- a/harmonie/Views/Authentication/AuthenticationView.swift +++ b/harmonie/Views/Authentication/AuthenticationView.swift @@ -8,7 +8,7 @@ import AsyncSwiftUI import VRCKit -struct AuthenticationView: View { +struct AuthenticationView: View, AuthenticationServicePresentable { @AppStorage(Constants.Keys.isSavedOnKeyChain) var isSavedOnKeyChain = false @AppStorage(Constants.Keys.username) var username: String = "" @Environment(AppViewModel.self) var appVM: AppViewModel diff --git a/harmonie/Views/ContentView.swift b/harmonie/Views/ContentView.swift index 96a8147..7161340 100644 --- a/harmonie/Views/ContentView.swift +++ b/harmonie/Views/ContentView.swift @@ -8,7 +8,7 @@ import AsyncSwiftUI import VRCKit -struct ContentView: View { +struct ContentView: View, AuthenticationServicePresentable { @Environment(AppViewModel.self) var appVM: AppViewModel var body: some View { @@ -16,10 +16,10 @@ struct ContentView: View { case .initializing: ProgressScreen() .task { - appVM.step = await appVM.setup() + appVM.step = await appVM.setup(service: authenticationService) } .errorAlert { - Task { await appVM.logout() } + Task { await appVM.logout(service: authenticationService) } } case .loggingIn: AuthenticationView() diff --git a/harmonie/Views/Setting/SettingsView.swift b/harmonie/Views/Setting/SettingsView.swift index c4f3b4b..6fce225 100644 --- a/harmonie/Views/Setting/SettingsView.swift +++ b/harmonie/Views/Setting/SettingsView.swift @@ -9,7 +9,7 @@ import AsyncSwiftUI import LicenseList import VRCKit -struct SettingsView: View { +struct SettingsView: View, AuthenticationServicePresentable { @Environment(AppViewModel.self) var appVM: AppViewModel @State var destination: Destination? @State var isPresentedForm = false @@ -63,7 +63,7 @@ struct SettingsView: View { aboutSection Section { AsyncButton { - await appVM.logout() + await appVM.logout(service: authenticationService) } label: { Label { Text("Logout") From 53647aa087f0f1fbd8b3a84b5cb6f870e1f7262c Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 8 Sep 2024 23:20:06 +0900 Subject: [PATCH 12/16] feat: implement FriendServicePresentable protocol --- .../Protocols/FriendServicePresentable.swift | 21 +++++++++++++++++++ harmonie/ViewModels/AppViewModel.swift | 5 ----- harmonie/ViewModels/FriendViewModel.swift | 6 ++---- harmonie/Views/ContentView.swift | 4 ++-- harmonie/Views/Friend/FriendsView.swift | 9 ++++---- harmonie/Views/Location/LocationsView.swift | 4 ++-- harmonie/Views/MainTabView.swift | 4 ++-- 7 files changed, 33 insertions(+), 20 deletions(-) create mode 100644 harmonie/Protocols/FriendServicePresentable.swift diff --git a/harmonie/Protocols/FriendServicePresentable.swift b/harmonie/Protocols/FriendServicePresentable.swift new file mode 100644 index 0000000..da0a5a0 --- /dev/null +++ b/harmonie/Protocols/FriendServicePresentable.swift @@ -0,0 +1,21 @@ +// +// FriendServicePresentable.swift +// Harmonie +// +// Created by makinosp on 2024/09/08. +// + +import VRCKit + +protocol FriendServicePresentable { + var appVM: AppViewModel { get } +} + +extension FriendServicePresentable { + @MainActor + var friendService: FriendServiceProtocol { + appVM.isDemoMode + ? FriendPreviewService(client: appVM.client) + : FriendService(client: appVM.client) + } +} diff --git a/harmonie/ViewModels/AppViewModel.swift b/harmonie/ViewModels/AppViewModel.swift index abf70e6..cf273b5 100644 --- a/harmonie/ViewModels/AppViewModel.swift +++ b/harmonie/ViewModels/AppViewModel.swift @@ -102,11 +102,6 @@ class AppViewModel { } } - func generateFriendVM(user: User) -> FriendViewModel { - let service = isDemoMode ? FriendPreviewService(client: client) : FriendService(client: client) - return FriendViewModel(user: user, service: service) - } - func generateFavoriteVM(friendVM: FriendViewModel) -> FavoriteViewModel { let service = isDemoMode ? FavoritePreviewService(client: client) : FavoriteService(client: client) return FavoriteViewModel(service: service) diff --git a/harmonie/ViewModels/FriendViewModel.swift b/harmonie/ViewModels/FriendViewModel.swift index 9e640b1..5c7a4ca 100644 --- a/harmonie/ViewModels/FriendViewModel.swift +++ b/harmonie/ViewModels/FriendViewModel.swift @@ -20,11 +20,9 @@ class FriendViewModel { var sortOrder: SortOrder = .asc var isRequesting = true @ObservationIgnored let user: User - @ObservationIgnored private let service: any FriendServiceProtocol - init(user: User, service: any FriendServiceProtocol) { + init(user: User) { self.user = user - self.service = service } var allFriends: [Friend] { @@ -45,7 +43,7 @@ class FriendViewModel { } /// Fetch friends from API - func fetchAllFriends() async throws { + func fetchAllFriends(service: FriendServiceProtocol) async throws { async let onlineFriendsTask = service.fetchFriends( count: user.onlineFriends.count + user.activeFriends.count, offline: false diff --git a/harmonie/Views/ContentView.swift b/harmonie/Views/ContentView.swift index 7161340..1ace218 100644 --- a/harmonie/Views/ContentView.swift +++ b/harmonie/Views/ContentView.swift @@ -8,7 +8,7 @@ import AsyncSwiftUI import VRCKit -struct ContentView: View, AuthenticationServicePresentable { +struct ContentView: View, AuthenticationServicePresentable, FriendServicePresentable { @Environment(AppViewModel.self) var appVM: AppViewModel var body: some View { @@ -25,7 +25,7 @@ struct ContentView: View, AuthenticationServicePresentable { AuthenticationView() .errorAlert() case .done(let user): - let friendVM = appVM.generateFriendVM(user: user) + let friendVM = FriendViewModel(user: user) let favoriteVM = appVM.generateFavoriteVM(friendVM: friendVM) MainTabView() .environment(friendVM) diff --git a/harmonie/Views/Friend/FriendsView.swift b/harmonie/Views/Friend/FriendsView.swift index 6a06cd4..f1da7b5 100644 --- a/harmonie/Views/Friend/FriendsView.swift +++ b/harmonie/Views/Friend/FriendsView.swift @@ -8,7 +8,7 @@ import SwiftUI import VRCKit -struct FriendsView: View { +struct FriendsView: View, FriendServicePresentable { @Environment(AppViewModel.self) var appVM: AppViewModel @Environment(FriendViewModel.self) var friendVM: FriendViewModel @Environment(FavoriteViewModel.self) var favoriteVM: FavoriteViewModel @@ -23,7 +23,7 @@ struct FriendsView: View { } .refreshable { do { - try await friendVM.fetchAllFriends() + try await friendVM.fetchAllFriends(service: friendService) } catch { appVM.handleError(error) } @@ -99,8 +99,7 @@ struct FriendsView: View { #Preview { let appVM = AppViewModel() let friendVM = FriendViewModel( - user: PreviewDataProvider.shared.previewUser, - service: FriendPreviewService(client: appVM.client) + user: PreviewDataProvider.shared.previewUser ) let favoriteVM = FavoriteViewModel( service: FavoritePreviewService(client: appVM.client) @@ -108,7 +107,7 @@ struct FriendsView: View { FriendsView() .task { do { - try await friendVM.fetchAllFriends() + try await friendVM.fetchAllFriends(service: FriendPreviewService(client: appVM.client)) try await favoriteVM.fetchFavorite(friendVM: friendVM) } catch { appVM.handleError(error) diff --git a/harmonie/Views/Location/LocationsView.swift b/harmonie/Views/Location/LocationsView.swift index b751fad..f2306e6 100644 --- a/harmonie/Views/Location/LocationsView.swift +++ b/harmonie/Views/Location/LocationsView.swift @@ -9,7 +9,7 @@ import SwiftUI import SwiftUIIntrospect import VRCKit -struct LocationsView: View { +struct LocationsView: View, FriendServicePresentable { @Environment(AppViewModel.self) var appVM: AppViewModel @Environment(FriendViewModel.self) var friendVM: FriendViewModel @State var selected: InstanceLocation? @@ -47,7 +47,7 @@ struct LocationsView: View { .padding(.horizontal, UIDevice.current.userInterfaceIdiom == .pad ? 8 : .zero) .refreshable { do { - try await friendVM.fetchAllFriends() + try await friendVM.fetchAllFriends(service: friendService) } catch { appVM.handleError(error) } diff --git a/harmonie/Views/MainTabView.swift b/harmonie/Views/MainTabView.swift index aa42ecc..4f1bc77 100644 --- a/harmonie/Views/MainTabView.swift +++ b/harmonie/Views/MainTabView.swift @@ -8,7 +8,7 @@ import SwiftUI import VRCKit -struct MainTabView: View { +struct MainTabView: View, FriendServicePresentable { @Environment(AppViewModel.self) var appVM: AppViewModel @Environment(FriendViewModel.self) var friendVM: FriendViewModel @Environment(FavoriteViewModel.self) var favoriteVM: FavoriteViewModel @@ -27,7 +27,7 @@ struct MainTabView: View { .task { do { defer { friendVM.isRequesting = false } - try await friendVM.fetchAllFriends() + try await friendVM.fetchAllFriends(service: friendService) } catch { appVM.handleError(error) } From f62ab7c30369ae0fe2a2d9ef8b2ba02b46263e4f Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 8 Sep 2024 23:26:54 +0900 Subject: [PATCH 13/16] feat: implement FavoriteServicePresentable protocol --- .../FavoriteServicePresentable.swift | 21 +++++++++++++++++++ harmonie/ViewModels/AppViewModel.swift | 5 ----- harmonie/ViewModels/FavoriteViewModel.swift | 11 ++-------- harmonie/Views/ContentView.swift | 6 ++---- harmonie/Views/Friend/FriendsView.swift | 13 ++++++------ harmonie/Views/MainTabView.swift | 7 +++++-- .../UserDetail/UserDetailView+Actions.swift | 1 + .../Views/UserDetail/UserDetailView.swift | 2 +- 8 files changed, 38 insertions(+), 28 deletions(-) create mode 100644 harmonie/Protocols/FavoriteServicePresentable.swift diff --git a/harmonie/Protocols/FavoriteServicePresentable.swift b/harmonie/Protocols/FavoriteServicePresentable.swift new file mode 100644 index 0000000..42b96b2 --- /dev/null +++ b/harmonie/Protocols/FavoriteServicePresentable.swift @@ -0,0 +1,21 @@ +// +// FavoriteServicePresentable.swift +// Harmonie +// +// Created by makinosp on 2024/09/08. +// + +import VRCKit + +protocol FavoriteServicePresentable { + var appVM: AppViewModel { get } +} + +extension FavoriteServicePresentable { + @MainActor + var favoriteService: FavoriteServiceProtocol { + appVM.isDemoMode + ? FavoritePreviewService(client: appVM.client) + : FavoriteService(client: appVM.client) + } +} diff --git a/harmonie/ViewModels/AppViewModel.swift b/harmonie/ViewModels/AppViewModel.swift index cf273b5..1d265a2 100644 --- a/harmonie/ViewModels/AppViewModel.swift +++ b/harmonie/ViewModels/AppViewModel.swift @@ -102,11 +102,6 @@ class AppViewModel { } } - func generateFavoriteVM(friendVM: FriendViewModel) -> FavoriteViewModel { - let service = isDemoMode ? FavoritePreviewService(client: client) : FavoriteService(client: client) - return FavoriteViewModel(service: service) - } - func logout(service: any AuthenticationServiceProtocol) async { do { try await service.logout() diff --git a/harmonie/ViewModels/FavoriteViewModel.swift b/harmonie/ViewModels/FavoriteViewModel.swift index 50b49d1..bf4a9ba 100644 --- a/harmonie/ViewModels/FavoriteViewModel.swift +++ b/harmonie/ViewModels/FavoriteViewModel.swift @@ -17,18 +17,11 @@ class FavoriteViewModel { var favoriteFriends: [FavoriteFriend] = [] var favoriteWorlds: [World] = [] var segment: Segment = .friend - @ObservationIgnored let service: any FavoriteServiceProtocol enum Segment { case friend, world } - /// Initializes the view model with the specified HTTP client. - /// - Parameter client: The `APIClient` instance used for making network requests. - init(service: any FavoriteServiceProtocol) { - self.service = service - } - /// A filtered list of favorite groups that contain only friend-type groups. /// It retrieves friend-type groups from the `favoriteGroups` property. var favoriteFriendGroups: [FavoriteGroup] { @@ -37,7 +30,7 @@ class FavoriteViewModel { /// Asynchronously fetches and updates the favorite groups and their details. /// - Throws: An error if any network request or decoding operation fails. - func fetchFavorite(friendVM: FriendViewModel) async throws { + func fetchFavorite(service: FavoriteServiceProtocol, friendVM: FriendViewModel) async throws { favoriteGroups = try await service.listFavoriteGroups() let favoriteDetails = try await service.fetchFavoriteGroupDetails(favoriteGroups: favoriteGroups) let favoriteDetailsOfFriends = favoriteDetails.filter { $0.allFavoritesAre(.friend) } @@ -114,7 +107,7 @@ class FavoriteViewModel { /// - Parameters: /// - friendId: The friend's id whose favorite status is being updated. /// - targetGroup: The `FavoriteGroup` object representing the target group for the favorite status. - func updateFavorite(friend: Friend, targetGroup: FavoriteGroup) async throws { + func updateFavorite(service: FavoriteServiceProtocol, friend: Friend, targetGroup: FavoriteGroup) async throws { let sourceGroupId = findFavoriteGroupIdForFriend(friendId: friend.id) if let sourceGroupId = sourceGroupId { _ = try await service.removeFavorite(favoriteId: friend.id) diff --git a/harmonie/Views/ContentView.swift b/harmonie/Views/ContentView.swift index 1ace218..717c976 100644 --- a/harmonie/Views/ContentView.swift +++ b/harmonie/Views/ContentView.swift @@ -25,11 +25,9 @@ struct ContentView: View, AuthenticationServicePresentable, FriendServicePresent AuthenticationView() .errorAlert() case .done(let user): - let friendVM = FriendViewModel(user: user) - let favoriteVM = appVM.generateFavoriteVM(friendVM: friendVM) MainTabView() - .environment(friendVM) - .environment(favoriteVM) + .environment(FriendViewModel(user: user)) + .environment(FavoriteViewModel()) .errorAlert() } } diff --git a/harmonie/Views/Friend/FriendsView.swift b/harmonie/Views/Friend/FriendsView.swift index f1da7b5..70b5875 100644 --- a/harmonie/Views/Friend/FriendsView.swift +++ b/harmonie/Views/Friend/FriendsView.swift @@ -98,17 +98,16 @@ struct FriendsView: View, FriendServicePresentable { #Preview { let appVM = AppViewModel() - let friendVM = FriendViewModel( - user: PreviewDataProvider.shared.previewUser - ) - let favoriteVM = FavoriteViewModel( - service: FavoritePreviewService(client: appVM.client) - ) + let friendVM = FriendViewModel(user: PreviewDataProvider.shared.previewUser) + let favoriteVM = FavoriteViewModel() FriendsView() .task { do { try await friendVM.fetchAllFriends(service: FriendPreviewService(client: appVM.client)) - try await favoriteVM.fetchFavorite(friendVM: friendVM) + try await favoriteVM.fetchFavorite( + service: FavoritePreviewService(client: appVM.client), + friendVM: friendVM + ) } catch { appVM.handleError(error) } diff --git a/harmonie/Views/MainTabView.swift b/harmonie/Views/MainTabView.swift index 4f1bc77..5f7b445 100644 --- a/harmonie/Views/MainTabView.swift +++ b/harmonie/Views/MainTabView.swift @@ -8,7 +8,7 @@ import SwiftUI import VRCKit -struct MainTabView: View, FriendServicePresentable { +struct MainTabView: View, FriendServicePresentable, FavoriteServicePresentable { @Environment(AppViewModel.self) var appVM: AppViewModel @Environment(FriendViewModel.self) var friendVM: FriendViewModel @Environment(FavoriteViewModel.self) var favoriteVM: FavoriteViewModel @@ -32,7 +32,10 @@ struct MainTabView: View, FriendServicePresentable { appVM.handleError(error) } do { - try await favoriteVM.fetchFavorite(friendVM: friendVM) + try await favoriteVM.fetchFavorite( + service: favoriteService, + friendVM: friendVM + ) } catch { appVM.handleError(error) } diff --git a/harmonie/Views/UserDetail/UserDetailView+Actions.swift b/harmonie/Views/UserDetail/UserDetailView+Actions.swift index 3603c2b..69a2d94 100644 --- a/harmonie/Views/UserDetail/UserDetailView+Actions.swift +++ b/harmonie/Views/UserDetail/UserDetailView+Actions.swift @@ -25,6 +25,7 @@ extension UserDetailView { guard let friend = friendVM.getFriend(id: friendId) else { return } do { try await favoriteVM.updateFavorite( + service: favoriteService, friend: friend, targetGroup: group ) diff --git a/harmonie/Views/UserDetail/UserDetailView.swift b/harmonie/Views/UserDetail/UserDetailView.swift index d801e9f..c00c4b2 100644 --- a/harmonie/Views/UserDetail/UserDetailView.swift +++ b/harmonie/Views/UserDetail/UserDetailView.swift @@ -9,7 +9,7 @@ import AsyncSwiftUI import NukeUI import VRCKit -struct UserDetailView: View { +struct UserDetailView: View, FavoriteServicePresentable { @Environment(AppViewModel.self) var appVM: AppViewModel @Environment(FavoriteViewModel.self) var favoriteVM: FavoriteViewModel @Environment(FriendViewModel.self) var friendVM: FriendViewModel From 5f43b49e1b13013f1c42f178cffeb48c02bf34d4 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 8 Sep 2024 23:47:57 +0900 Subject: [PATCH 14/16] refact: refactoring --- harmonie/Extensions/NSError+isCancelled.swift | 15 ++++ harmonie/Utils/DateUtil.swift | 2 +- harmonie/ViewModels/AppViewModel.swift | 25 +++---- harmonie/ViewModels/FavoriteViewModel.swift | 4 +- harmonie/ViewModels/FriendViewModel.swift | 4 +- .../ViewModels/ProfileEditViewModel.swift | 2 +- .../View+ErrorAlertModifier.swift | 0 .../View+SectionModifier.swift | 0 .../View+SizeModifier.swift | 0 harmonie/Views/Favorite/FavoritesView.swift | 69 +++++++++---------- .../Views/Friend/FriendsView+Filters.swift | 4 +- .../Views/Location/LocationCardView.swift | 8 +-- harmonie/Views/Location/LocationsView.swift | 6 +- harmonie/Views/MainTabView.swift | 10 ++- .../Setting/Profile/ProfileEditView.swift | 10 +-- .../Setting/SettingsView+AboutSection.swift | 4 +- harmonie/Views/Setting/SettingsView.swift | 4 +- .../Views/UserDetail/UserDetailView.swift | 2 +- 18 files changed, 87 insertions(+), 82 deletions(-) create mode 100644 harmonie/Extensions/NSError+isCancelled.swift rename harmonie/{Extensions => ViewModifiers}/View+ErrorAlertModifier.swift (100%) rename harmonie/{Extensions => ViewModifiers}/View+SectionModifier.swift (100%) rename harmonie/{Extensions => ViewModifiers}/View+SizeModifier.swift (100%) diff --git a/harmonie/Extensions/NSError+isCancelled.swift b/harmonie/Extensions/NSError+isCancelled.swift new file mode 100644 index 0000000..bccba01 --- /dev/null +++ b/harmonie/Extensions/NSError+isCancelled.swift @@ -0,0 +1,15 @@ +// +// NSError+isCancelled.swift +// Harmonie +// +// Created by makinosp on 2024/09/08. +// + +import Foundation + +extension NSError { + /// A Boolean value indicating whether the error represents a cancelled network request. + var isCancelled: Bool { + self.domain == NSURLErrorDomain && self.code == NSURLErrorCancelled + } +} diff --git a/harmonie/Utils/DateUtil.swift b/harmonie/Utils/DateUtil.swift index 2076446..2bc465f 100644 --- a/harmonie/Utils/DateUtil.swift +++ b/harmonie/Utils/DateUtil.swift @@ -7,7 +7,7 @@ import Foundation -class DateUtil { +final class DateUtil { static let shared = DateUtil() private let yyyyMMddDateFormatter: DateFormatter = { diff --git a/harmonie/ViewModels/AppViewModel.swift b/harmonie/ViewModels/AppViewModel.swift index 1d265a2..6102758 100644 --- a/harmonie/ViewModels/AppViewModel.swift +++ b/harmonie/ViewModels/AppViewModel.swift @@ -9,8 +9,8 @@ import Foundation import Observation import VRCKit -@MainActor @Observable -class AppViewModel { +@Observable +final class AppViewModel { var user: User? var step: Step = .initializing var isPresentedAlert = false @@ -22,12 +22,6 @@ class AppViewModel { case initializing, loggingIn, done(User) } - func reset() { - user = nil - step = .initializing - client = APIClient() - } - /// Check the authentication status of the user, /// fetch the user information, and perform the initialization process. /// - Returns: Depending on the status, either `loggingIn` or `done` is returned. @@ -111,6 +105,12 @@ class AppViewModel { } } + private func reset() { + user = nil + step = .initializing + client = APIClient() + } + func handleError(_ error: Error) { if let error = error as? VRCKitError { if error == .unauthorized { @@ -124,14 +124,7 @@ class AppViewModel { isPresentedAlert = vrckError != nil } - func isCancelled(_ error: Error) -> Bool { + private func isCancelled(_ error: Error) -> Bool { (error as NSError?)?.isCancelled ?? false } } - -fileprivate extension NSError { - /// A Boolean value indicating whether the error represents a cancelled network request. - var isCancelled: Bool { - self.domain == NSURLErrorDomain && self.code == NSURLErrorCancelled - } -} diff --git a/harmonie/ViewModels/FavoriteViewModel.swift b/harmonie/ViewModels/FavoriteViewModel.swift index bf4a9ba..799f2ca 100644 --- a/harmonie/ViewModels/FavoriteViewModel.swift +++ b/harmonie/ViewModels/FavoriteViewModel.swift @@ -10,8 +10,8 @@ import VRCKit /// View model for managing favorite groups and their associated details. /// Acts as an interface between the UI and backend services for handling favorite group operations. -@MainActor @Observable -class FavoriteViewModel { +@Observable +final class FavoriteViewModel { typealias FavoriteFriend = (favoriteGroupId: String, friends: [Friend]) var favoriteGroups: [FavoriteGroup] = [] var favoriteFriends: [FavoriteFriend] = [] diff --git a/harmonie/ViewModels/FriendViewModel.swift b/harmonie/ViewModels/FriendViewModel.swift index 5c7a4ca..10a62dd 100644 --- a/harmonie/ViewModels/FriendViewModel.swift +++ b/harmonie/ViewModels/FriendViewModel.swift @@ -8,8 +8,8 @@ import Observation import VRCKit -@MainActor @Observable -class FriendViewModel { +@Observable +final class FriendViewModel { var onlineFriends: [Friend] = [] var offlineFriends: [Friend] = [] var friendsLocations: [FriendsLocation] = [] diff --git a/harmonie/ViewModels/ProfileEditViewModel.swift b/harmonie/ViewModels/ProfileEditViewModel.swift index ec1cc91..1547016 100644 --- a/harmonie/ViewModels/ProfileEditViewModel.swift +++ b/harmonie/ViewModels/ProfileEditViewModel.swift @@ -9,7 +9,7 @@ import Observation import VRCKit @Observable -class ProfileEditViewModel { +final class ProfileEditViewModel { var editingUserInfo: EditableUserInfo @ObservationIgnored private let id: String diff --git a/harmonie/Extensions/View+ErrorAlertModifier.swift b/harmonie/ViewModifiers/View+ErrorAlertModifier.swift similarity index 100% rename from harmonie/Extensions/View+ErrorAlertModifier.swift rename to harmonie/ViewModifiers/View+ErrorAlertModifier.swift diff --git a/harmonie/Extensions/View+SectionModifier.swift b/harmonie/ViewModifiers/View+SectionModifier.swift similarity index 100% rename from harmonie/Extensions/View+SectionModifier.swift rename to harmonie/ViewModifiers/View+SectionModifier.swift diff --git a/harmonie/Extensions/View+SizeModifier.swift b/harmonie/ViewModifiers/View+SizeModifier.swift similarity index 100% rename from harmonie/Extensions/View+SizeModifier.swift rename to harmonie/ViewModifiers/View+SizeModifier.swift diff --git a/harmonie/Views/Favorite/FavoritesView.swift b/harmonie/Views/Favorite/FavoritesView.swift index 30df7b9..1a985c6 100644 --- a/harmonie/Views/Favorite/FavoritesView.swift +++ b/harmonie/Views/Favorite/FavoritesView.swift @@ -10,7 +10,7 @@ import VRCKit struct FavoritesView: View { @Environment(FavoriteViewModel.self) var favoriteVM: FavoriteViewModel - @State var selected: Selected? + @State private var selected: Selected? var body: some View { NavigationSplitView(columnVisibility: .constant(.all)) { @@ -93,39 +93,38 @@ struct FavoritesView: View { } } - var listWorldView: some View { - List { - ForEach(groupedWorlds.keys.sorted(), id: \.self) { group in - if let worlds = groupedWorlds[group] { - Section(header: Text(group)) { - ForEach(worlds) { world in - rowWorldView(world) - } - } - } - } - } - } - - var groupedWorlds: [String: [World]] { - let grouped = Dictionary(grouping: favoriteVM.favoriteWorlds, by: { String($0.name.prefix(1)) }) - return grouped + var listWorldView: some View { + List { + ForEach(groupedWorlds.keys.sorted(), id: \.self) { group in + if let worlds = groupedWorlds[group] { + Section(header: Text(group)) { + ForEach(worlds) { world in + rowWorldView(world) + } + } + } + } } + } - func rowWorldView(_ world: World) -> some View { - Button { - selected = Selected(id: world.id) - } label: { - VStack(alignment: .leading) { - Text(world.name) - .font(.headline) - Text(world.description) - .font(.subheadline) - .foregroundColor(.gray) - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding() - } - .contentShape(Rectangle()) - } - } + var groupedWorlds: [String: [World]] { + Dictionary(grouping: favoriteVM.favoriteWorlds, by: { String($0.name.prefix(1)) }) + } + + func rowWorldView(_ world: World) -> some View { + Button { + selected = Selected(id: world.id) + } label: { + VStack(alignment: .leading) { + Text(world.name) + .font(.headline) + Text(world.description) + .font(.subheadline) + .foregroundColor(.gray) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding() + } + .contentShape(Rectangle()) + } +} diff --git a/harmonie/Views/Friend/FriendsView+Filters.swift b/harmonie/Views/Friend/FriendsView+Filters.swift index 15c7e4d..8020dc5 100644 --- a/harmonie/Views/Friend/FriendsView+Filters.swift +++ b/harmonie/Views/Friend/FriendsView+Filters.swift @@ -25,7 +25,7 @@ extension FriendsView { } } - var filterUserStatusMenu: some View { + private var filterUserStatusMenu: some View { Menu("Statuses") { ForEach(FriendViewModel.FilterUserStatus.allCases) { filterUserStatus in Button { @@ -43,7 +43,7 @@ extension FriendsView { } } - var filterFavoriteGroupsMenu: some View { + private var filterFavoriteGroupsMenu: some View { Menu("Favorite Groups") { Button { friendVM.applyFilterFavoriteGroup(.all) diff --git a/harmonie/Views/Location/LocationCardView.swift b/harmonie/Views/Location/LocationCardView.swift index 1a242d8..42cba6d 100644 --- a/harmonie/Views/Location/LocationCardView.swift +++ b/harmonie/Views/Location/LocationCardView.swift @@ -18,7 +18,7 @@ struct LocationCardView: View { let service: any InstanceServiceProtocol let location: FriendsLocation - var backGroundColor: Color { + private var backGroundColor: Color { switch UIDevice.current.userInterfaceIdiom { case .pad: Color(uiColor: .tertiarySystemGroupedBackground) @@ -55,7 +55,7 @@ struct LocationCardView: View { } } - func locationCardContent(instance: Instance) -> some View { + private func locationCardContent(instance: Instance) -> some View { HStack(spacing: 16) { SquareURLImage(url: instance.world.imageUrl(.x512)) HStack { @@ -86,13 +86,13 @@ struct LocationCardView: View { .padding() } - func personAmount(_ instance: Instance) -> String { + private func personAmount(_ instance: Instance) -> String { [location.friends.count, instance.userCount, instance.capacity] .map { $0.description } .joined(separator: " / ") } - func friendThumbnail(friend: Friend) -> some View { + private func friendThumbnail(friend: Friend) -> some View { ZStack { Circle() .foregroundStyle(friend.status.color) diff --git a/harmonie/Views/Location/LocationsView.swift b/harmonie/Views/Location/LocationsView.swift index f2306e6..ff2325a 100644 --- a/harmonie/Views/Location/LocationsView.swift +++ b/harmonie/Views/Location/LocationsView.swift @@ -18,7 +18,7 @@ struct LocationsView: View, FriendServicePresentable { appVM.isDemoMode ? InstancePreviewService(client: appVM.client) : InstanceService(client: appVM.client) } - var backGroundColor: Color { + private var backGroundColor: Color { switch UIDevice.current.userInterfaceIdiom { case .pad: Color(uiColor: .secondarySystemGroupedBackground) @@ -58,7 +58,7 @@ struct LocationsView: View, FriendServicePresentable { } } - var locationList: some View { + private var locationList: some View { ScrollView { LazyVStack { ForEach(friendVM.friendsLocations) { location in @@ -84,7 +84,7 @@ struct LocationsView: View, FriendServicePresentable { } } - func locatoinItem(_ location: FriendsLocation) -> some View { + private func locatoinItem(_ location: FriendsLocation) -> some View { LocationCardView(selected: $selected, service: service, location: location) } } diff --git a/harmonie/Views/MainTabView.swift b/harmonie/Views/MainTabView.swift index 5f7b445..6ffb9b9 100644 --- a/harmonie/Views/MainTabView.swift +++ b/harmonie/Views/MainTabView.swift @@ -13,6 +13,10 @@ struct MainTabView: View, FriendServicePresentable, FavoriteServicePresentable { @Environment(FriendViewModel.self) var friendVM: FriendViewModel @Environment(FavoriteViewModel.self) var favoriteVM: FavoriteViewModel + enum Tab: String, CaseIterable { + case locations, friends, favorites, settings + } + var body: some View { TabView { ForEach(Tab.allCases) { tab in @@ -52,12 +56,6 @@ struct MainTabView: View, FriendServicePresentable, FavoriteServicePresentable { } } -extension MainTabView { - enum Tab: String, CaseIterable { - case locations, friends, favorites, settings - } -} - extension MainTabView.Tab { @ViewBuilder var content: some View { switch self { diff --git a/harmonie/Views/Setting/Profile/ProfileEditView.swift b/harmonie/Views/Setting/Profile/ProfileEditView.swift index a412963..a9262c5 100644 --- a/harmonie/Views/Setting/Profile/ProfileEditView.swift +++ b/harmonie/Views/Setting/Profile/ProfileEditView.swift @@ -52,7 +52,7 @@ struct ProfileEditView: View { } } - var statusSection: some View { + private var statusSection: some View { Section("Status") { Picker(selection: $profileEditVM.editingUserInfo.status) { ForEach(UserStatus.allCases) { status in @@ -73,13 +73,13 @@ struct ProfileEditView: View { } } - var descriptionSection: some View { + private var descriptionSection: some View { Section("Description") { TextEditor(text: $profileEditVM.editingUserInfo.bio) } } - @ViewBuilder var languageSection: some View { + @ViewBuilder private var languageSection: some View { Section("Language") { ForEach(profileEditVM.editingUserInfo.tags.languageTags) { tag in Text(tag.description) @@ -102,7 +102,7 @@ struct ProfileEditView: View { .listSectionSpacing(.compact) } - @ViewBuilder var bioLinksSection: some View { + @ViewBuilder private var bioLinksSection: some View { Section("Bio Links") { ForEach(profileEditVM.editingUserInfo.bioLinks) { url in Link(destination: url) { @@ -136,7 +136,7 @@ struct ProfileEditView: View { .listSectionSpacing(.compact) } - @ToolbarContentBuilder var toolbarContents: some ToolbarContent { + @ToolbarContentBuilder private var toolbarContents: some ToolbarContent { ToolbarItem(placement: .cancellationAction) { Button { dismiss() diff --git a/harmonie/Views/Setting/SettingsView+AboutSection.swift b/harmonie/Views/Setting/SettingsView+AboutSection.swift index fee9821..96a5874 100644 --- a/harmonie/Views/Setting/SettingsView+AboutSection.swift +++ b/harmonie/Views/Setting/SettingsView+AboutSection.swift @@ -8,11 +8,11 @@ import SwiftUI extension SettingsView { - var appName: String { + private var appName: String { Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? "" } - var appVersion: String { + private var appVersion: String { Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "" } diff --git a/harmonie/Views/Setting/SettingsView.swift b/harmonie/Views/Setting/SettingsView.swift index 6fce225..962b457 100644 --- a/harmonie/Views/Setting/SettingsView.swift +++ b/harmonie/Views/Setting/SettingsView.swift @@ -40,7 +40,7 @@ struct SettingsView: View, AuthenticationServicePresentable { } @ViewBuilder - func presentDestination(_ destination: Destination) -> some View { + private func presentDestination(_ destination: Destination) -> some View { switch destination { case .userDetail: if let user = appVM.user { @@ -55,7 +55,7 @@ struct SettingsView: View, AuthenticationServicePresentable { } } - var settingsContent: some View { + private var settingsContent: some View { List { if let user = appVM.user { profileSection(user: user) diff --git a/harmonie/Views/UserDetail/UserDetailView.swift b/harmonie/Views/UserDetail/UserDetailView.swift index c00c4b2..287dedf 100644 --- a/harmonie/Views/UserDetail/UserDetailView.swift +++ b/harmonie/Views/UserDetail/UserDetailView.swift @@ -44,7 +44,7 @@ struct UserDetailView: View, FavoriteServicePresentable { } } - var contentStacks: some View { + private var contentStacks: some View { VStack(spacing: 12) { locationSection noteSection From 9610af29ed568802060e00a708458c0bed04c698 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 8 Sep 2024 23:55:27 +0900 Subject: [PATCH 15/16] fix: remove test code --- harmonieTests/HarmonieTests.swift | 30 ---------------- harmonieUITests/HarmonieUITests.swift | 35 ------------------- .../HarmonieUITestsLaunchTests.swift | 26 -------------- 3 files changed, 91 deletions(-) diff --git a/harmonieTests/HarmonieTests.swift b/harmonieTests/HarmonieTests.swift index d3d93e7..b10d9f7 100644 --- a/harmonieTests/HarmonieTests.swift +++ b/harmonieTests/HarmonieTests.swift @@ -4,33 +4,3 @@ // // Created by makinosp on 2024/03/03. // - -import XCTest -@testable import Harmonie - -final class HarmonieTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - // Any test you write for XCTest can be annotated as throws and async. - // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. - // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/harmonieUITests/HarmonieUITests.swift b/harmonieUITests/HarmonieUITests.swift index 228655e..6333aac 100644 --- a/harmonieUITests/HarmonieUITests.swift +++ b/harmonieUITests/HarmonieUITests.swift @@ -4,38 +4,3 @@ // // Created by makinosp on 2024/03/03. // - -import XCTest - -final class HarmonieUITests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - - // In UI tests it is usually best to stop immediately when a failure occurs. - continueAfterFailure = false - - // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // UI tests must launch the application that they test. - let app = XCUIApplication() - app.launch() - - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testLaunchPerformance() throws { - if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { - // This measures how long it takes to launch your application. - measure(metrics: [XCTApplicationLaunchMetric()]) { - XCUIApplication().launch() - } - } - } -} diff --git a/harmonieUITests/HarmonieUITestsLaunchTests.swift b/harmonieUITests/HarmonieUITestsLaunchTests.swift index ffe5bff..9182e58 100644 --- a/harmonieUITests/HarmonieUITestsLaunchTests.swift +++ b/harmonieUITests/HarmonieUITestsLaunchTests.swift @@ -4,29 +4,3 @@ // // Created by makinosp on 2024/03/03. // - -import XCTest - -final class HarmonieUITestsLaunchTests: XCTestCase { - - override class var runsForEachTargetApplicationUIConfiguration: Bool { - true - } - - override func setUpWithError() throws { - continueAfterFailure = false - } - - func testLaunch() throws { - let app = XCUIApplication() - app.launch() - - // Insert steps here to perform after app launch but before taking a screenshot, - // such as logging into a test account or navigating somewhere in the app - - let attachment = XCTAttachment(screenshot: app.screenshot()) - attachment.name = "Launch Screen" - attachment.lifetime = .keepAlways - add(attachment) - } -} From 536fcef3f520fd0db520b3ab407212fb0730b174 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 8 Sep 2024 23:56:46 +0900 Subject: [PATCH 16/16] update: bump up to 0.5.0 --- Harmonie.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Harmonie.xcodeproj/project.pbxproj b/Harmonie.xcodeproj/project.pbxproj index d991fdf..b87b7eb 100644 --- a/Harmonie.xcodeproj/project.pbxproj +++ b/Harmonie.xcodeproj/project.pbxproj @@ -483,7 +483,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.4.2; + MARKETING_VERSION = 0.5.0; PRODUCT_BUNDLE_IDENTIFIER = jp.mknn.harmonie; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -518,7 +518,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.4.2; + MARKETING_VERSION = 0.5.0; PRODUCT_BUNDLE_IDENTIFIER = jp.mknn.harmonie; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES;