-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
337 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
224 changes: 224 additions & 0 deletions
224
Mlem/App/Views/Root/Tabs/Settings/ProfileSettingsView.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// | ||
// ImageUploadMenu.swift | ||
// Mlem | ||
// | ||
// Created by Sjmarf on 2024-12-04. | ||
// | ||
|
||
import MlemMiddleware | ||
import SwiftUI | ||
|
||
struct ImageUploadMenu<Label: View>: 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) | ||
} | ||
} |
Oops, something went wrong.