diff --git a/Harmonie.xcodeproj/project.pbxproj b/Harmonie.xcodeproj/project.pbxproj index 7827c86..980e744 100644 --- a/Harmonie.xcodeproj/project.pbxproj +++ b/Harmonie.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ 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 */; }; + 207836FD2C79AB1B00659C91 /* SettingsView+AboutSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207836FC2C79AB1B00659C91 /* SettingsView+AboutSection.swift */; }; 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 */; }; 207FC6372C4256E60018E605 /* View+SectionModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207FC6342C4256E60018E605 /* View+SectionModifier.swift */; }; @@ -114,6 +115,7 @@ 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/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; }; + 207836FC2C79AB1B00659C91 /* SettingsView+AboutSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "SettingsView+AboutSection.swift"; path = "Harmonie/Views/Setting/SettingsView+AboutSection.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 = ""; }; @@ -316,6 +318,7 @@ children = ( 207836FB2C798A8C00659C91 /* Profile */, 205B3BF12C00D51A0097D62A /* SettingsView.swift */, + 207836FC2C79AB1B00659C91 /* SettingsView+AboutSection.swift */, ); path = Setting; sourceTree = ""; @@ -533,6 +536,7 @@ 20460CAE2C561ED200B276E3 /* UserDetailPresentationView.swift in Sources */, 209A788A2C2FC0FA00BEFAE6 /* FavoriteViewModel.swift in Sources */, 205AA45E2C72E61B008CCC8A /* UserDetailView+Toolbar.swift in Sources */, + 207836FD2C79AB1B00659C91 /* SettingsView+AboutSection.swift in Sources */, 20D50FA72C77F94F00F89B4F /* LanguagePickerView.swift in Sources */, 207FC6372C4256E60018E605 /* View+SectionModifier.swift in Sources */, 2099A4582C78326600104842 /* URL+Identifiable.swift in Sources */, @@ -743,7 +747,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.3.0; + MARKETING_VERSION = 0.4.0; PRODUCT_BUNDLE_IDENTIFIER = net.nemushee.harmonie; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -777,7 +781,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.3.0; + MARKETING_VERSION = 0.4.0; PRODUCT_BUNDLE_IDENTIFIER = net.nemushee.harmonie; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/harmonie/Views/Favorite/FavoritesView.swift b/harmonie/Views/Favorite/FavoritesView.swift index 4ea0c81..a2cbe6e 100644 --- a/harmonie/Views/Favorite/FavoritesView.swift +++ b/harmonie/Views/Favorite/FavoritesView.swift @@ -53,14 +53,22 @@ struct FavoritesView: View { Button { selected = Selected(id: friend.id) } label: { - HStack { - CircleURLImage( - imageUrl: friend.thumbnailUrl, - size: Constants.IconSize.thumbnail - ) - Text(friend.displayName) - Spacer() + LabeledContent { Constants.Icon.forward + } label: { + Label { + Text(friend.displayName) + } icon: { + ZStack { + Circle() + .foregroundStyle(friend.status.color) + .frame(size: Constants.IconSize.thumbnailOutside) + CircleURLImage( + imageUrl: friend.thumbnailUrl, + size: Constants.IconSize.thumbnail + ) + } + } } .frame(maxWidth: .infinity, alignment: .leading) .contentShape(Rectangle()) diff --git a/harmonie/Views/Friend/FriendsView.swift b/harmonie/Views/Friend/FriendsView.swift index 5e1564f..f292592 100644 --- a/harmonie/Views/Friend/FriendsView.swift +++ b/harmonie/Views/Friend/FriendsView.swift @@ -39,19 +39,22 @@ struct FriendsView: View { Button { selected = Selected(id: friend.id) } label: { - HStack { - ZStack { - Circle() - .foregroundStyle(friend.status.color) - .frame(size: Constants.IconSize.thumbnailOutside) - CircleURLImage( - imageUrl: friend.thumbnailUrl, - size: Constants.IconSize.thumbnail - ) - } - Text(friend.displayName) - Spacer() + LabeledContent { Constants.Icon.forward + } label: { + Label { + Text(friend.displayName) + } icon: { + ZStack { + Circle() + .foregroundStyle(friend.status.color) + .frame(size: Constants.IconSize.thumbnailOutside) + CircleURLImage( + imageUrl: friend.thumbnailUrl, + size: Constants.IconSize.thumbnail + ) + } + } } .frame(maxWidth: .infinity, alignment: .leading) .contentShape(Rectangle()) diff --git a/harmonie/Views/Location/LocationCardView.swift b/harmonie/Views/Location/LocationCardView.swift index 83fdf96..1c9dbc8 100644 --- a/harmonie/Views/Location/LocationCardView.swift +++ b/harmonie/Views/Location/LocationCardView.swift @@ -51,40 +51,41 @@ struct LocationCardView: View { func locationCardContent(instance: Instance) -> some View { HStack(alignment: .top) { - VStack(alignment: .leading) { - Text(instance.world.name) - .font(.body) - HStack { - Text(instance.typeDescription) - .font(.footnote) - .foregroundStyle(Color.gray) - Spacer() - Text(personAmount(instance)) - .font(.footnote) - .foregroundStyle(Color.gray) - } - ScrollView(.horizontal) { - HStack(spacing: -8) { - ForEach(location.friends) { friend in - ZStack { - Circle() - .foregroundStyle(friend.status.color) - .frame(size: Constants.IconSize.thumbnailOutside) - CircleURLImage( - imageUrl: friend.thumbnailUrl, - size: Constants.IconSize.thumbnail - ) + locationThumbnail(instance.world.imageUrl) + Spacer() + HStack { + VStack(alignment: .leading) { + Text(instance.world.name) + .font(.body) + HStack { + Text(instance.typeDescription) + .font(.footnote) + .foregroundStyle(Color.gray) + Text(personAmount(instance)) + .font(.footnote) + .foregroundStyle(Color.gray) + } + ScrollView(.horizontal) { + HStack(spacing: -8) { + ForEach(location.friends) { friend in + ZStack { + Circle() + .foregroundStyle(friend.status.color) + .frame(size: Constants.IconSize.thumbnailOutside) + CircleURLImage( + imageUrl: friend.thumbnailUrl, + size: Constants.IconSize.thumbnail + ) + } } } } } + .frame(maxWidth: .infinity, alignment: .leading) + Constants.Icon.forward } - .padding() - .frame(maxWidth: .infinity, alignment: .leading) - Spacer() - locationThumbnail(instance.world.imageUrl) - .padding() } + .padding() } func personAmount(_ instance: Instance) -> String { diff --git a/harmonie/Views/Location/LocationDetailView.swift b/harmonie/Views/Location/LocationDetailView.swift index b043995..a205ec4 100644 --- a/harmonie/Views/Location/LocationDetailView.swift +++ b/harmonie/Views/Location/LocationDetailView.swift @@ -50,7 +50,9 @@ struct LocationDetailView: View { var friendList: some View { ForEach(location.friends) { friend in NavigationLink(value: Selected(id: friend.id)) { - HStack { + Label { + Text(friend.displayName) + } icon: { ZStack { Circle() .foregroundStyle(friend.status.color) @@ -60,7 +62,6 @@ struct LocationDetailView: View { size: Constants.IconSize.thumbnail ) } - Text(friend.displayName) } .frame(maxWidth: .infinity, alignment: .leading) .contentShape(Rectangle()) diff --git a/harmonie/Views/Setting/SettingsView+AboutSection.swift b/harmonie/Views/Setting/SettingsView+AboutSection.swift new file mode 100644 index 0000000..fee9821 --- /dev/null +++ b/harmonie/Views/Setting/SettingsView+AboutSection.swift @@ -0,0 +1,73 @@ +// +// SettingsView+AboutSection.swift +// Harmonie +// +// Created by makinosp on 2024/08/24. +// + +import SwiftUI + +extension SettingsView { + var appName: String { + Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? "" + } + + var appVersion: String { + Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "" + } + + var aboutSection: some View { + Section("About") { + LabeledContent { + Constants.Icon.forward + } label: { + Button { + destination = .about + } label: { + Label { + Text("About This App") + } icon: { + Image(systemName: "info.circle") + } + } + } + Link(destination: URL(string: "https://github.com/makinosp/harmonie")!) { + Label { + Text("Source Code") + } icon: { + Image(systemName: "curlybraces") + } + } + LabeledContent { + Constants.Icon.forward + } label: { + Button { + destination = .license + } label: { + Label { + Text("Third Party Licence") + } icon: { + Image(systemName: "lightbulb") + } + } + } + } + } + + var aboutThisApp: some View { + List { + LabeledContent { + Text(appName) + } label: { + Text("App Name") + } + LabeledContent { + Text(appVersion) + } label: { + Text("App Version") + } + } + .navigationTitle("About") + .navigationBarTitleDisplayMode(.inline) + } +} diff --git a/harmonie/Views/Setting/SettingsView.swift b/harmonie/Views/Setting/SettingsView.swift index 1189c33..cef9c61 100644 --- a/harmonie/Views/Setting/SettingsView.swift +++ b/harmonie/Views/Setting/SettingsView.swift @@ -15,23 +15,16 @@ struct SettingsView: View { @State var isPresentedForm = false enum Destination: Hashable { - case userDetail, license + case userDetail, about, license } var body: some View { NavigationSplitView(columnVisibility: .constant(.all)) { - VStack { - settingsContent - HStack { - Text(appName) - Text(appVersion) + settingsContent + .navigationDestination(item: $destination) { destination in + presentDestination(destination) } - .font(.footnote) - } - .navigationDestination(item: $destination) { destination in - presentDestination(destination) - } - .navigationTitle("Settings") + .navigationTitle("Settings") } .navigationSplitViewStyle(.balanced) .onAppear { @@ -53,6 +46,8 @@ struct SettingsView: View { if let user = appVM.user { UserDetailPresentationView(id: user.id) } + case .about: + aboutThisApp case .license: LicenseListView() } @@ -63,25 +58,7 @@ struct SettingsView: View { if let user = appVM.user { profileSection(user: user) } - Section(header: Text("Open Source License Notice")) { - Link(destination: URL(string: "https://github.com/makinosp/harmonie")!) { - Label { - Text("Source Code") - } icon: { - Image(systemName: "curlybraces.square.fill") - } - } - Button { - destination = .license - } label: { - Label { - Text("Third Party Licence") - } icon: { - Image(systemName: "info.circle.fill") - } - } - } - .textCase(nil) + aboutSection Section { AsyncButton { await appVM.logout() @@ -97,12 +74,4 @@ struct SettingsView: View { } } } - - var appName: String { - Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? "" - } - - var appVersion: String { - Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "" - } } diff --git a/harmonie/Views/UserDetail/UserDetailView+Actions.swift b/harmonie/Views/UserDetail/UserDetailView+Actions.swift index 30d5f51..4707e84 100644 --- a/harmonie/Views/UserDetail/UserDetailView+Actions.swift +++ b/harmonie/Views/UserDetail/UserDetailView+Actions.swift @@ -30,20 +30,6 @@ extension UserDetailView { } } - func saveUserInfo() async { - let service = appVM.isDemoMode - ? UserPreviewService(client: appVM.client) - : UserService(client: appVM.client) - do { - try await service.updateUser( - id: user.id, - editedInfo: editingUserInfo - ) - } catch { - appVM.handleError(error) - } - } - func saveNote() async { let service = appVM.isDemoMode ? UserNotePreviewService(client: appVM.client) diff --git a/harmonie/Views/UserDetail/UserDetailView+Toolbar.swift b/harmonie/Views/UserDetail/UserDetailView+Toolbar.swift index 9127f84..6e19b41 100644 --- a/harmonie/Views/UserDetail/UserDetailView+Toolbar.swift +++ b/harmonie/Views/UserDetail/UserDetailView+Toolbar.swift @@ -29,7 +29,7 @@ extension UserDetailView { } } - func favoriteMenuItem(group: FavoriteGroup) -> some View { + private func favoriteMenuItem(group: FavoriteGroup) -> some View { AsyncButton { await updateFavorite(friendId: user.id, group: group) } label: { diff --git a/harmonie/Views/UserDetail/UserDetailView.swift b/harmonie/Views/UserDetail/UserDetailView.swift index 5884d13..ac85c03 100644 --- a/harmonie/Views/UserDetail/UserDetailView.swift +++ b/harmonie/Views/UserDetail/UserDetailView.swift @@ -15,23 +15,23 @@ struct UserDetailView: View { @Environment(\.dismiss) private var dismiss @State var user: UserDetail @State var instance: Instance? - @State var editingUserInfo: EditableUserInfo @State var note: String private let initialValue: EditableUserInfo private let initialNoteValue: String init(user: UserDetail) { _user = State(initialValue: user) - initialValue = EditableUserInfo(detail: user) - _editingUserInfo = State(initialValue: initialValue) initialNoteValue = user.note _note = State(initialValue: initialNoteValue) + initialValue = EditableUserInfo(detail: user) } var body: some View { ScrollView { VStack(spacing: 0) { - profileImageContainer + if let url = user.thumbnailUrl { + profileImageContainer(url: url) + } contentStacks } } @@ -45,34 +45,23 @@ struct UserDetailView: View { } } - var hasAnyDiff: Bool { - editingUserInfo != initialValue || - note != initialNoteValue - } - - var isOwned: Bool { - user.id == appVM.user?.id - } - var statusColor: Color { user.state == .offline ? UserStatus.offline.color : user.status.color } - @ViewBuilder var profileImageContainer: some View { - if let url = user.thumbnailUrl { - GradientOverlayImageView( - url: url, - maxHeight: 250, - topContent: { topBar }, - bottomContent: { bottomBar } - ) - } + func profileImageContainer(url: URL) -> some View { + GradientOverlayImageView( + url: url, + maxHeight: 250, + topContent: { topBar }, + bottomContent: { bottomBar } + ) } var topBar: some View { HStack { Spacer() - if hasAnyDiff { + if note != initialNoteValue { saveButton } } @@ -82,9 +71,6 @@ struct UserDetailView: View { var saveButton: some View { AsyncButton("Save") { - if editingUserInfo != initialValue { - await saveUserInfo() - } if note != initialNoteValue { await saveNote() } @@ -107,27 +93,16 @@ struct UserDetailView: View { .font(.headline) statusDescription } + .frame(maxWidth: .infinity, alignment: .leading) } .padding(.vertical, 8) .padding(.horizontal, 12) - .foregroundStyle(Color.white) } - @ViewBuilder var statusDescription: some View { - if isOwned { - TextField("StatusDescription", text: $editingUserInfo.statusDescription) - .font(.subheadline) - .padding(.vertical, 4) - .padding(.horizontal, 8) - .background( - RoundedRectangle(cornerRadius: 8) - .foregroundStyle(Color(uiColor: .systemBackground).opacity(0.25)) - ) - } else { - Text(user.statusDescription) - .lineLimit(1) - .font(.subheadline) - } + var statusDescription: some View { + Text(user.statusDescription) + .lineLimit(1) + .font(.subheadline) } var displayStatusAndName: some View {