Upload files to "/"
This commit is contained in:
commit
ed506f6892
|
@ -0,0 +1,93 @@
|
||||||
|
// File generated by FlutterFire CLI.
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||||
|
import 'package:flutter/foundation.dart'
|
||||||
|
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||||
|
|
||||||
|
/// Default [FirebaseOptions] for use with your Firebase apps.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// import 'firebase_options.dart';
|
||||||
|
/// // ...
|
||||||
|
/// await Firebase.initializeApp(
|
||||||
|
/// options: DefaultFirebaseOptions.currentPlatform,
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
class DefaultFirebaseOptions {
|
||||||
|
static FirebaseOptions get currentPlatform {
|
||||||
|
if (kIsWeb) {
|
||||||
|
return web;
|
||||||
|
}
|
||||||
|
switch (defaultTargetPlatform) {
|
||||||
|
case TargetPlatform.android:
|
||||||
|
return android;
|
||||||
|
case TargetPlatform.iOS:
|
||||||
|
return ios;
|
||||||
|
case TargetPlatform.macOS:
|
||||||
|
return macos;
|
||||||
|
case TargetPlatform.windows:
|
||||||
|
return windows;
|
||||||
|
case TargetPlatform.linux:
|
||||||
|
throw UnsupportedError(
|
||||||
|
'DefaultFirebaseOptions have not been configured for linux - '
|
||||||
|
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw UnsupportedError(
|
||||||
|
'DefaultFirebaseOptions are not supported for this platform.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const FirebaseOptions web = FirebaseOptions(
|
||||||
|
apiKey: 'AIzaSyCWEGWYt71sCQnsaBLWheHM--6xAuZFsoc',
|
||||||
|
appId: '1:155191766776:web:8fc63761106a3d1134d50d',
|
||||||
|
messagingSenderId: '155191766776',
|
||||||
|
projectId: 'rumahoto-c7c9b',
|
||||||
|
authDomain: 'rumahoto-c7c9b.firebaseapp.com',
|
||||||
|
databaseURL: 'https://rumahoto-c7c9b-default-rtdb.firebaseio.com',
|
||||||
|
storageBucket: 'rumahoto-c7c9b.firebasestorage.app',
|
||||||
|
measurementId: 'G-SLPDDLCZLC',
|
||||||
|
);
|
||||||
|
|
||||||
|
static const FirebaseOptions android = FirebaseOptions(
|
||||||
|
apiKey: 'AIzaSyDhXwFT0yCV4lAXPyKIMcfRzbNIxVVudl8',
|
||||||
|
appId: '1:155191766776:android:d9bad1e8c5a79ce234d50d',
|
||||||
|
messagingSenderId: '155191766776',
|
||||||
|
projectId: 'rumahoto-c7c9b',
|
||||||
|
databaseURL: 'https://rumahoto-c7c9b-default-rtdb.firebaseio.com',
|
||||||
|
storageBucket: 'rumahoto-c7c9b.firebasestorage.app',
|
||||||
|
);
|
||||||
|
|
||||||
|
static const FirebaseOptions ios = FirebaseOptions(
|
||||||
|
apiKey: 'AIzaSyB4C3unwhECub_lmNO_ZMzW0eM4GBIId9E',
|
||||||
|
appId: '1:155191766776:ios:160ad5dc9403ab9534d50d',
|
||||||
|
messagingSenderId: '155191766776',
|
||||||
|
projectId: 'rumahoto-c7c9b',
|
||||||
|
databaseURL: 'https://rumahoto-c7c9b-default-rtdb.firebaseio.com',
|
||||||
|
storageBucket: 'rumahoto-c7c9b.firebasestorage.app',
|
||||||
|
iosBundleId: 'com.example.aplikasiPertama',
|
||||||
|
);
|
||||||
|
|
||||||
|
static const FirebaseOptions macos = FirebaseOptions(
|
||||||
|
apiKey: 'AIzaSyB4C3unwhECub_lmNO_ZMzW0eM4GBIId9E',
|
||||||
|
appId: '1:155191766776:ios:160ad5dc9403ab9534d50d',
|
||||||
|
messagingSenderId: '155191766776',
|
||||||
|
projectId: 'rumahoto-c7c9b',
|
||||||
|
databaseURL: 'https://rumahoto-c7c9b-default-rtdb.firebaseio.com',
|
||||||
|
storageBucket: 'rumahoto-c7c9b.firebasestorage.app',
|
||||||
|
iosBundleId: 'com.example.aplikasiPertama',
|
||||||
|
);
|
||||||
|
|
||||||
|
static const FirebaseOptions windows = FirebaseOptions(
|
||||||
|
apiKey: 'AIzaSyCWEGWYt71sCQnsaBLWheHM--6xAuZFsoc',
|
||||||
|
appId: '1:155191766776:web:f898d71c18f44df934d50d',
|
||||||
|
messagingSenderId: '155191766776',
|
||||||
|
projectId: 'rumahoto-c7c9b',
|
||||||
|
authDomain: 'rumahoto-c7c9b.firebaseapp.com',
|
||||||
|
databaseURL: 'https://rumahoto-c7c9b-default-rtdb.firebaseio.com',
|
||||||
|
storageBucket: 'rumahoto-c7c9b.firebasestorage.app',
|
||||||
|
measurementId: 'G-09H7KLZ96P',
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,150 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
|
|
||||||
|
class ForgotPasswordPage extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_ForgotPasswordPageState createState() => _ForgotPasswordPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ForgotPasswordPageState extends State<ForgotPasswordPage> {
|
||||||
|
final TextEditingController emailController = TextEditingController();
|
||||||
|
bool isLoading = false;
|
||||||
|
|
||||||
|
Future<void> sendPasswordResetEmail(String email) async {
|
||||||
|
setState(() {
|
||||||
|
isLoading = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await FirebaseAuth.instance.sendPasswordResetEmail(email: email);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Link reset password dikirim ke $email')),
|
||||||
|
);
|
||||||
|
} on FirebaseAuthException catch (e) {
|
||||||
|
String message = 'Terjadi kesalahan';
|
||||||
|
if (e.code == 'user-not-found') {
|
||||||
|
message = 'Email tidak ditemukan';
|
||||||
|
}
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text(message)),
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setState(() {
|
||||||
|
isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: double.infinity,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
Color(0xFFB3E5FC), // biru pastel muda
|
||||||
|
Color(0xFFE1F5FE), // biru pastel pucat
|
||||||
|
],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.lock_reset, size: 80, color: Colors.blueGrey[800]),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
|
'Reset Password',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 26,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.blueGrey[800],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.95),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.blueGrey.withOpacity(0.1),
|
||||||
|
blurRadius: 20,
|
||||||
|
offset: const Offset(0, 10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
controller: emailController,
|
||||||
|
keyboardType: TextInputType.emailAddress,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
prefixIcon: Icon(Icons.email, color: Colors.blueGrey),
|
||||||
|
labelText: 'Email',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: isLoading
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
final email = emailController.text.trim();
|
||||||
|
if (email.isNotEmpty) {
|
||||||
|
sendPasswordResetEmail(email);
|
||||||
|
} else {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Masukkan email terlebih dahulu'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: const Color(0xFF81D4FA),
|
||||||
|
minimumSize: const Size(double.infinity, 48),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: isLoading
|
||||||
|
? const CircularProgressIndicator(color: Colors.white)
|
||||||
|
: const Text(
|
||||||
|
'Kirim Link Reset',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
letterSpacing: 1.1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: const Text(
|
||||||
|
'Kembali ke Login',
|
||||||
|
style: TextStyle(color: Colors.blueGrey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,234 @@
|
||||||
|
import 'package:app_sensor/forgotpassword_page.dart';
|
||||||
|
import 'package:app_sensor/monitoring_page.dart';
|
||||||
|
import 'package:app_sensor/signup_page.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
|
import 'package:lottie/lottie.dart';
|
||||||
|
|
||||||
|
class LoginPage extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_LoginPageState createState() => _LoginPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginPageState extends State<LoginPage> {
|
||||||
|
final TextEditingController emailController = TextEditingController();
|
||||||
|
final TextEditingController passwordController = TextEditingController();
|
||||||
|
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||||
|
|
||||||
|
bool _obscurePassword = true;
|
||||||
|
bool _isLoading = false; // loading state
|
||||||
|
|
||||||
|
void _login() async {
|
||||||
|
String email = emailController.text.trim();
|
||||||
|
String password = passwordController.text.trim();
|
||||||
|
|
||||||
|
if (email.isEmpty || password.isEmpty) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Email dan Password tidak boleh kosong!'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_isLoading = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await _auth.signInWithEmailAndPassword(
|
||||||
|
email: email,
|
||||||
|
password: password,
|
||||||
|
);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Menggunakan push agar halaman login tetap bisa dikunjungi kembali
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (_) => MonitoringPage()),
|
||||||
|
);
|
||||||
|
} on FirebaseAuthException catch (e) {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
String message = 'Terjadi kesalahan';
|
||||||
|
if (e.code == 'user-not-found') {
|
||||||
|
message = 'Pengguna tidak ditemukan';
|
||||||
|
} else if (e.code == 'wrong-password') {
|
||||||
|
message = 'Password salah';
|
||||||
|
}
|
||||||
|
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(message),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: double.infinity,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
Color(0xFFB3E5FC),
|
||||||
|
Color(0xFFE1F5FE),
|
||||||
|
],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: EdgeInsets.all(24),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Lottie.asset(
|
||||||
|
'assets/animations/home.json',
|
||||||
|
width: 140,
|
||||||
|
height: 140,
|
||||||
|
),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
'RumahOto',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 30,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.blueGrey[800],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 30),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.95),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.blueGrey.withOpacity(0.1),
|
||||||
|
blurRadius: 20,
|
||||||
|
offset: Offset(0, 10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
controller: emailController,
|
||||||
|
keyboardType: TextInputType.emailAddress,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
prefixIcon: Icon(Icons.email, color: Colors.blueGrey),
|
||||||
|
labelText: 'Email',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 15),
|
||||||
|
TextField(
|
||||||
|
controller: passwordController,
|
||||||
|
obscureText: _obscurePassword,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
prefixIcon: Icon(Icons.lock, color: Colors.blueGrey),
|
||||||
|
labelText: 'Password',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
_obscurePassword ? Icons.visibility_off : Icons.visibility,
|
||||||
|
color: Colors.blueGrey,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_obscurePassword = !_obscurePassword;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (_) => ForgotPasswordPage()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
"Lupa Password?",
|
||||||
|
style: TextStyle(color: Colors.blueGrey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
_isLoading
|
||||||
|
? CircularProgressIndicator(
|
||||||
|
color: Colors.blueGrey,
|
||||||
|
)
|
||||||
|
: ElevatedButton(
|
||||||
|
onPressed: _login,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Color(0xFF81D4FA),
|
||||||
|
minimumSize: Size(double.infinity, 48),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'LOGIN',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
letterSpacing: 1.1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (_) => SignUpPage()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
text: "Belum punya akun? ",
|
||||||
|
style: TextStyle(color: Colors.blueGrey[700]),
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: 'Daftar Sekarang',
|
||||||
|
style: TextStyle(
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
|
import 'package:intl/date_symbol_data_local.dart'; // Tambahkan ini
|
||||||
|
import 'firebase_options.dart';
|
||||||
|
import 'login_page.dart'; // Pastikan file login_page.dart ada
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
// Inisialisasi Firebase
|
||||||
|
await Firebase.initializeApp(
|
||||||
|
options: DefaultFirebaseOptions.currentPlatform,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Inisialisasi data tanggal untuk locale Indonesia
|
||||||
|
await initializeDateFormatting('id_ID', null);
|
||||||
|
|
||||||
|
runApp(SmartHomeApp());
|
||||||
|
}
|
||||||
|
|
||||||
|
class SmartHomeApp extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
title: 'Smart Home',
|
||||||
|
theme: ThemeData(
|
||||||
|
primarySwatch: Colors.blue,
|
||||||
|
fontFamily: 'Sans',
|
||||||
|
),
|
||||||
|
home: LoginPage(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,243 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:lottie/lottie.dart';
|
||||||
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
|
|
||||||
|
class MonitoringPage extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_MonitoringPageState createState() => _MonitoringPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MonitoringPageState extends State<MonitoringPage> {
|
||||||
|
double temperature = 0.0;
|
||||||
|
double humidity = 0.0;
|
||||||
|
int gasLevel = 0;
|
||||||
|
bool lampStatus = false;
|
||||||
|
bool doorStatus = false;
|
||||||
|
bool buzzerStatus = false; // ✅ Tambahan
|
||||||
|
|
||||||
|
final int gasThreshold = 300;
|
||||||
|
final double tempThreshold = 30.0;
|
||||||
|
|
||||||
|
String getFormattedDate() {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final day = DateFormat('EEEE', 'id_ID').format(now);
|
||||||
|
final date = DateFormat('dd MMMM yyyy', 'id_ID').format(now);
|
||||||
|
return '$day, $date';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
final dbRef = FirebaseDatabase.instance.ref();
|
||||||
|
|
||||||
|
dbRef.child('sensor').onValue.listen((event) {
|
||||||
|
final data = event.snapshot.value as Map;
|
||||||
|
setState(() {
|
||||||
|
temperature = (data['suhu'] ?? 0).toDouble();
|
||||||
|
humidity = (data['kelembaban'] ?? 0).toDouble();
|
||||||
|
gasLevel = (data['mq2'] ?? 0).toInt();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
dbRef.child('status').onValue.listen((event) {
|
||||||
|
final data = event.snapshot.value as Map;
|
||||||
|
setState(() {
|
||||||
|
lampStatus = data['lampu'] == 1;
|
||||||
|
doorStatus = data['pintu'] == 1;
|
||||||
|
buzzerStatus = data['buzzer'] == 1; // ✅ Tambahan
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final bool isGasDanger = gasLevel > gasThreshold;
|
||||||
|
final bool isHot = temperature > tempThreshold;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFF8F6FF),
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Colors.blue.shade900,
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
),
|
||||||
|
title: const Text('Monitoring', style: TextStyle(color: Colors.white)),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Center(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Lottie.asset('assets/animations/monitor.json', width: 80, height: 80),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
const Text('Monitoring', style: TextStyle(fontSize: 26, color: Colors.black87, fontWeight: FontWeight.bold)),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(getFormattedDate(), style: const TextStyle(fontSize: 16, color: Colors.black54)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Kadar Gas + Buzzer
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
gradient: const LinearGradient(
|
||||||
|
colors: [Color(0xFFE0F7FA), Colors.white],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 6, offset: Offset(2, 2))],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Lottie.asset('assets/animations/gas.json', width: 60, height: 60),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text('Kadar Gas', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text('$gasLevel ppm', style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.blue)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
buzzerStatus ? 'Buzzer ON' : 'Buzzer Mati',
|
||||||
|
style: TextStyle(
|
||||||
|
color: buzzerStatus ? Colors.green : Colors.red,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Grid Info Box
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: GridView.count(
|
||||||
|
crossAxisCount: 2,
|
||||||
|
crossAxisSpacing: 16,
|
||||||
|
mainAxisSpacing: 16,
|
||||||
|
children: [
|
||||||
|
_buildInfoBox(
|
||||||
|
title: 'Temperature',
|
||||||
|
iconPath: 'assets/animations/temperature.json',
|
||||||
|
value: '${temperature.toStringAsFixed(1)}°C',
|
||||||
|
statusText: isHot ? 'Blower ON' : 'Blower OFF',
|
||||||
|
statusColor: isHot ? Colors.green : Colors.red,
|
||||||
|
),
|
||||||
|
_buildInfoBox(
|
||||||
|
title: 'Humidity',
|
||||||
|
iconPath: 'assets/animations/humidity.json',
|
||||||
|
value: '${humidity.toStringAsFixed(0)}%',
|
||||||
|
),
|
||||||
|
_buildStatusBox(
|
||||||
|
title: 'Lampu',
|
||||||
|
iconPath: 'assets/animations/lamp.json',
|
||||||
|
isActive: lampStatus,
|
||||||
|
activeColor: Colors.green,
|
||||||
|
inactiveColor: Colors.red,
|
||||||
|
activeLabel: 'ON',
|
||||||
|
inactiveLabel: 'OFF',
|
||||||
|
),
|
||||||
|
_buildStatusBox(
|
||||||
|
title: 'Pintu',
|
||||||
|
iconPath: 'assets/animations/door.json',
|
||||||
|
isActive: doorStatus,
|
||||||
|
activeColor: Colors.red,
|
||||||
|
inactiveColor: Colors.green,
|
||||||
|
activeLabel: 'Terbuka',
|
||||||
|
inactiveLabel: 'Tertutup',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Info Box (Temperature, Humidity, Blower)
|
||||||
|
Widget _buildInfoBox({
|
||||||
|
required String title,
|
||||||
|
required String iconPath,
|
||||||
|
required String value,
|
||||||
|
String? statusText,
|
||||||
|
Color? statusColor,
|
||||||
|
}) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: const LinearGradient(colors: [Color(0xFFE0F7FA), Colors.white], begin: Alignment.topLeft, end: Alignment.bottomRight),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 6, offset: Offset(2, 2))],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Lottie.asset(iconPath, width: 60, height: 60),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(title, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text(value, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: Colors.blue)),
|
||||||
|
if (statusText != null) ...[
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text(statusText, style: TextStyle(fontWeight: FontWeight.bold, color: statusColor ?? Colors.black)),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Status Box (Lampu & Pintu)
|
||||||
|
Widget _buildStatusBox({
|
||||||
|
required String title,
|
||||||
|
required String iconPath,
|
||||||
|
required bool isActive,
|
||||||
|
required Color activeColor,
|
||||||
|
required Color inactiveColor,
|
||||||
|
required String activeLabel,
|
||||||
|
required String inactiveLabel,
|
||||||
|
}) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: const LinearGradient(colors: [Color(0xFFE0F7FA), Colors.white], begin: Alignment.topLeft, end: Alignment.bottomRight),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 6, offset: Offset(2, 2))],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Lottie.asset(iconPath, width: 60, height: 60, animate: isActive),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(title, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text(
|
||||||
|
isActive ? activeLabel : inactiveLabel,
|
||||||
|
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: isActive ? activeColor : inactiveColor),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue