Skip to content

Commit

Permalink
Profile Settings (#1484)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sjmarf authored Dec 9, 2024
1 parent 085b0e5 commit 3daecd4
Show file tree
Hide file tree
Showing 13 changed files with 338 additions and 24 deletions.
14 changes: 11 additions & 3 deletions Mlem.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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 */; };
Expand Down Expand Up @@ -571,6 +573,8 @@
0389DDD22C39E4D40005B808 /* PasteLinkButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteLinkButtonView.swift; sourceTree = "<group>"; };
0389DDD42C39F1290005B808 /* CommunityListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityListRow.swift; sourceTree = "<group>"; };
0389DDDA2C3AB6340005B808 /* ActionBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionBuilder.swift; sourceTree = "<group>"; };
0391E0F82CFF17AE0040CCA8 /* ProfileSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSettingsView.swift; sourceTree = "<group>"; };
0391E0FA2D0066240040CCA8 /* ImageUploadMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUploadMenu.swift; sourceTree = "<group>"; };
0397D45F2C66113F002C6CDC /* CommentBodyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentBodyView.swift; sourceTree = "<group>"; };
0397D4612C676B46002C6CDC /* ApiSortType+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ApiSortType+Extensions.swift"; sourceTree = "<group>"; };
0397D4632C676CA8002C6CDC /* FeedSortPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedSortPicker.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -610,9 +614,9 @@
03AB48542CBC0B8000567FF9 /* AccountAdvancedSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountAdvancedSettingsView.swift; sourceTree = "<group>"; };
03AB48562CBC0DFC00567FF9 /* AccountSignInSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSignInSettingsView.swift; sourceTree = "<group>"; };
03AB48582CBC14CE00567FF9 /* AccountEmailSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountEmailSettingsView.swift; sourceTree = "<group>"; };
03AD09E72CF88007001EF9F7 /* MoreRepliesButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreRepliesButton.swift; sourceTree = "<group>"; };
03AD0A812CFDBFA0001EF9F7 /* AccountLocalSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountLocalSettingsView.swift; sourceTree = "<group>"; };
03AD0A832CFDC557001EF9F7 /* AccountNicknameFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountNicknameFieldView.swift; sourceTree = "<group>"; };
03AD09E72CF88007001EF9F7 /* MoreRepliesButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreRepliesButton.swift; sourceTree = "<group>"; };
03AF91DC2C1B23E500E56644 /* ImageViewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewer.swift; sourceTree = "<group>"; };
03AF91DE2C1B243D00E56644 /* ZoomableContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZoomableContainer.swift; sourceTree = "<group>"; };
03AF91E02C1B25DE00E56644 /* UIDevice+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+Extensions.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -1509,6 +1514,7 @@
0355F9482C16406E00605248 /* Line.swift */,
03D2A6402C011F3E00ED4FF2 /* ListRow */,
03B431C12C45BA00001A1EB5 /* MarkdownEditorToolbarView.swift */,
0391E0FA2D0066240040CCA8 /* ImageUploadMenu.swift */,
034B94842C09348400039AF4 /* MarkdownImageView.swift */,
03B431B32C4481C3001A1EB5 /* MarkdownTextEditor.swift */,
03D3A1EE2BB9CA1D009DE55E /* MenuButton.swift */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
);
Expand Down Expand Up @@ -2919,7 +2927,7 @@
repositoryURL = "https://github.com/mlemgroup/MlemMiddleware";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.51.0;
minimumVersion = 0.52.0;
};
};
CDE4AC402CA3706400981010 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
},
{
Expand Down
2 changes: 2 additions & 0 deletions Mlem/App/Configuration/Icons.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
7 changes: 7 additions & 0 deletions Mlem/App/Views/Root/Tabs/Profile/Profile View.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions Mlem/App/Views/Root/Tabs/Settings/AccountSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
224 changes: 224 additions & 0 deletions Mlem/App/Views/Root/Tabs/Settings/ProfileSettingsView.swift
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)
}
}
Loading

0 comments on commit 3daecd4

Please sign in to comment.