343 lines
11 KiB
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 [];
|
|
}
|
|
}
|
|
}
|