fixes
This commit is contained in:
parent
76fadd931e
commit
9fc720c10d
6 changed files with 720 additions and 255 deletions
|
|
@ -55,10 +55,10 @@ class PackDtoConverter {
|
||||||
? null
|
? null
|
||||||
: canOpenForAdVal
|
: canOpenForAdVal
|
||||||
? AssetPackTip(
|
? AssetPackTip(
|
||||||
'asset:icons/ad.webp',
|
'icons/ad.webp',
|
||||||
position: PackTipPosition.bottomRight,
|
position: PackTipPosition.bottomRight,
|
||||||
)
|
)
|
||||||
: AssetPackTip('asset:icons/lock.webp',
|
: AssetPackTip('icons/lock.webp',
|
||||||
position: PackTipPosition.bottomRight,),
|
position: PackTipPosition.bottomRight,),
|
||||||
version: model.version,
|
version: model.version,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,6 @@ import 'package:yx_scope_flutter/yx_scope_flutter.dart';
|
||||||
import 'package:yx_state_flutter/yx_state_flutter.dart';
|
import 'package:yx_state_flutter/yx_state_flutter.dart';
|
||||||
|
|
||||||
import 'package:mnemo_cards_web_v2/di/app_scope/app_scope_container.dart';
|
import 'package:mnemo_cards_web_v2/di/app_scope/app_scope_container.dart';
|
||||||
import 'package:mnemo_cards_web_v2/domain/services/statistics_service.dart';
|
|
||||||
import 'package:mnemo_cards_web_v2/presentation/widgets/simple_chart.dart';
|
|
||||||
import 'package:mnemo_cards_web_v2/presentation/widgets/stats_card.dart';
|
|
||||||
|
|
||||||
/// Profile page
|
/// Profile page
|
||||||
///
|
///
|
||||||
|
|
@ -156,9 +153,6 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get statistics
|
|
||||||
final statistics = userScope.statisticsService.getStatistics(user);
|
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -168,22 +162,26 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||||
_buildUserHeader(context, user),
|
_buildUserHeader(context, user),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
// Statistics cards
|
|
||||||
_buildStatisticsSection(context, statistics),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
|
|
||||||
// Daily progress chart
|
|
||||||
SimpleChart(data: statistics.dailyProgress),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
|
|
||||||
// Account info card
|
// Account info card
|
||||||
_buildAccountInfoCard(context, user),
|
_buildAccountInfoCard(context, user),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Subscription section
|
||||||
|
_buildSubscriptionSection(context, user),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
// Settings section
|
// Settings section
|
||||||
_buildSettingsSection(context),
|
_buildSettingsSection(context),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Data & Privacy section
|
||||||
|
_buildDataPrivacySection(context),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// About section
|
||||||
|
_buildAboutSection(context),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
// Logout button
|
// Logout button
|
||||||
_buildLogoutButton(context),
|
_buildLogoutButton(context),
|
||||||
],
|
],
|
||||||
|
|
@ -197,157 +195,416 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 2,
|
elevation: 4,
|
||||||
child: Padding(
|
shape: RoundedRectangleBorder(
|
||||||
padding: const EdgeInsets.all(16.0),
|
borderRadius: BorderRadius.circular(20),
|
||||||
child: Row(
|
),
|
||||||
children: [
|
child: Container(
|
||||||
// Avatar
|
decoration: BoxDecoration(
|
||||||
CircleAvatar(
|
borderRadius: BorderRadius.circular(20),
|
||||||
radius: 32,
|
gradient: LinearGradient(
|
||||||
backgroundColor: theme.colorScheme.primary,
|
begin: Alignment.topLeft,
|
||||||
child: Text(
|
end: Alignment.bottomRight,
|
||||||
(user.name?.isNotEmpty == true
|
colors: [
|
||||||
? user.name![0]
|
theme.colorScheme.primary.withOpacity(0.1),
|
||||||
: user.email?.isNotEmpty == true
|
theme.colorScheme.primary.withOpacity(0.05),
|
||||||
? user.email![0]
|
],
|
||||||
: 'U')
|
),
|
||||||
.toUpperCase(),
|
),
|
||||||
style: TextStyle(
|
child: Padding(
|
||||||
fontSize: 28,
|
padding: const EdgeInsets.all(20.0),
|
||||||
color: theme.colorScheme.onPrimary,
|
child: Row(
|
||||||
fontWeight: FontWeight.bold,
|
children: [
|
||||||
|
// Avatar with shadow
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: theme.colorScheme.primary.withOpacity(0.3),
|
||||||
|
blurRadius: 12,
|
||||||
|
offset: const Offset(0, 4),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
child: CircleAvatar(
|
||||||
),
|
radius: 40,
|
||||||
const SizedBox(width: 16),
|
backgroundColor: theme.colorScheme.primary,
|
||||||
// User info
|
child: Text(
|
||||||
Expanded(
|
(user.name?.isNotEmpty == true
|
||||||
child: Column(
|
? user.name![0]
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
: user.email?.isNotEmpty == true
|
||||||
children: [
|
? user.email![0]
|
||||||
Text(
|
: 'U')
|
||||||
user.name ?? user.email ?? 'User',
|
.toUpperCase(),
|
||||||
style: theme.textTheme.titleLarge?.copyWith(
|
style: TextStyle(
|
||||||
|
fontSize: 32,
|
||||||
|
color: theme.colorScheme.onPrimary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (user.email != null) ...[
|
),
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
user.email!,
|
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
|
||||||
color: theme.textTheme.bodySmall?.color,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
if (user.subscription == true) ...[
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 8,
|
|
||||||
vertical: 4,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: theme.colorScheme.primaryContainer,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.star,
|
|
||||||
size: 16,
|
|
||||||
color: theme.colorScheme.onPrimaryContainer,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Text(
|
|
||||||
'Premium',
|
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
|
||||||
color: theme.colorScheme.onPrimaryContainer,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 20),
|
||||||
],
|
// User info
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
user.name ?? user.email ?? 'User',
|
||||||
|
style: theme.textTheme.titleLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 22,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (user.email != null) ...[
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text(
|
||||||
|
user.email!,
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
if (user.subscription == true) ...[
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 6,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
theme.colorScheme.primaryContainer,
|
||||||
|
theme.colorScheme.primaryContainer.withOpacity(0.8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: theme.colorScheme.primary.withOpacity(0.2),
|
||||||
|
blurRadius: 8,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.star,
|
||||||
|
size: 18,
|
||||||
|
color: theme.colorScheme.onPrimaryContainer,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
'Premium',
|
||||||
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
color: theme.colorScheme.onPrimaryContainer,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 13,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStatisticsSection(
|
Widget _buildSubscriptionSection(BuildContext context, UserDto user) {
|
||||||
BuildContext context,
|
final theme = Theme.of(context);
|
||||||
UserStatistics statistics,
|
|
||||||
) {
|
|
||||||
// Format study time
|
|
||||||
final hours = statistics.studyTime.inHours;
|
|
||||||
final minutes = statistics.studyTime.inMinutes % 60;
|
|
||||||
final studyTimeStr = hours > 0 ? '${hours}h ${minutes}m' : '${minutes}m';
|
|
||||||
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: StatsCard(
|
|
||||||
icon: Icons.school,
|
|
||||||
label: 'Learned Words',
|
|
||||||
value: statistics.learnedWords.toString(),
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: StatsCard(
|
|
||||||
icon: Icons.quiz,
|
|
||||||
label: 'Tests Completed',
|
|
||||||
value: statistics.testsCompleted.toString(),
|
|
||||||
color: Colors.green,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: StatsCard(
|
|
||||||
icon: Icons.timer,
|
|
||||||
label: 'Study Time',
|
|
||||||
value: studyTimeStr,
|
|
||||||
color: Colors.orange,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildAccountInfoCard(BuildContext context, UserDto user) {
|
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 2,
|
elevation: 3,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
child: Text(
|
child: Row(
|
||||||
'Account Info',
|
children: [
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.colorScheme.primary.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.star,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
'Подписка',
|
||||||
|
style: theme.textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: user.subscription == true
|
||||||
|
? Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.check_circle,
|
||||||
|
color: Colors.green,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Premium активна',
|
||||||
|
style: theme.textTheme.bodyLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'У вас есть доступ ко всем функциям',
|
||||||
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.info_outline,
|
||||||
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Бесплатная версия',
|
||||||
|
style: theme.textTheme.bodyLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'Обновитесь до Premium для доступа ко всем функциям',
|
||||||
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDataPrivacySection(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
elevation: 3,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(20.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.colorScheme.primary.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.security,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
'Данные и приватность',
|
||||||
|
style: theme.textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.download),
|
||||||
|
title: const Text('Экспорт данных'),
|
||||||
|
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
|
||||||
|
onTap: () {
|
||||||
|
// TODO: Implement data export
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.delete_outline),
|
||||||
|
title: const Text('Удалить аккаунт'),
|
||||||
|
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
|
||||||
|
onTap: () {
|
||||||
|
// TODO: Implement account deletion
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAboutSection(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
elevation: 3,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(20.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.colorScheme.primary.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.info,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
'О приложении',
|
||||||
|
style: theme.textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.description),
|
||||||
|
title: const Text('Версия'),
|
||||||
|
trailing: const Text('1.0.0'),
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.privacy_tip),
|
||||||
|
title: const Text('Политика конфиденциальности'),
|
||||||
|
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
|
||||||
|
onTap: () {
|
||||||
|
// TODO: Open privacy policy
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.description),
|
||||||
|
title: const Text('Условия использования'),
|
||||||
|
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
|
||||||
|
onTap: () {
|
||||||
|
// TODO: Open terms of service
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAccountInfoCard(BuildContext context, UserDto user) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
elevation: 3,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(20.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.colorScheme.primary.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.account_circle,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
'Информация об аккаунте',
|
||||||
|
style: theme.textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
_StatRow(
|
_StatRow(
|
||||||
icon: Icons.collections_bookmark,
|
icon: Icons.collections_bookmark,
|
||||||
label: 'Packs',
|
label: 'Паки',
|
||||||
value: user.packs.length.toString(),
|
value: user.packs.length.toString(),
|
||||||
|
color: Colors.blue,
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
_StatRow(
|
_StatRow(
|
||||||
icon: Icons.shopping_bag,
|
icon: Icons.shopping_bag,
|
||||||
label: 'Purchases',
|
label: 'Покупки',
|
||||||
value: user.purchases.length.toString(),
|
value: user.purchases.length.toString(),
|
||||||
|
color: Colors.green,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -355,6 +612,8 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSettingsSection(BuildContext context) {
|
Widget _buildSettingsSection(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
return ScopeBuilder<AppScopeContainer>(
|
return ScopeBuilder<AppScopeContainer>(
|
||||||
builder: (context, appScope) {
|
builder: (context, appScope) {
|
||||||
if (appScope == null) {
|
if (appScope == null) {
|
||||||
|
|
@ -367,17 +626,37 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||||
final isDark = themeState.mode == ThemeMode.dark;
|
final isDark = themeState.mode == ThemeMode.dark;
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 2,
|
elevation: 3,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
child: Text(
|
child: Row(
|
||||||
'Settings',
|
children: [
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.colorScheme.primary.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.settings,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
'Настройки',
|
||||||
|
style: theme.textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
|
|
@ -417,6 +696,8 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildLogoutButton(BuildContext context) {
|
Widget _buildLogoutButton(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
|
|
@ -429,10 +710,20 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||||
)
|
)
|
||||||
: const Icon(Icons.logout),
|
: const Icon(Icons.logout),
|
||||||
label: Text(
|
label: Text(
|
||||||
_isLoggingOut ? 'Logging out...' : 'Logout',
|
_isLoggingOut ? 'Выход...' : 'Выйти',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
elevation: 2,
|
||||||
|
backgroundColor: theme.colorScheme.errorContainer,
|
||||||
|
foregroundColor: theme.colorScheme.onErrorContainer,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -445,31 +736,53 @@ class _StatRow extends StatelessWidget {
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.value,
|
required this.value,
|
||||||
|
this.color,
|
||||||
});
|
});
|
||||||
|
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final String label;
|
final String label;
|
||||||
final String value;
|
final String value;
|
||||||
|
final Color? color;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final iconColor = color ?? theme.colorScheme.primary;
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 16.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, size: 24),
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: iconColor.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Icon(icon, size: 22, color: iconColor),
|
||||||
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
label,
|
label,
|
||||||
style: const TextStyle(fontSize: 16),
|
style: theme.textTheme.bodyLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Container(
|
||||||
value,
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
style: const TextStyle(
|
decoration: BoxDecoration(
|
||||||
fontSize: 16,
|
color: iconColor.withOpacity(0.1),
|
||||||
fontWeight: FontWeight.bold,
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
value,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: iconColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ class StatisticsPage extends StatefulWidget {
|
||||||
|
|
||||||
class _StatisticsPageState extends State<StatisticsPage> with TickerProviderStateMixin {
|
class _StatisticsPageState extends State<StatisticsPage> with TickerProviderStateMixin {
|
||||||
late TabController _tabController;
|
late TabController _tabController;
|
||||||
|
bool _hasLoaded = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -116,6 +117,18 @@ class _StatisticsPageState extends State<StatisticsPage> with TickerProviderStat
|
||||||
return const Center(child: Text('User scope not available'));
|
return const Center(child: Text('User scope not available'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Загружаем статистику при первом отображении страницы
|
||||||
|
if (!_hasLoaded) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (mounted) {
|
||||||
|
userScope.statisticsStateManager.loadStatistics();
|
||||||
|
setState(() {
|
||||||
|
_hasLoaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return StateBuilder<StatisticsState>(
|
return StateBuilder<StatisticsState>(
|
||||||
stateReadable: userScope.statisticsStateManager,
|
stateReadable: userScope.statisticsStateManager,
|
||||||
builder: (context, state, _) {
|
builder: (context, state, _) {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||||
|
import 'package:yx_scope_flutter/yx_scope_flutter.dart';
|
||||||
|
|
||||||
|
import '../../di/user_scope/user_scope.dart';
|
||||||
import '../../domain/config/api_config_v2.dart';
|
import '../../domain/config/api_config_v2.dart';
|
||||||
import '../../presentation/theme/app_colors.dart';
|
import '../../presentation/theme/app_colors.dart';
|
||||||
import 'mnemo_text.dart';
|
import 'mnemo_text.dart';
|
||||||
|
|
@ -35,17 +38,24 @@ class _CardViewerState extends State<CardViewer> {
|
||||||
late PageController _pageController;
|
late PageController _pageController;
|
||||||
late int _currentIndex;
|
late int _currentIndex;
|
||||||
final Map<int, bool> _flippedCards = {};
|
final Map<int, bool> _flippedCards = {};
|
||||||
|
final FocusNode _focusNode = FocusNode();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_currentIndex = widget.initialIndex;
|
_currentIndex = widget.initialIndex;
|
||||||
_pageController = PageController(initialPage: widget.initialIndex);
|
_pageController = PageController(initialPage: widget.initialIndex);
|
||||||
|
|
||||||
|
// Устанавливаем фокус для обработки клавиатуры
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_focusNode.requestFocus();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_pageController.dispose();
|
_pageController.dispose();
|
||||||
|
_focusNode.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,95 +67,213 @@ class _CardViewerState extends State<CardViewer> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _goToPrevious() {
|
||||||
|
if (widget.cards.isEmpty) return;
|
||||||
|
|
||||||
|
final newIndex = _currentIndex == 0
|
||||||
|
? widget.cards.length - 1 // Циклический переход: с первой на последнюю
|
||||||
|
: _currentIndex - 1;
|
||||||
|
|
||||||
|
_pageController.animateToPage(
|
||||||
|
newIndex,
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _goToNext() {
|
||||||
|
if (widget.cards.isEmpty) return;
|
||||||
|
|
||||||
|
final newIndex = _currentIndex == widget.cards.length - 1
|
||||||
|
? 0 // Циклический переход: с последней на первую
|
||||||
|
: _currentIndex + 1;
|
||||||
|
|
||||||
|
_pageController.animateToPage(
|
||||||
|
newIndex,
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _handleKeyEvent(KeyEvent event) {
|
||||||
|
if (event is KeyDownEvent) {
|
||||||
|
if (event.logicalKey == LogicalKeyboardKey.space) {
|
||||||
|
_goToNext();
|
||||||
|
return true;
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
|
||||||
|
_goToPrevious();
|
||||||
|
return true;
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
|
||||||
|
_goToNext();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final packColor = widget.packColor ?? AppColors.borderGray;
|
final packColor = widget.packColor ?? AppColors.borderGray;
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final colorScheme = theme.colorScheme;
|
final colorScheme = theme.colorScheme;
|
||||||
|
|
||||||
return Scaffold(
|
return KeyboardListener(
|
||||||
backgroundColor: theme.scaffoldBackgroundColor,
|
focusNode: _focusNode,
|
||||||
body: SafeArea(
|
onKeyEvent: _handleKeyEvent,
|
||||||
child: Stack(
|
child: Scaffold(
|
||||||
children: [
|
backgroundColor: theme.scaffoldBackgroundColor,
|
||||||
// PageView с карточками
|
body: SafeArea(
|
||||||
PageView.builder(
|
child: Stack(
|
||||||
controller: _pageController,
|
children: [
|
||||||
itemCount: widget.cards.length,
|
// PageView с карточками
|
||||||
onPageChanged: (index) {
|
PageView.builder(
|
||||||
setState(() {
|
controller: _pageController,
|
||||||
_currentIndex = index;
|
itemCount: widget.cards.length,
|
||||||
});
|
onPageChanged: (index) {
|
||||||
},
|
setState(() {
|
||||||
itemBuilder: (context, index) {
|
_currentIndex = index;
|
||||||
return AnimatedBuilder(
|
});
|
||||||
animation: _pageController,
|
// Обновляем фокус для обработки клавиатуры
|
||||||
builder: (context, child) {
|
_focusNode.requestFocus();
|
||||||
double value = 1.0;
|
},
|
||||||
if (_pageController.position.haveDimensions) {
|
itemBuilder: (context, index) {
|
||||||
value = (_pageController.page ?? 0) - index;
|
return AnimatedBuilder(
|
||||||
value = (1 - (value.abs() * 0.3)).clamp(0.7, 1.0);
|
animation: _pageController,
|
||||||
|
builder: (context, child) {
|
||||||
|
double value = 1.0;
|
||||||
|
if (_pageController.position.haveDimensions) {
|
||||||
|
value = (_pageController.page ?? 0) - index;
|
||||||
|
value = (1 - (value.abs() * 0.3)).clamp(0.7, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: Transform.scale(
|
||||||
|
scale: value,
|
||||||
|
child: _buildCard(
|
||||||
|
widget.cards[index],
|
||||||
|
index,
|
||||||
|
packColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
// Кнопка закрытия
|
||||||
|
Positioned(
|
||||||
|
top: 16,
|
||||||
|
left: 16,
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
icon: Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colorScheme.surface.withOpacity(0.7),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.close,
|
||||||
|
color: colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Кнопка избранного
|
||||||
|
Positioned(
|
||||||
|
top: 16,
|
||||||
|
right: 80,
|
||||||
|
child: ScopeBuilder<UserScope>(
|
||||||
|
builder: (context, userScope) {
|
||||||
|
if (userScope == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Center(
|
if (widget.cards.isEmpty) {
|
||||||
child: Transform.scale(
|
return const SizedBox.shrink();
|
||||||
scale: value,
|
}
|
||||||
child: _buildCard(
|
|
||||||
widget.cards[index],
|
final currentCard = widget.cards[_currentIndex];
|
||||||
index,
|
final isFavorite = userScope.favoritesStateManager.isFavorite(currentCard.id);
|
||||||
packColor,
|
|
||||||
|
return IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await userScope.favoritesStateManager.toggleFavorite(currentCard.id);
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colorScheme.surface.withOpacity(0.9),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: colorScheme.shadow.withOpacity(0.2),
|
||||||
|
blurRadius: 8,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
isFavorite ? Icons.favorite : Icons.favorite_border,
|
||||||
|
color: isFavorite ? Colors.red : colorScheme.onSurface,
|
||||||
|
size: 24,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
),
|
|
||||||
|
// Индикатор текущей карточки
|
||||||
// Кнопка закрытия
|
Positioned(
|
||||||
Positioned(
|
top: 16,
|
||||||
top: 16,
|
right: 16,
|
||||||
left: 16,
|
child: Container(
|
||||||
child: IconButton(
|
padding: const EdgeInsets.symmetric(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
horizontal: 16,
|
||||||
icon: Container(
|
vertical: 8,
|
||||||
padding: const EdgeInsets.all(8),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: colorScheme.surface.withOpacity(0.7),
|
color: colorScheme.surface.withOpacity(0.7),
|
||||||
shape: BoxShape.circle,
|
borderRadius: BorderRadius.circular(20),
|
||||||
),
|
),
|
||||||
child: Icon(
|
child: Text(
|
||||||
Icons.close,
|
'${_currentIndex + 1} / ${widget.cards.length}',
|
||||||
color: colorScheme.onSurface,
|
style: TextStyle(
|
||||||
|
color: colorScheme.onSurface,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
// Кнопки навигации
|
||||||
// Индикатор текущей карточки
|
Positioned(
|
||||||
Positioned(
|
bottom: 40,
|
||||||
top: 16,
|
left: 20,
|
||||||
right: 16,
|
child: _buildNavigationButton(
|
||||||
child: Container(
|
icon: Icons.arrow_back,
|
||||||
padding: const EdgeInsets.symmetric(
|
onPressed: _goToPrevious,
|
||||||
horizontal: 16,
|
colorScheme: colorScheme,
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: colorScheme.surface.withOpacity(0.7),
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'${_currentIndex + 1} / ${widget.cards.length}',
|
|
||||||
style: TextStyle(
|
|
||||||
color: colorScheme.onSurface,
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Positioned(
|
||||||
],
|
bottom: 40,
|
||||||
|
right: 20,
|
||||||
|
child: _buildNavigationButton(
|
||||||
|
icon: Icons.arrow_forward,
|
||||||
|
onPressed: _goToNext,
|
||||||
|
colorScheme: colorScheme,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -211,6 +339,33 @@ class _CardViewerState extends State<CardViewer> {
|
||||||
isFront: false,
|
isFront: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildNavigationButton({
|
||||||
|
required IconData icon,
|
||||||
|
required VoidCallback onPressed,
|
||||||
|
required ColorScheme colorScheme,
|
||||||
|
}) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colorScheme.surface.withOpacity(0.9),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: colorScheme.shadow.withOpacity(0.2),
|
||||||
|
blurRadius: 8,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: onPressed,
|
||||||
|
icon: Icon(icon),
|
||||||
|
iconSize: 28,
|
||||||
|
color: colorScheme.onSurface,
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CardSide extends StatelessWidget {
|
class _CardSide extends StatelessWidget {
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,8 @@ class _MainShellState extends State<MainShell> {
|
||||||
case 0:
|
case 0:
|
||||||
context.go('/home');
|
context.go('/home');
|
||||||
case 1:
|
case 1:
|
||||||
context.go('/games');
|
|
||||||
case 2:
|
|
||||||
context.go('/tasks');
|
|
||||||
case 3:
|
|
||||||
context.go('/statistics');
|
context.go('/statistics');
|
||||||
case 4:
|
case 2:
|
||||||
context.go('/profile');
|
context.go('/profile');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -40,39 +36,38 @@ class _MainShellState extends State<MainShell> {
|
||||||
final location = GoRouterState.of(context).uri.path;
|
final location = GoRouterState.of(context).uri.path;
|
||||||
if (location.startsWith('/home')) {
|
if (location.startsWith('/home')) {
|
||||||
_selectedIndex = 0;
|
_selectedIndex = 0;
|
||||||
} else if (location.startsWith('/games')) {
|
|
||||||
_selectedIndex = 1;
|
|
||||||
} else if (location.startsWith('/tasks')) {
|
|
||||||
_selectedIndex = 2;
|
|
||||||
} else if (location.startsWith('/statistics')) {
|
} else if (location.startsWith('/statistics')) {
|
||||||
_selectedIndex = 3;
|
_selectedIndex = 1;
|
||||||
} else if (location.startsWith('/profile')) {
|
} else if (location.startsWith('/profile')) {
|
||||||
_selectedIndex = 4;
|
_selectedIndex = 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
_updateSelectedIndex(context);
|
_updateSelectedIndex(context);
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: widget.child,
|
body: widget.child,
|
||||||
bottomNavigationBar: BottomNavigationBar(
|
bottomNavigationBar: BottomNavigationBar(
|
||||||
currentIndex: _selectedIndex,
|
currentIndex: _selectedIndex,
|
||||||
onTap: _onItemTapped,
|
onTap: _onItemTapped,
|
||||||
|
type: BottomNavigationBarType.fixed,
|
||||||
|
selectedItemColor: theme.colorScheme.primary,
|
||||||
|
unselectedItemColor: theme.colorScheme.onSurfaceVariant,
|
||||||
|
backgroundColor: theme.colorScheme.surface,
|
||||||
|
selectedLabelStyle: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
unselectedLabelStyle: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
items: const [
|
items: const [
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Icon(Icons.home),
|
icon: Icon(Icons.home),
|
||||||
label: 'Темы',
|
label: 'Темы',
|
||||||
),
|
),
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: Icon(Icons.games),
|
|
||||||
label: 'Игры',
|
|
||||||
),
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: Icon(Icons.assignment),
|
|
||||||
label: 'Задания',
|
|
||||||
),
|
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Icon(Icons.analytics_outlined),
|
icon: Icon(Icons.analytics_outlined),
|
||||||
label: 'Статистика',
|
label: 'Статистика',
|
||||||
|
|
|
||||||
|
|
@ -172,20 +172,9 @@ class PackCardVertical extends StatelessWidget {
|
||||||
_ => Alignment.topRight,
|
_ => Alignment.topRight,
|
||||||
};
|
};
|
||||||
|
|
||||||
final borderRadius = switch (tip.position) {
|
final borderRadius = const BorderRadius.all(
|
||||||
PackTipPosition.topRight => const BorderRadius.only(
|
Radius.circular(12.0),
|
||||||
topRight: Radius.circular(12.0),
|
);
|
||||||
bottomLeft: Radius.circular(12.0),
|
|
||||||
),
|
|
||||||
PackTipPosition.bottomRight => const BorderRadius.only(
|
|
||||||
bottomRight: Radius.circular(12.0),
|
|
||||||
topLeft: Radius.circular(12.0),
|
|
||||||
),
|
|
||||||
_ => const BorderRadius.only(
|
|
||||||
topRight: Radius.circular(12.0),
|
|
||||||
bottomLeft: Radius.circular(12.0),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
return Positioned.fill(
|
return Positioned.fill(
|
||||||
child: Align(
|
child: Align(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue