History graph now is zoomable and draggable.

Also fixed missing finesse data in old endcontexts
This commit is contained in:
dan63047 2024-01-17 01:55:21 +03:00
parent a6b3a0282a
commit 14def01b57
2 changed files with 85 additions and 51 deletions

View File

@ -548,13 +548,13 @@ class EndContextSingle {
late Duration finalTime; late Duration finalTime;
late int tSpins; late int tSpins;
late Clears clears; late Clears clears;
late Finesse finesse; late Finesse? finesse;
double get pps => piecesPlaced / (finalTime.inMicroseconds / 1000000); double get pps => piecesPlaced / (finalTime.inMicroseconds / 1000000);
double get kpp => inputs / piecesPlaced; double get kpp => inputs / piecesPlaced;
double get spp => score / piecesPlaced; double get spp => score / piecesPlaced;
double get kps => inputs / (finalTime.inMicroseconds / 1000000); double get kps => inputs / (finalTime.inMicroseconds / 1000000);
double get finessePercentage => finesse.perfectPieces / piecesPlaced; double get finessePercentage => finesse != null ? finesse!.perfectPieces / piecesPlaced : 0;
EndContextSingle( EndContextSingle(
{required this.gameType, {required this.gameType,
@ -585,7 +585,7 @@ class EndContextSingle {
tSpins = json['tspins']; tSpins = json['tspins'];
piecesPlaced = json['piecesplaced']; piecesPlaced = json['piecesplaced'];
clears = Clears.fromJson(json['clears']); clears = Clears.fromJson(json['clears']);
finesse = Finesse.fromJson(json['finesse']); finesse = json.containsKey("finesse") ? Finesse.fromJson(json['finesse']) : null;
gameType = json['gametype']; gameType = json['gametype'];
} }
@ -602,7 +602,7 @@ class EndContextSingle {
data['tspins'] = tSpins; data['tspins'] = tSpins;
data['piecesplaced'] = piecesPlaced; data['piecesplaced'] = piecesPlaced;
data['clears'] = clears.toJson(); data['clears'] = clears.toJson();
data['finesse'] = finesse.toJson(); if (finesse != null) data['finesse'] = finesse!.toJson();
data['finalTime'] = finalTime; data['finalTime'] = finalTime;
data['gametype'] = gameType; data['gametype'] = gameType;
return data; return data;

View File

@ -312,13 +312,13 @@ class _MainState extends State<MainView> with TickerProviderStateMixin {
onRefresh: () { onRefresh: () {
return Future(() => changePlayer(snapshot.data![0].userId)); return Future(() => changePlayer(snapshot.data![0].userId));
}, },
notificationPredicate: (notification) { // notificationPredicate: (notification) {
// with NestedScrollView local(depth == 2) OverscrollNotification are not sent // // with NestedScrollView local(depth == 2) OverscrollNotification are not sent
if (!kIsWeb && (notification is OverscrollNotification || Platform.isIOS)) { // if (!kIsWeb && (notification is OverscrollNotification || Platform.isIOS)) {
return notification.depth == 2; // return notification.depth == 2;
} // }
return notification.depth == 0; // return notification.depth == 0;
}, // },
child: NestedScrollView( child: NestedScrollView(
controller: _scrollController, controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
@ -617,7 +617,9 @@ class _HistoryChartThigyState extends State<_HistoryChartThigy> {
late double minX; late double minX;
late double maxX; late double maxX;
late double minY; late double minY;
late double actualMinY;
late double maxY; late double maxY;
late double actualMaxY;
@override @override
void initState(){ void initState(){
@ -640,11 +642,25 @@ class _HistoryChartThigyState extends State<_HistoryChartThigy> {
return element; return element;
} }
}).y; }).y;
actualMaxY = maxY;
actualMinY = minY;
}
@override
void dispose(){
super.dispose();
actualMinY = 0;
minY = 0;
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
GlobalKey graphKey = GlobalKey();
double xScale = maxX - minX; double xScale = maxX - minX;
double yScale = maxY - minY;
double scaleFactor = 5e2;
double dragFactor = 7e2;
double xInterval = widget.bigScreen ? max(1, xScale / 6) : max(1, xScale / 3); double xInterval = widget.bigScreen ? max(1, xScale / 6) : max(1, xScale / 3);
EdgeInsets padding = widget.bigScreen ? const EdgeInsets.fromLTRB(40, 30, 40, 30) : const EdgeInsets.fromLTRB(0, 40, 16, 48); EdgeInsets padding = widget.bigScreen ? const EdgeInsets.fromLTRB(40, 30, 40, 30) : const EdgeInsets.fromLTRB(0, 40, 16, 48);
double graphStartX = padding.left+widget.leftSpace; double graphStartX = padding.left+widget.leftSpace;
@ -653,35 +669,48 @@ class _HistoryChartThigyState extends State<_HistoryChartThigy> {
width: MediaQuery.of(context).size.width, width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height - 104, height: MediaQuery.of(context).size.height - 104,
child: Listener( child: Listener(
behavior: HitTestBehavior.translucent,
onPointerSignal: (signal) { onPointerSignal: (signal) {
if (signal is PointerScrollEvent) { if (signal is PointerScrollEvent) {
RenderBox graphBox = graphKey.currentContext?.findRenderObject() as RenderBox;
Offset graphPosition = graphBox.localToGlobal(Offset.zero);
double scrollPosRelativeX = (signal.position.dx - graphStartX) / (graphEndX - graphStartX); double scrollPosRelativeX = (signal.position.dx - graphStartX) / (graphEndX - graphStartX);
double newMinX, newMaxX; double scrollPosRelativeY = (signal.position.dy - graphPosition.dy) / (graphBox.size.height - 30); // size - bottom titles height
newMinX = minX - (xScale / 5e2) * signal.scrollDelta.dy * scrollPosRelativeX; double newMinX, newMaxX, newMinY, newMaxY;
newMaxX = maxX + (xScale / 5e2) * signal.scrollDelta.dy * (1-scrollPosRelativeX); newMinX = minX - (xScale / scaleFactor) * signal.scrollDelta.dy * scrollPosRelativeX;
newMaxX = maxX + (xScale / scaleFactor) * signal.scrollDelta.dy * (1-scrollPosRelativeX);
newMinY = minY - (yScale / scaleFactor) * signal.scrollDelta.dy * (1-scrollPosRelativeY);
newMaxY = maxY + (yScale / scaleFactor) * signal.scrollDelta.dy * scrollPosRelativeY;
if ((newMaxX - newMinX).isNegative) return; if ((newMaxX - newMinX).isNegative) return;
if ((newMaxY - newMinY).isNegative) return;
setState(() { setState(() {
minX = max(newMinX, widget.data.first.x); minX = max(newMinX, widget.data.first.x);
maxX = min(newMaxX, widget.data.last.x); maxX = min(newMaxX, widget.data.last.x);
_scrollController.jumpTo(_scrollController.position.maxScrollExtent); minY = max(newMinY, actualMinY);
maxY = min(newMaxY, actualMaxY);
_scrollController.jumpTo(_scrollController.position.maxScrollExtent - signal.scrollDelta.dy);
}); });
} }
}, },
child: child:
GestureDetector( GestureDetector(
behavior: HitTestBehavior.opaque,
onDoubleTap: () { onDoubleTap: () {
setState(() { setState(() {
minX = widget.data.first.x; minX = widget.data.first.x;
maxX = widget.data.last.x; maxX = widget.data.last.x;
minY = actualMinY;
maxY = actualMaxY;
}); });
}, },
onHorizontalDragUpdate: (dragUpdDet) { onPanUpdate: (dragUpdDet) {
var horizontalDistance = dragUpdDet.primaryDelta ?? 0; print(dragUpdDet);
if (horizontalDistance == 0) return;
setState(() { setState(() {
minX -= (xScale / 7e2) * horizontalDistance; minX -= (xScale / dragFactor) * dragUpdDet.delta.dx;
maxX -= (xScale / 7e2) * horizontalDistance; maxX -= (xScale / dragFactor) * dragUpdDet.delta.dx;
minY += (yScale / dragFactor) * dragUpdDet.delta.dy;
maxY += (yScale / dragFactor) * dragUpdDet.delta.dy;
if (minX < widget.data.first.x) { if (minX < widget.data.first.x) {
minX = widget.data.first.x; minX = widget.data.first.x;
@ -693,8 +722,10 @@ class _HistoryChartThigyState extends State<_HistoryChartThigy> {
} }
}); });
}, },
child: AbsorbPointer(
child: Padding( padding: padding, child: Padding( padding: padding,
child: LineChart( child: LineChart(
key: graphKey,
LineChartData( LineChartData(
lineBarsData: [LineChartBarData(spots: widget.data)], lineBarsData: [LineChartBarData(spots: widget.data)],
clipData: const FlClipData.all(), clipData: const FlClipData.all(),
@ -702,6 +733,8 @@ class _HistoryChartThigyState extends State<_HistoryChartThigy> {
gridData: FlGridData(verticalInterval: xInterval), gridData: FlGridData(verticalInterval: xInterval),
minX: minX, minX: minX,
maxX: maxX, maxX: maxX,
minY: minY,
maxY: maxY,
titlesData: FlTitlesData(topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), titlesData: FlTitlesData(topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
bottomTitles: AxisTitles(sideTitles: SideTitles(interval: xInterval, showTitles: true, reservedSize: 30, getTitlesWidget: (double value, TitleMeta meta){ bottomTitles: AxisTitles(sideTitles: SideTitles(interval: xInterval, showTitles: true, reservedSize: 30, getTitlesWidget: (double value, TitleMeta meta){
@ -723,6 +756,7 @@ class _HistoryChartThigyState extends State<_HistoryChartThigy> {
), ),
), ),
), ),
),
) )
); );
} }
@ -810,12 +844,12 @@ class _RecordThingy extends StatelessWidget {
fractionDigits: 2, fractionDigits: 2,
isScreenBig: bigScreen, isScreenBig: bigScreen,
higherIsBetter: true,), higherIsBetter: true,),
StatCellNum( if (record!.endContext!.finesse != null) StatCellNum(
playerStat: record!.endContext!.finesse.faults, playerStat: record!.endContext!.finesse!.faults,
playerStatLabel: t.statCellNum.finesseFaults, playerStatLabel: t.statCellNum.finesseFaults,
isScreenBig: bigScreen, isScreenBig: bigScreen,
higherIsBetter: false,), higherIsBetter: false,),
StatCellNum( if (record!.endContext!.finesse != null) StatCellNum(
playerStat: playerStat:
record!.endContext!.finessePercentage * 100, record!.endContext!.finessePercentage * 100,
playerStatLabel: t.statCellNum.finessePercentage, playerStatLabel: t.statCellNum.finessePercentage,