420 lines
16 KiB
Dart
420 lines
16 KiB
Dart
// ignore_for_file: deprecated_member_use_from_same_package
|
|
|
|
import 'package:fl_chart/fl_chart.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
|
import 'package:hoshan/core/utils/date_time.dart';
|
|
import 'package:hoshan/data/model/empty_states_enum.dart';
|
|
import 'package:hoshan/ui/screens/setting/bloc/report_of_use_bloc.dart';
|
|
import 'package:hoshan/ui/screens/setting/cubit/report_pi_coin_cubit.dart';
|
|
import 'package:hoshan/ui/theme/colors.dart';
|
|
import 'package:hoshan/ui/theme/cubit/theme_mode_cubit.dart';
|
|
import 'package:hoshan/ui/theme/responsive.dart';
|
|
import 'package:hoshan/ui/theme/text.dart';
|
|
import 'package:hoshan/ui/widgets/components/button/loading_button.dart';
|
|
import 'package:hoshan/ui/widgets/components/chart/custome_pi_chart.dart';
|
|
import 'package:hoshan/ui/widgets/components/dialog/dialog_handler.dart';
|
|
import 'package:hoshan/ui/widgets/components/text/credit_cost.dart';
|
|
import 'package:hoshan/ui/widgets/sections/empty/empty_states.dart';
|
|
import 'package:hoshan/ui/widgets/sections/header/reversible_appbar.dart';
|
|
import 'package:shamsi_date/shamsi_date.dart';
|
|
|
|
class UtilizationReportPage extends StatefulWidget {
|
|
const UtilizationReportPage({super.key});
|
|
|
|
@override
|
|
State<UtilizationReportPage> createState() => _UtilizationReportPageState();
|
|
}
|
|
|
|
class _UtilizationReportPageState extends State<UtilizationReportPage> {
|
|
ValueNotifier<Jalali> selectedDate =
|
|
ValueNotifier(DateTimeUtils.getNowJalali());
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: ReversibleAppbar(
|
|
context,
|
|
titleText: 'گزارش میزان مصرف',
|
|
),
|
|
body: Responsive(context).builder(
|
|
desktop: SingleChildScrollView(
|
|
physics: const BouncingScrollPhysics(),
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
children: [
|
|
const SizedBox(
|
|
height: 16,
|
|
),
|
|
calenderBtn(),
|
|
const SizedBox(
|
|
height: 16,
|
|
),
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Expanded(
|
|
child: chartBar(),
|
|
),
|
|
const SizedBox(
|
|
width: 16,
|
|
),
|
|
Expanded(
|
|
child: piChart(context),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
mobile: SingleChildScrollView(
|
|
physics: const BouncingScrollPhysics(),
|
|
child: Column(
|
|
children: [
|
|
const SizedBox(
|
|
height: 16,
|
|
),
|
|
calenderBtn(),
|
|
const SizedBox(
|
|
height: 16,
|
|
),
|
|
chartBar(),
|
|
piChart(context)
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
ValueListenableBuilder<Jalali> calenderBtn() {
|
|
return ValueListenableBuilder(
|
|
valueListenable: selectedDate,
|
|
builder: (context, date, _) {
|
|
return LoadingButton(
|
|
onPressed: () {
|
|
DialogHandler(context: context).showShamsiYearMonthPicker(
|
|
initailDate: date,
|
|
onDateSelected: (selectedDate) {
|
|
this.selectedDate.value = selectedDate;
|
|
|
|
context.read<ReportOfUseBloc>().add(GetReport(
|
|
startDate: selectedDate,
|
|
));
|
|
context.read<ReportPiCoinCubit>().getReport(
|
|
startDate: selectedDate,
|
|
);
|
|
},
|
|
);
|
|
},
|
|
child: Text(
|
|
'${date.formatter.mN} ${date.year}',
|
|
style: AppTextStyles.body4.copyWith(color: Colors.white),
|
|
));
|
|
});
|
|
}
|
|
|
|
Container piChart(BuildContext context) {
|
|
return Container(
|
|
margin: const EdgeInsets.all(16),
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).colorScheme.surface,
|
|
borderRadius: BorderRadius.circular(10)),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
'اعتبار باقی مانده',
|
|
style: AppTextStyles.headline6
|
|
.copyWith(color: Theme.of(context).colorScheme.onSurface),
|
|
),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Text(
|
|
'سکه',
|
|
style: AppTextStyles.body4
|
|
.copyWith(color: Theme.of(context).colorScheme.onSurface),
|
|
),
|
|
const SizedBox(
|
|
width: 8,
|
|
),
|
|
const CreditCost(),
|
|
],
|
|
),
|
|
BlocBuilder<ReportPiCoinCubit, ReportPiCoinState>(
|
|
builder: (context, state) {
|
|
if (state is ReportPiCoinEmpty || state is ReportPiCoinFail) {
|
|
return Container(
|
|
width: Responsive(context).isDesktop()
|
|
? 400
|
|
: MediaQuery.sizeOf(context).width * 0.7,
|
|
height: Responsive(context).isDesktop()
|
|
? 400
|
|
: MediaQuery.sizeOf(context).width * 0.7,
|
|
margin: const EdgeInsets.symmetric(horizontal: 32),
|
|
padding: const EdgeInsets.all(38),
|
|
decoration: BoxDecoration(
|
|
border: Border.all(
|
|
color: AppColors.secondryColor.defaultShade,
|
|
width: 38),
|
|
shape: BoxShape.circle),
|
|
child: Center(
|
|
child: Text(
|
|
'داده ای موجود نیست',
|
|
style: AppTextStyles.headline5.copyWith(
|
|
color: Theme.of(context).colorScheme.onSurface),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
if (state is ReportPiCoinSuccess) {
|
|
return CustomePiChart(
|
|
points: state.points,
|
|
);
|
|
}
|
|
return Padding(
|
|
padding: const EdgeInsets.all(32.0),
|
|
child: SpinKitDualRing(
|
|
color: AppColors.secondryColor.defaultShade,
|
|
size: Responsive(context).isDesktop()
|
|
? 200
|
|
: MediaQuery.sizeOf(context).width / 3,
|
|
lineWidth: Responsive(context).isDesktop()
|
|
? 50
|
|
: MediaQuery.sizeOf(context).width / 8,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
BlocBuilder<ReportOfUseBloc, ReportOfUseState> chartBar() {
|
|
return BlocBuilder<ReportOfUseBloc, ReportOfUseState>(
|
|
builder: (context, state) {
|
|
if (state is ReportOfUseSuccess) {
|
|
try {
|
|
int maxVal = state.reportModel.report!.fold<int>(
|
|
100,
|
|
(previousValue, report) {
|
|
try {
|
|
if (report.coinUsage! > previousValue) {
|
|
return report.coinUsage!;
|
|
}
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Error is: $e');
|
|
}
|
|
}
|
|
|
|
return previousValue;
|
|
},
|
|
);
|
|
maxVal = maxVal + (maxVal % 100 == 0 ? 0 : (100 - (maxVal % 100)));
|
|
final List<int> leftTitle = [0];
|
|
do {
|
|
leftTitle.add((leftTitle.last + (maxVal / 5).round()));
|
|
} while (leftTitle.length < 6);
|
|
final points = state.reportModel.report!.map(
|
|
(e) {
|
|
final date = DateTimeUtils.getDateFromString(false, e.date!);
|
|
return FlSpot(
|
|
date.day.toDouble(), ((e.coinUsage! * 5) / maxVal));
|
|
},
|
|
).toList();
|
|
|
|
return AspectRatio(
|
|
aspectRatio: 4 / 3,
|
|
child: Container(
|
|
margin: const EdgeInsets.all(16),
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).colorScheme.surface,
|
|
borderRadius: BorderRadius.circular(10)),
|
|
child: LineChart(LineChartData(
|
|
borderData: FlBorderData(show: false),
|
|
gridData: FlGridData(
|
|
show: true,
|
|
horizontalInterval: 1,
|
|
verticalInterval: 5,
|
|
getDrawingHorizontalLine: (value) => FlLine(
|
|
color: AppColors.gray[
|
|
context.read<ThemeModeCubit>().isDark()
|
|
? 600
|
|
: 900]
|
|
.withAlpha(50),
|
|
strokeWidth: 0.5),
|
|
getDrawingVerticalLine: (value) => FlLine(
|
|
color: AppColors.gray[
|
|
context.read<ThemeModeCubit>().isDark()
|
|
? 600
|
|
: 900]
|
|
.withAlpha(50),
|
|
strokeWidth: 0.5),
|
|
),
|
|
titlesData: FlTitlesData(
|
|
show: true,
|
|
topTitles: const AxisTitles(
|
|
sideTitles: SideTitles(showTitles: false)),
|
|
rightTitles: const AxisTitles(
|
|
sideTitles: SideTitles(showTitles: false)),
|
|
bottomTitles: AxisTitles(
|
|
sideTitles: SideTitles(
|
|
showTitles: true,
|
|
reservedSize: 30,
|
|
interval: 5,
|
|
getTitlesWidget: (value, meta) {
|
|
return SideTitleWidget(
|
|
fitInside:
|
|
SideTitleFitInsideData.fromTitleMeta(meta),
|
|
axisSide: AxisSide.bottom,
|
|
child: Center(
|
|
child: Padding(
|
|
padding: EdgeInsets.only(
|
|
left: value == 30 ? 40 : 0,
|
|
right: value == 1 ? 12 : 0),
|
|
child: Text(
|
|
value == 31 ? '' : '${value.round()}',
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: AppTextStyles.body6.copyWith(
|
|
color: Theme.of(context)
|
|
.colorScheme
|
|
.onSurface),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
)),
|
|
leftTitles: AxisTitles(
|
|
sideTitles: SideTitles(
|
|
showTitles: true,
|
|
reservedSize: 36,
|
|
interval: 1,
|
|
getTitlesWidget: (value, meta) {
|
|
return Padding(
|
|
padding: EdgeInsets.only(
|
|
top: value == 0 ? 12 : 0.0, right: 8),
|
|
child: Text(
|
|
value == 0
|
|
? 'سکه\nروز'
|
|
: '${leftTitle[value.round()]}',
|
|
textAlign: TextAlign.start,
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: AppTextStyles.body6.copyWith(
|
|
color:
|
|
Theme.of(context).colorScheme.onSurface),
|
|
),
|
|
);
|
|
},
|
|
))),
|
|
minX: 1,
|
|
maxX: 31,
|
|
minY: 0,
|
|
maxY: 5,
|
|
lineTouchData: LineTouchData(
|
|
touchTooltipData: LineTouchTooltipData(
|
|
getTooltipColor: (touchedSpot) =>
|
|
Theme.of(context).colorScheme.onSurface,
|
|
getTooltipItems: (List<LineBarSpot> touchedBarSpots) {
|
|
return touchedBarSpots.map((barSpot) {
|
|
final flSpot = barSpot;
|
|
if (flSpot.x == 0 || flSpot.x == 6) {
|
|
return null;
|
|
}
|
|
|
|
final date = DateTimeUtils.getDateFromString(
|
|
false,
|
|
state.reportModel.report![flSpot.spotIndex]
|
|
.date!)
|
|
.formatter;
|
|
|
|
return LineTooltipItem(
|
|
'${(date.wN).replaceAll(' ', '\u200C')} ${date.d} ${date.mN} \n${state.reportModel.report![flSpot.spotIndex].coinUsage ?? 0} سکه',
|
|
AppTextStyles.body5.copyWith(
|
|
color: Theme.of(context).colorScheme.surface,
|
|
),
|
|
textDirection: TextDirection.rtl);
|
|
}).toList();
|
|
},
|
|
),
|
|
),
|
|
lineBarsData: [
|
|
LineChartBarData(
|
|
spots: points,
|
|
isCurved: false,
|
|
curveSmoothness: 0.2,
|
|
gradient: LinearGradient(colors: [
|
|
Theme.of(context).colorScheme.primary,
|
|
Theme.of(context).colorScheme.secondary,
|
|
]),
|
|
barWidth: 1.5,
|
|
isStrokeCapRound: true,
|
|
dotData: const FlDotData(show: false),
|
|
belowBarData: BarAreaData(
|
|
show: true,
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
Theme.of(context).colorScheme.primary,
|
|
Theme.of(context).colorScheme.secondary,
|
|
]
|
|
.map(
|
|
(color) => color.withValues(alpha: 0.3))
|
|
.toList())))
|
|
],
|
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
|
)),
|
|
),
|
|
);
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Error is: $e');
|
|
}
|
|
return const SizedBox.shrink();
|
|
}
|
|
}
|
|
return state is ReportOfUseFail || state is ReportOfUseEmpty
|
|
? Container(
|
|
margin: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).colorScheme.surface,
|
|
borderRadius: BorderRadius.circular(10)),
|
|
child: Center(
|
|
child: EmptyStates.getEmptyState(
|
|
scale: 0.8,
|
|
status: EmptyStatesEnum.amount,
|
|
title: 'دادهای برای نمایش وجود ندارد'),
|
|
),
|
|
)
|
|
: AspectRatio(
|
|
aspectRatio: 4 / 3,
|
|
child: Container(
|
|
margin: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).colorScheme.surface,
|
|
borderRadius: BorderRadius.circular(10)),
|
|
child: Center(
|
|
child: SpinKitThreeBounce(
|
|
color: Theme.of(context).colorScheme.primary,
|
|
size: 46,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|