Update Bottom Navigator in MainScreen

This commit is contained in:
anhtunz
2024-12-24 09:26:43 +07:00
parent 9046b21831
commit e047fe1e27
14 changed files with 944 additions and 633 deletions

View File

@@ -0,0 +1,82 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:sfm_app/feature/devices/device_model.dart';
import 'package:sfm_app/product/base/bloc/base_bloc.dart';
import 'package:sfm_app/product/services/api_services.dart';
import 'package:sfm_app/product/utils/date_time_utils.dart';
import '../../product/utils/device_utils.dart';
import 'device_logs_model.dart';
class DeviceLogsBloc extends BlocBase {
APIServices apiServices = APIServices();
final fromDate = StreamController<String>.broadcast();
StreamSink<String> get sinkFromDate => fromDate.sink;
Stream<String> get streamFromDate => fromDate.stream;
final hasMore = StreamController<bool>.broadcast();
StreamSink<bool> get sinkHasMore => hasMore.sink;
Stream<bool> get streamHasMore => hasMore.stream;
final allDevices = StreamController<List<Device>>.broadcast();
StreamSink<List<Device>> get sinkAllDevices => allDevices.sink;
Stream<List<Device>> get streamAllDevices => allDevices.stream;
final sensors = StreamController<List<SensorLogs>>.broadcast();
StreamSink<List<SensorLogs>> get sinkSensors => sensors.sink;
Stream<List<SensorLogs>> get streamSensors => sensors.stream;
@override
void dispose() {}
void getAllDevices() async {
String body = await apiServices.getOwnerDevices();
if (body != "") {
final data = jsonDecode(body);
List<dynamic> items = data['items'];
List<Device> originalDevices = Device.fromJsonDynamicList(items);
List<Device> devices =
DeviceUtils.instance.sortDeviceByState(originalDevices);
sinkAllDevices.add(devices);
}
}
void getDeviceLogByThingID(
int offset,
String thingID,
DateTime fromDate,
List<SensorLogs> sensors,
) async {
log("SensorLength: ${sensors.length}");
String fromDateString =
DateTimeUtils.instance.formatDateTimeToString(fromDate);
String now = DateTimeUtils.instance.formatDateTimeToString(DateTime.now());
// List<SensorLogs> sensors = [];
Map<String, dynamic> params = {
'thing_id': thingID,
'from': fromDateString,
'to': now,
'limit': '30',
"offset": offset.toString(),
"asc": "true"
};
final body = await apiServices.getLogsOfDevice(thingID, params);
if (body != "") {
final data = jsonDecode(body);
DeviceLog devicesListLog = DeviceLog.fromJson(data);
if (devicesListLog.sensors!.isEmpty) {
bool hasMore = false;
sinkHasMore.add(hasMore);
}
if (devicesListLog.sensors!.isNotEmpty) {
for (var sensor in devicesListLog.sensors!) {
sensors.add(sensor);
}
}
sinkSensors.add(sensors);
}
}
}

View File

@@ -0,0 +1,66 @@
class DeviceLog {
String? thingId;
DateTime? from;
DateTime? to;
int? total;
int? offset;
List<SensorLogs>? sensors;
DeviceLog({
this.thingId,
this.from,
this.to,
this.total,
this.offset,
this.sensors,
});
DeviceLog.fromJson(Map<String, dynamic> json) {
thingId = json["thing_id"];
from = DateTime.parse(json["from"]);
to = DateTime.parse(json["to"]);
total = json["total"];
offset = json["offset"];
if (json['sensors'] != null) {
sensors = [];
json['sensors'].forEach((v) {
sensors!.add(SensorLogs.fromJson(v));
});
}
}
}
class SensorLogs {
String? name;
int? value;
int? time;
LogDetail? detail;
SensorLogs({
this.name,
this.value,
this.time,
this.detail,
});
SensorLogs.fromJson(Map<String, dynamic> json) {
name = json["n"];
value = json["v"];
time = json["t"];
detail = json["x"] != null ? LogDetail.fromJson(json["x"]) : null;
}
}
class LogDetail {
String? username;
String? note;
LogDetail({
this.username,
this.note,
});
LogDetail.fromJson(Map<String, dynamic> json) {
username = json["username"];
note = json["note"];
}
}

View File

@@ -0,0 +1,302 @@
import 'dart:developer';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:sfm_app/feature/devices/device_model.dart';
import 'package:sfm_app/feature/device_log/device_logs_bloc.dart';
import 'package:sfm_app/product/constant/icon/icon_constants.dart';
import 'package:sfm_app/product/extention/context_extention.dart';
import 'package:sfm_app/product/services/language_services.dart';
import 'package:sfm_app/product/shared/shared_snack_bar.dart';
import 'package:sfm_app/product/utils/date_time_utils.dart';
import 'package:sfm_app/product/utils/device_utils.dart';
import '../../product/base/bloc/base_bloc.dart';
import 'device_logs_model.dart';
class DeviceLogsScreen extends StatefulWidget {
const DeviceLogsScreen({super.key});
@override
State<DeviceLogsScreen> createState() => _DeviceLogsScreenState();
}
class _DeviceLogsScreenState extends State<DeviceLogsScreen> {
TextEditingController fromDate = TextEditingController();
String fromDateApi = '';
DateTime? dateTime;
String thingID = "";
late DeviceLogsBloc deviceLogsBloc;
List<Device> allDevices = [];
List<SensorLogs> sensors = [];
int offset = 0;
final controller = ScrollController();
bool hasMore = true;
@override
void initState() {
super.initState();
deviceLogsBloc = BlocProvider.of(context);
controller.addListener(
() {
if (controller.position.maxScrollExtent == controller.offset) {
offset += 30;
deviceLogsBloc.getDeviceLogByThingID(
offset, thingID, dateTime!, sensors);
}
},
);
}
@override
void dispose() {
controller.dispose();
sensors.clear();
deviceLogsBloc.sinkSensors.add(sensors);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<List<Device>>(
stream: deviceLogsBloc.streamAllDevices,
builder: (context, allDevicesSnapshot) {
if (allDevicesSnapshot.data?[0].thingId == null) {
deviceLogsBloc.getAllDevices();
return const Center(
child: CircularProgressIndicator(),
);
} else {
return StreamBuilder<List<SensorLogs>>(
stream: deviceLogsBloc.streamSensors,
initialData: sensors,
builder: (context, sensorsSnapshot) {
return Padding(
padding: context.paddingLow,
child: Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
DropdownButtonFormField2<String>(
isExpanded: true,
decoration: InputDecoration(
contentPadding: context.paddingLowVertical,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
),
),
hint: Text(
appLocalization(context)
.choose_device_dropdownButton,
style: const TextStyle(
fontSize: 14,
),
),
items: allDevicesSnapshot.data?.isNotEmpty ?? false
? allDevicesSnapshot.data!
.map(
(device) => DropdownMenuItem<String>(
value: device.thingId,
child: Text(
device.name!,
style: const TextStyle(
fontSize: 14,
),
),
),
)
.toList()
: [],
buttonStyleData: const ButtonStyleData(
padding: EdgeInsets.only(right: 8),
),
onChanged: (value) {
thingID = value!;
setState(() {});
},
onSaved: (value) {
log("On Saved");
},
iconStyleData: const IconStyleData(
icon: Icon(
Icons.arrow_drop_down,
),
iconSize: 24,
),
dropdownStyleData: DropdownStyleData(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
),
),
menuItemStyleData: const MenuItemStyleData(
padding: EdgeInsets.symmetric(horizontal: 16),
),
),
Padding(
padding: context.paddingLowVertical,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: context.dynamicWidth(0.6),
child: TextField(
controller: fromDate,
decoration: InputDecoration(
icon: IconConstants.instance
.getMaterialIcon(
Icons.calendar_month_outlined,
),
labelText: appLocalization(context)
.choose_date_start_datePicker,
),
readOnly: true,
onTap: () async {
DateTime? datePicker =
await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2023),
lastDate: DateTime(2050),
);
dateTime = datePicker;
if (datePicker != null) {
fromDateApi = DateTimeUtils.instance
.formatDateTimeToString(datePicker);
setState(
() {
fromDate.text =
DateFormat('yyyy-MM-dd')
.format(datePicker);
},
);
}
},
),
),
Center(
child: TextButton.icon(
style: const ButtonStyle(
backgroundColor: MaterialStatePropertyAll(
Colors.green),
),
onPressed: () {
if (fromDateApi.isEmpty) {
showNoIconTopSnackBar(
context,
appLocalization(context)
.notification_enter_all_inf,
Colors.red,
Colors.white,
);
} else {
deviceLogsBloc.getDeviceLogByThingID(
offset,
thingID,
dateTime!,
sensors);
}
log("ThingID: $thingID");
log("From Date: ${DateTimeUtils.instance.formatDateTimeToString(dateTime!)}");
},
icon: IconConstants.instance
.getMaterialIcon(Icons.search),
label: Text(
appLocalization(context)
.find_button_content,
),
),
),
],
),
),
Divider(
height: context.lowValue,
),
Expanded(
child: sensorsSnapshot.data?.isEmpty ?? false
? Center(
child: Text(appLocalization(context)
.no_data_message),
)
: RefreshIndicator(
onRefresh: refresh,
child: ListView.builder(
controller: controller,
itemCount:
sensorsSnapshot.data!.length + 1,
itemBuilder: (context, index) {
if (index <
sensorsSnapshot.data!.length) {
return logDetail(
sensorsSnapshot.data![index],
index,
);
} else {
return Padding(
padding: context.paddingLow,
child: StreamBuilder<bool>(
stream:
deviceLogsBloc.streamHasMore,
builder:
(context, hasMoreSnapshot) {
return Center(
child: hasMoreSnapshot.data ??
hasMore
? const CircularProgressIndicator()
: Text(appLocalization(
context)
.main_no_data),
);
},
),
);
}
},
),
),
),
],
),
),
),
);
},
);
}
},
),
);
}
Widget logDetail(SensorLogs sensor, int index) {
return Column(
children: [
ListTile(
subtitle:
Text(DeviceUtils.instance.getDeviceSensorsLog(context, sensor)),
// leading: const Icon(Icons.sensors_outlined),
leading: Text(index.toString()),
title: Text(
DateTimeUtils.instance
.convertCurrentMillisToDateTimeString(sensor.time ?? 0),
),
),
const Divider(
thickness: 0.5,
indent: 50,
endIndent: 100,
),
],
);
}
Future<void> refresh() async {
offset = 0;
sensors.clear();
deviceLogsBloc.sensors.add(sensors);
hasMore = true;
deviceLogsBloc.sinkHasMore.add(hasMore);
deviceLogsBloc.getDeviceLogByThingID(offset, thingID, dateTime!, sensors);
}
}