statistics_result_view.dart 7.95 KB
Newer Older
CLOUARD Regis's avatar
CLOUARD Regis committed
1
import 'package:flutter/material.dart';
CLOUARD Regis's avatar
CLOUARD Regis committed
2
import 'package:sprintf/sprintf.dart';
CLOUARD Regis's avatar
CLOUARD Regis committed
3
4
5
6
7
8
9
import 'package:vikazimut/map/geodesic_point.dart';
import 'package:vikazimut/theme.dart';
import 'package:vikazimut/utils/time.dart';
import 'package:vikazimut/utils/translations.dart';

import '../result.dart';

CLOUARD Regis's avatar
CLOUARD Regis committed
10
/// Displays the statistics from the result of the current course
CLOUARD Regis's avatar
CLOUARD Regis committed
11
class StatisticsResultView extends StatelessWidget {
CLOUARD Regis's avatar
CLOUARD Regis committed
12
13
  final Result result;
  late final String _title;
CLOUARD Regis's avatar
CLOUARD Regis committed
14
15
  final GlobalKey _tooltipColumn3Key = new GlobalKey();
  final GlobalKey _tooltipColumn4Key = new GlobalKey();
CLOUARD Regis's avatar
CLOUARD Regis committed
16
17
18
19
20
21
22

  StatisticsResultView(this.result) {
    _title = result.getMap()!.getName();
  }

  @override
  Widget build(BuildContext context) {
CLOUARD Regis's avatar
CLOUARD Regis committed
23
    return Scaffold(
CLOUARD Regis's avatar
CLOUARD Regis committed
24
      appBar: AppBar(
CLOUARD Regis's avatar
CLOUARD Regis committed
25
        title: Text(_title),
CLOUARD Regis's avatar
CLOUARD Regis committed
26
27
28
29
      ),
      body: SingleChildScrollView(
        child: Container(
          width: double.infinity,
Clouard Regis's avatar
Clouard Regis committed
30
          margin: const EdgeInsets.all(10),
CLOUARD Regis's avatar
CLOUARD Regis committed
31
          child: Theme(
CLOUARD Regis's avatar
CLOUARD Regis committed
32
            data: Theme.of(context).copyWith(dividerColor: primaryColor),
CLOUARD Regis's avatar
CLOUARD Regis committed
33
            child: DataTable(
CLOUARD Regis's avatar
CLOUARD Regis committed
34
              horizontalMargin: 10,
CLOUARD Regis's avatar
CLOUARD Regis committed
35
              columnSpacing: 0,
CLOUARD Regis's avatar
CLOUARD Regis committed
36
              headingRowColor: MaterialStateProperty.all(primaryColor),
CLOUARD Regis's avatar
CLOUARD Regis committed
37
38
39
40
41
42
43
44
45
46
47
48
49
              headingTextStyle: TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold, color: Colors.white),
              dataTextStyle: TextStyle(fontSize: 12.0, color: Colors.black),
              columns: _buildColumns(),
              rows: _buildContents(),
            ),
          ),
        ),
      ),
    );
  }

  List<DataColumn> _buildColumns() {
    return [
CLOUARD Regis's avatar
CLOUARD Regis committed
50
51
      DataColumn(label: Expanded(child: TextCentered(Translations.getString('control_point_index')))),
      DataColumn(label: Expanded(child: TextCentered(Translations.getString('interval_time')))),
52
      DataColumn(
CLOUARD Regis's avatar
CLOUARD Regis committed
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
        label: Expanded(
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              TextCentered(Translations.getString('interval_distance')),
              Tooltip(
                key: _tooltipColumn3Key,
                message: Translations.getString("result_table_distance_column_tooltip"),
                child: IconButton(
                  constraints: BoxConstraints(maxHeight: 18, maxWidth: 18),
                  padding: const EdgeInsets.all(0.0),
                  icon: Icon(
                    Icons.help_outline,
                    color: Colors.white,
                    size: 16.0,
                  ),
                  onPressed: () {
                    final dynamic tooltip = _tooltipColumn3Key.currentState;
                    tooltip.ensureTooltipVisible();
                  },
73
74
                ),
              ),
CLOUARD Regis's avatar
CLOUARD Regis committed
75
76
            ],
          ),
77
78
79
        ),
      ),
      DataColumn(
CLOUARD Regis's avatar
CLOUARD Regis committed
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
        label: Expanded(
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              TextCentered(Translations.getString('interval_pace')),
              Tooltip(
                key: _tooltipColumn4Key,
                message: Translations.getString("result_table_pace_column_tooltip"),
                child: IconButton(
                  constraints: BoxConstraints(maxHeight: 18, maxWidth: 18),
                  padding: const EdgeInsets.all(0.0),
                  icon: Icon(Icons.help_outline, color: Colors.white, size: 16.0),
                  onPressed: () {
                    final dynamic tooltip = _tooltipColumn4Key.currentState;
                    tooltip.ensureTooltipVisible();
                  },
                ),
97
              ),
CLOUARD Regis's avatar
CLOUARD Regis committed
98
99
            ],
          ),
100
101
        ),
      ),
CLOUARD Regis's avatar
CLOUARD Regis committed
102
      DataColumn(label: Expanded(child: TextCentered(Translations.getString('cumulative_time')))),
CLOUARD Regis's avatar
CLOUARD Regis committed
103
104
105
106
107
108
    ];
  }

  List<DataRow> _buildContents() {
    List<DataRow> rows = [];
    List<Leg> timeTable = getDetailedResult();
CLOUARD Regis's avatar
CLOUARD Regis committed
109
    var totalTheoreticalDistance = 0;
CLOUARD Regis's avatar
CLOUARD Regis committed
110
    for (int i = 1; i < timeTable.length; i++) {
CLOUARD Regis's avatar
CLOUARD Regis committed
111
112
      if (timeTable[i].punchTime > 0) {
        totalTheoreticalDistance += timeTable[i].theoreticalDistance;
CLOUARD Regis's avatar
CLOUARD Regis committed
113
      }
CLOUARD Regis's avatar
CLOUARD Regis committed
114
      List<DataCell> cells = [
CLOUARD Regis's avatar
CLOUARD Regis committed
115
        DataCell(Center(child: _buildControlNumberCell(timeTable, i))),
CLOUARD Regis's avatar
CLOUARD Regis committed
116
117
118
119
        DataCell(Center(child: _buildLegTimeCell(timeTable, i))),
        DataCell(Center(child: _buildLegDistanceCell(timeTable, i))),
        DataCell(Center(child: _buildPaceCell(timeTable, i))),
        DataCell(Center(child: _buildCumulatedCell(timeTable, i))),
CLOUARD Regis's avatar
CLOUARD Regis committed
120
121
      ];
      DataRow tableRow = DataRow(cells: cells);
CLOUARD Regis's avatar
CLOUARD Regis committed
122
123
      rows.add(tableRow);
    }
Clouard Regis's avatar
Clouard Regis committed
124
125
    var totalLength = result.getActualRouteLengthInMeters().toInt();
    var totalDistance = totalTheoreticalDistance;
CLOUARD Regis's avatar
CLOUARD Regis committed
126
    DataRow totalTableRow = DataRow(
CLOUARD Regis's avatar
CLOUARD Regis committed
127
      color: MaterialStateProperty.all(primaryColorDisabled),
CLOUARD Regis's avatar
CLOUARD Regis committed
128
      cells: [
CLOUARD Regis's avatar
CLOUARD Regis committed
129
130
131
132
133
        DataCell(Center(child: TextCenteredBold(Translations.getString('Total')))),
        DataCell(Center(child: TextCenteredBold('${result.getTotalTimeWithoutPenaltyAsString()}'))),
        DataCell(Center(child: TextCenteredBold('${Translations.formatNumber(totalLength)}\n(${Translations.formatNumber(totalDistance)})\n+${Result.computeOvercost(totalLength, totalDistance)}%'))),
        DataCell(Center(child: TextCenteredBold('${formatPace(result.getGlobalPaceInMillis())}\n${formatSpeed(Result.calculateSpeedFromPace(result.getGlobalPaceInMillis()))}'))),
        DataCell(Center(child: TextCenteredBold('${result.getTotalTimeWithoutPenaltyAsString()}'))),
CLOUARD Regis's avatar
CLOUARD Regis committed
134
135
      ],
    );
CLOUARD Regis's avatar
CLOUARD Regis committed
136
    rows.add(totalTableRow);
CLOUARD Regis's avatar
CLOUARD Regis committed
137
138
139
    return rows;
  }

CLOUARD Regis's avatar
CLOUARD Regis committed
140
141
  TextCentered _buildCumulatedCell(List<Leg> timeTable, int i) => TextCentered(formatLegCumulatedTime(timeTable[i].punchTime));

CLOUARD Regis's avatar
CLOUARD Regis committed
142
143
144
  TextCentered _buildPaceCell(List<Leg> timeTable, int i) {
    return TextCentered('${formatPace(timeTable[i].paceInMinK)}\n${formatSpeed(Result.calculateSpeedFromPace(timeTable[i].paceInMinK))}');
  }
CLOUARD Regis's avatar
CLOUARD Regis committed
145
146
147

  Widget _buildLegDistanceCell(List<Leg> timeTable, int i) {
    String actualDistance = formatActualDistance(timeTable[i].actualDistance);
Clouard Regis's avatar
Clouard Regis committed
148
    String theoreticalDistance = Translations.formatNumber(timeTable[i].theoreticalDistance);
CLOUARD Regis's avatar
CLOUARD Regis committed
149
    String distanceText;
CLOUARD Regis's avatar
CLOUARD Regis committed
150
    if (timeTable[i].punchTime <= 0) {
CLOUARD Regis's avatar
CLOUARD Regis committed
151
      distanceText = '\n';
CLOUARD Regis's avatar
CLOUARD Regis committed
152
    } else {
CLOUARD Regis's avatar
CLOUARD Regis committed
153
154
      var overcost = Result.computeOvercost(timeTable[i].actualDistance, timeTable[i].theoreticalDistance);
      distanceText = '$actualDistance\n($theoreticalDistance)\n+$overcost%';
CLOUARD Regis's avatar
CLOUARD Regis committed
155
    }
CLOUARD Regis's avatar
CLOUARD Regis committed
156
    return TextCentered(distanceText);
CLOUARD Regis's avatar
CLOUARD Regis committed
157
158
159
  }

  Widget _buildLegTimeCell(List<Leg> timeTable, int i) {
CLOUARD Regis's avatar
CLOUARD Regis committed
160
    return TextCentered(formatLegDuration(timeTable[i].duration));
CLOUARD Regis's avatar
CLOUARD Regis committed
161
162
163
164
165
166
167
168
169
170
  }

  Widget _buildControlNumberCell(List<Leg> timeTable, int i) {
    if (timeTable[i].index < timeTable.length - 1) {
      return TextCenteredBold((timeTable[i].index).toString());
    } else {
      return TextCenteredBold(Translations.getString('cp_finish'));
    }
  }

CLOUARD Regis's avatar
CLOUARD Regis committed
171
  List<Leg> getDetailedResult() {
CLOUARD Regis's avatar
CLOUARD Regis committed
172
    return result.getDetailedStatistics();
CLOUARD Regis's avatar
CLOUARD Regis committed
173
174
175
176
  }

  static String formatLegCumulatedTime(int time) {
    if (time <= 0) {
CLOUARD Regis's avatar
CLOUARD Regis committed
177
      return '';
CLOUARD Regis's avatar
CLOUARD Regis committed
178
179
180
181
182
183
184
    } else {
      return formatTimeAsString(time);
    }
  }

  static String formatActualDistance(int distance) {
    if (distance <= 0) {
CLOUARD Regis's avatar
CLOUARD Regis committed
185
      return '';
CLOUARD Regis's avatar
CLOUARD Regis committed
186
    } else {
Clouard Regis's avatar
Clouard Regis committed
187
      return Translations.formatNumber(distance);
CLOUARD Regis's avatar
CLOUARD Regis committed
188
189
190
191
192
    }
  }

  static String formatPace(int pace) {
    if (pace <= 0) {
CLOUARD Regis's avatar
CLOUARD Regis committed
193
      return '';
CLOUARD Regis's avatar
CLOUARD Regis committed
194
195
196
197
198
    } else {
      return formatTimeAsString(pace, withHour: false);
    }
  }

CLOUARD Regis's avatar
CLOUARD Regis committed
199
200
201
202
203
204
205
206
  static String formatSpeed(double speed) {
    if (speed <= 0) {
      return '';
    } else {
      return sprintf("(%.1f)", [speed]);
    }
  }

CLOUARD Regis's avatar
CLOUARD Regis committed
207
208
  static String formatLegDuration(int duration) {
    if (duration <= 0) {
CLOUARD Regis's avatar
CLOUARD Regis committed
209
      return '';
CLOUARD Regis's avatar
CLOUARD Regis committed
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
    } else {
      return formatTimeAsString(duration);
    }
  }
}

class TextCentered extends Text {
  const TextCentered(text) : super(text, textAlign: TextAlign.center);
}

class TextCenteredBold extends Text {
  TextCenteredBold(text) : super(text, textAlign: TextAlign.center, style: TextStyle(fontWeight: FontWeight.bold));
}

class Leg {
  late int index;
  late int punchTime;
  late GeodesicPoint controlPoint;
  List<GeodesicPoint>? waypoints;
  late int duration;
  late int actualDistance;
  late int theoreticalDistance;
  late int paceInMinK;
}