From 27d2848a45e693d36af90be4032089b2f7302967 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 12 Dec 2022 00:53:53 +0100 Subject: [PATCH 01/12] Implemented the GetUserUseCase use case for the Login module. --- .../Sources/DataModels/Models/Account.swift | 2 +- .../Login/Logic/Defines/Typealiases.swift | 11 ++++ .../Logic/Use Cases/GetUserUseCase.swift | 55 +++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 Modules/Sources/Login/Logic/Defines/Typealiases.swift create mode 100644 Modules/Sources/Login/Logic/Use Cases/GetUserUseCase.swift diff --git a/Libraries/Sources/DataModels/Models/Account.swift b/Libraries/Sources/DataModels/Models/Account.swift index c0b1711..8ecf488 100644 --- a/Libraries/Sources/DataModels/Models/Account.swift +++ b/Libraries/Sources/DataModels/Models/Account.swift @@ -6,7 +6,7 @@ // Copyright © 2022 Röck+Cöde. All rights reserved. // -public struct Account: Codable { +public struct Account: Codable, Equatable { // MARK: Properties diff --git a/Modules/Sources/Login/Logic/Defines/Typealiases.swift b/Modules/Sources/Login/Logic/Defines/Typealiases.swift new file mode 100644 index 0000000..cdc0382 --- /dev/null +++ b/Modules/Sources/Login/Logic/Defines/Typealiases.swift @@ -0,0 +1,11 @@ +// +// Typealiases.swift +// Login +// +// Created by Javier Cicchelli on 12/12/2022. +// Copyright © 2022 Röck+Cöde. All rights reserved. +// + +import DataModels + +public typealias LoginSuccessClosure = (Account, User) -> Void diff --git a/Modules/Sources/Login/Logic/Use Cases/GetUserUseCase.swift b/Modules/Sources/Login/Logic/Use Cases/GetUserUseCase.swift new file mode 100644 index 0000000..eefac37 --- /dev/null +++ b/Modules/Sources/Login/Logic/Use Cases/GetUserUseCase.swift @@ -0,0 +1,55 @@ +// +// GetUserUseCase.swift +// Login +// +// Created by Javier Cicchelli on 12/12/2022. +// Copyright © 2022 Röck+Cöde. All rights reserved. +// + +import APIService +import DependencyInjection +import Dependencies + +struct GetUserUseCase { + + // MARK: Dependencies + + @Dependency(\.apiService) private var apiService + + // MARK: Properties + + let success: LoginSuccessClosure + + // MARK: Functions + + func callAsFunction( + username: String, + password: String + ) async throws { + let me = try await apiService.getUser( + credentials: .init( + username: username, + password: password + ) + ) + + success( + .init( + username: username, + password: password + ), + .init( + profile: .init( + firstName: me.firstName, + lastName: me.lastName + ), + rootFolder: .init( + id: me.rootItem.id, + name: me.rootItem.name, + lastModifiedAt: me.rootItem.lastModifiedAt + ) + ) + ) + } + +} From 332b8ab245755b78759b834fff97416aac3b4143 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 12 Dec 2022 00:55:37 +0100 Subject: [PATCH 02/12] Integrated the GetUserUseCase use case into the LoginView view. --- .../Sources/Login/UI/Views/LoginView.swift | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Modules/Sources/Login/UI/Views/LoginView.swift b/Modules/Sources/Login/UI/Views/LoginView.swift index 754fe4c..619229b 100644 --- a/Modules/Sources/Login/UI/Views/LoginView.swift +++ b/Modules/Sources/Login/UI/Views/LoginView.swift @@ -7,10 +7,6 @@ // import APIService -import DataModels -import DependencyInjection -import Dependencies -import KeychainStorage import SwiftUI public struct LoginView: View { @@ -19,9 +15,15 @@ public struct LoginView: View { @State private var containerTopPadding: CGFloat = 0 + // MARK: Properties + + private let success: LoginSuccessClosure + // MARK: Initialisers - public init() {} + public init(success: @escaping LoginSuccessClosure) { + self.success = success + } // MARK: Body @@ -30,7 +32,7 @@ public struct LoginView: View { .vertical, showsIndicators: false ) { - LoginContainer() + LoginContainer(success: success) .padding(.horizontal, 24) .padding(.top, containerTopPadding) } @@ -39,6 +41,7 @@ public struct LoginView: View { .onPreferenceChange(ViewHeightPreferenceKey.self) { height in containerTopPadding = height * 0.1 } + .interactiveDismissDisabled() } } @@ -47,21 +50,23 @@ public struct LoginView: View { fileprivate extension LoginView { struct LoginContainer: View { - - // MARK: Dependencies - - @Dependency(\.apiService) private var apiService - - // MARK: Storages - - @KeychainStorage(key: .KeychainStorage.account) private var account: Account? - + // MARK: States @State private var isAuthenticating: Bool = false @State private var username: String = "" @State private var password: String = "" @State private var errorMessage: String? + + // MARK: Properties + + private let getUser: GetUserUseCase + + // MARK: Initialisers + + init(success: @escaping LoginSuccessClosure) { + self.getUser = .init(success: success) + } // MARK: Body @@ -133,12 +138,7 @@ private extension LoginView.LoginContainer { guard isAuthenticating else { return } do { - _ = try await apiService.getUser(credentials: .init( - username: username, - password: password - )) - - account = .init( + try await getUser( username: username, password: password ) @@ -157,6 +157,6 @@ private extension LoginView.LoginContainer { struct LoginView_Previews: PreviewProvider { static var previews: some View { - LoginView() + LoginView { _, _ in } } } From 05023985a1ac6c74591dcd7d5ebec44529a1ff43 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 12 Dec 2022 01:11:06 +0100 Subject: [PATCH 03/12] Defined the ActionClosure closure in the Typealiases definition of the DataModels library. --- Libraries/Sources/DataModels/Defines/Typealiases.swift | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Libraries/Sources/DataModels/Defines/Typealiases.swift diff --git a/Libraries/Sources/DataModels/Defines/Typealiases.swift b/Libraries/Sources/DataModels/Defines/Typealiases.swift new file mode 100644 index 0000000..11cf7ab --- /dev/null +++ b/Libraries/Sources/DataModels/Defines/Typealiases.swift @@ -0,0 +1,9 @@ +// +// Typealiases.swift +// DataModels +// +// Created by Javier Cicchelli on 12/12/2022. +// Copyright © 2022 Röck+Cöde. All rights reserved. +// + +public typealias ActionClosure = () -> Void From 3835dab788f6b4b99b6d7f3e1a21cbdaeb729add Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 12 Dec 2022 01:13:50 +0100 Subject: [PATCH 04/12] Added the "createFolder", "uploadFile" and "showProfile" closures to the BrowseToolbar toolbar. --- Modules/Package.swift | 4 +++ .../Browse/UI/Toolbars/BrowseToolbar.swift | 19 +++++++++--- .../Sources/Browse/UI/Views/BrowseView.swift | 31 +++++++++++++++++-- 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/Modules/Package.swift b/Modules/Package.swift index 5e4826e..4f7f896 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -31,6 +31,10 @@ let package = Package( ), .target( name: "Browse", + dependencies: [ + "Cores", + "Libraries" + ], resources: [.process("Resources")] ), .target( diff --git a/Modules/Sources/Browse/UI/Toolbars/BrowseToolbar.swift b/Modules/Sources/Browse/UI/Toolbars/BrowseToolbar.swift index 746a3ba..a76540e 100644 --- a/Modules/Sources/Browse/UI/Toolbars/BrowseToolbar.swift +++ b/Modules/Sources/Browse/UI/Toolbars/BrowseToolbar.swift @@ -1,19 +1,29 @@ // // BrowseToolbar.swift -// BeReal +// Browse // // Created by Javier Cicchelli on 03/12/2022. // Copyright © 2022 Röck+Cöde. All rights reserved. // +import DataModels import SwiftUI struct BrowseToolbar: ToolbarContent { + + // MARK: Properties + + let createFolder: ActionClosure + let uploadFile: ActionClosure + let showProfile: ActionClosure + + // MARK: Body + var body: some ToolbarContent { ToolbarItem(placement: .primaryAction) { Menu { Button { - // TODO: Implement the creation of a new folder. + createFolder() } label: { Label { Text( @@ -27,7 +37,7 @@ struct BrowseToolbar: ToolbarContent { } Button { - // TODO: Implement the upload of a file from the device to the API. + uploadFile() } label: { Label { Text( @@ -55,7 +65,7 @@ struct BrowseToolbar: ToolbarContent { ToolbarItem(placement: .navigationBarTrailing) { Button { - // TODO: Implement the show of the user profile. + showProfile() } label: { Label { Text( @@ -70,6 +80,7 @@ struct BrowseToolbar: ToolbarContent { } } } + } // MARK: - Image+Constants diff --git a/Modules/Sources/Browse/UI/Views/BrowseView.swift b/Modules/Sources/Browse/UI/Views/BrowseView.swift index 18fcf98..2ee78a4 100644 --- a/Modules/Sources/Browse/UI/Views/BrowseView.swift +++ b/Modules/Sources/Browse/UI/Views/BrowseView.swift @@ -6,13 +6,28 @@ // Copyright © 2022 Röck+Cöde. All rights reserved. // +import DataModels import SwiftUI public struct BrowseView: View { + // MARK: Properties + + private let createFolder: ActionClosure + private let uploadFile: ActionClosure + private let showProfile: ActionClosure + // MARK: Initialisers - public init() {} + public init( + createFolder: @escaping ActionClosure, + uploadFile: @escaping ActionClosure, + showProfile: @escaping ActionClosure + ) { + self.createFolder = createFolder + self.uploadFile = uploadFile + self.showProfile = showProfile + } // MARK: Body @@ -106,7 +121,11 @@ public struct BrowseView: View { .background(Color.red) .navigationTitle("Folder name") .toolbar { - BrowseToolbar() + BrowseToolbar( + createFolder: createFolder, + uploadFile: uploadFile, + showProfile: showProfile + ) } } } @@ -123,7 +142,13 @@ private extension Image { struct BrowseView_Previews: PreviewProvider { static var previews: some View { NavigationView { - BrowseView() + BrowseView { + // ... + } uploadFile: { + // ... + } showProfile: { + // ... + } } } } From 5eb6164836e3b4f31b6d7708efdb56a3545bb1e5 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 12 Dec 2022 01:21:57 +0100 Subject: [PATCH 05/12] Defined the SheetView enumeration in the BeReal app target. --- BeReal.xcodeproj/project.pbxproj | 20 ++++++++++++++++++++ BeReal/UI/Enumerations/SheetView.swift | 20 ++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 BeReal/UI/Enumerations/SheetView.swift diff --git a/BeReal.xcodeproj/project.pbxproj b/BeReal.xcodeproj/project.pbxproj index b46752b..e849508 100644 --- a/BeReal.xcodeproj/project.pbxproj +++ b/BeReal.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 02659B192946AA6900C3AD63 /* SheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02659B182946AA6900C3AD63 /* SheetView.swift */; }; 026D9825293B6374009FE888 /* Libraries in Frameworks */ = {isa = PBXBuildFile; productRef = 026D9824293B6374009FE888 /* Libraries */; }; 02AE64EF29363DBF005A4AF3 /* BeRealApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02AE64EE29363DBF005A4AF3 /* BeRealApp.swift */; }; 02AE64F129363DBF005A4AF3 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02AE64F029363DBF005A4AF3 /* ContentView.swift */; }; @@ -37,6 +38,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 02659B182946AA6900C3AD63 /* SheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetView.swift; sourceTree = ""; }; 026D9823293B6365009FE888 /* Libraries */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Libraries; sourceTree = ""; }; 02784F03293A8331005F839D /* Modules */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Modules; sourceTree = ""; }; 02AE64EB29363DBF005A4AF3 /* BeReal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BeReal.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -80,6 +82,22 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 02659B152946AA2700C3AD63 /* UI */ = { + isa = PBXGroup; + children = ( + 02659B162946AA2E00C3AD63 /* Enumerations */, + ); + path = UI; + sourceTree = ""; + }; + 02659B162946AA2E00C3AD63 /* Enumerations */ = { + isa = PBXGroup; + children = ( + 02659B182946AA6900C3AD63 /* SheetView.swift */, + ); + path = Enumerations; + sourceTree = ""; + }; 02AE64E229363DBF005A4AF3 = { isa = PBXGroup; children = ( @@ -110,6 +128,7 @@ 02AE64EE29363DBF005A4AF3 /* BeRealApp.swift */, 02AE64F029363DBF005A4AF3 /* ContentView.swift */, 02AE64F229363DC1005A4AF3 /* Assets.xcassets */, + 02659B152946AA2700C3AD63 /* UI */, 02AE64F429363DC1005A4AF3 /* Preview Content */, ); path = BeReal; @@ -284,6 +303,7 @@ buildActionMask = 2147483647; files = ( 02AE64F129363DBF005A4AF3 /* ContentView.swift in Sources */, + 02659B192946AA6900C3AD63 /* SheetView.swift in Sources */, 02AE64EF29363DBF005A4AF3 /* BeRealApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/BeReal/UI/Enumerations/SheetView.swift b/BeReal/UI/Enumerations/SheetView.swift new file mode 100644 index 0000000..ea67624 --- /dev/null +++ b/BeReal/UI/Enumerations/SheetView.swift @@ -0,0 +1,20 @@ +// +// SheetView.swift +// BeReal +// +// Created by Javier Cicchelli on 12/12/2022. +// Copyright © 2022 Röck+Cöde. All rights reserved. +// + +// MARK: - Enumerations + +enum SheetView: Int { + case login + case profile +} + +// MARK: - Identifiable + +extension SheetView: Identifiable { + var id: Int { rawValue } +} From 5cf978df69f83fea5252f0a720a05a65d296da81 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 12 Dec 2022 01:23:52 +0100 Subject: [PATCH 06/12] Implemented the showing of the Login and the Profile views from the ContentView view in the BeReal app target. --- BeReal.xcodeproj/project.pbxproj | 10 +++- BeReal/ContentView.swift | 49 ------------------- BeReal/UI/Views/ContentView.swift | 78 +++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 50 deletions(-) delete mode 100644 BeReal/ContentView.swift create mode 100644 BeReal/UI/Views/ContentView.swift diff --git a/BeReal.xcodeproj/project.pbxproj b/BeReal.xcodeproj/project.pbxproj index e849508..f0ad43e 100644 --- a/BeReal.xcodeproj/project.pbxproj +++ b/BeReal.xcodeproj/project.pbxproj @@ -86,6 +86,7 @@ isa = PBXGroup; children = ( 02659B162946AA2E00C3AD63 /* Enumerations */, + 02659B172946AA4400C3AD63 /* Views */, ); path = UI; sourceTree = ""; @@ -98,6 +99,14 @@ path = Enumerations; sourceTree = ""; }; + 02659B172946AA4400C3AD63 /* Views */ = { + isa = PBXGroup; + children = ( + 02AE64F029363DBF005A4AF3 /* ContentView.swift */, + ); + path = Views; + sourceTree = ""; + }; 02AE64E229363DBF005A4AF3 = { isa = PBXGroup; children = ( @@ -126,7 +135,6 @@ isa = PBXGroup; children = ( 02AE64EE29363DBF005A4AF3 /* BeRealApp.swift */, - 02AE64F029363DBF005A4AF3 /* ContentView.swift */, 02AE64F229363DC1005A4AF3 /* Assets.xcassets */, 02659B152946AA2700C3AD63 /* UI */, 02AE64F429363DC1005A4AF3 /* Preview Content */, diff --git a/BeReal/ContentView.swift b/BeReal/ContentView.swift deleted file mode 100644 index 2c544e1..0000000 --- a/BeReal/ContentView.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// ContentView.swift -// BeReal -// -// Created by Javier Cicchelli on 29/11/2022. -// Copyright © 2022 Röck+Cöde. All rights reserved. -// - -import Browse -import DataModels -import Login -import KeychainStorage -import Profile -import SwiftUI - -struct ContentView: View { - - // MARK: Storages - - @KeychainStorage(key: .KeychainStorage.account) private var account: Account? - - // MARK: Body - - var body: some View { - NavigationView { - BrowseView() - } - .sheet(isPresented: showLogin) { - LoginView() - } - } - -} - -// MARK: - Helpers - -private extension ContentView { - var showLogin: Binding { - .init { account == nil } set: { _ in } - } -} - -// MARK: - Previews - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} diff --git a/BeReal/UI/Views/ContentView.swift b/BeReal/UI/Views/ContentView.swift new file mode 100644 index 0000000..d8bbabd --- /dev/null +++ b/BeReal/UI/Views/ContentView.swift @@ -0,0 +1,78 @@ +// +// ContentView.swift +// BeReal +// +// Created by Javier Cicchelli on 29/11/2022. +// Copyright © 2022 Röck+Cöde. All rights reserved. +// + +import Browse +import DataModels +import Login +import KeychainStorage +import Profile +import SwiftUI + +struct ContentView: View { + + // MARK: Storages + + @KeychainStorage(key: .KeychainStorage.account) private var account: Account? + + // MARK: States + + @State private var user: User? + @State private var showSheet: SheetView? + + // MARK: Body + + var body: some View { + NavigationView { + BrowseView { + // ... + } uploadFile: { + // ... + } showProfile: { + showSheet = .profile + } + } + .onAppear { + shouldShowLogin() + } + .onChange(of: account) { _ in + shouldShowLogin() + } + .sheet(item: $showSheet) { sheet in + switch sheet { + case .login: + LoginView { + account = $0 + user = $1 + } + case .profile: + ProfileView { + account = nil + } + } + } + } + +} + +// MARK: - Helpers + +private extension ContentView { + func shouldShowLogin() { + showSheet = account == nil + ? .login + : nil + } +} + +// MARK: - Previews + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} From 1ef3ec6d8798a6f6ae2be7a040e91c6809a53a5f Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 12 Dec 2022 01:26:16 +0100 Subject: [PATCH 07/12] Added the Cores and Libraries packages to the list of dependencies for the Profile module. --- Modules/Package.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Modules/Package.swift b/Modules/Package.swift index 4f7f896..a32ed73 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -39,6 +39,10 @@ let package = Package( ), .target( name: "Profile", + dependencies: [ + "Cores", + "Libraries" + ], resources: [.process("Resources")] ) ] From 71871c6ae024d4e2137423fe5cc0ff8b7504375a Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 12 Dec 2022 01:30:49 +0100 Subject: [PATCH 08/12] Renamed the LoginSuccessClosure closure as AuthenticatedClosure. --- .../Login/Logic/Defines/Typealiases.swift | 2 +- .../Login/Logic/Use Cases/GetUserUseCase.swift | 4 ++-- Modules/Sources/Login/UI/Views/LoginView.swift | 16 +++++++++------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Modules/Sources/Login/Logic/Defines/Typealiases.swift b/Modules/Sources/Login/Logic/Defines/Typealiases.swift index cdc0382..b65b046 100644 --- a/Modules/Sources/Login/Logic/Defines/Typealiases.swift +++ b/Modules/Sources/Login/Logic/Defines/Typealiases.swift @@ -8,4 +8,4 @@ import DataModels -public typealias LoginSuccessClosure = (Account, User) -> Void +public typealias AuthenticatedClosure = (Account, User) -> Void diff --git a/Modules/Sources/Login/Logic/Use Cases/GetUserUseCase.swift b/Modules/Sources/Login/Logic/Use Cases/GetUserUseCase.swift index eefac37..f49fa92 100644 --- a/Modules/Sources/Login/Logic/Use Cases/GetUserUseCase.swift +++ b/Modules/Sources/Login/Logic/Use Cases/GetUserUseCase.swift @@ -18,7 +18,7 @@ struct GetUserUseCase { // MARK: Properties - let success: LoginSuccessClosure + let authenticated: AuthenticatedClosure // MARK: Functions @@ -33,7 +33,7 @@ struct GetUserUseCase { ) ) - success( + authenticated( .init( username: username, password: password diff --git a/Modules/Sources/Login/UI/Views/LoginView.swift b/Modules/Sources/Login/UI/Views/LoginView.swift index 619229b..6dbb018 100644 --- a/Modules/Sources/Login/UI/Views/LoginView.swift +++ b/Modules/Sources/Login/UI/Views/LoginView.swift @@ -17,12 +17,12 @@ public struct LoginView: View { // MARK: Properties - private let success: LoginSuccessClosure + private let authenticated: AuthenticatedClosure // MARK: Initialisers - public init(success: @escaping LoginSuccessClosure) { - self.success = success + public init(authenticated: @escaping AuthenticatedClosure) { + self.authenticated = authenticated } // MARK: Body @@ -32,7 +32,7 @@ public struct LoginView: View { .vertical, showsIndicators: false ) { - LoginContainer(success: success) + LoginContainer(authenticated: authenticated) .padding(.horizontal, 24) .padding(.top, containerTopPadding) } @@ -64,8 +64,8 @@ fileprivate extension LoginView { // MARK: Initialisers - init(success: @escaping LoginSuccessClosure) { - self.getUser = .init(success: success) + init(authenticated: @escaping AuthenticatedClosure) { + self.getUser = .init(authenticated: authenticated) } // MARK: Body @@ -157,6 +157,8 @@ private extension LoginView.LoginContainer { struct LoginView_Previews: PreviewProvider { static var previews: some View { - LoginView { _, _ in } + LoginView { _, _ in + // closure for authenticated action. + } } } From d7ec5aad474dff34ac3b96cfbe5cb0d8ace6a8d5 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 12 Dec 2022 02:48:36 +0100 Subject: [PATCH 09/12] Implemented the StringAdapter and the DateAdapter adapters for the Profile module. --- .../Profile/Logic/Adapters/DateAdapter.swift | 41 +++++++++++++++++++ .../Logic/Adapters/StringAdapter.swift | 13 ++++++ .../Logic/Extensions/String+Constants.swift | 13 ++++++ 3 files changed, 67 insertions(+) create mode 100644 Modules/Sources/Profile/Logic/Adapters/DateAdapter.swift create mode 100644 Modules/Sources/Profile/Logic/Adapters/StringAdapter.swift create mode 100644 Modules/Sources/Profile/Logic/Extensions/String+Constants.swift diff --git a/Modules/Sources/Profile/Logic/Adapters/DateAdapter.swift b/Modules/Sources/Profile/Logic/Adapters/DateAdapter.swift new file mode 100644 index 0000000..441030a --- /dev/null +++ b/Modules/Sources/Profile/Logic/Adapters/DateAdapter.swift @@ -0,0 +1,41 @@ +// +// DateAdapter.swift +// Profile +// +// Created by Javier Cicchelli on 12/12/2022. +// Copyright © 2022 Röck+Cöde. All rights reserved. +// + +import Foundation + +struct DateAdapter { + + // MARK: Properties + + private let dateFormatter: DateFormatter = .dateTimeFormatter + + // MARK: Functions + + func callAsFunction(value: Date?) -> String { + if let value { + return dateFormatter.string(from: value) + } else { + return .Constants.noValue + } + } + +} + +// MARK: - DateFormatter+Formats + +private extension DateFormatter { + static let dateTimeFormatter = { + let formatter = DateFormatter() + + formatter.dateStyle = .long + formatter.timeStyle = .short + formatter.locale = .current + + return formatter + }() +} diff --git a/Modules/Sources/Profile/Logic/Adapters/StringAdapter.swift b/Modules/Sources/Profile/Logic/Adapters/StringAdapter.swift new file mode 100644 index 0000000..4cee887 --- /dev/null +++ b/Modules/Sources/Profile/Logic/Adapters/StringAdapter.swift @@ -0,0 +1,13 @@ +// +// StringAdapter.swift +// Profile +// +// Created by Javier Cicchelli on 12/12/2022. +// Copyright © 2022 Röck+Cöde. All rights reserved. +// + +struct StringAdapter { + func callAsFunction(value: String?) -> String { + value ?? .Constants.noValue + } +} diff --git a/Modules/Sources/Profile/Logic/Extensions/String+Constants.swift b/Modules/Sources/Profile/Logic/Extensions/String+Constants.swift new file mode 100644 index 0000000..ba0dca4 --- /dev/null +++ b/Modules/Sources/Profile/Logic/Extensions/String+Constants.swift @@ -0,0 +1,13 @@ +// +// String+Constants.swift +// Profile +// +// Created by Javier Cicchelli on 12/12/2022. +// Copyright © 2022 Röck+Cöde. All rights reserved. +// + +extension String { + enum Constants { + static let noValue = "-" + } +} From 8b8f0d9f1f751ae583e0a49e0fe244be7d3f9ea0 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 12 Dec 2022 02:50:32 +0100 Subject: [PATCH 10/12] Implemented the ProfileSection UI component for the Profile module. --- .../UI/Components/ProfileSection.swift | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 Modules/Sources/Profile/UI/Components/ProfileSection.swift diff --git a/Modules/Sources/Profile/UI/Components/ProfileSection.swift b/Modules/Sources/Profile/UI/Components/ProfileSection.swift new file mode 100644 index 0000000..1246c44 --- /dev/null +++ b/Modules/Sources/Profile/UI/Components/ProfileSection.swift @@ -0,0 +1,75 @@ +// +// ProfileSection.swift +// Profile +// +// Created by Javier Cicchelli on 12/12/2022. +// + +import SwiftUI + +struct ProfileSection: View { + + // MARK: Properties + + let header: LocalizedStringKey + let items: [Item] + + // MARK: Body + + var body: some View { + Section { + ForEach(items) { item in + Label { + Text(item.value) + } icon: { + Text( + item.key, + bundle: .module + ) + } + .labelStyle(.nameAndValue) + } + } header: { + Text( + header, + bundle: .module + ) + } + } + +} + +// MARK: - Structs + +extension ProfileSection { + struct Item { + let key: LocalizedStringKey + let value: String + } +} + +// MARK: - Identifiable + +extension ProfileSection.Item: Identifiable { + var id: String { UUID().uuidString } +} + +// MARK: - Previews + +struct ProfileSection_Previews: PreviewProvider { + static var previews: some View { + ProfileSection( + header: "some-localised-header-key", + items: [ + .init( + key: "some-localized-key", + value: "some value" + ), + .init( + key: "some-other-localised-key", + value: "some other value" + ) + ] + ) + } +} From c1f790edca90533eea47725cb8df3966a8a10a71 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 12 Dec 2022 02:52:25 +0100 Subject: [PATCH 11/12] Improved the ProfileView view for the Profile module with the integration of the StringAdapter and the DataAdapter adapter and the ProfileSection component. --- .../Profile/UI/Views/ProfileView.swift | 143 +++++++----------- 1 file changed, 56 insertions(+), 87 deletions(-) diff --git a/Modules/Sources/Profile/UI/Views/ProfileView.swift b/Modules/Sources/Profile/UI/Views/ProfileView.swift index 3794647..0633db2 100644 --- a/Modules/Sources/Profile/UI/Views/ProfileView.swift +++ b/Modules/Sources/Profile/UI/Views/ProfileView.swift @@ -6,18 +6,27 @@ // Copyright © 2022 Röck+Cöde. All rights reserved. // +import DataModels import SwiftUI public struct ProfileView: View { // MARK: Properties - private let logOut: () -> Void + private let user: User? + private let logout: ActionClosure + + private let stringAdapter = StringAdapter() + private let dateAdapter = DateAdapter() // MARK: Initialisers - public init(logOut: @escaping () -> Void) { - self.logOut = logOut + public init( + user: User?, + logout: @escaping ActionClosure + ) { + self.user = user + self.logout = logout } // MARK: Body @@ -33,96 +42,45 @@ public struct ProfileView: View { } .listRowBackground(Color.clear) - Section { - Label { - Text("Javier") - } icon: { - Text( - "profile.sections.names.label.first_name.text", - bundle: .module, - comment: "First name label text." + ProfileSection( + header: "profile.sections.names.header.text", + items: [ + .init( + key: "profile.sections.names.label.first_name.text", + value: stringAdapter(value: user?.profile.firstName) + ), + .init( + key: "profile.sections.names.label.last_name.text", + value: stringAdapter(value: user?.profile.lastName) ) - } - .labelStyle(.nameAndValue) - - Label { - Text("Cicchelli") - } icon: { - Text( - "profile.sections.names.label.last_name.text", - bundle: .module, - comment: "Last name label text." + ] + ) + + ProfileSection( + header: "profile.sections.root_info.header.text", + items: [ + .init( + key: "profile.sections.root_info.label.identifier.text", + value: stringAdapter(value: user?.rootFolder.id) + ), + .init( + key: "profile.sections.root_info.label.name.text", + value: stringAdapter(value: user?.rootFolder.name) + ), + .init( + key: "profile.sections.root_info.label.last_modified.text", + value: dateAdapter(value: user?.rootFolder.lastModifiedAt) ) - } - .labelStyle(.nameAndValue) - } header: { - Text( - "profile.sections.names.header.text", - bundle: .module, - comment: "Names section header text." - ) - } + ] + ) - Section { - Label { - Text("71207ee4c0573fde80b03643caafe62731406404") - } icon: { - Text( - "profile.sections.root_info.label.identifier.text", - bundle: .module, - comment: "Identifier label text." - ) - } - .labelStyle(.nameAndValue) - - Label { - Text("Yes") - } icon: { - Text( - "profile.sections.root_info.label.is_directory.text", - bundle: .module, - comment: "Is directory label text." - ) - } - .labelStyle(.nameAndValue) - - Label { - Text("3 days ago") - } icon: { - Text( - "profile.sections.root_info.label.last_modified.text", - bundle: .module, - comment: "Last modified label text." - ) - } - .labelStyle(.nameAndValue) - - Label { - Text("My files") - } icon: { - Text( - "profile.sections.root_info.label.name.text", - bundle: .module, - comment: "Root name label text." - ) - } - .labelStyle(.nameAndValue) - } header: { - Text( - "profile.sections.root_info.header.text", - bundle: .module, - comment: "Root item information header text." - ) - } - Section { Button { - logOut() + logout() } label: { Text( "profile.button.log_out.text", - bundle: .module, - comment: "Log out button text." + bundle: .module ) .fontWeight(.semibold) .foregroundColor(.primary) @@ -137,6 +95,7 @@ public struct ProfileView: View { } .background(Color.red) } + } // MARK: - Images+Constants @@ -149,8 +108,18 @@ private extension Image { struct ProfileView_Previews: PreviewProvider { static var previews: some View { - ProfileView { - // closure for log out action. + ProfileView(user: .init( + profile: .init( + firstName: "Some first name...", + lastName: "Some last name..." + ), + rootFolder: .init( + id: "1234567890", + name: "Some folder name...", + lastModifiedAt: .now + ) + )) { + // closure for logout action. } } } From 3a7efb2227b7d4d5814dc28e0e0cef1a269a93a1 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Mon, 12 Dec 2022 03:21:35 +0100 Subject: [PATCH 12/12] Improved how the "user" and "account" properties are assigned and cleared in the ContentView view of the BeReal app target. --- BeReal/UI/Views/ContentView.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/BeReal/UI/Views/ContentView.swift b/BeReal/UI/Views/ContentView.swift index d8bbabd..aa429f0 100644 --- a/BeReal/UI/Views/ContentView.swift +++ b/BeReal/UI/Views/ContentView.swift @@ -46,11 +46,12 @@ struct ContentView: View { switch sheet { case .login: LoginView { - account = $0 user = $1 + account = $0 } case .profile: - ProfileView { + ProfileView(user: user) { + user = nil account = nil } }