Skip to content

Commit

Permalink
Allow updating bio
Browse files Browse the repository at this point in the history
  • Loading branch information
LaurentTreguier committed Oct 21, 2024
1 parent 58d1fd4 commit afb0055
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 11 deletions.
4 changes: 4 additions & 0 deletions Fyreplace.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
4D351AEB2CA6BD45002EEB8F /* SettingsScreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D351AEA2CA6BD3A002EEB8F /* SettingsScreenTests.swift */; };
4D351AED2CA6BE2D002EEB8F /* FakeScreenBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D351AEC2CA6BE27002EEB8F /* FakeScreenBase.swift */; };
4D39A4C82BF516B7003FA52E /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 4D39A4C72BF516B7003FA52E /* Localizable.xcstrings */; };
4D40ACB42CC3ECBC00B26FDF /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D40ACB32CC3ECB300B26FDF /* User.swift */; };
4D4AF71C2C7CE72900621FF3 /* Tokens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D4AF71B2C7CE72900621FF3 /* Tokens.swift */; };
4D4D394A2C086DA2007196D2 /* PublishedScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D4D39492C086DA2007196D2 /* PublishedScreen.swift */; };
4D51F2802C621ADB0018E76E /* ViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D51F27F2C621ADB0018E76E /* ViewProtocol.swift */; };
Expand Down Expand Up @@ -111,6 +112,7 @@
4D351AEA2CA6BD3A002EEB8F /* SettingsScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenTests.swift; sourceTree = "<group>"; };
4D351AEC2CA6BE27002EEB8F /* FakeScreenBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeScreenBase.swift; sourceTree = "<group>"; };
4D39A4C72BF516B7003FA52E /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
4D40ACB32CC3ECB300B26FDF /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
4D4AF71B2C7CE72900621FF3 /* Tokens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tokens.swift; sourceTree = "<group>"; };
4D4D39492C086DA2007196D2 /* PublishedScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishedScreen.swift; sourceTree = "<group>"; };
4D51F27F2C621ADB0018E76E /* ViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewProtocol.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -425,6 +427,7 @@
4DE785872C88F392000EC4E5 /* HTTPField.swift */,
4D060BCA2C9438E8008C32D1 /* View.swift */,
4D5251F22C109FAC00018CD2 /* Label+Destination.swift */,
4D40ACB32CC3ECB300B26FDF /* User.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -644,6 +647,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4D40ACB42CC3ECBC00B26FDF /* User.swift in Sources */,
4DA04EE22CAEEAD800B70D73 /* CGFloat.swift in Sources */,
4D9B3B3D2C34B13E00A8F7AD /* LogoHeader.swift in Sources */,
4DA7BFBB2C5FDEC1005CC4FF /* FakeClient.swift in Sources */,
Expand Down
3 changes: 3 additions & 0 deletions Fyreplace/Extensions/User.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
extension Components.Schemas.User {
static let maxBioSize = 3000
}
7 changes: 5 additions & 2 deletions Fyreplace/Fakes/FakeClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -361,10 +361,13 @@ extension FakeClient {
}
}

func setCurrentUserBio(_: Operations.setCurrentUserBio.Input) async throws
func setCurrentUserBio(_ input: Operations.setCurrentUserBio.Input) async throws
-> Operations.setCurrentUserBio.Output
{
fatalError("Not implemented")
return switch input.body {
case let .plainText(text):
.ok(.init(body: .plainText(text)))
}
}

func setUserBanned(_: Operations.setUserBanned.Input) async throws
Expand Down
30 changes: 30 additions & 0 deletions Fyreplace/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,36 @@
}
}
},
"Settings.Bio.Footer:%lld,%lld" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "%1$lld/%2$lld"
}
}
}
},
"Settings.Bio.Header" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Bio"
}
}
}
},
"Settings.Bio.Update" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Save"
}
}
}
},
"Settings.DateJoined:%@" : {
"localizations" : {
"en" : {
Expand Down
35 changes: 30 additions & 5 deletions Fyreplace/Views/Screens/SettingsScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ struct SettingsScreen: View, SettingsScreenProtocol {
@State
var currentUser: Components.Schemas.User?

@State
var bio = ""

@State
var isLoadingAvatar = false

Expand All @@ -29,9 +32,12 @@ struct SettingsScreen: View, SettingsScreenProtocol {
@State
private var avatarItem: PhotosPickerItem?

@FocusState
private var bioFocused: Bool

var body: some View {
DynamicForm {
Section {
Section("Settings.Profile.Header") {
let logoutButton = Button("Settings.Logout", role: .destructive, action: logout)

HStack {
Expand Down Expand Up @@ -70,11 +76,32 @@ struct SettingsScreen: View, SettingsScreenProtocol {
Spacer()
}
#endif
} header: {
Text("Settings.Profile.Header")
}

Section {
TextEditor(text: $bio)
.scrollContentBackground(.hidden)
.frame(maxHeight: 160)

HStack {
Spacer()
Button("Settings.Bio.Update") {
Task {
await updateBio()
}
}
.disabled(!canUpdateBio)
#if !os(macOS)
Spacer()
#endif
}
} header: {
Text("Settings.Bio.Header")
} footer: {
Text("Settings.Bio.Footer:\(bio.count),\(Components.Schemas.User.maxBioSize)")
}

Section("Settings.About.Header") {
Link(destination: config.app.info.website) {
Label("App.Help.Website", systemImage: "safari")
}
Expand All @@ -94,8 +121,6 @@ struct SettingsScreen: View, SettingsScreenProtocol {
Label("App.Help.SourceCode", systemImage: "curlybraces")
}
.foregroundStyle(.tint)
} header: {
Text("Settings.About.Header")
}
}
.navigationTitle(Destination.settings.titleKey)
Expand Down
37 changes: 37 additions & 0 deletions Fyreplace/Views/Screens/SettingsScreenProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@ protocol SettingsScreenProtocol: ViewProtocol {

var token: String { get nonmutating set }
var currentUser: Components.Schemas.User? { get nonmutating set }
var bio: String { get nonmutating set }
var isLoadingAvatar: Bool { get nonmutating set }
}

@MainActor
extension SettingsScreenProtocol {
var canUpdateBio: Bool {
bio != currentUser?.bio ?? "" && bio.count <= Components.Schemas.User.maxBioSize
}

func getCurrentUser() async {
await call {
let response = try await api.getCurrentUser()
Expand All @@ -21,6 +26,7 @@ extension SettingsScreenProtocol {
switch ok.body {
case let .json(user):
currentUser = user
bio = user.bio
}

return nil
Expand Down Expand Up @@ -94,6 +100,37 @@ extension SettingsScreenProtocol {
isLoadingAvatar = false
}

func updateBio() async {
await call {
let response = try await api.setCurrentUserBio(
body: .plainText(bio.isEmpty ? .init() : .init(stringLiteral: bio)))

switch response {
case let .ok(ok):
switch ok.body {
case let .plainText(text):
bio = try await .init(
collecting: text, upTo: Components.Schemas.User.maxBioSize * 4)
currentUser?.bio = bio
}

return nil

case .badRequest(_):
return .failure(
title: "Error.BadRequest.Title",
text: "Error.BadRequest.Message"
)

case .unauthorized:
return .authorizationIssue()

case .forbidden, .default:
return .error()
}
}
}

func logout() {
token = ""
}
Expand Down
46 changes: 42 additions & 4 deletions FyreplaceTests/Screens/SettingsScreenTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ struct SettingsScreenTests {
class FakeScreen: FakeScreenBase, SettingsScreenProtocol {
var token = ""
var currentUser: Components.Schemas.User?
var bio = ""
var isLoadingAvatar = false
}

Expand All @@ -25,7 +26,8 @@ struct SettingsScreenTests {
let screen = FakeScreen(eventBus: eventBus, api: .fake())
await screen.getCurrentUser()
await screen.updateAvatar(
with: try await .init(collecting: FakeClient.largeImageBody, upTo: 64))
with: try await .init(collecting: FakeClient.largeImageBody, upTo: 64)
)
#expect(eventBus.storedEvents.count == 1)
#expect(screen.currentUser?.avatar == "")
}
Expand All @@ -36,7 +38,8 @@ struct SettingsScreenTests {
let screen = FakeScreen(eventBus: eventBus, api: .fake())
await screen.getCurrentUser()
await screen.updateAvatar(
with: try await .init(collecting: FakeClient.notImageBody, upTo: 64))
with: try await .init(collecting: FakeClient.notImageBody, upTo: 64)
)
#expect(eventBus.storedEvents.count == 1)
#expect(screen.currentUser?.avatar == "")
}
Expand All @@ -47,7 +50,8 @@ struct SettingsScreenTests {
let screen = FakeScreen(eventBus: eventBus, api: .fake())
await screen.getCurrentUser()
await screen.updateAvatar(
with: try await .init(collecting: FakeClient.normalImageBody, upTo: 64))
with: try await .init(collecting: FakeClient.normalImageBody, upTo: 64)
)
#expect(eventBus.storedEvents.isEmpty)
#expect(screen.currentUser?.avatar == FakeClient.avatar)
}
Expand All @@ -58,9 +62,43 @@ struct SettingsScreenTests {
let screen = FakeScreen(eventBus: eventBus, api: .fake())
await screen.getCurrentUser()
await screen.updateAvatar(
with: try await .init(collecting: FakeClient.normalImageBody, upTo: 64))
with: try await .init(collecting: FakeClient.normalImageBody, upTo: 64)
)
await screen.removeAvatar()
#expect(eventBus.storedEvents.isEmpty)
#expect(screen.currentUser?.avatar == "")
}

@Test("Bio must have correct length")
func bioMustHaveCorrectLength() async throws {
let screen = FakeScreen(eventBus: .init(), api: .fake())
await screen.getCurrentUser()
screen.bio = "Hello"
#expect(screen.canUpdateBio)
screen.bio = .init(repeating: "a", count: Components.Schemas.User.maxBioSize)
#expect(screen.canUpdateBio)
screen.bio += "a"
#expect(!screen.canUpdateBio)
}

@Test("Bio must be different")
func bioMustHaveBeDifferent() async throws {
let screen = FakeScreen(eventBus: .init(), api: .fake())
await screen.getCurrentUser()
screen.bio = "Hello"
#expect(screen.canUpdateBio)
await screen.updateBio()
#expect(!screen.canUpdateBio)
}

@Test("Updating bio produces no failures")
func updateBioProducesNoFailures() async throws {
let eventBus = StoringEventBus()
let screen = FakeScreen(eventBus: eventBus, api: .fake())
await screen.getCurrentUser()
screen.bio = "Hello"
await screen.updateBio()
#expect(eventBus.storedEvents.isEmpty)
#expect(screen.currentUser?.bio == screen.bio)
}
}

0 comments on commit afb0055

Please sign in to comment.