| import UIKit |
| import Delivery_Club_Models |
| import Delivery_Club_Managers |
| import Delivery_Club_Remote_Config |
| import Delivery_Club_Storages |
| final class RestaurantRepository { |
| enum DataError: Swift.Error { |
| case mapingData |
| case requestError(Swift.Error) |
| case noVendor |
| case requestedMenuForBooking |
| } |
| enum CartAddProductError: Error { |
| case request(Error) |
| case needClearCart |
| case bonusNoAuthBefore |
| case bonusNoAuthAfter |
| case bonusProductAlreadyInCart |
| case bonusNeedPoints(Int) |
| case bonusNeedReplacement |
| } |
| private let dataMapper: RestaurantDataMapperProtocol |
| private let infoMapper: RestaurantInfoMapperProtocol |
| private let serviceManager: ServiceManagerProtocol |
| private let accountManager: AccountManagerProtocol |
| private let addressManager: AddressManagerProtocol |
| private let cartManager: CartManagersProtocol |
| private let favoritesManager: FavoritesManagerProtocol |
| private let restaurauntDataStorage: RestaurauntDataStorageProtocol |
| private let vendorContainerRepository: VendorContainerRepositoryProtocol |
| private var observers: [Weak<RestaurantRepositoryObserver>] = [] |
| init(dataMapper: RestaurantDataMapperProtocol, |
| infoMapper: RestaurantInfoMapperProtocol, |
| serviceManager: ServiceManagerProtocol, |
| accountManager: AccountManagerProtocol, |
| cartManager: CartManagersProtocol, |
| addressManager: AddressManagerProtocol, |
| favoritesManager: FavoritesManagerProtocol, |
| restaurauntDataStorage: RestaurauntDataStorageProtocol, |
| vendorContainerRepository: VendorContainerRepositoryProtocol) { |
| self.serviceManager = serviceManager |
| self.accountManager = accountManager |
| self.cartManager = cartManager |
| self.addressManager = addressManager |
| self.dataMapper = dataMapper |
| self.infoMapper = infoMapper |
| self.favoritesManager = favoritesManager |
| self.restaurauntDataStorage = restaurauntDataStorage |
| self.vendorContainerRepository = vendorContainerRepository |
| setup() |
| } |
| private func setup() { |
| favoritesManager.addObserver(self) |
| } |
| private func cleanReleasedObservers() { |
| observers.removeAll(where: { $0.isReleased }) |
| } |
| private func notifyObserversFavoriteDidUpdate() { |
| cleanReleasedObservers() |
| observers.forEach({ $0.object?.didUpdateFavorites() }) |
| } |
| private func notifyObserversFavoriteDidAdd(serviceId: Int) { |
| cleanReleasedObservers() |
| observers.forEach({ $0.object?.didAddFavorite(serviceId: serviceId) }) |
| } |
| private func notifyObserversFavoriteDidRemove(serviceId: Int) { |
| cleanReleasedObservers() |
| observers.forEach({ $0.object?.didRemoveFavorite(serviceId: serviceId) }) |
| } |
| } |
| extension RestaurantRepository: RestaurantRepositoryProtocol { |
| func saveResaurauntData(_ data: AnalyticsRestaurantData) { |
| restaurauntDataStorage.saveAnalyticsRestaurantData(data) |
| } |
| func addObserver(_ observer: RestaurantRepositoryObserver) { |
| cleanReleasedObservers() |
| observers.append(Weak(observer)) |
| } |
| func requestRestaurantInfo(serviceId: Int, vendorId: Int, completion: @escaping RestaurantInfoCompletion) { |
| vendorContainerRepository.requestVendorContainer(withServiceId: serviceId, vendorId: vendorId) { result in |
| completion(.init(catching: { |
| try result.get() |
| })) |
| } |
| } |
| func requestData(serviceId: Int, vendorId: Int, containerTab: RestaurantContainerTab, completion: @escaping RestuarantDataCompletion) { |
| vendorContainerRepository.requestVendorContainer(withServiceId: serviceId, vendorId: vendorId) { [weak self] result in |
| guard let self = self else { return } |
| switch result { |
| case .failure(let error): |
| completion(.failure(error)) |
| case .success(let vendorContainer): |
| let deliveryType: DCDeliveryType |
| var correctVendorId = vendorId |
| switch containerTab { |
| case .delivery: |
| guard let service = vendorContainer.deliveryVendor else { |
| completion(.failure(DataError.noVendor)) |
| return |
| } |
| deliveryType = service.mostAccurateDeliveryType |
| correctVendorId = service.identifier |
| case .takeaway: |
| deliveryType = .takeAway |
| case .booking: |
| completion(.failure(DataError.requestedMenuForBooking)) |
| return |
| } |
| self.requestMenuAndPromo( |
| vendorContainer: vendorContainer, |
| vendorId: correctVendorId, |
| deliveryType: deliveryType, |
| isFavorite: self.isServiceInFavorite(serviceId: serviceId), |
| isTakeaway: containerTab == .takeaway, |
| completion: completion |
| ) |
| } |
| } |
| } |
| func addProduct(_ product: DCFullProductObject, |
| service: RestaurantServiceProtocol, |
| deliveryType: DCDeliveryType, |
| isFavorite: Bool, |
| completion: @escaping CartAddProductCompletion) { |
| switch product.classId { |
| case .default: |
| addDefaultProductToCart(product, service: service, deliveryType: deliveryType, isFavorite: isFavorite, completion: completion) |
| case .points: |
| addPointProductToCart(product, service: service, isFavorite: isFavorite, completion: completion) |
| case .variantsIngredients: |
| completion(.success(.variantsIngredients)) |
| @unknown default: |
| return |
| } |
| } |
| func deleteProduct(_ product: DCFullProductObject) { |
| cartManager.requestDeleteProduct?( |
| product, |
| quantity: 1, |
| completion: nil, |
| failure: nil) |
| } |
| func removeProduct(_ product: DCFullProductObject, service: DCServiceObject) { |
| guard let cartProductModel = obtainCartProductModels([product], service: service).first?.1 else { return } |
| cartManager.requestRemoveProduct?(cartProductModel, completion: nil) |
| } |
| func clearCart() { |
| cartManager.clearCart() |
| } |
| func obtainCartProductModels(_ products: [DCFullProductObject], service: RestaurantServiceProtocol?) -> [ProductIdentifier: CartProductModel] { |
| guard let service = service else { return [:] } |
| return products.reduce(into: [ProductIdentifier: CartProductModel]()) { result, product in |
| let baseService = DCBaseServiceObject.baseService(from: service) |
| guard let productModel = cartManager.getProductModel?(product, service: baseService) |
| else { return } |
| result[product.identifier] = productModel |
| } |
| } |
| func getCartData() -> RestaurantCartData { |
| return RestaurantCartData( |
| totalQuantity: cartManager.totalQuantity, |
| totalPrice: cartManager.totalPrice, |
| hint: cartManager.getCartModel().getRestriction()?.hint, |
| state: cartManager.state |
| ) |
| } |
| func deletePointsProduct() { |
| guard let pointProduct = cartManager.getPointsProduct?() else { return } |
| cartManager.requestRemoveProduct?(pointProduct, completion: nil) |
| } |
| func isCartModelServiceEqualService(_ service: RestaurantServiceProtocol) -> Bool { |
| return cartManager.getService()?.serviceId == service.serviceId |
| } |
| func getRestriction() -> DCRestrictionsObject? { |
| return cartManager.getCartModel().getRestriction() |
| } |
| func isServiceInFavorite(serviceId: Int) -> Bool { |
| return favoritesManager.isFavorite(identifier: serviceId) |
| } |
| func toggleFavorites(serviceId: Int, completion: @escaping RestuarantFavoriteCompletion) { |
| guard isServiceInFavorite(serviceId: serviceId) else { |
| addFavorite(serviceId: serviceId, completion: completion) |
| return |
| } |
| removeFavorite(serviceId: serviceId, completion: completion) |
| } |
| } |
| private extension RestaurantRepository { |
| func requestAddFavorite(serviceId: Int, completion: @escaping RestuarantFavoriteCompletion) { |
| serviceManager.requestAddFavorites(serviceId: serviceId, completion: completion) |
| } |
| func requestRemoveFavorite(serviceId: Int, completion: @escaping RestuarantFavoriteCompletion) { |
| serviceManager.requestRemoveFromFavorites(serviceId: serviceId, completion: completion) |
| } |
| func removeFavorite(serviceId: Int, completion: @escaping RestuarantFavoriteCompletion) { |
| favoritesManager.removeFavorite(identifier: serviceId) |
| requestRemoveFavorite(serviceId: serviceId) { [weak self] result in |
| guard let self = self else { return } |
| switch result { |
| case .success: break |
| case .failure: self.favoritesManager.addFavorite(identifier: serviceId) |
| } |
| completion(result) |
| } |
| } |
| func addFavorite(serviceId: Int, completion: @escaping RestuarantFavoriteCompletion) { |
| favoritesManager.addFavorite(identifier: serviceId) |
| requestAddFavorite(serviceId: serviceId) { [weak self] result in |
| guard let self = self else { return } |
| switch result { |
| case .success: break |
| case .failure: self.favoritesManager.removeFavorite(identifier: serviceId) |
| } |
| completion(result) |
| } |
| } |
| func requestMenuAndPromo(vendorContainer: VendorContainer?, |
| vendorId: Int, |
| deliveryType: DCDeliveryType, |
| isFavorite: Bool, |
| isTakeaway: Bool, |
| completion: @escaping RestuarantDataCompletion) { |
| serviceManager.requestMenuAndPromoactions( |
| serviceIdentifier: vendorId, |
| supportedRewards: [.productDiscount, .freeProduct], |
| deliveryType: deliveryType) { [weak self] result in |
| guard let self = self else { return } |
| switch result { |
| case .success(let response): |
| let data = self.dataMapper.mapRestaurantData( |
| vendorContainer: vendorContainer, |
| deliveryType: deliveryType, |
| menu: response.0, |
| promoActions: response.1, |
| isFavorite: isFavorite, |
| isTakeaway: isTakeaway |
| ) |
| completion(.success(data)) |
| case .failure(let error): |
| completion(.failure(DataError.requestError(error))) |
| } |
| } |
| } |
| func addDefaultProductToCart(_ product: DCFullProductObject, |
| service: RestaurantServiceProtocol, |
| deliveryType: DCDeliveryType, |
| isFavorite: Bool, |
| completion: @escaping CartAddProductCompletion) { |
| guard cartManager.canAddProductFromRestaurant(withServiceId: service.serviceId, vendorId: service.identifier) else { |
| completion(.failure(.needClearCart)) |
| return |
| } |
| saveAddressIfNeeded { [weak self] result in |
| guard let self = self else { return } |
| switch result { |
| case .failure(let error): |
| completion(.failure(error)) |
| case .success: |
| self.addProductToCart(product, service: service, isFavorite: isFavorite) |
| completion(.success(.default)) |
| } |
| } |
| } |
| func addPointProductToCart(_ product: DCFullProductObject, |
| service: RestaurantServiceProtocol, |
| isFavorite: Bool, |
| completion: @escaping CartAddProductCompletion) { |
| guard accountManager.isAuthorized else { |
| completion(.failure(.bonusNoAuthBefore)) |
| return |
| } |
| accountManager.requestUser(withProgress: true) { [weak self] (result) in |
| guard let self = self else { return } |
| switch result { |
| case .failure(let error): |
| completion(.failure(.request(error))) |
| case .success: |
| let mediumService = DCMediumServiceObject.mediumService(from: service) |
| let isAlreadyInCart = self.cartManager.getCountInCartForProduct?(product, service: mediumService) ?? .zero > .zero |
| let isNeedPoints = self.accountManager.scorePoints < product.points |
| let hasPointsProducts = self.cartManager.hasPointsProducts ?? false |
| switch product.classId { |
| case .points: |
| if !self.accountManager.isAuthorized { |
| completion(.failure(.bonusNoAuthAfter)) |
| } else if isAlreadyInCart { |
| completion(.failure(.bonusProductAlreadyInCart)) |
| } else if isNeedPoints { |
| completion(.failure(.bonusNeedPoints(product.points - self.accountManager.scorePoints))) |
| } |
| case .default, .variantsIngredients: break |
| @unknown default: break |
| } |
| guard !hasPointsProducts else { |
| completion(.failure(.bonusNeedReplacement)) |
| return |
| } |
| self.addProductToCart(product, service: service, isFavorite: isFavorite) |
| completion(.success(.points)) |
| } |
| } |
| } |
| func addProductToCart(_ product: DCFullProductObject, service: RestaurantServiceProtocol, isFavorite: Bool) { |
| //TODO Siri? |
| //[DCSiriHelper registerService:self.service.title serviceId:self.service.serviceId viewController:(UIViewController *)self.view]; |
| let mediumService = DCMediumServiceObject.mediumService(from: service) |
| cartManager.requestAddProduct?( |
| product, |
| service: mediumService, |
| ingredients: product.ingredients, |
| variantItems: nil, |
| quantity: 1, |
| recommendationId: nil, |
| isFromFavorites: isFavorite, |
| completion: nil, |
| failure: nil |
| ) |
| } |
| func saveAddressIfNeeded(_ completion: @escaping (Result<Void, CartAddProductError>) -> Void) { |
| guard let address = accountManager.lastAddress, |
| let city = address.city, |
| address.identifier == .zero && accountManager.isAuthorized else { |
| completion(.success(Void())) |
| return |
| } |
| addressManager.requestAddAddress(address, cityTitle: city) { [weak self] (result) in |
| guard let self = self else { return } |
| switch result { |
| case .failure(let error): |
| completion(.failure(.request(error))) |
| case .success(let responseAddress): |
| self.accountManager.setLastAddress(responseAddress) |
| completion(.success(Void())) |
| } |
| } |
| } |
| } |
| extension RestaurantRepository: FavoritesManagerObserver { |
| func didUpdateFavorites() { |
| notifyObserversFavoriteDidUpdate() |
| } |
| func didAddFavorite(identifier: Int) { |
| notifyObserversFavoriteDidAdd(serviceId: identifier) |
| } |
| func didRemoveFavorite(identifier: Int) { |
| notifyObserversFavoriteDidRemove(serviceId: identifier) |
| } |
| } |
| //MARK: - Error localization |
| extension RestaurantRepository.DataError: LocalizedError { |
| public var errorDescription: String? { |
| switch self { |
| case .mapingData: return Localized("SOMETHING_GOES_WRONG") |
| case .requestError(let error): return error.localizedDescription |
| case .noVendor, .requestedMenuForBooking: return nil |
| } |
| } |
| } |
| extension RestaurantRepository.CartAddProductError: LocalizedError { |
| public var errorDescription: String? { |
| switch self { |
| case .bonusNoAuthAfter: return Localized("BONUS_NO_AUTH_MESSAGE") |
| case .bonusProductAlreadyInCart: return Localized("PRODUCT_IS_ALREADY_IN_THE_CART") |
| case .bonusNeedPoints(let points): return String(format: Localized("MORE_POINTS_NEEDED_PATTERN"), points) |
| case .request(let error): return error.localizedDescription |
| case .needClearCart, .bonusNeedReplacement, .bonusNoAuthBefore: return nil |
| } |
| } |
| } |
Комментарии