amoriai/lib/providers/amor_chat_provider.dart

162 lines
6.1 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import '../services/firebase_service.dart';
// ── Model ──────────────────────────────────────────────────────────────────
class AmorChatEntry {
final String text;
final bool isUser;
final DateTime time;
AmorChatEntry({
required this.text,
required this.isUser,
required this.time,
});
Map<String, dynamic> toJson() => {
'text': text,
'isUser': isUser,
'time': time.toIso8601String(),
};
factory AmorChatEntry.fromJson(Map<String, dynamic> json) => AmorChatEntry(
text: json['text'] as String,
isUser: json['isUser'] as bool,
time: DateTime.parse(json['time'] as String),
);
}
// ── State ──────────────────────────────────────────────────────────────────
class AmorChatState {
final List<AmorChatEntry> messages;
final bool isLoading;
final bool isLoadedFromFirestore;
const AmorChatState({
this.messages = const [],
this.isLoading = false,
this.isLoadedFromFirestore = false,
});
AmorChatState copyWith({
List<AmorChatEntry>? messages,
bool? isLoading,
bool? isLoadedFromFirestore,
}) =>
AmorChatState(
messages: messages ?? this.messages,
isLoading: isLoading ?? this.isLoading,
isLoadedFromFirestore:
isLoadedFromFirestore ?? this.isLoadedFromFirestore,
);
}
// ── Notifier ───────────────────────────────────────────────────────────────
class AmorChatNotifier extends StateNotifier<AmorChatState> {
final FirebaseFirestore _db = FirebaseFirestore.instance;
static const String _chatCollection = 'amorChatHistory';
static const int _maxMessages = 100; // batas simpan
AmorChatNotifier() : super(const AmorChatState());
String? get _uid => FirebaseService().currentUser?.uid;
// ── Load riwayat dari Firestore ──────────────────────────────────────────
Future<void> loadHistory() async {
if (state.isLoadedFromFirestore) return; // sudah dimuat, skip
final uid = _uid;
if (uid == null) return;
try {
final doc = await _db.collection(_chatCollection).doc(uid).get();
if (doc.exists && doc.data() != null) {
final raw = doc.data()!['messages'] as List<dynamic>? ?? [];
final msgs = raw
.map((e) =>
AmorChatEntry.fromJson(Map<String, dynamic>.from(e as Map)))
.toList();
state = state.copyWith(
messages: msgs,
isLoadedFromFirestore: true,
);
} else {
state = state.copyWith(isLoadedFromFirestore: true);
}
} catch (e) {
debugPrint('AmorChat loadHistory error: $e');
state = state.copyWith(isLoadedFromFirestore: true);
}
}
// ── Tambah pesan user ────────────────────────────────────────────────────
void addUserMessage(String text) {
final entry = AmorChatEntry(
text: text,
isUser: true,
time: DateTime.now(),
);
state = state.copyWith(messages: [...state.messages, entry]);
}
// ── Tambah pesan AI & simpan ke Firestore ────────────────────────────────
Future<void> addAIMessage(String text) async {
final entry = AmorChatEntry(
text: text,
isUser: false,
time: DateTime.now(),
);
final updated = [...state.messages, entry];
state = state.copyWith(messages: updated);
await _saveToFirestore(updated);
}
// ── Set loading ──────────────────────────────────────────────────────────
void setLoading(bool v) => state = state.copyWith(isLoading: v);
// ── Hapus semua riwayat ──────────────────────────────────────────────────
Future<void> clearHistory() async {
state = state.copyWith(messages: []);
final uid = _uid;
if (uid == null) return;
try {
await _db.collection(_chatCollection).doc(uid).delete();
} catch (e) {
debugPrint('AmorChat clearHistory error: $e');
}
}
// ── Simpan ke Firestore ──────────────────────────────────────────────────
Future<void> _saveToFirestore(List<AmorChatEntry> messages) async {
final uid = _uid;
if (uid == null) return;
try {
// Batasi ke _maxMessages terakhir agar tidak membengkak
final limited = messages.length > _maxMessages
? messages.sublist(messages.length - _maxMessages)
: messages;
await _db.collection(_chatCollection).doc(uid).set({
'messages': limited.map((m) => m.toJson()).toList(),
'updatedAt': DateTime.now().toIso8601String(),
});
} catch (e) {
debugPrint('AmorChat _saveToFirestore error: $e');
}
}
// Kembalikan history dalam format yang dibutuhkan GeminiService
List<Map<String, String>> get aiHistory => state.messages
.map((m) => {
'role': m.isUser ? 'user' : 'model',
'text': m.text,
})
.toList();
}
// ── Provider ───────────────────────────────────────────────────────────────
final amorChatProvider =
StateNotifierProvider<AmorChatNotifier, AmorChatState>(
(ref) => AmorChatNotifier(),
);