From b5840acaf92a672d6fc39b6332881ff04149e968 Mon Sep 17 00:00:00 2001 From: makinosp Date: Thu, 19 Sep 2024 15:11:01 +0900 Subject: [PATCH 01/17] update: bump up to 0.7.0 --- Harmonie.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Harmonie.xcodeproj/project.pbxproj b/Harmonie.xcodeproj/project.pbxproj index 83ac8e5..cbd6677 100644 --- a/Harmonie.xcodeproj/project.pbxproj +++ b/Harmonie.xcodeproj/project.pbxproj @@ -470,7 +470,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.6.0; + MARKETING_VERSION = 0.7.0; PRODUCT_BUNDLE_IDENTIFIER = jp.mknn.harmonie; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -505,7 +505,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.6.0; + MARKETING_VERSION = 0.7.0; PRODUCT_BUNDLE_IDENTIFIER = jp.mknn.harmonie; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; From ed2f9d73d3e1757d1e90bd72db61c549a7fbd76b Mon Sep 17 00:00:00 2001 From: makinosp Date: Thu, 19 Sep 2024 21:19:42 +0900 Subject: [PATCH 02/17] feat: replace screen transition in SettingsView from navigationDestination to List selection --- harmonie/Views/Setting/SettingsView.swift | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/harmonie/Views/Setting/SettingsView.swift b/harmonie/Views/Setting/SettingsView.swift index 962b457..6bf0d0b 100644 --- a/harmonie/Views/Setting/SettingsView.swift +++ b/harmonie/Views/Setting/SettingsView.swift @@ -13,18 +13,20 @@ struct SettingsView: View, AuthenticationServicePresentable { @Environment(AppViewModel.self) var appVM: AppViewModel @State var destination: Destination? @State var isPresentedForm = false + @State private var columnVisibility: NavigationSplitViewVisibility = .all enum Destination: Hashable { case userDetail, about, license } var body: some View { - NavigationSplitView(columnVisibility: .constant(.all)) { + NavigationSplitView(columnVisibility: $columnVisibility) { settingsContent - .navigationDestination(item: $destination) { destination in - presentDestination(destination) - } .navigationTitle("Settings") + } detail: { + if let destination = destination { + presentDestination(destination) + } } .navigationSplitViewStyle(.balanced) .onAppear { @@ -56,7 +58,7 @@ struct SettingsView: View, AuthenticationServicePresentable { } private var settingsContent: some View { - List { + List(selection: $destination) { if let user = appVM.user { profileSection(user: user) } From d37f343808946cc8c559b6f991e2cf7ac202dcf2 Mon Sep 17 00:00:00 2001 From: kiripoipoi Date: Thu, 19 Sep 2024 21:46:58 +0900 Subject: [PATCH 03/17] feat: add BittenCircle and FriendStatusCircle --- harmonie/Components/BittenCircle.swift | 42 +++++++++++++++++++ harmonie/Components/FriendStatusCircle.swift | 35 ++++++++++++++++ harmonie/Extensions/Status+isWebColor.swift | 19 +++++++++ .../Favorite/FavoriteFriendListView.swift | 8 ++-- harmonie/Views/Friend/FriendsListView.swift | 8 ++-- .../Views/Location/LocationCardView.swift | 8 ++-- 6 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 harmonie/Components/BittenCircle.swift create mode 100644 harmonie/Components/FriendStatusCircle.swift create mode 100644 harmonie/Extensions/Status+isWebColor.swift diff --git a/harmonie/Components/BittenCircle.swift b/harmonie/Components/BittenCircle.swift new file mode 100644 index 0000000..5ccb867 --- /dev/null +++ b/harmonie/Components/BittenCircle.swift @@ -0,0 +1,42 @@ +// +// BittenCircle.swift +// Harmonie +// +// Created by xili on 2024/09/18. +// +import SwiftUI +struct BittenCircle: Shape { + private let biteSize: CGFloat = Constants.IconSize.thumbnailOutside.width * 0.4 + private let offsetRatio: CGFloat = 0.65 + func path(in rect: CGRect) -> Path { + var path = rectanglePath(rect) + path.addPath(circlePath(rect)) + return path + } + private func rectanglePath(_ rect: CGRect) -> Path { + Rectangle().path(in: rect) + } + private func circlePath(_ rect: CGRect) -> Path { + Circle() + .path(in: circleRect(rect, size: biteSize)) + } + private func circleRect(_ rect: CGRect, size: CGFloat) -> CGRect { + CGRect(origin: .zero, size: CGSize(width: size, height: size)) + .offsetBy(dx: offsetBy(rect.maxX), dy: offsetBy(rect.maxX)) + } + private func offsetBy(_ maxX: CGFloat) -> CGFloat { + maxX * offsetRatio + } +} + +struct BittenCircle_Previews: PreviewProvider { + static var previews: some View { + Circle() + .foregroundStyle(.blue) + .mask( + BittenCircle() + .fill(style: FillStyle(eoFill: true)) + ) + .frame(width: Constants.IconSize.thumbnailOutside.width, height: Constants.IconSize.thumbnailOutside.height) + } +} diff --git a/harmonie/Components/FriendStatusCircle.swift b/harmonie/Components/FriendStatusCircle.swift new file mode 100644 index 0000000..e543ed9 --- /dev/null +++ b/harmonie/Components/FriendStatusCircle.swift @@ -0,0 +1,35 @@ +// +// FriendStatusCircle.swift +// Harmonie +// +// Created by xili on 2024/09/19. +// +import SwiftUI +struct FriendStatusCircle: View { + private let statusColor: Color + private let platformColor: Color + init(statusColor: Color, platformColor: Color) { + self.statusColor = statusColor + self.platformColor = platformColor + } + var body: some View { + Circle() + .frame( + width: Constants.IconSize.thumbnailOutside.width * 0.3, + height: Constants.IconSize.thumbnailOutside.height * 0.3 + ) + .foregroundColor(statusColor) + .overlay( + Circle() + .frame( + width: Constants.IconSize.thumbnailOutside.width * 0.15, + height: Constants.IconSize.thumbnailOutside.height * 0.15 + ) + .foregroundColor(platformColor) + ) + .offset( + x: Constants.IconSize.thumbnailOutside.width * 0.36, + y: Constants.IconSize.thumbnailOutside.height * 0.36 + ) + } +} diff --git a/harmonie/Extensions/Status+isWebColor.swift b/harmonie/Extensions/Status+isWebColor.swift new file mode 100644 index 0000000..d5b5ba9 --- /dev/null +++ b/harmonie/Extensions/Status+isWebColor.swift @@ -0,0 +1,19 @@ +// +// Status+isWebColor.swift +// Harmonie +// +// Created by xili on 2024/09/17. +// + +import SwiftUI +import VRCKit + +extension UserPlatform { + var isWebColor: Color { + switch self { + case .web: Color.black.opacity(0.7) + case .standalonewindows: .clear + default: Color.black.opacity(0.7) + } + } +} diff --git a/harmonie/Views/Favorite/FavoriteFriendListView.swift b/harmonie/Views/Favorite/FavoriteFriendListView.swift index dd5e0d7..27fa3ea 100644 --- a/harmonie/Views/Favorite/FavoriteFriendListView.swift +++ b/harmonie/Views/Favorite/FavoriteFriendListView.swift @@ -48,12 +48,14 @@ struct FavoriteFriendListView: View { Text(friend.displayName) } icon: { ZStack { - Circle() - .foregroundStyle(friend.status.color) - .frame(size: Constants.IconSize.thumbnailOutside) CircleURLImage( imageUrl: friend.imageUrl(.x256), size: Constants.IconSize.thumbnail + ) + .mask(BittenCircle().fill(style: FillStyle(eoFill: true))) + FriendStatusCircle( + statusColor: friend.status.color, + platformColor: friend.platform.isWebColor ) } } diff --git a/harmonie/Views/Friend/FriendsListView.swift b/harmonie/Views/Friend/FriendsListView.swift index f1288cc..cc0d5d0 100644 --- a/harmonie/Views/Friend/FriendsListView.swift +++ b/harmonie/Views/Friend/FriendsListView.swift @@ -30,12 +30,14 @@ struct FriendsListView: View, FriendServicePresentable { } } icon: { ZStack { - Circle() - .foregroundStyle(friend.status.color) - .frame(size: Constants.IconSize.thumbnailOutside) CircleURLImage( imageUrl: friend.imageUrl(.x256), size: Constants.IconSize.thumbnail + ) + .mask(BittenCircle().fill(style: FillStyle(eoFill: true))) + FriendStatusCircle( + statusColor: friend.status.color, + platformColor: friend.platform.isWebColor ) } } diff --git a/harmonie/Views/Location/LocationCardView.swift b/harmonie/Views/Location/LocationCardView.swift index accba60..e129f54 100644 --- a/harmonie/Views/Location/LocationCardView.swift +++ b/harmonie/Views/Location/LocationCardView.swift @@ -75,12 +75,14 @@ struct LocationCardView: View, InstanceServicePresentable { private func friendThumbnail(friend: Friend) -> some View { ZStack { - Circle() - .foregroundStyle(friend.status.color) - .frame(size: Constants.IconSize.thumbnailOutside) CircleURLImage( imageUrl: friend.imageUrl(.x256), size: Constants.IconSize.thumbnail + ) + .mask(BittenCircle().fill(style: FillStyle(eoFill: true))) + FriendStatusCircle( + statusColor: friend.status.color, + platformColor: friend.platform.isWebColor ) } } From 2048a14ea14d452140ec54b9726560846b17e87a Mon Sep 17 00:00:00 2001 From: makinosp Date: Thu, 19 Sep 2024 21:49:54 +0900 Subject: [PATCH 04/17] feat: selectable item in SettingsView --- .../Profile/SettingsView+ProfileSection.swift | 35 +++++++--------- .../Setting/SettingsView+AboutSection.swift | 42 +++++++++---------- harmonie/Views/Setting/SettingsView.swift | 36 ++++++++-------- 3 files changed, 54 insertions(+), 59 deletions(-) diff --git a/harmonie/Views/Setting/Profile/SettingsView+ProfileSection.swift b/harmonie/Views/Setting/Profile/SettingsView+ProfileSection.swift index ef0c554..9aac472 100644 --- a/harmonie/Views/Setting/Profile/SettingsView+ProfileSection.swift +++ b/harmonie/Views/Setting/Profile/SettingsView+ProfileSection.swift @@ -10,31 +10,28 @@ 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.imageUrl(.x256), - size: Constants.IconSize.ll - ) - } - Spacer() + Section(header: Text("Profile")) { + LabeledContent { + if UIDevice.current.userInterfaceIdiom == .phone { Constants.Icon.forward } - .frame(maxWidth: .infinity, alignment: .leading) - .contentShape(Rectangle()) + } label: { + Label { + Text(user.displayName) + } icon: { + CircleURLImage( + imageUrl: user.imageUrl(.x256), + size: Constants.IconSize.ll + ) + } + .padding(.vertical, 8) } + .tag(Destination.userDetail) + Button { isPresentedForm = true } label: { - HStack(alignment: .center) { - Label("Edit", systemImage: "pencil") - } + Label("Edit", systemImage: "pencil") } } .textCase(nil) diff --git a/harmonie/Views/Setting/SettingsView+AboutSection.swift b/harmonie/Views/Setting/SettingsView+AboutSection.swift index 52e969b..4aec049 100644 --- a/harmonie/Views/Setting/SettingsView+AboutSection.swift +++ b/harmonie/Views/Setting/SettingsView+AboutSection.swift @@ -18,39 +18,35 @@ extension SettingsView { var aboutSection: some View { Section("About") { - Button { - destination = .about + LabeledContent { + Constants.Icon.forward } label: { - LabeledContent { - Constants.Icon.forward - } label: { - Label { - Text("About This App") - } icon: { - Image(systemName: "info.circle") - } - } - } - Link(destination: URL(string: "https://github.com/makinosp/harmonie")!) { Label { - Text("Source Code") + Text("About This App") } icon: { - Image(systemName: "curlybraces") + Image(systemName: "info.circle") } } - Button { - destination = .license - } label: { - LabeledContent { - Constants.Icon.forward - } label: { + .tag(Destination.about) + if let sourceCodeUrl = URL(string: "https://github.com/makinosp/harmonie") { + Link(destination: sourceCodeUrl) { Label { - Text("Third Party Licence") + Text("Source Code") } icon: { - Image(systemName: "lightbulb") + Image(systemName: "curlybraces") } } } + LabeledContent { + Constants.Icon.forward + } label: { + Label { + Text("Third Party Licence") + } icon: { + Image(systemName: "lightbulb") + } + } + .tag(Destination.license) } } diff --git a/harmonie/Views/Setting/SettingsView.swift b/harmonie/Views/Setting/SettingsView.swift index 6bf0d0b..a060315 100644 --- a/harmonie/Views/Setting/SettingsView.swift +++ b/harmonie/Views/Setting/SettingsView.swift @@ -11,10 +11,12 @@ import VRCKit struct SettingsView: View, AuthenticationServicePresentable { @Environment(AppViewModel.self) var appVM: AppViewModel - @State var destination: Destination? + @State var destination: Destination? = UIDevice.current.userInterfaceIdiom == .pad ? .userDetail : nil @State var isPresentedForm = false @State private var columnVisibility: NavigationSplitViewVisibility = .all + @State private var selectedLibrary: Library? + enum Destination: Hashable { case userDetail, about, license } @@ -29,11 +31,6 @@ struct SettingsView: View, AuthenticationServicePresentable { } } .navigationSplitViewStyle(.balanced) - .onAppear { - if UIDevice.current.userInterfaceIdiom == .pad { - destination = .userDetail - } - } .sheet(isPresented: $isPresentedForm) { if let user = appVM.user { ProfileEditView(profileEditVM: ProfileEditViewModel(user: user)) @@ -51,9 +48,20 @@ struct SettingsView: View, AuthenticationServicePresentable { case .about: aboutThisApp case .license: - LicenseListView() - .navigationTitle("Third Party Licence") - .navigationBarTitleDisplayMode(.inline) + Group { + if let selectedLibrary = selectedLibrary { + LicenseView(library: selectedLibrary) + .onDisappear { + self.selectedLibrary = nil + } + } else { + LicenseListView { library in + selectedLibrary = library + } + } + } + .navigationTitle("Third Party Licence") + .navigationBarTitleDisplayMode(.inline) } } @@ -64,16 +72,10 @@ struct SettingsView: View, AuthenticationServicePresentable { } aboutSection Section { - AsyncButton { + AsyncButton(role: .destructive) { await appVM.logout(service: authenticationService) } label: { - Label { - Text("Logout") - .foregroundStyle(Color.red) - } icon: { - Image(systemName: "rectangle.portrait.and.arrow.forward") - .foregroundStyle(Color.red) - } + Label("Logout", systemImage: "rectangle.portrait.and.arrow.forward") } } } From 4172c3361ca55134f0bdede3c861c08130eb2465 Mon Sep 17 00:00:00 2001 From: makinosp Date: Thu, 19 Sep 2024 22:09:24 +0900 Subject: [PATCH 05/17] feat: presentation LicenceView with sheet --- harmonie/Views/Setting/SettingsView.swift | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/harmonie/Views/Setting/SettingsView.swift b/harmonie/Views/Setting/SettingsView.swift index a060315..094821b 100644 --- a/harmonie/Views/Setting/SettingsView.swift +++ b/harmonie/Views/Setting/SettingsView.swift @@ -36,6 +36,9 @@ struct SettingsView: View, AuthenticationServicePresentable { ProfileEditView(profileEditVM: ProfileEditViewModel(user: user)) } } + .sheet(item: $selectedLibrary) { library in + LicenseView(library: library) + } } @ViewBuilder @@ -48,16 +51,11 @@ struct SettingsView: View, AuthenticationServicePresentable { case .about: aboutThisApp case .license: - Group { - if let selectedLibrary = selectedLibrary { - LicenseView(library: selectedLibrary) - .onDisappear { - self.selectedLibrary = nil - } - } else { - LicenseListView { library in - selectedLibrary = library - } + List(Library.libraries) { library in + Button { + selectedLibrary = library + } label: { + Text(library.name) } } .navigationTitle("Third Party Licence") From 3ff4855fa592427bf3981681c69f5bca39ee755c Mon Sep 17 00:00:00 2001 From: makinosp Date: Thu, 19 Sep 2024 22:13:49 +0900 Subject: [PATCH 06/17] refact: lint --- harmonie/Components/BittenCircle.swift | 2 ++ harmonie/Components/FriendStatusCircle.swift | 2 ++ 2 files changed, 4 insertions(+) diff --git a/harmonie/Components/BittenCircle.swift b/harmonie/Components/BittenCircle.swift index 5ccb867..1f78cc0 100644 --- a/harmonie/Components/BittenCircle.swift +++ b/harmonie/Components/BittenCircle.swift @@ -4,7 +4,9 @@ // // Created by xili on 2024/09/18. // + import SwiftUI + struct BittenCircle: Shape { private let biteSize: CGFloat = Constants.IconSize.thumbnailOutside.width * 0.4 private let offsetRatio: CGFloat = 0.65 diff --git a/harmonie/Components/FriendStatusCircle.swift b/harmonie/Components/FriendStatusCircle.swift index e543ed9..d380bb3 100644 --- a/harmonie/Components/FriendStatusCircle.swift +++ b/harmonie/Components/FriendStatusCircle.swift @@ -4,7 +4,9 @@ // // Created by xili on 2024/09/19. // + import SwiftUI + struct FriendStatusCircle: View { private let statusColor: Color private let platformColor: Color From d68b7f9a3a4b5094854e25c4d216d1bca1f5e35d Mon Sep 17 00:00:00 2001 From: makinosp Date: Thu, 19 Sep 2024 22:21:11 +0900 Subject: [PATCH 07/17] refact: code refactoring --- harmonie/Components/BittenCircle.swift | 32 ++++++++++++------- .../Favorite/FavoriteFriendListView.swift | 10 +++--- harmonie/Views/Friend/FriendsListView.swift | 10 +++--- .../Views/Location/LocationCardView.swift | 10 +++--- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/harmonie/Components/BittenCircle.swift b/harmonie/Components/BittenCircle.swift index 1f78cc0..9bfe51d 100644 --- a/harmonie/Components/BittenCircle.swift +++ b/harmonie/Components/BittenCircle.swift @@ -8,37 +8,47 @@ import SwiftUI struct BittenCircle: Shape { - private let biteSize: CGFloat = Constants.IconSize.thumbnailOutside.width * 0.4 + private let biteSize: CGFloat private let offsetRatio: CGFloat = 0.65 + + init(biteSize: CGFloat) { + self.biteSize = biteSize + } + func path(in rect: CGRect) -> Path { var path = rectanglePath(rect) path.addPath(circlePath(rect)) return path } + private func rectanglePath(_ rect: CGRect) -> Path { Rectangle().path(in: rect) } + private func circlePath(_ rect: CGRect) -> Path { Circle() .path(in: circleRect(rect, size: biteSize)) } + private func circleRect(_ rect: CGRect, size: CGFloat) -> CGRect { CGRect(origin: .zero, size: CGSize(width: size, height: size)) .offsetBy(dx: offsetBy(rect.maxX), dy: offsetBy(rect.maxX)) } + private func offsetBy(_ maxX: CGFloat) -> CGFloat { maxX * offsetRatio } } -struct BittenCircle_Previews: PreviewProvider { - static var previews: some View { - Circle() - .foregroundStyle(.blue) - .mask( - BittenCircle() - .fill(style: FillStyle(eoFill: true)) - ) - .frame(width: Constants.IconSize.thumbnailOutside.width, height: Constants.IconSize.thumbnailOutside.height) - } +#Preview { + Circle() + .fill(.blue) + .mask( + BittenCircle(biteSize: Constants.IconSize.thumbnailOutside.width * 0.4) + .fill(style: FillStyle(eoFill: true)) + ) + .frame( + width: Constants.IconSize.thumbnailOutside.width, + height: Constants.IconSize.thumbnailOutside.height + ) } diff --git a/harmonie/Views/Favorite/FavoriteFriendListView.swift b/harmonie/Views/Favorite/FavoriteFriendListView.swift index 27fa3ea..102a827 100644 --- a/harmonie/Views/Favorite/FavoriteFriendListView.swift +++ b/harmonie/Views/Favorite/FavoriteFriendListView.swift @@ -52,11 +52,11 @@ struct FavoriteFriendListView: View { imageUrl: friend.imageUrl(.x256), size: Constants.IconSize.thumbnail ) - .mask(BittenCircle().fill(style: FillStyle(eoFill: true))) - FriendStatusCircle( - statusColor: friend.status.color, - platformColor: friend.platform.isWebColor - ) +// .mask(BittenCircle().fill(style: FillStyle(eoFill: true))) +// FriendStatusCircle( +// statusColor: friend.status.color, +// platformColor: friend.platform.isWebColor +// ) } } } diff --git a/harmonie/Views/Friend/FriendsListView.swift b/harmonie/Views/Friend/FriendsListView.swift index cc0d5d0..c33e410 100644 --- a/harmonie/Views/Friend/FriendsListView.swift +++ b/harmonie/Views/Friend/FriendsListView.swift @@ -34,11 +34,11 @@ struct FriendsListView: View, FriendServicePresentable { imageUrl: friend.imageUrl(.x256), size: Constants.IconSize.thumbnail ) - .mask(BittenCircle().fill(style: FillStyle(eoFill: true))) - FriendStatusCircle( - statusColor: friend.status.color, - platformColor: friend.platform.isWebColor - ) +// .mask(BittenCircle().fill(style: FillStyle(eoFill: true))) +// FriendStatusCircle( +// statusColor: friend.status.color, +// platformColor: friend.platform.isWebColor +// ) } } } diff --git a/harmonie/Views/Location/LocationCardView.swift b/harmonie/Views/Location/LocationCardView.swift index e129f54..ac7f2bc 100644 --- a/harmonie/Views/Location/LocationCardView.swift +++ b/harmonie/Views/Location/LocationCardView.swift @@ -79,11 +79,11 @@ struct LocationCardView: View, InstanceServicePresentable { imageUrl: friend.imageUrl(.x256), size: Constants.IconSize.thumbnail ) - .mask(BittenCircle().fill(style: FillStyle(eoFill: true))) - FriendStatusCircle( - statusColor: friend.status.color, - platformColor: friend.platform.isWebColor - ) +// .mask(BittenCircle().fill(style: FillStyle(eoFill: true))) +// FriendStatusCircle( +// statusColor: friend.status.color, +// platformColor: friend.platform.isWebColor +// ) } } } From 2699ccfe8779abdd1910f3731cfcc6c2d2b4bb8b Mon Sep 17 00:00:00 2001 From: makinosp Date: Thu, 19 Sep 2024 23:05:03 +0900 Subject: [PATCH 08/17] refact: refactoring BittenCircleMask, BittenView --- ...tenCircle.swift => BittenCircleMask.swift} | 28 ++++------- harmonie/Components/BittenView.swift | 47 +++++++++++++++++++ 2 files changed, 55 insertions(+), 20 deletions(-) rename harmonie/Components/{BittenCircle.swift => BittenCircleMask.swift} (50%) create mode 100644 harmonie/Components/BittenView.swift diff --git a/harmonie/Components/BittenCircle.swift b/harmonie/Components/BittenCircleMask.swift similarity index 50% rename from harmonie/Components/BittenCircle.swift rename to harmonie/Components/BittenCircleMask.swift index 9bfe51d..c8a5e8e 100644 --- a/harmonie/Components/BittenCircle.swift +++ b/harmonie/Components/BittenCircleMask.swift @@ -1,5 +1,5 @@ // -// BittenCircle.swift +// BittenCircleMask.swift // Harmonie // // Created by xili on 2024/09/18. @@ -7,12 +7,13 @@ import SwiftUI -struct BittenCircle: Shape { - private let biteSize: CGFloat - private let offsetRatio: CGFloat = 0.65 +struct BittenCircleMask: Shape { + private let biteSize: CGSize + private let offsetRatio: CGFloat - init(biteSize: CGFloat) { + init(biteSize: CGSize, offsetRatio: CGFloat = 0.65) { self.biteSize = biteSize + self.offsetRatio = offsetRatio } func path(in rect: CGRect) -> Path { @@ -30,8 +31,8 @@ struct BittenCircle: Shape { .path(in: circleRect(rect, size: biteSize)) } - private func circleRect(_ rect: CGRect, size: CGFloat) -> CGRect { - CGRect(origin: .zero, size: CGSize(width: size, height: size)) + private func circleRect(_ rect: CGRect, size: CGSize) -> CGRect { + CGRect(origin: .zero, size: size) .offsetBy(dx: offsetBy(rect.maxX), dy: offsetBy(rect.maxX)) } @@ -39,16 +40,3 @@ struct BittenCircle: Shape { maxX * offsetRatio } } - -#Preview { - Circle() - .fill(.blue) - .mask( - BittenCircle(biteSize: Constants.IconSize.thumbnailOutside.width * 0.4) - .fill(style: FillStyle(eoFill: true)) - ) - .frame( - width: Constants.IconSize.thumbnailOutside.width, - height: Constants.IconSize.thumbnailOutside.height - ) -} diff --git a/harmonie/Components/BittenView.swift b/harmonie/Components/BittenView.swift new file mode 100644 index 0000000..d663dfa --- /dev/null +++ b/harmonie/Components/BittenView.swift @@ -0,0 +1,47 @@ +// +// BittenView.swift +// Harmonie +// +// Created by xili on 2024/09/18. +// + +import SwiftUI + +struct BittenView: View where Content: View { + @State private var size: CGSize? + private let content: Content + private let ratio: CGFloat + + init(ratio: CGFloat = 0.4, @ViewBuilder content: () -> Content) { + self.content = content() + self.ratio = ratio + } + + private var transformed: CGSize { + guard let size = size else { return .zero } + return CGSize(width: size.width * ratio, height: size.height * ratio) + } + + var body: some View { + content + .mask( + BittenCircleMask(biteSize: transformed) + .fill(style: FillStyle(eoFill: true)) + ) + .background { + GeometryReader { geometry in + Color.clear.onAppear { + size = geometry.size + } + } + } + } +} + +#Preview { + BittenView { + Circle() + .fill(.blue) + .frame(width: 120, height: 120) + } +} From 852a7b23e72d39c44aad00038b1140f290f6d981 Mon Sep 17 00:00:00 2001 From: makinosp Date: Thu, 19 Sep 2024 23:05:35 +0900 Subject: [PATCH 09/17] refact: refactoring SectionView --- harmonie/Components/SectionView.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/harmonie/Components/SectionView.swift b/harmonie/Components/SectionView.swift index 4a6c002..e1ac346 100644 --- a/harmonie/Components/SectionView.swift +++ b/harmonie/Components/SectionView.swift @@ -8,10 +8,12 @@ import SwiftUI struct SectionView: View where Content: View { - var content: Content + private let content: Content + init(@ViewBuilder content: () -> Content) { self.content = content() } + var body: some View { VStack(alignment: .leading) { content From cf7b9e4bb806c17aa1e87ad9f750ee6866d405df Mon Sep 17 00:00:00 2001 From: makinosp Date: Thu, 19 Sep 2024 23:33:05 +0900 Subject: [PATCH 10/17] refact: refactoring StatusIndicator --- harmonie/Components/FriendStatusCircle.swift | 37 -------------- harmonie/Components/StatusIndicator.swift | 52 ++++++++++++++++++++ 2 files changed, 52 insertions(+), 37 deletions(-) delete mode 100644 harmonie/Components/FriendStatusCircle.swift create mode 100644 harmonie/Components/StatusIndicator.swift diff --git a/harmonie/Components/FriendStatusCircle.swift b/harmonie/Components/FriendStatusCircle.swift deleted file mode 100644 index d380bb3..0000000 --- a/harmonie/Components/FriendStatusCircle.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// FriendStatusCircle.swift -// Harmonie -// -// Created by xili on 2024/09/19. -// - -import SwiftUI - -struct FriendStatusCircle: View { - private let statusColor: Color - private let platformColor: Color - init(statusColor: Color, platformColor: Color) { - self.statusColor = statusColor - self.platformColor = platformColor - } - var body: some View { - Circle() - .frame( - width: Constants.IconSize.thumbnailOutside.width * 0.3, - height: Constants.IconSize.thumbnailOutside.height * 0.3 - ) - .foregroundColor(statusColor) - .overlay( - Circle() - .frame( - width: Constants.IconSize.thumbnailOutside.width * 0.15, - height: Constants.IconSize.thumbnailOutside.height * 0.15 - ) - .foregroundColor(platformColor) - ) - .offset( - x: Constants.IconSize.thumbnailOutside.width * 0.36, - y: Constants.IconSize.thumbnailOutside.height * 0.36 - ) - } -} diff --git a/harmonie/Components/StatusIndicator.swift b/harmonie/Components/StatusIndicator.swift new file mode 100644 index 0000000..93fde43 --- /dev/null +++ b/harmonie/Components/StatusIndicator.swift @@ -0,0 +1,52 @@ +// +// StatusIndicator.swift +// Harmonie +// +// Created by xili on 2024/09/19. +// + +import SwiftUI + +struct StatusIndicator: View where S: ShapeStyle { + private let content: S + private let isCutedOut: Bool + private let outer: CGFloat + + init(_ content: S, outer: CGFloat, isCutedOut: Bool = false) { + self.content = content + self.outer = outer + self.isCutedOut = isCutedOut + } + + private var frameSize: CGFloat { + outer * 0.3 + } + + private var cutoutSize: CGFloat { + outer * 0.15 + } + + private var offset: CGFloat { + outer * 0.36 + } + + var body: some View { + Circle() + .fill(content) + .frame(width: frameSize, height: frameSize) + .overlay { + Circle() + .blendMode(.destinationOut) + .frame( + width: isCutedOut ? cutoutSize : .zero, + height: isCutedOut ? cutoutSize : .zero + ) + } + .compositingGroup() + .offset(x: offset, y: offset) + } +} + +#Preview { + StatusIndicator(.blue, outer: 120) +} From 3f3125dfe9ba98e7461b6eba7c3a1c668cab3cc4 Mon Sep 17 00:00:00 2001 From: makinosp Date: Fri, 20 Sep 2024 08:47:57 +0900 Subject: [PATCH 11/17] feat: add extension CGSize+Operator --- harmonie/Extensions/CGSize+Operator.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 harmonie/Extensions/CGSize+Operator.swift diff --git a/harmonie/Extensions/CGSize+Operator.swift b/harmonie/Extensions/CGSize+Operator.swift new file mode 100644 index 0000000..1ac6753 --- /dev/null +++ b/harmonie/Extensions/CGSize+Operator.swift @@ -0,0 +1,14 @@ +// +// CGSize+Operator.swift +// Harmonie +// +// Created by makinosp on 2024/09/20. +// + +import CoreGraphics + +extension CGSize { + static func * (lhs: CGSize, rhs: CGFloat) -> CGSize { + return CGSize(width: lhs.width * rhs, height: lhs.height * rhs) + } +} From b76cbca50e9e8076cdec627a266f9256bce5d777 Mon Sep 17 00:00:00 2001 From: makinosp Date: Fri, 20 Sep 2024 08:48:39 +0900 Subject: [PATCH 12/17] refact: refactor StatusIndicator initializer --- harmonie/Components/StatusIndicator.swift | 32 +++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/harmonie/Components/StatusIndicator.swift b/harmonie/Components/StatusIndicator.swift index 93fde43..46b5865 100644 --- a/harmonie/Components/StatusIndicator.swift +++ b/harmonie/Components/StatusIndicator.swift @@ -10,43 +10,43 @@ import SwiftUI struct StatusIndicator: View where S: ShapeStyle { private let content: S private let isCutedOut: Bool - private let outer: CGFloat + private let outerSize: CGSize - init(_ content: S, outer: CGFloat, isCutedOut: Bool = false) { + init(_ content: S, outerSize: CGSize, isCutedOut: Bool = false) { self.content = content - self.outer = outer + self.outerSize = outerSize self.isCutedOut = isCutedOut } - private var frameSize: CGFloat { - outer * 0.3 + private var frameSize: CGSize { + outerSize * 0.3 } - private var cutoutSize: CGFloat { - outer * 0.15 + private var cutoutSize: CGSize { + outerSize * 0.15 } - private var offset: CGFloat { - outer * 0.36 + private var offset: CGSize { + outerSize * 0.36 } var body: some View { Circle() .fill(content) - .frame(width: frameSize, height: frameSize) + .frame(size: frameSize) .overlay { Circle() .blendMode(.destinationOut) - .frame( - width: isCutedOut ? cutoutSize : .zero, - height: isCutedOut ? cutoutSize : .zero - ) + .frame(size: cutoutSize) } .compositingGroup() - .offset(x: offset, y: offset) + .offset(x: offset.width, y: offset.height) } } #Preview { - StatusIndicator(.blue, outer: 120) + StatusIndicator( + .blue, + outerSize: CGSize(width: 120, height: 120) + ) } From 50acc11d1f0c1dbdacbc7861ea02f579a1101762 Mon Sep 17 00:00:00 2001 From: makinosp Date: Fri, 20 Sep 2024 09:45:44 +0900 Subject: [PATCH 13/17] refact: remove unnecessary code --- harmonie/Extensions/Status+isWebColor.swift | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 harmonie/Extensions/Status+isWebColor.swift diff --git a/harmonie/Extensions/Status+isWebColor.swift b/harmonie/Extensions/Status+isWebColor.swift deleted file mode 100644 index d5b5ba9..0000000 --- a/harmonie/Extensions/Status+isWebColor.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// Status+isWebColor.swift -// Harmonie -// -// Created by xili on 2024/09/17. -// - -import SwiftUI -import VRCKit - -extension UserPlatform { - var isWebColor: Color { - switch self { - case .web: Color.black.opacity(0.7) - case .standalonewindows: .clear - default: Color.black.opacity(0.7) - } - } -} From f5f23b5d4e3e18517b09507d471cd9b07b841a34 Mon Sep 17 00:00:00 2001 From: makinosp Date: Fri, 20 Sep 2024 09:45:59 +0900 Subject: [PATCH 14/17] feat: apply UserIcon view --- harmonie/Components/StatusIndicator.swift | 8 ++--- harmonie/Components/UserIcon.swift | 33 +++++++++++++++++++ .../Favorite/FavoriteFriendListView.swift | 12 +------ harmonie/Views/Friend/FriendsListView.swift | 12 +------ .../Views/Location/LocationDetailView.swift | 10 +----- 5 files changed, 40 insertions(+), 35 deletions(-) create mode 100644 harmonie/Components/UserIcon.swift diff --git a/harmonie/Components/StatusIndicator.swift b/harmonie/Components/StatusIndicator.swift index 46b5865..97e9070 100644 --- a/harmonie/Components/StatusIndicator.swift +++ b/harmonie/Components/StatusIndicator.swift @@ -9,13 +9,13 @@ import SwiftUI struct StatusIndicator: View where S: ShapeStyle { private let content: S - private let isCutedOut: Bool + private let isCutOut: Bool private let outerSize: CGSize - init(_ content: S, outerSize: CGSize, isCutedOut: Bool = false) { + init(_ content: S, outerSize: CGSize, isCutOut: Bool = false) { self.content = content self.outerSize = outerSize - self.isCutedOut = isCutedOut + self.isCutOut = isCutOut } private var frameSize: CGSize { @@ -37,7 +37,7 @@ struct StatusIndicator: View where S: ShapeStyle { .overlay { Circle() .blendMode(.destinationOut) - .frame(size: cutoutSize) + .frame(size: isCutOut ? cutoutSize : .zero) } .compositingGroup() .offset(x: offset.width, y: offset.height) diff --git a/harmonie/Components/UserIcon.swift b/harmonie/Components/UserIcon.swift new file mode 100644 index 0000000..f812774 --- /dev/null +++ b/harmonie/Components/UserIcon.swift @@ -0,0 +1,33 @@ +// +// UserIcon.swift +// Harmonie +// +// Created by makinosp on 2024/09/20. +// + +import SwiftUI +import VRCKit + +struct UserIcon: View { + private let user: any ProfileElementRepresentable + + init(user: any ProfileElementRepresentable) { + self.user = user + } + + var body: some View { + ZStack { + BittenView { + CircleURLImage( + imageUrl: user.imageUrl(.x256), + size: Constants.IconSize.thumbnail + ) + } + StatusIndicator( + user.status.color, + outerSize: Constants.IconSize.thumbnail, + isCutOut: user.platform == .web + ) + } + } +} diff --git a/harmonie/Views/Favorite/FavoriteFriendListView.swift b/harmonie/Views/Favorite/FavoriteFriendListView.swift index 102a827..1f5c6d9 100644 --- a/harmonie/Views/Favorite/FavoriteFriendListView.swift +++ b/harmonie/Views/Favorite/FavoriteFriendListView.swift @@ -47,17 +47,7 @@ struct FavoriteFriendListView: View { Label { Text(friend.displayName) } icon: { - ZStack { - CircleURLImage( - imageUrl: friend.imageUrl(.x256), - size: Constants.IconSize.thumbnail - ) -// .mask(BittenCircle().fill(style: FillStyle(eoFill: true))) -// FriendStatusCircle( -// statusColor: friend.status.color, -// platformColor: friend.platform.isWebColor -// ) - } + UserIcon(user: friend) } } .frame(maxWidth: .infinity, alignment: .leading) diff --git a/harmonie/Views/Friend/FriendsListView.swift b/harmonie/Views/Friend/FriendsListView.swift index c33e410..4784a4b 100644 --- a/harmonie/Views/Friend/FriendsListView.swift +++ b/harmonie/Views/Friend/FriendsListView.swift @@ -29,17 +29,7 @@ struct FriendsListView: View, FriendServicePresentable { Text(friend.displayName) } } icon: { - ZStack { - CircleURLImage( - imageUrl: friend.imageUrl(.x256), - size: Constants.IconSize.thumbnail - ) -// .mask(BittenCircle().fill(style: FillStyle(eoFill: true))) -// FriendStatusCircle( -// statusColor: friend.status.color, -// platformColor: friend.platform.isWebColor -// ) - } + UserIcon(user: friend) } } .overlay { overlayView } diff --git a/harmonie/Views/Location/LocationDetailView.swift b/harmonie/Views/Location/LocationDetailView.swift index 77ea729..35e0345 100644 --- a/harmonie/Views/Location/LocationDetailView.swift +++ b/harmonie/Views/Location/LocationDetailView.swift @@ -88,15 +88,7 @@ struct LocationDetailView: View { Label { Text(friend.displayName) } icon: { - ZStack { - Circle() - .foregroundStyle(friend.status.color) - .frame(size: Constants.IconSize.thumbnailOutside) - CircleURLImage( - imageUrl: friend.imageUrl(.x256), - size: Constants.IconSize.thumbnail - ) - } + UserIcon(user: friend) } .frame(maxWidth: .infinity, alignment: .leading) .contentShape(Rectangle()) From c5ddcbcb41c3e095825e3bc64c5c967d8b05b73a Mon Sep 17 00:00:00 2001 From: makinosp Date: Fri, 20 Sep 2024 09:56:33 +0900 Subject: [PATCH 15/17] feat: apply UserIcon, size --- harmonie/Components/UserIcon.swift | 17 ++++++++--------- .../Views/Favorite/FavoriteFriendListView.swift | 2 +- harmonie/Views/Friend/FriendsListView.swift | 2 +- .../Views/Location/LocationDetailView.swift | 2 +- .../Profile/SettingsView+ProfileSection.swift | 5 +---- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/harmonie/Components/UserIcon.swift b/harmonie/Components/UserIcon.swift index f812774..8464db9 100644 --- a/harmonie/Components/UserIcon.swift +++ b/harmonie/Components/UserIcon.swift @@ -10,22 +10,21 @@ import VRCKit struct UserIcon: View { private let user: any ProfileElementRepresentable + private let size: CGSize - init(user: any ProfileElementRepresentable) { + init(user: any ProfileElementRepresentable, size: CGSize) { self.user = user + self.size = size } var body: some View { - ZStack { - BittenView { - CircleURLImage( - imageUrl: user.imageUrl(.x256), - size: Constants.IconSize.thumbnail - ) - } + BittenView { + CircleURLImage(imageUrl: user.imageUrl(.x256), size: size) + } + .overlay { StatusIndicator( user.status.color, - outerSize: Constants.IconSize.thumbnail, + outerSize: size, isCutOut: user.platform == .web ) } diff --git a/harmonie/Views/Favorite/FavoriteFriendListView.swift b/harmonie/Views/Favorite/FavoriteFriendListView.swift index 1f5c6d9..6933cc7 100644 --- a/harmonie/Views/Favorite/FavoriteFriendListView.swift +++ b/harmonie/Views/Favorite/FavoriteFriendListView.swift @@ -47,7 +47,7 @@ struct FavoriteFriendListView: View { Label { Text(friend.displayName) } icon: { - UserIcon(user: friend) + UserIcon(user: friend, size: Constants.IconSize.thumbnail) } } .frame(maxWidth: .infinity, alignment: .leading) diff --git a/harmonie/Views/Friend/FriendsListView.swift b/harmonie/Views/Friend/FriendsListView.swift index 4784a4b..421e15e 100644 --- a/harmonie/Views/Friend/FriendsListView.swift +++ b/harmonie/Views/Friend/FriendsListView.swift @@ -29,7 +29,7 @@ struct FriendsListView: View, FriendServicePresentable { Text(friend.displayName) } } icon: { - UserIcon(user: friend) + UserIcon(user: friend, size: Constants.IconSize.thumbnail) } } .overlay { overlayView } diff --git a/harmonie/Views/Location/LocationDetailView.swift b/harmonie/Views/Location/LocationDetailView.swift index 35e0345..7fdc9fd 100644 --- a/harmonie/Views/Location/LocationDetailView.swift +++ b/harmonie/Views/Location/LocationDetailView.swift @@ -88,7 +88,7 @@ struct LocationDetailView: View { Label { Text(friend.displayName) } icon: { - UserIcon(user: friend) + UserIcon(user: friend, size: Constants.IconSize.thumbnail) } .frame(maxWidth: .infinity, alignment: .leading) .contentShape(Rectangle()) diff --git a/harmonie/Views/Setting/Profile/SettingsView+ProfileSection.swift b/harmonie/Views/Setting/Profile/SettingsView+ProfileSection.swift index ef0c554..e0e9fc9 100644 --- a/harmonie/Views/Setting/Profile/SettingsView+ProfileSection.swift +++ b/harmonie/Views/Setting/Profile/SettingsView+ProfileSection.swift @@ -18,10 +18,7 @@ extension SettingsView { Label { Text(user.displayName) } icon: { - CircleURLImage( - imageUrl: user.imageUrl(.x256), - size: Constants.IconSize.ll - ) + UserIcon(user: user, size: Constants.IconSize.ll) } Spacer() Constants.Icon.forward From c6e447352f6e898a3236004f48bdbec68f9d41e5 Mon Sep 17 00:00:00 2001 From: makinosp Date: Fri, 20 Sep 2024 10:01:56 +0900 Subject: [PATCH 16/17] refact: refactoring --- harmonie/Components/BittenView.swift | 2 +- harmonie/Extensions/CGSize+Operator.swift | 2 +- .../Views/Location/LocationCardView.swift | 19 ++++--------------- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/harmonie/Components/BittenView.swift b/harmonie/Components/BittenView.swift index d663dfa..8f24a48 100644 --- a/harmonie/Components/BittenView.swift +++ b/harmonie/Components/BittenView.swift @@ -42,6 +42,6 @@ struct BittenView: View where Content: View { BittenView { Circle() .fill(.blue) - .frame(width: 120, height: 120) + .frame(size: 120) } } diff --git a/harmonie/Extensions/CGSize+Operator.swift b/harmonie/Extensions/CGSize+Operator.swift index 1ac6753..098050a 100644 --- a/harmonie/Extensions/CGSize+Operator.swift +++ b/harmonie/Extensions/CGSize+Operator.swift @@ -9,6 +9,6 @@ import CoreGraphics extension CGSize { static func * (lhs: CGSize, rhs: CGFloat) -> CGSize { - return CGSize(width: lhs.width * rhs, height: lhs.height * rhs) + CGSize(width: lhs.width * rhs, height: lhs.height * rhs) } } diff --git a/harmonie/Views/Location/LocationCardView.swift b/harmonie/Views/Location/LocationCardView.swift index ac7f2bc..eecb451 100644 --- a/harmonie/Views/Location/LocationCardView.swift +++ b/harmonie/Views/Location/LocationCardView.swift @@ -53,7 +53,10 @@ struct LocationCardView: View, InstanceServicePresentable { ScrollView(.horizontal) { HStack(spacing: -8) { ForEach(location.friends) { friend in - friendThumbnail(friend: friend) + CircleURLImage( + imageUrl: friend.imageUrl(.x256), + size: Constants.IconSize.thumbnail + ) } } } @@ -72,18 +75,4 @@ struct LocationCardView: View, InstanceServicePresentable { .map { $0.description } .joined(separator: " / ") } - - private func friendThumbnail(friend: Friend) -> some View { - ZStack { - CircleURLImage( - imageUrl: friend.imageUrl(.x256), - size: Constants.IconSize.thumbnail - ) -// .mask(BittenCircle().fill(style: FillStyle(eoFill: true))) -// FriendStatusCircle( -// statusColor: friend.status.color, -// platformColor: friend.platform.isWebColor -// ) - } - } } From f3c9973896633353606ea0802e8f6b2749dfc5e3 Mon Sep 17 00:00:00 2001 From: makinosp Date: Fri, 20 Sep 2024 10:49:24 +0900 Subject: [PATCH 17/17] fix: preview --- harmonie/Components/BittenView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/harmonie/Components/BittenView.swift b/harmonie/Components/BittenView.swift index 8f24a48..d663dfa 100644 --- a/harmonie/Components/BittenView.swift +++ b/harmonie/Components/BittenView.swift @@ -42,6 +42,6 @@ struct BittenView: View where Content: View { BittenView { Circle() .fill(.blue) - .frame(size: 120) + .frame(width: 120, height: 120) } }