end widget
This commit is contained in:
parent
d2c04fc2c8
commit
b3344ae794
21 changed files with 403 additions and 168 deletions
BIN
icons/crown.png
Normal file
BIN
icons/crown.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 351 B |
BIN
icons/skull.png
Normal file
BIN
icons/skull.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 563 B |
BIN
icons/small_circle.png
Normal file
BIN
icons/small_circle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 312 B |
|
|
@ -22,6 +22,16 @@ class ProgressWidget extends StatefulWidget {
|
|||
@override
|
||||
State<StatefulWidget> createState() => _ProgressWidgetState();
|
||||
|
||||
factory ProgressWidget.value(int value, int length, Color color) =>
|
||||
ProgressWidget(
|
||||
List.generate(
|
||||
length,
|
||||
(i) => i < value ? Result.correct : Result.not_visited,
|
||||
),
|
||||
length,
|
||||
color,
|
||||
);
|
||||
|
||||
ProgressWidget(
|
||||
this.results,
|
||||
this.length,
|
||||
|
|
@ -156,36 +166,6 @@ class _ProgressWidgetState extends State<ProgressWidget> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget correctAnswers() {
|
||||
return Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Container(
|
||||
height: 6,
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Row(
|
||||
children: [
|
||||
...List.generate(
|
||||
length,
|
||||
(index) => Expanded(
|
||||
child: AnimatedContainer(
|
||||
curve: Curves.linear,
|
||||
duration: Duration(milliseconds: 300),
|
||||
color: index < results.length
|
||||
? results[index] == Result.correct
|
||||
? Colors.lightGreenAccent[200]
|
||||
: results[index] == Result.wrong
|
||||
? Colors.redAccent[100]
|
||||
: Colors.white
|
||||
: Colors.transparent,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_ProgressWidgetState();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class InputButtonsTestState extends TestQuestionState {
|
|||
|
||||
InputButtonsTestState({
|
||||
required this.seed,
|
||||
required super.word,
|
||||
this.answer = const [],
|
||||
this.answerIds = const [],
|
||||
this.isCorrect = false,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ part of 'input_buttons_test_state.dart';
|
|||
abstract class _$InputButtonsTestStateCWProxy {
|
||||
InputButtonsTestState seed(int seed);
|
||||
|
||||
InputButtonsTestState word(String word);
|
||||
|
||||
InputButtonsTestState answer(List<String> answer);
|
||||
|
||||
InputButtonsTestState answerIds(List<String> answerIds);
|
||||
|
|
@ -29,6 +31,7 @@ abstract class _$InputButtonsTestStateCWProxy {
|
|||
/// ````
|
||||
InputButtonsTestState call({
|
||||
int? seed,
|
||||
String? word,
|
||||
List<String>? answer,
|
||||
List<String>? answerIds,
|
||||
bool? isCorrect,
|
||||
|
|
@ -48,6 +51,9 @@ class _$InputButtonsTestStateCWProxyImpl
|
|||
@override
|
||||
InputButtonsTestState seed(int seed) => this(seed: seed);
|
||||
|
||||
@override
|
||||
InputButtonsTestState word(String word) => this(word: word);
|
||||
|
||||
@override
|
||||
InputButtonsTestState answer(List<String> answer) => this(answer: answer);
|
||||
|
||||
|
|
@ -79,6 +85,7 @@ class _$InputButtonsTestStateCWProxyImpl
|
|||
/// ````
|
||||
InputButtonsTestState call({
|
||||
Object? seed = const $CopyWithPlaceholder(),
|
||||
Object? word = const $CopyWithPlaceholder(),
|
||||
Object? answer = const $CopyWithPlaceholder(),
|
||||
Object? answerIds = const $CopyWithPlaceholder(),
|
||||
Object? isCorrect = const $CopyWithPlaceholder(),
|
||||
|
|
@ -91,6 +98,10 @@ class _$InputButtonsTestStateCWProxyImpl
|
|||
? _value.seed
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: seed as int,
|
||||
word: word == const $CopyWithPlaceholder() || word == null
|
||||
? _value.word
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: word as String,
|
||||
answer: answer == const $CopyWithPlaceholder() || answer == null
|
||||
? _value.answer
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
|
|
@ -135,6 +146,7 @@ InputButtonsTestState _$InputButtonsTestStateFromJson(
|
|||
Map<String, dynamic> json) =>
|
||||
InputButtonsTestState(
|
||||
seed: (json['seed'] as num).toInt(),
|
||||
word: json['word'] as String,
|
||||
answer: (json['answer'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
|
|
@ -155,6 +167,7 @@ Map<String, dynamic> _$InputButtonsTestStateToJson(
|
|||
InputButtonsTestState instance) =>
|
||||
<String, dynamic>{
|
||||
'testType': _$TestQuestionTypeEnumMap[instance.testType]!,
|
||||
'word': instance.word,
|
||||
'answer': instance.answer,
|
||||
'answerIds': instance.answerIds,
|
||||
'isCorrect': instance.isCorrect,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ class SimpleTestState extends TestQuestionState {
|
|||
|
||||
SimpleTestState({
|
||||
required this.seed,
|
||||
required super.word,
|
||||
this.answer = '',
|
||||
this.isCorrect = false,
|
||||
this.isAnswered = false,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ part of 'simple_test_state.dart';
|
|||
abstract class _$SimpleTestStateCWProxy {
|
||||
SimpleTestState seed(int seed);
|
||||
|
||||
SimpleTestState word(String word);
|
||||
|
||||
SimpleTestState answer(String answer);
|
||||
|
||||
SimpleTestState isCorrect(bool isCorrect);
|
||||
|
|
@ -27,6 +29,7 @@ abstract class _$SimpleTestStateCWProxy {
|
|||
/// ````
|
||||
SimpleTestState call({
|
||||
int? seed,
|
||||
String? word,
|
||||
String? answer,
|
||||
bool? isCorrect,
|
||||
bool? isAnswered,
|
||||
|
|
@ -44,6 +47,9 @@ class _$SimpleTestStateCWProxyImpl implements _$SimpleTestStateCWProxy {
|
|||
@override
|
||||
SimpleTestState seed(int seed) => this(seed: seed);
|
||||
|
||||
@override
|
||||
SimpleTestState word(String word) => this(word: word);
|
||||
|
||||
@override
|
||||
SimpleTestState answer(String answer) => this(answer: answer);
|
||||
|
||||
|
|
@ -70,6 +76,7 @@ class _$SimpleTestStateCWProxyImpl implements _$SimpleTestStateCWProxy {
|
|||
/// ````
|
||||
SimpleTestState call({
|
||||
Object? seed = const $CopyWithPlaceholder(),
|
||||
Object? word = const $CopyWithPlaceholder(),
|
||||
Object? answer = const $CopyWithPlaceholder(),
|
||||
Object? isCorrect = const $CopyWithPlaceholder(),
|
||||
Object? isAnswered = const $CopyWithPlaceholder(),
|
||||
|
|
@ -81,6 +88,10 @@ class _$SimpleTestStateCWProxyImpl implements _$SimpleTestStateCWProxy {
|
|||
? _value.seed
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: seed as int,
|
||||
word: word == const $CopyWithPlaceholder() || word == null
|
||||
? _value.word
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: word as String,
|
||||
answer: answer == const $CopyWithPlaceholder() || answer == null
|
||||
? _value.answer
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
|
|
@ -119,6 +130,7 @@ extension $SimpleTestStateCopyWith on SimpleTestState {
|
|||
SimpleTestState _$SimpleTestStateFromJson(Map<String, dynamic> json) =>
|
||||
SimpleTestState(
|
||||
seed: (json['seed'] as num).toInt(),
|
||||
word: json['word'] as String,
|
||||
answer: json['answer'] as String? ?? '',
|
||||
isCorrect: json['isCorrect'] as bool? ?? false,
|
||||
isAnswered: json['isAnswered'] as bool? ?? false,
|
||||
|
|
@ -131,6 +143,7 @@ SimpleTestState _$SimpleTestStateFromJson(Map<String, dynamic> json) =>
|
|||
Map<String, dynamic> _$SimpleTestStateToJson(SimpleTestState instance) =>
|
||||
<String, dynamic>{
|
||||
'testType': _$TestQuestionTypeEnumMap[instance.testType]!,
|
||||
'word': instance.word,
|
||||
'answer': instance.answer,
|
||||
'isCorrect': instance.isCorrect,
|
||||
'isAnswered': instance.isAnswered,
|
||||
|
|
|
|||
|
|
@ -9,11 +9,14 @@ class TestQuestionState {
|
|||
|
||||
bool get isCorrect => false;
|
||||
|
||||
// isAnswered == not skipped by scrolling
|
||||
final bool isAnswered;
|
||||
final bool isSkipped;
|
||||
final String word;
|
||||
|
||||
const TestQuestionState({
|
||||
required this.testType,
|
||||
required this.word,
|
||||
this.isAnswered = false,
|
||||
this.isSkipped = false,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ part of 'test_question_state.dart';
|
|||
abstract class _$TestQuestionStateCWProxy {
|
||||
TestQuestionState testType(TestQuestionType testType);
|
||||
|
||||
TestQuestionState word(String word);
|
||||
|
||||
TestQuestionState isAnswered(bool isAnswered);
|
||||
|
||||
TestQuestionState isSkipped(bool isSkipped);
|
||||
|
|
@ -21,6 +23,7 @@ abstract class _$TestQuestionStateCWProxy {
|
|||
/// ````
|
||||
TestQuestionState call({
|
||||
TestQuestionType? testType,
|
||||
String? word,
|
||||
bool? isAnswered,
|
||||
bool? isSkipped,
|
||||
});
|
||||
|
|
@ -36,6 +39,9 @@ class _$TestQuestionStateCWProxyImpl implements _$TestQuestionStateCWProxy {
|
|||
TestQuestionState testType(TestQuestionType testType) =>
|
||||
this(testType: testType);
|
||||
|
||||
@override
|
||||
TestQuestionState word(String word) => this(word: word);
|
||||
|
||||
@override
|
||||
TestQuestionState isAnswered(bool isAnswered) => this(isAnswered: isAnswered);
|
||||
|
||||
|
|
@ -52,6 +58,7 @@ class _$TestQuestionStateCWProxyImpl implements _$TestQuestionStateCWProxy {
|
|||
/// ````
|
||||
TestQuestionState call({
|
||||
Object? testType = const $CopyWithPlaceholder(),
|
||||
Object? word = const $CopyWithPlaceholder(),
|
||||
Object? isAnswered = const $CopyWithPlaceholder(),
|
||||
Object? isSkipped = const $CopyWithPlaceholder(),
|
||||
}) {
|
||||
|
|
@ -60,6 +67,10 @@ class _$TestQuestionStateCWProxyImpl implements _$TestQuestionStateCWProxy {
|
|||
? _value.testType
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: testType as TestQuestionType,
|
||||
word: word == const $CopyWithPlaceholder() || word == null
|
||||
? _value.word
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
: word as String,
|
||||
isAnswered:
|
||||
isAnswered == const $CopyWithPlaceholder() || isAnswered == null
|
||||
? _value.isAnswered
|
||||
|
|
|
|||
|
|
@ -18,10 +18,13 @@ class HorizontalBlockedScrollPhysics extends ScrollPhysics {
|
|||
/// If [true] it blocks the right movement.
|
||||
final bool blockRightMovement;
|
||||
|
||||
final ValueGetter<bool>? canScroll;
|
||||
|
||||
const HorizontalBlockedScrollPhysics({
|
||||
ScrollPhysics? parent,
|
||||
this.blockLeftMovement = false,
|
||||
this.blockRightMovement = false,
|
||||
this.canScroll,
|
||||
}) : super(parent: parent);
|
||||
|
||||
@override
|
||||
|
|
@ -33,6 +36,12 @@ class HorizontalBlockedScrollPhysics extends ScrollPhysics {
|
|||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get allowUserScrolling => (canScroll == null || canScroll!.call());
|
||||
|
||||
@override
|
||||
bool get allowImplicitScrolling => (canScroll == null || canScroll!.call());
|
||||
|
||||
@override
|
||||
double applyBoundaryConditions(ScrollMetrics position, double value) {
|
||||
assert(() {
|
||||
|
|
@ -95,6 +104,14 @@ class HorizontalBlockedScrollPhysics extends ScrollPhysics {
|
|||
|
||||
// We're moving right and we want to block.
|
||||
if (!isMovingLeft && blockRightMovement && !isPointInScreenLeftRange) {
|
||||
if (pointInScreen.abs() < delta.abs()) {
|
||||
// fix for strong movements
|
||||
return pointInScreen;
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
|
||||
if (canScroll != null && !canScroll!.call()) {
|
||||
return delta;
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +155,8 @@ class LeftBlockedScrollPhysics extends HorizontalBlockedScrollPhysics {
|
|||
class RightBlockedScrollPhysics extends HorizontalBlockedScrollPhysics {
|
||||
const RightBlockedScrollPhysics({
|
||||
ScrollPhysics? parent,
|
||||
}) : super(parent: parent, blockRightMovement: true);
|
||||
ValueGetter<bool>? canScroll,
|
||||
}) : super(parent: parent, canScroll: canScroll, blockRightMovement: true);
|
||||
|
||||
@override
|
||||
RightBlockedScrollPhysics applyTo(ScrollPhysics? ancestor) {
|
||||
|
|
|
|||
192
lib/features/tests/test_complete_widget.dart
Normal file
192
lib/features/tests/test_complete_widget.dart
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:mnemo_cards/utils/color_helper.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
import '../../widgets/big_back_button.dart';
|
||||
import '../../widgets/horizontal_progress.dart';
|
||||
import 'question_states/test_question_state.dart';
|
||||
|
||||
class TestCompleteWidget extends StatelessWidget {
|
||||
final Iterable<TestQuestionState> states;
|
||||
final Color successColor;
|
||||
|
||||
static final _pieChartKey = GlobalKey<ImplicitlyAnimatedWidgetState>();
|
||||
|
||||
TestCompleteWidget(
|
||||
this.states,
|
||||
this.successColor,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final total = states.length;
|
||||
final correct = states.where((e) => e.isCorrect).length;
|
||||
final incorrect = states.where((e) => !e.isCorrect && e.isAnswered).length;
|
||||
final skipped = states.where((e) => !e.isAnswered).length;
|
||||
|
||||
final words = states
|
||||
.fold(
|
||||
Map<String, WordStat>(),
|
||||
(map, state) => map
|
||||
..[state.word] = (map[state.word] ?? WordStat.zero(state.word))
|
||||
.inc(state.isCorrect),
|
||||
)
|
||||
.values
|
||||
.toList()
|
||||
..sort((p, n) => p.correctCount.compareTo(n.correctCount));
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 12.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width / 1.8,
|
||||
height: MediaQuery.of(context).size.width / 1.8,
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: StreamBuilder<int>(
|
||||
stream: Stream.fromIterable([5])
|
||||
.interval(Duration(milliseconds: 0)),
|
||||
initialData: 0,
|
||||
builder: (context, snapshot) {
|
||||
final a = min(snapshot.requireData / 5.0, 1.0);
|
||||
return PieChart(
|
||||
key: _pieChartKey,
|
||||
swapAnimationDuration: Duration(milliseconds: 600),
|
||||
PieChartData(
|
||||
pieTouchData: PieTouchData(),
|
||||
borderData: FlBorderData(
|
||||
show: false,
|
||||
),
|
||||
sectionsSpace: 0,
|
||||
centerSpaceRadius:
|
||||
MediaQuery.of(context).size.width / 8,
|
||||
sections: [
|
||||
PieChartSectionData(
|
||||
color: successColor,
|
||||
showTitle: false,
|
||||
value: correct.toDouble() * a,
|
||||
badgeWidget: Image.asset(
|
||||
'icons/crown.png',
|
||||
width: 20.w,
|
||||
),
|
||||
),
|
||||
PieChartSectionData(
|
||||
color: successColor.lighten(0.15),
|
||||
showTitle: false,
|
||||
value: incorrect.toDouble() * a,
|
||||
badgeWidget: Image.asset(
|
||||
'icons/skull.png',
|
||||
width: 20.w,
|
||||
),
|
||||
),
|
||||
PieChartSectionData(
|
||||
color: Colors.white,
|
||||
showTitle: false,
|
||||
value: skipped.toDouble(),
|
||||
badgeWidget: Image.asset(
|
||||
'icons/small_circle.png',
|
||||
width: 20.w,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
Text(
|
||||
'$correct/$total',
|
||||
style: TextStyle(
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'1:47',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 0.85,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Container(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: words.length,
|
||||
itemBuilder: (context, index) {
|
||||
final word = words[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12.0,
|
||||
right: 24,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'${word.word}',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width / 2.5,
|
||||
child: HorizontalProgressWidget(
|
||||
word.correctCount,
|
||||
word.totalCount,
|
||||
successColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const BigBackButton(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class WordStat {
|
||||
String word;
|
||||
int correctCount;
|
||||
int totalCount;
|
||||
|
||||
WordStat(this.word, this.correctCount, this.totalCount);
|
||||
|
||||
factory WordStat.zero(String word) => WordStat(word, 0, 0);
|
||||
|
||||
WordStat inc(bool isCorrect) => isCorrect ? correct(this) : incorrect(this);
|
||||
|
||||
WordStat correct(WordStat stat) => WordStat(
|
||||
stat.word,
|
||||
stat.correctCount + 1,
|
||||
stat.totalCount + 1,
|
||||
);
|
||||
|
||||
WordStat incorrect(WordStat stat) => WordStat(
|
||||
stat.word,
|
||||
stat.correctCount,
|
||||
stat.totalCount + 1,
|
||||
);
|
||||
}
|
||||
|
|
@ -4,12 +4,11 @@ import 'dart:math' as math;
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:mnemo_cards/features/tests/question_states/test_question_state.dart';
|
||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||
import 'package:pie_chart/pie_chart.dart';
|
||||
|
||||
import '../../di/locator.dart';
|
||||
import 'scroll_physics.dart';
|
||||
import 'test_complete_widget.dart';
|
||||
import 'test_progress_widget.dart';
|
||||
import 'test_widgets/input_buttons_test.dart';
|
||||
import 'test_widgets/simple_test.dart';
|
||||
|
|
@ -25,7 +24,6 @@ class TestPage extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _TestPageState extends State<TestPage> {
|
||||
|
||||
PageController get pageController => locator.testManager.pageController;
|
||||
|
||||
@override
|
||||
|
|
@ -44,13 +42,11 @@ class _TestPageState extends State<TestPage> {
|
|||
|
||||
void _pageScrollListener() {
|
||||
final index = pageController.page?.floor() ?? 0;
|
||||
if (index > 0) {
|
||||
final prevId =
|
||||
locator.testManager.questions[index - 1].id!;
|
||||
final state =
|
||||
locator.testManager.getState(prevId);
|
||||
if (index > 0 && index <= locator.testManager.questions.length) {
|
||||
final prevId = locator.testManager.questions[index - 1].id!;
|
||||
final state = locator.testManager.getState(prevId);
|
||||
if (state?.isAnswered == false) {
|
||||
log('Question skipped');
|
||||
// log('Question skipped');
|
||||
locator.testManager.skip(prevId);
|
||||
}
|
||||
}
|
||||
|
|
@ -90,90 +86,18 @@ class _TestPageState extends State<TestPage> {
|
|||
),
|
||||
Expanded(
|
||||
child: PageView.builder(
|
||||
physics: RightBlockedScrollPhysics(),
|
||||
physics: RightBlockedScrollPhysics(
|
||||
canScroll: _canScroll,
|
||||
),
|
||||
controller: locator.testManager.pageController,
|
||||
itemBuilder: (context, index) {
|
||||
if (index > locator.testManager.questions.length) {
|
||||
return null;
|
||||
}
|
||||
if (index == locator.testManager.questions.length) {
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: PieChart(
|
||||
dataMap: {
|
||||
'Правильно': locator.testManager.states
|
||||
.where((e) => e.isCorrect)
|
||||
.length
|
||||
.toDouble(),
|
||||
'Ошибка': locator.testManager.states
|
||||
.where((e) =>
|
||||
!e.isCorrect && e.isAnswered)
|
||||
.length
|
||||
.toDouble(),
|
||||
'Пропущено': locator.testManager.states
|
||||
.where((e) => !e.isAnswered)
|
||||
.length
|
||||
.toDouble(),
|
||||
},
|
||||
animationDuration:
|
||||
Duration(milliseconds: 800),
|
||||
chartLegendSpacing: 32,
|
||||
chartRadius:
|
||||
MediaQuery.of(context).size.width /
|
||||
3.2,
|
||||
colorList: [
|
||||
Colors.lightGreenAccent[200]!,
|
||||
Colors.redAccent[100]!,
|
||||
Colors.white,
|
||||
],
|
||||
initialAngleInDegree: 0,
|
||||
chartType: ChartType.ring,
|
||||
ringStrokeWidth: 32,
|
||||
legendOptions: LegendOptions(
|
||||
showLegendsInRow: false,
|
||||
legendPosition: LegendPosition.right,
|
||||
showLegends: true,
|
||||
legendShape: BoxShape.circle,
|
||||
legendTextStyle: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
chartValuesOptions: ChartValuesOptions(
|
||||
showChartValueBackground: false,
|
||||
showChartValues: true,
|
||||
showChartValuesOutside: false,
|
||||
decimalPlaces: 0,
|
||||
chartValueStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!,
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
AutoRouter.of(context).maybePop();
|
||||
},
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
color: locator.testManager.color,
|
||||
height: 60,
|
||||
child: FittedBox(
|
||||
child: Text(
|
||||
'В меню',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium,
|
||||
)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
return TestCompleteWidget(
|
||||
locator.testManager.states,
|
||||
locator.testManager.color,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -207,6 +131,12 @@ class _TestPageState extends State<TestPage> {
|
|||
}),
|
||||
);
|
||||
}
|
||||
|
||||
bool _canScroll() {
|
||||
return locator.testManager.pageController.hasClients &&
|
||||
locator.testManager.pageController.page !=
|
||||
locator.testManager.questions.length;
|
||||
}
|
||||
}
|
||||
|
||||
class _LifeWidget extends StatelessWidget {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
|
|
@ -24,21 +25,30 @@ class _TestProgressWidgetState extends State<TestProgressWidget> {
|
|||
|
||||
Stream<Duration>? timer;
|
||||
|
||||
Timer? _updateTimer;
|
||||
|
||||
void _updateDataListener() {
|
||||
if (_updateTimer == null || !_updateTimer!.isActive) {
|
||||
_updateTimer = Timer(Duration(milliseconds: 100), _updateData);
|
||||
}
|
||||
}
|
||||
|
||||
void _updateData() {
|
||||
results = <Result>[
|
||||
Result.correct,
|
||||
];
|
||||
length = locator.testManager.questions.length + 1;
|
||||
final pageController = locator.testManager.pageController;
|
||||
for (final state in locator.testManager.states) {
|
||||
if (state.isAnswered) {
|
||||
if (state.isCorrect) {
|
||||
results.add(Result.correct);
|
||||
} else {
|
||||
// results.add(Result.wrong);
|
||||
results.add(Result.not_visited);
|
||||
results.add(Result.wrong);
|
||||
}
|
||||
} else if (state.isSkipped) {
|
||||
results.add(Result.correct);
|
||||
results.add(Result.skiped);
|
||||
} else {
|
||||
results.add(Result.not_visited);
|
||||
}
|
||||
|
|
@ -51,8 +61,10 @@ class _TestProgressWidgetState extends State<TestProgressWidget> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
locator.testManager.removeListener(_updateData);
|
||||
locator.testManager.addListener(_updateData);
|
||||
// locator.testManager.removeListener(_updateData);
|
||||
// locator.testManager.addListener(_updateData);
|
||||
locator.testManager.pageController.removeListener(_updateDataListener);
|
||||
locator.testManager.pageController.addListener(_updateDataListener);
|
||||
timer = Stream.periodic(
|
||||
Duration(seconds: 1),
|
||||
(tick) => Duration(seconds: 300 - tick - 1),
|
||||
|
|
|
|||
|
|
@ -55,9 +55,15 @@ class TestHolder {
|
|||
for (final question in questions) {
|
||||
switch (question.questionType) {
|
||||
case TestQuestionType.simple:
|
||||
stateQuestions[question.id!] = SimpleTestState(seed: seed);
|
||||
stateQuestions[question.id!] = SimpleTestState(
|
||||
seed: seed,
|
||||
word: question.word,
|
||||
);
|
||||
case TestQuestionType.input_buttons:
|
||||
stateQuestions[question.id!] = InputButtonsTestState(seed: seed);
|
||||
stateQuestions[question.id!] = InputButtonsTestState(
|
||||
seed: seed,
|
||||
word: question.word,
|
||||
);
|
||||
case TestQuestionType.matrix:
|
||||
// TODO: Handle this case.
|
||||
case TestQuestionType.match:
|
||||
|
|
|
|||
|
|
@ -58,11 +58,12 @@ class SimpleTestWidget extends StatelessWidget {
|
|||
answer: answer,
|
||||
),
|
||||
);
|
||||
// only setting isCorrect once
|
||||
manager.setState(
|
||||
model.id!,
|
||||
state.copyWith(
|
||||
isAnswered: true,
|
||||
isCorrect: isCorrect,
|
||||
isCorrect: state.isAnswered ? state.isCorrect : isCorrect,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import '../admin/all_users.dart';
|
|||
import '../di/locator.dart';
|
||||
import '../features/analytics/analytics.dart';
|
||||
import '../theme/themes.dart';
|
||||
import '../widgets/big_back_button.dart';
|
||||
import '../widgets/shared_pref_button.dart';
|
||||
import '../widgets/text_button.dart';
|
||||
|
||||
|
|
@ -178,31 +179,7 @@ class ProfilePage extends StatelessWidget {
|
|||
// },
|
||||
// ),
|
||||
// ),
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsets.symmetric(horizontal: 10.0.w, vertical: 10.0.h),
|
||||
child: InkWell(
|
||||
onTap: AutoRouter.of(context).maybePop,
|
||||
child: Container(
|
||||
height: 90.h,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border.all(color: borderGray),
|
||||
borderRadius: BorderRadius.circular(16.0),
|
||||
),
|
||||
padding: EdgeInsets.all(10.0.w),
|
||||
child: Text(
|
||||
'Назад',
|
||||
style: TextStyle(
|
||||
fontSize: 25.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 0.85,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const BigBackButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
38
lib/widgets/big_back_button.dart
Normal file
38
lib/widgets/big_back_button.dart
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:mnemo_cards/main.dart';
|
||||
|
||||
import '../theme/themes.dart';
|
||||
|
||||
class BigBackButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 10.0.w, vertical: 10.0.h),
|
||||
child: InkWell(
|
||||
onTap: appRouter.maybePop,
|
||||
child: Container(
|
||||
height: 90.h,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border.all(color: borderGray),
|
||||
borderRadius: BorderRadius.circular(16.0),
|
||||
),
|
||||
padding: EdgeInsets.all(10.0.w),
|
||||
child: Text(
|
||||
'Назад',
|
||||
style: TextStyle(
|
||||
fontSize: 25.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 0.85,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const BigBackButton();
|
||||
}
|
||||
31
lib/widgets/horizontal_progress.dart
Normal file
31
lib/widgets/horizontal_progress.dart
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
import '../theme/themes.dart';
|
||||
|
||||
class HorizontalProgressWidget extends StatelessWidget {
|
||||
final int value;
|
||||
final int length;
|
||||
final Color color;
|
||||
|
||||
HorizontalProgressWidget(this.value, this.length, this.color);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16.h),
|
||||
child: Container(
|
||||
height: 16.h,
|
||||
color: white,
|
||||
child: AnimatedFractionallySizedBox(
|
||||
alignment: Alignment.centerLeft,
|
||||
widthFactor: length == 0 ? 0 : value.toDouble() / length,
|
||||
duration: Duration(milliseconds: 600),
|
||||
child: Container(
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
24
pubspec.lock
24
pubspec.lock
|
|
@ -321,6 +321,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
equatable:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: equatable
|
||||
sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -425,6 +433,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
fl_chart:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: fl_chart
|
||||
sha256: d0f0d49112f2f4b192481c16d05b6418bd7820e021e265a3c22db98acf7ed7fb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.68.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
|
|
@ -919,14 +935,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.2"
|
||||
pie_chart:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: pie_chart
|
||||
sha256: "58e6a46999ac938bfa1c3e5be414d6e149f037647197dca03ba3614324c12c82"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.4.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ dependencies:
|
|||
reorderables: ^0.6.0
|
||||
story: ^1.1.0
|
||||
flutter_screenutil: ^5.9.0
|
||||
pie_chart: ^5.4.0
|
||||
fl_chart: ^0.68.0
|
||||
yookassa_client: ^1.0.2
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
|
|
|
|||
Loading…
Reference in a new issue