diff --git a/app/assets/solana.png b/app/assets/solana.png new file mode 100644 index 00000000..fc30eb41 Binary files /dev/null and b/app/assets/solana.png differ diff --git a/app/lib/models/wallet.dart b/app/lib/models/wallet.dart index f55a1915..ad925e4d 100644 --- a/app/lib/models/wallet.dart +++ b/app/lib/models/wallet.dart @@ -4,6 +4,8 @@ enum ChainType { Stellar, TFChain } enum BridgeOperation { Withdraw, Deposit } +enum DepositChain { Stellar, Solana } + class Wallet { Wallet({ required this.name, diff --git a/app/lib/screens/wallets/bridge.dart b/app/lib/screens/wallets/bridge.dart index ef62ec2b..c9120be0 100644 --- a/app/lib/screens/wallets/bridge.dart +++ b/app/lib/screens/wallets/bridge.dart @@ -172,6 +172,7 @@ class _WalletBridgeScreenState extends State { @override Widget build(BuildContext context) { final bool disableDeposit = widget.wallet.stellarBalance == '-1'; + const DepositChain depositChain = DepositChain.Solana; if (disableDeposit && !isWithdraw) { onTransactionChange(BridgeOperation.Withdraw); } @@ -191,7 +192,8 @@ class _WalletBridgeScreenState extends State { SwapTransactionWidget( bridgeOperation: transactionType, onTransactionChange: onTransactionChange, - disableDeposit: disableDeposit), + disableDeposit: disableDeposit, + depositChain: depositChain), const SizedBox(height: 20), ListTile( title: TextField( diff --git a/app/lib/widgets/wallets/swap_transaction_widget.dart b/app/lib/widgets/wallets/swap_transaction_widget.dart index 582197c6..8cd29372 100644 --- a/app/lib/widgets/wallets/swap_transaction_widget.dart +++ b/app/lib/widgets/wallets/swap_transaction_widget.dart @@ -7,13 +7,13 @@ class SwapTransactionWidget extends StatefulWidget { required this.bridgeOperation, required this.onTransactionChange, required this.disableDeposit, + required this.depositChain, }); final void Function(BridgeOperation bridgeOperation) onTransactionChange; final BridgeOperation bridgeOperation; final bool disableDeposit; - final String withdrawIcon = 'assets/tf_chain.png'; - final String depositIcon = 'assets/stellar.png'; + final DepositChain depositChain; @override _SwapTransactionWidgetState createState() => _SwapTransactionWidgetState(); @@ -21,6 +21,10 @@ class SwapTransactionWidget extends StatefulWidget { class _SwapTransactionWidgetState extends State { late BridgeOperation currentOperation; + final GlobalKey<_ChainLabelsState> _leftChainKey = + GlobalKey<_ChainLabelsState>(); + final GlobalKey<_ChainLabelsState> _rightChainKey = + GlobalKey<_ChainLabelsState>(); @override void initState() { @@ -30,32 +34,35 @@ class _SwapTransactionWidgetState extends State { void _swapTransactionType() { setState(() { - // Swap between Withdraw and Deposit currentOperation = currentOperation == BridgeOperation.Withdraw ? BridgeOperation.Deposit : BridgeOperation.Withdraw; }); widget.onTransactionChange(currentOperation); + + // Swap the selected chains + final leftChain = _leftChainKey.currentState?.selectedChain.value; + final rightChain = _rightChainKey.currentState?.selectedChain.value; + + if (leftChain != null && rightChain != null) { + _leftChainKey.currentState?.updateSelectedChain(rightChain); + _rightChainKey.currentState?.updateSelectedChain(leftChain); + } } @override Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; final colorScheme = Theme.of(context).colorScheme; - final String leftIcon; final String leftChainLabel; - final String rightIcon; final String rightChainLabel; + if (currentOperation == BridgeOperation.Withdraw) { - leftIcon = widget.withdrawIcon; - leftChainLabel = 'TFChain'; - rightIcon = widget.depositIcon; - rightChainLabel = 'Stellar'; + leftChainLabel = 'TF Chain'; + rightChainLabel = widget.depositChain.name; } else { - leftIcon = widget.depositIcon; - leftChainLabel = 'Stellar'; - rightIcon = widget.withdrawIcon; - rightChainLabel = 'TFChain'; + leftChainLabel = widget.depositChain.name; + rightChainLabel = 'TF Chain'; } return Container( @@ -70,103 +77,216 @@ class _SwapTransactionWidgetState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ - // Left Icon and Labels + _buildChainInfo(context, leftChainLabel, key: _leftChainKey), + _buildSwapButton(context), + _buildChainInfo(context, rightChainLabel, + isLeftSide: false, key: _rightChainKey), + ], + ), + ); + } + + Widget _buildChainInfo( + BuildContext context, + String chainLabel, { + required Key key, + bool isLeftSide = true, + }) { + final colorScheme = Theme.of(context).colorScheme; + + return Flexible( + flex: 1, + child: Row( + mainAxisAlignment: + isLeftSide ? MainAxisAlignment.start : MainAxisAlignment.end, + children: [ + const SizedBox(width: 5), Flexible( - flex: 1, - child: Row( - children: [ - Image.asset( - leftIcon, - fit: BoxFit.contain, - color: colorScheme.onSurface, - width: 30, - height: 30, - ), - const SizedBox(width: 5), - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'TFT', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: colorScheme.onSurface, - fontWeight: FontWeight.bold), - ), - Text( - leftChainLabel, - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: colorScheme.onSurface, - ), - softWrap: true, - ), - ], - ), - ), - ], + child: _ChainLabels( + key: key, + chainLabel: chainLabel, + colorScheme: colorScheme, + textTheme: Theme.of(context).textTheme, + excludeChain: isLeftSide + ? null + : _leftChainKey.currentState?.selectedChain.value, ), ), - // Swap Icon - SizedBox( - width: 50, - child: Center( - child: GestureDetector( - onTap: widget.disableDeposit ? null : _swapTransactionType, - child: CircleAvatar( - radius: 22, - backgroundColor: widget.disableDeposit - ? Theme.of(context).disabledColor - : colorScheme.primaryContainer, - child: Icon( - Icons.swap_horiz, - color: widget.disableDeposit - ? colorScheme.onSurface.withOpacity(0.5) - : colorScheme.onPrimaryContainer, - size: 25, - ), - ), - ), + ], + ), + ); + } + + Widget _buildSwapButton(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + + return SizedBox( + width: 50, + child: Center( + child: GestureDetector( + onTap: widget.disableDeposit ? null : _swapTransactionType, + child: CircleAvatar( + radius: 22, + backgroundColor: widget.disableDeposit + ? Theme.of(context).disabledColor + : colorScheme.primaryContainer, + child: Icon( + Icons.swap_horiz, + color: widget.disableDeposit + ? colorScheme.onSurface.withOpacity(0.5) + : colorScheme.onPrimaryContainer, + size: 25, ), ), - // Right Icon and Labels - Flexible( - flex: 1, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Image.asset( - rightIcon, - fit: BoxFit.contain, - color: colorScheme.onSurface, - width: 30, - height: 30, - ), - const SizedBox(width: 5), - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'TFT', - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: colorScheme.onSurface, - fontWeight: FontWeight.bold), + ), + ), + ); + } +} + +class _ChainLabels extends StatefulWidget { + final String chainLabel; + final ColorScheme colorScheme; + final TextTheme textTheme; + final List chains = ['TF Chain', 'Stellar', 'Solana']; + final String? excludeChain; + + _ChainLabels({ + required Key key, + required this.chainLabel, + required this.colorScheme, + required this.textTheme, + this.excludeChain, + }) : super(key: key); + + @override + _ChainLabelsState createState() => _ChainLabelsState(); +} + +class _ChainLabelsState extends State<_ChainLabels> { + late ValueNotifier selectedChain; + + @override + void initState() { + super.initState(); + selectedChain = ValueNotifier(widget.chainLabel); + } + + void updateSelectedChain(String newChain) { + selectedChain.value = newChain; + } + + @override + Widget build(BuildContext context) { + final filteredChains = widget.excludeChain != null + ? widget.chains.where((chain) => chain != widget.excludeChain).toList() + : widget.chains; + + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + DropdownButtonHideUnderline( + child: ValueListenableBuilder( + valueListenable: selectedChain, + builder: (context, value, child) { + return DropdownButton( + value: value, + selectedItemBuilder: (BuildContext context) { + return filteredChains.map((String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 6.0), + child: Row( + children: [ + Image.asset( + _getChainIcon(value), + fit: BoxFit.contain, + color: widget.colorScheme.onSurface, + width: 30, + height: 30, + ), + const SizedBox(width: 5), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'TFT', + style: widget.textTheme.bodySmall!.copyWith( + color: widget.colorScheme.onSurface, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 2), + Text( + value, + style: widget.textTheme.bodySmall!.copyWith( + color: widget.colorScheme.onSurface, + ), + softWrap: true, + ), + ], + ), + ], ), - Text( - rightChainLabel, - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: colorScheme.onSurface, + ); + }).toList(); + }, + items: filteredChains.map((String value) { + return DropdownMenuItem( + value: value, + child: Row( + children: [ + Image.asset( + _getChainIcon(value), + fit: BoxFit.contain, + color: widget.colorScheme.onSurface, + width: 20, + height: 20, + ), + const SizedBox(width: 5), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'TFT', + style: widget.textTheme.bodySmall!.copyWith( + color: widget.colorScheme.onSurface, + fontWeight: FontWeight.bold, + ), ), - softWrap: true, - ), - ], - ), - ), - ], - ), + const SizedBox(height: 2), + Text( + value, + style: widget.textTheme.bodySmall!.copyWith( + color: widget.colorScheme.onSurface, + ), + softWrap: true, + ), + ], + ), + ], + ), + ); + }).toList(), + onChanged: (String? newValue) { + selectedChain.value = newValue!; + }, + ); + }, ), - ], - ), + ), + ], ); } + + String _getChainIcon(String chain) { + switch (chain) { + case 'Stellar': + return 'assets/stellar.png'; + case 'Solana': + return 'assets/solana.png'; + case 'TF Chain': + default: + return 'assets/tf_chain.png'; + } + } }