diff --git a/lib/widgets/account/activity_presence.dart b/lib/widgets/account/activity_presence.dart index be438921..8ce73a25 100644 --- a/lib/widgets/account/activity_presence.dart +++ b/lib/widgets/account/activity_presence.dart @@ -393,202 +393,234 @@ class _ActivityPresenceWidgetState extends State ], ).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; - 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( - 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( - 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; + 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( + 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),