From fbc7552e7169e403c4014e2b1202bcea596f232d Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 18 Aug 2024 19:30:38 +0900 Subject: [PATCH 01/34] feat: change screen transition of FavoritesView from sheet to NavigationSplitView --- harmonie/Views/Favorite/FavoritesView.swift | 34 ++++++++++++--------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/harmonie/Views/Favorite/FavoritesView.swift b/harmonie/Views/Favorite/FavoritesView.swift index b122ce6..ab10cdb 100644 --- a/harmonie/Views/Favorite/FavoritesView.swift +++ b/harmonie/Views/Favorite/FavoritesView.swift @@ -14,7 +14,7 @@ struct FavoritesView: View { @State var isFetching = false var body: some View { - NavigationSplitView { + NavigationSplitView(columnVisibility: .constant(.all)) { if !isFetching { List { ForEach(favoriteVM.favoriteFriendGroups) { group in @@ -27,10 +27,8 @@ struct FavoritesView: View { } } } - .sheet(item: $selected) { selected in + .navigationDestination(item: $selected) { selected in UserDetailPresentationView(id: selected.id) - .presentationDetents([.medium, .large]) - .presentationBackground(Color(UIColor.systemGroupedBackground)) } .toolbar { ToolbarItem(placement: .principal) { @@ -48,22 +46,28 @@ struct FavoritesView: View { .navigationTitle("Favorites") } } detail: { - EmptyView() + Text("Select a friend") } + .navigationSplitViewStyle(.balanced) } func rowView(_ friend: Friend) -> some View { - HStack { - CircleURLImage( - imageUrl: friend.thumbnailUrl, - size: Constants.IconSize.thumbnail - ) - Text(friend.displayName) - } - .frame(maxWidth: .infinity, alignment: .leading) - .contentShape(Rectangle()) - .onTapGesture { + Button { selected = Selected(id: friend.id) + } label: { + HStack { + CircleURLImage( + imageUrl: friend.thumbnailUrl, + size: Constants.IconSize.thumbnail + ) + Text(friend.displayName) + Spacer() + Image(systemName: "chevron.right") + .foregroundStyle(Color(uiColor: .systemGray)) + .imageScale(.small) + } + .frame(maxWidth: .infinity, alignment: .leading) + .contentShape(Rectangle()) } } } From 62157246b4920ac42d69ff2fd0286c9b18a1972a Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 18 Aug 2024 19:36:40 +0900 Subject: [PATCH 02/34] refact: replace Color initializers using UIColor --- harmonie/Components/ProgressScreen.swift | 2 +- harmonie/Extensions/View+SectionModifier.swift | 2 +- harmonie/Views/Authentication/AuthenticationView.swift | 2 +- harmonie/Views/Location/LocationCardView.swift | 4 ++-- harmonie/Views/Location/LocationsView.swift | 4 ++-- harmonie/Views/UserDetail/UserDetailView.swift | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/harmonie/Components/ProgressScreen.swift b/harmonie/Components/ProgressScreen.swift index 855896b..71699bf 100644 --- a/harmonie/Components/ProgressScreen.swift +++ b/harmonie/Components/ProgressScreen.swift @@ -10,7 +10,7 @@ import SwiftUI struct ProgressScreen: View { var body: some View { ZStack { - Color(UIColor.systemBackground) + Color(uiColor: .systemBackground) ProgressView() .controlSize(.large) } diff --git a/harmonie/Extensions/View+SectionModifier.swift b/harmonie/Extensions/View+SectionModifier.swift index 5af45a0..a91562c 100644 --- a/harmonie/Extensions/View+SectionModifier.swift +++ b/harmonie/Extensions/View+SectionModifier.swift @@ -20,7 +20,7 @@ private struct SectionModifier: ViewModifier { .padding() .background { RoundedRectangle(cornerRadius: 8) - .foregroundStyle(Color(UIColor.secondarySystemGroupedBackground)) + .foregroundStyle(Color(uiColor: .secondarySystemGroupedBackground)) } } } diff --git a/harmonie/Views/Authentication/AuthenticationView.swift b/harmonie/Views/Authentication/AuthenticationView.swift index c94d326..b88c979 100644 --- a/harmonie/Views/Authentication/AuthenticationView.swift +++ b/harmonie/Views/Authentication/AuthenticationView.swift @@ -63,7 +63,7 @@ struct AuthenticationView: View { .padding(.horizontal, 8) .background( RoundedRectangle(cornerRadius: 8) - .foregroundStyle(Color(UIColor.systemBackground)) + .foregroundStyle(Color(uiColor: .systemBackground)) ) } diff --git a/harmonie/Views/Location/LocationCardView.swift b/harmonie/Views/Location/LocationCardView.swift index 3b3ae60..83fdf96 100644 --- a/harmonie/Views/Location/LocationCardView.swift +++ b/harmonie/Views/Location/LocationCardView.swift @@ -20,9 +20,9 @@ struct LocationCardView: View { var backGroundColor: Color { switch UIDevice.current.userInterfaceIdiom { case .pad: - Color(UIColor.tertiarySystemGroupedBackground) + Color(uiColor: .tertiarySystemGroupedBackground) default: - Color(UIColor.secondarySystemGroupedBackground) + Color(uiColor: .secondarySystemGroupedBackground) } } diff --git a/harmonie/Views/Location/LocationsView.swift b/harmonie/Views/Location/LocationsView.swift index 38d3c0f..4c69bc9 100644 --- a/harmonie/Views/Location/LocationsView.swift +++ b/harmonie/Views/Location/LocationsView.swift @@ -27,9 +27,9 @@ struct LocationsView: View { var backGroundColor: Color { switch UIDevice.current.userInterfaceIdiom { case .pad: - Color(UIColor.secondarySystemGroupedBackground) + Color(uiColor: .secondarySystemGroupedBackground) default: - Color(UIColor.systemGroupedBackground) + Color(uiColor: .systemGroupedBackground) } } diff --git a/harmonie/Views/UserDetail/UserDetailView.swift b/harmonie/Views/UserDetail/UserDetailView.swift index 4485564..ba0b1c4 100644 --- a/harmonie/Views/UserDetail/UserDetailView.swift +++ b/harmonie/Views/UserDetail/UserDetailView.swift @@ -122,7 +122,7 @@ struct UserDetailView: View { .padding(.horizontal, 8) .background( RoundedRectangle(cornerRadius: 8) - .foregroundStyle(Color(UIColor.systemBackground).opacity(0.25)) + .foregroundStyle(Color(uiColor: .systemBackground).opacity(0.25)) ) } else { Text(user.statusDescription) From 28f442e9d1b17a555a14e4c533e5efb883729534 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 18 Aug 2024 19:38:14 +0900 Subject: [PATCH 03/34] refact: lint --- harmonie/ViewModels/FavoriteViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/harmonie/ViewModels/FavoriteViewModel.swift b/harmonie/ViewModels/FavoriteViewModel.swift index 7f66a2d..7a68b07 100644 --- a/harmonie/ViewModels/FavoriteViewModel.swift +++ b/harmonie/ViewModels/FavoriteViewModel.swift @@ -19,7 +19,7 @@ class FavoriteViewModel: ObservableObject { @Published var segment: Segment = .friend let friendVM: FriendViewModel let service: any FavoriteServiceProtocol - + enum Segment { case friend, world } From 801961091c9a20828a9d1c3f6f982913fe238f6f Mon Sep 17 00:00:00 2001 From: makinosp Date: Sun, 18 Aug 2024 19:43:05 +0900 Subject: [PATCH 04/34] refact: format code in FavoriteView --- harmonie/Views/Favorite/FavoritesView.swift | 54 ++++++++++----------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/harmonie/Views/Favorite/FavoritesView.swift b/harmonie/Views/Favorite/FavoritesView.swift index ab10cdb..8a9a1ab 100644 --- a/harmonie/Views/Favorite/FavoritesView.swift +++ b/harmonie/Views/Favorite/FavoritesView.swift @@ -11,44 +11,42 @@ import VRCKit struct FavoritesView: View { @EnvironmentObject var favoriteVM: FavoriteViewModel @State var selected: Selected? - @State var isFetching = false var body: some View { NavigationSplitView(columnVisibility: .constant(.all)) { - if !isFetching { - List { - ForEach(favoriteVM.favoriteFriendGroups) { group in - if let friends = favoriteVM.getFavoriteFriends(group.id) { - Section(header: Text(group.displayName)) { - ForEach(friends) { friend in - rowView(friend) - } - } + listView + } detail: { + Text("Select a friend") + } + .navigationSplitViewStyle(.balanced) + } + + var listView: some View { + List { + ForEach(favoriteVM.favoriteFriendGroups) { group in + if let friends = favoriteVM.getFavoriteFriends(group.id) { + Section(header: Text(group.displayName)) { + ForEach(friends) { friend in + rowView(friend) } } } - .navigationDestination(item: $selected) { selected in - UserDetailPresentationView(id: selected.id) - } - .toolbar { - ToolbarItem(placement: .principal) { - Picker("", selection: $favoriteVM.segment) { - ForEach(FavoriteViewModel.Segment.allCases) { segment in - Text(segment.description).tag(segment) - } - } - .pickerStyle(SegmentedPickerStyle()) + } + } + .navigationDestination(item: $selected) { selected in + UserDetailPresentationView(id: selected.id) + } + .toolbar { + ToolbarItem(placement: .principal) { + Picker("", selection: $favoriteVM.segment) { + ForEach(FavoriteViewModel.Segment.allCases) { segment in + Text(segment.description).tag(segment) } } - .navigationTitle("Favorites") - } else { - ProgressScreen() - .navigationTitle("Favorites") + .pickerStyle(SegmentedPickerStyle()) } - } detail: { - Text("Select a friend") } - .navigationSplitViewStyle(.balanced) + .navigationTitle("Favorites") } func rowView(_ friend: Friend) -> some View { From 622f897c3986ffa5587b11298fdd98b8f5025c22 Mon Sep 17 00:00:00 2001 From: makinosp Date: Mon, 19 Aug 2024 11:31:18 +0900 Subject: [PATCH 05/34] feat: move favorite menu into toolbar of UserDetailView --- harmonie/Utils/Constants.swift | 1 + .../Views/UserDetail/UserDetailView.swift | 42 ++++++++++--------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/harmonie/Utils/Constants.swift b/harmonie/Utils/Constants.swift index c30da7e..a7eba05 100644 --- a/harmonie/Utils/Constants.swift +++ b/harmonie/Utils/Constants.swift @@ -17,6 +17,7 @@ enum Constants { enum IconName { static let check = "checkmark" static let circleFilled = "circle.fill" + static let dots = "ellipsis.circle" static let exclamation = "exclamationmark.circle" static let loctaion = "location.fill" static let filter = "line.3.horizontal.decrease" diff --git a/harmonie/Views/UserDetail/UserDetailView.swift b/harmonie/Views/UserDetail/UserDetailView.swift index ba0b1c4..65be32a 100644 --- a/harmonie/Views/UserDetail/UserDetailView.swift +++ b/harmonie/Views/UserDetail/UserDetailView.swift @@ -35,6 +35,27 @@ struct UserDetailView: View { contentStacks } } + .navigationTitle(user.displayName) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Menu("Actions", systemImage: Constants.IconName.dots) { + if user.isFriend { + Menu { + ForEach(favoriteVM.favoriteFriendGroups) { group in + favoriteMenuItem(group: group) + } + } label: { + Label { + Text("Favorite") + } icon: { + Image(systemName: favoriteIconName) + } + } + } + } + } + } .task { if user.isVisible { await fetchInstance() @@ -104,10 +125,6 @@ struct UserDetailView: View { .font(.headline) statusDescription } - Spacer() - if user.isFriend { - favoriteMenu - } } .padding(.vertical, 8) .padding(.horizontal, 12) @@ -126,6 +143,7 @@ struct UserDetailView: View { ) } else { Text(user.statusDescription) + .lineLimit(1) .font(.subheadline) } } @@ -136,22 +154,6 @@ struct UserDetailView: View { : Constants.IconName.favorite } - var favoriteMenu: some View { - Menu { - ForEach(favoriteVM.favoriteFriendGroups) { group in - favoriteMenuItem(group: group) - } - } label: { - Image(systemName: favoriteIconName) - .frame(size: CGSize(width: 12, height: 12)) - .padding(12) - .background { - Circle() - .foregroundStyle(Material.regularMaterial) - } - } - } - func favoriteMenuItem(group: FavoriteGroup) -> some View { AsyncButton { await updateFavorite(friendId: user.id, group: group) From f7cbccb364f5aee6dbfb90ae47f3aa779b35faa6 Mon Sep 17 00:00:00 2001 From: makinosp Date: Mon, 19 Aug 2024 11:35:41 +0900 Subject: [PATCH 06/34] refact: cut out toolbar implementation of UserDetailView into a file --- Harmonie.xcodeproj/project.pbxproj | 4 ++ .../UserDetail/UserDetailView+Toolbar.swift | 54 +++++++++++++++++++ .../Views/UserDetail/UserDetailView.swift | 43 +-------------- 3 files changed, 59 insertions(+), 42 deletions(-) create mode 100644 harmonie/Views/UserDetail/UserDetailView+Toolbar.swift diff --git a/Harmonie.xcodeproj/project.pbxproj b/Harmonie.xcodeproj/project.pbxproj index 9732ace..292c888 100644 --- a/Harmonie.xcodeproj/project.pbxproj +++ b/Harmonie.xcodeproj/project.pbxproj @@ -29,6 +29,7 @@ 20460CB02C56215700B276E3 /* Selected.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20460CAF2C56215700B276E3 /* Selected.swift */; }; 2052C7AE2C4BC97100B341D1 /* VRCKit in Frameworks */ = {isa = PBXBuildFile; productRef = 2052C7AD2C4BC97100B341D1 /* VRCKit */; }; 20592B032C45640D00E1C8B8 /* AsyncSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 20592B022C45640D00E1C8B8 /* AsyncSwiftUI */; }; + 205AA45E2C72E61B008CCC8A /* UserDetailView+Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 205AA45D2C72E61B008CCC8A /* UserDetailView+Toolbar.swift */; }; 205B3BF22C00D51A0097D62A /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 205B3BF12C00D51A0097D62A /* SettingsView.swift */; }; 205B3BF42C00D8BF0097D62A /* ProgressScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 205B3BF32C00D8BF0097D62A /* ProgressScreen.swift */; }; 205C9DAF2C20612500E3782C /* Binding+NilCoalescing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 205C9DAE2C20612500E3782C /* Binding+NilCoalescing.swift */; }; @@ -98,6 +99,7 @@ 20395DC32B94653100003921 /* FriendsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsView.swift; sourceTree = ""; }; 20460CAD2C561ED200B276E3 /* UserDetailPresentationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UserDetailPresentationView.swift; path = harmonie/Views/UserDetail/UserDetailPresentationView.swift; sourceTree = SOURCE_ROOT; }; 20460CAF2C56215700B276E3 /* Selected.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Selected.swift; path = Harmonie/Utils/Selected.swift; sourceTree = SOURCE_ROOT; }; + 205AA45D2C72E61B008CCC8A /* UserDetailView+Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "UserDetailView+Toolbar.swift"; path = "Harmonie/Views/UserDetail/UserDetailView+Toolbar.swift"; sourceTree = SOURCE_ROOT; }; 205B3BF12C00D51A0097D62A /* SettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 205B3BF32C00D8BF0097D62A /* ProgressScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressScreen.swift; sourceTree = ""; }; 205C9DAE2C20612500E3782C /* Binding+NilCoalescing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+NilCoalescing.swift"; sourceTree = ""; }; @@ -281,6 +283,7 @@ 200723962BA56377002C27E8 /* UserDetailView.swift */, 20F95D272C69BC2D002CDC5F /* UserDetailView+Actions.swift */, 20F95D292C69BC4B002CDC5F /* UserDetailView+ActivitySection.swift */, + 205AA45D2C72E61B008CCC8A /* UserDetailView+Toolbar.swift */, 20460CAD2C561ED200B276E3 /* UserDetailPresentationView.swift */, ); path = UserDetail; @@ -501,6 +504,7 @@ 20102BCF2C157C77007738D3 /* HarmonieApp.swift in Sources */, 20460CAE2C561ED200B276E3 /* UserDetailPresentationView.swift in Sources */, 209A788A2C2FC0FA00BEFAE6 /* FavoriteViewModel.swift in Sources */, + 205AA45E2C72E61B008CCC8A /* UserDetailView+Toolbar.swift in Sources */, 207FC6372C4256E60018E605 /* View+SectionModifier.swift in Sources */, 20395D972B945CD700003921 /* ContentView.swift in Sources */, 200FA7AD2C1D61AB0001E655 /* CircleURLImage.swift in Sources */, diff --git a/harmonie/Views/UserDetail/UserDetailView+Toolbar.swift b/harmonie/Views/UserDetail/UserDetailView+Toolbar.swift new file mode 100644 index 0000000..34468dc --- /dev/null +++ b/harmonie/Views/UserDetail/UserDetailView+Toolbar.swift @@ -0,0 +1,54 @@ +// +// UserDetailView+Toolbar.swift +// Harmonie +// +// Created by makinosp on 2024/08/19. +// + +import AsyncSwiftUI +import VRCKit + +extension UserDetailView { + var toolbar: some ToolbarContent { + ToolbarItem(placement: .topBarTrailing) { + Menu("Actions", systemImage: Constants.IconName.dots) { + if user.isFriend { + Menu { + ForEach(favoriteVM.favoriteFriendGroups) { group in + favoriteMenuItem(group: group) + } + } label: { + Label { + Text("Favorite") + } icon: { + Image(systemName: favoriteIconName) + } + } + } + } + } + } + + func favoriteMenuItem(group: FavoriteGroup) -> some View { + AsyncButton { + await updateFavorite(friendId: user.id, group: group) + } label: { + Label { + Text(group.displayName) + } icon: { + if favoriteVM.isFriendInFavoriteGroup( + friendId: user.id, + groupId: group.id + ) { + Image(systemName: Constants.IconName.check) + } + } + } + } + + var favoriteIconName: String { + favoriteVM.isAdded(friendId: user.id) + ? Constants.IconName.favoriteFilled + : Constants.IconName.favorite + } +} diff --git a/harmonie/Views/UserDetail/UserDetailView.swift b/harmonie/Views/UserDetail/UserDetailView.swift index 65be32a..5884d13 100644 --- a/harmonie/Views/UserDetail/UserDetailView.swift +++ b/harmonie/Views/UserDetail/UserDetailView.swift @@ -37,25 +37,7 @@ struct UserDetailView: View { } .navigationTitle(user.displayName) .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .topBarTrailing) { - Menu("Actions", systemImage: Constants.IconName.dots) { - if user.isFriend { - Menu { - ForEach(favoriteVM.favoriteFriendGroups) { group in - favoriteMenuItem(group: group) - } - } label: { - Label { - Text("Favorite") - } icon: { - Image(systemName: favoriteIconName) - } - } - } - } - } - } + .toolbar { toolbar } .task { if user.isVisible { await fetchInstance() @@ -148,29 +130,6 @@ struct UserDetailView: View { } } - var favoriteIconName: String { - favoriteVM.isAdded(friendId: user.id) - ? Constants.IconName.favoriteFilled - : Constants.IconName.favorite - } - - func favoriteMenuItem(group: FavoriteGroup) -> some View { - AsyncButton { - await updateFavorite(friendId: user.id, group: group) - } label: { - Label { - Text(group.displayName) - } icon: { - if favoriteVM.isFriendInFavoriteGroup( - friendId: user.id, - groupId: group.id - ) { - Image(systemName: Constants.IconName.check) - } - } - } - } - var displayStatusAndName: some View { HStack(alignment: .bottom) { Label { From 212bcffa67c7d523e0a7b6ea9e4a2e8e7514f15f Mon Sep 17 00:00:00 2001 From: makinosp Date: Tue, 20 Aug 2024 13:41:03 +0900 Subject: [PATCH 07/34] refact: common forward icon --- harmonie/Utils/Constants.swift | 10 +++++++++- harmonie/Views/Favorite/FavoritesView.swift | 4 +--- harmonie/Views/Friend/FriendsView.swift | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/harmonie/Utils/Constants.swift b/harmonie/Utils/Constants.swift index a7eba05..e29f14f 100644 --- a/harmonie/Utils/Constants.swift +++ b/harmonie/Utils/Constants.swift @@ -5,9 +5,17 @@ // Created by makinosp on 2024/07/22. // -import Foundation +import SwiftUI enum Constants { + enum Icon { + static var forward: some View { + Image(systemName: "chevron.forward") + .foregroundStyle(Color(uiColor: .systemGray)) + .imageScale(.small) + } + } + enum IconSize { static let thumbnail = CGSize(width: 28, height: 28) static let thumbnailOutside = CGSize(width: 32, height: 32) diff --git a/harmonie/Views/Favorite/FavoritesView.swift b/harmonie/Views/Favorite/FavoritesView.swift index 8a9a1ab..7904656 100644 --- a/harmonie/Views/Favorite/FavoritesView.swift +++ b/harmonie/Views/Favorite/FavoritesView.swift @@ -60,9 +60,7 @@ struct FavoritesView: View { ) Text(friend.displayName) Spacer() - Image(systemName: "chevron.right") - .foregroundStyle(Color(uiColor: .systemGray)) - .imageScale(.small) + Constants.Icon.forward } .frame(maxWidth: .infinity, alignment: .leading) .contentShape(Rectangle()) diff --git a/harmonie/Views/Friend/FriendsView.swift b/harmonie/Views/Friend/FriendsView.swift index 93106df..5e1564f 100644 --- a/harmonie/Views/Friend/FriendsView.swift +++ b/harmonie/Views/Friend/FriendsView.swift @@ -51,7 +51,7 @@ struct FriendsView: View { } Text(friend.displayName) Spacer() - Image(systemName: "chevron.right") + Constants.Icon.forward } .frame(maxWidth: .infinity, alignment: .leading) .contentShape(Rectangle()) From 3b2d3e89544a7cf80455c4279ecccf51f08cc44b Mon Sep 17 00:00:00 2001 From: makinosp Date: Tue, 20 Aug 2024 15:16:48 +0900 Subject: [PATCH 08/34] feat: update UI of SettingsView --- harmonie/Views/Setting/SettingsView.swift | 66 +++++++++++++++++------ 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/harmonie/Views/Setting/SettingsView.swift b/harmonie/Views/Setting/SettingsView.swift index 5734324..6c43e0f 100644 --- a/harmonie/Views/Setting/SettingsView.swift +++ b/harmonie/Views/Setting/SettingsView.swift @@ -50,22 +50,7 @@ struct SettingsView: View { var settingsContent: some View { List { if let user = appVM.user { - Section(header: Text("My Profile")) { - Button { - destination = .userDetail - } label: { - HStack { - CircleURLImage( - imageUrl: user.thumbnailUrl, - size: Constants.IconSize.ll - ) - Text(user.displayName) - } - .frame(maxWidth: .infinity, alignment: .leading) - .contentShape(Rectangle()) - } - } - .textCase(nil) + profileSection(user: user) } Section(header: Text("Open Source License Notice")) { Link(destination: URL(string: "https://github.com/makinosp/harmonie")!) { @@ -101,6 +86,55 @@ struct SettingsView: View { } } } + + func profileSection(user: User) -> some View { + Section(header: Text("My Profile")) { + Button { + destination = .userDetail + } label: { + HStack(alignment: .center) { + Label { + Text(user.displayName) + } icon: { + CircleURLImage( + imageUrl: user.thumbnailUrl, + size: Constants.IconSize.ll + ) + } + Spacer() + Constants.Icon.forward + } + .frame(maxWidth: .infinity, alignment: .leading) + .contentShape(Rectangle()) + } + Button {} label: { + HStack(alignment: .center) { + Label { + Text("Status") + } icon: { + Image(systemName: Constants.IconName.circleFilled) + .foregroundStyle(user.status.color) + } + Spacer() + Text(user.status.description) + .foregroundStyle(Color(uiColor: .secondaryLabel)) + .font(.callout) + Constants.Icon.forward + } + } + Button {} label: { + HStack(alignment: .center) { + Label("Description", systemImage: "quote.bubble") + Spacer() + Text(user.statusDescription) + .foregroundStyle(Color(uiColor: .secondaryLabel)) + .font(.callout) + Constants.Icon.forward + } + } + } + .textCase(nil) + } var appName: String { Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? "" From 6e6edbe58524ff35f45d352fa83f8e6999856b78 Mon Sep 17 00:00:00 2001 From: makinosp Date: Wed, 21 Aug 2024 21:11:24 +0900 Subject: [PATCH 09/34] refact: cut out profile section of SettingsView into a file --- Harmonie.xcodeproj/project.pbxproj | 4 ++ .../Setting/SettingsView+ProfileSection.swift | 42 ++++++++++++++++ harmonie/Views/Setting/SettingsView.swift | 49 ------------------- 3 files changed, 46 insertions(+), 49 deletions(-) create mode 100644 harmonie/Views/Setting/SettingsView+ProfileSection.swift diff --git a/Harmonie.xcodeproj/project.pbxproj b/Harmonie.xcodeproj/project.pbxproj index 292c888..5846653 100644 --- a/Harmonie.xcodeproj/project.pbxproj +++ b/Harmonie.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 200723972BA56377002C27E8 /* UserDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 200723962BA56377002C27E8 /* UserDetailView.swift */; }; + 200BC2D72C74B49A0025872B /* SettingsView+ProfileSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 200BC2D62C74B49A0025872B /* SettingsView+ProfileSection.swift */; }; 200FA7AD2C1D61AB0001E655 /* CircleURLImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 200FA7AC2C1D61AB0001E655 /* CircleURLImage.swift */; }; 200FA7B02C1D745B0001E655 /* LocationCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 200FA7AF2C1D745B0001E655 /* LocationCardView.swift */; }; 20102BCF2C157C77007738D3 /* HarmonieApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20102BCE2C157C77007738D3 /* HarmonieApp.swift */; }; @@ -80,6 +81,7 @@ /* Begin PBXFileReference section */ 200723962BA56377002C27E8 /* UserDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDetailView.swift; sourceTree = ""; }; + 200BC2D62C74B49A0025872B /* SettingsView+ProfileSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "SettingsView+ProfileSection.swift"; path = "Harmonie/Views/Setting/SettingsView+ProfileSection.swift"; sourceTree = SOURCE_ROOT; }; 200FA7AC2C1D61AB0001E655 /* CircleURLImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircleURLImage.swift; sourceTree = ""; }; 200FA7AF2C1D745B0001E655 /* LocationCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationCardView.swift; sourceTree = ""; }; 20102BCE2C157C77007738D3 /* HarmonieApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HarmonieApp.swift; sourceTree = ""; }; @@ -302,6 +304,7 @@ isa = PBXGroup; children = ( 205B3BF12C00D51A0097D62A /* SettingsView.swift */, + 200BC2D62C74B49A0025872B /* SettingsView+ProfileSection.swift */, ); path = Setting; sourceTree = ""; @@ -529,6 +532,7 @@ 205B3BF42C00D8BF0097D62A /* ProgressScreen.swift in Sources */, 205B3BF22C00D51A0097D62A /* SettingsView.swift in Sources */, 202B9E372C6A426500B981C9 /* FriendsView+Filters.swift in Sources */, + 200BC2D72C74B49A0025872B /* SettingsView+ProfileSection.swift in Sources */, 20E1902E2C4E820A003F39AF /* Constants.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/harmonie/Views/Setting/SettingsView+ProfileSection.swift b/harmonie/Views/Setting/SettingsView+ProfileSection.swift new file mode 100644 index 0000000..b7aced4 --- /dev/null +++ b/harmonie/Views/Setting/SettingsView+ProfileSection.swift @@ -0,0 +1,42 @@ +// +// SettingsView+ProfileSection.swift +// Harmonie +// +// Created by makinosp on 2024/08/20. +// + +import SwiftUI +import VRCKit + +extension SettingsView { + func profileSection(user: User) -> some View { + Section(header: Text("My Profile")) { + Button { + destination = .userDetail + } label: { + HStack(alignment: .center) { + Label { + Text(user.displayName) + } icon: { + CircleURLImage( + imageUrl: user.thumbnailUrl, + size: Constants.IconSize.ll + ) + } + Spacer() + Constants.Icon.forward + } + .frame(maxWidth: .infinity, alignment: .leading) + .contentShape(Rectangle()) + } + Button { + isPresentedForm = true + } label: { + HStack(alignment: .center) { + Label("Edit", systemImage: "pencil") + } + } + } + .textCase(nil) + } +} diff --git a/harmonie/Views/Setting/SettingsView.swift b/harmonie/Views/Setting/SettingsView.swift index 6c43e0f..a42bc6c 100644 --- a/harmonie/Views/Setting/SettingsView.swift +++ b/harmonie/Views/Setting/SettingsView.swift @@ -86,55 +86,6 @@ struct SettingsView: View { } } } - - func profileSection(user: User) -> some View { - Section(header: Text("My Profile")) { - Button { - destination = .userDetail - } label: { - HStack(alignment: .center) { - Label { - Text(user.displayName) - } icon: { - CircleURLImage( - imageUrl: user.thumbnailUrl, - size: Constants.IconSize.ll - ) - } - Spacer() - Constants.Icon.forward - } - .frame(maxWidth: .infinity, alignment: .leading) - .contentShape(Rectangle()) - } - Button {} label: { - HStack(alignment: .center) { - Label { - Text("Status") - } icon: { - Image(systemName: Constants.IconName.circleFilled) - .foregroundStyle(user.status.color) - } - Spacer() - Text(user.status.description) - .foregroundStyle(Color(uiColor: .secondaryLabel)) - .font(.callout) - Constants.Icon.forward - } - } - Button {} label: { - HStack(alignment: .center) { - Label("Description", systemImage: "quote.bubble") - Spacer() - Text(user.statusDescription) - .foregroundStyle(Color(uiColor: .secondaryLabel)) - .font(.callout) - Constants.Icon.forward - } - } - } - .textCase(nil) - } var appName: String { Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? "" From 0d9b281f99c5f673be09ca33f5813c643c1c3854 Mon Sep 17 00:00:00 2001 From: makinosp Date: Wed, 21 Aug 2024 21:13:13 +0900 Subject: [PATCH 10/34] feat: implement the status picker in ProfileEditView form --- Harmonie.xcodeproj/project.pbxproj | 4 ++ harmonie/Views/Setting/ProfileEditView.swift | 54 ++++++++++++++++++++ harmonie/Views/Setting/SettingsView.swift | 11 ++++ 3 files changed, 69 insertions(+) create mode 100644 harmonie/Views/Setting/ProfileEditView.swift diff --git a/Harmonie.xcodeproj/project.pbxproj b/Harmonie.xcodeproj/project.pbxproj index 5846653..edf3c4a 100644 --- a/Harmonie.xcodeproj/project.pbxproj +++ b/Harmonie.xcodeproj/project.pbxproj @@ -36,6 +36,7 @@ 205C9DAF2C20612500E3782C /* Binding+NilCoalescing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 205C9DAE2C20612500E3782C /* Binding+NilCoalescing.swift */; }; 205D81A62C71B9D70047C1CC /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = 205D81A52C71B9D70047C1CC /* SwiftUIIntrospect */; }; 206088E92C48079900626DDC /* LocationDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 206088E82C48079900626DDC /* LocationDetailView.swift */; }; + 20634BC32C760A2D00D57D57 /* ProfileEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20634BC22C760A2D00D57D57 /* ProfileEditView.swift */; }; 206AC9012BFA07AA00E0D5AE /* AsyncSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 206AC9002BFA07AA00E0D5AE /* AsyncSwiftUI */; }; 207FC6352C4256E60018E605 /* View+SizeModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207FC6322C4256E60018E605 /* View+SizeModifier.swift */; }; 207FC6362C4256E60018E605 /* View+ErrorAlertModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207FC6332C4256E60018E605 /* View+ErrorAlertModifier.swift */; }; @@ -106,6 +107,7 @@ 205B3BF32C00D8BF0097D62A /* ProgressScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressScreen.swift; sourceTree = ""; }; 205C9DAE2C20612500E3782C /* Binding+NilCoalescing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+NilCoalescing.swift"; sourceTree = ""; }; 206088E82C48079900626DDC /* LocationDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDetailView.swift; sourceTree = ""; }; + 20634BC22C760A2D00D57D57 /* ProfileEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ProfileEditView.swift; path = Harmonie/Views/Setting/ProfileEditView.swift; sourceTree = SOURCE_ROOT; }; 207FC6322C4256E60018E605 /* View+SizeModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+SizeModifier.swift"; sourceTree = ""; }; 207FC6332C4256E60018E605 /* View+ErrorAlertModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+ErrorAlertModifier.swift"; sourceTree = ""; }; 207FC6342C4256E60018E605 /* View+SectionModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+SectionModifier.swift"; sourceTree = ""; }; @@ -303,6 +305,7 @@ 206088F02C4808C500626DDC /* Setting */ = { isa = PBXGroup; children = ( + 20634BC22C760A2D00D57D57 /* ProfileEditView.swift */, 205B3BF12C00D51A0097D62A /* SettingsView.swift */, 200BC2D62C74B49A0025872B /* SettingsView+ProfileSection.swift */, ); @@ -509,6 +512,7 @@ 209A788A2C2FC0FA00BEFAE6 /* FavoriteViewModel.swift in Sources */, 205AA45E2C72E61B008CCC8A /* UserDetailView+Toolbar.swift in Sources */, 207FC6372C4256E60018E605 /* View+SectionModifier.swift in Sources */, + 20634BC32C760A2D00D57D57 /* ProfileEditView.swift in Sources */, 20395D972B945CD700003921 /* ContentView.swift in Sources */, 200FA7AD2C1D61AB0001E655 /* CircleURLImage.swift in Sources */, 200FA7B02C1D745B0001E655 /* LocationCardView.swift in Sources */, diff --git a/harmonie/Views/Setting/ProfileEditView.swift b/harmonie/Views/Setting/ProfileEditView.swift new file mode 100644 index 0000000..4e010e9 --- /dev/null +++ b/harmonie/Views/Setting/ProfileEditView.swift @@ -0,0 +1,54 @@ +// +// ProfileEditView.swift +// Harmonie +// +// Created by makinosp on 2024/08/21. +// + +import SwiftUI +import VRCKit + +struct ProfileEditView: View { + @State var status: UserStatus + + init(user: User) { + _status = State(initialValue: user.status) + } + + var body: some View { + NavigationStack { + Form { + Picker(selection: $status) { + ForEach(UserStatus.allCases) { status in + Text(status.description).tag(status) + } + } label: { + Label { + Text("Status") + } icon: { + Image(systemName: "circle.fill") + .foregroundStyle(status.color) + } + } + } + .toolbar { toolbarContents } + } + } + + @ToolbarContentBuilder var toolbarContents: some ToolbarContent { + ToolbarItem(placement: .cancellationAction) { + Button { + print("Saving!") + } label: { + Text("Cancel") + } + } + ToolbarItem { + Button { + print("Saving!") + } label: { + Text("Save") + } + } + } +} diff --git a/harmonie/Views/Setting/SettingsView.swift b/harmonie/Views/Setting/SettingsView.swift index a42bc6c..1189c33 100644 --- a/harmonie/Views/Setting/SettingsView.swift +++ b/harmonie/Views/Setting/SettingsView.swift @@ -12,6 +12,7 @@ import VRCKit struct SettingsView: View { @EnvironmentObject var appVM: AppViewModel @State var destination: Destination? + @State var isPresentedForm = false enum Destination: Hashable { case userDetail, license @@ -33,6 +34,16 @@ struct SettingsView: View { .navigationTitle("Settings") } .navigationSplitViewStyle(.balanced) + .onAppear { + if UIDevice.current.userInterfaceIdiom == .pad { + destination = .userDetail + } + } + .sheet(isPresented: $isPresentedForm) { + if let user = appVM.user { + ProfileEditView(user: user) + } + } } @ViewBuilder From 182f258d8b7d023846c20ff20d96601a2f1e6b33 Mon Sep 17 00:00:00 2001 From: makinosp Date: Wed, 21 Aug 2024 21:39:49 +0900 Subject: [PATCH 11/34] feat: implement bio editor in ProfileEditView form --- harmonie/Views/Setting/ProfileEditView.swift | 33 +++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/harmonie/Views/Setting/ProfileEditView.swift b/harmonie/Views/Setting/ProfileEditView.swift index 4e010e9..0784376 100644 --- a/harmonie/Views/Setting/ProfileEditView.swift +++ b/harmonie/Views/Setting/ProfileEditView.swift @@ -9,26 +9,37 @@ import SwiftUI import VRCKit struct ProfileEditView: View { + @Environment(\.dismiss) private var dismiss @State var status: UserStatus + @State var statusDescription: String + @State var bio: String init(user: User) { _status = State(initialValue: user.status) + _statusDescription = State(initialValue: user.statusDescription) + _bio = State(initialValue: user.bio ?? "") } var body: some View { NavigationStack { Form { - Picker(selection: $status) { - ForEach(UserStatus.allCases) { status in - Text(status.description).tag(status) - } - } label: { - Label { - Text("Status") - } icon: { - Image(systemName: "circle.fill") - .foregroundStyle(status.color) + Section("Status") { + Picker(selection: $status) { + ForEach(UserStatus.allCases) { status in + Text(status.description).tag(status) + } + } label: { + Label { + Text("Status") + } icon: { + Image(systemName: "circle.fill") + .foregroundStyle(status.color) + } } + TextField("Status Description", text: $statusDescription) + } + Section("Description") { + TextEditor(text: $bio) } } .toolbar { toolbarContents } @@ -38,7 +49,7 @@ struct ProfileEditView: View { @ToolbarContentBuilder var toolbarContents: some ToolbarContent { ToolbarItem(placement: .cancellationAction) { Button { - print("Saving!") + dismiss() } label: { Text("Cancel") } From 311f6d248aca7c3f5c7ab854bd9ac77b390e9110 Mon Sep 17 00:00:00 2001 From: makinosp Date: Wed, 21 Aug 2024 22:05:55 +0900 Subject: [PATCH 12/34] update: package dependencies --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 885646c..1fb883d 100644 --- a/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "2eeb86afc5727bde810cfe9658cb0153a0ffde99fcf6775df12b79c9ac590451", + "originHash" : "f0fb8ceb3ffd5128d02593ebef3a373aa7fe9597383061f9f65348fd33476726", "pins" : [ { "identity" : "async-swift-ui", @@ -52,7 +52,7 @@ "location" : "https://github.com/makinosp/vrckit", "state" : { "branch" : "develop", - "revision" : "eabad8f790cec60415c9e2b4565bffef21f3b283" + "revision" : "1bedc433e592c34ee1d7312f25f624782963b4d8" } } ], From 5e8097df5c858825fe752309bd68435f1fa5ea1e Mon Sep 17 00:00:00 2001 From: makinosp Date: Wed, 21 Aug 2024 22:06:08 +0900 Subject: [PATCH 13/34] feat: implement language tag picker in ProfileEditView --- harmonie/Views/Setting/ProfileEditView.swift | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/harmonie/Views/Setting/ProfileEditView.swift b/harmonie/Views/Setting/ProfileEditView.swift index 0784376..eee809a 100644 --- a/harmonie/Views/Setting/ProfileEditView.swift +++ b/harmonie/Views/Setting/ProfileEditView.swift @@ -13,11 +13,13 @@ struct ProfileEditView: View { @State var status: UserStatus @State var statusDescription: String @State var bio: String + @State var languageTags: [LanguageTag] init(user: User) { _status = State(initialValue: user.status) _statusDescription = State(initialValue: user.statusDescription) _bio = State(initialValue: user.bio ?? "") + _languageTags = State(initialValue: user.tags.languageTags) } var body: some View { @@ -41,6 +43,21 @@ struct ProfileEditView: View { Section("Description") { TextEditor(text: $bio) } + Section("Language") { + ForEach(languageTags) { tag in + Text(tag.rawValue) + .swipeActions { + Button(role: .destructive) { + // Delete action + } label: { + Text("Delete") + } + } + } + } + Button {} label: { + Label("Add", systemImage: "plus") + } } .toolbar { toolbarContents } } From 697c6adb777f19316f5b491d6d3f09205f785d6d Mon Sep 17 00:00:00 2001 From: makinosp Date: Thu, 22 Aug 2024 21:35:36 +0900 Subject: [PATCH 14/34] 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 1fb883d..2cfce23 100644 --- a/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -52,7 +52,7 @@ "location" : "https://github.com/makinosp/vrckit", "state" : { "branch" : "develop", - "revision" : "1bedc433e592c34ee1d7312f25f624782963b4d8" + "revision" : "6b12d35197e023659f295bdf71aa54cd14c37d93" } } ], From b6373de1089a4ec2034016f663ed6f76844d9c7f Mon Sep 17 00:00:00 2001 From: makinosp Date: Thu, 22 Aug 2024 22:04:43 +0900 Subject: [PATCH 15/34] feat: implement language picker in ProfileEditView --- harmonie/Views/Setting/ProfileEditView.swift | 29 ++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/harmonie/Views/Setting/ProfileEditView.swift b/harmonie/Views/Setting/ProfileEditView.swift index eee809a..914cf10 100644 --- a/harmonie/Views/Setting/ProfileEditView.swift +++ b/harmonie/Views/Setting/ProfileEditView.swift @@ -10,6 +10,8 @@ import VRCKit struct ProfileEditView: View { @Environment(\.dismiss) private var dismiss + @State private var isPresentedLanguagePicker = false + @State private var addedLanguage: LanguageTag? @State var status: UserStatus @State var statusDescription: String @State var bio: String @@ -45,7 +47,7 @@ struct ProfileEditView: View { } Section("Language") { ForEach(languageTags) { tag in - Text(tag.rawValue) + Text(tag.description) .swipeActions { Button(role: .destructive) { // Delete action @@ -55,12 +57,35 @@ struct ProfileEditView: View { } } } - Button {} label: { + Button { + isPresentedLanguagePicker = true + } label: { Label("Add", systemImage: "plus") } } .toolbar { toolbarContents } } + .sheet(isPresented: $isPresentedLanguagePicker) { + languagePicker.presentationDetents([.medium]) + } + } + + var languagePicker: some View { + Form { + Picker("Languages", selection: $addedLanguage) { + ForEach(LanguageTag.allCases) { languageTag in + Text(languageTag.description) + .tag(LanguageTag?.some(languageTag)) + } + } + .pickerStyle(.inline) + } + .onChange(of: addedLanguage) { + if let addedLanguage = addedLanguage, !languageTags.contains(addedLanguage) { + languageTags.append(addedLanguage) + self.addedLanguage = nil + } + } } @ToolbarContentBuilder var toolbarContents: some ToolbarContent { From be2d513a13b6590b873d9687bc5ed9f127852666 Mon Sep 17 00:00:00 2001 From: makinosp Date: Thu, 22 Aug 2024 22:09:28 +0900 Subject: [PATCH 16/34] 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 2cfce23..e895cff 100644 --- a/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -52,7 +52,7 @@ "location" : "https://github.com/makinosp/vrckit", "state" : { "branch" : "develop", - "revision" : "6b12d35197e023659f295bdf71aa54cd14c37d93" + "revision" : "b2669ad2ec30b5b6784c760e26e80424f4ce6ab0" } } ], From b26997d5dca5c1b310018c398179565f400a06bb Mon Sep 17 00:00:00 2001 From: makinosp Date: Fri, 23 Aug 2024 08:15:48 +0900 Subject: [PATCH 17/34] feat: LanugageSelectionView for presenting sheet --- Harmonie.xcodeproj/project.pbxproj | 4 ++ .../Views/Setting/LanguagePickerView.swift | 41 +++++++++++++++++++ harmonie/Views/Setting/ProfileEditView.swift | 25 ++++------- 3 files changed, 52 insertions(+), 18 deletions(-) create mode 100644 harmonie/Views/Setting/LanguagePickerView.swift diff --git a/Harmonie.xcodeproj/project.pbxproj b/Harmonie.xcodeproj/project.pbxproj index edf3c4a..9368018 100644 --- a/Harmonie.xcodeproj/project.pbxproj +++ b/Harmonie.xcodeproj/project.pbxproj @@ -56,6 +56,7 @@ 209D77302C2251C200A11666 /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 209D772F2C2251C200A11666 /* MainTabView.swift */; }; 20AD8D982C68C5C300140232 /* NavigationSplitView+initializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20AD8D972C68C5C300140232 /* NavigationSplitView+initializers.swift */; }; 20AFBC842C68EE5D006E9239 /* DateUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20AFBC832C68EE5D006E9239 /* DateUtil.swift */; }; + 20D50FA72C77F94F00F89B4F /* LanguagePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20D50FA62C77F94F00F89B4F /* LanguagePickerView.swift */; }; 20DC3C292C422786001101DB /* VRCKit in Frameworks */ = {isa = PBXBuildFile; productRef = 20DC3C282C422786001101DB /* VRCKit */; }; 20E1902E2C4E820A003F39AF /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20E1902D2C4E820A003F39AF /* Constants.swift */; }; 20F213DD2C11FB470061DE29 /* NukeUI in Frameworks */ = {isa = PBXBuildFile; productRef = 20F213DC2C11FB470061DE29 /* NukeUI */; }; @@ -123,6 +124,7 @@ 209D772F2C2251C200A11666 /* MainTabView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainTabView.swift; sourceTree = ""; }; 20AD8D972C68C5C300140232 /* NavigationSplitView+initializers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "NavigationSplitView+initializers.swift"; path = "Harmonie/Extensions/NavigationSplitView+initializers.swift"; sourceTree = SOURCE_ROOT; }; 20AFBC832C68EE5D006E9239 /* DateUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DateUtil.swift; path = Harmonie/Utils/DateUtil.swift; sourceTree = SOURCE_ROOT; }; + 20D50FA62C77F94F00F89B4F /* LanguagePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LanguagePickerView.swift; path = Harmonie/Views/Setting/LanguagePickerView.swift; sourceTree = SOURCE_ROOT; }; 20E1902D2C4E820A003F39AF /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Constants.swift; path = Harmonie/Utils/Constants.swift; sourceTree = SOURCE_ROOT; }; 20F95D272C69BC2D002CDC5F /* UserDetailView+Actions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UserDetailView+Actions.swift"; path = "Harmonie/Views/UserDetail/UserDetailView+Actions.swift"; sourceTree = SOURCE_ROOT; }; 20F95D292C69BC4B002CDC5F /* UserDetailView+ActivitySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "UserDetailView+ActivitySection.swift"; path = "Harmonie/Views/UserDetail/UserDetailView+ActivitySection.swift"; sourceTree = SOURCE_ROOT; }; @@ -305,6 +307,7 @@ 206088F02C4808C500626DDC /* Setting */ = { isa = PBXGroup; children = ( + 20D50FA62C77F94F00F89B4F /* LanguagePickerView.swift */, 20634BC22C760A2D00D57D57 /* ProfileEditView.swift */, 205B3BF12C00D51A0097D62A /* SettingsView.swift */, 200BC2D62C74B49A0025872B /* SettingsView+ProfileSection.swift */, @@ -511,6 +514,7 @@ 20460CAE2C561ED200B276E3 /* UserDetailPresentationView.swift in Sources */, 209A788A2C2FC0FA00BEFAE6 /* FavoriteViewModel.swift in Sources */, 205AA45E2C72E61B008CCC8A /* UserDetailView+Toolbar.swift in Sources */, + 20D50FA72C77F94F00F89B4F /* LanguagePickerView.swift in Sources */, 207FC6372C4256E60018E605 /* View+SectionModifier.swift in Sources */, 20634BC32C760A2D00D57D57 /* ProfileEditView.swift in Sources */, 20395D972B945CD700003921 /* ContentView.swift in Sources */, diff --git a/harmonie/Views/Setting/LanguagePickerView.swift b/harmonie/Views/Setting/LanguagePickerView.swift new file mode 100644 index 0000000..19410e4 --- /dev/null +++ b/harmonie/Views/Setting/LanguagePickerView.swift @@ -0,0 +1,41 @@ +// +// LanguagePickerView.swift +// Harmonie +// +// Created by makinosp on 2024/08/23. +// + +import SwiftUI +import VRCKit + +struct LanguagePickerView: View { + @Environment(\.dismiss) private var dismiss + @Binding var selectedLanguage: LanguageTag? + + var body: some View { + NavigationStack { + Form { + ForEach(LanguageTag.allCases) { languageTag in + Button { + selectedLanguage = languageTag + dismiss() + } label: { + Text(languageTag.description) + } + } + } + .labelsHidden() + .navigationTitle("Languages") + .toolbarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button { + dismiss() + } label: { + Text("Cancel") + } + } + } + } + } +} diff --git a/harmonie/Views/Setting/ProfileEditView.swift b/harmonie/Views/Setting/ProfileEditView.swift index 914cf10..68f6b9f 100644 --- a/harmonie/Views/Setting/ProfileEditView.swift +++ b/harmonie/Views/Setting/ProfileEditView.swift @@ -11,7 +11,7 @@ import VRCKit struct ProfileEditView: View { @Environment(\.dismiss) private var dismiss @State private var isPresentedLanguagePicker = false - @State private var addedLanguage: LanguageTag? + @State private var selectedLanguage: LanguageTag? @State var status: UserStatus @State var statusDescription: String @State var bio: String @@ -66,24 +66,13 @@ struct ProfileEditView: View { .toolbar { toolbarContents } } .sheet(isPresented: $isPresentedLanguagePicker) { - languagePicker.presentationDetents([.medium]) + LanguagePickerView(selectedLanguage: $selectedLanguage) + .presentationDetents([.medium]) } - } - - var languagePicker: some View { - Form { - Picker("Languages", selection: $addedLanguage) { - ForEach(LanguageTag.allCases) { languageTag in - Text(languageTag.description) - .tag(LanguageTag?.some(languageTag)) - } - } - .pickerStyle(.inline) - } - .onChange(of: addedLanguage) { - if let addedLanguage = addedLanguage, !languageTags.contains(addedLanguage) { - languageTags.append(addedLanguage) - self.addedLanguage = nil + .onChange(of: selectedLanguage) { + if let selectedLanguage = selectedLanguage, !languageTags.contains(selectedLanguage) { + languageTags.append(selectedLanguage) + self.selectedLanguage = nil } } } From 903094faddf6a443df265a3e68591e9ffed845da Mon Sep 17 00:00:00 2001 From: makinosp Date: Fri, 23 Aug 2024 12:00:41 +0900 Subject: [PATCH 18/34] feat: implement bio links section in ProfileEditView, improve UI --- Harmonie.xcodeproj/project.pbxproj | 4 +++ harmonie/Extensions/URL+Identifiable.swift | 12 +++++++++ .../Views/Setting/LanguagePickerView.swift | 1 - harmonie/Views/Setting/ProfileEditView.swift | 27 ++++++++++++++++--- 4 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 harmonie/Extensions/URL+Identifiable.swift diff --git a/Harmonie.xcodeproj/project.pbxproj b/Harmonie.xcodeproj/project.pbxproj index 9368018..87324ed 100644 --- a/Harmonie.xcodeproj/project.pbxproj +++ b/Harmonie.xcodeproj/project.pbxproj @@ -50,6 +50,7 @@ 2094C4002C43BBC100F88986 /* VRCKit in Frameworks */ = {isa = PBXBuildFile; productRef = 2094C3FF2C43BBC100F88986 /* VRCKit */; }; 2096016D2C4BD30100D905AF /* VRCKit in Frameworks */ = {isa = PBXBuildFile; productRef = 2096016C2C4BD30100D905AF /* VRCKit */; }; 209938AA2C109C8C003B4A8A /* SectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 209938A92C109C8C003B4A8A /* SectionView.swift */; }; + 2099A4582C78326600104842 /* URL+Identifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2099A4572C78326600104842 /* URL+Identifiable.swift */; }; 209A78892C2FC0FA00BEFAE6 /* AppViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 209A78852C2FC0FA00BEFAE6 /* AppViewModel.swift */; }; 209A788A2C2FC0FA00BEFAE6 /* FavoriteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 209A78862C2FC0FA00BEFAE6 /* FavoriteViewModel.swift */; }; 209A788B2C2FC0FA00BEFAE6 /* FriendViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 209A78872C2FC0FA00BEFAE6 /* FriendViewModel.swift */; }; @@ -118,6 +119,7 @@ 208E6E742C55FF9F00B61822 /* GradientOverlayImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GradientOverlayImageView.swift; path = Harmonie/Components/GradientOverlayImageView.swift; sourceTree = SOURCE_ROOT; }; 2090C6F22C4B7877009667CC /* Status+color.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Status+color.swift"; path = "Harmonie/Extensions/Status+color.swift"; sourceTree = SOURCE_ROOT; }; 209938A92C109C8C003B4A8A /* SectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionView.swift; sourceTree = ""; }; + 2099A4572C78326600104842 /* URL+Identifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "URL+Identifiable.swift"; path = "Harmonie/Extensions/URL+Identifiable.swift"; sourceTree = SOURCE_ROOT; }; 209A78852C2FC0FA00BEFAE6 /* AppViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppViewModel.swift; sourceTree = ""; }; 209A78862C2FC0FA00BEFAE6 /* FavoriteViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FavoriteViewModel.swift; sourceTree = ""; }; 209A78872C2FC0FA00BEFAE6 /* FriendViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FriendViewModel.swift; sourceTree = ""; }; @@ -202,6 +204,7 @@ 207FC6332C4256E60018E605 /* View+ErrorAlertModifier.swift */, 207FC6342C4256E60018E605 /* View+SectionModifier.swift */, 207FC6322C4256E60018E605 /* View+SizeModifier.swift */, + 2099A4572C78326600104842 /* URL+Identifiable.swift */, ); path = Extensions; sourceTree = ""; @@ -516,6 +519,7 @@ 205AA45E2C72E61B008CCC8A /* UserDetailView+Toolbar.swift in Sources */, 20D50FA72C77F94F00F89B4F /* LanguagePickerView.swift in Sources */, 207FC6372C4256E60018E605 /* View+SectionModifier.swift in Sources */, + 2099A4582C78326600104842 /* URL+Identifiable.swift in Sources */, 20634BC32C760A2D00D57D57 /* ProfileEditView.swift in Sources */, 20395D972B945CD700003921 /* ContentView.swift in Sources */, 200FA7AD2C1D61AB0001E655 /* CircleURLImage.swift in Sources */, diff --git a/harmonie/Extensions/URL+Identifiable.swift b/harmonie/Extensions/URL+Identifiable.swift new file mode 100644 index 0000000..780db65 --- /dev/null +++ b/harmonie/Extensions/URL+Identifiable.swift @@ -0,0 +1,12 @@ +// +// URL+Identifiable.swift +// Harmonie +// +// Created by makinosp on 2024/08/23. +// + +import Foundation + +extension URL: Identifiable { + public var id: Int { hashValue } +} diff --git a/harmonie/Views/Setting/LanguagePickerView.swift b/harmonie/Views/Setting/LanguagePickerView.swift index 19410e4..00e631b 100644 --- a/harmonie/Views/Setting/LanguagePickerView.swift +++ b/harmonie/Views/Setting/LanguagePickerView.swift @@ -24,7 +24,6 @@ struct LanguagePickerView: View { } } } - .labelsHidden() .navigationTitle("Languages") .toolbarTitleDisplayMode(.inline) .toolbar { diff --git a/harmonie/Views/Setting/ProfileEditView.swift b/harmonie/Views/Setting/ProfileEditView.swift index 68f6b9f..a5d0518 100644 --- a/harmonie/Views/Setting/ProfileEditView.swift +++ b/harmonie/Views/Setting/ProfileEditView.swift @@ -16,12 +16,14 @@ struct ProfileEditView: View { @State var statusDescription: String @State var bio: String @State var languageTags: [LanguageTag] + @State var bioLinks: [URL] init(user: User) { _status = State(initialValue: user.status) _statusDescription = State(initialValue: user.statusDescription) _bio = State(initialValue: user.bio ?? "") _languageTags = State(initialValue: user.tags.languageTags) + _bioLinks = State(initialValue: user.bioLinks.elements) } var body: some View { @@ -57,13 +59,30 @@ struct ProfileEditView: View { } } } - Button { - isPresentedLanguagePicker = true - } label: { - Label("Add", systemImage: "plus") + Section { + Button { + isPresentedLanguagePicker = true + } label: { + Label("Add", systemImage: "plus") + } + } + .listSectionSpacing(.compact) + Section("Bio Links") { + ForEach(bioLinks) { url in + Link(url.description, destination: url) + } + } + Section { + Button { + } label: { + Label("Add", systemImage: "plus") + } } + .listSectionSpacing(.compact) } .toolbar { toolbarContents } + .navigationTitle("Edit Profile") + .navigationBarTitleDisplayMode(.inline) } .sheet(isPresented: $isPresentedLanguagePicker) { LanguagePickerView(selectedLanguage: $selectedLanguage) From 0ed4e9382c129a3e869b1b7de5a24fcc8fefeded Mon Sep 17 00:00:00 2001 From: makinosp Date: Fri, 23 Aug 2024 12:05:39 +0900 Subject: [PATCH 19/34] refact: sectioning in ProfileEditView --- harmonie/Views/Setting/ProfileEditView.swift | 116 +++++++++++-------- 1 file changed, 66 insertions(+), 50 deletions(-) diff --git a/harmonie/Views/Setting/ProfileEditView.swift b/harmonie/Views/Setting/ProfileEditView.swift index a5d0518..83e90b6 100644 --- a/harmonie/Views/Setting/ProfileEditView.swift +++ b/harmonie/Views/Setting/ProfileEditView.swift @@ -29,56 +29,10 @@ struct ProfileEditView: View { var body: some View { NavigationStack { Form { - Section("Status") { - Picker(selection: $status) { - ForEach(UserStatus.allCases) { status in - Text(status.description).tag(status) - } - } label: { - Label { - Text("Status") - } icon: { - Image(systemName: "circle.fill") - .foregroundStyle(status.color) - } - } - TextField("Status Description", text: $statusDescription) - } - Section("Description") { - TextEditor(text: $bio) - } - Section("Language") { - ForEach(languageTags) { tag in - Text(tag.description) - .swipeActions { - Button(role: .destructive) { - // Delete action - } label: { - Text("Delete") - } - } - } - } - Section { - Button { - isPresentedLanguagePicker = true - } label: { - Label("Add", systemImage: "plus") - } - } - .listSectionSpacing(.compact) - Section("Bio Links") { - ForEach(bioLinks) { url in - Link(url.description, destination: url) - } - } - Section { - Button { - } label: { - Label("Add", systemImage: "plus") - } - } - .listSectionSpacing(.compact) + statusSection + descriptionSection + languageSection + bioLinksSection } .toolbar { toolbarContents } .navigationTitle("Edit Profile") @@ -95,6 +49,68 @@ struct ProfileEditView: View { } } } + + var statusSection: some View { + Section("Status") { + Picker(selection: $status) { + ForEach(UserStatus.allCases) { status in + Text(status.description).tag(status) + } + } label: { + Label { + Text("Status") + } icon: { + Image(systemName: "circle.fill") + .foregroundStyle(status.color) + } + } + TextField("Status Description", text: $statusDescription) + } + } + + var descriptionSection: some View { + Section("Description") { + TextEditor(text: $bio) + } + } + + @ViewBuilder var languageSection: some View { + Section("Language") { + ForEach(languageTags) { tag in + Text(tag.description) + .swipeActions { + Button(role: .destructive) { + // Delete action + } label: { + Text("Delete") + } + } + } + } + Section { + Button { + isPresentedLanguagePicker = true + } label: { + Label("Add", systemImage: "plus") + } + } + .listSectionSpacing(.compact) + } + + @ViewBuilder var bioLinksSection: some View { + Section("Bio Links") { + ForEach(bioLinks) { url in + Link(url.description, destination: url) + } + } + Section { + Button { + } label: { + Label("Add", systemImage: "plus") + } + } + .listSectionSpacing(.compact) + } @ToolbarContentBuilder var toolbarContents: some ToolbarContent { ToolbarItem(placement: .cancellationAction) { From c2e9aaa553128d35f8c2b5ec9b03d718e2d88543 Mon Sep 17 00:00:00 2001 From: makinosp Date: Fri, 23 Aug 2024 18:14:58 +0900 Subject: [PATCH 20/34] feat: ObservableObjects to manage state in ProfileEditView --- Harmonie.xcodeproj/project.pbxproj | 4 ++ .../ViewModels/ProfileEditViewModel.swift | 17 +++++++ harmonie/Views/Setting/ProfileEditView.swift | 50 +++++++++++-------- 3 files changed, 50 insertions(+), 21 deletions(-) create mode 100644 harmonie/ViewModels/ProfileEditViewModel.swift diff --git a/Harmonie.xcodeproj/project.pbxproj b/Harmonie.xcodeproj/project.pbxproj index 87324ed..279385c 100644 --- a/Harmonie.xcodeproj/project.pbxproj +++ b/Harmonie.xcodeproj/project.pbxproj @@ -37,6 +37,7 @@ 205D81A62C71B9D70047C1CC /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = 205D81A52C71B9D70047C1CC /* SwiftUIIntrospect */; }; 206088E92C48079900626DDC /* LocationDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 206088E82C48079900626DDC /* LocationDetailView.swift */; }; 20634BC32C760A2D00D57D57 /* ProfileEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20634BC22C760A2D00D57D57 /* ProfileEditView.swift */; }; + 2065F8702C788A59007F132B /* ProfileEditViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2065F86F2C788A59007F132B /* ProfileEditViewModel.swift */; }; 206AC9012BFA07AA00E0D5AE /* AsyncSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 206AC9002BFA07AA00E0D5AE /* AsyncSwiftUI */; }; 207FC6352C4256E60018E605 /* View+SizeModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207FC6322C4256E60018E605 /* View+SizeModifier.swift */; }; 207FC6362C4256E60018E605 /* View+ErrorAlertModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207FC6332C4256E60018E605 /* View+ErrorAlertModifier.swift */; }; @@ -110,6 +111,7 @@ 205C9DAE2C20612500E3782C /* Binding+NilCoalescing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+NilCoalescing.swift"; sourceTree = ""; }; 206088E82C48079900626DDC /* LocationDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDetailView.swift; sourceTree = ""; }; 20634BC22C760A2D00D57D57 /* ProfileEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ProfileEditView.swift; path = Harmonie/Views/Setting/ProfileEditView.swift; sourceTree = SOURCE_ROOT; }; + 2065F86F2C788A59007F132B /* ProfileEditViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProfileEditViewModel.swift; path = Harmonie/ViewModels/ProfileEditViewModel.swift; sourceTree = SOURCE_ROOT; }; 207FC6322C4256E60018E605 /* View+SizeModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+SizeModifier.swift"; sourceTree = ""; }; 207FC6332C4256E60018E605 /* View+ErrorAlertModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+ErrorAlertModifier.swift"; sourceTree = ""; }; 207FC6342C4256E60018E605 /* View+SectionModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+SectionModifier.swift"; sourceTree = ""; }; @@ -347,6 +349,7 @@ 209A78862C2FC0FA00BEFAE6 /* FavoriteViewModel.swift */, 209A78872C2FC0FA00BEFAE6 /* FriendViewModel.swift */, 202B9E382C6A430900B981C9 /* FriendViewModel+Filters.swift */, + 2065F86F2C788A59007F132B /* ProfileEditViewModel.swift */, ); path = ViewModels; sourceTree = ""; @@ -513,6 +516,7 @@ 209938AA2C109C8C003B4A8A /* SectionView.swift in Sources */, 205C9DAF2C20612500E3782C /* Binding+NilCoalescing.swift in Sources */, 202844C42BA5CE1B00357D09 /* LocationsView.swift in Sources */, + 2065F8702C788A59007F132B /* ProfileEditViewModel.swift in Sources */, 20102BCF2C157C77007738D3 /* HarmonieApp.swift in Sources */, 20460CAE2C561ED200B276E3 /* UserDetailPresentationView.swift in Sources */, 209A788A2C2FC0FA00BEFAE6 /* FavoriteViewModel.swift in Sources */, diff --git a/harmonie/ViewModels/ProfileEditViewModel.swift b/harmonie/ViewModels/ProfileEditViewModel.swift new file mode 100644 index 0000000..5ec01a5 --- /dev/null +++ b/harmonie/ViewModels/ProfileEditViewModel.swift @@ -0,0 +1,17 @@ +// +// ProfileEditViewModel.swift +// Harmonie +// +// Created by makinosp on 2024/08/23. +// + +import Foundation +import VRCKit + +class ProfileEditViewModel: ObservableObject { + @Published var editingUserInfo: EditableUserInfo + + init(user: User) { + editingUserInfo = EditableUserInfo(detail: user) + } +} diff --git a/harmonie/Views/Setting/ProfileEditView.swift b/harmonie/Views/Setting/ProfileEditView.swift index 83e90b6..b311165 100644 --- a/harmonie/Views/Setting/ProfileEditView.swift +++ b/harmonie/Views/Setting/ProfileEditView.swift @@ -9,23 +9,16 @@ import SwiftUI import VRCKit struct ProfileEditView: View { + @EnvironmentObject var appVM: AppViewModel @Environment(\.dismiss) private var dismiss + @StateObject private var profileEditVM: ProfileEditViewModel @State private var isPresentedLanguagePicker = false @State private var selectedLanguage: LanguageTag? - @State var status: UserStatus - @State var statusDescription: String - @State var bio: String - @State var languageTags: [LanguageTag] - @State var bioLinks: [URL] - + init(user: User) { - _status = State(initialValue: user.status) - _statusDescription = State(initialValue: user.statusDescription) - _bio = State(initialValue: user.bio ?? "") - _languageTags = State(initialValue: user.tags.languageTags) - _bioLinks = State(initialValue: user.bioLinks.elements) + _profileEditVM = StateObject(wrappedValue: ProfileEditViewModel(user: user)) } - + var body: some View { NavigationStack { Form { @@ -43,8 +36,9 @@ struct ProfileEditView: View { .presentationDetents([.medium]) } .onChange(of: selectedLanguage) { - if let selectedLanguage = selectedLanguage, !languageTags.contains(selectedLanguage) { - languageTags.append(selectedLanguage) + if let selectedLanguage = selectedLanguage, + !profileEditVM.editingUserInfo.tags.languageTags.contains(selectedLanguage) { + profileEditVM.editingUserInfo.tags.languageTags.append(selectedLanguage) self.selectedLanguage = nil } } @@ -52,7 +46,7 @@ struct ProfileEditView: View { var statusSection: some View { Section("Status") { - Picker(selection: $status) { + Picker(selection: $profileEditVM.editingUserInfo.status) { ForEach(UserStatus.allCases) { status in Text(status.description).tag(status) } @@ -61,22 +55,22 @@ struct ProfileEditView: View { Text("Status") } icon: { Image(systemName: "circle.fill") - .foregroundStyle(status.color) + .foregroundStyle(profileEditVM.editingUserInfo.status.color) } } - TextField("Status Description", text: $statusDescription) + TextField("Status Description", text: $profileEditVM.editingUserInfo.statusDescription) } } var descriptionSection: some View { Section("Description") { - TextEditor(text: $bio) + TextEditor(text: $profileEditVM.editingUserInfo.bio) } } @ViewBuilder var languageSection: some View { Section("Language") { - ForEach(languageTags) { tag in + ForEach(profileEditVM.editingUserInfo.tags.languageTags) { tag in Text(tag.description) .swipeActions { Button(role: .destructive) { @@ -99,7 +93,7 @@ struct ProfileEditView: View { @ViewBuilder var bioLinksSection: some View { Section("Bio Links") { - ForEach(bioLinks) { url in + ForEach(profileEditVM.editingUserInfo.bioLinks) { url in Link(url.description, destination: url) } } @@ -111,7 +105,7 @@ struct ProfileEditView: View { } .listSectionSpacing(.compact) } - + @ToolbarContentBuilder var toolbarContents: some ToolbarContent { ToolbarItem(placement: .cancellationAction) { Button { @@ -128,4 +122,18 @@ struct ProfileEditView: View { } } } + + func saveUserInfo(id: String) async { + let service = appVM.isDemoMode + ? UserPreviewService(client: appVM.client) + : UserService(client: appVM.client) + do { + try await service.updateUser( + id: id, + editedInfo: profileEditVM.editingUserInfo + ) + } catch { + appVM.handleError(error) + } + } } From ab0ad92dbd6378f99b095130a5576e60a9a37d4c Mon Sep 17 00:00:00 2001 From: makinosp Date: Fri, 23 Aug 2024 18:33:46 +0900 Subject: [PATCH 21/34] 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 e895cff..1c34ec0 100644 --- a/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -52,7 +52,7 @@ "location" : "https://github.com/makinosp/vrckit", "state" : { "branch" : "develop", - "revision" : "b2669ad2ec30b5b6784c760e26e80424f4ce6ab0" + "revision" : "adb79ce2bf8d49bd8fe350aacb890d049e7eaa5a" } } ], From 785f49a0c2614c07696da205d933399f5c6fd4a8 Mon Sep 17 00:00:00 2001 From: makinosp Date: Fri, 23 Aug 2024 21:46:26 +0900 Subject: [PATCH 22/34] update: package dependencies --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1c34ec0..5a57b8d 100644 --- a/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,8 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/SimplyDanny/SwiftLintPlugins", "state" : { - "revision" : "9ad9487c2448b3f1265ea2cc8af246b9cae6878d", - "version" : "0.56.1" + "revision" : "6c3d6c32a37224179dc290f21e03d1238f3d963b", + "version" : "0.56.2" } }, { From 83226030da3d8d420142bd2eddf43950f9a6cbeb Mon Sep 17 00:00:00 2001 From: makinosp Date: Fri, 23 Aug 2024 21:48:34 +0900 Subject: [PATCH 23/34] feat: implement saving action, requesting --- .../ViewModels/ProfileEditViewModel.swift | 11 ++++- harmonie/Views/Setting/ProfileEditView.swift | 41 +++++++++++-------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/harmonie/ViewModels/ProfileEditViewModel.swift b/harmonie/ViewModels/ProfileEditViewModel.swift index 5ec01a5..a91db99 100644 --- a/harmonie/ViewModels/ProfileEditViewModel.swift +++ b/harmonie/ViewModels/ProfileEditViewModel.swift @@ -10,8 +10,17 @@ import VRCKit class ProfileEditViewModel: ObservableObject { @Published var editingUserInfo: EditableUserInfo - + private let id: String + init(user: User) { editingUserInfo = EditableUserInfo(detail: user) + id = user.id + } + + func saveProfile(service: any UserServiceProtocol) async throws { + try await service.updateUser( + id: id, + editedInfo: editingUserInfo + ) } } diff --git a/harmonie/Views/Setting/ProfileEditView.swift b/harmonie/Views/Setting/ProfileEditView.swift index b311165..b5fca16 100644 --- a/harmonie/Views/Setting/ProfileEditView.swift +++ b/harmonie/Views/Setting/ProfileEditView.swift @@ -5,20 +5,21 @@ // Created by makinosp on 2024/08/21. // -import SwiftUI +import AsyncSwiftUI import VRCKit struct ProfileEditView: View { - @EnvironmentObject var appVM: AppViewModel + @EnvironmentObject private var appVM: AppViewModel @Environment(\.dismiss) private var dismiss @StateObject private var profileEditVM: ProfileEditViewModel @State private var isPresentedLanguagePicker = false + @State private var isRequesting = false @State private var selectedLanguage: LanguageTag? - + init(user: User) { _profileEditVM = StateObject(wrappedValue: ProfileEditViewModel(user: user)) } - + var body: some View { NavigationStack { Form { @@ -43,7 +44,7 @@ struct ProfileEditView: View { } } } - + var statusSection: some View { Section("Status") { Picker(selection: $profileEditVM.editingUserInfo.status) { @@ -61,13 +62,13 @@ struct ProfileEditView: View { TextField("Status Description", text: $profileEditVM.editingUserInfo.statusDescription) } } - + var descriptionSection: some View { Section("Description") { TextEditor(text: $profileEditVM.editingUserInfo.bio) } } - + @ViewBuilder var languageSection: some View { Section("Language") { ForEach(profileEditVM.editingUserInfo.tags.languageTags) { tag in @@ -90,7 +91,7 @@ struct ProfileEditView: View { } .listSectionSpacing(.compact) } - + @ViewBuilder var bioLinksSection: some View { Section("Bio Links") { ForEach(profileEditVM.editingUserInfo.bioLinks) { url in @@ -105,7 +106,7 @@ struct ProfileEditView: View { } .listSectionSpacing(.compact) } - + @ToolbarContentBuilder var toolbarContents: some ToolbarContent { ToolbarItem(placement: .cancellationAction) { Button { @@ -115,23 +116,27 @@ struct ProfileEditView: View { } } ToolbarItem { - Button { - print("Saving!") + AsyncButton { + await saveProfileAction() } label: { - Text("Save") + if isRequesting { + ProgressView() + } else { + Text("Save") + } } } } - - func saveUserInfo(id: String) async { + + private func saveProfileAction() async { let service = appVM.isDemoMode ? UserPreviewService(client: appVM.client) : UserService(client: appVM.client) + isRequesting = true do { - try await service.updateUser( - id: id, - editedInfo: profileEditVM.editingUserInfo - ) + defer { isRequesting = false } + try await profileEditVM.saveProfile(service: service) + dismiss() } catch { appVM.handleError(error) } From eaec2099dc31cf92c3de83386d00d5f6a962d6de Mon Sep 17 00:00:00 2001 From: makinosp Date: Fri, 23 Aug 2024 21:55:54 +0900 Subject: [PATCH 24/34] refact: format code in AuthenticationView --- .../Authentication/AuthenticationView.swift | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/harmonie/Views/Authentication/AuthenticationView.swift b/harmonie/Views/Authentication/AuthenticationView.swift index b88c979..393eb56 100644 --- a/harmonie/Views/Authentication/AuthenticationView.swift +++ b/harmonie/Views/Authentication/AuthenticationView.swift @@ -9,12 +9,12 @@ import AsyncSwiftUI import VRCKit struct AuthenticationView: View { - @EnvironmentObject var appVM: AppViewModel - @State var verifyType: VerifyType? - @State var username: String = "" - @State var password: String = "" - @State var code: String = "" - @State var isRunning = false + @EnvironmentObject private var appVM: AppViewModel + @State private var verifyType: VerifyType? + @State private var username: String = "" + @State private var password: String = "" + @State private var code: String = "" + @State private var isRequesting = false var body: some View { VStack(spacing: 16) { @@ -34,7 +34,7 @@ struct AuthenticationView: View { .ignoresSafeArea(.keyboard) } - var usernamePasswordFields: some View { + private var usernamePasswordFields: some View { VStack(spacing: 8) { HStack { Image(systemName: "at") @@ -53,7 +53,7 @@ struct AuthenticationView: View { } } - var otpField: some View { + private var otpField: some View { HStack { Image(systemName: "ellipsis") .foregroundStyle(Color.gray) @@ -68,16 +68,13 @@ struct AuthenticationView: View { } @ViewBuilder - func loginButton( - _ text: String, - _ action: @escaping () async -> Void - ) -> some View { + private func loginButton(_ text: String, action: @escaping () async -> Void) -> some View { AsyncButton { - isRunning = true + defer { isRequesting = false } + isRequesting = true await action() - isRunning = false } label: { - if isRunning { + if isRequesting { ProgressView() } else { Text(text) @@ -85,6 +82,6 @@ struct AuthenticationView: View { } .buttonStyle(.bordered) .buttonBorderShape(.capsule) - .disabled(isRunning) + .disabled(isRequesting) } } From 954e35189c26f2bb55b0cdb26fd5e02013bb5402 Mon Sep 17 00:00:00 2001 From: makinosp Date: Fri, 23 Aug 2024 22:37:16 +0900 Subject: [PATCH 25/34] feat: implement URLEditorView --- Harmonie.xcodeproj/project.pbxproj | 4 ++ harmonie/Views/Setting/ProfileEditView.swift | 8 ++++ harmonie/Views/Setting/URLEditorView.swift | 46 ++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 harmonie/Views/Setting/URLEditorView.swift diff --git a/Harmonie.xcodeproj/project.pbxproj b/Harmonie.xcodeproj/project.pbxproj index 279385c..fb7c076 100644 --- a/Harmonie.xcodeproj/project.pbxproj +++ b/Harmonie.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 202844C42BA5CE1B00357D09 /* LocationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 202844C32BA5CE1B00357D09 /* LocationsView.swift */; }; 202B9E372C6A426500B981C9 /* FriendsView+Filters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 202B9E362C6A426500B981C9 /* FriendsView+Filters.swift */; }; 202B9E392C6A430900B981C9 /* FriendViewModel+Filters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 202B9E382C6A430900B981C9 /* FriendViewModel+Filters.swift */; }; + 2033B9412C78C0CA0005BD36 /* URLEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2033B9402C78C0CA0005BD36 /* URLEditorView.swift */; }; 20395D972B945CD700003921 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20395D962B945CD700003921 /* ContentView.swift */; }; 20395D9B2B945CD800003921 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 20395D9A2B945CD800003921 /* Assets.xcassets */; }; 20395D9E2B945CD800003921 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 20395D9D2B945CD800003921 /* Preview Assets.xcassets */; }; @@ -96,6 +97,7 @@ 202844C32BA5CE1B00357D09 /* LocationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationsView.swift; sourceTree = ""; }; 202B9E362C6A426500B981C9 /* FriendsView+Filters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "FriendsView+Filters.swift"; path = "Harmonie/Views/Friend/FriendsView+Filters.swift"; sourceTree = SOURCE_ROOT; }; 202B9E382C6A430900B981C9 /* FriendViewModel+Filters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "FriendViewModel+Filters.swift"; path = "Harmonie/ViewModels/FriendViewModel+Filters.swift"; sourceTree = SOURCE_ROOT; }; + 2033B9402C78C0CA0005BD36 /* URLEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = URLEditorView.swift; path = Harmonie/Views/Setting/URLEditorView.swift; sourceTree = SOURCE_ROOT; }; 20395D912B945CD700003921 /* Harmonie.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Harmonie.app; sourceTree = BUILT_PRODUCTS_DIR; }; 20395D962B945CD700003921 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 20395D9A2B945CD800003921 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -316,6 +318,7 @@ 20634BC22C760A2D00D57D57 /* ProfileEditView.swift */, 205B3BF12C00D51A0097D62A /* SettingsView.swift */, 200BC2D62C74B49A0025872B /* SettingsView+ProfileSection.swift */, + 2033B9402C78C0CA0005BD36 /* URLEditorView.swift */, ); path = Setting; sourceTree = ""; @@ -514,6 +517,7 @@ 209A78892C2FC0FA00BEFAE6 /* AppViewModel.swift in Sources */, 20AFBC842C68EE5D006E9239 /* DateUtil.swift in Sources */, 209938AA2C109C8C003B4A8A /* SectionView.swift in Sources */, + 2033B9412C78C0CA0005BD36 /* URLEditorView.swift in Sources */, 205C9DAF2C20612500E3782C /* Binding+NilCoalescing.swift in Sources */, 202844C42BA5CE1B00357D09 /* LocationsView.swift in Sources */, 2065F8702C788A59007F132B /* ProfileEditViewModel.swift in Sources */, diff --git a/harmonie/Views/Setting/ProfileEditView.swift b/harmonie/Views/Setting/ProfileEditView.swift index b5fca16..a0b17ea 100644 --- a/harmonie/Views/Setting/ProfileEditView.swift +++ b/harmonie/Views/Setting/ProfileEditView.swift @@ -13,8 +13,10 @@ struct ProfileEditView: View { @Environment(\.dismiss) private var dismiss @StateObject private var profileEditVM: ProfileEditViewModel @State private var isPresentedLanguagePicker = false + @State private var isPresentedURLEditor = false @State private var isRequesting = false @State private var selectedLanguage: LanguageTag? + @State private var inputtedURL: String = "" init(user: User) { _profileEditVM = StateObject(wrappedValue: ProfileEditViewModel(user: user)) @@ -36,6 +38,10 @@ struct ProfileEditView: View { LanguagePickerView(selectedLanguage: $selectedLanguage) .presentationDetents([.medium]) } + .sheet(isPresented: $isPresentedURLEditor) { + URLEditorView(inputtedURL: $inputtedURL) + .presentationDetents([.medium]) + } .onChange(of: selectedLanguage) { if let selectedLanguage = selectedLanguage, !profileEditVM.editingUserInfo.tags.languageTags.contains(selectedLanguage) { @@ -100,6 +106,7 @@ struct ProfileEditView: View { } Section { Button { + isPresentedURLEditor = true } label: { Label("Add", systemImage: "plus") } @@ -125,6 +132,7 @@ struct ProfileEditView: View { Text("Save") } } + .disabled(isRequesting) } } diff --git a/harmonie/Views/Setting/URLEditorView.swift b/harmonie/Views/Setting/URLEditorView.swift new file mode 100644 index 0000000..0706e00 --- /dev/null +++ b/harmonie/Views/Setting/URLEditorView.swift @@ -0,0 +1,46 @@ +// +// URLEditorView.swift +// Harmonie +// +// Created by makinosp on 2024/08/23. +// + +import SwiftUI +import VRCKit + +struct URLEditorView: View { + @Environment(\.dismiss) private var dismiss + @Binding private var inputtedURL: String + + init(inputtedURL: Binding) { + _inputtedURL = inputtedURL + } + + var body: some View { + NavigationStack { + Form { + TextField("https://", text: $inputtedURL) + } + .navigationTitle("Enter URL") + .toolbarTitleDisplayMode(.inline) + .toolbar { toolbarContents } + } + } + + @ToolbarContentBuilder private var toolbarContents: some ToolbarContent { + ToolbarItem(placement: .cancellationAction) { + Button { + dismiss() + } label: { + Text("Cancel") + } + } + ToolbarItem { + Button { + dismiss() + } label: { + Text("Confirm") + } + } + } +} From 2c3acdd87a8858a49df29eeb3997cf9d459d6c14 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sat, 24 Aug 2024 08:11:44 +0900 Subject: [PATCH 26/34] fix: problem that cannot be imported --- harmonieTests/HarmonieTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/harmonieTests/HarmonieTests.swift b/harmonieTests/HarmonieTests.swift index d0d9c74..d3d93e7 100644 --- a/harmonieTests/HarmonieTests.swift +++ b/harmonieTests/HarmonieTests.swift @@ -6,7 +6,7 @@ // import XCTest -@testable import harmonie +@testable import Harmonie final class HarmonieTests: XCTestCase { From 3d3c8f5e32c3180cf8785b05e31907656b17263e Mon Sep 17 00:00:00 2001 From: makinosp Date: Sat, 24 Aug 2024 08:12:07 +0900 Subject: [PATCH 27/34] feat: add preview in URLEditorView --- harmonie/Views/Setting/URLEditorView.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/harmonie/Views/Setting/URLEditorView.swift b/harmonie/Views/Setting/URLEditorView.swift index 0706e00..48d3630 100644 --- a/harmonie/Views/Setting/URLEditorView.swift +++ b/harmonie/Views/Setting/URLEditorView.swift @@ -44,3 +44,8 @@ struct URLEditorView: View { } } } + +#Preview { + @State var inputtedURL: String = "" + return URLEditorView(inputtedURL: $inputtedURL) +} From 61c2499cfd8aa44f9265f36eb48e6e889b3163a1 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sat, 24 Aug 2024 12:23:27 +0900 Subject: [PATCH 28/34] feat: implement entering URL text field --- harmonie/Views/Setting/URLEditorView.swift | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/harmonie/Views/Setting/URLEditorView.swift b/harmonie/Views/Setting/URLEditorView.swift index 48d3630..ecfc5a3 100644 --- a/harmonie/Views/Setting/URLEditorView.swift +++ b/harmonie/Views/Setting/URLEditorView.swift @@ -11,15 +11,22 @@ import VRCKit struct URLEditorView: View { @Environment(\.dismiss) private var dismiss @Binding private var inputtedURL: String + @State private var urlString: String + @State private var isInvalid = false init(inputtedURL: Binding) { _inputtedURL = inputtedURL + _urlString = State(initialValue: inputtedURL.wrappedValue) } var body: some View { NavigationStack { Form { - TextField("https://", text: $inputtedURL) + TextField("https://", text: $urlString) + .textInputAutocapitalization(.never) + .onChange(of: urlString, initial: true) { + isInvalid = !isValidURLFormat(urlString) + } } .navigationTitle("Enter URL") .toolbarTitleDisplayMode(.inline) @@ -27,6 +34,14 @@ struct URLEditorView: View { } } + private func isValidURLFormat(_ string: String) -> Bool { + if let url = URL(string: string), UIApplication.shared.canOpenURL(url) { + return true + } else { + return false + } + } + @ToolbarContentBuilder private var toolbarContents: some ToolbarContent { ToolbarItem(placement: .cancellationAction) { Button { @@ -37,10 +52,12 @@ struct URLEditorView: View { } ToolbarItem { Button { + inputtedURL = urlString dismiss() } label: { Text("Confirm") } + .disabled(isInvalid) } } } From a0b51f7b11d31a0b4d917dc36f55b536191d1fb3 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sat, 24 Aug 2024 12:28:31 +0900 Subject: [PATCH 29/34] refact: directory structure of profile settings --- Harmonie.xcodeproj/project.pbxproj | 20 +++++++++++++------ .../{ => Profile}/LanguagePickerView.swift | 0 .../{ => Profile}/ProfileEditView.swift | 0 .../SettingsView+ProfileSection.swift | 0 .../Setting/{ => Profile}/URLEditorView.swift | 0 5 files changed, 14 insertions(+), 6 deletions(-) rename harmonie/Views/Setting/{ => Profile}/LanguagePickerView.swift (100%) rename harmonie/Views/Setting/{ => Profile}/ProfileEditView.swift (100%) rename harmonie/Views/Setting/{ => Profile}/SettingsView+ProfileSection.swift (100%) rename harmonie/Views/Setting/{ => Profile}/URLEditorView.swift (100%) diff --git a/Harmonie.xcodeproj/project.pbxproj b/Harmonie.xcodeproj/project.pbxproj index fb7c076..7827c86 100644 --- a/Harmonie.xcodeproj/project.pbxproj +++ b/Harmonie.xcodeproj/project.pbxproj @@ -86,7 +86,7 @@ /* Begin PBXFileReference section */ 200723962BA56377002C27E8 /* UserDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDetailView.swift; sourceTree = ""; }; - 200BC2D62C74B49A0025872B /* SettingsView+ProfileSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "SettingsView+ProfileSection.swift"; path = "Harmonie/Views/Setting/SettingsView+ProfileSection.swift"; sourceTree = SOURCE_ROOT; }; + 200BC2D62C74B49A0025872B /* SettingsView+ProfileSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "SettingsView+ProfileSection.swift"; path = "harmonie/Views/Setting/Profile/SettingsView+ProfileSection.swift"; sourceTree = SOURCE_ROOT; }; 200FA7AC2C1D61AB0001E655 /* CircleURLImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircleURLImage.swift; sourceTree = ""; }; 200FA7AF2C1D745B0001E655 /* LocationCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationCardView.swift; sourceTree = ""; }; 20102BCE2C157C77007738D3 /* HarmonieApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HarmonieApp.swift; sourceTree = ""; }; @@ -97,7 +97,7 @@ 202844C32BA5CE1B00357D09 /* LocationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationsView.swift; sourceTree = ""; }; 202B9E362C6A426500B981C9 /* FriendsView+Filters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "FriendsView+Filters.swift"; path = "Harmonie/Views/Friend/FriendsView+Filters.swift"; sourceTree = SOURCE_ROOT; }; 202B9E382C6A430900B981C9 /* FriendViewModel+Filters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "FriendViewModel+Filters.swift"; path = "Harmonie/ViewModels/FriendViewModel+Filters.swift"; sourceTree = SOURCE_ROOT; }; - 2033B9402C78C0CA0005BD36 /* URLEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = URLEditorView.swift; path = Harmonie/Views/Setting/URLEditorView.swift; sourceTree = SOURCE_ROOT; }; + 2033B9402C78C0CA0005BD36 /* URLEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = URLEditorView.swift; path = harmonie/Views/Setting/Profile/URLEditorView.swift; sourceTree = SOURCE_ROOT; }; 20395D912B945CD700003921 /* Harmonie.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Harmonie.app; sourceTree = BUILT_PRODUCTS_DIR; }; 20395D962B945CD700003921 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 20395D9A2B945CD800003921 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -112,7 +112,7 @@ 205B3BF32C00D8BF0097D62A /* ProgressScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressScreen.swift; sourceTree = ""; }; 205C9DAE2C20612500E3782C /* Binding+NilCoalescing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+NilCoalescing.swift"; sourceTree = ""; }; 206088E82C48079900626DDC /* LocationDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDetailView.swift; sourceTree = ""; }; - 20634BC22C760A2D00D57D57 /* ProfileEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ProfileEditView.swift; path = Harmonie/Views/Setting/ProfileEditView.swift; sourceTree = SOURCE_ROOT; }; + 20634BC22C760A2D00D57D57 /* ProfileEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ProfileEditView.swift; path = harmonie/Views/Setting/Profile/ProfileEditView.swift; sourceTree = SOURCE_ROOT; }; 2065F86F2C788A59007F132B /* ProfileEditViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProfileEditViewModel.swift; path = Harmonie/ViewModels/ProfileEditViewModel.swift; sourceTree = SOURCE_ROOT; }; 207FC6322C4256E60018E605 /* View+SizeModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+SizeModifier.swift"; sourceTree = ""; }; 207FC6332C4256E60018E605 /* View+ErrorAlertModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+ErrorAlertModifier.swift"; sourceTree = ""; }; @@ -130,7 +130,7 @@ 209D772F2C2251C200A11666 /* MainTabView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainTabView.swift; sourceTree = ""; }; 20AD8D972C68C5C300140232 /* NavigationSplitView+initializers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "NavigationSplitView+initializers.swift"; path = "Harmonie/Extensions/NavigationSplitView+initializers.swift"; sourceTree = SOURCE_ROOT; }; 20AFBC832C68EE5D006E9239 /* DateUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DateUtil.swift; path = Harmonie/Utils/DateUtil.swift; sourceTree = SOURCE_ROOT; }; - 20D50FA62C77F94F00F89B4F /* LanguagePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LanguagePickerView.swift; path = Harmonie/Views/Setting/LanguagePickerView.swift; sourceTree = SOURCE_ROOT; }; + 20D50FA62C77F94F00F89B4F /* LanguagePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LanguagePickerView.swift; path = harmonie/Views/Setting/Profile/LanguagePickerView.swift; sourceTree = SOURCE_ROOT; }; 20E1902D2C4E820A003F39AF /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Constants.swift; path = Harmonie/Utils/Constants.swift; sourceTree = SOURCE_ROOT; }; 20F95D272C69BC2D002CDC5F /* UserDetailView+Actions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UserDetailView+Actions.swift"; path = "Harmonie/Views/UserDetail/UserDetailView+Actions.swift"; sourceTree = SOURCE_ROOT; }; 20F95D292C69BC4B002CDC5F /* UserDetailView+ActivitySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "UserDetailView+ActivitySection.swift"; path = "Harmonie/Views/UserDetail/UserDetailView+ActivitySection.swift"; sourceTree = SOURCE_ROOT; }; @@ -312,15 +312,23 @@ sourceTree = ""; }; 206088F02C4808C500626DDC /* Setting */ = { + isa = PBXGroup; + children = ( + 207836FB2C798A8C00659C91 /* Profile */, + 205B3BF12C00D51A0097D62A /* SettingsView.swift */, + ); + path = Setting; + sourceTree = ""; + }; + 207836FB2C798A8C00659C91 /* Profile */ = { isa = PBXGroup; children = ( 20D50FA62C77F94F00F89B4F /* LanguagePickerView.swift */, 20634BC22C760A2D00D57D57 /* ProfileEditView.swift */, - 205B3BF12C00D51A0097D62A /* SettingsView.swift */, 200BC2D62C74B49A0025872B /* SettingsView+ProfileSection.swift */, 2033B9402C78C0CA0005BD36 /* URLEditorView.swift */, ); - path = Setting; + path = Profile; sourceTree = ""; }; 208555112BF8E8BB0079A92F /* Utils */ = { diff --git a/harmonie/Views/Setting/LanguagePickerView.swift b/harmonie/Views/Setting/Profile/LanguagePickerView.swift similarity index 100% rename from harmonie/Views/Setting/LanguagePickerView.swift rename to harmonie/Views/Setting/Profile/LanguagePickerView.swift diff --git a/harmonie/Views/Setting/ProfileEditView.swift b/harmonie/Views/Setting/Profile/ProfileEditView.swift similarity index 100% rename from harmonie/Views/Setting/ProfileEditView.swift rename to harmonie/Views/Setting/Profile/ProfileEditView.swift diff --git a/harmonie/Views/Setting/SettingsView+ProfileSection.swift b/harmonie/Views/Setting/Profile/SettingsView+ProfileSection.swift similarity index 100% rename from harmonie/Views/Setting/SettingsView+ProfileSection.swift rename to harmonie/Views/Setting/Profile/SettingsView+ProfileSection.swift diff --git a/harmonie/Views/Setting/URLEditorView.swift b/harmonie/Views/Setting/Profile/URLEditorView.swift similarity index 100% rename from harmonie/Views/Setting/URLEditorView.swift rename to harmonie/Views/Setting/Profile/URLEditorView.swift From 64eb9ae0c6fae7227e0579047131214f682c5b84 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sat, 24 Aug 2024 12:51:27 +0900 Subject: [PATCH 30/34] refact: improvement UI of ProfileEditView --- .../Setting/Profile/ProfileEditView.swift | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/harmonie/Views/Setting/Profile/ProfileEditView.swift b/harmonie/Views/Setting/Profile/ProfileEditView.swift index a0b17ea..858000d 100644 --- a/harmonie/Views/Setting/Profile/ProfileEditView.swift +++ b/harmonie/Views/Setting/Profile/ProfileEditView.swift @@ -65,7 +65,10 @@ struct ProfileEditView: View { .foregroundStyle(profileEditVM.editingUserInfo.status.color) } } - TextField("Status Description", text: $profileEditVM.editingUserInfo.statusDescription) + TextField( + "Status Description", + text: $profileEditVM.editingUserInfo.statusDescription + ) } } @@ -101,7 +104,25 @@ struct ProfileEditView: View { @ViewBuilder var bioLinksSection: some View { Section("Bio Links") { ForEach(profileEditVM.editingUserInfo.bioLinks) { url in - Link(url.description, destination: url) + Link(destination: url) { + Label { + Text(url.description) + } icon: { + Image(systemName: "link") + .foregroundStyle(Color(uiColor: .label)) + } + } + .swipeActions { + Button(role: .destructive) { + // Delete action + } label: { + Label { + Text("Delete") + } icon: { + Image(systemName: "message.badge") + } + } + } } } Section { From f56d37de99f42d92414e75b3d1186ddfd497e0b5 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sat, 24 Aug 2024 12:56:02 +0900 Subject: [PATCH 31/34] 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 5a57b8d..d128900 100644 --- a/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Harmonie.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -52,7 +52,7 @@ "location" : "https://github.com/makinosp/vrckit", "state" : { "branch" : "develop", - "revision" : "adb79ce2bf8d49bd8fe350aacb890d049e7eaa5a" + "revision" : "43f7122284ee3e34a997b6dd7ae2614dd1d0c8d9" } } ], From 1b5501cf5072f23148c8a4c4f2af4f6a3cfdcf5a Mon Sep 17 00:00:00 2001 From: makinosp Date: Sat, 24 Aug 2024 13:07:17 +0900 Subject: [PATCH 32/34] feat: link the entered URL in URLEditorView to ProfileEditView --- harmonie/Views/Setting/Profile/ProfileEditView.swift | 7 ++++++- harmonie/Views/Setting/Profile/URLEditorView.swift | 10 +++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/harmonie/Views/Setting/Profile/ProfileEditView.swift b/harmonie/Views/Setting/Profile/ProfileEditView.swift index 858000d..591cb16 100644 --- a/harmonie/Views/Setting/Profile/ProfileEditView.swift +++ b/harmonie/Views/Setting/Profile/ProfileEditView.swift @@ -16,7 +16,7 @@ struct ProfileEditView: View { @State private var isPresentedURLEditor = false @State private var isRequesting = false @State private var selectedLanguage: LanguageTag? - @State private var inputtedURL: String = "" + @State private var inputtedURL: URL? init(user: User) { _profileEditVM = StateObject(wrappedValue: ProfileEditViewModel(user: user)) @@ -49,6 +49,11 @@ struct ProfileEditView: View { self.selectedLanguage = nil } } + .onChange(of: inputtedURL) { + guard let inputtedURL = inputtedURL else { return } + profileEditVM.editingUserInfo.bioLinks.append(inputtedURL) + self.inputtedURL = nil + } } var statusSection: some View { diff --git a/harmonie/Views/Setting/Profile/URLEditorView.swift b/harmonie/Views/Setting/Profile/URLEditorView.swift index ecfc5a3..c4aa720 100644 --- a/harmonie/Views/Setting/Profile/URLEditorView.swift +++ b/harmonie/Views/Setting/Profile/URLEditorView.swift @@ -10,13 +10,13 @@ import VRCKit struct URLEditorView: View { @Environment(\.dismiss) private var dismiss - @Binding private var inputtedURL: String + @Binding private var inputtedURL: URL? @State private var urlString: String @State private var isInvalid = false - init(inputtedURL: Binding) { + init(inputtedURL: Binding, urlString: String = "") { _inputtedURL = inputtedURL - _urlString = State(initialValue: inputtedURL.wrappedValue) + _urlString = State(initialValue: urlString) } var body: some View { @@ -52,7 +52,7 @@ struct URLEditorView: View { } ToolbarItem { Button { - inputtedURL = urlString + inputtedURL = URL(string: urlString) dismiss() } label: { Text("Confirm") @@ -63,6 +63,6 @@ struct URLEditorView: View { } #Preview { - @State var inputtedURL: String = "" + @State var inputtedURL: URL? return URLEditorView(inputtedURL: $inputtedURL) } From 32c5f1d74507e44b7a1f647f349fff0b751d931b Mon Sep 17 00:00:00 2001 From: makinosp Date: Sat, 24 Aug 2024 13:10:01 +0900 Subject: [PATCH 33/34] refact: improvement text fields in AuthenticationView --- harmonie/Views/Authentication/AuthenticationView.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/harmonie/Views/Authentication/AuthenticationView.swift b/harmonie/Views/Authentication/AuthenticationView.swift index 393eb56..eb59679 100644 --- a/harmonie/Views/Authentication/AuthenticationView.swift +++ b/harmonie/Views/Authentication/AuthenticationView.swift @@ -40,14 +40,15 @@ struct AuthenticationView: View { Image(systemName: "at") .foregroundStyle(Color.gray) TextField("UserName", text: $username) - .textFieldStyle(RoundedBorderTextFieldStyle()) + .textInputAutocapitalization(.never) + .textFieldStyle(.roundedBorder) } .padding(.horizontal, 8) HStack { Image(systemName: "lock") .foregroundStyle(Color.gray) SecureField("Password", text: $password) - .textFieldStyle(RoundedBorderTextFieldStyle()) + .textFieldStyle(.roundedBorder) } .padding(.horizontal, 8) } @@ -58,7 +59,8 @@ struct AuthenticationView: View { Image(systemName: "ellipsis") .foregroundStyle(Color.gray) TextField("Code", text: $code) - .textFieldStyle(RoundedBorderTextFieldStyle()) + .keyboardType(.decimalPad) + .textFieldStyle(.roundedBorder) } .padding(.horizontal, 8) .background( From eca2803447e4f76d83033be329e828f5077117d8 Mon Sep 17 00:00:00 2001 From: makinosp Date: Sat, 24 Aug 2024 13:16:16 +0900 Subject: [PATCH 34/34] refact: lint --- harmonie/Utils/Constants.swift | 2 +- harmonie/Views/Favorite/FavoritesView.swift | 2 +- harmonie/Views/UserDetail/UserDetailView+Toolbar.swift | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/harmonie/Utils/Constants.swift b/harmonie/Utils/Constants.swift index e29f14f..4a0e001 100644 --- a/harmonie/Utils/Constants.swift +++ b/harmonie/Utils/Constants.swift @@ -15,7 +15,7 @@ enum Constants { .imageScale(.small) } } - + enum IconSize { static let thumbnail = CGSize(width: 28, height: 28) static let thumbnailOutside = CGSize(width: 32, height: 32) diff --git a/harmonie/Views/Favorite/FavoritesView.swift b/harmonie/Views/Favorite/FavoritesView.swift index 7904656..4ea0c81 100644 --- a/harmonie/Views/Favorite/FavoritesView.swift +++ b/harmonie/Views/Favorite/FavoritesView.swift @@ -20,7 +20,7 @@ struct FavoritesView: View { } .navigationSplitViewStyle(.balanced) } - + var listView: some View { List { ForEach(favoriteVM.favoriteFriendGroups) { group in diff --git a/harmonie/Views/UserDetail/UserDetailView+Toolbar.swift b/harmonie/Views/UserDetail/UserDetailView+Toolbar.swift index 34468dc..9127f84 100644 --- a/harmonie/Views/UserDetail/UserDetailView+Toolbar.swift +++ b/harmonie/Views/UserDetail/UserDetailView+Toolbar.swift @@ -28,7 +28,7 @@ extension UserDetailView { } } } - + func favoriteMenuItem(group: FavoriteGroup) -> some View { AsyncButton { await updateFavorite(friendId: user.id, group: group) @@ -45,7 +45,7 @@ extension UserDetailView { } } } - + var favoriteIconName: String { favoriteVM.isAdded(friendId: user.id) ? Constants.IconName.favoriteFilled