import "package:bus_running_record/models/channels/base_channel.dart"; import "package:supabase_flutter/supabase_flutter.dart"; class TextChannelMessage { const TextChannelMessage({ required this.id, required this.channelId, required this.authorUserId, required this.content, required this.createdAt, }); final String id; final String channelId; final String authorUserId; final String content; final DateTime? createdAt; factory TextChannelMessage.fromMap(Map map) { return TextChannelMessage( id: (map["id"] ?? "").toString(), channelId: (map["channel_id"] ?? "").toString(), authorUserId: (map["author_user_id"] ?? "").toString(), content: (map["content"] ?? "").toString(), createdAt: DateTime.tryParse((map["created_at"] ?? "").toString()), ); } Map toJson() { return { "id": id, "channel_id": channelId, "author_user_id": authorUserId, "content": content, "created_at": createdAt?.toIso8601String(), }; } } class TextChannel extends BaseChannel { TextChannel({ required super.client, required super.id, required super.organizationId, required super.name, required super.description, required super.slug, required super.isPrivate, required super.position, }) : super(kind: ChannelKind.text); factory TextChannel.fromApi({ required SupabaseClient client, required Map map, }) { return TextChannel( client: client, id: (map["id"] ?? "").toString(), organizationId: (map["organization_id"] ?? "").toString(), name: (map["name"] ?? "").toString(), description: (map["description"] ?? map["topic"] ?? "").toString(), slug: (map["slug"] ?? "").toString(), isPrivate: map["is_private"] == true, position: (map["position"] as num?)?.toInt() ?? 0, ); } Future> listMessages({int limit = 50}) async { final rows = await client .from("messages") .select("id, channel_id, author_user_id, content, created_at") .eq("channel_id", id) .order("created_at", ascending: true) .limit(limit); return BaseChannel.asList(rows) .map((row) => TextChannelMessage.fromMap(BaseChannel.asMap(row))) .where((message) => message.id.isNotEmpty) .toList(); } Future sendMessage(String content) async { final trimmed = content.trim(); if (trimmed.isEmpty) return null; final authorUserId = client.auth.currentUser?.id; if (authorUserId == null || authorUserId.isEmpty) { throw StateError("Cannot send message without an authenticated user."); } final inserted = await client .from("messages") .insert({ "channel_id": id, "author_user_id": authorUserId, "content": trimmed, }) .select("id, channel_id, author_user_id, content, created_at") .single(); return TextChannelMessage.fromMap(BaseChannel.asMap(inserted)); } RealtimeChannel subscribeToMessages({ required void Function() onMessageChanged, void Function(RealtimeSubscribeStatus status, Object? error)? onStatus, }) { final topic = "messages:$id"; return client.channel(topic) ..onPostgresChanges( event: PostgresChangeEvent.all, schema: "public", table: "messages", filter: PostgresChangeFilter( type: PostgresChangeFilterType.eq, column: "channel_id", value: id, ), callback: (_) => onMessageChanged(), ) ..subscribe((status, [error]) { onStatus?.call(status, error); }); } Future unsubscribe(RealtimeChannel channel) async { await client.removeChannel(channel); } }