Surface/lib/widgets/check_in.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);
}
}