Improve edit profile screen.

This commit is contained in:
Marcin Czachursk 2023-03-26 08:21:22 +02:00
parent 7205b14d32
commit f4322f74e4
11 changed files with 121 additions and 38 deletions

View File

@ -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.";

View File

@ -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.";

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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" */;

View File

@ -44,8 +44,8 @@ extension View {
AccountsPhotoView(listType: listType)
case .search:
SearchView()
case .editProfile(let account):
EditProfileView(account: account)
case .editProfile:
EditProfileView()
}
}
}

View File

@ -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 {

View File

@ -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")

View File

@ -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))

View File

@ -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)

View File

@ -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)