Shows images, url from presense

This commit is contained in:
2025-11-02 00:03:16 +08:00
parent 3de73538c7
commit c093123e3a
5 changed files with 516 additions and 28 deletions

View File

@@ -84,6 +84,10 @@ sealed class SnPresenceActivity with _$SnPresenceActivity {
required String? title,
required String? subtitle,
required String? caption,
required String? titleUrl,
required String? subtitleUrl,
required String? smallImage,
required String? largeImage,
required Map<String, dynamic>? meta,
required int leaseMinutes,
required DateTime leaseExpiresAt,

View File

@@ -1429,7 +1429,7 @@ $SnCheckInResultCopyWith<$Res>? get checkInResult {
/// @nodoc
mixin _$SnPresenceActivity {
String get id; int get type; String? get manualId; String? get title; String? get subtitle; String? get caption; Map<String, dynamic>? get meta; int get leaseMinutes; DateTime get leaseExpiresAt; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
String get id; int get type; String? get manualId; String? get title; String? get subtitle; String? get caption; String? get titleUrl; String? get subtitleUrl; String? get smallImage; String? get largeImage; Map<String, dynamic>? get meta; int get leaseMinutes; DateTime get leaseExpiresAt; String get accountId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnPresenceActivity
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -1442,16 +1442,16 @@ $SnPresenceActivityCopyWith<SnPresenceActivity> get copyWith => _$SnPresenceActi
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPresenceActivity&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.manualId, manualId) || other.manualId == manualId)&&(identical(other.title, title) || other.title == title)&&(identical(other.subtitle, subtitle) || other.subtitle == subtitle)&&(identical(other.caption, caption) || other.caption == caption)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.leaseMinutes, leaseMinutes) || other.leaseMinutes == leaseMinutes)&&(identical(other.leaseExpiresAt, leaseExpiresAt) || other.leaseExpiresAt == leaseExpiresAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPresenceActivity&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.manualId, manualId) || other.manualId == manualId)&&(identical(other.title, title) || other.title == title)&&(identical(other.subtitle, subtitle) || other.subtitle == subtitle)&&(identical(other.caption, caption) || other.caption == caption)&&(identical(other.titleUrl, titleUrl) || other.titleUrl == titleUrl)&&(identical(other.subtitleUrl, subtitleUrl) || other.subtitleUrl == subtitleUrl)&&(identical(other.smallImage, smallImage) || other.smallImage == smallImage)&&(identical(other.largeImage, largeImage) || other.largeImage == largeImage)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.leaseMinutes, leaseMinutes) || other.leaseMinutes == leaseMinutes)&&(identical(other.leaseExpiresAt, leaseExpiresAt) || other.leaseExpiresAt == leaseExpiresAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,type,manualId,title,subtitle,caption,const DeepCollectionEquality().hash(meta),leaseMinutes,leaseExpiresAt,accountId,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hash(runtimeType,id,type,manualId,title,subtitle,caption,titleUrl,subtitleUrl,smallImage,largeImage,const DeepCollectionEquality().hash(meta),leaseMinutes,leaseExpiresAt,accountId,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnPresenceActivity(id: $id, type: $type, manualId: $manualId, title: $title, subtitle: $subtitle, caption: $caption, meta: $meta, leaseMinutes: $leaseMinutes, leaseExpiresAt: $leaseExpiresAt, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnPresenceActivity(id: $id, type: $type, manualId: $manualId, title: $title, subtitle: $subtitle, caption: $caption, titleUrl: $titleUrl, subtitleUrl: $subtitleUrl, smallImage: $smallImage, largeImage: $largeImage, meta: $meta, leaseMinutes: $leaseMinutes, leaseExpiresAt: $leaseExpiresAt, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
@@ -1462,7 +1462,7 @@ abstract mixin class $SnPresenceActivityCopyWith<$Res> {
factory $SnPresenceActivityCopyWith(SnPresenceActivity value, $Res Function(SnPresenceActivity) _then) = _$SnPresenceActivityCopyWithImpl;
@useResult
$Res call({
String id, int type, String? manualId, String? title, String? subtitle, String? caption, Map<String, dynamic>? meta, int leaseMinutes, DateTime leaseExpiresAt, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, int type, String? manualId, String? title, String? subtitle, String? caption, String? titleUrl, String? subtitleUrl, String? smallImage, String? largeImage, Map<String, dynamic>? meta, int leaseMinutes, DateTime leaseExpiresAt, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@@ -1479,7 +1479,7 @@ class _$SnPresenceActivityCopyWithImpl<$Res>
/// Create a copy of SnPresenceActivity
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? manualId = freezed,Object? title = freezed,Object? subtitle = freezed,Object? caption = freezed,Object? meta = freezed,Object? leaseMinutes = null,Object? leaseExpiresAt = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? manualId = freezed,Object? title = freezed,Object? subtitle = freezed,Object? caption = freezed,Object? titleUrl = freezed,Object? subtitleUrl = freezed,Object? smallImage = freezed,Object? largeImage = freezed,Object? meta = freezed,Object? leaseMinutes = null,Object? leaseExpiresAt = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
@@ -1487,6 +1487,10 @@ as int,manualId: freezed == manualId ? _self.manualId : manualId // ignore: cast
as String?,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String?,subtitle: freezed == subtitle ? _self.subtitle : subtitle // ignore: cast_nullable_to_non_nullable
as String?,caption: freezed == caption ? _self.caption : caption // ignore: cast_nullable_to_non_nullable
as String?,titleUrl: freezed == titleUrl ? _self.titleUrl : titleUrl // ignore: cast_nullable_to_non_nullable
as String?,subtitleUrl: freezed == subtitleUrl ? _self.subtitleUrl : subtitleUrl // ignore: cast_nullable_to_non_nullable
as String?,smallImage: freezed == smallImage ? _self.smallImage : smallImage // ignore: cast_nullable_to_non_nullable
as String?,largeImage: freezed == largeImage ? _self.largeImage : largeImage // ignore: cast_nullable_to_non_nullable
as String?,meta: freezed == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,leaseMinutes: null == leaseMinutes ? _self.leaseMinutes : leaseMinutes // ignore: cast_nullable_to_non_nullable
as int,leaseExpiresAt: null == leaseExpiresAt ? _self.leaseExpiresAt : leaseExpiresAt // ignore: cast_nullable_to_non_nullable
@@ -1576,10 +1580,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, int type, String? manualId, String? title, String? subtitle, String? caption, Map<String, dynamic>? meta, int leaseMinutes, DateTime leaseExpiresAt, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, int type, String? manualId, String? title, String? subtitle, String? caption, String? titleUrl, String? subtitleUrl, String? smallImage, String? largeImage, Map<String, dynamic>? meta, int leaseMinutes, DateTime leaseExpiresAt, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnPresenceActivity() when $default != null:
return $default(_that.id,_that.type,_that.manualId,_that.title,_that.subtitle,_that.caption,_that.meta,_that.leaseMinutes,_that.leaseExpiresAt,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return $default(_that.id,_that.type,_that.manualId,_that.title,_that.subtitle,_that.caption,_that.titleUrl,_that.subtitleUrl,_that.smallImage,_that.largeImage,_that.meta,_that.leaseMinutes,_that.leaseExpiresAt,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return orElse();
}
@@ -1597,10 +1601,10 @@ return $default(_that.id,_that.type,_that.manualId,_that.title,_that.subtitle,_t
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, int type, String? manualId, String? title, String? subtitle, String? caption, Map<String, dynamic>? meta, int leaseMinutes, DateTime leaseExpiresAt, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, int type, String? manualId, String? title, String? subtitle, String? caption, String? titleUrl, String? subtitleUrl, String? smallImage, String? largeImage, Map<String, dynamic>? meta, int leaseMinutes, DateTime leaseExpiresAt, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
switch (_that) {
case _SnPresenceActivity():
return $default(_that.id,_that.type,_that.manualId,_that.title,_that.subtitle,_that.caption,_that.meta,_that.leaseMinutes,_that.leaseExpiresAt,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);}
return $default(_that.id,_that.type,_that.manualId,_that.title,_that.subtitle,_that.caption,_that.titleUrl,_that.subtitleUrl,_that.smallImage,_that.largeImage,_that.meta,_that.leaseMinutes,_that.leaseExpiresAt,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);}
}
/// A variant of `when` that fallback to returning `null`
///
@@ -1614,10 +1618,10 @@ return $default(_that.id,_that.type,_that.manualId,_that.title,_that.subtitle,_t
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, int type, String? manualId, String? title, String? subtitle, String? caption, Map<String, dynamic>? meta, int leaseMinutes, DateTime leaseExpiresAt, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, int type, String? manualId, String? title, String? subtitle, String? caption, String? titleUrl, String? subtitleUrl, String? smallImage, String? largeImage, Map<String, dynamic>? meta, int leaseMinutes, DateTime leaseExpiresAt, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
switch (_that) {
case _SnPresenceActivity() when $default != null:
return $default(_that.id,_that.type,_that.manualId,_that.title,_that.subtitle,_that.caption,_that.meta,_that.leaseMinutes,_that.leaseExpiresAt,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return $default(_that.id,_that.type,_that.manualId,_that.title,_that.subtitle,_that.caption,_that.titleUrl,_that.subtitleUrl,_that.smallImage,_that.largeImage,_that.meta,_that.leaseMinutes,_that.leaseExpiresAt,_that.accountId,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return null;
}
@@ -1629,7 +1633,7 @@ return $default(_that.id,_that.type,_that.manualId,_that.title,_that.subtitle,_t
@JsonSerializable()
class _SnPresenceActivity implements SnPresenceActivity {
const _SnPresenceActivity({required this.id, required this.type, required this.manualId, required this.title, required this.subtitle, required this.caption, required final Map<String, dynamic>? meta, required this.leaseMinutes, required this.leaseExpiresAt, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt}): _meta = meta;
const _SnPresenceActivity({required this.id, required this.type, required this.manualId, required this.title, required this.subtitle, required this.caption, required this.titleUrl, required this.subtitleUrl, required this.smallImage, required this.largeImage, required final Map<String, dynamic>? meta, required this.leaseMinutes, required this.leaseExpiresAt, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt}): _meta = meta;
factory _SnPresenceActivity.fromJson(Map<String, dynamic> json) => _$SnPresenceActivityFromJson(json);
@override final String id;
@@ -1638,6 +1642,10 @@ class _SnPresenceActivity implements SnPresenceActivity {
@override final String? title;
@override final String? subtitle;
@override final String? caption;
@override final String? titleUrl;
@override final String? subtitleUrl;
@override final String? smallImage;
@override final String? largeImage;
final Map<String, dynamic>? _meta;
@override Map<String, dynamic>? get meta {
final value = _meta;
@@ -1667,16 +1675,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPresenceActivity&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.manualId, manualId) || other.manualId == manualId)&&(identical(other.title, title) || other.title == title)&&(identical(other.subtitle, subtitle) || other.subtitle == subtitle)&&(identical(other.caption, caption) || other.caption == caption)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.leaseMinutes, leaseMinutes) || other.leaseMinutes == leaseMinutes)&&(identical(other.leaseExpiresAt, leaseExpiresAt) || other.leaseExpiresAt == leaseExpiresAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPresenceActivity&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.manualId, manualId) || other.manualId == manualId)&&(identical(other.title, title) || other.title == title)&&(identical(other.subtitle, subtitle) || other.subtitle == subtitle)&&(identical(other.caption, caption) || other.caption == caption)&&(identical(other.titleUrl, titleUrl) || other.titleUrl == titleUrl)&&(identical(other.subtitleUrl, subtitleUrl) || other.subtitleUrl == subtitleUrl)&&(identical(other.smallImage, smallImage) || other.smallImage == smallImage)&&(identical(other.largeImage, largeImage) || other.largeImage == largeImage)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.leaseMinutes, leaseMinutes) || other.leaseMinutes == leaseMinutes)&&(identical(other.leaseExpiresAt, leaseExpiresAt) || other.leaseExpiresAt == leaseExpiresAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,type,manualId,title,subtitle,caption,const DeepCollectionEquality().hash(_meta),leaseMinutes,leaseExpiresAt,accountId,createdAt,updatedAt,deletedAt);
int get hashCode => Object.hash(runtimeType,id,type,manualId,title,subtitle,caption,titleUrl,subtitleUrl,smallImage,largeImage,const DeepCollectionEquality().hash(_meta),leaseMinutes,leaseExpiresAt,accountId,createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnPresenceActivity(id: $id, type: $type, manualId: $manualId, title: $title, subtitle: $subtitle, caption: $caption, meta: $meta, leaseMinutes: $leaseMinutes, leaseExpiresAt: $leaseExpiresAt, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnPresenceActivity(id: $id, type: $type, manualId: $manualId, title: $title, subtitle: $subtitle, caption: $caption, titleUrl: $titleUrl, subtitleUrl: $subtitleUrl, smallImage: $smallImage, largeImage: $largeImage, meta: $meta, leaseMinutes: $leaseMinutes, leaseExpiresAt: $leaseExpiresAt, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
@@ -1687,7 +1695,7 @@ abstract mixin class _$SnPresenceActivityCopyWith<$Res> implements $SnPresenceAc
factory _$SnPresenceActivityCopyWith(_SnPresenceActivity value, $Res Function(_SnPresenceActivity) _then) = __$SnPresenceActivityCopyWithImpl;
@override @useResult
$Res call({
String id, int type, String? manualId, String? title, String? subtitle, String? caption, Map<String, dynamic>? meta, int leaseMinutes, DateTime leaseExpiresAt, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
String id, int type, String? manualId, String? title, String? subtitle, String? caption, String? titleUrl, String? subtitleUrl, String? smallImage, String? largeImage, Map<String, dynamic>? meta, int leaseMinutes, DateTime leaseExpiresAt, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@@ -1704,7 +1712,7 @@ class __$SnPresenceActivityCopyWithImpl<$Res>
/// Create a copy of SnPresenceActivity
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? manualId = freezed,Object? title = freezed,Object? subtitle = freezed,Object? caption = freezed,Object? meta = freezed,Object? leaseMinutes = null,Object? leaseExpiresAt = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? manualId = freezed,Object? title = freezed,Object? subtitle = freezed,Object? caption = freezed,Object? titleUrl = freezed,Object? subtitleUrl = freezed,Object? smallImage = freezed,Object? largeImage = freezed,Object? meta = freezed,Object? leaseMinutes = null,Object? leaseExpiresAt = null,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnPresenceActivity(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
@@ -1712,6 +1720,10 @@ as int,manualId: freezed == manualId ? _self.manualId : manualId // ignore: cast
as String?,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String?,subtitle: freezed == subtitle ? _self.subtitle : subtitle // ignore: cast_nullable_to_non_nullable
as String?,caption: freezed == caption ? _self.caption : caption // ignore: cast_nullable_to_non_nullable
as String?,titleUrl: freezed == titleUrl ? _self.titleUrl : titleUrl // ignore: cast_nullable_to_non_nullable
as String?,subtitleUrl: freezed == subtitleUrl ? _self.subtitleUrl : subtitleUrl // ignore: cast_nullable_to_non_nullable
as String?,smallImage: freezed == smallImage ? _self.smallImage : smallImage // ignore: cast_nullable_to_non_nullable
as String?,largeImage: freezed == largeImage ? _self.largeImage : largeImage // ignore: cast_nullable_to_non_nullable
as String?,meta: freezed == meta ? _self._meta : meta // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,leaseMinutes: null == leaseMinutes ? _self.leaseMinutes : leaseMinutes // ignore: cast_nullable_to_non_nullable
as int,leaseExpiresAt: null == leaseExpiresAt ? _self.leaseExpiresAt : leaseExpiresAt // ignore: cast_nullable_to_non_nullable

View File

@@ -131,6 +131,10 @@ _SnPresenceActivity _$SnPresenceActivityFromJson(Map<String, dynamic> json) =>
title: json['title'] as String?,
subtitle: json['subtitle'] as String?,
caption: json['caption'] as String?,
titleUrl: json['title_url'] as String?,
subtitleUrl: json['subtitle_url'] as String?,
smallImage: json['small_image'] as String?,
largeImage: json['large_image'] as String?,
meta: json['meta'] as Map<String, dynamic>?,
leaseMinutes: (json['lease_minutes'] as num).toInt(),
leaseExpiresAt: DateTime.parse(json['lease_expires_at'] as String),
@@ -151,6 +155,10 @@ Map<String, dynamic> _$SnPresenceActivityToJson(_SnPresenceActivity instance) =>
'title': instance.title,
'subtitle': instance.subtitle,
'caption': instance.caption,
'title_url': instance.titleUrl,
'subtitle_url': instance.subtitleUrl,
'small_image': instance.smallImage,
'large_image': instance.largeImage,
'meta': instance.meta,
'lease_minutes': instance.leaseMinutes,
'lease_expires_at': instance.leaseExpiresAt.toIso8601String(),

View File

@@ -1,9 +1,56 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/activity.dart';
import 'package:island/pods/activity/activity_rpc.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:url_launcher/url_launcher_string.dart';
part 'activity_presence.g.dart';
@riverpod
Future<Map<String, String>?> discordAssets(
Ref ref,
SnPresenceActivity activity,
) async {
final hasDiscordSmall =
activity.smallImage != null &&
activity.smallImage!.startsWith('discord:');
final hasDiscordLarge =
activity.largeImage != null &&
activity.largeImage!.startsWith('discord:');
if (hasDiscordSmall || hasDiscordLarge) {
final dio = Dio();
final response = await dio.get(
'https://discordapp.com/api/oauth2/applications/${activity.manualId}/assets',
);
final data = response.data as List<dynamic>;
return {
for (final item in data) item['name'] as String: item['id'] as String,
};
}
return null;
}
@riverpod
Future<String?> discordAssetsUrl(
Ref ref,
SnPresenceActivity activity,
String key,
) async {
final assets = await ref.watch(discordAssetsProvider(activity).future);
if (assets != null && assets.containsKey(key)) {
final assetId = assets[key]!;
return 'https://cdn.discordapp.com/app-assets/${activity.manualId}/$assetId.png';
}
return null;
}
const kPresenseActivityTypes = [
'unknown',
@@ -12,11 +59,82 @@ const kPresenseActivityTypes = [
'presenceTypeWorkout',
];
const kPresenseActivityIcons = <IconData>[
Symbols.question_mark_rounded,
Symbols.play_arrow_rounded,
Symbols.music_note_rounded,
Symbols.running_with_errors,
];
class ActivityPresenceWidget extends ConsumerWidget {
final String uname;
const ActivityPresenceWidget({super.key, required this.uname});
List<Widget> _buildDiscordImages(WidgetRef ref, SnPresenceActivity activity) {
final List<Widget> images = [];
if (activity.largeImage != null &&
activity.largeImage!.startsWith('discord:')) {
final key = activity.largeImage!.substring('discord:'.length);
final urlAsync = ref.watch(discordAssetsUrlProvider(activity, key));
images.add(
urlAsync.when(
data:
(url) =>
url != null
? ClipRRect(
borderRadius: BorderRadius.circular(8),
child: CachedNetworkImage(
imageUrl: url,
width: 64,
height: 64,
),
)
: const SizedBox.shrink(),
loading:
() => const SizedBox(
width: 64,
height: 64,
child: CircularProgressIndicator(strokeWidth: 2),
),
error: (error, stack) => const SizedBox.shrink(),
),
);
}
if (activity.smallImage != null &&
activity.smallImage!.startsWith('discord:')) {
final key = activity.smallImage!.substring('discord:'.length);
final urlAsync = ref.watch(discordAssetsUrlProvider(activity, key));
images.add(
urlAsync.when(
data:
(url) =>
url != null
? ClipRRect(
borderRadius: BorderRadius.circular(8),
child: CachedNetworkImage(
imageUrl: url,
width: 32,
height: 32,
),
)
: const SizedBox.shrink(),
loading:
() => const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
),
error: (error, stack) => const SizedBox.shrink(),
),
);
}
return images;
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final activitiesAsync = ref.watch(presenceActivitiesProvider(uname));
@@ -40,8 +158,10 @@ class ActivityPresenceWidget extends ConsumerWidget {
Text('dataEmpty').tr().fontSize(13),
],
).opacity(0.75).padding(horizontal: 8),
...activities.map(
(activity) => Card(
...activities.map((activity) {
final dcImages = _buildDiscordImages(ref, activity);
return Card(
elevation: 0,
shape: RoundedRectangleBorder(
side: BorderSide(color: Colors.grey.shade300, width: 1),
@@ -49,19 +169,49 @@ class ActivityPresenceWidget extends ConsumerWidget {
),
margin: EdgeInsets.zero,
child: ListTile(
title: Text(
(activity.title?.isEmpty ?? true)
? 'Untitled Activity'
: activity.title!,
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (dcImages.isNotEmpty)
Row(
crossAxisAlignment: CrossAxisAlignment.end,
spacing: 8,
children: dcImages,
).padding(vertical: 4),
Text(
(activity.title?.isEmpty ?? true)
? 'unknown'.tr()
: activity.title!,
),
],
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(kPresenseActivityTypes[activity.type]).tr(),
Row(
spacing: 4,
children: [
Text(kPresenseActivityTypes[activity.type]).tr(),
Icon(
kPresenseActivityIcons[activity.type],
size: 16,
fill: 1,
),
],
),
StreamBuilder(
stream: Stream.periodic(const Duration(seconds: 1)),
builder: (context, snapshot) {
final duration = DateTime.now().difference(
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(
@@ -83,11 +233,38 @@ class ActivityPresenceWidget extends ConsumerWidget {
Text(activity.subtitle!),
if (activity.caption?.isNotEmpty ?? false)
Text(activity.caption!),
if ((activity.titleUrl?.isNotEmpty ?? false) ||
(activity.subtitleUrl?.isNotEmpty ?? false))
Row(
spacing: 8,
children: [
if (activity.titleUrl != null && activity.titleUrl!.isNotEmpty)
ElevatedButton.icon(
onPressed: () => launchUrlString(activity.titleUrl!),
icon: const Icon(Symbols.link, size: 16),
label: const Text('Open Title Link'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
textStyle: const TextStyle(fontSize: 12),
),
),
if (activity.subtitleUrl != null && activity.subtitleUrl!.isNotEmpty)
ElevatedButton.icon(
onPressed: () => launchUrlString(activity.subtitleUrl!),
icon: const Icon(Symbols.link, size: 16),
label: const Text('Open Subtitle Link'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
textStyle: const TextStyle(fontSize: 12),
),
),
],
),
],
),
),
),
),
);
}),
],
).padding(all: 8),
),

View File

@@ -0,0 +1,287 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'activity_presence.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$discordAssetsHash() => r'3ef8465188059de96cf2ac9660ed3d88910443bf';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
/// See also [discordAssets].
@ProviderFor(discordAssets)
const discordAssetsProvider = DiscordAssetsFamily();
/// See also [discordAssets].
class DiscordAssetsFamily extends Family<AsyncValue<Map<String, String>?>> {
/// See also [discordAssets].
const DiscordAssetsFamily();
/// See also [discordAssets].
DiscordAssetsProvider call(SnPresenceActivity activity) {
return DiscordAssetsProvider(activity);
}
@override
DiscordAssetsProvider getProviderOverride(
covariant DiscordAssetsProvider provider,
) {
return call(provider.activity);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'discordAssetsProvider';
}
/// See also [discordAssets].
class DiscordAssetsProvider
extends AutoDisposeFutureProvider<Map<String, String>?> {
/// See also [discordAssets].
DiscordAssetsProvider(SnPresenceActivity activity)
: this._internal(
(ref) => discordAssets(ref as DiscordAssetsRef, activity),
from: discordAssetsProvider,
name: r'discordAssetsProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$discordAssetsHash,
dependencies: DiscordAssetsFamily._dependencies,
allTransitiveDependencies:
DiscordAssetsFamily._allTransitiveDependencies,
activity: activity,
);
DiscordAssetsProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.activity,
}) : super.internal();
final SnPresenceActivity activity;
@override
Override overrideWith(
FutureOr<Map<String, String>?> Function(DiscordAssetsRef provider) create,
) {
return ProviderOverride(
origin: this,
override: DiscordAssetsProvider._internal(
(ref) => create(ref as DiscordAssetsRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
activity: activity,
),
);
}
@override
AutoDisposeFutureProviderElement<Map<String, String>?> createElement() {
return _DiscordAssetsProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is DiscordAssetsProvider && other.activity == activity;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, activity.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin DiscordAssetsRef on AutoDisposeFutureProviderRef<Map<String, String>?> {
/// The parameter `activity` of this provider.
SnPresenceActivity get activity;
}
class _DiscordAssetsProviderElement
extends AutoDisposeFutureProviderElement<Map<String, String>?>
with DiscordAssetsRef {
_DiscordAssetsProviderElement(super.provider);
@override
SnPresenceActivity get activity => (origin as DiscordAssetsProvider).activity;
}
String _$discordAssetsUrlHash() => r'a32f9333c3fb4d50ff88a54a6b8b72fbf5ba3ea1';
/// See also [discordAssetsUrl].
@ProviderFor(discordAssetsUrl)
const discordAssetsUrlProvider = DiscordAssetsUrlFamily();
/// See also [discordAssetsUrl].
class DiscordAssetsUrlFamily extends Family<AsyncValue<String?>> {
/// See also [discordAssetsUrl].
const DiscordAssetsUrlFamily();
/// See also [discordAssetsUrl].
DiscordAssetsUrlProvider call(SnPresenceActivity activity, String key) {
return DiscordAssetsUrlProvider(activity, key);
}
@override
DiscordAssetsUrlProvider getProviderOverride(
covariant DiscordAssetsUrlProvider provider,
) {
return call(provider.activity, provider.key);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'discordAssetsUrlProvider';
}
/// See also [discordAssetsUrl].
class DiscordAssetsUrlProvider extends AutoDisposeFutureProvider<String?> {
/// See also [discordAssetsUrl].
DiscordAssetsUrlProvider(SnPresenceActivity activity, String key)
: this._internal(
(ref) => discordAssetsUrl(ref as DiscordAssetsUrlRef, activity, key),
from: discordAssetsUrlProvider,
name: r'discordAssetsUrlProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$discordAssetsUrlHash,
dependencies: DiscordAssetsUrlFamily._dependencies,
allTransitiveDependencies:
DiscordAssetsUrlFamily._allTransitiveDependencies,
activity: activity,
key: key,
);
DiscordAssetsUrlProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.activity,
required this.key,
}) : super.internal();
final SnPresenceActivity activity;
final String key;
@override
Override overrideWith(
FutureOr<String?> Function(DiscordAssetsUrlRef provider) create,
) {
return ProviderOverride(
origin: this,
override: DiscordAssetsUrlProvider._internal(
(ref) => create(ref as DiscordAssetsUrlRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
activity: activity,
key: key,
),
);
}
@override
AutoDisposeFutureProviderElement<String?> createElement() {
return _DiscordAssetsUrlProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is DiscordAssetsUrlProvider &&
other.activity == activity &&
other.key == key;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, activity.hashCode);
hash = _SystemHash.combine(hash, key.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin DiscordAssetsUrlRef on AutoDisposeFutureProviderRef<String?> {
/// The parameter `activity` of this provider.
SnPresenceActivity get activity;
/// The parameter `key` of this provider.
String get key;
}
class _DiscordAssetsUrlProviderElement
extends AutoDisposeFutureProviderElement<String?>
with DiscordAssetsUrlRef {
_DiscordAssetsUrlProviderElement(super.provider);
@override
SnPresenceActivity get activity =>
(origin as DiscordAssetsUrlProvider).activity;
@override
String get key => (origin as DiscordAssetsUrlProvider).key;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package