New activity presence

This commit is contained in:
2025-11-01 20:16:54 +08:00
parent 53a3a32907
commit 5ee2e70442
6 changed files with 483 additions and 28 deletions

View File

@@ -4,14 +4,18 @@ 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';
import 'package:island/widgets/account/status.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shelf/shelf.dart';
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';
@@ -294,13 +298,24 @@ class _WsSocketWrapper {
class ServerState {
final String status;
final List<String> activities;
final String? currentActivityManualId;
ServerState({required this.status, this.activities = const []});
ServerState({
required this.status,
this.activities = const [],
this.currentActivityManualId,
});
ServerState copyWith({String? status, List<String>? activities}) {
ServerState copyWith({
String? status,
List<String>? activities,
String? currentActivityManualId,
}) {
return ServerState(
status: status ?? this.status,
activities: activities ?? this.activities,
currentActivityManualId:
currentActivityManualId ?? this.currentActivityManualId,
);
}
}
@@ -333,6 +348,10 @@ class ServerStateNotifier extends StateNotifier<ServerState> {
void addActivity(String activity) {
state = state.copyWith(activities: [...state.activities, activity]);
}
void setCurrentActivityManualId(String? id) {
state = state.copyWith(currentActivityManualId: id);
}
}
// Providers
@@ -377,7 +396,26 @@ final rpcServerStateProvider =
final appId = socket.clientId;
final meta = data['args']['activity'];
try {
await setRemoteActivityStatus(ref, label, appId, meta);
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',
@@ -410,7 +448,12 @@ final rpcServerStateProvider =
notifier.updateStatus('Client disconnected');
final appId = socket.clientId;
try {
await unsetRemoteActivityStatus(ref, appId);
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');
@@ -425,30 +468,13 @@ final rpcServerProvider = Provider<ActivityRpcServer>((ref) {
return notifier.server;
});
Future<void> setRemoteActivityStatus(
@riverpod
Future<List<SnPresenceActivity>> presenceActivities(
Ref ref,
String label,
String appId,
Map<String, dynamic> meta,
String uname,
) async {
final apiClient = ref.read(apiClientProvider);
await apiClient.post(
'/pass/accounts/me/statuses',
data: {
'is_invisible': false,
'is_not_disturb': false,
'is_automated': true,
'label': label,
'app_identifier': appId,
'meta': meta,
},
);
}
Future<void> unsetRemoteActivityStatus(Ref ref, String appId) async {
final apiClient = ref.read(apiClientProvider);
await apiClient.delete(
'/pass/accounts/me/statuses',
queryParameters: {'app': appId},
);
final apiClient = ref.watch(apiClientProvider);
final response = await apiClient.get('/pass/accounts/$uname/activities');
final data = response.data as List<dynamic>;
return data.map((json) => SnPresenceActivity.fromJson(json)).toList();
}