💄 Shows listening activities are from spotfiy
This commit is contained in:
@@ -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),
|
||||||
|
|||||||
Reference in New Issue
Block a user