Complete refactoring SFM App Source Code
This commit is contained in:
82
lib/feature/log/device_logs_bloc.dart
Normal file
82
lib/feature/log/device_logs_bloc.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
66
lib/feature/log/device_logs_model.dart
Normal file
66
lib/feature/log/device_logs_model.dart
Normal 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"];
|
||||
}
|
||||
}
|
||||
295
lib/feature/log/device_logs_screen.dart
Normal file
295
lib/feature/log/device_logs_screen.dart
Normal file
@@ -0,0 +1,295 @@
|
||||
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/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 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user