694 lines
22 KiB
Dart
694 lines
22 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:firebase_database/firebase_database.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'DataDisplayPage.dart';
|
|
import 'SettingsPage.dart';
|
|
|
|
DatabaseReference _dataListrikRef =
|
|
FirebaseDatabase.instance.reference().child('data-listrik');
|
|
|
|
final TextEditingController addTokenController = TextEditingController();
|
|
Stream<String> _streamArus = FirebaseDatabase.instance
|
|
.reference()
|
|
.child('sensor')
|
|
.child('current')
|
|
.onValue
|
|
.map((event) => event.snapshot.value.toString());
|
|
|
|
Stream<String> _streamTegangan = FirebaseDatabase.instance
|
|
.reference()
|
|
.child('sensor')
|
|
.child('voltage')
|
|
.onValue
|
|
.map((event) => event.snapshot.value.toString());
|
|
|
|
Stream<String> _streamKwh = FirebaseDatabase.instance
|
|
.reference()
|
|
.child('sensor')
|
|
.child('energy')
|
|
.onValue
|
|
.map((event) => event.snapshot.value.toString());
|
|
|
|
Stream<String> _streamWatt = FirebaseDatabase.instance
|
|
.reference()
|
|
.child('sensor')
|
|
.child('power')
|
|
.onValue
|
|
.map((event) => event.snapshot.value.toString());
|
|
|
|
class Dashboard extends StatefulWidget {
|
|
@override
|
|
_DashboardState createState() => _DashboardState();
|
|
|
|
static void sendMessageToTelegram(String s) {}
|
|
}
|
|
|
|
class _DashboardState extends State<Dashboard> {
|
|
DatabaseReference _tokenRef =
|
|
FirebaseDatabase.instance.reference().child('tokens');
|
|
DatabaseReference _channelRef =
|
|
FirebaseDatabase.instance.reference().child('channels');
|
|
|
|
static Future<void> sendMessageToTelegram(String message) async {
|
|
final String botToken = '6904712924:AAGEVa4_ejmj-4uKz_nt_i-ceghMPWYW_5M';
|
|
final String chatId = '1709517653';
|
|
final String url = 'https://api.telegram.org/bot$botToken/sendMessage';
|
|
|
|
try {
|
|
await http.post(
|
|
Uri.parse(url),
|
|
body: {
|
|
'chat_id': chatId,
|
|
'text': message,
|
|
},
|
|
);
|
|
} catch (e) {
|
|
print('Error: $e');
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: Color(0xFF102C57),
|
|
body: SingleChildScrollView(
|
|
child: Container(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
account(context),
|
|
SizedBox(height: 20),
|
|
monitoring(),
|
|
resetButton(),
|
|
kwhInput(),
|
|
button(),
|
|
relayControl(),
|
|
SizedBox(height: 20),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void resetDataListrik() {
|
|
_dataListrikRef.remove().then((_) {
|
|
print('Data-listrik deleted successfully');
|
|
}).catchError((error) {
|
|
print('Error deleting data-listrik: $error');
|
|
});
|
|
}
|
|
|
|
void _showSuccessDialog() {
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return AlertDialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20.0),
|
|
),
|
|
backgroundColor: Colors.transparent,
|
|
content: Container(
|
|
padding: EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
shape: BoxShape.rectangle,
|
|
borderRadius: BorderRadius.circular(20),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black,
|
|
blurRadius: 10.0,
|
|
offset: Offset(0.0, 10.0),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
"Proses",
|
|
style: TextStyle(
|
|
fontSize: 22,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
SizedBox(height: 20),
|
|
Text(
|
|
"Token berhasil dimasukkan ke dalam database dan diproses.",
|
|
style: TextStyle(
|
|
fontSize: 16.0,
|
|
),
|
|
textAlign: TextAlign.left,
|
|
),
|
|
SizedBox(height: 24),
|
|
Align(
|
|
alignment: Alignment.bottomRight,
|
|
child: TextButton(
|
|
onPressed: () {
|
|
Navigator.of(context).pop(); // Tutup dialog
|
|
},
|
|
child: Text(
|
|
"OK",
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<bool> _checkIfTokenExists(String token) async {
|
|
DatabaseEvent event = await _tokenRef.child(token).once();
|
|
DataSnapshot snapshot = event.snapshot;
|
|
return snapshot.value != null;
|
|
}
|
|
|
|
void _addTokenToDatabase(String token) {
|
|
String tokenWithoutSpaces = token.replaceAll(' ', '');
|
|
|
|
if (tokenWithoutSpaces.isEmpty) {
|
|
print('Token harus diisi');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Menyimpan token sebagai nilai di bawah node 'tokens'
|
|
_tokenRef.child('tokens').set(tokenWithoutSpaces).then((_) {
|
|
print('Token berhasil diperbarui: $tokenWithoutSpaces');
|
|
sendMessageToTelegram(
|
|
'Token berhasil diperbarui dan diproses: $tokenWithoutSpaces');
|
|
sendMessageToTelegram(
|
|
'Tunggu beberapa saat untuk memvalidasi token dan Silakan kirim kata /photo untuk memvalidasi kwh.');
|
|
_showSuccessDialog();
|
|
}).catchError((error) {
|
|
print('Error: $error');
|
|
sendMessageToTelegram('Gagal memperbarui token: $error');
|
|
});
|
|
} catch (e) {
|
|
print('Error: $e');
|
|
sendMessageToTelegram('Gagal memperbarui token: $e');
|
|
}
|
|
}
|
|
|
|
void _updateChannelStatus(String channelName, bool status) {
|
|
try {
|
|
_channelRef.update({
|
|
channelName: status ? "On" : "Off",
|
|
});
|
|
print(
|
|
'Status $channelName berhasil diperbarui: ${status ? "On" : "Off"}');
|
|
} catch (e) {
|
|
print('Error: $e');
|
|
}
|
|
}
|
|
|
|
Widget account(BuildContext context) {
|
|
return Center(
|
|
child: Container(
|
|
margin: EdgeInsets.only(top: 50),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Container(
|
|
margin: EdgeInsets.only(left: 20),
|
|
height: 40,
|
|
width: 40,
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
image: DecorationImage(
|
|
image: AssetImage('assets/user.png'),
|
|
fit: BoxFit.cover,
|
|
),
|
|
),
|
|
),
|
|
SizedBox(width: 20),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Hai !',
|
|
style: GoogleFonts.poppins(color: Colors.white),
|
|
),
|
|
Text(
|
|
'Nuril Akbar',
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 16,
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
Row(
|
|
children: [
|
|
IconButton(
|
|
onPressed: () {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(builder: (context) => SettingsPage()),
|
|
);
|
|
},
|
|
icon: Icon(
|
|
Icons.settings,
|
|
color: Colors.white,
|
|
size: 30,
|
|
),
|
|
),
|
|
IconButton(
|
|
onPressed: () {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => DataDisplayPage()),
|
|
);
|
|
},
|
|
icon: Icon(
|
|
CupertinoIcons.doc_chart_fill,
|
|
color: Colors.white,
|
|
size: 30,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget monitoring() {
|
|
return Center(
|
|
child: Container(
|
|
margin: EdgeInsets.only(top: 20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Monitoring',
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
SizedBox(height: 14),
|
|
Container(
|
|
height: 230,
|
|
width: 330,
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(16),
|
|
color: Colors.white,
|
|
),
|
|
child: Container(
|
|
margin: EdgeInsets.all(10),
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
_buildMonitoringBox('Arus', _streamArus),
|
|
_buildMonitoringBox('Tegangan', _streamTegangan),
|
|
],
|
|
),
|
|
SizedBox(height: 10),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
_buildMonitoringBox('kWh', _streamKwh),
|
|
_buildMonitoringBox('Watt', _streamWatt),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
SizedBox(height: 2),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildMonitoringBox(String title, Stream<String> stream) {
|
|
return Container(
|
|
width: 150,
|
|
child: Column(
|
|
children: [
|
|
Text(title),
|
|
SizedBox(height: 8),
|
|
Container(
|
|
height: 70,
|
|
width: double.infinity,
|
|
decoration: BoxDecoration(
|
|
color: Colors.amber,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
alignment: Alignment.center,
|
|
child: StreamBuilder(
|
|
stream: stream,
|
|
builder: (context, snapshot) {
|
|
if (!snapshot.hasData || snapshot.hasError) {
|
|
return CircularProgressIndicator(); // Placeholder jika data belum dimuat
|
|
}
|
|
var value = snapshot.data ?? '0';
|
|
return Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontSize: 30,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget resetButton() {
|
|
return Column(
|
|
children: [
|
|
SizedBox(height: 2),
|
|
Align(
|
|
alignment: Alignment.center,
|
|
child: Container(
|
|
margin: EdgeInsets.symmetric(vertical: 20),
|
|
height: 54,
|
|
width: 220,
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(28),
|
|
color: Color(0xFFDAC0A3),
|
|
),
|
|
child: TextButton(
|
|
onPressed: () {
|
|
resetDataListrik(); // Call the reset function here
|
|
},
|
|
child: Text(
|
|
'Reset',
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.black,
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget kwhInput() {
|
|
return Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Token KWH',
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
SizedBox(height: 12),
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(20),
|
|
color: Colors.white,
|
|
),
|
|
child: Padding(
|
|
padding: EdgeInsets.all(15),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
TextFormField(
|
|
controller: addTokenController,
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w300,
|
|
fontSize: 20,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
inputFormatters: [
|
|
LengthLimitingTextInputFormatter(24),
|
|
FilteringTextInputFormatter.digitsOnly,
|
|
_TokenInputFormatter(),
|
|
],
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration.collapsed(
|
|
hintText: '',
|
|
hintStyle: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w300,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
validator: (value) {
|
|
if (value!.isEmpty) {
|
|
return 'Token harus diisi';
|
|
}
|
|
if (value.replaceAll(' ', '').length != 20) {
|
|
return 'Token harus terdiri dari 20 digit';
|
|
}
|
|
return null; // Remove validation message if length is 20
|
|
},
|
|
),
|
|
SizedBox(height: 8),
|
|
Text(
|
|
'*Token harus terdiri dari 20 digit',
|
|
style: TextStyle(color: Colors.red),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget button() {
|
|
return Padding(
|
|
padding: EdgeInsets.symmetric(vertical: 20),
|
|
child: Align(
|
|
alignment: Alignment.center,
|
|
child: Container(
|
|
height: 54,
|
|
width: 220,
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(28),
|
|
color: Color(0xFFDAC0A3),
|
|
),
|
|
child: TextButton(
|
|
onPressed: () async {
|
|
String token = addTokenController.text.trim();
|
|
if (token.replaceAll(' ', '').length == 20) {
|
|
_addTokenToDatabase(token);
|
|
|
|
addTokenController
|
|
.clear(); // Membersihkan input field setelah menambahkan token
|
|
} else {
|
|
print('Token harus terdiri dari 20 karakter');
|
|
}
|
|
},
|
|
child: Text(
|
|
'Input',
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.black,
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget relayControl() {
|
|
return Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Switch Control',
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
SizedBox(height: 12),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: [
|
|
Column(
|
|
children: [
|
|
Text(
|
|
'Switch 1',
|
|
style: TextStyle(fontSize: 16, color: Colors.white),
|
|
),
|
|
SizedBox(height: 8),
|
|
Transform.scale(
|
|
scale: 1.4,
|
|
child: StreamBuilder(
|
|
stream: _channelRef.child('channel1').onValue,
|
|
builder: (context, snapshot) {
|
|
if (!snapshot.hasData || snapshot.hasError) {
|
|
return CircularProgressIndicator(); // Placeholder jika data belum dimuat
|
|
}
|
|
var status = snapshot.data!.snapshot.value ?? false;
|
|
return Switch(
|
|
value: status == "On",
|
|
activeColor: Theme.of(context).primaryColor,
|
|
onChanged: (newValue) {
|
|
setState(() {
|
|
_updateChannelStatus('channel1', newValue);
|
|
});
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Column(
|
|
children: [
|
|
Text(
|
|
'Switch 2',
|
|
style: TextStyle(fontSize: 16, color: Colors.white),
|
|
),
|
|
SizedBox(height: 8),
|
|
Transform.scale(
|
|
scale: 1.4,
|
|
child: StreamBuilder(
|
|
stream: _channelRef.child('channel2').onValue,
|
|
builder: (context, snapshot) {
|
|
if (!snapshot.hasData || snapshot.hasError) {
|
|
return CircularProgressIndicator(); // Placeholder jika data belum dimuat
|
|
}
|
|
var status = snapshot.data!.snapshot.value ?? false;
|
|
return Switch(
|
|
value: status == "On",
|
|
activeColor: Theme.of(context).primaryColor,
|
|
onChanged: (newValue) {
|
|
setState(() {
|
|
_updateChannelStatus('channel2', newValue);
|
|
});
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 12),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: [
|
|
Column(
|
|
children: [
|
|
Text(
|
|
'Switch 3',
|
|
style: TextStyle(fontSize: 16, color: Colors.white),
|
|
),
|
|
SizedBox(height: 8),
|
|
Transform.scale(
|
|
scale: 1.4,
|
|
child: StreamBuilder(
|
|
stream: _channelRef.child('channel3').onValue,
|
|
builder: (context, snapshot) {
|
|
if (!snapshot.hasData || snapshot.hasError) {
|
|
return CircularProgressIndicator(); // Placeholder jika data belum dimuat
|
|
}
|
|
var status = snapshot.data!.snapshot.value ?? false;
|
|
return Switch(
|
|
value: status == "On",
|
|
activeColor: Theme.of(context).primaryColor,
|
|
onChanged: (newValue) {
|
|
setState(() {
|
|
_updateChannelStatus('channel3', newValue);
|
|
});
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Column(
|
|
children: [
|
|
Text(
|
|
'Switch 4',
|
|
style: TextStyle(fontSize: 16, color: Colors.white),
|
|
),
|
|
SizedBox(height: 8),
|
|
Transform.scale(
|
|
scale: 1.4,
|
|
child: StreamBuilder(
|
|
stream: _channelRef.child('channel4').onValue,
|
|
builder: (context, snapshot) {
|
|
if (!snapshot.hasData || snapshot.hasError) {
|
|
return CircularProgressIndicator(); // Placeholder jika data belum dimuat
|
|
}
|
|
var status = snapshot.data!.snapshot.value ?? false;
|
|
return Switch(
|
|
value: status == "On",
|
|
activeColor: Theme.of(context).primaryColor,
|
|
onChanged: (newValue) {
|
|
setState(() {
|
|
_updateChannelStatus('channel4', newValue);
|
|
});
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _TokenInputFormatter extends TextInputFormatter {
|
|
@override
|
|
TextEditingValue formatEditUpdate(
|
|
TextEditingValue oldValue, TextEditingValue newValue) {
|
|
if (newValue.selection.baseOffset == 0) {
|
|
return newValue;
|
|
}
|
|
final StringBuffer newText = StringBuffer();
|
|
for (int i = 0; i < newValue.text.length; i++) {
|
|
if (i > 0 && i % 4 == 0) {
|
|
newText.write(' ');
|
|
}
|
|
newText.write(newValue.text[i]);
|
|
}
|
|
return TextEditingValue(
|
|
text: newText.toString(),
|
|
selection: TextSelection.collapsed(offset: newText.length),
|
|
);
|
|
}
|
|
}
|