TIF_E41221079/lib/core/service/student_service.dart

368 lines
12 KiB
Dart

// lib/core/service/student_service.dart
import 'dart:convert';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:googleapis_auth/auth_io.dart' as auth;
import 'package:http/http.dart' as http;
import 'package:smpn1_lengkong/core/models/attendance_log_model.dart';
import 'package:smpn1_lengkong/core/models/student_model.dart';
import 'package:smpn1_lengkong/core/models/violation_history_model.dart';
import 'package:url_launcher/url_launcher.dart';
class StudentService {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final FirebaseAuth _auth = FirebaseAuth.instance;
Future<StudentModel> getCurrentStudentData() async {
try {
final user = _auth.currentUser;
if (user == null) {
throw Exception("Sesi berakhir, silakan login kembali.");
}
// MODIFIKASI: Menambahkan GetOptions agar mengambil data terbaru dari server
// Ini memastikan informasi pribadi yang baru diedit langsung sinkron
DocumentSnapshot studentDoc = await _firestore
.collection('siswa')
.doc(user.uid)
.get(const GetOptions(source: Source.serverAndCache));
if (!studentDoc.exists) throw Exception("Data siswa tidak ditemukan.");
// Bagian pengambilan data pelanggaran tetap sama
QuerySnapshot violationSnapshot =
await _firestore
.collection('violations')
.where('student_id', isEqualTo: user.uid)
.get();
List<ViolationHistory> realViolations =
violationSnapshot.docs.map((v) {
return ViolationHistory.fromFirestore(
v.data() as Map<String, dynamic>,
);
}).toList();
// Ambil data absensi real
List<AttendanceLog> realAttendance = await getAttendanceData(user.uid);
return StudentModel.fromFirestore(
studentDoc.data() as Map<String, dynamic>,
studentDoc.id,
violations: realViolations,
attendance: realAttendance,
);
} catch (e) {
rethrow;
}
}
Future<List<AttendanceLog>> getAttendanceData(String studentId) async {
List<AttendanceLog> logs = [];
try {
// Ambil data dari koleksi 'absensi'
QuerySnapshot absensiSnapshot =
await _firestore.collection('absensi').get();
for (var doc in absensiSnapshot.docs) {
Map<String, dynamic> docData = doc.data() as Map<String, dynamic>;
// Cek apakah data untuk UID siswa ini ada di dokumen tersebut
if (docData['data'] != null && docData['data'][studentId] != null) {
var studentRecord = docData['data'][studentId];
var metadata = docData['metadata'];
// Ambil tanggal dari metadata (format YYYY-MM-DD)
String? rawDate = metadata != null ? metadata['tanggal'] : null;
if (rawDate != null) {
logs.add(
AttendanceLog(
date: DateTime.parse(rawDate),
status: studentRecord['status'] ?? 'Hadir',
description: studentRecord['mapel'] ?? '-',
className: studentRecord['kelas'] ?? '-',
),
);
}
}
}
// Urutkan dari yang terbaru
logs.sort((a, b) => b.date.compareTo(a.date));
return logs;
} catch (e) {
debugPrint("Error detail fetch absensi: $e");
return [];
}
}
Future<List<Map<String, dynamic>>> getBimbinganHistory(
String studentId,
String className,
) async {
try {
final snapshot =
await _firestore
.collection('Bimbingan')
.where('student_id', isEqualTo: studentId)
.where(
'kelas',
isEqualTo: className,
) // Filter berdasarkan kelas yang sedang dilihat
.orderBy('createdAt', descending: true)
.get();
return snapshot.docs.map((doc) => {'id': doc.id, ...doc.data()}).toList();
} catch (e) {
debugPrint("Error detail fetch bimbingan: $e");
return [];
}
}
// Di dalam class StudentService
Future<List<Map<String, dynamic>>> getNotifications(
String userId,
String className,
) async {
final snapshot =
await _firestore
.collection('notifikasi')
.where('id_user', isEqualTo: userId)
.where('kelas', isEqualTo: className) // TAMBAHKAN FILTER INI
.get();
return snapshot.docs.map((doc) => {'id': doc.id, ...doc.data()}).toList();
}
Future<void> markNotificationAsRead(String notificationId) async {
await _firestore.collection('notifikasi').doc(notificationId).update({
'status': 'read',
});
}
Future<void> markAllAsRead(String userId, String className) async {
try {
// Ambil semua notifikasi unread milik user di kelas ini
final querySnapshot =
await _firestore
.collection('notifikasi')
.where('id_user', isEqualTo: userId)
.where('kelas', isEqualTo: className)
.where('status', isEqualTo: 'unread')
.get();
if (querySnapshot.docs.isEmpty) return;
// Gunakan WriteBatch untuk efisiensi update massal
WriteBatch batch = _firestore.batch();
for (var doc in querySnapshot.docs) {
batch.update(doc.reference, {'status': 'read'});
}
await batch.commit();
} catch (e) {
debugPrint("Error mark all as read: $e");
}
}
Future<String> _getAccessToken() async {
try {
// Pastikan nama file ini sesuai dengan yang ada di folder assets dan pubspec.yaml
final serviceAccountJson = await rootBundle.loadString(
'assets/service.json',
);
final data = json.decode(serviceAccountJson);
final accountCredentials = auth.ServiceAccountCredentials.fromJson(data);
final scopes = ['https://www.googleapis.com/auth/firebase.messaging'];
final client = await auth.clientViaServiceAccount(
accountCredentials,
scopes,
);
return client.credentials.accessToken.data;
} catch (e) {
debugPrint("Gagal mendapatkan Access Token: $e");
rethrow;
}
}
Future<void> _sendPushNotification(
String token,
String title,
String body,
) async {
try {
final String accessToken = await _getAccessToken();
const String projectId = 'smpn1lengkong-b7621';
final String url = 'https://fcm.googleapis.com/v1/projects/$projectId/messages:send';
final response = await http.post(
Uri.parse(url),
headers: <String, String>{
'Content-Type': 'application/json',
'Authorization': 'Bearer $accessToken',
},
body: jsonEncode({
'message': {
'token': token,
'notification': {'title': title, 'body': body},
'android': {
'priority': 'high', // Memastikan prioritas tinggi
'notification': {
'channel_id': 'high_importance_channel', // Harus sama dengan di FcmService
'sound': 'default',
'click_action': 'FLUTTER_NOTIFICATION_CLICK', // Penting untuk perangkat fisik
'notification_priority': 'PRIORITY_MAX', // Tambahan untuk Android lawas/fisik
},
},
'data': {
'type': 'info',
'click_action': 'FLUTTER_NOTIFICATION_CLICK',
},
},
}),
);
// Tambahkan log untuk debugging di perangkat fisik
if (response.statusCode != 200) {
debugPrint("FCM Error: ${response.statusCode} - ${response.body}");
}
} catch (e) {
debugPrint("Error FCM V1: $e");
}
}
// 3. Fungsi Update Data & Trigger Notifikasi
Future<void> updateStudentPersonalData({
required StudentModel student,
required String fatherName,
required String motherName,
required String job,
required String address,
required String phoneNumber,
}) async {
try {
// Update Profil Siswa
await _firestore.collection('siswa').doc(student.id).update({
'nama_ayah': fatherName,
'nama_ibu': motherName,
'pekerjaan_ortu': job,
'alamat': address,
'no_hp': phoneNumber,
'updated_at': FieldValue.serverTimestamp(),
});
// Ambil Token FCM
DocumentSnapshot userDoc =
await _firestore.collection('siswa').doc(student.id).get();
String? fcmToken = userDoc.get('fcm_token');
final String currentTime = DateTime.now().toString().substring(0, 19);
final String title = "📝 Pembaruan Informasi Pribadi";
final String body =
"Yth. Bapak/Ibu Wali, Informasi pribadi untuk siswa atas nama ${student.name} telah berhasil diperbarui. Terima kasih.";
// Tambah Dokumen ke Koleksi Notifikasi
await _firestore.collection('notifikasi').add({
'id_user': student.id,
'angkatan': student.angkatan,
'body': body,
'tanggal': currentTime,
'fcm_token': fcmToken ?? "",
'kelas': student.className,
'nama_siswa': student.name,
'siswa_id': student.id,
'status': 'unread',
'title': title,
'type': 'info',
});
// Kirim Notifikasi ke Device
if (fcmToken != null && fcmToken.isNotEmpty) {
await _sendPushNotification(fcmToken, title, body);
}
} catch (e) {
debugPrint("Error update data: $e");
throw Exception("Gagal menyimpan perubahan.");
}
}
// Fungsi untuk mengambil link WA dari koleksi administrasi
Future<String?> getContactLink() async {
try {
// Memastikan query sesuai dengan aturan read di Firebase
final snapshot =
await _firestore.collection('administrasi').limit(1).get();
if (snapshot.docs.isNotEmpty) {
return snapshot.docs.first.data()['contact'];
}
return null;
} catch (e) {
// Jika masih error permission, akan tercetak di sini
debugPrint("Error fetch contact: $e");
return null;
}
}
// lib/core/service/student_service.dart
Future<void> launchWhatsApp(String url) async {
try {
final Uri uri = Uri.parse(url);
// Gunakan LaunchMode.externalApplication agar Android
// langsung mencari aplikasi WA atau Browser di luar app kita
if (await canLaunchUrl(uri)) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
} else {
throw 'WhatsApp tidak terinstal atau tautan tidak valid';
}
} catch (e) {
debugPrint("Error launching URL: $e");
rethrow;
}
}
Future<void> sendBimbinganNotification({
required String studentId,
required String studentName,
required String className,
required String category,
}) async {
try {
// 1. Ambil Token FCM terbaru dari database
DocumentSnapshot userDoc = await _firestore.collection('siswa').doc(studentId).get();
String? fcmToken = userDoc.get('fcm_token');
final String currentTime = DateTime.now().toString().substring(0, 19);
final String title = "🗓️ Permohonan Kunjungan Dikirim";
final String body = "Permohonan kunjungan untuk $studentName (Kelas $className) telah berhasil diajukan ke sekolah. Mohon tunggu konfirmasi jadwal.";
// 2. Simpan ke koleksi 'notifikasi' agar tampil di NotificationScreen
await _firestore.collection('notifikasi').add({
'id_user': studentId,
'body': body,
'tanggal': currentTime,
'fcm_token': fcmToken ?? "",
'kelas': className,
'nama_siswa': studentName,
'siswa_id': studentId,
'status': 'unread',
'title': title,
'type': 'info',
});
// 3. Kirim Push Notification ke perangkat
if (fcmToken != null && fcmToken.isNotEmpty) {
await _sendPushNotification(fcmToken, title, body);
}
} catch (e) {
debugPrint("Gagal mengirim notifikasi bimbingan: $e");
}
}
}