💄 Optimize the pfc and show the activities

This commit is contained in:
2025-11-02 00:25:08 +08:00
parent c093123e3a
commit bc60ce5d42
3 changed files with 192 additions and 29 deletions

View File

@@ -206,7 +206,7 @@ class _AccountProfileDetail extends StatelessWidget {
child: Row( child: Row(
spacing: 6, spacing: 6,
children: [ children: [
Icon(Symbols.star, size: 17, fill: 1).padding(right: 2), Icon(Symbols.attribution, size: 17, fill: 1).padding(right: 2),
Text('${data.profile.socialCredits.toStringAsFixed(2)} pts'), Text('${data.profile.socialCredits.toStringAsFixed(2)} pts'),
Text('·').bold(), Text('·').bold(),
switch (data.profile.socialCreditsLevel) { switch (data.profile.socialCreditsLevel) {

View File

@@ -11,8 +11,9 @@ import 'package:island/screens/account/profile.dart';
import 'package:island/services/time.dart'; import 'package:island/services/time.dart';
import 'package:island/services/timezone/native.dart'; import 'package:island/services/timezone/native.dart';
import 'package:island/widgets/account/account_name.dart'; import 'package:island/widgets/account/account_name.dart';
import 'package:island/widgets/account/activity_presence.dart';
import 'package:island/widgets/account/badge.dart'; import 'package:island/widgets/account/badge.dart';
import 'package:island/widgets/account/leveling_progress.dart';
import 'package:island/widgets/account/status.dart'; import 'package:island/widgets/account/status.dart';
import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/response.dart'; import 'package:island/widgets/response.dart';
@@ -54,7 +55,30 @@ class AccountProfileCard extends HookConsumerWidget {
children: [ children: [
Row( Row(
children: [ children: [
ProfilePictureWidget(file: data.profile.picture), GestureDetector(
child: Badge(
isLabelVisible: true,
padding: EdgeInsets.all(2),
label: Icon(
Symbols.launch,
size: 12,
color: Theme.of(context).colorScheme.onPrimary,
),
backgroundColor:
Theme.of(context).colorScheme.primary,
offset: Offset(4, 28),
child: ProfilePictureWidget(
file: data.profile.picture,
),
),
onTap: () {
Navigator.pop(context);
context.pushNamed(
'accountProfile',
pathParameters: {'name': data.name},
);
},
),
const Gap(12), const Gap(12),
Expanded( Expanded(
child: Column( child: Column(
@@ -81,7 +105,7 @@ class AccountProfileCard extends HookConsumerWidget {
spacing: 6, spacing: 6,
children: [ children: [
Icon( Icon(
Symbols.star, Symbols.attribution,
size: 17, size: 17,
fill: 1, fill: 1,
).padding(right: 2), ).padding(right: 2),
@@ -144,25 +168,40 @@ class AccountProfileCard extends HookConsumerWidget {
).padding(top: 2); ).padding(top: 2);
} }
}(), }(),
Row(
spacing: 6,
children: [
Icon(
Symbols.stairs,
size: 17,
fill: 1,
).padding(right: 2),
Text(
'levelingProgressLevel'.tr(
args: [data.profile.level.toString()],
),
).fontSize(12),
Expanded(
child: Tooltip(
message:
'${(data.profile.levelingProgress * 100).toStringAsFixed(2)}%',
child: LinearProgressIndicator(
value: data.profile.levelingProgress,
stopIndicatorRadius: 0,
trackGap: 0,
minHeight: 4,
).padding(top: 1),
),
),
],
).padding(top: 2),
if (data.badges.isNotEmpty) if (data.badges.isNotEmpty)
BadgeList(badges: data.badges).padding(top: 12), BadgeList(badges: data.badges).padding(top: 12),
LevelingProgressCard( ActivityPresenceWidget(
uname: uname,
isCompact: true, isCompact: true,
level: data.profile.level, compactPadding: const EdgeInsets.only(top: 12),
experience: data.profile.experience, ),
progress: data.profile.levelingProgress,
).padding(top: 12),
FilledButton.tonalIcon(
onPressed: () {
Navigator.pop(context);
context.pushNamed(
'accountProfile',
pathParameters: {'name': data.name},
);
},
icon: const Icon(Symbols.launch),
label: Text('accountProfileView').tr(),
).padding(top: 12, horizontal: 2),
], ],
).padding(horizontal: 24, vertical: 16), ).padding(horizontal: 24, vertical: 16),
], ],

View File

@@ -68,8 +68,15 @@ const kPresenseActivityIcons = <IconData>[
class ActivityPresenceWidget extends ConsumerWidget { class ActivityPresenceWidget extends ConsumerWidget {
final String uname; final String uname;
final bool isCompact;
final EdgeInsets compactPadding;
const ActivityPresenceWidget({super.key, required this.uname}); const ActivityPresenceWidget({
super.key,
required this.uname,
this.isCompact = false,
this.compactPadding = EdgeInsets.zero,
});
List<Widget> _buildDiscordImages(WidgetRef ref, SnPresenceActivity activity) { List<Widget> _buildDiscordImages(WidgetRef ref, SnPresenceActivity activity) {
final List<Widget> images = []; final List<Widget> images = [];
@@ -139,6 +146,106 @@ class ActivityPresenceWidget extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final activitiesAsync = ref.watch(presenceActivitiesProvider(uname)); final activitiesAsync = ref.watch(presenceActivitiesProvider(uname));
if (isCompact) {
return activitiesAsync.when(
data: (activities) {
if (activities.isEmpty) return const SizedBox.shrink();
final activity = activities.first;
return Padding(
padding: compactPadding,
child: Row(
spacing: 8,
children: [
if (activity.largeImage != null &&
activity.largeImage!.startsWith('discord:'))
ref
.watch(
discordAssetsUrlProvider(
activity,
activity.largeImage!.substring('discord:'.length),
),
)
.when(
data:
(url) =>
url != null
? ClipRRect(
borderRadius: BorderRadius.circular(4),
child: CachedNetworkImage(
imageUrl: url,
width: 32,
height: 32,
),
)
: const SizedBox.shrink(),
loading:
() => const SizedBox(
width: 32,
height: 32,
child: CircularProgressIndicator(strokeWidth: 1),
),
error: (error, stack) => const SizedBox.shrink(),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
(activity.title?.isEmpty ?? true)
? 'unknown'.tr()
: activity.title!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
).fontSize(13),
Row(
children: [
Text(
kPresenseActivityTypes[activity.type],
).tr().fontSize(11),
Icon(
kPresenseActivityIcons[activity.type],
size: 15,
fill: 1,
),
],
),
],
),
),
StreamBuilder(
stream: Stream.periodic(const Duration(seconds: 1)),
builder: (context, snapshot) {
final now = DateTime.now();
// Check if lease has expired and refresh if needed
if (now.isAfter(activity.leaseExpiresAt)) {
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.invalidate(presenceActivitiesProvider(uname));
});
}
final duration = now.difference(activity.createdAt);
final hours = duration.inHours.toString().padLeft(2, '0');
final minutes = (duration.inMinutes % 60)
.toString()
.padLeft(2, '0');
final seconds = (duration.inSeconds % 60)
.toString()
.padLeft(2, '0');
return Text(
'$hours:$minutes:$seconds',
).textColor(Colors.green).fontSize(12);
},
),
],
),
);
},
loading: () => const SizedBox.shrink(),
error: (error, stack) => const SizedBox.shrink(),
);
}
return activitiesAsync.when( return activitiesAsync.when(
data: data:
(activities) => Card( (activities) => Card(
@@ -206,8 +313,12 @@ class ActivityPresenceWidget extends ConsumerWidget {
// Check if lease has expired and refresh if needed // Check if lease has expired and refresh if needed
if (now.isAfter(activity.leaseExpiresAt)) { if (now.isAfter(activity.leaseExpiresAt)) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((
ref.invalidate(presenceActivitiesProvider(uname)); _,
) {
ref.invalidate(
presenceActivitiesProvider(uname),
);
}); });
} }
@@ -238,23 +349,36 @@ class ActivityPresenceWidget extends ConsumerWidget {
Row( Row(
spacing: 8, spacing: 8,
children: [ children: [
if (activity.titleUrl != null && activity.titleUrl!.isNotEmpty) if (activity.titleUrl != null &&
activity.titleUrl!.isNotEmpty)
ElevatedButton.icon( ElevatedButton.icon(
onPressed: () => launchUrlString(activity.titleUrl!), onPressed:
() =>
launchUrlString(activity.titleUrl!),
icon: const Icon(Symbols.link, size: 16), icon: const Icon(Symbols.link, size: 16),
label: const Text('Open Title Link'), label: const Text('Open Title Link'),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
textStyle: const TextStyle(fontSize: 12), textStyle: const TextStyle(fontSize: 12),
), ),
), ),
if (activity.subtitleUrl != null && activity.subtitleUrl!.isNotEmpty) if (activity.subtitleUrl != null &&
activity.subtitleUrl!.isNotEmpty)
ElevatedButton.icon( ElevatedButton.icon(
onPressed: () => launchUrlString(activity.subtitleUrl!), onPressed:
() => launchUrlString(
activity.subtitleUrl!,
),
icon: const Icon(Symbols.link, size: 16), icon: const Icon(Symbols.link, size: 16),
label: const Text('Open Subtitle Link'), label: const Text('Open Subtitle Link'),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
textStyle: const TextStyle(fontSize: 12), textStyle: const TextStyle(fontSize: 12),
), ),
), ),