Daily sign history

This commit is contained in:
LittleSheep 2024-09-08 00:23:59 +08:00
parent 4e8f2ddef3
commit 0a04c72468
5 changed files with 368 additions and 54 deletions

View File

@ -2,9 +2,30 @@ import 'package:get/get.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/exceptions/unauthorized.dart';
import 'package:solian/models/daily_sign.dart';
import 'package:solian/models/pagination.dart';
import 'package:solian/providers/auth.dart';
class DailySignProvider extends GetxController {
Future<List<DailySignRecord>> listLastRecord(int take) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('id');
final resp = await client.get('/daily?take=$take');
if (resp.statusCode != 200 && resp.statusCode != 404) {
throw RequestException(resp);
} else if (resp.statusCode == 404) {
return List.empty();
}
final result = PaginationResult.fromJson(resp.body);
return List.from(
result.data?.map((x) => DailySignRecord.fromJson(x)) ?? [],
);
}
Future<DailySignRecord?> getToday() async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();

View File

@ -24,6 +24,7 @@ import 'package:solian/providers/websocket.dart';
import 'package:solian/router.dart';
import 'package:solian/screens/account/notification.dart';
import 'package:solian/widgets/chat/chat_event.dart';
import 'package:solian/widgets/daily_sign/history_chart.dart';
import 'package:solian/widgets/posts/post_list.dart';
class DashboardScreen extends StatefulWidget {
@ -80,10 +81,14 @@ class _DashboardScreenState extends State<DashboardScreen> {
bool _signingDaily = true;
DailySignRecord? _signRecord;
List<DailySignRecord>? _signRecordHistory;
Future<void> _pullDaily() async {
try {
_signRecord = await _dailySign.getToday();
_dailySign.listLastRecord(30).then((value) {
setState(() => _signRecordHistory = value);
});
} catch (e) {
context.showErrorDialog(e);
}
@ -137,61 +142,79 @@ class _DashboardScreenState extends State<DashboardScreen> {
],
).paddingOnly(top: 8, left: 18, right: 18, bottom: 12),
Card(
child: ListTile(
leading: AnimatedSwitcher(
switchInCurve: Curves.fastOutSlowIn,
switchOutCurve: Curves.fastOutSlowIn,
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, animation) {
return ScaleTransition(
scale: animation,
child: child,
);
},
child: _signRecord == null
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
DateFormat('dd').format(DateTime.now()),
style: GoogleFonts.robotoMono(
fontSize: 22, height: 1.2),
child: Column(
children: [
ListTile(
leading: AnimatedSwitcher(
switchInCurve: Curves.fastOutSlowIn,
switchOutCurve: Curves.fastOutSlowIn,
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, animation) {
return ScaleTransition(
scale: animation,
child: child,
);
},
child: _signRecord == null
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
DateFormat('dd').format(DateTime.now()),
style: GoogleFonts.robotoMono(
fontSize: 22, height: 1.2),
),
Text(
DateFormat('yy/MM').format(DateTime.now()),
style: GoogleFonts.robotoMono(fontSize: 12),
),
],
)
: Text(
_signRecord!.symbol,
style: GoogleFonts.notoSerifHk(
fontSize: 20, height: 1),
).paddingSymmetric(horizontal: 9),
).paddingOnly(left: 4),
title: _signRecord == null
? Text('dailySign'.tr)
: Text(_signRecord!.overviewSuggestion),
subtitle: _signRecord == null
? Text('dailySignNone'.tr)
: Text('+${_signRecord!.resultExperience} EXP'),
trailing: AnimatedSwitcher(
switchInCurve: Curves.fastOutSlowIn,
switchOutCurve: Curves.fastOutSlowIn,
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, animation) {
return ScaleTransition(
scale: animation,
child: child,
);
},
child: _signRecord == null
? IconButton(
tooltip: '上香求签',
icon: const Icon(Icons.local_fire_department),
onPressed: _signingDaily ? null : _signDaily,
)
: IconButton(
tooltip: '查看运势历史',
icon: const Icon(Icons.history),
onPressed: () {
showDialog(
context: context,
useRootNavigator: true,
builder: (context) =>
DailySignHistoryChartDialog(
data: _signRecordHistory,
),
);
},
),
Text(
DateFormat('yy/MM').format(DateTime.now()),
style: GoogleFonts.robotoMono(fontSize: 12),
),
],
)
: Text(
_signRecord!.symbol,
style: GoogleFonts.notoSerifHk(fontSize: 20, height: 1),
).paddingSymmetric(horizontal: 9),
).paddingOnly(left: 4),
title: _signRecord == null
? Text('dailySign'.tr)
: Text(_signRecord!.overviewSuggestion),
subtitle: _signRecord == null
? Text('dailySignNone'.tr)
: Text('+${_signRecord!.resultExperience} EXP'),
trailing: AnimatedSwitcher(
switchInCurve: Curves.fastOutSlowIn,
switchOutCurve: Curves.fastOutSlowIn,
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, animation) {
return ScaleTransition(
scale: animation,
child: child,
);
},
child: _signRecord == null
? IconButton(
tooltip: '上香求签',
icon: const Icon(Icons.local_fire_department),
onPressed: _signingDaily ? null : _signDaily,
)
: const SizedBox.shrink(),
),
),
),
],
),
).paddingSymmetric(horizontal: 8),
const Divider(thickness: 0.3).paddingSymmetric(vertical: 8),

View File

@ -0,0 +1,253 @@
import 'dart:math';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:solian/models/daily_sign.dart';
class DailySignHistoryChartDialog extends StatelessWidget {
final List<DailySignRecord>? data;
const DailySignHistoryChartDialog({super.key, required this.data});
static List<String> signSymbols = ['大凶', '', '中平', '', '大吉'];
DateTime? get _firstRecordDate => data?.map((x) => x.createdAt).reduce(
(a, b) => DateTime.fromMillisecondsSinceEpoch(
min(a.millisecondsSinceEpoch, b.millisecondsSinceEpoch)));
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('运势历史'),
Text(
'${DateFormat('yyyy/MM/dd').format(_firstRecordDate!)} - ${DateFormat('yyyy/MM/dd').format(DateTime.now())}',
style: TextStyle(
fontSize: 13,
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.75),
),
),
],
),
content: data == null
? SizedBox(
height: 180,
width: max(640, MediaQuery.of(context).size.width),
child: const Center(
child: CircularProgressIndicator(),
),
)
: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('近期运势', style: Theme.of(context).textTheme.titleMedium)
.paddingOnly(bottom: 18),
SizedBox(
height: 180,
width: max(640, MediaQuery.of(context).size.width),
child: LineChart(
LineChartData(
lineBarsData: [
LineChartBarData(
isCurved: true,
isStrokeCapRound: true,
isStrokeJoinRound: true,
color: Theme.of(context).colorScheme.primary,
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
colors: List.filled(
data!.length,
Theme.of(context)
.colorScheme
.primary
.withOpacity(0.3),
).toList(),
),
),
spots: data!
.map(
(x) => FlSpot(
x.createdAt
.copyWith(
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
microsecond: 0,
)
.millisecondsSinceEpoch
.toDouble(),
x.resultTier.toDouble(),
),
)
.toList(),
)
],
lineTouchData: LineTouchData(
touchTooltipData: LineTouchTooltipData(
getTooltipItems: (spots) => spots
.map((spot) => LineTooltipItem(
'${signSymbols[spot.y.toInt()]}\n${DateFormat('MM/dd').format(DateTime.fromMillisecondsSinceEpoch(spot.x.toInt()))}',
TextStyle(
color:
Theme.of(context).colorScheme.onSurface,
),
))
.toList(),
getTooltipColor: (_) =>
Theme.of(context).colorScheme.surfaceContainerHigh,
)),
titlesData: FlTitlesData(
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 40,
interval: 1,
getTitlesWidget: (value, _) => Align(
alignment: Alignment.centerRight,
child: Text(
signSymbols[value.toInt()],
textAlign: TextAlign.right,
).paddingOnly(right: 8),
),
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 28,
interval: 86400000,
getTitlesWidget: (value, _) => Text(
DateFormat('MM/dd').format(
DateTime.fromMillisecondsSinceEpoch(
value.toInt(),
),
),
textAlign: TextAlign.center,
).paddingOnly(top: 8),
),
),
),
gridData: const FlGridData(show: false),
borderData: FlBorderData(show: false),
),
),
).marginOnly(right: 24, bottom: 8, top: 8),
const Gap(16),
Text('功德趋势', style: Theme.of(context).textTheme.titleMedium)
.paddingOnly(bottom: 18),
SizedBox(
height: 180,
width: max(640, MediaQuery.of(context).size.width),
child: LineChart(
LineChartData(
lineBarsData: [
LineChartBarData(
isCurved: true,
isStrokeCapRound: true,
isStrokeJoinRound: true,
color: Theme.of(context).colorScheme.primary,
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
colors: List.filled(
data!.length,
Theme.of(context)
.colorScheme
.primary
.withOpacity(0.3),
).toList(),
),
),
spots: data!
.map(
(x) => FlSpot(
x.createdAt
.copyWith(
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
microsecond: 0,
)
.millisecondsSinceEpoch
.toDouble(),
x.resultExperience.toDouble(),
),
)
.toList(),
)
],
lineTouchData: LineTouchData(
touchTooltipData: LineTouchTooltipData(
getTooltipItems: (spots) => spots
.map((spot) => LineTooltipItem(
'+${spot.y.toStringAsFixed(0)} EXP\n${DateFormat('MM/dd').format(DateTime.fromMillisecondsSinceEpoch(spot.x.toInt()))}',
TextStyle(
color:
Theme.of(context).colorScheme.onSurface,
),
))
.toList(),
getTooltipColor: (_) =>
Theme.of(context).colorScheme.surfaceContainerHigh,
)),
titlesData: FlTitlesData(
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 40,
getTitlesWidget: (value, _) => Align(
alignment: Alignment.centerRight,
child: Text(
value.toStringAsFixed(0),
textAlign: TextAlign.right,
).paddingOnly(right: 8),
),
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 28,
interval: 86400000,
getTitlesWidget: (value, _) => Text(
DateFormat('MM/dd').format(
DateTime.fromMillisecondsSinceEpoch(
value.toInt(),
),
),
textAlign: TextAlign.center,
).paddingOnly(top: 8),
),
),
),
gridData: const FlGridData(show: false),
borderData: FlBorderData(show: false),
),
),
).marginOnly(right: 24, bottom: 8, top: 8),
],
),
);
}
}

View File

@ -390,6 +390,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.9"
equatable:
dependency: transitive
description:
name: equatable
sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2
url: "https://pub.dev"
source: hosted
version: "2.0.5"
fake_async:
dependency: transitive
description:
@ -582,6 +590,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
fl_chart:
dependency: "direct main"
description:
name: fl_chart
sha256: "94307bef3a324a0d329d3ab77b2f0c6e5ed739185ffc029ed28c0f9b019ea7ef"
url: "https://pub.dev"
source: hosted
version: "0.69.0"
floor:
dependency: "direct main"
description:

View File

@ -77,6 +77,7 @@ dependencies:
freezed_annotation: ^2.4.4
json_annotation: ^4.9.0
gap: ^3.0.1
fl_chart: ^0.69.0
dev_dependencies:
flutter_test: