From 71753e09ba6e7805cf67c93d31c74bbd231e49aa Mon Sep 17 00:00:00 2001 From: Stephan D Date: Thu, 22 Jan 2026 18:17:41 +0100 Subject: [PATCH] Fixed wallets load --- frontend/pshared/lib/controllers/wallets.dart | 78 ++++++------- frontend/pweb/lib/main.dart | 5 +- .../dashboard/buttons/balance/amount.dart | 6 +- .../dashboard/buttons/balance/balance.dart | 30 ++++- .../dashboard/buttons/balance/carousel.dart | 106 +++++------------- .../dashboard/buttons/balance/controller.dart | 21 ++++ .../dashboard/buttons/balance/indicator.dart | 29 ++--- .../pweb/lib/pages/dashboard/dashboard.dart | 12 +- frontend/pweb/lib/providers/carousel.dart | 15 --- 9 files changed, 135 insertions(+), 167 deletions(-) create mode 100644 frontend/pweb/lib/pages/dashboard/buttons/balance/controller.dart delete mode 100644 frontend/pweb/lib/providers/carousel.dart diff --git a/frontend/pshared/lib/controllers/wallets.dart b/frontend/pshared/lib/controllers/wallets.dart index d73da230..daca2180 100644 --- a/frontend/pshared/lib/controllers/wallets.dart +++ b/frontend/pshared/lib/controllers/wallets.dart @@ -12,9 +12,9 @@ class WalletsController with ChangeNotifier { String? _orgRef; /// UI-only: which wallets are allowed to be visible - final Set _visibleWalletIds = {}; + final Set _visibleWalletRefs = {}; - String? _selectedWalletId; + String? _selectedWalletRef; bool get isLoading => _wallets.isLoading; Exception? get error => _wallets.error; @@ -27,64 +27,60 @@ class WalletsController with ChangeNotifier { if (orgChanged) { _orgRef = nextOrgRef; - _visibleWalletIds.clear(); // All wallets hidden on org change - _selectedWalletId = null; + _visibleWalletRefs.clear(); // All wallets hidden on org change + _selectedWalletRef = null; } // Remove ids that no longer exist final ids = wallets.wallets.map((w) => w.id).toSet(); - _visibleWalletIds.removeWhere((id) => !ids.contains(id)); + _visibleWalletRefs.removeWhere((id) => !ids.contains(id)); - final beforeSelected = _selectedWalletId; - - _selectedWalletId = _resolveSelectedId( - currentId: _selectedWalletId, + _selectedWalletRef = _resolveSelectedId( + currentRef: _selectedWalletRef, wallets: wallets.wallets, - visibleIds: _visibleWalletIds, + visibleRefs: _visibleWalletRefs, ); - if (beforeSelected != _selectedWalletId || orgChanged) { - notifyListeners(); - } + notifyListeners(); } List get wallets => _wallets.wallets; - bool isVisible(String walletId) => _visibleWalletIds.contains(walletId); - bool isHidden(String walletId) => !isVisible(walletId); + bool isVisible(String walletRef) => _visibleWalletRefs.contains(walletRef); + bool isHidden(String walletRef) => !isVisible(walletRef); List get visibleWallets => - wallets.where((w) => _visibleWalletIds.contains(w.id)).toList(growable: false); + wallets.where((w) => _visibleWalletRefs.contains(w.id)).toList(growable: false); Wallet? get selectedWallet { - final id = _selectedWalletId; + final id = _selectedWalletRef; if (id == null) return null; return wallets.firstWhereOrNull((w) => w.id == id); } - String? get selectedWalletId => _selectedWalletId; + String? get selectedWalletRef => _selectedWalletRef; - void selectWallet(Wallet wallet) => selectWalletId(wallet.id); + void selectWallet(Wallet wallet) => selectWalletByRef(wallet.id); - void selectWalletId(String walletId) { - if (_selectedWalletId == walletId) return; + void selectWalletByRef(String walletRef) { + if (_selectedWalletRef == walletRef) return; // Prevent selecting a hidden wallet - if (!_visibleWalletIds.contains(walletId)) return; + if (!_visibleWalletRefs.contains(walletRef)) return; - _selectedWalletId = walletId; + _selectedWalletRef = walletRef; notifyListeners(); } /// Toggle wallet visibility void toggleVisibility(String walletId) { - final existed = _visibleWalletIds.remove(walletId); - if (!existed) _visibleWalletIds.add(walletId); + final existed = _visibleWalletRefs.remove(walletId); + if (!existed) _visibleWalletRefs.add(walletId); - _selectedWalletId = _resolveSelectedId( - currentId: _selectedWalletId, + _selectedWalletRef = _resolveSelectedId( + currentRef: _selectedWalletRef, wallets: wallets, - visibleIds: _visibleWalletIds, + visibleRefs: _visibleWalletRefs, ); notifyListeners(); @@ -92,36 +88,36 @@ class WalletsController with ChangeNotifier { /// Show all wallets (bulk action) void showAll() { - final allIds = wallets.map((w) => w.id); - _visibleWalletIds + final allRefs = wallets.map((w) => w.id); + _visibleWalletRefs ..clear() - ..addAll(allIds); + ..addAll(allRefs); - _selectedWalletId = _resolveSelectedId( - currentId: _selectedWalletId, + _selectedWalletRef = _resolveSelectedId( + currentRef: _selectedWalletRef, wallets: wallets, - visibleIds: _visibleWalletIds, + visibleRefs: _visibleWalletRefs, ); notifyListeners(); } String? _resolveSelectedId({ - required String? currentId, + required String? currentRef, required List wallets, - required Set visibleIds, + required Set visibleRefs, }) { if (wallets.isEmpty) return null; // Keep current selection if it still exists and is visible - if (currentId != null && - visibleIds.contains(currentId) && - wallets.any((w) => w.id == currentId)) { - return currentId; + if (currentRef != null && + visibleRefs.contains(currentRef) && + wallets.any((w) => w.id == currentRef)) { + return currentRef; } // Select the first visible wallet - final firstVisible = wallets.firstWhereOrNull((w) => visibleIds.contains(w.id)); + final firstVisible = wallets.firstWhereOrNull((w) => visibleRefs.contains(w.id)); return firstVisible?.id; } } diff --git a/frontend/pweb/lib/main.dart b/frontend/pweb/lib/main.dart index 7293e2d2..e041c1e9 100644 --- a/frontend/pweb/lib/main.dart +++ b/frontend/pweb/lib/main.dart @@ -8,6 +8,7 @@ import 'package:provider/provider.dart'; import 'package:logging/logging.dart'; import 'package:pshared/config/constants.dart'; +import 'package:pshared/controllers/wallets.dart'; import 'package:pshared/provider/locale.dart'; import 'package:pshared/provider/permissions.dart'; import 'package:pshared/provider/account.dart'; @@ -20,10 +21,8 @@ import 'package:pshared/provider/invitations.dart'; import 'package:pshared/service/payment/wallets.dart'; import 'package:pweb/app/app.dart'; -import 'package:pshared/controllers/wallets.dart'; import 'package:pweb/pages/invitations/widgets/list/view_model.dart'; import 'package:pweb/app/timeago.dart'; -import 'package:pweb/providers/carousel.dart'; import 'package:pweb/providers/operatioins.dart'; import 'package:pweb/providers/two_factor.dart'; import 'package:pweb/providers/upload_history.dart'; @@ -75,8 +74,6 @@ void main() async { create: (_) => EmployeesProvider(), update: (context, organizations, provider) => provider!..updateProviders(organizations), ), - ChangeNotifierProvider(create: (_) => CarouselIndexProvider()), - ChangeNotifierProvider( create: (_) => UploadHistoryProvider(service: MockUploadHistoryService())..load(), ), diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/amount.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/amount.dart index ed240fae..2d2af3de 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/amount.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/amount.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; + import 'package:provider/provider.dart'; + import 'package:pshared/controllers/wallets.dart'; - -import 'package:pshared/utils/currency.dart'; - import 'package:pshared/models/payment/wallet.dart'; +import 'package:pshared/utils/currency.dart'; class BalanceAmount extends StatelessWidget { diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/balance.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/balance.dart index 6f6b1e40..4c257aa1 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/balance.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/balance.dart @@ -6,6 +6,7 @@ import 'package:pshared/controllers/wallets.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/generated/i18n/app_localizations.dart'; @@ -13,26 +14,45 @@ import 'package:pweb/generated/i18n/app_localizations.dart'; class BalanceWidget extends StatelessWidget { final ValueChanged onTopUp; - const BalanceWidget({super.key, required this.onTopUp}); + const BalanceWidget({ + super.key, + required this.onTopUp, + }); @override Widget build(BuildContext context) { final walletsController = context.watch(); + final carousel = context.watch(); final loc = AppLocalizations.of(context)!; - + if (walletsController.isLoading) { return const Center(child: CircularProgressIndicator()); } - + final wallets = walletsController.wallets; - + if (wallets.isEmpty) { return Center(child: Text(loc.noWalletsAvailable)); } + // Ensure index is always valid when wallets list changes + carousel.setIndex(carousel.index, wallets.length); + + final index = carousel.index; + final wallet = wallets[index]; + + // Single source of truth: controller + if (walletsController.selectedWallet?.id != wallet.id) { + walletsController.selectWallet(wallet); + } + return WalletCarousel( wallets: wallets, - onWalletChanged: walletsController.selectWallet, + currentIndex: index, + onIndexChanged: (i) { + carousel.setIndex(i, wallets.length); + walletsController.selectWallet(wallets[i]); + }, onTopUp: onTopUp, ); } diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel.dart index 669ed800..15ae1750 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/carousel.dart @@ -1,101 +1,44 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - import 'package:pshared/models/payment/wallet.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/providers/carousel.dart'; -class WalletCarousel extends StatefulWidget { +class WalletCarousel extends StatelessWidget { final List wallets; - final ValueChanged onWalletChanged; + final int currentIndex; + final ValueChanged onIndexChanged; final ValueChanged onTopUp; const WalletCarousel({ super.key, required this.wallets, - required this.onWalletChanged, + required this.currentIndex, + required this.onIndexChanged, required this.onTopUp, }); - @override - State createState() => _WalletCarouselState(); -} - -class _WalletCarouselState extends State { - late final PageController _pageController; - int _currentPage = 0; - - @override - void initState() { - super.initState(); - _pageController = PageController( - viewportFraction: WalletCardConfig.viewportFraction, - ); - WidgetsBinding.instance.addPostFrameCallback((_) { - if (widget.wallets.isNotEmpty) { - widget.onWalletChanged(widget.wallets[_currentPage]); - } - }); - } - - @override - void dispose() { - _pageController.dispose(); - super.dispose(); - } - - void _onPageChanged(int index) { - setState(() { - _currentPage = index; - }); - context.read().updateIndex(index); - widget.onWalletChanged(widget.wallets[index]); - } - - void _goToPreviousPage() { - if (_currentPage > 0) { - _pageController.animateToPage( - _currentPage - 1, - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - ); - } - } - - void _goToNextPage() { - if (_currentPage < widget.wallets.length - 1) { - _pageController.animateToPage( - _currentPage + 1, - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - ); - } - } - @override Widget build(BuildContext context) { + if (wallets.isEmpty) { + return const SizedBox.shrink(); + } + + final safeIndex = currentIndex.clamp(0, wallets.length - 1); + final wallet = wallets[safeIndex]; + return Column( children: [ SizedBox( height: WalletCardConfig.cardHeight, - child: PageView.builder( - controller: _pageController, - physics: const NeverScrollableScrollPhysics(), - itemCount: widget.wallets.length, - onPageChanged: _onPageChanged, - itemBuilder: (context, index) { - return Padding( - padding: WalletCardConfig.cardPadding, - child: WalletCard( - wallet: widget.wallets[index], - onTopUp: () => widget.onTopUp(widget.wallets[index]), - ), - ); - }, + child: Padding( + padding: WalletCardConfig.cardPadding, + child: WalletCard( + wallet: wallet, + onTopUp: () => onTopUp(wallet), + ), ), ), const SizedBox(height: 16), @@ -103,15 +46,20 @@ class _WalletCarouselState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( - onPressed: _currentPage > 0 ? _goToPreviousPage : null, + onPressed: safeIndex > 0 + ? () => onIndexChanged(safeIndex - 1) + : null, icon: const Icon(Icons.arrow_back), ), const SizedBox(width: 16), - CarouselIndicator(itemCount: widget.wallets.length), + CarouselIndicator( + itemCount: wallets.length, + index: safeIndex, + ), const SizedBox(width: 16), IconButton( - onPressed: _currentPage < widget.wallets.length - 1 - ? _goToNextPage + onPressed: safeIndex < wallets.length - 1 + ? () => onIndexChanged(safeIndex + 1) : null, icon: const Icon(Icons.arrow_forward), ), diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/controller.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/controller.dart new file mode 100644 index 00000000..3c97136a --- /dev/null +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/controller.dart @@ -0,0 +1,21 @@ +import 'package:flutter/foundation.dart'; + + +class CarouselIndexController with ChangeNotifier { + int _index = 0; + + int get index => _index; + + void setIndex(int value, int max) { + final next = value.clamp(0, max > 0 ? max - 1 : 0); + if (next == _index) return; + _index = next; + notifyListeners(); + } + + void reset() { + if (_index == 0) return; + _index = 0; + notifyListeners(); + } +} diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/indicator.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/indicator.dart index d28c8c2a..cade6153 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/indicator.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/indicator.dart @@ -1,31 +1,26 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:pweb/pages/dashboard/buttons/balance/config.dart'; -import 'package:pweb/providers/carousel.dart'; - class CarouselIndicator extends StatelessWidget { final int itemCount; + final int index; const CarouselIndicator({ super.key, required this.itemCount, + required this.index, }); @override - Widget build(BuildContext context) { - final currentIndex = context.watch().currentIndex; - - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: List.generate( - itemCount, - (index) => _Dot(isActive: currentIndex == index), - ), - ); - } + Widget build(BuildContext context) => Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + itemCount, + (i) => _Dot(isActive: i == index), + ), + ); } class _Dot extends StatelessWidget { @@ -35,15 +30,15 @@ class _Dot extends StatelessWidget { @override Widget build(BuildContext context) { + final color = Theme.of(context).colorScheme.primary; + return Container( width: WalletCardConfig.dotSize, height: WalletCardConfig.dotSize, margin: WalletCardConfig.dotMargin, decoration: BoxDecoration( shape: BoxShape.circle, - color: isActive - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.primary.withAlpha(60), + color: isActive ? color : color.withAlpha(64), ), ); } diff --git a/frontend/pweb/lib/pages/dashboard/dashboard.dart b/frontend/pweb/lib/pages/dashboard/dashboard.dart index ce1bc2c6..a348c539 100644 --- a/frontend/pweb/lib/pages/dashboard/dashboard.dart +++ b/frontend/pweb/lib/pages/dashboard/dashboard.dart @@ -1,10 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + import 'package:pshared/models/payment/type.dart'; import 'package:pshared/models/recipient/recipient.dart'; - import 'package:pshared/models/payment/wallet.dart'; + import 'package:pweb/pages/dashboard/buttons/balance/balance.dart'; +import 'package:pweb/pages/dashboard/buttons/balance/controller.dart'; import 'package:pweb/pages/dashboard/buttons/buttons.dart'; import 'package:pweb/pages/dashboard/payouts/multiple/title.dart'; import 'package:pweb/pages/dashboard/payouts/multiple/widget.dart'; @@ -80,8 +83,11 @@ class _DashboardPageState extends State { ], ), const SizedBox(height: AppSpacing.medium), - BalanceWidget( - onTopUp: widget.onTopUp, + ChangeNotifierProvider( + create: (_) => CarouselIndexController(), + child: BalanceWidget( + onTopUp: widget.onTopUp, + ), ), const SizedBox(height: AppSpacing.small), if (_showContainerMultiple) TitleMultiplePayout(), diff --git a/frontend/pweb/lib/providers/carousel.dart b/frontend/pweb/lib/providers/carousel.dart deleted file mode 100644 index 8a7bfcd0..00000000 --- a/frontend/pweb/lib/providers/carousel.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:flutter/material.dart'; - - -class CarouselIndexProvider extends ChangeNotifier { - int _currentIndex = 0; - - int get currentIndex => _currentIndex; - - void updateIndex(int index) { - if (_currentIndex != index) { - _currentIndex = index; - notifyListeners(); - } - } -} -- 2.49.1