From 530b82e3f6346d036a78fadfc60edb8644e11dc6 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 27 Oct 2024 17:07:13 +0900 Subject: [PATCH 01/11] feat: remove task that relogin when back active --- harmonie/Views/MainTabView.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/harmonie/Views/MainTabView.swift b/harmonie/Views/MainTabView.swift index 0a68957..089f54b 100644 --- a/harmonie/Views/MainTabView.swift +++ b/harmonie/Views/MainTabView.swift @@ -61,9 +61,6 @@ struct MainTabView: View { switch scenePhase { case .active: restoreUserData() - Task { - if await appVM.login() == nil { return } - } case .background, .inactive: guard let user = appVM.user else { return } userData = user.rawValue From 81b4fcd5707246ac34e1a76b8e9d5be614fe2c40 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 27 Oct 2024 17:07:25 +0900 Subject: [PATCH 02/11] refact: code refactoring --- harmonie/Views/MainTabView.swift | 45 +++++++++++++++++--------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/harmonie/Views/MainTabView.swift b/harmonie/Views/MainTabView.swift index 089f54b..360fa1d 100644 --- a/harmonie/Views/MainTabView.swift +++ b/harmonie/Views/MainTabView.swift @@ -8,27 +8,6 @@ import SwiftUI import VRCKit -@MainActor -extension MainTabViewSegment { - @ViewBuilder var content: some View { - switch self { - case .social: LocationsView() - case .friends: FriendsView() - case .favorites: FavoritesView() - case .settings: SettingsView() - } - } - - var label: Label { - Label(description, systemImage: icon.systemName) - } - - @available(iOS 18, *) - var tab: some TabContent { - Tab(description, systemImage: icon.systemName, value: self) { content } - } -} - struct MainTabView: View { @Environment(\.scenePhase) var scenePhase @Environment(AppViewModel.self) var appVM: AppViewModel @@ -56,7 +35,31 @@ struct MainTabView: View { scenePhaseHandler(scenePhase) } } +} + +@MainActor +extension MainTabViewSegment { + @ViewBuilder var content: some View { + switch self { + case .social: LocationsView() + case .friends: FriendsView() + case .favorites: FavoritesView() + case .settings: SettingsView() + } + } + var label: Label { + Label(description, systemImage: icon.systemName) + } + + @available(iOS 18, *) + var tab: some TabContent { + Tab(description, systemImage: icon.systemName, value: self) { content } + } +} + +@MainActor +extension MainTabView { private func scenePhaseHandler(_ scenePhase: ScenePhase) { switch scenePhase { case .active: From 64509d4bf570d2cba6632b4e3414bf5bd05c627c Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 27 Oct 2024 17:33:38 +0900 Subject: [PATCH 03/11] feat: improvement LoginView, OtpView --- harmonie/Views/Authentication/HelpView.swift | 1 + harmonie/Views/Authentication/LoginView.swift | 12 +++++++----- harmonie/Views/Authentication/OtpView.swift | 3 ++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/harmonie/Views/Authentication/HelpView.swift b/harmonie/Views/Authentication/HelpView.swift index 43029f6..d68aea7 100644 --- a/harmonie/Views/Authentication/HelpView.swift +++ b/harmonie/Views/Authentication/HelpView.swift @@ -20,6 +20,7 @@ struct HelpView: View { VStack(spacing: 32) { ForEach(contents, id: \.hashValue) { content($0) } } + .multilineTextAlignment(.leading) .foregroundStyle(Color(.systemGray)) .padding() } diff --git a/harmonie/Views/Authentication/LoginView.swift b/harmonie/Views/Authentication/LoginView.swift index da46906..9d8826f 100644 --- a/harmonie/Views/Authentication/LoginView.swift +++ b/harmonie/Views/Authentication/LoginView.swift @@ -76,11 +76,13 @@ struct LoginView: View { private var loginFields: some View { VStack(alignment: .leading, spacing: 8) { - TextField("Username", text: $username) - .textInputAutocapitalization(.never) - .textFieldStyle(.roundedBorder) - SecureField("Password", text: $password) - .textFieldStyle(.roundedBorder) + Group { + TextField("Username", text: $username) + SecureField("Password", text: $password) + } + .textInputAutocapitalization(.never) + .textFieldStyle(.roundedBorder) + .multilineTextAlignment(.center) HStack { Text("Connect your VRChat account.") .font(.footnote) diff --git a/harmonie/Views/Authentication/OtpView.swift b/harmonie/Views/Authentication/OtpView.swift index b777225..3b98a7d 100644 --- a/harmonie/Views/Authentication/OtpView.swift +++ b/harmonie/Views/Authentication/OtpView.swift @@ -19,9 +19,9 @@ struct OtpView: View { .font(.headline) VStack { TextField("Code", text: $code) + .multilineTextAlignment(TextAlignment.center) .keyboardType(.decimalPad) .textFieldStyle(.roundedBorder) - .frame(maxWidth: 120) Text(explanation) .foregroundStyle(.gray) .font(.caption2) @@ -29,6 +29,7 @@ struct OtpView: View { .padding(.horizontal, 8) enterButton } + .frame(maxWidth: 560) .padding(32) .ignoresSafeArea(.keyboard) } From 1c4b99fb0a6e26a68efc1c54988f060bb0085740 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 27 Oct 2024 21:12:17 +0900 Subject: [PATCH 04/11] refact: remove unnecessary flag --- harmonie/ViewModels/AppViewModel.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/harmonie/ViewModels/AppViewModel.swift b/harmonie/ViewModels/AppViewModel.swift index 01bcbca..1dc0383 100644 --- a/harmonie/ViewModels/AppViewModel.swift +++ b/harmonie/ViewModels/AppViewModel.swift @@ -15,7 +15,6 @@ final class AppViewModel { var step: Step = .initializing var isPresentedAlert = false var vrckError: VRCKitError? - var isRequesting = false var services: APIServiceUtil var verifyType: VerifyType? @ObservationIgnored var client: APIClient @@ -82,9 +81,7 @@ final class AppViewModel { } func login() async -> Either? { - defer { isRequesting = false } var result: Either? - isRequesting = true do { result = try await services.authenticationService.loginUserInfo() } catch { From 4c941d2da479a057f264656b823283d274607012 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 27 Oct 2024 21:12:55 +0900 Subject: [PATCH 05/11] fix: layout of LocationsView --- harmonie/Views/Location/LocationsView.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/harmonie/Views/Location/LocationsView.swift b/harmonie/Views/Location/LocationsView.swift index fe54d26..f815dc4 100644 --- a/harmonie/Views/Location/LocationsView.swift +++ b/harmonie/Views/Location/LocationsView.swift @@ -106,8 +106,9 @@ struct LocationsView: View { VStack(alignment: .leading) { Text("Private Instances") .font(.body) + .lineLimit(1) Text(friendVM.friendsInPrivate.count.description) - .font(.footnote) + .font(.caption) .foregroundStyle(Color.gray) } .frame(maxWidth: .infinity, alignment: .leading) @@ -124,8 +125,8 @@ struct LocationsView: View { } } } + .padding(.top, 4) } - .padding(.top, 4) } } } @@ -148,3 +149,9 @@ fileprivate extension View { ) } } + +#Preview { + PreviewContainer { + LocationsView() + } +} From 353e11a50f4033bf1a73236b1168fe37882f2113 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 27 Oct 2024 21:13:20 +0900 Subject: [PATCH 06/11] feat: redacted locations in LocationsView --- harmonie/Previews/FriendsLocation.swift | 17 +++++++++ harmonie/ViewModels/FriendViewModel.swift | 8 ++++- harmonie/Views/Location/LocationsView.swift | 40 ++++++++++++++------- harmonie/Views/MainTabView.swift | 1 - 4 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 harmonie/Previews/FriendsLocation.swift diff --git a/harmonie/Previews/FriendsLocation.swift b/harmonie/Previews/FriendsLocation.swift new file mode 100644 index 0000000..3e90715 --- /dev/null +++ b/harmonie/Previews/FriendsLocation.swift @@ -0,0 +1,17 @@ +// +// FriendsLocation.swift +// Harmonie +// +// Created by makinosp on 2024/10/27. +// + +import VRCKit + +extension PreviewDataProvider { + static var friendsLocation: FriendsLocation { + FriendsLocation( + location: .offline, + friends: (0...5).map { _ in friend } + ) + } +} diff --git a/harmonie/ViewModels/FriendViewModel.swift b/harmonie/ViewModels/FriendViewModel.swift index fb6241d..f25906d 100644 --- a/harmonie/ViewModels/FriendViewModel.swift +++ b/harmonie/ViewModels/FriendViewModel.swift @@ -18,7 +18,7 @@ final class FriendViewModel { var filterFavoriteGroups: Set = [] var filterText: String = "" var sortType: SortType = .latest - var isRequesting = true + var isFetchingAllFriends = true var isProcessingFilter = false @ObservationIgnored let appVM: AppViewModel @ObservationIgnored var favoriteFriends: [FavoriteFriend] = [] @@ -51,6 +51,8 @@ final class FriendViewModel { /// Fetch friends from API func fetchAllFriends() async throws { + defer { isFetchingAllFriends = false } + isFetchingAllFriends = true guard let user = appVM.user else { throw ApplicationError.UserIsNotSetError } async let onlineFriendsTask = appVM.services.friendService.fetchFriends( count: user.onlineFriends.count + user.activeFriends.count, @@ -69,4 +71,8 @@ final class FriendViewModel { func setFavoriteFriends(_ favoriteFriends: [FavoriteFriend]) { self.favoriteFriends = favoriteFriends } + + var isContentUnavailable: Bool { + friendsLocations.isEmpty && !isFetchingAllFriends + } } diff --git a/harmonie/Views/Location/LocationsView.swift b/harmonie/Views/Location/LocationsView.swift index f815dc4..2bce956 100644 --- a/harmonie/Views/Location/LocationsView.swift +++ b/harmonie/Views/Location/LocationsView.swift @@ -18,6 +18,13 @@ struct LocationsView: View { var body: some View { NavigationSplitView(columnVisibility: $columnVisibility) { locationList + .overlay { + if friendVM.isContentUnavailable { + ContentUnavailableView { + Label("No Friend Location", systemImage: IconSet.friends.systemName) + } + } + } .navigationTitle("Social") .setColumn() } content: { @@ -48,7 +55,10 @@ struct LocationsView: View { } } .id(selection.selected.id) - } else { + } + } + .overlay { + if selection == nil { ContentUnavailableView { Label("Select a friend or world", systemImage: IconSet.info.systemName) } @@ -69,13 +79,8 @@ struct LocationsView: View { private var locationList: some View { List(selection: $selectedInstance) { friendLocations - inPrivateInstance - } - .overlay { - if friendVM.friendsLocations.isEmpty { - ContentUnavailableView { - Label("No Friend Location", systemImage: IconSet.friends.systemName) - } + if !friendVM.isFetchingAllFriends { + inPrivateInstance } } } @@ -83,11 +88,20 @@ struct LocationsView: View { @ViewBuilder private var friendLocations: some View { let friendsLocations = friendVM.friendsLocations.filter(\.location.isVisible) Section { - ForEach(friendsLocations) { location in - LocationCardView( - selected: $selectedInstance, - location: location - ) + if friendVM.isFetchingAllFriends { + ForEach(0...7, id: \.self) { _ in + LocationCardView( + selected: .constant(nil), + location: PreviewDataProvider.friendsLocation + ) + } + } else { + ForEach(friendsLocations) { location in + LocationCardView( + selected: $selectedInstance, + location: location + ) + } } } header: { HStack { diff --git a/harmonie/Views/MainTabView.swift b/harmonie/Views/MainTabView.swift index 360fa1d..35f6f17 100644 --- a/harmonie/Views/MainTabView.swift +++ b/harmonie/Views/MainTabView.swift @@ -97,7 +97,6 @@ extension MainTabView { private func fetchFriendsTask() async { do { - defer { friendVM.isRequesting = false } try await friendVM.fetchAllFriends() } catch { appVM.handleError(error) From c01670b33eeb340131394c488336902d286e95fa Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 27 Oct 2024 21:24:56 +0900 Subject: [PATCH 07/11] refact: refactoring LocationsView --- harmonie/Views/Location/LocationsView.swift | 105 +++++++++++--------- 1 file changed, 58 insertions(+), 47 deletions(-) diff --git a/harmonie/Views/Location/LocationsView.swift b/harmonie/Views/Location/LocationsView.swift index 2bce956..220ecd4 100644 --- a/harmonie/Views/Location/LocationsView.swift +++ b/harmonie/Views/Location/LocationsView.swift @@ -17,54 +17,11 @@ struct LocationsView: View { var body: some View { NavigationSplitView(columnVisibility: $columnVisibility) { - locationList - .overlay { - if friendVM.isContentUnavailable { - ContentUnavailableView { - Label("No Friend Location", systemImage: IconSet.friends.systemName) - } - } - } - .navigationTitle("Social") - .setColumn() + sidebar } content: { - Group { - if let selectedInstance = selectedInstance { - LocationDetailView( - selection: $selection, - instanceLocation: selectedInstance - ) - } else { - ContentUnavailableView { - Label("Select a location", systemImage: IconSet.location.systemName) - } - } - } - .setColumn() + content } detail: { - Group { - if let selection = selection { - Group { - switch selection.segment { - case .friends: - UserDetailPresentationView(selected: selection.selected) - case .world: - WorldPresentationView(id: selection.selected.id) - default: - EmptyView() - } - } - .id(selection.selected.id) - } - } - .overlay { - if selection == nil { - ContentUnavailableView { - Label("Select a friend or world", systemImage: IconSet.info.systemName) - } - } - } - .setColumn() + detail } .navigationSplitViewStyle(.balanced) .refreshable { @@ -76,13 +33,67 @@ struct LocationsView: View { } } - private var locationList: some View { + private var sidebar: some View { List(selection: $selectedInstance) { friendLocations if !friendVM.isFetchingAllFriends { inPrivateInstance } } + .overlay { + if friendVM.isContentUnavailable { + ContentUnavailableView { + Label("No Friend Location", systemImage: IconSet.friends.systemName) + } + } + } + .navigationTitle("Social") + .setColumn() + } + + private var content: some View { + Group { + if let selectedInstance = selectedInstance { + LocationDetailView( + selection: $selection, + instanceLocation: selectedInstance + ) + } + } + .overlay { + if selectedInstance == nil { + ContentUnavailableView { + Label("Select a location", systemImage: IconSet.location.systemName) + } + } + } + .setColumn() + } + + private var detail: some View { + Group { + if let selection = selection { + Group { + switch selection.segment { + case .friends: + UserDetailPresentationView(selected: selection.selected) + case .world: + WorldPresentationView(id: selection.selected.id) + default: + EmptyView() + } + } + .id(selection.selected.id) + } + } + .overlay { + if selection == nil { + ContentUnavailableView { + Label("Select a friend or world", systemImage: IconSet.info.systemName) + } + } + } + .setColumn() } @ViewBuilder private var friendLocations: some View { From a211bd783992ec428044e0edb10cd77d921f4222 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 27 Oct 2024 21:37:02 +0900 Subject: [PATCH 08/11] refact: make horizontal profile view component --- .../Components/HorizontalProfileImages.swift | 27 +++++++++++++++++++ .../Views/Location/LocationCardView.swift | 15 +++-------- harmonie/Views/Location/LocationsView.swift | 12 ++------- 3 files changed, 32 insertions(+), 22 deletions(-) create mode 100644 harmonie/Components/HorizontalProfileImages.swift diff --git a/harmonie/Components/HorizontalProfileImages.swift b/harmonie/Components/HorizontalProfileImages.swift new file mode 100644 index 0000000..48750a0 --- /dev/null +++ b/harmonie/Components/HorizontalProfileImages.swift @@ -0,0 +1,27 @@ +// +// HorizontalProfileImages.swift +// Harmonie +// +// Created by makinosp on 2024/10/27. +// + +import SwiftUI +import MemberwiseInit +import VRCKit + +@MemberwiseInit +struct HorizontalProfileImages: View where T: ProfileElementRepresentable { + @Init(.internal, label: "_") private let profiles: [T] + var body: some View { + ScrollView(.horizontal) { + LazyHStack(spacing: -8) { + ForEach(profiles) { profile in + CircleURLImage( + imageUrl: profile.imageUrl(.x256), + size: Constants.IconSize.thumbnail + ) + } + } + } + } +} diff --git a/harmonie/Views/Location/LocationCardView.swift b/harmonie/Views/Location/LocationCardView.swift index 9ed8923..87612f8 100644 --- a/harmonie/Views/Location/LocationCardView.swift +++ b/harmonie/Views/Location/LocationCardView.swift @@ -63,19 +63,10 @@ struct LocationCardView: View { .frame(maxWidth: .infinity, alignment: .leading) NavigationLabel() } - ScrollView(.horizontal) { - LazyHStack(spacing: -8) { - ForEach(location.friends) { friend in - CircleURLImage( - imageUrl: friend.imageUrl(.x256), - size: Constants.IconSize.thumbnail - ) - } + HorizontalProfileImages(location.friends) + .onTapGesture { + selected = tag(instance) } - } - .onTapGesture { - selected = tag(instance) - } } .padding(.top, 4) } diff --git a/harmonie/Views/Location/LocationsView.swift b/harmonie/Views/Location/LocationsView.swift index 220ecd4..e6131d9 100644 --- a/harmonie/Views/Location/LocationsView.swift +++ b/harmonie/Views/Location/LocationsView.swift @@ -118,6 +118,7 @@ struct LocationsView: View { HStack { Text("Friend Locations") Text("(\(friendsLocations.count.description))") + .redacted(reason: friendVM.isFetchingAllFriends ? .placeholder : []) } } } @@ -139,16 +140,7 @@ struct LocationsView: View { .frame(maxWidth: .infinity, alignment: .leading) NavigationLabel() } - ScrollView(.horizontal) { - LazyHStack(spacing: -8) { - ForEach(friendVM.friendsInPrivate) { friend in - CircleURLImage( - imageUrl: friend.imageUrl(.x256), - size: Constants.IconSize.thumbnail - ) - } - } - } + HorizontalProfileImages(friendVM.friendsInPrivate) } .padding(.top, 4) } From ca3e41ef113e5948b1075d016392486932f5309e Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 27 Oct 2024 21:41:01 +0900 Subject: [PATCH 09/11] refact: code refactoring --- harmonie/ViewModels/FriendViewModel.swift | 4 ++++ harmonie/Views/Location/LocationsView.swift | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/harmonie/ViewModels/FriendViewModel.swift b/harmonie/ViewModels/FriendViewModel.swift index f25906d..7878040 100644 --- a/harmonie/ViewModels/FriendViewModel.swift +++ b/harmonie/ViewModels/FriendViewModel.swift @@ -49,6 +49,10 @@ final class FriendViewModel { } } + var visibleFriendsLocations: [FriendsLocation] { + friendsLocations.filter(\.location.isVisible) + } + /// Fetch friends from API func fetchAllFriends() async throws { defer { isFetchingAllFriends = false } diff --git a/harmonie/Views/Location/LocationsView.swift b/harmonie/Views/Location/LocationsView.swift index e6131d9..0d63194 100644 --- a/harmonie/Views/Location/LocationsView.swift +++ b/harmonie/Views/Location/LocationsView.swift @@ -97,7 +97,6 @@ struct LocationsView: View { } @ViewBuilder private var friendLocations: some View { - let friendsLocations = friendVM.friendsLocations.filter(\.location.isVisible) Section { if friendVM.isFetchingAllFriends { ForEach(0...7, id: \.self) { _ in @@ -107,7 +106,7 @@ struct LocationsView: View { ) } } else { - ForEach(friendsLocations) { location in + ForEach(friendVM.visibleFriendsLocations) { location in LocationCardView( selected: $selectedInstance, location: location @@ -117,7 +116,7 @@ struct LocationsView: View { } header: { HStack { Text("Friend Locations") - Text("(\(friendsLocations.count.description))") + Text(verbatim: "(\(friendVM.visibleFriendsLocations.count))") .redacted(reason: friendVM.isFetchingAllFriends ? .placeholder : []) } } From 9c2df315b2761d76fea0385ef244e6d1717f995f Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 27 Oct 2024 21:41:34 +0900 Subject: [PATCH 10/11] update: localization --- harmonie/Localization/Localizable.xcstrings | 3 --- 1 file changed, 3 deletions(-) diff --git a/harmonie/Localization/Localizable.xcstrings b/harmonie/Localization/Localizable.xcstrings index 8b7d1aa..17c18ec 100644 --- a/harmonie/Localization/Localizable.xcstrings +++ b/harmonie/Localization/Localizable.xcstrings @@ -4,9 +4,6 @@ "" : { "shouldTranslate" : false }, - "(%@)" : { - "shouldTranslate" : false - }, "%@ / %@" : { "localizations" : { "en" : { From b25ed82a4975d1e254b1d448d75f6f71c286526b Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 27 Oct 2024 21:42:13 +0900 Subject: [PATCH 11/11] update: bump up to 0.9.5 --- 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 9c796ba..f24a272 100644 --- a/Harmonie.xcodeproj/project.pbxproj +++ b/Harmonie.xcodeproj/project.pbxproj @@ -473,7 +473,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.9.4; + MARKETING_VERSION = 0.9.5; PRODUCT_BUNDLE_IDENTIFIER = jp.mknn.harmonie; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -509,7 +509,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.9.4; + MARKETING_VERSION = 0.9.5; PRODUCT_BUNDLE_IDENTIFIER = jp.mknn.harmonie; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES;