Solian/lib/screens/dashboard.dart

429 lines
16 KiB
Dart
Raw Normal View History

2024-09-03 15:07:20 +00:00
import 'dart:developer';
import 'dart:math' hide log;
2024-09-01 09:20:26 +00:00
import 'package:flutter/material.dart';
2024-09-03 15:07:20 +00:00
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
2024-09-01 09:20:26 +00:00
import 'package:get/get.dart';
2024-09-02 15:11:40 +00:00
import 'package:google_fonts/google_fonts.dart';
2024-09-01 09:20:26 +00:00
import 'package:intl/intl.dart';
2024-09-02 15:11:40 +00:00
import 'package:solian/exts.dart';
import 'package:solian/models/daily_sign.dart';
2024-09-03 15:07:20 +00:00
import 'package:solian/models/event.dart';
2024-09-02 15:11:40 +00:00
import 'package:solian/models/pagination.dart';
import 'package:solian/models/post.dart';
import 'package:solian/providers/content/posts.dart';
import 'package:solian/providers/daily_sign.dart';
2024-09-03 15:07:20 +00:00
import 'package:solian/providers/last_read.dart';
import 'package:solian/providers/message/adaptor.dart';
2024-09-01 09:20:26 +00:00
import 'package:solian/providers/websocket.dart';
2024-09-02 15:11:40 +00:00
import 'package:solian/router.dart';
2024-09-01 09:20:26 +00:00
import 'package:solian/screens/account/notification.dart';
2024-09-03 15:07:20 +00:00
import 'package:solian/widgets/chat/chat_event.dart';
2024-09-02 15:11:40 +00:00
import 'package:solian/widgets/posts/post_list.dart';
2024-09-01 09:20:26 +00:00
class DashboardScreen extends StatefulWidget {
const DashboardScreen({super.key});
@override
State<DashboardScreen> createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> {
2024-09-03 15:07:20 +00:00
late final LastReadProvider _lastRead = Get.find();
2024-09-01 09:20:26 +00:00
late final WebSocketProvider _ws = Get.find();
2024-09-02 15:11:40 +00:00
late final PostProvider _posts = Get.find();
late final DailySignProvider _dailySign = Get.find();
2024-09-02 15:15:24 +00:00
Color get _unFocusColor =>
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
2024-09-02 15:11:40 +00:00
List<Post>? _currentPosts;
2024-09-03 15:07:20 +00:00
int? _currentPostsCount;
2024-09-02 15:11:40 +00:00
Future<void> _pullPosts() async {
2024-09-03 15:07:20 +00:00
if (_lastRead.feedLastReadAt == null) return;
log('[Dashboard] Pulling posts with pivot: ${_lastRead.feedLastReadAt}');
final resp = await _posts.seeWhatsNew(_lastRead.feedLastReadAt!);
2024-09-02 15:11:40 +00:00
final result = PaginationResult.fromJson(resp.body);
2024-09-03 15:07:20 +00:00
setState(() {
_currentPostsCount = result.count;
_currentPosts = result.data?.map((e) => Post.fromJson(e)).toList();
});
}
List<Event>? _currentMessages;
int? _currentMessagesCount;
Future<void> _pullMessages() async {
if (_lastRead.messagesLastReadAt == null) return;
log('[Dashboard] Pulling messages with pivot: ${_lastRead.messagesLastReadAt}');
final out = await getWhatsNewEvents(_lastRead.messagesLastReadAt!);
if (out == null) return;
setState(() {
_currentMessages = out.$1;
_currentMessagesCount = out.$2;
});
2024-09-02 15:11:40 +00:00
}
bool _signingDaily = true;
DailySignRecord? _signRecord;
Future<void> _pullDaily() async {
try {
_signRecord = await _dailySign.getToday();
} catch (e) {
context.showErrorDialog(e);
}
setState(() => _signingDaily = false);
}
Future<void> _signDaily() async {
setState(() => _signingDaily = true);
try {
_signRecord = await _dailySign.signToday();
} catch (e) {
context.showErrorDialog(e);
}
setState(() => _signingDaily = false);
}
@override
void initState() {
super.initState();
_pullPosts();
2024-09-03 15:07:20 +00:00
_pullMessages();
2024-09-02 15:11:40 +00:00
_pullDaily();
}
2024-09-01 09:20:26 +00:00
@override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return ListView(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('today'.tr, style: Theme.of(context).textTheme.headlineSmall),
Text(DateFormat('yyyy/MM/dd').format(DateTime.now())),
],
2024-09-02 15:11:40 +00:00
).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),
),
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)
2024-09-02 15:11:40 +00:00
: Text(_signRecord!.overviewSuggestion),
subtitle: _signRecord == null
? Text('dailySignNone'.tr)
2024-09-02 15:11:40 +00:00
: 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(),
),
),
).paddingSymmetric(horizontal: 8),
2024-09-01 09:20:26 +00:00
const Divider(thickness: 0.3).paddingSymmetric(vertical: 8),
Obx(
() => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'notification'.tr,
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontSize: 18),
),
Text(
'notificationUnreadCount'.trParams({
'count': _ws.notifications.length.toString(),
}),
),
],
),
IconButton(
icon: const Icon(Icons.more_horiz),
onPressed: () {
showModalBottomSheet(
useRootNavigator: true,
isScrollControlled: true,
context: context,
builder: (context) => const NotificationScreen(),
).then((_) => _ws.notificationUnread.value = 0);
},
),
],
).paddingOnly(left: 18, right: 18, bottom: 8),
if (_ws.notifications.isNotEmpty)
SizedBox(
height: 76,
width: width,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: min(_ws.notifications.length, 3),
itemBuilder: (context, idx) {
final x = _ws.notifications[idx];
return SizedBox(
width: width,
child: Card(
child: ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 4,
),
title: Text(x.title),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (x.subtitle != null) Text(x.subtitle!),
Text(x.body),
],
),
),
).paddingSymmetric(horizontal: 8),
);
},
),
)
else
Card(
child: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
trailing: const Icon(Icons.inbox_outlined),
title: Text('notifyEmpty'.tr),
subtitle: Text('notifyEmptyCaption'.tr),
),
).paddingSymmetric(horizontal: 8),
],
2024-09-02 15:11:40 +00:00
).paddingOnly(bottom: 12),
2024-09-01 09:20:26 +00:00
),
2024-09-02 15:11:40 +00:00
if (_currentPosts?.isNotEmpty ?? false)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'feed'.tr,
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontSize: 18),
),
Text(
2024-09-03 15:07:20 +00:00
'feedUnreadCount'.trParams({
'count': (_currentPostsCount ?? 0).toString(),
2024-09-02 15:11:40 +00:00
}),
),
],
),
IconButton(
icon: const Icon(Icons.arrow_forward),
onPressed: () {
AppRouter.instance.goNamed('feed');
},
),
],
).paddingOnly(left: 18, right: 18, bottom: 8),
SizedBox(
height: 360,
width: width,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: _currentPosts!.length,
itemBuilder: (context, idx) {
final item = _currentPosts![idx];
return SizedBox(
width: width,
child: Card(
2024-09-03 15:07:20 +00:00
child: PostListEntryWidget(
item: item,
isClickable: true,
isShowEmbed: true,
isNestedClickable: true,
onUpdate: (_) {
_pullPosts();
},
backgroundColor:
Theme.of(context).colorScheme.surfaceContainerLow,
),
).paddingSymmetric(horizontal: 8),
);
},
),
)
],
),
if (_currentMessages?.isNotEmpty ?? false)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'messages'.tr,
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontSize: 18),
),
Text(
'messagesUnreadCount'.trParams({
'count': (_currentMessagesCount ?? 0).toString(),
}),
),
],
),
IconButton(
icon: const Icon(Icons.arrow_forward),
onPressed: () {
AppRouter.instance.goNamed('chat');
},
),
],
).paddingOnly(left: 18, right: 18, bottom: 8),
SizedBox(
height: 240,
width: width,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: _currentMessages!.length,
itemBuilder: (context, idx) {
final item = _currentMessages![idx];
return SizedBox(
width: width,
child: Card(
child: Column(
children: [
ListTile(
tileColor: Theme.of(context)
.colorScheme
.surfaceContainerHigh,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8),
topRight: Radius.circular(8),
)),
leading: CircleAvatar(
backgroundColor: item.channel!.realmId == null
? Theme.of(context).colorScheme.primary
: Colors.transparent,
radius: 20,
child: FaIcon(
FontAwesomeIcons.hashtag,
color: item.channel!.realmId == null
? Theme.of(context).colorScheme.onPrimary
: Theme.of(context).colorScheme.primary,
size: 16,
),
),
contentPadding:
const EdgeInsets.symmetric(horizontal: 16),
title: Text(item.channel!.name),
subtitle: Text(item.channel!.description),
onTap: () {
AppRouter.instance.pushNamed(
'channelChat',
pathParameters: {
'alias': item.channel!.alias
},
queryParameters: {
if (item.channel!.realmId != null)
'realm': item.channel!.realm!.alias,
},
);
},
),
ChatEvent(item: item).paddingOnly(
bottom: 8, top: 16, left: 8, right: 8),
],
2024-09-02 15:11:40 +00:00
),
).paddingSymmetric(horizontal: 8),
);
},
),
)
],
),
2024-09-02 15:15:24 +00:00
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Powered by Solar Network',
style: TextStyle(color: _unFocusColor, fontSize: 12),
),
Text(
'dashboardFooter'.tr,
2024-09-03 15:07:20 +00:00
style: GoogleFonts.notoSerifHk(
color: _unFocusColor,
fontSize: 12,
),
2024-09-02 15:15:24 +00:00
)
],
).paddingAll(8),
2024-09-01 09:20:26 +00:00
],
);
}
}