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