Clinicas Parceiras e curiosidades retiradas.
This commit is contained in:
@@ -3,14 +3,25 @@ import 'dart:math' as math;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lottie/lottie.dart';
|
||||
|
||||
typedef QuizNextBuilder = Route<void> Function(BuildContext context, int nextScore);
|
||||
typedef QuizNextBuilder =
|
||||
Route<void> Function(BuildContext context, int nextScore);
|
||||
|
||||
enum QuizAnswerType { text, image, number, yesNo }
|
||||
|
||||
class QuizAnswer {
|
||||
const QuizAnswer({required this.title, required this.description, required this.weight});
|
||||
const QuizAnswer({
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.weight,
|
||||
this.imagePath,
|
||||
this.value,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String description;
|
||||
final int weight;
|
||||
final String? imagePath;
|
||||
final String? value;
|
||||
}
|
||||
|
||||
class QuizQuestionScreen extends StatefulWidget {
|
||||
@@ -24,6 +35,7 @@ class QuizQuestionScreen extends StatefulWidget {
|
||||
this.onFinished,
|
||||
this.isFinal = false,
|
||||
this.showBackButton = false,
|
||||
this.answerType = QuizAnswerType.text,
|
||||
});
|
||||
|
||||
final String title;
|
||||
@@ -34,6 +46,7 @@ class QuizQuestionScreen extends StatefulWidget {
|
||||
final VoidCallback? onFinished;
|
||||
final bool isFinal;
|
||||
final bool showBackButton;
|
||||
final QuizAnswerType answerType;
|
||||
|
||||
@override
|
||||
State<QuizQuestionScreen> createState() => _QuizQuestionScreenState();
|
||||
@@ -41,11 +54,30 @@ class QuizQuestionScreen extends StatefulWidget {
|
||||
|
||||
class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
int? _selected;
|
||||
TextEditingController? _numberController;
|
||||
int? _numberValue;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.answerType == QuizAnswerType.number) {
|
||||
_numberController = TextEditingController();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_numberController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.sizeOf(context);
|
||||
final bool canProceed = _selected != null;
|
||||
bool canProceed = _selected != null;
|
||||
if (widget.answerType == QuizAnswerType.number) {
|
||||
canProceed = _numberValue != null && _numberValue! >= 0;
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
@@ -57,10 +89,7 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Color(0xFFFFE6F1),
|
||||
Color(0xFFFFC9DF),
|
||||
],
|
||||
colors: [Color(0xFFFFE6F1), Color(0xFFFFC9DF)],
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -119,7 +148,11 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Escolha apenas uma opção',
|
||||
widget.answerType == QuizAnswerType.number
|
||||
? 'Insira o número'
|
||||
: widget.answerType == QuizAnswerType.yesNo
|
||||
? 'Escolha uma opção'
|
||||
: 'Escolha apenas uma opção',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.black.withValues(alpha: 0.55),
|
||||
@@ -132,18 +165,21 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: ListView.separated(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
itemCount: widget.answers.length,
|
||||
separatorBuilder: (context, index) => const SizedBox(height: 12),
|
||||
itemBuilder: (context, i) {
|
||||
return _QuizAnswerTile(
|
||||
answer: widget.answers[i],
|
||||
selected: _selected == i,
|
||||
onTap: () => setState(() => _selected = i),
|
||||
);
|
||||
},
|
||||
),
|
||||
child: widget.answerType == QuizAnswerType.number
|
||||
? _buildNumberInput()
|
||||
: ListView.separated(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
itemCount: widget.answers.length,
|
||||
separatorBuilder: (context, index) =>
|
||||
const SizedBox(height: 12),
|
||||
itemBuilder: (context, i) {
|
||||
return _QuizAnswerTile(
|
||||
answer: widget.answers[i],
|
||||
selected: _selected == i,
|
||||
onTap: () => setState(() => _selected = i),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
@@ -154,41 +190,77 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
width: size.width * 0.62,
|
||||
height: 46,
|
||||
child: FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF2F9E94),
|
||||
foregroundColor: Colors.white,
|
||||
shape: const StadiumBorder(),
|
||||
textStyle: const TextStyle(fontWeight: FontWeight.w900),
|
||||
).copyWith(
|
||||
animationDuration: const Duration(milliseconds: 180),
|
||||
splashFactory: InkSparkle.splashFactory,
|
||||
overlayColor: WidgetStateProperty.resolveWith<Color?>(
|
||||
(states) {
|
||||
if (states.contains(WidgetState.pressed)) {
|
||||
return Colors.white.withValues(alpha: 0.14);
|
||||
}
|
||||
if (states.contains(WidgetState.hovered) || states.contains(WidgetState.focused)) {
|
||||
return Colors.white.withValues(alpha: 0.08);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
style:
|
||||
FilledButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF2F9E94),
|
||||
foregroundColor: Colors.white,
|
||||
shape: const StadiumBorder(),
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
).copyWith(
|
||||
animationDuration: const Duration(
|
||||
milliseconds: 180,
|
||||
),
|
||||
splashFactory: InkSparkle.splashFactory,
|
||||
overlayColor:
|
||||
WidgetStateProperty.resolveWith<Color?>(
|
||||
(states) {
|
||||
if (states.contains(
|
||||
WidgetState.pressed,
|
||||
)) {
|
||||
return Colors.white.withValues(
|
||||
alpha: 0.14,
|
||||
);
|
||||
}
|
||||
if (states.contains(
|
||||
WidgetState.hovered,
|
||||
) ||
|
||||
states.contains(
|
||||
WidgetState.focused,
|
||||
)) {
|
||||
return Colors.white.withValues(
|
||||
alpha: 0.08,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
onPressed: !canProceed
|
||||
? null
|
||||
: () {
|
||||
final picked = widget.answers[_selected!];
|
||||
final nextScore = widget.currentScore + picked.weight;
|
||||
int nextScore = widget.currentScore;
|
||||
if (widget.answerType ==
|
||||
QuizAnswerType.number) {
|
||||
nextScore =
|
||||
widget.currentScore +
|
||||
(_numberValue ?? 0);
|
||||
} else {
|
||||
final picked =
|
||||
widget.answers[_selected!];
|
||||
nextScore =
|
||||
widget.currentScore + picked.weight;
|
||||
}
|
||||
|
||||
if (widget.isFinal) {
|
||||
widget.onFinished?.call();
|
||||
Navigator.of(context).popUntil((r) => r.isFirst);
|
||||
final finishedRoute = widget.nextRoute(
|
||||
context,
|
||||
nextScore,
|
||||
);
|
||||
Navigator.of(
|
||||
context,
|
||||
).pushReplacement(finishedRoute);
|
||||
return;
|
||||
}
|
||||
|
||||
Navigator.of(context).push(widget.nextRoute(context, nextScore));
|
||||
Navigator.of(context).push(
|
||||
widget.nextRoute(context, nextScore),
|
||||
);
|
||||
},
|
||||
child: Text(widget.isFinal ? 'Concluir' : 'Avançar'),
|
||||
child: Text(
|
||||
widget.isFinal ? 'Concluir' : 'Avançar',
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.showBackButton) ...[
|
||||
@@ -197,27 +269,45 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
width: size.width * 0.62,
|
||||
height: 42,
|
||||
child: FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF2F9E94),
|
||||
foregroundColor: Colors.white,
|
||||
shape: const StadiumBorder(),
|
||||
textStyle: const TextStyle(fontWeight: FontWeight.w900),
|
||||
).copyWith(
|
||||
animationDuration: const Duration(milliseconds: 180),
|
||||
splashFactory: InkSparkle.splashFactory,
|
||||
overlayColor: WidgetStateProperty.resolveWith<Color?>(
|
||||
(states) {
|
||||
if (states.contains(WidgetState.pressed)) {
|
||||
return Colors.white.withValues(alpha: 0.14);
|
||||
}
|
||||
if (states.contains(WidgetState.hovered) || states.contains(WidgetState.focused)) {
|
||||
return Colors.white.withValues(alpha: 0.08);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
onPressed: () => Navigator.of(context).maybePop(),
|
||||
style:
|
||||
FilledButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF2F9E94),
|
||||
foregroundColor: Colors.white,
|
||||
shape: const StadiumBorder(),
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
).copyWith(
|
||||
animationDuration: const Duration(
|
||||
milliseconds: 180,
|
||||
),
|
||||
splashFactory: InkSparkle.splashFactory,
|
||||
overlayColor:
|
||||
WidgetStateProperty.resolveWith<
|
||||
Color?
|
||||
>((states) {
|
||||
if (states.contains(
|
||||
WidgetState.pressed,
|
||||
)) {
|
||||
return Colors.white.withValues(
|
||||
alpha: 0.14,
|
||||
);
|
||||
}
|
||||
if (states.contains(
|
||||
WidgetState.hovered,
|
||||
) ||
|
||||
states.contains(
|
||||
WidgetState.focused,
|
||||
)) {
|
||||
return Colors.white.withValues(
|
||||
alpha: 0.08,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
),
|
||||
onPressed: () =>
|
||||
Navigator.of(context).maybePop(),
|
||||
child: const Text('Voltar'),
|
||||
),
|
||||
),
|
||||
@@ -234,10 +324,67 @@ class _QuizQuestionScreenState extends State<QuizQuestionScreen> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNumberInput() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 150,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.70),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: Colors.black.withValues(alpha: 0.12),
|
||||
width: 1.0,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.06),
|
||||
blurRadius: 18,
|
||||
offset: const Offset(0, 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: TextField(
|
||||
controller: _numberController,
|
||||
keyboardType: TextInputType.number,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Color(0xFF2F9E94),
|
||||
),
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: '0',
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.grey,
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 20),
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_numberValue = int.tryParse(value);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _QuizAnswerTile extends StatelessWidget {
|
||||
const _QuizAnswerTile({required this.answer, required this.selected, required this.onTap});
|
||||
const _QuizAnswerTile({
|
||||
required this.answer,
|
||||
required this.selected,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final QuizAnswer answer;
|
||||
final bool selected;
|
||||
@@ -245,8 +392,12 @@ class _QuizAnswerTile extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final borderColor = selected ? const Color(0xFF2F9E94) : Colors.black.withValues(alpha: 0.12);
|
||||
final bg = selected ? Colors.white.withValues(alpha: 0.88) : Colors.white.withValues(alpha: 0.70);
|
||||
final borderColor = selected
|
||||
? const Color(0xFF2F9E94)
|
||||
: Colors.black.withValues(alpha: 0.12);
|
||||
final bg = selected
|
||||
? Colors.white.withValues(alpha: 0.88)
|
||||
: Colors.white.withValues(alpha: 0.70);
|
||||
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 220),
|
||||
@@ -310,7 +461,9 @@ class _QuizAnswerTile extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
crossFadeState: selected ? CrossFadeState.showSecond : CrossFadeState.showFirst,
|
||||
crossFadeState: selected
|
||||
? CrossFadeState.showSecond
|
||||
: CrossFadeState.showFirst,
|
||||
duration: const Duration(milliseconds: 220),
|
||||
firstCurve: Curves.easeIn,
|
||||
secondCurve: Curves.easeOut,
|
||||
|
||||
Reference in New Issue
Block a user