552 lines
14 KiB
Dart
552 lines
14 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
import 'package:firebase_auth/firebase_auth.dart';
|
|
import 'package:firebase_database/firebase_database.dart';
|
|
|
|
class TambahUserDialog extends StatefulWidget {
|
|
const TambahUserDialog({super.key});
|
|
|
|
@override
|
|
State<TambahUserDialog> createState() =>
|
|
_TambahUserDialogState();
|
|
}
|
|
|
|
class _TambahUserDialogState
|
|
extends State<TambahUserDialog> {
|
|
final FirebaseFirestore firestore =
|
|
FirebaseFirestore.instance;
|
|
|
|
final FirebaseAuth auth =
|
|
FirebaseAuth.instance;
|
|
|
|
final DatabaseReference rfidRef =
|
|
FirebaseDatabase.instance.ref(
|
|
'rfid_uid',
|
|
);
|
|
|
|
final DatabaseReference usersRef =
|
|
FirebaseDatabase.instance.ref(
|
|
'users',
|
|
);
|
|
|
|
StreamSubscription? listener;
|
|
|
|
final namaController =
|
|
TextEditingController();
|
|
|
|
final emailController =
|
|
TextEditingController();
|
|
|
|
final passwordController =
|
|
TextEditingController();
|
|
|
|
final confirmPasswordController =
|
|
TextEditingController();
|
|
|
|
final rfidController =
|
|
TextEditingController();
|
|
|
|
String role = "user";
|
|
|
|
bool loading = false;
|
|
|
|
bool hidePassword = true;
|
|
bool hideConfirmPassword = true;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
listener = rfidRef.onValue.listen(
|
|
(event) {
|
|
final data =
|
|
event.snapshot.value;
|
|
|
|
if (data != null &&
|
|
data.toString().isNotEmpty) {
|
|
rfidController.text =
|
|
data.toString();
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
listener?.cancel();
|
|
|
|
namaController.dispose();
|
|
emailController.dispose();
|
|
passwordController.dispose();
|
|
confirmPasswordController.dispose();
|
|
rfidController.dispose();
|
|
|
|
super.dispose();
|
|
}
|
|
|
|
Widget buildTextField({
|
|
required String label,
|
|
required IconData icon,
|
|
required TextEditingController controller,
|
|
bool readOnly = false,
|
|
TextInputType keyboardType =
|
|
TextInputType.text,
|
|
}) {
|
|
return Padding(
|
|
padding:
|
|
const EdgeInsets.only(bottom: 15),
|
|
child: TextField(
|
|
controller: controller,
|
|
readOnly: readOnly,
|
|
keyboardType: keyboardType,
|
|
decoration: InputDecoration(
|
|
labelText: label,
|
|
prefixIcon: Icon(icon),
|
|
filled: true,
|
|
fillColor:
|
|
Colors.grey.shade100,
|
|
border:
|
|
OutlineInputBorder(
|
|
borderRadius:
|
|
BorderRadius.circular(
|
|
15),
|
|
borderSide:
|
|
BorderSide.none,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget buildPasswordField({
|
|
required String label,
|
|
required TextEditingController controller,
|
|
required bool obscureText,
|
|
required VoidCallback onToggle,
|
|
}) {
|
|
return Padding(
|
|
padding:
|
|
const EdgeInsets.only(bottom: 15),
|
|
child: TextField(
|
|
controller: controller,
|
|
obscureText: obscureText,
|
|
decoration: InputDecoration(
|
|
labelText: label,
|
|
prefixIcon:
|
|
const Icon(Icons.lock),
|
|
suffixIcon: IconButton(
|
|
icon: Icon(
|
|
obscureText
|
|
? Icons.visibility_off
|
|
: Icons.visibility,
|
|
),
|
|
onPressed: onToggle,
|
|
),
|
|
filled: true,
|
|
fillColor:
|
|
Colors.grey.shade100,
|
|
border:
|
|
OutlineInputBorder(
|
|
borderRadius:
|
|
BorderRadius.circular(
|
|
15),
|
|
borderSide:
|
|
BorderSide.none,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> saveUser() async {
|
|
try {
|
|
setState(() {
|
|
loading = true;
|
|
});
|
|
|
|
if (namaController.text.isEmpty ||
|
|
emailController.text.isEmpty ||
|
|
passwordController.text.isEmpty) {
|
|
throw Exception(
|
|
"Lengkapi semua data");
|
|
}
|
|
|
|
if (confirmPasswordController
|
|
.text.isEmpty) {
|
|
throw Exception(
|
|
"Konfirmasi password wajib diisi");
|
|
}
|
|
|
|
if (passwordController
|
|
.text.length <
|
|
6) {
|
|
throw Exception(
|
|
"Password minimal 6 karakter");
|
|
}
|
|
|
|
if (passwordController.text !=
|
|
confirmPasswordController
|
|
.text) {
|
|
throw Exception(
|
|
"Password dan konfirmasi password tidak sama");
|
|
}
|
|
|
|
if (role == 'user' &&
|
|
rfidController.text.isEmpty) {
|
|
throw Exception(
|
|
"Silakan scan RFID terlebih dahulu");
|
|
}
|
|
|
|
if (role == 'user') {
|
|
final existing =
|
|
await firestore
|
|
.collection(
|
|
'users')
|
|
.where(
|
|
'rfid_uid',
|
|
isEqualTo:
|
|
rfidController.text,
|
|
)
|
|
.get();
|
|
|
|
if (existing.docs.isNotEmpty) {
|
|
throw Exception(
|
|
"RFID sudah digunakan");
|
|
}
|
|
}
|
|
|
|
final credential =
|
|
await auth
|
|
.createUserWithEmailAndPassword(
|
|
email: emailController.text
|
|
.trim(),
|
|
password:
|
|
passwordController.text
|
|
.trim(),
|
|
);
|
|
|
|
await firestore
|
|
.collection('users')
|
|
.doc(
|
|
credential.user!.uid,
|
|
)
|
|
.set({
|
|
"nama":
|
|
namaController.text.trim(),
|
|
"email":
|
|
emailController.text.trim(),
|
|
"role": role,
|
|
"rfid_uid":
|
|
role == "user"
|
|
? rfidController.text
|
|
: "",
|
|
});
|
|
|
|
if (role == "user") {
|
|
await usersRef
|
|
.child(
|
|
rfidController.text,
|
|
)
|
|
.set({
|
|
"nama":
|
|
namaController.text
|
|
.trim(),
|
|
"saldo": 0,
|
|
"botol_plastik": 0,
|
|
"botol_kaleng": 0,
|
|
"botol_kaca": 0,
|
|
"history": {},
|
|
});
|
|
}
|
|
|
|
await rfidRef.set("");
|
|
|
|
if (mounted) {
|
|
Navigator.pop(context);
|
|
|
|
ScaffoldMessenger.of(context)
|
|
.showSnackBar(
|
|
const SnackBar(
|
|
content: Text(
|
|
"User berhasil ditambahkan",
|
|
),
|
|
),
|
|
);
|
|
}
|
|
} catch (e) {
|
|
// ignore: use_build_context_synchronously
|
|
ScaffoldMessenger.of(context)
|
|
.showSnackBar(
|
|
SnackBar(
|
|
content: Text(
|
|
e.toString().replaceAll(
|
|
"Exception: ",
|
|
"",
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (mounted) {
|
|
setState(() {
|
|
loading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(
|
|
BuildContext context) {
|
|
return Dialog(
|
|
shape:
|
|
RoundedRectangleBorder(
|
|
borderRadius:
|
|
BorderRadius.circular(
|
|
25),
|
|
),
|
|
child: SingleChildScrollView(
|
|
child: Padding(
|
|
padding:
|
|
const EdgeInsets.all(20),
|
|
child: Column(
|
|
mainAxisSize:
|
|
MainAxisSize.min,
|
|
children: [
|
|
CircleAvatar(
|
|
radius: 40,
|
|
backgroundColor:
|
|
const Color(
|
|
0xFFA5CC36),
|
|
child: const Icon(
|
|
Icons.person_add,
|
|
size: 40,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
|
|
const SizedBox(
|
|
height: 15),
|
|
|
|
const Text(
|
|
"Tambah Pengguna",
|
|
style: TextStyle(
|
|
fontSize: 22,
|
|
fontWeight:
|
|
FontWeight.bold,
|
|
),
|
|
),
|
|
|
|
const SizedBox(
|
|
height: 20),
|
|
|
|
buildTextField(
|
|
label: "Nama",
|
|
icon: Icons.person,
|
|
controller:
|
|
namaController,
|
|
),
|
|
|
|
buildTextField(
|
|
label: "Email",
|
|
icon: Icons.email,
|
|
controller:
|
|
emailController,
|
|
keyboardType:
|
|
TextInputType
|
|
.emailAddress,
|
|
),
|
|
|
|
buildPasswordField(
|
|
label: "Password",
|
|
controller:
|
|
passwordController,
|
|
obscureText:
|
|
hidePassword,
|
|
onToggle: () {
|
|
setState(() {
|
|
hidePassword =
|
|
!hidePassword;
|
|
});
|
|
},
|
|
),
|
|
|
|
buildPasswordField(
|
|
label:
|
|
"Konfirmasi Password",
|
|
controller:
|
|
confirmPasswordController,
|
|
obscureText:
|
|
hideConfirmPassword,
|
|
onToggle: () {
|
|
setState(() {
|
|
hideConfirmPassword =
|
|
!hideConfirmPassword;
|
|
});
|
|
},
|
|
),
|
|
|
|
DropdownButtonFormField(
|
|
value: role,
|
|
decoration:
|
|
InputDecoration(
|
|
filled: true,
|
|
fillColor:
|
|
Colors.grey
|
|
.shade100,
|
|
border:
|
|
OutlineInputBorder(
|
|
borderRadius:
|
|
BorderRadius
|
|
.circular(
|
|
15),
|
|
borderSide:
|
|
BorderSide
|
|
.none,
|
|
),
|
|
),
|
|
items: const [
|
|
DropdownMenuItem(
|
|
value: "user",
|
|
child:
|
|
Text("User"),
|
|
),
|
|
DropdownMenuItem(
|
|
value: "admin",
|
|
child:
|
|
Text("Admin"),
|
|
),
|
|
],
|
|
onChanged: (value) {
|
|
setState(() {
|
|
role = value!;
|
|
});
|
|
},
|
|
),
|
|
|
|
const SizedBox(
|
|
height: 15),
|
|
|
|
if (role == "user")
|
|
Container(
|
|
padding:
|
|
const EdgeInsets
|
|
.all(15),
|
|
decoration:
|
|
BoxDecoration(
|
|
color: Colors
|
|
.green
|
|
.shade50,
|
|
borderRadius:
|
|
BorderRadius
|
|
.circular(
|
|
15),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
const Row(
|
|
children: [
|
|
Icon(
|
|
Icons
|
|
.nfc),
|
|
SizedBox(
|
|
width:
|
|
10),
|
|
Text(
|
|
"Scan RFID",
|
|
style:
|
|
TextStyle(
|
|
fontWeight:
|
|
FontWeight.bold,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
const SizedBox(
|
|
height:
|
|
10),
|
|
|
|
buildTextField(
|
|
label:
|
|
"RFID UID",
|
|
icon: Icons
|
|
.credit_card,
|
|
controller:
|
|
rfidController,
|
|
readOnly:
|
|
true,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(
|
|
height: 20),
|
|
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child:
|
|
OutlinedButton(
|
|
onPressed: () {
|
|
Navigator.pop(
|
|
context);
|
|
},
|
|
child: const Text(
|
|
"Batal"),
|
|
),
|
|
),
|
|
|
|
const SizedBox(
|
|
width: 10),
|
|
|
|
Expanded(
|
|
child:
|
|
ElevatedButton.icon(
|
|
onPressed:
|
|
loading
|
|
? null
|
|
: saveUser,
|
|
icon: loading
|
|
? const SizedBox(
|
|
height:
|
|
18,
|
|
width:
|
|
18,
|
|
child:
|
|
CircularProgressIndicator(
|
|
strokeWidth:
|
|
2,
|
|
),
|
|
)
|
|
: const Icon(
|
|
Icons
|
|
.save),
|
|
label: Text(
|
|
loading
|
|
? "Menyimpan..."
|
|
: "Simpan",
|
|
),
|
|
style:
|
|
ElevatedButton
|
|
.styleFrom(
|
|
backgroundColor:
|
|
const Color(
|
|
0xFFA5CC36),
|
|
foregroundColor:
|
|
Colors.black,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
} |