invoice/lib/ui/dashboard/dashboard_chart.dart

236 lines
8.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/dashboard/dashboard_selectors.dart';
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:invoiceninja_flutter/utils/formatting.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
class DashboardChart extends StatefulWidget {
const DashboardChart({this.data, this.title, this.currencyId});
final List<ChartDataGroup> data;
final String title;
final String currencyId;
static const PERIOD_CURRENT = 'current';
static const PERIOD_PREVIOUS = 'previous';
@override
_DashboardChartState createState() => _DashboardChartState();
}
class _DashboardChartState extends State<DashboardChart> {
String _selected;
int _selectedIndex = 0;
void _onSelectionChanged(charts.SelectionModel model) {
final selectedDatum = model.selectedDatum;
DateTime date;
double total = 0.0;
final measures = <String, num>{};
if (selectedDatum.isNotEmpty) {
date = selectedDatum.first.datum.date;
selectedDatum
.where((charts.SeriesDatum datumPair) =>
datumPair.series.id == DashboardChart.PERIOD_CURRENT)
.forEach((charts.SeriesDatum datumPair) {
total += datumPair.datum.amount;
measures[datumPair.series.displayName] = datumPair.datum.amount;
});
}
setState(() {
if (date != null) {
_selected = formatDate(date.toIso8601String(), context) +
'' +
formatNumber(total, context, currencyId: widget.currencyId);
} else {
_selected = null;
}
});
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final localization = AppLocalization.of(context);
final state = StoreProvider.of<AppState>(context).state;
final color = state.uiState.enableDarkMode
? charts.MaterialPalette.white
: charts.MaterialPalette.gray.shade700;
final series = widget.data[_selectedIndex];
final chart = charts.TimeSeriesChart(
series.chartSeries,
animate: true,
selectionModels: [
charts.SelectionModelConfig(
type: charts.SelectionModelType.info,
changedListener: _onSelectionChanged,
)
],
behaviors: [
charts.SeriesLegend(
outsideJustification: charts.OutsideJustification.endDrawArea,
)
],
domainAxis: charts.DateTimeAxisSpec(
renderSpec: charts.SmallTickRendererSpec(
labelStyle: charts.TextStyleSpec(color: color),
lineStyle: charts.LineStyleSpec(color: color))),
primaryMeasureAxis: charts.NumericAxisSpec(
renderSpec: charts.GridlineRendererSpec(
labelStyle: charts.TextStyleSpec(color: color),
lineStyle: charts.LineStyleSpec(color: color))),
);
return FormCard(
children: <Widget>[
Padding(
padding: EdgeInsets.all(14.0),
child: Text(
widget.title,
style: theme.textTheme.headline,
),
),
Divider(height: 1.0),
Container(
width: double.infinity,
height: state.dashboardUIState.enableComparison ? 106 : 86,
child: ListView(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
children: widget.data.map((dataGroup) {
final int index = widget.data.indexOf(dataGroup);
final bool isSelected = index == _selectedIndex;
final bool isIncrease = dataGroup.total > dataGroup.previousTotal;
final String changeAmount = (isIncrease ? '+' : '') +
formatNumber(
dataGroup.total - dataGroup.previousTotal, context,
currencyId: widget.currencyId);
final changePercent = (isIncrease ? '+' : '') +
formatNumber(
dataGroup.total != 0 && dataGroup.previousTotal != 0
? round(
(dataGroup.total - dataGroup.previousTotal) /
dataGroup.previousTotal *
100,
2)
: 0.0,
context,
formatNumberType: FormatNumberType.percent,
currencyId: widget.currencyId);
final String changeString = dataGroup.total == 0 ||
dataGroup.previousTotal == 0 ||
dataGroup.total == dataGroup.previousTotal
? (state.dashboardUIState.enableComparison ? ' ' : '')
: '$changeAmount ($changePercent)';
return GestureDetector(
onTap: () {
setState(() {
_selectedIndex = index;
_selected = null;
});
},
child: Container(
color: isSelected ? Colors.blue : theme.cardColor,
padding:
EdgeInsets.only(left: 16, top: 16, right: 32, bottom: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(localization.lookup(dataGroup.name),
style: theme.textTheme.subhead.copyWith(
color: isSelected ? Colors.white : null,
fontWeight: FontWeight.w400)),
SizedBox(height: 4.0),
Text(
formatNumber(dataGroup.total, context,
currencyId: widget.currencyId),
style: theme.textTheme.headline.copyWith(
color: isSelected ? Colors.white : null)),
SizedBox(height: 2.0),
changeString.isNotEmpty
? Text(
changeString,
style: TextStyle(
fontSize: 16.0,
color: isSelected
? Colors.white
: (isIncrease ? Colors.green : Colors.red),
fontWeight: FontWeight.bold,
),
)
: SizedBox(),
],
),
),
);
}).toList(),
),
),
Divider(height: 1.0),
SizedBox(
height: 240.0,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: chart,
),
),
Divider(height: 1.0),
Container(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Text(
localization.average +
': ' +
formatNumber(series.average, context,
currencyId: widget.currencyId),
style: theme.textTheme.subhead,
),
),
_selected != null
? Text(
_selected,
style: theme.textTheme.subhead,
)
: SizedBox(),
],
),
),
],
);
}
}
class FormCard extends StatelessWidget {
const FormCard({
Key key,
@required this.children,
}) : super(key: key);
final List<Widget> children;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(12.0),
child: Card(
elevation: 4.0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
),
),
);
}
}