💄 Shows listening activities are from spotfiy

This commit is contained in:
2025-11-02 16:55:16 +08:00
parent cfbe6e580b
commit a76b97d1d2

View File

@@ -393,202 +393,234 @@ class _ActivityPresenceWidgetState extends State<ActivityPresenceWidget>
],
).opacity(0.75).padding(horizontal: 16, bottom: 8),
...activities.map((activity) {
final dcImages = _buildImages(ref, activity);
final images = _buildImages(ref, activity);
return Card(
elevation: 0,
shape: RoundedRectangleBorder(
side: BorderSide(
color: Colors.grey.shade300,
width: 1,
),
borderRadius: BorderRadius.circular(8),
),
margin: EdgeInsets.zero,
child: ListTile(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (dcImages.isNotEmpty)
Row(
crossAxisAlignment: CrossAxisAlignment.end,
spacing: 8,
children: dcImages,
).padding(vertical: 4),
Row(
spacing: 2,
return Stack(
children: [
Card(
elevation: 0,
shape: RoundedRectangleBorder(
side: BorderSide(
color: Colors.grey.shade300,
width: 1,
),
borderRadius: BorderRadius.circular(8),
),
margin: EdgeInsets.zero,
child: ListTile(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
child: Text(
(activity.title?.isEmpty ?? true)
? 'unknown'.tr()
: activity.title!,
),
),
if (activity.titleUrl != null &&
activity.titleUrl!.isNotEmpty)
IconButton(
onPressed: () {
launchUrlString(activity.titleUrl!);
},
icon: const Icon(Symbols.launch_rounded),
iconSize: 16,
padding: EdgeInsets.all(4),
constraints: const BoxConstraints(
maxWidth: 28,
maxHeight: 28,
if (images.isNotEmpty)
Row(
crossAxisAlignment:
CrossAxisAlignment.end,
spacing: 8,
children: images,
).padding(vertical: 4),
Row(
spacing: 2,
children: [
Flexible(
child: Text(
(activity.title?.isEmpty ?? true)
? 'unknown'.tr()
: activity.title!,
),
),
),
if (activity.titleUrl != null &&
activity.titleUrl!.isNotEmpty)
IconButton(
onPressed: () {
launchUrlString(activity.titleUrl!);
},
icon: const Icon(
Symbols.launch_rounded,
),
iconSize: 16,
padding: EdgeInsets.all(4),
constraints: const BoxConstraints(
maxWidth: 28,
maxHeight: 28,
),
),
],
),
],
),
],
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
spacing: 4,
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
kPresenceActivityTypes[activity.type],
).tr(),
Icon(
kPresenceActivityIcons[activity.type],
size: 16,
fill: 1,
Row(
spacing: 4,
children: [
Text(
kPresenceActivityTypes[activity.type],
).tr(),
Icon(
kPresenceActivityIcons[activity.type],
size: 16,
fill: 1,
),
],
),
],
),
if (activity.manualId == 'spotify' &&
activity.meta != null)
StreamBuilder(
stream: Stream.periodic(
const Duration(seconds: 1),
),
builder: (context, snapshot) {
final now = DateTime.now();
final meta =
activity.meta as Map<String, dynamic>;
final progressMs =
meta['progress_ms'] as int? ?? 0;
final durationMs =
meta['track_duration_ms'] as int? ?? 1;
final elapsed =
now
.difference(activity.createdAt)
.inMilliseconds;
final currentProgressMs =
(progressMs + elapsed) % durationMs;
final progressValue =
currentProgressMs / durationMs;
if (progressValue != _endProgress) {
_startProgress = _endProgress;
_endProgress = progressValue;
_progressAnimation = Tween<double>(
begin: _startProgress,
end: _endProgress,
).animate(_progressController);
_progressController.forward(from: 0.0);
}
return AnimatedBuilder(
animation: _progressAnimation,
builder: (context, child) {
final animatedValue =
_progressAnimation.value;
final animatedProgressMs =
(animatedValue * durationMs)
.toInt();
final currentMin =
animatedProgressMs ~/ 60000;
final currentSec =
(animatedProgressMs % 60000) ~/
1000;
final totalMin = durationMs ~/ 60000;
final totalSec =
(durationMs % 60000) ~/ 1000;
return Column(
crossAxisAlignment:
CrossAxisAlignment.start,
spacing: 4,
children: [
LinearProgressIndicator(
value: animatedValue,
backgroundColor:
Colors.grey.shade300,
trackGap: 0,
stopIndicatorColor: Colors.green,
valueColor:
AlwaysStoppedAnimation<Color>(
Colors.green,
if (activity.manualId == 'spotify' &&
activity.meta != null)
StreamBuilder(
stream: Stream.periodic(
const Duration(seconds: 1),
),
builder: (context, snapshot) {
final now = DateTime.now();
final meta =
activity.meta
as Map<String, dynamic>;
final progressMs =
meta['progress_ms'] as int? ?? 0;
final durationMs =
meta['track_duration_ms'] as int? ??
1;
final elapsed =
now
.difference(activity.createdAt)
.inMilliseconds;
final currentProgressMs =
(progressMs + elapsed) % durationMs;
final progressValue =
currentProgressMs / durationMs;
if (progressValue != _endProgress) {
_startProgress = _endProgress;
_endProgress = progressValue;
_progressAnimation = Tween<double>(
begin: _startProgress,
end: _endProgress,
).animate(_progressController);
_progressController.forward(
from: 0.0,
);
}
return AnimatedBuilder(
animation: _progressAnimation,
builder: (context, child) {
final animatedValue =
_progressAnimation.value;
final animatedProgressMs =
(animatedValue * durationMs)
.toInt();
final currentMin =
animatedProgressMs ~/ 60000;
final currentSec =
(animatedProgressMs % 60000) ~/
1000;
final totalMin =
durationMs ~/ 60000;
final totalSec =
(durationMs % 60000) ~/ 1000;
return Column(
crossAxisAlignment:
CrossAxisAlignment.start,
spacing: 4,
children: [
LinearProgressIndicator(
value: animatedValue,
backgroundColor:
Colors.grey.shade300,
trackGap: 0,
stopIndicatorColor:
Colors.green,
valueColor:
AlwaysStoppedAnimation<
Color
>(Colors.green),
).padding(top: 3),
Text(
'${currentMin.toString().padLeft(2, '0')}:${currentSec.toString().padLeft(2, '0')} / ${totalMin.toString().padLeft(2, '0')}:${totalSec.toString().padLeft(2, '0')}',
style: TextStyle(
fontSize: 12,
color: Colors.green,
),
).padding(top: 3),
Text(
'${currentMin.toString().padLeft(2, '0')}:${currentSec.toString().padLeft(2, '0')} / ${totalMin.toString().padLeft(2, '0')}:${totalSec.toString().padLeft(2, '0')}',
style: TextStyle(
fontSize: 12,
color: Colors.green,
),
),
],
),
],
);
},
);
},
);
},
)
else
StreamBuilder(
stream: Stream.periodic(
const Duration(seconds: 1),
),
builder: (context, snapshot) {
final now = DateTime.now();
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);
},
),
if (activity.subtitle?.isNotEmpty ?? false)
Row(
spacing: 2,
children: [
Flexible(child: Text(activity.subtitle!)),
if (activity.titleUrl != null &&
activity.titleUrl!.isNotEmpty)
IconButton(
onPressed: () {
launchUrlString(activity.titleUrl!);
},
icon: const Icon(
Symbols.launch_rounded,
),
iconSize: 16,
padding: EdgeInsets.all(4),
constraints: const BoxConstraints(
maxWidth: 28,
maxHeight: 28,
),
)
else
StreamBuilder(
stream: Stream.periodic(
const Duration(seconds: 1),
),
],
builder: (context, snapshot) {
final now = DateTime.now();
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);
},
),
if (activity.subtitle?.isNotEmpty ?? false)
Row(
spacing: 2,
children: [
Flexible(
child: Text(activity.subtitle!),
),
if (activity.titleUrl != null &&
activity.titleUrl!.isNotEmpty)
IconButton(
onPressed: () {
launchUrlString(
activity.titleUrl!,
);
},
icon: const Icon(
Symbols.launch_rounded,
),
iconSize: 16,
padding: EdgeInsets.all(4),
constraints: const BoxConstraints(
maxWidth: 28,
maxHeight: 28,
),
),
],
),
if (activity.caption?.isNotEmpty ?? false)
Text(activity.caption!),
],
),
),
).padding(horizontal: 8),
if (activity.manualId == 'spotify')
Positioned(
top: 16,
right: 24,
child: Tooltip(
message: 'Listening on Spotify',
child: Image.asset(
'assets/images/oidc/spotify.png',
width: 24,
height: 24,
),
if (activity.caption?.isNotEmpty ?? false)
Text(activity.caption!),
],
),
),
).padding(horizontal: 8);
),
),
],
);
}),
],
).padding(horizontal: 8, top: 8, bottom: 16),