♻️ Refactor account badge showing

This commit is contained in:
LittleSheep 2025-03-09 12:57:37 +08:00
parent 0241c5f804
commit c1aef89b84
6 changed files with 92 additions and 114 deletions

View File

@ -18,6 +18,7 @@ import 'package:surface/types/account.dart';
import 'package:surface/types/check_in.dart'; import 'package:surface/types/check_in.dart';
import 'package:surface/types/post.dart'; import 'package:surface/types/post.dart';
import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/account/badge.dart';
import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/universal_image.dart'; import 'package:surface/widgets/universal_image.dart';
import 'package:surface/theme.dart'; import 'package:surface/theme.dart';
@ -484,34 +485,7 @@ class _UserScreenState extends State<UserScreen>
Wrap( Wrap(
children: _account!.badges children: _account!.badges
.map( .map(
(ele) => Tooltip( (ele) => AccountBadge(badge: ele),
richMessage: TextSpan(
children: [
TextSpan(
text: kBadgesMeta[ele.type]?.$1.tr() ??
'unknown'.tr(),
),
if (ele.metadata['title'] != null)
TextSpan(
text: '\n${ele.metadata['title']}',
style: const TextStyle(
fontWeight: FontWeight.bold),
),
TextSpan(text: '\n'),
TextSpan(
text: DateFormat.yMEd().format(ele.createdAt),
),
],
),
child: Icon(
kBadgesMeta[ele.type]?.$2 ??
Symbols.question_mark,
color: ele.metadata['color'] != null
? HexColor.fromHex(ele.metadata['color']!)
: kBadgesMeta[ele.type]?.$3,
fill: 1,
),
),
) )
.toList(), .toList(),
).padding(horizontal: 8), ).padding(horizontal: 8),

View File

@ -8,9 +8,9 @@ import 'package:relative_time/relative_time.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/experience.dart'; import 'package:surface/providers/experience.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/screens/account/profile_page.dart';
import 'package:surface/types/account.dart'; import 'package:surface/types/account.dart';
import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/account/badge.dart';
import 'package:surface/widgets/universal_image.dart'; import 'package:surface/widgets/universal_image.dart';
class AccountPopoverCard extends StatelessWidget { class AccountPopoverCard extends StatelessWidget {
@ -72,37 +72,21 @@ class AccountPopoverCard extends StatelessWidget {
const Gap(8) const Gap(8)
], ],
).padding(horizontal: 16), ).padding(horizontal: 16),
if (data.badges.isNotEmpty) const Gap(12),
if (data.badges.isNotEmpty) if (data.badges.isNotEmpty)
Wrap( Wrap(
spacing: 4, spacing: 4,
children: data.badges children: data.badges
.map( .map(
(ele) => Tooltip( (ele) => AccountBadge(badge: ele),
richMessage: TextSpan(
children: [
TextSpan(text: kBadgesMeta[ele.type]?.$1.tr() ?? 'unknown'.tr()),
if (ele.metadata['title'] != null)
TextSpan(
text: '\n${ele.metadata['title']}',
style: const TextStyle(fontWeight: FontWeight.bold),
),
TextSpan(text: '\n'),
TextSpan(
text: DateFormat.yMEd().format(ele.createdAt),
),
],
),
child: Icon(
kBadgesMeta[ele.type]?.$2 ?? Symbols.question_mark,
color: kBadgesMeta[ele.type]?.$3,
fill: 1,
),
),
) )
.toList(), .toList(),
).padding(horizontal: 24), ).padding(horizontal: 24, bottom: 12, top: 12),
const Gap(8), if (data.profile?.description.isNotEmpty ?? false)
Text(
data.profile?.description ?? '',
maxLines: 2,
overflow: TextOverflow.ellipsis,
).padding(horizontal: 26, bottom: 8),
Row( Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
@ -110,7 +94,9 @@ class AccountPopoverCard extends StatelessWidget {
const Gap(8), const Gap(8),
Text('Lv${getLevelFromExp(data.profile?.experience ?? 0)}'), Text('Lv${getLevelFromExp(data.profile?.experience ?? 0)}'),
const Gap(8), const Gap(8),
Text(calcLevelUpProgressLevel(data.profile?.experience ?? 0)).fontSize(11).opacity(0.5), Text(calcLevelUpProgressLevel(data.profile?.experience ?? 0))
.fontSize(11)
.opacity(0.5),
const Gap(8), const Gap(8),
Container( Container(
width: double.infinity, width: double.infinity,
@ -126,15 +112,17 @@ class AccountPopoverCard extends StatelessWidget {
FutureBuilder( FutureBuilder(
future: sn.client.get('/cgi/id/users/${data.name}/status'), future: sn.client.get('/cgi/id/users/${data.name}/status'),
builder: (context, snapshot) { builder: (context, snapshot) {
final SnAccountStatusInfo? status = final SnAccountStatusInfo? status = snapshot.hasData
snapshot.hasData ? SnAccountStatusInfo.fromJson(snapshot.data!.data) : null; ? SnAccountStatusInfo.fromJson(snapshot.data!.data)
: null;
return Row( return Row(
children: [ children: [
Icon( Icon(
Symbols.circle, Symbols.circle,
fill: 1, fill: 1,
size: 16, size: 16,
color: (status?.isOnline ?? false) ? Colors.green : Colors.grey, color:
(status?.isOnline ?? false) ? Colors.green : Colors.grey,
).padding(all: 4), ).padding(all: 4),
const Gap(8), const Gap(8),
Text( Text(
@ -144,7 +132,9 @@ class AccountPopoverCard extends StatelessWidget {
: 'accountStatusOffline'.tr() : 'accountStatusOffline'.tr()
: 'loading'.tr(), : 'loading'.tr(),
), ),
if (status != null && !status.isOnline && status.lastSeenAt != null) if (status != null &&
!status.isOnline &&
status.lastSeenAt != null)
Text( Text(
'accountStatusLastSeen'.tr(args: [ 'accountStatusLastSeen'.tr(args: [
status.lastSeenAt != null status.lastSeenAt != null

View File

@ -0,0 +1,50 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:surface/screens/account/profile_page.dart' show kBadgesMeta;
import 'package:surface/types/account.dart';
class AccountBadge extends StatelessWidget {
final SnAccountBadge badge;
final double radius;
final EdgeInsets? padding;
const AccountBadge({
super.key,
required this.badge,
this.radius = 20,
this.padding,
});
@override
Widget build(BuildContext context) {
return Tooltip(
richMessage: TextSpan(
children: [
TextSpan(text: kBadgesMeta[badge.type]?.$1.tr() ?? 'unknown'.tr()),
if (badge.metadata['title'] != null)
TextSpan(
text: '\n${badge.metadata['title']}',
style: const TextStyle(fontWeight: FontWeight.bold),
),
TextSpan(text: '\n'),
TextSpan(
text: DateFormat.yMEd().format(badge.createdAt),
),
],
),
child: Container(
padding: padding ?? EdgeInsets.all(3),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(radius),
color: kBadgesMeta[badge.type]?.$3,
),
child: Icon(
kBadgesMeta[badge.type]?.$2 ?? Symbols.question_mark,
color: Colors.white,
fill: 1,
size: radius - 4,
),
),
);
}
}

View File

@ -12,10 +12,10 @@ import 'package:surface/providers/config.dart';
import 'package:surface/providers/keypair.dart'; import 'package:surface/providers/keypair.dart';
import 'package:surface/providers/user_directory.dart'; import 'package:surface/providers/user_directory.dart';
import 'package:surface/providers/userinfo.dart'; import 'package:surface/providers/userinfo.dart';
import 'package:surface/screens/account/profile_page.dart';
import 'package:surface/types/chat.dart'; import 'package:surface/types/chat.dart';
import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/account/account_popover.dart'; import 'package:surface/widgets/account/account_popover.dart';
import 'package:surface/widgets/account/badge.dart';
import 'package:surface/widgets/attachment/attachment_list.dart'; import 'package:surface/widgets/attachment/attachment_list.dart';
import 'package:surface/widgets/context_menu.dart'; import 'package:surface/widgets/context_menu.dart';
import 'package:surface/widgets/link_preview.dart'; import 'package:surface/widgets/link_preview.dart';
@ -109,18 +109,10 @@ class ChatMessage extends StatelessWidget {
child: AccountImage( child: AccountImage(
content: user?.avatar, content: user?.avatar,
badge: (user?.badges.isNotEmpty ?? false) badge: (user?.badges.isNotEmpty ?? false)
? Icon( ? AccountBadge(
kBadgesMeta[user!.badges.first.type]?.$2 ?? badge: user!.badges.first,
Symbols.question_mark, radius: 16,
color: kBadgesMeta[user.badges.first.type]?.$3, padding: EdgeInsets.all(2),
fill: 1,
size: 18,
shadows: [
Shadow(
offset: Offset(1, 1),
blurRadius: 5.0,
color: Color.fromARGB(200, 0, 0, 0)),
],
) )
: null, : null,
), ),

View File

@ -29,6 +29,7 @@ import 'package:surface/types/attachment.dart';
import 'package:surface/types/post.dart'; import 'package:surface/types/post.dart';
import 'package:surface/types/reaction.dart'; import 'package:surface/types/reaction.dart';
import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/account/badge.dart';
import 'package:surface/widgets/attachment/attachment_item.dart'; import 'package:surface/widgets/attachment/attachment_item.dart';
import 'package:surface/widgets/attachment/attachment_list.dart'; import 'package:surface/widgets/attachment/attachment_list.dart';
import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/dialog.dart';
@ -43,8 +44,6 @@ import 'package:surface/widgets/post/publisher_popover.dart';
import 'package:surface/widgets/universal_image.dart'; import 'package:surface/widgets/universal_image.dart';
import 'package:xml/xml.dart'; import 'package:xml/xml.dart';
import '../../screens/account/profile_page.dart' show kBadgesMeta;
class OpenablePostItem extends StatelessWidget { class OpenablePostItem extends StatelessWidget {
final SnPost data; final SnPost data;
final bool showReactions; final bool showReactions;
@ -887,7 +886,7 @@ class _PostContentHeader extends StatelessWidget {
'publisherId': data.publisherId, 'publisherId': data.publisherId,
}); });
if (!context.mounted) return; if (!context.mounted) return;
context.showSnackbar('postDeleted'.tr(args: ['#${data.id}'])); onDeleted.call();
} catch (err) { } catch (err) {
if (!context.mounted) return; if (!context.mounted) return;
context.showErrorDialog(err); context.showErrorDialog(err);
@ -930,19 +929,10 @@ class _PostContentHeader extends StatelessWidget {
borderRadius: borderRadius:
data.publisher.type == 1 ? (isCompact ? 4 : 8) : 20, data.publisher.type == 1 ? (isCompact ? 4 : 8) : 20,
badge: (user?.badges.isNotEmpty ?? false) badge: (user?.badges.isNotEmpty ?? false)
? Icon( ? AccountBadge(
kBadgesMeta[user!.badges.first.type]?.$2 ?? badge: user!.badges.first,
Symbols.question_mark, radius: 16,
color: kBadgesMeta[user.badges.first.type]?.$3, padding: EdgeInsets.all(2),
fill: 1,
size: 18,
shadows: [
Shadow(
offset: Offset(1, 1),
blurRadius: 5.0,
color: Color.fromARGB(200, 0, 0, 0),
),
],
) )
: null, : null,
) )

View File

@ -9,10 +9,9 @@ import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/user_directory.dart'; import 'package:surface/providers/user_directory.dart';
import 'package:surface/types/post.dart'; import 'package:surface/types/post.dart';
import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/account/badge.dart';
import 'package:surface/widgets/universal_image.dart'; import 'package:surface/widgets/universal_image.dart';
import '../../screens/account/profile_page.dart' show kBadgesMeta;
class PublisherPopoverCard extends StatelessWidget { class PublisherPopoverCard extends StatelessWidget {
final SnPublisher data; final SnPublisher data;
@ -76,39 +75,22 @@ class PublisherPopoverCard extends StatelessWidget {
const Gap(8) const Gap(8)
], ],
).padding(horizontal: 16), ).padding(horizontal: 16),
if (user != null && user.badges.isNotEmpty) const Gap(16),
if (user != null && user.badges.isNotEmpty) if (user != null && user.badges.isNotEmpty)
Wrap( Wrap(
spacing: 4, spacing: 4,
children: user.badges children: user.badges
.map( .map(
(ele) => Tooltip( (ele) => AccountBadge(badge: ele),
richMessage: TextSpan(
children: [
TextSpan(
text: kBadgesMeta[ele.type]?.$1.tr() ??
'unknown'.tr()),
if (ele.metadata['title'] != null)
TextSpan(
text: '\n${ele.metadata['title']}',
style: const TextStyle(fontWeight: FontWeight.bold),
),
TextSpan(text: '\n'),
TextSpan(
text: DateFormat.yMEd().format(ele.createdAt),
),
],
),
child: Icon(
kBadgesMeta[ele.type]?.$2 ?? Symbols.question_mark,
color: kBadgesMeta[ele.type]?.$3,
fill: 1,
),
),
) )
.toList(), .toList(),
).padding(horizontal: 24), ).padding(horizontal: 24, top: 16),
const Gap(16), const Gap(16),
if (data.description.isNotEmpty)
Text(
data.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
).padding(horizontal: 26, bottom: 20),
Row( Row(
children: [ children: [
Expanded( Expanded(