end widget

This commit is contained in:
Dmitry 2024-06-11 03:28:57 +03:00
parent d2c04fc2c8
commit b3344ae794
21 changed files with 403 additions and 168 deletions

BIN
icons/crown.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

BIN
icons/skull.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

BIN
icons/small_circle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

View file

@ -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();
}

View file

@ -19,6 +19,7 @@ class InputButtonsTestState extends TestQuestionState {
InputButtonsTestState({
required this.seed,
required super.word,
this.answer = const [],
this.answerIds = const [],
this.isCorrect = false,

View file

@ -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,

View file

@ -18,6 +18,7 @@ class SimpleTestState extends TestQuestionState {
SimpleTestState({
required this.seed,
required super.word,
this.answer = '',
this.isCorrect = false,
this.isAnswered = false,

View file

@ -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,

View file

@ -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,
});

View file

@ -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

View file

@ -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,20 +36,26 @@ 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(() {
if (value == position.pixels) {
throw FlutterError(
'$runtimeType.applyBoundaryConditions() was called redundantly.\n'
'The proposed new position, $value, is exactly equal to the current position of the '
'given ${position.runtimeType}, ${position.pixels}.\n'
'The applyBoundaryConditions method should only be called when the value is '
'going to actually change the pixels, otherwise it is redundant.\n'
'The physics object in question was:\n'
' $this\n'
'The position object in question was:\n'
' $position\n');
'The proposed new position, $value, is exactly equal to the current position of the '
'given ${position.runtimeType}, ${position.pixels}.\n'
'The applyBoundaryConditions method should only be called when the value is '
'going to actually change the pixels, otherwise it is redundant.\n'
'The physics object in question was:\n'
' $this\n'
'The position object in question was:\n'
' $position\n');
}
return true;
}());
@ -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) {
@ -146,4 +164,4 @@ class RightBlockedScrollPhysics extends HorizontalBlockedScrollPhysics {
parent: buildParent(ancestor),
);
}
}
}

View 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,
);
}

View file

@ -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 {

View file

@ -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),

View file

@ -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:

View file

@ -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,
),
);
});

View file

@ -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(),
],
),
),

View 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();
}

View 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,
),
),
),
);
}
}

View file

@ -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:

View file

@ -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.