225 lines
7.3 KiB
Dart
225 lines
7.3 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:auto_route/auto_route.dart';
|
|
import 'package:dio/dio.dart';
|
|
import 'package:easy_localization/easy_localization.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:gap/gap.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:island/models/activity.dart';
|
|
import 'package:island/pods/network.dart';
|
|
import 'package:island/pods/userinfo.dart';
|
|
import 'package:island/route.gr.dart';
|
|
import 'package:island/screens/auth/captcha.dart';
|
|
import 'package:island/widgets/alert.dart';
|
|
import 'package:island/widgets/content/cloud_files.dart';
|
|
import 'package:material_symbols_icons/symbols.dart';
|
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
import 'package:styled_widget/styled_widget.dart';
|
|
|
|
part 'check_in.g.dart';
|
|
|
|
@riverpod
|
|
Future<SnCheckInResult?> checkInResultToday(Ref ref) async {
|
|
final client = ref.watch(apiClientProvider);
|
|
try {
|
|
final resp = await client.get('/accounts/me/check-in');
|
|
return SnCheckInResult.fromJson(resp.data);
|
|
} catch (err) {
|
|
if (err is DioException) {
|
|
if (err.response?.statusCode == 404) {
|
|
return null;
|
|
}
|
|
}
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
class CheckInWidget extends HookConsumerWidget {
|
|
const CheckInWidget({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final todayResult = ref.watch(checkInResultTodayProvider);
|
|
|
|
Future<void> checkIn() async {
|
|
final client = ref.read(apiClientProvider);
|
|
try {
|
|
await client.post('/accounts/me/check-in');
|
|
ref.invalidate(checkInResultTodayProvider);
|
|
} catch (err) {
|
|
if (err is DioException) {
|
|
if (err.response?.statusCode == 423 && context.mounted) {
|
|
final captchaTk = await Navigator.of(
|
|
context,
|
|
).push(MaterialPageRoute(builder: (context) => CaptchaScreen()));
|
|
if (captchaTk == null) return;
|
|
await client.post(
|
|
'/accounts/me/check-in',
|
|
data: jsonEncode(captchaTk),
|
|
);
|
|
ref.invalidate(checkInResultTodayProvider);
|
|
final userNotifier = ref.read(userInfoProvider.notifier);
|
|
userNotifier.fetchUser();
|
|
return;
|
|
}
|
|
}
|
|
showErrorAlert(err);
|
|
}
|
|
}
|
|
|
|
return Card(
|
|
margin: EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 8),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
spacing: 16,
|
|
children: [
|
|
ClipRRect(
|
|
borderRadius: BorderRadius.circular(8),
|
|
child: Container(
|
|
color: Theme.of(context).colorScheme.secondaryContainer,
|
|
width: 56,
|
|
height: 56,
|
|
child:
|
|
Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
Text(DateFormat('EEE').format(DateTime.now()))
|
|
.fontSize(16)
|
|
.bold()
|
|
.textColor(
|
|
Theme.of(context).colorScheme.onSecondaryContainer,
|
|
),
|
|
Text(DateFormat('MM/dd').format(DateTime.now()))
|
|
.fontSize(12)
|
|
.textColor(
|
|
Theme.of(context).colorScheme.onSecondaryContainer,
|
|
),
|
|
],
|
|
).center(),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: AnimatedSwitcher(
|
|
duration: const Duration(milliseconds: 300),
|
|
child: todayResult.when(
|
|
data: (result) {
|
|
if (result == null) return _CheckInNoneWidget();
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Text(
|
|
'checkInResultLevel${result.level}',
|
|
).tr().fontSize(15).bold(),
|
|
Text(
|
|
result.tips
|
|
.map(
|
|
(e) => '${e.isPositive ? '宜' : '忌'} ${e.title}',
|
|
)
|
|
.join(' · '),
|
|
).fontSize(11),
|
|
],
|
|
);
|
|
},
|
|
loading: () => _CheckInNoneWidget(),
|
|
error:
|
|
(err, stack) => Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('error').tr().fontSize(15).bold(),
|
|
Text(err.toString()).fontSize(11),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
IconButton.outlined(
|
|
onPressed: () {
|
|
if (todayResult.valueOrNull == null) {
|
|
checkIn();
|
|
} else {
|
|
context.router.push(MyselfEventCalendarRoute());
|
|
}
|
|
},
|
|
icon: AnimatedSwitcher(
|
|
duration: const Duration(milliseconds: 300),
|
|
child: todayResult.when(
|
|
data:
|
|
(result) => Icon(
|
|
result == null
|
|
? Symbols.local_fire_department
|
|
: Symbols.event,
|
|
key: ValueKey(result != null),
|
|
),
|
|
loading: () => const Icon(Symbols.refresh),
|
|
error: (_, __) => const Icon(Symbols.error),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
).padding(horizontal: 16, vertical: 12),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _CheckInNoneWidget extends StatelessWidget {
|
|
const _CheckInNoneWidget();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Text('checkInNone').tr().fontSize(15).bold(),
|
|
Text('checkInNoneHint').tr().fontSize(11),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class CheckInActivityWidget extends StatelessWidget {
|
|
final SnActivity item;
|
|
const CheckInActivityWidget({super.key, required this.item});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final result = SnCheckInResult.fromJson(item.data);
|
|
return Row(
|
|
spacing: 12,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
ProfilePictureWidget(
|
|
fileId: result.account!.profile.pictureId,
|
|
radius: 12,
|
|
),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Icon(Symbols.local_fire_department, size: 14),
|
|
const Gap(4),
|
|
Text('checkIn').fontSize(11).tr(),
|
|
],
|
|
).opacity(0.85),
|
|
Text('checkInActivityTitle')
|
|
.tr(
|
|
args: [
|
|
result.account!.nick,
|
|
DateFormat.yMd().format(result.createdAt),
|
|
'checkInResultLevel${result.level}'.tr(),
|
|
],
|
|
)
|
|
.fontSize(13)
|
|
.padding(left: 2),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
).padding(horizontal: 16, vertical: 12);
|
|
}
|
|
}
|