refactor(ui): update DetailDevice screen layout

This commit is contained in:
anhtunz
2024-12-26 11:31:21 +07:00
parent 77afc09d19
commit a69429b05f
26 changed files with 1231 additions and 671 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
assets/icons/humidity.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
assets/icons/signal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
assets/icons/volt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

View File

@@ -92,7 +92,8 @@ class DetailDeviceBloc extends BlocBase {
'thing_id': thingID, 'thing_id': thingID,
'from': from, 'from': from,
'to': now, 'to': now,
'limit': '500', 'limit': '100',
'n': '7',
}; };
final body = await apiServices.getLogsOfDevice(thingID, params); final body = await apiServices.getLogsOfDevice(thingID, params);
if (body != "") { if (body != "") {
@@ -100,13 +101,7 @@ class DetailDeviceBloc extends BlocBase {
DeviceLog devicesListLog = DeviceLog.fromJson(data); DeviceLog devicesListLog = DeviceLog.fromJson(data);
if (devicesListLog.sensors!.isNotEmpty) { if (devicesListLog.sensors!.isNotEmpty) {
for (var sensor in devicesListLog.sensors!) { for (var sensor in devicesListLog.sensors!) {
if (sensor.name == "8") {
if (sensorTemps.length < 100) {
sensorTemps.add(sensor); sensorTemps.add(sensor);
} else {
break;
}
}
} }
sensorTemps = sensorTemps.reversed.toList(); sensorTemps = sensorTemps.reversed.toList();
sinkSensorTemps.add(sensorTemps); sinkSensorTemps.add(sensorTemps);

View File

@@ -3,9 +3,10 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:sfm_app/feature/device_log/device_logs_model.dart'; import 'package:sfm_app/feature/device_log/device_logs_model.dart';
import 'package:sfm_app/product/constant/image/image_constants.dart';
import 'package:sfm_app/product/shared/shared_line_chart.dart'; import 'package:sfm_app/product/shared/shared_line_chart.dart';
import 'package:simple_ripple_animation/simple_ripple_animation.dart'; import 'package:simple_ripple_animation/simple_ripple_animation.dart';
import 'dart:math' as math; import '../../../product/shared/shared_curve.dart';
import '../device_model.dart'; import '../device_model.dart';
import '../../../product/base/bloc/base_bloc.dart'; import '../../../product/base/bloc/base_bloc.dart';
import '../../../product/extention/context_extention.dart'; import '../../../product/extention/context_extention.dart';
@@ -28,14 +29,15 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
IconConstants.instance.getIcon("offline_icon"), IconConstants.instance.getIcon("offline_icon"),
IconConstants.instance.getIcon("flame_icon"), IconConstants.instance.getIcon("flame_icon"),
]; ];
String stateImgAssets(int state) { String stateImgAssets(int state) {
String imgStringAsset; String imgStringAsset;
if (state == 0) { if (state == 0) {
imgStringAsset = imageAssets[0]; imgStringAsset = imageAssets[0];
} else if (state == 1) { } else if (state == 1) {
imgStringAsset = imageAssets[1];
} else {
imgStringAsset = imageAssets[2]; imgStringAsset = imageAssets[2];
} else {
imgStringAsset = imageAssets[1];
} }
return imgStringAsset; return imgStringAsset;
} }
@@ -50,14 +52,31 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
detailDeviceBloc = BlocProvider.of(context); detailDeviceBloc = BlocProvider.of(context);
} }
TextStyle textstyle = const TextStyle(
fontSize: 25,
fontWeight: FontWeight.w600,
);
BoxDecoration boxDecoration = BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: Colors.grey.withOpacity(0.1),
border: Border.all(
width: 1,
color: Colors.grey.withOpacity(0.6),
),
);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
return StreamBuilder<Device>( return StreamBuilder<Device>(
stream: detailDeviceBloc.streamDeviceInfo, stream: detailDeviceBloc.streamDeviceInfo,
builder: (context, deviceSnapshot) { builder: (context, deviceSnapshot) {
if (deviceSnapshot.data?.extId == null) { if (deviceSnapshot.data?.extId == null) {
detailDeviceBloc.getDeviceDetail(context, widget.thingID, controller); detailDeviceBloc.getDeviceDetail(
context,
widget.thingID,
controller,
);
return const Center( return const Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
); );
@@ -71,128 +90,443 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
title: Text(appLocalization(context).detail_message), title: Text(appLocalization(context).detail_message),
centerTitle: true, centerTitle: true,
), ),
body: SafeArea( body: SingleChildScrollView(
child: SingleChildScrollView(
child: Column( child: Column(
children: <Widget>[
Stack(
children: [ children: [
// device Name ClipPath(
Card( clipper: CuveEdgesCustom(),
child: Container( child: Container(
width: context.dynamicWidth(1), padding: const EdgeInsets.all(0),
height: context.highValue, child: SizedBox(
decoration: const BoxDecoration( height: context.dynamicHeight(0.25),
borderRadius: BorderRadius.all( child: Stack(
Radius.circular(12),
),
),
child: Center(
child: Text(
'${appLocalization(context).device_title}: ${deviceSnapshot.data!.name}',
style: const TextStyle(
fontSize: 20,
),
),
),
),
),
// Tinh trang va nhiet do
Row(
children: [ children: [
Card( Positioned.fill(
child: Image.asset(
ImageConstants.instance
.getImage('smoke-detector'),
fit: BoxFit.fill,
),
),
Center(
child: Container( child: Container(
width: (screenWidth - 20) / 2, height: 50,
height: context.highValue, width: 400,
decoration: const BoxDecoration( // color: Colors.blueAccent,
borderRadius: BorderRadius.all( alignment: Alignment.centerRight,
Radius.circular(12), margin: const EdgeInsets.fromLTRB(
), 0, 0, 0, 50),
),
padding:
const EdgeInsets.fromLTRB(5, 5, 0, 5),
alignment: Alignment.centerLeft,
child: Row( child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [ children: [
// SizedBox( const SizedBox(),
// height: 25, Text(
// width: 25, deviceSnapshot.data?.name ?? "",
// child: RippleAnimation( style: const TextStyle(
// color: DeviceUtils.instance fontSize: 25,
// .getColorRiple( fontWeight: FontWeight.w600,
// deviceSnapshot.data!.state!), ),
// delay: ),
// const Duration(milliseconds: 800), ],
// repeat: true, ),
// minRadius: 40, ),
// ripplesCount: 6, ),
// duration: const Duration( ],
// milliseconds: 6 * 300), ),
// child: CircleAvatar( ),
// minRadius: 20, ),
// maxRadius: 20, ),
// backgroundImage: AssetImage( Positioned(
// stateImgAssets( bottom: 0,
// deviceSnapshot.data!.state!, left: MediaQuery.of(context).size.width / 2 - 100,
// ), child: Container(
// ), height: context.dynamicHeight(0.08),
// ), width: context.dynamicWidth(0.5),
// ), decoration: BoxDecoration(
// ), color: DeviceUtils.instance.getTableRowColor(
CircleAvatar( deviceSnapshot.data?.state ?? 3),
minRadius: 20, borderRadius: BorderRadius.circular(50),
maxRadius: 20, ),
alignment: Alignment.bottomCenter,
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
SizedBox(
height: context.mediumValue,
width: context.mediumValue,
child: deviceSnapshot.data?.state == 1
? RippleAnimation(
color: Colors.red,
delay: context
.dynamicMilliSecondDuration(
800,
),
repeat: true,
minRadius: 10,
ripplesCount: 5,
duration: context
.dynamicMilliSecondDuration(
1800,
),
child: CircleAvatar(
minRadius: context.mediumValue,
maxRadius: context.mediumValue,
backgroundImage: AssetImage( backgroundImage: AssetImage(
stateImgAssets( stateImgAssets(
deviceSnapshot.data!.state!, deviceSnapshot.data!.state!,
), ),
), ),
), ),
SizedBox( )
width: context.lowValue, : CircleAvatar(
backgroundColor: DeviceUtils
.instance
.getTableRowColor(
deviceSnapshot.data?.state ?? 3,
), ),
Text( minRadius: context.mediumValue,
maxRadius: context.mediumValue,
backgroundImage: AssetImage(
stateImgAssets(
deviceSnapshot.data!.state!,
),
),
),
),
Center(
child: Text(
DeviceUtils.instance.checkStateDevice( DeviceUtils.instance.checkStateDevice(
context, context,
deviceSnapshot.data!.state!, deviceSnapshot.data!.state!,
), ),
style: const TextStyle( style: const TextStyle(
fontSize: 15, fontSize: 20,
fontWeight: FontWeight.w600,
color: Colors.white,
),
), ),
), ),
], ],
), ),
), ),
), ),
Card( ],
child: SizedBox( ),
width: (screenWidth - 20) / 2, SizedBox(
height: context.highValue, height: context.normalValue,
child: Container( ),
alignment: Alignment.centerLeft, // Muc song va muc pin
padding: Row(
const EdgeInsets.fromLTRB(5, 5, 0, 5), mainAxisAlignment: MainAxisAlignment.spaceAround,
child: Row(
children: [ children: [
const Icon( Container(
Icons.thermostat, height: context.dynamicHeight(0.18),
color: Colors.blue, width: context.dynamicWidth(0.45),
decoration: boxDecoration,
child: Padding(
padding: context.paddingLow,
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
appLocalization(context)
.paginated_data_table_column_deviceSignal,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
SizedBox(
height: context.dynamicWidth(0.12),
width: context.dynamicWidth(0.12),
child: Icon(
DeviceUtils.instance.getSignalIcon(
context,
sensorSnapshot.data!['sensorCsq'],
),
size: 30, size: 30,
color: DeviceUtils.instance
.getSignalIconColor(
context,
sensorSnapshot.data!['sensorCsq'],
),
),
),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.center,
children: <Widget>[
Container(
height: context.dynamicHeight(0.09),
alignment: Alignment.centerLeft,
child: Text(
sensorSnapshot.data!['sensorCsq'],
style: TextStyle(
color: DeviceUtils.instance
.getSignalIconColor(
context,
sensorSnapshot
.data!['sensorCsq'],
),
fontSize: 40,
fontWeight: FontWeight.w900,
),
),
),
],
),
],
),
),
),
Container(
height: context.dynamicHeight(0.18),
width: context.dynamicWidth(0.45),
decoration: boxDecoration,
child: Padding(
padding: context.paddingLow,
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
appLocalization(context)
.paginated_data_table_column_deviceBaterry,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
SizedBox(
height: context.dynamicWidth(0.12),
width: context.dynamicWidth(0.12),
child: Image.asset(
DeviceUtils.instance
.getDeviceBatteryImg(
int.parse(
sensorSnapshot
.data!['sensorBattery'],
),
),
color: DeviceUtils.instance
.getDeviceBatteryColor(
int.parse(
sensorSnapshot
.data!['sensorBattery'],
),
),
),
),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.center,
children: <Widget>[
Container(
height: context.dynamicHeight(0.09),
alignment: Alignment.centerLeft,
child: Text(
sensorSnapshot
.data!['sensorBattery'],
style: TextStyle(
color: DeviceUtils.instance
.getDeviceBatteryColor(
int.parse(
sensorSnapshot
.data!['sensorBattery'],
),
),
fontSize: 50,
fontWeight: FontWeight.w900,
),
),
),
SizedBox(
width: context.lowValue,
),
Container(
height: context.dynamicHeight(0.09),
width: 60,
alignment: Alignment.centerLeft,
child: Text(
'%',
style: TextStyle(
color: DeviceUtils.instance
.getDeviceBatteryColor(
int.parse(
sensorSnapshot
.data!['sensorBattery'],
),
),
fontSize: 30,
fontWeight: FontWeight.w900,
),
),
),
],
),
],
),
),
),
],
),
// Nhiet do
Padding(
padding: context.paddingLow,
child: Container(
height: 150,
width: MediaQuery.of(context).size.width,
decoration: boxDecoration,
child: Padding(
padding: context.paddingLow,
child: Column(
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
appLocalization(context)
.paginated_data_table_column_deviceTemperature,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
SizedBox(
height: context.dynamicWidth(0.12),
width: context.dynamicWidth(0.12),
child: Image.asset(
'assets/icons/temperature.png',
color: DeviceUtils.instance
.getDeviceTempColor(
int.parse(
sensorSnapshot
.data!['sensorTemp'],
),
),
),
),
],
), ),
const SizedBox( const SizedBox(
width: 10, height: 10,
), ),
Stack(
children: [
Container(
width: double.infinity,
height: 20,
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.3),
borderRadius:
BorderRadius.circular(10),
),
),
LayoutBuilder(
builder: (context, constraints) =>
Container(
width: constraints.maxWidth *
(int.parse(sensorSnapshot
.data!['sensorTemp']) /
75),
height: 20,
decoration: BoxDecoration(
color: DeviceUtils.instance
.getDeviceTempColor(
int.parse(sensorSnapshot
.data!['sensorTemp']),
),
borderRadius:
BorderRadius.circular(10),
),
),
)
],
),
const SizedBox(
height: 5,
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text( Text(
"${appLocalization(context).paginated_data_table_column_deviceTemperature}: ${sensorSnapshot.data?['sensorTemp'] ?? 100}", "${sensorSnapshot.data!['sensorTemp']} °C",
style: TextStyle(
color: DeviceUtils.instance
.getDeviceTempColor(
int.parse(
sensorSnapshot
.data!['sensorTemp'],
),
),
fontSize: 30,
fontWeight: FontWeight.w900,
),
),
const Text(
"75 °C",
style: TextStyle(
fontSize: 20,
),
),
],
)
],
),
),
),
),
// Dien ap
Padding(
padding: context.paddingLow,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
appLocalization(context)
.paginated_data_table_column_devicePower,
style: const TextStyle( style: const TextStyle(
fontSize: 15, fontSize: 18,
fontWeight: FontWeight.bold,
),
),
SizedBox(
height: context.dynamicWidth(0.12),
width: context.dynamicWidth(0.12),
child: Image.asset(
'assets/icons/volt.png',
), ),
), ),
], ],
), ),
), ),
SizedBox(
height: context.lowValue,
), ),
), // Bieu do dien ap
],
),
StreamBuilder<List<SensorLogs>>( StreamBuilder<List<SensorLogs>>(
stream: detailDeviceBloc.streamSensorTemps, stream: detailDeviceBloc.streamSensorTemps,
builder: (context, sensorTempsSnapshot) { builder: (context, sensorTempsSnapshot) {
@@ -200,175 +534,93 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
detailDeviceBloc detailDeviceBloc
.getNearerSensorValue(widget.thingID); .getNearerSensorValue(widget.thingID);
return const AspectRatio( return const AspectRatio(
aspectRatio: 1.5, aspectRatio: 3,
child: Center( child: Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
), ),
); );
} else { } else {
return AspectRatio( return AspectRatio(
aspectRatio: 1.5, aspectRatio: 3,
child: Container( child: Container(
margin: context.paddingLow, margin: context.paddingLow,
child: sharedLineChart( child: sharedLineChart(
"Nhiệt độ đo được (°C)", appLocalization(context)
.detail_device_volt_message,
sensorTempsSnapshot.data ?? [], sensorTempsSnapshot.data ?? [],
60,
), ),
), ),
); );
} }
}, },
), ),
Row(
children: [
Card(
child: Container(
width: (screenWidth - 20) / 2,
height: context.highValue,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(12),
),
),
alignment: Alignment.centerLeft,
padding:
const EdgeInsets.fromLTRB(10, 5, 0, 5),
child: Row(
children: [
Transform.rotate(
angle: 90 * math.pi / 180,
child: Icon(
DeviceUtils.instance.getBatteryIcon(
int.parse(
sensorSnapshot
.data!['sensorBattery'],
),
),
color: Colors.blue,
size: 30,
),
),
SizedBox( SizedBox(
width: context.lowValue, height: context.lowValue,
), ),
Text( // Map
"${appLocalization(context).paginated_data_table_column_deviceBaterry}: ${sensorSnapshot.data!['sensorBattery']}%", Padding(
style: const TextStyle( padding: context.paddingLow,
fontSize: 15,
),
),
],
),
),
),
Card(
child: Container( child: Container(
width: (screenWidth - 20) / 2, decoration: boxDecoration,
height: context.highValue, child: Column(
alignment: Alignment.centerLeft, mainAxisAlignment: MainAxisAlignment.center,
padding:
const EdgeInsets.fromLTRB(10, 5, 0, 5),
child: Row(
children: [ children: [
Icon( Container(
DeviceUtils.instance.getSignalIcon( height: context.dynamicHeight(0.3),
context, padding: context.paddingLow,
sensorSnapshot.data!['sensorCsq'],
),
color: Colors.blue,
size: 30,
),
SizedBox(
width: context.lowValue,
),
Text(
"${appLocalization(context).paginated_data_table_column_deviceSignal}: ${sensorSnapshot.data!['sensorCsq']}",
style: const TextStyle(fontSize: 15),
maxLines: 2,
overflow: TextOverflow.ellipsis,
softWrap: true,
),
],
),
),
),
],
),
Card(
child: Container(
padding: const EdgeInsets.all(10.0),
height: context.highValue,
child: Row(
children: [
const Icon(
Icons.location_on,
color: Colors.blue,
size: 30,
),
SizedBox(
width: context.lowValue,
),
Expanded(
child: StreamBuilder<String>(
stream:
detailDeviceBloc.streamDeviceLocation,
builder: (context, locationSnapshot) {
if (locationSnapshot.data != null) {
return Text(
locationSnapshot.data ?? "",
style:
const TextStyle(fontSize: 13),
maxLines: 3,
overflow: TextOverflow.ellipsis,
softWrap: true,
);
} else {
detailDeviceBloc.findLocation(context,
deviceSnapshot.data!.areaPath!);
return Text(appLocalization(context)
.undefine_message);
}
},
),
),
],
),
),
),
Card(
child: Container(
height: 300,
padding: const EdgeInsets.fromLTRB(5, 5, 5, 5),
decoration: const BoxDecoration( decoration: const BoxDecoration(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(
Radius.circular(15), Radius.circular(15),
), ),
), ),
child: deviceSnapshot.data!.settings!.latitude != child: deviceSnapshot
.data!.settings!.latitude !=
"" ""
? GoogleMap( ? StreamBuilder<String>(
initialCameraPosition: initialCamera, stream: detailDeviceBloc
.streamDeviceLocation,
builder: (context, locationSnapshot) {
if (locationSnapshot.data == null) {
detailDeviceBloc.findLocation(
context,
deviceSnapshot
.data!.areaPath!);
}
return GoogleMap(
initialCameraPosition:
initialCamera,
mapType: MapType.normal, mapType: MapType.normal,
markers: { markers: {
Marker( Marker(
infoWindow: InfoWindow(
title:
locationSnapshot.data ??
"",
),
markerId: MarkerId( markerId: MarkerId(
deviceSnapshot.data!.thingId!), deviceSnapshot
.data!.thingId!),
position: LatLng( position: LatLng(
double.parse(deviceSnapshot double.parse(deviceSnapshot
.data!.settings!.latitude!), .data!
.settings!
.latitude!),
double.parse(deviceSnapshot double.parse(deviceSnapshot
.data!.settings!.longitude!), .data!
.settings!
.longitude!),
), ),
), ),
}, },
onMapCreated: (mapcontroller) { onMapCreated: (mapcontroller) {
controller.complete(mapcontroller); controller
.complete(mapcontroller);
}, },
mapToolbarEnabled: false, mapToolbarEnabled: false,
zoomControlsEnabled: false, zoomControlsEnabled: false,
liteModeEnabled: true, liteModeEnabled: true,
) );
})
: Center( : Center(
child: Text( child: Text(
appLocalization(context) appLocalization(context)
@@ -376,11 +628,21 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
), ),
), ),
), ),
Text(
appLocalization(context)
.device_update_location,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
), ),
)
], ],
), ),
), ),
), ),
],
),
),
); );
} else { } else {
return Scaffold( return Scaffold(

View File

@@ -63,7 +63,6 @@ class _DevicesManagerScreenState extends State<DevicesManagerScreen> {
} else { } else {
return SingleChildScrollView( return SingleChildScrollView(
child: Column( child: Column(
// mainAxisSize: MainAxisSize.min,
children: [ children: [
StreamBuilder<String>( StreamBuilder<String>(
stream: devicesManagerBloc.streamUserRole, stream: devicesManagerBloc.streamUserRole,

View File

@@ -10,13 +10,6 @@ class MainBloc extends BlocBase {
StreamSink<Bell> get sinkBellBloc => bellBloc.sink; StreamSink<Bell> get sinkBellBloc => bellBloc.sink;
Stream<Bell> get streamBellBloc => bellBloc.stream; Stream<Bell> get streamBellBloc => bellBloc.stream;
final title = StreamController<String>.broadcast();
StreamSink<String> get sinkTitle => title.sink;
Stream<String> get streamTitle => title.stream;
final role = StreamController<String>.broadcast();
StreamSink<String> get sinkRole => role.sink;
Stream<String> get streamRole => role.stream;
final language = StreamController<Locale?>.broadcast(); final language = StreamController<Locale?>.broadcast();
StreamSink<Locale?> get sinkLanguage => language.sink; StreamSink<Locale?> get sinkLanguage => language.sink;
@@ -30,10 +23,6 @@ class MainBloc extends BlocBase {
StreamSink<bool> get sinkIsVNIcon => isVNIcon.sink; StreamSink<bool> get sinkIsVNIcon => isVNIcon.sink;
Stream<bool> get streamIsVNIcon => isVNIcon.stream; Stream<bool> get streamIsVNIcon => isVNIcon.stream;
final currentPageIndex = StreamController<int>.broadcast();
StreamSink<int> get sinkCurrentPageIndex => currentPageIndex.sink;
Stream<int> get streamCurrentPageIndex => currentPageIndex.stream;
@override @override
void dispose() {} void dispose() {}
} }

View File

@@ -13,6 +13,8 @@ import 'package:sfm_app/product/constant/app/app_constants.dart';
import 'package:sfm_app/product/constant/enums/app_route_enums.dart'; import 'package:sfm_app/product/constant/enums/app_route_enums.dart';
import 'package:sfm_app/product/constant/enums/role_enums.dart'; import 'package:sfm_app/product/constant/enums/role_enums.dart';
import 'package:sfm_app/product/permission/location_permission.dart'; import 'package:sfm_app/product/permission/location_permission.dart';
import 'package:sfm_app/product/shared/shared_language_switch.dart';
import '../../product/shared/shared_light_dark_switch.dart';
import '../devices/devices_manager_bloc.dart'; import '../devices/devices_manager_bloc.dart';
import '../devices/devices_manager_screen.dart'; import '../devices/devices_manager_screen.dart';
import '../home/home_screen.dart'; import '../home/home_screen.dart';
@@ -53,8 +55,6 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
Bell bell = Bell(); Bell bell = Bell();
void initialCheck() async { void initialCheck() async {
role = await apiServices.getUserRole();
mainBloc.sinkRole.add(role);
String language = await apiServices.checkLanguage(); String language = await apiServices.checkLanguage();
String theme = await apiServices.checkTheme(); String theme = await apiServices.checkTheme();
if (language == LanguageConstants.VIETNAM) { if (language == LanguageConstants.VIETNAM) {
@@ -73,9 +73,12 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
LocationPermissionRequest.instance.checkLocationPermission(context); LocationPermissionRequest.instance.checkLocationPermission(context);
} }
// For test
late bool dayNightToggle2;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
dayNightToggle2 = false;
mainBloc = BlocProvider.of(context); mainBloc = BlocProvider.of(context);
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
initialCheck(); initialCheck();
@@ -176,116 +179,38 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
ThemeNotifier themeNotifier = context.watch<ThemeNotifier>(); ThemeNotifier themeNotifier = context.watch<ThemeNotifier>();
checkSelectedIndex(currentPageIndex);
List<Widget> userDestinations = [
NavigationDestination(
selectedIcon: IconConstants.instance.getMaterialIcon(Icons.home),
icon: IconConstants.instance.getMaterialIcon(Icons.home_outlined),
label: appLocalization(context).home_page_destination,
tooltip: appLocalization(context).home_page_destination,
),
NavigationDestination(
selectedIcon: IconConstants.instance.getMaterialIcon(Icons.settings),
icon: IconConstants.instance.getMaterialIcon(Icons.settings_outlined),
label: appLocalization(context).manager_page_destination,
tooltip: appLocalization(context).device_manager_page_name,
),
NavigationDestination(
selectedIcon: IconConstants.instance.getMaterialIcon(Icons.location_on),
icon:
IconConstants.instance.getMaterialIcon(Icons.location_on_outlined),
label: appLocalization(context).map_page_destination,
tooltip: appLocalization(context).map_page_destination,
),
NavigationDestination(
// selectedIcon: IconConstants.instance.getMaterialIcon(Icons.histor),
icon: IconConstants.instance.getMaterialIcon(Icons.history_rounded),
label: appLocalization(context).history_page_destination,
tooltip: appLocalization(context).history_page_destination_tooltip,
),
NavigationDestination(
selectedIcon: IconConstants.instance.getMaterialIcon(Icons.group),
icon: IconConstants.instance.getMaterialIcon(Icons.group_outlined),
label: appLocalization(context).group_page_destination,
tooltip: appLocalization(context).group_page_destination_tooltip,
),
];
List<Widget> userBody = [
BlocProvider(child: const HomeScreen(), blocBuilder: () => HomeBloc()),
BlocProvider(
child: const DevicesManagerScreen(),
blocBuilder: () => DevicesManagerBloc()),
BlocProvider(
child: const MapScreen(),
blocBuilder: () => MapBloc(),
),
BlocProvider(
child: const DeviceLogsScreen(),
blocBuilder: () => DeviceLogsBloc(),
),
BlocProvider(
child: const InterFamilyScreen(),
blocBuilder: () => InterFamilyBloc(),
),
];
List<Widget> modDestinations = [
NavigationDestination(
selectedIcon: IconConstants.instance.getMaterialIcon(Icons.home),
icon: IconConstants.instance.getMaterialIcon(Icons.home_outlined),
label: appLocalization(context).home_page_destination,
tooltip: appLocalization(context).home_page_destination,
),
NavigationDestination(
selectedIcon: IconConstants.instance.getMaterialIcon(Icons.settings),
icon: IconConstants.instance.getMaterialIcon(Icons.settings_outlined),
label: appLocalization(context).manager_page_destination,
tooltip: appLocalization(context).device_manager_page_name,
),
NavigationDestination(
selectedIcon: IconConstants.instance.getMaterialIcon(Icons.location_on),
icon:
IconConstants.instance.getMaterialIcon(Icons.location_on_outlined),
label: appLocalization(context).map_page_destination,
tooltip: appLocalization(context).map_page_destination,
),
];
List<Widget> modBody = [
BlocProvider(child: const HomeScreen(), blocBuilder: () => HomeBloc()),
BlocProvider(
child: const DevicesManagerScreen(),
blocBuilder: () => DevicesManagerBloc()),
BlocProvider(
child: const MapScreen(),
blocBuilder: () => MapBloc(),
),
];
return StreamBuilder<String>(
stream: mainBloc.streamRole,
initialData: role,
builder: (context, roleSnapshot) {
return StreamBuilder<int>(
stream: mainBloc.streamCurrentPageIndex,
initialData: currentPageIndex,
builder: (context, indexSnapshot) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
// centerTitle: true, actions: [
// title: StreamBuilder<String>( // LightDarkSwitch(
// stream: mainBloc.streamTitle, // value: !isLight,
// initialData: titlePage, // onChanged: (value) {
// builder: (context, titleSnapshot) { // themeNotifier.changeTheme();
// return Text( // isLight = !isLight;
// titleSnapshot.data ?? ApplicationConstants.APP_NAME,
// );
// }, // },
// ), // ),
actions: [ // SizedBox(
// width: context.lowValue,
// ),
// StreamBuilder<bool>(
// stream: mainBloc.streamIsVNIcon,
// builder: (context, isVNSnapshot) {
// return LanguageSwitch(
// value: isVNSnapshot.data ?? isVN,
// onChanged: (value) async {
// Locale locale = await LanguageServices().setLocale(isVN
// ? LanguageConstants.ENGLISH
// : LanguageConstants.VIETNAM);
// MyApp.setLocale(context, locale);
// isVN = !isVN;
// mainBloc.sinkIsVNIcon.add(isVN);
// },
// );
// }),
// SizedBox(
// width: context.lowValue,
// ),
StreamBuilder<bool>( StreamBuilder<bool>(
stream: mainBloc.streamThemeMode, stream: mainBloc.streamThemeMode,
initialData: isLight, initialData: isLight,
@@ -311,8 +236,7 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
return IconButton( return IconButton(
onPressed: () async { onPressed: () async {
log("Locale: ${LanguageServices().getLocale()}"); log("Locale: ${LanguageServices().getLocale()}");
Locale locale = await LanguageServices().setLocale( Locale locale = await LanguageServices().setLocale(isVN
isVN
? LanguageConstants.ENGLISH ? LanguageConstants.ENGLISH
: LanguageConstants.VIETNAM); : LanguageConstants.VIETNAM);
MyApp.setLocale(context, locale); MyApp.setLocale(context, locale);
@@ -321,9 +245,7 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
}, },
icon: Image.asset( icon: Image.asset(
IconConstants.instance.getIcon( IconConstants.instance.getIcon(
isVnSnapshot.data ?? isVN isVnSnapshot.data ?? isVN ? 'vi_icon' : 'en_icon'),
? 'vi_icon'
: 'en_icon'),
height: 24, height: 24,
width: 24, width: 24,
), ),
@@ -353,12 +275,9 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
color: Colors.red, color: Colors.red,
size: 5, size: 5,
), ),
badgeAnimation: badgeAnimation: const badges.BadgeAnimation.slide(
const badges.BadgeAnimation.slide( animationDuration: Duration(milliseconds: 200),
animationDuration: colorChangeAnimationDuration: Duration(seconds: 1),
Duration(milliseconds: 200),
colorChangeAnimationDuration:
Duration(seconds: 1),
loopAnimation: false, loopAnimation: false,
curve: Curves.decelerate, curve: Curves.decelerate,
colorChangeAnimationCurve: Curves.easeInCirc, colorChangeAnimationCurve: Curves.easeInCirc,
@@ -470,10 +389,6 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
navBarStyle: NavBarStyle.style4, navBarStyle: NavBarStyle.style4,
), ),
); );
},
);
},
);
} }
Future<void> getBellNotification() async { Future<void> getBellNotification() async {
@@ -487,23 +402,4 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
} }
return !bells.any((bell) => bell.status == 0); return !bells.any((bell) => bell.status == 0);
} }
void checkSelectedIndex(int current) {
if (current == 0) {
titlePage = appLocalization(context).home_page_name;
mainBloc.sinkTitle.add(titlePage);
} else if (current == 1) {
titlePage = appLocalization(context).device_manager_page_name;
mainBloc.sinkTitle.add(titlePage);
} else if (current == 2) {
titlePage = appLocalization(context).map_page_destination;
mainBloc.sinkTitle.add(titlePage);
} else if (current == 3) {
titlePage = appLocalization(context).device_log_page_name;
mainBloc.sinkTitle.add(titlePage);
} else if (current == 4) {
titlePage = appLocalization(context).interfamily_page_name;
mainBloc.sinkTitle.add(titlePage);
}
}
} }

View File

@@ -38,7 +38,6 @@ class _MyAppState extends State<MyApp> {
Locale? _locale; Locale? _locale;
late MainBloc mainBloc; late MainBloc mainBloc;
LanguageServices languageServices = LanguageServices(); LanguageServices languageServices = LanguageServices();
// late ThemeNotifier themeNotifier;
setLocale(Locale locale) { setLocale(Locale locale) {
_locale = locale; _locale = locale;
mainBloc.sinkLanguage.add(_locale); mainBloc.sinkLanguage.add(_locale);
@@ -48,10 +47,6 @@ class _MyAppState extends State<MyApp> {
void initState() { void initState() {
super.initState(); super.initState();
mainBloc = BlocProvider.of(context); mainBloc = BlocProvider.of(context);
// themeNotifier = Provider.of<ThemeNotifier>(context, listen: false);
// ThemeNotifier().loadThemeFromPreferences();
// log("ThemeKey1: ${LocaleManager.instance.getStringValue(PreferencesKeys.THEME)}");
// log("Date: ${DateTime.parse("2024-11-16T07:17:36.785Z")}");
} }
@override @override

View File

@@ -71,9 +71,8 @@ extension PageExtension on BuildContext {
extension DurationExtension on BuildContext { extension DurationExtension on BuildContext {
Duration get lowDuration => const Duration(milliseconds: 150); Duration get lowDuration => const Duration(milliseconds: 150);
Duration get normalDuration => const Duration(milliseconds: 500); Duration get normalDuration => const Duration(milliseconds: 500);
Duration dynamicSecondDuration(int seconds) => Duration(seconds: seconds); Duration dynamicMilliSecondDuration(int milliseconds) => Duration(milliseconds: milliseconds);
Duration dynamicMinutesDuration(int minutes) => Duration(minutes: minutes); Duration dynamicMinutesDuration(int minutes) => Duration(minutes: minutes);
} }
// RADIUS // RADIUS
@@ -81,6 +80,7 @@ extension RadiusExtension on BuildContext {
Radius get lowRadius => Radius.circular(width * 0.02); Radius get lowRadius => Radius.circular(width * 0.02);
Radius get normalRadius => Radius.circular(width * 0.05); Radius get normalRadius => Radius.circular(width * 0.05);
Radius get highRadius => Radius.circular(width * 0.1); Radius get highRadius => Radius.circular(width * 0.1);
Radius dynamicRadius(double radius) => Radius.circular(radius);
} }
extension TextStyleExtention on BuildContext { extension TextStyleExtention on BuildContext {
@@ -100,4 +100,3 @@ extension TextStyleExtention on BuildContext {
TextStyle get headlineLargeTextStyle => TextStyle get headlineLargeTextStyle =>
Theme.of(this).textTheme.headlineLarge!; Theme.of(this).textTheme.headlineLarge!;
} }

View File

@@ -178,6 +178,7 @@
"device_update_ward": "Ward/Commune", "device_update_ward": "Ward/Commune",
"description_NOTUSE10": "This is english language in DetailDevicePage", "description_NOTUSE10": "This is english language in DetailDevicePage",
"detail_device_dont_has_location_message": "No location information available yet", "detail_device_dont_has_location_message": "No location information available yet",
"detail_device_volt_message": "Measured voltage (V)",
"no_data_message": "No data yet", "no_data_message": "No data yet",
"normal_message": "Normal", "normal_message": "Normal",
"warning_status_message": "Warning", "warning_status_message": "Warning",

View File

@@ -178,6 +178,7 @@
"device_update_ward": "Phường/Xã", "device_update_ward": "Phường/Xã",
"description_NOTUSE10": "This is vietnamese language in DetailDevicePage", "description_NOTUSE10": "This is vietnamese language in DetailDevicePage",
"detail_device_dont_has_location_message": "Chưa có thông tin về vị trí", "detail_device_dont_has_location_message": "Chưa có thông tin về vị trí",
"detail_device_volt_message": "Nguồn điện đo được (V)",
"no_data_message": "Chưa có", "no_data_message": "Chưa có",
"normal_message": "Bình thường", "normal_message": "Bình thường",
"warning_status_message": "Cảnh báo", "warning_status_message": "Cảnh báo",

View File

@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
class CuveEdgesCustom extends CustomClipper<Path> {
@override
getClip(Size size) {
Path path = Path();
path.lineTo(0, size.height);
final firstCurve = Offset(0, size.height - 30);
final lastCurve = Offset(30, size.height - 30);
path.quadraticBezierTo(
firstCurve.dx, firstCurve.dy, lastCurve.dx, lastCurve.dy);
final secondFirstCurve = Offset(0, size.height - 30);
final secondLastCurve = Offset(size.width - 30, size.height - 30);
path.quadraticBezierTo(secondFirstCurve.dx, secondFirstCurve.dy,
secondLastCurve.dx, secondLastCurve.dy);
final thirdFirstCurve = Offset(size.width, size.height - 30);
final thirdLastCurve = Offset(size.width, size.height);
path.quadraticBezierTo(thirdFirstCurve.dx, thirdFirstCurve.dy,
thirdLastCurve.dx, thirdLastCurve.dy);
path.lineTo(size.width, 0);
path.close();
return path;
}
@override
bool shouldReclip(covariant CustomClipper oldClipper) {
return true;
}
}

View File

@@ -0,0 +1,127 @@
import 'package:flutter/material.dart';
import 'package:sfm_app/product/constant/icon/icon_constants.dart';
const int _kDuration = 300;
const double _kWidth = 60;
const double _kheight = 30;
class LanguageSwitch extends StatefulWidget {
const LanguageSwitch({
super.key,
required this.value,
this.onChanged,
});
/// Whether this switch is on or off.
///
/// This property must not be null.
final bool value;
/// Called when the user toggles the switch on or off.
///
/// The switch passes the new value to the callback but does not actually
/// change state until the parent widget rebuilds the switch with the new
/// value.
///
/// If null, the switch will be displayed as disabled.
///
/// The callback provided to [onChanged] should update the state of the parent
/// [StatefulWidget] using the [State.setState] method, so that the parent
/// gets rebuilt; for example:
///
/// ```dart
/// LanguageSwitch(
/// value: _giveVerse,
/// onChanged: (bool newValue) {
/// setState(() {
/// _giveVerse = newValue;
/// });
/// },
/// )
/// ```
final ValueChanged<bool>? onChanged;
@override
State<LanguageSwitch> createState() => _LanguageSwitchState();
}
class _LanguageSwitchState extends State<LanguageSwitch> {
@override
Widget build(BuildContext context) {
bool toggleState = widget.value;
const dayColor = Colors.blue;
const nightColor = Colors.grey;
return InkWell(
onTap: () => setState(() {
toggleState = !toggleState;
widget.onChanged?.call(toggleState);
}),
customBorder: const StadiumBorder(),
child: AnimatedContainer(
duration: const Duration(milliseconds: _kDuration),
width: _kWidth,
height: _kheight,
decoration: ShapeDecoration(
color: toggleState ? dayColor : nightColor,
shape: const StadiumBorder(),
),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Stack(
alignment: Alignment.center,
children: [
//day icon
AnimatedOpacity(
opacity: toggleState ? 1 : 0,
duration: const Duration(milliseconds: _kDuration),
child: AnimatedAlign(
alignment: toggleState
? Alignment.centerLeft
: Alignment.centerRight,
duration: const Duration(milliseconds: _kDuration),
// child: const Icon(
// Icons.circle,
// size: 30,
// // color: Colors.white,
// ),
child: Image.asset(
IconConstants.instance.getIcon('vi_icon'),
width: 30,
height: 30,
),
),
),
//night Icon
AnimatedOpacity(
opacity: toggleState ? 0 : 1,
duration: const Duration(milliseconds: _kDuration),
child: AnimatedAlign(
alignment: toggleState
? Alignment.centerLeft
: Alignment.centerRight,
duration: const Duration(milliseconds: _kDuration),
child: AnimatedRotation(
turns: toggleState ? 0.0 : 0.5,
duration: const Duration(milliseconds: _kDuration),
// child: const Icon(
// Icons.nightlight,
// size: 30,
// // color: Colors.white,
// ),
child: Image.asset(
IconConstants.instance.getIcon('en_icon'),
width: 30,
height: 30,
),
),
),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,212 @@
import 'package:flutter/material.dart';
const int _kDuration = 300;
const double _kWidth = 60;
const double _kheight = 30;
class LightDarkSwitch extends StatefulWidget {
const LightDarkSwitch({
super.key,
required this.value,
this.onChanged,
});
/// Whether this switch is on or off.
///
/// This property must not be null.
final bool value;
/// Called when the user toggles the switch on or off.
///
/// The switch passes the new value to the callback but does not actually
/// change state until the parent widget rebuilds the switch with the new
/// value.
///
/// If null, the switch will be displayed as disabled.
///
/// The callback provided to [onChanged] should update the state of the parent
/// [StatefulWidget] using the [State.setState] method, so that the parent
/// gets rebuilt; for example:
///
/// ```dart
/// LightDarkSwitch(
/// value: _giveVerse,
/// onChanged: (bool newValue) {
/// setState(() {
/// _giveVerse = newValue;
/// });
/// },
/// )
/// ```
final ValueChanged<bool>? onChanged;
@override
State<LightDarkSwitch> createState() => _LightDarkSwitchState();
}
class _LightDarkSwitchState extends State<LightDarkSwitch> {
@override
Widget build(BuildContext context) {
bool toggleState = widget.value;
const activeColor = Colors.blue;
const inactiveColor = Colors.black;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
onTap: () => setState(() {
toggleState = !toggleState;
widget.onChanged?.call(toggleState);
}),
customBorder: const StadiumBorder(),
child: AnimatedContainer(
duration: const Duration(milliseconds: _kDuration),
width: _kWidth,
height: _kheight,
decoration: ShapeDecoration(
color: toggleState ? activeColor : inactiveColor,
shape: const StadiumBorder(),
),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Stack(
alignment: Alignment.center,
children: [
//Light icon
AnimatedOpacity(
opacity: toggleState ? 1 : 0,
duration: const Duration(milliseconds: _kDuration),
child: AnimatedAlign(
alignment: toggleState
? Alignment.centerLeft
: Alignment.centerRight,
duration: const Duration(milliseconds: _kDuration),
child: const Icon(
Icons.sunny,
size: 25,
color: Colors.white,
),
),
),
AnimatedPositioned(
top: 2,
right: toggleState ? 6 : 40,
duration: const Duration(milliseconds: _kDuration),
child: AnimatedOpacity(
opacity: toggleState ? 1 : 0,
duration: const Duration(milliseconds: _kDuration),
child: const Icon(
Icons.circle,
size: 8,
color: Colors.white,
),
),
),
AnimatedPositioned(
top: 16,
right: toggleState ? 14 : 40,
duration: const Duration(milliseconds: _kDuration),
child: const Icon(
Icons.circle,
size: 3,
color: Colors.white,
),
),
//Dark Icon
AnimatedOpacity(
opacity: toggleState ? 0 : 1,
duration: const Duration(milliseconds: _kDuration),
child: AnimatedAlign(
alignment: toggleState
? Alignment.centerLeft
: Alignment.centerRight,
duration: const Duration(milliseconds: _kDuration),
child: const Icon(
Icons.mode_night_sharp,
size: 25,
color: Colors.white,
),
),
),
AnimatedPositioned(
bottom: 3,
left: toggleState ? 40 : 14,
duration: const Duration(milliseconds: _kDuration),
child: AnimatedOpacity(
opacity: toggleState ? 0 : 1,
duration: const Duration(milliseconds: _kDuration),
child: const Icon(
Icons.star,
size: 8,
color: Colors.white,
),
),
),
AnimatedPositioned(
top: 2,
left: toggleState ? 40 : 4,
duration: const Duration(milliseconds: _kDuration),
child: AnimatedOpacity(
opacity: toggleState ? 0 : 1,
duration: const Duration(milliseconds: _kDuration),
child: const Icon(
Icons.star,
size: 10,
color: Colors.white,
),
),
),
AnimatedPositioned(
top: 10,
left: toggleState ? 40 : 16,
duration: const Duration(milliseconds: _kDuration),
child: AnimatedOpacity(
opacity: toggleState ? 0 : 1,
duration: const Duration(milliseconds: _kDuration),
child: const Icon(
Icons.circle,
size: 3,
color: Colors.white,
),
),
),
AnimatedPositioned(
top: 4,
left: toggleState ? 40 : 22,
duration: const Duration(milliseconds: _kDuration),
child: AnimatedOpacity(
opacity: toggleState ? 0 : 1,
duration: const Duration(milliseconds: _kDuration),
child: const Icon(
Icons.circle,
size: 3,
color: Colors.white,
),
),
),
],
),
),
),
),
// const SizedBox(
// width: 8.0,
// ),
// Text(
// "Dark",
// style: TextStyle(
// color: toggleState ? Colors.grey : inactiveColor,
// fontWeight: FontWeight.bold,
// ),
// ),
],
);
}
}

View File

@@ -3,18 +3,12 @@ import 'package:flutter/material.dart';
import 'package:sfm_app/feature/device_log/device_logs_model.dart'; import 'package:sfm_app/feature/device_log/device_logs_model.dart';
import 'package:sfm_app/product/utils/date_time_utils.dart'; import 'package:sfm_app/product/utils/date_time_utils.dart';
Widget sharedLineChart( Widget sharedLineChart(String chartName, List<SensorLogs> sensors) {
String chartName, List<SensorLogs> sensors, double maxValue) {
double max = sensors
.map((sensor) => sensor.value!) // Lấy giá trị của từng sensor
.reduce((a, b) => a > b ? a : b)
.toDouble();
double averageValue = (0 + maxValue) / 2;
return LineChart( return LineChart(
LineChartData( LineChartData(
minX: 0, minX: 0,
minY: 0, minY: 0,
maxY: max + 20, maxY: 4000,
titlesData: FlTitlesData( titlesData: FlTitlesData(
show: true, show: true,
topTitles: const AxisTitles( topTitles: const AxisTitles(
@@ -29,24 +23,20 @@ Widget sharedLineChart(
), ),
bottomTitles: AxisTitles( bottomTitles: AxisTitles(
axisNameSize: 20, axisNameSize: 20,
axisNameWidget: Text(chartName), axisNameWidget: Text(
chartName,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
), ),
leftTitles: AxisTitles( ),
),
leftTitles: const AxisTitles(
sideTitles: SideTitles( sideTitles: SideTitles(
showTitles: true, showTitles: false,
reservedSize: 40, ),
getTitlesWidget: (value, meta) { ),
if (value == 0) { ),
return const Text("0");
} else if (value == averageValue) {
return Text(averageValue.toInt().toString());
} else if (value == maxValue) {
return Text(maxValue.toInt().toString());
} else {
return Container();
}
},
))),
lineTouchData: LineTouchData( lineTouchData: LineTouchData(
touchTooltipData: LineTouchTooltipData( touchTooltipData: LineTouchTooltipData(
tooltipBgColor: Colors.grey.withOpacity(0.3), tooltipBgColor: Colors.grey.withOpacity(0.3),
@@ -55,7 +45,7 @@ Widget sharedLineChart(
final index = spot.x.toInt(); final index = spot.x.toInt();
final sensorData = sensors[index]; final sensorData = sensors[index];
return LineTooltipItem( return LineTooltipItem(
'Time: ${DateTimeUtils.instance.convertCurrentMillisToDateTimeString(sensorData.time!)}\nValue: ${sensorData.value}', 'Time: ${DateTimeUtils.instance.convertCurrentMillisToDateTimeString(sensorData.time!)}\nValue: ${(sensorData.value! / 1000).toDouble()}V',
const TextStyle(), const TextStyle(),
); );
}).toList(); }).toList();
@@ -93,8 +83,11 @@ Widget sharedLineChart(
border: Border( border: Border(
top: BorderSide.none, top: BorderSide.none,
right: BorderSide.none, right: BorderSide.none,
left: BorderSide(color: Colors.black.withOpacity(0.7)), left: BorderSide.none,
bottom: BorderSide(color: Colors.black.withOpacity(0.7))), bottom: BorderSide(
color: Colors.black.withOpacity(0.7),
),
),
), ),
), ),
); );

View File

@@ -47,40 +47,40 @@ class SharedPieChart extends StatelessWidget {
color: Colors.grey, color: Colors.grey,
value: offlineCount.toDouble(), value: offlineCount.toDouble(),
title: offlineCount.toString(), title: offlineCount.toString(),
radius: context.dynamicWidth(0.3), radius: context.dynamicWidth(0.2),
titleStyle: titleStyle, titleStyle: titleStyle,
), ),
PieChartSectionData( PieChartSectionData(
color: Colors.green, color: Colors.green,
value: normalCount.toDouble(), value: normalCount.toDouble(),
title: normalCount.toString(), title: normalCount.toString(),
radius: context.dynamicWidth(0.3), radius: context.dynamicWidth(0.2),
titleStyle: titleStyle, titleStyle: titleStyle,
), ),
PieChartSectionData( PieChartSectionData(
color: Colors.red, color: Colors.red,
value: warningCount.toDouble(), value: warningCount.toDouble(),
title: warningCount.toString(), title: warningCount.toString(),
radius: context.dynamicWidth(0.3), radius: context.dynamicWidth(0.2),
titleStyle: titleStyle, titleStyle: titleStyle,
), ),
PieChartSectionData( PieChartSectionData(
color: Colors.yellow, color: Colors.yellow,
value: inProgressCount.toDouble(), value: inProgressCount.toDouble(),
title: inProgressCount.toString(), title: inProgressCount.toString(),
radius: context.dynamicWidth(0.3), radius: context.dynamicWidth(0.2),
titleStyle: titleStyle, titleStyle: titleStyle,
), ),
PieChartSectionData( PieChartSectionData(
color: Colors.black, // Có thể thêm màu cho trạng thái lỗi color: Colors.black, // Có thể thêm màu cho trạng thái lỗi
value: errorCount.toDouble(), value: errorCount.toDouble(),
title: errorCount.toString(), title: errorCount.toString(),
radius: context.dynamicWidth(0.3), radius: context.dynamicWidth(0.2),
titleStyle: titleStyle, titleStyle: titleStyle,
), ),
], ],
centerSpaceRadius: 0, centerSpaceRadius: context.dynamicWidth(0.1),
sectionsSpace: 1, sectionsSpace: 2,
), ),
), ),
), ),

View File

@@ -82,6 +82,17 @@ class ThemeNotifier extends ChangeNotifier {
AppThemes _currenThemeEnum = AppThemes.LIGHT; AppThemes _currenThemeEnum = AppThemes.LIGHT;
AppThemes get currenThemeEnum => _currenThemeEnum; AppThemes get currenThemeEnum => _currenThemeEnum;
Future<void> loadThemeFromPreferences() async {
// String themeKey =
// LocaleManager.instance.getStringValue(PreferencesKeys.THEME);
// if (themeKey == AppThemes.LIGHT.name) {
// _currentTheme = AppThemeLight.instance.theme;
// } else {
// _currentTheme = AppThemeDark.instance.theme;
// }
// notifyListeners();
}
void changeValue(AppThemes theme) { void changeValue(AppThemes theme) {
if (theme == AppThemes.LIGHT) { if (theme == AppThemes.LIGHT) {
_currentTheme = AppThemeLight.instance.theme; _currentTheme = AppThemeLight.instance.theme;

View File

@@ -8,6 +8,7 @@ import 'package:sfm_app/product/shared/model/district_model.dart';
import 'package:sfm_app/product/shared/model/province_model.dart'; import 'package:sfm_app/product/shared/model/province_model.dart';
import '../../feature/devices/device_model.dart'; import '../../feature/devices/device_model.dart';
import '../constant/icon/icon_constants.dart';
import '../shared/model/ward_model.dart'; import '../shared/model/ward_model.dart';
class DeviceUtils { class DeviceUtils {
@@ -63,13 +64,13 @@ class DeviceUtils {
} }
} }
if (sensor.name == "7") { if (sensor.name == "7") {
map['sensorVolt'] = "${(sensor.value!) / 1000} V"; map['sensorVolt'] = "${(sensor.value!) / 1000}";
} }
if (sensor.name == "8") { if (sensor.name == "8") {
map['sensorTemp'] = "${sensor.value}°C"; map['sensorTemp'] = "${sensor.value}";
} }
if (sensor.name == "9") { if (sensor.name == "9") {
map['sensorHum'] = "${sensor.value} %"; map['sensorHum'] = "${sensor.value}";
} }
if (sensor.name == "10") { if (sensor.name == "10") {
map['sensorBattery'] = "${sensor.value}"; map['sensorBattery'] = "${sensor.value}";
@@ -270,4 +271,53 @@ class DeviceUtils {
return Colors.green; return Colors.green;
} }
} }
List<String> deviceBatteryImg = [
IconConstants.instance.getIcon("full-battery"),
IconConstants.instance.getIcon("half-battery"),
IconConstants.instance.getIcon("low-battery"),
IconConstants.instance.getIcon("empty-battery"),
];
String getDeviceBatteryImg(int battery) {
if (battery <= 5) {
return deviceBatteryImg[3];
} else if (battery <= 20) {
return deviceBatteryImg[2];
} else if (battery <= 90) {
return deviceBatteryImg[1];
} else {
return deviceBatteryImg[0];
}
}
Color getDeviceTempColor(int temp) {
if (temp < 30) {
return Colors.green;
} else if (temp < 50) {
return Colors.orange;
} else {
return Colors.red;
}
}
Color getDeviceBatteryColor(int battery) {
if (battery < 20) {
return Colors.red;
} else if (battery < 80) {
return Colors.orange;
} else {
return Colors.green;
}
}
Color getSignalIconColor(BuildContext context, String signal) {
if (signal == appLocalization(context).gf_weak_signal_message) {
return Colors.red;
} else if (signal == appLocalization(context).gf_moderate_signal_message) {
return Colors.yellow;
} else {
return Colors.green;
}
}
} }