TKK_E32221274/lib/presentation/pages/device_dashboard/device_dashboard.dart

1402 lines
58 KiB
Dart

import 'dart:convert';
import 'package:bottom_picker/bottom_picker.dart';
import 'package:bottom_picker/resources/arrays.dart';
import 'package:custom_refresh_indicator/custom_refresh_indicator.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../core/constants/color_constants.dart';
import '../../../features/database/bloc/device/device_bloc.dart';
import '../../../features/database/bloc/device_schedule/device_schedule_bloc.dart';
import '../../../features/database/bloc/mesh_network/mesh_network_bloc.dart';
import '../../../features/database/models/device.dart';
import '../../../features/mqtt/bloc/mqtt_bloc.dart';
class DeviceDashboardPage extends StatefulWidget {
final Device device;
final bool currentStatus;
final bool currentOnline;
final String currentRSSI;
const DeviceDashboardPage({
super.key,
required this.device,
required this.currentStatus,
required this.currentOnline,
required this.currentRSSI,
});
@override
State<DeviceDashboardPage> createState() => _DeviceDashboardPageState();
}
class _DeviceDashboardPageState extends State<DeviceDashboardPage> {
final GlobalKey<CustomRefreshIndicatorState> _refreshKey =
GlobalKey<CustomRefreshIndicatorState>();
late bool _currentOnline;
late bool _currentStatus;
late String _currentRSSI;
late TextEditingController _controllerDeviceName;
bool _isEditingDeviceName = false;
late final FocusNode _focusDeviceNameTextField;
late TextEditingController _controllerMeshName;
bool _isEditingMeshName = false;
late final FocusNode _focusMeshNameTextField;
List<Map<String, dynamic>> scheduleList = [];
String? selectedState;
TimeOfDay? selectedTime;
@override
void initState() {
super.initState();
_currentOnline = widget.currentOnline;
_currentStatus = widget.currentStatus;
_currentRSSI = widget.currentRSSI;
_controllerDeviceName = TextEditingController(
text: widget.device.name,
);
_controllerMeshName = TextEditingController(
text: widget.device.meshNetwork.name,
);
_focusDeviceNameTextField = FocusNode();
_focusMeshNameTextField = FocusNode();
}
@override
void dispose() {
_controllerDeviceName.dispose();
_controllerMeshName.dispose();
_focusDeviceNameTextField.dispose();
_focusMeshNameTextField.dispose();
super.dispose();
}
void _toggleSwitch(bool isCurrentlyOnline) {
final newCommand = isCurrentlyOnline ? 'OFF' : 'ON';
context.read<MQTTBloc>().add(
SetDeviceState(
macRoot: widget.device.meshNetwork.macRoot,
nodeId: widget.device.nodeId,
value: newCommand,
),
);
setState(() {
_currentStatus = !_currentStatus;
});
}
void _saveDeviceName() {
final newDeviceName = _controllerDeviceName.text.trim();
if (newDeviceName.isNotEmpty && newDeviceName != widget.device.name) {
context.read<DeviceBloc>().add(
UpdateDeviceName(
id: widget.device.id!,
name: newDeviceName,
),
);
setState(() {
_isEditingDeviceName = false;
_controllerDeviceName.text = newDeviceName;
});
}
}
void _saveMeshName() {
final newMeshName = _controllerMeshName.text.trim();
if (newMeshName.isNotEmpty &&
newMeshName != widget.device.meshNetwork.name) {
context.read<MeshNetworkBloc>().add(
UpdateMeshNetworkName(
id: widget.device.meshNetwork.id!,
name: newMeshName,
),
);
setState(() {
_isEditingMeshName = false;
_controllerMeshName.text = newMeshName;
});
}
}
@override
Widget build(BuildContext context) {
return MultiBlocListener(
listeners: [
BlocListener<MQTTBloc, MQTTState>(
listener: (context, state) {
if (state is MQTTConnected) {
final matched = state.deviceStatuses.where(
(ds) => ds.nodeId == widget.device.nodeId,
);
if (matched.isNotEmpty) {
final Map<String, dynamic> statusMap =
json.decode(matched.last.value);
setState(() {
_currentOnline = true;
});
if (statusMap.containsKey('rssi')) {
final signalStrength = (statusMap['rssi']).toString();
setState(() {
_currentRSSI = signalStrength;
});
}
if (statusMap.containsKey('status')) {
final currentStatus =
(statusMap['status']).toString().toUpperCase() == 'ON';
setState(() {
_currentStatus = currentStatus;
});
}
}
} else if (state is! MQTTConnected) {
setState(() {
_currentOnline = false;
});
}
},
),
BlocListener<DeviceBloc, DeviceState>(
listener: (context, state) {
if (state is DeviceLoading) {
const Center(child: CircularProgressIndicator());
} else if (state is UpdateDeviceSuccess) {
showDialog(
barrierDismissible: false,
context: context,
builder: (_) {
return PopScope(
canPop: false,
child: AlertDialog(
backgroundColor: ColorConstants.lightBlueAppColor,
title: const Text('Update Device Name'),
content: const Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Device name successfully updated!'),
],
),
actions: [
TextButton(
onPressed: () {
context.read<MQTTBloc>().add(
RequestDevicesData(
macRoot: widget.device.meshNetwork.macRoot,
command: 'getNodes',
),
);
Navigator.pop(context);
},
child: Text(
'OK',
style: TextStyle(
color: ColorConstants.blackAppColor,
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
),
],
),
);
},
);
} else if (state is DeviceFailure) {
showDialog(
barrierDismissible: false,
context: context,
builder: (_) {
return PopScope(
canPop: false,
child: AlertDialog(
backgroundColor: ColorConstants.lightBlueAppColor,
title: const Text('Update Device Name'),
content: const Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Device name failed to update'),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text(
'OK',
style: TextStyle(
color: ColorConstants.blackAppColor,
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
),
],
),
);
},
);
}
},
),
BlocListener<MeshNetworkBloc, MeshNetworkState>(
listener: (context, state) {
if (state is MeshNetworkLoading) {
const Center(child: CircularProgressIndicator());
} else if (state is UpdateMeshNetworkSuccess) {
showDialog(
barrierDismissible: false,
context: context,
builder: (_) {
return PopScope(
canPop: false,
child: AlertDialog(
backgroundColor: ColorConstants.lightBlueAppColor,
title: const Text('Update Mesh Name'),
content: const Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Mesh name successfully updated!'),
],
),
actions: [
TextButton(
onPressed: () {
context.read<DeviceBloc>().add(GetDevices());
context.read<MQTTBloc>().add(
RequestDevicesData(
macRoot: widget.device.meshNetwork.macRoot,
command: 'getNodes',
),
);
Navigator.pop(context);
},
child: Text(
'OK',
style: TextStyle(
color: ColorConstants.blackAppColor,
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
),
],
),
);
},
);
} else if (state is MeshNetworkFailure) {
showDialog(
barrierDismissible: false,
context: context,
builder: (_) {
return PopScope(
canPop: false,
child: AlertDialog(
backgroundColor: ColorConstants.lightBlueAppColor,
title: const Text('Update Mesh Name'),
content: const Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Mesh name failed to update'),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text(
'OK',
style: TextStyle(
color: ColorConstants.blackAppColor,
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
),
],
),
);
},
);
}
},
),
BlocListener<DeviceScheduleBloc, DeviceScheduleState>(
listener: (context, state) {
if (state is DeviceScheduleLoading) {
const Center(child: CircularProgressIndicator());
} else if (state is DeviceScheduleLoaded) {
setState(() {
scheduleList = state.schedules
.map(
(s) => s.toDisplayMap(),
)
.toList();
});
} else if (state is SaveDeviceScheduleSuccess) {
showDialog(
barrierDismissible: false,
context: context,
builder: (_) {
return PopScope(
canPop: false,
child: AlertDialog(
backgroundColor: ColorConstants.lightBlueAppColor,
title: const Text('Add Device Schedule'),
content: const Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Device schedule successfully added!'),
Text("Dont forget to set the schedule to device"),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text(
'OK',
style: TextStyle(
color: ColorConstants.blackAppColor,
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
),
],
),
);
},
);
} else if (state is DeleteDeviceScheduleSuccess) {
showDialog(
barrierDismissible: false,
context: context,
builder: (_) {
return PopScope(
canPop: false,
child: AlertDialog(
backgroundColor: ColorConstants.lightBlueAppColor,
title: const Text('Delete Device Schedule'),
content: const Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Device schedule deleted successfully!'),
Text("Dont forget to set the schedule to device"),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text(
'OK',
style: TextStyle(
color: ColorConstants.blackAppColor,
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
),
],
),
);
},
);
} else if (state is DeviceScheduleFailure) {
showDialog(
barrierDismissible: false,
context: context,
builder: (_) {
return PopScope(
canPop: false,
child: AlertDialog(
backgroundColor: ColorConstants.lightBlueAppColor,
title: const Text('Device Schedule Failure'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Gagal: ${state.message}'),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text(
'OK',
style: TextStyle(
color: ColorConstants.blackAppColor,
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
),
],
),
);
},
);
}
},
),
],
child: Scaffold(
appBar: AppBar(
backgroundColor: ColorConstants.lightBlueAppColor,
leading: Builder(
builder: (context) => GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => _onBackButtonTapped(context),
child: const Icon(
Icons.chevron_left,
color: Colors.black,
),
),
),
title: const Text(
"Device Dashboard",
style: TextStyle(
color: Colors.black,
),
),
actions: [
// Refresh manual dengan tombol
IconButton(
icon: const Icon(Icons.refresh_rounded),
onPressed: () {
_refreshKey.currentState?.refresh(
draggingCurve: Curves.easeOutBack,
);
},
tooltip: "Refresh Device",
),
const SizedBox(width: 5),
],
),
body: CustomRefreshIndicator(
key: _refreshKey,
onRefresh: () async {
print("Home Req Device Data: ${widget.device.nodeId}");
context.read<MQTTBloc>().add(
RequestDeviceData(
macRoot: widget.device.meshNetwork.macRoot,
nodeId: widget.device.nodeId,
),
);
},
builder: (BuildContext context, Widget child,
IndicatorController controller) {
return AnimatedBuilder(
animation: controller,
builder: (context, _) {
final double pulledExtent = controller.value.clamp(0.0, 1.0);
return Stack(
alignment: Alignment.topCenter,
children: [
Padding(
padding: const EdgeInsets.all(16),
child: SizedBox(
height: 30,
width: 30,
child: CircularProgressIndicator(
strokeWidth: 2.5,
color: Colors.blueAccent,
value: pulledExtent,
),
),
),
Padding(
padding: EdgeInsets.only(top: pulledExtent * 60),
child: child,
),
],
);
},
);
},
child: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.only(top: 8, left: 20, right: 20),
width: MediaQuery.of(context).size.width,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Row(
children: [
Expanded(
child: TextField(
controller: _controllerDeviceName,
enabled: true,
readOnly: !_isEditingDeviceName,
focusNode: _focusDeviceNameTextField,
style: const TextStyle(
fontSize: 26,
color: Colors.black,
),
decoration: const InputDecoration(
isDense: true,
border: InputBorder.none,
),
onTapOutside: (event) {
setState(() {
_controllerDeviceName.text =
widget.device.name;
_isEditingDeviceName = false;
});
_focusDeviceNameTextField.unfocus();
},
onSubmitted: (_) {
showDialog(
context: context,
builder: (_) => AlertDialog(
backgroundColor:
ColorConstants.lightBlueAppColor,
title: const Text(
"Update Device Name",
),
content: const Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Yakin ingin mengganti nama device?",
),
],
),
actions: [
TextButton(
style: const ButtonStyle(
backgroundColor:
WidgetStatePropertyAll<Color>(
Colors.green,
),
),
onPressed: () {
setState(() {
_controllerDeviceName.text =
widget.device.name;
_isEditingDeviceName = false;
});
Navigator.pop(context);
},
child: Text(
'Tidak',
style: TextStyle(
color: ColorConstants
.blackAppColor,
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
),
TextButton(
style: const ButtonStyle(
backgroundColor:
WidgetStatePropertyAll<Color>(
Colors.red,
),
),
onPressed: () {
_saveDeviceName(); // simpan dan disable field
Navigator.pop(context);
},
child: Text(
'Ya!',
style: TextStyle(
color: ColorConstants
.blackAppColor,
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
),
],
),
);
},
),
),
IconButton(
onPressed: () {
setState(() {
_isEditingDeviceName = true;
});
Future.delayed(
const Duration(milliseconds: 100), () {
_focusDeviceNameTextField.requestFocus();
_controllerDeviceName.selection =
TextSelection.collapsed(
offset: _controllerDeviceName.text.length,
);
});
},
icon: const Icon(Icons.edit),
iconSize: 24,
constraints: const BoxConstraints(),
tooltip: "Edit Device Name",
),
],
),
),
const SizedBox(width: 10),
Switch(
value: _currentStatus,
onChanged: _currentOnline
? (_) => _toggleSwitch(_currentStatus)
: null, // Nonaktifkan jika offline
activeColor: Colors.green,
inactiveThumbColor:
_currentOnline ? Colors.red : Colors.grey,
inactiveTrackColor: _currentOnline
? Colors.red.shade200
: Colors.grey.shade400,
thumbIcon: WidgetStateProperty.resolveWith<Icon?>(
(Set<WidgetState> states) {
if (states.contains(WidgetState.selected)) {
return const Icon(
Icons.lightbulb_rounded,
color: Colors.white,
);
}
return const Icon(
Icons.lightbulb_rounded,
color: Colors.black54,
);
}),
trackOutlineColor:
WidgetStateProperty.resolveWith<Color?>(
(Set<WidgetState> states) {
if (states.contains(WidgetState.selected)) {
return _currentOnline
? Colors.green
: Colors.grey;
}
return _currentOnline ? Colors.red : Colors.grey;
},
),
),
],
),
),
Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
children: [
const SizedBox(
width: 120,
child: Text(
"Node ID",
style: TextStyle(
fontSize: 16,
),
),
),
const SizedBox(width: 8, child: Text(":")),
SizedBox(
width: MediaQuery.of(context).size.width / 2,
child: Text(
widget.device.nodeId,
style: const TextStyle(
fontSize: 16,
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
children: [
const SizedBox(
width: 120,
child: Text(
"Status",
style: TextStyle(
fontSize: 16,
),
),
),
const SizedBox(width: 8, child: Text(":")),
SizedBox(
width: MediaQuery.of(context).size.width / 2,
child: Text(
_currentOnline ? "Online" : "Offline",
style: const TextStyle(
fontSize: 16,
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
children: [
const SizedBox(
width: 120,
child: Text(
"RSSI",
style: TextStyle(
fontSize: 16,
),
),
),
const SizedBox(width: 8, child: Text(":")),
SizedBox(
width: MediaQuery.of(context).size.width / 2,
child: Text(
_currentRSSI,
style: const TextStyle(
fontSize: 16,
),
),
),
],
),
),
Row(
children: [
const SizedBox(
width: 120,
child: Text(
"Role",
style: TextStyle(
fontSize: 16,
),
),
),
const SizedBox(width: 8, child: Text(":")),
SizedBox(
width: MediaQuery.of(context).size.width / 2,
child: Text(
widget.device.role,
style: const TextStyle(
fontSize: 16,
),
),
),
],
),
Row(
children: [
const SizedBox(
width: 120,
child: Text(
"Mesh Name",
style: TextStyle(
fontSize: 16,
),
),
),
const SizedBox(width: 8, child: Text(":")),
Expanded(
child: Row(
children: [
Expanded(
child: TextField(
controller: _controllerMeshName,
enabled: true,
readOnly: !_isEditingMeshName,
focusNode: _focusMeshNameTextField,
style: const TextStyle(
fontSize: 16,
color: Colors.black,
),
decoration: const InputDecoration(
isDense: true,
border: InputBorder.none,
),
onTapOutside: (event) {
setState(() {
_controllerMeshName.text =
widget.device.meshNetwork.name;
_isEditingMeshName = false;
});
_focusDeviceNameTextField.unfocus();
},
onSubmitted: (_) {
showDialog(
context: context,
builder: (_) => AlertDialog(
backgroundColor:
ColorConstants.lightBlueAppColor,
title: const Text(
"Update Mesh Name",
),
content: const Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Yakin ingin mengganti nama mesh?",
),
],
),
actions: [
TextButton(
style: const ButtonStyle(
backgroundColor:
WidgetStatePropertyAll<Color>(
Colors.green,
),
),
onPressed: () {
setState(() {
_controllerMeshName.text = widget
.device.meshNetwork.name;
_isEditingMeshName = false;
});
Navigator.pop(context);
},
child: Text(
'Tidak',
style: TextStyle(
color:
ColorConstants.blackAppColor,
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
),
TextButton(
style: const ButtonStyle(
backgroundColor:
WidgetStatePropertyAll<Color>(
Colors.red,
),
),
onPressed: () {
_saveMeshName(); // simpan dan disable field
Navigator.pop(context);
},
child: Text(
'Ya!',
style: TextStyle(
color:
ColorConstants.blackAppColor,
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
),
],
),
);
},
),
),
IconButton(
onPressed: () {
setState(() {
_isEditingMeshName = true;
});
Future.delayed(
const Duration(milliseconds: 100), () {
_focusMeshNameTextField.requestFocus();
_controllerMeshName.selection =
TextSelection.collapsed(
offset: _controllerMeshName.text.length,
);
});
},
icon: const Icon(Icons.edit),
iconSize: 20,
constraints: const BoxConstraints(),
tooltip: "Edit Mesh Name",
),
],
),
),
],
),
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
children: [
const SizedBox(
width: 120,
child: Text(
"Mesh Address",
style: TextStyle(
fontSize: 16,
),
),
),
const SizedBox(width: 8, child: Text(":")),
SizedBox(
width: MediaQuery.of(context).size.width / 2,
child: Text(
widget.device.meshNetwork.macRoot,
style: const TextStyle(
fontSize: 16,
),
),
),
],
),
),
Divider(
color: ColorConstants.darkBlueAppColor,
thickness: 2,
height: 32,
),
Container(
margin: const EdgeInsets.symmetric(vertical: 8),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: Column(
children: [
// Header
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
"Device Schedule",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
IconButton(
onPressed: () => _openDeviceTimePicker(context),
icon:
const Icon(Icons.add_circle_outline_rounded),
tooltip: "Add Device Schedule",
),
],
),
const Divider(thickness: 1),
// Table Header
const Row(
children: [
SizedBox(width: 40), // icon delete space
Expanded(child: Center(child: Text("Time"))),
Expanded(child: Center(child: Text("State"))),
Expanded(child: Center(child: Text("Enabled"))),
],
),
const SizedBox(height: 8),
// Cek apakah scheduleList kosong
if (scheduleList.isEmpty)
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Center(
child: Column(
children: [
const Text(
"Timer schedule kosong",
style: TextStyle(
fontSize: 16,
fontStyle: FontStyle.italic,
),
),
IconButton(
onPressed: () =>
_openDeviceTimePicker(context),
icon: const Icon(
Icons.add_circle_outline_rounded,
),
tooltip: "Add Device Schedule",
),
],
),
),
)
else
...scheduleList.asMap().entries.map(
(entry) {
int index = entry.key;
var item = entry.value;
return Padding(
padding:
const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
children: [
// Delete button
SizedBox(
width: 40,
child: IconButton(
onPressed: () {
setState(() {
scheduleList.removeAt(index);
});
context
.read<DeviceScheduleBloc>()
.add(
DeleteDeviceSchedule(
scheduleId:
item["scheduleId"],
),
);
},
icon: const Icon(Icons.delete),
iconSize: 20,
),
),
// Time
Expanded(
child: Center(
child: Text(
item["time"],
style: const TextStyle(fontSize: 16),
),
),
),
// State
Expanded(
child: Center(
child: Text(
item["state"],
style: const TextStyle(fontSize: 16),
),
),
),
// Toggle switch
Expanded(
child: Center(
child: Switch(
value: item["enabled"],
onChanged: _currentOnline
? (value) {
setState(() {
scheduleList[index]
["enabled"] = value;
});
context
.read<
DeviceScheduleBloc>()
.add(
UpdateDeviceScheduleEnabled(
scheduleId: item[
"scheduleId"],
enabled: value,
),
);
}
: null,
activeColor: Colors.green,
inactiveThumbColor: _currentOnline
? Colors.red
: Colors.grey,
inactiveTrackColor: _currentOnline
? Colors.red.shade200
: Colors.grey.shade400,
thumbIcon: WidgetStateProperty
.resolveWith<Icon?>(
(Set<WidgetState> states) {
if (states.contains(
WidgetState.selected)) {
return const Icon(
Icons.check_rounded,
color: Colors.white,
);
}
return const Icon(
Icons.close_rounded,
color: Colors.black54,
);
}),
trackOutlineColor: WidgetStateProperty
.resolveWith<Color?>(
(Set<WidgetState> states) {
if (states.contains(
WidgetState.selected)) {
return _currentOnline
? Colors.green
: Colors.grey;
}
return _currentOnline
? Colors.red
: Colors.grey;
},
),
),
),
),
],
),
);
},
),
const SizedBox(height: 12),
// SET Button
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
),
onPressed: !_currentOnline ||
scheduleList.isEmpty ||
!scheduleList
.any((item) => item['enabled'] == true)
? null // tombol disable
: () {
showDialog(
context: context,
builder: (_) => AlertDialog(
backgroundColor:
ColorConstants.lightBlueAppColor,
title: const Text(
"Set Device Schedule",
),
content: const Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Yakin ingin menambahkan Schedule ini ke Device?"),
],
),
actions: [
TextButton(
style: const ButtonStyle(
backgroundColor:
WidgetStatePropertyAll<Color>(
Colors.green,
),
),
onPressed: () {
Navigator.pop(context);
},
child: Text(
'Tidak',
style: TextStyle(
color:
ColorConstants.blackAppColor,
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
),
TextButton(
style: const ButtonStyle(
backgroundColor:
WidgetStatePropertyAll<Color>(
Colors.red,
),
),
onPressed: () =>
_onSetDeviceSchedule(context),
child: Text(
'Ya!',
style: TextStyle(
color:
ColorConstants.blackAppColor,
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
),
],
),
);
},
child: const Text(
"SET SCHEDULE",
style: TextStyle(fontSize: 16, color: Colors.white),
),
),
],
),
),
],
),
),
),
),
),
);
}
void _openDeviceTimePicker(BuildContext context) {
BottomPicker.time(
pickerTitle: Text(
'Set your Device State Schedule',
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 16,
color: ColorConstants.darkBlueAppColor,
),
),
use24hFormat: true,
showTimeSeparator: true,
initialTime: Time(hours: 0, minutes: 0),
bottomPickerTheme: BottomPickerTheme.blue,
onSubmit: (time) {
final jam = DateTime.parse(time.toString());
print('Time picked: ${jam.hour}:${jam.minute}');
selectedTime = TimeOfDay(hour: jam.hour, minute: jam.minute);
// Setelah selesai, buka picker berikutnya
Future.delayed(const Duration(milliseconds: 300), () {
_openDeviceStatePicker(context);
});
},
onCloseButtonPressed: () {
selectedTime = null;
print('Time picker closed');
},
).show(context);
}
void _openDeviceStatePicker(BuildContext context) {
BottomPicker(
items: const [
Center(child: Text('OFF')),
Center(child: Text('ON')),
],
selectedItemIndex: 0,
pickerTitle: Text(
'Select Device State',
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 16,
color: ColorConstants.darkBlueAppColor,
),
),
bottomPickerTheme: BottomPickerTheme.plumPlate,
onSubmit: (index) {
selectedState = index == 0 ? 'OFF' : 'ON';
print('Selected: $selectedState');
if (selectedTime != null && selectedState != null) {
// Simpan ke list
setState(() {
scheduleList.add({
'time':
'${selectedTime!.hour.toString().padLeft(2, '0')}:${selectedTime!.minute.toString().padLeft(2, '0')}',
'state': selectedState!,
'enabled': true
});
});
context.read<DeviceScheduleBloc>().add(
InsertDeviceSchedulewithDeviceId(
deviceId: widget.device.id!,
time:
'${selectedTime!.hour.toString().padLeft(2, '0')}:${selectedTime!.minute.toString().padLeft(2, '0')}',
state: selectedState!,
enabled: true,
),
);
// Cetak sebagai JSON
print(jsonEncode(scheduleList));
}
},
onCloseButtonPressed: () {
selectedState = null;
// Jika ditutup, kembali ke picker sebelumnya
Future.delayed(const Duration(milliseconds: 300), () {
_openDeviceTimePicker(context);
});
print('Device state picker closed');
},
).show(context);
}
void _onSetDeviceSchedule(BuildContext context) {
// Filter hanya yang enabled, lalu ambil hanya 'time' dan 'state'
List<Map<String, String>> filteredScheduleList = scheduleList
.where((item) => item['enabled'] == true)
.map((item) => {
"time": item["time"].toString(),
"state": item["state"].toString(),
})
.toList();
String strfilteredScheduleList = jsonEncode(filteredScheduleList);
print(strfilteredScheduleList);
context.read<MQTTBloc>().add(
SetDeviceSchedule(
macRoot: widget.device.meshNetwork.macRoot,
nodeId: widget.device.nodeId,
scheduleList: strfilteredScheduleList,
),
);
showDialog(
barrierDismissible: false,
context: context,
builder: (_) {
return PopScope(
canPop: false,
child: AlertDialog(
backgroundColor: ColorConstants.lightBlueAppColor,
title: const Text('Send Device Schedule'),
content: const Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Mengirimkan schedule ke device!'),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
Navigator.pop(context);
},
child: Text(
'OK',
style: TextStyle(
color: ColorConstants.blackAppColor,
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
),
],
),
);
},
);
}
void _onBackButtonTapped(BuildContext context) {
Navigator.pop(context);
}
}