TKK_E32221274/lib/presentation/pages/home/home_page.dart

421 lines
17 KiB
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/mesh_network/mesh_network_bloc.dart';
import '../../../features/database/models/mesh_network.dart';
import '../../../features/mqtt/bloc/mqtt_bloc.dart';
import '../../widgets/device_toggle/device_toggle_widget.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final GlobalKey<CustomRefreshIndicatorState> _refreshKey =
GlobalKey<CustomRefreshIndicatorState>();
List<MeshNetwork>? cachedMeshNetworksForRequest;
@override
Widget build(BuildContext context) {
return MultiBlocListener(
listeners: [
BlocListener<MeshNetworkBloc, MeshNetworkState>(
listener: (context, state) {
if (state is MeshNetworksLoaded) {
print("Home MeshNetworksLoaded: ${state.meshNetworks}");
setState(() {
cachedMeshNetworksForRequest = state.meshNetworks;
});
if (cachedMeshNetworksForRequest != null) {
print(
"MQTT Home Sub&Req Mesh dijalankan dari MeshNetworksLoaded");
for (var mesh in cachedMeshNetworksForRequest!) {
print("Home Sub&Req Mesh from : ${mesh.id}/${mesh.macRoot}");
// Saat menerima data Mesh Network langsung subcribe dan request data device
context.read<MQTTBloc>().add(
SubscribedMeshNetwork(
macRoot: mesh.macRoot,
),
);
context.read<MQTTBloc>().add(
RequestDevicesData(
macRoot: mesh.macRoot,
command: 'getNodes',
),
);
}
}
} else if (state is DeleteAllMeshDeviceRelationsSuccess) {
print("Home reset mesh device dijalankan");
if (cachedMeshNetworksForRequest != null) {
for (var mesh in cachedMeshNetworksForRequest!) {
print("Home Unsubcribe Mesh: ${mesh.id}/${mesh.macRoot}");
context.read<MQTTBloc>().add(
UnsubscribedMeshNetwork(
macRoot: mesh.macRoot,
),
);
}
}
context.read<DeviceBloc>().add(GetDevices());
setState(() {
// Kosongkan semua Mesh network di halaman homepage
cachedMeshNetworksForRequest = null;
});
}
},
),
BlocListener<MQTTBloc, MQTTState>(
listenWhen: (previous, current) {
// print("-- Previous $previous");
// print("-- Current $current");
return previous is! MQTTConnected && current is MQTTConnected;
},
listener: (context, state) {
// Memanggil fungsi ini untuk listen ulang mqtt saat client melakukan reconnect
context.read<MQTTBloc>().add(ProcessDeviceMessage());
if (cachedMeshNetworksForRequest != null) {
print("MQTT Home Sub&Req Mesh dijalankan dari MQTTConnected");
for (var mesh in cachedMeshNetworksForRequest!) {
print("Home Sub&Req Mesh: ${mesh.id}/${mesh.macRoot}");
// Saat menerima data Mesh Network langsung subcribe dan request data device
context.read<MQTTBloc>().add(
SubscribedMeshNetwork(
macRoot: mesh.macRoot,
),
);
context.read<MQTTBloc>().add(
RequestDevicesData(
macRoot: mesh.macRoot,
command: 'getNodes',
),
);
}
}
},
),
],
child: Scaffold(
appBar: AppBar(
backgroundColor: ColorConstants.lightBlueAppColor,
title: BlocBuilder<MeshNetworkBloc, MeshNetworkState>(
builder: (context, state) {
final isMeshNetworksLoaded = state is MeshNetworksLoaded;
final meshNetworks = state is MeshNetworksLoaded
? state.meshNetworks
: <MeshNetwork>[];
return GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text("Informations"),
BlocBuilder<MQTTBloc, MQTTState>(
builder: (context, state) {
Color indicatorColor;
// Dart versi 3 memperkenalkan pattern matching
switch (state) {
case MQTTConnecting _:
indicatorColor = Colors.orange;
break;
case MQTTConnected _:
indicatorColor = Colors.green;
break;
case MQTTDisconnected _:
indicatorColor = Colors.red;
break;
default:
indicatorColor = Colors.grey;
}
return Container(
width: 16,
height: 16,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: indicatorColor,
boxShadow: [
BoxShadow(
color: indicatorColor.withOpacity(0.6),
spreadRadius: 2,
blurRadius: 6,
),
],
),
);
},
),
],
),
backgroundColor: ColorConstants.lightBlueAppColor,
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
isMeshNetworksLoaded && meshNetworks.isNotEmpty
? "${meshNetworks.length} Mesh-Net terhubung."
: "Belum ada Mesh-Net yang terhubung.",
style: const TextStyle(
fontSize: 16,
),
),
// penggunaan spread operator (...[]) agar hanya menambahkan widget jika kondisi terpenuhi
if (isMeshNetworksLoaded &&
meshNetworks.isNotEmpty) ...[
const SizedBox(height: 8),
const Text("Rincian:"),
for (var mesh in state.meshNetworks)
Text("${mesh.name} - ${mesh.macRoot}"),
],
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(
'Tutup',
style: TextStyle(
color: ColorConstants.blackAppColor,
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
),
],
),
);
},
child: const Text('Mesh-Net App'),
);
},
),
actions: [
// Refresh manual dengan tombol
IconButton(
icon: const Icon(Icons.refresh_rounded),
onPressed: () {
_refreshKey.currentState?.refresh(
draggingCurve: Curves.easeOutBack,
);
},
tooltip: "Refresh Devices",
),
IconButton(
icon: const Icon(Icons.add_circle_outline_rounded),
onPressed: () => _onDeviceProvisioningTapped(context),
tooltip: "Add Device",
),
const SizedBox(width: 5),
],
),
body: CustomRefreshIndicator(
key: _refreshKey,
onRefresh: () async {
context.read<DeviceBloc>().add(GetDevices());
if (cachedMeshNetworksForRequest != null) {
for (var mesh in cachedMeshNetworksForRequest!) {
print("Home RequestDevicesData: ${mesh.id}/${mesh.macRoot}");
context.read<MQTTBloc>().add(
RequestDevicesData(
macRoot: mesh.macRoot,
command: 'getNodes',
),
);
}
}
},
builder: (BuildContext context, Widget child,
IndicatorController controller) {
// tidak keluar karena tidak memakai delay di onRefresh
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: BlocBuilder<DeviceBloc, DeviceState>(
builder: (context, state) {
if (state is DeviceLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is DevicesLoaded) {
final devices = state.devices;
print("Home Devices: $devices");
if (devices.isNotEmpty) {
return ListView.builder(
padding: const EdgeInsets.only(top: 10),
itemCount: devices.length,
itemBuilder: (context, index) {
return DeviceToggleWidget(
key: ValueKey(devices[index].id), // ID unik dari device
device: devices[index],
);
},
);
}
return SingleChildScrollView(
// agar bisa pull-down meski tidak ada konten
physics: const AlwaysScrollableScrollPhysics(),
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.75,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text(
'No Device Available',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
SizedBox.fromSize(size: const Size.fromHeight(20)),
Material(
color: ColorConstants.whiteAppColor,
borderRadius: BorderRadius.circular(16),
child: InkWell(
onTap: () => _onDeviceProvisioningTapped(context),
borderRadius: BorderRadius.circular(16),
child: Container(
color: Colors.transparent,
width: 240,
height: 48,
child: Center(
child: Text(
"Add Mesh-Net Device",
style: TextStyle(
color: ColorConstants.darkBlueAppColor,
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
),
),
),
),
],
),
),
);
} else if (state is DeviceFailure) {
return SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.6,
child: Center(
child: Text(
'Error loading devices. Error: ${state.message}',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
),
);
}
return SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.75,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text(
'No Device Available',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
SizedBox.fromSize(size: const Size.fromHeight(20)),
Material(
color: ColorConstants.whiteAppColor,
borderRadius: BorderRadius.circular(16),
child: InkWell(
onTap: () => _onDeviceProvisioningTapped(context),
borderRadius: BorderRadius.circular(16),
child: Container(
color: Colors.transparent,
width: 240,
height: 48,
child: Center(
child: Text(
"Add Mesh-Net Device",
style: TextStyle(
color: ColorConstants.darkBlueAppColor,
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
),
),
),
),
],
),
),
);
},
),
),
),
);
}
void _onDeviceProvisioningTapped(BuildContext context) {
Navigator.pushNamed(
context,
'/DeviceProvisioning',
arguments: context,
);
}
}