import 'package:firebase_auth/firebase_auth.dart'; import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import '../page2/History.dart'; import '../page_auth/login.dart'; class AuthService { static const String _isLoggedInKey = 'isLoggedIn'; static const String _lastActiveMsKey = 'lastActiveMs'; static const String _userIdKey = 'userId'; static const String _userNameKey = 'userName'; static const String _ammoniaThresholdKey = 'ammoniaThreshold'; static const String _murkyThresholdKey = 'murkyThreshold'; static const String _murkyAutoKey = 'murkyAutoStatus'; static const String _timerLightStartKey = 'timerLightStart'; static const String _timerLightEndKey = 'timerLightEnd'; static const String _timerLightEnabledKey = 'timerLightEnabled'; static const String _volumeLevelKey = 'volumeLevel'; static const String _aquariumHeightKey = 'aquariumHeight'; static const String _localHistoryKey = 'localSensorHistory'; // Cek status login static Future isLoggedIn() async { final prefs = await SharedPreferences.getInstance(); final isLoggedIn = prefs.getBool(_isLoggedInKey) ?? false; if (!isLoggedIn) return false; final lastActiveMs = prefs.getInt(_lastActiveMsKey); if (lastActiveMs == null) { await _touchSession(prefs); return true; } const maxIdle = Duration(days: 7); final idleDuration = DateTime.now().millisecondsSinceEpoch - lastActiveMs; if (idleDuration > maxIdle.inMilliseconds) { await FirebaseAuth.instance.signOut(); await logout(); return false; } await _touchSession(prefs); return true; } // Simpan status login static Future setLoggedIn(bool value) async { final prefs = await SharedPreferences.getInstance(); await prefs.setBool(_isLoggedInKey, value); if (value) { await _touchSession(prefs); } else { await prefs.remove(_lastActiveMsKey); } } static Future _touchSession(SharedPreferences prefs) async { await prefs.setInt(_lastActiveMsKey, DateTime.now().millisecondsSinceEpoch); } // Simpan data user static Future saveUserData({ required String userId, required String userName, }) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString(_userIdKey, userId); await prefs.setString(_userNameKey, userName); } // Ambil data user static Future> getUserData() async { final prefs = await SharedPreferences.getInstance(); return { 'userId': prefs.getString(_userIdKey), 'userName': prefs.getString(_userNameKey), }; } // Logout static Future logout() async { final prefs = await SharedPreferences.getInstance(); await prefs.remove(_isLoggedInKey); await prefs.remove(_lastActiveMsKey); await prefs.remove(_userIdKey); await prefs.remove(_userNameKey); } Future signup({ required String email, required String password, required BuildContext context }) async { try { final credential = await FirebaseAuth.instance.createUserWithEmailAndPassword( email: email, password: password ); if (credential.user != null) { try { await credential.user!.sendEmailVerification(); } catch (e) { // Tetap lanjut; verifikasi bisa dikirim ulang nanti } // Penting: sign out agar user tidak bisa masuk sebelum verifikasi await FirebaseAuth.instance.signOut(); } if (context.mounted) { Fluttertoast.showToast( msg: 'Daftar berhasil. Cek email (dan folder spam) untuk verifikasi, lalu login.', toastLength: Toast.LENGTH_LONG, gravity: ToastGravity.SNACKBAR, backgroundColor: Colors.black54, textColor: Colors.white, fontSize: 14.0, ); Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => const Login()), ); } } on FirebaseAuthException catch (e) { String message; if (e.code == 'weak-password') { message = 'Kata sandi terlalu lemah'; } else if (e.code == 'email-already-in-use') { message = 'Akun sudah terdaftar'; } else if (e.code == 'invalid-email') { message = 'Email tidak valid'; } else { message = 'Terjadi kesalahan. Silakan coba lagi.'; } Fluttertoast.showToast( msg: message, toastLength: Toast.LENGTH_LONG, gravity: ToastGravity.SNACKBAR, backgroundColor: Colors.black54, textColor: Colors.white, fontSize: 14.0, ); } } Future signin({ required String email, required String password, required BuildContext context }) async { try { await FirebaseAuth.instance.signInWithEmailAndPassword( email: email, password: password ); final user = FirebaseAuth.instance.currentUser; if (user != null) { await user.reload(); final currentUser = FirebaseAuth.instance.currentUser; if (currentUser != null && !currentUser.emailVerified) { await FirebaseAuth.instance.signOut(); if (context.mounted) { Fluttertoast.showToast( msg: 'Verifikasi email dulu. Cek inbox (dan folder spam).', toastLength: Toast.LENGTH_LONG, gravity: ToastGravity.SNACKBAR, backgroundColor: Colors.black54, textColor: Colors.white, fontSize: 14.0, ); } return; } } await Future.delayed(const Duration(seconds: 1)); if (context.mounted) { await setLoggedIn(true); final currentUser = FirebaseAuth.instance.currentUser; if (currentUser != null) { await saveUserData( userId: currentUser.uid, userName: currentUser.email ?? 'User', ); } Navigator.pushReplacement( context, MaterialPageRoute(builder: (BuildContext context) => const Monitoring()), ); } } on FirebaseAuthException catch (e) { String message = 'Masukkan email'; if (e.code == 'invalid-email') { message = 'User Tidak Ditemukan'; } else if (e.code == 'invalid-credential') { message = 'Password Salah'; } Fluttertoast.showToast( msg: message, toastLength: Toast.LENGTH_LONG, gravity: ToastGravity.SNACKBAR, backgroundColor: Colors.black54, textColor: Colors.white, fontSize: 14.0, ); } } /// Kirim ulang email verifikasi (untuk user yang belum verifikasi). static Future sendEmailVerification() async { final user = FirebaseAuth.instance.currentUser; if (user == null || user.emailVerified) return false; await user.sendEmailVerification(); return true; } Future signout({required BuildContext context}) async { await FirebaseAuth.instance.signOut(); await Future.delayed(const Duration(seconds: 1)); Navigator.pushReplacement( context, MaterialPageRoute(builder: (BuildContext context) => const Login()), ); } // Simpan nilai threshold amonia static Future saveAmmoniaThreshold(double value) async { final prefs = await SharedPreferences.getInstance(); await prefs.setDouble(_ammoniaThresholdKey, value); } // Ambil nilai threshold amonia static Future getAmmoniaThreshold() async { final prefs = await SharedPreferences.getInstance(); return prefs.getDouble(_ammoniaThresholdKey) ?? 0.1; // Default 0.1 } // Murky water threshold (0.1 step) static Future saveMurkyThreshold(double value) async { final prefs = await SharedPreferences.getInstance(); await prefs.setDouble(_murkyThresholdKey, value); } static Future getMurkyThreshold() async { final prefs = await SharedPreferences.getInstance(); return prefs.getDouble(_murkyThresholdKey) ?? 0.0; } // Murky water pompa automation static Future saveMurkyAutoStatus(bool value) async { final prefs = await SharedPreferences.getInstance(); await prefs.setBool(_murkyAutoKey, value); } static Future getMurkyAutoStatus() async { final prefs = await SharedPreferences.getInstance(); return prefs.getBool(_murkyAutoKey) ?? false; } // Timer light: start "HH:mm", end "HH:mm", enabled static Future saveTimerLight({ required String start, required String end, required bool enabled, }) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString(_timerLightStartKey, start); await prefs.setString(_timerLightEndKey, end); await prefs.setBool(_timerLightEnabledKey, enabled); } static Future> getTimerLight() async { final prefs = await SharedPreferences.getInstance(); return { 'start': prefs.getString(_timerLightStartKey) ?? '12:00', 'end': prefs.getString(_timerLightEndKey) ?? '16:00', 'enabled': prefs.getBool(_timerLightEnabledKey) ?? false, }; } /// Volume fill level saat pompa aktif (30, 60, atau 90) static Future saveVolumeLevel(int level) async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt(_volumeLevelKey, level); } static Future getVolumeLevel() async { final prefs = await SharedPreferences.getInstance(); return prefs.getInt(_volumeLevelKey) ?? 90; } /// Tinggi fisik aquarium (cm). static Future saveAquariumHeight(int height) async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt(_aquariumHeightKey, height); } static Future getAquariumHeight() async { final prefs = await SharedPreferences.getInstance(); return prefs.getInt(_aquariumHeightKey) ?? 40; } /// Simpan riwayat lokal sensor sebagai fallback saat endpoint history server belum tersedia. static Future saveLocalHistorySample({ required double ammonia, required double kekeruhan, required double distance, required bool lampAuto, int maxItems = 100, }) async { final prefs = await SharedPreferences.getInstance(); final raw = prefs.getString(_localHistoryKey); final List list = raw == null ? [] : (jsonDecode(raw) as List); list.add({ 'ts': DateTime.now().millisecondsSinceEpoch, 'amonia': ammonia, 'kekeruhan': kekeruhan, 'distance': distance, 'lampu': lampAuto, }); if (list.length > maxItems) { list.removeRange(0, list.length - maxItems); } await prefs.setString(_localHistoryKey, jsonEncode(list)); } static Future>> getLocalHistoryRows() async { final prefs = await SharedPreferences.getInstance(); final raw = prefs.getString(_localHistoryKey); if (raw == null || raw.isEmpty) return []; try { final decoded = jsonDecode(raw); if (decoded is! List) return []; return decoded .whereType() .map((e) => Map.from(e)) .toList(); } catch (_) { return []; } } }