Nova tela de login | Adaptaçao nova da AppBar | Animções novas

This commit is contained in:
Carlos Correia
2026-07-07 21:40:05 +01:00
parent a8e04ceeb2
commit 2ced93afdd
15 changed files with 1680 additions and 1322 deletions

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image_picker/image_picker.dart';
import 'package:lottie/lottie.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
@@ -12,7 +13,10 @@ import 'quiz/quiz1.dart';
import 'quiz/quiz_prefs.dart';
import 'screens/settings_screen.dart';
import 'screens/video_screen.dart';
import 'widgets/animated_nav_icon.dart';
import 'widgets/app_dialogs.dart';
import 'widgets/entrance.dart';
import 'widgets/tap_bounce.dart';
class LoggedHomeScreen extends StatefulWidget {
const LoggedHomeScreen({super.key});
@@ -28,6 +32,7 @@ class _LoggedHomeScreenState extends State<LoggedHomeScreen>
static const double _collapsedAppBarHeight = 104;
static const double _expandedAppBarHeight = 180;
static const double _nameOnlyAppBarHeight = 130;
int _index = 0;
@@ -207,8 +212,14 @@ class _LoggedHomeScreenState extends State<LoggedHomeScreen>
@override
Widget build(BuildContext context) {
final size = MediaQuery.sizeOf(context);
final int? score = _lastScore;
final int? maxScore = _lastMaxScore;
final bool hasScore = score != null && maxScore != null && maxScore > 0;
final int percent = hasScore ? ((score / maxScore) * 100).round() : 0;
final double appBarHeight = _index == 0
? _expandedAppBarHeight
? (hasScore ? _expandedAppBarHeight : _nameOnlyAppBarHeight)
: _collapsedAppBarHeight;
final double toolbarHeight = _index == 0 ? kToolbarHeight : appBarHeight;
final String title = _index == 0
@@ -224,10 +235,6 @@ class _LoggedHomeScreenState extends State<LoggedHomeScreen>
final shownName = _cachedUserName;
final int? score = _lastScore;
final int? maxScore = _lastMaxScore;
final bool hasScore = score != null && maxScore != null && maxScore > 0;
final int percent = hasScore ? ((score / maxScore) * 100).round() : 0;
final double bodyTopPadding = _index == 0 ? 0 : 10;
return Scaffold(
@@ -251,47 +258,41 @@ class _LoggedHomeScreenState extends State<LoggedHomeScreen>
opacity: 0.22,
child: Transform.scale(scale: 1.25),
),
Positioned(
left: 0,
right: 0,
top: toolbarHeight + 26,
child: Center(
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
if (hasScore)
Positioned(
left: 0,
right: 0,
top: toolbarHeight + 26,
child: Center(
child: Text(
(_selectedChildName ?? '').trim(),
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.w800,
color: Colors.white.withValues(alpha: 0.92),
fontSize: 14,
),
),
),
)
else if ((_selectedChildName ?? '').trim().isNotEmpty)
Positioned(
left: 0,
right: 0,
top: toolbarHeight,
bottom: 0,
child: Center(
child: Text(
_selectedChildName!.trim(),
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.w800,
color: Colors.white.withValues(alpha: 0.92),
fontSize: 14,
),
children: [
if (((_selectedChildName ?? '')
.trim()
.isNotEmpty))
TextSpan(text: _selectedChildName!.trim()),
if (((_selectedChildName ?? '')
.trim()
.isNotEmpty) &&
hasScore)
const WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 12,
),
child: Text(
'',
style: TextStyle(color: Colors.white),
),
),
),
if (hasScore)
TextSpan(text: '$score/$maxScore'),
],
),
),
),
),
if (hasScore)
Positioned(
left: 0,
@@ -308,63 +309,65 @@ class _LoggedHomeScreenState extends State<LoggedHomeScreen>
child: _index == 0
? Padding(
padding: const EdgeInsets.only(left: 16, right: 10),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(30),
onTap: () => setState(() => _index = 1),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 4,
horizontal: 4,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
CircleAvatar(
radius: 20,
backgroundColor: Colors.white.withValues(
alpha: 0.25,
child: TapBounce(
scale: 0.96,
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(30),
onTap: () => setState(() => _index = 1),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 4,
horizontal: 4,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
CircleAvatar(
radius: 20,
backgroundColor: Colors.white
.withValues(alpha: 0.25),
backgroundImage:
(_cachedPhotoUrl ?? '').isNotEmpty
? NetworkImage(_cachedPhotoUrl!)
: null,
child: (_cachedPhotoUrl ?? '').isEmpty
? const Icon(
Icons.person_rounded,
color: Colors.white,
)
: null,
),
backgroundImage:
(_cachedPhotoUrl ?? '').isNotEmpty
? NetworkImage(_cachedPhotoUrl!)
: null,
child: (_cachedPhotoUrl ?? '').isEmpty
? const Icon(
Icons.person_rounded,
color: Colors.white,
)
: null,
),
const SizedBox(width: 10),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
_greeting(),
style: TextStyle(
fontWeight: FontWeight.w600,
color: Colors.white.withValues(
alpha: 0.85,
const SizedBox(width: 10),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
_greeting(),
style: TextStyle(
fontWeight: FontWeight.w600,
color: Colors.white.withValues(
alpha: 0.85,
),
fontSize: 12,
),
fontSize: 12,
),
),
Text(
shownName,
textAlign: TextAlign.left,
style: const TextStyle(
fontWeight: FontWeight.w900,
color: Colors.white,
fontSize: 19,
Text(
shownName,
textAlign: TextAlign.left,
style: const TextStyle(
fontWeight: FontWeight.w900,
color: Colors.white,
fontSize: 19,
),
),
),
],
),
],
],
),
],
),
),
),
),
@@ -442,22 +445,35 @@ class _LoggedHomeScreenState extends State<LoggedHomeScreen>
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _index,
onTap: (i) => setState(() => _index = i),
onTap: (i) {
if (i == _index) return;
HapticFeedback.selectionClick();
setState(() => _index = i);
},
backgroundColor: const Color(0xFFFFE6F1),
selectedItemColor: _teal,
unselectedItemColor: Colors.black54,
type: BottomNavigationBarType.fixed,
items: const [
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home_rounded),
icon: AnimatedNavIcon(
icon: Icons.home_rounded,
selected: _index == 0,
),
label: 'Início',
),
BottomNavigationBarItem(
icon: Icon(Icons.person_rounded),
icon: AnimatedNavIcon(
icon: Icons.person_rounded,
selected: _index == 1,
),
label: 'Perfil',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings_rounded),
icon: AnimatedNavIcon(
icon: Icons.settings_rounded,
selected: _index == 2,
),
label: 'Ajustes',
),
],
@@ -485,7 +501,7 @@ class _RiskArcGauge extends StatelessWidget {
width: 120,
height: 60,
child: Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
Positioned.fill(
child: CustomPaint(
@@ -493,29 +509,19 @@ class _RiskArcGauge extends StatelessWidget {
),
),
Positioned(
bottom: 4,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'$shown%',
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.w900,
height: 1,
),
top: 38,
left: 6,
right: 0,
child: Center(
child: Text(
'$shown%',
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.w900,
height: 1,
),
const SizedBox(height: 2),
Text(
'',
style: TextStyle(
color: Colors.white.withValues(alpha: 0.92),
fontSize: 8,
fontWeight: FontWeight.w900,
),
),
],
),
),
),
],
@@ -630,19 +636,30 @@ class _InicioTab extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_HeroQuizCard(
childName: selectedChildName,
onStartQuiz: () => _startQuiz(context),
FadeSlideIn(
child: TapBounce(
scale: 0.97,
child: _HeroQuizCard(
childName: selectedChildName,
onStartQuiz: () => _startQuiz(context),
),
),
),
const SizedBox(height: 16),
_VideoLibraryCard(
onOpenLibrary: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const VideoScreen(),
),
);
},
FadeSlideIn(
delay: const Duration(milliseconds: 90),
child: TapBounce(
scale: 0.97,
child: _VideoLibraryCard(
onOpenLibrary: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const VideoScreen(),
),
);
},
),
),
),
const SizedBox(height: 16),
],
@@ -741,7 +758,9 @@ Future<Map<String, dynamic>?> _pickChildSheet(
final label = age != null ? '$name$age anos' : name;
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Material(
child: TapBounce(
scale: 0.97,
child: Material(
color: Colors.white.withValues(alpha: 0.85),
borderRadius: BorderRadius.circular(16),
child: InkWell(
@@ -770,6 +789,7 @@ Future<Map<String, dynamic>?> _pickChildSheet(
),
),
),
),
),
);
}),
@@ -1327,7 +1347,8 @@ class _PerfilTabState extends State<_PerfilTab> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Material(
FadeSlideIn(
child: Material(
elevation: 10,
color: Colors.white,
borderRadius: BorderRadius.circular(20),
@@ -1337,7 +1358,9 @@ class _PerfilTabState extends State<_PerfilTab> {
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
InkWell(
TapBounce(
scale: 0.92,
child: InkWell(
borderRadius: BorderRadius.circular(40),
onTap: _updatingPhoto
? null
@@ -1415,6 +1438,7 @@ class _PerfilTabState extends State<_PerfilTab> {
],
),
),
),
const SizedBox(width: 14),
Expanded(
child: Column(
@@ -1453,6 +1477,7 @@ class _PerfilTabState extends State<_PerfilTab> {
),
),
),
),
const SizedBox(height: 22),
Padding(
padding: const EdgeInsets.only(left: 4, bottom: 10),
@@ -1554,9 +1579,15 @@ class _PerfilTabState extends State<_PerfilTab> {
].join('');
final bool selected = i == selectedIndex;
return Padding(
return FadeSlideIn(
delay: Duration(
milliseconds: 60 * i.clamp(0, 6),
),
child: Padding(
padding: const EdgeInsets.only(bottom: 12),
child: InkWell(
child: TapBounce(
scale: 0.97,
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () => widget.onChildSelected(
i,
@@ -1662,47 +1693,53 @@ class _PerfilTabState extends State<_PerfilTab> {
],
),
),
),
),
),
);
}),
SizedBox(
height: 48,
child: FilledButton.icon(
style: FilledButton.styleFrom(
backgroundColor: const Color(0xFF2F9E94),
foregroundColor: Colors.white,
shape: const StadiumBorder(),
textStyle: const TextStyle(
fontWeight: FontWeight.w800,
TapBounce(
child: SizedBox(
height: 48,
child: FilledButton.icon(
style: FilledButton.styleFrom(
backgroundColor: const Color(0xFF2F9E94),
foregroundColor: Colors.white,
shape: const StadiumBorder(),
textStyle: const TextStyle(
fontWeight: FontWeight.w800,
),
),
onPressed: _addingChild
? null
: () => _addAnotherChild(context, uid),
icon: const Icon(Icons.add_rounded),
label: const Text('Adicionar criança'),
),
onPressed: _addingChild
? null
: () => _addAnotherChild(context, uid),
icon: const Icon(Icons.add_rounded),
label: const Text('Adicionar criança'),
),
),
const SizedBox(height: 22),
SizedBox(
height: 46,
child: OutlinedButton.icon(
style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFFFF55A7),
side: const BorderSide(
color: Color(0xFFFF55A7),
width: 1.4,
),
shape: const StadiumBorder(),
textStyle: const TextStyle(
fontWeight: FontWeight.w800,
TapBounce(
child: SizedBox(
height: 46,
child: OutlinedButton.icon(
style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFFFF55A7),
side: const BorderSide(
color: Color(0xFFFF55A7),
width: 1.4,
),
shape: const StadiumBorder(),
textStyle: const TextStyle(
fontWeight: FontWeight.w800,
),
),
onPressed: () async {
await supabase.auth.signOut();
},
icon: const Icon(Icons.logout_rounded),
label: const Text('Sair'),
),
onPressed: () async {
await supabase.auth.signOut();
},
icon: const Icon(Icons.logout_rounded),
label: const Text('Sair'),
),
),
const SizedBox(height: 12),
@@ -1844,17 +1881,21 @@ class _AddChildSheetState extends State<_AddChildSheet> {
),
const SizedBox(width: 10),
Expanded(
child: SizedBox(
height: 44,
child: FilledButton(
style: FilledButton.styleFrom(
backgroundColor: const Color(0xFF2F9E94),
foregroundColor: Colors.white,
shape: const StadiumBorder(),
textStyle: const TextStyle(fontWeight: FontWeight.w900),
child: TapBounce(
child: SizedBox(
height: 44,
child: FilledButton(
style: FilledButton.styleFrom(
backgroundColor: const Color(0xFF2F9E94),
foregroundColor: Colors.white,
shape: const StadiumBorder(),
textStyle: const TextStyle(
fontWeight: FontWeight.w900,
),
),
onPressed: _submit,
child: const Text('Adicionar'),
),
onPressed: _submit,
child: const Text('Adicionar'),
),
),
),