Improve edit profile screen.
This commit is contained in:
parent
d437478797
commit
88ca1a3f5c
|
@ -266,5 +266,6 @@
|
|||
"editProfile.title.website" = "Website";
|
||||
"editProfile.title.save" = "Save";
|
||||
"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.error.saveAccountFailed" = "Saving profile failed.";
|
||||
"editProfile.error.loadingAvatarFailed" = "Loading avatar failed.";
|
||||
|
|
|
@ -266,5 +266,6 @@
|
|||
"editProfile.title.website" = "Strona";
|
||||
"editProfile.title.save" = "Zapisz";
|
||||
"editProfile.title.accountSaved" = "Profil zaktualizowano.";
|
||||
"editProfile.title.photoInfo" = "Zmienione zdjęcie będzie widoczne w aplikacji oraz na stronie z małym opóźnieniem.";
|
||||
"editProfile.error.saveAccountFailed" = "Błąd podczas aktualizacji profilu.";
|
||||
"editProfile.error.loadingAvatarFailed" = "Błąd podczas wczytywania zdjęcia.";
|
||||
|
|
|
@ -170,4 +170,13 @@ public extension PixelfedClientAuthenticated {
|
|||
|
||||
return try await downloadJson(Account.self, request: request)
|
||||
}
|
||||
|
||||
func avatar(image: Data?) async throws -> Account {
|
||||
let request = try Self.request(
|
||||
for: baseURL,
|
||||
target: Pixelfed.Account.updateAvatar(image),
|
||||
withBearerToken: token)
|
||||
|
||||
return try await downloadJson(Account.self, request: request)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ extension Pixelfed {
|
|||
case relationships([EntityId])
|
||||
case search(SearchQuery, Int)
|
||||
case updateCredentials(String, String, String, Data?)
|
||||
case updateAvatar(Data?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,6 +60,8 @@ extension Pixelfed.Account: TargetType {
|
|||
return "\(apiPath)/search"
|
||||
case .updateCredentials(_, _, _, _):
|
||||
return "\(apiPath)/update_credentials"
|
||||
case .updateAvatar(_):
|
||||
return "\(apiPath)/update_credentials"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,8 +69,10 @@ extension Pixelfed.Account: TargetType {
|
|||
switch self {
|
||||
case .follow(_), .unfollow(_), .block(_), .unblock(_), .mute(_), .unmute(_):
|
||||
return .post
|
||||
case .updateCredentials(_, _, _, _):
|
||||
return .patch
|
||||
case .updateCredentials(_, _, _, _), .updateAvatar(_):
|
||||
// Mastodon API uses PATCH, however in Pixelfed we have to use POST: https://github.com/pixelfed/pixelfed/issues/4250
|
||||
// Also it seems that we have to speparatelly save text fields and avatar(?).
|
||||
return .post
|
||||
default:
|
||||
return .get
|
||||
}
|
||||
|
@ -113,19 +118,10 @@ extension Pixelfed.Account: TargetType {
|
|||
minId = _minId
|
||||
limit = _limit
|
||||
page = _page
|
||||
case .updateCredentials(let displayName, let bio, let website, let image):
|
||||
if image == nil {
|
||||
return [
|
||||
("display_name", displayName),
|
||||
("note", bio),
|
||||
("website", website),
|
||||
("_pe", "1")
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
("_pe", "1")
|
||||
]
|
||||
}
|
||||
case .updateCredentials(_, _, _, _), .updateAvatar(_):
|
||||
return [
|
||||
("_pe", "1")
|
||||
]
|
||||
case .account(_), .verifyCredentials:
|
||||
return [
|
||||
("_pe", "1")
|
||||
|
@ -155,12 +151,8 @@ extension Pixelfed.Account: TargetType {
|
|||
|
||||
public var headers: [String: String]? {
|
||||
switch self {
|
||||
case .updateCredentials(_, _, _, let image):
|
||||
if image != nil {
|
||||
return ["content-type": "multipart/form-data; boundary=\(multipartBoundary)"]
|
||||
} else {
|
||||
return ["content-type": "application/x-www-form-urlencoded"]
|
||||
}
|
||||
case .updateCredentials(_, _, _, _), .updateAvatar(_):
|
||||
return ["content-type": "multipart/form-data; boundary=\(multipartBoundary)"]
|
||||
default:
|
||||
return [:].contentTypeApplicationJson
|
||||
}
|
||||
|
@ -169,18 +161,25 @@ extension Pixelfed.Account: TargetType {
|
|||
public var httpBody: Data? {
|
||||
switch self {
|
||||
case .updateCredentials(let displayName, let bio, let website, let image):
|
||||
if let image {
|
||||
let formDataBuilder = MultipartFormData(boundary: multipartBoundary)
|
||||
|
||||
formDataBuilder.addTextField(named: "display_name", value: displayName)
|
||||
formDataBuilder.addTextField(named: "note", value: bio)
|
||||
formDataBuilder.addTextField(named: "website", value: website)
|
||||
formDataBuilder.addDataField(named: "avatar", fileName: "avatar.jpg", data: image, mimeType: "image/jpeg")
|
||||
let formDataBuilder = MultipartFormData(boundary: multipartBoundary)
|
||||
|
||||
formDataBuilder.addTextField(named: "display_name", value: displayName)
|
||||
formDataBuilder.addTextField(named: "note", value: bio)
|
||||
formDataBuilder.addTextField(named: "website", value: website)
|
||||
|
||||
return formDataBuilder.build()
|
||||
} else {
|
||||
return nil
|
||||
if let image {
|
||||
formDataBuilder.addDataField(named: "avatar", fileName: "avatar.jpg", data: image, mimeType: "image/jpeg")
|
||||
}
|
||||
|
||||
return formDataBuilder.build()
|
||||
case .updateAvatar(let image):
|
||||
let formDataBuilder = MultipartFormData(boundary: multipartBoundary)
|
||||
|
||||
if let image {
|
||||
formDataBuilder.addDataField(named: "avatar", fileName: "avatar.jpg", data: image, mimeType: "image/jpeg")
|
||||
}
|
||||
|
||||
return formDataBuilder.build()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1278,7 +1278,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 82;
|
||||
CURRENT_PROJECT_VERSION = 83;
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageWidget/Info.plist;
|
||||
|
@ -1306,7 +1306,7 @@
|
|||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 82;
|
||||
CURRENT_PROJECT_VERSION = 83;
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageWidget/Info.plist;
|
||||
|
@ -1454,7 +1454,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 81;
|
||||
CURRENT_PROJECT_VERSION = 83;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
@ -1494,7 +1494,7 @@
|
|||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 81;
|
||||
CURRENT_PROJECT_VERSION = 83;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
|
|
@ -86,5 +86,9 @@ extension Client {
|
|||
func update(displayName: String, bio: String, website: String, image: Data?) async throws -> Account {
|
||||
return try await pixelfedClient.update(displayName: displayName, bio: bio, website: website, image: image)
|
||||
}
|
||||
|
||||
func avatar(image: Data?) async throws -> Account {
|
||||
return try await pixelfedClient.avatar(image: image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ public class AccountModel: ObservableObject, Identifiable {
|
|||
public let refreshToken: String?
|
||||
public let acct: String
|
||||
public let avatar: URL?
|
||||
public let avatarData: Data?
|
||||
public let clientId: String
|
||||
public let clientSecret: String
|
||||
public let clientVapidKey: String
|
||||
|
@ -29,6 +28,8 @@ public class AccountModel: ObservableObject, Identifiable {
|
|||
public let username: String
|
||||
public let lastSeenStatusId: String?
|
||||
|
||||
@Published public var avatarData: Data?
|
||||
|
||||
init(accountData: AccountData) {
|
||||
self.accessToken = accountData.accessToken
|
||||
self.refreshToken = accountData.refreshToken
|
||||
|
|
|
@ -22,6 +22,9 @@ struct EditProfileView: View {
|
|||
@State private var avatarData: Data?
|
||||
|
||||
private let account: Account
|
||||
private let bioMaxLength = 200
|
||||
private let displayNameMaxLength = 30
|
||||
private let websiteMaxLength = 120
|
||||
|
||||
init(account: Account) {
|
||||
self.account = account
|
||||
|
@ -64,8 +67,20 @@ struct EditProfileView: View {
|
|||
}
|
||||
|
||||
Text("@\(self.account.acct)")
|
||||
.font(.subheadline)
|
||||
.font(.headline)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
|
||||
if self.avatarData != nil {
|
||||
HStack {
|
||||
Image(systemName: "info.circle")
|
||||
.font(.body)
|
||||
.foregroundColor(.accentColor)
|
||||
Text("editProfile.title.photoInfo")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
}
|
||||
.padding(.top, 4)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
@ -74,21 +89,50 @@ struct EditProfileView: View {
|
|||
.listRowBackground(Color(UIColor.systemGroupedBackground))
|
||||
.listRowSeparator(Visibility.hidden)
|
||||
|
||||
|
||||
Section("editProfile.title.displayName") {
|
||||
Section {
|
||||
TextField("", text: $displayName)
|
||||
.onChange(of: self.displayName, perform: { newValue in
|
||||
self.displayName = String(self.displayName.prefix(self.displayNameMaxLength))
|
||||
})
|
||||
} header: {
|
||||
Text("editProfile.title.displayName", comment: "Display name")
|
||||
} footer: {
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("\(self.displayName.count)/\(self.displayNameMaxLength)")
|
||||
}
|
||||
}
|
||||
|
||||
Section("editProfile.title.bio") {
|
||||
Section {
|
||||
TextField("", text: $bio, axis: .vertical)
|
||||
.lineLimit(5, reservesSpace: true)
|
||||
.onChange(of: self.bio, perform: { newValue in
|
||||
self.bio = String(self.bio.prefix(self.bioMaxLength))
|
||||
})
|
||||
} header: {
|
||||
Text("editProfile.title.bio", comment: "Bio")
|
||||
} footer: {
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("\(self.bio.count)/\(self.bioMaxLength)")
|
||||
}
|
||||
}
|
||||
|
||||
Section("editProfile.title.website") {
|
||||
Section {
|
||||
TextField("", text: $website)
|
||||
.autocapitalization(.none)
|
||||
.keyboardType(.URL)
|
||||
.autocorrectionDisabled()
|
||||
.onChange(of: self.website, perform: { newValue in
|
||||
self.website = String(self.website.prefix(self.websiteMaxLength))
|
||||
})
|
||||
} header: {
|
||||
Text("editProfile.title.website", comment: "Website")
|
||||
} footer: {
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("\(self.website.count)/\(self.websiteMaxLength)")
|
||||
}
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
|
@ -123,12 +167,26 @@ struct EditProfileView: View {
|
|||
matching: .images)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func saveProfile() async {
|
||||
do {
|
||||
let savedAccount = try await self.client.accounts?.update(displayName: self.displayName,
|
||||
bio: self.bio,
|
||||
website: self.website,
|
||||
image: self.avatarData)
|
||||
_ = try await self.client.accounts?.update(displayName: self.displayName,
|
||||
bio: self.bio,
|
||||
website: self.website,
|
||||
image: nil)
|
||||
|
||||
if let avatarData = self.avatarData {
|
||||
_ = try await self.client.accounts?.avatar(image: avatarData)
|
||||
|
||||
if let accountData = AccountDataHandler.shared.getAccountData(accountId: self.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,
|
||||
self.applicationState.updatedProfile = savedAccount
|
||||
|
||||
ToastrService.shared.showSuccess("editProfile.title.accountSaved", imageSystemName: "person.crop.circle")
|
||||
|
@ -162,7 +220,9 @@ struct EditProfileView: View {
|
|||
return
|
||||
}
|
||||
|
||||
self.avatarData = data
|
||||
withAnimation(.linear) {
|
||||
self.avatarData = data
|
||||
}
|
||||
|
||||
self.saveDisabled = false
|
||||
} catch {
|
||||
|
|
Loading…
Reference in New Issue