diff --git a/lib/app/create/create_page_select_tokens_stage.dart b/lib/app/create/create_page_select_tokens_stage.dart index d7a1978..98f302d 100644 --- a/lib/app/create/create_page_select_tokens_stage.dart +++ b/lib/app/create/create_page_select_tokens_stage.dart @@ -103,114 +103,111 @@ class _CreatePageState extends State with DeviceInf alignment: Alignment.topCenter, child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 490), - child: Align( - alignment: Alignment.topLeft, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - ZupPageTitle(S.of(context).createPageTitle), - SizedBox( - height: 58, - child: Text( - S.of(context).createPageDescription, - maxLines: 3, - - style: const TextStyle(fontSize: 14, color: ZupColors.gray), - ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + ZupPageTitle(S.of(context).createPageTitle), + SizedBox( + height: 58, + child: Text( + S.of(context).createPageDescription, + maxLines: 3, + + style: const TextStyle(fontSize: 14, color: ZupColors.gray), ), - const SizedBox(height: 20), - SizedBox( - width: double.infinity, - child: Wrap( - runSpacing: 10, - verticalDirection: VerticalDirection.up, - alignment: WrapAlignment.spaceBetween, - children: [ - Transform.translate( - offset: const Offset(0, 8), - child: Text( - S.of(context).token0, - style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14, color: ZupColors.gray), - ), + ), + const SizedBox(height: 20), + SizedBox( + width: double.infinity, + child: Wrap( + runSpacing: 10, + verticalDirection: VerticalDirection.up, + alignment: WrapAlignment.spaceBetween, + children: [ + Transform.translate( + offset: const Offset(0, 8), + child: Text( + S.of(context).token0, + style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14, color: ZupColors.gray), ), - StatefulBuilder( - builder: (context, localSetState) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - const ExchangesFilterDropdownButton(), - const SizedBox(width: 10), - Badge( - alignment: const Alignment(1.05, -1.05), - smallSize: cache.getPoolSearchSettings().isDefault ? 0 : 6, - backgroundColor: ZupThemeColors.alert.themed(context.brightness), - child: ZupMiniButton( - key: const Key("pool-search-settings-button"), - onPressed: (buttonContext) => CreatePageSettingsDropdown.show( - buttonContext, - onClose: () { - if (mounted) { - WidgetsBinding.instance.addPostFrameCallback((_) => localSetState(() {})); - } - }, - ), - title: S.of(context).createPageSelectTokensStageSearchSettings, - icon: Assets.icons.gear.svg(height: 18), + ), + StatefulBuilder( + builder: (context, localSetState) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + const ExchangesFilterDropdownButton(), + const SizedBox(width: 10), + Badge( + alignment: const Alignment(1.05, -1.05), + smallSize: cache.getPoolSearchSettings().isDefault ? 0 : 6, + backgroundColor: ZupThemeColors.alert.themed(context.brightness), + child: ZupMiniButton( + key: const Key("pool-search-settings-button"), + onPressed: (buttonContext) => CreatePageSettingsDropdown.show( + buttonContext, + onClose: () { + if (mounted) { + WidgetsBinding.instance.addPostFrameCallback((_) => localSetState(() {})); + } + }, ), + title: S.of(context).createPageSelectTokensStageSearchSettings, + icon: Assets.icons.gear.svg(height: 18), ), - ], - ); - }, - ), - ], - ), - ), - const SizedBox(height: 12), - TokenSelectorButton(key: const Key("token-a-selector"), controller: token0SelectorController), - const SizedBox(height: 10), - Text( - S.of(context).token1, - style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14, color: ZupColors.gray), - ), - const SizedBox(height: 5), - TokenSelectorButton(key: const Key("token-b-selector"), controller: token1SelectorController), - const SizedBox(height: 20), - StreamBuilder( - stream: StreamGroup.mergeBroadcast([ - token0SelectorController.selectionStream, - token1SelectorController.selectionStream, - ]), - builder: (context, _) { - return ZupPrimaryButton( - key: const Key("search-button"), - height: 50, - fixedIcon: true, - alignCenter: true, - title: S.of(context).createPageShowMeTheMoney, - foregroundColor: ZupColors.white, - icon: Assets.icons.sparkleMagnifyingglass.svg(), - onPressed: token0SelectorController.hasSelection && token1SelectorController.hasSelection - ? (buttonContext) { - return navigator.navigateToDeposit( - network: appCubit.selectedNetwork, - group0: token0SelectorController.selectedTokenGroup?.id, - group1: token1SelectorController.selectedTokenGroup?.id, - token0: (appCubit.selectedNetwork.isAllNetworks) - ? token0SelectorController.selectedToken?.internalId - : token0SelectorController.selectedToken?.addresses[appCubit.currentChainId], - token1: (appCubit.selectedNetwork.isAllNetworks) - ? token1SelectorController.selectedToken?.internalId - : token1SelectorController.selectedToken?.addresses[appCubit.currentChainId]!, - ); - } - : null, - mainAxisSize: MainAxisSize.max, - ); - }, + ), + ], + ); + }, + ), + ], ), - ], - ), + ), + const SizedBox(height: 12), + TokenSelectorButton(key: const Key("token-a-selector"), controller: token0SelectorController), + const SizedBox(height: 10), + Text( + S.of(context).token1, + style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14, color: ZupColors.gray), + ), + const SizedBox(height: 5), + TokenSelectorButton(key: const Key("token-b-selector"), controller: token1SelectorController), + const SizedBox(height: 20), + StreamBuilder( + stream: StreamGroup.mergeBroadcast([ + token0SelectorController.selectionStream, + token1SelectorController.selectionStream, + ]), + builder: (context, _) { + return ZupPrimaryButton( + key: const Key("search-button"), + height: 50, + fixedIcon: true, + alignCenter: true, + title: S.of(context).createPageShowMeTheMoney, + foregroundColor: ZupColors.white, + icon: Assets.icons.sparkleMagnifyingglass.svg(), + onPressed: token0SelectorController.hasSelection && token1SelectorController.hasSelection + ? (buttonContext) { + return navigator.navigateToYields( + network: appCubit.selectedNetwork, + group0: token0SelectorController.selectedTokenGroup?.id, + group1: token1SelectorController.selectedTokenGroup?.id, + token0: (appCubit.selectedNetwork.isAllNetworks) + ? token0SelectorController.selectedToken?.internalId + : token0SelectorController.selectedToken?.addresses[appCubit.currentChainId], + token1: (appCubit.selectedNetwork.isAllNetworks) + ? token1SelectorController.selectedToken?.internalId + : token1SelectorController.selectedToken?.addresses[appCubit.currentChainId]!, + ); + } + : null, + mainAxisSize: MainAxisSize.max, + ); + }, + ), + ], ), ), ), diff --git a/lib/app/create/deposit/deposit_page.dart b/lib/app/create/deposit/deposit_page.dart deleted file mode 100644 index d996f43..0000000 --- a/lib/app/create/deposit/deposit_page.dart +++ /dev/null @@ -1,1233 +0,0 @@ -import 'dart:async'; - -import 'package:decimal/decimal.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:intl/intl.dart'; -import 'package:lottie/lottie.dart'; -import 'package:web3kit/web3kit.dart'; -import 'package:zup_app/app/app_cubit/app_cubit.dart'; -import 'package:zup_app/app/create/deposit/deposit_cubit.dart'; -import 'package:zup_app/app/create/deposit/widgets/deposit_settings_dropdown_child.dart'; -import 'package:zup_app/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal.dart'; -import 'package:zup_app/app/create/deposit/widgets/range_selector.dart'; -import 'package:zup_app/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card.dart'; -import 'package:zup_app/core/cache.dart'; -import 'package:zup_app/core/concentrated_liquidity_utils/cl_pool_constants.dart'; -import 'package:zup_app/core/concentrated_liquidity_utils/cl_pool_conversors_mixin.dart'; -import 'package:zup_app/core/concentrated_liquidity_utils/cl_pool_liquidity_calculations_mixin.dart'; -import 'package:zup_app/core/concentrated_liquidity_utils/cl_sqrt_price_math_mixin.dart'; -import 'package:zup_app/core/dtos/deposit_settings_dto.dart'; -import 'package:zup_app/core/dtos/pool_search_filters_dto.dart'; -import 'package:zup_app/core/dtos/token_dto.dart'; -import 'package:zup_app/core/dtos/yield_dto.dart'; -import 'package:zup_app/core/dtos/yields_dto.dart'; -import 'package:zup_app/core/enums/networks.dart'; -import 'package:zup_app/core/enums/yield_timeframe.dart'; -import 'package:zup_app/core/enums/zup_navigator_paths.dart'; -import 'package:zup_app/core/extensions/num_extension.dart'; -import 'package:zup_app/core/extensions/string_extension.dart'; -import 'package:zup_app/core/extensions/widget_extension.dart'; -import 'package:zup_app/core/injections.dart'; -import 'package:zup_app/core/pool_service.dart'; -import 'package:zup_app/core/repositories/yield_repository.dart'; -import 'package:zup_app/core/slippage.dart'; -import 'package:zup_app/core/zup_analytics.dart'; -import 'package:zup_app/core/zup_navigator.dart'; -import 'package:zup_app/core/zup_route_params_names.dart'; -import 'package:zup_app/gen/assets.gen.dart'; -import 'package:zup_app/l10n/gen/app_localizations.dart'; -import 'package:zup_app/widgets/yield_card.dart'; -import 'package:zup_app/widgets/zup_page_title.dart'; -import 'package:zup_core/zup_core.dart'; -import 'package:zup_ui_kit/zup_ui_kit.dart'; - -Route routeBuilder(BuildContext context, RouteSettings settings) { - return PageRouteBuilder( - settings: settings, - transitionDuration: const Duration(milliseconds: 500), - pageBuilder: (_, a1, a2) => BlocProvider( - create: (context) => DepositCubit( - inject(), - inject(), - inject(), - inject(), - inject(), - inject(), - inject(), - ), - child: const DepositPage(), - ), - transitionsBuilder: (_, a1, a2, child) => SlideTransition( - position: Tween(begin: const Offset(0, 1), end: Offset.zero).animate(a1), - child: FadeTransition(opacity: a1, child: child), - ), - ); -} - -class DepositPage extends StatefulWidget { - const DepositPage({super.key}); - - @override - State createState() => _DepositPageState(); -} - -class _DepositPageState extends State - with - CLPoolConversorsMixin, - CLPoolLiquidityCalculationsMixin, - DeviceInfoMixin, - CLPoolLiquidityCalculationsMixin, - CLSqrtPriceMath { - final lottieClick = inject(instanceName: InjectInstanceNames.lottieClick); - final lottieEmpty = inject(instanceName: InjectInstanceNames.lottieEmpty); - final lottieRadar = inject(instanceName: InjectInstanceNames.lottieRadar); - final lottieNumbers = inject(instanceName: InjectInstanceNames.lottieNumbers); - final lottieMatching = inject(instanceName: InjectInstanceNames.lottieMatching); - final lottieSearching = inject(instanceName: InjectInstanceNames.lottieList); - - final baseTokenAmountController = TextEditingController(); - final quoteTokenAmountController = TextEditingController(); - final yieldsPageController = PageController(initialPage: 0); - final wallet = inject(); - final selectRangeSectorKey = GlobalKey(); - - ZupNavigator get _navigator => inject(); - DepositCubit get _cubit => context.read(); - AppCubit get _appCubit => inject(); - - String? get token0Id { - return _navigator.getParam(ZupNavigatorPaths.deposit.routeParamsNames().token0); - } - - String? get token1Id { - return _navigator.getParam(ZupNavigatorPaths.deposit.routeParamsNames().token1); - } - - String? get group0Id { - return _navigator.getParam(ZupNavigatorPaths.deposit.routeParamsNames().group0); - } - - String? get group1Id { - return _navigator.getParam(ZupNavigatorPaths.deposit.routeParamsNames().group1); - } - - TokenDto get baseToken { - return areTokensReversed ? _cubit.selectedYield!.token1 : _cubit.selectedYield!.token0; - } - - TokenDto get quoteToken { - return areTokensReversed ? _cubit.selectedYield!.token0 : _cubit.selectedYield!.token1; - } - - num currentYieldPage = 0; - bool areTokensReversed = false; - bool isMaxRangeInfinity = true; - bool isMinRangeInfinity = true; - bool isBaseTokenAmountUserInput = false; - double? percentRange; - double minPrice = 0; - double maxPrice = 0; - RangeController minRangeController = RangeController(); - RangeController maxRangeController = RangeController(); - StreamSubscription? _poolSqrtPriceX96StreamSubscription; - YieldTimeFrame selectedYieldTimeFrame = YieldTimeFrame.day; - - late Slippage selectedSlippage = _cubit.depositSettings.slippage; - late Duration selectedDeadline = _cubit.depositSettings.deadline; - - bool get isRangeInvalid { - if (isMaxRangeInfinity || isMinRangeInfinity) return false; - - return (minPrice) >= (maxPrice); - } - - bool get isBaseTokenNeeded => !isOutOfRange.maxPrice; - - bool get isQuoteTokenNeeded => !isOutOfRange.minPrice; - - double get currentPrice { - if (_cubit.latestPoolSqrtPriceX96 == null || _cubit.selectedYield == null) return 0; - - final price = sqrtPriceX96ToPrice( - sqrtPriceX96: _cubit.latestPoolSqrtPriceX96!, - poolToken0Decimals: _cubit.selectedYield!.token0NetworkDecimals, - poolToken1Decimals: _cubit.selectedYield!.token1NetworkDecimals, - ); - - return areTokensReversed ? price.token1PerToken0 : price.token0PerToken1; - } - - ({bool minPrice, bool maxPrice, bool any}) get isOutOfRange { - if (_cubit.latestPoolSqrtPriceX96 == null) return (minPrice: false, maxPrice: false, any: false); - - final isMinPriceOutOfRange = !isMinRangeInfinity && (minPrice) > currentPrice; - final isMaxPriceOutOfRange = !isMaxRangeInfinity && (maxPrice) < currentPrice; - - return ( - minPrice: isMinPriceOutOfRange, - maxPrice: isMaxPriceOutOfRange, - any: isMinPriceOutOfRange || isMaxPriceOutOfRange, - ); - } - - void setFullRange() { - setState(() { - percentRange = null; - isMinRangeInfinity = true; - isMaxRangeInfinity = true; - }); - - minPrice = 0; - maxPrice = 0; - - calculateDepositTokensAmount(); - } - - void setPercentageRange(double percentage) { - if (currentPrice == 0) return; - - setState(() { - percentRange = percentage; - isMinRangeInfinity = false; - isMaxRangeInfinity = false; - - final percentageDecimals = percentage / 100; - final percentageDifference = currentPrice * percentageDecimals; - - minPrice = currentPrice - percentageDifference; - maxPrice = currentPrice + percentageDifference; - - minRangeController.setRange(minPrice); - maxRangeController.setRange(maxPrice); - - calculateDepositTokensAmount(); - }); - } - - void selectYield(YieldDto? yieldDto) async { - _cubit.selectYield(yieldDto).then((_) => calculateDepositTokensAmount()); - - if (yieldDto != null) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (selectRangeSectorKey.currentContext != null) { - Scrollable.ensureVisible( - selectRangeSectorKey.currentContext!, - duration: const Duration(milliseconds: 600), - curve: Curves.fastEaseInToSlowEaseOut, - ); - } - }); - } - } - - void switchTokens(bool isReversed) { - setState(() => areTokensReversed = isReversed); - - final currentBaseTokenDepositAmount = baseTokenAmountController.text; - final currentQuoteTokenDepositAmount = quoteTokenAmountController.text; - - baseTokenAmountController.text = currentQuoteTokenDepositAmount; - quoteTokenAmountController.text = currentBaseTokenDepositAmount; - isBaseTokenAmountUserInput = isReversed && !isBaseTokenAmountUserInput; - - if (percentRange != null) setPercentageRange(percentRange!); - calculateDepositTokensAmount(); - } - - void calculateDepositTokensAmount() { - if (_cubit.latestPoolSqrtPriceX96 == null || _cubit.selectedYield == null) return; - - if (isOutOfRange.minPrice) return quoteTokenAmountController.clear(); - if (isOutOfRange.maxPrice) return baseTokenAmountController.clear(); - - final maxTickPrice = tickToPrice( - tick: CLPoolConstants.maxTick, - poolToken0Decimals: _cubit.selectedYield!.token0NetworkDecimals, - poolToken1Decimals: _cubit.selectedYield!.token1NetworkDecimals, - ); - - final minTickPrice = tickToPrice( - tick: CLPoolConstants.minTick, - poolToken0Decimals: _cubit.selectedYield!.token0NetworkDecimals, - poolToken1Decimals: _cubit.selectedYield!.token1NetworkDecimals, - ); - - double getMinPrice() { - if (minPrice != 0 && !isMinRangeInfinity) return minPrice; - - return areTokensReversed ? maxTickPrice.priceAsQuoteToken : minTickPrice.priceAsBaseToken; - } - - double getMaxPrice() { - if (maxPrice != 0 && !isMaxRangeInfinity) return maxPrice; - - return areTokensReversed ? minTickPrice.priceAsQuoteToken : maxTickPrice.priceAsBaseToken; - } - - final newQuoteTokenAmount = Decimal.tryParse( - calculateToken1AmountFromToken0( - currentPrice: currentPrice, - priceLower: getMinPrice(), - priceUpper: getMaxPrice(), - tokenXAmount: double.tryParse(baseTokenAmountController.text) ?? 0, - ).toString(), - )?.toStringAsFixed(quoteToken.decimals[_cubit.selectedYield!.network.chainId]!); - - final newBaseTokenAmount = Decimal.tryParse( - calculateToken0AmountFromToken1( - currentPrice: currentPrice, - priceLower: getMinPrice(), - priceUpper: getMaxPrice(), - tokenYAmount: double.tryParse(quoteTokenAmountController.text) ?? 0, - ).toString(), - )?.toStringAsFixed(baseToken.decimals[_cubit.selectedYield!.network.chainId]!); - - if (isBaseTokenAmountUserInput) { - if (newQuoteTokenAmount?.isEmptyOrZero ?? true) return quoteTokenAmountController.clear(); - quoteTokenAmountController.text = newQuoteTokenAmount!; - - return; - } - - if (newBaseTokenAmount?.isEmptyOrZero ?? true) return baseTokenAmountController.clear(); - baseTokenAmountController.text = newBaseTokenAmount!; - } - - Future<({String title, Widget? icon, Function()? onPressed})> depositButtonState() async { - final userWalletBaseTokenAmount = await _cubit.getWalletTokenAmount( - baseToken.addresses[_cubit.selectedYield!.network.chainId]!, - network: _cubit.selectedYield!.network, - ); - - final userWalletQuoteTokenAmount = await _cubit.getWalletTokenAmount( - quoteToken.addresses[_cubit.selectedYield!.network.chainId]!, - network: _cubit.selectedYield!.network, - ); - - if (isRangeInvalid) return (title: S.of(context).depositPageInvalidRange, icon: null, onPressed: null); - - if (isBaseTokenNeeded && baseTokenAmountController.text.isEmptyOrZero) { - return ( - title: S.of(context).depositPageInvalidTokenAmount(tokenSymbol: baseToken.symbol), - icon: null, - onPressed: null, - ); - } - - if (isQuoteTokenNeeded && quoteTokenAmountController.text.isEmptyOrZero) { - return ( - title: S.of(context).depositPageInvalidTokenAmount(tokenSymbol: quoteToken.symbol), - icon: null, - onPressed: null, - ); - } - - if (userWalletBaseTokenAmount < (double.tryParse(baseTokenAmountController.text) ?? 0) && isBaseTokenNeeded) { - return ( - title: S.of(context).depositPageInsufficientTokenBalance(tokenSymbol: baseToken.symbol), - icon: null, - onPressed: null, - ); - } - - if (userWalletQuoteTokenAmount < (double.tryParse(quoteTokenAmountController.text) ?? 0) && isQuoteTokenNeeded) { - return ( - title: S.of(context).depositPageInsufficientTokenBalance(tokenSymbol: quoteToken.symbol), - icon: null, - onPressed: null, - ); - } - - return ( - title: S.of(context).preview, - icon: Assets.icons.scrollFill.svg(), - onPressed: () { - PreviewDepositModal( - key: const Key("preview-deposit-modal"), - yieldTimeFrame: selectedYieldTimeFrame, - deadline: selectedDeadline, - maxSlippage: selectedSlippage, - currentYield: _cubit.selectedYield!, - isReversed: areTokensReversed, - token0DepositAmountController: areTokensReversed ? quoteTokenAmountController : baseTokenAmountController, - token1DepositAmountController: areTokensReversed ? baseTokenAmountController : quoteTokenAmountController, - maxPrice: (isInfinity: isMaxRangeInfinity, price: maxPrice), - minPrice: (isInfinity: isMinRangeInfinity, price: minPrice), - ).show(context, currentPriceX96: _cubit.latestPoolSqrtPriceX96 ?? BigInt.zero); - }, - ); - } - - @override - void initState() { - _cubit.setup(); - - final currentNetworkFromUrl = - _navigator.getParam(ZupNavigatorPaths.deposit.routeParamsNames().network) ?? ""; - - yieldsPageController.addListener(() { - WidgetsBinding.instance.addPostFrameCallback((_) { - final currentControllerPage = yieldsPageController.page!.toInt(); - if (currentControllerPage == currentYieldPage) return; - - setState(() => currentYieldPage = currentControllerPage); - }); - }); - - if (currentNetworkFromUrl.isNotEmpty) { - final currentNetwork = AppNetworks.fromValue(currentNetworkFromUrl); - if (currentNetwork != null && currentNetwork != _appCubit.selectedNetwork) { - _appCubit.updateAppNetwork(currentNetwork); - } - } - - WidgetsBinding.instance.addPostFrameCallback((_) { - _cubit.getBestPools( - token0AddressOrId: token0Id, - token1AddressOrId: token1Id, - group0Id: group0Id, - group1Id: group1Id, - ); - }); - - _poolSqrtPriceX96StreamSubscription = _cubit.poolSqrtPriceX96Stream.listen((sqrtPriceX96) { - if (sqrtPriceX96 != null) { - WidgetsBinding.instance.addPostFrameCallback((_) { - setState(() => calculateDepositTokensAmount()); - }); - } - }); - - super.initState(); - } - - @override - void dispose() { - minRangeController.dispose(); - maxRangeController.dispose(); - _poolSqrtPriceX96StreamSubscription?.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Padding( - padding: isMobileSize(context) ? const EdgeInsets.all(20) : EdgeInsets.zero, - child: BlocBuilder( - builder: (context, state) { - return state.maybeWhen( - orElse: () => _buildLoadingState(), - noYields: (filtersApplied) => _buildNoYieldsState(filtersApplied: filtersApplied), - error: () => _buildErrorState(), - success: (yields) => StreamBuilder( - stream: _cubit.selectedYieldStream, - builder: (context, selectedYieldSnapshot) { - return Padding( - padding: EdgeInsets.only(top: isMobileSize(context) ? 20 : 60), - child: Center( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 650), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ZupTextButton( - key: const Key("back-button"), - onPressed: () => _navigator.navigateToNewPosition(), - icon: Assets.icons.arrowLeft.svg(), - label: S.of(context).depositPageBackButtonTitle, - ), - Row( - children: [ - ZupPageTitle(S.of(context).depositPageTitle), - const Spacer(), - const SizedBox(width: 14), - ZupPillButton( - key: const Key("deposit-settings-button"), - backgroundColor: - selectedSlippage.riskBackgroundColor(context.brightness) ?? - ZupThemeColors.tertiaryButtonBackground.themed(context.brightness), - foregroundColor: - selectedSlippage.riskForegroundColor(context.brightness) ?? - ZupColors.brand.lighter(0.3), - title: selectedSlippage.value != DepositSettingsDto.defaultMaxSlippage - ? S - .of(context) - .depositPagePercentSlippage( - valuePercent: selectedSlippage.value.formatPercent, - ) - : null, - onPressed: (buttonContext) => ZupPopover.show( - adjustment: const Offset(0, 10), - showBasedOnContext: buttonContext, - child: DepositSettingsDropdownChild( - context, - selectedDeadline: selectedDeadline, - selectedSlippage: selectedSlippage, - onSettingsChanged: (slippage, deadline) { - _cubit.saveDepositSettings(slippage, deadline); - - setState(() { - selectedDeadline = deadline; - selectedSlippage = slippage; - }); - }, - ), - ), - icon: Assets.icons.gear.svg( - colorFilter: ColorFilter.mode( - ZupThemeColors.background.themed(context.brightness), - BlendMode.srcIn, - ), - height: 20, - width: 20, - ), - ), - ], - ), - const SizedBox(height: 16), - _buildYieldSelectionSector(yields), - const SizedBox(height: 20), - if (selectedYieldSnapshot.data != null) ...[ - _buildSelectRangeSector(), - const SizedBox(height: 20), - _buildDepositSection(), - ], - const SizedBox(height: 200), - ], - ), - ), - ), - ); - }, - ), - ); - }, - ), - ); - } - - Widget _sectionTitle(String title) => Text( - title, - style: TextStyle( - fontSize: 17, - fontWeight: FontWeight.w600, - color: ZupThemeColors.primaryText.themed(context.brightness), - ), - ); - - Widget _buildNoYieldsState({required PoolSearchFiltersDto filtersApplied}) => Center( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const SizedBox(height: 60), - SizedBox( - width: 400, - child: ZupInfoState( - icon: Transform.scale(scale: 3, child: lottieEmpty), - iconSize: 120, - title: S.of(context).depositPageEmptyStateTitle, - description: S.of(context).depositPageEmptyStateDescription, - helpButtonTitle: S.of(context).depositPageEmptyStateHelpButtonTitle, - helpButtonIcon: Assets.icons.arrowLeft.svg(), - onHelpButtonTap: () => _navigator.navigateToNewPosition(), - ), - ), - const SizedBox(height: 60), - if (filtersApplied.minTvlUsd > 0) - Text.rich( - TextSpan( - children: [ - WidgetSpan( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 5), - child: Assets.icons.infoCircle.svg( - colorFilter: const ColorFilter.mode(ZupColors.gray, BlendMode.srcIn), - ), - ), - ), - TextSpan( - text: S - .of(context) - .depositPageMinLiquiditySearchAlert( - minLiquidity: NumberFormat.compactSimpleCurrency().format( - _cubit.poolSearchSettings.minLiquidityUSD, - ), - ), - style: const TextStyle(color: ZupColors.gray, fontSize: 14), - ), - WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: Transform.translate( - offset: const Offset(-2, 0), - child: TextButton( - key: const Key("search-all-pools-button"), - onPressed: () => _cubit.getBestPools( - token0AddressOrId: token0Id, - token1AddressOrId: token1Id, - group0Id: group0Id, - group1Id: group1Id, - ignoreMinLiquidity: true, - ), - style: ButtonStyle(padding: WidgetStateProperty.all(const EdgeInsets.all(6))), - child: Text( - S.of(context).depositPageTrySearchAllPools, - style: const TextStyle(color: ZupColors.brand, fontSize: 14, fontWeight: FontWeight.w500), - ), - ), - ), - ), - ], - ), - ), - ], - ), - ); - - Widget _buildErrorState() => Center( - child: SizedBox( - width: 400, - child: ZupInfoState( - icon: const IgnorePointer( - child: Text(":(", style: TextStyle(color: ZupColors.brand)), - ), - title: S.of(context).depositPageErrorStateTitle, - description: S.of(context).depositPageErrorStateDescription, - helpButtonTitle: S.of(context).letsGiveItAnotherShot, - helpButtonIcon: Assets.icons.arrowClockwise.svg(), - onHelpButtonTap: () => _cubit.getBestPools( - token0AddressOrId: token0Id, - token1AddressOrId: token1Id, - group0Id: group0Id, - group1Id: group1Id, - ), - ), - ), - ); - - Widget _buildLoadingState() { - bool isGroupSearch = group0Id != null || group1Id != null; - - return Container( - color: ZupThemeColors.background.themed(context.brightness), - child: Center( - child: ZupSteppedLoading( - stepDuration: Duration(seconds: isGroupSearch ? 8 : 6), - steps: [ - ZupSteppedLoadingStep( - title: S.of(context).depositPageLoadingStep1Title, - description: S.of(context).depositPageLoadingStep1Description, - icon: ColorFiltered( - colorFilter: const ColorFilter.mode(ZupColors.brand, BlendMode.srcIn), - child: lottieMatching, - ), - iconSize: 200, - ), - ZupSteppedLoadingStep( - title: S.of(context).depositPageLoadingStep2Title, - description: S.of(context).depositPageLoadingStep2Description, - icon: ColorFiltered( - colorFilter: const ColorFilter.mode(ZupColors.brand, BlendMode.srcIn), - child: lottieRadar, - ), - iconSize: 200, - ), - ZupSteppedLoadingStep( - title: S.of(context).depositPageLoadingStep3Title, - description: S.of(context).depositPageLoadingStep3Description, - icon: ColorFiltered( - colorFilter: const ColorFilter.mode(ZupColors.brand, BlendMode.srcIn), - child: lottieNumbers, - ), - iconSize: 200, - ), - ZupSteppedLoadingStep( - title: S.of(context).depositPageLoadingStep4Title, - description: S.of(context).depositPageLoadingStep4Description, - icon: ColorFiltered( - colorFilter: const ColorFilter.mode(ZupColors.brand, BlendMode.srcIn), - child: lottieSearching, - ), - iconSize: 200, - ), - ], - ), - ), - ); - } - - Widget _buildYieldSelectionSector(YieldsDto yields) { - final poolsCount = yields.poolsSortedByTimeframe(selectedYieldTimeFrame).length; - final yieldCardsPerPage = isMobileSize(context) ? 1 : 2; - final yieldsPagesCount = (poolsCount / yieldCardsPerPage).ceil(); - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 10), - decoration: BoxDecoration( - color: ZupThemeColors.tertiaryButtonBackground.themed(context.brightness), - borderRadius: BorderRadius.circular(12), - ), - child: Wrap( - runSpacing: 10, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - Text( - S.of(context).depositPageBestYieldsIn, - style: TextStyle( - color: ZupThemeColors.primaryText.themed(context.brightness), - fontSize: 14, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(width: 5), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CupertinoSlidingSegmentedControl( - proportionalWidth: true, - onValueChanged: (timeframe) { - setState(() { - selectedYieldTimeFrame = timeframe ?? YieldTimeFrame.day; - }); - - yieldsPageController.jumpToPage(0); - }, - groupValue: selectedYieldTimeFrame, - children: Map.fromEntries( - YieldTimeFrame.values.map( - (timeframe) => MapEntry( - timeframe, - IgnorePointer( - key: Key("${timeframe.name}-timeframe-button"), - child: Text( - timeframe.compactDaysLabel(context), - style: TextStyle( - color: ZupThemeColors.primaryText.themed(context.brightness), - fontSize: 14, - fontWeight: FontWeight.w600, - ), - ), - ) - .animatedHover(animationValue: 0.2, type: ZupAnimatedHoverType.opacity) - .animatedHover(animationValue: 0.95, type: ZupAnimatedHoverType.scale), - ), - ), - ), - ), - const SizedBox(width: 10), - ZupTooltip.text( - key: const Key("timeframe-tooltip"), - message: S.of(context).depositPageTimeFrameTooltipMessage, - child: Assets.icons.infoCircle.svg( - colorFilter: const ColorFilter.mode(ZupColors.gray, BlendMode.srcIn), - ), - ), - ], - ), - ], - ), - ), - const SizedBox(height: 5), - - SizedBox( - height: 150, - child: PageView.builder( - physics: isMobileSize(context) ? const BouncingScrollPhysics() : const NeverScrollableScrollPhysics(), - controller: yieldsPageController, - pageSnapping: true, - padEnds: false, - scrollDirection: Axis.horizontal, - itemCount: yieldsPagesCount, - itemBuilder: (_, pageIndex) { - final startIndex = pageIndex * yieldCardsPerPage; - final endIndex = (startIndex + yieldCardsPerPage).clamp(0, poolsCount); - - final yieldsInThisPage = yields - .poolsSortedByTimeframe(selectedYieldTimeFrame) - .sublist(startIndex, endIndex); - - return Padding( - padding: const EdgeInsets.all(5), - child: Row( - mainAxisSize: MainAxisSize.min, - spacing: 5, - children: yieldsInThisPage - .map( - (yieldItem) => Expanded( - child: YieldCard( - key: Key("yield-card-${yieldItem.poolAddress}"), - isHotestYield: yieldItem.equals( - yields.poolsSortedByTimeframe(selectedYieldTimeFrame).first, - ), - currentYield: yieldItem, - onChangeSelection: (yield) { - selectYield(yield); - }, - isSelected: _cubit.selectedYield.equals(yieldItem), - timeFrame: selectedYieldTimeFrame, - ), - ), - ) - .toList(), - ), - ); - }, - ), - ), - const SizedBox(height: 10), - Row( - children: [ - ZupIconButton( - key: const Key("previous-yield-page-button"), - icon: Assets.icons.arrowLeft.svg(height: 12, width: 12), - padding: const EdgeInsets.all(10), - onPressed: (_) async { - yieldsPageController.previousPage( - duration: const Duration(milliseconds: 400), - curve: Curves.fastEaseInToSlowEaseOut, - ); - }, - ), - const SizedBox(width: 10), - - ZupIconButton( - key: const Key("next-yield-page-button"), - padding: const EdgeInsets.all(10), - icon: Assets.icons.arrowRight.svg(height: 12, width: 12), - onPressed: (_) async { - yieldsPageController.nextPage( - duration: const Duration(milliseconds: 400), - curve: Curves.fastEaseInToSlowEaseOut, - ); - }, - ), - const Spacer(), - - Row( - children: List.generate( - (currentYieldPage + 4).clamp(0, yieldsPagesCount).ceil(), - (index) => AnimatedContainer( - key: Key("yield-page-indicator-$index"), - duration: const Duration(milliseconds: 200), - height: (index != currentYieldPage) ? 8 : 12, - width: (index != currentYieldPage) ? 8 : 12, - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: () async { - yieldsPageController.animateToPage( - index, - duration: const Duration(milliseconds: 600), - curve: Curves.decelerate, - ); - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 2), - child: CircleAvatar( - backgroundColor: (currentYieldPage.truncate() == index) - ? ZupColors.brand - : ZupThemeColors.disabledButtonBackground.themed(context.brightness), - ), - ), - ).animatedHover(animationValue: index != currentYieldPage ? 4 : 1), - ), - ), - ), - ), - ], - ), - const SizedBox(height: 10), - if (_cubit.poolSearchSettings.minLiquidityUSD > 0) - Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Assets.icons.infoCircle.svg( - height: 14, - colorFilter: const ColorFilter.mode(ZupColors.gray, BlendMode.srcIn), - ), - const SizedBox(width: 5), - Flexible( - fit: FlexFit.loose, - child: Text( - yields.filters.minTvlUsd > 0 - ? "${S.of(context).depositPageShowingOnlyPoolsWithMoreThan(minLiquidity: NumberFormat.compactSimpleCurrency().format(_cubit.poolSearchSettings.minLiquidityUSD))} " - : "${S.of(context).depositPageShowingAllPools} ", - style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14, color: ZupColors.gray), - ), - ), - ], - ), - Transform.translate( - offset: const Offset(-6, 0), - child: TextButton( - key: const Key("hide-show-all-pools-button"), - onPressed: () => _cubit.getBestPools( - token0AddressOrId: token0Id, - token1AddressOrId: token1Id, - group0Id: group0Id, - group1Id: group1Id, - ignoreMinLiquidity: yields.filters.minTvlUsd > 0, - ), - style: ButtonStyle(padding: WidgetStateProperty.all(const EdgeInsets.all(6))), - child: Text( - yields.filters.minTvlUsd > 0 - ? S.of(context).depositPageSearchAllPools - : S - .of(context) - .depositPageSearchOnlyForPoolsWithMorethan( - minLiquidity: NumberFormat.compactSimpleCurrency().format( - _cubit.poolSearchSettings.minLiquidityUSD, - ), - ), - style: const TextStyle(color: ZupColors.brand, fontSize: 14, fontWeight: FontWeight.w600), - ), - ), - ), - ], - ), - if (_cubit.selectedYield == null) ...[ - const SizedBox(height: 60), - Center( - child: ZupInfoState( - iconSize: 90, - icon: lottieClick, - title: S.of(context).depositPageNoYieldSelectedTitle, - description: S.of(context).depositPageNoYieldSelectedDescription, - ), - ), - ], - ], - ); - } - - Widget _buildSelectRangeSector() { - Widget tokenSwitcher = CupertinoSlidingSegmentedControl( - groupValue: areTokensReversed, - children: { - false: MouseRegion( - key: const Key("reverse-tokens-not-reversed"), - cursor: SystemMouseCursors.click, - child: IgnorePointer( - ignoring: true, - child: Text( - "${_cubit.selectedYield?.token0.symbol} / ${_cubit.selectedYield?.token1.symbol}", - style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500), - ), - ), - ), - true: MouseRegion( - key: const Key("reverse-tokens-reversed"), - cursor: SystemMouseCursors.click, - child: IgnorePointer( - ignoring: true, - child: Text( - "${_cubit.selectedYield?.token1.symbol} / ${_cubit.selectedYield?.token0.symbol}", - style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500), - ), - ), - ), - }, - onValueChanged: (isReversed) { - switchTokens(isReversed ?? false); - }, - ); - - return Column( - key: selectRangeSectorKey, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - _sectionTitle(S.of(context).depositPageRangeSectionTitle), - const SizedBox(width: 12), - const Spacer(), - if (!isMobileSize(context)) tokenSwitcher, - ], - ), - if (isMobileSize(context)) ...[const SizedBox(height: 5), tokenSwitcher, const SizedBox(height: 5)], - const SizedBox(height: 10), - StreamBuilder( - stream: _cubit.poolSqrtPriceX96Stream, - initialData: _cubit.latestPoolSqrtPriceX96, - builder: (context, poolSqrtPriceX96Snaphot) { - return Text( - "1 ${baseToken.symbol} ≈ ${() { - final currentPrice = sqrtPriceX96ToPrice(sqrtPriceX96: poolSqrtPriceX96Snaphot.data ?? BigInt.zero, poolToken0Decimals: _cubit.selectedYield!.token0NetworkDecimals, poolToken1Decimals: _cubit.selectedYield!.token1NetworkDecimals); - - return areTokensReversed ? currentPrice.token1PerToken0 : currentPrice.token0PerToken1; - }.call().formatCurrency(useLessThan: true, maxDecimals: 4, isUSD: false)} ${quoteToken.symbol}", - style: TextStyle( - fontSize: 17, - fontWeight: FontWeight.w500, - color: ZupThemeColors.primaryText.themed(context.brightness), - ), - ).redacted(enabled: poolSqrtPriceX96Snaphot.data == null); - }, - ), - const SizedBox(height: 10), - Wrap( - spacing: 10, - runSpacing: 10, - children: [ - ZupMiniButton( - key: const Key("full-range-button"), - onPressed: (_) => setFullRange(), - isSelected: isMaxRangeInfinity && isMinRangeInfinity, - title: S.of(context).depositPageRangeSectionFullRange, - icon: Assets.icons.circleDotted.svg(), - ), - ZupMiniButton( - key: const Key("5-percent-range-button"), - onPressed: (_) => setPercentageRange(5), - isSelected: percentRange == 5, - title: "5%", - icon: Assets.icons.plusminus.svg(), - // alignLeft: true, - ), - ZupMiniButton( - key: const Key("20-percent-range-button"), - onPressed: (_) => setPercentageRange(20), - isSelected: percentRange == 20, - title: "20%", - icon: Assets.icons.plusminus.svg(), - // alignLeft: true, - ), - ZupMiniButton( - key: const Key("50-percent-range-button"), - onPressed: (_) => setPercentageRange(50), - isSelected: percentRange == 50, - title: "50%", - icon: Assets.icons.plusminus.svg(), - // alignLeft: true, - ), - ], - ), - const SizedBox(height: 10), - StreamBuilder( - stream: _cubit.poolSqrtPriceX96Stream, - builder: (context, _) { - return RangeSelector( - key: const Key("min-price-selector"), - onUserType: () => percentRange = null, - onPriceChanged: (price) { - setState(() { - if (price == 0) { - isMinRangeInfinity = true; - - return calculateDepositTokensAmount(); - } - - isMinRangeInfinity = false; - minPrice = price; - calculateDepositTokensAmount(); - }); - }, - initialPrice: minPrice, - poolToken0Decimals: _cubit.selectedYield!.token0NetworkDecimals, - poolToken1Decimals: _cubit.selectedYield!.token1NetworkDecimals, - isReversed: areTokensReversed, - displayBaseTokenSymbol: baseToken.symbol, - displayQuoteTokenSymbol: quoteToken.symbol, - tickSpacing: _cubit.selectedYield!.tickSpacing, - type: RangeSelectorType.minPrice, - isInfinity: isMinRangeInfinity, - rangeController: minRangeController, - state: () { - if (isOutOfRange.minPrice) { - return RangeSelectorState( - type: RangeSelectorStateType.warning, - message: S.of(context).depositPageMinRangeOutOfRangeWarningText, - ); - } - - return const RangeSelectorState(type: RangeSelectorStateType.regular); - }.call(), - ); - }, - ), - const SizedBox(height: 6), - StreamBuilder( - stream: _cubit.poolSqrtPriceX96Stream, - builder: (context, _) { - return RangeSelector( - key: const Key("max-price-selector"), - displayBaseTokenSymbol: baseToken.symbol, - displayQuoteTokenSymbol: quoteToken.symbol, - onUserType: () => percentRange = null, - onPriceChanged: (price) { - setState(() { - if (price == 0) { - isMaxRangeInfinity = true; - - return calculateDepositTokensAmount(); - } - - isMaxRangeInfinity = false; - maxPrice = price; - - calculateDepositTokensAmount(); - }); - }, - type: RangeSelectorType.maxPrice, - isInfinity: isMaxRangeInfinity, - initialPrice: maxPrice, - poolToken0Decimals: _cubit.selectedYield!.token0NetworkDecimals, - poolToken1Decimals: _cubit.selectedYield!.token1NetworkDecimals, - isReversed: areTokensReversed, - tickSpacing: _cubit.selectedYield!.tickSpacing, - rangeController: maxRangeController, - state: () { - if (isRangeInvalid) { - return RangeSelectorState( - type: RangeSelectorStateType.error, - message: S.of(context).depositPageInvalidRangeErrorText, - ); - } - - if (isOutOfRange.maxPrice) { - return RangeSelectorState( - type: RangeSelectorStateType.warning, - message: S.of(context).depositPageMaxRangeOutOfRangeWarningText, - ); - } - - return const RangeSelectorState(type: RangeSelectorStateType.regular); - }.call(), - ); - }, - ), - ], - ); - } - - Widget _buildDepositSection() => IgnorePointer( - key: const Key("deposit-section"), - ignoring: isRangeInvalid, - child: AnimatedOpacity( - duration: const Duration(milliseconds: 300), - opacity: isRangeInvalid ? 0.2 : 1, - child: StreamBuilder( - stream: _cubit.poolSqrtPriceX96Stream, - initialData: _cubit.latestPoolSqrtPriceX96, - builder: (context, sqrtPriceX96Snapshot) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _sectionTitle(S.of(context).depositPageDepositSectionTitle), - const SizedBox(height: 12), - TokenAmountInputCard( - key: const Key("base-token-input-card"), - token: baseToken, - isNative: baseToken.addresses[_cubit.selectedYield!.network.chainId]!.lowercasedEquals( - EthereumConstants.zeroAddress, - ), - onRefreshBalance: () => setState(() {}), - disabledText: () { - if (!isBaseTokenNeeded) { - return S.of(context).depositPageDepositSectionTokenNotNeeded(tokenSymbol: baseToken.symbol); - } - - if (!isBaseTokenAmountUserInput && - !sqrtPriceX96Snapshot.hasData && - quoteTokenAmountController.text.isNotEmpty) { - return S.of(context).loading; - } - }.call(), - onInput: (amount) { - setState(() { - isBaseTokenAmountUserInput = true; - - calculateDepositTokensAmount(); - }); - }, - controller: baseTokenAmountController, - network: _cubit.selectedYield!.network, - ), - const SizedBox(height: 6), - TokenAmountInputCard( - key: const Key("quote-token-input-card"), - token: quoteToken, - isNative: quoteToken.addresses[_cubit.selectedYield!.network.chainId]!.lowercasedEquals( - EthereumConstants.zeroAddress, - ), - onRefreshBalance: () => setState(() {}), - disabledText: () { - if (!isQuoteTokenNeeded) { - return S.of(context).depositPageDepositSectionTokenNotNeeded(tokenSymbol: quoteToken.symbol); - } - - if (isBaseTokenAmountUserInput && - !sqrtPriceX96Snapshot.hasData && - baseTokenAmountController.text.isNotEmpty) { - return S.of(context).loading; - } - }.call(), - onInput: (amount) { - setState(() { - isBaseTokenAmountUserInput = false; - - calculateDepositTokensAmount(); - }); - }, - controller: quoteTokenAmountController, - network: _cubit.selectedYield!.network, - ), - const SizedBox(height: 20), - Row( - children: [ - Expanded( - child: StreamBuilder( - key: const Key("deposit-button"), - stream: wallet.signerStream, - initialData: wallet.signer, - builder: (context, signerSnapshot) { - if (!signerSnapshot.hasData) { - return ZupPrimaryButton( - width: double.maxFinite, - title: S.of(context).connectWallet, - icon: Assets.icons.walletBifold.svg(), - fixedIcon: true, - alignCenter: true, - hoverElevation: 0, - backgroundColor: ZupColors.brand.withValues(alpha: 0.1), - foregroundColor: ZupColors.brand, - onPressed: (buttonContext) => ConnectModal().show(context), - ); - } - - return FutureBuilder( - future: depositButtonState(), - builder: (context, stateSnapshot) { - return ZupPrimaryButton( - alignCenter: true, - title: stateSnapshot.data?.title ?? "Loading...", - icon: stateSnapshot.data?.icon, - isLoading: stateSnapshot.connectionState == ConnectionState.waiting, - fixedIcon: true, - onPressed: stateSnapshot.data?.onPressed == null - ? null - : (buttonContext) => stateSnapshot.data?.onPressed!(), - width: double.maxFinite, - ); - }, - ); - }, - ), - ), - ], - ), - ], - ); - }, - ), - ), - ); -} diff --git a/lib/app/create/deposit/deposit_cubit.dart b/lib/app/create/yields/[id]/deposit/deposit_cubit.dart similarity index 52% rename from lib/app/create/deposit/deposit_cubit.dart rename to lib/app/create/yields/[id]/deposit/deposit_cubit.dart index feec52d..63aedab 100644 --- a/lib/app/create/deposit/deposit_cubit.dart +++ b/lib/app/create/yields/[id]/deposit/deposit_cubit.dart @@ -3,20 +3,19 @@ import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:web3kit/web3kit.dart'; -import 'package:zup_app/app/app_cubit/app_cubit.dart'; import 'package:zup_app/core/cache.dart'; import 'package:zup_app/core/concentrated_liquidity_utils/cl_pool_conversors_mixin.dart'; +import 'package:zup_app/core/dtos/deposit_page_arguments_dto.dart'; import 'package:zup_app/core/dtos/deposit_settings_dto.dart'; -import 'package:zup_app/core/dtos/pool_search_filters_dto.dart'; import 'package:zup_app/core/dtos/pool_search_settings_dto.dart'; import 'package:zup_app/core/dtos/yield_dto.dart'; -import 'package:zup_app/core/dtos/yields_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; import 'package:zup_app/core/mixins/keys_mixin.dart'; import 'package:zup_app/core/pool_service.dart'; import 'package:zup_app/core/repositories/yield_repository.dart'; import 'package:zup_app/core/slippage.dart'; -import 'package:zup_app/core/zup_analytics.dart'; +import 'package:zup_app/core/zup_navigator.dart'; +import 'package:zup_app/core/zup_route_params_names.dart'; import 'package:zup_core/zup_core.dart'; part 'deposit_cubit.freezed.dart'; @@ -28,30 +27,40 @@ class DepositCubit extends Cubit with KeysMixin, CLPoolConversorsM this._zupSingletonCache, this._wallet, this._cache, - this._appCubit, - this._zupAnalytics, this._poolService, - ) : super(const DepositState.initial()); + this._navigator, + ) : super(const DepositState.initial()) { + final yieldPoolFromArguments = DepositPageArgumentsDto.fromJson(_navigator.currentPageArguments).yieldPool; + + if (yieldPoolFromArguments != null) { + yieldPool = yieldPoolFromArguments; + _latestPoolSqrtPriceX96 = BigInt.parse(yieldPoolFromArguments.latestSqrtPriceX96); + + Future.microtask( + () => _poolSqrtPriceX96StreamController.add(BigInt.parse(yieldPoolFromArguments.latestSqrtPriceX96)), + ); + + emit(DepositState.success(yieldPoolFromArguments)); + } else { + fetchCurrentPoolInfo(); + } + } final YieldRepository _yieldRepository; + final ZupNavigator _navigator; final ZupSingletonCache _zupSingletonCache; final Wallet _wallet; final PoolService _poolService; final Cache _cache; - final AppCubit _appCubit; - final ZupAnalytics _zupAnalytics; final StreamController _poolSqrtPriceX96StreamController = StreamController.broadcast(); - final StreamController _selectedYieldStreamController = StreamController.broadcast(); final Duration _poolSqrtPriceX96CacheExpiration = const Duration(seconds: 30); BigInt? _latestPoolSqrtPriceX96; - YieldDto? _selectedYield; + YieldDto? yieldPool; - late final Stream selectedYieldStream = _selectedYieldStreamController.stream; late final Stream poolSqrtPriceX96Stream = _poolSqrtPriceX96StreamController.stream; - YieldDto? get selectedYield => _selectedYield; BigInt? get latestPoolSqrtPriceX96 => _latestPoolSqrtPriceX96; DepositSettingsDto get depositSettings => _cache.getDepositSettings(); PoolSearchSettingsDto get poolSearchSettings => _cache.getPoolSearchSettings(); @@ -60,83 +69,43 @@ class DepositCubit extends Cubit with KeysMixin, CLPoolConversorsM Timer.periodic(_poolSqrtPriceX96CacheExpiration, (timer) { if (_poolSqrtPriceX96StreamController.isClosed) return timer.cancel(); - if (selectedYield != null) getSelectedPoolSqrtPriceX96(); + if (yieldPool != null) getPoolSqrtPriceX96(); }); } - Future getBestPools({ - required String? token0AddressOrId, - required String? token1AddressOrId, - required String? group0Id, - required String? group1Id, - bool ignoreMinLiquidity = false, - }) async { + Future fetchCurrentPoolInfo() async { try { - _zupAnalytics.logSearch( - token0: token0AddressOrId, - token1: token1AddressOrId, - group0: group0Id, - group1: group1Id, - network: _appCubit.selectedNetwork.label, - ); - emit(const DepositState.loading()); - final yields = _appCubit.selectedNetwork.isAllNetworks - ? await _yieldRepository.getAllNetworksYield( - blockedProtocolIds: _cache.blockedProtocolsIds, - token0InternalId: token0AddressOrId, - token1InternalId: token1AddressOrId, - group0Id: group0Id, - group1Id: group1Id, - searchSettings: ignoreMinLiquidity ? poolSearchSettings.copyWith(minLiquidityUSD: 0) : poolSearchSettings, - testnetMode: _appCubit.isTestnetMode, - ) - : await _yieldRepository.getSingleNetworkYield( - blockedProtocolIds: _cache.blockedProtocolsIds, - token0Address: token0AddressOrId, - token1Address: token1AddressOrId, - group0Id: group0Id, - group1Id: group1Id, - network: _appCubit.selectedNetwork, - searchSettings: ignoreMinLiquidity ? poolSearchSettings.copyWith(minLiquidityUSD: 0) : poolSearchSettings, - ); - - if (yields.isEmpty) { - return emit(DepositState.noYields(filtersApplied: yields.filters)); - } - - emit(DepositState.success(yields)); - } catch (e) { - emit(const DepositState.error()); - } - } - Future selectYield(YieldDto? yieldDto) async { - _selectedYield = yieldDto; - _selectedYieldStreamController.add(selectedYield); + final poolNetwork = AppNetworks.fromValue(_navigator.getQueryParam(DepositRouteParamsNames().network) ?? ""); + final poolAddress = _navigator.getIdFromPath; + final parseWrappedToNative = + bool.tryParse(_navigator.getQueryParam(DepositRouteParamsNames().parseWrappedToNative).toString()) ?? true; - if (selectedYield != null) { - _latestPoolSqrtPriceX96 = BigInt.parse(yieldDto!.latestSqrtPriceX96); - _poolSqrtPriceX96StreamController.add(_latestPoolSqrtPriceX96); + final pool = await _yieldRepository.getPoolInfo( + poolAddress: poolAddress!, + poolNetwork: poolNetwork!, + parseWrappedToNative: parseWrappedToNative, + ); - await getSelectedPoolSqrtPriceX96(forceRefresh: true); - } - } + yieldPool = pool; - Future getSelectedPoolSqrtPriceX96({bool forceRefresh = false}) async { - if (selectedYield == null) return; + await getPoolSqrtPriceX96(); - final selectedYieldBeforeCall = selectedYield; + emit(DepositState.success(pool)); + } catch (e) { + emit(const DepositState.error()); + } + } + Future getPoolSqrtPriceX96({bool forceRefresh = false}) async { final sqrtPriceX96 = await _zupSingletonCache.run( - () => _poolService.getSqrtPriceX96(selectedYieldBeforeCall!), + () => _poolService.getSqrtPriceX96(yieldPool!), expiration: _poolSqrtPriceX96CacheExpiration - const Duration(seconds: 1), ignoreCache: forceRefresh, - key: poolSqrtPriceCacheKey(network: selectedYield!.network, poolAddress: selectedYield!.poolAddress), + key: poolSqrtPriceCacheKey(network: yieldPool!.network, poolAddress: yieldPool!.poolAddress), ); - if (selectedYieldBeforeCall != selectedYield) return await getSelectedPoolSqrtPriceX96(); - _poolSqrtPriceX96StreamController.add(sqrtPriceX96); _latestPoolSqrtPriceX96 = sqrtPriceX96; } @@ -173,7 +142,6 @@ class DepositCubit extends Cubit with KeysMixin, CLPoolConversorsM @override Future close() async { await _poolSqrtPriceX96StreamController.close(); - await _selectedYieldStreamController.close(); return super.close(); } } diff --git a/lib/app/create/yields/[id]/deposit/deposit_page.dart b/lib/app/create/yields/[id]/deposit/deposit_page.dart new file mode 100644 index 0000000..da047e3 --- /dev/null +++ b/lib/app/create/yields/[id]/deposit/deposit_page.dart @@ -0,0 +1,887 @@ +import 'dart:async'; + +import 'package:decimal/decimal.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_animate/flutter_animate.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:web3kit/web3kit.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/deposit_cubit.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/widgets/deposit_settings_dropdown_child.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/widgets/preview_deposit_modal/preview_deposit_modal.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/widgets/range_selector.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/widgets/token_amount_input_card/token_amount_input_card.dart'; +import 'package:zup_app/core/cache.dart'; +import 'package:zup_app/core/concentrated_liquidity_utils/cl_pool_constants.dart'; +import 'package:zup_app/core/concentrated_liquidity_utils/cl_pool_conversors_mixin.dart'; +import 'package:zup_app/core/concentrated_liquidity_utils/cl_pool_liquidity_calculations_mixin.dart'; +import 'package:zup_app/core/concentrated_liquidity_utils/cl_sqrt_price_math_mixin.dart'; +import 'package:zup_app/core/dtos/deposit_settings_dto.dart'; +import 'package:zup_app/core/dtos/token_dto.dart'; +import 'package:zup_app/core/dtos/yield_dto.dart'; +import 'package:zup_app/core/enums/networks.dart'; +import 'package:zup_app/core/enums/yield_timeframe.dart'; +import 'package:zup_app/core/extensions/num_extension.dart'; +import 'package:zup_app/core/extensions/string_extension.dart'; +import 'package:zup_app/core/extensions/widget_extension.dart'; +import 'package:zup_app/core/injections.dart'; +import 'package:zup_app/core/mixins/keys_mixin.dart'; +import 'package:zup_app/core/pool_service.dart'; +import 'package:zup_app/core/repositories/yield_repository.dart'; +import 'package:zup_app/core/slippage.dart'; +import 'package:zup_app/core/zup_navigator.dart'; +import 'package:zup_app/core/zup_route_params_names.dart'; +import 'package:zup_app/gen/assets.gen.dart'; +import 'package:zup_app/l10n/gen/app_localizations.dart'; +import 'package:zup_app/widgets/yield_card.dart'; +import 'package:zup_app/widgets/zup_skeletonizer.dart'; +import 'package:zup_core/zup_core.dart'; +import 'package:zup_ui_kit/zup_ui_kit.dart'; + +Route routeBuilder(BuildContext context, RouteSettings settings) { + return PageRouteBuilder( + settings: settings, + transitionDuration: const Duration(milliseconds: 400), + pageBuilder: (context, a1, a2) => BlocProvider( + create: (context) => DepositCubit( + inject(), + inject(), + inject(), + inject(), + inject(), + inject(), + ), + child: Container(color: ZupThemeColors.background.themed(context.brightness), child: const DepositPage()), + ), + transitionsBuilder: (_, a1, a2, child) => SlideTransition( + position: Tween(begin: const Offset(0, 0.2), end: const Offset(0, 0)).animate(a1), + child: FadeTransition(opacity: a1, child: child), + ), + ); +} + +class DepositPage extends StatefulWidget { + const DepositPage({super.key}); + + @override + State createState() => _DepositPageState(); +} + +class _DepositPageState extends State + with + CLPoolConversorsMixin, + CLPoolLiquidityCalculationsMixin, + DeviceInfoMixin, + CLPoolLiquidityCalculationsMixin, + CLSqrtPriceMath, + KeysMixin { + final appScrollController = inject(instanceName: InjectInstanceNames.appScrollController); + + final baseTokenAmountController = TextEditingController(); + final quoteTokenAmountController = TextEditingController(); + final wallet = inject(); + + ZupNavigator get _navigator => inject(); + DepositCubit get _cubit => context.read(); + + AppNetworks get networkFromUrl => + AppNetworks.fromValue(_navigator.getQueryParam(DepositRouteParamsNames().network)!) ?? AppNetworks.mainnet; + + YieldTimeFrame get yieldTimeFrameFromUrl => + YieldTimeFrame.fromValue(_navigator.getQueryParam(DepositRouteParamsNames().timeframe)!) ?? YieldTimeFrame.day; + + TokenDto get baseToken { + return areTokensReversed ? _cubit.yieldPool!.token1 : _cubit.yieldPool!.token0; + } + + TokenDto get quoteToken { + return areTokensReversed ? _cubit.yieldPool!.token0 : _cubit.yieldPool!.token1; + } + + double desktopYieldCardTopPadding = 175; + bool areTokensReversed = false; + bool isMaxRangeInfinity = true; + bool isMinRangeInfinity = true; + bool isBaseTokenAmountUserInput = false; + double? percentRange; + double minPrice = 0; + double maxPrice = 0; + RangeController minRangeController = RangeController(); + RangeController maxRangeController = RangeController(); + StreamSubscription? _poolSqrtPriceX96StreamSubscription; + YieldTimeFrame yieldPoolTimeFrame = YieldTimeFrame.day; + + late Slippage selectedSlippage = _cubit.depositSettings.slippage; + late Duration selectedDeadline = _cubit.depositSettings.deadline; + + bool get shouldYieldCardBeInColumn => MediaQuery.sizeOf(context).width < 750; + + bool get isRangeInvalid { + if (isMaxRangeInfinity || isMinRangeInfinity) return false; + + return (minPrice) >= (maxPrice); + } + + bool get isBaseTokenNeeded => !isOutOfRange.maxPrice; + bool get isQuoteTokenNeeded => !isOutOfRange.minPrice; + + double get currentPrice { + if (_cubit.latestPoolSqrtPriceX96 == null) return 0; + + final price = sqrtPriceX96ToPrice( + sqrtPriceX96: _cubit.latestPoolSqrtPriceX96!, + poolToken0Decimals: _cubit.yieldPool!.token0NetworkDecimals, + poolToken1Decimals: _cubit.yieldPool!.token1NetworkDecimals, + ); + + return areTokensReversed ? price.token1PerToken0 : price.token0PerToken1; + } + + ({bool minPrice, bool maxPrice, bool any}) get isOutOfRange { + if (_cubit.latestPoolSqrtPriceX96 == null) return (minPrice: false, maxPrice: false, any: false); + + final isMinPriceOutOfRange = !isMinRangeInfinity && (minPrice) > currentPrice; + final isMaxPriceOutOfRange = !isMaxRangeInfinity && (maxPrice) < currentPrice; + + return ( + minPrice: isMinPriceOutOfRange, + maxPrice: isMaxPriceOutOfRange, + any: isMinPriceOutOfRange || isMaxPriceOutOfRange, + ); + } + + void setFullRange() { + setState(() { + percentRange = null; + isMinRangeInfinity = true; + isMaxRangeInfinity = true; + }); + + minPrice = 0; + maxPrice = 0; + + calculateDepositTokensAmount(); + } + + void setPercentageRange(double percentage) { + if (currentPrice == 0) return; + + setState(() { + percentRange = percentage; + isMinRangeInfinity = false; + isMaxRangeInfinity = false; + + final percentageDecimals = percentage / 100; + final percentageDifference = currentPrice * percentageDecimals; + + minPrice = currentPrice - percentageDifference; + maxPrice = currentPrice + percentageDifference; + + minRangeController.setRange(minPrice); + maxRangeController.setRange(maxPrice); + + calculateDepositTokensAmount(); + }); + } + + void switchTokens(bool isReversed) { + setState(() => areTokensReversed = isReversed); + + final currentBaseTokenDepositAmount = baseTokenAmountController.text; + final currentQuoteTokenDepositAmount = quoteTokenAmountController.text; + + baseTokenAmountController.text = currentQuoteTokenDepositAmount; + quoteTokenAmountController.text = currentBaseTokenDepositAmount; + isBaseTokenAmountUserInput = isReversed && !isBaseTokenAmountUserInput; + + if (percentRange != null) setPercentageRange(percentRange!); + calculateDepositTokensAmount(); + } + + void calculateDepositTokensAmount() { + if (_cubit.latestPoolSqrtPriceX96 == null) return; + + if (isOutOfRange.minPrice) return quoteTokenAmountController.clear(); + if (isOutOfRange.maxPrice) return baseTokenAmountController.clear(); + + final maxTickPrice = tickToPrice( + tick: CLPoolConstants.maxTick, + poolToken0Decimals: _cubit.yieldPool!.token0NetworkDecimals, + poolToken1Decimals: _cubit.yieldPool!.token1NetworkDecimals, + ); + + final minTickPrice = tickToPrice( + tick: CLPoolConstants.minTick, + poolToken0Decimals: _cubit.yieldPool!.token0NetworkDecimals, + poolToken1Decimals: _cubit.yieldPool!.token1NetworkDecimals, + ); + + double getMinPrice() { + if (minPrice != 0 && !isMinRangeInfinity) return minPrice; + + return areTokensReversed ? maxTickPrice.priceAsQuoteToken : minTickPrice.priceAsBaseToken; + } + + double getMaxPrice() { + if (maxPrice != 0 && !isMaxRangeInfinity) return maxPrice; + + return areTokensReversed ? minTickPrice.priceAsQuoteToken : maxTickPrice.priceAsBaseToken; + } + + final newQuoteTokenAmount = Decimal.tryParse( + calculateToken1AmountFromToken0( + currentPrice: currentPrice, + priceLower: getMinPrice(), + priceUpper: getMaxPrice(), + tokenXAmount: double.tryParse(baseTokenAmountController.text) ?? 0, + ).toString(), + )?.toStringAsFixed(quoteToken.decimals[_cubit.yieldPool!.network.chainId]!); + + final newBaseTokenAmount = Decimal.tryParse( + calculateToken0AmountFromToken1( + currentPrice: currentPrice, + priceLower: getMinPrice(), + priceUpper: getMaxPrice(), + tokenYAmount: double.tryParse(quoteTokenAmountController.text) ?? 0, + ).toString(), + )?.toStringAsFixed(baseToken.decimals[_cubit.yieldPool!.network.chainId]!); + + if (isBaseTokenAmountUserInput) { + if (newQuoteTokenAmount?.isEmptyOrZero ?? true) return quoteTokenAmountController.clear(); + quoteTokenAmountController.text = newQuoteTokenAmount!; + + return; + } + + if (newBaseTokenAmount?.isEmptyOrZero ?? true) return baseTokenAmountController.clear(); + baseTokenAmountController.text = newBaseTokenAmount!; + } + + Future<({String title, Widget? icon, Function()? onPressed})> depositButtonState() async { + final userWalletBaseTokenAmount = await _cubit.getWalletTokenAmount( + baseToken.addresses[_cubit.yieldPool!.network.chainId]!, + network: _cubit.yieldPool!.network, + ); + + final userWalletQuoteTokenAmount = await _cubit.getWalletTokenAmount( + quoteToken.addresses[_cubit.yieldPool!.network.chainId]!, + network: _cubit.yieldPool!.network, + ); + + if (isRangeInvalid) return (title: S.of(context).depositPageInvalidRange, icon: null, onPressed: null); + + if (isBaseTokenNeeded && baseTokenAmountController.text.isEmptyOrZero) { + return ( + title: S.of(context).depositPageInvalidTokenAmount(tokenSymbol: baseToken.symbol), + icon: null, + onPressed: null, + ); + } + + if (isQuoteTokenNeeded && quoteTokenAmountController.text.isEmptyOrZero) { + return ( + title: S.of(context).depositPageInvalidTokenAmount(tokenSymbol: quoteToken.symbol), + icon: null, + onPressed: null, + ); + } + + if (userWalletBaseTokenAmount < (double.tryParse(baseTokenAmountController.text) ?? 0) && isBaseTokenNeeded) { + return ( + title: S.of(context).depositPageInsufficientTokenBalance(tokenSymbol: baseToken.symbol), + icon: null, + onPressed: null, + ); + } + + if (userWalletQuoteTokenAmount < (double.tryParse(quoteTokenAmountController.text) ?? 0) && isQuoteTokenNeeded) { + return ( + title: S.of(context).depositPageInsufficientTokenBalance(tokenSymbol: quoteToken.symbol), + icon: null, + onPressed: null, + ); + } + + return ( + title: "Preview Deposit", + icon: Assets.icons.scrollFill.svg(), + onPressed: () { + PreviewDepositModal( + key: const Key("preview-deposit-modal"), + yieldTimeFrame: yieldPoolTimeFrame, + deadline: selectedDeadline, + maxSlippage: selectedSlippage, + currentYield: _cubit.yieldPool!, + isReversed: areTokensReversed, + token0DepositAmountController: areTokensReversed ? quoteTokenAmountController : baseTokenAmountController, + token1DepositAmountController: areTokensReversed ? baseTokenAmountController : quoteTokenAmountController, + maxPrice: (isInfinity: isMaxRangeInfinity, price: maxPrice), + minPrice: (isInfinity: isMinRangeInfinity, price: minPrice), + ).show(context, currentPriceX96: _cubit.latestPoolSqrtPriceX96 ?? BigInt.zero); + }, + ); + } + + @override + void initState() { + _cubit.setup(); + + WidgetsBinding.instance.addPostFrameCallback((_) { + appScrollController.animateTo(0, duration: const Duration(milliseconds: 500), curve: Curves.decelerate); + }); + + _poolSqrtPriceX96StreamSubscription = _cubit.poolSqrtPriceX96Stream.listen((sqrtPriceX96) { + if (sqrtPriceX96 != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() => calculateDepositTokensAmount()); + }); + } + }); + + super.initState(); + } + + @override + void dispose() { + minRangeController.dispose(); + maxRangeController.dispose(); + _poolSqrtPriceX96StreamSubscription?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: isMobileSize(context) ? const EdgeInsets.all(20) : const EdgeInsets.symmetric(horizontal: 40), + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => _buildLoadingState(), + error: () => _buildErrorState(), + success: (yieldPool) => Padding( + padding: EdgeInsets.only(top: isMobileSize(context) ? 20 : 60), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: 550, minHeight: shouldYieldCardBeInColumn ? 1500 : 1100), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + ZupTextButton( + key: const Key("back-button"), + onPressed: () { + _navigator.canBack(context) + ? _navigator.back(context) + : _navigator.navigateToNewPosition(); + }, + icon: Assets.icons.arrowLeft.svg(), + label: _navigator.canBack(context) + ? S.of(context).depositPageBackToYieldsButtonTitle + : S.of(context).depositPageBackToNewPositionButtonTitle, + ), + const Spacer(), + ZupPillButton( + key: const Key("deposit-settings-button"), + backgroundColor: + selectedSlippage.riskBackgroundColor(context.brightness) ?? + ZupThemeColors.tertiaryButtonBackground.themed(context.brightness), + foregroundColor: + selectedSlippage.riskForegroundColor(context.brightness) ?? + ZupColors.brand.lighter(0.3), + title: selectedSlippage.value != DepositSettingsDto.defaultMaxSlippage + ? S + .of(context) + .depositPagePercentSlippage( + valuePercent: selectedSlippage.value.formatPercent, + ) + : null, + onPressed: (buttonContext) => ZupPopover.show( + adjustment: const Offset(0, 10), + showBasedOnContext: buttonContext, + child: DepositSettingsDropdownChild( + context, + selectedDeadline: selectedDeadline, + selectedSlippage: selectedSlippage, + onSettingsChanged: (slippage, deadline) { + _cubit.saveDepositSettings(slippage, deadline); + + setState(() { + selectedDeadline = deadline; + selectedSlippage = slippage; + }); + }, + ), + ), + icon: Assets.icons.gear.svg( + colorFilter: ColorFilter.mode( + ZupThemeColors.background.themed(context.brightness), + BlendMode.srcIn, + ), + height: 20, + width: 20, + ), + ), + ], + ), + + if (shouldYieldCardBeInColumn) ...[ + const SizedBox(height: 30), + + Center(child: _buildYieldCard(yieldPool)), + const SizedBox(height: 30), + ], + const SizedBox(height: 10), + _buildSelectRangeSector(), + const SizedBox(height: 20), + _buildDepositSection(), + ], + ), + ), + ), + if (!shouldYieldCardBeInColumn) ...[ + const SizedBox(width: 20), + Column( + children: [ + SizedBox(height: desktopYieldCardTopPadding), + _buildYieldCard(yieldPool), + ], + ), + ], + ], + ), + ), + ); + }, + ), + ); + } + + Widget _sectionTitle(String title) => Text( + title, + style: TextStyle( + fontSize: 17, + fontWeight: FontWeight.w600, + color: ZupThemeColors.primaryText.themed(context.brightness), + ), + ); + + Widget _buildErrorState() => Center( + child: SizedBox( + width: 400, + child: ZupInfoState( + icon: const IgnorePointer( + child: Text(":(", style: TextStyle(color: ZupColors.brand)), + ), + title: S.of(context).depositPageErrorStateTitle, + description: S.of(context).depositPageErrorStateDescription, + helpButtonTitle: S.of(context).letsGiveItAnotherShot, + helpButtonIcon: Assets.icons.arrowClockwise.svg(), + onHelpButtonTap: () => _cubit.fetchCurrentPoolInfo(), + ), + ), + ); + + Widget _buildLoadingState() { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ZupSkeletonizer( + child: YieldCard( + yieldPool: YieldDto.fixture().copyWith(chainId: networkFromUrl.chainId), + yieldTimeFrame: YieldTimeFrame.day, + showHotestYieldAnimation: false, + ), + ).animate( + onPlay: (controller) => controller.repeat(reverse: true), + effects: [ + const MoveEffect( + duration: Duration(milliseconds: 900), + curve: Curves.decelerate, + begin: Offset(0, 0), + end: Offset(0, -35), + ), + ], + ), + const SizedBox(height: 20), + Text( + S.of(context).depositPageLoadingTitle, + style: TextStyle( + color: ZupThemeColors.primaryText.themed(context.brightness), + fontSize: 17, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ); + } + + Widget _buildYieldCard(YieldDto yieldPool) => YieldCard( + yieldPool: yieldPool, + yieldTimeFrame: yieldTimeFrameFromUrl, + expandWidth: shouldYieldCardBeInColumn, + showHotestYieldAnimation: false, + showTimeframe: !_navigator.canBack(context), + mainButton: const ZupPrimaryButton( + title: "Pool Stats (Soon)", + fixedIcon: true, + isTrailingIcon: true, + onPressed: null, + backgroundColor: ZupColors.gray6, + foregroundColor: ZupColors.brand, + hoverElevation: 0, + ), + ); + + Widget _buildSelectRangeSector() { + Widget tokenSwitcher = CupertinoSlidingSegmentedControl( + groupValue: areTokensReversed, + children: { + false: MouseRegion( + key: const Key("reverse-tokens-not-reversed"), + cursor: SystemMouseCursors.click, + child: IgnorePointer( + ignoring: true, + child: Text( + "${_cubit.yieldPool!.token0.symbol} / ${_cubit.yieldPool!.token1.symbol}", + style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500), + ), + ), + ), + true: MouseRegion( + key: const Key("reverse-tokens-reversed"), + cursor: SystemMouseCursors.click, + child: IgnorePointer( + ignoring: true, + child: Text( + "${_cubit.yieldPool!.token1.symbol} / ${_cubit.yieldPool!.token0.symbol}", + style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500), + ), + ), + ), + }, + onValueChanged: (isReversed) { + switchTokens(isReversed ?? false); + }, + ); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + + children: [ + Builder( + builder: (context) { + WidgetsBinding.instance.addPostFrameCallback((_) { + final nextDesktopYieldCardTopPadding = (context.size!.height + 40); + + if (desktopYieldCardTopPadding != nextDesktopYieldCardTopPadding) { + setState(() => desktopYieldCardTopPadding = nextDesktopYieldCardTopPadding); + } + }); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _sectionTitle(S.of(context).depositPageRangeSectionTitle), + const SizedBox(height: 10), + SizedBox( + width: double.infinity, + child: Wrap( + alignment: WrapAlignment.spaceBetween, + crossAxisAlignment: WrapCrossAlignment.center, + runSpacing: 10, + children: [ + StreamBuilder( + stream: _cubit.poolSqrtPriceX96Stream, + initialData: _cubit.latestPoolSqrtPriceX96, + builder: (context, poolSqrtPriceX96Snaphot) { + return Text( + "1 ${baseToken.symbol} ≈ ${() { + final currentPrice = sqrtPriceX96ToPrice(sqrtPriceX96: poolSqrtPriceX96Snaphot.data ?? BigInt.zero, poolToken0Decimals: _cubit.yieldPool!.token0NetworkDecimals, poolToken1Decimals: _cubit.yieldPool!.token1NetworkDecimals); + + return areTokensReversed ? currentPrice.token1PerToken0 : currentPrice.token0PerToken1; + }.call().formatCurrency(useLessThan: true, maxDecimals: 4, isUSD: false)} ${quoteToken.symbol}", + style: TextStyle( + fontSize: 17, + fontWeight: FontWeight.w500, + color: ZupThemeColors.primaryText.themed(context.brightness), + ), + ).redacted(enabled: poolSqrtPriceX96Snaphot.data == null); + }, + ), + const SizedBox(height: 10), + tokenSwitcher, + ], + ), + ), + const SizedBox(height: 20), + Wrap( + spacing: 10, + runSpacing: 10, + children: [ + ZupMiniButton( + key: const Key("full-range-button"), + onPressed: (_) => setFullRange(), + isSelected: isMaxRangeInfinity && isMinRangeInfinity, + title: S.of(context).depositPageRangeSectionFullRange, + icon: Assets.icons.circleDotted.svg(), + ), + ZupMiniButton( + key: const Key("5-percent-range-button"), + onPressed: (_) => setPercentageRange(5), + isSelected: percentRange == 5, + title: "5%", + icon: Assets.icons.plusminus.svg(), + // alignLeft: true, + ), + ZupMiniButton( + key: const Key("20-percent-range-button"), + onPressed: (_) => setPercentageRange(20), + isSelected: percentRange == 20, + title: "20%", + icon: Assets.icons.plusminus.svg(), + // alignLeft: true, + ), + ZupMiniButton( + key: const Key("50-percent-range-button"), + onPressed: (_) => setPercentageRange(50), + isSelected: percentRange == 50, + title: "50%", + icon: Assets.icons.plusminus.svg(), + // alignLeft: true, + ), + ], + ), + const SizedBox(height: 10), + ], + ); + }, + ), + StreamBuilder( + stream: _cubit.poolSqrtPriceX96Stream, + builder: (context, _) { + return RangeSelector( + key: const Key("min-price-selector"), + onUserType: () => percentRange = null, + onPriceChanged: (price) { + setState(() { + if (price == 0) { + isMinRangeInfinity = true; + + return calculateDepositTokensAmount(); + } + + isMinRangeInfinity = false; + minPrice = price; + calculateDepositTokensAmount(); + }); + }, + initialPrice: minPrice, + poolToken0Decimals: _cubit.yieldPool!.token0NetworkDecimals, + poolToken1Decimals: _cubit.yieldPool!.token1NetworkDecimals, + isReversed: areTokensReversed, + displayBaseTokenSymbol: baseToken.symbol, + displayQuoteTokenSymbol: quoteToken.symbol, + tickSpacing: _cubit.yieldPool!.tickSpacing, + type: RangeSelectorType.minPrice, + isInfinity: isMinRangeInfinity, + rangeController: minRangeController, + state: () { + if (isOutOfRange.minPrice) { + return RangeSelectorState( + type: RangeSelectorStateType.warning, + message: S.of(context).depositPageMinRangeOutOfRangeWarningText, + ); + } + + return const RangeSelectorState(type: RangeSelectorStateType.regular); + }.call(), + ); + }, + ), + const SizedBox(height: 6), + StreamBuilder( + stream: _cubit.poolSqrtPriceX96Stream, + builder: (context, _) { + return RangeSelector( + key: const Key("max-price-selector"), + displayBaseTokenSymbol: baseToken.symbol, + displayQuoteTokenSymbol: quoteToken.symbol, + onUserType: () => percentRange = null, + onPriceChanged: (price) { + setState(() { + if (price == 0) { + isMaxRangeInfinity = true; + + return calculateDepositTokensAmount(); + } + + isMaxRangeInfinity = false; + maxPrice = price; + + calculateDepositTokensAmount(); + }); + }, + type: RangeSelectorType.maxPrice, + isInfinity: isMaxRangeInfinity, + initialPrice: maxPrice, + poolToken0Decimals: _cubit.yieldPool!.token0NetworkDecimals, + poolToken1Decimals: _cubit.yieldPool!.token1NetworkDecimals, + isReversed: areTokensReversed, + tickSpacing: _cubit.yieldPool!.tickSpacing, + rangeController: maxRangeController, + state: () { + if (isRangeInvalid) { + return RangeSelectorState( + type: RangeSelectorStateType.error, + message: S.of(context).depositPageInvalidRangeErrorText, + ); + } + + if (isOutOfRange.maxPrice) { + return RangeSelectorState( + type: RangeSelectorStateType.warning, + message: S.of(context).depositPageMaxRangeOutOfRangeWarningText, + ); + } + + return const RangeSelectorState(type: RangeSelectorStateType.regular); + }.call(), + ); + }, + ), + ], + ); + } + + Widget _buildDepositSection() => IgnorePointer( + key: const Key("deposit-section"), + ignoring: isRangeInvalid, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 300), + opacity: isRangeInvalid ? 0.2 : 1, + child: StreamBuilder( + stream: _cubit.poolSqrtPriceX96Stream, + initialData: _cubit.latestPoolSqrtPriceX96, + builder: (context, sqrtPriceX96Snapshot) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _sectionTitle(S.of(context).depositPageDepositSectionTitle), + const SizedBox(height: 12), + TokenAmountInputCard( + key: const Key("base-token-input-card"), + token: baseToken, + isNative: baseToken.addresses[_cubit.yieldPool!.network.chainId]!.lowercasedEquals( + EthereumConstants.zeroAddress, + ), + onRefreshBalance: () => setState(() {}), + disabledText: () { + if (!isBaseTokenNeeded) { + return S.of(context).depositPageDepositSectionTokenNotNeeded(tokenSymbol: baseToken.symbol); + } + + if (!isBaseTokenAmountUserInput && + !sqrtPriceX96Snapshot.hasData && + quoteTokenAmountController.text.isNotEmpty) { + return S.of(context).loading; + } + }.call(), + onInput: (amount) { + setState(() { + isBaseTokenAmountUserInput = true; + + calculateDepositTokensAmount(); + }); + }, + controller: baseTokenAmountController, + network: _cubit.yieldPool!.network, + ), + const SizedBox(height: 6), + TokenAmountInputCard( + key: const Key("quote-token-input-card"), + token: quoteToken, + isNative: quoteToken.addresses[_cubit.yieldPool!.network.chainId]!.lowercasedEquals( + EthereumConstants.zeroAddress, + ), + onRefreshBalance: () => setState(() {}), + disabledText: () { + if (!isQuoteTokenNeeded) { + return S.of(context).depositPageDepositSectionTokenNotNeeded(tokenSymbol: quoteToken.symbol); + } + + if (isBaseTokenAmountUserInput && + !sqrtPriceX96Snapshot.hasData && + baseTokenAmountController.text.isNotEmpty) { + return S.of(context).loading; + } + }.call(), + onInput: (amount) { + setState(() { + isBaseTokenAmountUserInput = false; + + calculateDepositTokensAmount(); + }); + }, + controller: quoteTokenAmountController, + network: _cubit.yieldPool!.network, + ), + const SizedBox(height: 20), + Row( + children: [ + Expanded( + child: StreamBuilder( + key: const Key("deposit-button"), + stream: wallet.signerStream, + initialData: wallet.signer, + builder: (context, signerSnapshot) { + if (!signerSnapshot.hasData) { + return ZupPrimaryButton( + width: double.maxFinite, + title: S.of(context).connectWallet, + icon: Assets.icons.walletBifold.svg(), + fixedIcon: true, + alignCenter: true, + hoverElevation: 0, + backgroundColor: ZupColors.brand.withValues(alpha: 0.1), + foregroundColor: ZupColors.brand, + onPressed: (buttonContext) => ConnectModal().show(context), + ); + } + + return FutureBuilder( + future: depositButtonState(), + builder: (context, stateSnapshot) { + return ZupPrimaryButton( + alignCenter: true, + title: stateSnapshot.data?.title ?? S.of(context).loading, + icon: stateSnapshot.data?.icon, + isLoading: stateSnapshot.connectionState == ConnectionState.waiting, + fixedIcon: true, + onPressed: stateSnapshot.data?.onPressed == null + ? null + : (buttonContext) => stateSnapshot.data?.onPressed!(), + width: double.maxFinite, + ); + }, + ); + }, + ), + ), + ], + ), + ], + ); + }, + ), + ), + ); +} diff --git a/lib/app/create/deposit/deposit_state.dart b/lib/app/create/yields/[id]/deposit/deposit_state.dart similarity index 58% rename from lib/app/create/deposit/deposit_state.dart rename to lib/app/create/yields/[id]/deposit/deposit_state.dart index 38dfa3e..6accdbd 100644 --- a/lib/app/create/deposit/deposit_state.dart +++ b/lib/app/create/yields/[id]/deposit/deposit_state.dart @@ -4,7 +4,6 @@ part of 'deposit_cubit.dart'; class DepositState with _$DepositState { const factory DepositState.initial() = _Initial; const factory DepositState.loading() = _Loading; - const factory DepositState.success(YieldsDto yields) = _Success; + const factory DepositState.success(YieldDto yieldPool) = _Success; const factory DepositState.error() = _Error; - const factory DepositState.noYields({required PoolSearchFiltersDto filtersApplied}) = _NoYields; } diff --git a/lib/app/create/deposit/widgets/deposit_settings_dropdown_child.dart b/lib/app/create/yields/[id]/deposit/widgets/deposit_settings_dropdown_child.dart similarity index 100% rename from lib/app/create/deposit/widgets/deposit_settings_dropdown_child.dart rename to lib/app/create/yields/[id]/deposit/widgets/deposit_settings_dropdown_child.dart diff --git a/lib/app/create/deposit/widgets/deposit_success_modal.dart b/lib/app/create/yields/[id]/deposit/widgets/deposit_success_modal.dart similarity index 100% rename from lib/app/create/deposit/widgets/deposit_success_modal.dart rename to lib/app/create/yields/[id]/deposit/widgets/deposit_success_modal.dart diff --git a/lib/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal.dart b/lib/app/create/yields/[id]/deposit/widgets/preview_deposit_modal/preview_deposit_modal.dart similarity index 99% rename from lib/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal.dart rename to lib/app/create/yields/[id]/deposit/widgets/preview_deposit_modal/preview_deposit_modal.dart index acab2de..cbf576e 100644 --- a/lib/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal.dart +++ b/lib/app/create/yields/[id]/deposit/widgets/preview_deposit_modal/preview_deposit_modal.dart @@ -7,8 +7,8 @@ import 'package:web3kit/web3kit.dart'; import 'package:zup_app/abis/erc_20.abi.g.dart'; import 'package:zup_app/abis/uniswap_permit2.abi.g.dart'; import 'package:zup_app/abis/uniswap_v3_position_manager.abi.g.dart'; -import 'package:zup_app/app/create/deposit/widgets/deposit_success_modal.dart'; -import 'package:zup_app/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/widgets/deposit_success_modal.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit.dart'; import 'package:zup_app/core/concentrated_liquidity_utils/cl_pool_constants.dart'; import 'package:zup_app/core/concentrated_liquidity_utils/cl_pool_conversors_mixin.dart'; import 'package:zup_app/core/concentrated_liquidity_utils/cl_sqrt_price_math_mixin.dart'; diff --git a/lib/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit.dart b/lib/app/create/yields/[id]/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit.dart similarity index 99% rename from lib/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit.dart rename to lib/app/create/yields/[id]/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit.dart index 0550ad4..8687653 100644 --- a/lib/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit.dart +++ b/lib/app/create/yields/[id]/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit.dart @@ -9,7 +9,7 @@ import 'package:web3kit/web3kit.dart'; import 'package:zup_app/abis/erc_20.abi.g.dart'; import 'package:zup_app/abis/uniswap_permit2.abi.g.dart'; import 'package:zup_app/abis/uniswap_v3_position_manager.abi.g.dart'; -import 'package:zup_app/app/create/deposit/widgets/deposit_success_modal.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/widgets/deposit_success_modal.dart'; import 'package:zup_app/core/concentrated_liquidity_utils/cl_pool_constants.dart'; import 'package:zup_app/core/concentrated_liquidity_utils/cl_pool_conversors_mixin.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; diff --git a/lib/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_state.dart b/lib/app/create/yields/[id]/deposit/widgets/preview_deposit_modal/preview_deposit_modal_state.dart similarity index 100% rename from lib/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_state.dart rename to lib/app/create/yields/[id]/deposit/widgets/preview_deposit_modal/preview_deposit_modal_state.dart diff --git a/lib/app/create/deposit/widgets/range_selector.dart b/lib/app/create/yields/[id]/deposit/widgets/range_selector.dart similarity index 100% rename from lib/app/create/deposit/widgets/range_selector.dart rename to lib/app/create/yields/[id]/deposit/widgets/range_selector.dart diff --git a/lib/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card.dart b/lib/app/create/yields/[id]/deposit/widgets/token_amount_input_card/token_amount_input_card.dart similarity index 98% rename from lib/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card.dart rename to lib/app/create/yields/[id]/deposit/widgets/token_amount_input_card/token_amount_input_card.dart index 76e5445..140c133 100644 --- a/lib/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card.dart +++ b/lib/app/create/yields/[id]/deposit/widgets/token_amount_input_card/token_amount_input_card.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:web3kit/web3kit.dart'; -import 'package:zup_app/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_cubit.dart'; -import 'package:zup_app/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_user_balance_cubit.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/widgets/token_amount_input_card/token_amount_input_card_cubit.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/widgets/token_amount_input_card/token_amount_input_card_user_balance_cubit.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; import 'package:zup_app/core/extensions/num_extension.dart'; diff --git a/lib/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_cubit.dart b/lib/app/create/yields/[id]/deposit/widgets/token_amount_input_card/token_amount_input_card_cubit.dart similarity index 100% rename from lib/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_cubit.dart rename to lib/app/create/yields/[id]/deposit/widgets/token_amount_input_card/token_amount_input_card_cubit.dart diff --git a/lib/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_state.dart b/lib/app/create/yields/[id]/deposit/widgets/token_amount_input_card/token_amount_input_card_state.dart similarity index 100% rename from lib/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_state.dart rename to lib/app/create/yields/[id]/deposit/widgets/token_amount_input_card/token_amount_input_card_state.dart diff --git a/lib/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_user_balance_cubit.dart b/lib/app/create/yields/[id]/deposit/widgets/token_amount_input_card/token_amount_input_card_user_balance_cubit.dart similarity index 100% rename from lib/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_user_balance_cubit.dart rename to lib/app/create/yields/[id]/deposit/widgets/token_amount_input_card/token_amount_input_card_user_balance_cubit.dart diff --git a/lib/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_user_balance_state.dart b/lib/app/create/yields/[id]/deposit/widgets/token_amount_input_card/token_amount_input_card_user_balance_state.dart similarity index 100% rename from lib/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_user_balance_state.dart rename to lib/app/create/yields/[id]/deposit/widgets/token_amount_input_card/token_amount_input_card_user_balance_state.dart diff --git a/lib/app/create/yields/[id]/deposit/widgets/yield_card_temp.dart b/lib/app/create/yields/[id]/deposit/widgets/yield_card_temp.dart new file mode 100644 index 0000000..8128ca6 --- /dev/null +++ b/lib/app/create/yields/[id]/deposit/widgets/yield_card_temp.dart @@ -0,0 +1,207 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; +import 'package:intl/intl.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:zup_app/app/app_cubit/app_cubit.dart'; +import 'package:zup_app/core/dtos/yield_dto.dart'; +import 'package:zup_app/core/enums/yield_timeframe.dart'; +import 'package:zup_app/core/extensions/num_extension.dart'; +import 'package:zup_app/core/injections.dart'; +import 'package:zup_app/l10n/gen/app_localizations.dart'; +import 'package:zup_app/widgets/token_avatar.dart'; +import 'package:zup_app/widgets/zup_cached_image.dart'; +import 'package:zup_core/extensions/extensions.dart'; +import 'package:zup_ui_kit/zup_ui_kit.dart'; + +class YieldCardTemp extends StatefulWidget { + const YieldCardTemp({ + super.key, + required this.currentYield, + required this.onChangeSelection, + required this.isSelected, + required this.timeFrame, + this.isHotestYield = false, + }); + + final YieldDto currentYield; + final bool isSelected; + final Function(YieldDto? yield) onChangeSelection; + final YieldTimeFrame timeFrame; + final bool isHotestYield; + + @override + State createState() => _YieldCardTempState(); +} + +class _YieldCardTempState extends State { + final zupCachedImage = inject(); + final appCubit = inject(); + final infinityAnimationAutoPlay = inject(instanceName: InjectInstanceNames.infinityAnimationAutoPlay); + + final selectionAnimationDuration = const Duration(milliseconds: 150); + + Widget get yieldText => Text( + widget.currentYield.yieldTimeframed(widget.timeFrame).formatPercent, + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.w600, + color: ZupThemeColors.backgroundInverse.themed(context.brightness), + ), + ); + + @override + Widget build(BuildContext context) { + return ZupSelectableCard( + isSelected: widget.isSelected, + selectionAnimationDuration: selectionAnimationDuration, + + onPressed: () { + return widget.onChangeSelection(widget.isSelected ? null : widget.currentYield); + }, + padding: const EdgeInsets.all(10).copyWith(right: 2, top: 2, bottom: 0), + child: Stack( + children: [ + if (appCubit.selectedNetwork.isAllNetworks) + Positioned( + right: 2, + top: 2, + child: ZupTooltip.text( + message: S.of(context).yieldCardThisPoolIsAtNetwork(network: widget.currentYield.network.label), + trailingIcon: widget.currentYield.network.icon(context.brightness), + child: Container( + height: 40, + padding: const EdgeInsets.all(6), + width: 40, + decoration: BoxDecoration( + color: widget.isSelected + ? ZupColors.brand.withValues(alpha: 0.1) + : (context.brightness.isDark ? ZupColors.black2 : ZupColors.gray6), + borderRadius: BorderRadius.circular(8), + ), + child: widget.currentYield.network.icon(context.brightness), + ), + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(top: 10), + child: Text( + widget.timeFrame.isDay + ? S.of(context).yieldCardYearlyYield + : S.of(context).yieldCardAverageYieldYearly, + style: TextStyle(fontSize: 14, color: ZupThemeColors.primaryText.themed(context.brightness)), + ), + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.isHotestYield) ...[ + yieldText.animate( + effects: [ + const ScaleEffect( + duration: Duration(milliseconds: 200), + alignment: Alignment.center, + begin: Offset(1.1, 1.1), + end: Offset(1, 1), + ), + ShimmerEffect( + duration: const Duration(seconds: 2), + color: ZupThemeColors.shimmer.themed(context.brightness), + curve: Curves.decelerate, + angle: 90, + size: 1, + ), + ], + autoPlay: infinityAnimationAutoPlay, + onComplete: (controller) => controller.repeat(reverse: true), + ), + ] else + yieldText, + ], + ), + Text( + "${NumberFormat.compactSimpleCurrency(decimalDigits: 2).format(widget.currentYield.totalValueLockedUSD)} ${S.of(context).tvl}", + style: const TextStyle(fontSize: 14, height: 1, color: ZupColors.gray), + ), + const SizedBox(height: 10), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: widget.isSelected + ? (context.brightness.isDark ? ZupColors.brand.withValues(alpha: 0.1) : ZupColors.brand5) + : (context.brightness.isDark ? ZupColors.black2 : ZupColors.gray6), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + ZupMergedWidgets( + firstWidget: TokenAvatar(asset: widget.currentYield.token0, size: 25), + secondWidget: TokenAvatar(asset: widget.currentYield.token1, size: 25), + spacing: 0, + ), + const SizedBox(width: 10), + Text( + "${widget.currentYield.token0.symbol}/${widget.currentYield.token1.symbol}", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: ZupThemeColors.primaryText.themed(context.brightness), + ), + ), + ], + ), + ), + const SizedBox(width: 10), + const Text(">", style: TextStyle(color: ZupColors.gray)), + const SizedBox(width: 10), + if (widget.currentYield.protocol.logo.isNotEmpty) + zupCachedImage.build( + context, + widget.currentYield.protocol.logo, + height: 25, + width: 25, + radius: 50, + errorWidget: (context, error, stackTrace) { + return Container( + height: 25, + width: 25, + decoration: BoxDecoration(color: ZupColors.gray6, borderRadius: BorderRadius.circular(50)), + ); + }, + ), + const SizedBox(width: 5), + Flexible( + child: ZupTooltip.text( + message: "", + helperButtonTitle: S + .of(context) + .yieldCardVisitProtocol(protocolName: widget.currentYield.protocol.name), + helperButtonOnPressed: () => launchUrl(Uri.parse(widget.currentYield.protocol.url)), + child: Text( + widget.currentYield.protocol.name, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: ZupThemeColors.backgroundInverse.themed(context.brightness), + ), + ), + ), + ), + const SizedBox(width: 20), + ], + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/app/create/yields/yields_cubit.dart b/lib/app/create/yields/yields_cubit.dart new file mode 100644 index 0000000..9fcea02 --- /dev/null +++ b/lib/app/create/yields/yields_cubit.dart @@ -0,0 +1,73 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:zup_app/app/app_cubit/app_cubit.dart'; +import 'package:zup_app/core/cache.dart'; +import 'package:zup_app/core/dtos/pool_search_filters_dto.dart'; +import 'package:zup_app/core/dtos/yields_dto.dart'; +import 'package:zup_app/core/repositories/yield_repository.dart'; +import 'package:zup_app/core/zup_analytics.dart'; + +part 'yields_cubit.freezed.dart'; +part 'yields_state.dart'; + +class YieldsCubit extends Cubit { + YieldsCubit(this.appCubit, this.appCache, this.yieldRepository, this.zupAnalytics) + : super(const YieldsState.initial()); + + final AppCubit appCubit; + final ZupAnalytics zupAnalytics; + final Cache appCache; + final YieldRepository yieldRepository; + + Future fetchYields({ + required String? token0AddressOrId, + required String? token1AddressOrId, + required String? group0Id, + required String? group1Id, + bool ignoreMinLiquidity = false, + }) async { + try { + zupAnalytics.logSearch( + token0: token0AddressOrId, + token1: token1AddressOrId, + group0: group0Id, + group1: group1Id, + network: appCubit.selectedNetwork.label, + ); + + emit(const YieldsState.loading()); + + final yields = appCubit.selectedNetwork.isAllNetworks + ? await yieldRepository.getAllNetworksYield( + blockedProtocolIds: appCache.blockedProtocolsIds, + token0InternalId: token0AddressOrId, + token1InternalId: token1AddressOrId, + group0Id: group0Id, + group1Id: group1Id, + searchSettings: ignoreMinLiquidity + ? appCache.getPoolSearchSettings().copyWith(minLiquidityUSD: 0) + : appCache.getPoolSearchSettings(), + testnetMode: appCubit.isTestnetMode, + ) + : await yieldRepository.getSingleNetworkYield( + blockedProtocolIds: appCache.blockedProtocolsIds, + token0Address: token0AddressOrId, + token1Address: token1AddressOrId, + group0Id: group0Id, + group1Id: group1Id, + network: appCubit.selectedNetwork, + searchSettings: ignoreMinLiquidity + ? appCache.getPoolSearchSettings().copyWith(minLiquidityUSD: 0) + : appCache.getPoolSearchSettings(), + ); + + if (yields.isEmpty) { + return emit(YieldsState.noYields(filtersApplied: yields.filters)); + } + + emit(YieldsState.success(yields)); + } catch (e, stackTrace) { + emit(YieldsState.error(e.toString(), stackTrace.toString())); + } + } +} diff --git a/lib/app/create/yields/yields_page.dart b/lib/app/create/yields/yields_page.dart new file mode 100644 index 0000000..a7b6f5f --- /dev/null +++ b/lib/app/create/yields/yields_page.dart @@ -0,0 +1,531 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:lottie/lottie.dart'; +import 'package:skeletonizer/skeletonizer.dart'; +import 'package:zup_app/app/app_cubit/app_cubit.dart'; +import 'package:zup_app/app/create/yields/yields_cubit.dart'; +import 'package:zup_app/core/cache.dart'; +import 'package:zup_app/core/dtos/pool_search_filters_dto.dart'; +import 'package:zup_app/core/dtos/yields_dto.dart'; +import 'package:zup_app/core/enums/networks.dart'; +import 'package:zup_app/core/enums/yield_timeframe.dart'; +import 'package:zup_app/core/enums/zup_navigator_paths.dart'; +import 'package:zup_app/core/extensions/num_extension.dart'; +import 'package:zup_app/core/injections.dart'; +import 'package:zup_app/core/mixins/keys_mixin.dart'; +import 'package:zup_app/core/repositories/yield_repository.dart'; +import 'package:zup_app/core/zup_analytics.dart'; +import 'package:zup_app/core/zup_navigator.dart'; +import 'package:zup_app/core/zup_route_params_names.dart'; +import 'package:zup_app/gen/assets.gen.dart'; +import 'package:zup_app/l10n/gen/app_localizations.dart'; +import 'package:zup_app/widgets/timeframe_selector.dart'; +import 'package:zup_app/widgets/yield_card.dart'; +import 'package:zup_app/widgets/zup_page_title.dart'; +import 'package:zup_core/extensions/extensions.dart'; +import 'package:zup_core/mixins/device_info_mixin.dart'; +import 'package:zup_ui_kit/zup_ui_kit.dart'; + +// Used by Routefly to build custom routes +Route routeBuilder(BuildContext context, RouteSettings settings) { + return PageRouteBuilder( + settings: settings, + transitionDuration: const Duration(milliseconds: 700), + pageBuilder: (_, a1, a2) => BlocProvider( + create: (context) => + YieldsCubit(inject(), inject(), inject(), inject()), + child: const YieldsPage(), + ), + transitionsBuilder: (_, a1, a2, child) => SlideTransition( + position: Tween(begin: const Offset(0, 1), end: Offset.zero).animate(a1), + child: FadeTransition(opacity: a1, child: child), + ), + ); +} + +class YieldsPage extends StatefulWidget { + const YieldsPage({super.key}); + + @override + State createState() => _YieldsPageState(); +} + +class _YieldsPageState extends State with DeviceInfoMixin, SingleTickerProviderStateMixin, KeysMixin { + final navigator = inject(); + final appCubit = inject(); + final appCache = inject(); + final lottieEmpty = inject(instanceName: InjectInstanceNames.lottieEmpty); + final lottieRadar = inject(instanceName: InjectInstanceNames.lottieRadar); + final lottieNumbers = inject(instanceName: InjectInstanceNames.lottieNumbers); + final lottieMatching = inject(instanceName: InjectInstanceNames.lottieMatching); + final lottieSearching = inject(instanceName: InjectInstanceNames.lottieList); + final appScrollController = inject(instanceName: InjectInstanceNames.appScrollController); + final pageController = PageController(initialPage: 0); + final pageTransitionDuration = const Duration(milliseconds: 800); + final pageTransitionCurve = Curves.easeInOutCubicEmphasized; + final double mobilePageHorizontalPadding = 20; + + YieldsCubit get cubit => context.read(); + + String? get token0QueryParam { + return navigator.getQueryParam(ZupNavigatorPaths.yields.routeParamsNames().token0); + } + + String? get token1QueryParam { + return navigator.getQueryParam(ZupNavigatorPaths.yields.routeParamsNames().token1); + } + + String? get group0QueryParam { + return navigator.getQueryParam(ZupNavigatorPaths.yields.routeParamsNames().group0); + } + + String? get group1QueryParam { + return navigator.getQueryParam(ZupNavigatorPaths.yields.routeParamsNames().group1); + } + + YieldTimeFrame selectedYieldTimeFrame = YieldTimeFrame.day; + num currentYieldPage = 0; + bool isYieldsPageGoingBackwards = false; + + void resetScrollAndFetch({bool ignoreMinLiquidity = false}) { + appScrollController.animateTo(0, duration: const Duration(milliseconds: 500), curve: Curves.decelerate); + + cubit.fetchYields( + token0AddressOrId: token0QueryParam, + token1AddressOrId: token1QueryParam, + group0Id: group0QueryParam, + group1Id: group1QueryParam, + ignoreMinLiquidity: ignoreMinLiquidity, + ); + } + + @override + void initState() { + final currentNetworkFromUrl = + navigator.getQueryParam(ZupNavigatorPaths.yields.routeParamsNames().network) ?? ""; + + if (currentNetworkFromUrl.isNotEmpty) { + final currentNetwork = AppNetworks.fromValue(currentNetworkFromUrl); + if (currentNetwork != null) appCubit.updateAppNetwork(currentNetwork); + } + + pageController.addListener(() { + WidgetsBinding.instance.addPostFrameCallback((_) { + final currentControllerPage = pageController.page!.toInt(); + if (currentControllerPage == currentYieldPage) return; + + setState(() => currentYieldPage = currentControllerPage); + }); + }); + + WidgetsBinding.instance.addPostFrameCallback((_) => resetScrollAndFetch()); + + super.initState(); + } + + @override + void dispose() { + pageController.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + success: (yields) => _buildSuccessState(yields), + noYields: (filtersApplied) => _buildNoYieldsState(filtersApplied: filtersApplied), + error: (error, stackTrace) => _buildErrorState, + orElse: () => _buildLoadingState, + ); + }, + ); + } + + Widget _buildSuccessState(YieldsDto yields) { + int getYieldDisplayCountPerPage() { + return yields.pools.length.clamp(1, isMobileSize(context) ? 1 : 2); + } + + final yieldsPagesCount = (yields.pools.length / getYieldDisplayCountPerPage()).ceil(); + + return Padding( + padding: EdgeInsets.only(top: isMobileSize(context) ? 20 : 30), + child: Align( + alignment: Alignment.topCenter, + child: Container( + constraints: const BoxConstraints(maxWidth: 800), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (!isMobileSize(context)) ...[ + yieldsPagesCount == 1 + ? const SizedBox(width: 60) + : AnimatedOpacity( + key: const Key("move-to-previous-yields-page-button"), + opacity: currentYieldPage > 0 ? 1 : 0.3, + duration: const Duration(milliseconds: 200), + child: Padding( + padding: const EdgeInsets.all(20), + child: ZupIconButton( + icon: Assets.icons.arrowLeft.svg(height: 12, width: 12), + onPressed: (context) async { + if (currentYieldPage.equals(0)) return; + isYieldsPageGoingBackwards = true; + + pageController.previousPage(duration: pageTransitionDuration, curve: pageTransitionCurve); + }, + circle: true, + padding: const EdgeInsets.all(15), + ).animatedHover(animationValue: 1.2), + ), + ), + ], + Flexible( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.symmetric( + horizontal: isMobileSize(context) ? mobilePageHorizontalPadding : 0, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ZupTextButton( + key: const Key("back-button"), + onPressed: () => navigator.navigateToNewPosition(), + icon: Assets.icons.arrowLeft.svg(), + label: S.of(context).yieldsPageBackButtonTitle, + ), + ZupPageTitle(S.of(context).yieldsPageTitle), + Text( + S.of(context).yieldsPageDescription, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: ZupColors.gray), + ), + const SizedBox(height: 10), + TimeframeSelector( + selectedTimeframe: selectedYieldTimeFrame, + onTimeframeSelected: (timeframe) { + setState(() { + pageController.jumpTo(0); + selectedYieldTimeFrame = timeframe ?? YieldTimeFrame.day; + }); + }, + ), + ], + ), + ), + const SizedBox(height: 20), + _buildYieldsSection( + yieldsPagesCount: yieldsPagesCount, + yieldsCardPerPage: getYieldDisplayCountPerPage(), + yields: yields, + ), + const SizedBox(height: 20), + if (yieldsPagesCount > 1) _buildPageIndicator(pageCount: yieldsPagesCount), + const SizedBox(height: 20), + ..._buildMinTvlFilterAlert(currentMinTVLUSD: yields.filters.minTvlUsd), + const SizedBox(height: 60), + ], + ), + ), + if (!isMobileSize(context)) ...[ + yieldsPagesCount == 1 + ? const SizedBox(width: 60) + : AnimatedOpacity( + key: const Key("move-to-next-yields-page-button"), + opacity: currentYieldPage.equals(yieldsPagesCount - 1) ? 0.3 : 1, + duration: const Duration(milliseconds: 200), + child: Padding( + padding: const EdgeInsets.all(20), + child: ZupIconButton( + icon: Assets.icons.arrowRight.svg(height: 12, width: 12), + onPressed: (context) { + if (currentYieldPage.equals(yieldsPagesCount - 1)) return; + isYieldsPageGoingBackwards = false; + + pageController.nextPage(duration: pageTransitionDuration, curve: pageTransitionCurve); + }, + circle: true, + padding: const EdgeInsets.all(15), + ).animatedHover(animationValue: 1.2), + ), + ), + ], + ], + ), + ), + ), + ); + } + + Widget _buildYieldsSection({ + required int yieldsPagesCount, + required int yieldsCardPerPage, + required YieldsDto yields, + }) { + final poolsSortedByTimeframe = yields.poolsSortedByTimeframe(selectedYieldTimeFrame); + + return SizedBox( + height: 315, + child: PageView.builder( + physics: isMobileSize(context) ? const BouncingScrollPhysics() : const NeverScrollableScrollPhysics(), + controller: pageController, + padEnds: false, + pageSnapping: true, + scrollDirection: Axis.horizontal, + itemCount: yieldsPagesCount, + itemBuilder: (_, pageIndex) { + final firstYieldIndex = pageIndex * yieldsCardPerPage; + final lastYieldIndex = (firstYieldIndex + yieldsCardPerPage).clamp(0, yields.pools.length); + + final yieldsCurrentPage = poolsSortedByTimeframe.sublist(firstYieldIndex, lastYieldIndex); + + return Center( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: isMobileSize(context) ? mobilePageHorizontalPadding : 0), + child: Row( + mainAxisSize: MainAxisSize.max, + spacing: 10, + children: yieldsCurrentPage.map((yieldItem) { + final animationDurationMultiplier = () { + if (isYieldsPageGoingBackwards) { + return yieldsCurrentPage.reversed.toList().indexOf(yieldItem) + 1; + } + + return yieldsCurrentPage.indexOf(yieldItem) + 1; + }(); + + return Flexible( + child: Center( + child: YieldCard( + key: Key("yield-card-${yieldItem.poolAddress}"), + yieldPool: yieldItem, + yieldTimeFrame: selectedYieldTimeFrame, + showHotestYieldAnimation: yieldItem.equals(poolsSortedByTimeframe.first), + expandWidth: isMobileSize(context), + mainButton: ZupPrimaryButton( + key: Key("deposit-button-${yieldItem.poolAddress}"), + title: S.of(context).yieldCardDeposit, + onPressed: (_) { + navigator.navigateToDeposit( + yieldPool: yieldItem, + selectedTimeframe: selectedYieldTimeFrame, + parseWrappedToNative: yieldItem.isToken0Native || yieldItem.isToken1Native, + ); + }, + width: 200, + height: 45, + hoverElevation: 0, + fixedIcon: true, + alignCenter: true, + isTrailingIcon: true, + icon: Skeleton.ignore(child: Assets.icons.arrowRight.svg(height: 12, width: 12)), + ), + ), + ), + ).animate( + autoPlay: true, + effects: [ + SlideEffect( + begin: Offset(isYieldsPageGoingBackwards ? 1 : -1, 0), + end: const Offset(0, 0), + curve: Curves.easeOutQuart, + duration: Duration(milliseconds: 600 * animationDurationMultiplier), + ), + FadeEffect( + duration: const Duration(milliseconds: 800), + begin: 0, + end: 1, + curve: Curves.easeOutQuart, + delay: Duration(milliseconds: (100 * animationDurationMultiplier)), + ), + ], + ); + }).toList(), + ), + ), + ); + }, + ), + ); + } + + List _buildMinTvlFilterAlert({required num currentMinTVLUSD}) => [ + if (currentMinTVLUSD > 0) ...[ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Center( + child: ZupInlineTextActionButton( + text: S.of(context).yieldsPageDisplayingPoolsWithMinTvlAlert(tvlUSD: currentMinTVLUSD.formatCurrency()), + style: TextStyle(color: ZupThemeColors.primaryText.themed(context.brightness), fontSize: 13), + onActionButtonPressed: () => resetScrollAndFetch(ignoreMinLiquidity: true), + actionButtonTitle: S.of(context).yieldsPageSearchAllPools, + ), + ), + ), + ] else if (appCache.getPoolSearchSettings().minLiquidityUSD > 0) ...[ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Center( + child: ZupInlineTextActionButton( + text: S.of(context).yieldsPageDisplayingAllPoolsAlert, + style: TextStyle(color: ZupThemeColors.primaryText.themed(context.brightness), fontSize: 13), + onActionButtonPressed: () => resetScrollAndFetch(ignoreMinLiquidity: false), + actionButtonTitle: S + .of(context) + .yieldsPageApplyTvlFilterButtonTitle( + tvlUSD: appCache.getPoolSearchSettings().minLiquidityUSD.formatCurrency(), + ), + ), + ), + ), + ], + ]; + + Widget _buildNoYieldsState({required PoolSearchFiltersDto filtersApplied}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 60), + SizedBox( + width: 400, + child: ZupInfoState( + icon: Transform.scale(scale: 3, child: lottieEmpty), + iconSize: 120, + title: S.of(context).yieldsPageEmptyStateTitle, + description: S.of(context).yieldsPageEmptyStateDescription, + helpButtonTitle: S.of(context).yieldsPageEmptyStateHelperButtonTitle, + helpButtonIcon: Assets.icons.arrowLeft.svg(), + onHelpButtonTap: () => navigator.navigateToNewPosition(), + ), + ), + const SizedBox(height: 60), + if (filtersApplied.minTvlUsd > 0) ...[ + Center( + child: ZupInlineTextActionButton( + text: S.of(context).yieldsPageEmptyStateMinTVLAlert(tvlUSD: filtersApplied.minTvlUsd.formatCurrency()), + style: TextStyle(color: ZupThemeColors.primaryText.themed(context.brightness), fontSize: 13), + onActionButtonPressed: () => resetScrollAndFetch(ignoreMinLiquidity: true), + actionButtonTitle: S.of(context).yieldsPageSearchAllPools, + ), + ), + ], + ], + ); + } + + Widget get _buildErrorState { + return Center( + child: SizedBox( + width: 400, + child: ZupInfoState( + icon: const IgnorePointer( + child: Text(":(", style: TextStyle(color: ZupColors.brand)), + ), + title: S.of(context).yieldsPageErrorStateTitle, + description: S.of(context).yieldsPageErrorStateDescription, + helpButtonTitle: S.of(context).letsGiveItAnotherShot, + helpButtonIcon: Assets.icons.arrowClockwise.svg(), + onHelpButtonTap: () => resetScrollAndFetch(), + ), + ), + ); + } + + Widget get _buildLoadingState { + bool isGroupSearch = group0QueryParam != null || group1QueryParam != null; + + return Container( + color: ZupThemeColors.background.themed(context.brightness), + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Center( + child: ZupSteppedLoading( + stepDuration: Duration(seconds: isGroupSearch ? 8 : 6), + steps: [ + ZupSteppedLoadingStep( + title: S.of(context).yieldsPageLoadingStep1Title, + description: S.of(context).yieldsPageLoadingStep1Description, + icon: ColorFiltered( + colorFilter: const ColorFilter.mode(ZupColors.brand, BlendMode.srcIn), + child: lottieMatching, + ), + iconSize: 200, + ), + ZupSteppedLoadingStep( + title: S.of(context).yieldsPageLoadingStep2Title, + description: S.of(context).yieldsPageLoadingStep2Description, + icon: ColorFiltered( + colorFilter: const ColorFilter.mode(ZupColors.brand, BlendMode.srcIn), + child: lottieRadar, + ), + iconSize: 200, + ), + ZupSteppedLoadingStep( + title: S.of(context).yieldsPageLoadingStep3Title, + description: S.of(context).yieldsPageLoadingStep3Description, + icon: ColorFiltered( + colorFilter: const ColorFilter.mode(ZupColors.brand, BlendMode.srcIn), + child: lottieNumbers, + ), + iconSize: 200, + ), + ZupSteppedLoadingStep( + title: S.of(context).yieldsPageLoadingStep4Title, + description: S.of(context).yieldsPageLoadingStep4Description, + icon: ColorFiltered( + colorFilter: const ColorFilter.mode(ZupColors.brand, BlendMode.srcIn), + child: lottieSearching, + ), + iconSize: 200, + ), + ], + ), + ), + ); + } + + Widget _buildPageIndicator({required int pageCount}) => Center( + child: SizedBox( + height: 16, + child: Row( + mainAxisSize: MainAxisSize.min, + children: List.generate( + (currentYieldPage + 4).clamp(0, pageCount).ceil(), + (index) => AnimatedContainer( + key: Key("yield-page-indicator-$index"), + duration: const Duration(milliseconds: 200), + height: (index != currentYieldPage) ? 8 : 12, + width: (index != currentYieldPage) ? 8 : 12, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () async { + pageController.animateToPage( + index, + duration: const Duration(milliseconds: 600), + curve: Curves.easeInOutExpo, + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 2), + child: CircleAvatar( + backgroundColor: (currentYieldPage.truncate() == index) + ? ZupColors.brand + : ZupThemeColors.disabledButtonBackground.themed(context.brightness), + ), + ), + ).animatedHover(animationValue: index != currentYieldPage ? 4 : 1), + ), + ), + ), + ), + ), + ); +} diff --git a/lib/app/create/yields/yields_state.dart b/lib/app/create/yields/yields_state.dart new file mode 100644 index 0000000..2b7bbad --- /dev/null +++ b/lib/app/create/yields/yields_state.dart @@ -0,0 +1,10 @@ +part of 'yields_cubit.dart'; + +@freezed +abstract class YieldsState with _$YieldsState { + const factory YieldsState.initial() = _Initial; + const factory YieldsState.loading() = _Loading; + const factory YieldsState.success(YieldsDto yields) = _Success; + const factory YieldsState.error(String message, String stackTrace) = _Error; + const factory YieldsState.noYields({required PoolSearchFiltersDto filtersApplied}) = _NoYields; +} diff --git a/lib/core/dtos/deposit_page_arguments_dto.dart b/lib/core/dtos/deposit_page_arguments_dto.dart new file mode 100644 index 0000000..3393f21 --- /dev/null +++ b/lib/core/dtos/deposit_page_arguments_dto.dart @@ -0,0 +1,13 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:zup_app/core/dtos/yield_dto.dart'; + +part 'deposit_page_arguments_dto.freezed.dart'; +part 'deposit_page_arguments_dto.g.dart'; + +@freezed +sealed class DepositPageArgumentsDto with _$DepositPageArgumentsDto { + @JsonSerializable(explicitToJson: true) + const factory DepositPageArgumentsDto({YieldDto? yieldPool}) = _DepositPageArgumentsDto; + + factory DepositPageArgumentsDto.fromJson(Map json) => _$DepositPageArgumentsDtoFromJson(json); +} diff --git a/lib/core/dtos/protocol_dto.dart b/lib/core/dtos/protocol_dto.dart index bb4461a..b61682d 100644 --- a/lib/core/dtos/protocol_dto.dart +++ b/lib/core/dtos/protocol_dto.dart @@ -21,11 +21,11 @@ sealed class ProtocolDto with _$ProtocolDto { factory ProtocolDto.fromJson(Map json) => _$ProtocolDtoFromJson(json); - factory ProtocolDto.fixture() => const ProtocolDto( - rawId: "1", - id: ProtocolId.unknown, - name: "Uniswap", - url: "https://app.uniswap.org/pool", - logo: "https://raw.githubusercontent.com/trustwallet/assets/master/dapps/app.uniswap.org.png", - ); + factory ProtocolDto.fixture() => ProtocolDto( + rawId: ProtocolId.velodromeSlipstream.toRawJsonValue, + id: ProtocolId.velodromeSlipstream, + name: "Uniswap", + url: "https://app.uniswap.org/pool", + logo: "https://raw.githubusercontent.com/trustwallet/assets/master/dapps/app.uniswap.org.png", + ); } diff --git a/lib/core/dtos/yield_dto.dart b/lib/core/dtos/yield_dto.dart index 3dbc2cb..8d7685c 100644 --- a/lib/core/dtos/yield_dto.dart +++ b/lib/core/dtos/yield_dto.dart @@ -5,6 +5,7 @@ import 'package:zup_app/core/dtos/token_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; import 'package:zup_app/core/enums/pool_type.dart'; import 'package:zup_app/core/enums/yield_timeframe.dart'; +import 'package:zup_app/core/extensions/num_extension.dart'; part 'yield_dto.freezed.dart'; part 'yield_dto.g.dart'; @@ -46,6 +47,19 @@ sealed class YieldDto with _$YieldDto { int get token0NetworkDecimals => token0.decimals[network.chainId]!; int get token1NetworkDecimals => token1.decimals[network.chainId]!; + String timeframedYieldFormatted(YieldTimeFrame yieldTimeFrame) { + switch (yieldTimeFrame) { + case YieldTimeFrame.day: + return yield24h == 0 ? "-" : yield24h.formatPercent; + case YieldTimeFrame.week: + return yield7d == 0 ? "-" : yield7d.formatPercent; + case YieldTimeFrame.month: + return yield30d == 0 ? "-" : yield30d.formatPercent; + case YieldTimeFrame.threeMonth: + return yield90d == 0 ? "-" : yield90d.formatPercent; + } + } + num yieldTimeframed(YieldTimeFrame yieldTimeFrame) { switch (yieldTimeFrame) { case YieldTimeFrame.day: diff --git a/lib/core/enums/yield_timeframe.dart b/lib/core/enums/yield_timeframe.dart index b5cfe50..bcc0c73 100644 --- a/lib/core/enums/yield_timeframe.dart +++ b/lib/core/enums/yield_timeframe.dart @@ -1,7 +1,17 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:zup_app/l10n/gen/app_localizations.dart'; -enum YieldTimeFrame { day, week, month, threeMonth } +enum YieldTimeFrame { + day, + week, + month, + threeMonth; + + static YieldTimeFrame? fromValue(String name) { + return YieldTimeFrame.values.firstWhereOrNull((e) => e.name == name); + } +} extension YieldTimeFrameExtension on YieldTimeFrame { bool get isDay => this == YieldTimeFrame.day; diff --git a/lib/core/enums/zup_navigator_paths.dart b/lib/core/enums/zup_navigator_paths.dart index dc8adec..53fed3a 100644 --- a/lib/core/enums/zup_navigator_paths.dart +++ b/lib/core/enums/zup_navigator_paths.dart @@ -4,19 +4,22 @@ import 'package:zup_app/zup_app.dart'; enum ZupNavigatorPaths { initial, newPosition, + yields, deposit; String get path => switch (this) { initial => routePaths.create.path, newPosition => routePaths.create.path, - deposit => routePaths.create.deposit, + yields => routePaths.create.yields.path, + deposit => routePaths.create.yields.$id.deposit, }; T routeParamsNames() { final params = switch (this) { - initial => ZupInitialRouteParamsNames(), - newPosition => ZupNewPositionRouteParamsNames(), - deposit => ZupDepositRouteParamsNames(), + initial => InitialRouteParamsNames(), + newPosition => NewPositionRouteParamsNames(), + yields => YieldsRouteParamsNames(), + deposit => DepositRouteParamsNames(), }; return params as T; diff --git a/lib/core/extensions/num_extension.dart b/lib/core/extensions/num_extension.dart index f5b30cf..66c9642 100644 --- a/lib/core/extensions/num_extension.dart +++ b/lib/core/extensions/num_extension.dart @@ -29,42 +29,25 @@ extension NumExtension on num { return 0; } - String formatCurrency({ - bool isUSD = true, - bool useLessThan = false, - int maxDecimals = 4, - }) { + String formatCurrency({bool isUSD = true, bool useLessThan = false, int maxDecimals = 4}) { int decimalsDigits = decimals; final maxDecimalsNumber = double.parse("0.${"0" * (maxDecimals - 1)}1"); if (decimals > maxDecimals && (this > maxDecimalsNumber)) decimalsDigits = maxDecimals; if (useLessThan && this < maxDecimalsNumber) return toAmount(useLessThan: true, maxFixedDigits: maxDecimals); - if (this < 0.1) return "${(isUSD ? "\$" : "")}${Decimal.parse(toString()).toString()}"; + if (this < 0.1) return "${(isUSD ? "\$" : "")}${Decimal.parse(toString())}"; - return NumberFormat.simpleCurrency( - decimalDigits: decimalsDigits, - name: isUSD ? null : "", - ).format(this); + return NumberFormat.simpleCurrency(decimalDigits: decimalsDigits, name: isUSD ? null : "").format(this); } - String formatCompactCurrency({ - bool isUSD = true, - bool useMoreThan = false, - num? maxBeforeMoreThan, - }) { + String formatCompactCurrency({bool isUSD = true, bool useMoreThan = false, num? maxBeforeMoreThan}) { final maxWithoutMoreThan = maxBeforeMoreThan ?? pow(10, 12) * 999; if (useMoreThan && this > maxWithoutMoreThan) { - return NumberFormat.compactCurrency( - decimalDigits: decimals, - name: ">", - ).format(maxWithoutMoreThan); + return NumberFormat.compactCurrency(decimalDigits: decimals, name: ">").format(maxWithoutMoreThan); } - return NumberFormat.compactCurrency( - decimalDigits: decimals, - name: isUSD ? "USD " : "", - ).format(this); + return NumberFormat.compactCurrency(decimalDigits: decimals, name: isUSD ? "USD " : "").format(this); } String maybeFormatCompactCurrency({ @@ -78,17 +61,10 @@ extension NumExtension on num { final maxWithoutCompact = maxBeforeCompact ?? maxWithoutMoreThan; if (this > maxWithoutCompact) { - return formatCompactCurrency( - isUSD: isUSD, - useMoreThan: useMoreThan, - maxBeforeMoreThan: maxWithoutMoreThan, - ); + return formatCompactCurrency(isUSD: isUSD, useMoreThan: useMoreThan, maxBeforeMoreThan: maxWithoutMoreThan); } - return formatCurrency( - isUSD: isUSD, - useLessThan: useLessThan, - ); + return formatCurrency(isUSD: isUSD, useLessThan: useLessThan); } String get formatPercent { diff --git a/lib/core/mixins/keys_mixin.dart b/lib/core/mixins/keys_mixin.dart index b8b99d1..c93e5a2 100644 --- a/lib/core/mixins/keys_mixin.dart +++ b/lib/core/mixins/keys_mixin.dart @@ -19,4 +19,6 @@ mixin KeysMixin { } String get protocolsListKey => 'zup-supported-protocols'; + + String yieldCardHeroKey(String poolAddress) => 'yield-card-hero-$poolAddress'; } diff --git a/lib/core/repositories/yield_repository.dart b/lib/core/repositories/yield_repository.dart index 1935bcf..508a705 100644 --- a/lib/core/repositories/yield_repository.dart +++ b/lib/core/repositories/yield_repository.dart @@ -1,5 +1,6 @@ import 'package:dio/dio.dart'; import 'package:zup_app/core/dtos/pool_search_settings_dto.dart'; +import 'package:zup_app/core/dtos/yield_dto.dart'; import 'package:zup_app/core/dtos/yields_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; @@ -17,21 +18,22 @@ class YieldRepository { required PoolSearchSettingsDto searchSettings, required List blockedProtocolIds, }) async { - final response = await _zupAPIDio.post("/pools/search/${network.chainId}", queryParameters: { - if (token0Address != null) "token0Address": token0Address, - if (token1Address != null) "token1Address": token1Address, - if (group0Id != null) "group0Id": group0Id, - if (group1Id != null) "group1Id": group1Id, - }, data: { - "filters": { - "minTvlUsd": searchSettings.minLiquidityUSD, - "blockedProtocols": blockedProtocolIds, - "allowedPoolTypes": [ - if (searchSettings.allowV3Search) "V3", - if (searchSettings.allowV4Search) "V4", - ], - } - }); + final response = await _zupAPIDio.post( + "/pools/search/${network.chainId}", + queryParameters: { + if (token0Address != null) "token0Address": token0Address, + if (token1Address != null) "token1Address": token1Address, + if (group0Id != null) "group0Id": group0Id, + if (group1Id != null) "group1Id": group1Id, + }, + data: { + "filters": { + "minTvlUsd": searchSettings.minLiquidityUSD, + "blockedProtocols": blockedProtocolIds, + "allowedPoolTypes": [if (searchSettings.allowV3Search) "V3", if (searchSettings.allowV4Search) "V4"], + }, + }, + ); return YieldsDto.fromJson(response.data); } @@ -45,23 +47,37 @@ class YieldRepository { required List blockedProtocolIds, bool testnetMode = false, }) async { - final response = await _zupAPIDio.post("/pools/search/all", queryParameters: { - if (token0InternalId != null) "token0Id": token0InternalId, - if (token1InternalId != null) "token1Id": token1InternalId, - if (group0Id != null) "group0Id": group0Id, - if (group1Id != null) "group1Id": group1Id, - }, data: { - "filters": { - "minTvlUsd": searchSettings.minLiquidityUSD, - "testnetMode": testnetMode, - "blockedProtocols": blockedProtocolIds, - "allowedPoolTypes": [ - if (searchSettings.allowV3Search) "V3", - if (searchSettings.allowV4Search) "V4", - ], - } - }); + final response = await _zupAPIDio.post( + "/pools/search/all", + queryParameters: { + if (token0InternalId != null) "token0Id": token0InternalId, + if (token1InternalId != null) "token1Id": token1InternalId, + if (group0Id != null) "group0Id": group0Id, + if (group1Id != null) "group1Id": group1Id, + }, + data: { + "filters": { + "minTvlUsd": searchSettings.minLiquidityUSD, + "testnetMode": testnetMode, + "blockedProtocols": blockedProtocolIds, + "allowedPoolTypes": [if (searchSettings.allowV3Search) "V3", if (searchSettings.allowV4Search) "V4"], + }, + }, + ); return YieldsDto.fromJson(response.data); } + + Future getPoolInfo({ + required String poolAddress, + required AppNetworks poolNetwork, + bool parseWrappedToNative = true, + }) async { + final response = await _zupAPIDio.get( + "/pools/$poolAddress/${poolNetwork.chainId}", + queryParameters: {"parseWrappedToNative": parseWrappedToNative}, + ); + + return YieldDto.fromJson(response.data); + } } diff --git a/lib/core/zup_navigator.dart b/lib/core/zup_navigator.dart index 1eef5c3..5750bfc 100644 --- a/lib/core/zup_navigator.dart +++ b/lib/core/zup_navigator.dart @@ -1,39 +1,77 @@ import 'package:flutter/material.dart'; import 'package:routefly/routefly.dart'; +import 'package:zup_app/core/dtos/deposit_page_arguments_dto.dart'; +import 'package:zup_app/core/dtos/yield_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; +import 'package:zup_app/core/enums/yield_timeframe.dart'; import 'package:zup_app/core/enums/zup_navigator_paths.dart'; import 'package:zup_app/core/zup_route_params_names.dart'; class ZupNavigator { Listenable get listenable => Routefly.listenable; + String get currentRoute => Routefly.currentUri.path; - String? getParam(String paramName) => Routefly.query.params[paramName]; + String? getQueryParam(String paramName) => Routefly.query.params[paramName]; + + String? getQuery(String queryName) => Routefly.query[queryName].toString(); + + String? get getIdFromPath => Routefly.query["id"].toString(); + + Map get currentPageArguments => Routefly.query.arguments ?? {}; + + void back(BuildContext context) async => Routefly.pop(context); - Future back(BuildContext context) async => Routefly.pop(context); + bool canBack(BuildContext context) => Navigator.of(context).canPop(); Future navigateToNewPosition() async => await Routefly.navigate(ZupNavigatorPaths.newPosition.path); - Future navigateToDeposit({ + Future navigateToYields({ required String? token0, required String? token1, required String? group0, required String? group1, required AppNetworks network, }) async { - const depositPath = ZupNavigatorPaths.deposit; - final depositPathParams = depositPath.routeParamsNames(); + const yieldsPath = ZupNavigatorPaths.yields; + final yieldsPathParamNames = yieldsPath.routeParamsNames(); - final token0UrlParam = token0 != null ? "${depositPathParams.token0}=$token0" : ""; - final token1UrlParam = token1 != null ? "${depositPathParams.token1}=$token1" : ""; - final group0UrlParam = group0 != null ? "${depositPathParams.group0}=$group0" : ""; - final group1UrlParam = group1 != null ? "${depositPathParams.group1}=$group1" : ""; - final networkUrlParam = "${depositPathParams.network}=${network.name}"; + final token0UrlParam = token0 != null ? _buildUrlParam(yieldsPathParamNames.token0, token0) : ""; + final token1UrlParam = token1 != null ? _buildUrlParam(yieldsPathParamNames.token1, token1) : ""; + final group0UrlParam = group0 != null ? _buildUrlParam(yieldsPathParamNames.group0, group0) : ""; + final group1UrlParam = group1 != null ? _buildUrlParam(yieldsPathParamNames.group1, group1) : ""; + final networkUrlParam = _buildUrlParam(yieldsPathParamNames.network, network.name); - await Routefly.pushNavigate( - "${depositPath.path}?$token0UrlParam&$token1UrlParam&$group0UrlParam&$group1UrlParam&$networkUrlParam", + return await Routefly.pushNavigate( + "${yieldsPath.path}?$token0UrlParam&$token1UrlParam&$group0UrlParam&$group1UrlParam&$networkUrlParam", ); } Future navigateToInitial() async => await Routefly.navigate(ZupNavigatorPaths.initial.path); + + Future navigateToDeposit({ + required YieldDto yieldPool, + required YieldTimeFrame selectedTimeframe, + required bool parseWrappedToNative, + }) async { + final depositPathParamNames = ZupNavigatorPaths.deposit.routeParamsNames(); + + final poolNetworkUrlParam = _buildUrlParam(depositPathParamNames.network, yieldPool.network.name); + final timeframeUrlParam = _buildUrlParam(depositPathParamNames.timeframe, selectedTimeframe.name); + final parseWrappedToNativeParam = _buildUrlParam( + depositPathParamNames.parseWrappedToNative, + parseWrappedToNative.toString(), + ); + + final rawPath = ZupNavigatorPaths.deposit.path.changes({"id": yieldPool.poolAddress}); + + await Routefly.pushNavigate( + "$rawPath?$poolNetworkUrlParam&$timeframeUrlParam&$parseWrappedToNativeParam", + arguments: DepositPageArgumentsDto(yieldPool: yieldPool).toJson(), + ); + } + + String _buildUrlParam(String paramName, String paramValue) { + return "$paramName=$paramValue"; + } } diff --git a/lib/core/zup_route_params_names.dart b/lib/core/zup_route_params_names.dart index 7c1c8b0..c6068e5 100644 --- a/lib/core/zup_route_params_names.dart +++ b/lib/core/zup_route_params_names.dart @@ -1,17 +1,23 @@ abstract class ZupRouteParamsNames {} -class ZupDepositRouteParamsNames extends ZupRouteParamsNames { +class InitialRouteParamsNames extends ZupRouteParamsNames { + InitialRouteParamsNames(); +} + +class NewPositionRouteParamsNames extends ZupRouteParamsNames { + NewPositionRouteParamsNames(); +} + +class DepositRouteParamsNames extends ZupRouteParamsNames { + final String timeframe = "timeframe"; + final String network = "network"; + final String parseWrappedToNative = "parseWrappedToNative"; +} + +class YieldsRouteParamsNames extends ZupRouteParamsNames { final String token0 = "token0"; final String token1 = "token1"; final String group0 = "group0"; final String group1 = "group1"; final String network = "network"; } - -class ZupInitialRouteParamsNames extends ZupRouteParamsNames { - ZupInitialRouteParamsNames(); -} - -class ZupNewPositionRouteParamsNames extends ZupRouteParamsNames { - ZupNewPositionRouteParamsNames(); -} diff --git a/lib/l10n/en.arb b/lib/l10n/en.arb index 00d3829..21b217c 100644 --- a/lib/l10n/en.arb +++ b/lib/l10n/en.arb @@ -1,390 +1,549 @@ { - "@@locale": "en", - "yieldCardNetworkTooltipDescription": "This pool is at {network}", - "@yieldCardNetworkTooltipDescription": { - "placeholders": { - "network": { - "type": "String" - } - } + "@@locale": "en", + "appBottomNavigationBarMyPositions": "My Positions (Soon)", + "appBottomNavigationBarNewPosition": "New Position", + "appCookiesConsentWidgetDescription": "We use cookies to ensure that we give you the best experience on our app. By continuing to use Zup Protocol, you agree to our", + "appFooterContactUs": "Contact Us", + "appFooterDocs": "Docs", + "appFooterFAQ": "FAQ", + "appFooterTermsOfUse": "Terms of Use", + "appHeaderMyPositions": "My Positions (Soon)", + "appHeaderNewPosition": "New Position", + "appSettingsDropdownTestnetMode": "Testnet Mode", + "close": "Close", + "connectMyWallet": "Connect My Wallet", + "connectWallet": "Connect Wallet", + "connectYourWallet": "Connect your wallet", + "createNewPosition": "Create new position", + "createPageDescription": "Ready to dive in? First, pick the dynamic duo of tokens you want to team up in the pool. Just choose your pair right below and you’re set to make some magic!", + "createPageSelectTokensStageSearchSettings": "Search Settings", + "createPageSelectTokensStageTokenA": "Token A", + "createPageSelectTokensStageTokenB": "Token B", + "createPageSettingsDropdownAllowedPoolTypes": "Allowed Pool Types", + "createPageSettingsDropdownAllowedPoolTypesDescription": "Filter the types of liquidity pools to include in your search", + "createPageSettingsDropdownMinimumLiquidity": "Minimum Pool Liquidity", + "createPageSettingsDropdownMinimumLiquidityExplanation": "Filter pools by minimum liquidity. We’ll exclude pools with less liquidity than specified, as low Liquidity can lead to misleading yields. This helps you find more reliable opportunities", + "createPageSettingsDropdownMiniumLiquidityLowWarning": "Low minimum TVL can lead to misleading yields.", + "createPageShowMeTheMoney": "Show me the money!", + "createPageTitle": "New Position", + "dark": "Dark", + "depositPageLoadingTitle": "Getting the pool ready for you...", + "@depositPageLoadingTitle": { + "description": "Used by the deposit page as title for the loading state, before the pool is ready for the user to deposit" + }, + "depositPageBackToYieldsButtonTitle": "Select Yield", + "@depositPageBackToYieldsButtonTitle": { + "description": "Used by the deposit page as title for the back button to navigate back to the yields page, if the previous page was to select a yield" + }, + "depositPageBackToNewPositionButtonTitle": "Search other pools", + "@depositPageBackToNewPositionButtonTitle": { + "description": "Used by the deposit page as title for the back button to navigate back to the new position page, if the previous page isn't to select a yield, like paste the url to deposit directly" + }, + "depositPageBestYieldsIn": "Best Yields in", + "depositPageDeposit": "Deposit", + "depositPageDepositSectionTitle": "Deposit", + "depositPageDepositSectionTokenNotNeeded": "{tokenSymbol} is not necessary for your selected range", + "@depositPageDepositSectionTokenNotNeeded": { + "placeholders": { + "tokenSymbol": { + "type": "String" + } + } + }, + "depositPageDepositWithNativeToken": "Deposit with Native {tokenSymbol}", + "@depositPageDepositWithNativeToken": { + "placeholders": { + "tokenSymbol": { + "type": "String" + } + } + }, + "depositPageEmptyStateDescription": "Seems like that there are no pools matching your defined settings at the moment. Would you like to either change your settings or try another combination?", + "depositPageEmptyStateHelpButtonTitle": "Go Back to New Position", + "depositPageEmptyStateTitle": "No Pools Found", + "depositPageErrorStateDescription": "We ran into an issue while fetching the pool data. Try again, and if it keeps happening, don't hesitate to reach out to us!", + "@depositPageErrorStateDescription": { + "description": "Used by the deposit page as description for the error state when fetching yield info" + }, + "depositPageErrorStateTitle": "Oops! Something went wrong!", + "@depositPageErrorStateTitle": { + "description": "Used by the deposit page as title for the error state when fetching yield info" + }, + "depositPageInsufficientTokenBalance": "Insufficient {tokenSymbol} balance", + "@depositPageInsufficientTokenBalance": { + "placeholders": { + "tokenSymbol": { + "type": "String" + } + } + }, + "depositPageInvalidRange": "Invalid range", + "depositPageInvalidRangeErrorText": "Max range should be greater than min range", + "depositPageInvalidTokenAmount": "Enter a valid amount for {tokenSymbol}", + "@depositPageInvalidTokenAmount": { + "placeholders": { + "tokenSymbol": { + "type": "String" + } + } + }, + "depositPageLoadingStep1Description": "Pairing Token A and Token B to kick off the search for top yields!", + "depositPageLoadingStep1Title": "Matching Tokens...", + "depositPageLoadingStep2Description": "Searching through more than a thousand pool combos… so you don't have to", + "depositPageLoadingStep2Title": "Pair hunting…", + "depositPageLoadingStep3Description": "Scanning pools, calculating returns, and filtering the noise", + "depositPageLoadingStep3Title": "Yield optimizer at work…", + "depositPageLoadingStep4Description": "Hang tight, we're filtering and organizing the best pools for you", + "depositPageLoadingStep4Title": "Organizing the best pools for you…", + "depositPageMaxRangeOutOfRangeWarningText": "You will not earn fees until the market price move down into your range", + "depositPageMinLiquiditySearchAlert": "You’ve set the search to only show pools with more than {minLiquidity}.", + "@depositPageMinLiquiditySearchAlert": { + "placeholders": { + "minLiquidity": { + "type": "String" + } + } + }, + "depositPageMinRangeOutOfRangeWarningText": "You will not earn fees until the market price move up into your range", + "depositPageNoYieldSelectedDescription": "Pick any yield card above and dive into depositing your liquidity!", + "depositPageNoYieldSelectedTitle": "Pick a yield to deposit", + "depositPagePercentSlippage": "{valuePercent} Slippage", + "@depositPagePercentSlippage": { + "placeholders": { + "valuePercent": { + "type": "String" + } + } + }, + "depositPagePleaseEnterAmountForToken": "Please enter an amount for {tokenSymbol}", + "@depositPagePleaseEnterAmountForToken": { + "placeholders": { + "tokenSymbol": { + "type": "String" + } + } + }, + "depositPageRangeSectionFullRange": "Full Range", + "depositPageRangeSectionTitle": "Select Range", + "depositPageSearchAllPools": "Search all pools?", + "depositPageSearchOnlyForPoolsWithMorethan": "Search only for pools with more than {minLiquidity}?", + "@depositPageSearchOnlyForPoolsWithMorethan": { + "placeholders": { + "minLiquidity": { + "type": "String" + } + } + }, + "depositPageShowingAllPools": "Showing all liquidity pools.", + "depositPageShowingOnlyPoolsWithMoreThan": "Showing only liquidity pools with more than {minLiquidity}.", + "@depositPageShowingOnlyPoolsWithMoreThan": { + "placeholders": { + "minLiquidity": { + "type": "String" + } + } + }, + "depositPageTimeFrameTitle": "Preferred time frame", + "depositPageTimeFrameTooltipHelperButtonTitle": " Learn more", + "depositPageTimeFrameTooltipMessage": "Each time frame shows yields based on past performance. Shorter terms (24h, 7d) reflect recent trends and may suit short-term strategies. Longer terms (30d, 90d) offer a broader performance view for long-term decisions.", + "depositPageTitle": "Add liquidity", + "depositPageTrySearchAllPools": "Try search all pools?", + "depositSettingsDropdownChildHighSlippageWarningText": "High slippage can lead to Front Running and losses. Be careful! ", + "depositSettingsDropdownChildMaxSlippage": "Max Slippage", + "depositSettingsDropdownChildTransactionDeadlineExplanation": "Your transaction will be reverted if it is pending for more than this amount of time", + "depositSettingsDropdownTransactionDeadline": "Transaction Deadline", + "depositSuccessModalDescriptionPart1": "You have successfully deposited into", + "depositSuccessModalDescriptionPart2": "Pool at", + "depositSuccessModalDescriptionPart3": "on", + "depositSuccessModalTitle": "Deposit Successful!", + "depositSuccessModalViewPositionOnDEX": "View Position on {dexName}", + "@depositSuccessModalViewPositionOnDEX": { + "placeholders": { + "dexName": { + "type": "String" + } + } + }, + "exchangesFilterDropdownButtonDropdownClearAll": "Clear All", + "exchangesFilterDropdownButtonDropdownNotFoundStateDescription": "No supported exchanges found with this name", + "exchangesFilterDropdownButtonDropdownNotFoundStateTitle": "Not found", + "exchangesFilterDropdownButtonDropdownSearchHint": "Search by name", + "exchangesFilterDropdownButtonDropdownSelectAll": "Select All", + "exchangesFilterDropdownButtonErrorSnackBarMessage": "Uh-oh! Something went wrong loading the exchanges. Please try refreshing the page.", + "exchangesFilterDropdownButtonTitle": "Exchanges", + "exchangesFilterDropdownButtonTitleNumered": "Exchanges ({exchangesCount})", + "@exchangesFilterDropdownButtonTitleNumered": { + "placeholders": { + "exchangesCount": { + "type": "String" + } + } + }, + "letsGiveItAnotherGo": "Let’s give it another go, because why not?", + "letsGiveItAnotherShot": "Let’s give it another shot", + "@letsGiveItAnotherShot": { + "description": "Used by many pages in the app to try something again, usually after something went wrong" + }, + "light": "Light", + "loading": "Loading...", + "minutes": "Minutes", + "month": "Month", + "monthCompact": "30d", + "newPosition": "New Position", + "noResultsFor": "No results for", + "popularTokens": "Popular Tokens", + "positionCardLiquidity": "Liquidity: ", + "positionCardMax": "Max: ", + "positionCardMin": "Min: ", + "positionCardTokenPerToken": "{token0Qtd} {token0Symbol} per {token1Symbol}", + "@positionCardTokenPerToken": { + "placeholders": { + "token0Qtd": { + "type": "String" + }, + "token0Symbol": { + "type": "String" + }, + "token1Symbol": { + "type": "String" + } + } + }, + "positionCardUnclaimedFees": "Unclaimed fees: ", + "positionCardViewMore": "View more", + "positionStatusClosed": "Closed", + "positionStatusInRange": "In Range", + "positionStatusOutOfRange": "Out of Range", + "positionsPageCantFindAPosition": "Can’t find a position? Try switching the app’s network to \"All Networks\" or reload the page", + "positionsPageErrorStateDescription": "An error occurred while loading your positions.\nPlease try again. If the issue persists, feel free to contact us", + "positionsPageMyPositions": "My Positions", + "positionsPageNoPositionsDescription": "Hm… It looks like you don’t have any positions yet.\nWant to create one?", + "positionsPageNoPositionsInNetwork": "No positions in {network}", + "@positionsPageNoPositionsInNetwork": { + "placeholders": { + "network": { + "type": "String" + } + } + }, + "positionsPageNoPositionsInNetworkDescription": "It looks like you don’t have any positions in {network} yet.\nGo ahead and create one to get started!", + "@positionsPageNoPositionsInNetworkDescription": { + "placeholders": { + "network": { + "type": "String" + } + } + }, + "positionsPageNoPositionsTitle": "You don’t have any positions yet", + "positionsPageNotConnectedDescription": "Wallet not connected. Please connect your\nwallet to view your positions", + "positionsPageShowHideClosedPositions": "{isHidden, select, true {Show} false {Hide} other {Show/Hide}} closed positions", + "@positionsPageShowHideClosedPositions": { + "description": "Dynamically shows 'Hide' or 'Show' based on the isHidden boolean.", + "placeholders": { + "isHidden": { + "type": "String", + "example": "true" + } + } + }, + "preview": "Preview", + "previewDepositModalApproveSuccessSnackBarHelperButtonTitle": "View Transaction", + "previewDepositModalApproveSuccessSnackBarMessage": "{tokenSymbol} approved successfully. ", + "@previewDepositModalApproveSuccessSnackBarMessage": { + "placeholders": { + "tokenSymbol": { + "type": "String" + } + } + }, + "previewDepositModalApproveToken": "Approve {tokenSymbol}", + "@previewDepositModalApproveToken": { + "placeholders": { + "tokenSymbol": { + "type": "String" + } + } + }, + "previewDepositModalApprovingToken": "Approving {tokenSymbol}", + "@previewDepositModalApprovingToken": { + "placeholders": { + "tokenSymbol": { + "type": "String" + } + } + }, + "previewDepositModalAutoSlippageCheckErrorMessage": "Strong market movement! Slippage exceeded. Try again or adjust tolerance.", + "previewDepositModalCubitApprovedSnackBarMessage": "Successfully approved {tokenSymbol}", + "@previewDepositModalCubitApprovedSnackBarMessage": { + "placeholders": { + "tokenSymbol": { + "type": "String" + } + } + }, + "previewDepositModalCubitApprovingSnackBarMessage": "Approving...", + "previewDepositModalCubitDepositingSnackBarMessage": "Depositing into {token0Symbol}/{token1Symbol} Pool...", + "@previewDepositModalCubitDepositingSnackBarMessage": { + "placeholders": { + "token0Symbol": { + "type": "String" + }, + "token1Symbol": { + "type": "String" + } + } + }, + "previewDepositModalCurrentPrice": "Current Price", + "previewDepositModalDeposit": "Deposit", + "previewDepositModalDepositSuccessSnackBarHelperButtonTitle": "View Transaction", + "previewDepositModalDepositSuccessSnackBarMessage": "Successfully Deposited into the {baseTokenSymbol}/{quoteTokenSymbol} Pool at {protocol}. ", + "@previewDepositModalDepositSuccessSnackBarMessage": { + "placeholders": { + "baseTokenSymbol": { + "type": "String" + }, + "quoteTokenSymbol": { + "type": "String" + }, + "protocol": { + "type": "String" + } + } + }, + "previewDepositModalDepositingIntoPool": "Depositing into {baseTokenSymbol}/{quoteTokenSymbol} pool", + "@previewDepositModalDepositingIntoPool": { + "placeholders": { + "baseTokenSymbol": { + "type": "String" + }, + "quoteTokenSymbol": { + "type": "String" + } + } + }, + "previewDepositModalError": "Error", + "previewDepositModalInRange": "In Range", + "previewDepositModalMaxPrice": "Max Price", + "previewDepositModalMinPrice": "Min Price", + "previewDepositModalMyPosition": "My Position", + "previewDepositModalNetwork": "Network", + "previewDepositModalOutOfRange": "Out of Range", + "previewDepositModalProtocol": "Protocol", + "previewDepositModalSlippageCheckErrorMessage": "Slippage Check! Please try increasing your slippage for this transaction", + "previewDepositModalTitle": "Preview Deposit", + "previewDepositModalTransactionErrorSnackBarHelperButtonTitle": "Contact us", + "previewDepositModalTransactionErrorSnackBarMessage": "Transaction Failed. Please try again, if the problem persists, ", + "previewDepositModalWaitingTransaction": "Waiting Transaction", + "previewDepositModalWaitingTransactionSnackBarHelperButtonTitle": "View on Explorer", + "previewDepositModalWaitingTransactionSnackBarMessage": "Waiting transaction to be confirmed. ", + "previewDepositModalYearlyYield": "Yearly Yield", + "privacyPolicy": "Privacy Policy", + "rangeSelectorMaxRange": "Max Range", + "rangeSelectorMinRange": "Min Range", + "searchResults": "Search results", + "selectToken": "Select Token", + "slippageExplanation": "Slippage protects you by reverting the transaction if the price changes unfavorably beyond the percentage. This is necessary to prevent losses while adding liquidity", + "somethingWhenWrong": "Something went wrong", + "system": "System", + "threeMonths": "3 Months", + "threeMonthsCompact": "90d", + "token0": "Token A", + "token1": "Token B", + "tokenSelectorModalDescription": "Pick a token from our list or search by symbol or address to build your ideal liquidity pool!", + "tokenSelectorModalErrorDescription": "We hit a snag loading your token list. Give it another go, and if it keeps happening, feel free to reach us", + "tokenSelectorModalSearchAlertForAllNetworks": "When ‘All Networks’ is selected, you can only search by name or symbol. To search by address as well, please select a specific network", + "tokenSelectorModalSearchErrorDescription": "We hit a snag while searching for a token matching {searchedTerm}. Give it another go, and if it keeps happening, feel free to reach us", + "@tokenSelectorModalSearchErrorDescription": { + "placeholders": { + "searchedTerm": { + "type": "String" + } + } + }, + "tokenSelectorModalSearchTitle": "Search token or paste address", + "tokenSelectorModalSearchTitleAllNetworks": "Search token by symbol or name", + "tokenSelectorModalTitle": "Select a token", + "tokenSelectorModalTokenGroups": "Token Groups", + "tokenSelectorModalTokenGroupsTooltipMessage": "Token groups let you search pools using multiple tokens in one go. Think of them like batch queries, want all USD stablecoins? Pick the group and we'll surface every relevant pool. You can also match groups against single tokens or other groups to discover deep liquidity.", + "tvl": "TVL", + "twentyFourHours": "24h", + "twentyFourHoursCompact": "24h", + "understood": "Understood", + "unknown": "Unknown", + "week": "Week", + "weekCompact": "7d", + "whatsThisQuestionText": "What's this?", + "yieldCardAverageYieldYearly": "Average Yearly Yield", + "yieldCardDeposit": "Deposit", + "@yieldCardDeposit": { + "description": "Used by the yield card to proceed to the deposit page with the selected yield card", + "type": "text" + }, + "yieldCardNetworkTooltipDescription": "This pool is at {network}", + "@yieldCardNetworkTooltipDescription": { + "placeholders": { + "network": { + "type": "String" + } + } + }, + "yieldCardThisPoolIsAtNetwork": "This pool is at {network}", + "@yieldCardThisPoolIsAtNetwork": { + "description": "Used by the yield card to explain to the user that the pool is at the {network}, in form of a tooltip", + "placeholders": { + "network": { + "type": "String" + } + } + }, + "yieldCardTimeFrameBest": "{timeFrame} best", + "@yieldCardTimeFrameBest": { + "placeholders": { + "timeFrame": { + "type": "String" + } + } + }, + "yieldCardTimeframeYield": "{timeframe} Yield", + "@yieldCardTimeframeYield": { + "description": "Used by the yield card to show a title for the yield based on the time frame selected", + "placeholders": { + "timeframe": { + "description": "The timeframe for which the yield is calculated (24h, 7d, etc.)", + "type": "String" + } + } + }, + "yieldCardVisitProtocol": "Visit {protocolName}", + "@yieldCardVisitProtocol": { + "placeholders": { + "protocolName": { + "type": "String" + } + } + }, + "yieldCardYearlyYield": "Yearly Yield", + "@yieldCardYearlyYield": { + "description": "Used by the yield card as title for the percentage of yearly yield" + }, + "yieldCardYieldExplanation": "Estimated annual yield based on {timeframeLabel} of data from fees distributed to liquidity providers.", + "@yieldCardYieldExplanation": { + "placeholders": { + "timeframeLabel": { + "description": "The timeframe for which the yield is calculated (24h, 7d, etc.)", + "type": "String" + } }, - "twentyFourHours": "24h", - "light": "Light", - "dark": "Dark", - "system": "System", - "week": "Week", - "weekCompact": "7d", - "appFooterTermsOfUse": "Terms of Use", - "privacyPolicy": "Privacy Policy", - "appFooterContactUs": "Contact Us", - "appCookiesConsentWidgetDescription": "We use cookies to ensure that we give you the best experience on our app. By continuing to use Zup Protocol, you agree to our", - "appFooterDocs": "Docs", - "appFooterFAQ": "FAQ", - "understood": "Understood", - "depositPageShowingOnlyPoolsWithMoreThan": "Showing only liquidity pools with more than {minLiquidity}.", - "@depositPageShowingOnlyPoolsWithMoreThan": { - "placeholders": { - "minLiquidity": { - "type": "String" - } - } - }, - "depositPageShowingAllPools": "Showing all liquidity pools.", - "depositPageSearchAllPools": "Search all pools?", - "depositPageSearchOnlyForPoolsWithMorethan": "Search only for pools with more than {minLiquidity}?", - "@depositPageSearchOnlyForPoolsWithMorethan": { - "placeholders": { - "minLiquidity": { - "type": "String" - } - } - }, - "depositPageTrySearchAllPools": "Try search all pools?", - "depositPageMinLiquiditySearchAlert": "You’ve set the search to only show pools with more than {minLiquidity}.", - "@depositPageMinLiquiditySearchAlert": { - "placeholders": { - "minLiquidity": { - "type": "String" - } - } - }, - "slippageExplanation": "Slippage protects you by reverting the transaction if the price changes unfavorably beyond the percentage. This is necessary to prevent losses while adding liquidity", - "month": "Month", - "minutes": "Minutes", - "depositSettingsDropdownChildHighSlippageWarningText": "High slippage can lead to Front Running and losses. Be careful! ", - "whatsThisQuestionText": "What's this?", - "depositSettingsDropdownChildTransactionDeadlineExplanation": "Your transaction will be reverted if it is pending for more than this amount of time", - "depositSettingsDropdownTransactionDeadline": "Transaction Deadline", - "depositPagePercentSlippage": "{valuePercent} Slippage", - "@depositPagePercentSlippage": { - "placeholders": { - "valuePercent": { - "type": "String" - } - } - }, - "threeMonths": "3 Months", - "twentyFourHoursCompact": "24h", - "monthCompact": "30d", - "threeMonthsCompact": "90d", - "depositSettingsDropdownChildMaxSlippage": "Max Slippage", - "depositPageDepositSectionTitle": "Deposit", - "previewDepositModalWaitingTransaction": "Waiting Transaction", - "previewDepositModalApprovingToken": "Approving {tokenSymbol}", - "@previewDepositModalApprovingToken": { - "placeholders": { - "tokenSymbol": { - "type": "String" - } - } - }, - "previewDepositModalDepositingIntoPool": "Depositing into {baseTokenSymbol}/{quoteTokenSymbol} pool", - "@previewDepositModalDepositingIntoPool": { - "placeholders": { - "baseTokenSymbol": { - "type": "String" - }, - "quoteTokenSymbol": { - "type": "String" - } - } - }, - "previewDepositModalApproveToken": "Approve {tokenSymbol}", - "@previewDepositModalApproveToken": { - "placeholders": { - "tokenSymbol": { - "type": "String" - } - } - }, - "previewDepositModalDeposit": "Deposit", - "previewDepositModalError": "Error", - "previewDepositModalCurrentPrice": "Current Price", - "depositPageDepositSectionTokenNotNeeded": "{tokenSymbol} is not necessary for your selected range", - "@depositPageDepositSectionTokenNotNeeded": { - "placeholders": { - "tokenSymbol": { - "type": "String" - } - } - }, - "preview": "Preview", - "previewDepositModalTitle": "Preview Deposit", - "previewDepositModalWaitingTransactionSnackBarMessage": "Waiting transaction to be confirmed. ", - "previewDepositModalApproveSuccessSnackBarMessage": "{tokenSymbol} approved successfully. ", - "@previewDepositModalApproveSuccessSnackBarMessage": { - "placeholders": { - "tokenSymbol": { - "type": "String" - } - } - }, - "previewDepositModalMyPosition": "My Position", - "previewDepositModalOutOfRange": "Out of Range", - "previewDepositModalInRange": "In Range", - "previewDepositModalProtocol": "Protocol", - "previewDepositModalNetwork": "Network", - "previewDepositModalYearlyYield": "Yearly Yield", - "previewDepositModalTransactionErrorSnackBarMessage": "Transaction Failed. Please try again, if the problem persists, ", - "previewDepositModalTransactionErrorSnackBarHelperButtonTitle": "Contact us", - "previewDepositModalDepositSuccessSnackBarMessage": "Successfully Deposited into the {baseTokenSymbol}/{quoteTokenSymbol} Pool at {protocol}. ", - "@previewDepositModalDepositSuccessSnackBarMessage": { - "placeholders": { - "baseTokenSymbol": { - "type": "String" - }, - "quoteTokenSymbol": { - "type": "String" - }, - "protocol": { - "type": "String" - } - } - }, - "yieldCardTimeFrameBest": "{timeFrame} best", - "@yieldCardTimeFrameBest": { - "placeholders": { - "timeFrame": { - "type": "String" - } - } - }, - "yieldCardThisPoolIsAtNetwork": "This pool is at {network}", - "@yieldCardThisPoolIsAtNetwork": { - "placeholders": { - "network": { - "type": "String" - } - } - }, - "yieldCardYearlyYield": "Yearly Yield", - "yieldCardVisitProtocol": "Visit {protocolName}", - "@yieldCardVisitProtocol": { - "placeholders": { - "protocolName": { - "type": "String" - } - } - }, - "yieldCardAverageYieldYearly": "Average Yearly Yield", - "previewDepositModalDepositSuccessSnackBarHelperButtonTitle": "View Transaction", - "previewDepositModalApproveSuccessSnackBarHelperButtonTitle": "View Transaction", - "previewDepositModalWaitingTransactionSnackBarHelperButtonTitle": "View on Explorer", - "depositPageInvalidRange": "Invalid range", - "depositPageMinRangeOutOfRangeWarningText": "You will not earn fees until the market price move up into your range", - "depositPageMaxRangeOutOfRangeWarningText": "You will not earn fees until the market price move down into your range", - "depositPageInvalidRangeErrorText": "Max range should be greater than min range", - "previewDepositModalMinPrice": "Min Price", - "previewDepositModalMaxPrice": "Max Price", - "rangeSelectorMinRange": "Min Range", - "rangeSelectorMaxRange": "Max Range", - "loading": "Loading...", - "exchangesFilterDropdownButtonDropdownClearAll": "Clear All", - "exchangesFilterDropdownButtonDropdownSelectAll": "Select All", - "exchangesFilterDropdownButtonDropdownSearchHint": "Search by name", - "exchangesFilterDropdownButtonDropdownNotFoundStateTitle": "Not found", - "exchangesFilterDropdownButtonDropdownNotFoundStateDescription": "No supported exchanges found with this name", - "exchangesFilterDropdownButtonErrorSnackBarMessage": "Uh-oh! Something went wrong loading the exchanges. Please try refreshing the page.", - "exchangesFilterDropdownButtonTitle": "Exchanges", - "exchangesFilterDropdownButtonTitleNumered": "Exchanges ({exchangesCount})", - "@exchangesFilterDropdownButtonTitleNumered": { - "placeholders": { - "exchangesCount": { - "type": "String" - } - } - }, - "createPageSelectTokensStageTokenA": "Token A", - "createPageSelectTokensStageTokenB": "Token B", - "createPageSelectTokensStageSearchSettings": "Search Settings", - "depositPageLoadingStep1Title": "Matching Tokens...", - "depositPageLoadingStep1Description": "Pairing Token A and Token B to kick off the search for top yields!", - "depositPageLoadingStep2Title": "Pair hunting…", - "depositPageLoadingStep2Description": "Searching through more than a thousand pool combos… so you don't have to", - "depositPageLoadingStep3Title": "Yield optimizer at work…", - "depositPageLoadingStep3Description": "Scanning pools, calculating returns, and filtering the noise", - "depositPageBestYieldsIn": "Best Yields in", - "depositPageLoadingStep4Title": "Organizing the best pools for you…", - "depositPageLoadingStep4Description": "Hang tight, we're filtering and organizing the best pools for you", - "depositPageErrorStateTitle": "Oops! Something went wrong!", - "depositPageErrorStateDescription": "We ran into a issue while trying to find the best pool. Give it another shot, and if it keeps happening, don’t hesitate to reach out to us!", - "depositPageEmptyStateTitle": "No Pools Found", - "depositPageEmptyStateDescription": "Seems like that there are no pools matching your defined settings at the moment. Would you like to either change your settings or try another combination?", - "depositPageEmptyStateHelpButtonTitle": "Go Back to New Position", - "depositPageBackButtonTitle": "Select Pair", - "depositPageTitle": "Add liquidity", - "depositPageTimeFrameTooltipMessage": "Each time frame shows yields based on past performance. Shorter terms (24h, 7d) reflect recent trends and may suit short-term strategies. Longer terms (30d, 90d) offer a broader performance view for long-term decisions.", - "depositPageTimeFrameTooltipHelperButtonTitle": " Learn more", - "depositPageTimeFrameTitle": "Preferred time frame", - "depositPageNoYieldSelectedTitle": "Pick a yield to deposit", - "depositPageNoYieldSelectedDescription": "Pick any yield card above and dive into depositing your liquidity!", - "depositPageRangeSectionTitle": "Select Range", - "depositPageRangeSectionFullRange": "Full Range", - "depositPageInvalidTokenAmount": "Enter a valid amount for {tokenSymbol}", - "@depositPageInvalidTokenAmount": { - "placeholders": { - "tokenSymbol": { - "type": "String" - } - } - }, - "depositPageInsufficientTokenBalance": "Insufficient {tokenSymbol} balance", - "@depositPageInsufficientTokenBalance": { - "placeholders": { - "tokenSymbol": { - "type": "String" - } - } - }, - "depositPagePleaseEnterAmountForToken": "Please enter an amount for {tokenSymbol}", - "@depositPagePleaseEnterAmountForToken": { - "placeholders": { - "tokenSymbol": { - "type": "String" - } - } - }, - "depositPageDeposit": "Deposit", - "connectYourWallet": "Connect your wallet", - "tokenSelectorModalTitle": "Select a token", - "tokenSelectorModalTokenGroups": "Token Groups", - "tokenSelectorModalTokenGroupsTooltipMessage": "Token groups let you search pools using multiple tokens in one go. Think of them like batch queries, want all USD stablecoins? Pick the group and we'll surface every relevant pool. You can also match groups against single tokens or other groups to discover deep liquidity.", - "tokenSelectorModalDescription": "Pick a token from our list or search by symbol or address to build your ideal liquidity pool!", - "tokenSelectorModalSearchTitle": "Search token or paste address", - "tokenSelectorModalSearchTitleAllNetworks": "Search token by symbol or name", - "tokenSelectorModalSearchAlertForAllNetworks": "When ‘All Networks’ is selected, you can only search by name or symbol. To search by address as well, please select a specific network", - "tokenSelectorModalErrorDescription": "We hit a snag loading your token list. Give it another go, and if it keeps happening, feel free to reach us", - "tokenSelectorModalSearchErrorDescription": "We hit a snag while searching for a token matching {searchedTerm}. Give it another go, and if it keeps happening, feel free to reach us", - "noResultsFor": "No results for", - "searchResults": "Search results", - "popularTokens": "Popular Tokens", - "@tokenSelectorModalSearchErrorDescription": { - "placeholders": { - "searchedTerm": { - "type": "String" - } - } - }, - "connectMyWallet": "Connect My Wallet", - "connectWallet": "Connect Wallet", - "positionsPageNotConnectedDescription": "Wallet not connected. Please connect your\nwallet to view your positions", - "positionsPageNoPositionsTitle": "You don’t have any positions yet", - "positionsPageNoPositionsDescription": "Hm… It looks like you don’t have any positions yet.\nWant to create one?", - "newPosition": "New Position", - "createNewPosition": "Create new position", - "positionCardMin": "Min: ", - "positionCardMax": "Max: ", - "positionCardLiquidity": "Liquidity: ", - "positionCardUnclaimedFees": "Unclaimed fees: ", - "positionCardViewMore": "View more", - "positionsPageNoPositionsInNetwork": "No positions in {network}", - "positionsPageShowHideClosedPositions": "{isHidden, select, true {Show} false {Hide} other {Show/Hide}} closed positions", - "@positionsPageShowHideClosedPositions": { - "description": "Dynamically shows 'Hide' or 'Show' based on the isHidden boolean.", - "placeholders": { - "isHidden": { - "type": "String", - "example": "true" - } - } - }, - "@positionsPageNoPositionsInNetwork": { - "placeholders": { - "network": { - "type": "String" - } - } - }, - "positionsPageNoPositionsInNetworkDescription": "It looks like you don’t have any positions in {network} yet.\nGo ahead and create one to get started!", - "@positionsPageNoPositionsInNetworkDescription": { - "placeholders": { - "network": { - "type": "String" - } - } - }, - "positionCardTokenPerToken": "{token0Qtd} {token0Symbol} per {token1Symbol}", - "@positionCardTokenPerToken": { - "placeholders": { - "token0Qtd": { - "type": "String" - }, - "token0Symbol": { - "type": "String" - }, - "token1Symbol": { - "type": "String" - } - } - }, - "positionsPageMyPositions": "My Positions", - "appHeaderMyPositions": "My Positions (Soon)", - "appHeaderNewPosition": "New Position", - "appBottomNavigationBarMyPositions": "My Positions (Soon)", - "appBottomNavigationBarNewPosition": "New Position", - "positionsPageCantFindAPosition": "Can’t find a position? Try switching the app’s network to \"All Networks\" or reload the page", - "somethingWhenWrong": "Something went wrong", - "positionsPageErrorStateDescription": "An error occurred while loading your positions.\nPlease try again. If the issue persists, feel free to contact us", - "positionStatusInRange": "In Range", - "positionStatusOutOfRange": "Out of Range", - "positionStatusClosed": "Closed", - "unknown": "Unknown", - "selectToken": "Select Token", - "token0": "Token A", - "token1": "Token B", - "depositSuccessModalTitle": "Deposit Successful!", - "depositSuccessModalDescriptionPart1": "You have successfully deposited into", - "depositSuccessModalDescriptionPart2": "Pool at", - "depositSuccessModalDescriptionPart3": "on", - "previewDepositModalCubitDepositingSnackBarMessage": "Depositing into {token0Symbol}/{token1Symbol} Pool...", - "@previewDepositModalCubitDepositingSnackBarMessage": { - "placeholders": { - "token0Symbol": { - "type": "String" - }, - "token1Symbol": { - "type": "String" - } - } - }, - "previewDepositModalCubitApprovingSnackBarMessage": "Approving...", - "previewDepositModalCubitApprovedSnackBarMessage": "Successfully approved {tokenSymbol}", - "@previewDepositModalCubitApprovedSnackBarMessage": { - "placeholders": { - "tokenSymbol": { - "type": "String" - } - } - }, - "depositSuccessModalViewPositionOnDEX": "View Position on {dexName}", - "@depositSuccessModalViewPositionOnDEX": { - "placeholders": { - "dexName": { - "type": "String" - } - } - }, - "close": "Close", - "depositPageDepositWithNativeToken": "Deposit with Native {tokenSymbol}", - "@depositPageDepositWithNativeToken": { - "placeholders": { - "tokenSymbol": { - "type": "String" - } - } - }, - "createPageSettingsDropdownMinimumLiquidity": "Minimum Pool Liquidity", - "createPageSettingsDropdownAllowedPoolTypes": "Allowed Pool Types", - "createPageSettingsDropdownAllowedPoolTypesDescription": "Filter the types of liquidity pools to include in your search", - "createPageSettingsDropdownMinimumLiquidityExplanation": "Filter pools by minimum liquidity. We’ll exclude pools with less liquidity than specified, as low Liquidity can lead to misleading yields. This helps you find more reliable opportunities", - "createPageSettingsDropdownMiniumLiquidityLowWarning": "Low minimum TVL can lead to misleading yields.", - "appSettingsDropdownTestnetMode": "Testnet Mode", - "previewDepositModalSlippageCheckErrorMessage": "Slippage Check! Please try increasing your slippage for this transaction", - "previewDepositModalAutoSlippageCheckErrorMessage": "Strong market movement! Slippage exceeded. Try again or adjust tolerance.", - "createPageTitle": "New Position", - "tvl": "TVL", - "createPageShowMeTheMoney": "Show me the money!", - "createPageDescription": "Ready to dive in? First, pick the dynamic duo of tokens you want to team up in the pool. Just choose your pair right below and you’re set to make some magic!", - "letsGiveItAnotherGo": "Let’s give it another go, because why not?", - "letsGiveItAnotherShot": "Let’s give it another shot" + "description": "Used by the yield card to explain to the user how the yield is calculated in form of a tooltip" + }, + "yieldsPageApplyTvlFilterButtonTitle": "Show only pools with more than {tvlUSD} TVL.", + "@yieldsPageApplyTvlFilterButtonTitle": { + "description": "Used by the yields page as title for the button to search only for pools with more than the specified TVL", + "placeholders": { + "tvlUSD": { + "description": "The min tvl USD set for the search formatted as USD", + "type": "String" + } + } + }, + "yieldsPageBackButtonTitle": "Select Pair", + "@yieldsPageBackButtonTitle": { + "description": "Used by the yields page as title for the back button, to go back to the previous step" + }, + "yieldsPageDescription": "Select the yield that most suits your needs and deposit to start earning", + "@yieldsPageDescription": { + "description": "The description of the yield page" + }, + "yieldsPageDisplayingAllPoolsAlert": "Displaying all liquidity pools.", + "@yieldsPageDisplayingAllPoolsAlert": { + "description": "Used by the yields page to alert the user that their search was made to find any pool, ignoring user-set filters" + }, + "yieldsPageDisplayingPoolsWithMinTvlAlert": "Displaying only liquidity pools with more than {tvlUSD} TVL.", + "@yieldsPageDisplayingPoolsWithMinTvlAlert": { + "description": "Used by the yields page to alert the user that their search was limited to pools with more than the specified TVL", + "placeholders": { + "tvlUSD": { + "description": "The min tvl USD set for the search formatted as USD", + "type": "String" + } + } + }, + "yieldsPageEmptyStateDescription": "Seems like that there are no pools matching your defined settings at the moment. Would you like to either change your settings or try another combination?", + "@yieldsPageEmptyStateDescription": { + "description": "Used by the yields page as description for the empty state when no pools are found for the selected pair" + }, + "yieldsPageEmptyStateHelperButtonTitle": "Go Back to New Position", + "@yieldsPageEmptyStateHelperButtonTitle": { + "description": "Used by the yields page as title for the helper button in the empty state when no pools are found for the selected pair, useful for going back or doing something else" + }, + "yieldsPageEmptyStateMinTVLAlert": "Searched only for liquidity pools with more than {tvlUSD} TVL", + "@yieldsPageEmptyStateMinTVLAlert": { + "description": "Used in the empty state on the Yields page to alert the user that their search was limited to pools with more than the specified TVL. This may explain why no pools were found", + "placeholders": { + "tvlUSD": { + "description": "The min total value locked USD set for the search formatted as USD", + "type": "String" + } + } + }, + "yieldsPageEmptyStateTitle": "No Pools Found", + "@yieldsPageEmptyStateTitle": { + "description": "Used by the yields page as title for the empty state when no pools are found for the selected pair" + }, + "yieldsPageErrorStateDescription": "We ran into a issue while trying to find the best pool. Give it another shot, and if it keeps happening, don't hesitate to reach out to us!", + "@yieldsPageErrorStateDescription": { + "description": "Used by the yields page as description for the error state when something went wrong while trying to find the yields for the selected pair" + }, + "yieldsPageErrorStateTitle": "Oops! Something went wrong!", + "@yieldsPageErrorStateTitle": { + "description": "Used by the yields page as title for the error state when something went wrong while trying to find the yields for the selected pair" + }, + "yieldsPageLoadingStep1Description": "Pairing Token A and Token B to kick off the search for top yields!", + "@yieldsPageLoadingStep1Description": { + "description": "Used by the yields page as description for the first loading step when searching for the best yield for the selected pair" + }, + "yieldsPageLoadingStep1Title": "Matching Tokens...", + "@yieldsPageLoadingStep1Title": { + "description": "Used by the yields page as title for the first loading step when searching for the best yield for the selected pair" + }, + "yieldsPageLoadingStep2Description": "Searching through more than a thousand pool combos… so you don't have to", + "@yieldsPageLoadingStep2Description": { + "description": "Used by the yields page as description for the second loading step when searching for the best yield for the selected pair" + }, + "yieldsPageLoadingStep2Title": "Pair hunting…", + "@yieldsPageLoadingStep2Title": { + "description": "Used by the yields page as title for the second loading step when searching for the best yield for the selected pair" + }, + "yieldsPageLoadingStep3Description": "Scanning pools, calculating returns, and filtering the noise", + "@yieldsPageLoadingStep3Description": { + "description": "Used by the yields page as description for the third loading step when searching for the best yield for the selected pair" + }, + "yieldsPageLoadingStep3Title": "Yield optimizer at work…", + "@yieldsPageLoadingStep3Title": { + "description": "Used by the yields page as title for the third loading step when searching for the best yield for the selected pair" + }, + "yieldsPageLoadingStep4Description": "Hang tight, we're filtering and organizing the best pools for you", + "@yieldsPageLoadingStep4Description": { + "description": "Used by the yields page as description for the fourth loading step when searching for the best yield for the selected pair" + }, + "yieldsPageLoadingStep4Title": "Organizing the best pools for you…", + "@yieldsPageLoadingStep4Title": { + "description": "Used by the yields page as title for the fourth loading step when searching for the best yield for the selected pair" + }, + "yieldsPageSearchAllPools": "Search all pools", + "@yieldsPageSearchAllPools": { + "description": "Used by the yields page as a button to search all pools, ignoring user-set filters" + }, + "yieldsPageTimeframeExplanation": "Each time frame shows yields based on past performance. Shorter windows (24h, 7d) highlight recent trends for quick moves. Longer windows (30d, 90d) provide a broader view for mid to long-term decisions", + "@yieldsPageTimeframeExplanation": { + "description": "Used by by the yields page to explain what are the time frames and how they differ from each other" + }, + "yieldsPageTimeframeSelectorTitle": "Best yields in", + "@yieldsPageTimeframeSelectorTitle": { + "description": "Used by the yields page as a title for the timeframe selector, to show yields based in the selected timeframe" + }, + "yieldsPageTitle": "Choose a pool for you", + "@yieldsPageTitle": { + "description": "The main title of the yield page" + } } \ No newline at end of file diff --git a/lib/l10n/gen/app_localizations.dart b/lib/l10n/gen/app_localizations.dart index 1be06d7..e8d6fcd 100644 --- a/lib/l10n/gen/app_localizations.dart +++ b/lib/l10n/gen/app_localizations.dart @@ -93,47 +93,41 @@ abstract class S { /// A list of this localizations delegate's supported locales. static const List supportedLocales = [Locale('en')]; - /// No description provided for @yieldCardNetworkTooltipDescription. - /// - /// In en, this message translates to: - /// **'This pool is at {network}'** - String yieldCardNetworkTooltipDescription({required String network}); - - /// No description provided for @twentyFourHours. + /// No description provided for @appBottomNavigationBarMyPositions. /// /// In en, this message translates to: - /// **'24h'** - String get twentyFourHours; + /// **'My Positions (Soon)'** + String get appBottomNavigationBarMyPositions; - /// No description provided for @light. + /// No description provided for @appBottomNavigationBarNewPosition. /// /// In en, this message translates to: - /// **'Light'** - String get light; + /// **'New Position'** + String get appBottomNavigationBarNewPosition; - /// No description provided for @dark. + /// No description provided for @appCookiesConsentWidgetDescription. /// /// In en, this message translates to: - /// **'Dark'** - String get dark; + /// **'We use cookies to ensure that we give you the best experience on our app. By continuing to use Zup Protocol, you agree to our'** + String get appCookiesConsentWidgetDescription; - /// No description provided for @system. + /// No description provided for @appFooterContactUs. /// /// In en, this message translates to: - /// **'System'** - String get system; + /// **'Contact Us'** + String get appFooterContactUs; - /// No description provided for @week. + /// No description provided for @appFooterDocs. /// /// In en, this message translates to: - /// **'Week'** - String get week; + /// **'Docs'** + String get appFooterDocs; - /// No description provided for @weekCompact. + /// No description provided for @appFooterFAQ. /// /// In en, this message translates to: - /// **'7d'** - String get weekCompact; + /// **'FAQ'** + String get appFooterFAQ; /// No description provided for @appFooterTermsOfUse. /// @@ -141,159 +135,155 @@ abstract class S { /// **'Terms of Use'** String get appFooterTermsOfUse; - /// No description provided for @privacyPolicy. + /// No description provided for @appHeaderMyPositions. /// /// In en, this message translates to: - /// **'Privacy Policy'** - String get privacyPolicy; + /// **'My Positions (Soon)'** + String get appHeaderMyPositions; - /// No description provided for @appFooterContactUs. + /// No description provided for @appHeaderNewPosition. /// /// In en, this message translates to: - /// **'Contact Us'** - String get appFooterContactUs; + /// **'New Position'** + String get appHeaderNewPosition; - /// No description provided for @appCookiesConsentWidgetDescription. + /// No description provided for @appSettingsDropdownTestnetMode. /// /// In en, this message translates to: - /// **'We use cookies to ensure that we give you the best experience on our app. By continuing to use Zup Protocol, you agree to our'** - String get appCookiesConsentWidgetDescription; + /// **'Testnet Mode'** + String get appSettingsDropdownTestnetMode; - /// No description provided for @appFooterDocs. + /// No description provided for @close. /// /// In en, this message translates to: - /// **'Docs'** - String get appFooterDocs; + /// **'Close'** + String get close; - /// No description provided for @appFooterFAQ. + /// No description provided for @connectMyWallet. /// /// In en, this message translates to: - /// **'FAQ'** - String get appFooterFAQ; + /// **'Connect My Wallet'** + String get connectMyWallet; - /// No description provided for @understood. + /// No description provided for @connectWallet. /// /// In en, this message translates to: - /// **'Understood'** - String get understood; + /// **'Connect Wallet'** + String get connectWallet; - /// No description provided for @depositPageShowingOnlyPoolsWithMoreThan. + /// No description provided for @connectYourWallet. /// /// In en, this message translates to: - /// **'Showing only liquidity pools with more than {minLiquidity}.'** - String depositPageShowingOnlyPoolsWithMoreThan({ - required String minLiquidity, - }); + /// **'Connect your wallet'** + String get connectYourWallet; - /// No description provided for @depositPageShowingAllPools. + /// No description provided for @createNewPosition. /// /// In en, this message translates to: - /// **'Showing all liquidity pools.'** - String get depositPageShowingAllPools; + /// **'Create new position'** + String get createNewPosition; - /// No description provided for @depositPageSearchAllPools. + /// No description provided for @createPageDescription. /// /// In en, this message translates to: - /// **'Search all pools?'** - String get depositPageSearchAllPools; + /// **'Ready to dive in? First, pick the dynamic duo of tokens you want to team up in the pool. Just choose your pair right below and you’re set to make some magic!'** + String get createPageDescription; - /// No description provided for @depositPageSearchOnlyForPoolsWithMorethan. + /// No description provided for @createPageSelectTokensStageSearchSettings. /// /// In en, this message translates to: - /// **'Search only for pools with more than {minLiquidity}?'** - String depositPageSearchOnlyForPoolsWithMorethan({ - required String minLiquidity, - }); + /// **'Search Settings'** + String get createPageSelectTokensStageSearchSettings; - /// No description provided for @depositPageTrySearchAllPools. + /// No description provided for @createPageSelectTokensStageTokenA. /// /// In en, this message translates to: - /// **'Try search all pools?'** - String get depositPageTrySearchAllPools; + /// **'Token A'** + String get createPageSelectTokensStageTokenA; - /// No description provided for @depositPageMinLiquiditySearchAlert. + /// No description provided for @createPageSelectTokensStageTokenB. /// /// In en, this message translates to: - /// **'You’ve set the search to only show pools with more than {minLiquidity}.'** - String depositPageMinLiquiditySearchAlert({required String minLiquidity}); + /// **'Token B'** + String get createPageSelectTokensStageTokenB; - /// No description provided for @slippageExplanation. + /// No description provided for @createPageSettingsDropdownAllowedPoolTypes. /// /// In en, this message translates to: - /// **'Slippage protects you by reverting the transaction if the price changes unfavorably beyond the percentage. This is necessary to prevent losses while adding liquidity'** - String get slippageExplanation; + /// **'Allowed Pool Types'** + String get createPageSettingsDropdownAllowedPoolTypes; - /// No description provided for @month. + /// No description provided for @createPageSettingsDropdownAllowedPoolTypesDescription. /// /// In en, this message translates to: - /// **'Month'** - String get month; + /// **'Filter the types of liquidity pools to include in your search'** + String get createPageSettingsDropdownAllowedPoolTypesDescription; - /// No description provided for @minutes. + /// No description provided for @createPageSettingsDropdownMinimumLiquidity. /// /// In en, this message translates to: - /// **'Minutes'** - String get minutes; + /// **'Minimum Pool Liquidity'** + String get createPageSettingsDropdownMinimumLiquidity; - /// No description provided for @depositSettingsDropdownChildHighSlippageWarningText. + /// No description provided for @createPageSettingsDropdownMinimumLiquidityExplanation. /// /// In en, this message translates to: - /// **'High slippage can lead to Front Running and losses. Be careful! '** - String get depositSettingsDropdownChildHighSlippageWarningText; + /// **'Filter pools by minimum liquidity. We’ll exclude pools with less liquidity than specified, as low Liquidity can lead to misleading yields. This helps you find more reliable opportunities'** + String get createPageSettingsDropdownMinimumLiquidityExplanation; - /// No description provided for @whatsThisQuestionText. + /// No description provided for @createPageSettingsDropdownMiniumLiquidityLowWarning. /// /// In en, this message translates to: - /// **'What\'s this?'** - String get whatsThisQuestionText; + /// **'Low minimum TVL can lead to misleading yields.'** + String get createPageSettingsDropdownMiniumLiquidityLowWarning; - /// No description provided for @depositSettingsDropdownChildTransactionDeadlineExplanation. + /// No description provided for @createPageShowMeTheMoney. /// /// In en, this message translates to: - /// **'Your transaction will be reverted if it is pending for more than this amount of time'** - String get depositSettingsDropdownChildTransactionDeadlineExplanation; + /// **'Show me the money!'** + String get createPageShowMeTheMoney; - /// No description provided for @depositSettingsDropdownTransactionDeadline. + /// No description provided for @createPageTitle. /// /// In en, this message translates to: - /// **'Transaction Deadline'** - String get depositSettingsDropdownTransactionDeadline; + /// **'New Position'** + String get createPageTitle; - /// No description provided for @depositPagePercentSlippage. + /// No description provided for @dark. /// /// In en, this message translates to: - /// **'{valuePercent} Slippage'** - String depositPagePercentSlippage({required String valuePercent}); + /// **'Dark'** + String get dark; - /// No description provided for @threeMonths. + /// Used by the deposit page as title for the loading state, before the pool is ready for the user to deposit /// /// In en, this message translates to: - /// **'3 Months'** - String get threeMonths; + /// **'Getting the pool ready for you...'** + String get depositPageLoadingTitle; - /// No description provided for @twentyFourHoursCompact. + /// Used by the deposit page as title for the back button to navigate back to the yields page, if the previous page was to select a yield /// /// In en, this message translates to: - /// **'24h'** - String get twentyFourHoursCompact; + /// **'Select Yield'** + String get depositPageBackToYieldsButtonTitle; - /// No description provided for @monthCompact. + /// Used by the deposit page as title for the back button to navigate back to the new position page, if the previous page isn't to select a yield, like paste the url to deposit directly /// /// In en, this message translates to: - /// **'30d'** - String get monthCompact; + /// **'Search other pools'** + String get depositPageBackToNewPositionButtonTitle; - /// No description provided for @threeMonthsCompact. + /// No description provided for @depositPageBestYieldsIn. /// /// In en, this message translates to: - /// **'90d'** - String get threeMonthsCompact; + /// **'Best Yields in'** + String get depositPageBestYieldsIn; - /// No description provided for @depositSettingsDropdownChildMaxSlippage. + /// No description provided for @depositPageDeposit. /// /// In en, this message translates to: - /// **'Max Slippage'** - String get depositSettingsDropdownChildMaxSlippage; + /// **'Deposit'** + String get depositPageDeposit; /// No description provided for @depositPageDepositSectionTitle. /// @@ -301,266 +291,291 @@ abstract class S { /// **'Deposit'** String get depositPageDepositSectionTitle; - /// No description provided for @previewDepositModalWaitingTransaction. + /// No description provided for @depositPageDepositSectionTokenNotNeeded. /// /// In en, this message translates to: - /// **'Waiting Transaction'** - String get previewDepositModalWaitingTransaction; + /// **'{tokenSymbol} is not necessary for your selected range'** + String depositPageDepositSectionTokenNotNeeded({required String tokenSymbol}); - /// No description provided for @previewDepositModalApprovingToken. + /// No description provided for @depositPageDepositWithNativeToken. /// /// In en, this message translates to: - /// **'Approving {tokenSymbol}'** - String previewDepositModalApprovingToken({required String tokenSymbol}); + /// **'Deposit with Native {tokenSymbol}'** + String depositPageDepositWithNativeToken({required String tokenSymbol}); - /// No description provided for @previewDepositModalDepositingIntoPool. + /// No description provided for @depositPageEmptyStateDescription. /// /// In en, this message translates to: - /// **'Depositing into {baseTokenSymbol}/{quoteTokenSymbol} pool'** - String previewDepositModalDepositingIntoPool({ - required String baseTokenSymbol, - required String quoteTokenSymbol, - }); + /// **'Seems like that there are no pools matching your defined settings at the moment. Would you like to either change your settings or try another combination?'** + String get depositPageEmptyStateDescription; - /// No description provided for @previewDepositModalApproveToken. + /// No description provided for @depositPageEmptyStateHelpButtonTitle. /// /// In en, this message translates to: - /// **'Approve {tokenSymbol}'** - String previewDepositModalApproveToken({required String tokenSymbol}); + /// **'Go Back to New Position'** + String get depositPageEmptyStateHelpButtonTitle; - /// No description provided for @previewDepositModalDeposit. + /// No description provided for @depositPageEmptyStateTitle. /// /// In en, this message translates to: - /// **'Deposit'** - String get previewDepositModalDeposit; + /// **'No Pools Found'** + String get depositPageEmptyStateTitle; - /// No description provided for @previewDepositModalError. + /// Used by the deposit page as description for the error state when fetching yield info /// /// In en, this message translates to: - /// **'Error'** - String get previewDepositModalError; + /// **'We ran into an issue while fetching the pool data. Try again, and if it keeps happening, don\'t hesitate to reach out to us!'** + String get depositPageErrorStateDescription; - /// No description provided for @previewDepositModalCurrentPrice. + /// Used by the deposit page as title for the error state when fetching yield info /// /// In en, this message translates to: - /// **'Current Price'** - String get previewDepositModalCurrentPrice; + /// **'Oops! Something went wrong!'** + String get depositPageErrorStateTitle; - /// No description provided for @depositPageDepositSectionTokenNotNeeded. + /// No description provided for @depositPageInsufficientTokenBalance. /// /// In en, this message translates to: - /// **'{tokenSymbol} is not necessary for your selected range'** - String depositPageDepositSectionTokenNotNeeded({required String tokenSymbol}); + /// **'Insufficient {tokenSymbol} balance'** + String depositPageInsufficientTokenBalance({required String tokenSymbol}); - /// No description provided for @preview. + /// No description provided for @depositPageInvalidRange. /// /// In en, this message translates to: - /// **'Preview'** - String get preview; + /// **'Invalid range'** + String get depositPageInvalidRange; - /// No description provided for @previewDepositModalTitle. + /// No description provided for @depositPageInvalidRangeErrorText. /// /// In en, this message translates to: - /// **'Preview Deposit'** - String get previewDepositModalTitle; + /// **'Max range should be greater than min range'** + String get depositPageInvalidRangeErrorText; - /// No description provided for @previewDepositModalWaitingTransactionSnackBarMessage. + /// No description provided for @depositPageInvalidTokenAmount. /// /// In en, this message translates to: - /// **'Waiting transaction to be confirmed. '** - String get previewDepositModalWaitingTransactionSnackBarMessage; + /// **'Enter a valid amount for {tokenSymbol}'** + String depositPageInvalidTokenAmount({required String tokenSymbol}); - /// No description provided for @previewDepositModalApproveSuccessSnackBarMessage. + /// No description provided for @depositPageLoadingStep1Description. /// /// In en, this message translates to: - /// **'{tokenSymbol} approved successfully. '** - String previewDepositModalApproveSuccessSnackBarMessage({ - required String tokenSymbol, - }); + /// **'Pairing Token A and Token B to kick off the search for top yields!'** + String get depositPageLoadingStep1Description; - /// No description provided for @previewDepositModalMyPosition. + /// No description provided for @depositPageLoadingStep1Title. /// /// In en, this message translates to: - /// **'My Position'** - String get previewDepositModalMyPosition; + /// **'Matching Tokens...'** + String get depositPageLoadingStep1Title; - /// No description provided for @previewDepositModalOutOfRange. + /// No description provided for @depositPageLoadingStep2Description. /// /// In en, this message translates to: - /// **'Out of Range'** - String get previewDepositModalOutOfRange; + /// **'Searching through more than a thousand pool combos… so you don\'t have to'** + String get depositPageLoadingStep2Description; - /// No description provided for @previewDepositModalInRange. + /// No description provided for @depositPageLoadingStep2Title. /// /// In en, this message translates to: - /// **'In Range'** - String get previewDepositModalInRange; + /// **'Pair hunting…'** + String get depositPageLoadingStep2Title; - /// No description provided for @previewDepositModalProtocol. + /// No description provided for @depositPageLoadingStep3Description. /// /// In en, this message translates to: - /// **'Protocol'** - String get previewDepositModalProtocol; + /// **'Scanning pools, calculating returns, and filtering the noise'** + String get depositPageLoadingStep3Description; - /// No description provided for @previewDepositModalNetwork. + /// No description provided for @depositPageLoadingStep3Title. /// /// In en, this message translates to: - /// **'Network'** - String get previewDepositModalNetwork; + /// **'Yield optimizer at work…'** + String get depositPageLoadingStep3Title; - /// No description provided for @previewDepositModalYearlyYield. + /// No description provided for @depositPageLoadingStep4Description. /// /// In en, this message translates to: - /// **'Yearly Yield'** - String get previewDepositModalYearlyYield; + /// **'Hang tight, we\'re filtering and organizing the best pools for you'** + String get depositPageLoadingStep4Description; - /// No description provided for @previewDepositModalTransactionErrorSnackBarMessage. + /// No description provided for @depositPageLoadingStep4Title. /// /// In en, this message translates to: - /// **'Transaction Failed. Please try again, if the problem persists, '** - String get previewDepositModalTransactionErrorSnackBarMessage; + /// **'Organizing the best pools for you…'** + String get depositPageLoadingStep4Title; - /// No description provided for @previewDepositModalTransactionErrorSnackBarHelperButtonTitle. + /// No description provided for @depositPageMaxRangeOutOfRangeWarningText. /// /// In en, this message translates to: - /// **'Contact us'** - String get previewDepositModalTransactionErrorSnackBarHelperButtonTitle; + /// **'You will not earn fees until the market price move down into your range'** + String get depositPageMaxRangeOutOfRangeWarningText; - /// No description provided for @previewDepositModalDepositSuccessSnackBarMessage. + /// No description provided for @depositPageMinLiquiditySearchAlert. /// /// In en, this message translates to: - /// **'Successfully Deposited into the {baseTokenSymbol}/{quoteTokenSymbol} Pool at {protocol}. '** - String previewDepositModalDepositSuccessSnackBarMessage({ - required String baseTokenSymbol, - required String quoteTokenSymbol, - required String protocol, - }); + /// **'You’ve set the search to only show pools with more than {minLiquidity}.'** + String depositPageMinLiquiditySearchAlert({required String minLiquidity}); - /// No description provided for @yieldCardTimeFrameBest. + /// No description provided for @depositPageMinRangeOutOfRangeWarningText. /// /// In en, this message translates to: - /// **'{timeFrame} best'** - String yieldCardTimeFrameBest({required String timeFrame}); + /// **'You will not earn fees until the market price move up into your range'** + String get depositPageMinRangeOutOfRangeWarningText; - /// No description provided for @yieldCardThisPoolIsAtNetwork. + /// No description provided for @depositPageNoYieldSelectedDescription. /// /// In en, this message translates to: - /// **'This pool is at {network}'** - String yieldCardThisPoolIsAtNetwork({required String network}); + /// **'Pick any yield card above and dive into depositing your liquidity!'** + String get depositPageNoYieldSelectedDescription; - /// No description provided for @yieldCardYearlyYield. + /// No description provided for @depositPageNoYieldSelectedTitle. /// /// In en, this message translates to: - /// **'Yearly Yield'** - String get yieldCardYearlyYield; + /// **'Pick a yield to deposit'** + String get depositPageNoYieldSelectedTitle; - /// No description provided for @yieldCardVisitProtocol. + /// No description provided for @depositPagePercentSlippage. /// /// In en, this message translates to: - /// **'Visit {protocolName}'** - String yieldCardVisitProtocol({required String protocolName}); + /// **'{valuePercent} Slippage'** + String depositPagePercentSlippage({required String valuePercent}); - /// No description provided for @yieldCardAverageYieldYearly. + /// No description provided for @depositPagePleaseEnterAmountForToken. /// /// In en, this message translates to: - /// **'Average Yearly Yield'** - String get yieldCardAverageYieldYearly; + /// **'Please enter an amount for {tokenSymbol}'** + String depositPagePleaseEnterAmountForToken({required String tokenSymbol}); - /// No description provided for @previewDepositModalDepositSuccessSnackBarHelperButtonTitle. + /// No description provided for @depositPageRangeSectionFullRange. /// /// In en, this message translates to: - /// **'View Transaction'** - String get previewDepositModalDepositSuccessSnackBarHelperButtonTitle; + /// **'Full Range'** + String get depositPageRangeSectionFullRange; - /// No description provided for @previewDepositModalApproveSuccessSnackBarHelperButtonTitle. + /// No description provided for @depositPageRangeSectionTitle. /// /// In en, this message translates to: - /// **'View Transaction'** - String get previewDepositModalApproveSuccessSnackBarHelperButtonTitle; + /// **'Select Range'** + String get depositPageRangeSectionTitle; - /// No description provided for @previewDepositModalWaitingTransactionSnackBarHelperButtonTitle. + /// No description provided for @depositPageSearchAllPools. /// /// In en, this message translates to: - /// **'View on Explorer'** - String get previewDepositModalWaitingTransactionSnackBarHelperButtonTitle; + /// **'Search all pools?'** + String get depositPageSearchAllPools; - /// No description provided for @depositPageInvalidRange. + /// No description provided for @depositPageSearchOnlyForPoolsWithMorethan. /// /// In en, this message translates to: - /// **'Invalid range'** - String get depositPageInvalidRange; + /// **'Search only for pools with more than {minLiquidity}?'** + String depositPageSearchOnlyForPoolsWithMorethan({ + required String minLiquidity, + }); - /// No description provided for @depositPageMinRangeOutOfRangeWarningText. + /// No description provided for @depositPageShowingAllPools. /// /// In en, this message translates to: - /// **'You will not earn fees until the market price move up into your range'** - String get depositPageMinRangeOutOfRangeWarningText; + /// **'Showing all liquidity pools.'** + String get depositPageShowingAllPools; - /// No description provided for @depositPageMaxRangeOutOfRangeWarningText. + /// No description provided for @depositPageShowingOnlyPoolsWithMoreThan. /// /// In en, this message translates to: - /// **'You will not earn fees until the market price move down into your range'** - String get depositPageMaxRangeOutOfRangeWarningText; + /// **'Showing only liquidity pools with more than {minLiquidity}.'** + String depositPageShowingOnlyPoolsWithMoreThan({ + required String minLiquidity, + }); - /// No description provided for @depositPageInvalidRangeErrorText. + /// No description provided for @depositPageTimeFrameTitle. /// /// In en, this message translates to: - /// **'Max range should be greater than min range'** - String get depositPageInvalidRangeErrorText; + /// **'Preferred time frame'** + String get depositPageTimeFrameTitle; - /// No description provided for @previewDepositModalMinPrice. + /// No description provided for @depositPageTimeFrameTooltipHelperButtonTitle. /// /// In en, this message translates to: - /// **'Min Price'** - String get previewDepositModalMinPrice; + /// **' Learn more'** + String get depositPageTimeFrameTooltipHelperButtonTitle; - /// No description provided for @previewDepositModalMaxPrice. + /// No description provided for @depositPageTimeFrameTooltipMessage. /// /// In en, this message translates to: - /// **'Max Price'** - String get previewDepositModalMaxPrice; + /// **'Each time frame shows yields based on past performance. Shorter terms (24h, 7d) reflect recent trends and may suit short-term strategies. Longer terms (30d, 90d) offer a broader performance view for long-term decisions.'** + String get depositPageTimeFrameTooltipMessage; - /// No description provided for @rangeSelectorMinRange. + /// No description provided for @depositPageTitle. /// /// In en, this message translates to: - /// **'Min Range'** - String get rangeSelectorMinRange; + /// **'Add liquidity'** + String get depositPageTitle; - /// No description provided for @rangeSelectorMaxRange. + /// No description provided for @depositPageTrySearchAllPools. /// /// In en, this message translates to: - /// **'Max Range'** - String get rangeSelectorMaxRange; + /// **'Try search all pools?'** + String get depositPageTrySearchAllPools; - /// No description provided for @loading. + /// No description provided for @depositSettingsDropdownChildHighSlippageWarningText. /// /// In en, this message translates to: - /// **'Loading...'** - String get loading; + /// **'High slippage can lead to Front Running and losses. Be careful! '** + String get depositSettingsDropdownChildHighSlippageWarningText; - /// No description provided for @exchangesFilterDropdownButtonDropdownClearAll. + /// No description provided for @depositSettingsDropdownChildMaxSlippage. /// /// In en, this message translates to: - /// **'Clear All'** - String get exchangesFilterDropdownButtonDropdownClearAll; + /// **'Max Slippage'** + String get depositSettingsDropdownChildMaxSlippage; - /// No description provided for @exchangesFilterDropdownButtonDropdownSelectAll. + /// No description provided for @depositSettingsDropdownChildTransactionDeadlineExplanation. /// /// In en, this message translates to: - /// **'Select All'** - String get exchangesFilterDropdownButtonDropdownSelectAll; + /// **'Your transaction will be reverted if it is pending for more than this amount of time'** + String get depositSettingsDropdownChildTransactionDeadlineExplanation; - /// No description provided for @exchangesFilterDropdownButtonDropdownSearchHint. + /// No description provided for @depositSettingsDropdownTransactionDeadline. /// /// In en, this message translates to: - /// **'Search by name'** - String get exchangesFilterDropdownButtonDropdownSearchHint; + /// **'Transaction Deadline'** + String get depositSettingsDropdownTransactionDeadline; - /// No description provided for @exchangesFilterDropdownButtonDropdownNotFoundStateTitle. + /// No description provided for @depositSuccessModalDescriptionPart1. /// /// In en, this message translates to: - /// **'Not found'** - String get exchangesFilterDropdownButtonDropdownNotFoundStateTitle; + /// **'You have successfully deposited into'** + String get depositSuccessModalDescriptionPart1; + + /// No description provided for @depositSuccessModalDescriptionPart2. + /// + /// In en, this message translates to: + /// **'Pool at'** + String get depositSuccessModalDescriptionPart2; + + /// No description provided for @depositSuccessModalDescriptionPart3. + /// + /// In en, this message translates to: + /// **'on'** + String get depositSuccessModalDescriptionPart3; + + /// No description provided for @depositSuccessModalTitle. + /// + /// In en, this message translates to: + /// **'Deposit Successful!'** + String get depositSuccessModalTitle; + + /// No description provided for @depositSuccessModalViewPositionOnDEX. + /// + /// In en, this message translates to: + /// **'View Position on {dexName}'** + String depositSuccessModalViewPositionOnDEX({required String dexName}); + + /// No description provided for @exchangesFilterDropdownButtonDropdownClearAll. + /// + /// In en, this message translates to: + /// **'Clear All'** + String get exchangesFilterDropdownButtonDropdownClearAll; /// No description provided for @exchangesFilterDropdownButtonDropdownNotFoundStateDescription. /// @@ -568,6 +583,24 @@ abstract class S { /// **'No supported exchanges found with this name'** String get exchangesFilterDropdownButtonDropdownNotFoundStateDescription; + /// No description provided for @exchangesFilterDropdownButtonDropdownNotFoundStateTitle. + /// + /// In en, this message translates to: + /// **'Not found'** + String get exchangesFilterDropdownButtonDropdownNotFoundStateTitle; + + /// No description provided for @exchangesFilterDropdownButtonDropdownSearchHint. + /// + /// In en, this message translates to: + /// **'Search by name'** + String get exchangesFilterDropdownButtonDropdownSearchHint; + + /// No description provided for @exchangesFilterDropdownButtonDropdownSelectAll. + /// + /// In en, this message translates to: + /// **'Select All'** + String get exchangesFilterDropdownButtonDropdownSelectAll; + /// No description provided for @exchangesFilterDropdownButtonErrorSnackBarMessage. /// /// In en, this message translates to: @@ -588,253 +621,391 @@ abstract class S { required String exchangesCount, }); - /// No description provided for @createPageSelectTokensStageTokenA. + /// No description provided for @letsGiveItAnotherGo. /// /// In en, this message translates to: - /// **'Token A'** - String get createPageSelectTokensStageTokenA; + /// **'Let’s give it another go, because why not?'** + String get letsGiveItAnotherGo; - /// No description provided for @createPageSelectTokensStageTokenB. + /// Used by many pages in the app to try something again, usually after something went wrong /// /// In en, this message translates to: - /// **'Token B'** - String get createPageSelectTokensStageTokenB; + /// **'Let’s give it another shot'** + String get letsGiveItAnotherShot; - /// No description provided for @createPageSelectTokensStageSearchSettings. + /// No description provided for @light. /// /// In en, this message translates to: - /// **'Search Settings'** - String get createPageSelectTokensStageSearchSettings; + /// **'Light'** + String get light; - /// No description provided for @depositPageLoadingStep1Title. + /// No description provided for @loading. /// /// In en, this message translates to: - /// **'Matching Tokens...'** - String get depositPageLoadingStep1Title; + /// **'Loading...'** + String get loading; - /// No description provided for @depositPageLoadingStep1Description. + /// No description provided for @minutes. /// /// In en, this message translates to: - /// **'Pairing Token A and Token B to kick off the search for top yields!'** - String get depositPageLoadingStep1Description; + /// **'Minutes'** + String get minutes; - /// No description provided for @depositPageLoadingStep2Title. + /// No description provided for @month. /// /// In en, this message translates to: - /// **'Pair hunting…'** - String get depositPageLoadingStep2Title; + /// **'Month'** + String get month; - /// No description provided for @depositPageLoadingStep2Description. + /// No description provided for @monthCompact. /// /// In en, this message translates to: - /// **'Searching through more than a thousand pool combos… so you don\'t have to'** - String get depositPageLoadingStep2Description; + /// **'30d'** + String get monthCompact; - /// No description provided for @depositPageLoadingStep3Title. + /// No description provided for @newPosition. /// /// In en, this message translates to: - /// **'Yield optimizer at work…'** - String get depositPageLoadingStep3Title; + /// **'New Position'** + String get newPosition; - /// No description provided for @depositPageLoadingStep3Description. + /// No description provided for @noResultsFor. /// /// In en, this message translates to: - /// **'Scanning pools, calculating returns, and filtering the noise'** - String get depositPageLoadingStep3Description; + /// **'No results for'** + String get noResultsFor; - /// No description provided for @depositPageBestYieldsIn. + /// No description provided for @popularTokens. /// /// In en, this message translates to: - /// **'Best Yields in'** - String get depositPageBestYieldsIn; + /// **'Popular Tokens'** + String get popularTokens; - /// No description provided for @depositPageLoadingStep4Title. + /// No description provided for @positionCardLiquidity. /// /// In en, this message translates to: - /// **'Organizing the best pools for you…'** - String get depositPageLoadingStep4Title; + /// **'Liquidity: '** + String get positionCardLiquidity; - /// No description provided for @depositPageLoadingStep4Description. + /// No description provided for @positionCardMax. /// /// In en, this message translates to: - /// **'Hang tight, we\'re filtering and organizing the best pools for you'** - String get depositPageLoadingStep4Description; + /// **'Max: '** + String get positionCardMax; - /// No description provided for @depositPageErrorStateTitle. + /// No description provided for @positionCardMin. /// /// In en, this message translates to: - /// **'Oops! Something went wrong!'** - String get depositPageErrorStateTitle; + /// **'Min: '** + String get positionCardMin; - /// No description provided for @depositPageErrorStateDescription. + /// No description provided for @positionCardTokenPerToken. /// /// In en, this message translates to: - /// **'We ran into a issue while trying to find the best pool. Give it another shot, and if it keeps happening, don’t hesitate to reach out to us!'** - String get depositPageErrorStateDescription; + /// **'{token0Qtd} {token0Symbol} per {token1Symbol}'** + String positionCardTokenPerToken({ + required String token0Qtd, + required String token0Symbol, + required String token1Symbol, + }); - /// No description provided for @depositPageEmptyStateTitle. + /// No description provided for @positionCardUnclaimedFees. /// /// In en, this message translates to: - /// **'No Pools Found'** - String get depositPageEmptyStateTitle; + /// **'Unclaimed fees: '** + String get positionCardUnclaimedFees; - /// No description provided for @depositPageEmptyStateDescription. + /// No description provided for @positionCardViewMore. /// /// In en, this message translates to: - /// **'Seems like that there are no pools matching your defined settings at the moment. Would you like to either change your settings or try another combination?'** - String get depositPageEmptyStateDescription; + /// **'View more'** + String get positionCardViewMore; - /// No description provided for @depositPageEmptyStateHelpButtonTitle. + /// No description provided for @positionStatusClosed. /// /// In en, this message translates to: - /// **'Go Back to New Position'** - String get depositPageEmptyStateHelpButtonTitle; + /// **'Closed'** + String get positionStatusClosed; - /// No description provided for @depositPageBackButtonTitle. + /// No description provided for @positionStatusInRange. /// /// In en, this message translates to: - /// **'Select Pair'** - String get depositPageBackButtonTitle; + /// **'In Range'** + String get positionStatusInRange; - /// No description provided for @depositPageTitle. + /// No description provided for @positionStatusOutOfRange. /// /// In en, this message translates to: - /// **'Add liquidity'** - String get depositPageTitle; + /// **'Out of Range'** + String get positionStatusOutOfRange; - /// No description provided for @depositPageTimeFrameTooltipMessage. + /// No description provided for @positionsPageCantFindAPosition. /// /// In en, this message translates to: - /// **'Each time frame shows yields based on past performance. Shorter terms (24h, 7d) reflect recent trends and may suit short-term strategies. Longer terms (30d, 90d) offer a broader performance view for long-term decisions.'** - String get depositPageTimeFrameTooltipMessage; + /// **'Can’t find a position? Try switching the app’s network to \"All Networks\" or reload the page'** + String get positionsPageCantFindAPosition; - /// No description provided for @depositPageTimeFrameTooltipHelperButtonTitle. + /// No description provided for @positionsPageErrorStateDescription. /// /// In en, this message translates to: - /// **' Learn more'** - String get depositPageTimeFrameTooltipHelperButtonTitle; + /// **'An error occurred while loading your positions.\nPlease try again. If the issue persists, feel free to contact us'** + String get positionsPageErrorStateDescription; - /// No description provided for @depositPageTimeFrameTitle. + /// No description provided for @positionsPageMyPositions. /// /// In en, this message translates to: - /// **'Preferred time frame'** - String get depositPageTimeFrameTitle; + /// **'My Positions'** + String get positionsPageMyPositions; - /// No description provided for @depositPageNoYieldSelectedTitle. + /// No description provided for @positionsPageNoPositionsDescription. /// /// In en, this message translates to: - /// **'Pick a yield to deposit'** - String get depositPageNoYieldSelectedTitle; + /// **'Hm… It looks like you don’t have any positions yet.\nWant to create one?'** + String get positionsPageNoPositionsDescription; - /// No description provided for @depositPageNoYieldSelectedDescription. + /// No description provided for @positionsPageNoPositionsInNetwork. /// /// In en, this message translates to: - /// **'Pick any yield card above and dive into depositing your liquidity!'** - String get depositPageNoYieldSelectedDescription; + /// **'No positions in {network}'** + String positionsPageNoPositionsInNetwork({required String network}); - /// No description provided for @depositPageRangeSectionTitle. + /// No description provided for @positionsPageNoPositionsInNetworkDescription. /// /// In en, this message translates to: - /// **'Select Range'** - String get depositPageRangeSectionTitle; + /// **'It looks like you don’t have any positions in {network} yet.\nGo ahead and create one to get started!'** + String positionsPageNoPositionsInNetworkDescription({ + required String network, + }); - /// No description provided for @depositPageRangeSectionFullRange. + /// No description provided for @positionsPageNoPositionsTitle. /// /// In en, this message translates to: - /// **'Full Range'** - String get depositPageRangeSectionFullRange; + /// **'You don’t have any positions yet'** + String get positionsPageNoPositionsTitle; - /// No description provided for @depositPageInvalidTokenAmount. + /// No description provided for @positionsPageNotConnectedDescription. /// /// In en, this message translates to: - /// **'Enter a valid amount for {tokenSymbol}'** - String depositPageInvalidTokenAmount({required String tokenSymbol}); + /// **'Wallet not connected. Please connect your\nwallet to view your positions'** + String get positionsPageNotConnectedDescription; - /// No description provided for @depositPageInsufficientTokenBalance. + /// Dynamically shows 'Hide' or 'Show' based on the isHidden boolean. /// /// In en, this message translates to: - /// **'Insufficient {tokenSymbol} balance'** - String depositPageInsufficientTokenBalance({required String tokenSymbol}); + /// **'{isHidden, select, true {Show} false {Hide} other {Show/Hide}} closed positions'** + String positionsPageShowHideClosedPositions({required String isHidden}); - /// No description provided for @depositPagePleaseEnterAmountForToken. + /// No description provided for @preview. /// /// In en, this message translates to: - /// **'Please enter an amount for {tokenSymbol}'** - String depositPagePleaseEnterAmountForToken({required String tokenSymbol}); + /// **'Preview'** + String get preview; - /// No description provided for @depositPageDeposit. + /// No description provided for @previewDepositModalApproveSuccessSnackBarHelperButtonTitle. /// /// In en, this message translates to: - /// **'Deposit'** - String get depositPageDeposit; + /// **'View Transaction'** + String get previewDepositModalApproveSuccessSnackBarHelperButtonTitle; - /// No description provided for @connectYourWallet. + /// No description provided for @previewDepositModalApproveSuccessSnackBarMessage. /// /// In en, this message translates to: - /// **'Connect your wallet'** - String get connectYourWallet; + /// **'{tokenSymbol} approved successfully. '** + String previewDepositModalApproveSuccessSnackBarMessage({ + required String tokenSymbol, + }); - /// No description provided for @tokenSelectorModalTitle. + /// No description provided for @previewDepositModalApproveToken. /// /// In en, this message translates to: - /// **'Select a token'** - String get tokenSelectorModalTitle; + /// **'Approve {tokenSymbol}'** + String previewDepositModalApproveToken({required String tokenSymbol}); - /// No description provided for @tokenSelectorModalTokenGroups. + /// No description provided for @previewDepositModalApprovingToken. /// /// In en, this message translates to: - /// **'Token Groups'** - String get tokenSelectorModalTokenGroups; + /// **'Approving {tokenSymbol}'** + String previewDepositModalApprovingToken({required String tokenSymbol}); - /// No description provided for @tokenSelectorModalTokenGroupsTooltipMessage. + /// No description provided for @previewDepositModalAutoSlippageCheckErrorMessage. /// /// In en, this message translates to: - /// **'Token groups let you search pools using multiple tokens in one go. Think of them like batch queries, want all USD stablecoins? Pick the group and we\'ll surface every relevant pool. You can also match groups against single tokens or other groups to discover deep liquidity.'** - String get tokenSelectorModalTokenGroupsTooltipMessage; + /// **'Strong market movement! Slippage exceeded. Try again or adjust tolerance.'** + String get previewDepositModalAutoSlippageCheckErrorMessage; - /// No description provided for @tokenSelectorModalDescription. + /// No description provided for @previewDepositModalCubitApprovedSnackBarMessage. /// /// In en, this message translates to: - /// **'Pick a token from our list or search by symbol or address to build your ideal liquidity pool!'** - String get tokenSelectorModalDescription; + /// **'Successfully approved {tokenSymbol}'** + String previewDepositModalCubitApprovedSnackBarMessage({ + required String tokenSymbol, + }); - /// No description provided for @tokenSelectorModalSearchTitle. + /// No description provided for @previewDepositModalCubitApprovingSnackBarMessage. /// /// In en, this message translates to: - /// **'Search token or paste address'** - String get tokenSelectorModalSearchTitle; + /// **'Approving...'** + String get previewDepositModalCubitApprovingSnackBarMessage; - /// No description provided for @tokenSelectorModalSearchTitleAllNetworks. + /// No description provided for @previewDepositModalCubitDepositingSnackBarMessage. /// /// In en, this message translates to: - /// **'Search token by symbol or name'** - String get tokenSelectorModalSearchTitleAllNetworks; + /// **'Depositing into {token0Symbol}/{token1Symbol} Pool...'** + String previewDepositModalCubitDepositingSnackBarMessage({ + required String token0Symbol, + required String token1Symbol, + }); - /// No description provided for @tokenSelectorModalSearchAlertForAllNetworks. + /// No description provided for @previewDepositModalCurrentPrice. /// /// In en, this message translates to: - /// **'When ‘All Networks’ is selected, you can only search by name or symbol. To search by address as well, please select a specific network'** - String get tokenSelectorModalSearchAlertForAllNetworks; + /// **'Current Price'** + String get previewDepositModalCurrentPrice; - /// No description provided for @tokenSelectorModalErrorDescription. + /// No description provided for @previewDepositModalDeposit. /// /// In en, this message translates to: - /// **'We hit a snag loading your token list. Give it another go, and if it keeps happening, feel free to reach us'** - String get tokenSelectorModalErrorDescription; + /// **'Deposit'** + String get previewDepositModalDeposit; - /// No description provided for @tokenSelectorModalSearchErrorDescription. + /// No description provided for @previewDepositModalDepositSuccessSnackBarHelperButtonTitle. /// /// In en, this message translates to: - /// **'We hit a snag while searching for a token matching {searchedTerm}. Give it another go, and if it keeps happening, feel free to reach us'** - String tokenSelectorModalSearchErrorDescription({ - required String searchedTerm, + /// **'View Transaction'** + String get previewDepositModalDepositSuccessSnackBarHelperButtonTitle; + + /// No description provided for @previewDepositModalDepositSuccessSnackBarMessage. + /// + /// In en, this message translates to: + /// **'Successfully Deposited into the {baseTokenSymbol}/{quoteTokenSymbol} Pool at {protocol}. '** + String previewDepositModalDepositSuccessSnackBarMessage({ + required String baseTokenSymbol, + required String quoteTokenSymbol, + required String protocol, }); - /// No description provided for @noResultsFor. + /// No description provided for @previewDepositModalDepositingIntoPool. /// /// In en, this message translates to: - /// **'No results for'** - String get noResultsFor; + /// **'Depositing into {baseTokenSymbol}/{quoteTokenSymbol} pool'** + String previewDepositModalDepositingIntoPool({ + required String baseTokenSymbol, + required String quoteTokenSymbol, + }); + + /// No description provided for @previewDepositModalError. + /// + /// In en, this message translates to: + /// **'Error'** + String get previewDepositModalError; + + /// No description provided for @previewDepositModalInRange. + /// + /// In en, this message translates to: + /// **'In Range'** + String get previewDepositModalInRange; + + /// No description provided for @previewDepositModalMaxPrice. + /// + /// In en, this message translates to: + /// **'Max Price'** + String get previewDepositModalMaxPrice; + + /// No description provided for @previewDepositModalMinPrice. + /// + /// In en, this message translates to: + /// **'Min Price'** + String get previewDepositModalMinPrice; + + /// No description provided for @previewDepositModalMyPosition. + /// + /// In en, this message translates to: + /// **'My Position'** + String get previewDepositModalMyPosition; + + /// No description provided for @previewDepositModalNetwork. + /// + /// In en, this message translates to: + /// **'Network'** + String get previewDepositModalNetwork; + + /// No description provided for @previewDepositModalOutOfRange. + /// + /// In en, this message translates to: + /// **'Out of Range'** + String get previewDepositModalOutOfRange; + + /// No description provided for @previewDepositModalProtocol. + /// + /// In en, this message translates to: + /// **'Protocol'** + String get previewDepositModalProtocol; + + /// No description provided for @previewDepositModalSlippageCheckErrorMessage. + /// + /// In en, this message translates to: + /// **'Slippage Check! Please try increasing your slippage for this transaction'** + String get previewDepositModalSlippageCheckErrorMessage; + + /// No description provided for @previewDepositModalTitle. + /// + /// In en, this message translates to: + /// **'Preview Deposit'** + String get previewDepositModalTitle; + + /// No description provided for @previewDepositModalTransactionErrorSnackBarHelperButtonTitle. + /// + /// In en, this message translates to: + /// **'Contact us'** + String get previewDepositModalTransactionErrorSnackBarHelperButtonTitle; + + /// No description provided for @previewDepositModalTransactionErrorSnackBarMessage. + /// + /// In en, this message translates to: + /// **'Transaction Failed. Please try again, if the problem persists, '** + String get previewDepositModalTransactionErrorSnackBarMessage; + + /// No description provided for @previewDepositModalWaitingTransaction. + /// + /// In en, this message translates to: + /// **'Waiting Transaction'** + String get previewDepositModalWaitingTransaction; + + /// No description provided for @previewDepositModalWaitingTransactionSnackBarHelperButtonTitle. + /// + /// In en, this message translates to: + /// **'View on Explorer'** + String get previewDepositModalWaitingTransactionSnackBarHelperButtonTitle; + + /// No description provided for @previewDepositModalWaitingTransactionSnackBarMessage. + /// + /// In en, this message translates to: + /// **'Waiting transaction to be confirmed. '** + String get previewDepositModalWaitingTransactionSnackBarMessage; + + /// No description provided for @previewDepositModalYearlyYield. + /// + /// In en, this message translates to: + /// **'Yearly Yield'** + String get previewDepositModalYearlyYield; + + /// No description provided for @privacyPolicy. + /// + /// In en, this message translates to: + /// **'Privacy Policy'** + String get privacyPolicy; + + /// No description provided for @rangeSelectorMaxRange. + /// + /// In en, this message translates to: + /// **'Max Range'** + String get rangeSelectorMaxRange; + + /// No description provided for @rangeSelectorMinRange. + /// + /// In en, this message translates to: + /// **'Min Range'** + String get rangeSelectorMinRange; /// No description provided for @searchResults. /// @@ -842,352 +1013,349 @@ abstract class S { /// **'Search results'** String get searchResults; - /// No description provided for @popularTokens. + /// No description provided for @selectToken. /// /// In en, this message translates to: - /// **'Popular Tokens'** - String get popularTokens; + /// **'Select Token'** + String get selectToken; - /// No description provided for @connectMyWallet. + /// No description provided for @slippageExplanation. /// /// In en, this message translates to: - /// **'Connect My Wallet'** - String get connectMyWallet; + /// **'Slippage protects you by reverting the transaction if the price changes unfavorably beyond the percentage. This is necessary to prevent losses while adding liquidity'** + String get slippageExplanation; - /// No description provided for @connectWallet. + /// No description provided for @somethingWhenWrong. /// /// In en, this message translates to: - /// **'Connect Wallet'** - String get connectWallet; + /// **'Something went wrong'** + String get somethingWhenWrong; - /// No description provided for @positionsPageNotConnectedDescription. + /// No description provided for @system. /// /// In en, this message translates to: - /// **'Wallet not connected. Please connect your\nwallet to view your positions'** - String get positionsPageNotConnectedDescription; + /// **'System'** + String get system; - /// No description provided for @positionsPageNoPositionsTitle. + /// No description provided for @threeMonths. + /// + /// In en, this message translates to: + /// **'3 Months'** + String get threeMonths; + + /// No description provided for @threeMonthsCompact. /// /// In en, this message translates to: - /// **'You don’t have any positions yet'** - String get positionsPageNoPositionsTitle; + /// **'90d'** + String get threeMonthsCompact; - /// No description provided for @positionsPageNoPositionsDescription. + /// No description provided for @token0. /// /// In en, this message translates to: - /// **'Hm… It looks like you don’t have any positions yet.\nWant to create one?'** - String get positionsPageNoPositionsDescription; + /// **'Token A'** + String get token0; - /// No description provided for @newPosition. + /// No description provided for @token1. /// /// In en, this message translates to: - /// **'New Position'** - String get newPosition; + /// **'Token B'** + String get token1; - /// No description provided for @createNewPosition. + /// No description provided for @tokenSelectorModalDescription. /// /// In en, this message translates to: - /// **'Create new position'** - String get createNewPosition; + /// **'Pick a token from our list or search by symbol or address to build your ideal liquidity pool!'** + String get tokenSelectorModalDescription; - /// No description provided for @positionCardMin. + /// No description provided for @tokenSelectorModalErrorDescription. /// /// In en, this message translates to: - /// **'Min: '** - String get positionCardMin; + /// **'We hit a snag loading your token list. Give it another go, and if it keeps happening, feel free to reach us'** + String get tokenSelectorModalErrorDescription; - /// No description provided for @positionCardMax. + /// No description provided for @tokenSelectorModalSearchAlertForAllNetworks. /// /// In en, this message translates to: - /// **'Max: '** - String get positionCardMax; + /// **'When ‘All Networks’ is selected, you can only search by name or symbol. To search by address as well, please select a specific network'** + String get tokenSelectorModalSearchAlertForAllNetworks; - /// No description provided for @positionCardLiquidity. + /// No description provided for @tokenSelectorModalSearchErrorDescription. /// /// In en, this message translates to: - /// **'Liquidity: '** - String get positionCardLiquidity; + /// **'We hit a snag while searching for a token matching {searchedTerm}. Give it another go, and if it keeps happening, feel free to reach us'** + String tokenSelectorModalSearchErrorDescription({ + required String searchedTerm, + }); - /// No description provided for @positionCardUnclaimedFees. + /// No description provided for @tokenSelectorModalSearchTitle. /// /// In en, this message translates to: - /// **'Unclaimed fees: '** - String get positionCardUnclaimedFees; + /// **'Search token or paste address'** + String get tokenSelectorModalSearchTitle; - /// No description provided for @positionCardViewMore. + /// No description provided for @tokenSelectorModalSearchTitleAllNetworks. /// /// In en, this message translates to: - /// **'View more'** - String get positionCardViewMore; + /// **'Search token by symbol or name'** + String get tokenSelectorModalSearchTitleAllNetworks; - /// No description provided for @positionsPageNoPositionsInNetwork. + /// No description provided for @tokenSelectorModalTitle. /// /// In en, this message translates to: - /// **'No positions in {network}'** - String positionsPageNoPositionsInNetwork({required String network}); + /// **'Select a token'** + String get tokenSelectorModalTitle; - /// Dynamically shows 'Hide' or 'Show' based on the isHidden boolean. + /// No description provided for @tokenSelectorModalTokenGroups. /// /// In en, this message translates to: - /// **'{isHidden, select, true {Show} false {Hide} other {Show/Hide}} closed positions'** - String positionsPageShowHideClosedPositions({required String isHidden}); + /// **'Token Groups'** + String get tokenSelectorModalTokenGroups; - /// No description provided for @positionsPageNoPositionsInNetworkDescription. + /// No description provided for @tokenSelectorModalTokenGroupsTooltipMessage. /// /// In en, this message translates to: - /// **'It looks like you don’t have any positions in {network} yet.\nGo ahead and create one to get started!'** - String positionsPageNoPositionsInNetworkDescription({ - required String network, - }); + /// **'Token groups let you search pools using multiple tokens in one go. Think of them like batch queries, want all USD stablecoins? Pick the group and we\'ll surface every relevant pool. You can also match groups against single tokens or other groups to discover deep liquidity.'** + String get tokenSelectorModalTokenGroupsTooltipMessage; - /// No description provided for @positionCardTokenPerToken. + /// No description provided for @tvl. /// /// In en, this message translates to: - /// **'{token0Qtd} {token0Symbol} per {token1Symbol}'** - String positionCardTokenPerToken({ - required String token0Qtd, - required String token0Symbol, - required String token1Symbol, - }); + /// **'TVL'** + String get tvl; - /// No description provided for @positionsPageMyPositions. + /// No description provided for @twentyFourHours. /// /// In en, this message translates to: - /// **'My Positions'** - String get positionsPageMyPositions; + /// **'24h'** + String get twentyFourHours; - /// No description provided for @appHeaderMyPositions. + /// No description provided for @twentyFourHoursCompact. /// /// In en, this message translates to: - /// **'My Positions (Soon)'** - String get appHeaderMyPositions; + /// **'24h'** + String get twentyFourHoursCompact; - /// No description provided for @appHeaderNewPosition. + /// No description provided for @understood. /// /// In en, this message translates to: - /// **'New Position'** - String get appHeaderNewPosition; + /// **'Understood'** + String get understood; - /// No description provided for @appBottomNavigationBarMyPositions. + /// No description provided for @unknown. /// /// In en, this message translates to: - /// **'My Positions (Soon)'** - String get appBottomNavigationBarMyPositions; + /// **'Unknown'** + String get unknown; - /// No description provided for @appBottomNavigationBarNewPosition. + /// No description provided for @week. /// /// In en, this message translates to: - /// **'New Position'** - String get appBottomNavigationBarNewPosition; + /// **'Week'** + String get week; - /// No description provided for @positionsPageCantFindAPosition. + /// No description provided for @weekCompact. /// /// In en, this message translates to: - /// **'Can’t find a position? Try switching the app’s network to \"All Networks\" or reload the page'** - String get positionsPageCantFindAPosition; + /// **'7d'** + String get weekCompact; - /// No description provided for @somethingWhenWrong. + /// No description provided for @whatsThisQuestionText. /// /// In en, this message translates to: - /// **'Something went wrong'** - String get somethingWhenWrong; + /// **'What\'s this?'** + String get whatsThisQuestionText; - /// No description provided for @positionsPageErrorStateDescription. + /// No description provided for @yieldCardAverageYieldYearly. /// /// In en, this message translates to: - /// **'An error occurred while loading your positions.\nPlease try again. If the issue persists, feel free to contact us'** - String get positionsPageErrorStateDescription; + /// **'Average Yearly Yield'** + String get yieldCardAverageYieldYearly; - /// No description provided for @positionStatusInRange. + /// Used by the yield card to proceed to the deposit page with the selected yield card /// /// In en, this message translates to: - /// **'In Range'** - String get positionStatusInRange; + /// **'Deposit'** + String get yieldCardDeposit; - /// No description provided for @positionStatusOutOfRange. + /// No description provided for @yieldCardNetworkTooltipDescription. /// /// In en, this message translates to: - /// **'Out of Range'** - String get positionStatusOutOfRange; + /// **'This pool is at {network}'** + String yieldCardNetworkTooltipDescription({required String network}); - /// No description provided for @positionStatusClosed. + /// Used by the yield card to explain to the user that the pool is at the {network}, in form of a tooltip /// /// In en, this message translates to: - /// **'Closed'** - String get positionStatusClosed; + /// **'This pool is at {network}'** + String yieldCardThisPoolIsAtNetwork({required String network}); - /// No description provided for @unknown. + /// No description provided for @yieldCardTimeFrameBest. /// /// In en, this message translates to: - /// **'Unknown'** - String get unknown; + /// **'{timeFrame} best'** + String yieldCardTimeFrameBest({required String timeFrame}); - /// No description provided for @selectToken. + /// Used by the yield card to show a title for the yield based on the time frame selected /// /// In en, this message translates to: - /// **'Select Token'** - String get selectToken; + /// **'{timeframe} Yield'** + String yieldCardTimeframeYield({required String timeframe}); - /// No description provided for @token0. + /// No description provided for @yieldCardVisitProtocol. /// /// In en, this message translates to: - /// **'Token A'** - String get token0; + /// **'Visit {protocolName}'** + String yieldCardVisitProtocol({required String protocolName}); - /// No description provided for @token1. + /// Used by the yield card as title for the percentage of yearly yield /// /// In en, this message translates to: - /// **'Token B'** - String get token1; + /// **'Yearly Yield'** + String get yieldCardYearlyYield; - /// No description provided for @depositSuccessModalTitle. + /// Used by the yield card to explain to the user how the yield is calculated in form of a tooltip /// /// In en, this message translates to: - /// **'Deposit Successful!'** - String get depositSuccessModalTitle; + /// **'Estimated annual yield based on {timeframeLabel} of data from fees distributed to liquidity providers.'** + String yieldCardYieldExplanation({required String timeframeLabel}); - /// No description provided for @depositSuccessModalDescriptionPart1. + /// Used by the yields page as title for the button to search only for pools with more than the specified TVL /// /// In en, this message translates to: - /// **'You have successfully deposited into'** - String get depositSuccessModalDescriptionPart1; + /// **'Show only pools with more than {tvlUSD} TVL.'** + String yieldsPageApplyTvlFilterButtonTitle({required String tvlUSD}); - /// No description provided for @depositSuccessModalDescriptionPart2. + /// Used by the yields page as title for the back button, to go back to the previous step /// /// In en, this message translates to: - /// **'Pool at'** - String get depositSuccessModalDescriptionPart2; + /// **'Select Pair'** + String get yieldsPageBackButtonTitle; - /// No description provided for @depositSuccessModalDescriptionPart3. + /// The description of the yield page /// /// In en, this message translates to: - /// **'on'** - String get depositSuccessModalDescriptionPart3; + /// **'Select the yield that most suits your needs and deposit to start earning'** + String get yieldsPageDescription; - /// No description provided for @previewDepositModalCubitDepositingSnackBarMessage. + /// Used by the yields page to alert the user that their search was made to find any pool, ignoring user-set filters /// /// In en, this message translates to: - /// **'Depositing into {token0Symbol}/{token1Symbol} Pool...'** - String previewDepositModalCubitDepositingSnackBarMessage({ - required String token0Symbol, - required String token1Symbol, - }); + /// **'Displaying all liquidity pools.'** + String get yieldsPageDisplayingAllPoolsAlert; - /// No description provided for @previewDepositModalCubitApprovingSnackBarMessage. + /// Used by the yields page to alert the user that their search was limited to pools with more than the specified TVL /// /// In en, this message translates to: - /// **'Approving...'** - String get previewDepositModalCubitApprovingSnackBarMessage; + /// **'Displaying only liquidity pools with more than {tvlUSD} TVL.'** + String yieldsPageDisplayingPoolsWithMinTvlAlert({required String tvlUSD}); - /// No description provided for @previewDepositModalCubitApprovedSnackBarMessage. + /// Used by the yields page as description for the empty state when no pools are found for the selected pair /// /// In en, this message translates to: - /// **'Successfully approved {tokenSymbol}'** - String previewDepositModalCubitApprovedSnackBarMessage({ - required String tokenSymbol, - }); + /// **'Seems like that there are no pools matching your defined settings at the moment. Would you like to either change your settings or try another combination?'** + String get yieldsPageEmptyStateDescription; - /// No description provided for @depositSuccessModalViewPositionOnDEX. + /// Used by the yields page as title for the helper button in the empty state when no pools are found for the selected pair, useful for going back or doing something else /// /// In en, this message translates to: - /// **'View Position on {dexName}'** - String depositSuccessModalViewPositionOnDEX({required String dexName}); + /// **'Go Back to New Position'** + String get yieldsPageEmptyStateHelperButtonTitle; - /// No description provided for @close. + /// Used in the empty state on the Yields page to alert the user that their search was limited to pools with more than the specified TVL. This may explain why no pools were found /// /// In en, this message translates to: - /// **'Close'** - String get close; + /// **'Searched only for liquidity pools with more than {tvlUSD} TVL'** + String yieldsPageEmptyStateMinTVLAlert({required String tvlUSD}); - /// No description provided for @depositPageDepositWithNativeToken. + /// Used by the yields page as title for the empty state when no pools are found for the selected pair /// /// In en, this message translates to: - /// **'Deposit with Native {tokenSymbol}'** - String depositPageDepositWithNativeToken({required String tokenSymbol}); + /// **'No Pools Found'** + String get yieldsPageEmptyStateTitle; - /// No description provided for @createPageSettingsDropdownMinimumLiquidity. + /// Used by the yields page as description for the error state when something went wrong while trying to find the yields for the selected pair /// /// In en, this message translates to: - /// **'Minimum Pool Liquidity'** - String get createPageSettingsDropdownMinimumLiquidity; + /// **'We ran into a issue while trying to find the best pool. Give it another shot, and if it keeps happening, don\'t hesitate to reach out to us!'** + String get yieldsPageErrorStateDescription; - /// No description provided for @createPageSettingsDropdownAllowedPoolTypes. + /// Used by the yields page as title for the error state when something went wrong while trying to find the yields for the selected pair /// /// In en, this message translates to: - /// **'Allowed Pool Types'** - String get createPageSettingsDropdownAllowedPoolTypes; + /// **'Oops! Something went wrong!'** + String get yieldsPageErrorStateTitle; - /// No description provided for @createPageSettingsDropdownAllowedPoolTypesDescription. + /// Used by the yields page as description for the first loading step when searching for the best yield for the selected pair /// /// In en, this message translates to: - /// **'Filter the types of liquidity pools to include in your search'** - String get createPageSettingsDropdownAllowedPoolTypesDescription; + /// **'Pairing Token A and Token B to kick off the search for top yields!'** + String get yieldsPageLoadingStep1Description; - /// No description provided for @createPageSettingsDropdownMinimumLiquidityExplanation. + /// Used by the yields page as title for the first loading step when searching for the best yield for the selected pair /// /// In en, this message translates to: - /// **'Filter pools by minimum liquidity. We’ll exclude pools with less liquidity than specified, as low Liquidity can lead to misleading yields. This helps you find more reliable opportunities'** - String get createPageSettingsDropdownMinimumLiquidityExplanation; + /// **'Matching Tokens...'** + String get yieldsPageLoadingStep1Title; - /// No description provided for @createPageSettingsDropdownMiniumLiquidityLowWarning. + /// Used by the yields page as description for the second loading step when searching for the best yield for the selected pair /// /// In en, this message translates to: - /// **'Low minimum TVL can lead to misleading yields.'** - String get createPageSettingsDropdownMiniumLiquidityLowWarning; + /// **'Searching through more than a thousand pool combos… so you don\'t have to'** + String get yieldsPageLoadingStep2Description; - /// No description provided for @appSettingsDropdownTestnetMode. + /// Used by the yields page as title for the second loading step when searching for the best yield for the selected pair /// /// In en, this message translates to: - /// **'Testnet Mode'** - String get appSettingsDropdownTestnetMode; + /// **'Pair hunting…'** + String get yieldsPageLoadingStep2Title; - /// No description provided for @previewDepositModalSlippageCheckErrorMessage. + /// Used by the yields page as description for the third loading step when searching for the best yield for the selected pair /// /// In en, this message translates to: - /// **'Slippage Check! Please try increasing your slippage for this transaction'** - String get previewDepositModalSlippageCheckErrorMessage; + /// **'Scanning pools, calculating returns, and filtering the noise'** + String get yieldsPageLoadingStep3Description; - /// No description provided for @previewDepositModalAutoSlippageCheckErrorMessage. + /// Used by the yields page as title for the third loading step when searching for the best yield for the selected pair /// /// In en, this message translates to: - /// **'Strong market movement! Slippage exceeded. Try again or adjust tolerance.'** - String get previewDepositModalAutoSlippageCheckErrorMessage; + /// **'Yield optimizer at work…'** + String get yieldsPageLoadingStep3Title; - /// No description provided for @createPageTitle. + /// Used by the yields page as description for the fourth loading step when searching for the best yield for the selected pair /// /// In en, this message translates to: - /// **'New Position'** - String get createPageTitle; + /// **'Hang tight, we\'re filtering and organizing the best pools for you'** + String get yieldsPageLoadingStep4Description; - /// No description provided for @tvl. + /// Used by the yields page as title for the fourth loading step when searching for the best yield for the selected pair /// /// In en, this message translates to: - /// **'TVL'** - String get tvl; + /// **'Organizing the best pools for you…'** + String get yieldsPageLoadingStep4Title; - /// No description provided for @createPageShowMeTheMoney. + /// Used by the yields page as a button to search all pools, ignoring user-set filters /// /// In en, this message translates to: - /// **'Show me the money!'** - String get createPageShowMeTheMoney; + /// **'Search all pools'** + String get yieldsPageSearchAllPools; - /// No description provided for @createPageDescription. + /// Used by by the yields page to explain what are the time frames and how they differ from each other /// /// In en, this message translates to: - /// **'Ready to dive in? First, pick the dynamic duo of tokens you want to team up in the pool. Just choose your pair right below and you’re set to make some magic!'** - String get createPageDescription; + /// **'Each time frame shows yields based on past performance. Shorter windows (24h, 7d) highlight recent trends for quick moves. Longer windows (30d, 90d) provide a broader view for mid to long-term decisions'** + String get yieldsPageTimeframeExplanation; - /// No description provided for @letsGiveItAnotherGo. + /// Used by the yields page as a title for the timeframe selector, to show yields based in the selected timeframe /// /// In en, this message translates to: - /// **'Let’s give it another go, because why not?'** - String get letsGiveItAnotherGo; + /// **'Best yields in'** + String get yieldsPageTimeframeSelectorTitle; - /// No description provided for @letsGiveItAnotherShot. + /// The main title of the yield page /// /// In en, this message translates to: - /// **'Let’s give it another shot'** - String get letsGiveItAnotherShot; + /// **'Choose a pool for you'** + String get yieldsPageTitle; } class _SDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/gen/app_localizations_en.dart b/lib/l10n/gen/app_localizations_en.dart index 7f754ab..675d3cf 100644 --- a/lib/l10n/gen/app_localizations_en.dart +++ b/lib/l10n/gen/app_localizations_en.dart @@ -9,296 +9,308 @@ class SEn extends S { SEn([String locale = 'en']) : super(locale); @override - String yieldCardNetworkTooltipDescription({required String network}) { - return 'This pool is at $network'; - } - - @override - String get twentyFourHours => '24h'; + String get appBottomNavigationBarMyPositions => 'My Positions (Soon)'; @override - String get light => 'Light'; + String get appBottomNavigationBarNewPosition => 'New Position'; @override - String get dark => 'Dark'; + String get appCookiesConsentWidgetDescription => + 'We use cookies to ensure that we give you the best experience on our app. By continuing to use Zup Protocol, you agree to our'; @override - String get system => 'System'; + String get appFooterContactUs => 'Contact Us'; @override - String get week => 'Week'; + String get appFooterDocs => 'Docs'; @override - String get weekCompact => '7d'; + String get appFooterFAQ => 'FAQ'; @override String get appFooterTermsOfUse => 'Terms of Use'; @override - String get privacyPolicy => 'Privacy Policy'; + String get appHeaderMyPositions => 'My Positions (Soon)'; @override - String get appFooterContactUs => 'Contact Us'; + String get appHeaderNewPosition => 'New Position'; @override - String get appCookiesConsentWidgetDescription => - 'We use cookies to ensure that we give you the best experience on our app. By continuing to use Zup Protocol, you agree to our'; + String get appSettingsDropdownTestnetMode => 'Testnet Mode'; @override - String get appFooterDocs => 'Docs'; + String get close => 'Close'; @override - String get appFooterFAQ => 'FAQ'; + String get connectMyWallet => 'Connect My Wallet'; @override - String get understood => 'Understood'; + String get connectWallet => 'Connect Wallet'; @override - String depositPageShowingOnlyPoolsWithMoreThan({ - required String minLiquidity, - }) { - return 'Showing only liquidity pools with more than $minLiquidity.'; - } + String get connectYourWallet => 'Connect your wallet'; @override - String get depositPageShowingAllPools => 'Showing all liquidity pools.'; + String get createNewPosition => 'Create new position'; @override - String get depositPageSearchAllPools => 'Search all pools?'; + String get createPageDescription => + 'Ready to dive in? First, pick the dynamic duo of tokens you want to team up in the pool. Just choose your pair right below and you’re set to make some magic!'; @override - String depositPageSearchOnlyForPoolsWithMorethan({ - required String minLiquidity, - }) { - return 'Search only for pools with more than $minLiquidity?'; - } + String get createPageSelectTokensStageSearchSettings => 'Search Settings'; @override - String get depositPageTrySearchAllPools => 'Try search all pools?'; + String get createPageSelectTokensStageTokenA => 'Token A'; @override - String depositPageMinLiquiditySearchAlert({required String minLiquidity}) { - return 'You’ve set the search to only show pools with more than $minLiquidity.'; - } + String get createPageSelectTokensStageTokenB => 'Token B'; @override - String get slippageExplanation => - 'Slippage protects you by reverting the transaction if the price changes unfavorably beyond the percentage. This is necessary to prevent losses while adding liquidity'; + String get createPageSettingsDropdownAllowedPoolTypes => 'Allowed Pool Types'; @override - String get month => 'Month'; + String get createPageSettingsDropdownAllowedPoolTypesDescription => + 'Filter the types of liquidity pools to include in your search'; @override - String get minutes => 'Minutes'; + String get createPageSettingsDropdownMinimumLiquidity => + 'Minimum Pool Liquidity'; @override - String get depositSettingsDropdownChildHighSlippageWarningText => - 'High slippage can lead to Front Running and losses. Be careful! '; + String get createPageSettingsDropdownMinimumLiquidityExplanation => + 'Filter pools by minimum liquidity. We’ll exclude pools with less liquidity than specified, as low Liquidity can lead to misleading yields. This helps you find more reliable opportunities'; @override - String get whatsThisQuestionText => 'What\'s this?'; + String get createPageSettingsDropdownMiniumLiquidityLowWarning => + 'Low minimum TVL can lead to misleading yields.'; @override - String get depositSettingsDropdownChildTransactionDeadlineExplanation => - 'Your transaction will be reverted if it is pending for more than this amount of time'; + String get createPageShowMeTheMoney => 'Show me the money!'; @override - String get depositSettingsDropdownTransactionDeadline => - 'Transaction Deadline'; + String get createPageTitle => 'New Position'; @override - String depositPagePercentSlippage({required String valuePercent}) { - return '$valuePercent Slippage'; - } + String get dark => 'Dark'; @override - String get threeMonths => '3 Months'; + String get depositPageLoadingTitle => 'Getting the pool ready for you...'; @override - String get twentyFourHoursCompact => '24h'; + String get depositPageBackToYieldsButtonTitle => 'Select Yield'; @override - String get monthCompact => '30d'; + String get depositPageBackToNewPositionButtonTitle => 'Search other pools'; @override - String get threeMonthsCompact => '90d'; + String get depositPageBestYieldsIn => 'Best Yields in'; @override - String get depositSettingsDropdownChildMaxSlippage => 'Max Slippage'; + String get depositPageDeposit => 'Deposit'; @override String get depositPageDepositSectionTitle => 'Deposit'; @override - String get previewDepositModalWaitingTransaction => 'Waiting Transaction'; + String depositPageDepositSectionTokenNotNeeded({ + required String tokenSymbol, + }) { + return '$tokenSymbol is not necessary for your selected range'; + } @override - String previewDepositModalApprovingToken({required String tokenSymbol}) { - return 'Approving $tokenSymbol'; + String depositPageDepositWithNativeToken({required String tokenSymbol}) { + return 'Deposit with Native $tokenSymbol'; } @override - String previewDepositModalDepositingIntoPool({ - required String baseTokenSymbol, - required String quoteTokenSymbol, - }) { - return 'Depositing into $baseTokenSymbol/$quoteTokenSymbol pool'; - } + String get depositPageEmptyStateDescription => + 'Seems like that there are no pools matching your defined settings at the moment. Would you like to either change your settings or try another combination?'; @override - String previewDepositModalApproveToken({required String tokenSymbol}) { - return 'Approve $tokenSymbol'; - } + String get depositPageEmptyStateHelpButtonTitle => 'Go Back to New Position'; @override - String get previewDepositModalDeposit => 'Deposit'; + String get depositPageEmptyStateTitle => 'No Pools Found'; @override - String get previewDepositModalError => 'Error'; + String get depositPageErrorStateDescription => + 'We ran into an issue while fetching the pool data. Try again, and if it keeps happening, don\'t hesitate to reach out to us!'; @override - String get previewDepositModalCurrentPrice => 'Current Price'; + String get depositPageErrorStateTitle => 'Oops! Something went wrong!'; @override - String depositPageDepositSectionTokenNotNeeded({ - required String tokenSymbol, - }) { - return '$tokenSymbol is not necessary for your selected range'; + String depositPageInsufficientTokenBalance({required String tokenSymbol}) { + return 'Insufficient $tokenSymbol balance'; } @override - String get preview => 'Preview'; + String get depositPageInvalidRange => 'Invalid range'; @override - String get previewDepositModalTitle => 'Preview Deposit'; + String get depositPageInvalidRangeErrorText => + 'Max range should be greater than min range'; @override - String get previewDepositModalWaitingTransactionSnackBarMessage => - 'Waiting transaction to be confirmed. '; + String depositPageInvalidTokenAmount({required String tokenSymbol}) { + return 'Enter a valid amount for $tokenSymbol'; + } @override - String previewDepositModalApproveSuccessSnackBarMessage({ - required String tokenSymbol, - }) { - return '$tokenSymbol approved successfully. '; - } + String get depositPageLoadingStep1Description => + 'Pairing Token A and Token B to kick off the search for top yields!'; @override - String get previewDepositModalMyPosition => 'My Position'; + String get depositPageLoadingStep1Title => 'Matching Tokens...'; @override - String get previewDepositModalOutOfRange => 'Out of Range'; + String get depositPageLoadingStep2Description => + 'Searching through more than a thousand pool combos… so you don\'t have to'; @override - String get previewDepositModalInRange => 'In Range'; + String get depositPageLoadingStep2Title => 'Pair hunting…'; @override - String get previewDepositModalProtocol => 'Protocol'; + String get depositPageLoadingStep3Description => + 'Scanning pools, calculating returns, and filtering the noise'; @override - String get previewDepositModalNetwork => 'Network'; + String get depositPageLoadingStep3Title => 'Yield optimizer at work…'; @override - String get previewDepositModalYearlyYield => 'Yearly Yield'; + String get depositPageLoadingStep4Description => + 'Hang tight, we\'re filtering and organizing the best pools for you'; @override - String get previewDepositModalTransactionErrorSnackBarMessage => - 'Transaction Failed. Please try again, if the problem persists, '; + String get depositPageLoadingStep4Title => + 'Organizing the best pools for you…'; @override - String get previewDepositModalTransactionErrorSnackBarHelperButtonTitle => - 'Contact us'; + String get depositPageMaxRangeOutOfRangeWarningText => + 'You will not earn fees until the market price move down into your range'; @override - String previewDepositModalDepositSuccessSnackBarMessage({ - required String baseTokenSymbol, - required String quoteTokenSymbol, - required String protocol, - }) { - return 'Successfully Deposited into the $baseTokenSymbol/$quoteTokenSymbol Pool at $protocol. '; + String depositPageMinLiquiditySearchAlert({required String minLiquidity}) { + return 'You’ve set the search to only show pools with more than $minLiquidity.'; } @override - String yieldCardTimeFrameBest({required String timeFrame}) { - return '$timeFrame best'; + String get depositPageMinRangeOutOfRangeWarningText => + 'You will not earn fees until the market price move up into your range'; + + @override + String get depositPageNoYieldSelectedDescription => + 'Pick any yield card above and dive into depositing your liquidity!'; + + @override + String get depositPageNoYieldSelectedTitle => 'Pick a yield to deposit'; + + @override + String depositPagePercentSlippage({required String valuePercent}) { + return '$valuePercent Slippage'; } @override - String yieldCardThisPoolIsAtNetwork({required String network}) { - return 'This pool is at $network'; + String depositPagePleaseEnterAmountForToken({required String tokenSymbol}) { + return 'Please enter an amount for $tokenSymbol'; } @override - String get yieldCardYearlyYield => 'Yearly Yield'; + String get depositPageRangeSectionFullRange => 'Full Range'; @override - String yieldCardVisitProtocol({required String protocolName}) { - return 'Visit $protocolName'; + String get depositPageRangeSectionTitle => 'Select Range'; + + @override + String get depositPageSearchAllPools => 'Search all pools?'; + + @override + String depositPageSearchOnlyForPoolsWithMorethan({ + required String minLiquidity, + }) { + return 'Search only for pools with more than $minLiquidity?'; } @override - String get yieldCardAverageYieldYearly => 'Average Yearly Yield'; + String get depositPageShowingAllPools => 'Showing all liquidity pools.'; @override - String get previewDepositModalDepositSuccessSnackBarHelperButtonTitle => - 'View Transaction'; + String depositPageShowingOnlyPoolsWithMoreThan({ + required String minLiquidity, + }) { + return 'Showing only liquidity pools with more than $minLiquidity.'; + } @override - String get previewDepositModalApproveSuccessSnackBarHelperButtonTitle => - 'View Transaction'; + String get depositPageTimeFrameTitle => 'Preferred time frame'; @override - String get previewDepositModalWaitingTransactionSnackBarHelperButtonTitle => - 'View on Explorer'; + String get depositPageTimeFrameTooltipHelperButtonTitle => ' Learn more'; @override - String get depositPageInvalidRange => 'Invalid range'; + String get depositPageTimeFrameTooltipMessage => + 'Each time frame shows yields based on past performance. Shorter terms (24h, 7d) reflect recent trends and may suit short-term strategies. Longer terms (30d, 90d) offer a broader performance view for long-term decisions.'; @override - String get depositPageMinRangeOutOfRangeWarningText => - 'You will not earn fees until the market price move up into your range'; + String get depositPageTitle => 'Add liquidity'; @override - String get depositPageMaxRangeOutOfRangeWarningText => - 'You will not earn fees until the market price move down into your range'; + String get depositPageTrySearchAllPools => 'Try search all pools?'; @override - String get depositPageInvalidRangeErrorText => - 'Max range should be greater than min range'; + String get depositSettingsDropdownChildHighSlippageWarningText => + 'High slippage can lead to Front Running and losses. Be careful! '; @override - String get previewDepositModalMinPrice => 'Min Price'; + String get depositSettingsDropdownChildMaxSlippage => 'Max Slippage'; @override - String get previewDepositModalMaxPrice => 'Max Price'; + String get depositSettingsDropdownChildTransactionDeadlineExplanation => + 'Your transaction will be reverted if it is pending for more than this amount of time'; @override - String get rangeSelectorMinRange => 'Min Range'; + String get depositSettingsDropdownTransactionDeadline => + 'Transaction Deadline'; @override - String get rangeSelectorMaxRange => 'Max Range'; + String get depositSuccessModalDescriptionPart1 => + 'You have successfully deposited into'; @override - String get loading => 'Loading...'; + String get depositSuccessModalDescriptionPart2 => 'Pool at'; @override - String get exchangesFilterDropdownButtonDropdownClearAll => 'Clear All'; + String get depositSuccessModalDescriptionPart3 => 'on'; @override - String get exchangesFilterDropdownButtonDropdownSelectAll => 'Select All'; + String get depositSuccessModalTitle => 'Deposit Successful!'; @override - String get exchangesFilterDropdownButtonDropdownSearchHint => - 'Search by name'; + String depositSuccessModalViewPositionOnDEX({required String dexName}) { + return 'View Position on $dexName'; + } @override - String get exchangesFilterDropdownButtonDropdownNotFoundStateTitle => - 'Not found'; + String get exchangesFilterDropdownButtonDropdownClearAll => 'Clear All'; @override String get exchangesFilterDropdownButtonDropdownNotFoundStateDescription => 'No supported exchanges found with this name'; + @override + String get exchangesFilterDropdownButtonDropdownNotFoundStateTitle => + 'Not found'; + + @override + String get exchangesFilterDropdownButtonDropdownSearchHint => + 'Search by name'; + + @override + String get exchangesFilterDropdownButtonDropdownSelectAll => 'Select All'; + @override String get exchangesFilterDropdownButtonErrorSnackBarMessage => 'Uh-oh! Something went wrong loading the exchanges. Please try refreshing the page.'; @@ -314,142 +326,289 @@ class SEn extends S { } @override - String get createPageSelectTokensStageTokenA => 'Token A'; + String get letsGiveItAnotherGo => + 'Let’s give it another go, because why not?'; @override - String get createPageSelectTokensStageTokenB => 'Token B'; + String get letsGiveItAnotherShot => 'Let’s give it another shot'; @override - String get createPageSelectTokensStageSearchSettings => 'Search Settings'; + String get light => 'Light'; @override - String get depositPageLoadingStep1Title => 'Matching Tokens...'; + String get loading => 'Loading...'; @override - String get depositPageLoadingStep1Description => - 'Pairing Token A and Token B to kick off the search for top yields!'; + String get minutes => 'Minutes'; + + @override + String get month => 'Month'; + + @override + String get monthCompact => '30d'; + + @override + String get newPosition => 'New Position'; + + @override + String get noResultsFor => 'No results for'; + + @override + String get popularTokens => 'Popular Tokens'; + + @override + String get positionCardLiquidity => 'Liquidity: '; + + @override + String get positionCardMax => 'Max: '; + + @override + String get positionCardMin => 'Min: '; + + @override + String positionCardTokenPerToken({ + required String token0Qtd, + required String token0Symbol, + required String token1Symbol, + }) { + return '$token0Qtd $token0Symbol per $token1Symbol'; + } + + @override + String get positionCardUnclaimedFees => 'Unclaimed fees: '; + + @override + String get positionCardViewMore => 'View more'; + + @override + String get positionStatusClosed => 'Closed'; + + @override + String get positionStatusInRange => 'In Range'; + + @override + String get positionStatusOutOfRange => 'Out of Range'; + + @override + String get positionsPageCantFindAPosition => + 'Can’t find a position? Try switching the app’s network to \"All Networks\" or reload the page'; + + @override + String get positionsPageErrorStateDescription => + 'An error occurred while loading your positions.\nPlease try again. If the issue persists, feel free to contact us'; + + @override + String get positionsPageMyPositions => 'My Positions'; + + @override + String get positionsPageNoPositionsDescription => + 'Hm… It looks like you don’t have any positions yet.\nWant to create one?'; + + @override + String positionsPageNoPositionsInNetwork({required String network}) { + return 'No positions in $network'; + } + + @override + String positionsPageNoPositionsInNetworkDescription({ + required String network, + }) { + return 'It looks like you don’t have any positions in $network yet.\nGo ahead and create one to get started!'; + } + + @override + String get positionsPageNoPositionsTitle => + 'You don’t have any positions yet'; + + @override + String get positionsPageNotConnectedDescription => + 'Wallet not connected. Please connect your\nwallet to view your positions'; + + @override + String positionsPageShowHideClosedPositions({required String isHidden}) { + String _temp0 = intl.Intl.selectLogic(isHidden, { + 'true': 'Show', + 'false': 'Hide', + 'other': 'Show/Hide', + }); + return '$_temp0 closed positions'; + } + + @override + String get preview => 'Preview'; + + @override + String get previewDepositModalApproveSuccessSnackBarHelperButtonTitle => + 'View Transaction'; + + @override + String previewDepositModalApproveSuccessSnackBarMessage({ + required String tokenSymbol, + }) { + return '$tokenSymbol approved successfully. '; + } + + @override + String previewDepositModalApproveToken({required String tokenSymbol}) { + return 'Approve $tokenSymbol'; + } + + @override + String previewDepositModalApprovingToken({required String tokenSymbol}) { + return 'Approving $tokenSymbol'; + } + + @override + String get previewDepositModalAutoSlippageCheckErrorMessage => + 'Strong market movement! Slippage exceeded. Try again or adjust tolerance.'; + + @override + String previewDepositModalCubitApprovedSnackBarMessage({ + required String tokenSymbol, + }) { + return 'Successfully approved $tokenSymbol'; + } + + @override + String get previewDepositModalCubitApprovingSnackBarMessage => 'Approving...'; + + @override + String previewDepositModalCubitDepositingSnackBarMessage({ + required String token0Symbol, + required String token1Symbol, + }) { + return 'Depositing into $token0Symbol/$token1Symbol Pool...'; + } + + @override + String get previewDepositModalCurrentPrice => 'Current Price'; + + @override + String get previewDepositModalDeposit => 'Deposit'; + + @override + String get previewDepositModalDepositSuccessSnackBarHelperButtonTitle => + 'View Transaction'; + + @override + String previewDepositModalDepositSuccessSnackBarMessage({ + required String baseTokenSymbol, + required String quoteTokenSymbol, + required String protocol, + }) { + return 'Successfully Deposited into the $baseTokenSymbol/$quoteTokenSymbol Pool at $protocol. '; + } @override - String get depositPageLoadingStep2Title => 'Pair hunting…'; + String previewDepositModalDepositingIntoPool({ + required String baseTokenSymbol, + required String quoteTokenSymbol, + }) { + return 'Depositing into $baseTokenSymbol/$quoteTokenSymbol pool'; + } @override - String get depositPageLoadingStep2Description => - 'Searching through more than a thousand pool combos… so you don\'t have to'; + String get previewDepositModalError => 'Error'; @override - String get depositPageLoadingStep3Title => 'Yield optimizer at work…'; + String get previewDepositModalInRange => 'In Range'; @override - String get depositPageLoadingStep3Description => - 'Scanning pools, calculating returns, and filtering the noise'; + String get previewDepositModalMaxPrice => 'Max Price'; @override - String get depositPageBestYieldsIn => 'Best Yields in'; + String get previewDepositModalMinPrice => 'Min Price'; @override - String get depositPageLoadingStep4Title => - 'Organizing the best pools for you…'; + String get previewDepositModalMyPosition => 'My Position'; @override - String get depositPageLoadingStep4Description => - 'Hang tight, we\'re filtering and organizing the best pools for you'; + String get previewDepositModalNetwork => 'Network'; @override - String get depositPageErrorStateTitle => 'Oops! Something went wrong!'; + String get previewDepositModalOutOfRange => 'Out of Range'; @override - String get depositPageErrorStateDescription => - 'We ran into a issue while trying to find the best pool. Give it another shot, and if it keeps happening, don’t hesitate to reach out to us!'; + String get previewDepositModalProtocol => 'Protocol'; @override - String get depositPageEmptyStateTitle => 'No Pools Found'; + String get previewDepositModalSlippageCheckErrorMessage => + 'Slippage Check! Please try increasing your slippage for this transaction'; @override - String get depositPageEmptyStateDescription => - 'Seems like that there are no pools matching your defined settings at the moment. Would you like to either change your settings or try another combination?'; + String get previewDepositModalTitle => 'Preview Deposit'; @override - String get depositPageEmptyStateHelpButtonTitle => 'Go Back to New Position'; + String get previewDepositModalTransactionErrorSnackBarHelperButtonTitle => + 'Contact us'; @override - String get depositPageBackButtonTitle => 'Select Pair'; + String get previewDepositModalTransactionErrorSnackBarMessage => + 'Transaction Failed. Please try again, if the problem persists, '; @override - String get depositPageTitle => 'Add liquidity'; + String get previewDepositModalWaitingTransaction => 'Waiting Transaction'; @override - String get depositPageTimeFrameTooltipMessage => - 'Each time frame shows yields based on past performance. Shorter terms (24h, 7d) reflect recent trends and may suit short-term strategies. Longer terms (30d, 90d) offer a broader performance view for long-term decisions.'; + String get previewDepositModalWaitingTransactionSnackBarHelperButtonTitle => + 'View on Explorer'; @override - String get depositPageTimeFrameTooltipHelperButtonTitle => ' Learn more'; + String get previewDepositModalWaitingTransactionSnackBarMessage => + 'Waiting transaction to be confirmed. '; @override - String get depositPageTimeFrameTitle => 'Preferred time frame'; + String get previewDepositModalYearlyYield => 'Yearly Yield'; @override - String get depositPageNoYieldSelectedTitle => 'Pick a yield to deposit'; + String get privacyPolicy => 'Privacy Policy'; @override - String get depositPageNoYieldSelectedDescription => - 'Pick any yield card above and dive into depositing your liquidity!'; + String get rangeSelectorMaxRange => 'Max Range'; @override - String get depositPageRangeSectionTitle => 'Select Range'; + String get rangeSelectorMinRange => 'Min Range'; @override - String get depositPageRangeSectionFullRange => 'Full Range'; + String get searchResults => 'Search results'; @override - String depositPageInvalidTokenAmount({required String tokenSymbol}) { - return 'Enter a valid amount for $tokenSymbol'; - } + String get selectToken => 'Select Token'; @override - String depositPageInsufficientTokenBalance({required String tokenSymbol}) { - return 'Insufficient $tokenSymbol balance'; - } + String get slippageExplanation => + 'Slippage protects you by reverting the transaction if the price changes unfavorably beyond the percentage. This is necessary to prevent losses while adding liquidity'; @override - String depositPagePleaseEnterAmountForToken({required String tokenSymbol}) { - return 'Please enter an amount for $tokenSymbol'; - } + String get somethingWhenWrong => 'Something went wrong'; @override - String get depositPageDeposit => 'Deposit'; + String get system => 'System'; @override - String get connectYourWallet => 'Connect your wallet'; + String get threeMonths => '3 Months'; @override - String get tokenSelectorModalTitle => 'Select a token'; + String get threeMonthsCompact => '90d'; @override - String get tokenSelectorModalTokenGroups => 'Token Groups'; + String get token0 => 'Token A'; @override - String get tokenSelectorModalTokenGroupsTooltipMessage => - 'Token groups let you search pools using multiple tokens in one go. Think of them like batch queries, want all USD stablecoins? Pick the group and we\'ll surface every relevant pool. You can also match groups against single tokens or other groups to discover deep liquidity.'; + String get token1 => 'Token B'; @override String get tokenSelectorModalDescription => 'Pick a token from our list or search by symbol or address to build your ideal liquidity pool!'; @override - String get tokenSelectorModalSearchTitle => 'Search token or paste address'; - - @override - String get tokenSelectorModalSearchTitleAllNetworks => - 'Search token by symbol or name'; + String get tokenSelectorModalErrorDescription => + 'We hit a snag loading your token list. Give it another go, and if it keeps happening, feel free to reach us'; @override String get tokenSelectorModalSearchAlertForAllNetworks => 'When ‘All Networks’ is selected, you can only search by name or symbol. To search by address as well, please select a specific network'; - @override - String get tokenSelectorModalErrorDescription => - 'We hit a snag loading your token list. Give it another go, and if it keeps happening, feel free to reach us'; - @override String tokenSelectorModalSearchErrorDescription({ required String searchedTerm, @@ -458,222 +617,167 @@ class SEn extends S { } @override - String get noResultsFor => 'No results for'; + String get tokenSelectorModalSearchTitle => 'Search token or paste address'; @override - String get searchResults => 'Search results'; + String get tokenSelectorModalSearchTitleAllNetworks => + 'Search token by symbol or name'; @override - String get popularTokens => 'Popular Tokens'; + String get tokenSelectorModalTitle => 'Select a token'; @override - String get connectMyWallet => 'Connect My Wallet'; + String get tokenSelectorModalTokenGroups => 'Token Groups'; @override - String get connectWallet => 'Connect Wallet'; + String get tokenSelectorModalTokenGroupsTooltipMessage => + 'Token groups let you search pools using multiple tokens in one go. Think of them like batch queries, want all USD stablecoins? Pick the group and we\'ll surface every relevant pool. You can also match groups against single tokens or other groups to discover deep liquidity.'; @override - String get positionsPageNotConnectedDescription => - 'Wallet not connected. Please connect your\nwallet to view your positions'; + String get tvl => 'TVL'; @override - String get positionsPageNoPositionsTitle => - 'You don’t have any positions yet'; + String get twentyFourHours => '24h'; @override - String get positionsPageNoPositionsDescription => - 'Hm… It looks like you don’t have any positions yet.\nWant to create one?'; + String get twentyFourHoursCompact => '24h'; @override - String get newPosition => 'New Position'; + String get understood => 'Understood'; @override - String get createNewPosition => 'Create new position'; + String get unknown => 'Unknown'; @override - String get positionCardMin => 'Min: '; + String get week => 'Week'; @override - String get positionCardMax => 'Max: '; + String get weekCompact => '7d'; @override - String get positionCardLiquidity => 'Liquidity: '; + String get whatsThisQuestionText => 'What\'s this?'; @override - String get positionCardUnclaimedFees => 'Unclaimed fees: '; + String get yieldCardAverageYieldYearly => 'Average Yearly Yield'; @override - String get positionCardViewMore => 'View more'; + String get yieldCardDeposit => 'Deposit'; @override - String positionsPageNoPositionsInNetwork({required String network}) { - return 'No positions in $network'; + String yieldCardNetworkTooltipDescription({required String network}) { + return 'This pool is at $network'; } @override - String positionsPageShowHideClosedPositions({required String isHidden}) { - String _temp0 = intl.Intl.selectLogic(isHidden, { - 'true': 'Show', - 'false': 'Hide', - 'other': 'Show/Hide', - }); - return '$_temp0 closed positions'; + String yieldCardThisPoolIsAtNetwork({required String network}) { + return 'This pool is at $network'; } @override - String positionsPageNoPositionsInNetworkDescription({ - required String network, - }) { - return 'It looks like you don’t have any positions in $network yet.\nGo ahead and create one to get started!'; + String yieldCardTimeFrameBest({required String timeFrame}) { + return '$timeFrame best'; } @override - String positionCardTokenPerToken({ - required String token0Qtd, - required String token0Symbol, - required String token1Symbol, - }) { - return '$token0Qtd $token0Symbol per $token1Symbol'; + String yieldCardTimeframeYield({required String timeframe}) { + return '$timeframe Yield'; } @override - String get positionsPageMyPositions => 'My Positions'; - - @override - String get appHeaderMyPositions => 'My Positions (Soon)'; - - @override - String get appHeaderNewPosition => 'New Position'; - - @override - String get appBottomNavigationBarMyPositions => 'My Positions (Soon)'; - - @override - String get appBottomNavigationBarNewPosition => 'New Position'; - - @override - String get positionsPageCantFindAPosition => - 'Can’t find a position? Try switching the app’s network to \"All Networks\" or reload the page'; - - @override - String get somethingWhenWrong => 'Something went wrong'; - - @override - String get positionsPageErrorStateDescription => - 'An error occurred while loading your positions.\nPlease try again. If the issue persists, feel free to contact us'; - - @override - String get positionStatusInRange => 'In Range'; - - @override - String get positionStatusOutOfRange => 'Out of Range'; - - @override - String get positionStatusClosed => 'Closed'; - - @override - String get unknown => 'Unknown'; - - @override - String get selectToken => 'Select Token'; + String yieldCardVisitProtocol({required String protocolName}) { + return 'Visit $protocolName'; + } @override - String get token0 => 'Token A'; + String get yieldCardYearlyYield => 'Yearly Yield'; @override - String get token1 => 'Token B'; + String yieldCardYieldExplanation({required String timeframeLabel}) { + return 'Estimated annual yield based on $timeframeLabel of data from fees distributed to liquidity providers.'; + } @override - String get depositSuccessModalTitle => 'Deposit Successful!'; + String yieldsPageApplyTvlFilterButtonTitle({required String tvlUSD}) { + return 'Show only pools with more than $tvlUSD TVL.'; + } @override - String get depositSuccessModalDescriptionPart1 => - 'You have successfully deposited into'; + String get yieldsPageBackButtonTitle => 'Select Pair'; @override - String get depositSuccessModalDescriptionPart2 => 'Pool at'; + String get yieldsPageDescription => + 'Select the yield that most suits your needs and deposit to start earning'; @override - String get depositSuccessModalDescriptionPart3 => 'on'; + String get yieldsPageDisplayingAllPoolsAlert => + 'Displaying all liquidity pools.'; @override - String previewDepositModalCubitDepositingSnackBarMessage({ - required String token0Symbol, - required String token1Symbol, - }) { - return 'Depositing into $token0Symbol/$token1Symbol Pool...'; + String yieldsPageDisplayingPoolsWithMinTvlAlert({required String tvlUSD}) { + return 'Displaying only liquidity pools with more than $tvlUSD TVL.'; } @override - String get previewDepositModalCubitApprovingSnackBarMessage => 'Approving...'; + String get yieldsPageEmptyStateDescription => + 'Seems like that there are no pools matching your defined settings at the moment. Would you like to either change your settings or try another combination?'; @override - String previewDepositModalCubitApprovedSnackBarMessage({ - required String tokenSymbol, - }) { - return 'Successfully approved $tokenSymbol'; - } + String get yieldsPageEmptyStateHelperButtonTitle => 'Go Back to New Position'; @override - String depositSuccessModalViewPositionOnDEX({required String dexName}) { - return 'View Position on $dexName'; + String yieldsPageEmptyStateMinTVLAlert({required String tvlUSD}) { + return 'Searched only for liquidity pools with more than $tvlUSD TVL'; } @override - String get close => 'Close'; - - @override - String depositPageDepositWithNativeToken({required String tokenSymbol}) { - return 'Deposit with Native $tokenSymbol'; - } + String get yieldsPageEmptyStateTitle => 'No Pools Found'; @override - String get createPageSettingsDropdownMinimumLiquidity => - 'Minimum Pool Liquidity'; + String get yieldsPageErrorStateDescription => + 'We ran into a issue while trying to find the best pool. Give it another shot, and if it keeps happening, don\'t hesitate to reach out to us!'; @override - String get createPageSettingsDropdownAllowedPoolTypes => 'Allowed Pool Types'; + String get yieldsPageErrorStateTitle => 'Oops! Something went wrong!'; @override - String get createPageSettingsDropdownAllowedPoolTypesDescription => - 'Filter the types of liquidity pools to include in your search'; + String get yieldsPageLoadingStep1Description => + 'Pairing Token A and Token B to kick off the search for top yields!'; @override - String get createPageSettingsDropdownMinimumLiquidityExplanation => - 'Filter pools by minimum liquidity. We’ll exclude pools with less liquidity than specified, as low Liquidity can lead to misleading yields. This helps you find more reliable opportunities'; + String get yieldsPageLoadingStep1Title => 'Matching Tokens...'; @override - String get createPageSettingsDropdownMiniumLiquidityLowWarning => - 'Low minimum TVL can lead to misleading yields.'; + String get yieldsPageLoadingStep2Description => + 'Searching through more than a thousand pool combos… so you don\'t have to'; @override - String get appSettingsDropdownTestnetMode => 'Testnet Mode'; + String get yieldsPageLoadingStep2Title => 'Pair hunting…'; @override - String get previewDepositModalSlippageCheckErrorMessage => - 'Slippage Check! Please try increasing your slippage for this transaction'; + String get yieldsPageLoadingStep3Description => + 'Scanning pools, calculating returns, and filtering the noise'; @override - String get previewDepositModalAutoSlippageCheckErrorMessage => - 'Strong market movement! Slippage exceeded. Try again or adjust tolerance.'; + String get yieldsPageLoadingStep3Title => 'Yield optimizer at work…'; @override - String get createPageTitle => 'New Position'; + String get yieldsPageLoadingStep4Description => + 'Hang tight, we\'re filtering and organizing the best pools for you'; @override - String get tvl => 'TVL'; + String get yieldsPageLoadingStep4Title => + 'Organizing the best pools for you…'; @override - String get createPageShowMeTheMoney => 'Show me the money!'; + String get yieldsPageSearchAllPools => 'Search all pools'; @override - String get createPageDescription => - 'Ready to dive in? First, pick the dynamic duo of tokens you want to team up in the pool. Just choose your pair right below and you’re set to make some magic!'; + String get yieldsPageTimeframeExplanation => + 'Each time frame shows yields based on past performance. Shorter windows (24h, 7d) highlight recent trends for quick moves. Longer windows (30d, 90d) provide a broader view for mid to long-term decisions'; @override - String get letsGiveItAnotherGo => - 'Let’s give it another go, because why not?'; + String get yieldsPageTimeframeSelectorTitle => 'Best yields in'; @override - String get letsGiveItAnotherShot => 'Let’s give it another shot'; + String get yieldsPageTitle => 'Choose a pool for you'; } diff --git a/lib/widgets/timeframe_selector.dart b/lib/widgets/timeframe_selector.dart new file mode 100644 index 0000000..9d391a2 --- /dev/null +++ b/lib/widgets/timeframe_selector.dart @@ -0,0 +1,81 @@ +import 'package:flutter/cupertino.dart'; +import 'package:zup_app/core/enums/yield_timeframe.dart'; +import 'package:zup_app/gen/assets.gen.dart'; +import 'package:zup_app/l10n/gen/app_localizations.dart'; +import 'package:zup_core/extensions/build_context_extension.dart'; +import 'package:zup_ui_kit/zup_ui_kit.dart'; + +class TimeframeSelector extends StatefulWidget { + const TimeframeSelector({super.key, required this.selectedTimeframe, required this.onTimeframeSelected}); + + final YieldTimeFrame selectedTimeframe; + final Function(YieldTimeFrame? newTimeframe) onTimeframeSelected; + + @override + State createState() => _TimeframeSelectorState(); +} + +class _TimeframeSelectorState extends State { + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 10), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(12)), + child: Wrap( + runSpacing: 10, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Text( + S.of(context).yieldsPageTimeframeSelectorTitle, + style: TextStyle( + color: ZupThemeColors.primaryText.themed(context.brightness), + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(width: 10), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CupertinoSlidingSegmentedControl( + proportionalWidth: true, + onValueChanged: (timeframe) async { + widget.onTimeframeSelected(timeframe); + }, + groupValue: widget.selectedTimeframe, + children: Map.fromEntries( + YieldTimeFrame.values.map( + (timeframe) => MapEntry( + timeframe, + IgnorePointer( + key: Key("${timeframe.name}-timeframe-button"), + child: Text( + timeframe.compactDaysLabel(context), + style: TextStyle( + color: ZupThemeColors.primaryText.themed(context.brightness), + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ) + .animatedHover(animationValue: 0.2, type: ZupAnimatedHoverType.opacity) + .animatedHover(animationValue: 0.95, type: ZupAnimatedHoverType.scale), + ), + ), + ), + ), + const SizedBox(width: 10), + ZupTooltip.text( + key: const Key("timeframe-tooltip"), + message: S.of(context).yieldsPageTimeframeExplanation, + child: Assets.icons.infoCircle.svg( + colorFilter: const ColorFilter.mode(ZupColors.gray, BlendMode.srcIn), + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/widgets/yield_card.dart b/lib/widgets/yield_card.dart index 2caf27c..d92daf7 100644 --- a/lib/widgets/yield_card.dart +++ b/lib/widgets/yield_card.dart @@ -1,207 +1,295 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:intl/intl.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'package:zup_app/app/app_cubit/app_cubit.dart'; +import 'package:skeletonizer/skeletonizer.dart' hide ShimmerEffect; import 'package:zup_app/core/dtos/yield_dto.dart'; import 'package:zup_app/core/enums/yield_timeframe.dart'; -import 'package:zup_app/core/extensions/num_extension.dart'; import 'package:zup_app/core/injections.dart'; +import 'package:zup_app/core/mixins/keys_mixin.dart'; +import 'package:zup_app/gen/assets.gen.dart'; import 'package:zup_app/l10n/gen/app_localizations.dart'; import 'package:zup_app/widgets/token_avatar.dart'; import 'package:zup_app/widgets/zup_cached_image.dart'; import 'package:zup_core/extensions/extensions.dart'; +import 'package:zup_core/mixins/device_info_mixin.dart'; import 'package:zup_ui_kit/zup_ui_kit.dart'; class YieldCard extends StatefulWidget { const YieldCard({ super.key, - required this.currentYield, - required this.onChangeSelection, - required this.isSelected, - required this.timeFrame, - this.isHotestYield = false, + required this.yieldPool, + required this.yieldTimeFrame, + required this.showHotestYieldAnimation, + this.mainButton, + this.expandWidth = false, + this.showTimeframe = false, }); - final YieldDto currentYield; - final bool isSelected; - final Function(YieldDto? yield) onChangeSelection; - final YieldTimeFrame timeFrame; - final bool isHotestYield; + final YieldDto yieldPool; + final bool showHotestYieldAnimation; + final bool showTimeframe; + final bool expandWidth; + final YieldTimeFrame yieldTimeFrame; + final Widget? mainButton; @override State createState() => _YieldCardState(); } -class _YieldCardState extends State { +class _YieldCardState extends State with DeviceInfoMixin, KeysMixin { final zupCachedImage = inject(); - final appCubit = inject(); final infinityAnimationAutoPlay = inject(instanceName: InjectInstanceNames.infinityAnimationAutoPlay); - final selectionAnimationDuration = const Duration(milliseconds: 150); - - Widget get yieldText => Text( - widget.currentYield.yieldTimeframed(widget.timeFrame).formatPercent, - style: TextStyle( - fontSize: 26, - fontWeight: FontWeight.w600, - color: ZupThemeColors.backgroundInverse.themed(context.brightness), - ), - ); + List get timeframesExcludingCurrent { + return YieldTimeFrame.values.where((timeframe) => timeframe != widget.yieldTimeFrame).toList(); + } @override Widget build(BuildContext context) { - return ZupSelectableCard( - isSelected: widget.isSelected, - selectionAnimationDuration: selectionAnimationDuration, - - onPressed: () { - return widget.onChangeSelection(widget.isSelected ? null : widget.currentYield); - }, - padding: const EdgeInsets.all(10).copyWith(right: 2, top: 2, bottom: 0), - child: Stack( - children: [ - if (appCubit.selectedNetwork.isAllNetworks) - Positioned( - right: 2, - top: 2, - child: ZupTooltip.text( - message: S.of(context).yieldCardThisPoolIsAtNetwork(network: widget.currentYield.network.label), - trailingIcon: widget.currentYield.network.icon(context.brightness), - child: Container( - height: 40, - padding: const EdgeInsets.all(6), - width: 40, - decoration: BoxDecoration( - color: widget.isSelected - ? ZupColors.brand.withValues(alpha: 0.1) - : (context.brightness.isDark ? ZupColors.black2 : ZupColors.gray6), - borderRadius: BorderRadius.circular(8), - ), - child: widget.currentYield.network.icon(context.brightness), - ), - ), - ), - Column( + return Hero( + tag: yieldCardHeroKey(widget.yieldPool.poolAddress), + flightShuttleBuilder: + ( + BuildContext _, + Animation _, + HeroFlightDirection _, + BuildContext fromHeroContext, + BuildContext toHeroContext, + ) { + return Align(alignment: Alignment.topRight, child: toHeroContext.widget); + }, + child: Container( + padding: const EdgeInsets.all(25), + width: widget.expandWidth ? double.infinity : null, + constraints: const BoxConstraints(maxWidth: 450), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + color: context.brightness.isDark ? ZupColors.black3 : ZupColors.white, + border: context.brightness.isDark + ? null + : Border.all(width: 0.5, color: ZupThemeColors.borderOnBackgroundSurface.themed(context.brightness)), + ), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 250), + child: Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ - Padding( - padding: const EdgeInsets.only(top: 10), - child: Text( - widget.timeFrame.isDay - ? S.of(context).yieldCardYearlyYield - : S.of(context).yieldCardAverageYieldYearly, - style: TextStyle(fontSize: 14, color: ZupThemeColors.primaryText.themed(context.brightness)), - ), - ), Row( mainAxisSize: MainAxisSize.min, children: [ - if (widget.isHotestYield) ...[ - yieldText.animate( - effects: [ - const ScaleEffect( - duration: Duration(milliseconds: 200), - alignment: Alignment.center, - begin: Offset(1.1, 1.1), - end: Offset(1, 1), - ), - ShimmerEffect( - duration: const Duration(seconds: 2), - color: ZupThemeColors.shimmer.themed(context.brightness), - curve: Curves.decelerate, - angle: 90, - size: 1, - ), - ], - autoPlay: infinityAnimationAutoPlay, - onComplete: (controller) => controller.repeat(reverse: true), + Stack( + clipBehavior: Clip.none, + children: [ + TokenAvatar(asset: widget.yieldPool.token0, size: 27), + Positioned(left: 20, child: TokenAvatar(asset: widget.yieldPool.token1, size: 27)), + ], + ), + const SizedBox(width: 27), + Text( + "${widget.yieldPool.token0.symbol.clampMax(8)}/${widget.yieldPool.token1.symbol.clampMax(8)}", + style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 15), + overflow: TextOverflow.ellipsis, + ), + const Spacer(), + ZupTooltip.text( + key: Key("yield-card-network-${widget.yieldPool.network.label}"), + message: S.of(context).yieldCardThisPoolIsAtNetwork(network: widget.yieldPool.network.label), + trailingIcon: widget.yieldPool.network.icon(context.brightness), + child: Container( + height: 40, + width: 40, + alignment: Alignment.center, + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: context.brightness.isDark + ? ZupThemeColors.background.themed(Brightness.dark) + : ZupColors.gray6, + borderRadius: BorderRadius.circular(12), + ), + child: widget.yieldPool.network.icon(context.brightness), ), - ] else - yieldText, + ), ], ), - Text( - "${NumberFormat.compactSimpleCurrency(decimalDigits: 2).format(widget.currentYield.totalValueLockedUSD)} ${S.of(context).tvl}", - style: const TextStyle(fontSize: 14, height: 1, color: ZupColors.gray), + const SizedBox(height: 25), + Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + if (widget.showHotestYieldAnimation) ...[ + yieldPercentText.animate( + autoPlay: infinityAnimationAutoPlay, + onComplete: (controller) => controller.repeat(reverse: true), + effects: [ + const ScaleEffect( + duration: Duration(milliseconds: 200), + alignment: Alignment.center, + begin: Offset(1.1, 1.1), + end: Offset(1, 1), + ), + ShimmerEffect( + duration: const Duration(seconds: 2), + color: context.brightness.isDark ? ZupColors.orange5 : ZupColors.orange, + curve: Curves.decelerate, + angle: 90, + size: 1, + ), + ], + ), + ] else ...[ + yieldPercentText, + ], + const SizedBox(width: 5), + Skeleton.ignore( + child: ZupTooltip.widget( + key: Key("yield-breakdown-tooltip-${widget.yieldPool.poolAddress}"), + tooltipChild: yieldBreakdownTooltipChild, + child: Assets.icons.infoCircle.svg( + width: 14, + height: 14, + colorFilter: const ColorFilter.mode(ZupColors.gray, BlendMode.srcIn), + ), + ), + ), + ], + ), + Text( + "${S.of(context).yieldCardYearlyYield} ${widget.showTimeframe ? "(${widget.yieldTimeFrame.compactDaysLabel(context)})" : ""}", + style: const TextStyle(fontSize: 15, color: ZupColors.gray), + ), + ], + ), + const Spacer(), + Container( + height: 50, + width: 1, + color: ZupThemeColors.borderOnBackgroundSurface.themed(context.brightness), + ), + const Spacer(), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + NumberFormat.compactSimpleCurrency( + decimalDigits: 2, + ).format(widget.yieldPool.totalValueLockedUSD), + style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 20), + ), + Text(S.of(context).tvl, style: const TextStyle(fontSize: 15, color: ZupColors.gray)), + ], + ), + ], ), - const SizedBox(height: 10), + const SizedBox(height: 35), Row( mainAxisSize: MainAxisSize.min, children: [ - Container( - padding: const EdgeInsets.all(6), - decoration: BoxDecoration( - color: widget.isSelected - ? (context.brightness.isDark ? ZupColors.brand.withValues(alpha: 0.1) : ZupColors.brand5) - : (context.brightness.isDark ? ZupColors.black2 : ZupColors.gray6), - borderRadius: BorderRadius.circular(8), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - ZupMergedWidgets( - firstWidget: TokenAvatar(asset: widget.currentYield.token0, size: 25), - secondWidget: TokenAvatar(asset: widget.currentYield.token1, size: 25), - spacing: 0, - ), - const SizedBox(width: 10), - Text( - "${widget.currentYield.token0.symbol}/${widget.currentYield.token1.symbol}", - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: ZupThemeColors.primaryText.themed(context.brightness), - ), - ), - ], - ), + zupCachedImage.build( + context, + widget.yieldPool.protocol.logo, + radius: 20, + height: 25, + width: 25, + backgroundColor: ZupColors.white, ), const SizedBox(width: 10), - const Text(">", style: TextStyle(color: ZupColors.gray)), - const SizedBox(width: 10), - if (widget.currentYield.protocol.logo.isNotEmpty) - zupCachedImage.build( - context, - widget.currentYield.protocol.logo, - height: 25, - width: 25, - radius: 50, - errorWidget: (context, error, stackTrace) { - return Container( - height: 25, - width: 25, - decoration: BoxDecoration(color: ZupColors.gray6, borderRadius: BorderRadius.circular(50)), - ); - }, - ), - const SizedBox(width: 5), Flexible( - child: ZupTooltip.text( - message: "", - helperButtonTitle: S - .of(context) - .yieldCardVisitProtocol(protocolName: widget.currentYield.protocol.name), - helperButtonOnPressed: () => launchUrl(Uri.parse(widget.currentYield.protocol.url)), - child: Text( - widget.currentYield.protocol.name, - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: ZupThemeColors.backgroundInverse.themed(context.brightness), - ), - ), + child: Text( + widget.yieldPool.protocol.name, + style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 15), + overflow: TextOverflow.ellipsis, ), ), - const SizedBox(width: 20), ], ), + if (widget.mainButton != null) ...[ + const SizedBox(height: 30), + Row( + children: [ + // ZupIconButton( + // icon: Assets.icons.chartBar.svg(height: 15, width: 15), + // onPressed: (_) {}, + // backgroundColor: context.brightness.isDark + // ? ZupThemeColors.background.themed(Brightness.dark) + // : ZupColors.gray6, + // padding: const EdgeInsets.all(15), + // ), + // const SizedBox(width: 20), + Expanded(child: widget.mainButton!), + ], + ), + ], ], ), - ], + ), ), ); } + + Widget get yieldPercentText { + final text = Text( + widget.yieldPool.timeframedYieldFormatted(widget.yieldTimeFrame), + key: Key("yield-card-yield-${widget.yieldPool.poolAddress}"), + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 28, + color: context.brightness.isDark ? ZupColors.brand5 : ZupColors.brand3, + ), + ); + + if (isMobileDevice) return ZupTooltip.widget(tooltipChild: yieldBreakdownTooltipChild, child: text); + + return text; + } + + Widget get yieldBreakdownTooltipChild => SizedBox( + width: 200, + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.of(context).yieldCardYieldExplanation(timeframeLabel: widget.yieldTimeFrame.compactDaysLabel(context)), + style: const TextStyle(color: ZupColors.gray, fontSize: 14), + ), + const SizedBox(height: 16), + + ...timeframesExcludingCurrent.mapIndexed( + (index, yieldTimeFrame) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + S.of(context).yieldCardTimeframeYield(timeframe: yieldTimeFrame.compactDaysLabel(context)), + style: TextStyle( + color: ZupThemeColors.primaryText.themed(context.brightness), + fontWeight: FontWeight.w600, + fontSize: 15, + ), + ), + const Spacer(), + Text( + widget.yieldPool.timeframedYieldFormatted(yieldTimeFrame), + style: const TextStyle(color: ZupColors.gray, fontSize: 15), + ), + ], + ), + if (index < timeframesExcludingCurrent.length - 1) ...[ + ZupDivider(color: ZupThemeColors.borderOnBackgroundSurface.themed(context.brightness)), + ], + ], + ), + ), + ], + ), + ), + ); } diff --git a/lib/widgets/zup_cached_image.dart b/lib/widgets/zup_cached_image.dart index d135b8f..1afc19c 100644 --- a/lib/widgets/zup_cached_image.dart +++ b/lib/widgets/zup_cached_image.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:skeletonizer/skeletonizer.dart'; import 'package:zup_core/extensions/extensions.dart'; import 'package:zup_ui_kit/zup_ui_kit.dart'; @@ -30,24 +31,26 @@ class ZupCachedImage { // cache not implemented yet because of web issue rendering images from other domains (https://github.com/Baseflow/flutter_cached_network_image/issues/972) child: ClipRRect( borderRadius: BorderRadius.circular(radius ?? 0), - child: Container( - color: backgroundColor, - child: Image.network( - _parseImageUrl(url), - height: height, - width: width, - fit: BoxFit.cover, - errorBuilder: errorWidget, - frameBuilder: (context, child, frame, wasSynchronouslyLoaded) { - if (frame == null) { - return Container( - color: ZupThemeColors.background.themed(context.brightness), - child: placeholder ?? ZupCircularLoadingIndicator(size: height ?? 20), - ); - } - return child; - }, - webHtmlElementStrategy: WebHtmlElementStrategy.fallback, + child: Skeleton.shade( + child: Container( + color: backgroundColor, + child: Image.network( + _parseImageUrl(url), + height: height, + width: width, + fit: BoxFit.cover, + errorBuilder: errorWidget, + frameBuilder: (context, child, frame, wasSynchronouslyLoaded) { + if (frame == null) { + return Container( + color: ZupThemeColors.background.themed(context.brightness), + child: placeholder ?? ZupCircularLoadingIndicator(size: height ?? 20), + ); + } + return child; + }, + webHtmlElementStrategy: WebHtmlElementStrategy.fallback, + ), ), ), ), diff --git a/pubspec.lock b/pubspec.lock index 2d1f5e9..12791b2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -899,7 +899,7 @@ packages: description: path: "." ref: hotfix-vercel - resolved-ref: e650b6ab6c14ecd419761f1d406c05dbe60075b1 + resolved-ref: "436317b637bd30b5bf61a1927a5075f2f43f33c9" url: "https://github.com/RyanHolanda/routefly.git" source: git version: "3.1.3" @@ -1330,7 +1330,7 @@ packages: description: path: "." ref: main - resolved-ref: "91d77f97bb4581db2fbeca62aa78916c0249493c" + resolved-ref: "72ba56fe442b4b41ae9ef98b1a73a7e19e0f3ee8" url: "https://github.com/Zup-Protocol/zup-core.git" source: git version: "0.0.1" @@ -1339,7 +1339,7 @@ packages: description: path: "." ref: main - resolved-ref: "1c79304c781b45416a579af726833bf6be8bc547" + resolved-ref: "7e91f16e6cde62eaabeeccbb646b41803037f211" url: "https://github.com/Zup-Protocol/zup-ui-kit.git" source: git version: "0.0.1" diff --git a/pubspec.yaml b/pubspec.yaml index 18ff89d..40e560b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,12 +55,12 @@ dependencies: async: ^2.13.0 # dependency_overrides: - # zup_core: - # path: ../zup-core - # web3kit: - # path: ../web3kit - # zup_ui_kit: - # path: ../zup-ui-kit +# zup_core: +# path: ../zup-core +# web3kit: +# path: ../web3kit +# zup_ui_kit: +# path: ../zup-ui-kit dev_dependencies: flutter_test: diff --git a/test/app/create/create_page_select_token_stage_test.dart b/test/app/create/create_page_select_token_stage_test.dart index 1e32f86..9dd0e37 100644 --- a/test/app/create/create_page_select_token_stage_test.dart +++ b/test/app/create/create_page_select_token_stage_test.dart @@ -313,7 +313,7 @@ void main() { when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.allNetworks); when(() => tokensRepository.getTokenList(any())).thenAnswer((_) async => TokenListDto(popularTokens: tokens)); when( - () => zupNavigator.navigateToDeposit( + () => zupNavigator.navigateToYields( group0: any(named: "group0"), group1: any(named: "group1"), network: any(named: "network"), @@ -338,7 +338,7 @@ void main() { await tester.pumpAndSettle(); verify( - () => zupNavigator.navigateToDeposit( + () => zupNavigator.navigateToYields( token0: token0Id, token1: token1Id, network: AppNetworks.allNetworks, @@ -357,14 +357,16 @@ void main() { when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.allNetworks); when(() => tokensRepository.getTokenList(any())).thenAnswer((_) async => TokenListDto(tokenGroups: tokenGroups)); when( - () => zupNavigator.navigateToDeposit( + () => zupNavigator.navigateToYields( group0: any(named: "group0"), group1: any(named: "group1"), network: any(named: "network"), token0: any(named: "token0"), token1: any(named: "token1"), ), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async { + return; + }); await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); @@ -382,7 +384,7 @@ void main() { await tester.pumpAndSettle(); verify( - () => zupNavigator.navigateToDeposit( + () => zupNavigator.navigateToYields( token0: null, token1: null, network: AppNetworks.allNetworks, diff --git a/test/app/create/deposit/deposit_cubit_test.dart b/test/app/create/deposit/deposit_cubit_test.dart index 0e8da21..2ec9345 100644 --- a/test/app/create/deposit/deposit_cubit_test.dart +++ b/test/app/create/deposit/deposit_cubit_test.dart @@ -1,14 +1,14 @@ import 'package:clock/clock.dart'; import 'package:fake_async/fake_async.dart'; +import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:web3kit/web3kit.dart'; import 'package:zup_app/abis/uniswap_v3_pool.abi.g.dart'; -import 'package:zup_app/app/app_cubit/app_cubit.dart'; -import 'package:zup_app/app/create/deposit/deposit_cubit.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/deposit_cubit.dart'; import 'package:zup_app/core/cache.dart'; +import 'package:zup_app/core/dtos/deposit_page_arguments_dto.dart'; import 'package:zup_app/core/dtos/deposit_settings_dto.dart'; -import 'package:zup_app/core/dtos/pool_search_filters_dto.dart'; import 'package:zup_app/core/dtos/pool_search_settings_dto.dart'; import 'package:zup_app/core/dtos/yield_dto.dart'; import 'package:zup_app/core/dtos/yields_dto.dart'; @@ -16,7 +16,8 @@ import 'package:zup_app/core/enums/networks.dart'; import 'package:zup_app/core/pool_service.dart'; import 'package:zup_app/core/repositories/yield_repository.dart'; import 'package:zup_app/core/slippage.dart'; -import 'package:zup_app/core/zup_analytics.dart'; +import 'package:zup_app/core/zup_navigator.dart'; +import 'package:zup_app/core/zup_route_params_names.dart'; import 'package:zup_core/zup_singleton_cache.dart'; import '../../../mocks.dart'; @@ -29,8 +30,7 @@ void main() { late UniswapV3PoolImpl uniswapV3PoolImpl; late DepositCubit sut; late Cache cache; - late AppCubit appCubit; - late ZupAnalytics zupAnalytics; + late ZupNavigator navigator; late PoolService poolService; final poolSqrtPriceX96 = BigInt.from(31276567121); @@ -47,12 +47,14 @@ void main() { uniswapV3Pool = UniswapV3PoolMock(); uniswapV3PoolImpl = UniswapV3PoolImplMock(); cache = CacheMock(); - appCubit = AppCubitMock(); - zupAnalytics = ZupAnalyticsMock(); + navigator = ZupNavigatorMock(); - sut = DepositCubit(yieldRepository, zupSingletonCache, wallet, cache, appCubit, zupAnalytics, poolService); + when( + () => navigator.currentPageArguments, + ).thenReturn(const DepositPageArgumentsDto().copyWith(yieldPool: YieldDto.fixture()).toJson()); + + sut = DepositCubit(yieldRepository, zupSingletonCache, wallet, cache, poolService, navigator); - when(() => appCubit.isTestnetMode).thenReturn(false); when(() => cache.blockedProtocolsIds).thenReturn([]); when( () => yieldRepository.getAllNetworksYield( @@ -66,7 +68,6 @@ void main() { ), ).thenAnswer((_) async => YieldsDto.fixture()); - when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.sepolia); when(() => cache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto.fixture()); when( () => uniswapV3Pool.fromRpcProvider( @@ -75,16 +76,6 @@ void main() { ), ).thenReturn(uniswapV3PoolImpl); - when( - () => zupAnalytics.logSearch( - network: any(named: "network"), - token0: any(named: "token0"), - token1: any(named: "token1"), - group0: any(named: "group0"), - group1: any(named: "group1"), - ), - ).thenAnswer((_) async {}); - when(() => uniswapV3PoolImpl.slot0()).thenAnswer( (_) async => ( feeProtocol: BigInt.zero, @@ -113,9 +104,6 @@ void main() { int eventsCounter = 0; const minutesPassed = 3; - final selectedYield = YieldDto.fixture(); - await sut.selectYield(selectedYield); - fakeAsync((async) { sut.setup(); @@ -133,25 +121,24 @@ void main() { test( """And when the minuted passed, but the selected yield is null - it should not execute the task to get the pool sqrtPriceX96""", + it should not execute the task to get the pool sqrtPriceX96""", () async { - BigInt? actualLastEmittedSqrtPriceX96; + when(() => navigator.currentPageArguments).thenReturn({}); + + sut = DepositCubit(yieldRepository, zupSingletonCache, wallet, cache, poolService, navigator); + int eventsCounter = 0; const minutesPassed = 3; - await sut.selectYield(null); - fakeAsync((async) { sut.setup(); sut.poolSqrtPriceX96Stream.listen((event) { - actualLastEmittedSqrtPriceX96 = event; eventsCounter++; }); async.elapse(const Duration(minutes: minutesPassed)); - expect(actualLastEmittedSqrtPriceX96, null); expect(eventsCounter, 0); }); }, @@ -162,8 +149,6 @@ void main() { it should not execute the task to get the pool sqrtPriceX96 and cancel the periodic task""", () async { - final selectedYield = YieldDto.fixture(); - await sut.selectYield(selectedYield); int eventCount = 0; fakeAsync((async) { @@ -184,569 +169,10 @@ void main() { }, ); - test("When calling `getBestPools` it should emit the loading state", () async { - expectLater(sut.stream, emits(const DepositState.loading())); - - await sut.getBestPools(token0AddressOrId: "", token1AddressOrId: "", group0Id: "", group1Id: ""); - }); - - test("When calling `getBestPools` it should call the yield repository to get the best pools", () async { - when( - () => yieldRepository.getSingleNetworkYield( - token0Address: any(named: "token0Address"), - token1Address: any(named: "token1Address"), - searchSettings: any(named: "searchSettings"), - blockedProtocolIds: any(named: "blockedProtocolIds"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - network: any(named: "network"), - ), - ).thenAnswer((_) async => YieldsDto.fixture()); - - const token0Address = "token0Address"; - const token1Address = "token1Address"; - - await sut.getBestPools( - token0AddressOrId: token0Address, - token1AddressOrId: token1Address, - group0Id: null, - group1Id: null, - ); - - verify( - () => yieldRepository.getSingleNetworkYield( - token0Address: token0Address, - token1Address: token1Address, - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - searchSettings: any(named: "searchSettings"), - network: any(named: "network"), - blockedProtocolIds: any(named: "blockedProtocolIds"), - ), - ).called(1); - }); - - test( - """When calling `getBestPools` with group ids, it should call the yield repository to get the best pools - passing the group ids""", - () async { - when( - () => yieldRepository.getSingleNetworkYield( - token0Address: any(named: "token0Address"), - token1Address: any(named: "token1Address"), - searchSettings: any(named: "searchSettings"), - blockedProtocolIds: any(named: "blockedProtocolIds"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - network: any(named: "network"), - ), - ).thenAnswer((_) async => YieldsDto.fixture()); - - const group0Id = "group0"; - const group1Id = "group1"; - - await sut.getBestPools(token0AddressOrId: null, token1AddressOrId: null, group0Id: group0Id, group1Id: group1Id); - - verify( - () => yieldRepository.getSingleNetworkYield( - token0Address: null, - token1Address: null, - group0Id: group0Id, - group1Id: group1Id, - searchSettings: any(named: "searchSettings"), - network: any(named: "network"), - blockedProtocolIds: any(named: "blockedProtocolIds"), - ), - ).called(1); - }, - ); - - test( - """When calling `getBestPools` with group ids and token addresses, it should call the yield repository to get the best pools - passing both the token addresses and the group ids""", - () async { - when( - () => yieldRepository.getSingleNetworkYield( - token0Address: any(named: "token0Address"), - token1Address: any(named: "token1Address"), - searchSettings: any(named: "searchSettings"), - blockedProtocolIds: any(named: "blockedProtocolIds"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - network: any(named: "network"), - ), - ).thenAnswer((_) async => YieldsDto.fixture()); - - const group0Id = "group0"; - const group1Id = "group1"; - - const token0Address = "token0Address"; - const token1Address = "token1Address"; - - await sut.getBestPools( - token0AddressOrId: token0Address, - token1AddressOrId: token1Address, - group0Id: group0Id, - group1Id: group1Id, - ); - - verify( - () => yieldRepository.getSingleNetworkYield( - token0Address: token0Address, - token1Address: token1Address, - group0Id: group0Id, - group1Id: group1Id, - searchSettings: any(named: "searchSettings"), - network: any(named: "network"), - blockedProtocolIds: any(named: "blockedProtocolIds"), - ), - ).called(1); - }, - ); - - test( - """When calling `getBestPools` with group ids, and the network is all networks, - it should call the yield repository to get the best pools for all networks - passing the group ids""", - () async { - when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); - - when( - () => yieldRepository.getAllNetworksYield( - token0InternalId: any(named: "token0InternalId"), - token1InternalId: any(named: "token1InternalId"), - searchSettings: any(named: "searchSettings"), - blockedProtocolIds: any(named: "blockedProtocolIds"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - ), - ).thenAnswer((_) async => YieldsDto.fixture()); - - const group0Id = "group0"; - const group1Id = "group1"; - - await sut.getBestPools(token0AddressOrId: null, token1AddressOrId: null, group0Id: group0Id, group1Id: group1Id); - - verify( - () => yieldRepository.getAllNetworksYield( - token0InternalId: null, - token1InternalId: null, - group0Id: group0Id, - group1Id: group1Id, - searchSettings: any(named: "searchSettings"), - - blockedProtocolIds: any(named: "blockedProtocolIds"), - ), - ).called(1); - }, - ); - - test( - """When calling `getBestPools` with group ids and token addresses at all networks, - it should call the yield repository to get the best pools passing both the token - addresses and the group ids to get pools for all networks""", - () async { - when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); - - when( - () => yieldRepository.getAllNetworksYield( - token0InternalId: any(named: "token0InternalId"), - token1InternalId: any(named: "token1InternalId"), - searchSettings: any(named: "searchSettings"), - blockedProtocolIds: any(named: "blockedProtocolIds"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - ), - ).thenAnswer((_) async => YieldsDto.fixture()); - - const group0Id = "group0"; - const group1Id = "group1"; - const token0Address = "token0Address"; - const token1Address = "token1Address"; - - await sut.getBestPools( - token0AddressOrId: token0Address, - token1AddressOrId: token1Address, - group0Id: group0Id, - group1Id: group1Id, - ); - - verify( - () => yieldRepository.getAllNetworksYield( - token0InternalId: token0Address, - token1InternalId: token1Address, - group0Id: group0Id, - group1Id: group1Id, - searchSettings: any(named: "searchSettings"), - blockedProtocolIds: any(named: "blockedProtocolIds"), - ), - ).called(1); - }, - ); - - test( - """When calling `getBestPools` and receiving an empty list of pools, - it should emit the noYields state with the min liquidity searched returned - from the repository""", - () async { - const minLiquidityUSD = 123; - - when( - () => yieldRepository.getSingleNetworkYield( - blockedProtocolIds: any(named: "blockedProtocolIds"), - token0Address: any(named: "token0Address"), - token1Address: any(named: "token1Address"), - searchSettings: any(named: "searchSettings"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - network: any(named: "network"), - ), - ).thenAnswer( - (_) async => const YieldsDto( - pools: [], - filters: PoolSearchFiltersDto(minTvlUsd: minLiquidityUSD), - ), - ); - - expectLater( - sut.stream, - emitsInOrder([ - const DepositState.loading(), - const DepositState.noYields(filtersApplied: PoolSearchFiltersDto(minTvlUsd: minLiquidityUSD)), - ]), - ); - - await sut.getBestPools(token0AddressOrId: "", token1AddressOrId: "", group0Id: null, group1Id: null); - }, - ); - - test("When calling `getBestPools` and receiving a list of pools it should emit success state", () async { - final pools = YieldsDto.fixture(); - - when( - () => yieldRepository.getSingleNetworkYield( - blockedProtocolIds: any(named: "blockedProtocolIds"), - token0Address: any(named: "token0Address"), - token1Address: any(named: "token1Address"), - searchSettings: any(named: "searchSettings"), - group1Id: any(named: "group1Id"), - group0Id: any(named: "group0Id"), - network: any(named: "network"), - ), - ).thenAnswer((_) async => pools); - - expectLater(sut.stream, emitsInOrder([const DepositState.loading(), DepositState.success(pools)])); - - await sut.getBestPools(token0AddressOrId: "", token1AddressOrId: "", group0Id: null, group1Id: null); - }); - - test("When calling `getBestPools` and receiving an error, it should emit the error state", () async { - when( - () => yieldRepository.getSingleNetworkYield( - blockedProtocolIds: any(named: "blockedProtocolIds"), - token0Address: any(named: "token0Address"), - token1Address: any(named: "token1Address"), - searchSettings: any(named: "searchSettings"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - network: any(named: "network"), - ), - ).thenThrow(Exception()); - - expectLater(sut.stream, emitsInOrder([const DepositState.loading(), const DepositState.error()])); - - await sut.getBestPools(token0AddressOrId: "", token1AddressOrId: "", group0Id: null, group1Id: null); - }); - - test("When calling `selectYield` it should save the selected yield in a variable", () async { - final selectedYield = YieldDto.fixture(); - - await sut.selectYield(selectedYield); - - expect(sut.selectedYield, selectedYield); - }); - - test("When calling `selectYield` it should emit the selected yield in the stream", () async { - final selectedYield = YieldDto.fixture(); - - expectLater(sut.selectedYieldStream, emits(selectedYield)); - - await sut.selectYield(selectedYield); - }); - - test("When calling `selectYield` with a non-empty yield it should get the pool sqrtPriceX96", () async { - final selectedYield = YieldDto.fixture(); - - await sut.selectYield(selectedYield); - - verify(() => poolService.getSqrtPriceX96(selectedYield)).called(1); - }); - - test("When calling `selectYield` but the yield is null, it should not get the pool sqrtPriceX96", () async { - await sut.selectYield(null); - - verifyNever(() => uniswapV3PoolImpl.slot0()); - }); - - test("When calling `getSelectedPoolSqrtPriceX96` it should set the latest pool sqrtPriceX96 to null", () async { - expectLater(sut.latestPoolSqrtPriceX96, null); - - await sut.selectYield(YieldDto.fixture()); - await sut.getSelectedPoolSqrtPriceX96(); - }); - - test("When calling `getSelectedPoolSqrtPriceX96` it should use the pool service to get it", () async { - final yieldDto = YieldDto.fixture(); - await sut.selectYield(yieldDto); - await sut.getSelectedPoolSqrtPriceX96(); - - verify(() => poolService.getSqrtPriceX96(yieldDto)).called(1); - }); - - test( - """" - When calling `getSelectedPoolSqrtPriceX96` for a selected pool, - but when the call to the contract completes, the selected pool - is not the same as the one passed to the call, it shoul re-call - `getSelectedPoolSqrtPriceX96` to get the correct pool sqrtPriceX96""", - () async { - final expectedYieldBsqrtPriceX96 = BigInt.from(326287637265372111); - - const yieldAPoolAddress = "0x3263782637263"; - const yieldBPoolAddress = "0xPoolAddressYieldB"; - - final yieldA = YieldDto.fixture().copyWith(poolAddress: yieldAPoolAddress); - final yieldB = YieldDto.fixture().copyWith(poolAddress: yieldBPoolAddress); - - when(() => poolService.getSqrtPriceX96(any())).thenAnswer((_) async { - when(() => poolService.getSqrtPriceX96(any())).thenAnswer((_) async { - return expectedYieldBsqrtPriceX96; - }); - - await sut.selectYield(yieldB); - - return poolSqrtPriceX96; - }); - - await sut.selectYield(yieldA); // assuming that select yield will call `getSelectedPoolSqrtPriceX96` - - verify( - () => poolService.getSqrtPriceX96(yieldB), - ).called(1); // 2 because of the check in the `getSelectedPoolSqrtPriceX96` that will re-call, and the selection - - expect(sut.latestPoolSqrtPriceX96, expectedYieldBsqrtPriceX96); - }, - ); - - test( - """When calling `selectYield` it should first emit the selected yield latest sqrtPriceX96 from the DTO - (without making a contract call)""", - () { - final sqrtPriceX96 = BigInt.from(27189); - - expectLater(sut.poolSqrtPriceX96Stream, emits(sqrtPriceX96)); - - sut.selectYield(YieldDto.fixture().copyWith(latestSqrtPriceX96: sqrtPriceX96.toString())); - - expect(sut.latestPoolSqrtPriceX96, sqrtPriceX96); - }, - ); - - test( - """When calling 'getSelectedPoolSqrtPriceX96' with `forceRefresh` true, - it should get the sqrtPriceX96 again regardless of the cached value""", - () async { - final selectedYield = YieldDto.fixture(); - when(() => poolService.getSqrtPriceX96(any())).thenAnswer((_) async => poolSqrtPriceX96); - - await sut.selectYield(selectedYield); - await sut.getSelectedPoolSqrtPriceX96(forceRefresh: true); - - verify(() => poolService.getSqrtPriceX96(selectedYield)).called(2); - }, - ); - - test( - """When calling 'getSelectedPoolSqrtPriceX96' multiple times for the same - pool within a minute, it should not get the sqrtPriceX96 again from the - contract. Instead, it should use the cached value""", - () async { - final selectedYield = YieldDto.fixture(); - when(() => poolService.getSqrtPriceX96(any())).thenAnswer((_) async => poolSqrtPriceX96); - - await sut.selectYield(selectedYield); - - await sut.getSelectedPoolSqrtPriceX96(); - await sut.getSelectedPoolSqrtPriceX96(); - await sut.getSelectedPoolSqrtPriceX96(); - await sut.getSelectedPoolSqrtPriceX96(); - await sut.getSelectedPoolSqrtPriceX96(); - - verify(() => poolService.getSqrtPriceX96(selectedYield)).called(1); - }, - ); - - test( - """When calling 'getSelectedPoolSqrtPriceX96' multiple times for different - pools in the same network, it should get the sqrtPriceX96 again for each - one and emit it""", - () async { - final yieldA = YieldDto.fixture().copyWith(poolAddress: "0xPoolAddressYieldA"); - final yieldB = YieldDto.fixture().copyWith(poolAddress: "0xPoolAddressYieldB"); - final yieldC = YieldDto.fixture().copyWith(poolAddress: "0xPoolAddressYieldC"); - - when(() => poolService.getSqrtPriceX96(any())).thenAnswer((_) async => poolSqrtPriceX96); - - await sut.selectYield(yieldA); // assuming that select yield will call `getSelectedPoolSqrtPriceX96` - await sut.selectYield(yieldB); - await sut.selectYield(yieldC); - - verify(() => poolService.getSqrtPriceX96(yieldA)).called(1); - verify(() => poolService.getSqrtPriceX96(yieldB)).called(1); - verify(() => poolService.getSqrtPriceX96(yieldC)).called(1); - }, - ); - - test( - """When calling 'getSelectedPoolSqrtPriceX96' multiple times for different - pools in other networks, it should get the sqrtPriceX96 again for each - one and emit it""", - () async { - final yieldA = YieldDto.fixture().copyWith( - poolAddress: "0xPoolAddressYieldA", - chainId: AppNetworks.mainnet.chainId, - ); - final yieldB = YieldDto.fixture().copyWith( - poolAddress: "0xPoolAddressYieldB", - chainId: AppNetworks.sepolia.chainId, - ); - final yieldC = YieldDto.fixture().copyWith( - poolAddress: "0xPoolAddressYieldC", - chainId: AppNetworks.unichain.chainId, - ); - - when(() => poolService.getSqrtPriceX96(any())).thenAnswer((_) async => poolSqrtPriceX96); - - await sut.selectYield(yieldA); // assuming that select yield will call `getSelectedPoolSqrtPriceX96` - await sut.selectYield(yieldB); - await sut.selectYield(yieldC); - - verify(() => poolService.getSqrtPriceX96(yieldA)).called(1); - verify(() => poolService.getSqrtPriceX96(yieldB)).called(1); - verify(() => poolService.getSqrtPriceX96(yieldC)).called(1); - }, - ); - - test( - """When calling 'getSelectedPoolSqrtPriceX96' multiple times for the same - pool address but in other networks, it should get the sqrtPriceX96 again for each - one and emit it""", - () async { - const poolAddress = "0xPoolAddress"; - final yieldA = YieldDto.fixture().copyWith(poolAddress: poolAddress, chainId: AppNetworks.mainnet.chainId); - final yieldB = YieldDto.fixture().copyWith(poolAddress: poolAddress, chainId: AppNetworks.sepolia.chainId); - final yieldC = YieldDto.fixture().copyWith(poolAddress: poolAddress, chainId: AppNetworks.unichain.chainId); - - when(() => poolService.getSqrtPriceX96(any())).thenAnswer((_) async => poolSqrtPriceX96); - - await sut.selectYield(yieldA); // assuming that select yield will call `getSelectedPoolSqrtPriceX96` - await sut.selectYield(yieldB); - await sut.selectYield(yieldC); - - verify(() => poolService.getSqrtPriceX96(yieldA)).called(1); - verify(() => poolService.getSqrtPriceX96(yieldB)).called(1); - verify(() => poolService.getSqrtPriceX96(yieldC)).called(1); - }, - ); - - test( - """When calling 'getSelectedPoolSqrtPriceX96', it should use the zup singleton cache - with a expiration of half a minute (-1 second to not cause race conditions)""", - () async { - final selectedYield = YieldDto.fixture(); - - zupSingletonCache = ZupSingletonCacheMock(); - sut = DepositCubit(yieldRepository, zupSingletonCache, wallet, cache, appCubit, zupAnalytics, poolService); - when(() => poolService.getSqrtPriceX96(any())).thenAnswer((_) async => poolSqrtPriceX96); - when(() => zupSingletonCache.clear()).thenAnswer((_) async => {}); - when( - () => zupSingletonCache.run( - any(), - key: any(named: "key"), - expiration: any(named: "expiration"), - ignoreCache: any(named: "ignoreCache"), - ), - ).thenAnswer((_) async => poolSqrtPriceX96); - - await sut.selectYield(selectedYield); - await sut.getSelectedPoolSqrtPriceX96(); - - verify( - () => zupSingletonCache.run( - any(), - key: "sqrtPrice-${selectedYield.poolAddress}-${selectedYield.network.name}", - expiration: const Duration(seconds: 30 - 1), - ignoreCache: false, - ), - ).called(1); - }, - ); - - test( - """When calling `getSelectedPoolSqrtPriceX96` - it should emit the pool sqrtPriceX96 got from - the contract, after emitting the one - from the yield call""", - () async { - final newExpectedSqrtPriceX96 = BigInt.from(97866745634534392); - final latestsqrtPriceX96 = BigInt.from(27189); - - when(() => poolService.getSqrtPriceX96(any())).thenAnswer((_) async => newExpectedSqrtPriceX96); - - expectLater(sut.poolSqrtPriceX96Stream, emitsInOrder([latestsqrtPriceX96, newExpectedSqrtPriceX96])); - - await sut.selectYield( - YieldDto.fixture().copyWith(latestSqrtPriceX96: latestsqrtPriceX96.toString()), - ); // assuming that select yield will call `getSelectedPoolSqrtPriceX96` - }, - ); - - test("When calling `getSelectedPoolSqrtPriceX96` it should save the pool sqrtPriceX96 in the cubit", () async { - final expectedPoolSqrtPriceX96 = BigInt.from(97866745634534392); - - when(() => poolService.getSqrtPriceX96(any())).thenAnswer((_) async => expectedPoolSqrtPriceX96); - - await sut.selectYield(YieldDto.fixture()); // assuming that select yield will call `getSelectedPoolSqrtPriceX96` - - expect(sut.latestPoolSqrtPriceX96, expectedPoolSqrtPriceX96); - }); - - test("When calling `getSelectedPoolSqrtPriceX96` it should save the same sqrtPriceX96 as the emitted ", () async { - final newWxpectedPoolSqrtPriceX96 = BigInt.from(97866745634534392); - final yieldSqrtPriceX96 = BigInt.from(27189); - - when(() => poolService.getSqrtPriceX96(any())).thenAnswer((_) async => newWxpectedPoolSqrtPriceX96); - - expectLater(sut.poolSqrtPriceX96Stream, emitsInOrder([yieldSqrtPriceX96, newWxpectedPoolSqrtPriceX96])); - - await sut.selectYield( - YieldDto.fixture().copyWith(latestSqrtPriceX96: yieldSqrtPriceX96.toString()), - ); // assuming that select yield will call `getSelectedPoolSqrtPriceX96` - - expect(sut.latestPoolSqrtPriceX96, newWxpectedPoolSqrtPriceX96); - }); - test("when closing the cubit, it should close the pool sqrtPriceX96 stream", () async { - await sut.selectYield(YieldDto.fixture()); await sut.close(); - expect(() async => await sut.getSelectedPoolSqrtPriceX96(), throwsA(isA())); - }); - - test("When closing the cubit, it should close the selected yield stream", () async { - await sut.close(); - - expect(() async => await sut.selectYield(YieldDto.fixture()), throwsA(isA())); + expect(() async => await sut.getPoolSqrtPriceX96(), throwsA(isA())); }); test("When calling `getWalletTokenAmount` and there's no connected signer, it should return 0", () async { @@ -891,222 +317,298 @@ void main() { expect(actualPoolSearchSettings, expectedPoolSearchSettings); }); + test("When calling 'fetchCurrentPoolInfo' it should emit the loading state", () async { + expectLater(sut.stream, emits(const DepositState.loading())); + + await sut.fetchCurrentPoolInfo(); + }); + test( - """When calling 'getBestPools' with the param 'ignoreMinLiquidity' true, - it should pass the minLiquidityUSD as 0 to the repository""", + """When calling 'fetchCurrentPoolInfo' it should call the yield repository + to fetch pool data, using the params from the url gotten from the navigator""", () async { - when(() => cache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 129816)); + const expectedNetwork = AppNetworks.sepolia; + const expectedPoolAddress = "0x12322"; + const expectedParseWrappedToNative = true; + when(() => navigator.getQueryParam(DepositRouteParamsNames().network)).thenReturn(expectedNetwork.name); + when(() => navigator.getIdFromPath).thenReturn(expectedPoolAddress); when( - () => yieldRepository.getSingleNetworkYield( - blockedProtocolIds: any(named: "blockedProtocolIds"), - token0Address: any(named: "token0Address"), - token1Address: any(named: "token1Address"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - network: any(named: "network"), - searchSettings: any(named: "searchSettings"), - ), - ).thenAnswer((_) async => YieldsDto.fixture()); - - await sut.getBestPools( - token0AddressOrId: "0x", - token1AddressOrId: "0x", - group0Id: null, - group1Id: null, - ignoreMinLiquidity: true, - ); + () => navigator.getQueryParam(DepositRouteParamsNames().parseWrappedToNative), + ).thenReturn(expectedParseWrappedToNative.toString()); + + await sut.fetchCurrentPoolInfo(); verify( - () => yieldRepository.getSingleNetworkYield( - blockedProtocolIds: any(named: "blockedProtocolIds"), - token0Address: any(named: "token0Address"), - token1Address: any(named: "token1Address"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - network: any(named: "network"), - searchSettings: PoolSearchSettingsDto.fixture().copyWith(minLiquidityUSD: 0), + () => yieldRepository.getPoolInfo( + poolAddress: expectedPoolAddress, + poolNetwork: expectedNetwork, + parseWrappedToNative: expectedParseWrappedToNative, ), ).called(1); }, ); test( - """When calling 'getBestPools' with the current network as all networks and the param - 'ignoreMinLiquidity' true, it should pass the minLiquidityUSD as 0 to the repository""", + """When calling 'fetchCurrentPoolInfo', after fetching the pool data from the repository, + it should get the pool sqrt price from the pool service, and emit the new sqrt price gotten""", () async { - when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); - when(() => cache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 129816)); + final pool = YieldDto.fixture(); + final expectedSqrtPriceX96 = BigInt.from(126128912198); + + when(() => poolService.getSqrtPriceX96(pool)).thenAnswer((_) async => expectedSqrtPriceX96); + when(() => navigator.getQueryParam(DepositRouteParamsNames().network)).thenReturn("sepolia"); + when(() => navigator.getIdFromPath).thenReturn("0xbas"); + when(() => navigator.getQueryParam(DepositRouteParamsNames().parseWrappedToNative)).thenReturn(true.toString()); when( - () => yieldRepository.getAllNetworksYield( - blockedProtocolIds: any(named: "blockedProtocolIds"), - token0InternalId: any(named: "token0InternalId"), - token1InternalId: any(named: "token1InternalId"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - searchSettings: any(named: "searchSettings"), + () => yieldRepository.getPoolInfo( + poolAddress: any(named: "poolAddress"), + poolNetwork: any(named: "poolNetwork"), + parseWrappedToNative: any(named: "parseWrappedToNative"), ), - ).thenAnswer((_) async => YieldsDto.fixture()); - - await sut.getBestPools( - token0AddressOrId: "0x", - token1AddressOrId: "0x", - ignoreMinLiquidity: true, - group0Id: null, - group1Id: null, - ); + ).thenAnswer((_) async => pool); - verify( - () => yieldRepository.getAllNetworksYield( - blockedProtocolIds: any(named: "blockedProtocolIds"), - token0InternalId: any(named: "token0InternalId"), - token1InternalId: any(named: "token1InternalId"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - searchSettings: PoolSearchSettingsDto.fixture().copyWith(minLiquidityUSD: 0), + expectLater(sut.poolSqrtPriceX96Stream, emits(expectedSqrtPriceX96)); + + await sut.fetchCurrentPoolInfo(); + + verify(() => poolService.getSqrtPriceX96(pool)).called(1); + }, + ); + + test( + """When calling 'fetchCurrentPoolInfo', after fetching the pool data from the repository, + it should get the pool sqrt price from the pool service, and assign it to the latest + poolSqrtPriceX96 variable""", + () async { + final pool = YieldDto.fixture(); + final expectedSqrtPriceX96 = BigInt.from(1111); + + when(() => poolService.getSqrtPriceX96(pool)).thenAnswer((_) async => expectedSqrtPriceX96); + when(() => navigator.getQueryParam(DepositRouteParamsNames().network)).thenReturn("sepolia"); + when(() => navigator.getIdFromPath).thenReturn("0xbas"); + when(() => navigator.getQueryParam(DepositRouteParamsNames().parseWrappedToNative)).thenReturn(true.toString()); + when( + () => yieldRepository.getPoolInfo( + poolAddress: any(named: "poolAddress"), + poolNetwork: any(named: "poolNetwork"), + parseWrappedToNative: any(named: "parseWrappedToNative"), ), - ).called(1); + ).thenAnswer((_) async => pool); + + await sut.fetchCurrentPoolInfo(); + + expect(sut.latestPoolSqrtPriceX96, expectedSqrtPriceX96); + verify(() => poolService.getSqrtPriceX96(pool)).called(1); }, ); test( - """When calling 'getBestPools' with the param 'ignoreMinLiquidity' false, - it should pass the minLiquidityUSD as the saved value to the repository""", + """When calling 'fetchCurrentPoolInfo', after fetching the pool data from the repository, + it should emit the success state with the pool data""", () async { - const minLiquiditySaved = 129816; + final pool = YieldDto.fixture().copyWith(poolAddress: "pool for testing emit success"); - when(() => cache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: minLiquiditySaved)); + when(() => navigator.getQueryParam(DepositRouteParamsNames().network)).thenReturn("sepolia"); + when(() => navigator.getIdFromPath).thenReturn("0xbas"); + when(() => navigator.getQueryParam(DepositRouteParamsNames().parseWrappedToNative)).thenReturn(true.toString()); when( - () => yieldRepository.getSingleNetworkYield( - blockedProtocolIds: any(named: "blockedProtocolIds"), - token0Address: any(named: "token0Address"), - token1Address: any(named: "token1Address"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - network: any(named: "network"), - searchSettings: any(named: "searchSettings"), + () => yieldRepository.getPoolInfo( + poolAddress: any(named: "poolAddress"), + poolNetwork: any(named: "poolNetwork"), + parseWrappedToNative: any(named: "parseWrappedToNative"), ), - ).thenAnswer((_) async => YieldsDto.fixture()); - - await sut.getBestPools( - token0AddressOrId: "0x", - token1AddressOrId: "0x", - ignoreMinLiquidity: false, - group0Id: null, - group1Id: null, - ); + ).thenAnswer((_) async => pool); + + await sut.fetchCurrentPoolInfo(); + + expect(sut.state, DepositState.success(pool)); + }, + ); + + test( + """When instanciating the cubit, and there is no pool coming from the arguments, + it should fetch the pool info using the params from the url""", + () { + const poolAddress = "0x98abas"; + const poolNetwork = AppNetworks.base; + const parseWrappedToNative = true; + + when(() => navigator.getQueryParam(DepositRouteParamsNames().network)).thenReturn(poolNetwork.name); + when(() => navigator.getIdFromPath).thenReturn(poolAddress); + when( + () => navigator.getQueryParam(DepositRouteParamsNames().parseWrappedToNative), + ).thenReturn(parseWrappedToNative.toString()); + + when(() => navigator.currentPageArguments).thenReturn({}); + + sut = DepositCubit(yieldRepository, zupSingletonCache, wallet, cache, poolService, navigator); verify( - () => yieldRepository.getSingleNetworkYield( - blockedProtocolIds: any(named: "blockedProtocolIds"), - token0Address: any(named: "token0Address"), - token1Address: any(named: "token1Address"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - network: any(named: "network"), - searchSettings: PoolSearchSettingsDto.fixture().copyWith(minLiquidityUSD: minLiquiditySaved), + () => yieldRepository.getPoolInfo( + poolAddress: poolAddress, + poolNetwork: poolNetwork, + parseWrappedToNative: parseWrappedToNative, ), ).called(1); }, ); - test("when calling `getBestPools` it should log the search on analytics with the correct events", () { - const token0Address = "0x123"; - const token1Address = "0x456"; - const network = AppNetworks.sepolia; + test( + """When instaciating the cubit, and there is a pool coming from the arguments, + it should assign it to the yieldpool variable and not fetch the pool info from the repository""", + () { + final pool = YieldDto.fixture().copyWith(poolAddress: "jajajajaja"); - when(() => appCubit.selectedNetwork).thenReturn(network); + when(() => navigator.currentPageArguments).thenReturn(DepositPageArgumentsDto(yieldPool: pool).toJson()); - sut.getBestPools( - token0AddressOrId: token0Address, - token1AddressOrId: token1Address, - group0Id: null, - group1Id: null, - ); + sut = DepositCubit(yieldRepository, zupSingletonCache, wallet, cache, poolService, navigator); - verify( - () => zupAnalytics.logSearch( - token0: token0Address, - token1: token1Address, - network: network.label, - group0: null, - group1: null, - ), - ).called(1); - }); + expect(sut.yieldPool, pool); + + verifyNever( + () => yieldRepository.getPoolInfo( + poolAddress: any(named: "poolAddress"), + poolNetwork: any(named: "poolNetwork"), + parseWrappedToNative: any(named: "parseWrappedToNative"), + ), + ); + }, + ); test( - "When calling `getBestPools` and the network is all networks, it should call the endpoint to search in all networks", + """When instaciating the cubit, and there is a pool coming from the arguments, + it should emit a new pool sqrt price got from the yield dto""", () async { - const token0Address = "0x123"; - const token1Address = "0x456"; + final expectedSqrtPriceX96 = BigInt.from(989998899); + final pool = YieldDto.fixture().copyWith( + poolAddress: "jajajajaja", + latestSqrtPriceX96: expectedSqrtPriceX96.toString(), + ); + + when(() => navigator.currentPageArguments).thenReturn(DepositPageArgumentsDto(yieldPool: pool).toJson()); - when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); + await expectLater( + DepositCubit(yieldRepository, zupSingletonCache, wallet, cache, poolService, navigator).poolSqrtPriceX96Stream, + emits(expectedSqrtPriceX96), + ); + }, + ); - await sut.getBestPools( - token0AddressOrId: token0Address, - token1AddressOrId: token1Address, - group0Id: null, - group1Id: null, + test( + """When instaciating the cubit, and there is a pool coming from the arguments, + it should assign the pool sqrt price to the latest pool sqrt price variable""", + () async { + final expectedSqrtPriceX96 = BigInt.from(5545529927344); + final pool = YieldDto.fixture().copyWith( + poolAddress: "jajajajaja", + latestSqrtPriceX96: expectedSqrtPriceX96.toString(), ); + when(() => navigator.currentPageArguments).thenReturn(DepositPageArgumentsDto(yieldPool: pool).toJson()); + + final cubit = DepositCubit(yieldRepository, zupSingletonCache, wallet, cache, poolService, navigator); + + expect(cubit.latestPoolSqrtPriceX96, expectedSqrtPriceX96); + }, + ); + + test( + """When instaciating the cubit, and there is a pool coming from the arguments, + it should emit the success state passing the pool from the arguments""", + () async { + final pool = YieldDto.fixture().copyWith(poolAddress: "someel cool pool idool"); + + when(() => navigator.currentPageArguments).thenReturn(DepositPageArgumentsDto(yieldPool: pool).toJson()); + + final cubit = DepositCubit(yieldRepository, zupSingletonCache, wallet, cache, poolService, navigator); + + expect(cubit.state, DepositState.success(pool)); + }, + ); + + test( + """When calling 'getPoolSqrtPriceX96' it should use the zup singleton cache to get the pool sqrt price, + with a expiration of 29 seconds""", + () async { + final mockZupSingletonCache = ZupSingletonCacheMock(); + + when( + () => mockZupSingletonCache.run( + any(), + expiration: any(named: "expiration"), + ignoreCache: any(named: "ignoreCache"), + key: any(named: "key"), + ), + ).thenAnswer((_) async => BigInt.from(989998899)); + + final cubit = DepositCubit(yieldRepository, mockZupSingletonCache, wallet, cache, poolService, navigator); + await cubit.getPoolSqrtPriceX96(); + verify( - () => yieldRepository.getAllNetworksYield( - blockedProtocolIds: any(named: "blockedProtocolIds"), - token0InternalId: token0Address, - token1InternalId: token1Address, - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - searchSettings: any(named: "searchSettings"), + () => mockZupSingletonCache.run( + any(), + expiration: 29.seconds, + ignoreCache: any(named: "ignoreCache"), + key: any(named: "key"), ), ).called(1); }, ); test( - """When calling 'getBestPools' and all networks is the selected network, - it should call the repository to get it passing the blocked protocol ids got - from the cache""", + """When calling 'getPoolSqrtPriceX96' with ignore cache true, it should use the zup singleton + cache to get the pool sqrt price, passing ignoreCache to true""", () async { - final cachedBlockedProtocolIds = ["0x1", "0x2", "ababa"]; - when(() => cache.blockedProtocolsIds).thenReturn(cachedBlockedProtocolIds); - when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); + final mockZupSingletonCache = ZupSingletonCacheMock(); - await sut.getBestPools(token0AddressOrId: "0x", token1AddressOrId: "0x", group0Id: null, group1Id: null); + when( + () => mockZupSingletonCache.run( + any(), + expiration: any(named: "expiration"), + ignoreCache: any(named: "ignoreCache"), + key: any(named: "key"), + ), + ).thenAnswer((_) async => BigInt.from(989998899)); + + final cubit = DepositCubit(yieldRepository, mockZupSingletonCache, wallet, cache, poolService, navigator); + await cubit.getPoolSqrtPriceX96(forceRefresh: true); verify( - () => yieldRepository.getAllNetworksYield( - blockedProtocolIds: cachedBlockedProtocolIds, - token0InternalId: any(named: "token0InternalId"), - token1InternalId: any(named: "token1InternalId"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - searchSettings: any(named: "searchSettings"), + () => mockZupSingletonCache.run( + any(), + expiration: any(named: "expiration"), + ignoreCache: true, + key: any(named: "key"), ), ).called(1); }, ); test( - """When calling 'getBestPools' and all networks is not the selected network, - it should call the repository to get it passing the blocked protocol ids got - from the cache""", + """When calling 'getPoolSqrtPriceX96' with ignore cache true, it should use the zup singleton + cache to get the pool sqrt price, passing the correct key""", () async { - final cachedBlockedProtocolIds = ["017628761", "asaas", "ababa"]; - when(() => cache.blockedProtocolsIds).thenReturn(cachedBlockedProtocolIds); - when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.sepolia); + final mockZupSingletonCache = ZupSingletonCacheMock(); + + when( + () => mockZupSingletonCache.run( + any(), + expiration: any(named: "expiration"), + ignoreCache: any(named: "ignoreCache"), + key: any(named: "key"), + ), + ).thenAnswer((_) async => BigInt.from(989998899)); - await sut.getBestPools(token0AddressOrId: "0x", token1AddressOrId: "0x", group0Id: null, group1Id: null); + final cubit = DepositCubit(yieldRepository, mockZupSingletonCache, wallet, cache, poolService, navigator); + await cubit.getPoolSqrtPriceX96(forceRefresh: true); verify( - () => yieldRepository.getSingleNetworkYield( - blockedProtocolIds: cachedBlockedProtocolIds, - network: any(named: "network"), - token0Address: any(named: "token0Address"), - token1Address: any(named: "token1Address"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - searchSettings: any(named: "searchSettings"), + () => mockZupSingletonCache.run( + any(), + expiration: any(named: "expiration"), + ignoreCache: true, + key: "sqrtPrice-${cubit.yieldPool!.poolAddress}-${cubit.yieldPool!.network.name}", ), ).called(1); }, diff --git a/test/app/create/deposit/deposit_page_test.dart b/test/app/create/deposit/deposit_page_test.dart index 4b0dbc6..36992cb 100644 --- a/test/app/create/deposit/deposit_page_test.dart +++ b/test/app/create/deposit/deposit_page_test.dart @@ -1,11 +1,9 @@ import 'dart:async'; -import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:golden_toolkit/golden_toolkit.dart'; -import 'package:lottie/lottie.dart'; import 'package:mocktail/mocktail.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import 'package:web3kit/web3kit.dart'; @@ -14,14 +12,12 @@ import 'package:zup_app/abis/uniswap_permit2.abi.g.dart'; import 'package:zup_app/abis/uniswap_v3_pool.abi.g.dart'; import 'package:zup_app/abis/uniswap_v3_position_manager.abi.g.dart'; import 'package:zup_app/app/app_cubit/app_cubit.dart'; -import 'package:zup_app/app/create/deposit/deposit_cubit.dart'; -import 'package:zup_app/app/create/deposit/deposit_page.dart'; -import 'package:zup_app/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/deposit_cubit.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/deposit_page.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/widgets/preview_deposit_modal/preview_deposit_modal.dart'; import 'package:zup_app/core/cache.dart'; import 'package:zup_app/core/dtos/deposit_settings_dto.dart'; -import 'package:zup_app/core/dtos/pool_search_filters_dto.dart'; import 'package:zup_app/core/dtos/pool_search_settings_dto.dart'; -import 'package:zup_app/core/dtos/protocol_dto.dart'; import 'package:zup_app/core/dtos/token_price_dto.dart'; import 'package:zup_app/core/dtos/yield_dto.dart'; import 'package:zup_app/core/dtos/yields_dto.dart'; @@ -35,7 +31,6 @@ import 'package:zup_app/core/zup_analytics.dart'; import 'package:zup_app/core/zup_links.dart'; import 'package:zup_app/core/zup_navigator.dart'; import 'package:zup_app/core/zup_route_params_names.dart'; -import 'package:zup_app/gen/assets.gen.dart'; import 'package:zup_app/widgets/zup_cached_image.dart'; import 'package:zup_core/zup_core.dart'; @@ -78,32 +73,8 @@ void main() { registerFallbackValue(DepositSettingsDto.fixture()); registerFallbackValue(Duration.zero); - inject.registerFactory( - () => Assets.lotties.click.lottie(animate: false), - instanceName: InjectInstanceNames.lottieClick, - ); - inject.registerFactory( - () => Assets.lotties.empty.lottie(animate: false), - instanceName: InjectInstanceNames.lottieEmpty, - ); - inject.registerFactory( - () => Assets.lotties.numbers.lottie(animate: false), - instanceName: InjectInstanceNames.lottieNumbers, - ); - inject.registerFactory( - () => Assets.lotties.radar.lottie(animate: false), - instanceName: InjectInstanceNames.lottieRadar, - ); - inject.registerFactory( - () => Assets.lotties.matching.lottie(animate: false), - instanceName: InjectInstanceNames.lottieMatching, - ); - inject.registerFactory( - () => Assets.lotties.list.lottie(animate: false), - instanceName: InjectInstanceNames.lottieList, - ); inject.registerFactory( - () => ScrollController(), + () => GoldenConfig.scrollController, instanceName: InjectInstanceNames.appScrollController, ); @@ -129,17 +100,10 @@ void main() { when(() => tokensRepository.getTokenPrice(any(), any())).thenAnswer((_) async => TokenPriceDto.fixture()); when(() => cubit.stream).thenAnswer((_) => const Stream.empty()); when(() => cubit.state).thenAnswer((_) => const DepositState.initial()); - when( - () => cubit.getBestPools( - token0AddressOrId: any(named: "token0AddressOrId"), - token1AddressOrId: any(named: "token1AddressOrId"), - ignoreMinLiquidity: any(named: "ignoreMinLiquidity"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - ), - ).thenAnswer((_) async {}); + when(() => cubit.fetchCurrentPoolInfo()).thenAnswer((_) async { + return; + }); when(() => cache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto.fixture()); - when(() => cubit.selectedYieldStream).thenAnswer((_) => const Stream.empty()); when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.sepolia); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => const Stream.empty()); when(() => cubit.latestPoolSqrtPriceX96).thenAnswer((_) => BigInt.parse("79121973566864535878904")); @@ -148,6 +112,11 @@ void main() { when(() => cubit.saveDepositSettings(any(), any())).thenAnswer((_) async => ()); when(() => cubit.depositSettings).thenReturn(DepositSettingsDto.fixture()); when(() => cubit.poolSearchSettings).thenReturn(PoolSearchSettingsDto.fixture()); + when(() => cubit.yieldPool).thenReturn(YieldDto.fixture()); + when(() => navigator.getQueryParam(DepositRouteParamsNames().network)).thenReturn(AppNetworks.mainnet.name); + when(() => navigator.getQueryParam(DepositRouteParamsNames().timeframe)).thenReturn(YieldTimeFrame.day.name); + when(() => navigator.getQueryParam(DepositRouteParamsNames().parseWrappedToNative)).thenReturn(true.toString()); + when(() => navigator.canBack(any())).thenReturn(false); }); tearDown(() async { @@ -169,37 +138,6 @@ void main() { verify(() => cubit.setup()).called(1); }); - zGoldenTest( - """When initializing the page it should get the list of best pools, - passing the correct token addresses and group ids (from the url)""", - (tester) async { - const token0Address = "0xToken0"; - const token1Address = "0xToken1"; - const group0Id = "0xGroup0"; - const group1Id = "0xGroup1"; - - when(() => navigator.getParam(ZupDepositRouteParamsNames().group0)).thenReturn(group0Id); - when(() => navigator.getParam(ZupDepositRouteParamsNames().group1)).thenReturn(group1Id); - when(() => navigator.getParam(ZupDepositRouteParamsNames().token0)).thenReturn(token0Address); - when(() => navigator.getParam(ZupDepositRouteParamsNames().token1)).thenReturn(token1Address); - - await tester.runAsync(() async { - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - - verify( - () => cubit.getBestPools( - token0AddressOrId: token0Address, - token1AddressOrId: token1Address, - group0Id: group0Id, - group1Id: group1Id, - ignoreMinLiquidity: false, - ), - ).called(1); - }); - }, - ); - zGoldenTest( "When the cubit state is loading it should show the loading state", goldenFileName: "deposit_page_loading", @@ -215,90 +153,6 @@ void main() { }, ); - zGoldenTest( - "When the cubit state is noYields with no min liquidity searched, it should just show the noYields state", - goldenFileName: "deposit_page_no_yields", - (tester) async { - when( - () => cubit.state, - ).thenReturn(const DepositState.noYields(filtersApplied: PoolSearchFiltersDto(minTvlUsd: 0))); - - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - - await tester.pumpAndSettle(); - }, - ); - - zGoldenTest( - """When the cubit state is noYields and the search had a min liquidity set, it should show the noYields state - with a helper text saying it, and a button to search all pools""", - goldenFileName: "deposit_page_no_yields_filtered_by_min_liquidity", - (tester) async { - when( - () => cubit.state, - ).thenReturn(const DepositState.noYields(filtersApplied: PoolSearchFiltersDto(minTvlUsd: 97654))); - - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - - await tester.pumpAndSettle(); - }, - ); - - zGoldenTest( - "When clicking the helper button in the no yields state, to search all pools, it should call the cubit to search all pools", - (tester) async { - when( - () => cubit.getBestPools( - token0AddressOrId: any(named: "token0AddressOrId"), - token1AddressOrId: any(named: "token1AddressOrId"), - ignoreMinLiquidity: any(named: "ignoreMinLiquidity"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - ), - ).thenAnswer((_) async {}); - - when( - () => cubit.state, - ).thenReturn(const DepositState.noYields(filtersApplied: PoolSearchFiltersDto(minTvlUsd: 97654))); - - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - - await tester.tap(find.byKey(const Key("search-all-pools-button"))); - await tester.pumpAndSettle(); - - verify( - () => cubit.getBestPools( - token0AddressOrId: any(named: "token0AddressOrId"), - token1AddressOrId: any(named: "token1AddressOrId"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - ignoreMinLiquidity: true, - ), - ).called(1); - }, - ); - - zGoldenTest( - """When clicking the helper button in the no yields state, - it should navigate back to choose tokens stage""", - (tester) async { - when(() => navigator.navigateToNewPosition()).thenAnswer((_) async {}); - - when(() => cubit.state).thenReturn(const DepositState.noYields(filtersApplied: PoolSearchFiltersDto())); - - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - - await tester.tap(find.byKey(const Key("help-button"))); - await tester.pumpAndSettle(); - - verify(() => navigator.navigateToNewPosition()).called(1); - }, - ); - zGoldenTest("When the cubit state is error, it should show the error state", goldenFileName: "deposit_page_error", ( tester, ) async { @@ -312,18 +166,9 @@ void main() { zGoldenTest( """"When clicking the helper button in the error state, - it should try to get best pools again with the same tokens + it should try to fetch pool data again with the same tokens and groups""", (tester) async { - const token0Address = "0xToken0"; - const token1Address = "0xToken1"; - const group0Id = "0xGroup0"; - const group1Id = "0xGroup1"; - - when(() => navigator.getParam(ZupDepositRouteParamsNames().group0)).thenReturn(group0Id); - when(() => navigator.getParam(ZupDepositRouteParamsNames().group1)).thenReturn(group1Id); - when(() => navigator.getParam(ZupDepositRouteParamsNames().token0)).thenReturn(token0Address); - when(() => navigator.getParam(ZupDepositRouteParamsNames().token1)).thenReturn(token1Address); when(() => cubit.state).thenReturn(const DepositState.error()); await tester.pumpDeviceBuilder(await goldenBuilder()); @@ -332,14 +177,7 @@ void main() { await tester.tap(find.byKey(const Key("help-button"))); await tester.pumpAndSettle(); - verify( - () => cubit.getBestPools( - token0AddressOrId: token0Address, - token1AddressOrId: token1Address, - group0Id: group0Id, - group1Id: group1Id, - ), - ).called(2); // 2 because of the initial call + verify(() => cubit.fetchCurrentPoolInfo()).called(1); }, ); @@ -347,9 +185,9 @@ void main() { tester, ) async { await tester.runAsync(() async { - final yields = YieldsDto.fixture(); + final pool = YieldDto.fixture(); - when(() => cubit.state).thenReturn(DepositState.success(yields)); + when(() => cubit.state).thenReturn(DepositState.success(pool)); await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); @@ -358,184 +196,16 @@ void main() { }); }); - zGoldenTest( - """When the state is success, and the minimum liquidity search config is more than 0, - it should show a text about showing only pools with more than X(min) liquidity, and a button - to search all pools""", - goldenFileName: "deposit_page_success_filtered_by_min_liquidity", - (tester) async { - await tester.runAsync(() async { - final yields = YieldsDto.fixture(); - when(() => cubit.poolSearchSettings).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 97654)); - when(() => cubit.state).thenReturn(DepositState.success(yields)); - - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - - await tester.pumpAndSettle(); - }); - }, - ); - - zGoldenTest( - """When the state is success, and the minimum liquidity search config is 0, - it should not show a text about showing only pools with more than X(min) liquidity""", - goldenFileName: "deposit_page_success_not_filtered_by_min_liquidity", - (tester) async { - await tester.runAsync(() async { - final yields = YieldsDto.fixture(); - when(() => cubit.poolSearchSettings).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 0)); - when(() => cubit.state).thenReturn(DepositState.success(yields)); - - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - - await tester.pumpAndSettle(); - }); - }, - ); - - zGoldenTest( - """When the state is success, and the repository returns that the filter for mininum liquidity - search has zero, but the user has a local filter set, it should show a text and a button to search only pools - with the local filter amount set""", - goldenFileName: "deposit_page_success_filtered_by_min_liquidity_local_filter_set", - (tester) async { - await tester.runAsync(() async { - final yields = YieldsDto.fixture().copyWith( - filters: const PoolSearchFiltersDto(minTvlUsd: 0), - ); // api filter returns 0 - when( - () => cubit.poolSearchSettings, - ).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 2189)); // local filter set - when(() => cubit.state).thenReturn(DepositState.success(yields)); - - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - - await tester.pumpAndSettle(); - }); - }, - ); - - zGoldenTest( - """When clicking in the button to search all pools in the success state - that is with a filter for min liquidity, it should call the cubit to get pools with - the ignore min liquidity flag""", - (tester) async { - await tester.runAsync(() async { - final yields = YieldsDto.fixture().copyWith(filters: const PoolSearchFiltersDto(minTvlUsd: 12675)); - - when(() => cubit.poolSearchSettings).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 12675)); - when(() => cubit.state).thenReturn(DepositState.success(yields)); - when( - () => cubit.getBestPools( - token0AddressOrId: any(named: "token0AddressOrId"), - token1AddressOrId: any(named: "token1AddressOrId"), - ignoreMinLiquidity: any(named: "ignoreMinLiquidity"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - ), - ).thenAnswer((_) async {}); - - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - - await tester.tap(find.byKey(const Key("hide-show-all-pools-button"))); - await tester.pumpAndSettle(); - - verify( - () => cubit.getBestPools( - token0AddressOrId: any(named: "token0AddressOrId"), - token1AddressOrId: any(named: "token1AddressOrId"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - ignoreMinLiquidity: true, - ), - ).called(1); - }); - }, - ); - - zGoldenTest( - """When clicking in the button to search only pools with more than x amount in - the success state that is without a filter for min liquidity, it should call the cubit to get pools with - the min liquidity set to not be ignored""", - (tester) async { - await tester.runAsync(() async { - final yields = YieldsDto.fixture().copyWith( - filters: const PoolSearchFiltersDto(minTvlUsd: 0), - ); // api filter returns 0 - - when( - () => cubit.poolSearchSettings, - ).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 12675)); // local filter set - when(() => cubit.state).thenReturn(DepositState.success(yields)); - when( - () => cubit.getBestPools( - token0AddressOrId: any(named: "token0AddressOrId"), - token1AddressOrId: any(named: "token1AddressOrId"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - ignoreMinLiquidity: any(named: "ignoreMinLiquidity"), - ), - ).thenAnswer((_) async {}); - - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - - await tester.tap(find.byKey(const Key("hide-show-all-pools-button"))); - await tester.pumpAndSettle(); - - verify( - () => cubit.getBestPools( - token0AddressOrId: any(named: "token0AddressOrId"), - token1AddressOrId: any(named: "token1AddressOrId"), - group0Id: any(named: "group0Id"), - group1Id: any(named: "group1Id"), - ignoreMinLiquidity: false, - ), - ).called(2); // two calls, one when the page is loaded and one when the user clicks the button - }); - }, - ); - - zGoldenTest( - "When the state is sucess, and the running device is a mobile, the yield cards should be in a column", - goldenFileName: "deposit_page_success_mobile", - (tester) async { - await tester.runAsync(() async { - final yields = YieldsDto.fixture(); - - when( - () => cubit.depositSettings, - ).thenReturn(const DepositSettingsDto(deadlineMinutes: 10, maxSlippage: DepositSettingsDto.defaultMaxSlippage)); - - when(() => cubit.state).thenReturn(DepositState.success(yields)); - - await tester.pumpDeviceBuilder(await goldenBuilder(isMobile: true)); - - await tester.pumpAndSettle(); - }); - }, - ); - zGoldenTest( "When the running device is mobile, the range section should be adapted to it", goldenFileName: "deposit_page_range_section_mobile", (tester) async { await tester.runAsync(() async { - final selectedYield = YieldDto.fixture(); - final yields = YieldsDto.fixture(); - when( () => cubit.depositSettings, ).thenReturn(const DepositSettingsDto(deadlineMinutes: 10, maxSlippage: DepositSettingsDto.defaultMaxSlippage)); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(yields)); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(YieldDto.fixture())); await tester.pumpDeviceBuilder(await goldenBuilder(isMobile: true)); await tester.pumpAndSettle(); @@ -549,88 +219,51 @@ void main() { zGoldenTest("When clicking back in the success state, it should navigate to the choose tokens page", (tester) async { when(() => navigator.navigateToNewPosition()).thenAnswer((_) async {}); + when(() => cubit.state).thenReturn(DepositState.success(YieldDto.fixture())); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); + await tester.runAsync(() async { + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("back-button"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("back-button"))); + await tester.pumpAndSettle(); + }); verify(() => navigator.navigateToNewPosition()).called(1); }); zGoldenTest( - "When hovering the info icon of the pool time frame section, it should show a tooltip explaining it", - goldenFileName: "deposit_page_timeframe_tooltip", + """When clicking back in the success state, and the navigator returns true to canBack, + it should try to navigate back""", (tester) async { - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - - await tester.hover(find.byKey(const Key("timeframe-tooltip"))); - await tester.pumpAndSettle(); - }, - ); + when(() => navigator.canBack(any())).thenReturn(true); + when(() => navigator.navigateToNewPosition()).thenAnswer((_) async {}); + when(() => cubit.state).thenReturn(DepositState.success(YieldDto.fixture())); - zGoldenTest( - "When the selected yield stream in the cubit emits a yield, it should select the yield", - goldenFileName: "deposit_page_selected_yield_stream", - (tester) async { await tester.runAsync(() async { - final yields = YieldsDto.fixture(); - final selectedYield = yields.poolsSortedBy24hYield.first; - - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(yields)); - await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("back-button"))); await tester.pumpAndSettle(); }); + + verify(() => navigator.back(any())).called(1); }, ); - zGoldenTest("When selecting a yield, it should call select yield in the cubit", (tester) async { - final yields = YieldsDto.fixture(); - when(() => cubit.selectYield(any())).thenAnswer((_) async {}); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - - await tester.pumpDeviceBuilder(await goldenBuilder()); - await tester.pumpAndSettle(); - - await tester.tap(find.byKey(Key("yield-card-${yields.pools.first.poolAddress}")).last); - await tester.pumpAndSettle(); - - verify(() => cubit.selectYield(any())).called(1); - }); - zGoldenTest( - "When selecting a yield, it should scroll down to the range section", - goldenFileName: "deposit_page_select_yield_scroll", + """When the navigator returns true for .canBack, the back button title should be + to navigate to select yield page""", + goldenFileName: "deposit_page_back_button_can_back_true", (tester) async { - await tester.runAsync(() async { - final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; - final selectedYieldStreamController = StreamController.broadcast(); - - when(() => cubit.selectedYieldStream).thenAnswer((_) => selectedYieldStreamController.stream); - when(() => cubit.selectedYield).thenReturn(null); - when(() => cubit.selectYield(any())).thenAnswer((_) async {}); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => navigator.canBack(any())).thenReturn(true); + when(() => navigator.navigateToNewPosition()).thenAnswer((_) async {}); + when(() => cubit.state).thenReturn(DepositState.success(YieldDto.fixture())); + await tester.runAsync(() async { await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); - - selectedYieldStreamController.add(selectedYield); - when(() => cubit.selectedYield).thenReturn(selectedYield); - - await tester.tap(find.byKey(Key("yield-card-${selectedYield.poolAddress}"))); - await tester.pumpAndSettle(); - - verify(() => cubit.selectYield(any())).called(1); }); }, ); @@ -642,9 +275,8 @@ void main() { await tester.runAsync(() async { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); @@ -662,9 +294,8 @@ void main() { await tester.runAsync(() async { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); @@ -685,9 +316,8 @@ void main() { await tester.runAsync(() async { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); @@ -709,9 +339,8 @@ void main() { await tester.runAsync(() async { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(BigInt.from(174072))); await tester.pumpDeviceBuilder(await goldenBuilder()); @@ -728,9 +357,9 @@ void main() { await tester.runAsync(() async { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); + when(() => cubit.yieldPool).thenReturn(selectedYield); when( () => cubit.poolSqrtPriceX96Stream, ).thenAnswer((_) => Stream.value(BigInt.parse("79121973566864535878904"))); @@ -752,9 +381,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -780,9 +408,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsSqrtPriceX96 = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsSqrtPriceX96)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsSqrtPriceX96); @@ -809,9 +436,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -835,9 +461,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -867,9 +492,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -895,9 +519,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -930,9 +553,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -956,9 +578,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -984,9 +605,9 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1014,9 +635,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1040,9 +660,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1067,9 +686,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1093,9 +711,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1120,9 +737,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1146,9 +762,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1172,9 +787,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1200,9 +814,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1231,9 +844,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1261,9 +873,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1290,9 +901,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1324,9 +934,9 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsSqrtPriceX96 = BigInt.parse("5239001873626858491699987"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsSqrtPriceX96)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsSqrtPriceX96); @@ -1349,9 +959,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1376,9 +985,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1406,9 +1014,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1434,9 +1041,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1462,9 +1068,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1492,9 +1097,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1525,9 +1129,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1556,9 +1159,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1593,9 +1195,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1630,9 +1231,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1661,7 +1261,6 @@ void main() { }); }, ); - zGoldenTest( "When inputing the quote token amount, reversing the tokens and then changing the range, the quote token amount should be recalculated", goldenFileName: "deposit_page_input_quote_token_amount_reverse_tokens_and_change_range", @@ -1670,9 +1269,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1710,9 +1308,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1747,9 +1344,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1783,9 +1379,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1826,9 +1421,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1869,9 +1463,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1904,9 +1497,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1936,9 +1528,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -1974,9 +1565,8 @@ void main() { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; final currentPriceAsTick = BigInt.parse("79121973566864535878904"); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -2010,9 +1600,8 @@ void main() { when(() => wallet.signer).thenReturn(null); when(() => wallet.signerStream).thenAnswer((_) => Stream.value(null)); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -2037,9 +1626,8 @@ void main() { when(() => wallet.signer).thenReturn(null); when(() => wallet.signerStream).thenAnswer((_) => Stream.value(null)); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -2068,9 +1656,8 @@ void main() { when( () => cubit.getWalletTokenAmount(any(), network: any(named: "network")), ).thenAnswer((_) => Future.value(0.0)); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -2098,9 +1685,8 @@ void main() { when( () => cubit.getWalletTokenAmount(any(), network: any(named: "network")), ).thenAnswer((_) => Future.value(0.0)); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -2141,9 +1727,8 @@ void main() { network: any(named: "network"), ), ).thenAnswer((_) => Future.value(0)); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -2183,9 +1768,8 @@ void main() { network: any(named: "network"), ), ).thenAnswer((_) => Future.value(0)); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -2228,9 +1812,8 @@ void main() { network: any(named: "network"), ), ).thenAnswer((_) => Future.value(0)); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -2273,11 +1856,11 @@ void main() { network: any(named: "network"), ), ).thenAnswer((_) => Future.value(32576352673)); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); + when(() => cubit.yieldPool).thenReturn(selectedYield); await tester.pumpDeviceBuilder(await goldenBuilder()); await tester.pumpAndSettle(); @@ -2315,9 +1898,8 @@ void main() { network: any(named: "network"), ), ).thenAnswer((_) => Future.value(0)); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -2360,9 +1942,8 @@ void main() { network: any(named: "network"), ), ).thenAnswer((_) => Future.value(3237526)); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); @@ -2410,16 +1991,12 @@ void main() { network: any(named: "network"), ), ).thenAnswer((_) => Future.value(32576352673)); - when(() => cubit.selectYield(any())).thenAnswer((_) => Future.value()); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsTick)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsTick); await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.tap(find.byKey(Key("yield-card-${selectedYield.poolAddress}"))); - await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -2440,9 +2017,8 @@ void main() { await tester.runAsync(() async { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => const Stream.empty()); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(null); @@ -2465,9 +2041,8 @@ void main() { await tester.runAsync(() async { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => const Stream.empty()); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(null); @@ -2491,9 +2066,8 @@ void main() { await tester.runAsync(() async { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(BigInt.from(2131))); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(null); @@ -2517,9 +2091,8 @@ void main() { await tester.runAsync(() async { final selectedYield = YieldsDto.fixture().poolsSortedBy24hYield.first; - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(BigInt.from(2131))); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(null); @@ -2543,19 +2116,21 @@ void main() { const expectedDeadlineCallback = Duration(minutes: 21); when(() => cubit.saveDepositSettings(any(), any())).thenAnswer((_) async => () {}); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(YieldDto.fixture())); - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.tap(find.byKey(const Key("deposit-settings-button"))); - await tester.pumpAndSettle(); + await tester.runAsync(() async { + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.tap(find.byKey(const Key("deposit-settings-button"))); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("slippage-text-field")), expectedSlippageCallback.value.toString()); - await tester.enterText( - find.byKey(const Key("deadline-textfield")), - expectedDeadlineCallback.inMinutes.toString(), - ); - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("slippage-text-field")), expectedSlippageCallback.value.toString()); + await tester.enterText( + find.byKey(const Key("deadline-textfield")), + expectedDeadlineCallback.inMinutes.toString(), + ); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); + }); verify(() => cubit.saveDepositSettings(expectedSlippageCallback, expectedDeadlineCallback)).called(1); }, @@ -2567,15 +2142,16 @@ void main() { goldenFileName: "deposit_page_deposit_settings_button_slippage_title", (tester) async { when(() => cubit.saveDepositSettings(any(), any())).thenAnswer((_) async => () {}); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.tap(find.byKey(const Key("deposit-settings-button"))); - await tester.pumpAndSettle(); + when(() => cubit.state).thenReturn(DepositState.success(YieldDto.fixture())); - await tester.enterText(find.byKey(const Key("slippage-text-field")), "12.3"); - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); + await tester.runAsync(() async { + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.tap(find.byKey(const Key("deposit-settings-button"))); + await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("slippage-text-field")), "12.3"); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); + }); }, ); @@ -2585,15 +2161,17 @@ void main() { goldenFileName: "deposit_page_deposit_settings_button_orange", (tester) async { when(() => cubit.saveDepositSettings(any(), any())).thenAnswer((_) async => () {}); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(YieldDto.fixture())); - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.tap(find.byKey(const Key("deposit-settings-button"))); - await tester.pumpAndSettle(); + await tester.runAsync(() async { + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.tap(find.byKey(const Key("deposit-settings-button"))); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("slippage-text-field")), "1.2"); - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("slippage-text-field")), "1.2"); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); + }); }, ); zGoldenTest( @@ -2603,15 +2181,17 @@ void main() { goldenFileName: "deposit_page_deposit_settings_button_zup_purple_gray", (tester) async { when(() => cubit.saveDepositSettings(any(), any())).thenAnswer((_) async => () {}); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(YieldDto.fixture())); - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.tap(find.byKey(const Key("deposit-settings-button"))); - await tester.pumpAndSettle(); + await tester.runAsync(() async { + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.tap(find.byKey(const Key("deposit-settings-button"))); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("slippage-text-field")), "0.32"); - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("slippage-text-field")), "0.32"); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); + }); }, ); @@ -2622,15 +2202,17 @@ void main() { goldenFileName: "deposit_page_deposit_settings_button_red", (tester) async { when(() => cubit.saveDepositSettings(any(), any())).thenAnswer((_) async => () {}); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(YieldDto.fixture())); - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.tap(find.byKey(const Key("deposit-settings-button"))); - await tester.pumpAndSettle(); + await tester.runAsync(() async { + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.tap(find.byKey(const Key("deposit-settings-button"))); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("slippage-text-field")), "21.2"); - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("slippage-text-field")), "21.2"); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); + }); }, ); @@ -2642,19 +2224,21 @@ void main() { const expectedDeadlineCallback = Duration(minutes: 21); when(() => cubit.saveDepositSettings(any(), any())).thenAnswer((_) async => () {}); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(YieldDto.fixture())); - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.tap(find.byKey(const Key("deposit-settings-button"))); - await tester.pumpAndSettle(); + await tester.runAsync(() async { + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.tap(find.byKey(const Key("deposit-settings-button"))); + await tester.pumpAndSettle(); - await tester.enterText(find.byKey(const Key("slippage-text-field")), expectedSlippageCallback.value.toString()); - await tester.enterText( - find.byKey(const Key("deadline-textfield")), - expectedDeadlineCallback.inMinutes.toString(), - ); - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("slippage-text-field")), expectedSlippageCallback.value.toString()); + await tester.enterText( + find.byKey(const Key("deadline-textfield")), + expectedDeadlineCallback.inMinutes.toString(), + ); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); + }); verify(() => cubit.saveDepositSettings(expectedSlippageCallback, expectedDeadlineCallback)).called(1); }, @@ -2667,12 +2251,14 @@ void main() { (tester) async { const expectedDepositSettings = DepositSettingsDto(maxSlippage: 32.1, deadlineMinutes: 98); when(() => cubit.saveDepositSettings(any(), any())).thenAnswer((_) async => () {}); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(YieldDto.fixture())); when(() => cubit.depositSettings).thenReturn(expectedDepositSettings); - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.tap(find.byKey(const Key("deposit-settings-button"))); - await tester.pumpAndSettle(); + await tester.runAsync(() async { + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.tap(find.byKey(const Key("deposit-settings-button"))); + await tester.pumpAndSettle(); + }); }, ); @@ -2682,32 +2268,34 @@ void main() { goldenFileName: "deposit_page_deposit_settings_dropdown_reopening", (tester) async { when(() => cubit.saveDepositSettings(any(), any())).thenAnswer((_) async => () {}); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.tap(find.byKey(const Key("deposit-settings-button"))); - await tester.pumpAndSettle(); + when(() => cubit.state).thenReturn(DepositState.success(YieldDto.fixture())); + await tester.runAsync(() async { + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.tap(find.byKey(const Key("deposit-settings-button"))); + await tester.pumpAndSettle(); - await tester.enterText( - find.byKey(const Key("slippage-text-field")), - "0.7", - ); // expected slippage to be shown on reopening - await tester.enterText( - find.byKey(const Key("deadline-textfield")), - "76", - ); // expected deadline to be shown on reopening - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); + await tester.enterText( + find.byKey(const Key("slippage-text-field")), + "0.7", + ); // expected slippage to be shown on reopening + await tester.enterText( + find.byKey(const Key("deadline-textfield")), + "76", + ); // expected deadline to be shown on reopening + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); - // Closing the dropdown - await tester.tap(find.byKey(const Key("deposit-settings-button"))); - await tester.pumpAndSettle(); + // Closing the dropdown + await tester.tap(find.byKey(const Key("deposit-settings-button"))); + await tester.pumpAndSettle(); - // // Reopening the dropdown - await tester.tap(find.byKey(const Key("deposit-settings-button"))); - await tester.pumpAndSettle(); + // // Reopening the dropdown + await tester.tap(find.byKey(const Key("deposit-settings-button"))); + await tester.pumpAndSettle(); + }); }, ); + zGoldenTest( """When selecting a slippage and a deadline in the deposit settings dropdown, and then clicking to preview the deposit, it should pass the correct slippage and deadline @@ -2728,19 +2316,14 @@ void main() { () => cubit.getWalletTokenAmount(any(), network: any(named: "network")), ).thenAnswer((_) => Future.value(EthereumConstants.uint256Max.toDouble())); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => Stream.value(currentPriceAsSqrtPriceX96)); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsSqrtPriceX96); - when(() => cubit.selectYield(any())).thenAnswer((_) async => () {}); await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); await tester.pumpAndSettle(); - await tester.tap(find.byKey(Key("yield-card-${selectedYield.poolAddress}"))); - await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("deposit-settings-button"))); await tester.pumpAndSettle(); @@ -2769,22 +2352,10 @@ void main() { }, ); - zGoldenTest( - "When loading the screen, and the network in the path param is different from the selected one, it should switch the network", - (tester) async { - when(() => navigator.getParam(any())).thenAnswer((_) => AppNetworks.scroll.name); - - await tester.runAsync(() async => await tester.pumpDeviceBuilder(await goldenBuilder())); - await tester.pumpAndSettle(); - - verify(() => appCubit.updateAppNetwork(AppNetworks.scroll)).called(1); - }, - ); - zGoldenTest( "When loading the screen, and the network in the path param is equal from the selected one, it should not switch the network", (tester) async { - when(() => navigator.getParam(any())).thenAnswer((_) => appCubit.selectedNetwork.name); + when(() => navigator.getQueryParam(any())).thenAnswer((_) => appCubit.selectedNetwork.name); await tester.runAsync(() async => await tester.pumpDeviceBuilder(await goldenBuilder())); await tester.pumpAndSettle(); @@ -2819,16 +2390,13 @@ void main() { network: any(named: "network"), ), ).thenAnswer((_) => Future.value(32576352673)); - when(() => cubit.selectYield(any())).thenAnswer((_) => Future.value()); - when(() => cubit.selectedYieldStream).thenAnswer((_) => Stream.value(selectedYield)); - when(() => cubit.selectedYield).thenReturn(selectedYield); - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); + + when(() => cubit.state).thenReturn(DepositState.success(selectedYield)); + when(() => cubit.yieldPool).thenReturn(selectedYield); when(() => cubit.poolSqrtPriceX96Stream).thenAnswer((_) => poolSqrtPriceX96StreamController.stream); when(() => cubit.latestPoolSqrtPriceX96).thenReturn(currentPriceAsSqrtPriceX96); await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.tap(find.byKey(Key("yield-card-${selectedYield.poolAddress}"))); - await tester.pumpAndSettle(); await tester.drag(find.byKey(const Key("deposit-settings-button")), const Offset(0, -500)); await tester.pumpAndSettle(); @@ -2841,331 +2409,4 @@ void main() { }); }, ); - - zGoldenTest( - """When scrolling yields in the 24h timeframe, then clicking to switch to the - 7d timeframe, it should reset the scroll position""", - goldenFileName: "deposit_page_reset_scroll_position_on_timeframe_change", - (tester) async { - await tester.runAsync(() async { - final yields = YieldsDto.fixture().copyWith( - pools: List.generate(10, (index) => YieldDto.fixture()) - .mapIndexed( - (index, element) => element.copyWith( - yield24h: index, - yield7d: index * 2, - yield30d: index * 3, - protocol: ProtocolDto.fixture().copyWith(name: "$index"), - ), - ) - .toList(), - ); - - when(() => cubit.state).thenReturn(DepositState.success(yields)); - await tester.pumpDeviceBuilder(await goldenBuilder()); - - await tester.tap(find.byKey(const Key("next-yield-page-button"))); - await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("next-yield-page-button"))); - await tester.pumpAndSettle(); - - await tester.tap(find.byKey(Key("${YieldTimeFrame.week.name}-timeframe-button"))); - await tester.pumpAndSettle(); - }); - }, - ); - - zGoldenTest( - """When hovering the 24h timeframe button in the success state, - it should have an scale and opacity state""", - goldenFileName: "deposit_page_24h_timeframe_hover", - (tester) async { - await tester.runAsync(() async { - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - await tester.pumpDeviceBuilder(await goldenBuilder()); - - await tester.hover(find.byKey(Key("${YieldTimeFrame.day.name}-timeframe-button"))); - await tester.pumpAndSettle(); - }); - }, - ); - - zGoldenTest( - """When hovering the 7d timeframe button in the success state, - it should have an scale and opacity state""", - goldenFileName: "deposit_page_7d_timeframe_hover", - (tester) async { - await tester.runAsync(() async { - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - await tester.pumpDeviceBuilder(await goldenBuilder()); - - await tester.hover(find.byKey(Key("${YieldTimeFrame.week.name}-timeframe-button"))); - await tester.pumpAndSettle(); - }); - }, - ); - - zGoldenTest( - """When hovering the 30d timeframe button in the success state, - it should have an scale and opacity state""", - goldenFileName: "deposit_page_30d_timeframe_hover", - (tester) async { - await tester.runAsync(() async { - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - await tester.pumpDeviceBuilder(await goldenBuilder()); - - await tester.hover(find.byKey(Key("${YieldTimeFrame.month.name}-timeframe-button"))); - await tester.pumpAndSettle(); - }); - }, - ); - - zGoldenTest( - """When hovering the 90d timeframe button in the success state, - it should have an scale and opacity state""", - goldenFileName: "deposit_page_90d_timeframe_hover", - (tester) async { - await tester.runAsync(() async { - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - await tester.pumpDeviceBuilder(await goldenBuilder()); - - await tester.hover(find.byKey(Key("${YieldTimeFrame.threeMonth.name}-timeframe-button"))); - await tester.pumpAndSettle(); - }); - }, - ); - - zGoldenTest( - "When clicking the 7d timeframe in the success state, it should change the yields timeframe", - goldenFileName: "deposit_page_7d_timeframe", - (tester) async { - await tester.runAsync(() async { - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - await tester.pumpDeviceBuilder(await goldenBuilder()); - - await tester.tap(find.byKey(Key("${YieldTimeFrame.week.name}-timeframe-button"))); - await tester.pumpAndSettle(); - }); - }, - ); - - zGoldenTest( - "When clicking the 30d timeframe in the success state, it should change the yields timeframe", - goldenFileName: "deposit_page_30d_timeframe", - (tester) async { - await tester.runAsync(() async { - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - await tester.pumpDeviceBuilder(await goldenBuilder()); - - await tester.tap(find.byKey(Key("${YieldTimeFrame.month.name}-timeframe-button"))); - await tester.pumpAndSettle(); - }); - }, - ); - - zGoldenTest( - "When clicking the 90d timeframe in the success state, it should change the yields timeframe", - goldenFileName: "deposit_page_90d_timeframe", - (tester) async { - await tester.runAsync(() async { - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - await tester.pumpDeviceBuilder(await goldenBuilder()); - - await tester.tap(find.byKey(Key("${YieldTimeFrame.threeMonth.name}-timeframe-button"))); - await tester.pumpAndSettle(); - }); - }, - ); - - zGoldenTest( - """When clicking the 90d timeframe in the success state and then coming back to the 24h timeframe, - it should change the yields timeframe correctly back to 24h""", - goldenFileName: "deposit_page_90d_timeframe_back_to_24h", - (tester) async { - await tester.runAsync(() async { - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto.fixture())); - await tester.pumpDeviceBuilder(await goldenBuilder()); - - await tester.tap(find.byKey(Key("${YieldTimeFrame.threeMonth.name}-timeframe-button"))); - await tester.pumpAndSettle(); - await tester.tap(find.byKey(Key("${YieldTimeFrame.day.name}-timeframe-button"))); - await tester.pumpAndSettle(); - }); - }, - ); - - group( - """When the state is sucess, - all pools should be displayed, with - two per page, sorted descending by - the selected timeframe""", - () { - final poolsList = [ - YieldDto.fixture().copyWith(yield24h: 1, yield7d: 10, yield30d: 100, yield90d: 1000), - YieldDto.fixture().copyWith(yield24h: 2, yield7d: 20, yield30d: 200, yield90d: 2000), - YieldDto.fixture().copyWith(yield24h: 3, yield7d: 30, yield30d: 300, yield90d: 3000), - YieldDto.fixture().copyWith(yield24h: 4, yield7d: 40, yield30d: 400, yield90d: 4000), - YieldDto.fixture().copyWith(yield24h: 5, yield7d: 50, yield30d: 500, yield90d: 5000), - ]; - - zGoldenTest("24h timeframe Page 1", goldenFileName: "deposit_page_pools_page_1_24h_timeframe", (tester) async { - await tester.runAsync(() async { - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto(pools: poolsList))); - await tester.pumpDeviceBuilder(await goldenBuilder()); - - await tester.tap(find.byKey(Key("${YieldTimeFrame.day.name}-timeframe-button"))); - await tester.pumpAndSettle(); - }); - }); - - zGoldenTest("24h timeframe Page 2", goldenFileName: "deposit_page_pools_page_2_24h_timeframe", (tester) async { - await tester.runAsync(() async { - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto(pools: poolsList))); - await tester.pumpDeviceBuilder(await goldenBuilder()); - - await tester.tap(find.byKey(const Key("next-yield-page-button"))); - await tester.pumpAndSettle(); - - await tester.tap(find.byKey(Key("${YieldTimeFrame.day.name}-timeframe-button"))); - await tester.pumpAndSettle(); - }); - }); - - zGoldenTest("7d timeframe Page 1", goldenFileName: "deposit_page_pools_page_1_7d_timeframe", (tester) async { - await tester.runAsync(() async { - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto(pools: poolsList))); - await tester.pumpDeviceBuilder(await goldenBuilder()); - - await tester.tap(find.byKey(Key("${YieldTimeFrame.week.name}-timeframe-button"))); - await tester.pumpAndSettle(); - }); - }); - - zGoldenTest("""7d timeframe Page 2""", goldenFileName: "deposit_page_pools_page_2_7d_timeframe", (tester) async { - await tester.runAsync(() async { - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto(pools: poolsList))); - await tester.pumpDeviceBuilder(await goldenBuilder()); - - await tester.tap(find.byKey(Key("${YieldTimeFrame.week.name}-timeframe-button"))); - await tester.pumpAndSettle(); - - await tester.tap(find.byKey(const Key("next-yield-page-button"))); - await tester.pumpAndSettle(); - }); - }); - - zGoldenTest("30d timeframe Page 1", goldenFileName: "deposit_page_pools_page_1_30d_timeframe", (tester) async { - await tester.runAsync(() async { - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto(pools: poolsList))); - await tester.pumpDeviceBuilder(await goldenBuilder()); - - await tester.tap(find.byKey(Key("${YieldTimeFrame.month.name}-timeframe-button"))); - await tester.pumpAndSettle(); - }); - }); - - zGoldenTest("""30d timeframe Page 2""", goldenFileName: "deposit_page_pools_page_2_30d_timeframe", ( - tester, - ) async { - await tester.runAsync(() async { - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto(pools: poolsList))); - await tester.pumpDeviceBuilder(await goldenBuilder()); - - await tester.tap(find.byKey(Key("${YieldTimeFrame.month.name}-timeframe-button"))); - await tester.pumpAndSettle(); - - await tester.tap(find.byKey(const Key("next-yield-page-button"))); - await tester.pumpAndSettle(); - }); - }); - - zGoldenTest("90d timeframe Page 1", goldenFileName: "deposit_page_pools_page_1_90d_timeframe", (tester) async { - await tester.runAsync(() async { - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto(pools: poolsList))); - await tester.pumpDeviceBuilder(await goldenBuilder()); - - await tester.tap(find.byKey(Key("${YieldTimeFrame.threeMonth.name}-timeframe-button"))); - await tester.pumpAndSettle(); - }); - }); - - zGoldenTest("""90d timeframe Page 2""", goldenFileName: "deposit_page_pools_page_2_90d_timeframe", ( - tester, - ) async { - await tester.runAsync(() async { - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto(pools: poolsList))); - await tester.pumpDeviceBuilder(await goldenBuilder()); - - await tester.tap(find.byKey(Key("${YieldTimeFrame.threeMonth.name}-timeframe-button"))); - await tester.pumpAndSettle(); - - await tester.tap(find.byKey(const Key("next-yield-page-button"))); - await tester.pumpAndSettle(); - }); - }); - }, - ); - - zGoldenTest( - """When the state is sucess, - and the pools amount are odd, - the last page should show only - the last pool""", - goldenFileName: "deposit_page_odd_pools_last_page", - (tester) async { - final poolsList = [ - YieldDto.fixture().copyWith(yield24h: 1), - YieldDto.fixture().copyWith(yield24h: 2), - YieldDto.fixture().copyWith(yield24h: 3), - YieldDto.fixture().copyWith(yield24h: 4), - YieldDto.fixture().copyWith(yield24h: 5), - ]; - - await tester.runAsync(() async { - when(() => cubit.state).thenReturn(DepositState.success(YieldsDto(pools: poolsList))); - await tester.pumpDeviceBuilder(await goldenBuilder()); - - await tester.tap(find.byKey(const Key("next-yield-page-button"))); - await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("next-yield-page-button"))); - await tester.pumpAndSettle(); - }); - }, - ); - - zGoldenTest( - "When hovering the page indicator for the pools pages, it should scale", - goldenFileName: "deposit_page_pools_page_indicator_hover", - (tester) async { - await tester.runAsync(() async { - when( - () => cubit.state, - ).thenReturn(DepositState.success(YieldsDto(pools: List.generate(10, (index) => YieldDto.fixture())))); - - await tester.pumpDeviceBuilder(await goldenBuilder()); - - await tester.hover(find.byKey(const Key("yield-page-indicator-3"))); - await tester.pumpAndSettle(); - }); - }, - ); - - zGoldenTest( - "When clicking the page indicator for the pools pages, it should navigate to the page", - goldenFileName: "deposit_page_pools_page_indicator_click", - (tester) async { - await tester.runAsync(() async { - when(() => cubit.state).thenReturn( - DepositState.success( - YieldsDto(pools: List.generate(10, (index) => YieldDto.fixture().copyWith(yield24h: index + 1))), - ), - ); - - await tester.pumpDeviceBuilder(await goldenBuilder()); - - await tester.tap(find.byKey(const Key("yield-page-indicator-3"))); - await tester.pumpAndSettle(); - }); - }, - ); } diff --git a/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range.png b/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range.png index ed26b88..e6a2a99 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range.png and b/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range_reverse_tokens.png b/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range_reverse_tokens.png index 8eb488b..a410378 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range_reverse_tokens.png and b/test/app/create/deposit/goldens/deposit_page_5_percent_set_to_full_range_reverse_tokens.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_back_button_can_back_true.png b/test/app/create/deposit/goldens/deposit_page_back_button_can_back_true.png new file mode 100644 index 0000000..8d4b834 Binary files /dev/null and b/test/app/create/deposit/goldens/deposit_page_back_button_can_back_true.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_base_token_input_enabled_after_loading.png b/test/app/create/deposit/goldens/deposit_page_base_token_input_enabled_after_loading.png index 9c2e7ae..d551d10 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_base_token_input_enabled_after_loading.png and b/test/app/create/deposit/goldens/deposit_page_base_token_input_enabled_after_loading.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_base_token_input_loading.png b/test/app/create/deposit/goldens/deposit_page_base_token_input_loading.png index 686d6c7..0d33e38 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_base_token_input_loading.png and b/test/app/create/deposit/goldens/deposit_page_base_token_input_loading.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_calculate_price.png b/test/app/create/deposit/goldens/deposit_page_calculate_price.png index db9a3f1..5367564 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_calculate_price.png and b/test/app/create/deposit/goldens/deposit_page_calculate_price.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_calculate_price_reversed.png b/test/app/create/deposit/goldens/deposit_page_calculate_price_reversed.png index 8eb488b..a410378 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_calculate_price_reversed.png and b/test/app/create/deposit/goldens/deposit_page_calculate_price_reversed.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_orange.png b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_orange.png index da9dd04..0a6f85c 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_orange.png and b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_orange.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_red.png b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_red.png index fe64f51..c7557fe 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_red.png and b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_red.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_slippage_title.png b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_slippage_title.png index 6fff08a..90238a8 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_slippage_title.png and b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_slippage_title.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_zup_purple_gray.png b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_zup_purple_gray.png index 32498a4..35fed96 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_zup_purple_gray.png and b/test/app/create/deposit/goldens/deposit_page_deposit_settings_button_zup_purple_gray.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown.png b/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown.png index 72538b0..53f8a60 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown.png and b/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown_reopening.png b/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown_reopening.png index d19239f..555b0b6 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown_reopening.png and b/test/app/create/deposit/goldens/deposit_page_deposit_settings_dropdown_reopening.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_enough_balance_deposit_button.png b/test/app/create/deposit/goldens/deposit_page_enough_balance_deposit_button.png index 409a4fe..0e5fe6c 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_enough_balance_deposit_button.png and b/test/app/create/deposit/goldens/deposit_page_enough_balance_deposit_button.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_error.png b/test/app/create/deposit/goldens/deposit_page_error.png index f9c9fbe..ca8acf2 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_error.png and b/test/app/create/deposit/goldens/deposit_page_error.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount.png index 0c7ad01..39ed0dd 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_change_range.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_change_range.png index a25ccd7..de8f9c9 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_change_range.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_change_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse.png index 2384cb9..6c3404b 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse_back.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse_back.png index 66543ab..54eb1d4 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse_back.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_and_reverse_back.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reverse_tokens_and_change_range.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reverse_tokens_and_change_range.png index 6bf1a0f..fd965f0 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reverse_tokens_and_change_range.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reverse_tokens_and_change_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reversed.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reversed.png index b35c4b0..df7bc8d 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reversed.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_reversed.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_reverse_tokens_then_set_max_price_out_of_range.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_reverse_tokens_then_set_max_price_out_of_range.png index 9bddecf..c6ff04f 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_reverse_tokens_then_set_max_price_out_of_range.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_reverse_tokens_then_set_max_price_out_of_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_set_max_price_out_of_range.png b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_set_max_price_out_of_range.png index 68d3c28..c00e6df 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_set_max_price_out_of_range.png and b/test/app/create/deposit/goldens/deposit_page_input_base_token_amount_then_set_max_price_out_of_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount.png index 158d6c6..68b6399 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_change_range.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_change_range.png index 88107a3..ce2631c 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_change_range.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_change_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse.png index a4e47c2..dcc0f21 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse_back.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse_back.png index 825ff2c..6b9250d 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse_back.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_and_reverse_back.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reverse_tokens_and_change_range.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reverse_tokens_and_change_range.png index 51c63b1..5cc2c6b 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reverse_tokens_and_change_range.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reverse_tokens_and_change_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reversed.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reversed.png index e0313fe..3e1d11c 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reversed.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_reversed.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_reverse_tokens_then_set_min_price_out_of_range.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_reverse_tokens_then_set_min_price_out_of_range.png index 5c6009d..f34659a 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_reverse_tokens_then_set_min_price_out_of_range.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_reverse_tokens_then_set_min_price_out_of_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_set_min_price_out_of_range.png b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_set_min_price_out_of_range.png index 19d8e57..bbe1e3e 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_set_min_price_out_of_range.png and b/test/app/create/deposit/goldens/deposit_page_input_quote_token_amount_then_set_min_price_out_of_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_range_then_input_base_token_amount.png b/test/app/create/deposit/goldens/deposit_page_input_range_then_input_base_token_amount.png index eb1171a..4795d19 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_range_then_input_base_token_amount.png and b/test/app/create/deposit/goldens/deposit_page_input_range_then_input_base_token_amount.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_range_then_input_quote_token_amount.png b/test/app/create/deposit/goldens/deposit_page_input_range_then_input_quote_token_amount.png index 1977775..dab7049 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_range_then_input_quote_token_amount.png and b/test/app/create/deposit/goldens/deposit_page_input_range_then_input_quote_token_amount.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_base_token_amount.png b/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_base_token_amount.png index 51039ef..c439d98 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_base_token_amount.png and b/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_base_token_amount.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_quote_token_amount.png b/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_quote_token_amount.png index 94b9881..313612e 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_quote_token_amount.png and b/test/app/create/deposit/goldens/deposit_page_input_range_then_reverse_tokens_then_input_quote_token_amount.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_invalid_range_deposit_section.png b/test/app/create/deposit/goldens/deposit_page_invalid_range_deposit_section.png index 1ccf837..9eb682a 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_invalid_range_deposit_section.png and b/test/app/create/deposit/goldens/deposit_page_invalid_range_deposit_section.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_loading.png b/test/app/create/deposit/goldens/deposit_page_loading.png index bbd24d1..5d8107a 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_loading.png and b/test/app/create/deposit/goldens/deposit_page_loading.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_max_price_less_than_min_price.png b/test/app/create/deposit/goldens/deposit_page_max_price_less_than_min_price.png index 98a6938..00af807 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_max_price_less_than_min_price.png and b/test/app/create/deposit/goldens/deposit_page_max_price_less_than_min_price.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_max_price_out_of_range.png b/test/app/create/deposit/goldens/deposit_page_max_price_out_of_range.png index c4b41b0..8c6dcb9 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_max_price_out_of_range.png and b/test/app/create/deposit/goldens/deposit_page_max_price_out_of_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_max_price_set_to_full_range.png b/test/app/create/deposit/goldens/deposit_page_max_price_set_to_full_range.png index ed26b88..22be73f 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_max_price_set_to_full_range.png and b/test/app/create/deposit/goldens/deposit_page_max_price_set_to_full_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_max_price_set_to_infinity.png b/test/app/create/deposit/goldens/deposit_page_max_price_set_to_infinity.png index f2375ba..a68718b 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_max_price_set_to_infinity.png and b/test/app/create/deposit/goldens/deposit_page_max_price_set_to_infinity.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_max_range_out_of_range_deposit_button.png b/test/app/create/deposit/goldens/deposit_page_max_range_out_of_range_deposit_button.png index f1e91a6..cb4871f 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_max_range_out_of_range_deposit_button.png and b/test/app/create/deposit/goldens/deposit_page_max_range_out_of_range_deposit_button.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_min_and_max_price_set_to_full_range.png b/test/app/create/deposit/goldens/deposit_page_min_and_max_price_set_to_full_range.png index ed26b88..22be73f 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_min_and_max_price_set_to_full_range.png and b/test/app/create/deposit/goldens/deposit_page_min_and_max_price_set_to_full_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range.png b/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range.png index d63b112..36a8837 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range.png and b/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed.png b/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed.png index 0f1937a..fde882a 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed.png and b/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed_in_range.png b/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed_in_range.png index 3ca7254..9614783 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed_in_range.png and b/test/app/create/deposit/goldens/deposit_page_min_price_out_of_range_reversed_in_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_min_price_set_to_full_range.png b/test/app/create/deposit/goldens/deposit_page_min_price_set_to_full_range.png index ed26b88..22be73f 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_min_price_set_to_full_range.png and b/test/app/create/deposit/goldens/deposit_page_min_price_set_to_full_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_min_range_out_of_range_deposit_button.png b/test/app/create/deposit/goldens/deposit_page_min_range_out_of_range_deposit_button.png index a566956..19dcf1e 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_min_range_out_of_range_deposit_button.png and b/test/app/create/deposit/goldens/deposit_page_min_range_out_of_range_deposit_button.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_no_amount_deposit_button.png b/test/app/create/deposit/goldens/deposit_page_no_amount_deposit_button.png index 8d59cb1..b285113 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_no_amount_deposit_button.png and b/test/app/create/deposit/goldens/deposit_page_no_amount_deposit_button.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_not_connected.png b/test/app/create/deposit/goldens/deposit_page_not_connected.png index 9c9d9b3..5a3d219 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_not_connected.png and b/test/app/create/deposit/goldens/deposit_page_not_connected.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_not_connected_deposit_button_click.png b/test/app/create/deposit/goldens/deposit_page_not_connected_deposit_button_click.png index b956d66..7c210bc 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_not_connected_deposit_button_click.png and b/test/app/create/deposit/goldens/deposit_page_not_connected_deposit_button_click.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button.png b/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button.png index ca1104f..8973867 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button.png and b/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button_after_connecting.png b/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button_after_connecting.png index e5fa9ba..3a99794 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button_after_connecting.png and b/test/app/create/deposit/goldens/deposit_page_not_enough_base_token_balance_deposit_button_after_connecting.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button.png b/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button.png index 57e99aa..50cbc81 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button.png and b/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button_after_connecting.png b/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button_after_connecting.png index e1f73be..50cbc81 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button_after_connecting.png and b/test/app/create/deposit/goldens/deposit_page_not_enough_quote_token_balance_deposit_button_after_connecting.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_pool_tick_update_deposit_amount.png b/test/app/create/deposit/goldens/deposit_page_pool_tick_update_deposit_amount.png index 26abc6c..3db3179 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_pool_tick_update_deposit_amount.png and b/test/app/create/deposit/goldens/deposit_page_pool_tick_update_deposit_amount.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_preview_modal.png b/test/app/create/deposit/goldens/deposit_page_preview_modal.png index 1771428..b0d44c8 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_preview_modal.png and b/test/app/create/deposit/goldens/deposit_page_preview_modal.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_quote_token_input_enabled_after_loading.png b/test/app/create/deposit/goldens/deposit_page_quote_token_input_enabled_after_loading.png index e075db8..0c8d51c 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_quote_token_input_enabled_after_loading.png and b/test/app/create/deposit/goldens/deposit_page_quote_token_input_enabled_after_loading.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_quote_token_input_loading.png b/test/app/create/deposit/goldens/deposit_page_quote_token_input_loading.png index 101666d..aeb152f 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_quote_token_input_loading.png and b/test/app/create/deposit/goldens/deposit_page_quote_token_input_loading.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_range_section_mobile.png b/test/app/create/deposit/goldens/deposit_page_range_section_mobile.png index 2fcdac9..f8df159 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_range_section_mobile.png and b/test/app/create/deposit/goldens/deposit_page_range_section_mobile.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_reverse_tokens.png b/test/app/create/deposit/goldens/deposit_page_reverse_tokens.png index 8eb488b..a410378 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_reverse_tokens.png and b/test/app/create/deposit/goldens/deposit_page_reverse_tokens.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_reverse_tokens_back.png b/test/app/create/deposit/goldens/deposit_page_reverse_tokens_back.png index ed26b88..e6a2a99 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_reverse_tokens_back.png and b/test/app/create/deposit/goldens/deposit_page_reverse_tokens_back.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_20_percent_range.png b/test/app/create/deposit/goldens/deposit_page_set_20_percent_range.png index c99ed9f..28179dd 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_20_percent_range.png and b/test/app/create/deposit/goldens/deposit_page_set_20_percent_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_20_percent_range_reverse_tokens.png b/test/app/create/deposit/goldens/deposit_page_set_20_percent_range_reverse_tokens.png index a0f206c..0f8af3e 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_20_percent_range_reverse_tokens.png and b/test/app/create/deposit/goldens/deposit_page_set_20_percent_range_reverse_tokens.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_50_percent_range.png b/test/app/create/deposit/goldens/deposit_page_set_50_percent_range.png index 9368a08..121bf12 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_50_percent_range.png and b/test/app/create/deposit/goldens/deposit_page_set_50_percent_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_50_percent_range_reverse_tokens.png b/test/app/create/deposit/goldens/deposit_page_set_50_percent_range_reverse_tokens.png index 7255c44..b9cf55f 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_50_percent_range_reverse_tokens.png and b/test/app/create/deposit/goldens/deposit_page_set_50_percent_range_reverse_tokens.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_5_percent_range.png b/test/app/create/deposit/goldens/deposit_page_set_5_percent_range.png index dea24d6..0e06985 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_5_percent_range.png and b/test/app/create/deposit/goldens/deposit_page_set_5_percent_range.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_5_percent_range_reverse_tokens.png b/test/app/create/deposit/goldens/deposit_page_set_5_percent_range_reverse_tokens.png index 6756442..2bedefc 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_5_percent_range_reverse_tokens.png and b/test/app/create/deposit/goldens/deposit_page_set_5_percent_range_reverse_tokens.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_max_price_reverse_tokens.png b/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_max_price_reverse_tokens.png index e3ef98e..434612d 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_max_price_reverse_tokens.png and b/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_max_price_reverse_tokens.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_min_price_reverse_tokens.png b/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_min_price_reverse_tokens.png index 32a2e03..9fff2d3 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_min_price_reverse_tokens.png and b/test/app/create/deposit/goldens/deposit_page_set_percentage_range_then_type_min_price_reverse_tokens.png differ diff --git a/test/app/create/deposit/goldens/deposit_page_success.png b/test/app/create/deposit/goldens/deposit_page_success.png index 125d551..2ff0cc5 100644 Binary files a/test/app/create/deposit/goldens/deposit_page_success.png and b/test/app/create/deposit/goldens/deposit_page_success.png differ diff --git a/test/app/create/deposit/widgets/deposit_settings_dropdown_child_test.dart b/test/app/create/deposit/widgets/deposit_settings_dropdown_child_test.dart index aa7d539..e3cb4a3 100644 --- a/test/app/create/deposit/widgets/deposit_settings_dropdown_child_test.dart +++ b/test/app/create/deposit/widgets/deposit_settings_dropdown_child_test.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:golden_toolkit/golden_toolkit.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; -import 'package:zup_app/app/create/deposit/widgets/deposit_settings_dropdown_child.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/widgets/deposit_settings_dropdown_child.dart'; import 'package:zup_app/core/injections.dart'; import 'package:zup_app/core/slippage.dart'; import 'package:zup_core/zup_core.dart'; @@ -18,40 +18,40 @@ void main() { tearDown(() => inject.reset()); - Future goldenBuilder( - {Slippage selectedSlippage = Slippage.onePercent, - Duration selectedDeadline = const Duration(minutes: 30), - void Function(Slippage slippage, Duration deadline)? onSettingsChanged}) async => - await goldenDeviceBuilder( - SizedBox( - height: 400, - width: 500, - child: Center( - child: Builder(builder: (context) { - return SizedBox( - height: 400, - width: 500, - child: Center( - child: DepositSettingsDropdownChild( - context, - selectedSlippage: selectedSlippage, - selectedDeadline: selectedDeadline, - onSettingsChanged: onSettingsChanged ?? (slippage, deadline) {}, - ), + Future goldenBuilder({ + Slippage selectedSlippage = Slippage.onePercent, + Duration selectedDeadline = const Duration(minutes: 30), + void Function(Slippage slippage, Duration deadline)? onSettingsChanged, + }) async => await goldenDeviceBuilder( + SizedBox( + height: 400, + width: 500, + child: Center( + child: Builder( + builder: (context) { + return SizedBox( + height: 400, + width: 500, + child: Center( + child: DepositSettingsDropdownChild( + context, + selectedSlippage: selectedSlippage, + selectedDeadline: selectedDeadline, + onSettingsChanged: onSettingsChanged ?? (slippage, deadline) {}, ), - ); - }), - ), + ), + ); + }, ), - ); + ), + ), + ); zGoldenTest( "When initialiazing the widget with a custom slippage, the text field should be filled with the custom value", goldenFileName: "deposit_settings_dropdown_child_custom_slippage", (tester) async { - await tester.pumpDeviceBuilder( - await goldenBuilder(selectedSlippage: Slippage.custom(762)), - ); + await tester.pumpDeviceBuilder(await goldenBuilder(selectedSlippage: Slippage.custom(762))); }, ); @@ -59,9 +59,7 @@ void main() { "When initialiazing the widget, the deadline text field should be filled with the passed deadline", goldenFileName: "deposit_settings_dropdown_child_deadline", (tester) async { - await tester.pumpDeviceBuilder( - await goldenBuilder(selectedDeadline: const Duration(minutes: 1200)), - ); + await tester.pumpDeviceBuilder(await goldenBuilder(selectedDeadline: const Duration(minutes: 1200))); }, ); @@ -81,12 +79,14 @@ void main() { goldenFileName: "deposit_settings_dropdown_child_zero_point_one_percent_slippage", (tester) async { Slippage? expectedSlippage; - await tester.pumpDeviceBuilder(await goldenBuilder( - selectedSlippage: Slippage.halfPercent, - onSettingsChanged: (slippage, deadline) { - expectedSlippage = slippage; - }, - )); + await tester.pumpDeviceBuilder( + await goldenBuilder( + selectedSlippage: Slippage.halfPercent, + onSettingsChanged: (slippage, deadline) { + expectedSlippage = slippage; + }, + ), + ); await tester.tap(find.byKey(const Key("zero-point-one-percent-slippage"))); await tester.pumpAndSettle(); @@ -101,12 +101,14 @@ void main() { (tester) async { Slippage? expectedSlippage; - await tester.pumpDeviceBuilder(await goldenBuilder( - selectedSlippage: Slippage.halfPercent, - onSettingsChanged: (slippage, deadline) { - expectedSlippage = slippage; - }, - )); + await tester.pumpDeviceBuilder( + await goldenBuilder( + selectedSlippage: Slippage.halfPercent, + onSettingsChanged: (slippage, deadline) { + expectedSlippage = slippage; + }, + ), + ); await tester.tap(find.byKey(const Key("one-percent-slippage"))); await tester.pumpAndSettle(); @@ -136,41 +138,45 @@ void main() { }, ); - zGoldenTest( - "When typing in the text field, it should not callback with the slippage", - (tester) async { - Slippage? expectedSlippage; + zGoldenTest("When typing in the text field, it should not callback with the slippage", (tester) async { + Slippage? expectedSlippage; - await tester.pumpDeviceBuilder(await goldenBuilder( + await tester.pumpDeviceBuilder( + await goldenBuilder( selectedSlippage: Slippage.halfPercent, onSettingsChanged: (slippage, deadline) => expectedSlippage = slippage, - )); + ), + ); - await tester.enterText(find.byKey(const Key("slippage-text-field")), "123"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("slippage-text-field")), "123"); + await tester.pumpAndSettle(); - expect(expectedSlippage, null); - }, - ); + expect(expectedSlippage, null); + }); - zGoldenTest("""When typing a value greater than 50% in the slippage field and unfocusing it, + zGoldenTest( + """When typing a value greater than 50% in the slippage field and unfocusing it, it should reajust it to 50%, and callback with the new value (50%)""", - goldenFileName: "deposit_settings_dropdown_child_custom_slippage_greater_than_50_adjust", (tester) async { - Slippage? expectedSlippage; + goldenFileName: "deposit_settings_dropdown_child_custom_slippage_greater_than_50_adjust", + (tester) async { + Slippage? expectedSlippage; - await tester.pumpDeviceBuilder(await goldenBuilder( - selectedSlippage: Slippage.halfPercent, - onSettingsChanged: (slippage, deadline) => expectedSlippage = slippage, - )); + await tester.pumpDeviceBuilder( + await goldenBuilder( + selectedSlippage: Slippage.halfPercent, + onSettingsChanged: (slippage, deadline) => expectedSlippage = slippage, + ), + ); - await tester.enterText(find.byKey(const Key("slippage-text-field")), "76"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("slippage-text-field")), "76"); + await tester.pumpAndSettle(); - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); - expect(expectedSlippage, equals(Slippage.custom(50))); - }); + expect(expectedSlippage, equals(Slippage.custom(50))); + }, + ); zGoldenTest( "The slippage textfield should not allow caracteres other than numbers or a dot for decimal", @@ -183,76 +189,81 @@ void main() { }, ); - zGoldenTest("""When typing a value lower than 50% in the slippage + zGoldenTest( + """When typing a value lower than 50% in the slippage field and unfocusing it, it should callback with the new value typed""", - goldenFileName: "deposit_settings_dropdown_child_custom_slippage_lower_than_50", (tester) async { - Slippage? expectedSlippage; + goldenFileName: "deposit_settings_dropdown_child_custom_slippage_lower_than_50", + (tester) async { + Slippage? expectedSlippage; - await tester.pumpDeviceBuilder(await goldenBuilder( - selectedSlippage: Slippage.halfPercent, - onSettingsChanged: (slippage, deadline) => expectedSlippage = slippage, - )); + await tester.pumpDeviceBuilder( + await goldenBuilder( + selectedSlippage: Slippage.halfPercent, + onSettingsChanged: (slippage, deadline) => expectedSlippage = slippage, + ), + ); - await tester.enterText(find.byKey(const Key("slippage-text-field")), "3"); - await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key("slippage-text-field")), "3"); + await tester.pumpAndSettle(); - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); - expect(expectedSlippage, equals(Slippage.custom(3.0))); - }); + expect(expectedSlippage, equals(Slippage.custom(3.0))); + }, + ); - zGoldenTest("""When typing a value greater than 50% in the textfield, and not unfocusing it, + zGoldenTest( + """When typing a value greater than 50% in the textfield, and not unfocusing it, it should be in error state (with the border red)""", - goldenFileName: "deposit_settings_dropdown_child_custom_slippage_greater_than_50_error", (tester) async { - await tester.pumpDeviceBuilder(await goldenBuilder( - selectedSlippage: Slippage.halfPercent, - )); - - await tester.enterText(find.byKey(const Key("slippage-text-field")), "76"); - await tester.pumpAndSettle(); - }); - - zGoldenTest("When typing a value greater than 1%, a warning about front running should be shown", - goldenFileName: "deposit_settings_dropdown_child_front_running_warning", (tester) async { - await tester.pumpDeviceBuilder(await goldenBuilder( - selectedSlippage: Slippage.halfPercent, - )); + goldenFileName: "deposit_settings_dropdown_child_custom_slippage_greater_than_50_error", + (tester) async { + await tester.pumpDeviceBuilder(await goldenBuilder(selectedSlippage: Slippage.halfPercent)); - await tester.enterText(find.byKey(const Key("slippage-text-field")), "1.1"); - await tester.pumpAndSettle(); - }); + await tester.enterText(find.byKey(const Key("slippage-text-field")), "76"); + await tester.pumpAndSettle(); + }, + ); zGoldenTest( - "When clicking `what's this` in the front runnning warning, it should launch a front running blog post", + "When typing a value greater than 1%, a warning about front running should be shown", + goldenFileName: "deposit_settings_dropdown_child_front_running_warning", (tester) async { - await tester.pumpDeviceBuilder(await goldenBuilder( - selectedSlippage: Slippage.halfPercent, - )); + await tester.pumpDeviceBuilder(await goldenBuilder(selectedSlippage: Slippage.halfPercent)); await tester.enterText(find.byKey(const Key("slippage-text-field")), "1.1"); await tester.pumpAndSettle(); - - await tester.tap(find.byKey(const Key("whats-this-question-link"))); - await tester.pumpAndSettle(); - - expect( - UrlLauncherPlatformCustomMock.lastLaunchedUrl, - "https://www.cyfrin.io/blog/what-is-blockchain-and-crypto-front-running", - ); }, ); - zGoldenTest("When hovering the deadline title, a tooltip explaining the deadline should appear", - goldenFileName: "deposit_settings_dropdown_child_deadline_tooltip", (tester) async { - await tester.pumpDeviceBuilder(await goldenBuilder( - selectedSlippage: Slippage.halfPercent, - )); + zGoldenTest("When clicking `what's this` in the front runnning warning, it should launch a front running blog post", ( + tester, + ) async { + await tester.pumpDeviceBuilder(await goldenBuilder(selectedSlippage: Slippage.halfPercent)); + + await tester.enterText(find.byKey(const Key("slippage-text-field")), "1.1"); + await tester.pumpAndSettle(); - await tester.hover(find.byKey(const Key("deadline-tooltip"))); + await tester.tap(find.byKey(const Key("whats-this-question-link"))); await tester.pumpAndSettle(); + + expect( + UrlLauncherPlatformCustomMock.lastLaunchedUrl, + "https://www.cyfrin.io/blog/what-is-blockchain-and-crypto-front-running", + ); }); + zGoldenTest( + "When hovering the deadline title, a tooltip explaining the deadline should appear", + goldenFileName: "deposit_settings_dropdown_child_deadline_tooltip", + (tester) async { + await tester.pumpDeviceBuilder(await goldenBuilder(selectedSlippage: Slippage.halfPercent)); + + await tester.hover(find.byKey(const Key("deadline-tooltip"))); + await tester.pumpAndSettle(); + }, + ); + zGoldenTest( """When typing a value greater than 1200 in the deadline, and unfocusing the textfield, it should adjust it to 1200, @@ -261,10 +272,12 @@ void main() { (tester) async { Duration? expectedDeadline; - await tester.pumpDeviceBuilder(await goldenBuilder( - selectedSlippage: Slippage.halfPercent, - onSettingsChanged: (slippage, deadline) => expectedDeadline = deadline, - )); + await tester.pumpDeviceBuilder( + await goldenBuilder( + selectedSlippage: Slippage.halfPercent, + onSettingsChanged: (slippage, deadline) => expectedDeadline = deadline, + ), + ); await tester.enterText(find.byKey(const Key("deadline-textfield")), "1300"); await tester.pumpAndSettle(); @@ -283,10 +296,12 @@ void main() { (tester) async { Duration? expectedDeadline; - await tester.pumpDeviceBuilder(await goldenBuilder( - selectedSlippage: Slippage.halfPercent, - onSettingsChanged: (slippage, deadline) => expectedDeadline = deadline, - )); + await tester.pumpDeviceBuilder( + await goldenBuilder( + selectedSlippage: Slippage.halfPercent, + onSettingsChanged: (slippage, deadline) => expectedDeadline = deadline, + ), + ); await tester.enterText(find.byKey(const Key("deadline-textfield")), "600"); await tester.pumpAndSettle(); @@ -302,9 +317,7 @@ void main() { "When typing not numbers in the deadline textfield, it should not allow it (will not even show them)", goldenFileName: "deposit_settings_dropdown_child_deadline_not_numbers", (tester) async { - await tester.pumpDeviceBuilder(await goldenBuilder( - selectedSlippage: Slippage.halfPercent, - )); + await tester.pumpDeviceBuilder(await goldenBuilder(selectedSlippage: Slippage.halfPercent)); await tester.enterText(find.byKey(const Key("deadline-textfield")), "a.,';;][');~]"); await tester.pumpAndSettle(); @@ -319,12 +332,14 @@ void main() { (tester) async { Duration? expectedDeadline; - await tester.pumpDeviceBuilder(await goldenBuilder( - selectedSlippage: Slippage.halfPercent, - onSettingsChanged: (slippage, deadline) { - expectedDeadline = deadline; - }, - )); + await tester.pumpDeviceBuilder( + await goldenBuilder( + selectedSlippage: Slippage.halfPercent, + onSettingsChanged: (slippage, deadline) { + expectedDeadline = deadline; + }, + ), + ); await tester.enterText(find.byKey(const Key("deadline-textfield")), "1300"); await tester.pumpAndSettle(); @@ -338,9 +353,7 @@ void main() { then selecting a default option, it should clear the textfield""", goldenFileName: "deposit_settings_dropdown_child_slippage_clear_textfield_after_selecting_default", (tester) async { - await tester.pumpDeviceBuilder(await goldenBuilder( - selectedSlippage: Slippage.halfPercent, - )); + await tester.pumpDeviceBuilder(await goldenBuilder(selectedSlippage: Slippage.halfPercent)); await tester.enterText(find.byKey(const Key("slippage-text-field")), "12"); await tester.pumpAndSettle(); diff --git a/test/app/create/deposit/widgets/deposit_success_modal_test.dart b/test/app/create/deposit/widgets/deposit_success_modal_test.dart index 1d9ce4e..fe55ae2 100644 --- a/test/app/create/deposit/widgets/deposit_success_modal_test.dart +++ b/test/app/create/deposit/widgets/deposit_success_modal_test.dart @@ -4,7 +4,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:golden_toolkit/golden_toolkit.dart'; import 'package:mocktail/mocktail.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; -import 'package:zup_app/app/create/deposit/widgets/deposit_success_modal.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/widgets/deposit_success_modal.dart'; import 'package:zup_app/core/dtos/protocol_dto.dart'; import 'package:zup_app/core/dtos/yield_dto.dart'; import 'package:zup_app/core/injections.dart'; @@ -33,21 +33,22 @@ void main() { tearDown(() => inject.reset()); - Future goldenBuilder({ - YieldDto? customYield, - bool showAsBottomSheet = false, - }) async => - await goldenDeviceBuilder(Builder(builder: (context) { - WidgetsBinding.instance.addPostFrameCallback((_) { - DepositSuccessModal.show( - context, - depositedYield: customYield ?? YieldDto.fixture().copyWith(), - showAsBottomSheet: showAsBottomSheet, - ); - }); - - return const SizedBox.shrink(); - })); + Future goldenBuilder({YieldDto? customYield, bool showAsBottomSheet = false}) async => + await goldenDeviceBuilder( + Builder( + builder: (context) { + WidgetsBinding.instance.addPostFrameCallback((_) { + DepositSuccessModal.show( + context, + depositedYield: customYield ?? YieldDto.fixture().copyWith(), + showAsBottomSheet: showAsBottomSheet, + ); + }); + + return const SizedBox.shrink(); + }, + ), + ); zGoldenTest( "When calling .show in the deposit success modal, it should be displayed", @@ -82,9 +83,11 @@ void main() { const protocolUrl = "https://dale.com.zup"; await tester.pumpDeviceBuilder( - await goldenBuilder( - customYield: YieldDto.fixture().copyWith(protocol: ProtocolDto.fixture().copyWith(url: protocolUrl))), - wrapper: GoldenConfig.localizationsWrapper()); + await goldenBuilder( + customYield: YieldDto.fixture().copyWith(protocol: ProtocolDto.fixture().copyWith(url: protocolUrl)), + ), + wrapper: GoldenConfig.localizationsWrapper(), + ); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key("view-position-button"))); @@ -94,21 +97,27 @@ void main() { }, ); - zGoldenTest("When clicking the close button, the modal should be closed", - goldenFileName: "deposit_success_modal_close_button_tap", (tester) async { - await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); - await tester.pumpAndSettle(); + zGoldenTest( + "When clicking the close button, the modal should be closed", + goldenFileName: "deposit_success_modal_close_button_tap", + (tester) async { + await tester.pumpDeviceBuilder(await goldenBuilder(), wrapper: GoldenConfig.localizationsWrapper()); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("close-button"))); - await tester.pumpAndSettle(); - }); + await tester.tap(find.byKey(const Key("close-button"))); + await tester.pumpAndSettle(); + }, + ); - zGoldenTest("When passing showAsBottomSheet to true, the modal should be displayed as a bottom sheet", - goldenFileName: "deposit_success_modal_bottom_sheet", (tester) async { - await tester.pumpDeviceBuilder( - await goldenBuilder(showAsBottomSheet: true), - wrapper: GoldenConfig.localizationsWrapper(), - ); - await tester.pumpAndSettle(); - }); + zGoldenTest( + "When passing showAsBottomSheet to true, the modal should be displayed as a bottom sheet", + goldenFileName: "deposit_success_modal_bottom_sheet", + (tester) async { + await tester.pumpDeviceBuilder( + await goldenBuilder(showAsBottomSheet: true), + wrapper: GoldenConfig.localizationsWrapper(), + ); + await tester.pumpAndSettle(); + }, + ); } diff --git a/test/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit_test.dart b/test/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit_test.dart index d6028b1..e875f66 100644 --- a/test/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit_test.dart +++ b/test/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit_test.dart @@ -13,7 +13,7 @@ import 'package:zup_app/abis/erc_20.abi.g.dart'; import 'package:zup_app/abis/uniswap_permit2.abi.g.dart'; import 'package:zup_app/abis/uniswap_v3_pool.abi.g.dart'; import 'package:zup_app/abis/uniswap_v3_position_manager.abi.g.dart'; -import 'package:zup_app/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit.dart'; import 'package:zup_app/core/concentrated_liquidity_utils/cl_pool_constants.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; import 'package:zup_app/core/dtos/yield_dto.dart'; diff --git a/test/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_test.dart b/test/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_test.dart index fa93977..99c9874 100644 --- a/test/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_test.dart +++ b/test/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_test.dart @@ -12,8 +12,8 @@ import 'package:zup_app/abis/erc_20.abi.g.dart'; import 'package:zup_app/abis/uniswap_permit2.abi.g.dart'; import 'package:zup_app/abis/uniswap_v3_pool.abi.g.dart'; import 'package:zup_app/abis/uniswap_v3_position_manager.abi.g.dart'; -import 'package:zup_app/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal.dart'; -import 'package:zup_app/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/widgets/preview_deposit_modal/preview_deposit_modal.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit.dart'; import 'package:zup_app/core/concentrated_liquidity_utils/cl_pool_constants.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; import 'package:zup_app/core/dtos/yield_dto.dart'; diff --git a/test/app/create/deposit/widgets/range_selector_test.dart b/test/app/create/deposit/widgets/range_selector_test.dart index 2464733..ec74148 100644 --- a/test/app/create/deposit/widgets/range_selector_test.dart +++ b/test/app/create/deposit/widgets/range_selector_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:golden_toolkit/golden_toolkit.dart'; -import 'package:zup_app/app/create/deposit/widgets/range_selector.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/widgets/range_selector.dart'; import '../../../../golden_config.dart'; @@ -17,49 +17,55 @@ void main() { double? initialPrice, bool isInfinity = false, RangeSelectorState? state, - }) => - goldenDeviceBuilder( - Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - width: 600, - child: RangeSelector( - key: key, - displayBaseTokenSymbol: "Token A", - displayQuoteTokenSymbol: "Token B", - isReversed: isReversed, - onPriceChanged: onPriceChanged ?? (_) {}, - poolToken0Decimals: poolToken0Decimals ?? 18, - poolToken1Decimals: poolToken0Decimals ?? 18, - tickSpacing: tickSpacing, - type: type, - initialPrice: initialPrice, - isInfinity: isInfinity, - state: state ?? const RangeSelectorState(type: RangeSelectorStateType.regular), - ), - ), - ], + }) => goldenDeviceBuilder( + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: 600, + child: RangeSelector( + key: key, + displayBaseTokenSymbol: "Token A", + displayQuoteTokenSymbol: "Token B", + isReversed: isReversed, + onPriceChanged: onPriceChanged ?? (_) {}, + poolToken0Decimals: poolToken0Decimals ?? 18, + poolToken1Decimals: poolToken0Decimals ?? 18, + tickSpacing: tickSpacing, + type: type, + initialPrice: initialPrice, + isInfinity: isInfinity, + state: state ?? const RangeSelectorState(type: RangeSelectorStateType.regular), + ), ), - ); + ], + ), + ); - zGoldenTest("When the range selector type is min price, it should represent in the widget", - goldenFileName: "range_selector_min_price", (tester) async { - return tester.pumpDeviceBuilder(await goldenBuilder(type: RangeSelectorType.minPrice)); - }); + zGoldenTest( + "When the range selector type is min price, it should represent in the widget", + goldenFileName: "range_selector_min_price", + (tester) async { + return tester.pumpDeviceBuilder(await goldenBuilder(type: RangeSelectorType.minPrice)); + }, + ); - zGoldenTest("When the range selector type is max price, it should represent in the widget", - goldenFileName: "range_selector_max_price", (tester) async { - return tester.pumpDeviceBuilder(await goldenBuilder(type: RangeSelectorType.maxPrice)); - }); + zGoldenTest( + "When the range selector type is max price, it should represent in the widget", + goldenFileName: "range_selector_max_price", + (tester) async { + return tester.pumpDeviceBuilder(await goldenBuilder(type: RangeSelectorType.maxPrice)); + }, + ); - zGoldenTest("When the `isReversed` param is true, it should reverse the tokens in the widget", - goldenFileName: "range_selector_reversed", (tester) async { - return tester.pumpDeviceBuilder( - await goldenBuilder(isReversed: true), - ); - }); + zGoldenTest( + "When the `isReversed` param is true, it should reverse the tokens in the widget", + goldenFileName: "range_selector_reversed", + (tester) async { + return tester.pumpDeviceBuilder(await goldenBuilder(isReversed: true)); + }, + ); zGoldenTest( "When typing a price, and unfocusing the text field, it should callback with the typed price adjusted for the tick spacing", @@ -69,9 +75,11 @@ void main() { double actualAdjustedTypedPrice = 0; await tester.pumpDeviceBuilder( - await goldenBuilder(onPriceChanged: (price) { - actualAdjustedTypedPrice = price; - }), + await goldenBuilder( + onPriceChanged: (price) { + actualAdjustedTypedPrice = price; + }, + ), ); await tester.enterText(find.byType(TextField), typedPrice); @@ -88,159 +96,182 @@ void main() { (tester) async { const double initialPrice = 1200; + await tester.pumpDeviceBuilder(await goldenBuilder(initialPrice: initialPrice)); + }, + ); + + zGoldenTest( + "When the type is max price, and the isInfinity param is true, it should show the infinity symbol", + goldenFileName: "range_selector_max_price_infinity", + (tester) async { + await tester.pumpDeviceBuilder(await goldenBuilder(type: RangeSelectorType.maxPrice, isInfinity: true)); + }, + ); + + zGoldenTest( + "When the type is min price, and the isInfinity param is true, it should show 0", + goldenFileName: "range_selector_min_price_infinity", + (tester) async { + await tester.pumpDeviceBuilder(await goldenBuilder(type: RangeSelectorType.minPrice, isInfinity: true)); + }, + ); + + zGoldenTest( + "When the state is warning, it should set the colors to yellow", + goldenFileName: "range_selector_warning_state", + (tester) async { await tester.pumpDeviceBuilder( - await goldenBuilder(initialPrice: initialPrice), + await goldenBuilder( + state: const RangeSelectorState(type: RangeSelectorStateType.warning, message: "This is a warning message"), + ), ); }, ); - zGoldenTest("When the type is max price, and the isInfinity param is true, it should show the infinity symbol", - goldenFileName: "range_selector_max_price_infinity", (tester) async { - await tester.pumpDeviceBuilder( - await goldenBuilder(type: RangeSelectorType.maxPrice, isInfinity: true), - ); - }); - - zGoldenTest("When the type is min price, and the isInfinity param is true, it should show 0", - goldenFileName: "range_selector_min_price_infinity", (tester) async { - await tester.pumpDeviceBuilder( - await goldenBuilder(type: RangeSelectorType.minPrice, isInfinity: true), - ); - }); - - zGoldenTest("When the state is warning, it should set the colors to yellow", - goldenFileName: "range_selector_warning_state", (tester) async { - await tester.pumpDeviceBuilder( - await goldenBuilder( - state: const RangeSelectorState( - type: RangeSelectorStateType.warning, - message: "This is a warning message", - ), - ), - ); - }); - - zGoldenTest("When the state is error, it should set the colors to red", goldenFileName: "range_selector_error_state", - (tester) async { - await tester.pumpDeviceBuilder( - await goldenBuilder( - state: const RangeSelectorState( - type: RangeSelectorStateType.error, - message: "This is a error message", + zGoldenTest( + "When the state is error, it should set the colors to red", + goldenFileName: "range_selector_error_state", + (tester) async { + await tester.pumpDeviceBuilder( + await goldenBuilder( + state: const RangeSelectorState(type: RangeSelectorStateType.error, message: "This is a error message"), ), - ), - ); - }); + ); + }, + ); - zGoldenTest("""When updating the widget, and passing a isInfinity true, + zGoldenTest( + """When updating the widget, and passing a isInfinity true, while is not infinity, it should change the price to 0 if the type is min price""", - goldenFileName: "range_selector_update_infinity_price_min_price", (tester) async { - const key = Key("some-key"); + goldenFileName: "range_selector_update_infinity_price_min_price", + (tester) async { + const key = Key("some-key"); - await tester.pumpDeviceBuilder(await goldenBuilder(key: key, type: RangeSelectorType.minPrice)); - await tester.enterText(find.byKey(key), "1000"); - await tester.pumpAndSettle(); + await tester.pumpDeviceBuilder(await goldenBuilder(key: key, type: RangeSelectorType.minPrice)); + await tester.enterText(find.byKey(key), "1000"); + await tester.pumpAndSettle(); - await tester.pumpDeviceBuilder(await goldenBuilder(isInfinity: true, key: key, type: RangeSelectorType.minPrice)); - }); + await tester.pumpDeviceBuilder(await goldenBuilder(isInfinity: true, key: key, type: RangeSelectorType.minPrice)); + }, + ); - zGoldenTest("""When updating the widget, and passing a isInfinity true, + zGoldenTest( + """When updating the widget, and passing a isInfinity true, while is not infinity, it should change the price to infinity if the type is max price - """, goldenFileName: "range_selector_update_infinity_price_max_price", (tester) async { - const key = Key("some-key"); - - await tester.pumpDeviceBuilder(await goldenBuilder(key: key, type: RangeSelectorType.maxPrice)); - await tester.enterText(find.byKey(key), "1000"); - await tester.pumpAndSettle(); - - await tester.pumpDeviceBuilder(await goldenBuilder(isInfinity: true, key: key, type: RangeSelectorType.maxPrice)); - }); - - zGoldenTest(""""When updating the widget, and passing a different tick spacing - than it was before, it should recalculate the typed price and callback it""", (tester) async { - const expectedNewPrice = 1211.5369312111138; - const typedPrice = "1200"; - const key = Key("some-key"); - double actualNewPrice = 0; - - await tester.pumpDeviceBuilder(await goldenBuilder(key: key, tickSpacing: 10)); - await tester.enterText(find.byKey(key), typedPrice); - - await tester.pumpDeviceBuilder(await goldenBuilder( - key: key, - tickSpacing: 1000, - onPriceChanged: (price) => actualNewPrice = price, - )); - - expect(actualNewPrice, expectedNewPrice); - }); - - zGoldenTest("When passing typing a price with isReversed, it should correctly adjust and callback", - goldenFileName: "range_selector_is_reversed_typed_price", (tester) async { - const expectedAdjustedTypedPrice = 1200.0823982564434; - const typedPrice = "1200"; - double actualAdjustedTypedPrice = 0; - - await tester.pumpDeviceBuilder(await goldenBuilder( - isReversed: true, - tickSpacing: 1, - onPriceChanged: (price) { - actualAdjustedTypedPrice = price; - }, - )); - - await tester.enterText(find.byType(TextField), typedPrice); - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); - - expect(actualAdjustedTypedPrice, expectedAdjustedTypedPrice); - }); - - zGoldenTest("""When passing a price, and clicking the button to increase, + """, + goldenFileName: "range_selector_update_infinity_price_max_price", + (tester) async { + const key = Key("some-key"); + + await tester.pumpDeviceBuilder(await goldenBuilder(key: key, type: RangeSelectorType.maxPrice)); + await tester.enterText(find.byKey(key), "1000"); + await tester.pumpAndSettle(); + + await tester.pumpDeviceBuilder(await goldenBuilder(isInfinity: true, key: key, type: RangeSelectorType.maxPrice)); + }, + ); + + zGoldenTest( + """"When updating the widget, and passing a different tick spacing + than it was before, it should recalculate the typed price and callback it""", + (tester) async { + const expectedNewPrice = 1211.5369312111138; + const typedPrice = "1200"; + const key = Key("some-key"); + double actualNewPrice = 0; + + await tester.pumpDeviceBuilder(await goldenBuilder(key: key, tickSpacing: 10)); + await tester.enterText(find.byKey(key), typedPrice); + + await tester.pumpDeviceBuilder( + await goldenBuilder(key: key, tickSpacing: 1000, onPriceChanged: (price) => actualNewPrice = price), + ); + + expect(actualNewPrice, expectedNewPrice); + }, + ); + + zGoldenTest( + "When passing typing a price with isReversed, it should correctly adjust and callback", + goldenFileName: "range_selector_is_reversed_typed_price", + (tester) async { + const expectedAdjustedTypedPrice = 1200.0823982564434; + const typedPrice = "1200"; + double actualAdjustedTypedPrice = 0; + + await tester.pumpDeviceBuilder( + await goldenBuilder( + isReversed: true, + tickSpacing: 1, + onPriceChanged: (price) { + actualAdjustedTypedPrice = price; + }, + ), + ); + + await tester.enterText(find.byType(TextField), typedPrice); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); + + expect(actualAdjustedTypedPrice, expectedAdjustedTypedPrice); + }, + ); + + zGoldenTest( + """When passing a price, and clicking the button to increase, it should increase the price by 2 ticks (based on the tick spacing)""", - goldenFileName: "range_selector_increase_price", (tester) async { - const expectedIncreasedPrice = 1201.8837824865475; - const typedPrice = "1200"; - double actualIncreasedPrice = 0; + goldenFileName: "range_selector_increase_price", + (tester) async { + const expectedIncreasedPrice = 1201.8837824865475; + const typedPrice = "1200"; + double actualIncreasedPrice = 0; - await tester.pumpDeviceBuilder(await goldenBuilder( - onPriceChanged: (price) { - actualIncreasedPrice = price; - }, - )); + await tester.pumpDeviceBuilder( + await goldenBuilder( + onPriceChanged: (price) { + actualIncreasedPrice = price; + }, + ), + ); - await tester.enterText(find.byType(TextField), typedPrice); - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); + await tester.enterText(find.byType(TextField), typedPrice); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("increase-button"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("increase-button"))); + await tester.pumpAndSettle(); - expect(actualIncreasedPrice, expectedIncreasedPrice); - }); + expect(actualIncreasedPrice, expectedIncreasedPrice); + }, + ); - zGoldenTest("""When passing a price, and clicking the button to decrease, + zGoldenTest( + """When passing a price, and clicking the button to decrease, it should decrease the price by 2 ticks (based on the tick spacing)""", - goldenFileName: "range_selector_decrease_price", (tester) async { - const expectedDecreasedPrice = 1198.283713942248; - const typedPrice = "1200"; - double actualDecreasedPrice = 0; + goldenFileName: "range_selector_decrease_price", + (tester) async { + const expectedDecreasedPrice = 1198.283713942248; + const typedPrice = "1200"; + double actualDecreasedPrice = 0; - await tester.pumpDeviceBuilder(await goldenBuilder( - onPriceChanged: (price) { - actualDecreasedPrice = price; - }, - )); + await tester.pumpDeviceBuilder( + await goldenBuilder( + onPriceChanged: (price) { + actualDecreasedPrice = price; + }, + ), + ); - await tester.enterText(find.byType(TextField), typedPrice); - FocusManager.instance.primaryFocus?.unfocus(); - await tester.pumpAndSettle(); + await tester.enterText(find.byType(TextField), typedPrice); + FocusManager.instance.primaryFocus?.unfocus(); + await tester.pumpAndSettle(); - await tester.tap(find.byKey(const Key("decrease-button"))); - await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key("decrease-button"))); + await tester.pumpAndSettle(); - expect(actualDecreasedPrice, expectedDecreasedPrice); - }); + expect(actualDecreasedPrice, expectedDecreasedPrice); + }, + ); zGoldenTest( "When typing a price with `isReversed` true, and clicking the button to increase, it should increase the price by 2 ticks", @@ -250,12 +281,14 @@ void main() { const typedPrice = "1200"; double actualIncreasedPrice = 0; - await tester.pumpDeviceBuilder(await goldenBuilder( - isReversed: true, - onPriceChanged: (price) { - actualIncreasedPrice = price; - }, - )); + await tester.pumpDeviceBuilder( + await goldenBuilder( + isReversed: true, + onPriceChanged: (price) { + actualIncreasedPrice = price; + }, + ), + ); await tester.enterText(find.byType(TextField), typedPrice); FocusManager.instance.primaryFocus?.unfocus(); @@ -277,12 +310,14 @@ void main() { const typedPrice = "1200"; double actualDecreasedPrice = 0; - await tester.pumpDeviceBuilder(await goldenBuilder( - isReversed: true, - onPriceChanged: (price) { - actualDecreasedPrice = price; - }, - )); + await tester.pumpDeviceBuilder( + await goldenBuilder( + isReversed: true, + onPriceChanged: (price) { + actualDecreasedPrice = price; + }, + ), + ); await tester.enterText(find.byType(TextField), typedPrice); FocusManager.instance.primaryFocus?.unfocus(); @@ -302,14 +337,16 @@ void main() { const expectedIncreasedPrice = 9.996040641477102e-19; double actualIncreasedPrice = 0; - await tester.pumpDeviceBuilder(await goldenBuilder( - isInfinity: true, - poolToken0Decimals: 6, - poolToken1Decimals: 18, - onPriceChanged: (price) { - actualIncreasedPrice = price; - }, - )); + await tester.pumpDeviceBuilder( + await goldenBuilder( + isInfinity: true, + poolToken0Decimals: 6, + poolToken1Decimals: 18, + onPriceChanged: (price) { + actualIncreasedPrice = price; + }, + ), + ); await tester.tap(find.byKey(const Key("increase-button"))); await tester.pumpAndSettle(); @@ -326,15 +363,17 @@ void main() { const expectedIncreasedPrice = 9.996040641477102e-19; double actualIncreasedPrice = 0; - await tester.pumpDeviceBuilder(await goldenBuilder( - isInfinity: true, - isReversed: true, - poolToken0Decimals: 6, - poolToken1Decimals: 18, - onPriceChanged: (price) { - actualIncreasedPrice = price; - }, - )); + await tester.pumpDeviceBuilder( + await goldenBuilder( + isInfinity: true, + isReversed: true, + poolToken0Decimals: 6, + poolToken1Decimals: 18, + onPriceChanged: (price) { + actualIncreasedPrice = price; + }, + ), + ); await tester.tap(find.byKey(const Key("increase-button"))); await tester.pumpAndSettle(); @@ -351,15 +390,17 @@ void main() { const expectedIncreasedPrice = 0; double actualIncreasedPrice = 0; - await tester.pumpDeviceBuilder(await goldenBuilder( - isInfinity: true, - isReversed: true, - poolToken0Decimals: 6, - poolToken1Decimals: 18, - onPriceChanged: (price) { - actualIncreasedPrice = price; - }, - )); + await tester.pumpDeviceBuilder( + await goldenBuilder( + isInfinity: true, + isReversed: true, + poolToken0Decimals: 6, + poolToken1Decimals: 18, + onPriceChanged: (price) { + actualIncreasedPrice = price; + }, + ), + ); await tester.tap(find.byKey(const Key("decrease-button"))); await tester.pumpAndSettle(); @@ -375,14 +416,16 @@ void main() { const expectedIncreasedPrice = 0; double actualIncreasedPrice = 0; - await tester.pumpDeviceBuilder(await goldenBuilder( - isInfinity: true, - poolToken0Decimals: 6, - poolToken1Decimals: 18, - onPriceChanged: (price) { - actualIncreasedPrice = price; - }, - )); + await tester.pumpDeviceBuilder( + await goldenBuilder( + isInfinity: true, + poolToken0Decimals: 6, + poolToken1Decimals: 18, + onPriceChanged: (price) { + actualIncreasedPrice = price; + }, + ), + ); await tester.tap(find.byKey(const Key("decrease-button"))); await tester.pumpAndSettle(); diff --git a/test/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_cubit_test.dart b/test/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_cubit_test.dart index c337c76..94b614e 100644 --- a/test/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_cubit_test.dart +++ b/test/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_cubit_test.dart @@ -1,6 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:zup_app/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_cubit.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/widgets/token_amount_input_card/token_amount_input_card_cubit.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; import 'package:zup_app/core/dtos/token_price_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; @@ -46,14 +46,22 @@ void main() { final token = TokenDto.fixture(); const network = AppNetworks.sepolia; - when(() => zupSingletonCache.run(any(), expiration: any(named: "expiration"), key: any(named: "key"))) - .thenAnswer((_) async => 31); + when( + () => zupSingletonCache.run( + any(), + expiration: any(named: "expiration"), + key: any(named: "key"), + ), + ).thenAnswer((_) async => 31); await sut0.getTokenPrice(token: token, network: network); - verify(() => zupSingletonCache.run(any(), + verify( + () => zupSingletonCache.run( + any(), expiration: const Duration(minutes: 1), - key: _KeysMixinWrapper() - .tokenPriceCacheKey(tokenAddress: token.addresses[network.chainId]!, network: network))).called(1); + key: _KeysMixinWrapper().tokenPriceCacheKey(tokenAddress: token.addresses[network.chainId]!, network: network), + ), + ).called(1); }); test("When calling `getTokenPrice` it should use tokensRepository to get the token price", () async { diff --git a/test/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_test.dart b/test/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_test.dart index 8250d71..de44162 100644 --- a/test/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_test.dart +++ b/test/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_test.dart @@ -5,7 +5,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:golden_toolkit/golden_toolkit.dart'; import 'package:mocktail/mocktail.dart'; import 'package:web3kit/web3kit.dart'; -import 'package:zup_app/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/widgets/token_amount_input_card/token_amount_input_card.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; import 'package:zup_app/core/dtos/token_price_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; diff --git a/test/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_user_balance_cubit_test.dart b/test/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_user_balance_cubit_test.dart index 24797b7..3a0bd7a 100644 --- a/test/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_user_balance_cubit_test.dart +++ b/test/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_user_balance_cubit_test.dart @@ -4,7 +4,7 @@ import 'package:clock/clock.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:web3kit/web3kit.dart'; -import 'package:zup_app/app/create/deposit/widgets/token_amount_input_card/token_amount_input_card_user_balance_cubit.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/widgets/token_amount_input_card/token_amount_input_card_user_balance_cubit.dart'; import 'package:zup_app/core/dtos/token_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; import 'package:zup_core/zup_core.dart'; @@ -45,8 +45,9 @@ void main() { when(() => wallet.signer).thenReturn(signer); when(() => wallet.signerStream).thenAnswer((_) => signerStream); when(() => signer.address).thenAnswer((_) => Future.value("0x99E3CfADCD8Feecb5DdF91f88998cFfB3145F78c")); - when(() => wallet.nativeOrTokenBalance(any(), rpcUrl: any(named: "rpcUrl"))) - .thenAnswer((_) => Future.value(12.1)); + when( + () => wallet.nativeOrTokenBalance(any(), rpcUrl: any(named: "rpcUrl")), + ).thenAnswer((_) => Future.value(12.1)); final sut0 = TokenAmountCardUserBalanceCubit( wallet, @@ -142,115 +143,136 @@ void main() { verify(() => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: any(named: "rpcUrl"))).called(1); }); - test(""" + test( + """ When calling `getUserTokenAmount` it should use the zup singleton cache to get the user token balance, with a 10 minutes expiration time. If 10 minutes did not pass, it should not get the user token balance again. -""", () async { - final StreamController signerStreamController0 = StreamController.broadcast(); - final Stream signerStream = signerStreamController0.stream; +""", + () async { + final StreamController signerStreamController0 = StreamController.broadcast(); + final Stream signerStream = signerStreamController0.stream; - when(() => wallet.signer).thenReturn(signer); - when(() => wallet.signerStream).thenAnswer((_) => signerStream); - when(() => signer.address).thenAnswer((_) => Future.value("0x99E3CfADCD8Feecb5DdF91f88998cFfB3145F78c")); - when(() => wallet.nativeOrTokenBalance(any(), rpcUrl: any(named: "rpcUrl"))).thenAnswer((_) => Future.value(12.1)); + when(() => wallet.signer).thenReturn(signer); + when(() => wallet.signerStream).thenAnswer((_) => signerStream); + when(() => signer.address).thenAnswer((_) => Future.value("0x99E3CfADCD8Feecb5DdF91f88998cFfB3145F78c")); + when( + () => wallet.nativeOrTokenBalance(any(), rpcUrl: any(named: "rpcUrl")), + ).thenAnswer((_) => Future.value(12.1)); - final sut0 = TokenAmountCardUserBalanceCubit( - wallet, - tokenAddress, - AppNetworks.sepolia, - ZupSingletonCache.shared, - () {}, - ); + final sut0 = TokenAmountCardUserBalanceCubit( + wallet, + tokenAddress, + AppNetworks.sepolia, + ZupSingletonCache.shared, + () {}, + ); - const expectedUserBalance = userBalance; - const notExpectedUserBalance = 42891.1; + const expectedUserBalance = userBalance; + const notExpectedUserBalance = 42891.1; - when(() => wallet.tokenBalance(any(), rpcUrl: any(named: "rpcUrl"))) - .thenAnswer((_) => Future.value(expectedUserBalance)); - await sut0.getUserTokenAmount(); + when( + () => wallet.tokenBalance(any(), rpcUrl: any(named: "rpcUrl")), + ).thenAnswer((_) => Future.value(expectedUserBalance)); + await sut0.getUserTokenAmount(); - when(() => wallet.tokenBalance(any(), rpcUrl: any(named: "rpcUrl"))) - .thenAnswer((_) => Future.value(notExpectedUserBalance)); - await sut0.getUserTokenAmount(); + when( + () => wallet.tokenBalance(any(), rpcUrl: any(named: "rpcUrl")), + ).thenAnswer((_) => Future.value(notExpectedUserBalance)); + await sut0.getUserTokenAmount(); - verify(() => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: any(named: "rpcUrl"))).called(1); - expect(sut0.state, const TokenAmountCardUserBalanceState.showUserBalance(expectedUserBalance)); - }); + verify(() => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: any(named: "rpcUrl"))).called(1); + expect(sut0.state, const TokenAmountCardUserBalanceState.showUserBalance(expectedUserBalance)); + }, + ); - test(""" + test( + """ When calling `getUserTokenAmount` it should use the zup singleton cache to get the user token balance, with a 10 minutes expiration time. If 10 minutes pass, it should get the user token balance again. -""", () async { - final StreamController signerStreamController0 = StreamController.broadcast(); - final Stream signerStream = signerStreamController0.stream; - - when(() => wallet.signer).thenReturn(signer); - when(() => wallet.signerStream).thenAnswer((_) => signerStream); - when(() => signer.address).thenAnswer((_) => Future.value("0x99E3CfADCD8Feecb5DdF91f88998cFfB3145F78c")); - when(() => wallet.nativeOrTokenBalance(any(), rpcUrl: any(named: "rpcUrl"))).thenAnswer((_) => Future.value(12.1)); +""", + () async { + final StreamController signerStreamController0 = StreamController.broadcast(); + final Stream signerStream = signerStreamController0.stream; - final sut0 = TokenAmountCardUserBalanceCubit( - wallet, - tokenAddress, - AppNetworks.sepolia, - ZupSingletonCache.shared, - () {}, - ); + when(() => wallet.signer).thenReturn(signer); + when(() => wallet.signerStream).thenAnswer((_) => signerStream); + when(() => signer.address).thenAnswer((_) => Future.value("0x99E3CfADCD8Feecb5DdF91f88998cFfB3145F78c")); + when( + () => wallet.nativeOrTokenBalance(any(), rpcUrl: any(named: "rpcUrl")), + ).thenAnswer((_) => Future.value(12.1)); - const expectedUserBalance = 4311.322; - const notExpectedUserBalance = userBalance; + final sut0 = TokenAmountCardUserBalanceCubit( + wallet, + tokenAddress, + AppNetworks.sepolia, + ZupSingletonCache.shared, + () {}, + ); - when(() => wallet.nativeOrTokenBalance(any(), rpcUrl: any(named: "rpcUrl"))) - .thenAnswer((_) => Future.value(notExpectedUserBalance)); - await sut0.getUserTokenAmount(); + const expectedUserBalance = 4311.322; + const notExpectedUserBalance = userBalance; - await withClock(Clock(() => DateTime.now().add(const Duration(minutes: 11))), () async { - when(() => wallet.nativeOrTokenBalance(any(), rpcUrl: any(named: "rpcUrl"))) - .thenAnswer((_) => Future.value(expectedUserBalance)); + when( + () => wallet.nativeOrTokenBalance(any(), rpcUrl: any(named: "rpcUrl")), + ).thenAnswer((_) => Future.value(notExpectedUserBalance)); await sut0.getUserTokenAmount(); - verify(() => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: any(named: "rpcUrl"))).called(2); - expect(sut0.state, const TokenAmountCardUserBalanceState.showUserBalance(expectedUserBalance)); - }); - }); + await withClock(Clock(() => DateTime.now().add(const Duration(minutes: 11))), () async { + when( + () => wallet.nativeOrTokenBalance(any(), rpcUrl: any(named: "rpcUrl")), + ).thenAnswer((_) => Future.value(expectedUserBalance)); + await sut0.getUserTokenAmount(); + + verify(() => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: any(named: "rpcUrl"))).called(2); + expect(sut0.state, const TokenAmountCardUserBalanceState.showUserBalance(expectedUserBalance)); + }); + }, + ); - test(""" + test( + """ When calling `getUserTokenAmount` with `ignoreCache` param true, it should get the user token balance again. Does not matter the expiration time -""", () async { - final StreamController signerStreamController0 = StreamController.broadcast(); - final Stream signerStream = signerStreamController0.stream; +""", + () async { + final StreamController signerStreamController0 = StreamController.broadcast(); + final Stream signerStream = signerStreamController0.stream; - when(() => wallet.signer).thenReturn(signer); - when(() => wallet.signerStream).thenAnswer((_) => signerStream); - when(() => signer.address).thenAnswer((_) => Future.value("0x99E3CfADCD8Feecb5DdF91f88998cFfB3145F78c")); - when(() => wallet.nativeOrTokenBalance(any(), rpcUrl: any(named: "rpcUrl"))).thenAnswer((_) => Future.value(12.1)); + when(() => wallet.signer).thenReturn(signer); + when(() => wallet.signerStream).thenAnswer((_) => signerStream); + when(() => signer.address).thenAnswer((_) => Future.value("0x99E3CfADCD8Feecb5DdF91f88998cFfB3145F78c")); + when( + () => wallet.nativeOrTokenBalance(any(), rpcUrl: any(named: "rpcUrl")), + ).thenAnswer((_) => Future.value(12.1)); - final sut0 = TokenAmountCardUserBalanceCubit( - wallet, - tokenAddress, - AppNetworks.sepolia, - ZupSingletonCache.shared, - () {}, - ); + final sut0 = TokenAmountCardUserBalanceCubit( + wallet, + tokenAddress, + AppNetworks.sepolia, + ZupSingletonCache.shared, + () {}, + ); - const expectedUserBalance = 4311.322; - const notExpectedUserBalance = userBalance; + const expectedUserBalance = 4311.322; + const notExpectedUserBalance = userBalance; - when(() => wallet.nativeOrTokenBalance(any(), rpcUrl: any(named: "rpcUrl"))) - .thenAnswer((_) => Future.value(notExpectedUserBalance)); - await sut0.getUserTokenAmount(); + when( + () => wallet.nativeOrTokenBalance(any(), rpcUrl: any(named: "rpcUrl")), + ).thenAnswer((_) => Future.value(notExpectedUserBalance)); + await sut0.getUserTokenAmount(); - when(() => wallet.nativeOrTokenBalance(any(), rpcUrl: any(named: "rpcUrl"))) - .thenAnswer((_) => Future.value(expectedUserBalance)); - await sut0.getUserTokenAmount(ignoreCache: true); + when( + () => wallet.nativeOrTokenBalance(any(), rpcUrl: any(named: "rpcUrl")), + ).thenAnswer((_) => Future.value(expectedUserBalance)); + await sut0.getUserTokenAmount(ignoreCache: true); - verify(() => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: any(named: "rpcUrl"))).called(2); - expect(sut0.state, const TokenAmountCardUserBalanceState.showUserBalance(expectedUserBalance)); - }); + verify(() => wallet.nativeOrTokenBalance(tokenAddress, rpcUrl: any(named: "rpcUrl"))).called(2); + expect(sut0.state, const TokenAmountCardUserBalanceState.showUserBalance(expectedUserBalance)); + }, + ); test("when calling `updateToken` and the signer is not null, it should get the user token balance again", () async { const tokenAddress = "0x99E3CfADCD8Feecb5DdF91f88998cFfB3145F78c"; @@ -386,85 +408,98 @@ void main() { }, ); - test("When calling 'updateNativeTokenAndFetch' and the signer is null, it should not get the user token balance", - () async { - final sut0 = TokenAmountCardUserBalanceCubit( - wallet, - tokenAddress, - AppNetworks.sepolia, - ZupSingletonCache.shared, - () {}, - ); + test( + "When calling 'updateNativeTokenAndFetch' and the signer is null, it should not get the user token balance", + () async { + final sut0 = TokenAmountCardUserBalanceCubit( + wallet, + tokenAddress, + AppNetworks.sepolia, + ZupSingletonCache.shared, + () {}, + ); - when(() => wallet.signer).thenAnswer((_) => null); + when(() => wallet.signer).thenAnswer((_) => null); - await sut0.updateNativeTokenAndFetch(isNative: false, network: AppNetworks.sepolia); + await sut0.updateNativeTokenAndFetch(isNative: false, network: AppNetworks.sepolia); - verifyNever(() => wallet.nativeOrTokenBalance(any(), rpcUrl: any(named: "rpcUrl"))); - }); + verifyNever(() => wallet.nativeOrTokenBalance(any(), rpcUrl: any(named: "rpcUrl"))); + }, + ); - test("When calling `updateNativeTokenAndFetch` and the signer is not null, it should get the user token balance", - () async { - final sut0 = TokenAmountCardUserBalanceCubit( - wallet, - tokenAddress, - AppNetworks.sepolia, - ZupSingletonCache.shared, - () {}, - ); + test( + "When calling `updateNativeTokenAndFetch` and the signer is not null, it should get the user token balance", + () async { + final sut0 = TokenAmountCardUserBalanceCubit( + wallet, + tokenAddress, + AppNetworks.sepolia, + ZupSingletonCache.shared, + () {}, + ); - when(() => wallet.signer).thenAnswer((_) => signer); + when(() => wallet.signer).thenAnswer((_) => signer); - await sut0.updateNativeTokenAndFetch(isNative: false, network: AppNetworks.sepolia); + await sut0.updateNativeTokenAndFetch(isNative: false, network: AppNetworks.sepolia); - verify(() => wallet.nativeOrTokenBalance(any(), rpcUrl: any(named: "rpcUrl"))) - .called(2); // two times because of the initial load - }); + verify( + () => wallet.nativeOrTokenBalance(any(), rpcUrl: any(named: "rpcUrl")), + ).called(2); // two times because of the initial load + }, + ); - test("""When calling `updateNativeTokenAndFetch` + test( + """When calling `updateNativeTokenAndFetch` it should update the network variable for the one passed, - so the next time to fetch the balance it will use the new network""", () async { - const initialNetwork = AppNetworks.sepolia; - const nextNetwork = AppNetworks.mainnet; - final sut0 = TokenAmountCardUserBalanceCubit( - wallet, - tokenAddress, - initialNetwork, - ZupSingletonCache.shared, - () {}, - ); + so the next time to fetch the balance it will use the new network""", + () async { + const initialNetwork = AppNetworks.sepolia; + const nextNetwork = AppNetworks.mainnet; + final sut0 = TokenAmountCardUserBalanceCubit( + wallet, + tokenAddress, + initialNetwork, + ZupSingletonCache.shared, + () {}, + ); - when(() => wallet.signer).thenAnswer((_) => signer); + when(() => wallet.signer).thenAnswer((_) => signer); - await sut0.updateNativeTokenAndFetch(isNative: false, network: nextNetwork); + await sut0.updateNativeTokenAndFetch(isNative: false, network: nextNetwork); - verify(() => wallet.nativeOrTokenBalance(any(), rpcUrl: nextNetwork.rpcUrl)) - .called(2); // two times because of the initial load - }); + verify( + () => wallet.nativeOrTokenBalance(any(), rpcUrl: nextNetwork.rpcUrl), + ).called(2); // two times because of the initial load + }, + ); - test("""When calling `updateTokenAndNetwork` passsing the is native true, it should update the isNative variable, - and when a new signer is emitted it will get the native balance""", () async { - final signerStreamController = StreamController.broadcast(); + test( + """When calling `updateTokenAndNetwork` passsing the is native true, it should update the isNative variable, + and when a new signer is emitted it will get the native balance""", + () async { + final signerStreamController = StreamController.broadcast(); - when(() => wallet.signerStream).thenAnswer((_) => signerStreamController.stream); + when(() => wallet.signerStream).thenAnswer((_) => signerStreamController.stream); - final sut0 = TokenAmountCardUserBalanceCubit( - wallet, - tokenAddress, - AppNetworks.sepolia, - ZupSingletonCache.shared, - () {}, - ); + final sut0 = TokenAmountCardUserBalanceCubit( + wallet, + tokenAddress, + AppNetworks.sepolia, + ZupSingletonCache.shared, + () {}, + ); - final token = TokenDto.fixture(); - const network = AppNetworks.sepolia; + final token = TokenDto.fixture(); + const network = AppNetworks.sepolia; - await sut0.updateTokenAndNetwork(token.addresses[network.chainId]!, network, asNativeToken: true); + await sut0.updateTokenAndNetwork(token.addresses[network.chainId]!, network, asNativeToken: true); - signerStreamController.add(signer); - await Future.delayed(const Duration(seconds: 0)); + signerStreamController.add(signer); + await Future.delayed(const Duration(seconds: 0)); - verify(() => wallet.nativeOrTokenBalance(EthereumConstants.zeroAddress, rpcUrl: any(named: "rpcUrl"))) - .called(2); // two because of the initial load - }); + verify( + () => wallet.nativeOrTokenBalance(EthereumConstants.zeroAddress, rpcUrl: any(named: "rpcUrl")), + ).called(2); // two because of the initial load + }, + ); } diff --git a/test/app/create/yields/goldens/yields_page_error_state.png b/test/app/create/yields/goldens/yields_page_error_state.png new file mode 100644 index 0000000..a5667c4 Binary files /dev/null and b/test/app/create/yields/goldens/yields_page_error_state.png differ diff --git a/test/app/create/yields/goldens/yields_page_no_yields_state.png b/test/app/create/yields/goldens/yields_page_no_yields_state.png new file mode 100644 index 0000000..b1f0508 Binary files /dev/null and b/test/app/create/yields/goldens/yields_page_no_yields_state.png differ diff --git a/test/app/create/yields/goldens/yields_page_no_yields_state_min_tvl_set.png b/test/app/create/yields/goldens/yields_page_no_yields_state_min_tvl_set.png new file mode 100644 index 0000000..475ad2e Binary files /dev/null and b/test/app/create/yields/goldens/yields_page_no_yields_state_min_tvl_set.png differ diff --git a/test/app/create/yields/goldens/yields_page_success_click_page_indicator.png b/test/app/create/yields/goldens/yields_page_success_click_page_indicator.png new file mode 100644 index 0000000..1c39b9e Binary files /dev/null and b/test/app/create/yields/goldens/yields_page_success_click_page_indicator.png differ diff --git a/test/app/create/yields/goldens/yields_page_success_first_page_coming_back.png b/test/app/create/yields/goldens/yields_page_success_first_page_coming_back.png new file mode 100644 index 0000000..ab1f382 Binary files /dev/null and b/test/app/create/yields/goldens/yields_page_success_first_page_coming_back.png differ diff --git a/test/app/create/yields/goldens/yields_page_success_first_page_left_button_disabled.png b/test/app/create/yields/goldens/yields_page_success_first_page_left_button_disabled.png new file mode 100644 index 0000000..b3cf5bc Binary files /dev/null and b/test/app/create/yields/goldens/yields_page_success_first_page_left_button_disabled.png differ diff --git a/test/app/create/yields/goldens/yields_page_success_hover_page_indicator.png b/test/app/create/yields/goldens/yields_page_success_hover_page_indicator.png new file mode 100644 index 0000000..4a7934a Binary files /dev/null and b/test/app/create/yields/goldens/yields_page_success_hover_page_indicator.png differ diff --git a/test/app/create/yields/goldens/yields_page_success_min_tvl_warning.png b/test/app/create/yields/goldens/yields_page_success_min_tvl_warning.png new file mode 100644 index 0000000..5777679 Binary files /dev/null and b/test/app/create/yields/goldens/yields_page_success_min_tvl_warning.png differ diff --git a/test/app/create/yields/goldens/yields_page_success_mobile.png b/test/app/create/yields/goldens/yields_page_success_mobile.png new file mode 100644 index 0000000..559699c Binary files /dev/null and b/test/app/create/yields/goldens/yields_page_success_mobile.png differ diff --git a/test/app/create/yields/goldens/yields_page_success_no_min_tvl_warning.png b/test/app/create/yields/goldens/yields_page_success_no_min_tvl_warning.png new file mode 100644 index 0000000..daf6e75 Binary files /dev/null and b/test/app/create/yields/goldens/yields_page_success_no_min_tvl_warning.png differ diff --git a/test/app/create/yields/goldens/yields_page_success_odd_number_of_yields_last_page.png b/test/app/create/yields/goldens/yields_page_success_odd_number_of_yields_last_page.png new file mode 100644 index 0000000..19cd99a Binary files /dev/null and b/test/app/create/yields/goldens/yields_page_success_odd_number_of_yields_last_page.png differ diff --git a/test/app/create/yields/goldens/yields_page_success_one_yield_only.png b/test/app/create/yields/goldens/yields_page_success_one_yield_only.png new file mode 100644 index 0000000..5bb26d8 Binary files /dev/null and b/test/app/create/yields/goldens/yields_page_success_one_yield_only.png differ diff --git a/test/app/create/yields/goldens/yields_page_success_second_page_left_button_enabled.png b/test/app/create/yields/goldens/yields_page_success_second_page_left_button_enabled.png new file mode 100644 index 0000000..26783e1 Binary files /dev/null and b/test/app/create/yields/goldens/yields_page_success_second_page_left_button_enabled.png differ diff --git a/test/app/create/yields/goldens/yields_page_success_sorted_by_30d.png b/test/app/create/yields/goldens/yields_page_success_sorted_by_30d.png new file mode 100644 index 0000000..359a54d Binary files /dev/null and b/test/app/create/yields/goldens/yields_page_success_sorted_by_30d.png differ diff --git a/test/app/create/yields/goldens/yields_page_success_sorted_by_7d.png b/test/app/create/yields/goldens/yields_page_success_sorted_by_7d.png new file mode 100644 index 0000000..4e499d6 Binary files /dev/null and b/test/app/create/yields/goldens/yields_page_success_sorted_by_7d.png differ diff --git a/test/app/create/yields/goldens/yields_page_success_sorted_by_90d.png b/test/app/create/yields/goldens/yields_page_success_sorted_by_90d.png new file mode 100644 index 0000000..3b39070 Binary files /dev/null and b/test/app/create/yields/goldens/yields_page_success_sorted_by_90d.png differ diff --git a/test/app/create/yields/goldens/yields_page_success_sorted_reset_page.png b/test/app/create/yields/goldens/yields_page_success_sorted_reset_page.png new file mode 100644 index 0000000..4e499d6 Binary files /dev/null and b/test/app/create/yields/goldens/yields_page_success_sorted_reset_page.png differ diff --git a/test/app/create/yields/goldens/yields_page_success_swipe_to_next_page.png b/test/app/create/yields/goldens/yields_page_success_swipe_to_next_page.png new file mode 100644 index 0000000..6b1a983 Binary files /dev/null and b/test/app/create/yields/goldens/yields_page_success_swipe_to_next_page.png differ diff --git a/test/app/create/yields/goldens/yields_page_success_two_yields_only.png b/test/app/create/yields/goldens/yields_page_success_two_yields_only.png new file mode 100644 index 0000000..1de3aa1 Binary files /dev/null and b/test/app/create/yields/goldens/yields_page_success_two_yields_only.png differ diff --git a/test/app/create/yields/yields_cubit_test.dart b/test/app/create/yields/yields_cubit_test.dart new file mode 100644 index 0000000..b1c510e --- /dev/null +++ b/test/app/create/yields/yields_cubit_test.dart @@ -0,0 +1,453 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:zup_app/app/app_cubit/app_cubit.dart'; +import 'package:zup_app/app/create/yields/yields_cubit.dart'; +import 'package:zup_app/core/cache.dart'; +import 'package:zup_app/core/dtos/pool_search_filters_dto.dart'; +import 'package:zup_app/core/dtos/pool_search_settings_dto.dart'; +import 'package:zup_app/core/dtos/yields_dto.dart'; +import 'package:zup_app/core/enums/networks.dart'; +import 'package:zup_app/core/repositories/yield_repository.dart'; +import 'package:zup_app/core/zup_analytics.dart'; + +import '../../../mocks.dart'; + +void main() { + late YieldsCubit sut; + late AppCubit appCubit; + late Cache appCache; + late YieldRepository yieldRepository; + late ZupAnalytics zupAnalytics; + + setUp(() { + appCubit = AppCubitMock(); + appCache = CacheMock(); + yieldRepository = YieldRepositoryMock(); + zupAnalytics = ZupAnalyticsMock(); + + registerFallbackValue(PoolSearchSettingsDto()); + registerFallbackValue(AppNetworks.mainnet); + + sut = YieldsCubit(appCubit, appCache, yieldRepository, zupAnalytics); + + when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.mainnet); + when(() => appCubit.isTestnetMode).thenReturn(false); + when(() => appCache.blockedProtocolsIds).thenReturn([]); + when(() => appCache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto()); + when( + () => zupAnalytics.logSearch( + token0: any(named: "token0"), + token1: any(named: "token1"), + group0: any(named: "group0"), + group1: any(named: "group1"), + network: any(named: "network"), + ), + ).thenAnswer((_) async => ()); + }); + + test("When calling 'fetchYields' it should first emit the loading state", () async { + const token0AddressOrId = "token0AddressOrId"; + const token1AddressOrId = "token1AddressOrId"; + const group0Id = "group0Id"; + const group1Id = "group1Id"; + + expectLater(sut.stream, emits(const YieldsState.loading())); + + await sut.fetchYields( + token0AddressOrId: token0AddressOrId, + token1AddressOrId: token1AddressOrId, + group0Id: group0Id, + group1Id: group1Id, + ); + }); + + test( + """When calling 'fetchYields' it should use the analytics to log the + search passing the ids and the network""", + () async { + const token0AddressOrId = "xabas1"; + const token1AddressOrId = "xabas2"; + const group0Id = "sabax1"; + const group1Id = "sabax2"; + const network = AppNetworks.sepolia; + + when(() => appCubit.selectedNetwork).thenReturn(network); + + await sut.fetchYields( + token0AddressOrId: token0AddressOrId, + token1AddressOrId: token1AddressOrId, + group0Id: group0Id, + group1Id: group1Id, + ); + + verify( + () => zupAnalytics.logSearch( + token0: token0AddressOrId, + token1: token1AddressOrId, + group0: group0Id, + group1: group1Id, + network: network.label, + ), + ).called(1); + }, + ); + + test( + """When calling 'fetchYields' with all networks as selected + network in app cubit, it should call the repository to get + all networks yields passing the correct ids and cached + user preferences for the search""", + () async { + final expectedPoolSearchSettings = PoolSearchSettingsDto( + allowV3Search: true, + allowV4Search: false, + minLiquidityUSD: 12618291, + ); + final expectedBlockedProtocolsIds = ["xasb12", "xasb13", "xasb14"]; + + const expectedToken0AddressOrId = "xabas1"; + const expectedToken1AddressOrId = "xabas2"; + const expectedGroup0Id = "sabax1"; + const expectedGroup1Id = "sabax2"; + + when(() => appCache.blockedProtocolsIds).thenReturn(expectedBlockedProtocolsIds); + when(() => appCache.getPoolSearchSettings()).thenReturn(expectedPoolSearchSettings); + when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); + + await sut.fetchYields( + token0AddressOrId: expectedToken0AddressOrId, + token1AddressOrId: expectedToken1AddressOrId, + group0Id: expectedGroup0Id, + group1Id: expectedGroup1Id, + ); + + verify( + () => yieldRepository.getAllNetworksYield( + group0Id: expectedGroup0Id, + group1Id: expectedGroup1Id, + token0InternalId: expectedToken0AddressOrId, + token1InternalId: expectedToken1AddressOrId, + blockedProtocolIds: expectedBlockedProtocolsIds, + searchSettings: expectedPoolSearchSettings, + testnetMode: any(named: "testnetMode"), + ), + ).called(1); + }, + ); + + test( + """When calling 'fetchYields' with a single network selected + in app cubit, it should call the repository to get + the selected network yields passing the correct ids and cached + user preferences for the search""", + () async { + final expectedPoolSearchSettings = PoolSearchSettingsDto( + allowV3Search: true, + allowV4Search: false, + minLiquidityUSD: 12618291, + ); + final expectedBlockedProtocolsIds = ["xasb12", "xasb13", "xasb14"]; + const selectedNetwork = AppNetworks.mainnet; + const expectedToken0AddressOrId = "xabas1"; + const expectedToken1AddressOrId = "xabas2"; + const expectedGroup0Id = "sabax1"; + const expectedGroup1Id = "sabax2"; + + when(() => appCache.blockedProtocolsIds).thenReturn(expectedBlockedProtocolsIds); + when(() => appCache.getPoolSearchSettings()).thenReturn(expectedPoolSearchSettings); + when(() => appCubit.selectedNetwork).thenReturn(selectedNetwork); + + await sut.fetchYields( + token0AddressOrId: expectedToken0AddressOrId, + token1AddressOrId: expectedToken1AddressOrId, + group0Id: expectedGroup0Id, + group1Id: expectedGroup1Id, + ); + + verify( + () => yieldRepository.getSingleNetworkYield( + group0Id: expectedGroup0Id, + group1Id: expectedGroup1Id, + token0Address: expectedToken0AddressOrId, + token1Address: expectedToken1AddressOrId, + blockedProtocolIds: expectedBlockedProtocolsIds, + searchSettings: expectedPoolSearchSettings, + network: selectedNetwork, + ), + ).called(1); + }, + ); + + test( + """When calling 'fetchYields' with all networks as selected + network in app cubit and passing to ignore min liquidity, + it should ignore the cached min liquidity preference + and pass 0 to the repository""", + () async { + final savedPoolSearchSettings = PoolSearchSettingsDto( + allowV3Search: true, + allowV4Search: false, + minLiquidityUSD: 12618291, + ); + + when(() => appCache.getPoolSearchSettings()).thenReturn(savedPoolSearchSettings); + when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); + + await sut.fetchYields( + token0AddressOrId: "", + token1AddressOrId: "", + group0Id: "", + group1Id: "", + ignoreMinLiquidity: true, + ); + + verify( + () => yieldRepository.getAllNetworksYield( + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + token0InternalId: any(named: "token0InternalId"), + token1InternalId: any(named: "token1InternalId"), + blockedProtocolIds: any(named: "blockedProtocolIds"), + searchSettings: savedPoolSearchSettings.copyWith(minLiquidityUSD: 0), + testnetMode: any(named: "testnetMode"), + ), + ).called(1); + }, + ); + + test( + """When calling 'fetchYields' with a single network selected + in app cubit and passing to ignore min liquidity, + it should ignore the cached min liquidity preference + and pass 0 to the repository""", + () async { + final savedPoolSearchSettings = PoolSearchSettingsDto( + allowV3Search: true, + allowV4Search: false, + minLiquidityUSD: 12618291, + ); + + when(() => appCache.getPoolSearchSettings()).thenReturn(savedPoolSearchSettings); + when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.mainnet); + + await sut.fetchYields( + token0AddressOrId: "", + token1AddressOrId: "", + group0Id: "", + group1Id: "", + ignoreMinLiquidity: true, + ); + + verify( + () => yieldRepository.getSingleNetworkYield( + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + network: any(named: "network"), + token0Address: any(named: "token0Address"), + token1Address: any(named: "token1Address"), + blockedProtocolIds: any(named: "blockedProtocolIds"), + searchSettings: savedPoolSearchSettings.copyWith(minLiquidityUSD: 0), + ), + ).called(1); + }, + ); + + test( + """When calling 'fetchYields' with testnet mode enabled + in the app cubit, and `all networks`, + it should pass it to the repository call""", + () async { + when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); + when(() => appCubit.isTestnetMode).thenReturn(true); + + await sut.fetchYields( + token0AddressOrId: "", + token1AddressOrId: "", + group0Id: "", + group1Id: "", + ignoreMinLiquidity: true, + ); + + verify( + () => yieldRepository.getAllNetworksYield( + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + token0InternalId: any(named: "token0InternalId"), + token1InternalId: any(named: "token1InternalId"), + blockedProtocolIds: any(named: "blockedProtocolIds"), + searchSettings: any(named: "searchSettings"), + testnetMode: true, + ), + ).called(1); + }, + ); + + test( + """When returning empty pools list for all networks search, + it should emit empty state passing the returned filters applied""", + () async { + const expectedFilters = PoolSearchFiltersDto( + allowedPoolTypes: ["xabasbab", "jajajajajajajajajaja"], + blockedProtocols: ["chaves", "kiko"], + minTvlUsd: 12618, + testnetMode: true, + ); + + when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); + + when( + () => yieldRepository.getAllNetworksYield( + token0InternalId: any(named: "token0InternalId"), + token1InternalId: any(named: "token1InternalId"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + searchSettings: any(named: "searchSettings"), + blockedProtocolIds: any(named: "blockedProtocolIds"), + ), + ).thenAnswer((_) async => const YieldsDto(pools: [], filters: expectedFilters)); + + await sut.fetchYields( + token0AddressOrId: "token0AddressOrId", + token1AddressOrId: "token1AddressOrId", + group0Id: "group0Id", + group1Id: "group1Id", + ); + + expect(sut.state, const YieldsState.noYields(filtersApplied: expectedFilters)); + }, + ); + + test( + """When returning empty pools list for single network search, + it should emit empty state passing the returned filters applied""", + () async { + const expectedFilters = PoolSearchFiltersDto( + allowedPoolTypes: ["xabasbab", "jajajajajajajajajaja"], + blockedProtocols: ["chaves", "kiko"], + minTvlUsd: 12618, + testnetMode: true, + ); + + when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.mainnet); + + when( + () => yieldRepository.getSingleNetworkYield( + network: any(named: "network"), + token0Address: any(named: "token0Address"), + token1Address: any(named: "token1Address"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + searchSettings: any(named: "searchSettings"), + blockedProtocolIds: any(named: "blockedProtocolIds"), + ), + ).thenAnswer((_) async => const YieldsDto(pools: [], filters: expectedFilters)); + + await sut.fetchYields( + token0AddressOrId: "token0AddressOrId", + token1AddressOrId: "token1AddressOrId", + group0Id: "group0Id", + group1Id: "group1Id", + ); + + expect(sut.state, const YieldsState.noYields(filtersApplied: expectedFilters)); + }, + ); + + test( + """When returning a non empty pools list for a all networks + yield search, it should emit the success state,passing the + whole DTO to the state""", + () async { + final expectedYields = YieldsDto.fixture(); + + when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.allNetworks); + + when( + () => yieldRepository.getAllNetworksYield( + token0InternalId: any(named: "token0InternalId"), + token1InternalId: any(named: "token1InternalId"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + searchSettings: any(named: "searchSettings"), + blockedProtocolIds: any(named: "blockedProtocolIds"), + testnetMode: any(named: "testnetMode"), + ), + ).thenAnswer((_) async => expectedYields); + + await sut.fetchYields( + token0AddressOrId: "token0AddressOrId", + token1AddressOrId: "token1AddressOrId", + group0Id: "group0Id", + group1Id: "group1Id", + ); + + expect(sut.state, YieldsState.success(expectedYields)); + }, + ); + + test( + """When returning a non empty pools list for a single network + yield search, it should emit the success state, passing the + whole DTO to the state""", + () async { + final expectedYields = YieldsDto.fixture(); + + when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.mainnet); + + when( + () => yieldRepository.getSingleNetworkYield( + network: any(named: "network"), + token0Address: any(named: "token0Address"), + token1Address: any(named: "token1Address"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + searchSettings: any(named: "searchSettings"), + blockedProtocolIds: any(named: "blockedProtocolIds"), + ), + ).thenAnswer((_) async => expectedYields); + + await sut.fetchYields( + token0AddressOrId: "token0AddressOrId", + token1AddressOrId: "token1AddressOrId", + group0Id: "group0Id", + group1Id: "group1Id", + ); + + expect(sut.state, YieldsState.success(expectedYields)); + }, + ); + + test( + """When throwing an error while getting yields, it should emit + the error state passing the error and the stack trace""", + () async { + const expectedErrorString = "xabas"; + const expectedErrorStackTraceString = "sabaxStackStace"; + + when(() => appCubit.selectedNetwork).thenReturn(AppNetworks.mainnet); + + when( + () => yieldRepository.getSingleNetworkYield( + network: any(named: "network"), + token0Address: any(named: "token0Address"), + token1Address: any(named: "token1Address"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + searchSettings: any(named: "searchSettings"), + blockedProtocolIds: any(named: "blockedProtocolIds"), + ), + ).thenAnswer( + (_) => Error.throwWithStackTrace(expectedErrorString, StackTrace.fromString(expectedErrorStackTraceString)), + ); + + await sut.fetchYields( + token0AddressOrId: "token0AddressOrId", + token1AddressOrId: "token1AddressOrId", + group0Id: "group0Id", + group1Id: "group1Id", + ); + + expect(sut.state, const YieldsState.error(expectedErrorString, expectedErrorStackTraceString)); + }, + ); +} diff --git a/test/app/create/yields/yields_page_test.dart b/test/app/create/yields/yields_page_test.dart new file mode 100644 index 0000000..44bab7a --- /dev/null +++ b/test/app/create/yields/yields_page_test.dart @@ -0,0 +1,1104 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:golden_toolkit/golden_toolkit.dart'; +import 'package:lottie/lottie.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:web3kit/core/ethereum_constants.dart'; +import 'package:zup_app/app/app_cubit/app_cubit.dart'; +import 'package:zup_app/app/create/yields/yields_cubit.dart'; +import 'package:zup_app/app/create/yields/yields_page.dart'; +import 'package:zup_app/core/cache.dart'; +import 'package:zup_app/core/dtos/pool_search_filters_dto.dart'; +import 'package:zup_app/core/dtos/pool_search_settings_dto.dart'; +import 'package:zup_app/core/dtos/token_dto.dart'; +import 'package:zup_app/core/dtos/yield_dto.dart'; +import 'package:zup_app/core/dtos/yields_dto.dart'; +import 'package:zup_app/core/enums/networks.dart'; +import 'package:zup_app/core/enums/yield_timeframe.dart'; +import 'package:zup_app/core/injections.dart'; +import 'package:zup_app/core/zup_navigator.dart'; +import 'package:zup_app/core/zup_route_params_names.dart'; +import 'package:zup_app/gen/assets.gen.dart'; +import 'package:zup_app/widgets/yield_card.dart'; +import 'package:zup_app/widgets/zup_cached_image.dart'; +import 'package:zup_core/extensions/extensions.dart'; + +import '../../../golden_config.dart'; +import '../../../mocks.dart'; + +void main() { + late YieldsCubit cubit; + late ZupNavigator navigator; + late AppCubit appCubit; + late Cache appCache; + + setUp(() { + registerFallbackValue(YieldDto.fixture()); + registerFallbackValue(YieldTimeFrame.day); + + navigator = ZupNavigatorMock(); + cubit = YieldsCubitMock(); + appCache = CacheMock(); + appCubit = AppCubitMock(); + + inject.registerFactory(() => navigator); + inject.registerFactory(() => appCache); + inject.registerFactory(() => appCubit); + + inject.registerFactory( + () => Assets.lotties.empty.lottie(animate: false), + instanceName: InjectInstanceNames.lottieEmpty, + ); + + inject.registerFactory( + () => Assets.lotties.radar.lottie(animate: false), + instanceName: InjectInstanceNames.lottieRadar, + ); + + inject.registerFactory( + () => Assets.lotties.numbers.lottie(animate: false), + instanceName: InjectInstanceNames.lottieNumbers, + ); + + inject.registerFactory( + () => Assets.lotties.matching.lottie(animate: false), + instanceName: InjectInstanceNames.lottieMatching, + ); + + inject.registerFactory( + () => Assets.lotties.list.lottie(animate: false), + instanceName: InjectInstanceNames.lottieList, + ); + + inject.registerFactory( + () => GoldenConfig.scrollController, + instanceName: InjectInstanceNames.appScrollController, + ); + + inject.registerFactory(() => mockZupCachedImage()); + + inject.registerFactory(() => false, instanceName: InjectInstanceNames.infinityAnimationAutoPlay); + + when(() => navigator.navigateToNewPosition()).thenAnswer((_) => Future.value()); + when(() => appCache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 122)); + when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.sepolia); + when(() => cubit.stream).thenAnswer((_) => const Stream.empty()); + when(() => cubit.state).thenReturn(const YieldsState.initial()); + when( + () => cubit.fetchYields( + token0AddressOrId: any(named: "token0AddressOrId"), + token1AddressOrId: any(named: "token1AddressOrId"), + group0Id: any(named: "group0Id"), + group1Id: any(named: "group1Id"), + ignoreMinLiquidity: any(named: "ignoreMinLiquidity"), + ), + ).thenAnswer((_) => Future.value()); + }); + + tearDown(() => inject.reset()); + + Future goldenBuilder({bool isMobile = false}) async { + return await goldenDeviceBuilder( + BlocProvider.value(value: cubit, child: const YieldsPage()), + device: isMobile ? GoldenDevice.mobile : GoldenDevice.pc, + ); + } + + zGoldenTest( + """When initializing the page, it should call the app cubit + to set the app network for the one defined in the url params""", + (tester) async { + when(() => navigator.getQueryParam(YieldsRouteParamsNames().network)).thenReturn(AppNetworks.base.name); + + await tester.runAsync(() async { + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + }); + + verify(() => appCubit.updateAppNetwork(AppNetworks.base)).called(1); + }, + ); + + zGoldenTest( + """When initializing the page, it should call the yields cubit + to get the yields for the token0, token1 and group0, group1 defined + in the url params""", + (tester) async { + const token0Id = "Xabas1"; + const token1Id = "Xabas2"; + const group0Id = "121"; + const group1Id = "gta1"; + + when(() => navigator.getQueryParam(YieldsRouteParamsNames().token0)).thenReturn(token0Id); + when(() => navigator.getQueryParam(YieldsRouteParamsNames().token1)).thenReturn(token1Id); + when(() => navigator.getQueryParam(YieldsRouteParamsNames().group0)).thenReturn(group0Id); + when(() => navigator.getQueryParam(YieldsRouteParamsNames().group1)).thenReturn(group1Id); + + await tester.runAsync(() async { + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + }); + + verify( + () => cubit.fetchYields( + token0AddressOrId: token0Id, + token1AddressOrId: token1Id, + group0Id: group0Id, + group1Id: group1Id, + ignoreMinLiquidity: false, + ), + ).called(1); + }, + ); + + zGoldenTest( + "When the cubit state is error, it should show the error state", + goldenFileName: "yields_page_error_state", + (tester) async { + when(() => cubit.state).thenReturn(const YieldsState.error("", "")); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + """When the user clicks the helper button in the error state, + it try to fetch yields again, using the params from the url""", + (tester) async { + const token0Id = "toko-11"; + const token1Id = "toko-12"; + const group0Id = "gorpo-1"; + const group1Id = "gorpo-2"; + + when(() => navigator.getQueryParam(YieldsRouteParamsNames().token0)).thenReturn(token0Id); + when(() => navigator.getQueryParam(YieldsRouteParamsNames().token1)).thenReturn(token1Id); + when(() => navigator.getQueryParam(YieldsRouteParamsNames().group0)).thenReturn(group0Id); + when(() => navigator.getQueryParam(YieldsRouteParamsNames().group1)).thenReturn(group1Id); + + when(() => cubit.state).thenReturn(const YieldsState.error("", "")); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("help-button"))); + await tester.pumpAndSettle(); + + verify( + () => cubit.fetchYields( + token0AddressOrId: token0Id, + token1AddressOrId: token1Id, + group0Id: group0Id, + group1Id: group1Id, + ignoreMinLiquidity: false, + ), + ).called( + 2, + ); // the first call is when the page is initialized and the second is when the user clicks the help button + }, + ); + + zGoldenTest( + "When the cubit state is no yields, it should show the no yields state", + goldenFileName: "yields_page_no_yields_state", + (tester) async { + when( + () => cubit.state, + ).thenReturn(const YieldsState.noYields(filtersApplied: PoolSearchFiltersDto(minTvlUsd: 0))); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + """When the state is no yields, and the minTvlUSD in the filters is greater than 0, + it should an alert that says the min tvl usd is set""", + goldenFileName: "yields_page_no_yields_state_min_tvl_set", + (tester) async { + when( + () => cubit.state, + ).thenReturn(const YieldsState.noYields(filtersApplied: PoolSearchFiltersDto(minTvlUsd: 12517821))); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + """When clicking to search all pools in the min tvl usd alert at the no yields state, + it should call the cubit to get yields again with the same params from the url, + but setting true to ignore min liquidity""", + (tester) async { + const token0Id = "tok-21"; + const token1Id = "tok-98"; + const group0Id = "gpo-44"; + const group1Id = "gorpo-75"; + + when(() => navigator.getQueryParam(YieldsRouteParamsNames().token0)).thenReturn(token0Id); + when(() => navigator.getQueryParam(YieldsRouteParamsNames().token1)).thenReturn(token1Id); + when(() => navigator.getQueryParam(YieldsRouteParamsNames().group0)).thenReturn(group0Id); + when(() => navigator.getQueryParam(YieldsRouteParamsNames().group1)).thenReturn(group1Id); + when( + () => cubit.state, + ).thenReturn(const YieldsState.noYields(filtersApplied: PoolSearchFiltersDto(minTvlUsd: 12517821))); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("zup-inline-action-button-button"))); + await tester.pumpAndSettle(); + + verify( + () => cubit.fetchYields( + token0AddressOrId: token0Id, + token1AddressOrId: token1Id, + group0Id: group0Id, + group1Id: group1Id, + ignoreMinLiquidity: true, + ), + ).called(1); + }, + ); + + zGoldenTest( + """When clicking the helper button in the no yields state, it should try to navigate back + to new position page""", + (tester) async { + when(() => cubit.state).thenReturn(YieldsState.noYields(filtersApplied: PoolSearchFiltersDto.fixture())); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("help-button"))); + await tester.pumpAndSettle(); + + verify(() => navigator.navigateToNewPosition()).called(1); + }, + ); + + zGoldenTest( + """When the cubit state is success, and the yields page is the first one, + the button to move to previous page (left button) should be disabled""", + goldenFileName: "yields_page_success_first_page_left_button_disabled", + (tester) async { + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith( + pools: [YieldDto.fixture(), YieldDto.fixture(), YieldDto.fixture(), YieldDto.fixture(), YieldDto.fixture()], + ), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + """When the page is the second one, the button to move to previous page (left button) should be enabled""", + goldenFileName: "yields_page_success_second_page_left_button_enabled", + (tester) async { + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith( + pools: [ + YieldDto.fixture().copyWith(yield24h: 100), + YieldDto.fixture().copyWith(yield24h: 200), + YieldDto.fixture().copyWith(yield24h: 300), + YieldDto.fixture().copyWith(yield24h: 400), + YieldDto.fixture().copyWith(yield24h: 500), + ], + ), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("move-to-next-yields-page-button"))); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + """When the page is the second one, and clicking the button to move to previous page (left button) + it should move to the first page again""", + goldenFileName: "yields_page_success_first_page_coming_back", + (tester) async { + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith( + pools: [ + YieldDto.fixture().copyWith(yield24h: 100), + YieldDto.fixture().copyWith(yield24h: 200), + YieldDto.fixture().copyWith(yield24h: 300), + YieldDto.fixture().copyWith(yield24h: 400), + YieldDto.fixture().copyWith(yield24h: 500), + ], + ), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("move-to-next-yields-page-button"))); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("move-to-previous-yields-page-button"))); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + """When the device has mobile size, the success state should be rendered with only one + yield per page, without the pagination buttons""", + goldenFileName: "yields_page_success_mobile", + (tester) async { + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith( + pools: [ + YieldDto.fixture().copyWith(yield24h: 100), + YieldDto.fixture().copyWith(yield24h: 200), + YieldDto.fixture().copyWith(yield24h: 300), + YieldDto.fixture().copyWith(yield24h: 400), + YieldDto.fixture().copyWith(yield24h: 500), + ], + ), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder(isMobile: true)); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + """When clicking the back button, to navigate back to select tokens, + it should call the navigator to navigate to new position screen""", + (tester) async { + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith( + pools: [YieldDto.fixture(), YieldDto.fixture(), YieldDto.fixture(), YieldDto.fixture(), YieldDto.fixture()], + ), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("back-button"))); + await tester.pumpAndSettle(); + + verify(() => navigator.navigateToNewPosition()).called(1); + }, + ); + + zGoldenTest( + """When clicking the 7d button in the timeframe selector, in the success state, + it should sort the yields by 7d yield (Z-A)""", + goldenFileName: "yields_page_success_sorted_by_7d", + (tester) async { + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith( + pools: [ + YieldDto.fixture().copyWith(yield7d: 1000, yield24h: 1), + YieldDto.fixture().copyWith(yield7d: 2000, yield24h: 10), + YieldDto.fixture().copyWith(yield7d: 3000, yield24h: 100), + YieldDto.fixture().copyWith(yield7d: 4000, yield24h: 1000), + YieldDto.fixture().copyWith(yield7d: 5000, yield24h: 1000), + ], + ), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(Key("${YieldTimeFrame.week.name}-timeframe-button"))); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + """When clicking the 30d button in the timeframe selector, in the success state, + it should sort the yields by 30d yield (Z-A)""", + goldenFileName: "yields_page_success_sorted_by_30d", + (tester) async { + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith( + pools: [ + YieldDto.fixture().copyWith(yield7d: 1000, yield24h: 1, yield30d: 2000), + YieldDto.fixture().copyWith(yield7d: 2000, yield24h: 10, yield30d: 4000), + YieldDto.fixture().copyWith(yield7d: 3000, yield24h: 100, yield30d: 6000), + YieldDto.fixture().copyWith(yield7d: 4000, yield24h: 1000, yield30d: 8000), + YieldDto.fixture().copyWith(yield7d: 5000, yield24h: 1000, yield30d: 10000), + ], + ), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(Key("${YieldTimeFrame.month.name}-timeframe-button"))); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + """When clicking the 90d button in the timeframe selector, in the success state, + it should sort the yields by 90d yield (Z-A)""", + goldenFileName: "yields_page_success_sorted_by_90d", + (tester) async { + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith( + pools: [ + YieldDto.fixture().copyWith(yield7d: 1000, yield24h: 1, yield30d: 2000, yield90d: 4000), + YieldDto.fixture().copyWith(yield7d: 2000, yield24h: 10, yield30d: 4000, yield90d: 8000), + YieldDto.fixture().copyWith(yield7d: 3000, yield24h: 100, yield30d: 6000, yield90d: 12000), + YieldDto.fixture().copyWith(yield7d: 4000, yield24h: 1000, yield30d: 8000, yield90d: 16000), + YieldDto.fixture().copyWith(yield7d: 5000, yield24h: 1000, yield30d: 10000, yield90d: 20000), + ], + ), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(Key("${YieldTimeFrame.threeMonth.name}-timeframe-button"))); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + """When the user move a page forward in the yields page, and then select another timeframe, + it should sort the yields by the selected timeframe (Z-A) and reset the page to the first page""", + goldenFileName: "yields_page_success_sorted_reset_page", + (tester) async { + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith( + pools: [ + YieldDto.fixture().copyWith(yield7d: 1000, yield24h: 1, yield30d: 2000, yield90d: 4000), + YieldDto.fixture().copyWith(yield7d: 2000, yield24h: 10, yield30d: 4000, yield90d: 8000), + YieldDto.fixture().copyWith(yield7d: 3000, yield24h: 100, yield30d: 6000, yield90d: 12000), + YieldDto.fixture().copyWith(yield7d: 4000, yield24h: 1000, yield30d: 8000, yield90d: 16000), + YieldDto.fixture().copyWith(yield7d: 5000, yield24h: 1000, yield30d: 10000, yield90d: 20000), + ], + ), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("move-to-next-yields-page-button"))); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(Key("${YieldTimeFrame.week.name}-timeframe-button"))); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + """When the device is mobile, and the user swipe a yield from the right to the left, + it should pass to the next yield page""", + goldenFileName: "yields_page_success_swipe_to_next_page", + (tester) async { + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith( + pools: [ + YieldDto.fixture().copyWith(yield24h: 100), + YieldDto.fixture().copyWith(yield24h: 200), + YieldDto.fixture().copyWith(yield24h: 300), + YieldDto.fixture().copyWith(yield24h: 400), + YieldDto.fixture().copyWith(yield24h: 500), + ], + ), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder(isMobile: true)); + await tester.pumpAndSettle(); + + await tester.drag(find.byType(YieldCard), const Offset(-350, 0)); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + "When having a odd number of yields, the last yield page should be only one yield card", + goldenFileName: "yields_page_success_odd_number_of_yields_last_page", + (tester) async { + final yields = [ + YieldDto.fixture().copyWith(yield24h: 100), + YieldDto.fixture().copyWith(yield24h: 200), + YieldDto.fixture().copyWith(yield24h: 300), + YieldDto.fixture().copyWith(yield24h: 400), + YieldDto.fixture().copyWith(yield24h: 400), + ]; + when(() => cubit.state).thenReturn(YieldsState.success(YieldsDto.fixture().copyWith(pools: yields))); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + for (int i = 0; i < (yields.length / 2).ceil(); i++) { + await tester.tap(find.byKey(const Key("move-to-next-yields-page-button"))); + await tester.pumpAndSettle(); + } + }, + ); + + zGoldenTest( + """When having only one yield for the selected tokens, + the page controller and page indicator should not be visible""", + goldenFileName: "yields_page_success_one_yield_only", + (tester) async { + final yields = [YieldDto.fixture().copyWith(yield24h: 100)]; + when(() => cubit.state).thenReturn(YieldsState.success(YieldsDto.fixture().copyWith(pools: yields))); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + """When having only two yields for the selected tokens, + the page controller and page indicator should not be visible""", + goldenFileName: "yields_page_success_two_yields_only", + (tester) async { + final yields = [YieldDto.fixture().copyWith(yield24h: 100), YieldDto.fixture().copyWith(yield24h: 200)]; + when(() => cubit.state).thenReturn(YieldsState.success(YieldsDto.fixture().copyWith(pools: yields))); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + """When hovering the page indicator in the success state, + the page indicator size should be bigger""", + goldenFileName: "yields_page_success_hover_page_indicator", + (tester) async { + final yields = List.generate(10, (_) => YieldDto.fixture().copyWith(yield24h: 100)); + + when(() => cubit.state).thenReturn(YieldsState.success(YieldsDto.fixture().copyWith(pools: yields))); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.hover(find.byKey(const Key("yield-page-indicator-3"))); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + """When clicking the page indicator in the success state, + it should navigate to the corresponding page""", + goldenFileName: "yields_page_success_click_page_indicator", + (tester) async { + final yields = List.generate(10, (index) => YieldDto.fixture().copyWith(yield24h: 100 * index)); + + when(() => cubit.state).thenReturn(YieldsState.success(YieldsDto.fixture().copyWith(pools: yields))); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("yield-page-indicator-3"))); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + """When the search filters passed in the succes state has a min tvl with more than zero, + it should show a message + button below the yields cards explaining that the + search was only done for pools with a min tvl of the specified USD""", + goldenFileName: "yields_page_success_min_tvl_warning", + (tester) async { + final yields = List.generate(2, (index) => YieldDto.fixture().copyWith(yield24h: 100 * index)); + + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith(pools: yields, filters: const PoolSearchFiltersDto(minTvlUsd: 99898)), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + """When clicking 'search all pools' in the min TVL Usd set warning, + it should refetch pools without the min usd filter (zero) but use + the same params got from the url""", + (tester) async { + final yields = List.generate(2, (index) => YieldDto.fixture().copyWith(yield24h: 100 * index)); + const token0Id = "xabas"; + const token1Id = "sabax"; + const group0Id = "iuyip"; + const group1Id = "sayiji"; + + when(() => navigator.getQueryParam(YieldsRouteParamsNames().token0)).thenReturn(token0Id); + when(() => navigator.getQueryParam(YieldsRouteParamsNames().token1)).thenReturn(token1Id); + when(() => navigator.getQueryParam(YieldsRouteParamsNames().group0)).thenReturn(group0Id); + when(() => navigator.getQueryParam(YieldsRouteParamsNames().group1)).thenReturn(group1Id); + + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith(pools: yields, filters: const PoolSearchFiltersDto(minTvlUsd: 99898)), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("zup-inline-action-button-button"))); + await tester.pumpAndSettle(); + + verify( + () => cubit.fetchYields( + token0AddressOrId: token0Id, + token1AddressOrId: token1Id, + group0Id: group0Id, + group1Id: group1Id, + ignoreMinLiquidity: true, + ), + ).called(1); + }, + ); + + zGoldenTest( + """When the search filters passed in the succes state has a min tvl as zero, + but the saved one in the cache, was a min tvl greater than zero, + it should show a message + button below the yields cards explaining + that the search was fone for all pools without a min tvl""", + goldenFileName: "yields_page_success_no_min_tvl_warning", + (tester) async { + final yields = List.generate(2, (index) => YieldDto.fixture().copyWith(yield24h: 100 * index)); + + when(() => appCache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 122)); + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith(pools: yields, filters: const PoolSearchFiltersDto(minTvlUsd: 0)), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + }, + ); + + zGoldenTest( + """When clicking the button of the warning that the search is displaying all pools, + without min tvl, it should refetch pools with the flag to ignore min usd false and use + the same params got from the url at the initial request""", + (tester) async { + final yields = List.generate(2, (index) => YieldDto.fixture().copyWith(yield24h: 100 * index)); + const token0Id = "xabas"; + const token1Id = "sabax"; + const group0Id = "iuyip"; + const group1Id = "sayiji"; + + when(() => navigator.getQueryParam(YieldsRouteParamsNames().token0)).thenReturn(token0Id); + when(() => navigator.getQueryParam(YieldsRouteParamsNames().token1)).thenReturn(token1Id); + when(() => navigator.getQueryParam(YieldsRouteParamsNames().group0)).thenReturn(group0Id); + when(() => navigator.getQueryParam(YieldsRouteParamsNames().group1)).thenReturn(group1Id); + + when(() => appCache.getPoolSearchSettings()).thenReturn(PoolSearchSettingsDto(minLiquidityUSD: 122)); + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith(pools: yields, filters: const PoolSearchFiltersDto(minTvlUsd: 0)), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key("zup-inline-action-button-button"))); + await tester.pumpAndSettle(); + + verify( + () => cubit.fetchYields( + token0AddressOrId: token0Id, + token1AddressOrId: token1Id, + group0Id: group0Id, + group1Id: group1Id, + ignoreMinLiquidity: false, + ), + ).called(2); // the first one is the initial request and the second one is the refetch + }, + ); + + zGoldenTest( + """When clicking the deposit button in the yield card, + it should call the navigator to navigate to the deposit page + passing the yield pool that was clicked""", + (tester) async { + final yields = List.generate( + 2, + (index) => YieldDto.fixture().copyWith(yield24h: 100 * index, poolAddress: "0x$index"), + ); + + when( + () => navigator.navigateToDeposit( + yieldPool: any(named: "yieldPool"), + selectedTimeframe: any(named: "selectedTimeframe"), + parseWrappedToNative: any(named: "parseWrappedToNative"), + ), + ).thenAnswer((invocation) async {}); + + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith(pools: yields, filters: const PoolSearchFiltersDto(minTvlUsd: 0)), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(Key("deposit-button-${yields[0].poolAddress}"))); + await tester.pumpAndSettle(); + + verify( + () => navigator.navigateToDeposit( + yieldPool: yields[0], + parseWrappedToNative: any(named: "parseWrappedToNative"), + selectedTimeframe: any(named: "selectedTimeframe"), + ), + ).called(1); + }, + ); + + zGoldenTest( + """When clicking the deposit button in the yield card, + and the selected timeframe is week, it should pass the + week timeframe to the navigator to navigate to the deposit page""", + (tester) async { + final yields = List.generate( + 2, + (index) => YieldDto.fixture().copyWith(yield24h: 100 * index, poolAddress: "0x$index"), + ); + + when( + () => navigator.navigateToDeposit( + yieldPool: any(named: "yieldPool"), + selectedTimeframe: any(named: "selectedTimeframe"), + parseWrappedToNative: any(named: "parseWrappedToNative"), + ), + ).thenAnswer((invocation) async {}); + + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith(pools: yields, filters: const PoolSearchFiltersDto(minTvlUsd: 0)), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(Key("${YieldTimeFrame.week.name}-timeframe-button"))); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(Key("deposit-button-${yields[0].poolAddress}"))); + await tester.pumpAndSettle(); + + verify( + () => navigator.navigateToDeposit( + yieldPool: any(named: "yieldPool"), + parseWrappedToNative: any(named: "parseWrappedToNative"), + selectedTimeframe: YieldTimeFrame.week, + ), + ).called(1); + }, + ); + + zGoldenTest( + """When clicking the deposit button in the yield card, + and the selected timeframe is month, it should pass the + month timeframe to the navigator to navigate to the deposit page""", + (tester) async { + final yields = List.generate( + 2, + (index) => YieldDto.fixture().copyWith(yield24h: 100 * index, poolAddress: "0x$index"), + ); + + when( + () => navigator.navigateToDeposit( + yieldPool: any(named: "yieldPool"), + selectedTimeframe: any(named: "selectedTimeframe"), + parseWrappedToNative: any(named: "parseWrappedToNative"), + ), + ).thenAnswer((invocation) async {}); + + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith(pools: yields, filters: const PoolSearchFiltersDto(minTvlUsd: 0)), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(Key("${YieldTimeFrame.month.name}-timeframe-button"))); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(Key("deposit-button-${yields[0].poolAddress}"))); + await tester.pumpAndSettle(); + + verify( + () => navigator.navigateToDeposit( + yieldPool: any(named: "yieldPool"), + parseWrappedToNative: any(named: "parseWrappedToNative"), + selectedTimeframe: YieldTimeFrame.month, + ), + ).called(1); + }, + ); + + zGoldenTest( + """When clicking the deposit button in the yield card, + and the user didn't select any timeframe, the default + passed to the navigator to navigate to the deposit page + should be the day timeframe""", + (tester) async { + final yields = List.generate( + 2, + (index) => YieldDto.fixture().copyWith(yield24h: 100 * index, poolAddress: "0x$index"), + ); + + when( + () => navigator.navigateToDeposit( + yieldPool: any(named: "yieldPool"), + selectedTimeframe: any(named: "selectedTimeframe"), + parseWrappedToNative: any(named: "parseWrappedToNative"), + ), + ).thenAnswer((invocation) async {}); + + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith(pools: yields, filters: const PoolSearchFiltersDto(minTvlUsd: 0)), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(Key("${YieldTimeFrame.day.name}-timeframe-button"))); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(Key("deposit-button-${yields[0].poolAddress}"))); + await tester.pumpAndSettle(); + + verify( + () => navigator.navigateToDeposit( + yieldPool: any(named: "yieldPool"), + parseWrappedToNative: any(named: "parseWrappedToNative"), + selectedTimeframe: YieldTimeFrame.day, + ), + ).called(1); + }, + ); + + zGoldenTest( + """When clicking the deposit button in the yield card, + and the address of the token0 in the pool for the network + is zero, it should pass parseWrappedToNative as true + to the navigator to navigate to the deposit page""", + (tester) async { + const yieldNetwork = AppNetworks.sepolia; + + final yields = List.generate( + 2, + (index) => YieldDto.fixture().copyWith( + yield24h: 100 * index, + poolAddress: "0x$index", + chainId: yieldNetwork.chainId, + token0: TokenDto(addresses: {yieldNetwork.chainId: EthereumConstants.zeroAddress}), + ), + ); + + when( + () => navigator.navigateToDeposit( + yieldPool: any(named: "yieldPool"), + selectedTimeframe: any(named: "selectedTimeframe"), + parseWrappedToNative: any(named: "parseWrappedToNative"), + ), + ).thenAnswer((invocation) async {}); + + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith(pools: yields, filters: const PoolSearchFiltersDto(minTvlUsd: 0)), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(Key("deposit-button-${yields[0].poolAddress}"))); + await tester.pumpAndSettle(); + + verify( + () => navigator.navigateToDeposit( + yieldPool: any(named: "yieldPool"), + parseWrappedToNative: true, + selectedTimeframe: any(named: "selectedTimeframe"), + ), + ).called(1); + }, + ); + + zGoldenTest( + """When clicking the deposit button in the yield card, + and the address of the token1 in the pool for the network + is zero, it should pass parseWrappedToNative as true + to the navigator to navigate to the deposit page""", + (tester) async { + const yieldNetwork = AppNetworks.sepolia; + + final yields = List.generate( + 2, + (index) => YieldDto.fixture().copyWith( + yield24h: 100 * index, + poolAddress: "0x$index", + chainId: yieldNetwork.chainId, + token1: TokenDto(addresses: {yieldNetwork.chainId: EthereumConstants.zeroAddress}), + ), + ); + + when( + () => navigator.navigateToDeposit( + yieldPool: any(named: "yieldPool"), + selectedTimeframe: any(named: "selectedTimeframe"), + parseWrappedToNative: any(named: "parseWrappedToNative"), + ), + ).thenAnswer((invocation) async {}); + + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith(pools: yields, filters: const PoolSearchFiltersDto(minTvlUsd: 0)), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(Key("deposit-button-${yields[0].poolAddress}"))); + await tester.pumpAndSettle(); + + verify( + () => navigator.navigateToDeposit( + yieldPool: any(named: "yieldPool"), + parseWrappedToNative: true, + selectedTimeframe: any(named: "selectedTimeframe"), + ), + ).called(1); + }, + ); + + zGoldenTest( + """When clicking the deposit button in the yield card, + and the address of the token1 in the pool for the network + is wrapped native, it should not pass parseWrappedToNative as true + to the navigator to navigate to the deposit page""", + (tester) async { + const yieldNetwork = AppNetworks.sepolia; + + final yields = List.generate( + 2, + (index) => YieldDto.fixture().copyWith( + yield24h: 100 * index, + poolAddress: "0x$index", + chainId: yieldNetwork.chainId, + token1: TokenDto(addresses: {yieldNetwork.chainId: yieldNetwork.wrappedNativeTokenAddress}), + ), + ); + + when( + () => navigator.navigateToDeposit( + yieldPool: any(named: "yieldPool"), + selectedTimeframe: any(named: "selectedTimeframe"), + parseWrappedToNative: any(named: "parseWrappedToNative"), + ), + ).thenAnswer((invocation) async {}); + + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith(pools: yields, filters: const PoolSearchFiltersDto(minTvlUsd: 0)), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(Key("deposit-button-${yields[0].poolAddress}"))); + await tester.pumpAndSettle(); + + verify( + () => navigator.navigateToDeposit( + yieldPool: any(named: "yieldPool"), + parseWrappedToNative: false, + selectedTimeframe: any(named: "selectedTimeframe"), + ), + ).called(1); + }, + ); + + zGoldenTest( + """When clicking the deposit button in the yield card, + and the address of the token0 in the pool for the network + is wrapped native, it should not pass parseWrappedToNative as true + to the navigator to navigate to the deposit page""", + (tester) async { + const yieldNetwork = AppNetworks.sepolia; + + final yields = List.generate( + 2, + (index) => YieldDto.fixture().copyWith( + yield24h: 100 * index, + poolAddress: "0x$index", + chainId: yieldNetwork.chainId, + token0: TokenDto(addresses: {yieldNetwork.chainId: yieldNetwork.wrappedNativeTokenAddress}), + ), + ); + + when( + () => navigator.navigateToDeposit( + yieldPool: any(named: "yieldPool"), + selectedTimeframe: any(named: "selectedTimeframe"), + parseWrappedToNative: any(named: "parseWrappedToNative"), + ), + ).thenAnswer((invocation) async {}); + + when(() => cubit.state).thenReturn( + YieldsState.success( + YieldsDto.fixture().copyWith(pools: yields, filters: const PoolSearchFiltersDto(minTvlUsd: 0)), + ), + ); + + await tester.pumpDeviceBuilder(await goldenBuilder()); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(Key("deposit-button-${yields[0].poolAddress}"))); + await tester.pumpAndSettle(); + + verify( + () => navigator.navigateToDeposit( + yieldPool: any(named: "yieldPool"), + parseWrappedToNative: false, + selectedTimeframe: any(named: "selectedTimeframe"), + ), + ).called(1); + }, + ); +} diff --git a/test/core/dtos/yield_dto_test.dart b/test/core/dtos/yield_dto_test.dart index 0b96b7f..63fe0d1 100644 --- a/test/core/dtos/yield_dto_test.dart +++ b/test/core/dtos/yield_dto_test.dart @@ -120,4 +120,80 @@ void main() { EthereumConstants.zeroAddress, ); }); + + test( + """When using 'timeframedYieldFormatted' passing day timeframe, + and the 24h yield is not zero it should return me the formatted + 24h yield with percent sign""", + () { + final yieldDto = YieldDto.fixture().copyWith(yield24h: 2.3213); + expect(yieldDto.timeframedYieldFormatted(YieldTimeFrame.day), "2.3%"); + }, + ); + + test( + """When using 'timeframedYieldFormatted' passing week timeframe, + and the 7d yield is not zero it should return me the formatted + 7d yield with percent sign""", + () { + final yieldDto = YieldDto.fixture().copyWith(yield7d: 121.335); + expect(yieldDto.timeframedYieldFormatted(YieldTimeFrame.week), "121.3%"); + }, + ); + + test( + """When using 'timeframedYieldFormatted' passing month timeframe, + and the 30d yield is not zero it should return me the formatted + 30d yield with percent sign""", + () { + final yieldDto = YieldDto.fixture().copyWith(yield30d: 11.335); + expect(yieldDto.timeframedYieldFormatted(YieldTimeFrame.month), "11.3%"); + }, + ); + + test( + """When using 'timeframedYieldFormatted' passing a three month timeframe, + and the 90d yield is not zero it should return me the formatted + 90d yield with percent sign""", + () { + final yieldDto = YieldDto.fixture().copyWith(yield90d: 99.87); + expect(yieldDto.timeframedYieldFormatted(YieldTimeFrame.threeMonth), "99.9%"); + }, + ); + + test( + """When using 'timeframedYieldFormatted' passing day timeframe, + and the 24h yield is zero it should return me just a hyphen""", + () { + final yieldDto = YieldDto.fixture().copyWith(yield24h: 0); + expect(yieldDto.timeframedYieldFormatted(YieldTimeFrame.day), "-"); + }, + ); + + test( + """When using 'timeframedYieldFormatted' passing week timeframe, + and the 7d yield is zero it should return me just a hyphen""", + () { + final yieldDto = YieldDto.fixture().copyWith(yield7d: 0); + expect(yieldDto.timeframedYieldFormatted(YieldTimeFrame.week), "-"); + }, + ); + + test( + """When using 'timeframedYieldFormatted' passing month timeframe, + and the 30d yield is zero it should return me just a hyphen""", + () { + final yieldDto = YieldDto.fixture().copyWith(yield30d: 0); + expect(yieldDto.timeframedYieldFormatted(YieldTimeFrame.month), "-"); + }, + ); + + test( + """When using 'timeframedYieldFormatted' passing three months + timeframe, and the 90d yield is zero it should return me just a hyphen""", + () { + final yieldDto = YieldDto.fixture().copyWith(yield90d: 0); + expect(yieldDto.timeframedYieldFormatted(YieldTimeFrame.threeMonth), "-"); + }, + ); } diff --git a/test/core/enums/zup_navigator_paths_test.dart b/test/core/enums/zup_navigator_paths_test.dart index 9fe5a37..64bd17a 100644 --- a/test/core/enums/zup_navigator_paths_test.dart +++ b/test/core/enums/zup_navigator_paths_test.dart @@ -8,23 +8,31 @@ void main() { expect(ZupNavigatorPaths.initial.path, routePaths.create.path); }); - test("Zup navigator paths `path` extension should use routefly generated paths", () { - expect( - ZupNavigatorPaths.newPosition.path, - routePaths.create.path, - reason: "New position path does not match routefly path", - ); + test("The path for new position should use routefly generated paths", () { + expect(ZupNavigatorPaths.newPosition.path, routePaths.create.path); + }); + + test("The path for yields should use routefly generated paths", () { + expect(ZupNavigatorPaths.yields.path, routePaths.create.yields.path); + }); + + test("The path for deposit should use routefly generated paths", () { + expect(ZupNavigatorPaths.deposit.path, routePaths.create.yields.$id.deposit); + }); + + test("The route params names type for yields should be correct", () { + expect(ZupNavigatorPaths.yields.routeParamsNames().runtimeType, YieldsRouteParamsNames); }); test("The route params names type for deposit should be correct", () { - expect(ZupNavigatorPaths.deposit.routeParamsNames().runtimeType, ZupDepositRouteParamsNames); + expect(ZupNavigatorPaths.deposit.routeParamsNames().runtimeType, DepositRouteParamsNames); }); test("The route params names type for new position should be correct", () { - expect(ZupNavigatorPaths.newPosition.routeParamsNames().runtimeType, ZupNewPositionRouteParamsNames); + expect(ZupNavigatorPaths.newPosition.routeParamsNames().runtimeType, NewPositionRouteParamsNames); }); test("The route params names type for initial should be correct", () { - expect(ZupNavigatorPaths.initial.routeParamsNames().runtimeType, ZupInitialRouteParamsNames); + expect(ZupNavigatorPaths.initial.routeParamsNames().runtimeType, InitialRouteParamsNames); }); } diff --git a/test/core/pool_service_test.dart b/test/core/pool_service_test.dart index 09b4a58..5b6bd47 100644 --- a/test/core/pool_service_test.dart +++ b/test/core/pool_service_test.dart @@ -101,7 +101,9 @@ void main() { aerodromeV3PoolImpl = AerodromeV3PoolImplMock(); pancakeSwapInfinityCLPositionManagerImpl = PancakeSwapInfinityCLPositionManagerImplMock(); - currentYield = YieldDto.fixture(); + currentYield = YieldDto.fixture().copyWith( + protocol: ProtocolDto.fixture().copyWith(id: ProtocolId.unknown, rawId: "1"), + ); sut = PoolService( stateView, diff --git a/test/core/repositories/protocol_repository_test.dart b/test/core/repositories/protocol_repository_test.dart index a0cf821..ffe2735 100644 --- a/test/core/repositories/protocol_repository_test.dart +++ b/test/core/repositories/protocol_repository_test.dart @@ -2,6 +2,7 @@ import 'package:dio/dio.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:zup_app/core/dtos/protocol_dto.dart'; +import 'package:zup_app/core/enums/protocol_id.dart'; import 'package:zup_app/core/repositories/protocol_repository.dart'; import '../../mocks.dart'; @@ -17,11 +18,7 @@ void main() { test("When calling `getAllSupportedProtocols` it should call the correct endpoint", () async { when(() => zupApiDio.get(any())).thenAnswer( - (_) async => Response( - data: [ProtocolDto.fixture().toJson()], - statusCode: 200, - requestOptions: RequestOptions(), - ), + (_) async => Response(data: [ProtocolDto.fixture().toJson()], statusCode: 200, requestOptions: RequestOptions()), ); await sut.getAllSupportedProtocols(); @@ -31,8 +28,20 @@ void main() { test("When calling `getAllSupportedProtocols` it should return a list of protocols", () async { final protocols = [ - ProtocolDto.fixture().copyWith(rawId: "1", name: "LULU", logo: "LALA.png", url: "LALA.com"), - ProtocolDto.fixture().copyWith(rawId: "2", name: "LALA", logo: "LULU.png", url: "LULU.com"), + ProtocolDto.fixture().copyWith( + rawId: "1", + name: "LULU", + logo: "LALA.png", + url: "LALA.com", + id: ProtocolId.unknown, + ), + ProtocolDto.fixture().copyWith( + rawId: "2", + name: "LALA", + logo: "LULU.png", + url: "LULU.com", + id: ProtocolId.unknown, + ), ]; when(() => zupApiDio.get(any())).thenAnswer( diff --git a/test/core/zup_navigator_test.dart b/test/core/zup_navigator_test.dart index b0162c7..7f45586 100644 --- a/test/core/zup_navigator_test.dart +++ b/test/core/zup_navigator_test.dart @@ -2,7 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:routefly/routefly.dart'; import 'package:web3kit/web3kit.dart'; +import 'package:zup_app/core/dtos/deposit_page_arguments_dto.dart'; +import 'package:zup_app/core/dtos/yield_dto.dart'; import 'package:zup_app/core/enums/networks.dart'; +import 'package:zup_app/core/enums/yield_timeframe.dart'; import 'package:zup_app/core/enums/zup_navigator_paths.dart'; import 'package:zup_app/core/zup_navigator.dart'; import 'package:zup_app/core/zup_route_params_names.dart'; @@ -10,15 +13,26 @@ import 'package:zup_app/zup_app.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final MaterialApp material = MaterialApp.router( - localizationsDelegates: const [Web3KitLocalizations.delegate], - routerConfig: Routefly.routerConfig( - routes: routes, - initialPath: ZupNavigatorPaths.initial.path, - routeBuilder: (context, settings, child) => - PageRouteBuilder(settings: settings, pageBuilder: (context, __, ___) => const SizedBox()), - ), - ); + + late MaterialApp material; + + setUp(() { + material = MaterialApp.router( + key: GlobalKey(), + localizationsDelegates: const [Web3KitLocalizations.delegate], + routerConfig: Routefly.routerConfig( + navigatorKey: GlobalKey(), + routes: routes, + initialPath: ZupNavigatorPaths.initial.path, + routeBuilder: (context, settings, child) => + PageRouteBuilder(settings: settings, pageBuilder: (context, __, ___) => const SizedBox()), + ), + ); + }); + + tearDown(() { + Routefly.navigate("/", arguments: null); + }); testWidgets("When calling `navigateToNewPosition` it should use routefly to navigate to the new position page", ( tester, @@ -62,7 +76,7 @@ void main() { expect(listenerCalled, true); }); - testWidgets("When calling `navigateToDeposit` it should pass the params to the query", (tester) async { + testWidgets("When calling `navigateToYields` it should pass the params to the query", (tester) async { runApp(material); const group0 = "0x123"; const group1 = "0x456"; @@ -70,7 +84,7 @@ void main() { const token1 = "0xabc"; const network = AppNetworks.mainnet; - await ZupNavigator().navigateToDeposit( + await ZupNavigator().navigateToYields( group0: group0, group1: group1, token0: token0, @@ -79,15 +93,15 @@ void main() { ); expect(Routefly.query.params, { - ZupDepositRouteParamsNames().group0: group0, - ZupDepositRouteParamsNames().group1: group1, - ZupDepositRouteParamsNames().token0: token0, - ZupDepositRouteParamsNames().token1: token1, - ZupDepositRouteParamsNames().network: network.name, + YieldsRouteParamsNames().group0: group0, + YieldsRouteParamsNames().group1: group1, + YieldsRouteParamsNames().token0: token0, + YieldsRouteParamsNames().token1: token1, + YieldsRouteParamsNames().network: network.name, }); }); - testWidgets("When calling `navigateToDeposit` without group0, it should not pass it to the query", (tester) async { + testWidgets("When calling `navigateToYields` without group0, it should not pass it to the query", (tester) async { runApp(material); const group1 = "0x456"; @@ -95,7 +109,7 @@ void main() { const token1 = "0xabc"; const network = AppNetworks.mainnet; - await ZupNavigator().navigateToDeposit( + await ZupNavigator().navigateToYields( group0: null, group1: group1, token0: token0, @@ -104,14 +118,14 @@ void main() { ); expect(Routefly.query.params, { - ZupDepositRouteParamsNames().group1: group1, - ZupDepositRouteParamsNames().token0: token0, - ZupDepositRouteParamsNames().token1: token1, - ZupDepositRouteParamsNames().network: network.name, + YieldsRouteParamsNames().group1: group1, + YieldsRouteParamsNames().token0: token0, + YieldsRouteParamsNames().token1: token1, + YieldsRouteParamsNames().network: network.name, }); }); - testWidgets("When calling `navigateToDeposit` without group1, it should not pass it to the query", (tester) async { + testWidgets("When calling `navigateToYields` without group1, it should not pass it to the query", (tester) async { runApp(material); const group0 = "0x456"; @@ -119,7 +133,7 @@ void main() { const token1 = "0xabc"; const network = AppNetworks.mainnet; - await ZupNavigator().navigateToDeposit( + await ZupNavigator().navigateToYields( group0: group0, group1: null, token0: token0, @@ -128,43 +142,37 @@ void main() { ); expect(Routefly.query.params, { - ZupDepositRouteParamsNames().group0: group0, - ZupDepositRouteParamsNames().token0: token0, - ZupDepositRouteParamsNames().token1: token1, - ZupDepositRouteParamsNames().network: network.name, + YieldsRouteParamsNames().group0: group0, + YieldsRouteParamsNames().token0: token0, + YieldsRouteParamsNames().token1: token1, + YieldsRouteParamsNames().network: network.name, }); }); - testWidgets("When calling `navigateToDeposit` without groups, it should not pass it to the query", (tester) async { + testWidgets("When calling `navigateToYields` without groups, it should not pass it to the query", (tester) async { runApp(material); const token0 = "0x789"; const token1 = "0xabc"; const network = AppNetworks.mainnet; - await ZupNavigator().navigateToDeposit( - group0: null, - group1: null, - token0: token0, - token1: token1, - network: network, - ); + await ZupNavigator().navigateToYields(group0: null, group1: null, token0: token0, token1: token1, network: network); expect(Routefly.query.params, { - ZupDepositRouteParamsNames().token0: token0, - ZupDepositRouteParamsNames().token1: token1, - ZupDepositRouteParamsNames().network: network.name, + YieldsRouteParamsNames().token0: token0, + YieldsRouteParamsNames().token1: token1, + YieldsRouteParamsNames().network: network.name, }); }); - testWidgets("When calling `navigateToDeposit` without token0, it should not pass it to the query", (tester) async { + testWidgets("When calling `navigateToYields` without token0, it should not pass it to the query", (tester) async { runApp(material); const group0 = "0x123"; const group1 = "0x456"; const token1 = "0xabc"; const network = AppNetworks.mainnet; - await ZupNavigator().navigateToDeposit( + await ZupNavigator().navigateToYields( group0: group0, group1: group1, token0: null, @@ -173,21 +181,21 @@ void main() { ); expect(Routefly.query.params, { - ZupDepositRouteParamsNames().group0: group0, - ZupDepositRouteParamsNames().group1: group1, - ZupDepositRouteParamsNames().token1: token1, - ZupDepositRouteParamsNames().network: network.name, + YieldsRouteParamsNames().group0: group0, + YieldsRouteParamsNames().group1: group1, + YieldsRouteParamsNames().token1: token1, + YieldsRouteParamsNames().network: network.name, }); }); - testWidgets("When calling `navigateToDeposit` without token1, it should not pass it to the query", (tester) async { + testWidgets("When calling `navigateToYields` without token1, it should not pass it to the query", (tester) async { runApp(material); const group0 = "0x123"; const group1 = "0x456"; const token0 = "0xabc"; const network = AppNetworks.mainnet; - await ZupNavigator().navigateToDeposit( + await ZupNavigator().navigateToYields( group0: group0, group1: group1, token0: token0, @@ -196,31 +204,85 @@ void main() { ); expect(Routefly.query.params, { - ZupDepositRouteParamsNames().group0: group0, - ZupDepositRouteParamsNames().group1: group1, - ZupDepositRouteParamsNames().token0: token0, - ZupDepositRouteParamsNames().network: network.name, + YieldsRouteParamsNames().group0: group0, + YieldsRouteParamsNames().group1: group1, + YieldsRouteParamsNames().token0: token0, + YieldsRouteParamsNames().network: network.name, }); }); - testWidgets("When calling `navigateToDeposit` without tokens, it should not pass it to the query", (tester) async { + testWidgets("When calling `navigateToYields` without tokens, it should not pass it to the query", (tester) async { runApp(material); const group0 = "0x123"; const group1 = "0x456"; const network = AppNetworks.mainnet; - await ZupNavigator().navigateToDeposit( - group0: group0, - group1: group1, - token0: null, - token1: null, - network: network, - ); + await ZupNavigator().navigateToYields(group0: group0, group1: group1, token0: null, token1: null, network: network); expect(Routefly.query.params, { - ZupDepositRouteParamsNames().group0: group0, - ZupDepositRouteParamsNames().group1: group1, - ZupDepositRouteParamsNames().network: network.name, + YieldsRouteParamsNames().group0: group0, + YieldsRouteParamsNames().group1: group1, + YieldsRouteParamsNames().network: network.name, }); }); + + testWidgets( + """When calling `navigateToDeposit` it should add the pool network name to the query params, + and assigned the passed timeframe and parseWrappedToNative to the query params""", + (tester) async { + runApp(material); + + const network = AppNetworks.mainnet; + final yieldPool = YieldDto.fixture().copyWith(chainId: network.chainId); + const yieldTimeFrame = YieldTimeFrame.month; + const parseWrappedToNative = true; + + await ZupNavigator().navigateToDeposit( + yieldPool: yieldPool, + selectedTimeframe: yieldTimeFrame, + parseWrappedToNative: parseWrappedToNative, + ); + + expect(Routefly.query.params, { + DepositRouteParamsNames().network: network.name, + DepositRouteParamsNames().timeframe: yieldTimeFrame.name, + DepositRouteParamsNames().parseWrappedToNative: parseWrappedToNative.toString(), + }); + }, + ); + + testWidgets("When calling `navigateToDeposit` it should replace the id part with the pool address", (tester) async { + runApp(material); + + const network = AppNetworks.mainnet; + final yieldPool = YieldDto.fixture().copyWith(chainId: network.chainId, poolAddress: "xabasRAaa"); + const yieldTimeFrame = YieldTimeFrame.month; + const parseWrappedToNative = true; + + await ZupNavigator().navigateToDeposit( + yieldPool: yieldPool, + selectedTimeframe: yieldTimeFrame, + parseWrappedToNative: parseWrappedToNative, + ); + + expect(Routefly.currentUri.path, ZupNavigatorPaths.deposit.path.changes({"id": yieldPool.poolAddress})); + }); + + testWidgets("When calling `navigateToDeposit` it should pass the pool dto as argument", (tester) async { + await tester.pumpWidget(material); + + final yieldPool = YieldDto.fixture().copyWith(chainId: AppNetworks.mainnet.chainId, poolAddress: "Pull Address"); + const yieldTimeFrame = YieldTimeFrame.month; + const parseWrappedToNative = true; + + await ZupNavigator().navigateToDeposit( + yieldPool: yieldPool, + selectedTimeframe: yieldTimeFrame, + parseWrappedToNative: parseWrappedToNative, + ); + + await tester.pumpAndSettle(); + + expect(Routefly.query.arguments, DepositPageArgumentsDto(yieldPool: yieldPool).toJson()); + }); } diff --git a/test/mocks.dart b/test/mocks.dart index cfe2bf1..47facf7 100644 --- a/test/mocks.dart +++ b/test/mocks.dart @@ -24,8 +24,9 @@ import 'package:zup_app/abis/uniswap_v3_position_manager.abi.g.dart'; import 'package:zup_app/abis/uniswap_v4_position_manager.abi.g.dart'; import 'package:zup_app/abis/uniswap_v4_state_view.abi.g.dart'; import 'package:zup_app/app/app_cubit/app_cubit.dart'; -import 'package:zup_app/app/create/deposit/deposit_cubit.dart'; -import 'package:zup_app/app/create/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/deposit_cubit.dart'; +import 'package:zup_app/app/create/yields/%5Bid%5D/deposit/widgets/preview_deposit_modal/preview_deposit_modal_cubit.dart'; +import 'package:zup_app/app/create/yields/yields_cubit.dart'; import 'package:zup_app/core/cache.dart'; import 'package:zup_app/core/debouncer.dart'; import 'package:zup_app/core/pool_service.dart'; @@ -121,6 +122,8 @@ class ZupCachedImageMock extends Mock implements ZupCachedImage {} class ZupNavigatorMock extends Mock implements ZupNavigator {} +class YieldsCubitMock extends Mock implements YieldsCubit {} + class ZupSingletonCacheMock extends Mock implements ZupSingletonCache {} class PreviewDepositModalCubitMock extends Mock implements PreviewDepositModalCubit {} diff --git a/test/widgets/goldens/yield_card_blockchain_tooltip_hover.png b/test/widgets/goldens/yield_card_blockchain_tooltip_hover.png new file mode 100644 index 0000000..bbd42f3 Binary files /dev/null and b/test/widgets/goldens/yield_card_blockchain_tooltip_hover.png differ diff --git a/test/widgets/goldens/yield_card_compacte_tvl.png b/test/widgets/goldens/yield_card_compacte_tvl.png new file mode 100644 index 0000000..54b0466 Binary files /dev/null and b/test/widgets/goldens/yield_card_compacte_tvl.png differ diff --git a/test/widgets/goldens/yield_card_dark_mode.png b/test/widgets/goldens/yield_card_dark_mode.png new file mode 100644 index 0000000..a473274 Binary files /dev/null and b/test/widgets/goldens/yield_card_dark_mode.png differ diff --git a/test/widgets/goldens/yield_card_day_timeframe.png b/test/widgets/goldens/yield_card_day_timeframe.png new file mode 100644 index 0000000..562fffe Binary files /dev/null and b/test/widgets/goldens/yield_card_day_timeframe.png differ diff --git a/test/widgets/goldens/yield_card_month_timeframe.png b/test/widgets/goldens/yield_card_month_timeframe.png new file mode 100644 index 0000000..3810417 Binary files /dev/null and b/test/widgets/goldens/yield_card_month_timeframe.png differ diff --git a/test/widgets/goldens/yield_card_overflow_protocol_name.png b/test/widgets/goldens/yield_card_overflow_protocol_name.png new file mode 100644 index 0000000..4a1443f Binary files /dev/null and b/test/widgets/goldens/yield_card_overflow_protocol_name.png differ diff --git a/test/widgets/goldens/yield_card_overflow_token_symbol.png b/test/widgets/goldens/yield_card_overflow_token_symbol.png new file mode 100644 index 0000000..d29cd08 Binary files /dev/null and b/test/widgets/goldens/yield_card_overflow_token_symbol.png differ diff --git a/test/widgets/goldens/yield_card_show_yield_timeframe.png b/test/widgets/goldens/yield_card_show_yield_timeframe.png new file mode 100644 index 0000000..22ef99b Binary files /dev/null and b/test/widgets/goldens/yield_card_show_yield_timeframe.png differ diff --git a/test/widgets/goldens/yield_card_three_months_timeframe.png b/test/widgets/goldens/yield_card_three_months_timeframe.png new file mode 100644 index 0000000..32db0e2 Binary files /dev/null and b/test/widgets/goldens/yield_card_three_months_timeframe.png differ diff --git a/test/widgets/goldens/yield_card_week_timeframe.png b/test/widgets/goldens/yield_card_week_timeframe.png new file mode 100644 index 0000000..13f5abc Binary files /dev/null and b/test/widgets/goldens/yield_card_week_timeframe.png differ diff --git a/test/widgets/goldens/yield_card_yield_tap_mobile.png b/test/widgets/goldens/yield_card_yield_tap_mobile.png new file mode 100644 index 0000000..744ee2c Binary files /dev/null and b/test/widgets/goldens/yield_card_yield_tap_mobile.png differ diff --git a/test/widgets/goldens/yield_card_yield_tooltip_hover.png b/test/widgets/goldens/yield_card_yield_tooltip_hover.png new file mode 100644 index 0000000..29d6bda Binary files /dev/null and b/test/widgets/goldens/yield_card_yield_tooltip_hover.png differ diff --git a/test/widgets/yield_card_test.dart b/test/widgets/yield_card_test.dart index a1db2c3..38b40b0 100644 --- a/test/widgets/yield_card_test.dart +++ b/test/widgets/yield_card_test.dart @@ -1,148 +1,181 @@ -import 'package:flutter/widgets.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:golden_toolkit/golden_toolkit.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; -import 'package:zup_app/app/app_cubit/app_cubit.dart'; +import 'package:zup_app/core/dtos/protocol_dto.dart'; +import 'package:zup_app/core/dtos/token_dto.dart'; import 'package:zup_app/core/dtos/yield_dto.dart'; -import 'package:zup_app/core/enums/networks.dart'; import 'package:zup_app/core/enums/yield_timeframe.dart'; import 'package:zup_app/core/injections.dart'; import 'package:zup_app/widgets/yield_card.dart'; import 'package:zup_app/widgets/zup_cached_image.dart'; -import 'package:zup_core/zup_core.dart'; +import 'package:zup_core/extensions/widget_tester_extension.dart'; import '../golden_config.dart'; import '../mocks.dart'; void main() { - ZupCachedImage zupCachedImage = mockZupCachedImage(); - late AppCubit appCubit; - setUp(() { - appCubit = AppCubitMock(); - UrlLauncherPlatform.instance = UrlLauncherPlatformCustomMock(); - - inject.registerFactory(() => appCubit); - inject.registerFactory(() => zupCachedImage); + inject.registerFactory(() => mockZupCachedImage()); inject.registerFactory(() => false, instanceName: InjectInstanceNames.infinityAnimationAutoPlay); - - when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.sepolia); - when(() => appCubit.selectedNetworkStream).thenAnswer((_) => const Stream.empty()); }); tearDown(() => inject.reset()); Future goldenBuilder({ - YieldDto? currentYield, - Function(YieldDto? yield)? onChangeSelection, - bool isSelected = false, - YieldTimeFrame? timeFrame, + bool isHotestYield = true, + YieldDto? yieldPool, + YieldTimeFrame? yieldTimeFrame, + bool snapshotDarkMode = false, + bool showYieldTimeframe = false, }) async => await goldenDeviceBuilder( + darkMode: snapshotDarkMode, Center( child: SizedBox( - width: 300, - child: Center( - child: YieldCard( - isSelected: isSelected, - onChangeSelection: (selectedYield) => onChangeSelection?.call(selectedYield), - timeFrame: timeFrame ?? YieldTimeFrame.day, - currentYield: currentYield ?? YieldDto.fixture(), - ), + height: 310, + width: 340, + child: YieldCard( + showTimeframe: showYieldTimeframe, + showHotestYieldAnimation: false, + yieldPool: yieldPool ?? YieldDto.fixture(), + yieldTimeFrame: yieldTimeFrame ?? YieldTimeFrame.day, + mainButton: const SizedBox(), ), ), ), ); zGoldenTest( - "When the app network is all networks, the yield card should display an icon of the network of the yield", - goldenFileName: "yield_card_network_icon", + """When passing the yield timeframe as day, + it should show the day yield from the passed yield pool""", + goldenFileName: "yield_card_day_timeframe", (tester) async { - when(() => appCubit.selectedNetwork).thenAnswer((_) => AppNetworks.allNetworks); - - await tester.pumpDeviceBuilder(await goldenBuilder()); - - await tester.pumpAndSettle(); + final pool = YieldDto.fixture().copyWith(yield24h: 12518721); + await tester.pumpDeviceBuilder(await goldenBuilder(yieldTimeFrame: YieldTimeFrame.day, yieldPool: pool)); }, ); zGoldenTest( - "When passing `isSelected` as true, the yield card should be selected", - goldenFileName: "yield_card_selected", + """When passing the yield timeframe as week, + it should show the week yield from the passed yield pool""", + goldenFileName: "yield_card_week_timeframe", (tester) async { - await tester.pumpDeviceBuilder(await goldenBuilder(isSelected: true)); - - await tester.pumpAndSettle(); + final pool = YieldDto.fixture().copyWith(yield7d: 111122111); + await tester.pumpDeviceBuilder(await goldenBuilder(yieldTimeFrame: YieldTimeFrame.week, yieldPool: pool)); }, ); + zGoldenTest( - "When passing the 30d timeframe, the yield card should display the 30d yield", - goldenFileName: "yield_card_30d", + """When passing the yield timeframe as month, + it should show the month yield from the passed yield pool""", + goldenFileName: "yield_card_month_timeframe", (tester) async { - final currentYield = YieldDto.fixture().copyWith(yield30d: 87654.98765); - await tester.pumpDeviceBuilder(await goldenBuilder(timeFrame: YieldTimeFrame.month, currentYield: currentYield)); - - await tester.pumpAndSettle(); + final pool = YieldDto.fixture().copyWith(yield30d: 991); + await tester.pumpDeviceBuilder(await goldenBuilder(yieldTimeFrame: YieldTimeFrame.month, yieldPool: pool)); }, ); zGoldenTest( - "When passing the 24h timeframe, the yield card should display the 30d yield", - goldenFileName: "yield_card_24h", + """When passing the yield timeframe as three months, + it should show the three months yield from the passed yield pool""", + goldenFileName: "yield_card_three_months_timeframe", (tester) async { - final currentYield = YieldDto.fixture().copyWith(yield24h: 1447.23); - await tester.pumpDeviceBuilder(await goldenBuilder(timeFrame: YieldTimeFrame.day, currentYield: currentYield)); + final pool = YieldDto.fixture().copyWith(yield90d: 654); + await tester.pumpDeviceBuilder(await goldenBuilder(yieldTimeFrame: YieldTimeFrame.threeMonth, yieldPool: pool)); + }, + ); - await tester.pumpAndSettle(); + zGoldenTest( + "When the theme is in dark mode, the card should be in dark mode", + goldenFileName: "yield_card_dark_mode", + (tester) async { + await tester.pumpDeviceBuilder(await goldenBuilder(snapshotDarkMode: true)); }, ); zGoldenTest( - "When passing the 90d timeframe, the yield card should display the 90d yield", - goldenFileName: "yield_card_90d", + """When the user hovers the blockchain icon, it should display a tooltip + explaining that the pool is at n blockchain""", + goldenFileName: "yield_card_blockchain_tooltip_hover", (tester) async { - final currentYield = YieldDto.fixture().copyWith(yield90d: 1535421.32); - await tester.pumpDeviceBuilder( - await goldenBuilder(timeFrame: YieldTimeFrame.threeMonth, currentYield: currentYield), - ); + final yieldPool = YieldDto.fixture(); + await tester.pumpDeviceBuilder(await goldenBuilder(yieldPool: yieldPool)); + await tester.hover(find.byKey(Key("yield-card-network-${yieldPool.network.label}"))); await tester.pumpAndSettle(); }, ); zGoldenTest( - "When passing the 7d timeframe, the yield card should display the 7d yield", - goldenFileName: "yield_card_7d", + """When hovering the info icon after the yield percent, it should display a tooltip + explaining the yield percent, and showing other timesframes yields""", + goldenFileName: "yield_card_yield_tooltip_hover", (tester) async { - final currentYield = YieldDto.fixture().copyWith(yield7d: 7987897687.32); - await tester.pumpDeviceBuilder(await goldenBuilder(timeFrame: YieldTimeFrame.week, currentYield: currentYield)); + final yieldPool = YieldDto.fixture().copyWith(yield24h: 1212, yield7d: 2919, yield30d: 9824, yield90d: 1111); + await tester.pumpDeviceBuilder(await goldenBuilder(yieldPool: yieldPool)); + await tester.hover(find.byKey(Key("yield-breakdown-tooltip-${yieldPool.poolAddress}"))); await tester.pumpAndSettle(); }, ); zGoldenTest( - "When hovering the protocol name, it should show a tooltip to navigate to the protocol", - goldenFileName: "yield_card_tooltip_protocol", + """When the device is mobile, and the user clicks the yield, it should display a tooltip + explaining the yield percent, and showing other timesframes yields""", + goldenFileName: "yield_card_yield_tap_mobile", (tester) async { - final currentYield = YieldDto.fixture(); - await tester.pumpDeviceBuilder(await goldenBuilder(currentYield: currentYield)); + debugDefaultTargetPlatformOverride = TargetPlatform.android; + + final yieldPool = YieldDto.fixture().copyWith(yield24h: 1212, yield7d: 2919, yield30d: 9824, yield90d: 1111); + await tester.pumpDeviceBuilder(await goldenBuilder(yieldPool: yieldPool)); - await tester.hover(find.text(currentYield.protocol.name)); + await tester.hover(find.byKey(Key("yield-card-yield-${yieldPool.poolAddress}"))); await tester.pumpAndSettle(); + + debugDefaultTargetPlatformOverride = null; }, ); - zGoldenTest("Clicking the tooltip to naviagte to the protocol, it should launch the protocol link", (tester) async { - final currentYield = YieldDto.fixture(); - await tester.pumpDeviceBuilder(await goldenBuilder(currentYield: currentYield)); + zGoldenTest( + "When the tvl decimals is greater than 2, the tvl should be compact", + goldenFileName: "yield_card_compacte_tvl", + (tester) async { + final pool = YieldDto.fixture().copyWith(totalValueLockedUSD: 112152871.219201); + await tester.pumpDeviceBuilder(await goldenBuilder(yieldPool: pool)); + }, + ); - await tester.hover(find.text(currentYield.protocol.name)); - await tester.pumpAndSettle(); + zGoldenTest( + "When the protocol name is too big, it add a overflow ellipsis", + goldenFileName: "yield_card_overflow_protocol_name", + (tester) async { + final pool = YieldDto.fixture().copyWith( + protocol: ProtocolDto.fixture().copyWith(name: "Lorem ipsum dolor sit amet consectetur adipiscing elit"), + ); + await tester.pumpDeviceBuilder(await goldenBuilder(yieldPool: pool)); + }, + ); - await tester.tap(find.byKey(const Key("helper-button-tooltip"))); - await tester.pumpAndSettle(); + zGoldenTest( + "When the token symbol pass 8 chars, it should be overflowed with an ellipsis", + goldenFileName: "yield_card_overflow_token_symbol", + (tester) async { + final pool = YieldDto.fixture().copyWith( + token0: TokenDto.fixture().copyWith(symbol: "Lorem ipsum dolor sit amet consectetur adipiscing elit"), + token1: TokenDto.fixture().copyWith(symbol: "elit adipiscing consectetur amet sit dolor ipsum Lorem"), + ); + await tester.pumpDeviceBuilder(await goldenBuilder(yieldPool: pool)); + }, + ); - expect(UrlLauncherPlatformCustomMock.lastLaunchedUrl, currentYield.protocol.url); - }); + zGoldenTest( + "When passing 'showYieldTimframe' true, it should show the timeframe of the yield in the card", + goldenFileName: "yield_card_show_yield_timeframe", + (tester) async { + final pool = YieldDto.fixture().copyWith(yield90d: 654); + await tester.pumpDeviceBuilder( + await goldenBuilder(yieldTimeFrame: YieldTimeFrame.threeMonth, yieldPool: pool, showYieldTimeframe: true), + ); + }, + ); } diff --git a/web/index.html b/web/index.html index daf4388..4805b4c 100644 --- a/web/index.html +++ b/web/index.html @@ -41,10 +41,19 @@ @@ -82,6 +86,6 @@ inject(); - + - + \ No newline at end of file