fix bridge ui (#3189)

* fix bridge ui

* padding on info bottom sheet

* fix cutoff on infobottomsheet

* fix wording on bridge details ui

* no _amountController.clear() on bridge page navigation

* autofocus keyboard on bridge page opened

* remove KeyboardHideOverlay

* fix text field resize

* fix text field padding
This commit is contained in:
malik1004x
2026-04-17 23:46:58 +02:00
committed by GitHub
parent 21847cd958
commit 1cb05d5211
8 changed files with 170 additions and 174 deletions
+111 -118
View File
@@ -4,7 +4,6 @@ import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
import 'package:cake_wallet/utils/request_review_handler.dart';
import 'package:cake_wallet/new-ui/widgets/keyboard_hide_overlay.dart';
import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/view_model/bridge_history_view_model.dart';
@@ -33,7 +32,10 @@ class BridgeAmountPage extends StatefulWidget {
}
class _BridgeAmountPageState extends State<BridgeAmountPage> {
late final TextEditingController _amountController;
final TextEditingController _amountController = TextEditingController();
final FocusNode _amountFocusNode = FocusNode();
bool _amountFocused = false;
BridgeViewModel get bridgeViewModel => widget.bridgeViewModel;
@override
@@ -42,10 +44,11 @@ class _BridgeAmountPageState extends State<BridgeAmountPage> {
bridgeViewModel.applyInitialBridgeToken(widget.initialToken);
bridgeViewModel.onBridgeSuccess = _showBridgeSuccessBottomSheet;
_amountController = TextEditingController();
_amountFocusNode.addListener(() => setState(() => _amountFocused = _amountFocusNode.hasFocus));
WidgetsBinding.instance.addPostFrameCallback((_) {
bridgeViewModel.ensureFiatPriceForSelectedToken();
_amountFocusNode.requestFocus();
});
}
@@ -100,22 +103,23 @@ class _BridgeAmountPageState extends State<BridgeAmountPage> {
Widget build(BuildContext context) {
final theme = Theme.of(context);
return KeyboardHideOverlay(
unfocusOnTap: true,
child: Container(
decoration: BoxDecoration(
color: theme.colorScheme.surface,
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
),
return Container(
decoration: BoxDecoration(
color: theme.colorScheme.surface,
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
),
child: Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Column(
children: [
ModalTopBar(
title: 'Enter Amount',
leadingIcon: const Icon(Icons.arrow_back_ios_new, size: 18),
title: S.of(context).enter_amount,
leadingIcon: Icon(Icons.arrow_back_ios_new),
onLeadingPressed: () => Navigator.of(context, rootNavigator: true).pop(),
trailingIcon: const Icon(Icons.calendar_month, size: 18),
trailingIcon: Icon(Icons.history),
onTrailingPressed: () {
Navigator.pushNamed(context, Routes.bridgeHistoryPage, arguments: widget.bridgeHistoryViewModel);
Navigator.pushNamed(context, Routes.bridgeHistoryPage,
arguments: widget.bridgeHistoryViewModel);
},
),
Expanded(
@@ -130,66 +134,63 @@ class _BridgeAmountPageState extends State<BridgeAmountPage> {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Spacer(flex: 2),
LayoutBuilder(
builder: (context, constraints) {
return Center(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
ConstrainedBox(
constraints: BoxConstraints(
maxWidth: (constraints.maxWidth - 200)
.clamp(40.0, constraints.maxWidth),
minWidth: 40,
),
child: TextFormField(
controller: _amountController,
maxLines: 1,
onChanged: bridgeViewModel.setAmount,
autovalidateMode: AutovalidateMode.always,
validator: bridgeViewModel.decimalAmountValidator,
keyboardType: TextInputType.numberWithOptions(
signed: false,
decimal: true,
),
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(
RegExp(r'^\d*[.,]?\d*$'),
),
],
textAlign: TextAlign.center,
decoration: InputDecoration(
isDense: true,
hintText: '0.00',
hintStyle: theme.textTheme.displayMedium?.copyWith(
fontWeight: FontWeight.w400,
color: theme.colorScheme.onSurfaceVariant,
),
),
style: theme.textTheme.displayMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 45,
color: theme.colorScheme.onSurface,
),
),
Center(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
IntrinsicWidth(
child: TextFormField(
controller: _amountController,
focusNode: _amountFocusNode,
maxLines: 1,
onChanged: bridgeViewModel.setAmount,
autovalidateMode: AutovalidateMode.always,
validator: bridgeViewModel.decimalAmountValidator,
keyboardType: TextInputType.numberWithOptions(
signed: false,
decimal: true,
),
const SizedBox(width: 8),
Text(
bridgeViewModel.selectedToken?.title ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.displayMedium?.copyWith(
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(
RegExp(r'^\d*[.,]?\d*$'),
),
],
decoration: InputDecoration(
isDense: true,
isCollapsed: true,
contentPadding: EdgeInsets.zero,
fillColor: Colors.transparent,
hoverColor: Colors.transparent,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
hintText: _amountFocused || _amountController.text.isNotEmpty ? null : "0.00",
hintStyle: theme.textTheme.displayMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 45,
color: theme.colorScheme.onSurfaceVariant,
),
),
],
style: theme.textTheme.displayMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 45,
color: theme.colorScheme.onSurface,
),
),
),
);
},
const SizedBox(width: 8),
Text(
bridgeViewModel.selectedToken?.title ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.displayMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 45,
color: theme.colorScheme.onSurfaceVariant,
),
),
],
),
),
const SizedBox(height: 12),
Center(
@@ -217,78 +218,71 @@ class _BridgeAmountPageState extends State<BridgeAmountPage> {
],
const Spacer(flex: 3),
Row(
spacing: 8,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: RichText(
maxLines: 2,
overflow: TextOverflow.ellipsis,
text: TextSpan(
children: [
TextSpan(
text: 'Available ',
style: theme.textTheme.bodyLarge?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w400,
fontSize: 16,
letterSpacing: -0.08,
),
),
TextSpan(
text: '${bridgeViewModel.tokenBalanceFormatted} '
'${bridgeViewModel.selectedToken?.title ?? ''}',
style: theme.textTheme.bodyLarge?.copyWith(
color: theme.colorScheme.onSurface,
fontWeight: FontWeight.w400,
fontSize: 16,
letterSpacing: -0.08,
),
),
],
child: Row(
spacing: 8,
children: [
Text(
S.of(context).available,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant),
),
),
),
const SizedBox(width: 10),
TextButton(
style: TextButton.styleFrom(
textStyle: theme.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
letterSpacing: -0.07,
),
foregroundColor: theme.colorScheme.primary,
backgroundColor: theme.colorScheme.surfaceContainer,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(80),
),
),
onPressed: () {
Text(
"${bridgeViewModel.tokenBalanceFormatted} ${bridgeViewModel.selectedToken?.title ?? ""}",
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurface),
)
],
)),
GestureDetector(
onTap: () {
bridgeViewModel.setMaxAmount();
_amountController.text = bridgeViewModel.amount;
_amountController.selection = TextSelection.collapsed(
offset: _amountController.text.length,
);
},
child: Text(S.of(context).max),
child: Container(
height: 34,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(999999)),
child: Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Text(
S.of(context).max,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).colorScheme.primary),
),
)),
),
),
const SizedBox(width: 10),
IgnorePointer(
ignoring: !canNext,
child: ModernButton(
size: 48,
size: 34,
backgroundColor: theme.colorScheme.primary,
iconColor: theme.colorScheme.onPrimary,
icon: Icon(Icons.arrow_forward, size: 25),
icon: Icon(Icons.arrow_forward, size: 20),
onPressed: () {
_amountController.clear();
Navigator.pushNamed(
context, Routes.bridgeDestinationNetworkPage, arguments: bridgeViewModel);
context, Routes.bridgeDestinationNetworkPage,
arguments: bridgeViewModel);
},
),
),
],
),
SizedBox(
height: 24,
)
],
);
},
@@ -302,4 +296,3 @@ class _BridgeAmountPageState extends State<BridgeAmountPage> {
);
}
}
@@ -6,6 +6,7 @@ import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
import 'package:cake_wallet/new-ui/widgets/send_page/send_confirm_bottom_widget.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/view_model/bridge/bridge_view_model.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
@@ -98,7 +99,7 @@ class _BridgeConfirmSheetState extends State<BridgeConfirmSheet> {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 48),
child: Center(
child: CircularProgressIndicator(),
child: CupertinoActivityIndicator(),
),
);
}
@@ -176,9 +177,14 @@ class _BridgeConfirmSheetState extends State<BridgeConfirmSheet> {
),
],
),
NetworkPathPill(
sourceChainName: bridgeViewModel.wallet.type.name,
destChainName: bridgeViewModel.destinationChainInfo?.name ?? '',
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
NetworkPathPill(
sourceChainName: bridgeViewModel.wallet.type.name,
destChainName: bridgeViewModel.destinationChainInfo?.name ?? '',
),
],
),
ConfirmDetailsCard(bridgeViewModel: bridgeViewModel),
if (bridgeViewModel.executeError != null) ...[
@@ -199,22 +205,16 @@ class _BridgeConfirmSheetState extends State<BridgeConfirmSheet> {
child: Observer(
builder: (_) {
if (bridgeViewModel.isExecuting) {
return const LoadingBottomWidget(
text: 'Bridging...',
);
return LoadingBottomWidget(text: "${S.of(context).bridging}...",);
}
final canSwipe = bridgeViewModel.quote != null && !bridgeViewModel.isQuoteLoading;
return IgnorePointer(
ignoring: !canSwipe,
child: Opacity(
opacity: canSwipe ? 1 : 0.45,
child: ConfirmSwiper(
onConfirmed: () {
bridgeViewModel.executeBridge();
},
swiperText: 'Swipe to bridge',
),
),
if(bridgeViewModel.isQuoteLoading || bridgeViewModel.quote == null) {
return LoadingBottomWidget(text: "${S.of(context).loading}...");
}
return ConfirmSwiper(
onConfirmed: () {
bridgeViewModel.executeBridge();
},
swiperText: S.of(context).swipe_to_bridge,
);
},
),
@@ -28,18 +28,16 @@ class NetworkPathPill extends StatelessWidget {
color: scheme.onSurfaceVariant,
),
const SizedBox(width: 8),
Flexible(
child: Text(
sourceChainName.capitalized(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: scheme.onSurface,
fontWeight: FontWeight.w400,
fontSize: 16,
letterSpacing: -0.08,
),
),
Text(
sourceChainName.capitalized(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: scheme.onSurface,
fontWeight: FontWeight.w400,
fontSize: 16,
letterSpacing: -0.08,
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
@@ -56,18 +54,16 @@ class NetworkPathPill extends StatelessWidget {
color: scheme.onSurfaceVariant,
),
const SizedBox(width: 8),
Flexible(
child: Text(
destChainName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: scheme.onSurface,
fontWeight: FontWeight.w400,
fontSize: 16,
letterSpacing: -0.08,
),
),
Text(
destChainName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: scheme.onSurface,
fontWeight: FontWeight.w400,
fontSize: 16,
letterSpacing: -0.08,
),
),
],
);
@@ -55,13 +55,15 @@ abstract class BaseBottomSheet extends StatelessWidget {
topLeft: Radius.circular(30.0), topRight: Radius.circular(30.0)),
child: Container(
color: Theme.of(context).colorScheme.surface,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
_buildHeader(context),
contentWidget(context),
_buildFooter(context),
],
child: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
_buildHeader(context),
contentWidget(context),
_buildFooter(context),
],
),
),
),
),
@@ -108,7 +110,7 @@ abstract class BaseBottomSheet extends StatelessWidget {
case FooterType.slideActionButton:
return Padding(
padding: const EdgeInsets.fromLTRB(40, 12, 40, 34),
padding: const EdgeInsets.fromLTRB(40, 12, 40, 24),
child: StandardSlideButton(
key: ValueKey('base_bottom_sheet_widget_standard_slide_button_key'),
buttonText: slideActionButtonText ?? '',
@@ -120,7 +122,7 @@ abstract class BaseBottomSheet extends StatelessWidget {
case FooterType.singleActionButton:
return Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 34),
padding: const EdgeInsets.fromLTRB(16, 12, 16, 24),
child: LoadingPrimaryButton(
key: singleActionButtonKey,
text: singleActionButtonText ?? '',
@@ -134,7 +136,7 @@ abstract class BaseBottomSheet extends StatelessWidget {
case FooterType.doubleActionButton:
return Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 34),
padding: const EdgeInsets.fromLTRB(16, 0, 16, 24),
child: Row(
children: [
Expanded(
@@ -100,6 +100,7 @@ class InfoBottomSheet extends BaseBottomSheet {
)
else
Container(),
SizedBox(height: 24,),
if (content != null)
Expanded(
flex: 2,
+2 -1
View File
@@ -17,6 +17,7 @@ import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/bridge_transfers_store.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/crypto_amount_format.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/wallet_base.dart';
@@ -187,7 +188,7 @@ abstract class BridgeViewModelBase extends WalletChangeListenerViewModel with St
String get quoteNativeFeeFormattedForDisplay {
if (quoteNativeFee.isEmpty) return '';
return '${quoteNativeFee} ${wallet.currency.title}';
return '${quoteNativeFee.withMaxDecimals(8)} ${wallet.currency.title}';
}
@computed
@@ -97,9 +97,9 @@ abstract class BridgeDetailsViewModelBase with Store {
);
final sourceName =
evm?.getChainNameByChainId(transfer.sourceChainId) ?? '${transfer.sourceChainId}';
evm?.getChainInfoByChainId(transfer.sourceChainId)?.name ?? '${transfer.sourceChainId}';
final destName =
evm?.getChainNameByChainId(transfer.destinationChainId) ?? '${transfer.destinationChainId}';
evm?.getChainInfoByChainId(transfer.destinationChainId)?.name ?? '${transfer.destinationChainId}';
items.add(
StandartListItem(