diff --git a/Mlem.xcodeproj/project.pbxproj b/Mlem.xcodeproj/project.pbxproj index eb934471b..48484e0d1 100644 --- a/Mlem.xcodeproj/project.pbxproj +++ b/Mlem.xcodeproj/project.pbxproj @@ -153,6 +153,8 @@ 0389DDD32C39E4D40005B808 /* PasteLinkButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0389DDD22C39E4D40005B808 /* PasteLinkButtonView.swift */; }; 0389DDD52C39F1290005B808 /* CommunityListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0389DDD42C39F1290005B808 /* CommunityListRow.swift */; }; 0389DDDB2C3AB6340005B808 /* ActionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0389DDDA2C3AB6340005B808 /* ActionBuilder.swift */; }; + 0391E0F92CFF17AE0040CCA8 /* ProfileSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0391E0F82CFF17AE0040CCA8 /* ProfileSettingsView.swift */; }; + 0391E0FB2D0066240040CCA8 /* ImageUploadMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0391E0FA2D0066240040CCA8 /* ImageUploadMenu.swift */; }; 0397D4602C66113F002C6CDC /* CommentBodyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0397D45F2C66113F002C6CDC /* CommentBodyView.swift */; }; 0397D4622C676B46002C6CDC /* ApiSortType+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0397D4612C676B46002C6CDC /* ApiSortType+Extensions.swift */; }; 0397D4642C676CA8002C6CDC /* FeedSortPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0397D4632C676CA8002C6CDC /* FeedSortPicker.swift */; }; @@ -193,9 +195,9 @@ 03AB48552CBC0B8000567FF9 /* AccountAdvancedSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AB48542CBC0B8000567FF9 /* AccountAdvancedSettingsView.swift */; }; 03AB48572CBC0DFC00567FF9 /* AccountSignInSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AB48562CBC0DFC00567FF9 /* AccountSignInSettingsView.swift */; }; 03AB48592CBC14CE00567FF9 /* AccountEmailSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AB48582CBC14CE00567FF9 /* AccountEmailSettingsView.swift */; }; + 03AD09E82CF88007001EF9F7 /* MoreRepliesButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AD09E72CF88007001EF9F7 /* MoreRepliesButton.swift */; }; 03AD0A822CFDBFA0001EF9F7 /* AccountLocalSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AD0A812CFDBFA0001EF9F7 /* AccountLocalSettingsView.swift */; }; 03AD0A842CFDC557001EF9F7 /* AccountNicknameFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AD0A832CFDC557001EF9F7 /* AccountNicknameFieldView.swift */; }; - 03AD09E82CF88007001EF9F7 /* MoreRepliesButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AD09E72CF88007001EF9F7 /* MoreRepliesButton.swift */; }; 03AF91DD2C1B23E500E56644 /* ImageViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AF91DC2C1B23E500E56644 /* ImageViewer.swift */; }; 03AF91DF2C1B243D00E56644 /* ZoomableContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AF91DE2C1B243D00E56644 /* ZoomableContainer.swift */; }; 03AF91E12C1B25DE00E56644 /* UIDevice+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AF91E02C1B25DE00E56644 /* UIDevice+Extensions.swift */; }; @@ -571,6 +573,8 @@ 0389DDD22C39E4D40005B808 /* PasteLinkButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteLinkButtonView.swift; sourceTree = ""; }; 0389DDD42C39F1290005B808 /* CommunityListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityListRow.swift; sourceTree = ""; }; 0389DDDA2C3AB6340005B808 /* ActionBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionBuilder.swift; sourceTree = ""; }; + 0391E0F82CFF17AE0040CCA8 /* ProfileSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSettingsView.swift; sourceTree = ""; }; + 0391E0FA2D0066240040CCA8 /* ImageUploadMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUploadMenu.swift; sourceTree = ""; }; 0397D45F2C66113F002C6CDC /* CommentBodyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentBodyView.swift; sourceTree = ""; }; 0397D4612C676B46002C6CDC /* ApiSortType+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ApiSortType+Extensions.swift"; sourceTree = ""; }; 0397D4632C676CA8002C6CDC /* FeedSortPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedSortPicker.swift; sourceTree = ""; }; @@ -610,9 +614,9 @@ 03AB48542CBC0B8000567FF9 /* AccountAdvancedSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountAdvancedSettingsView.swift; sourceTree = ""; }; 03AB48562CBC0DFC00567FF9 /* AccountSignInSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSignInSettingsView.swift; sourceTree = ""; }; 03AB48582CBC14CE00567FF9 /* AccountEmailSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountEmailSettingsView.swift; sourceTree = ""; }; + 03AD09E72CF88007001EF9F7 /* MoreRepliesButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreRepliesButton.swift; sourceTree = ""; }; 03AD0A812CFDBFA0001EF9F7 /* AccountLocalSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountLocalSettingsView.swift; sourceTree = ""; }; 03AD0A832CFDC557001EF9F7 /* AccountNicknameFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountNicknameFieldView.swift; sourceTree = ""; }; - 03AD09E72CF88007001EF9F7 /* MoreRepliesButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreRepliesButton.swift; sourceTree = ""; }; 03AF91DC2C1B23E500E56644 /* ImageViewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewer.swift; sourceTree = ""; }; 03AF91DE2C1B243D00E56644 /* ZoomableContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZoomableContainer.swift; sourceTree = ""; }; 03AF91E02C1B25DE00E56644 /* UIDevice+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+Extensions.swift"; sourceTree = ""; }; @@ -902,6 +906,7 @@ 03267D832BED49CE009D6268 /* AccountSettingsView.swift */, 037DE0742CE023E3007F7B92 /* BlockListView.swift */, 03AB48512CBC042E00567FF9 /* AccountGeneralSettingsView.swift */, + 0391E0F82CFF17AE0040CCA8 /* ProfileSettingsView.swift */, 03AB48542CBC0B8000567FF9 /* AccountAdvancedSettingsView.swift */, 03AB48562CBC0DFC00567FF9 /* AccountSignInSettingsView.swift */, 03AB48582CBC14CE00567FF9 /* AccountEmailSettingsView.swift */, @@ -1509,6 +1514,7 @@ 0355F9482C16406E00605248 /* Line.swift */, 03D2A6402C011F3E00ED4FF2 /* ListRow */, 03B431C12C45BA00001A1EB5 /* MarkdownEditorToolbarView.swift */, + 0391E0FA2D0066240040CCA8 /* ImageUploadMenu.swift */, 034B94842C09348400039AF4 /* MarkdownImageView.swift */, 03B431B32C4481C3001A1EB5 /* MarkdownTextEditor.swift */, 03D3A1EE2BB9CA1D009DE55E /* MenuButton.swift */, @@ -2332,6 +2338,7 @@ CD4D59162B87B38C00B82964 /* UIApplication+Extensions.swift in Sources */, CDB41E8A2C83C24400BD2DE9 /* Section.swift in Sources */, CDCA44B22C17675600C092B3 /* Haptic.swift in Sources */, + 0391E0FB2D0066240040CCA8 /* ImageUploadMenu.swift in Sources */, 035BE08B2BDD903100F77D73 /* NavigationModel.swift in Sources */, 033F84BB2C2ACB96002E3EDF /* CommentView.swift in Sources */, 03AFD0E52C3C14D50054B8AD /* InstanceStubProviding+Extensions.swift in Sources */, @@ -2474,6 +2481,7 @@ 039F58972C7B68F100C61658 /* AboutMlemView.swift in Sources */, 035EDEFB2C2DF98700F51144 /* CommunityListRowBody.swift in Sources */, CD77437F2C1BA5CE0085BB43 /* MultiplatformView.swift in Sources */, + 0391E0F92CFF17AE0040CCA8 /* ProfileSettingsView.swift in Sources */, 03E614E72C0BCDC200F692A4 /* FullyQualifiedLinkView.swift in Sources */, B1B78D642A51D53900F72485 /* AppDelegate.swift in Sources */, ); @@ -2919,7 +2927,7 @@ repositoryURL = "https://github.com/mlemgroup/MlemMiddleware"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 0.51.0; + minimumVersion = 0.52.0; }; }; CDE4AC402CA3706400981010 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */ = { diff --git a/Mlem.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mlem.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c034a63b5..53c8be7e0 100644 --- a/Mlem.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mlem.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -51,8 +51,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/mlemgroup/MlemMiddleware", "state" : { - "revision" : "4014fd91a40312e3c5b20c3badac3c7f45d4fe19", - "version" : "0.51.0" + "revision" : "51db2875046e216fb01c8233cc43eadd147dd240", + "version" : "0.52.0" } }, { diff --git a/Mlem/App/Configuration/Icons.swift b/Mlem/App/Configuration/Icons.swift index 472fdf4a1..d5b61bd68 100644 --- a/Mlem/App/Configuration/Icons.swift +++ b/Mlem/App/Configuration/Icons.swift @@ -201,6 +201,7 @@ enum Icons { static let close: String = "multiply" static let closeCircle: String = "xmark.circle" static let closeCircleFill: String = "xmark.circle.fill" + static let addCircleFill: String = "plus.circle.fill" static let cakeDay: String = "birthday.cake" static let cakeDayFill: String = "birthday.cake.fill" static let undoCircleFill: String = "arrow.uturn.backward.circle.fill" @@ -246,6 +247,7 @@ enum Icons { static let refresh: String = "arrow.clockwise" static let select: String = "selection.pin.in.out" static let crossPost: String = "shuffle" + static let chooseFile: String = "folder" // settings static let upvoteOnSave: String = "arrow.up.heart" diff --git a/Mlem/App/Views/Pages/PostEditor/PostEditorView+ImageView.swift b/Mlem/App/Views/Pages/PostEditor/PostEditorView+ImageView.swift index afa542ebf..5c8480433 100644 --- a/Mlem/App/Views/Pages/PostEditor/PostEditorView+ImageView.swift +++ b/Mlem/App/Views/Pages/PostEditor/PostEditorView+ImageView.swift @@ -91,7 +91,7 @@ extension PostEditorView { guard let imageManager else { return } navigation.showPhotosPicker(for: imageManager, api: primaryApi) } - Button("Files", systemImage: "folder") { + Button("Files", systemImage: Icons.chooseFile) { guard let imageManager else { return } navigation.showFilePicker(for: imageManager, api: primaryApi) } diff --git a/Mlem/App/Views/Root/Tabs/Profile/Profile View.swift b/Mlem/App/Views/Root/Tabs/Profile/Profile View.swift index 2dda148af..b8ff71c0d 100644 --- a/Mlem/App/Views/Root/Tabs/Profile/Profile View.swift +++ b/Mlem/App/Views/Root/Tabs/Profile/Profile View.swift @@ -17,6 +17,13 @@ struct ProfileView: View { var body: some View { if let person = appState.firstPerson { PersonView(person: .init(person), isProfileTab: true) + .toolbar { + ToolbarItem(placement: .secondaryAction) { + Button("Edit", systemImage: Icons.edit) { + navigation.openSheet(.settings(.profile)) + } + } + } .id(person.actorId) } else if let instance = appState.firstSession.instance { InstanceView(instance: instance) diff --git a/Mlem/App/Views/Root/Tabs/Settings/AccountSettingsView.swift b/Mlem/App/Views/Root/Tabs/Settings/AccountSettingsView.swift index 3bc4f0a69..64dbbfed8 100644 --- a/Mlem/App/Views/Root/Tabs/Settings/AccountSettingsView.swift +++ b/Mlem/App/Views/Root/Tabs/Settings/AccountSettingsView.swift @@ -33,6 +33,12 @@ struct AccountSettingsView: View { if appState.firstSession is UserSession { Section { + NavigationLink( + "My Profile", + systemImage: Icons.personFill, + destination: .settings(.profile) + ) + .tint(palette.colorfulAccent(5)) NavigationLink( "Sign-In & Security", systemImage: "key.fill", diff --git a/Mlem/App/Views/Root/Tabs/Settings/ProfileSettingsView.swift b/Mlem/App/Views/Root/Tabs/Settings/ProfileSettingsView.swift new file mode 100644 index 000000000..bda4eb8d4 --- /dev/null +++ b/Mlem/App/Views/Root/Tabs/Settings/ProfileSettingsView.swift @@ -0,0 +1,224 @@ +// +// ProfileSettingsView.swift +// Mlem +// +// Created by Sjmarf on 2024-12-03. +// + +import MlemMiddleware +import SwiftUI + +struct ProfileSettingsView: View { + @Environment(NavigationLayer.self) var navigation + @Environment(Palette.self) var palette + + @Environment(\.colorScheme) var colorScheme + @Environment(\.dismiss) var dismiss + + let person: Person4 + @State var displayName: String + + @State var bioTextView: UITextView = .init() + @State var bioHasChanged: Bool = false + @State var uploadHistory: ImageUploadHistoryManager = .init() + + @State var avatarUrl: URL? + @State var avatarManager: ImageUploadManager = .init() + + @State var bannerUrl: URL? + @State var bannerManager: ImageUploadManager = .init() + + @State var isSubmitting: Bool = false + + init(person: Person4) { + self.person = person + self._displayName = .init(wrappedValue: person.displayName == person.name ? "" : person.displayName) + bioTextView.text = person.description ?? "" + self._avatarUrl = .init(wrappedValue: person.avatar) + self._bannerUrl = .init(wrappedValue: person.banner) + } + + var minTextEditorHeight: CGFloat { + UIFont.preferredFont(forTextStyle: .body).lineHeight * 6 + 20 + } + + var body: some View { + Form { + Section("Display Name") { + TextField("Display Name", text: $displayName, prompt: Text(person.name)) + .autocorrectionDisabled() + .textInputAutocapitalization(.never) + } footer: { + Text("The name that is displayed on your profile. This is not the same as your username, which cannot be changed.") + } + Section("Biography") { + MarkdownTextEditor( + onChange: { newValue in + bioHasChanged = (person.description ?? "") != newValue + }, + prompt: "Write a bit about yourself...", + textView: bioTextView, + insets: .init( + top: Constants.main.standardSpacing, + left: Constants.main.standardSpacing, + bottom: Constants.main.standardSpacing, + right: Constants.main.standardSpacing + ), + firstResponder: false, + sizingOffset: 10, + content: { + MarkdownEditorToolbarView( + textView: bioTextView, + uploadHistory: uploadHistory, + imageUploadApi: person.api + ) + } + ) + .frame( + maxWidth: .infinity, + minHeight: minTextEditorHeight, + maxHeight: .infinity, + alignment: .topLeading + ) + .listRowInsets(.init()) + } + avatarSection + bannerSection + } + .navigationTitle("My Profile") + .navigationBarTitleDisplayMode(.inline) + .scrollDismissesKeyboard(.interactively) + .navigationBarBackButtonHidden(showToolbarOptions) + .interactiveDismissDisabled(showToolbarOptions) + .toolbar { + if showToolbarOptions { + ToolbarItem(placement: .topBarLeading) { + Button("Cancel") { + displayName = person.displayName == person.name ? "" : person.displayName + bioTextView.text = person.description ?? "" + bioHasChanged = false + avatarUrl = person.avatar + bannerUrl = person.banner + } + .disabled(isSubmitting) + } + ToolbarItem(placement: .topBarTrailing) { + if isSubmitting { + ProgressView() + } else { + Button("Save") { + Task { @MainActor in + await submit() + } + } + } + } + } else if navigation.isInsideSheet { + ToolbarItem(placement: .topBarTrailing) { + CloseButtonView() + } + } + } + } + + var showToolbarOptions: Bool { + let originalDisplayName = (person.displayName == person.name) ? "" : person.displayName + return bioHasChanged || displayName != originalDisplayName || avatarUrl != person.avatar || bannerUrl != person.banner + } + + @ViewBuilder + var avatarSection: some View { + Section { + HStack(spacing: 15) { + CircleCroppedImageView(url: avatarUrl, frame: 48, fallback: .person) + .id(avatarUrl) + Text("Avatar") + Spacer() + CircleImageUploadButton(imageManager: avatarManager, url: $avatarUrl, api: person.api) + } + .onChange(of: avatarManager.image?.url) { + avatarUrl = avatarManager.image?.url + } + } + } + + @ViewBuilder + var bannerSection: some View { + Section { + VStack(spacing: 0) { + if let bannerUrl { + LargeImageView(url: bannerUrl, shouldBlur: false, cornerRadius: 0) + .id(bannerUrl) + .aspectRatio(contentMode: .fill) + .frame(height: 150) + .clipped() + } else { + palette.secondary.opacity(0.5) + .frame(height: 150) + } + HStack(spacing: 15) { + Text("Banner") + Spacer() + CircleImageUploadButton(imageManager: bannerManager, url: $bannerUrl, api: person.api) + } + .padding(.horizontal, 15) + .padding(.vertical, 10) + } + .onChange(of: bannerManager.image?.url) { + bannerUrl = bannerManager.image?.url + } + } + .listRowInsets(.init()) + } + + @MainActor + func submit() async { + isSubmitting = true + do { + try await person.updateProfile( + displayName: displayName.isEmpty ? nil : displayName, + description: bioTextView.text.isEmpty ? nil : bioTextView.text, + avatar: avatarUrl, + banner: bannerUrl + ) + dismiss() + } catch { + handleError(error) + } + isSubmitting = false + } +} + +private struct CircleImageUploadButton: View { + let imageManager: ImageUploadManager + @Binding var url: URL? + let api: ApiClient + + var body: some View { + Group { + if url != nil { + Button { + url = nil + } label: { + Image(systemName: Icons.closeCircleFill) + .resizable() + } + } else { + switch imageManager.state { + case .uploading: + ProgressView() + .controlSize(.extraLarge) + default: + ImageUploadMenu(imageManager: imageManager, imageUploadApi: api) { + Image(systemName: Icons.addCircleFill) + .resizable() + } + } + } + } + .aspectRatio(contentMode: .fit) + .frame(height: 48) + .symbolRenderingMode(.hierarchical) + .fontWeight(.thin) + } +} diff --git a/Mlem/App/Views/Shared/ImageUploadMenu.swift b/Mlem/App/Views/Shared/ImageUploadMenu.swift new file mode 100644 index 000000000..2f3649f8b --- /dev/null +++ b/Mlem/App/Views/Shared/ImageUploadMenu.swift @@ -0,0 +1,38 @@ +// +// ImageUploadMenu.swift +// Mlem +// +// Created by Sjmarf on 2024-12-04. +// + +import MlemMiddleware +import SwiftUI + +struct ImageUploadMenu: View { + @Environment(NavigationLayer.self) var navigation + + let imageManager: ImageUploadManager + let imageUploadApi: ApiClient + @ViewBuilder let label: () -> Label + + init(imageManager: ImageUploadManager, imageUploadApi: ApiClient, @ViewBuilder label: @escaping () -> Label) { + self.imageManager = imageManager + self.imageUploadApi = imageUploadApi + self.label = label + } + + var body: some View { + Menu(content: { + Button("Photo Library", systemImage: Icons.photo) { + navigation.showPhotosPicker(for: imageManager, api: imageUploadApi) + } + Button("Choose File", systemImage: Icons.chooseFile) { + navigation.showFilePicker(for: imageManager, api: imageUploadApi) + } + Button("Paste", systemImage: Icons.paste) { + navigation.uploadImageFromClipboard(for: imageManager, api: imageUploadApi) + } + }, label: label) + .disabled(imageManager.state != .idle) + } +} diff --git a/Mlem/App/Views/Shared/Images/Wrappers/LargeImageView.swift b/Mlem/App/Views/Shared/Images/Wrappers/LargeImageView.swift index 9081e3b78..b267b9f51 100644 --- a/Mlem/App/Views/Shared/Images/Wrappers/LargeImageView.swift +++ b/Mlem/App/Views/Shared/Images/Wrappers/LargeImageView.swift @@ -17,22 +17,29 @@ struct LargeImageView: View { let url: URL? let shouldBlur: Bool - var onTapActions: (() -> Void)? + let onTapActions: (() -> Void)? + let cornerRadius: CGFloat @State var blurred: Bool = false - init(url: URL?, shouldBlur: Bool, onTapActions: (() -> Void)? = nil) { + init( + url: URL?, + shouldBlur: Bool, + cornerRadius: CGFloat = Constants.main.mediumItemCornerRadius, + onTapActions: (() -> Void)? = nil + ) { self.url = url self.onTapActions = onTapActions self.shouldBlur = shouldBlur + self.cornerRadius = cornerRadius self._blurred = .init(wrappedValue: shouldBlur) } @State private var loading: MediaLoadingState? var body: some View { - DynamicMediaView(url: url) + DynamicMediaView(url: url, cornerRadius: cornerRadius) .dynamicBlur(blurred: blurred) - .clipShape(.rect(cornerRadius: Constants.main.mediumItemCornerRadius)) + .clipShape(.rect(cornerRadius: cornerRadius)) .overlay { NsfwOverlay(blurred: $blurred, shouldBlur: shouldBlur) } diff --git a/Mlem/App/Views/Shared/MarkdownEditorToolbarView.swift b/Mlem/App/Views/Shared/MarkdownEditorToolbarView.swift index 173012294..1c8417516 100644 --- a/Mlem/App/Views/Shared/MarkdownEditorToolbarView.swift +++ b/Mlem/App/Views/Shared/MarkdownEditorToolbarView.swift @@ -120,18 +120,9 @@ struct MarkdownEditorToolbarView: View { textView.toggleQuoteAtCursor() } if let imageUploadApi { - Menu("Image", systemImage: Icons.uploadImage) { - Button("Photo Library", systemImage: Icons.photo) { - navigation.showPhotosPicker(for: imageManager, api: imageUploadApi) - } - Button("Choose File", systemImage: "folder") { - navigation.showFilePicker(for: imageManager, api: imageUploadApi) - } - Button("Paste", systemImage: Icons.paste) { - navigation.uploadImageFromClipboard(for: imageManager, api: imageUploadApi) - } + ImageUploadMenu(imageManager: imageManager, imageUploadApi: imageUploadApi) { + Label("Image", systemImage: Icons.uploadImage) } - .disabled(imageManager.state != .idle) } Button("Spoiler", systemImage: Icons.spoiler) { textView.wrapSelectionWithSpoiler() diff --git a/Mlem/App/Views/Shared/MarkdownTextEditor.swift b/Mlem/App/Views/Shared/MarkdownTextEditor.swift index 8532d2d31..fa8ffa5fc 100644 --- a/Mlem/App/Views/Shared/MarkdownTextEditor.swift +++ b/Mlem/App/Views/Shared/MarkdownTextEditor.swift @@ -15,6 +15,7 @@ struct MarkdownTextEditor: UIViewRepresentable { let textView: UITextView let placeholderLabel: UILabel = .init() let font: UIFont + let sizingOffset: CGFloat let onChange: (String) -> Void @@ -35,6 +36,8 @@ struct MarkdownTextEditor: UIViewRepresentable { right: Constants.main.standardSpacing ), firstResponder: Bool = true, + // In forms this needs to be set to ~10 (I don't know why this is the case) + sizingOffset: CGFloat = 1, @ViewBuilder content: () -> Content ) { self.prompt = String(localized: prompt) @@ -43,6 +46,7 @@ struct MarkdownTextEditor: UIViewRepresentable { self.font = font self.insets = insets self.firstResponder = firstResponder + self.sizingOffset = sizingOffset self.onChange = onChange } @@ -84,7 +88,7 @@ struct MarkdownTextEditor: UIViewRepresentable { placeholderLabel.sizeToFit() textView.addSubview(placeholderLabel) placeholderLabel.frame.origin = CGPoint( - x: 15, + x: insets.left + 5, y: insets.top + 1 ) placeholderLabel.textColor = UIColor(Palette.main.tertiary) @@ -126,7 +130,7 @@ struct MarkdownTextEditor: UIViewRepresentable { // of the rounding logic above; it still happens when simply using `contentSize`. return .init( width: dimensions.width, - height: calculatedHeight + 1 + height: calculatedHeight + sizingOffset ) } diff --git a/Mlem/App/Views/Shared/Navigation/SettingsPage.swift b/Mlem/App/Views/Shared/Navigation/SettingsPage.swift index f36a37aeb..7f8c02d1f 100644 --- a/Mlem/App/Views/Shared/Navigation/SettingsPage.swift +++ b/Mlem/App/Views/Shared/Navigation/SettingsPage.swift @@ -11,7 +11,7 @@ import SwiftUI enum SettingsPage: Hashable { case root case accounts, account - case accountGeneral, accountAdvanced, accountSignIn, accountChangeEmail, accountLocal + case profile, accountGeneral, accountAdvanced, accountSignIn, accountChangeEmail, accountLocal case general, links, sorting case importExportSettings case theme, icon @@ -29,6 +29,12 @@ enum SettingsPage: Hashable { SettingsView() case .account: AccountSettingsView() + case .profile: + if let person = AppState.main.firstPerson { + ProfileSettingsView(person: person) + } else { + Text(verbatim: "Error: No active user account") + } case .accountGeneral: AccountGeneralSettingsView() case .accountSignIn: diff --git a/Mlem/Localizable.xcstrings b/Mlem/Localizable.xcstrings index 9a51a23cb..464ec195c 100644 --- a/Mlem/Localizable.xcstrings +++ b/Mlem/Localizable.xcstrings @@ -232,6 +232,9 @@ }, "Autoplay Media" : { + }, + "Avatar" : { + }, "Average: %@" : { @@ -256,9 +259,15 @@ }, "Banned from Instance" : { + }, + "Banner" : { + }, "Behavior" : { + }, + "Biography" : { + }, "Block" : { @@ -502,6 +511,9 @@ }, "Dismiss" : { + }, + "Display Name" : { + }, "Divider" : { @@ -924,6 +936,9 @@ }, "Most Comments" : { + }, + "My Profile" : { + }, "Never" : { @@ -1474,6 +1489,9 @@ } } } + }, + "The name that is displayed on your profile. This is not the same as your username, which cannot be changed." : { + }, "The name shown in the account switcher." : { @@ -1785,6 +1803,9 @@ }, "Wrap Code Block Lines" : { + }, + "Write a bit about yourself..." : { + }, "Year" : {