Improve edit profile screen.
This commit is contained in:
parent
7205b14d32
commit
f4322f74e4
|
@ -268,6 +268,8 @@
|
|||
"editProfile.title.accountSaved" = "Profile has been updated.";
|
||||
"editProfile.title.photoInfo" = "The changed photo will be visible in the app and on the website with a small delay.";
|
||||
"editProfile.title.privateAccount" = "Private account";
|
||||
"editProfile.title.privateAccountInfo" = "When your account is private, only people you approve can see your photos and videos on pixelfed. Your existing followers won't be affected.";
|
||||
"editProfile.title.privateAccountInfo" = "When your account is private, only people you approve can see your photos and videos on Pixelfed. Your existing followers won't be affected.";
|
||||
"editProfile.error.saveAccountFailed" = "Saving profile failed.";
|
||||
"editProfile.error.loadingAvatarFailed" = "Loading avatar failed.";
|
||||
"editProfile.error.noProfileData" = "Profile data cannot be displayed.";
|
||||
"editProfile.error.loadingAccountFailed" = "Error during download account from server.";
|
||||
|
|
|
@ -271,3 +271,5 @@
|
|||
"editProfile.title.privateAccountInfo" = "Kiedy Twoje konto jest prywatne, tylko osoby, które zaakceptujesz mogą oglądać Twoje zdjęcia i filmy na Pixelfed. Nie wpłynie to na Twoich obecnych obserwujących.";
|
||||
"editProfile.error.saveAccountFailed" = "Błąd podczas aktualizacji profilu.";
|
||||
"editProfile.error.loadingAvatarFailed" = "Błąd podczas wczytywania zdjęcia.";
|
||||
"editProfile.error.noProfileData" = "Dane profilu nie mogą zostać wyświetlone.";
|
||||
"editProfile.error.loadingAccountFailed" = "Błąd podczas pobierania profilu użytkownika.";
|
||||
|
|
|
@ -84,6 +84,9 @@ public struct Account: Codable {
|
|||
/// User website.
|
||||
public let website: String?
|
||||
|
||||
/// An extra attribute that contains source values to be used with API methods that verify credentials and update credentials.
|
||||
public let source: Source?
|
||||
|
||||
/// When the most recent status was posted.
|
||||
/// NULLABLE String (ISO 8601 Date), or null if no statuses
|
||||
public let lastStatusAt: String?
|
||||
|
@ -119,6 +122,7 @@ public struct Account: Codable {
|
|||
case lastStatusAt = "last_status_at"
|
||||
case recentPosts = "recent_posts"
|
||||
case website
|
||||
case source
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// An extra attribute that contains source values to be used with API methods that verify credentials and update credentials.
|
||||
public struct Source: Codable {
|
||||
|
||||
/// Profile bio, in plain-text instead of in HTML.
|
||||
public let note: String
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case note
|
||||
}
|
||||
}
|
|
@ -162,6 +162,7 @@
|
|||
F89AC00529A1F9B500F4159F /* AppMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89AC00429A1F9B500F4159F /* AppMetadata.swift */; };
|
||||
F89AC00729A208CC00F4159F /* PlaceSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89AC00629A208CC00F4159F /* PlaceSelectorView.swift */; };
|
||||
F89AC00929A20C5C00F4159F /* Client+Places.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89AC00829A20C5C00F4159F /* Client+Places.swift */; };
|
||||
F89B5CC029D019B600549F2F /* HTMLString in Frameworks */ = {isa = PBXBuildFile; productRef = F89B5CBF29D019B600549F2F /* HTMLString */; };
|
||||
F89CEB802984198600A1376F /* AttachmentData+HighestImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89CEB7F2984198600A1376F /* AttachmentData+HighestImage.swift */; };
|
||||
F89D6C3F29716E41001DA3D4 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89D6C3E29716E41001DA3D4 /* Theme.swift */; };
|
||||
F89D6C4229717FDC001DA3D4 /* AccountsSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89D6C4129717FDC001DA3D4 /* AccountsSectionView.swift */; };
|
||||
|
@ -452,6 +453,7 @@
|
|||
F83E00ED29A2237C005D25A3 /* PixelfedKit in Frameworks */,
|
||||
F8210DD92966BB7E001D9973 /* NukeUI in Frameworks */,
|
||||
F85E132529741F05006A051D /* ActivityIndicatorView in Frameworks */,
|
||||
F89B5CC029D019B600549F2F /* HTMLString in Frameworks */,
|
||||
F8B1E64F2973F61400EE0D10 /* Drops in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -974,6 +976,7 @@
|
|||
F85E132429741F05006A051D /* ActivityIndicatorView */,
|
||||
F88E4D4C297EA4290057491A /* EmojiText */,
|
||||
F83E00EC29A2237C005D25A3 /* PixelfedKit */,
|
||||
F89B5CBF29D019B600549F2F /* HTMLString */,
|
||||
);
|
||||
productName = Vernissage;
|
||||
productReference = F88C2468295C37B80006098B /* Vernissage.app */;
|
||||
|
@ -1012,6 +1015,7 @@
|
|||
F8B1E64D2973F61400EE0D10 /* XCRemoteSwiftPackageReference "Drops" */,
|
||||
F85E132329741F05006A051D /* XCRemoteSwiftPackageReference "ActivityIndicatorView" */,
|
||||
F88E4D4B297EA4290057491A /* XCRemoteSwiftPackageReference "EmojiText" */,
|
||||
F89B5CBE29D019B600549F2F /* XCRemoteSwiftPackageReference "HTMLString" */,
|
||||
);
|
||||
productRefGroup = F88C2469295C37B80006098B /* Products */;
|
||||
projectDirPath = "";
|
||||
|
@ -1580,6 +1584,14 @@
|
|||
minimumVersion = 2.6.0;
|
||||
};
|
||||
};
|
||||
F89B5CBE29D019B600549F2F /* XCRemoteSwiftPackageReference "HTMLString" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/alexaubry/HTMLString";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 6.0.0;
|
||||
};
|
||||
};
|
||||
F8B1E64D2973F61400EE0D10 /* XCRemoteSwiftPackageReference "Drops" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/omaralbeik/Drops";
|
||||
|
@ -1624,6 +1636,11 @@
|
|||
package = F88E4D4B297EA4290057491A /* XCRemoteSwiftPackageReference "EmojiText" */;
|
||||
productName = EmojiText;
|
||||
};
|
||||
F89B5CBF29D019B600549F2F /* HTMLString */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = F89B5CBE29D019B600549F2F /* XCRemoteSwiftPackageReference "HTMLString" */;
|
||||
productName = HTMLString;
|
||||
};
|
||||
F8B1E64E2973F61400EE0D10 /* Drops */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = F8B1E64D2973F61400EE0D10 /* XCRemoteSwiftPackageReference "Drops" */;
|
||||
|
|
|
@ -44,8 +44,8 @@ extension View {
|
|||
AccountsPhotoView(listType: listType)
|
||||
case .search:
|
||||
SearchView()
|
||||
case .editProfile(let account):
|
||||
EditProfileView(account: account)
|
||||
case .editProfile:
|
||||
EditProfileView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ enum RouteurDestinations: Hashable {
|
|||
case hashtags(listType: HashtagsView.ListType)
|
||||
case accountsPhoto(listType: AccountsPhotoView.ListType)
|
||||
case search
|
||||
case editProfile(account: Account)
|
||||
case editProfile
|
||||
}
|
||||
|
||||
enum SheetDestinations: Identifiable {
|
||||
|
|
|
@ -7,12 +7,14 @@
|
|||
import PhotosUI
|
||||
import SwiftUI
|
||||
import PixelfedKit
|
||||
import HTMLString
|
||||
|
||||
struct EditProfileView: View {
|
||||
@EnvironmentObject private var applicationState: ApplicationState
|
||||
@EnvironmentObject private var client: Client
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@State private var account: Account?
|
||||
@State private var photosPickerVisible = false
|
||||
@State private var selectedItems: [PhotosPickerItem] = []
|
||||
@State private var saveDisabled = false
|
||||
|
@ -21,17 +23,36 @@ struct EditProfileView: View {
|
|||
@State private var website: String = ""
|
||||
@State private var isPrivate = false
|
||||
@State private var avatarData: Data?
|
||||
@State private var state: ViewState = .loading
|
||||
|
||||
private let account: Account
|
||||
private let bioMaxLength = 200
|
||||
private let displayNameMaxLength = 30
|
||||
private let websiteMaxLength = 120
|
||||
|
||||
init(account: Account) {
|
||||
self.account = account
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
switch state {
|
||||
case .loading:
|
||||
LoadingIndicator()
|
||||
.task {
|
||||
await self.loadData()
|
||||
}
|
||||
case .loaded:
|
||||
if let account = self.account {
|
||||
self.editForm(account: account)
|
||||
} else {
|
||||
NoDataView(imageSystemName: "person.crop.circle", text: "editProfile.error.noProfileData")
|
||||
}
|
||||
case .error(let error):
|
||||
ErrorView(error: error) {
|
||||
self.state = .loading
|
||||
await self.loadData()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func editForm(account: Account) -> some View {
|
||||
Form {
|
||||
HStack {
|
||||
Spacer()
|
||||
|
@ -67,7 +88,7 @@ struct EditProfileView: View {
|
|||
.frame(width: 130, height: 130)
|
||||
}
|
||||
|
||||
Text("@\(self.account.acct)")
|
||||
Text("@\(account.acct)")
|
||||
.font(.headline)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
|
||||
|
@ -146,7 +167,7 @@ struct EditProfileView: View {
|
|||
.toolbar {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
ActionButton(showLoader: true) {
|
||||
await self.saveProfile()
|
||||
await self.saveProfile(account: account)
|
||||
} label: {
|
||||
Text("editProfile.title.save", comment: "Save")
|
||||
}
|
||||
|
@ -156,13 +177,18 @@ struct EditProfileView: View {
|
|||
}
|
||||
.navigationTitle("editProfile.navigationBar.title")
|
||||
.onAppear {
|
||||
self.displayName = self.account.displayName ?? String.empty()
|
||||
self.website = self.account.website ?? String.empty()
|
||||
self.isPrivate = self.account.locked
|
||||
self.displayName = account.displayName ?? String.empty()
|
||||
self.website = account.website ?? String.empty()
|
||||
self.isPrivate = account.locked
|
||||
|
||||
let markdownBio = self.account.note?.asMarkdown ?? String.empty()
|
||||
if let attributedString = try? AttributedString(markdown: markdownBio) {
|
||||
self.bio = String(attributedString.characters)
|
||||
// Bio should be set from source property (which is plain text).
|
||||
if let note = account.source?.note {
|
||||
self.bio = note.removingHTMLEntities()
|
||||
} else {
|
||||
let markdownBio = account.note?.asMarkdown ?? String.empty()
|
||||
if let attributedString = try? AttributedString(markdown: markdownBio) {
|
||||
self.bio = String(attributedString.characters)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: self.selectedItems) { selectedItem in
|
||||
|
@ -176,8 +202,22 @@ struct EditProfileView: View {
|
|||
matching: .images)
|
||||
}
|
||||
|
||||
private func loadData() async {
|
||||
do {
|
||||
self.account = try await self.client.accounts?.pixelfedClient.verifyCredentials()
|
||||
self.state = .loaded
|
||||
} catch {
|
||||
if !Task.isCancelled {
|
||||
ErrorService.shared.handle(error, message: "editProfile.error.loadingAccountFailed", showToastr: true)
|
||||
self.state = .error(error)
|
||||
} else {
|
||||
ErrorService.shared.handle(error, message: "editProfile.error.loadingAccountFailed", showToastr: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func saveProfile() async {
|
||||
private func saveProfile(account: Account) async {
|
||||
do {
|
||||
_ = try await self.client.accounts?.update(displayName: self.displayName,
|
||||
bio: self.bio,
|
||||
|
@ -188,15 +228,14 @@ struct EditProfileView: View {
|
|||
if let avatarData = self.avatarData {
|
||||
_ = try await self.client.accounts?.avatar(image: avatarData)
|
||||
|
||||
if let accountData = AccountDataHandler.shared.getAccountData(accountId: self.account.id) {
|
||||
if let accountData = AccountDataHandler.shared.getAccountData(accountId: account.id) {
|
||||
accountData.avatarData = avatarData
|
||||
self.applicationState.account?.avatarData = avatarData
|
||||
CoreDataHandler.shared.save()
|
||||
}
|
||||
}
|
||||
|
||||
let savedAccount = try await self.client.accounts?.account(withId: self.account.id)
|
||||
// self.applicationState.account?.avatar,
|
||||
let savedAccount = try await self.client.accounts?.account(withId: account.id)
|
||||
self.applicationState.updatedProfile = savedAccount
|
||||
|
||||
ToastrService.shared.showSuccess("editProfile.title.accountSaved", imageSystemName: "person.crop.circle")
|
||||
|
|
|
@ -13,7 +13,6 @@ private struct OffsetPreferenceKey: PreferenceKey {
|
|||
|
||||
struct HomeFeedView: View {
|
||||
@Environment(\.managedObjectContext) private var viewContext
|
||||
@Environment(\.refresh) private var refresh
|
||||
|
||||
@EnvironmentObject var applicationState: ApplicationState
|
||||
@EnvironmentObject var routerPath: RouterPath
|
||||
|
@ -88,11 +87,6 @@ struct HomeFeedView: View {
|
|||
self.newPhotosView()
|
||||
.offset(y: self.offset)
|
||||
.opacity(self.opacity)
|
||||
.onTapGesture {
|
||||
Task {
|
||||
await self.refresh?()
|
||||
}
|
||||
}
|
||||
}
|
||||
.refreshable {
|
||||
HapticService.shared.fireHaptic(of: .dataRefresh(intensity: 0.3))
|
||||
|
|
|
@ -41,8 +41,8 @@ struct ThirdPartyView: View {
|
|||
|
||||
Section("OAuth authrisation") {
|
||||
VStack(alignment: .leading) {
|
||||
Link("https://github.com/OAuthSwift/OAuthSwift.git",
|
||||
destination: URL(string: "https://github.com/OAuthSwift/OAuthSwift.git")!)
|
||||
Link("https://github.com/OAuthSwift/OAuthSwift",
|
||||
destination: URL(string: "https://github.com/OAuthSwift/OAuthSwift")!)
|
||||
.padding(.bottom, 4)
|
||||
Text("Swift based OAuth library for iOS and macOS.")
|
||||
}
|
||||
|
@ -61,13 +61,23 @@ struct ThirdPartyView: View {
|
|||
|
||||
Section("Loaders") {
|
||||
VStack(alignment: .leading) {
|
||||
Link("https://github.com/exyte/ActivityIndicatorView.git",
|
||||
destination: URL(string: "https://github.com/exyte/ActivityIndicatorView.git")!)
|
||||
Link("https://github.com/exyte/ActivityIndicatorView",
|
||||
destination: URL(string: "https://github.com/exyte/ActivityIndicatorView")!)
|
||||
.padding(.bottom, 4)
|
||||
Text("A number of preset loading indicators created with SwiftUI.")
|
||||
}
|
||||
.font(.footnote)
|
||||
}
|
||||
|
||||
Section("HTML String") {
|
||||
VStack(alignment: .leading) {
|
||||
Link("https://github.com/alexisakers/HTMLString",
|
||||
destination: URL(string: "https://github.com/alexisakers/HTMLString")!)
|
||||
.padding(.bottom, 4)
|
||||
Text("HTMLString is a library written in Swift that allows your program to add and remove HTML entities in Strings.")
|
||||
}
|
||||
.font(.footnote)
|
||||
}
|
||||
}
|
||||
.navigationTitle("thirdParty.navigationBar.title")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
|
|
|
@ -169,14 +169,11 @@ struct UserProfileView: View {
|
|||
Label(NSLocalizedString("userProfile.title.bookmarks", comment: "Bookmarks"), systemImage: "bookmark")
|
||||
}
|
||||
|
||||
if let account = self.account {
|
||||
Divider()
|
||||
|
||||
NavigationLink(value: RouteurDestinations.editProfile(account: account)) {
|
||||
Label(NSLocalizedString("userProfile.title.edit", comment: "Edit profile"), systemImage: "pencil")
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
|
||||
NavigationLink(value: RouteurDestinations.editProfile) {
|
||||
Label(NSLocalizedString("userProfile.title.edit", comment: "Edit profile"), systemImage: "pencil")
|
||||
}
|
||||
}, label: {
|
||||
Image(systemName: "gear")
|
||||
.tint(.mainTextColor)
|
||||
|
|
Loading…
Reference in New Issue