Frontend first draft
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
class BalanceAddFunds extends StatelessWidget {
|
||||
final VoidCallback onTopUp;
|
||||
|
||||
const BalanceAddFunds({
|
||||
super.key,
|
||||
required this.onTopUp,
|
||||
});
|
||||
|
||||
static const double _borderRadius = 5.0;
|
||||
static const double _iconSize = 24.0;
|
||||
static const double _paddingVertical = 2.0;
|
||||
static const double _spacingSmall = 3.0;
|
||||
static const double _spacingMedium = 5.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return InkWell(
|
||||
onTap: onTopUp,
|
||||
borderRadius: BorderRadius.circular(_borderRadius),
|
||||
hoverColor: colorScheme.primaryContainer,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: _paddingVertical),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(width: _spacingSmall),
|
||||
Icon(
|
||||
Icons.add_circle,
|
||||
color: colorScheme.primary,
|
||||
size: _iconSize,
|
||||
),
|
||||
const SizedBox(width: _spacingMedium),
|
||||
Text(
|
||||
'Add funds',
|
||||
style: textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.primary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: _spacingSmall),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pweb/models/wallet.dart';
|
||||
import 'package:pweb/utils/currency.dart';
|
||||
|
||||
|
||||
class BalanceAmount extends StatelessWidget {
|
||||
final Wallet wallet;
|
||||
final VoidCallback onToggleVisibility;
|
||||
|
||||
const BalanceAmount({
|
||||
super.key,
|
||||
required this.wallet,
|
||||
required this.onToggleVisibility,
|
||||
});
|
||||
|
||||
static const double _iconSpacing = 12.0;
|
||||
static const double _iconSize = 24.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final currencyBalance = currencyCodeToSymbol(wallet.currency);
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
wallet.isHidden ? '•••• $currencyBalance' : '${wallet.balance.toStringAsFixed(2)} $currencyBalance',
|
||||
style: textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: _iconSpacing),
|
||||
GestureDetector(
|
||||
onTap: onToggleVisibility,
|
||||
child: Icon(
|
||||
wallet.isHidden ? Icons.visibility_off : Icons.visibility,
|
||||
size: _iconSize,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/carousel.dart';
|
||||
import 'package:pweb/providers/wallets.dart';
|
||||
|
||||
|
||||
class BalanceWidget extends StatelessWidget {
|
||||
const BalanceWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final walletsProvider = context.watch<WalletsProvider>();
|
||||
|
||||
if (walletsProvider.isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
final wallets = walletsProvider.wallets;
|
||||
|
||||
if (wallets == null || wallets.isEmpty) {
|
||||
return const Center(child: Text('No wallets available'));
|
||||
}
|
||||
|
||||
return
|
||||
WalletCarousel(
|
||||
wallets: wallets,
|
||||
onWalletChanged: walletsProvider.selectWallet,
|
||||
);
|
||||
}
|
||||
}
|
||||
54
frontend/pweb/lib/pages/dashboard/buttons/balance/card.dart
Normal file
54
frontend/pweb/lib/pages/dashboard/buttons/balance/card.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pweb/models/wallet.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/add_funds.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/amount.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/config.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/header.dart';
|
||||
import 'package:pweb/providers/wallets.dart';
|
||||
|
||||
|
||||
class WalletCard extends StatelessWidget {
|
||||
final Wallet wallet;
|
||||
|
||||
const WalletCard({
|
||||
super.key,
|
||||
required this.wallet,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
color: Theme.of(context).colorScheme.onSecondary,
|
||||
elevation: WalletCardConfig.elevation,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(WalletCardConfig.borderRadius),
|
||||
),
|
||||
child: Padding(
|
||||
padding: WalletCardConfig.contentPadding,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
BalanceHeader(
|
||||
walletName: wallet.name,
|
||||
walletId: wallet.walletUserID,
|
||||
),
|
||||
BalanceAmount(
|
||||
wallet: wallet,
|
||||
onToggleVisibility: () {
|
||||
context.read<WalletsProvider>().toggleVisibility(wallet.id);
|
||||
},
|
||||
),
|
||||
BalanceAddFunds(
|
||||
onTopUp: () {
|
||||
// TODO: Implement top-up functionality
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
113
frontend/pweb/lib/pages/dashboard/buttons/balance/carousel.dart
Normal file
113
frontend/pweb/lib/pages/dashboard/buttons/balance/carousel.dart
Normal file
@@ -0,0 +1,113 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pweb/models/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 {
|
||||
final List<Wallet> wallets;
|
||||
final ValueChanged<Wallet> onWalletChanged;
|
||||
|
||||
const WalletCarousel({
|
||||
super.key,
|
||||
required this.wallets,
|
||||
required this.onWalletChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
State<WalletCarousel> createState() => _WalletCarouselState();
|
||||
}
|
||||
|
||||
class _WalletCarouselState extends State<WalletCarousel> {
|
||||
late final PageController _pageController;
|
||||
int _currentPage = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_pageController = PageController(
|
||||
viewportFraction: WalletCardConfig.viewportFraction,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pageController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onPageChanged(int index) {
|
||||
setState(() {
|
||||
_currentPage = index;
|
||||
});
|
||||
context.read<CarouselIndexProvider>().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) {
|
||||
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]),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: _currentPage > 0 ? _goToPreviousPage : null,
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
CarouselIndicator(itemCount: widget.wallets.length),
|
||||
const SizedBox(width: 16),
|
||||
IconButton(
|
||||
onPressed: _currentPage < widget.wallets.length - 1
|
||||
? _goToNextPage
|
||||
: null,
|
||||
icon: const Icon(Icons.arrow_forward),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
abstract class WalletCardConfig {
|
||||
static const double cardHeight = 130.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 dotSize = 8.0;
|
||||
static const EdgeInsets dotMargin = EdgeInsets.symmetric(horizontal: 4);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
class BalanceHeader extends StatelessWidget {
|
||||
final String walletName;
|
||||
final String walletId;
|
||||
|
||||
const BalanceHeader({
|
||||
super.key,
|
||||
required this.walletName,
|
||||
required this.walletId,
|
||||
});
|
||||
|
||||
static const double _spacing = 8.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
walletName,
|
||||
style: textTheme.titleMedium?.copyWith(
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: _spacing),
|
||||
Text(
|
||||
walletId,
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
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;
|
||||
|
||||
const CarouselIndicator({
|
||||
super.key,
|
||||
required this.itemCount,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currentIndex = context.watch<CarouselIndexProvider>().currentIndex;
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: List.generate(
|
||||
itemCount,
|
||||
(index) => _Dot(isActive: currentIndex == index),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Dot extends StatelessWidget {
|
||||
final bool isActive;
|
||||
|
||||
const _Dot({required this.isActive});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user