From 6c2d1dcdb65808622f027393501b557ef7490c35 Mon Sep 17 00:00:00 2001 From: Ivan Stepanok Date: Thu, 31 Aug 2023 10:20:26 +0300 Subject: [PATCH 1/3] Separate AppStorage to protocol oriented architecture. --- .../Presentation/Base/FieldsView.swift | 1 + Core/Core.xcodeproj/project.pbxproj | 8 +-- Core/Core/Data/CoreStorage.swift | 17 +++++ .../Core/Data/Repository/AuthRepository.swift | 4 +- Core/Core/Network/DownloadManager.swift | 4 +- Core/Core/Network/RequestInterceptor.swift | 14 ++--- Course/Course/Data/CourseRepository.swift | 4 +- .../Dashboard/Data/DashboardRepository.swift | 8 +-- .../Discovery/Data/DiscoveryRepository.swift | 4 +- .../Data/Model/Data_CreatedComment.swift | 4 +- .../Data/Network/DiscussionRepository.swift | 4 +- .../Base/BaseResponsesViewModel.swift | 6 +- .../Comments/Responses/ResponsesView.swift | 1 - .../Responses/ResponsesViewModel.swift | 3 +- .../Comments/Thread/ThreadView.swift | 63 ++++++++----------- .../Comments/Thread/ThreadViewModel.swift | 3 +- .../Base/BaseResponsesViewModelTests.swift | 28 ++++----- .../Comment/ThreadViewModelTests.swift | 8 --- .../Responses/ResponsesViewModelTests.swift | 7 --- OpenEdX.xcodeproj/project.pbxproj | 4 ++ OpenEdX/AppDelegate.swift | 2 +- OpenEdX/DI/AppAssembly.swift | 10 ++- OpenEdX/DI/NetworkAssembly.swift | 2 +- OpenEdX/DI/ScreenAssembly.swift | 14 ++--- {Core/Core => OpenEdX}/Data/AppStorage.swift | 20 +++--- OpenEdX/Data/DatabaseManager.swift | 23 ------- OpenEdX/RouteController.swift | 4 +- OpenEdX/Router.swift | 2 +- Profile/Data/ProfileStorage.swift | 22 +++++++ Profile/Profile.xcodeproj/project.pbxproj | 12 ++++ Profile/Profile/Data/ProfileRepository.swift | 26 ++++---- 31 files changed, 167 insertions(+), 165 deletions(-) create mode 100644 Core/Core/Data/CoreStorage.swift rename {Core/Core => OpenEdX}/Data/AppStorage.swift (93%) create mode 100644 Profile/Data/ProfileStorage.swift diff --git a/Authorization/Authorization/Presentation/Base/FieldsView.swift b/Authorization/Authorization/Presentation/Base/FieldsView.swift index c021b565c..1a71a2483 100644 --- a/Authorization/Authorization/Presentation/Base/FieldsView.swift +++ b/Authorization/Authorization/Presentation/Base/FieldsView.swift @@ -63,6 +63,7 @@ struct FieldsView: View { type: .discovery, fontSize: 90, screenWidth: proxy.size.width) ) + .id(UUID()) .padding(.horizontal, -6) case .unknown: diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 11e7938c4..680eb2cf4 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -99,7 +99,7 @@ 07460FE3294B72D700F70538 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07460FE2294B72D700F70538 /* Notification.swift */; }; 076F297F2A1F80C800967E7D /* Pagination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 076F297E2A1F80C800967E7D /* Pagination.swift */; }; 0770DE1928D0847D006D8A5D /* BaseRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE1828D0847D006D8A5D /* BaseRouter.swift */; }; - 0770DE2528D08FBA006D8A5D /* AppStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE2428D08FBA006D8A5D /* AppStorage.swift */; }; + 0770DE2528D08FBA006D8A5D /* CoreStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE2428D08FBA006D8A5D /* CoreStorage.swift */; }; 0770DE2A28D0929E006D8A5D /* HTTPTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE2928D0929E006D8A5D /* HTTPTask.swift */; }; 0770DE2C28D092B3006D8A5D /* NetworkLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE2B28D092B3006D8A5D /* NetworkLogger.swift */; }; 0770DE2E28D09743006D8A5D /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE2D28D09743006D8A5D /* API.swift */; }; @@ -221,7 +221,7 @@ 076F297E2A1F80C800967E7D /* Pagination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pagination.swift; sourceTree = ""; }; 0770DE0828D07831006D8A5D /* Core.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Core.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0770DE1828D0847D006D8A5D /* BaseRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseRouter.swift; sourceTree = ""; }; - 0770DE2428D08FBA006D8A5D /* AppStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStorage.swift; sourceTree = ""; }; + 0770DE2428D08FBA006D8A5D /* CoreStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreStorage.swift; sourceTree = ""; }; 0770DE2928D0929E006D8A5D /* HTTPTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPTask.swift; sourceTree = ""; }; 0770DE2B28D092B3006D8A5D /* NetworkLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkLogger.swift; sourceTree = ""; }; 0770DE2D28D09743006D8A5D /* API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = API.swift; sourceTree = ""; }; @@ -381,7 +381,7 @@ 02CF46C92954A42100A698EE /* Persistence */, 0236961728F9A21600EEF206 /* Repository */, 0727877528D2383C002E9142 /* Model */, - 0770DE2428D08FBA006D8A5D /* AppStorage.swift */, + 0770DE2428D08FBA006D8A5D /* CoreStorage.swift */, 02512FEF299533DE0024D438 /* CoreDataHandlerProtocol.swift */, ); path = Data; @@ -793,7 +793,7 @@ 021D925728DCF12900ACC565 /* AlertView.swift in Sources */, 027BD3A82909474200392132 /* KeyboardAvoidingViewController.swift in Sources */, 0770DE7B28D0C78C006D8A5D /* Theme.swift in Sources */, - 0770DE2528D08FBA006D8A5D /* AppStorage.swift in Sources */, + 0770DE2528D08FBA006D8A5D /* CoreStorage.swift in Sources */, 020306CC2932C0C4000949EA /* PickerView.swift in Sources */, 027BD3C52909707700392132 /* Shake.swift in Sources */, 027BD39C2908810C00392132 /* RegisterUser.swift in Sources */, diff --git a/Core/Core/Data/CoreStorage.swift b/Core/Core/Data/CoreStorage.swift new file mode 100644 index 000000000..4ff71e963 --- /dev/null +++ b/Core/Core/Data/CoreStorage.swift @@ -0,0 +1,17 @@ +// +// CoreStorage.swift +// Core +// +// Created by Vladimir Chekyrta on 13.09.2022. +// + +import Foundation + +public protocol CoreStorage { + var accessToken: String? {get set} + var refreshToken: String? {get set} + var cookiesDate: String? {get set} + var user: DataLayer.User? {get set} + var userSettings: UserSettings? {get set} + func clear() +} diff --git a/Core/Core/Data/Repository/AuthRepository.swift b/Core/Core/Data/Repository/AuthRepository.swift index e945c8ea4..1ed62fd68 100644 --- a/Core/Core/Data/Repository/AuthRepository.swift +++ b/Core/Core/Data/Repository/AuthRepository.swift @@ -19,10 +19,10 @@ public protocol AuthRepositoryProtocol { public class AuthRepository: AuthRepositoryProtocol { private let api: API - private let appStorage: AppStorage + private var appStorage: CoreStorage private let config: Config - public init(api: API, appStorage: AppStorage, config: Config) { + public init(api: API, appStorage: CoreStorage, config: Config) { self.api = api self.appStorage = appStorage self.config = config diff --git a/Core/Core/Network/DownloadManager.swift b/Core/Core/Network/DownloadManager.swift index 33a455cdb..3b5378b9e 100644 --- a/Core/Core/Network/DownloadManager.swift +++ b/Core/Core/Network/DownloadManager.swift @@ -71,7 +71,7 @@ public protocol DownloadManagerProtocol { public class DownloadManager: DownloadManagerProtocol { private let persistence: CorePersistenceProtocol - private let appStorage: Core.AppStorage + private let appStorage: CoreStorage private let connectivity: ConnectivityProtocol private var downloadRequest: DownloadRequest? private var currentDownload: DownloadData? @@ -79,7 +79,7 @@ public class DownloadManager: DownloadManagerProtocol { public init( persistence: CorePersistenceProtocol, - appStorage: Core.AppStorage, + appStorage: CoreStorage, connectivity: ConnectivityProtocol ) { self.persistence = persistence diff --git a/Core/Core/Network/RequestInterceptor.swift b/Core/Core/Network/RequestInterceptor.swift index 3f8e80b9a..cecdf0570 100644 --- a/Core/Core/Network/RequestInterceptor.swift +++ b/Core/Core/Network/RequestInterceptor.swift @@ -11,11 +11,11 @@ import Alamofire final public class RequestInterceptor: Alamofire.RequestInterceptor { private let config: Config - private let appStorage: AppStorage + private var storage: CoreStorage - public init(config: Config, appStorage: AppStorage) { + public init(config: Config, storage: CoreStorage) { self.config = config - self.appStorage = appStorage + self.storage = storage } private let lock = NSLock() @@ -34,7 +34,7 @@ final public class RequestInterceptor: Alamofire.RequestInterceptor { var urlRequest = urlRequest // Set the Authorization header value using the access token. - if let token = appStorage.accessToken { + if let token = storage.accessToken { urlRequest.setValue("Bearer " + token, forHTTPHeaderField: "Authorization") } @@ -57,7 +57,7 @@ final public class RequestInterceptor: Alamofire.RequestInterceptor { return completion(.doNotRetry) } - guard let token = appStorage.refreshToken else { + guard let token = storage.refreshToken else { return completion(.doNotRetryWithError(error)) } @@ -117,8 +117,8 @@ final public class RequestInterceptor: Alamofire.RequestInterceptor { refreshToken.count > 0 else { return completion(false) } - self.appStorage.accessToken = accessToken - self.appStorage.refreshToken = refreshToken + self.storage.accessToken = accessToken + self.storage.refreshToken = refreshToken completion(true) } catch { completion(false) diff --git a/Course/Course/Data/CourseRepository.swift b/Course/Course/Data/CourseRepository.swift index 6be4d39e9..28d3418bb 100644 --- a/Course/Course/Data/CourseRepository.swift +++ b/Course/Course/Data/CourseRepository.swift @@ -24,12 +24,12 @@ public protocol CourseRepositoryProtocol { public class CourseRepository: CourseRepositoryProtocol { private let api: API - private let appStorage: AppStorage + private let appStorage: CoreStorage private let config: Config private let persistence: CoursePersistenceProtocol public init(api: API, - appStorage: AppStorage, + appStorage: CoreStorage, config: Config, persistence: CoursePersistenceProtocol) { self.api = api diff --git a/Dashboard/Dashboard/Data/DashboardRepository.swift b/Dashboard/Dashboard/Data/DashboardRepository.swift index 0537afea9..ce5721784 100644 --- a/Dashboard/Dashboard/Data/DashboardRepository.swift +++ b/Dashboard/Dashboard/Data/DashboardRepository.swift @@ -16,20 +16,20 @@ public protocol DashboardRepositoryProtocol { public class DashboardRepository: DashboardRepositoryProtocol { private let api: API - private let appStorage: AppStorage + private let storage: CoreStorage private let config: Config private let persistence: DashboardPersistenceProtocol - public init(api: API, appStorage: AppStorage, config: Config, persistence: DashboardPersistenceProtocol) { + public init(api: API, storage: CoreStorage, config: Config, persistence: DashboardPersistenceProtocol) { self.api = api - self.appStorage = appStorage + self.storage = storage self.config = config self.persistence = persistence } public func getMyCourses(page: Int) async throws -> [CourseItem] { let result = try await api.requestData( - DashboardEndpoint.getMyCourses(username: appStorage.user?.username ?? "", page: page) + DashboardEndpoint.getMyCourses(username: storage.user?.username ?? "", page: page) ) .mapResponse(DataLayer.CourseEnrollments.self) .domain(baseURL: config.baseURL.absoluteString) diff --git a/Discovery/Discovery/Data/DiscoveryRepository.swift b/Discovery/Discovery/Data/DiscoveryRepository.swift index 021293fbf..4ba3fa74b 100644 --- a/Discovery/Discovery/Data/DiscoveryRepository.swift +++ b/Discovery/Discovery/Data/DiscoveryRepository.swift @@ -19,12 +19,12 @@ public protocol DiscoveryRepositoryProtocol { public class DiscoveryRepository: DiscoveryRepositoryProtocol { private let api: API - private let appStorage: AppStorage + private let appStorage: CoreStorage private let config: Config private let persistence: DiscoveryPersistenceProtocol public init(api: API, - appStorage: AppStorage, + appStorage: CoreStorage, config: Config, persistence: DiscoveryPersistenceProtocol) { self.api = api diff --git a/Discussion/Discussion/Data/Model/Data_CreatedComment.swift b/Discussion/Discussion/Data/Model/Data_CreatedComment.swift index e94cadb31..b5ac48d50 100644 --- a/Discussion/Discussion/Data/Model/Data_CreatedComment.swift +++ b/Discussion/Discussion/Data/Model/Data_CreatedComment.swift @@ -61,9 +61,7 @@ public extension DataLayer { public extension DataLayer.CreatedComment { var domain: Post { Post(authorName: author ?? DiscussionLocalization.anonymous, - authorAvatar: profileImage.imageURLSmall?.addingPercentEncoding( - withAllowedCharacters: .urlHostAllowed - ) ?? "", + authorAvatar: profileImage.imageURLSmall ?? "", postDate: Date(iso8601: createdAt), postTitle: "", postBodyHtml: renderedBody, diff --git a/Discussion/Discussion/Data/Network/DiscussionRepository.swift b/Discussion/Discussion/Data/Network/DiscussionRepository.swift index a2bc621ec..18530e784 100644 --- a/Discussion/Discussion/Data/Network/DiscussionRepository.swift +++ b/Discussion/Discussion/Data/Network/DiscussionRepository.swift @@ -36,11 +36,11 @@ public protocol DiscussionRepositoryProtocol { public class DiscussionRepository: DiscussionRepositoryProtocol { private let api: API - private let appStorage: AppStorage + private let appStorage: CoreStorage private let config: Config private let router: DiscussionRouter - public init(api: API, appStorage: AppStorage, config: Config, router: DiscussionRouter) { + public init(api: API, appStorage: CoreStorage, config: Config, router: DiscussionRouter) { self.api = api self.appStorage = appStorage self.config = config diff --git a/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift b/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift index d7b7ce10d..e1b8dca32 100644 --- a/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift +++ b/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift @@ -44,19 +44,16 @@ public class BaseResponsesViewModel { internal let interactor: DiscussionInteractorProtocol internal let router: DiscussionRouter internal let config: Config - internal let storage: Core.AppStorage internal let addPostSubject = CurrentValueSubject(nil) init(interactor: DiscussionInteractorProtocol, router: DiscussionRouter, - config: Config, - storage: Core.AppStorage + config: Config ) { self.interactor = interactor self.router = router self.config = config - self.storage = storage } @MainActor @@ -137,7 +134,6 @@ public class BaseResponsesViewModel { func addNewPost(_ post: Post) { var newPostWithAvatar = post - newPostWithAvatar.authorAvatar = storage.userProfile?.profileImage?.imageURLLarge ?? "" postComments?.comments.append(newPostWithAvatar) itemsCount += 1 } diff --git a/Discussion/Discussion/Presentation/Comments/Responses/ResponsesView.swift b/Discussion/Discussion/Presentation/Comments/Responses/ResponsesView.swift index 5d14e14ef..81e6de563 100644 --- a/Discussion/Discussion/Presentation/Comments/Responses/ResponsesView.swift +++ b/Discussion/Discussion/Presentation/Comments/Responses/ResponsesView.swift @@ -208,7 +208,6 @@ struct ResponsesView_Previews: PreviewProvider { interactor: DiscussionInteractor(repository: DiscussionRepositoryMock()), router: DiscussionRouterMock(), config: ConfigMock(), - storage: .mock, threadStateSubject: .init(nil) ) let post = Post( diff --git a/Discussion/Discussion/Presentation/Comments/Responses/ResponsesViewModel.swift b/Discussion/Discussion/Presentation/Comments/Responses/ResponsesViewModel.swift index 1b8e0acc3..fc2012e6e 100644 --- a/Discussion/Discussion/Presentation/Comments/Responses/ResponsesViewModel.swift +++ b/Discussion/Discussion/Presentation/Comments/Responses/ResponsesViewModel.swift @@ -20,11 +20,10 @@ public class ResponsesViewModel: BaseResponsesViewModel, ObservableObject { interactor: DiscussionInteractorProtocol, router: DiscussionRouter, config: Config, - storage: Core.AppStorage, threadStateSubject: CurrentValueSubject ) { self.threadStateSubject = threadStateSubject - super.init(interactor: interactor, router: router, config: config, storage: storage) + super.init(interactor: interactor, router: router, config: config) } func generateCommentsResponses(comments: [UserComment], parentComment: Post) -> Post? { diff --git a/Discussion/Discussion/Presentation/Comments/Thread/ThreadView.swift b/Discussion/Discussion/Presentation/Comments/Thread/ThreadView.swift index fcbdc1925..f942a4b6c 100644 --- a/Discussion/Discussion/Presentation/Comments/Thread/ThreadView.swift +++ b/Discussion/Discussion/Presentation/Comments/Thread/ThreadView.swift @@ -7,13 +7,11 @@ import SwiftUI import Core -import WebKit -import Kingfisher public struct ThreadView: View { private var title: String - private let thread: UserThread? + private let thread: UserThread private var onBackTapped: (() -> Void) = {} @ObservedObject private var viewModel: ThreadViewModel @@ -27,9 +25,6 @@ public struct ThreadView: View { self.thread = thread self.title = thread.title self.viewModel = viewModel - Task { - await viewModel.getPosts(thread: thread, page: 1) - } } public var body: some View { @@ -40,10 +35,8 @@ public struct ThreadView: View { VStack { ZStack(alignment: .top) { RefreshableScrollViewCompat(action: { - if let thread { - viewModel.comments = [] - _ = await viewModel.getPosts(thread: thread, page: 1) - } + viewModel.comments = [] + _ = await viewModel.getPosts(thread: thread, page: 1) }) { VStack { if let comments = viewModel.postComments { @@ -127,11 +120,9 @@ public struct ThreadView: View { ) }, onFetchMore: { - if let thread { - Task { - await viewModel.fetchMorePosts(thread: thread, - index: index) - } + Task { + await viewModel.fetchMorePosts(thread: thread, + index: index) } } ) @@ -153,23 +144,21 @@ public struct ThreadView: View { viewModel.sendUpdateUnreadState() } } - if let thread { - if !thread.closed { - FlexibleKeyboardInputView( - hint: DiscussionLocalization.Thread.addResponse, - sendText: { commentText in - if let threadID = viewModel.postComments?.threadID { - Task { - await viewModel.postComment( - threadID: threadID, - rawBody: commentText, - parentID: viewModel.postComments?.parentID - ) - } + if !thread.closed { + FlexibleKeyboardInputView( + hint: DiscussionLocalization.Thread.addResponse, + sendText: { commentText in + if let threadID = viewModel.postComments?.threadID { + Task { + await viewModel.postComment( + threadID: threadID, + rawBody: commentText, + parentID: viewModel.postComments?.parentID + ) } } - ) - } + } + ) } } .onReceive(viewModel.addPostSubject, perform: { newComment in @@ -227,6 +216,11 @@ public struct ThreadView: View { .navigationBarHidden(false) .navigationBarBackButtonHidden(false) .navigationTitle(title) + .onFirstAppear { + Task { + await viewModel.getPosts(thread: thread, page: 1) + } + } .onDisappear { onBackTapped() viewModel.sendUpdateUnreadState() @@ -239,11 +233,9 @@ public struct ThreadView: View { } private func reloadPage(onSuccess: @escaping () -> Void) { - if let thread { - Task { - if await viewModel.getPosts(thread: thread, - page: viewModel.nextPage-1) { onSuccess() } - } + Task { + if await viewModel.getPosts(thread: thread, + page: viewModel.nextPage-1) { onSuccess() } } } } @@ -275,7 +267,6 @@ struct CommentsView_Previews: PreviewProvider { let vm = ThreadViewModel(interactor: DiscussionInteractor.mock, router: DiscussionRouterMock(), config: ConfigMock(), - storage: .mock, postStateSubject: .init(nil)) ThreadView(thread: userThread, viewModel: vm) diff --git a/Discussion/Discussion/Presentation/Comments/Thread/ThreadViewModel.swift b/Discussion/Discussion/Presentation/Comments/Thread/ThreadViewModel.swift index 26d82ce7e..bd8edf468 100644 --- a/Discussion/Discussion/Presentation/Comments/Thread/ThreadViewModel.swift +++ b/Discussion/Discussion/Presentation/Comments/Thread/ThreadViewModel.swift @@ -22,11 +22,10 @@ public class ThreadViewModel: BaseResponsesViewModel, ObservableObject { interactor: DiscussionInteractorProtocol, router: DiscussionRouter, config: Config, - storage: Core.AppStorage, postStateSubject: CurrentValueSubject ) { self.postStateSubject = postStateSubject - super.init(interactor: interactor, router: router, config: config, storage: storage) + super.init(interactor: interactor, router: router, config: config) cancellable = threadStateSubject .receive(on: RunLoop.main) diff --git a/Discussion/DiscussionTests/Presentation/Comment/Base/BaseResponsesViewModelTests.swift b/Discussion/DiscussionTests/Presentation/Comment/Base/BaseResponsesViewModelTests.swift index ee4da8ee4..353968d86 100644 --- a/Discussion/DiscussionTests/Presentation/Comment/Base/BaseResponsesViewModelTests.swift +++ b/Discussion/DiscussionTests/Presentation/Comment/Base/BaseResponsesViewModelTests.swift @@ -54,7 +54,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let interactor = DiscussionInteractorProtocolMock() let router = DiscussionRouterMock() let config = ConfigMock() - let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config, storage: .mock) + let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) var result = false viewModel.postComments = post @@ -76,7 +76,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let interactor = DiscussionInteractorProtocolMock() let router = DiscussionRouterMock() let config = ConfigMock() - let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config, storage: .mock) + let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) var result = false viewModel.postComments = post @@ -99,7 +99,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let interactor = DiscussionInteractorProtocolMock() let router = DiscussionRouterMock() let config = ConfigMock() - let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config, storage: .mock) + let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) var result = false viewModel.postComments = post @@ -121,7 +121,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let interactor = DiscussionInteractorProtocolMock() let router = DiscussionRouterMock() let config = ConfigMock() - let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config, storage: .mock) + let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) var result = false viewModel.postComments = post @@ -145,7 +145,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let interactor = DiscussionInteractorProtocolMock() let router = DiscussionRouterMock() let config = ConfigMock() - let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config, storage: .mock) + let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) var result = false let noInternetError = AFError.sessionInvalidated(error: URLError(.notConnectedToInternet)) @@ -166,7 +166,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let interactor = DiscussionInteractorProtocolMock() let router = DiscussionRouterMock() let config = ConfigMock() - let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config, storage: .mock) + let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) var result = false Given(interactor, .voteThread(voted: .any, threadID: .any, willThrow: NSError())) @@ -185,7 +185,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let interactor = DiscussionInteractorProtocolMock() let router = DiscussionRouterMock() let config = ConfigMock() - let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config, storage: .mock) + let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) var result = false viewModel.postComments = post @@ -207,7 +207,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let interactor = DiscussionInteractorProtocolMock() let router = DiscussionRouterMock() let config = ConfigMock() - let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config, storage: .mock) + let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) var result = false viewModel.postComments = post @@ -229,7 +229,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let interactor = DiscussionInteractorProtocolMock() let router = DiscussionRouterMock() let config = ConfigMock() - let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config, storage: .mock) + let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) var result = false let noInternetError = AFError.sessionInvalidated(error: URLError(.notConnectedToInternet)) @@ -250,7 +250,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let interactor = DiscussionInteractorProtocolMock() let router = DiscussionRouterMock() let config = ConfigMock() - let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config, storage: .mock) + let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) var result = false Given(interactor, .flagThread(abuseFlagged: .any, threadID: .any, willThrow: NSError())) @@ -269,7 +269,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let interactor = DiscussionInteractorProtocolMock() let router = DiscussionRouterMock() let config = ConfigMock() - let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config, storage: .mock) + let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) var result = false viewModel.postComments = post @@ -291,7 +291,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let interactor = DiscussionInteractorProtocolMock() let router = DiscussionRouterMock() let config = ConfigMock() - let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config, storage: .mock) + let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) var result = false let noInternetError = AFError.sessionInvalidated(error: URLError(.notConnectedToInternet)) @@ -312,7 +312,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let interactor = DiscussionInteractorProtocolMock() let router = DiscussionRouterMock() let config = ConfigMock() - let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config, storage: .mock) + let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) var result = false Given(interactor, .followThread(following: .any, threadID: .any, willThrow: NSError())) @@ -331,7 +331,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let interactor = DiscussionInteractorProtocolMock() let router = DiscussionRouterMock() let config = ConfigMock() - let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config, storage: .mock) + let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) viewModel.postComments = post diff --git a/Discussion/DiscussionTests/Presentation/Comment/ThreadViewModelTests.swift b/Discussion/DiscussionTests/Presentation/Comment/ThreadViewModelTests.swift index d0909522b..724e99e32 100644 --- a/Discussion/DiscussionTests/Presentation/Comment/ThreadViewModelTests.swift +++ b/Discussion/DiscussionTests/Presentation/Comment/ThreadViewModelTests.swift @@ -212,7 +212,6 @@ final class ThreadViewModelTests: XCTestCase { let viewModel = ThreadViewModel(interactor: interactor, router: router, config: config, - storage: .mock, postStateSubject: .init(.readed(id: "1"))) Given(interactor, .readBody(threadID: .any, willProduce: {_ in})) @@ -242,7 +241,6 @@ final class ThreadViewModelTests: XCTestCase { let viewModel = ThreadViewModel(interactor: interactor, router: router, config: config, - storage: .mock, postStateSubject: .init(.readed(id: "1"))) Given(interactor, .readBody(threadID: .any, willProduce: {_ in})) @@ -272,7 +270,6 @@ final class ThreadViewModelTests: XCTestCase { let viewModel = ThreadViewModel(interactor: interactor, router: router, config: config, - storage: .mock, postStateSubject: .init(.readed(id: "1"))) let noInternetError = AFError.sessionInvalidated(error: URLError(.notConnectedToInternet)) @@ -304,7 +301,6 @@ final class ThreadViewModelTests: XCTestCase { let viewModel = ThreadViewModel(interactor: interactor, router: router, config: config, - storage: .mock, postStateSubject: .init(.readed(id: "1"))) Given(interactor, .readBody(threadID: .any, willThrow: NSError())) @@ -332,7 +328,6 @@ final class ThreadViewModelTests: XCTestCase { let viewModel = ThreadViewModel(interactor: interactor, router: router, config: config, - storage: .mock, postStateSubject: .init(.readed(id: "1"))) let post = Post(authorName: "", @@ -373,7 +368,6 @@ final class ThreadViewModelTests: XCTestCase { let viewModel = ThreadViewModel(interactor: interactor, router: router, config: config, - storage: .mock, postStateSubject: .init(.readed(id: "1"))) let noInternetError = AFError.sessionInvalidated(error: URLError(.notConnectedToInternet)) @@ -398,7 +392,6 @@ final class ThreadViewModelTests: XCTestCase { let viewModel = ThreadViewModel(interactor: interactor, router: router, config: config, - storage: .mock, postStateSubject: .init(.readed(id: "1"))) Given(interactor, .addCommentTo(threadID: .any, rawBody: .any, parentID: .any, willThrow: NSError()) ) @@ -422,7 +415,6 @@ final class ThreadViewModelTests: XCTestCase { let viewModel = ThreadViewModel(interactor: interactor, router: router, config: config, - storage: .mock, postStateSubject: .init(.readed(id: "1"))) viewModel.totalPages = 2 diff --git a/Discussion/DiscussionTests/Presentation/Responses/ResponsesViewModelTests.swift b/Discussion/DiscussionTests/Presentation/Responses/ResponsesViewModelTests.swift index 183174526..997364f10 100644 --- a/Discussion/DiscussionTests/Presentation/Responses/ResponsesViewModelTests.swift +++ b/Discussion/DiscussionTests/Presentation/Responses/ResponsesViewModelTests.swift @@ -108,7 +108,6 @@ final class ResponsesViewModelTests: XCTestCase { let viewModel = ResponsesViewModel(interactor: interactor, router: router, config: config, - storage: .mock, threadStateSubject: .init(.postAdded(id: "1"))) Given(interactor, .getCommentResponses(commentID: .any, page: .any, @@ -136,7 +135,6 @@ final class ResponsesViewModelTests: XCTestCase { let viewModel = ResponsesViewModel(interactor: interactor, router: router, config: config, - storage: .mock, threadStateSubject: .init(.postAdded(id: "1"))) let noInternetError = AFError.sessionInvalidated(error: URLError(.notConnectedToInternet)) @@ -163,7 +161,6 @@ final class ResponsesViewModelTests: XCTestCase { let viewModel = ResponsesViewModel(interactor: interactor, router: router, config: config, - storage: .mock, threadStateSubject: .init(.postAdded(id: "1"))) Given(interactor, .getCommentResponses(commentID: .any, page: .any, willThrow: NSError())) @@ -187,7 +184,6 @@ final class ResponsesViewModelTests: XCTestCase { let viewModel = ResponsesViewModel(interactor: interactor, router: router, config: config, - storage: .mock, threadStateSubject: .init(.postAdded(id: "1"))) Given(interactor, .addCommentTo(threadID: .any, rawBody: .any, parentID: .any, willReturn: post)) @@ -209,7 +205,6 @@ final class ResponsesViewModelTests: XCTestCase { let viewModel = ResponsesViewModel(interactor: interactor, router: router, config: config, - storage: .mock, threadStateSubject: .init(.postAdded(id: "1"))) let noInternetError = AFError.sessionInvalidated(error: URLError(.notConnectedToInternet)) @@ -233,7 +228,6 @@ final class ResponsesViewModelTests: XCTestCase { let viewModel = ResponsesViewModel(interactor: interactor, router: router, config: config, - storage: .mock, threadStateSubject: .init(.postAdded(id: "1"))) Given(interactor, .addCommentTo(threadID: .any, rawBody: .any, parentID: .any, willThrow: NSError())) @@ -255,7 +249,6 @@ final class ResponsesViewModelTests: XCTestCase { let viewModel = ResponsesViewModel(interactor: interactor, router: router, config: config, - storage: .mock, threadStateSubject: .init(.postAdded(id: "1"))) viewModel.totalPages = 2 diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index 430caae7c..d217ea7d3 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 0293A2072A6FCDA30090A336 /* DiscoveryPersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0293A2062A6FCDA30090A336 /* DiscoveryPersistence.swift */; }; 0293A2092A6FCDE50090A336 /* DashboardPersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0293A2082A6FCDE50090A336 /* DashboardPersistence.swift */; }; 0298DF302A4EF7230023A257 /* AnalyticsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0298DF2F2A4EF7230023A257 /* AnalyticsManager.swift */; }; + 02B089462A9F83D200754BD4 /* AppStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B089452A9F83D200754BD4 /* AppStorage.swift */; }; 02ED50D429A6554E008341CD /* сountries.json in Resources */ = {isa = PBXBuildFile; fileRef = 02ED50D629A6554E008341CD /* сountries.json */; }; 02ED50D829A66007008341CD /* languages.json in Resources */ = {isa = PBXBuildFile; fileRef = 02ED50DA29A66007008341CD /* languages.json */; }; 02F175312A4DA95B0019CD70 /* MainScreenAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F175302A4DA95B0019CD70 /* MainScreenAnalytics.swift */; }; @@ -78,6 +79,7 @@ 0293A2062A6FCDA30090A336 /* DiscoveryPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryPersistence.swift; sourceTree = ""; }; 0293A2082A6FCDE50090A336 /* DashboardPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardPersistence.swift; sourceTree = ""; }; 0298DF2F2A4EF7230023A257 /* AnalyticsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsManager.swift; sourceTree = ""; }; + 02B089452A9F83D200754BD4 /* AppStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStorage.swift; sourceTree = ""; }; 02B6B3C428E1E61400232911 /* CourseDetails.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CourseDetails.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 02ED50CA29A64AAA008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; 02ED50D529A6554E008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = uk; path = "uk.lproj/сountries.json"; sourceTree = ""; }; @@ -137,6 +139,7 @@ 0293A2042A6FCD430090A336 /* CoursePersistence.swift */, 0293A2062A6FCDA30090A336 /* DiscoveryPersistence.swift */, 0293A2082A6FCDE50090A336 /* DashboardPersistence.swift */, + 02B089452A9F83D200754BD4 /* AppStorage.swift */, ); path = Data; sourceTree = ""; @@ -371,6 +374,7 @@ buildActionMask = 2147483647; files = ( 0293A2052A6FCD430090A336 /* CoursePersistence.swift in Sources */, + 02B089462A9F83D200754BD4 /* AppStorage.swift in Sources */, 0298DF302A4EF7230023A257 /* AnalyticsManager.swift in Sources */, 0293A2072A6FCDA30090A336 /* DiscoveryPersistence.swift in Sources */, 07D5DA3528D075AA00752FD9 /* AppDelegate.swift in Sources */, diff --git a/OpenEdX/AppDelegate.swift b/OpenEdX/AppDelegate.swift index 6e847a5ed..3c3cad303 100644 --- a/OpenEdX/AppDelegate.swift +++ b/OpenEdX/AppDelegate.swift @@ -90,7 +90,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { lastForceLogoutTime = Date().timeIntervalSince1970 - Container.shared.resolve(AppStorage.self)?.clear() + Container.shared.resolve(CoreStorage.self)?.clear() Container.shared.resolve(DownloadManagerProtocol.self)?.deleteAllFiles() Container.shared.resolve(CoreDataHandlerProtocol.self)?.clear() window?.rootViewController = RouteController() diff --git a/OpenEdX/DI/AppAssembly.swift b/OpenEdX/DI/AppAssembly.swift index 978a5b6b4..edc74bd1f 100644 --- a/OpenEdX/DI/AppAssembly.swift +++ b/OpenEdX/DI/AppAssembly.swift @@ -84,7 +84,7 @@ class AppAssembly: Assembly { container.register(DownloadManagerProtocol.self, factory: { r in DownloadManager(persistence: r.resolve(CorePersistenceProtocol.self)!, - appStorage: r.resolve(AppStorage.self)!, + appStorage: r.resolve(CoreStorage.self)!, connectivity: r.resolve(ConnectivityProtocol.self)!) }).inObjectScope(.container) @@ -135,6 +135,14 @@ class AppAssembly: Assembly { ) }.inObjectScope(.container) + container.register(CoreStorage.self) { r in + r.resolve(AppStorage.self)! + }.inObjectScope(.container) + + container.register(ProfileStorage.self) { r in + r.resolve(AppStorage.self)! + }.inObjectScope(.container) + container.register(Validator.self) { _ in Validator() }.inObjectScope(.container) diff --git a/OpenEdX/DI/NetworkAssembly.swift b/OpenEdX/DI/NetworkAssembly.swift index f609b5bc5..1f036a860 100644 --- a/OpenEdX/DI/NetworkAssembly.swift +++ b/OpenEdX/DI/NetworkAssembly.swift @@ -13,7 +13,7 @@ import Swinject class NetworkAssembly: Assembly { func assemble(container: Container) { container.register(RequestInterceptor.self) { r in - RequestInterceptor(config: r.resolve(Config.self)!, appStorage: r.resolve(AppStorage.self)!) + RequestInterceptor(config: r.resolve(Config.self)!, storage: r.resolve(CoreStorage.self)!) }.inObjectScope(.container) container.register(Alamofire.Session.self) { r in diff --git a/OpenEdX/DI/ScreenAssembly.swift b/OpenEdX/DI/ScreenAssembly.swift index 618945106..1dd9ddddb 100644 --- a/OpenEdX/DI/ScreenAssembly.swift +++ b/OpenEdX/DI/ScreenAssembly.swift @@ -23,7 +23,7 @@ class ScreenAssembly: Assembly { container.register(AuthRepositoryProtocol.self) { r in AuthRepository( api: r.resolve(API.self)!, - appStorage: r.resolve(AppStorage.self)!, + appStorage: r.resolve(CoreStorage.self)!, config: r.resolve(Config.self)! ) } @@ -69,7 +69,7 @@ class ScreenAssembly: Assembly { container.register(DiscoveryRepositoryProtocol.self) { r in DiscoveryRepository( api: r.resolve(API.self)!, - appStorage: r.resolve(AppStorage.self)!, + appStorage: r.resolve(CoreStorage.self)!, config: r.resolve(Config.self)!, persistence: r.resolve(DiscoveryPersistenceProtocol.self)! ) @@ -105,7 +105,7 @@ class ScreenAssembly: Assembly { container.register(DashboardRepositoryProtocol.self) { r in DashboardRepository( api: r.resolve(API.self)!, - appStorage: r.resolve(AppStorage.self)!, + storage: r.resolve(CoreStorage.self)!, config: r.resolve(Config.self)!, persistence: r.resolve(DashboardPersistenceProtocol.self)! ) @@ -128,7 +128,7 @@ class ScreenAssembly: Assembly { container.register(ProfileRepositoryProtocol.self) { r in ProfileRepository( api: r.resolve(API.self)!, - appStorage: r.resolve(AppStorage.self)!, + storage: r.resolve(AppStorage.self)!, coreDataHandler: r.resolve(CoreDataHandlerProtocol.self)!, downloadManager: r.resolve(DownloadManagerProtocol.self)!, config: r.resolve(Config.self)! @@ -181,7 +181,7 @@ class ScreenAssembly: Assembly { container.register(CourseRepositoryProtocol.self) { r in CourseRepository( api: r.resolve(API.self)!, - appStorage: r.resolve(AppStorage.self)!, + appStorage: r.resolve(CoreStorage.self)!, config: r.resolve(Config.self)!, persistence: r.resolve(CoursePersistenceProtocol.self)! ) @@ -302,7 +302,7 @@ class ScreenAssembly: Assembly { container.register(DiscussionRepositoryProtocol.self) { r in DiscussionRepository( api: r.resolve(API.self)!, - appStorage: r.resolve(AppStorage.self)!, + appStorage: r.resolve(CoreStorage.self)!, config: r.resolve(Config.self)!, router: r.resolve(DiscussionRouter.self)! ) @@ -346,7 +346,6 @@ class ScreenAssembly: Assembly { interactor: r.resolve(DiscussionInteractorProtocol.self)!, router: r.resolve(DiscussionRouter.self)!, config: r.resolve(Config.self)!, - storage: r.resolve(AppStorage.self)!, postStateSubject: subject ) } @@ -356,7 +355,6 @@ class ScreenAssembly: Assembly { interactor: r.resolve(DiscussionInteractorProtocol.self)!, router: r.resolve(DiscussionRouter.self)!, config: r.resolve(Config.self)!, - storage: r.resolve(AppStorage.self)!, threadStateSubject: subject ) } diff --git a/Core/Core/Data/AppStorage.swift b/OpenEdX/Data/AppStorage.swift similarity index 93% rename from Core/Core/Data/AppStorage.swift rename to OpenEdX/Data/AppStorage.swift index ee8bccccb..da90b86cc 100644 --- a/Core/Core/Data/AppStorage.swift +++ b/OpenEdX/Data/AppStorage.swift @@ -1,18 +1,21 @@ // // AppStorage.swift -// Core +// OpenEdX // -// Created by Vladimir Chekyrta on 13.09.2022. +// Created by  Stepanok Ivan on 30.08.2023. // import Foundation import KeychainSwift +import Core +import Profile -public class AppStorage { - +public class AppStorage: CoreStorage, + ProfileStorage { + private let keychain: KeychainSwift private let userDefaults: UserDefaults - + public init(keychain: KeychainSwift, userDefaults: UserDefaults) { self.keychain = keychain self.userDefaults = userDefaults @@ -133,10 +136,3 @@ public class AppStorage { private let KEY_USER = "refreshToken" private let KEY_SETTINGS = "userSettings" } - -// Mark - For testing and SwiftUI preview -#if DEBUG -public extension AppStorage { - static let mock: AppStorage = .init(keychain: KeychainSwift(), userDefaults: UserDefaults.standard) -} -#endif diff --git a/OpenEdX/Data/DatabaseManager.swift b/OpenEdX/Data/DatabaseManager.swift index d6280c43c..326f42a10 100644 --- a/OpenEdX/Data/DatabaseManager.swift +++ b/OpenEdX/Data/DatabaseManager.swift @@ -35,29 +35,6 @@ class DatabaseManager: CoreDataHandlerProtocol { self.databaseName = databaseName } - public func saveCourseDetails() { - context.performAndWait { - let newCourseDetails = CDCourseDetails(context: context) - newCourseDetails.courseID = UUID().uuidString - newCourseDetails.org = "course.org" - newCourseDetails.courseTitle = "course.courseTitle" - newCourseDetails.courseDescription = "course.courseDescription" - newCourseDetails.courseStart = Date() - newCourseDetails.courseEnd = Date() - newCourseDetails.enrollmentStart = Date() - newCourseDetails.enrollmentEnd = Date() - newCourseDetails.isEnrolled = false - newCourseDetails.overviewHTML = "course.overviewHTML" - newCourseDetails.courseBannerURL = "course.courseBannerURL" - - do { - try context.save() - } catch { - print("⛔️⛔️⛔️⛔️⛔️", error) - } - } - } - private func createContainer() -> NSPersistentContainer { let model = NSManagedObjectModel.mergedModel(from: bundles)! let container = NSPersistentContainer(name: databaseName, managedObjectModel: model) diff --git a/OpenEdX/RouteController.swift b/OpenEdX/RouteController.swift index 7086e0ec5..1aa036dc1 100644 --- a/OpenEdX/RouteController.swift +++ b/OpenEdX/RouteController.swift @@ -16,8 +16,8 @@ class RouteController: UIViewController { diContainer.resolve(UINavigationController.self)! }() - private lazy var appStorage: Core.AppStorage = { - diContainer.resolve(AppStorage.self)! + private lazy var appStorage: CoreStorage = { + diContainer.resolve(CoreStorage.self)! }() private lazy var analytics: AuthorizationAnalytics = { diff --git a/OpenEdX/Router.swift b/OpenEdX/Router.swift index ba468cebd..9be51948e 100644 --- a/OpenEdX/Router.swift +++ b/OpenEdX/Router.swift @@ -17,7 +17,7 @@ import Discovery import Dashboard import Profile import Combine - + public class Router: AuthorizationRouter, DiscoveryRouter, ProfileRouter, diff --git a/Profile/Data/ProfileStorage.swift b/Profile/Data/ProfileStorage.swift new file mode 100644 index 000000000..2770f6060 --- /dev/null +++ b/Profile/Data/ProfileStorage.swift @@ -0,0 +1,22 @@ +// +// ProfileStorage.swift +// Profile +// +// Created by  Stepanok Ivan on 30.08.2023. +// + +import Foundation +import Core + +public protocol ProfileStorage { + var userProfile: DataLayer.UserProfile? {get set} +} + +#if DEBUG +public class ProfileStorageMock: ProfileStorage { + + public var userProfile: DataLayer.UserProfile? + + public init() {} +} +#endif diff --git a/Profile/Profile.xcodeproj/project.pbxproj b/Profile/Profile.xcodeproj/project.pbxproj index c92a7d9d7..ef60fd5ab 100644 --- a/Profile/Profile.xcodeproj/project.pbxproj +++ b/Profile/Profile.xcodeproj/project.pbxproj @@ -29,6 +29,7 @@ 02A9A91D2978194A00B55797 /* ProfileViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A9A91C2978194A00B55797 /* ProfileViewModelTests.swift */; }; 02A9A91E2978194A00B55797 /* Profile.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 020F834A28DB4CCD0062FA70 /* Profile.framework */; platformFilter = ios; }; 02A9A92B29781A6300B55797 /* ProfileMock.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A9A92A29781A6300B55797 /* ProfileMock.generated.swift */; }; + 02B089432A9F832200754BD4 /* ProfileStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B089422A9F832200754BD4 /* ProfileStorage.swift */; }; 02F175352A4DAD030019CD70 /* ProfileAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F175342A4DAD030019CD70 /* ProfileAnalytics.swift */; }; 02F3BFE7292539850051930C /* ProfileRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F3BFE6292539850051930C /* ProfileRouter.swift */; }; 0796C8C929B7905300444B05 /* ProfileBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0796C8C829B7905300444B05 /* ProfileBottomSheet.swift */; }; @@ -70,6 +71,7 @@ 02A9A91A2978194A00B55797 /* ProfileTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ProfileTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 02A9A91C2978194A00B55797 /* ProfileViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewModelTests.swift; sourceTree = ""; }; 02A9A92A29781A6300B55797 /* ProfileMock.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileMock.generated.swift; sourceTree = ""; }; + 02B089422A9F832200754BD4 /* ProfileStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileStorage.swift; sourceTree = ""; }; 02ED50CE29A64BAD008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; 02F175342A4DAD030019CD70 /* ProfileAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileAnalytics.swift; sourceTree = ""; }; 02F3BFE6292539850051930C /* ProfileRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileRouter.swift; sourceTree = ""; }; @@ -138,6 +140,7 @@ 020F834028DB4CCD0062FA70 = { isa = PBXGroup; children = ( + 02B089412A9F830D00754BD4 /* Data */, 021D925B28DDADBD00ACC565 /* swiftgen.yml */, 020F834C28DB4CCD0062FA70 /* Profile */, 02A9A91B2978194A00B55797 /* ProfileTests */, @@ -276,6 +279,14 @@ path = ProfileTests; sourceTree = ""; }; + 02B089412A9F830D00754BD4 /* Data */ = { + isa = PBXGroup; + children = ( + 02B089422A9F832200754BD4 /* ProfileStorage.swift */, + ); + path = Data; + sourceTree = ""; + }; 0766DFD3299AD9D800EBEF6A /* Presentation */ = { isa = PBXGroup; children = ( @@ -544,6 +555,7 @@ 021D925528DC92F800ACC565 /* ProfileInteractor.swift in Sources */, 029301DA2938948500E99AB8 /* ProfileType.swift in Sources */, 0259104429C39C9E004B5A55 /* SettingsView.swift in Sources */, + 02B089432A9F832200754BD4 /* ProfileStorage.swift in Sources */, 021D925228DC918D00ACC565 /* ProfileViewModel.swift in Sources */, 0248F9B128DDB09D0041327E /* Strings.swift in Sources */, 020306CA2932B14D000949EA /* EditProfileViewModel.swift in Sources */, diff --git a/Profile/Profile/Data/ProfileRepository.swift b/Profile/Profile/Data/ProfileRepository.swift index 1295ff44e..53b324a10 100644 --- a/Profile/Profile/Data/ProfileRepository.swift +++ b/Profile/Profile/Data/ProfileRepository.swift @@ -26,20 +26,20 @@ public protocol ProfileRepositoryProtocol { public class ProfileRepository: ProfileRepositoryProtocol { private let api: API - private let appStorage: AppStorage + private var storage: CoreStorage & ProfileStorage private let downloadManager: DownloadManagerProtocol private let coreDataHandler: CoreDataHandlerProtocol private let config: Config public init( api: API, - appStorage: AppStorage, + storage: CoreStorage & ProfileStorage, coreDataHandler: CoreDataHandlerProtocol, downloadManager: DownloadManagerProtocol, config: Config ) { self.api = api - self.appStorage = appStorage + self.storage = storage self.coreDataHandler = coreDataHandler self.downloadManager = downloadManager self.config = config @@ -48,14 +48,14 @@ public class ProfileRepository: ProfileRepositoryProtocol { public func getMyProfile() async throws -> UserProfile { let user = try await api.requestData( - ProfileEndpoint.getUserProfile(username: appStorage.user?.username ?? "") + ProfileEndpoint.getUserProfile(username: storage.user?.username ?? "") ).mapResponse(DataLayer.UserProfile.self) - appStorage.userProfile = user + storage.userProfile = user return user.domain } public func getMyProfileOffline() throws -> UserProfile { - if let user = appStorage.userProfile { + if let user = storage.userProfile { return user.domain } else { throw NoCachedDataError() @@ -63,11 +63,11 @@ public class ProfileRepository: ProfileRepositoryProtocol { } public func logOut() async throws { - guard let refreshToken = appStorage.refreshToken else { return } + guard let refreshToken = storage.refreshToken else { return } _ = try await api.request( ProfileEndpoint.logOut(refreshToken: refreshToken, clientID: config.oAuthClientId) ) - appStorage.clear() + storage.clear() downloadManager.deleteAllFiles() coreDataHandler.clear() } @@ -111,7 +111,7 @@ public class ProfileRepository: ProfileRepositoryProtocol { public func uploadProfilePicture(pictureData: Data) async throws { let response = try await api.request( - ProfileEndpoint.uploadProfilePicture(username: appStorage.user?.username ?? "", + ProfileEndpoint.uploadProfilePicture(username: storage.user?.username ?? "", pictureData: pictureData)) if response.statusCode != 204 { throw APIError.uploadError @@ -120,13 +120,13 @@ public class ProfileRepository: ProfileRepositoryProtocol { public func deleteProfilePicture() async throws -> Bool { let response = try await api.request( - ProfileEndpoint.deleteProfilePicture(username: appStorage.user?.username ?? "")) + ProfileEndpoint.deleteProfilePicture(username: storage.user?.username ?? "")) return response.statusCode == 204 } public func updateUserProfile(parameters: [String: Any]) async throws -> UserProfile { let response = try await api.requestData( - ProfileEndpoint.updateUserProfile(username: appStorage.user?.username ?? "", + ProfileEndpoint.updateUserProfile(username: storage.user?.username ?? "", parameters: parameters)) .mapResponse(DataLayer.UserProfile.self).domain return response @@ -138,7 +138,7 @@ public class ProfileRepository: ProfileRepositoryProtocol { } public func getSettings() -> UserSettings { - if let userSettings = appStorage.userSettings { + if let userSettings = storage.userSettings { return userSettings } else { return UserSettings(wifiOnly: true, downloadQuality: VideoQuality.auto) @@ -146,7 +146,7 @@ public class ProfileRepository: ProfileRepositoryProtocol { } public func saveSettings(_ settings: UserSettings) { - appStorage.userSettings = settings + storage.userSettings = settings } } From 8ed016e1650770877069be1ac65f7cc5860d7cb0 Mon Sep 17 00:00:00 2001 From: Ivan Stepanok Date: Thu, 31 Aug 2023 13:47:54 +0300 Subject: [PATCH 2/3] Separate AppStorage to protocol oriented architecture --- .../Base/BaseResponsesViewModel.swift | 11 ++++---- .../Comments/Responses/ResponsesView.swift | 1 - .../Base/BaseResponsesViewModelTests.swift | 12 +++++++++ OpenEdX.xcodeproj/project.pbxproj | 8 +++--- OpenEdX/Data/AppStorage.swift | 25 +++++++++---------- Profile/Profile/Data/ProfileRepository.swift | 10 ++++---- 6 files changed, 39 insertions(+), 28 deletions(-) diff --git a/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift b/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift index e1b8dca32..c1da7c39a 100644 --- a/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift +++ b/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift @@ -10,6 +10,7 @@ import SwiftUI import Core import Combine import Swinject +import Profile public class BaseResponsesViewModel { @@ -24,7 +25,7 @@ public class BaseResponsesViewModel { public var totalPages = 1 @Published public var itemsCount = 0 public var fetchInProgress = false - + var errorMessage: String? { didSet { withAnimation { @@ -44,12 +45,12 @@ public class BaseResponsesViewModel { internal let interactor: DiscussionInteractorProtocol internal let router: DiscussionRouter internal let config: Config - internal let addPostSubject = CurrentValueSubject(nil) - init(interactor: DiscussionInteractorProtocol, - router: DiscussionRouter, - config: Config + init( + interactor: DiscussionInteractorProtocol, + router: DiscussionRouter, + config: Config ) { self.interactor = interactor self.router = router diff --git a/Discussion/Discussion/Presentation/Comments/Responses/ResponsesView.swift b/Discussion/Discussion/Presentation/Comments/Responses/ResponsesView.swift index 81e6de563..2be19cf06 100644 --- a/Discussion/Discussion/Presentation/Comments/Responses/ResponsesView.swift +++ b/Discussion/Discussion/Presentation/Comments/Responses/ResponsesView.swift @@ -7,7 +7,6 @@ import SwiftUI import Core -import Kingfisher import Combine public struct ResponsesView: View { diff --git a/Discussion/DiscussionTests/Presentation/Comment/Base/BaseResponsesViewModelTests.swift b/Discussion/DiscussionTests/Presentation/Comment/Base/BaseResponsesViewModelTests.swift index 353968d86..b6940c034 100644 --- a/Discussion/DiscussionTests/Presentation/Comment/Base/BaseResponsesViewModelTests.swift +++ b/Discussion/DiscussionTests/Presentation/Comment/Base/BaseResponsesViewModelTests.swift @@ -77,6 +77,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let router = DiscussionRouterMock() let config = ConfigMock() let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) + var result = false viewModel.postComments = post @@ -100,6 +101,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let router = DiscussionRouterMock() let config = ConfigMock() let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) + var result = false viewModel.postComments = post @@ -122,6 +124,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let router = DiscussionRouterMock() let config = ConfigMock() let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) + var result = false viewModel.postComments = post @@ -146,6 +149,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let router = DiscussionRouterMock() let config = ConfigMock() let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) + var result = false let noInternetError = AFError.sessionInvalidated(error: URLError(.notConnectedToInternet)) @@ -167,6 +171,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let router = DiscussionRouterMock() let config = ConfigMock() let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) + var result = false Given(interactor, .voteThread(voted: .any, threadID: .any, willThrow: NSError())) @@ -186,6 +191,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let router = DiscussionRouterMock() let config = ConfigMock() let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) + var result = false viewModel.postComments = post @@ -208,6 +214,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let router = DiscussionRouterMock() let config = ConfigMock() let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) + var result = false viewModel.postComments = post @@ -230,6 +237,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let router = DiscussionRouterMock() let config = ConfigMock() let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) + var result = false let noInternetError = AFError.sessionInvalidated(error: URLError(.notConnectedToInternet)) @@ -251,6 +259,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let router = DiscussionRouterMock() let config = ConfigMock() let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) + var result = false Given(interactor, .flagThread(abuseFlagged: .any, threadID: .any, willThrow: NSError())) @@ -270,6 +279,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let router = DiscussionRouterMock() let config = ConfigMock() let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) + var result = false viewModel.postComments = post @@ -292,6 +302,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let router = DiscussionRouterMock() let config = ConfigMock() let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) + var result = false let noInternetError = AFError.sessionInvalidated(error: URLError(.notConnectedToInternet)) @@ -313,6 +324,7 @@ final class BaseResponsesViewModelTests: XCTestCase { let router = DiscussionRouterMock() let config = ConfigMock() let viewModel = BaseResponsesViewModel(interactor: interactor, router: router, config: config) + var result = false Given(interactor, .followThread(following: .any, threadID: .any, willThrow: NSError())) diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index d217ea7d3..715431a35 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 020CA5D92AA0A25300970AAF /* AppStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020CA5D82AA0A25300970AAF /* AppStorage.swift */; }; 0218196428F734FA00202564 /* Discussion.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0218196328F734FA00202564 /* Discussion.framework */; }; 0218196528F734FA00202564 /* Discussion.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0218196328F734FA00202564 /* Discussion.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 0219C67728F4347600D64452 /* Course.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0219C67628F4347600D64452 /* Course.framework */; }; @@ -21,7 +22,6 @@ 0293A2072A6FCDA30090A336 /* DiscoveryPersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0293A2062A6FCDA30090A336 /* DiscoveryPersistence.swift */; }; 0293A2092A6FCDE50090A336 /* DashboardPersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0293A2082A6FCDE50090A336 /* DashboardPersistence.swift */; }; 0298DF302A4EF7230023A257 /* AnalyticsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0298DF2F2A4EF7230023A257 /* AnalyticsManager.swift */; }; - 02B089462A9F83D200754BD4 /* AppStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B089452A9F83D200754BD4 /* AppStorage.swift */; }; 02ED50D429A6554E008341CD /* сountries.json in Resources */ = {isa = PBXBuildFile; fileRef = 02ED50D629A6554E008341CD /* сountries.json */; }; 02ED50D829A66007008341CD /* languages.json in Resources */ = {isa = PBXBuildFile; fileRef = 02ED50DA29A66007008341CD /* languages.json */; }; 02F175312A4DA95B0019CD70 /* MainScreenAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F175302A4DA95B0019CD70 /* MainScreenAnalytics.swift */; }; @@ -66,6 +66,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 020CA5D82AA0A25300970AAF /* AppStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStorage.swift; sourceTree = ""; }; 0218196328F734FA00202564 /* Discussion.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Discussion.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0219C67628F4347600D64452 /* Course.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Course.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 02450ABD29C35FF20094E2D0 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -79,7 +80,6 @@ 0293A2062A6FCDA30090A336 /* DiscoveryPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryPersistence.swift; sourceTree = ""; }; 0293A2082A6FCDE50090A336 /* DashboardPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardPersistence.swift; sourceTree = ""; }; 0298DF2F2A4EF7230023A257 /* AnalyticsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsManager.swift; sourceTree = ""; }; - 02B089452A9F83D200754BD4 /* AppStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStorage.swift; sourceTree = ""; }; 02B6B3C428E1E61400232911 /* CourseDetails.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CourseDetails.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 02ED50CA29A64AAA008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; 02ED50D529A6554E008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = uk; path = "uk.lproj/сountries.json"; sourceTree = ""; }; @@ -139,7 +139,7 @@ 0293A2042A6FCD430090A336 /* CoursePersistence.swift */, 0293A2062A6FCDA30090A336 /* DiscoveryPersistence.swift */, 0293A2082A6FCDE50090A336 /* DashboardPersistence.swift */, - 02B089452A9F83D200754BD4 /* AppStorage.swift */, + 020CA5D82AA0A25300970AAF /* AppStorage.swift */, ); path = Data; sourceTree = ""; @@ -374,7 +374,7 @@ buildActionMask = 2147483647; files = ( 0293A2052A6FCD430090A336 /* CoursePersistence.swift in Sources */, - 02B089462A9F83D200754BD4 /* AppStorage.swift in Sources */, + 020CA5D92AA0A25300970AAF /* AppStorage.swift in Sources */, 0298DF302A4EF7230023A257 /* AnalyticsManager.swift in Sources */, 0293A2072A6FCDA30090A336 /* DiscoveryPersistence.swift in Sources */, 07D5DA3528D075AA00752FD9 /* AppDelegate.swift in Sources */, diff --git a/OpenEdX/Data/AppStorage.swift b/OpenEdX/Data/AppStorage.swift index da90b86cc..99144be00 100644 --- a/OpenEdX/Data/AppStorage.swift +++ b/OpenEdX/Data/AppStorage.swift @@ -2,7 +2,7 @@ // AppStorage.swift // OpenEdX // -// Created by  Stepanok Ivan on 30.08.2023. +// Created by  Stepanok Ivan on 31.08.2023. // import Foundation @@ -10,17 +10,16 @@ import KeychainSwift import Core import Profile -public class AppStorage: CoreStorage, - ProfileStorage { - +public class AppStorage: CoreStorage, ProfileStorage { + private let keychain: KeychainSwift private let userDefaults: UserDefaults - + public init(keychain: KeychainSwift, userDefaults: UserDefaults) { self.keychain = keychain self.userDefaults = userDefaults } - + public var accessToken: String? { get { return keychain.get(KEY_ACCESS_TOKEN) @@ -33,7 +32,7 @@ public class AppStorage: CoreStorage, } } } - + public var refreshToken: String? { get { return keychain.get(KEY_REFRESH_TOKEN) @@ -46,7 +45,7 @@ public class AppStorage: CoreStorage, } } } - + public var cookiesDate: String? { get { return userDefaults.string(forKey: KEY_COOKIES_DATE) @@ -59,7 +58,7 @@ public class AppStorage: CoreStorage, } } } - + public var userProfile: DataLayer.UserProfile? { get { guard let userJson = userDefaults.data(forKey: KEY_USER_PROFILE) else { @@ -78,7 +77,7 @@ public class AppStorage: CoreStorage, } } } - + public var userSettings: UserSettings? { get { guard let userSettings = userDefaults.data(forKey: KEY_SETTINGS) else { @@ -102,7 +101,7 @@ public class AppStorage: CoreStorage, } } } - + public var user: DataLayer.User? { get { guard let userJson = userDefaults.data(forKey: KEY_USER) else { @@ -121,14 +120,14 @@ public class AppStorage: CoreStorage, } } } - + public func clear() { accessToken = nil refreshToken = nil cookiesDate = nil user = nil } - + private let KEY_ACCESS_TOKEN = "accessToken" private let KEY_REFRESH_TOKEN = "refreshToken" private let KEY_COOKIES_DATE = "cookiesDate" diff --git a/Profile/Profile/Data/ProfileRepository.swift b/Profile/Profile/Data/ProfileRepository.swift index 53b324a10..9c95c9a7a 100644 --- a/Profile/Profile/Data/ProfileRepository.swift +++ b/Profile/Profile/Data/ProfileRepository.swift @@ -110,17 +110,17 @@ public class ProfileRepository: ProfileRepositoryProtocol { } public func uploadProfilePicture(pictureData: Data) async throws { - let response = try await api.request( - ProfileEndpoint.uploadProfilePicture(username: storage.user?.username ?? "", - pictureData: pictureData)) + let response = try await api.request( + ProfileEndpoint.uploadProfilePicture(username: storage.user?.username ?? "", + pictureData: pictureData)) if response.statusCode != 204 { throw APIError.uploadError } } public func deleteProfilePicture() async throws -> Bool { - let response = try await api.request( - ProfileEndpoint.deleteProfilePicture(username: storage.user?.username ?? "")) + let response = try await api.request( + ProfileEndpoint.deleteProfilePicture(username: storage.user?.username ?? "")) return response.statusCode == 204 } From 97e246602bd3b0ec603677abe6e58cb2b36cb81d Mon Sep 17 00:00:00 2001 From: Ivan Stepanok Date: Thu, 31 Aug 2023 13:51:32 +0300 Subject: [PATCH 3/3] remove import Profile --- .../Presentation/Comments/Base/BaseResponsesViewModel.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift b/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift index c1da7c39a..bc2572e8d 100644 --- a/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift +++ b/Discussion/Discussion/Presentation/Comments/Base/BaseResponsesViewModel.swift @@ -10,7 +10,6 @@ import SwiftUI import Core import Combine import Swinject -import Profile public class BaseResponsesViewModel {