Refined presense activity

This commit is contained in:
2025-11-01 21:47:34 +08:00
parent 5ee2e70442
commit ba8d5cee09
11 changed files with 421 additions and 239 deletions

View File

@@ -1302,5 +1302,9 @@
"aiThought": "AI Thought",
"aiThoughtTitle": "Let sn-chan think",
"postReferenceUnavailable": "Referenced post is unavailable",
"fabLocation": "FAB Location"
"fabLocation": "FAB Location",
"activities": "Activities",
"presenceTypeGaming": "Playing",
"presenceTypeMusic": "Listening to Music",
"presenceTypeWorkout": "Working out"
}

View File

@@ -19,8 +19,8 @@ sealed class SnNotableDay with _$SnNotableDay {
}
@freezed
sealed class SnActivity with _$SnActivity {
const factory SnActivity({
sealed class SnTimelineEvent with _$SnTimelineEvent {
const factory SnTimelineEvent({
required String id,
required String type,
required String resourceIdentifier,
@@ -28,10 +28,10 @@ sealed class SnActivity with _$SnActivity {
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
}) = _SnActivity;
}) = _SnTimelineEvent;
factory SnActivity.fromJson(Map<String, dynamic> json) =>
_$SnActivityFromJson(json);
factory SnTimelineEvent.fromJson(Map<String, dynamic> json) =>
_$SnTimelineEventFromJson(json);
}
@freezed
@@ -79,7 +79,7 @@ sealed class SnEventCalendarEntry with _$SnEventCalendarEntry {
sealed class SnPresenceActivity with _$SnPresenceActivity {
const factory SnPresenceActivity({
required String id,
required String type,
required int type,
required String? manualId,
required String? title,
required String? subtitle,

View File

@@ -288,22 +288,22 @@ as List<int>,
/// @nodoc
mixin _$SnActivity {
mixin _$SnTimelineEvent {
String get id; String get type; String get resourceIdentifier; dynamic get data; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnActivity
/// Create a copy of SnTimelineEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SnActivityCopyWith<SnActivity> get copyWith => _$SnActivityCopyWithImpl<SnActivity>(this as SnActivity, _$identity);
$SnTimelineEventCopyWith<SnTimelineEvent> get copyWith => _$SnTimelineEventCopyWithImpl<SnTimelineEvent>(this as SnTimelineEvent, _$identity);
/// Serializes this SnActivity to a JSON map.
/// Serializes this SnTimelineEvent to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnActivity&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&const DeepCollectionEquality().equals(other.data, data)&&(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 SnTimelineEvent&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&const DeepCollectionEquality().equals(other.data, data)&&(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)
@@ -312,15 +312,15 @@ int get hashCode => Object.hash(runtimeType,id,type,resourceIdentifier,const Dee
@override
String toString() {
return 'SnActivity(id: $id, type: $type, resourceIdentifier: $resourceIdentifier, data: $data, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnTimelineEvent(id: $id, type: $type, resourceIdentifier: $resourceIdentifier, data: $data, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
abstract mixin class $SnActivityCopyWith<$Res> {
factory $SnActivityCopyWith(SnActivity value, $Res Function(SnActivity) _then) = _$SnActivityCopyWithImpl;
abstract mixin class $SnTimelineEventCopyWith<$Res> {
factory $SnTimelineEventCopyWith(SnTimelineEvent value, $Res Function(SnTimelineEvent) _then) = _$SnTimelineEventCopyWithImpl;
@useResult
$Res call({
String id, String type, String resourceIdentifier, dynamic data, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
@@ -331,14 +331,14 @@ $Res call({
}
/// @nodoc
class _$SnActivityCopyWithImpl<$Res>
implements $SnActivityCopyWith<$Res> {
_$SnActivityCopyWithImpl(this._self, this._then);
class _$SnTimelineEventCopyWithImpl<$Res>
implements $SnTimelineEventCopyWith<$Res> {
_$SnTimelineEventCopyWithImpl(this._self, this._then);
final SnActivity _self;
final $Res Function(SnActivity) _then;
final SnTimelineEvent _self;
final $Res Function(SnTimelineEvent) _then;
/// Create a copy of SnActivity
/// Create a copy of SnTimelineEvent
/// 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? resourceIdentifier = null,Object? data = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith(
@@ -356,8 +356,8 @@ as DateTime?,
}
/// Adds pattern-matching-related methods to [SnActivity].
extension SnActivityPatterns on SnActivity {
/// Adds pattern-matching-related methods to [SnTimelineEvent].
extension SnTimelineEventPatterns on SnTimelineEvent {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
@@ -370,10 +370,10 @@ extension SnActivityPatterns on SnActivity {
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnActivity value)? $default,{required TResult orElse(),}){
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnTimelineEvent value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _SnActivity() when $default != null:
case _SnTimelineEvent() when $default != null:
return $default(_that);case _:
return orElse();
@@ -392,10 +392,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnActivity value) $default,){
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnTimelineEvent value) $default,){
final _that = this;
switch (_that) {
case _SnActivity():
case _SnTimelineEvent():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
@@ -410,10 +410,10 @@ return $default(_that);}
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnActivity value)? $default,){
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnTimelineEvent value)? $default,){
final _that = this;
switch (_that) {
case _SnActivity() when $default != null:
case _SnTimelineEvent() when $default != null:
return $default(_that);case _:
return null;
@@ -433,7 +433,7 @@ return $default(_that);case _:
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String type, String resourceIdentifier, dynamic data, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnActivity() when $default != null:
case _SnTimelineEvent() when $default != null:
return $default(_that.id,_that.type,_that.resourceIdentifier,_that.data,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return orElse();
@@ -454,7 +454,7 @@ return $default(_that.id,_that.type,_that.resourceIdentifier,_that.data,_that.cr
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String type, String resourceIdentifier, dynamic data, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt) $default,) {final _that = this;
switch (_that) {
case _SnActivity():
case _SnTimelineEvent():
return $default(_that.id,_that.type,_that.resourceIdentifier,_that.data,_that.createdAt,_that.updatedAt,_that.deletedAt);}
}
/// A variant of `when` that fallback to returning `null`
@@ -471,7 +471,7 @@ return $default(_that.id,_that.type,_that.resourceIdentifier,_that.data,_that.cr
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String type, String resourceIdentifier, dynamic data, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt)? $default,) {final _that = this;
switch (_that) {
case _SnActivity() when $default != null:
case _SnTimelineEvent() when $default != null:
return $default(_that.id,_that.type,_that.resourceIdentifier,_that.data,_that.createdAt,_that.updatedAt,_that.deletedAt);case _:
return null;
@@ -483,9 +483,9 @@ return $default(_that.id,_that.type,_that.resourceIdentifier,_that.data,_that.cr
/// @nodoc
@JsonSerializable()
class _SnActivity implements SnActivity {
const _SnActivity({required this.id, required this.type, required this.resourceIdentifier, required this.data, required this.createdAt, required this.updatedAt, required this.deletedAt});
factory _SnActivity.fromJson(Map<String, dynamic> json) => _$SnActivityFromJson(json);
class _SnTimelineEvent implements SnTimelineEvent {
const _SnTimelineEvent({required this.id, required this.type, required this.resourceIdentifier, required this.data, required this.createdAt, required this.updatedAt, required this.deletedAt});
factory _SnTimelineEvent.fromJson(Map<String, dynamic> json) => _$SnTimelineEventFromJson(json);
@override final String id;
@override final String type;
@@ -495,20 +495,20 @@ class _SnActivity implements SnActivity {
@override final DateTime updatedAt;
@override final DateTime? deletedAt;
/// Create a copy of SnActivity
/// Create a copy of SnTimelineEvent
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SnActivityCopyWith<_SnActivity> get copyWith => __$SnActivityCopyWithImpl<_SnActivity>(this, _$identity);
_$SnTimelineEventCopyWith<_SnTimelineEvent> get copyWith => __$SnTimelineEventCopyWithImpl<_SnTimelineEvent>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnActivityToJson(this, );
return _$SnTimelineEventToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnActivity&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&const DeepCollectionEquality().equals(other.data, data)&&(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 _SnTimelineEvent&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.resourceIdentifier, resourceIdentifier) || other.resourceIdentifier == resourceIdentifier)&&const DeepCollectionEquality().equals(other.data, data)&&(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)
@@ -517,15 +517,15 @@ int get hashCode => Object.hash(runtimeType,id,type,resourceIdentifier,const Dee
@override
String toString() {
return 'SnActivity(id: $id, type: $type, resourceIdentifier: $resourceIdentifier, data: $data, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
return 'SnTimelineEvent(id: $id, type: $type, resourceIdentifier: $resourceIdentifier, data: $data, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
abstract mixin class _$SnActivityCopyWith<$Res> implements $SnActivityCopyWith<$Res> {
factory _$SnActivityCopyWith(_SnActivity value, $Res Function(_SnActivity) _then) = __$SnActivityCopyWithImpl;
abstract mixin class _$SnTimelineEventCopyWith<$Res> implements $SnTimelineEventCopyWith<$Res> {
factory _$SnTimelineEventCopyWith(_SnTimelineEvent value, $Res Function(_SnTimelineEvent) _then) = __$SnTimelineEventCopyWithImpl;
@override @useResult
$Res call({
String id, String type, String resourceIdentifier, dynamic data, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
@@ -536,17 +536,17 @@ $Res call({
}
/// @nodoc
class __$SnActivityCopyWithImpl<$Res>
implements _$SnActivityCopyWith<$Res> {
__$SnActivityCopyWithImpl(this._self, this._then);
class __$SnTimelineEventCopyWithImpl<$Res>
implements _$SnTimelineEventCopyWith<$Res> {
__$SnTimelineEventCopyWithImpl(this._self, this._then);
final _SnActivity _self;
final $Res Function(_SnActivity) _then;
final _SnTimelineEvent _self;
final $Res Function(_SnTimelineEvent) _then;
/// Create a copy of SnActivity
/// Create a copy of SnTimelineEvent
/// 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? resourceIdentifier = null,Object? data = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnActivity(
return _then(_SnTimelineEvent(
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
as String,resourceIdentifier: null == resourceIdentifier ? _self.resourceIdentifier : resourceIdentifier // ignore: cast_nullable_to_non_nullable
@@ -1429,7 +1429,7 @@ $SnCheckInResultCopyWith<$Res>? get checkInResult {
/// @nodoc
mixin _$SnPresenceActivity {
String get id; String 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; 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)
@@ -1462,7 +1462,7 @@ abstract mixin class $SnPresenceActivityCopyWith<$Res> {
factory $SnPresenceActivityCopyWith(SnPresenceActivity value, $Res Function(SnPresenceActivity) _then) = _$SnPresenceActivityCopyWithImpl;
@useResult
$Res call({
String id, String 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, Map<String, dynamic>? meta, int leaseMinutes, DateTime leaseExpiresAt, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@@ -1483,7 +1483,7 @@ class _$SnPresenceActivityCopyWithImpl<$Res>
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
as String,manualId: freezed == manualId ? _self.manualId : manualId // ignore: cast_nullable_to_non_nullable
as int,manualId: freezed == manualId ? _self.manualId : manualId // ignore: cast_nullable_to_non_nullable
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
@@ -1576,7 +1576,7 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String 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, 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 _:
@@ -1597,7 +1597,7 @@ return $default(_that.id,_that.type,_that.manualId,_that.title,_that.subtitle,_t
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String 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, 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);}
@@ -1614,7 +1614,7 @@ return $default(_that.id,_that.type,_that.manualId,_that.title,_that.subtitle,_t
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String 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, 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 _:
@@ -1633,7 +1633,7 @@ class _SnPresenceActivity implements SnPresenceActivity {
factory _SnPresenceActivity.fromJson(Map<String, dynamic> json) => _$SnPresenceActivityFromJson(json);
@override final String id;
@override final String type;
@override final int type;
@override final String? manualId;
@override final String? title;
@override final String? subtitle;
@@ -1687,7 +1687,7 @@ abstract mixin class _$SnPresenceActivityCopyWith<$Res> implements $SnPresenceAc
factory _$SnPresenceActivityCopyWith(_SnPresenceActivity value, $Res Function(_SnPresenceActivity) _then) = __$SnPresenceActivityCopyWithImpl;
@override @useResult
$Res call({
String id, String 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, Map<String, dynamic>? meta, int leaseMinutes, DateTime leaseExpiresAt, String accountId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@@ -1708,7 +1708,7 @@ class __$SnPresenceActivityCopyWithImpl<$Res>
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
as String,manualId: freezed == manualId ? _self.manualId : manualId // ignore: cast_nullable_to_non_nullable
as int,manualId: freezed == manualId ? _self.manualId : manualId // ignore: cast_nullable_to_non_nullable
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

View File

@@ -27,20 +27,21 @@ Map<String, dynamic> _$SnNotableDayToJson(_SnNotableDay instance) =>
'holidays': instance.holidays,
};
_SnActivity _$SnActivityFromJson(Map<String, dynamic> json) => _SnActivity(
id: json['id'] as String,
type: json['type'] as String,
resourceIdentifier: json['resource_identifier'] as String,
data: json['data'],
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt:
json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
);
_SnTimelineEvent _$SnTimelineEventFromJson(Map<String, dynamic> json) =>
_SnTimelineEvent(
id: json['id'] as String,
type: json['type'] as String,
resourceIdentifier: json['resource_identifier'] as String,
data: json['data'],
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt:
json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
);
Map<String, dynamic> _$SnActivityToJson(_SnActivity instance) =>
Map<String, dynamic> _$SnTimelineEventToJson(_SnTimelineEvent instance) =>
<String, dynamic>{
'id': instance.id,
'type': instance.type,
@@ -125,7 +126,7 @@ Map<String, dynamic> _$SnEventCalendarEntryToJson(
_SnPresenceActivity _$SnPresenceActivityFromJson(Map<String, dynamic> json) =>
_SnPresenceActivity(
id: json['id'] as String,
type: json['type'] as String,
type: (json['type'] as num).toInt(),
manualId: json['manual_id'] as String?,
title: json['title'] as String?,
subtitle: json['subtitle'] as String?,

View File

@@ -3,7 +3,6 @@ import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/account.dart';
import 'package:island/models/activity.dart';
import 'package:island/pods/network.dart';
import 'package:island/talker.dart';
@@ -14,11 +13,11 @@ import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:shelf_web_socket/shelf_web_socket.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
part 'activity_rpc.g.dart';
// Conditional imports for IPC server - use web stubs on web platform
import 'ipc_server.dart' if (dart.library.html) 'ipc_server.web.dart';
part 'activity_rpc.g.dart';
const String kRpcLogPrefix = 'arRPC.websocket';
const String kRpcIpcLogPrefix = 'arRPC.ipc';
@@ -125,7 +124,7 @@ class ActivityRpcServer {
talker.log('[$kRpcLogPrefix] IPC server error: $e');
}
} else {
talker.log('IPC server disabled on macOS or web in production mode');
talker.log('IPC server disabled on macOS or web');
}
}
@@ -326,6 +325,8 @@ class ServerStateNotifier extends StateNotifier<ServerState> {
ServerStateNotifier(this.server)
: super(ServerState(status: 'Server not started'));
String? get currentActivityManualId => state.currentActivityManualId;
Future<void> start() async {
if (!kIsWeb && !Platform.isAndroid && !Platform.isIOS) {
try {
@@ -354,114 +355,107 @@ class ServerStateNotifier extends StateNotifier<ServerState> {
}
}
const kPresenseActivityLease = 5;
// Providers
final rpcServerStateProvider =
StateNotifierProvider<ServerStateNotifier, ServerState>((ref) {
final server = ActivityRpcServer({});
final notifier = ServerStateNotifier(server);
server.updateHandlers({
'connection': (socket) {
final clientId =
socket is _WsSocketWrapper
? socket.clientId
: (socket as IpcSocketWrapper).clientId;
notifier.updateStatus('Client connected (ID: $clientId)');
socket.send({
'cmd': 'DISPATCH',
'data': {
'v': 1,
'config': {
'cdn_host': 'fake.cdn',
'api_endpoint': '//fake.api',
'environment': 'dev',
},
'user': {
'id': 'fake_user_id',
'username': 'FakeUser',
'discriminator': '0001',
'avatar': null,
'bot': false,
},
},
'evt': 'READY',
'nonce': '12345',
});
},
'message': (socket, dynamic data) async {
if (data['cmd'] == 'SET_ACTIVITY') {
notifier.addActivity(
'Activity: ${data['args']['activity']['details'] ?? ''}',
);
final label = data['args']['activity']['details'] ?? '';
final appId = socket.clientId;
final meta = data['args']['activity'];
try {
final apiClient = ref.watch(apiClientProvider);
final currentId = notifier.state.currentActivityManualId;
final isUpdate = currentId == appId;
final activityData = {
'type': 'Gaming',
'manualId': appId,
'title': label,
'meta': meta,
'leaseMinutes': 30,
};
if (isUpdate) {
await apiClient.put(
'/pass/activities',
queryParameters: {'manual_id': appId},
data: {'leaseMinutes': 30},
);
} else {
await apiClient.post('/pass/activities', data: activityData);
notifier.setCurrentActivityManualId(appId);
}
final now = DateTime.now();
final status = SnAccountStatus(
id: 'local_$appId',
attitude: 0,
isOnline: true,
isInvisible: false,
isNotDisturb: false,
isCustomized: true,
label: label,
meta: meta,
clearedAt: null,
accountId: 'me',
createdAt: now,
updatedAt: now,
deletedAt: null,
);
ref.read(currentAccountStatusProvider.notifier).setStatus(status);
} catch (e) {
talker.log('Failed to set remote activity status: $e');
}
socket.send({
'cmd': 'SET_ACTIVITY',
'data': data['args']['activity'],
'evt': null,
'nonce': data['nonce'],
});
}
},
'close': (socket) async {
notifier.updateStatus('Client disconnected');
final appId = socket.clientId;
try {
final apiClient = ref.watch(apiClientProvider);
await apiClient.delete(
'/pass/activities',
queryParameters: {'manual_id': appId},
);
notifier.setCurrentActivityManualId(null);
ref.read(currentAccountStatusProvider.notifier).clearStatus();
} catch (e) {
talker.log('Failed to unset remote activity status: $e');
}
final rpcServerStateProvider = StateNotifierProvider<
ServerStateNotifier,
ServerState
>((ref) {
final server = ActivityRpcServer({});
final notifier = ServerStateNotifier(server);
server.updateHandlers({
'connection': (socket) {
final clientId =
socket is _WsSocketWrapper
? socket.clientId
: (socket as IpcSocketWrapper).clientId;
notifier.updateStatus('Client connected (ID: $clientId)');
socket.send({
'cmd': 'DISPATCH',
'data': {
'v': 1,
'config': {
'cdn_host': 'fake.cdn',
'api_endpoint': '//fake.api',
'environment': 'dev',
},
'user': {
'id': 'fake_user_id',
'username': 'FakeUser',
'discriminator': '0001',
'avatar': null,
'bot': false,
},
},
'evt': 'READY',
'nonce': '12345',
});
return notifier;
});
},
'message': (socket, dynamic data) async {
if (data['cmd'] == 'SET_ACTIVITY') {
final activity = data['args']['activity'];
notifier.addActivity('Activity: ${activity['details'] ?? 'Untitled'}');
final appId = activity['application_id'] ?? socket.clientId;
// https://discord.com/developers/docs/topics/rpc#setactivity-set-activity-argument-structure
final type = switch (activity['type']) {
0 => 1, // Discord Playing -> Playing
2 => 2, // Discord Music -> Listening
3 => 2, // Discord Watching -> Listening
_ => 1, // Discord Competing (or null) -> Playing
};
try {
final apiClient = ref.watch(apiClientProvider);
final currentId = notifier.currentActivityManualId;
final isUpdate = currentId == appId;
final activityData = {
'type': type,
'manual_id': appId,
'title': activity['name'],
'subtitle': activity['details'],
'caption': activity['state'],
'meta': activity,
'lease_minutes': kPresenseActivityLease,
};
if (isUpdate) {
await apiClient.put(
'/pass/activities',
queryParameters: {'manualId': appId},
data: {'lease_minutes': kPresenseActivityLease},
);
} else {
await apiClient.post('/pass/activities', data: activityData);
notifier.setCurrentActivityManualId(appId);
}
} catch (e) {
talker.log('Failed to set remote activity status: $e');
}
socket.send({
'cmd': 'SET_ACTIVITY',
'data': data['args']['activity'],
'evt': null,
'nonce': data['nonce'],
});
}
},
'close': (socket) async {
notifier.updateStatus('Client disconnected');
final appId = socket.clientId;
try {
final apiClient = ref.watch(apiClientProvider);
await apiClient.delete(
'/pass/activities',
queryParameters: {'manualId': appId},
);
notifier.setCurrentActivityManualId(null);
ref.read(currentAccountStatusProvider.notifier).clearStatus();
} catch (e) {
talker.log('Failed to unset remote activity status: $e');
}
},
});
return notifier;
});
final rpcServerProvider = Provider<ActivityRpcServer>((ref) {
final notifier = ref.watch(rpcServerStateProvider.notifier);
@@ -474,7 +468,7 @@ Future<List<SnPresenceActivity>> presenceActivities(
String uname,
) async {
final apiClient = ref.watch(apiClientProvider);
final response = await apiClient.get('/pass/accounts/$uname/activities');
final response = await apiClient.get('/pass/activities/$uname');
final data = response.data as List<dynamic>;
return data.map((json) => SnPresenceActivity.fromJson(json)).toList();
}

View File

@@ -0,0 +1,157 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'activity_rpc.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$presenceActivitiesHash() =>
r'dcea3cad01b4010c0087f5281413d83a754c2a17';
/// 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 [presenceActivities].
@ProviderFor(presenceActivities)
const presenceActivitiesProvider = PresenceActivitiesFamily();
/// See also [presenceActivities].
class PresenceActivitiesFamily
extends Family<AsyncValue<List<SnPresenceActivity>>> {
/// See also [presenceActivities].
const PresenceActivitiesFamily();
/// See also [presenceActivities].
PresenceActivitiesProvider call(String uname) {
return PresenceActivitiesProvider(uname);
}
@override
PresenceActivitiesProvider getProviderOverride(
covariant PresenceActivitiesProvider provider,
) {
return call(provider.uname);
}
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'presenceActivitiesProvider';
}
/// See also [presenceActivities].
class PresenceActivitiesProvider
extends AutoDisposeFutureProvider<List<SnPresenceActivity>> {
/// See also [presenceActivities].
PresenceActivitiesProvider(String uname)
: this._internal(
(ref) => presenceActivities(ref as PresenceActivitiesRef, uname),
from: presenceActivitiesProvider,
name: r'presenceActivitiesProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$presenceActivitiesHash,
dependencies: PresenceActivitiesFamily._dependencies,
allTransitiveDependencies:
PresenceActivitiesFamily._allTransitiveDependencies,
uname: uname,
);
PresenceActivitiesProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.uname,
}) : super.internal();
final String uname;
@override
Override overrideWith(
FutureOr<List<SnPresenceActivity>> Function(PresenceActivitiesRef provider)
create,
) {
return ProviderOverride(
origin: this,
override: PresenceActivitiesProvider._internal(
(ref) => create(ref as PresenceActivitiesRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
uname: uname,
),
);
}
@override
AutoDisposeFutureProviderElement<List<SnPresenceActivity>> createElement() {
return _PresenceActivitiesProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is PresenceActivitiesProvider && other.uname == uname;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, uname.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin PresenceActivitiesRef
on AutoDisposeFutureProviderRef<List<SnPresenceActivity>> {
/// The parameter `uname` of this provider.
String get uname;
}
class _PresenceActivitiesProviderElement
extends AutoDisposeFutureProviderElement<List<SnPresenceActivity>>
with PresenceActivitiesRef {
_PresenceActivitiesProviderElement(super.provider);
@override
String get uname => (origin as PresenceActivitiesProvider).uname;
}
// 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

View File

@@ -1013,7 +1013,7 @@ class AccountProfileScreen extends HookConsumerWidget {
SliverToBoxAdapter(
child: ActivityPresenceWidget(
uname: name,
).padding(horizontal: 4),
).padding(horizontal: 8),
),
],
),

View File

@@ -634,7 +634,7 @@ class _DiscoveryActivityItem extends StatelessWidget {
}
class _ActivityListView extends HookConsumerWidget {
final CursorPagingData<SnActivity> data;
final CursorPagingData<SnTimelineEvent> data;
final int widgetCount;
final Widget endItemView;
final ActivityListNotifier activitiesNotifier;
@@ -697,13 +697,13 @@ class _ActivityListView extends HookConsumerWidget {
@riverpod
class ActivityListNotifier extends _$ActivityListNotifier
with CursorPagingNotifierMixin<SnActivity> {
with CursorPagingNotifierMixin<SnTimelineEvent> {
@override
Future<CursorPagingData<SnActivity>> build(String? filter) =>
Future<CursorPagingData<SnTimelineEvent>> build(String? filter) =>
fetch(cursor: null);
@override
Future<CursorPagingData<SnActivity>> fetch({required String? cursor}) async {
Future<CursorPagingData<SnTimelineEvent>> fetch({required String? cursor}) async {
final client = ref.read(apiClientProvider);
final take = 20;
@@ -720,9 +720,9 @@ class ActivityListNotifier extends _$ActivityListNotifier
queryParameters: queryParameters,
);
final List<SnActivity> items =
final List<SnTimelineEvent> items =
(response.data as List)
.map((e) => SnActivity.fromJson(e as Map<String, dynamic>))
.map((e) => SnTimelineEvent.fromJson(e as Map<String, dynamic>))
.toList();
final hasMore = (items.firstOrNull?.type ?? 'empty') != 'empty';
@@ -742,7 +742,7 @@ class ActivityListNotifier extends _$ActivityListNotifier
);
}
void updateOne(int index, SnActivity activity) {
void updateOne(int index, SnTimelineEvent activity) {
final currentState = state.valueOrNull;
if (currentState == null) return;

View File

@@ -7,7 +7,7 @@ part of 'explore.dart';
// **************************************************************************
String _$activityListNotifierHash() =>
r'167021cada54da7c8d8437eef1ffb387a92ea2e3';
r'a3ad3242f08139bef14a2f0fab6591ce8b3cb9f0';
/// Copied from Dart SDK
class _SystemHash {
@@ -31,10 +31,11 @@ class _SystemHash {
}
abstract class _$ActivityListNotifier
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnActivity>> {
extends
BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnTimelineEvent>> {
late final String? filter;
FutureOr<CursorPagingData<SnActivity>> build(String? filter);
FutureOr<CursorPagingData<SnTimelineEvent>> build(String? filter);
}
/// See also [ActivityListNotifier].
@@ -43,7 +44,7 @@ const activityListNotifierProvider = ActivityListNotifierFamily();
/// See also [ActivityListNotifier].
class ActivityListNotifierFamily
extends Family<AsyncValue<CursorPagingData<SnActivity>>> {
extends Family<AsyncValue<CursorPagingData<SnTimelineEvent>>> {
/// See also [ActivityListNotifier].
const ActivityListNotifierFamily();
@@ -79,7 +80,7 @@ class ActivityListNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
ActivityListNotifier,
CursorPagingData<SnActivity>
CursorPagingData<SnTimelineEvent>
> {
/// See also [ActivityListNotifier].
ActivityListNotifierProvider(String? filter)
@@ -110,7 +111,7 @@ class ActivityListNotifierProvider
final String? filter;
@override
FutureOr<CursorPagingData<SnActivity>> runNotifierBuild(
FutureOr<CursorPagingData<SnTimelineEvent>> runNotifierBuild(
covariant ActivityListNotifier notifier,
) {
return notifier.build(filter);
@@ -135,7 +136,7 @@ class ActivityListNotifierProvider
@override
AutoDisposeAsyncNotifierProviderElement<
ActivityListNotifier,
CursorPagingData<SnActivity>
CursorPagingData<SnTimelineEvent>
>
createElement() {
return _ActivityListNotifierProviderElement(this);
@@ -158,7 +159,7 @@ class ActivityListNotifierProvider
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin ActivityListNotifierRef
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnActivity>> {
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnTimelineEvent>> {
/// The parameter `filter` of this provider.
String? get filter;
}
@@ -167,7 +168,7 @@ class _ActivityListNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
ActivityListNotifier,
CursorPagingData<SnActivity>
CursorPagingData<SnTimelineEvent>
>
with ActivityListNotifierRef {
_ActivityListNotifierProviderElement(super.provider);

View File

@@ -1,7 +1,16 @@
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:styled_widget/styled_widget.dart';
const kPresenseActivityTypes = [
'unknown',
'presenceTypeGaming',
'presenceTypeMusic',
'presenceTypeWorkout',
];
class ActivityPresenceWidget extends ConsumerWidget {
final String uname;
@@ -13,48 +22,64 @@ class ActivityPresenceWidget extends ConsumerWidget {
final activitiesAsync = ref.watch(presenceActivitiesProvider(uname));
return activitiesAsync.when(
data: (activities) => _buildActivitiesList(activities),
data:
(activities) => Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8,
children: [
Text(
'activities',
).tr().bold().padding(horizontal: 8, vertical: 4),
if (activities.isEmpty)
Row(children: [
const Icon(Symbols.inbox),
Text('dataEmpty').tr()
],).opacity(0.75),
...activities.map(
(activity) => Card(
elevation: 0,
shape: RoundedRectangleBorder(
side: BorderSide(color: Colors.grey.shade300, width: 1),
borderRadius: BorderRadius.circular(8),
),
margin: EdgeInsets.zero,
child: ListTile(
title: Text(
(activity.title?.isEmpty ?? true)
? 'Untitled Activity'
: activity.title!,
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(kPresenseActivityTypes[activity.type]).tr(),
StreamBuilder(
stream: Stream.periodic(const Duration(seconds: 1)),
builder: (context, snapshot) {
final duration = DateTime.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)
Text(activity.subtitle!),
if (activity.caption?.isNotEmpty ?? false)
Text(activity.caption!),
],
),
),
),
),
],
).padding(all: 8),
),
loading: () => const Center(child: CircularProgressIndicator()),
error:
(error, stack) =>
Center(child: Text('Error loading activities: $error')),
);
}
Widget _buildActivitiesList(List<SnPresenceActivity> activities) {
if (activities.isEmpty) {
return const Center(child: Text('No active activities'));
}
return ListView.builder(
itemCount: activities.length,
itemBuilder: (context, index) {
final activity = activities[index];
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: ListTile(
title: Text(activity.title ?? 'Untitled Activity'),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Type: ${activity.type}'),
if (activity.subtitle != null) Text(activity.subtitle!),
if (activity.caption != null) Text(activity.caption!),
Text(
'Expires: ${activity.leaseExpiresAt.toLocal().toString()}',
style: const TextStyle(fontSize: 12),
),
],
),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
// TODO: Implement delete functionality
},
),
),
);
},
);
}
}

View File

@@ -296,7 +296,7 @@ class CheckInWidget extends HookConsumerWidget {
}
class CheckInActivityWidget extends StatelessWidget {
final SnActivity item;
final SnTimelineEvent item;
const CheckInActivityWidget({super.key, required this.item});
@override