import 'package:injectable/injectable.dart'; import 'package:mnemo_cards_backend/api/ads/ads_manager.dart'; import 'package:mnemo_cards_backend/extensions.dart'; import 'package:mnemo_cards_backend/repository/export.dart'; import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; import 'package:mnemo_cards_backend/packs/card_model_extension.dart'; import 'package:mnemo_cards_common/mnemo_cards_common.dart'; import 'products_price_resolver.dart'; bool canOpenForAd(String? id) => id != null; @LazySingleton() class PackDtoConverter { final ProductsPriceResolver _productsPriceResolver; final AdsManager _adsManager; final UserRepository _userRepository; PackDtoConverter( this._productsPriceResolver, this._adsManager, this._userRepository, ); Future toCardPackPreviewDto( CardPackModel model, UserModel? userModel, ) async { bool available = userModel != null && (userModel.packs.contains(model.id?.toString()) || userModel.admin); if (userModel != null && !available && userModel.id != null) { final subscription = await _userRepository.getUserSubscription( userModel.id!, ); available = subscription != null && subscription.isActive && subscription.features.contains(SubscriptionFeatureEnum.packs); } String? price = model.price; if (userModel != null && !available) { price = await _productsPriceResolver.userPackPrice(userModel, model); } final canOpenForAdVal = !available && canOpenForAd(model.id); // Generate cover image URL String? coverUrl; if (model.cover != null && model.cover!.isNotEmpty) { final coverValue = model.cover!.trim(); // If it's already a remote URL, use it directly if (coverValue.startsWith('http://') || coverValue.startsWith('https://')) { coverUrl = coverValue; } // If it's a UUID or any other value, use API endpoint else if (coverValue.isNotEmpty) { coverUrl = '/api/v2/packs/${model.id.toString()}/cover'; } } return CardPackPreviewDto( id: model.id.toString(), title: model.title, subtitle: model.subtitle, cards: model.cards.length, tests: model.tests.length, price: price, imageUrl: coverUrl, color: model.color, isAvailable: available, // trail: 'asset:icons/gift.png', tip: available ? null : canOpenForAdVal ? AssetPackTip('icons/ad.webp', position: PackTipPosition.bottomRight) : AssetPackTip( 'icons/lock.webp', position: PackTipPosition.bottomRight, ), version: model.version, ); } Future toCardPackBuyDto( CardPackModel model, UserModel? userModel, ) async { final price = userModel == null ? model.price : await _productsPriceResolver.userPackPrice(userModel, model); final canOpenForAdVal = userModel != null && canOpenForAd(model.id); String? _adsKey; if (canOpenForAdVal) { _adsKey = _adsManager.getAdsKey([model], userModel); } return CardPackBuyDto( id: model.id.toString(), title: model.title, subtitle: model.subtitle, appStoreId: model.appStoreId, rustoreId: model.rustoreId, googlePlayId: model.googlePlayId, cards: await Future.wait( (model.previewCards.isNotEmpty ? model.previewCards : model.cards.take(3)) .toDtosList(model.cardsOrder) .map( (dto) async => dto.copyWith( image: null, // Legacy: card images should be stored in MinIO, not read from disk ), ), ), items: [ if (model.description != null) TextItem(title: model.description), if (_adsKey == null) ...[ SpacerItem(), TextItem( subtitle: 'Открыть все темы с подпиской!', deeplink: SubscriptionOpenDeeplink().uri.toString(), ), ], if (_adsKey != null) ...[ SpacerItem(), TextItem( subtitle: 'Эту тему можно открыть, посмотрев рекламу', deeplink: ProductForAdDeeplink( model.toDto(), _adsKey, ).uri.toString(), ), ], SpacerItem(flex: 0, height: 10), if (_adsKey == null) ...[ TextItem( subtitle: 'Оплатить российской картой', deeplink: PurchaseInitDeeplink( id: model.id.toString(), productType: MnemoCardsProductType.pack, paymentId: model.id.toString(), paymentSystem: PaymentSystem.yookassa, ).uri.toString(), ), ], SpacerItem(height: 20, flex: 0), ], color: model.color, version: model.version, price: price, ); } Future toEditDto( CardPackModel model, { bool withCards = true, bool withTests = false, bool withUsers = false, }) async { return EditCardPackDto( id: model.id.toString(), title: model.title, subtitle: model.subtitle, color: model.color, version: model.version, cover: null, // Legacy: covers should be stored in MinIO, not read from disk size: model.size, googlePlayId: model.googlePlayId, rustoreId: model.rustoreId, appStoreId: model.appStoreId, price: model.price, description: model.description, enabled: model.enabled, addCardIds: withCards ? model.cards.map((c) => c.id.toString()).toList() : null, addTestIds: withTests ? model.tests.map((t) => t.id.toString()).toList() : null, removeCardIds: null, removeTestIds: null, previewCards: model.previewCards .map((card) => card.id.toString()) .whereNotNull() .toList(), order: model.order, cardsOrder: model.cardsOrder, ); } CardPackDto toDto(CardPackModel model) { return CardPackDto( id: model.id.toString(), title: model.title, subtitle: model.subtitle, cards: model.cards.toDtosList(model.cardsOrder), color: model.color, version: model.version, ); } CardPackModel toModel(EditCardPackDto dto, CardPackModel? model) { return CardPackModel( title: dto.title ?? model?.title ?? '', subtitle: dto.subtitle ?? model?.subtitle ?? '', size: dto.size ?? model?.size ?? 0, color: dto.color ?? model?.color, cover: dto.cover ?? model?.cover, description: dto.description ?? model?.description, version: dto.version ?? model?.version, id: model?.id ?? dto.id, googlePlayId: dto.googlePlayId ?? model?.googlePlayId, rustoreId: dto.rustoreId ?? model?.rustoreId, appStoreId: dto.appStoreId ?? model?.appStoreId, price: dto.price ?? model?.price, enabled: dto.enabled ?? model?.enabled ?? false, order: dto.order ?? model?.order ?? 0, cardsOrder: dto.cardsOrder ?? model?.cardsOrder ?? const [], currency: model?.currency ?? 'RUB', ); } }