Files
App/lib/widgets/account/account_name.dart
2025-10-12 23:53:28 +08:00

366 lines
11 KiB
Dart

import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:island/models/account.dart';
import 'package:island/models/wallet.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:styled_widget/styled_widget.dart';
const Map<String, Color> kUsernamePlainColors = {
'red': Colors.red,
'blue': Colors.blue,
'green': Colors.green,
'yellow': Colors.yellow,
'purple': Colors.purple,
'orange': Colors.orange,
'pink': Colors.pink,
'cyan': Colors.cyan,
'lime': Colors.lime,
'indigo': Colors.indigo,
'teal': Colors.teal,
'amber': Colors.amber,
'brown': Colors.brown,
'grey': Colors.grey,
'black': Colors.black,
'white': Colors.white,
};
const kVerificationMarkColors = [
Colors.teal,
Colors.blue,
Colors.amber,
Colors.blueGrey,
Colors.lightBlue,
];
class AccountName extends StatelessWidget {
final SnAccount account;
final TextStyle? style;
final bool ignorePermissions;
const AccountName({
super.key,
required this.account,
this.style,
this.ignorePermissions = false,
});
Alignment _parseGradientDirection(String direction) {
switch (direction) {
case 'to right':
return Alignment.centerLeft;
case 'to left':
return Alignment.centerRight;
case 'to bottom':
return Alignment.topCenter;
case 'to top':
return Alignment.bottomCenter;
case 'to bottom right':
return Alignment.topLeft;
case 'to bottom left':
return Alignment.topRight;
case 'to top right':
return Alignment.bottomLeft;
case 'to top left':
return Alignment.bottomRight;
default:
return Alignment.centerLeft;
}
}
Alignment _parseGradientEnd(String direction) {
switch (direction) {
case 'to right':
return Alignment.centerRight;
case 'to left':
return Alignment.centerLeft;
case 'to bottom':
return Alignment.bottomCenter;
case 'to top':
return Alignment.topCenter;
case 'to bottom right':
return Alignment.bottomRight;
case 'to bottom left':
return Alignment.bottomLeft;
case 'to top right':
return Alignment.topRight;
case 'to top left':
return Alignment.topLeft;
default:
return Alignment.centerRight;
}
}
@override
Widget build(BuildContext context) {
var nameStyle = (style ?? TextStyle());
// Apply username color based on membership tier and custom settings
if (account.profile.usernameColor != null) {
final usernameColor = account.profile.usernameColor!;
final tier = account.perkSubscription?.identifier;
// Check tier restrictions
final canUseCustomColor =
ignorePermissions ||
switch (tier) {
'solian.stellar.primary' =>
usernameColor.type == 'plain' &&
kUsernamePlainColors.containsKey(usernameColor.value),
'solian.stellar.nova' => usernameColor.type == 'plain',
'solian.stellar.supernova' => true,
_ => false,
};
if (canUseCustomColor) {
if (usernameColor.type == 'plain') {
// Plain color
Color? color;
if (kUsernamePlainColors.containsKey(usernameColor.value)) {
color = kUsernamePlainColors[usernameColor.value];
} else if (usernameColor.value != null) {
// Try to parse hex color
try {
color = Color(
int.parse(
usernameColor.value!.replaceFirst('#', ''),
radix: 16,
) +
0xFF000000,
);
} catch (_) {
// Invalid hex, ignore
}
}
if (color != null) {
nameStyle = nameStyle.copyWith(color: color);
}
} else if (usernameColor.type == 'gradient' &&
usernameColor.colors != null &&
usernameColor.colors!.isNotEmpty) {
// Gradient - use ShaderMask for text gradient
final colors = <Color>[];
for (final colorStr in usernameColor.colors!) {
Color? color;
if (kUsernamePlainColors.containsKey(colorStr)) {
color = kUsernamePlainColors[colorStr];
} else {
// Try to parse hex color
try {
color = Color(
int.parse(colorStr.replaceFirst('#', ''), radix: 16) +
0xFF000000,
);
} catch (_) {
// Invalid hex, skip
continue;
}
}
if (color != null) {
colors.add(color);
}
}
if (colors.isNotEmpty) {
final gradient = LinearGradient(
colors: colors,
begin: _parseGradientDirection(
usernameColor.direction ?? 'to right',
),
end: _parseGradientEnd(usernameColor.direction ?? 'to right'),
);
return Row(
mainAxisSize: MainAxisSize.min,
spacing: 4,
children: [
Flexible(
child: ShaderMask(
shaderCallback: (bounds) => gradient.createShader(bounds),
child: Text(
account.nick,
style: nameStyle.copyWith(color: Colors.white),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
if (account.perkSubscription != null)
StellarMembershipMark(membership: account.perkSubscription!),
if (account.profile.verification != null)
VerificationMark(mark: account.profile.verification!),
if (account.automatedId != null)
Tooltip(
message: 'accountAutomated'.tr(),
child: Icon(
Symbols.smart_toy,
size: 16,
color: nameStyle.color,
fill: 1,
),
),
],
);
}
}
}
} else if (account.perkSubscription != null) {
// Default membership colors if no custom color is set
nameStyle = nameStyle.copyWith(
color: (switch (account.perkSubscription!.identifier) {
'solian.stellar.primary' => Colors.blueAccent,
'solian.stellar.nova' => Color.fromRGBO(57, 197, 187, 1),
'solian.stellar.supernova' => Colors.amberAccent,
_ => null,
}),
);
}
return Row(
mainAxisSize: MainAxisSize.min,
spacing: 4,
children: [
Flexible(
child: Text(
account.nick,
style: nameStyle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
if (account.perkSubscription != null)
StellarMembershipMark(membership: account.perkSubscription!),
if (account.profile.verification != null)
VerificationMark(mark: account.profile.verification!),
if (account.automatedId != null)
Tooltip(
message: 'accountAutomated'.tr(),
child: Icon(
Symbols.smart_toy,
size: 16,
color: nameStyle.color,
fill: 1,
),
),
],
);
}
}
class VerificationMark extends StatelessWidget {
final SnVerificationMark mark;
const VerificationMark({super.key, required this.mark});
@override
Widget build(BuildContext context) {
return Tooltip(
richMessage: TextSpan(
text: mark.title ?? 'No title',
children: [
TextSpan(text: '\n'),
TextSpan(
text: mark.description ?? 'descriptionNone'.tr(),
style: TextStyle(fontWeight: FontWeight.normal),
),
],
style: TextStyle(fontWeight: FontWeight.bold),
),
child: Icon(
mark.type == 4
? Symbols.play_circle
: mark.type == 0
? Symbols.build_circle
: Symbols.verified,
size: 16,
color: kVerificationMarkColors[mark.type],
fill: 1,
),
);
}
}
class StellarMembershipMark extends StatelessWidget {
final SnWalletSubscriptionRef membership;
const StellarMembershipMark({super.key, required this.membership});
String _getMembershipTierName(String identifier) {
switch (identifier) {
case 'solian.stellar.primary':
return 'membershipTierStellar'.tr();
case 'solian.stellar.nova':
return 'membershipTierNova'.tr();
case 'solian.stellar.supernova':
return 'membershipTierSupernova'.tr();
default:
return 'membershipTierUnknown'.tr();
}
}
Color _getMembershipTierColor(String identifier) {
switch (identifier) {
case 'solian.stellar.primary':
return Colors.blue;
case 'solian.stellar.nova':
return Color.fromRGBO(57, 197, 187, 1);
case 'solian.stellar.supernova':
return Colors.amber;
default:
return Colors.grey;
}
}
@override
Widget build(BuildContext context) {
if (!membership.isActive) return const SizedBox.shrink();
final tierName = _getMembershipTierName(membership.identifier);
final tierColor = _getMembershipTierColor(membership.identifier);
final tierIcon = Symbols.kid_star;
return Tooltip(
richMessage: TextSpan(
text: 'stellarMembership'.tr(),
children: [
TextSpan(text: '\n'),
TextSpan(
text: 'currentMembershipMember'.tr(args: [tierName]),
style: TextStyle(fontWeight: FontWeight.normal),
),
],
style: TextStyle(fontWeight: FontWeight.bold),
),
child: Icon(tierIcon, size: 16, color: tierColor, fill: 1),
);
}
}
class VerificationStatusCard extends StatelessWidget {
final SnVerificationMark mark;
const VerificationStatusCard({super.key, required this.mark});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Icon(
mark.type == 4
? Symbols.play_circle
: mark.type == 0
? Symbols.build_circle
: Symbols.verified,
size: 32,
color: kVerificationMarkColors[mark.type],
fill: 1,
).alignment(Alignment.centerLeft),
const Gap(8),
Text(mark.title ?? 'No title').bold(),
Text(mark.description ?? 'descriptionNone'.tr()),
const Gap(6),
Text(
'Verified by\n${mark.verifiedBy ?? 'No one verified it'}',
).fontSize(11).opacity(0.8),
],
).padding(horizontal: 24, vertical: 16);
}
}