162 lines
6.1 KiB
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(),
|
|
);
|