858 lines
32 KiB
Dart
Executable File
858 lines
32 KiB
Dart
Executable File
// lib/screens/dashboard_screen.dart
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:flutter/painting.dart';
|
|
import 'dart:ui';
|
|
import 'dart:math' as math;
|
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; // ✅ Importação do Font Awesome
|
|
|
|
import 'package:kzeduca_app/services/i18n_service.dart';
|
|
import 'package:kzeduca_app/screens/add_transaction_screen.dart';
|
|
import 'package:kzeduca_app/screens/savings_simulator_screen.dart';
|
|
import 'package:kzeduca_app/screens/lessons_screen.dart';
|
|
import 'package:kzeduca_app/screens/settings_screen.dart';
|
|
import 'package:kzeduca_app/screens/budget_screen.dart';
|
|
import 'package:kzeduca_app/screens/statistics_screen.dart';
|
|
import 'package:kzeduca_app/screens/challenges_screen.dart';
|
|
import 'package:kzeduca_app/screens/quiz_screen.dart';
|
|
import 'package:kzeduca_app/screens/receive_history_screen.dart';
|
|
import 'package:kzeduca_app/screens/notification_settings_screen.dart';
|
|
import 'package:kzeduca_app/screens/user_profile_screen.dart';
|
|
import 'package:kzeduca_app/services/user_state_service.dart';
|
|
import 'package:kzeduca_app/models/app_models.dart' as app_models;
|
|
import 'package:kzeduca_app/models/transaction.dart';
|
|
import 'package:kzeduca_app/services/hive_service.dart';
|
|
import 'package:kzeduca_app/services/transaction_list_notifier.dart';
|
|
import 'package:firebase_auth/firebase_auth.dart';
|
|
|
|
class DashboardScreen extends StatefulWidget {
|
|
const DashboardScreen({super.key});
|
|
|
|
@override
|
|
State<DashboardScreen> createState() => _DashboardScreenState();
|
|
}
|
|
|
|
class _DashboardScreenState extends State<DashboardScreen> {
|
|
int _selectedIndex = 0;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
}
|
|
|
|
void _handleCurrencyChange(String newCurrency) {
|
|
print("Moeda alterada para: $newCurrency");
|
|
}
|
|
|
|
void _onItemTapped(int index) {
|
|
setState(() {
|
|
_selectedIndex = index;
|
|
});
|
|
}
|
|
|
|
// ✅ _buildNavItem modificado para aceitar IconData ou FaIcon
|
|
Widget _buildNavItem(int index, dynamic icon, String label) {
|
|
bool isSelected = _selectedIndex == index;
|
|
return Material(
|
|
color: Colors.transparent,
|
|
child: InkWell(
|
|
onTap: () => _onItemTapped(index),
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 2.0),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
Container(
|
|
padding: const EdgeInsets.all(6.0),
|
|
decoration: isSelected
|
|
? BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
gradient: const LinearGradient(
|
|
colors: [Color(0xFF8A2BE2), Color(0xFFDA70D6)],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: const Color(0xFFDA70D6).withOpacity(0.5),
|
|
spreadRadius: 1,
|
|
blurRadius: 5,
|
|
offset: const Offset(0, 3),
|
|
),
|
|
],
|
|
)
|
|
: null,
|
|
child: icon is IconData
|
|
? Icon(
|
|
icon,
|
|
color: isSelected ? Colors.white : Colors.white70,
|
|
size: 22,
|
|
)
|
|
: icon, // ✅ Aceita o widget FaIcon diretamente
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
label,
|
|
style: TextStyle(
|
|
color: isSelected ? Colors.white : Colors.white70,
|
|
fontSize: 8,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final i18n = Provider.of<I18nService>(context);
|
|
|
|
final List<Widget> widgetOptions = <Widget>[
|
|
const _DashboardMainContent(),
|
|
const BudgetScreen(),
|
|
const StatisticsScreen(),
|
|
const UserProfileScreen(),
|
|
];
|
|
|
|
return Scaffold(
|
|
backgroundColor: const Color(0xFF1E1C3A),
|
|
appBar: PreferredSize(
|
|
preferredSize: const Size.fromHeight(kToolbarHeight),
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
gradient: const LinearGradient(
|
|
colors: [Color(0xFF1E1C3A), Color(0xFF1E1C3A)],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.2),
|
|
spreadRadius: 2,
|
|
blurRadius: 8,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: AppBar(
|
|
backgroundColor: Colors.transparent,
|
|
elevation: 0,
|
|
leading: Builder(
|
|
builder: (context) => IconButton(
|
|
icon: const Icon(Icons.menu, color: Colors.white),
|
|
onPressed: () {
|
|
Scaffold.of(context).openDrawer();
|
|
},
|
|
),
|
|
),
|
|
actions: [
|
|
IconButton(
|
|
// ✅ Ícone Font Awesome para Idioma
|
|
icon: const Icon(FontAwesomeIcons.globe, color: Colors.white, size: 20),
|
|
tooltip: i18n.t('change_language'),
|
|
onPressed: () {
|
|
final i18nService = Provider.of<I18nService>(context, listen: false);
|
|
final nextLocale = i18nService.locale.languageCode == 'pt' ? const Locale('en') : const Locale('pt');
|
|
i18nService.setLocale(nextLocale);
|
|
},
|
|
),
|
|
IconButton(
|
|
// ✅ Ícone Font Awesome para Notificações
|
|
icon: const Icon(FontAwesomeIcons.bell, color: Colors.white, size: 20),
|
|
tooltip: i18n.t('notifications'),
|
|
onPressed: () {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(builder: (_) => const NotificationSettingsScreen()),
|
|
);
|
|
},
|
|
),
|
|
IconButton(
|
|
// ✅ Ícone Font Awesome para Perfil
|
|
icon: const Icon(FontAwesomeIcons.solidUser, color: Colors.white, size: 20),
|
|
tooltip: i18n.t('profile'),
|
|
onPressed: () {
|
|
setState(() {
|
|
_selectedIndex = 3;
|
|
});
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
drawer: Drawer(
|
|
backgroundColor: const Color(0xFF1E1C3A),
|
|
child: ListView(
|
|
padding: EdgeInsets.zero,
|
|
children: <Widget>[
|
|
const DrawerHeader(
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [Color(0xFF8A2BE2), Color(0xFFDA70D6)],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
),
|
|
child: Text(
|
|
'Menu',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 24,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
ListTile(
|
|
// ✅ Ícone Font Awesome para Configurações
|
|
leading: const Icon(FontAwesomeIcons.gear, color: Colors.white),
|
|
title: Text(i18n.t('settings_menu_item'), style: const TextStyle(color: Colors.white)),
|
|
onTap: () {
|
|
Navigator.pop(context);
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => SettingsScreen(
|
|
onCurrencyChanged: _handleCurrencyChange,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
ListTile(
|
|
// ✅ Ícone Font Awesome para Notificações
|
|
leading: const Icon(FontAwesomeIcons.bell, color: Colors.white),
|
|
title: Text(i18n.t('notifications'), style: const TextStyle(color: Colors.white)),
|
|
onTap: () {
|
|
Navigator.pop(context);
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(builder: (_) => const NotificationSettingsScreen()),
|
|
);
|
|
},
|
|
),
|
|
ListTile(
|
|
// ✅ Ícone Font Awesome para Perfil
|
|
leading: const Icon(FontAwesomeIcons.solidUser, color: Colors.white),
|
|
title: Text(i18n.t('profile'), style: const TextStyle(color: Colors.white)),
|
|
onTap: () {
|
|
Navigator.pop(context);
|
|
setState(() {
|
|
_selectedIndex = 3;
|
|
});
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
body: SafeArea(
|
|
child: widgetOptions.elementAt(_selectedIndex),
|
|
),
|
|
floatingActionButton: FloatingActionButton(
|
|
onPressed: () async {
|
|
final result = await Navigator.push(
|
|
context,
|
|
MaterialPageRoute(builder: (_) => const AddTransactionScreen()),
|
|
);
|
|
if (result == true) {
|
|
setState(() {});
|
|
}
|
|
},
|
|
shape: const CircleBorder(),
|
|
backgroundColor: Colors.transparent,
|
|
elevation: 0,
|
|
child: Container(
|
|
width: 60,
|
|
height: 60,
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
gradient: const LinearGradient(
|
|
colors: [Color(0xFF8A2BE2), Color(0xFFDA70D6)],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: const Color(0xFFDA70D6).withOpacity(0.5),
|
|
spreadRadius: 2,
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 5),
|
|
),
|
|
],
|
|
),
|
|
child: const Icon(Icons.add, color: Colors.white, size: 30),
|
|
),
|
|
),
|
|
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
|
bottomNavigationBar: BottomAppBar(
|
|
color: const Color(0xFF1E1C3A),
|
|
shape: const CircularNotchedRectangle(),
|
|
notchMargin: 8.0,
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: <Widget>[
|
|
// ✅ Ícones Font Awesome para a BottomNavigationBar
|
|
_buildNavItem(0, const Icon(FontAwesomeIcons.house, color: Colors.white, size: 22), i18n.t('home')),
|
|
_buildNavItem(1, const Icon(FontAwesomeIcons.wallet, color: Colors.white, size: 22), i18n.t('transactions_menu_item')),
|
|
_buildNavItem(2, const Icon(FontAwesomeIcons.chartBar, color: Colors.white, size: 22), i18n.t('statistics_menu_item')),
|
|
_buildNavItem(3, const Icon(FontAwesomeIcons.solidUser, color: Colors.white, size: 22), i18n.t('profile')),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _DashboardMainContent extends StatelessWidget {
|
|
const _DashboardMainContent();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final i18n = Provider.of<I18nService>(context);
|
|
|
|
return Consumer<UserStateService>(
|
|
builder: (context, userState, child) {
|
|
if (userState.status == UserStateStatus.loading) {
|
|
return const Center(
|
|
child: CircularProgressIndicator(),
|
|
);
|
|
}
|
|
|
|
if (userState.status == UserStateStatus.error) {
|
|
return Center(
|
|
child: Text(
|
|
userState.errorMessage ?? 'Não foi possível carregar os dados do usuário.',
|
|
style: const TextStyle(color: Colors.white),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
);
|
|
}
|
|
|
|
final user = userState.currentUser;
|
|
|
|
if (user == null) {
|
|
return Center(
|
|
child: Text(
|
|
i18n.t('user_not_found'),
|
|
style: const TextStyle(color: Colors.white),
|
|
),
|
|
);
|
|
}
|
|
|
|
final locale = Localizations.localeOf(context).languageCode;
|
|
final currencySymbol = 'Kz';
|
|
final formattedBalance = NumberFormat.currency(
|
|
locale: locale,
|
|
symbol: currencySymbol,
|
|
decimalDigits: 2,
|
|
).format(user.currentBalance);
|
|
|
|
final formattedDate = DateFormat.yMMMd(locale).format(DateTime.now());
|
|
|
|
return SingleChildScrollView(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
children: [
|
|
_KzeducaCardRealistic(user: user),
|
|
const SizedBox(height: 24),
|
|
const SizedBox(height: 16),
|
|
GridView.count(
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
crossAxisCount: MediaQuery.of(context).size.width < 600 ? 4 : 4,
|
|
mainAxisSpacing: 16,
|
|
crossAxisSpacing: 16,
|
|
children: [
|
|
// ✅ Ícones Font Awesome para os atalhos
|
|
_QuickActionIcon(
|
|
icon: FontAwesomeIcons.rightLeft, // ✅ Ícone para Transferir/Trocar
|
|
label: i18n.t('transfer'),
|
|
onTap: () => Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => const AddTransactionScreen()))),
|
|
_QuickActionIcon(
|
|
icon: FontAwesomeIcons.arrowDown, // ✅ Ícone para Receber
|
|
label: i18n.t('receive'),
|
|
onTap: () => Navigator.push(
|
|
context,
|
|
MaterialPageRoute(builder: (_) => const ReceiveHistoryScreen()),
|
|
)),
|
|
_QuickActionIcon(
|
|
icon: FontAwesomeIcons.gamepad, // ✅ Ícone para Agente
|
|
label: "Jogo",
|
|
onTap: () => Navigator.pushNamed(context, '/financial_agent')),
|
|
_QuickActionIcon(
|
|
icon: FontAwesomeIcons.sackDollar, // ✅ Ícone para Poupança
|
|
label: i18n.t('simulator'),
|
|
onTap: () => Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => const SavingsSimulatorScreen()))),
|
|
_QuickActionIcon(
|
|
icon: FontAwesomeIcons.book, // ✅ Ícone para Aulas
|
|
label: i18n.t('lessons'),
|
|
onTap: () => Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => const LessonsScreen()))),
|
|
_QuickActionIcon(
|
|
icon: FontAwesomeIcons.trophy, // ✅ Ícone para Desafios
|
|
label: i18n.t('challenges'),
|
|
onTap: () => Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => const ChallengesScreen()))),
|
|
_QuickActionIcon(
|
|
icon: FontAwesomeIcons.question, // ✅ Ícone para Quiz
|
|
label: i18n.t('quiz'),
|
|
onTap: () => Navigator.push(context,
|
|
MaterialPageRoute(builder: (_) => const QuizScreen()))),
|
|
_QuickActionIcon(
|
|
icon: FontAwesomeIcons.moneyBillTrendUp, // ✅ Ícone para Orçamento
|
|
label: i18n.t('budget'),
|
|
onTap: () => Navigator.push(context,
|
|
MaterialPageRoute(builder: (_) => const BudgetScreen()))),
|
|
],
|
|
),
|
|
const SizedBox(height: 32),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
i18n.t('Historic'),
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 22,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
Consumer<TransactionListNotifier>(
|
|
builder: (context, txnList, _) {
|
|
final transactions = txnList.transactions;
|
|
if (transactions.isEmpty) {
|
|
return const Text('Nenhuma transação encontrada.', style: TextStyle(color: Colors.white70));
|
|
}
|
|
return ListView.builder(
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
itemCount: transactions.length < 3 ? transactions.length : 3,
|
|
itemBuilder: (context, index) {
|
|
final transaction = transactions[transactions.length - 1 - index];
|
|
final color = transaction.type == TransactionType.income ? Colors.green : Colors.red;
|
|
final sign = transaction.type == TransactionType.income ? '+' : '-';
|
|
final formattedAmount = NumberFormat.currency(
|
|
locale: locale,
|
|
symbol: currencySymbol,
|
|
decimalDigits: 2,
|
|
).format(transaction.amount);
|
|
final formattedDate = DateFormat.MMMMd(locale).format(transaction.date);
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 16.0),
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFF2A284B),
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: ListTile(
|
|
// ✅ Ícone Font Awesome para a transação
|
|
leading: Icon(FontAwesomeIcons.moneyBillTransfer, color: color),
|
|
title: Text(transaction.title, style: const TextStyle(color: Colors.white)),
|
|
subtitle: Text(formattedDate, style: const TextStyle(color: Colors.white70)),
|
|
trailing: Text('$sign$formattedAmount', style: TextStyle(color: color, fontWeight: FontWeight.bold)),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
// ✅ NOVO WIDGET para os ícones de ação rápida, para evitar repetição de código.
|
|
class _QuickActionIcon extends StatelessWidget {
|
|
final IconData? icon;
|
|
final String label;
|
|
final VoidCallback onTap;
|
|
|
|
const _QuickActionIcon({
|
|
Key? key,
|
|
required this.icon,
|
|
required this.label,
|
|
required this.onTap,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GestureDetector(
|
|
onTap: onTap,
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
gradient: const LinearGradient(
|
|
colors: [Color(0xFF2A284B), Color(0xFF1E1C3A)],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.3),
|
|
spreadRadius: 2,
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 5),
|
|
),
|
|
],
|
|
),
|
|
child: Icon(
|
|
icon,
|
|
color: const Color(0xFFDA70D6),
|
|
size: 28,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
label,
|
|
style: const TextStyle(color: Colors.white70, fontSize: 12),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ... (Restante do código _KzeducaCardRealistic, CardPatternPainter, etc.)
|
|
|
|
class _KzeducaCardRealistic extends StatefulWidget {
|
|
final app_models.AppUser user;
|
|
const _KzeducaCardRealistic({Key? key, required this.user}) : super(key: key);
|
|
|
|
@override
|
|
State<_KzeducaCardRealistic> createState() => _KzeducaCardRealisticState();
|
|
}
|
|
|
|
class _KzeducaCardRealisticState extends State<_KzeducaCardRealistic> {
|
|
bool _isBalanceVisible = true;
|
|
String _cardNumber = '';
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_cardNumber = _generateCardNumber();
|
|
}
|
|
|
|
String _generateCardNumber() {
|
|
final math.Random random = math.Random();
|
|
String number = '';
|
|
for (int i = 0; i < 12; i++) {
|
|
number += random.nextInt(10).toString();
|
|
}
|
|
return '${number.substring(0, 4)} ${number.substring(4, 8)} ${number.substring(8, 12)} ${number.substring(12, 12)}';
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final locale = Localizations.localeOf(context).languageCode;
|
|
final currencySymbol = 'Kz';
|
|
final formattedBalance = NumberFormat.currency(
|
|
locale: locale,
|
|
symbol: currencySymbol,
|
|
decimalDigits: 2,
|
|
).format(widget.user.currentBalance);
|
|
|
|
final String displayBalance = _isBalanceVisible ? formattedBalance : 'Kz ****';
|
|
final balanceIcon = _isBalanceVisible ? Icons.visibility : Icons.visibility_off;
|
|
|
|
final now = DateTime.now();
|
|
final expiryDate = DateFormat('MM/yy').format(DateTime(now.year + 5, now.month));
|
|
|
|
return Container(
|
|
height: 220,
|
|
width: double.infinity,
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(20),
|
|
gradient: const RadialGradient(
|
|
colors: [
|
|
Color(0xFF6A1B9A),
|
|
Color(0xFF8E24AA),
|
|
Color(0xFF4A148C),
|
|
Color(0xFF263238),
|
|
],
|
|
center: Alignment.centerRight,
|
|
radius: 1.5,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.6),
|
|
spreadRadius: 3,
|
|
blurRadius: 15,
|
|
offset: const Offset(0, 8),
|
|
),
|
|
BoxShadow(
|
|
color: const Color(0xFFDA70D6).withOpacity(0.3),
|
|
spreadRadius: 1,
|
|
blurRadius: 5,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(20),
|
|
child: CustomPaint(
|
|
painter: CardPatternPainter(),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text(
|
|
'KzEduca',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.bold,
|
|
letterSpacing: 1.2,
|
|
),
|
|
),
|
|
Text(
|
|
'Aprende. Poupa. Progride.',
|
|
style: TextStyle(
|
|
color: Colors.white.withOpacity(0.8),
|
|
fontSize: 10,
|
|
fontStyle: FontStyle.italic),
|
|
),
|
|
],
|
|
),
|
|
Text(
|
|
expiryDate,
|
|
style: TextStyle(
|
|
color: Colors.white.withOpacity(0.8),
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const Spacer(flex: 1),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
SizedBox(
|
|
width: 50,
|
|
height: 35,
|
|
child: CustomPaint(
|
|
painter: ChipEMVPainter(),
|
|
),
|
|
),
|
|
GestureDetector(
|
|
onTap: () {
|
|
setState(() {
|
|
_isBalanceVisible = !_isBalanceVisible;
|
|
});
|
|
},
|
|
child: Row(
|
|
children: [
|
|
Text(
|
|
displayBalance,
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 22,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
//Icon(balanceIcon, color: Colors.white, size: 20),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const Spacer(flex: 1),
|
|
Text(
|
|
_cardNumber,
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 20,
|
|
letterSpacing: 4,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
const Spacer(flex: 1),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
widget.user.username?.toUpperCase() ?? 'UTILIZADOR',
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w600,
|
|
letterSpacing: 1.0,
|
|
),
|
|
),
|
|
Text(
|
|
'UID: ${widget.user.uid}',
|
|
style: const TextStyle(
|
|
color: Colors.white70,
|
|
fontSize: 10,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(
|
|
width: 40,
|
|
height: 30,
|
|
child: CustomPaint(
|
|
painter: CardLogoPainter(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class CardPatternPainter extends CustomPainter {
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
final paint = Paint()
|
|
..color = Colors.white.withOpacity(0.08)
|
|
..style = PaintingStyle.stroke
|
|
..strokeWidth = 0.8;
|
|
|
|
for (int i = -20; i < size.width + 20; i += 30) {
|
|
canvas.drawLine(Offset(i.toDouble(), 0), Offset(i.toDouble() - size.height, size.height), paint);
|
|
}
|
|
for (int i = 0; i < size.height; i += 30) {
|
|
canvas.drawLine(Offset(0, i.toDouble()), Offset(size.width, i.toDouble() - size.width), paint);
|
|
}
|
|
|
|
final circlePaint = Paint()
|
|
..color = Colors.white.withOpacity(0.04)
|
|
..style = PaintingStyle.fill;
|
|
final random = math.Random();
|
|
for (int i = 0; i < 30; i++) {
|
|
final x = random.nextDouble() * size.width;
|
|
final y = random.nextDouble() * size.height;
|
|
canvas.drawCircle(Offset(x, y), random.nextDouble() * 1.5 + 0.5, circlePaint);
|
|
}
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
|
}
|
|
|
|
class ChipEMVPainter extends CustomPainter {
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
final shadowPaint = Paint()..color = Colors.black.withOpacity(0.3);
|
|
final rrectShadow = RRect.fromRectAndRadius(
|
|
Rect.fromLTWH(2, 2, size.width - 2, size.height - 2), const Radius.circular(6));
|
|
canvas.drawRRect(rrectShadow, shadowPaint);
|
|
|
|
final paintBase = Paint()
|
|
..shader = const LinearGradient(
|
|
colors: [Color(0xFFD4AF37), Color(0xFFC0992D), Color(0xFFD4AF37)],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
).createShader(Rect.fromLTWH(0, 0, size.width, size.height));
|
|
final rrectBase = RRect.fromRectAndRadius(
|
|
Rect.fromLTWH(0, 0, size.width, size.height), const Radius.circular(6));
|
|
canvas.drawRRect(rrectBase, paintBase);
|
|
|
|
final paintHighlight = Paint()
|
|
..shader = const LinearGradient(
|
|
colors: [Color(0xFFFFF7AD), Color(0xFFD4AF37)],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
).createShader(Rect.fromLTWH(1, 1, size.width - 2, size.height - 2));
|
|
final rrectHighlight = RRect.fromRectAndRadius(
|
|
Rect.fromLTWH(1, 1, size.width - 2, size.height - 2), const Radius.circular(5));
|
|
canvas.drawRRect(rrectHighlight, paintHighlight);
|
|
|
|
final paintDetails = Paint()
|
|
..color = const Color(0xFF8B6B18)
|
|
..style = PaintingStyle.fill;
|
|
|
|
canvas.drawRRect(RRect.fromRectAndRadius(Rect.fromLTWH(size.width * 0.2, size.height * 0.15, size.width * 0.6, size.height * 0.2), const Radius.circular(2)), paintDetails);
|
|
canvas.drawRRect(RRect.fromRectAndRadius(Rect.fromLTWH(size.width * 0.2, size.height * 0.65, size.width * 0.6, size.height * 0.2), const Radius.circular(2)), paintDetails);
|
|
|
|
canvas.drawRect(Rect.fromLTWH(size.width * 0.1, size.height * 0.35, size.width * 0.1, size.height * 0.3), paintDetails);
|
|
canvas.drawRect(Rect.fromLTWH(size.width * 0.8, size.height * 0.35, size.width * 0.1, size.height * 0.3), paintDetails);
|
|
|
|
canvas.drawRect(Rect.fromLTWH(size.width * 0.3, size.height * 0.45, size.width * 0.4, size.height * 0.1), paintDetails);
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
|
}
|
|
|
|
class CardLogoPainter extends CustomPainter {
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
final paint = Paint()
|
|
..shader = const LinearGradient(
|
|
colors: [Color(0xFFFFA000), Color(0xFFFFC107)],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
).createShader(Rect.fromLTWH(0, 0, size.width, size.height));
|
|
|
|
final path = Path();
|
|
path.addOval(Rect.fromLTWH(0, 0, size.width * 0.6, size.height));
|
|
path.addOval(Rect.fromLTWH(size.width * 0.4, 0, size.width * 0.6, size.height));
|
|
canvas.drawPath(path, paint);
|
|
|
|
final wifiPaint = Paint()
|
|
..color = Colors.white.withOpacity(0.7);
|
|
|
|
final Path wifiPath = Path();
|
|
wifiPath.moveTo(size.width * 0.7, size.height * 0.3);
|
|
wifiPath.arcTo(
|
|
Rect.fromCircle(center: Offset(size.width * 0.8, size.height * 0.7), radius: 10),
|
|
-math.pi,
|
|
math.pi,
|
|
false,
|
|
);
|
|
canvas.drawPath(wifiPath, wifiPaint);
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
|
}
|