Frontend first draft

This commit is contained in:
Arseni
2025-11-13 15:06:15 +03:00
parent e47f343afb
commit ddb54ddfdc
504 changed files with 25498 additions and 1 deletions

View File

@@ -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),
],
),
),
);
}
}

View File

@@ -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,
),
),
],
);
}
}

View File

@@ -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,
);
}
}

View 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
},
),
],
),
),
);
}
}

View 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),
),
],
),
],
);
}
}

View File

@@ -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);
}

View File

@@ -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,
),
),
],
);
}
}

View File

@@ -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),
),
);
}
}