TKK_E32232198/lib/auth/auth_service.dart

343 lines
11 KiB
Dart

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<bool> 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<void> 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<void> _touchSession(SharedPreferences prefs) async {
await prefs.setInt(_lastActiveMsKey, DateTime.now().millisecondsSinceEpoch);
}
// Simpan data user
static Future<void> 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<Map<String, String?>> getUserData() async {
final prefs = await SharedPreferences.getInstance();
return {
'userId': prefs.getString(_userIdKey),
'userName': prefs.getString(_userNameKey),
};
}
// Logout
static Future<void> logout() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_isLoggedInKey);
await prefs.remove(_lastActiveMsKey);
await prefs.remove(_userIdKey);
await prefs.remove(_userNameKey);
}
Future<void> 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<void> 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<bool> sendEmailVerification() async {
final user = FirebaseAuth.instance.currentUser;
if (user == null || user.emailVerified) return false;
await user.sendEmailVerification();
return true;
}
Future<void> 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<void> saveAmmoniaThreshold(double value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setDouble(_ammoniaThresholdKey, value);
}
// Ambil nilai threshold amonia
static Future<double> getAmmoniaThreshold() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getDouble(_ammoniaThresholdKey) ?? 0.1; // Default 0.1
}
// Murky water threshold (0.1 step)
static Future<void> saveMurkyThreshold(double value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setDouble(_murkyThresholdKey, value);
}
static Future<double> getMurkyThreshold() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getDouble(_murkyThresholdKey) ?? 0.0;
}
// Murky water pompa automation
static Future<void> saveMurkyAutoStatus(bool value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_murkyAutoKey, value);
}
static Future<bool> getMurkyAutoStatus() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getBool(_murkyAutoKey) ?? false;
}
// Timer light: start "HH:mm", end "HH:mm", enabled
static Future<void> 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<Map<String, dynamic>> 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<void> saveVolumeLevel(int level) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt(_volumeLevelKey, level);
}
static Future<int> getVolumeLevel() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getInt(_volumeLevelKey) ?? 90;
}
/// Tinggi fisik aquarium (cm).
static Future<void> saveAquariumHeight(int height) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt(_aquariumHeightKey, height);
}
static Future<int> 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<void> 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<dynamic> list = raw == null ? <dynamic>[] : (jsonDecode(raw) as List<dynamic>);
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<List<Map<String, dynamic>>> 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>()
.map((e) => Map<String, dynamic>.from(e))
.toList();
} catch (_) {
return [];
}
}
}