feat: Phase 2 complete — calls, media, spaces, persistence, chat improvements
- LiveKit/MatrixRTC voice+video calls with full call screen UI - Incoming call overlay (accept/decline) - Media upload/download — file picker, image rendering, file download - Spaces navigation — space list + expandable child rooms - Drift persistence — rooms + messages written on every sync - Sync persistence auto-starts on login and session restore - Chat: typing indicators, long-press menu, reply, emoji reactions - User search dialog + start DM from rooms screen - Android: INTERNET + CAMERA + RECORD_AUDIO permissions in main manifest - Emoji picker for reactions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// Version: 1.0.0 | Created: 2026-04-01
|
||||
// Riverpod providers for chat timeline.
|
||||
// Version: 1.1.0 | Created: 2026-04-01
|
||||
// Riverpod providers for chat timeline, send, upload, react, reply.
|
||||
|
||||
import 'package:matrix/matrix.dart' show MatrixFile;
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
import '../data/chat_repository.dart';
|
||||
@@ -16,16 +17,83 @@ Stream<List<MessageModel>> chatTimeline(Ref ref, String roomId) {
|
||||
}
|
||||
|
||||
/// Sends a text message. Returns an error string on failure, null on success.
|
||||
/// Also handles sending replies when [inReplyToEventId] is set.
|
||||
@riverpod
|
||||
class SendMessage extends _$SendMessage {
|
||||
@override
|
||||
bool build() => false; // isSending
|
||||
|
||||
Future<String?> send(String roomId, String text) async {
|
||||
Future<String?> send(
|
||||
String roomId,
|
||||
String text, {
|
||||
String? inReplyToEventId,
|
||||
}) async {
|
||||
if (text.trim().isEmpty) return null;
|
||||
state = true;
|
||||
try {
|
||||
await ref.read(chatRepositoryProvider).sendTextMessage(roomId, text);
|
||||
await ref
|
||||
.read(chatRepositoryProvider)
|
||||
.sendTextMessage(roomId, text, inReplyToEventId: inReplyToEventId);
|
||||
return null;
|
||||
} on Exception catch (e) {
|
||||
return e.toString();
|
||||
} finally {
|
||||
state = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Uploads a file and sends it as a room message.
|
||||
/// State: null = idle, empty string = uploading, non-empty = error message.
|
||||
@riverpod
|
||||
class UploadFile extends _$UploadFile {
|
||||
@override
|
||||
String? build() => null; // null = idle
|
||||
|
||||
Future<void> upload(String roomId, MatrixFile file) async {
|
||||
state = ''; // uploading
|
||||
try {
|
||||
await ref.read(chatRepositoryProvider).sendFile(roomId, file);
|
||||
state = null; // success — back to idle
|
||||
} on Exception catch (e) {
|
||||
state = e.toString(); // error
|
||||
}
|
||||
}
|
||||
|
||||
void clearError() => state = null;
|
||||
}
|
||||
|
||||
/// Sends an emoji reaction to [eventId].
|
||||
@riverpod
|
||||
class SendReaction extends _$SendReaction {
|
||||
@override
|
||||
bool build() => false;
|
||||
|
||||
Future<String?> react(String roomId, String eventId, String emoji) async {
|
||||
state = true;
|
||||
try {
|
||||
await ref
|
||||
.read(chatRepositoryProvider)
|
||||
.sendReaction(roomId, eventId, emoji);
|
||||
return null;
|
||||
} on Exception catch (e) {
|
||||
return e.toString();
|
||||
} finally {
|
||||
state = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Deletes (redacts) a message by eventId.
|
||||
@riverpod
|
||||
class DeleteMessage extends _$DeleteMessage {
|
||||
@override
|
||||
bool build() => false;
|
||||
|
||||
Future<String?> delete(String roomId, String eventId) async {
|
||||
state = true;
|
||||
try {
|
||||
await ref.read(chatRepositoryProvider).redactEvent(roomId, eventId);
|
||||
return null;
|
||||
} on Exception catch (e) {
|
||||
return e.toString();
|
||||
|
||||
Reference in New Issue
Block a user