From 281b3834d3be0bc8d3f9c7fe15305a32e6038f79 Mon Sep 17 00:00:00 2001 From: Arseni Date: Fri, 6 Mar 2026 17:48:36 +0300 Subject: [PATCH] wallet card redesign --- .../pweb/lib/app/router/payout_shell.dart | 8 +- .../dashboard/balance/actions_ui.dart | 19 +++ .../dashboard/balance/carousel.dart} | 45 +++++- .../dashboard/balance/source_actions.dart | 112 +++++++++++++ .../dashboard/balance/source_copy.dart | 35 ++++ .../buttons/balance/actions/bar.dart | 83 ++++++++++ .../hover_expandable_action_button.dart | 80 +++++++++ .../dashboard/buttons/balance/amount.dart | 13 +- .../dashboard/buttons/balance/carousel.dart | 152 ------------------ .../buttons/balance/carousel/card_item.dart | 47 ++++++ .../buttons/balance/carousel/cards_view.dart | 61 +++++++ .../buttons/balance/carousel/carousel.dart | 63 ++++++++ .../buttons/balance/carousel/navigation.dart | 38 +++++ .../dashboard/buttons/balance/config.dart | 19 ++- .../dashboard/buttons/balance/header.dart | 67 ++++---- .../buttons/balance/ledger_amount.dart | 17 +- .../dashboard/buttons/balance/providers.dart | 3 +- .../balance/source/actions/ledger.dart | 33 ++-- .../balance/source/actions/wallet.dart | 30 +--- .../buttons/balance/source/card.dart | 45 +++++- .../buttons/balance/source/card_layout.dart | 43 ++--- .../balance/{ => source/cards}/ledger.dart | 0 .../{card.dart => source/cards/wallet.dart} | 0 .../source/layout/amount_with_refresh.dart | 38 +++++ .../balance/source/layout/copyable_field.dart | 62 +++++++ .../balance/source/layout/wide_body.dart | 89 ++++++++++ .../dashboard/buttons/balance/widget.dart | 9 +- .../payouts/amount/{feild.dart => field.dart} | 0 .../dashboard/payouts/amount/widget.dart | 3 +- 29 files changed, 927 insertions(+), 287 deletions(-) create mode 100644 frontend/pweb/lib/controllers/dashboard/balance/actions_ui.dart rename frontend/pweb/lib/{pages/dashboard/buttons/balance/controller.dart => controllers/dashboard/balance/carousel.dart} (75%) create mode 100644 frontend/pweb/lib/controllers/dashboard/balance/source_actions.dart create mode 100644 frontend/pweb/lib/controllers/dashboard/balance/source_copy.dart create mode 100644 frontend/pweb/lib/pages/dashboard/buttons/balance/actions/bar.dart create mode 100644 frontend/pweb/lib/pages/dashboard/buttons/balance/actions/hover_expandable_action_button.dart delete mode 100644 frontend/pweb/lib/pages/dashboard/buttons/balance/carousel.dart create mode 100644 frontend/pweb/lib/pages/dashboard/buttons/balance/carousel/card_item.dart create mode 100644 frontend/pweb/lib/pages/dashboard/buttons/balance/carousel/cards_view.dart create mode 100644 frontend/pweb/lib/pages/dashboard/buttons/balance/carousel/carousel.dart create mode 100644 frontend/pweb/lib/pages/dashboard/buttons/balance/carousel/navigation.dart rename frontend/pweb/lib/pages/dashboard/buttons/balance/{ => source/cards}/ledger.dart (100%) rename frontend/pweb/lib/pages/dashboard/buttons/balance/{card.dart => source/cards/wallet.dart} (100%) create mode 100644 frontend/pweb/lib/pages/dashboard/buttons/balance/source/layout/amount_with_refresh.dart create mode 100644 frontend/pweb/lib/pages/dashboard/buttons/balance/source/layout/copyable_field.dart create mode 100644 frontend/pweb/lib/pages/dashboard/buttons/balance/source/layout/wide_body.dart rename frontend/pweb/lib/pages/dashboard/payouts/amount/{feild.dart => field.dart} (100%) diff --git a/frontend/pweb/lib/app/router/payout_shell.dart b/frontend/pweb/lib/app/router/payout_shell.dart index 768b84b2..eda9c9b7 100644 --- a/frontend/pweb/lib/app/router/payout_shell.dart +++ b/frontend/pweb/lib/app/router/payout_shell.dart @@ -229,7 +229,7 @@ RouteBase payoutShellRoute() => ShellRoute( _startPayment(context, recipient: null, paymentType: type), onTopUp: (wallet) => _openWalletTopUp(context, wallet), onLedgerAddFunds: (account) => _openLedgerAddFunds(context, account), - onWalletTap: (wallet) => _openWalletEdit(context, wallet), + onWalletTap: (wallet) => _openWalletTopUp(context, wallet), onLedgerTap: (account) => _openLedgerEdit(context, account), ), ), @@ -385,12 +385,6 @@ void _openEditRecipient(BuildContext context, {required Recipient recipient}) { context.pushNamed(PayoutRoutes.editRecipient); } -void _openWalletEdit(BuildContext context, Wallet wallet) { - context.read().selectWallet(wallet); - context.read().selectWallet(wallet); - context.pushToEditWallet(); -} - void _openLedgerEdit(BuildContext context, LedgerAccount account) { context.read().selectLedgerByRef( account.ledgerAccountRef, diff --git a/frontend/pweb/lib/controllers/dashboard/balance/actions_ui.dart b/frontend/pweb/lib/controllers/dashboard/balance/actions_ui.dart new file mode 100644 index 00000000..ff772cfd --- /dev/null +++ b/frontend/pweb/lib/controllers/dashboard/balance/actions_ui.dart @@ -0,0 +1,19 @@ +import 'package:flutter/foundation.dart'; + + +class BalanceActionsUiController extends ChangeNotifier { + int? _hoveredButtonIndex; + + int? get hoveredButtonIndex => _hoveredButtonIndex; + + bool isExpanded(int index) => _hoveredButtonIndex == index; + + void onHoverChanged(int index, bool hovered) { + final next = hovered + ? index + : (_hoveredButtonIndex == index ? null : _hoveredButtonIndex); + if (next == _hoveredButtonIndex) return; + _hoveredButtonIndex = next; + notifyListeners(); + } +} diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/controller.dart b/frontend/pweb/lib/controllers/dashboard/balance/carousel.dart similarity index 75% rename from frontend/pweb/lib/pages/dashboard/buttons/balance/controller.dart rename to frontend/pweb/lib/controllers/dashboard/balance/carousel.dart index 88176825..f7129118 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/controller.dart +++ b/frontend/pweb/lib/controllers/dashboard/balance/carousel.dart @@ -1,15 +1,22 @@ -import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; import 'package:pshared/controllers/balance_mask/wallets.dart'; import 'package:pshared/provider/ledger.dart'; import 'package:pweb/models/dashboard/balance_item.dart'; +import 'package:pweb/pages/dashboard/buttons/balance/config.dart'; -class BalanceCarouselController with ChangeNotifier { +class BalanceCarouselController extends ChangeNotifier { + BalanceCarouselController() + : pageController = PageController( + viewportFraction: WalletCardConfig.viewportFraction, + ); + WalletsController? _walletsController; List _items = const [BalanceItem.addAction()]; int _index = 0; + final PageController pageController; List get items => _items; int get index => _index; @@ -32,6 +39,7 @@ class BalanceCarouselController with ChangeNotifier { _items = nextItems; _index = nextIndex; + _syncPageController(); if (hasItemsChanged || hasIndexChanged) { notifyListeners(); @@ -50,9 +58,24 @@ class BalanceCarouselController with ChangeNotifier { notifyListeners(); } - void goBack() => onPageChanged(_index - 1); + void goBack() => animateTo(_index - 1); - void goForward() => onPageChanged(_index + 1); + void goForward() => animateTo(_index + 1); + + void animateTo(int index) { + final target = _clampIndex(index, _items.length); + if (!pageController.hasClients) { + onPageChanged(target); + return; + } + pageController.animateToPage( + target, + duration: const Duration(milliseconds: 220), + curve: Curves.easeOut, + ); + } + + void syncPageController() => _syncPageController(); int _resolveNextIndex( List nextItems, @@ -126,4 +149,18 @@ class BalanceCarouselController with ChangeNotifier { if (walletsController.selectedWallet?.id == wallet.id) return; walletsController.selectWallet(wallet); } + + void _syncPageController() { + if (!pageController.hasClients || _items.isEmpty) return; + final current = pageController.page?.round(); + final target = _clampIndex(_index, _items.length); + if (current == target) return; + pageController.jumpToPage(target); + } + + @override + void dispose() { + pageController.dispose(); + super.dispose(); + } } diff --git a/frontend/pweb/lib/controllers/dashboard/balance/source_actions.dart b/frontend/pweb/lib/controllers/dashboard/balance/source_actions.dart new file mode 100644 index 00000000..c055e5c6 --- /dev/null +++ b/frontend/pweb/lib/controllers/dashboard/balance/source_actions.dart @@ -0,0 +1,112 @@ +import 'package:flutter/material.dart'; + +import 'package:go_router/go_router.dart'; +import 'package:provider/provider.dart'; + +import 'package:pshared/controllers/payment/source.dart'; +import 'package:pshared/models/payment/type.dart'; + +import 'package:pweb/app/router/payout_routes.dart'; + + +class BalanceActionButtonState { + final String label; + final IconData icon; + final VoidCallback onPressed; + + const BalanceActionButtonState({ + required this.label, + required this.icon, + required this.onPressed, + }); +} + +class BalanceActionsState { + final BalanceActionButtonState topLeading; + final BalanceActionButtonState topTrailing; + final BalanceActionButtonState bottom; + + const BalanceActionsState({ + required this.topLeading, + required this.topTrailing, + required this.bottom, + }); +} + +class BalanceSourceActionsController { + const BalanceSourceActionsController(); + + BalanceActionsState wallet({ + required BuildContext context, + required String walletRef, + required VoidCallback onAddFunds, + }) { + return BalanceActionsState( + topLeading: BalanceActionButtonState( + label: 'Operation History', + icon: Icons.history_rounded, + onPressed: () => _openWalletOperationHistory(context, walletRef), + ), + topTrailing: BalanceActionButtonState( + label: 'Send Payout', + icon: Icons.send_rounded, + onPressed: () => _sendWalletPayout(context, walletRef), + ), + bottom: BalanceActionButtonState( + label: 'Wallet Details / Add Funds', + icon: Icons.account_balance_wallet_rounded, + onPressed: onAddFunds, + ), + ); + } + + BalanceActionsState ledger({ + required BuildContext context, + required String ledgerAccountRef, + required VoidCallback onAddFunds, + required VoidCallback onWalletDetails, + }) { + return BalanceActionsState( + topLeading: BalanceActionButtonState( + label: 'Operation History / Wallet Details', + icon: Icons.receipt_long_rounded, + onPressed: onWalletDetails, + ), + topTrailing: BalanceActionButtonState( + label: 'Send Payout', + icon: Icons.send_rounded, + onPressed: () => _sendLedgerPayout(context, ledgerAccountRef), + ), + bottom: BalanceActionButtonState( + label: 'Add Funds', + icon: Icons.add_card_rounded, + onPressed: onAddFunds, + ), + ); + } + + void _openWalletOperationHistory(BuildContext context, String walletRef) { + context.read().selectWalletByRef(walletRef); + context.pushNamed(PayoutRoutes.editWallet); + } + + void _sendWalletPayout(BuildContext context, String walletRef) { + context.read().selectWalletByRef(walletRef); + context.pushNamed( + PayoutRoutes.payment, + queryParameters: PayoutRoutes.buildQueryParameters( + paymentType: PaymentType.wallet, + ), + ); + } + + void _sendLedgerPayout(BuildContext context, String ledgerAccountRef) { + context.read().selectLedgerByRef(ledgerAccountRef); + context.pushNamed( + PayoutRoutes.payment, + queryParameters: PayoutRoutes.buildQueryParameters( + paymentType: PaymentType.ledger, + ), + ); + } +} diff --git a/frontend/pweb/lib/controllers/dashboard/balance/source_copy.dart b/frontend/pweb/lib/controllers/dashboard/balance/source_copy.dart new file mode 100644 index 00000000..787a54c7 --- /dev/null +++ b/frontend/pweb/lib/controllers/dashboard/balance/source_copy.dart @@ -0,0 +1,35 @@ +import 'package:flutter/services.dart'; + + +class BalanceCopyState { + final String label; + final String payload; + + const BalanceCopyState({required this.label, required this.payload}); + + bool get canCopy => payload.trim().isNotEmpty; +} + +class BalanceSourceCopyController { + const BalanceSourceCopyController(); + + BalanceCopyState wallet(String? depositAddress) { + return BalanceCopyState( + label: 'Copy Deposit Address', + payload: depositAddress?.trim() ?? '', + ); + } + + BalanceCopyState ledger(String? accountCode) { + return BalanceCopyState( + label: 'Copy Deposit Address', + payload: accountCode?.trim() ?? '', + ); + } + + Future copy(BalanceCopyState state) async { + if (!state.canCopy) return false; + await Clipboard.setData(ClipboardData(text: state.payload)); + return true; + } +} diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/actions/bar.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/actions/bar.dart new file mode 100644 index 00000000..e4c4b62c --- /dev/null +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/actions/bar.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; + +import 'package:pweb/controllers/dashboard/balance/actions_ui.dart'; +import 'package:pweb/controllers/dashboard/balance/source_actions.dart'; +import 'package:pweb/pages/dashboard/buttons/balance/actions/hover_expandable_action_button.dart'; + + +class BalanceActionsBar extends StatefulWidget { + final BalanceActionsState state; + + const BalanceActionsBar({super.key, required this.state}); + + @override + State createState() => _BalanceActionsBarState(); +} + +class _BalanceActionsBarState extends State { + static const double _buttonHeight = 34.0; + static const double _buttonGap = 6.0; + static const double _iconSize = 18.0; + static const double _textGap = 8.0; + static const double _horizontalPadding = 6.0; + + final BalanceActionsUiController _uiController = BalanceActionsUiController(); + + @override + void dispose() { + _uiController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final textStyle = Theme.of(context).textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w400, + color: colorScheme.onSecondary, + fontSize: 14, + ); + final buttons = [ + widget.state.topLeading, + widget.state.topTrailing, + widget.state.bottom, + ]; + + return ListenableBuilder( + listenable: _uiController, + builder: (context, _) { + return Align( + alignment: Alignment.centerRight, + child: OverflowBox( + alignment: Alignment.centerRight, + minWidth: 0, + maxWidth: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + for (var i = 0; i < buttons.length; i++) ...[ + HoverExpandableActionButton( + height: _buttonHeight, + icon: buttons[i].icon, + label: buttons[i].label, + iconSize: _iconSize, + textStyle: textStyle, + expanded: _uiController.isExpanded(i), + textGap: _textGap, + horizontalPadding: _horizontalPadding, + onHoverChanged: (hovered) => + _uiController.onHoverChanged(i, hovered), + onPressed: buttons[i].onPressed, + ), + if (i != buttons.length - 1) + const SizedBox(height: _buttonGap), + ], + ], + ), + ), + ); + }, + ); + } +} diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/actions/hover_expandable_action_button.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/actions/hover_expandable_action_button.dart new file mode 100644 index 00000000..3dba3dea --- /dev/null +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/actions/hover_expandable_action_button.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; + + +class HoverExpandableActionButton extends StatelessWidget { + final double height; + final IconData icon; + final String label; + final double iconSize; + final TextStyle? textStyle; + final bool expanded; + final double textGap; + final double horizontalPadding; + final ValueChanged onHoverChanged; + final VoidCallback onPressed; + + const HoverExpandableActionButton({ + super.key, + required this.height, + required this.icon, + required this.label, + required this.iconSize, + required this.textStyle, + required this.expanded, + required this.textGap, + required this.horizontalPadding, + required this.onHoverChanged, + required this.onPressed, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + + return MouseRegion( + onEnter: (_) => onHoverChanged(true), + onExit: (_) => onHoverChanged(false), + child: AnimatedContainer( + duration: const Duration(milliseconds: 220), + curve: Curves.easeOutCubic, + height: height, + decoration: BoxDecoration( + color: colorScheme.primaryFixed, + borderRadius: BorderRadius.circular(999), + ), + child: Material( + type: MaterialType.transparency, + child: InkWell( + borderRadius: BorderRadius.circular(999), + onTap: onPressed, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: horizontalPadding), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: iconSize, color: colorScheme.onSecondary), + AnimatedSize( + duration: const Duration(milliseconds: 220), + curve: Curves.easeOutCubic, + alignment: Alignment.centerRight, + child: expanded + ? Padding( + padding: EdgeInsets.only(left: textGap), + child: Text( + label, + maxLines: 1, + overflow: TextOverflow.visible, + style: textStyle, + ), + ) + : const SizedBox.shrink(), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/amount.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/amount.dart index 3871379d..2eb8c096 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/amount.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/amount.dart @@ -29,12 +29,17 @@ class BalanceAmount extends StatelessWidget { final isMasked = wallets.isBalanceMasked(wallet.id); return Row( + mainAxisSize: MainAxisSize.min, children: [ Text( - isMasked ? '•••• $currencyBalance' : '${amountToString(wallet.balance)} $currencyBalance', - style: textTheme.headlineSmall?.copyWith( + isMasked + ? '•••• $currencyBalance' + : '${amountToString(wallet.balance)} $currencyBalance', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.bold, - color: colorScheme.onSurface, + color: colorScheme.primary, ), ), const SizedBox(width: _iconSpacing), @@ -43,7 +48,7 @@ class BalanceAmount extends StatelessWidget { child: Icon( isMasked ? Icons.visibility_off : Icons.visibility, size: _iconSize, - color: colorScheme.onSurface, + color: colorScheme.primary, ), ), ], diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel.dart deleted file mode 100644 index 999aba51..00000000 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel.dart +++ /dev/null @@ -1,152 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/gestures.dart'; - -import 'package:pshared/models/ledger/account.dart'; -import 'package:pshared/models/payment/wallet.dart'; - -import 'package:pweb/models/dashboard/balance_item.dart'; -import 'package:pweb/pages/dashboard/buttons/balance/add/card.dart'; -import 'package:pweb/pages/dashboard/buttons/balance/card.dart'; -import 'package:pweb/pages/dashboard/buttons/balance/config.dart'; -import 'package:pweb/pages/dashboard/buttons/balance/indicator.dart'; -import 'package:pweb/pages/dashboard/buttons/balance/ledger.dart'; - - -class BalanceCarousel extends StatefulWidget { - final List items; - final int currentIndex; - final ValueChanged onIndexChanged; - final ValueChanged onTopUp; - final ValueChanged onLedgerAddFunds; - final ValueChanged onWalletTap; - final ValueChanged onLedgerTap; - - const BalanceCarousel({ - super.key, - required this.items, - required this.currentIndex, - required this.onIndexChanged, - required this.onTopUp, - required this.onLedgerAddFunds, - required this.onWalletTap, - required this.onLedgerTap, - }); - - @override - State createState() => _BalanceCarouselState(); -} - -class _BalanceCarouselState extends State { - late final PageController _controller; - - @override - void initState() { - super.initState(); - _controller = PageController( - initialPage: widget.currentIndex, - viewportFraction: WalletCardConfig.viewportFraction, - ); - } - - @override - void didUpdateWidget(covariant BalanceCarousel oldWidget) { - super.didUpdateWidget(oldWidget); - if (!mounted) return; - if (_controller.hasClients) { - final currentPage = _controller.page?.round(); - if (currentPage != widget.currentIndex) { - _controller.jumpToPage(widget.currentIndex); - } - } - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - void _goToPage(int index) { - if (!_controller.hasClients) return; - _controller.animateToPage( - index, - duration: const Duration(milliseconds: 220), - curve: Curves.easeOut, - ); - } - - @override - Widget build(BuildContext context) { - if (widget.items.isEmpty) { - return const SizedBox.shrink(); - } - - final safeIndex = widget.currentIndex.clamp(0, widget.items.length - 1); - final scrollBehavior = ScrollConfiguration.of(context).copyWith( - dragDevices: const { - PointerDeviceKind.touch, - PointerDeviceKind.mouse, - PointerDeviceKind.trackpad, - }, - ); - - return Column( - children: [ - SizedBox( - height: WalletCardConfig.cardHeight, - child: MouseRegion( - cursor: SystemMouseCursors.grab, - child: ScrollConfiguration( - behavior: scrollBehavior, - child: PageView.builder( - controller: _controller, - onPageChanged: widget.onIndexChanged, - itemCount: widget.items.length, - itemBuilder: (context, index) { - final item = widget.items[index]; - final Widget card = switch (item) { - WalletBalanceItem(:final wallet) => WalletCard( - wallet: wallet, - onTopUp: () => widget.onTopUp(wallet), - onTap: () => widget.onWalletTap(wallet), - ), - LedgerBalanceItem(:final account) => LedgerAccountCard( - account: account, - onTap: () => widget.onLedgerTap(account), - onAddFunds: () => widget.onLedgerAddFunds(account), - ), - AddBalanceActionItem() => const AddBalanceCard(), - }; - - return Padding( - padding: WalletCardConfig.cardPadding, - child: card, - ); - }, - ), - ), - ), - ), - const SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - IconButton( - onPressed: safeIndex > 0 ? () => _goToPage(safeIndex - 1) : null, - icon: const Icon(Icons.arrow_back), - ), - const SizedBox(width: 16), - CarouselIndicator(itemCount: widget.items.length, index: safeIndex), - const SizedBox(width: 16), - IconButton( - onPressed: safeIndex < widget.items.length - 1 - ? () => _goToPage(safeIndex + 1) - : null, - icon: const Icon(Icons.arrow_forward), - ), - ], - ), - ], - ); - } -} diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel/card_item.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel/card_item.dart new file mode 100644 index 00000000..d672321e --- /dev/null +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel/card_item.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; + +import 'package:pshared/models/ledger/account.dart'; +import 'package:pshared/models/payment/wallet.dart'; + +import 'package:pweb/models/dashboard/balance_item.dart'; +import 'package:pweb/pages/dashboard/buttons/balance/add/card.dart'; +import 'package:pweb/pages/dashboard/buttons/balance/config.dart'; +import 'package:pweb/pages/dashboard/buttons/balance/source/cards/ledger.dart'; +import 'package:pweb/pages/dashboard/buttons/balance/source/cards/wallet.dart'; + + +class BalanceCarouselCardItem extends StatelessWidget { + final BalanceItem item; + final ValueChanged onTopUp; + final ValueChanged onLedgerAddFunds; + final ValueChanged onWalletTap; + final ValueChanged onLedgerTap; + + const BalanceCarouselCardItem({ + super.key, + required this.item, + required this.onTopUp, + required this.onLedgerAddFunds, + required this.onWalletTap, + required this.onLedgerTap, + }); + + @override + Widget build(BuildContext context) { + final card = switch (item) { + WalletBalanceItem(:final wallet) => WalletCard( + wallet: wallet, + onTopUp: () => onTopUp(wallet), + onTap: () => onWalletTap(wallet), + ), + LedgerBalanceItem(:final account) => LedgerAccountCard( + account: account, + onTap: () => onLedgerTap(account), + onAddFunds: () => onLedgerAddFunds(account), + ), + AddBalanceActionItem() => const AddBalanceCard(), + }; + + return Padding(padding: WalletCardConfig.cardPadding, child: card); + } +} diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel/cards_view.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel/cards_view.dart new file mode 100644 index 00000000..94a5fee8 --- /dev/null +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel/cards_view.dart @@ -0,0 +1,61 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +import 'package:pshared/models/ledger/account.dart'; +import 'package:pshared/models/payment/wallet.dart'; + +import 'package:pweb/controllers/dashboard/balance/carousel.dart'; +import 'package:pweb/pages/dashboard/buttons/balance/carousel/card_item.dart'; + + +class BalanceCarouselCardsView extends StatelessWidget { + final BalanceCarouselController controller; + final ValueChanged onTopUp; + final ValueChanged onLedgerAddFunds; + final ValueChanged onWalletTap; + final ValueChanged onLedgerTap; + final double height; + + const BalanceCarouselCardsView({ + super.key, + required this.controller, + required this.onTopUp, + required this.onLedgerAddFunds, + required this.onWalletTap, + required this.onLedgerTap, + required this.height, + }); + + @override + Widget build(BuildContext context) { + final scrollBehavior = ScrollConfiguration.of(context).copyWith( + dragDevices: const { + PointerDeviceKind.touch, + PointerDeviceKind.mouse, + PointerDeviceKind.trackpad, + }, + ); + + return SizedBox( + height: height, + child: MouseRegion( + cursor: SystemMouseCursors.grab, + child: ScrollConfiguration( + behavior: scrollBehavior, + child: PageView.builder( + controller: controller.pageController, + onPageChanged: controller.onPageChanged, + itemCount: controller.items.length, + itemBuilder: (context, index) => BalanceCarouselCardItem( + item: controller.items[index], + onTopUp: onTopUp, + onLedgerAddFunds: onLedgerAddFunds, + onWalletTap: onWalletTap, + onLedgerTap: onLedgerTap, + ), + ), + ), + ), + ); + } +} diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel/carousel.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel/carousel.dart new file mode 100644 index 00000000..52544c17 --- /dev/null +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel/carousel.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; + +import 'package:pshared/models/ledger/account.dart'; +import 'package:pshared/models/payment/wallet.dart'; + +import 'package:pweb/controllers/dashboard/balance/carousel.dart'; +import 'package:pweb/pages/dashboard/buttons/balance/carousel/cards_view.dart'; +import 'package:pweb/pages/dashboard/buttons/balance/carousel/navigation.dart'; +import 'package:pweb/pages/dashboard/buttons/balance/config.dart'; + + +class BalanceCarousel extends StatelessWidget { + final BalanceCarouselController controller; + final ValueChanged onTopUp; + final ValueChanged onLedgerAddFunds; + final ValueChanged onWalletTap; + final ValueChanged onLedgerTap; + + const BalanceCarousel({ + super.key, + required this.controller, + required this.onTopUp, + required this.onLedgerAddFunds, + required this.onWalletTap, + required this.onLedgerTap, + }); + + @override + Widget build(BuildContext context) { + if (controller.items.isEmpty) { + return const SizedBox.shrink(); + } + + WidgetsBinding.instance.addPostFrameCallback((_) { + controller.syncPageController(); + }); + + final safeIndex = controller.index.clamp(0, controller.items.length - 1); + + return LayoutBuilder( + builder: (context, constraints) { + final cardHeight = WalletCardConfig.cardHeightForWidth( + constraints.maxWidth, + ); + + return Column( + children: [ + BalanceCarouselCardsView( + controller: controller, + onTopUp: onTopUp, + onLedgerAddFunds: onLedgerAddFunds, + onWalletTap: onWalletTap, + onLedgerTap: onLedgerTap, + height: cardHeight, + ), + const SizedBox(height: 16), + BalanceCarouselNavigation(controller: controller, index: safeIndex), + ], + ); + }, + ); + } +} diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel/navigation.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel/navigation.dart new file mode 100644 index 00000000..33118d22 --- /dev/null +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel/navigation.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; + +import 'package:pweb/controllers/dashboard/balance/carousel.dart'; +import 'package:pweb/pages/dashboard/buttons/balance/indicator.dart'; + + +class BalanceCarouselNavigation extends StatelessWidget { + final BalanceCarouselController controller; + final int index; + + const BalanceCarouselNavigation({ + super.key, + required this.controller, + required this.index, + }); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + onPressed: index > 0 ? controller.goBack : null, + icon: const Icon(Icons.arrow_back), + ), + const SizedBox(width: 16), + CarouselIndicator(itemCount: controller.items.length, index: index), + const SizedBox(width: 16), + IconButton( + onPressed: index < controller.items.length - 1 + ? controller.goForward + : null, + icon: const Icon(Icons.arrow_forward), + ), + ], + ); + } +} diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/config.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/config.dart index a70b9234..269ae6c8 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/config.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/config.dart @@ -2,14 +2,21 @@ import 'package:flutter/material.dart'; abstract class WalletCardConfig { - static const double cardHeight = 145.0; static const double elevation = 4.0; static const double borderRadius = 16.0; - static const double viewportFraction = 0.9; - - static const EdgeInsets cardPadding = EdgeInsets.symmetric(horizontal: 8); - static const EdgeInsets contentPadding = EdgeInsets.all(16); - + static const double viewportFraction = 0.96; + + static const EdgeInsets cardPadding = EdgeInsets.symmetric(horizontal: 6); + static const EdgeInsets contentPadding = EdgeInsets.symmetric( + horizontal: 28, + vertical: 16, + ); + static const double dotSize = 8.0; static const EdgeInsets dotMargin = EdgeInsets.symmetric(horizontal: 4); + + static double cardHeightForWidth(double width) { + final adaptiveHeight = width * 0.18; + return adaptiveHeight.clamp(150.0, 230.0); + } } diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/header.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/header.dart index a00a2075..3e0eb4cb 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/header.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/header.dart @@ -20,46 +20,51 @@ class BalanceHeader extends StatelessWidget { final subtitleText = subtitle?.trim(); final badgeText = badge?.trim(); - return Row( + return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: Text( title, - style: textTheme.titleMedium?.copyWith( - color: colorScheme.onSurface, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: textTheme.titleLarge?.copyWith( + color: colorScheme.primary, + fontWeight: FontWeight.w700, ), ), - if (subtitleText != null && subtitleText.isNotEmpty) - Text( - subtitleText, - style: textTheme.bodySmall?.copyWith( - color: colorScheme.onSurfaceVariant, - fontWeight: FontWeight.w500, + ), + if (badgeText != null && badgeText.isNotEmpty) ...[ + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: colorScheme.primaryFixed, + borderRadius: BorderRadius.circular(999), + ), + child: Text( + badgeText, + style: textTheme.labelSmall?.copyWith( + color: colorScheme.onSecondary, + fontWeight: FontWeight.w700, ), ), - ], - ), - ), - if (badgeText != null && badgeText.isNotEmpty) ...[ - const SizedBox(width: 8), - Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), - decoration: BoxDecoration( - color: colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(999), - ), - child: Text( - badgeText, - style: textTheme.bodyMedium?.copyWith( - color: colorScheme.onPrimaryContainer, - fontWeight: FontWeight.w600, ), + ], + ], + ), + if (subtitleText != null && subtitleText.isNotEmpty) + Text( + subtitleText, + style: textTheme.titleSmall?.copyWith( + color: colorScheme.primaryFixed, + fontWeight: FontWeight.w500, ), + maxLines: 1, ), - ], ], ); } diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/ledger_amount.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/ledger_amount.dart index fe0c60c4..8922b636 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/ledger_amount.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/ledger_amount.dart @@ -26,14 +26,15 @@ class LedgerBalanceAmount extends StatelessWidget { : LedgerBalanceFormatter.format(account); return Row( + mainAxisSize: MainAxisSize.min, children: [ - Flexible( - child: Text( - balance, - style: textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, - ), + Text( + balance, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.bold, + color: colorScheme.primary, ), ), const SizedBox(width: 12), @@ -44,7 +45,7 @@ class LedgerBalanceAmount extends StatelessWidget { child: Icon( isMasked ? Icons.visibility_off : Icons.visibility, size: 24, - color: colorScheme.onSurface, + color: colorScheme.primary, ), ), ], diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/providers.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/providers.dart index 1de0dac2..8f0f2b35 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/providers.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/providers.dart @@ -5,7 +5,8 @@ import 'package:provider/provider.dart'; import 'package:pshared/controllers/balance_mask/wallets.dart'; import 'package:pshared/provider/ledger.dart'; -import 'package:pweb/pages/dashboard/buttons/balance/controller.dart'; +import 'package:pweb/controllers/dashboard/balance/carousel.dart'; + class BalanceWidgetProviders extends StatelessWidget { final Widget child; diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/source/actions/ledger.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/source/actions/ledger.dart index 3a58413a..10e7711f 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/source/actions/ledger.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/source/actions/ledger.dart @@ -1,44 +1,31 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import 'package:pshared/provider/ledger.dart'; - +import 'package:pweb/controllers/dashboard/balance/source_actions.dart'; import 'package:pweb/pages/dashboard/buttons/balance/actions/bar.dart'; -import 'package:pweb/generated/i18n/app_localizations.dart'; - class LedgerSourceActions extends StatelessWidget { final String ledgerAccountRef; final VoidCallback onAddFunds; + final VoidCallback onWalletDetails; const LedgerSourceActions({ super.key, required this.ledgerAccountRef, required this.onAddFunds, + required this.onWalletDetails, }); @override Widget build(BuildContext context) { - final ledgerProvider = context.watch(); - final loc = AppLocalizations.of(context)!; - final isBusy = - ledgerProvider.isWalletRefreshing(ledgerAccountRef) || - ledgerProvider.isLoading; - final hasTarget = ledgerProvider.accounts.any( - (a) => a.ledgerAccountRef == ledgerAccountRef, + const controller = BalanceSourceActionsController(); + final state = controller.ledger( + context: context, + ledgerAccountRef: ledgerAccountRef, + onAddFunds: onAddFunds, + onWalletDetails: onWalletDetails, ); - return BalanceActionsBar( - isRefreshBusy: isBusy, - canRefresh: hasTarget, - onRefresh: () { - context.read().refreshBalance(ledgerAccountRef); - }, - onAddFunds: onAddFunds, - refreshLabel: loc.refreshBalance, - addFundsLabel: loc.addFunds, - ); + return BalanceActionsBar(state: state); } } diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/source/actions/wallet.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/source/actions/wallet.dart index 8c872026..e699e513 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/source/actions/wallet.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/source/actions/wallet.dart @@ -1,13 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import 'package:pshared/provider/payment/wallets.dart'; - +import 'package:pweb/controllers/dashboard/balance/source_actions.dart'; import 'package:pweb/pages/dashboard/buttons/balance/actions/bar.dart'; -import 'package:pweb/generated/i18n/app_localizations.dart'; - class WalletSourceActions extends StatelessWidget { final String walletRef; @@ -21,22 +16,13 @@ class WalletSourceActions extends StatelessWidget { @override Widget build(BuildContext context) { - final walletsProvider = context.watch(); - final loc = AppLocalizations.of(context)!; - final isBusy = - walletsProvider.isWalletRefreshing(walletRef) || - walletsProvider.isLoading; - final hasTarget = walletsProvider.wallets.any((w) => w.id == walletRef); - - return BalanceActionsBar( - isRefreshBusy: isBusy, - canRefresh: hasTarget, - onRefresh: () { - context.read().refreshBalance(walletRef); - }, + const controller = BalanceSourceActionsController(); + final state = controller.wallet( + context: context, + walletRef: walletRef, onAddFunds: onAddFunds, - refreshLabel: loc.refreshBalance, - addFundsLabel: loc.addFunds, ); + + return BalanceActionsBar(state: state); } -} \ No newline at end of file +} diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/source/card.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/source/card.dart index bf56777f..5e511236 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/source/card.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/source/card.dart @@ -9,11 +9,15 @@ import 'package:pshared/models/payment/source_type.dart'; import 'package:pshared/models/payment/wallet.dart'; import 'package:pshared/utils/l10n/chain.dart'; +import 'package:pweb/controllers/dashboard/balance/source_copy.dart'; +import 'package:pweb/models/state/visibility.dart'; import 'package:pweb/pages/dashboard/buttons/balance/amount.dart'; import 'package:pweb/pages/dashboard/buttons/balance/ledger_amount.dart'; import 'package:pweb/pages/dashboard/buttons/balance/source/actions/ledger.dart'; import 'package:pweb/pages/dashboard/buttons/balance/source/actions/wallet.dart'; import 'package:pweb/pages/dashboard/buttons/balance/source/card_layout.dart'; +import 'package:pweb/widgets/refresh_balance/ledger.dart'; +import 'package:pweb/widgets/refresh_balance/wallet.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; @@ -24,6 +28,8 @@ class BalanceSourceCard extends StatelessWidget { final LedgerAccount? _ledgerAccount; final VoidCallback onTap; final VoidCallback onAddFunds; + static const BalanceSourceCopyController _copyController = + BalanceSourceCopyController(); const BalanceSourceCard.wallet({ super.key, @@ -55,12 +61,29 @@ class BalanceSourceCard extends StatelessWidget { ? null : wallet.network!.localizedName(context); final symbol = wallet.tokenSymbol?.trim(); + final copyState = _copyController.wallet(wallet.depositAddress); return BalanceSourceCardLayout( title: wallet.name, subtitle: networkLabel, badge: (symbol == null || symbol.isEmpty) ? null : symbol, - onTap: onTap, + onTap: null, + copyLabel: copyState.label, + canCopy: copyState.canCopy, + onCopy: copyState.canCopy + ? () async { + final copied = await _copyController.copy(copyState); + if (!copied || !context.mounted) return; + final loc = AppLocalizations.of(context)!; + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(loc.addressCopied))); + } + : null, + refreshButton: WalletBalanceRefreshButton( + walletRef: wallet.id, + iconOnly: VisibilityState.hidden, + ), actions: WalletSourceActions( walletRef: wallet.id, onAddFunds: onAddFunds, @@ -79,19 +102,35 @@ class BalanceSourceCard extends StatelessWidget { final accountName = account.name.trim(); final accountCode = account.accountCode.trim(); final title = accountName.isNotEmpty ? accountName : loc.paymentTypeLedger; - final subtitle = accountCode.isNotEmpty ? accountCode : null; final badge = account.currency.trim().isEmpty ? null : account.currency.toUpperCase(); + final copyState = _copyController.ledger(accountCode); return BalanceSourceCardLayout( title: title, - subtitle: subtitle, + subtitle: null, badge: badge, onTap: onTap, + copyLabel: copyState.label, + canCopy: copyState.canCopy, + onCopy: copyState.canCopy + ? () async { + final copied = await _copyController.copy(copyState); + if (!copied || !context.mounted) return; + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(loc.addressCopied))); + } + : null, + refreshButton: LedgerBalanceRefreshButton( + ledgerAccountRef: account.ledgerAccountRef, + iconOnly: VisibilityState.hidden, + ), actions: LedgerSourceActions( ledgerAccountRef: account.ledgerAccountRef, onAddFunds: onAddFunds, + onWalletDetails: onTap, ), amount: LedgerBalanceAmount(account: account), ); diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/source/card_layout.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/source/card_layout.dart index 9f85c09b..ef3626ef 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/source/card_layout.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/source/card_layout.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:pweb/pages/dashboard/buttons/balance/config.dart'; -import 'package:pweb/pages/dashboard/buttons/balance/header.dart'; +import 'package:pweb/pages/dashboard/buttons/balance/source/layout/wide_body.dart'; class BalanceSourceCardLayout extends StatelessWidget { @@ -9,8 +9,12 @@ class BalanceSourceCardLayout extends StatelessWidget { final String? subtitle; final String? badge; final Widget amount; + final Widget refreshButton; final Widget actions; - final VoidCallback onTap; + final VoidCallback? onTap; + final String copyLabel; + final bool canCopy; + final VoidCallback? onCopy; const BalanceSourceCardLayout({ super.key, @@ -18,40 +22,39 @@ class BalanceSourceCardLayout extends StatelessWidget { required this.subtitle, required this.badge, required this.amount, + required this.refreshButton, required this.actions, required this.onTap, + required this.copyLabel, + required this.canCopy, + required this.onCopy, }); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; + final borderRadius = BorderRadius.circular(WalletCardConfig.borderRadius); return Card( color: colorScheme.onSecondary, elevation: WalletCardConfig.elevation, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(WalletCardConfig.borderRadius), - ), + shape: RoundedRectangleBorder(borderRadius: borderRadius), child: InkWell( - borderRadius: BorderRadius.circular(WalletCardConfig.borderRadius), + borderRadius: borderRadius, onTap: onTap, child: SizedBox.expand( child: Padding( padding: WalletCardConfig.contentPadding, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - BalanceHeader(title: title, subtitle: subtitle, badge: badge), - Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Expanded(child: amount), - const SizedBox(width: 12), - actions, - ], - ), - ], + child: BalanceSourceBody( + title: title, + subtitle: subtitle, + badge: badge, + amount: amount, + refreshButton: refreshButton, + actions: actions, + copyLabel: copyLabel, + canCopy: canCopy, + onCopy: onCopy, ), ), ), diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/ledger.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/source/cards/ledger.dart similarity index 100% rename from frontend/pweb/lib/pages/dashboard/buttons/balance/ledger.dart rename to frontend/pweb/lib/pages/dashboard/buttons/balance/source/cards/ledger.dart diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/card.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/source/cards/wallet.dart similarity index 100% rename from frontend/pweb/lib/pages/dashboard/buttons/balance/card.dart rename to frontend/pweb/lib/pages/dashboard/buttons/balance/source/cards/wallet.dart diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/source/layout/amount_with_refresh.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/source/layout/amount_with_refresh.dart new file mode 100644 index 00000000..6d6303fb --- /dev/null +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/source/layout/amount_with_refresh.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; + + +class BalanceAmountWithRefresh extends StatelessWidget { + final Widget amount; + final Widget refreshButton; + + const BalanceAmountWithRefresh({ + super.key, + required this.amount, + required this.refreshButton, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButtonTheme( + data: IconButtonThemeData( + style: IconButton.styleFrom( + minimumSize: const Size(30, 30), + maximumSize: const Size(40, 40), + padding: EdgeInsets.zero, + foregroundColor: colorScheme.primary, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + ), + child: refreshButton, + ), + const SizedBox(width: 8), + amount, + ], + ); + } +} diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/source/layout/copyable_field.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/source/layout/copyable_field.dart new file mode 100644 index 00000000..4b709f5e --- /dev/null +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/source/layout/copyable_field.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; + + +class BalanceCopyableField extends StatelessWidget { + final String label; + final bool canCopy; + final VoidCallback? onCopy; + + const BalanceCopyableField({ + super.key, + required this.label, + required this.canCopy, + required this.onCopy, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; + + return Container( + decoration: BoxDecoration( + color: colorScheme.onSecondary, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: colorScheme.primaryFixed, width: 0.6), + ), + child: InkWell( + onTap: canCopy ? onCopy : null, + borderRadius: BorderRadius.circular(12), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.copy_rounded, + size: 16, + color: canCopy + ? colorScheme.primaryFixed + : colorScheme.primary.withValues(alpha: 0.35), + ), + const SizedBox(width: 6), + Flexible( + child: Text( + label, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.labelMedium?.copyWith( + color: canCopy + ? colorScheme.primaryFixed + : colorScheme.primary.withValues(alpha: 0.45), + fontWeight: FontWeight.normal, + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/source/layout/wide_body.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/source/layout/wide_body.dart new file mode 100644 index 00000000..648e6a61 --- /dev/null +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/source/layout/wide_body.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; + +import 'package:pweb/pages/dashboard/buttons/balance/header.dart'; +import 'package:pweb/pages/dashboard/buttons/balance/source/layout/amount_with_refresh.dart'; +import 'package:pweb/pages/dashboard/buttons/balance/source/layout/copyable_field.dart'; + + +class BalanceSourceBody extends StatelessWidget { + final String title; + final String? subtitle; + final String? badge; + final Widget amount; + final Widget refreshButton; + final Widget actions; + final String copyLabel; + final bool canCopy; + final VoidCallback? onCopy; + + const BalanceSourceBody({ + super.key, + required this.title, + required this.subtitle, + required this.badge, + required this.amount, + required this.refreshButton, + required this.actions, + required this.copyLabel, + required this.canCopy, + required this.onCopy, + }); + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final sideMaxWidth = constraints.maxWidth * 0.30; + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Flexible( + fit: FlexFit.loose, + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: sideMaxWidth), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + BalanceHeader( + title: title, + subtitle: subtitle, + badge: badge, + ), + SizedBox(height: constraints.maxHeight * 0.06), + BalanceCopyableField( + label: copyLabel, + canCopy: canCopy, + onCopy: onCopy, + ), + ], + ), + ), + ), + Expanded( + child: Align( + alignment: Alignment.center, + child: FittedBox( + fit: BoxFit.scaleDown, + child: BalanceAmountWithRefresh( + amount: amount, + refreshButton: refreshButton, + ), + ), + ), + ), + Flexible( + fit: FlexFit.loose, + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: sideMaxWidth), + child: SizedBox(height: constraints.maxHeight, child: actions), + ), + ), + ], + ); + }, + ); + } +} diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/widget.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/widget.dart index 65027d9c..ca9ce1c5 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/widget.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/widget.dart @@ -7,11 +7,12 @@ import 'package:pshared/models/ledger/account.dart'; import 'package:pshared/provider/ledger.dart'; import 'package:pshared/models/payment/wallet.dart'; -import 'package:pweb/pages/dashboard/buttons/balance/carousel.dart'; -import 'package:pweb/pages/dashboard/buttons/balance/controller.dart'; +import 'package:pweb/pages/dashboard/buttons/balance/carousel/carousel.dart'; +import 'package:pweb/controllers/dashboard/balance/carousel.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; + class BalanceWidget extends StatelessWidget { final ValueChanged onTopUp; final ValueChanged onLedgerAddFunds; @@ -46,9 +47,7 @@ class BalanceWidget extends StatelessWidget { } final carouselWidget = BalanceCarousel( - items: carousel.items, - currentIndex: carousel.index, - onIndexChanged: carousel.onPageChanged, + controller: carousel, onTopUp: onTopUp, onLedgerAddFunds: onLedgerAddFunds, onWalletTap: onWalletTap, diff --git a/frontend/pweb/lib/pages/dashboard/payouts/amount/feild.dart b/frontend/pweb/lib/pages/dashboard/payouts/amount/field.dart similarity index 100% rename from frontend/pweb/lib/pages/dashboard/payouts/amount/feild.dart rename to frontend/pweb/lib/pages/dashboard/payouts/amount/field.dart diff --git a/frontend/pweb/lib/pages/dashboard/payouts/amount/widget.dart b/frontend/pweb/lib/pages/dashboard/payouts/amount/widget.dart index 4ddaa3e7..7176d668 100644 --- a/frontend/pweb/lib/pages/dashboard/payouts/amount/widget.dart +++ b/frontend/pweb/lib/pages/dashboard/payouts/amount/widget.dart @@ -6,7 +6,8 @@ import 'package:pshared/controllers/payment/source.dart'; import 'package:pshared/provider/payment/amount.dart'; import 'package:pweb/controllers/payments/amount_field.dart'; -import 'package:pweb/pages/dashboard/payouts/amount/feild.dart'; +import 'package:pweb/pages/dashboard/payouts/amount/field.dart'; + class PaymentAmountWidget extends StatelessWidget { const PaymentAmountWidget({super.key});