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

View File

@@ -3,9 +3,10 @@ import 'dart:async';
import 'package:flutter/material.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/product/constant/image/image_constants.dart';
import 'package:sfm_app/product/shared/shared_line_chart.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 '../../../product/base/bloc/base_bloc.dart';
import '../../../product/extention/context_extention.dart';
@@ -28,14 +29,15 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
IconConstants.instance.getIcon("offline_icon"),
IconConstants.instance.getIcon("flame_icon"),
];
String stateImgAssets(int state) {
String imgStringAsset;
if (state == 0) {
imgStringAsset = imageAssets[0];
} else if (state == 1) {
imgStringAsset = imageAssets[1];
} else {
imgStringAsset = imageAssets[2];
} else {
imgStringAsset = imageAssets[1];
}
return imgStringAsset;
}
@@ -50,14 +52,31 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
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
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width;
return StreamBuilder<Device>(
stream: detailDeviceBloc.streamDeviceInfo,
builder: (context, deviceSnapshot) {
if (deviceSnapshot.data?.extId == null) {
detailDeviceBloc.getDeviceDetail(context, widget.thingID, controller);
detailDeviceBloc.getDeviceDetail(
context,
widget.thingID,
controller,
);
return const Center(
child: CircularProgressIndicator(),
);
@@ -71,314 +90,557 @@ class _DetailDeviceScreenState extends State<DetailDeviceScreen> {
title: Text(appLocalization(context).detail_message),
centerTitle: true,
),
body: SafeArea(
child: SingleChildScrollView(
child: Column(
children: [
// device Name
Card(
child: Container(
width: context.dynamicWidth(1),
height: context.highValue,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(12),
),
),
child: Center(
child: Text(
'${appLocalization(context).device_title}: ${deviceSnapshot.data!.name}',
style: const TextStyle(
fontSize: 20,
body: SingleChildScrollView(
child: Column(
children: <Widget>[
Stack(
children: [
ClipPath(
clipper: CuveEdgesCustom(),
child: Container(
padding: const EdgeInsets.all(0),
child: SizedBox(
height: context.dynamicHeight(0.25),
child: Stack(
children: [
Positioned.fill(
child: Image.asset(
ImageConstants.instance
.getImage('smoke-detector'),
fit: BoxFit.fill,
),
),
Center(
child: Container(
height: 50,
width: 400,
// color: Colors.blueAccent,
alignment: Alignment.centerRight,
margin: const EdgeInsets.fromLTRB(
0, 0, 0, 50),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
const SizedBox(),
Text(
deviceSnapshot.data?.name ?? "",
style: const TextStyle(
fontSize: 25,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
],
),
),
),
),
),
// Tinh trang va nhiet do
Row(
children: [
Card(
child: Container(
width: (screenWidth - 20) / 2,
height: context.highValue,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(12),
Positioned(
bottom: 0,
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(
deviceSnapshot.data?.state ?? 3),
borderRadius: BorderRadius.circular(50),
),
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(
stateImgAssets(
deviceSnapshot.data!.state!,
),
),
),
)
: CircleAvatar(
backgroundColor: DeviceUtils
.instance
.getTableRowColor(
deviceSnapshot.data?.state ?? 3,
),
minRadius: context.mediumValue,
maxRadius: context.mediumValue,
backgroundImage: AssetImage(
stateImgAssets(
deviceSnapshot.data!.state!,
),
),
),
),
),
padding:
const EdgeInsets.fromLTRB(5, 5, 0, 5),
alignment: Alignment.centerLeft,
child: Row(
children: [
// SizedBox(
// height: 25,
// width: 25,
// child: RippleAnimation(
// color: DeviceUtils.instance
// .getColorRiple(
// 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(
// stateImgAssets(
// deviceSnapshot.data!.state!,
// ),
// ),
// ),
// ),
// ),
CircleAvatar(
minRadius: 20,
maxRadius: 20,
backgroundImage: AssetImage(
stateImgAssets(
deviceSnapshot.data!.state!,
),
),
),
SizedBox(
width: context.lowValue,
),
Text(
Center(
child: Text(
DeviceUtils.instance.checkStateDevice(
context,
deviceSnapshot.data!.state!,
),
style: const TextStyle(
fontSize: 15,
fontSize: 20,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
],
),
),
],
),
),
Card(
child: SizedBox(
width: (screenWidth - 20) / 2,
height: context.highValue,
child: Container(
alignment: Alignment.centerLeft,
padding:
const EdgeInsets.fromLTRB(5, 5, 0, 5),
child: Row(
),
],
),
SizedBox(
height: context.normalValue,
),
// Muc song va muc pin
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
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: [
const Icon(
Icons.thermostat,
color: Colors.blue,
size: 30,
),
const SizedBox(
width: 10,
),
Text(
"${appLocalization(context).paginated_data_table_column_deviceTemperature}: ${sensorSnapshot.data?['sensorTemp'] ?? 100}",
appLocalization(context)
.paginated_data_table_column_deviceSignal,
style: const TextStyle(
fontSize: 15,
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,
color: DeviceUtils.instance
.getSignalIconColor(
context,
sensorSnapshot.data!['sensorCsq'],
),
),
),
],
),
),
),
),
],
),
StreamBuilder<List<SensorLogs>>(
stream: detailDeviceBloc.streamSensorTemps,
builder: (context, sensorTempsSnapshot) {
if (sensorTempsSnapshot.data == null) {
detailDeviceBloc
.getNearerSensorValue(widget.thingID);
return const AspectRatio(
aspectRatio: 1.5,
child: Center(
child: CircularProgressIndicator(),
),
);
} else {
return AspectRatio(
aspectRatio: 1.5,
child: Container(
margin: context.paddingLow,
child: sharedLineChart(
"Nhiệt độ đo được (°C)",
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'],
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,
),
),
color: Colors.blue,
size: 30,
),
),
SizedBox(
width: context.lowValue,
),
Text(
"${appLocalization(context).paginated_data_table_column_deviceBaterry}: ${sensorSnapshot.data!['sensorBattery']}%",
style: const TextStyle(
fontSize: 15,
),
),
],
),
),
),
Card(
child: Container(
width: (screenWidth - 20) / 2,
height: context.highValue,
alignment: Alignment.centerLeft,
padding:
const EdgeInsets.fromLTRB(10, 5, 0, 5),
child: Row(
children: [
Icon(
DeviceUtils.instance.getSignalIcon(
context,
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);
}
},
],
),
],
),
),
),
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(
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(
"${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,
),
),
],
)
],
),
),
),
Card(
child: Container(
height: 300,
padding: const EdgeInsets.fromLTRB(5, 5, 5, 5),
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(15),
),
// Dien ap
Padding(
padding: context.paddingLow,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
appLocalization(context)
.paginated_data_table_column_devicePower,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
child: deviceSnapshot.data!.settings!.latitude !=
""
? GoogleMap(
initialCameraPosition: initialCamera,
mapType: MapType.normal,
markers: {
Marker(
markerId: MarkerId(
deviceSnapshot.data!.thingId!),
position: LatLng(
double.parse(deviceSnapshot
.data!.settings!.latitude!),
double.parse(deviceSnapshot
.data!.settings!.longitude!),
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>>(
stream: detailDeviceBloc.streamSensorTemps,
builder: (context, sensorTempsSnapshot) {
if (sensorTempsSnapshot.data == null) {
detailDeviceBloc
.getNearerSensorValue(widget.thingID);
return const AspectRatio(
aspectRatio: 3,
child: Center(
child: CircularProgressIndicator(),
),
);
} else {
return AspectRatio(
aspectRatio: 3,
child: Container(
margin: context.paddingLow,
child: sharedLineChart(
appLocalization(context)
.detail_device_volt_message,
sensorTempsSnapshot.data ?? [],
),
),
);
}
},
),
SizedBox(
height: context.lowValue,
),
// Map
Padding(
padding: context.paddingLow,
child: Container(
decoration: boxDecoration,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: context.dynamicHeight(0.3),
padding: context.paddingLow,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(15),
),
),
child: deviceSnapshot
.data!.settings!.latitude !=
""
? StreamBuilder<String>(
stream: detailDeviceBloc
.streamDeviceLocation,
builder: (context, locationSnapshot) {
if (locationSnapshot.data == null) {
detailDeviceBloc.findLocation(
context,
deviceSnapshot
.data!.areaPath!);
}
return GoogleMap(
initialCameraPosition:
initialCamera,
mapType: MapType.normal,
markers: {
Marker(
infoWindow: InfoWindow(
title:
locationSnapshot.data ??
"",
),
markerId: MarkerId(
deviceSnapshot
.data!.thingId!),
position: LatLng(
double.parse(deviceSnapshot
.data!
.settings!
.latitude!),
double.parse(deviceSnapshot
.data!
.settings!
.longitude!),
),
),
},
onMapCreated: (mapcontroller) {
controller
.complete(mapcontroller);
},
mapToolbarEnabled: false,
zoomControlsEnabled: false,
liteModeEnabled: true,
);
})
: Center(
child: Text(
appLocalization(context)
.detail_device_dont_has_location_message,
),
),
},
onMapCreated: (mapcontroller) {
controller.complete(mapcontroller);
},
mapToolbarEnabled: false,
zoomControlsEnabled: false,
liteModeEnabled: true,
)
: Center(
child: Text(
appLocalization(context)
.detail_device_dont_has_location_message,
),
),
),
Text(
appLocalization(context)
.device_update_location,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
)
],
),
),
],
),
),
],
),
),
);

View File

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

View File

@@ -10,13 +10,6 @@ class MainBloc extends BlocBase {
StreamSink<Bell> get sinkBellBloc => bellBloc.sink;
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();
StreamSink<Locale?> get sinkLanguage => language.sink;
@@ -30,10 +23,6 @@ class MainBloc extends BlocBase {
StreamSink<bool> get sinkIsVNIcon => isVNIcon.sink;
Stream<bool> get streamIsVNIcon => isVNIcon.stream;
final currentPageIndex = StreamController<int>.broadcast();
StreamSink<int> get sinkCurrentPageIndex => currentPageIndex.sink;
Stream<int> get streamCurrentPageIndex => currentPageIndex.stream;
@override
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/role_enums.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_screen.dart';
import '../home/home_screen.dart';
@@ -53,8 +55,6 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
Bell bell = Bell();
void initialCheck() async {
role = await apiServices.getUserRole();
mainBloc.sinkRole.add(role);
String language = await apiServices.checkLanguage();
String theme = await apiServices.checkTheme();
if (language == LanguageConstants.VIETNAM) {
@@ -73,9 +73,12 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
LocationPermissionRequest.instance.checkLocationPermission(context);
}
// For test
late bool dayNightToggle2;
@override
void initState() {
super.initState();
dayNightToggle2 = false;
mainBloc = BlocProvider.of(context);
WidgetsBinding.instance.addObserver(this);
initialCheck();
@@ -176,303 +179,215 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
@override
Widget build(BuildContext context) {
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(
appBar: AppBar(
backgroundColor: Colors.transparent,
// centerTitle: true,
// title: StreamBuilder<String>(
// stream: mainBloc.streamTitle,
// initialData: titlePage,
// builder: (context, titleSnapshot) {
// return Text(
// titleSnapshot.data ?? ApplicationConstants.APP_NAME,
// );
// },
// ),
actions: [
StreamBuilder<bool>(
stream: mainBloc.streamThemeMode,
initialData: isLight,
builder: (context, themeModeSnapshot) {
return IconButton(
onPressed: () {
themeNotifier.changeTheme();
isLight = !isLight;
mainBloc.sinkThemeMode.add(isLight);
},
icon: Icon(
themeModeSnapshot.data ?? isLight
? Icons.light_mode_outlined
: Icons.dark_mode_outlined,
),
);
},
),
StreamBuilder<bool>(
stream: mainBloc.streamIsVNIcon,
initialData: isVN,
builder: (context, isVnSnapshot) {
return IconButton(
onPressed: () async {
log("Locale: ${LanguageServices().getLocale()}");
Locale locale = await LanguageServices().setLocale(
isVN
? LanguageConstants.ENGLISH
: LanguageConstants.VIETNAM);
MyApp.setLocale(context, locale);
isVN = !isVN;
mainBloc.sinkIsVNIcon.add(isVN);
},
icon: Image.asset(
IconConstants.instance.getIcon(
isVnSnapshot.data ?? isVN
? 'vi_icon'
: 'en_icon'),
height: 24,
width: 24,
),
);
},
),
StreamBuilder<Bell>(
stream: mainBloc.streamBellBloc,
builder: (context, bellSnapshot) {
return checkStatus(bellSnapshot.data?.items ?? [])
? IconButton(
onPressed: () {
context.pushNamed(AppRoutes.BELL.name);
},
icon: const Icon(
Icons.notifications,
),
)
: GestureDetector(
child: badges.Badge(
badgeStyle: const badges.BadgeStyle(
shape: badges.BadgeShape.twitter,
),
key: _badgeKey,
badgeContent: const Icon(
CupertinoIcons.circle_filled,
color: Colors.red,
size: 5,
),
badgeAnimation:
const badges.BadgeAnimation.slide(
animationDuration:
Duration(milliseconds: 200),
colorChangeAnimationDuration:
Duration(seconds: 1),
loopAnimation: false,
curve: Curves.decelerate,
colorChangeAnimationCurve: Curves.easeInCirc,
),
showBadge: true,
// ignorePointer: false,
child: const Icon(
Icons.notifications,
size: 30,
),
),
onTap: () {
context.pushNamed(AppRoutes.BELL.name);
},
);
},
),
PopupMenuButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
icon: const Icon(Icons.more_vert),
itemBuilder: (context) {
return [
PopupMenuItem(
value: ApplicationConstants.SETTINGS_PATH,
onTap: () {
context.pushNamed(AppRoutes.SETTINGS.name);
},
child: Row(
children: [
const Icon(Icons.person),
const SizedBox(width: 5),
Text(appLocalization(context).profile_icon_title)
],
),
),
PopupMenuItem(
value: ApplicationConstants.LOGOUT_PATH,
onTap: () {
Future.delayed(
const Duration(milliseconds: 200),
() async {
await apiServices.logOut(context);
},
);
},
child: Row(
children: [
const Icon(Icons.logout),
const SizedBox(width: 5),
Text(
appLocalization(context).log_out,
),
],
),
),
];
},
)
],
),
// bottomNavigationBar: Container(
// decoration:
// BoxDecoration(borderRadius: BorderRadius.circular(50)),
// padding: context.paddingLow,
// child: NavigationBar(
// onDestinationSelected: (index) {
// currentPageIndex = index;
// mainBloc.sinkCurrentPageIndex.add(currentPageIndex);
// checkSelectedIndex(currentPageIndex);
// },
// selectedIndex: indexSnapshot.data ?? currentPageIndex,
// destinations: roleSnapshot.data == RoleEnums.USER.name
// ? userDestinations
// : modDestinations,
// ),
// ),
// body: IndexedStack(
// index: indexSnapshot.data ?? currentPageIndex,
// children: roleSnapshot.data == RoleEnums.USER.name
// ? userBody
// : modBody,
// ),
body: PersistentTabView(
context,
controller: controller,
screens: _buildScreens(),
items: _navBarsItems(),
confineInSafeArea: true,
handleAndroidBackButtonPress: true,
resizeToAvoidBottomInset: true,
stateManagement: true,
hideNavigationBarWhenKeyboardShows: true,
// backgroundColor: Colors.transparent,
decoration: NavBarDecoration(
borderRadius: BorderRadius.circular(30.0),
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
actions: [
// LightDarkSwitch(
// value: !isLight,
// onChanged: (value) {
// themeNotifier.changeTheme();
// isLight = !isLight;
// },
// ),
// 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>(
stream: mainBloc.streamThemeMode,
initialData: isLight,
builder: (context, themeModeSnapshot) {
return IconButton(
onPressed: () {
themeNotifier.changeTheme();
isLight = !isLight;
mainBloc.sinkThemeMode.add(isLight);
},
icon: Icon(
themeModeSnapshot.data ?? isLight
? Icons.light_mode_outlined
: Icons.dark_mode_outlined,
),
popAllScreensOnTapOfSelectedTab: true,
itemAnimationProperties: const ItemAnimationProperties(
duration: Duration(milliseconds: 200),
curve: Curves.bounceInOut,
);
},
),
StreamBuilder<bool>(
stream: mainBloc.streamIsVNIcon,
initialData: isVN,
builder: (context, isVnSnapshot) {
return IconButton(
onPressed: () async {
log("Locale: ${LanguageServices().getLocale()}");
Locale locale = await LanguageServices().setLocale(isVN
? LanguageConstants.ENGLISH
: LanguageConstants.VIETNAM);
MyApp.setLocale(context, locale);
isVN = !isVN;
mainBloc.sinkIsVNIcon.add(isVN);
},
icon: Image.asset(
IconConstants.instance.getIcon(
isVnSnapshot.data ?? isVN ? 'vi_icon' : 'en_icon'),
height: 24,
width: 24,
),
screenTransitionAnimation: const ScreenTransitionAnimation(
animateTabTransition: true,
curve: Curves.linear,
duration: Duration(milliseconds: 200),
);
},
),
StreamBuilder<Bell>(
stream: mainBloc.streamBellBloc,
builder: (context, bellSnapshot) {
return checkStatus(bellSnapshot.data?.items ?? [])
? IconButton(
onPressed: () {
context.pushNamed(AppRoutes.BELL.name);
},
icon: const Icon(
Icons.notifications,
),
)
: GestureDetector(
child: badges.Badge(
badgeStyle: const badges.BadgeStyle(
shape: badges.BadgeShape.twitter,
),
key: _badgeKey,
badgeContent: const Icon(
CupertinoIcons.circle_filled,
color: Colors.red,
size: 5,
),
badgeAnimation: const badges.BadgeAnimation.slide(
animationDuration: Duration(milliseconds: 200),
colorChangeAnimationDuration: Duration(seconds: 1),
loopAnimation: false,
curve: Curves.decelerate,
colorChangeAnimationCurve: Curves.easeInCirc,
),
showBadge: true,
// ignorePointer: false,
child: const Icon(
Icons.notifications,
size: 30,
),
),
onTap: () {
context.pushNamed(AppRoutes.BELL.name);
},
);
},
),
PopupMenuButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
icon: const Icon(Icons.more_vert),
itemBuilder: (context) {
return [
PopupMenuItem(
value: ApplicationConstants.SETTINGS_PATH,
onTap: () {
context.pushNamed(AppRoutes.SETTINGS.name);
},
child: Row(
children: [
const Icon(Icons.person),
const SizedBox(width: 5),
Text(appLocalization(context).profile_icon_title)
],
),
),
navBarStyle: NavBarStyle.style4,
),
);
},
);
},
PopupMenuItem(
value: ApplicationConstants.LOGOUT_PATH,
onTap: () {
Future.delayed(
const Duration(milliseconds: 200),
() async {
await apiServices.logOut(context);
},
);
},
child: Row(
children: [
const Icon(Icons.logout),
const SizedBox(width: 5),
Text(
appLocalization(context).log_out,
),
],
),
),
];
},
)
],
),
// bottomNavigationBar: Container(
// decoration:
// BoxDecoration(borderRadius: BorderRadius.circular(50)),
// padding: context.paddingLow,
// child: NavigationBar(
// onDestinationSelected: (index) {
// currentPageIndex = index;
// mainBloc.sinkCurrentPageIndex.add(currentPageIndex);
// checkSelectedIndex(currentPageIndex);
// },
// selectedIndex: indexSnapshot.data ?? currentPageIndex,
// destinations: roleSnapshot.data == RoleEnums.USER.name
// ? userDestinations
// : modDestinations,
// ),
// ),
// body: IndexedStack(
// index: indexSnapshot.data ?? currentPageIndex,
// children: roleSnapshot.data == RoleEnums.USER.name
// ? userBody
// : modBody,
// ),
body: PersistentTabView(
context,
controller: controller,
screens: _buildScreens(),
items: _navBarsItems(),
confineInSafeArea: true,
handleAndroidBackButtonPress: true,
resizeToAvoidBottomInset: true,
stateManagement: true,
hideNavigationBarWhenKeyboardShows: true,
// backgroundColor: Colors.transparent,
decoration: NavBarDecoration(
borderRadius: BorderRadius.circular(30.0),
),
popAllScreensOnTapOfSelectedTab: true,
itemAnimationProperties: const ItemAnimationProperties(
duration: Duration(milliseconds: 200),
curve: Curves.bounceInOut,
),
screenTransitionAnimation: const ScreenTransitionAnimation(
animateTabTransition: true,
curve: Curves.linear,
duration: Duration(milliseconds: 200),
),
navBarStyle: NavBarStyle.style4,
),
);
}
@@ -487,23 +402,4 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
}
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;
late MainBloc mainBloc;
LanguageServices languageServices = LanguageServices();
// late ThemeNotifier themeNotifier;
setLocale(Locale locale) {
_locale = locale;
mainBloc.sinkLanguage.add(_locale);
@@ -48,10 +47,6 @@ class _MyAppState extends State<MyApp> {
void initState() {
super.initState();
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

View File

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

View File

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

View File

@@ -178,6 +178,7 @@
"device_update_ward": "Phường/Xã",
"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_volt_message": "Nguồn điện đo được (V)",
"no_data_message": "Chưa có",
"normal_message": "Bình thường",
"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,50 +3,40 @@ import 'package:flutter/material.dart';
import 'package:sfm_app/feature/device_log/device_logs_model.dart';
import 'package:sfm_app/product/utils/date_time_utils.dart';
Widget sharedLineChart(
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;
Widget sharedLineChart(String chartName, List<SensorLogs> sensors) {
return LineChart(
LineChartData(
minX: 0,
minY: 0,
maxY: max + 20,
maxY: 4000,
titlesData: FlTitlesData(
show: true,
topTitles: const AxisTitles(
sideTitles: SideTitles(
showTitles: false,
show: true,
topTitles: const AxisTitles(
sideTitles: SideTitles(
showTitles: false,
),
),
rightTitles: const AxisTitles(
sideTitles: SideTitles(
showTitles: false,
),
),
bottomTitles: AxisTitles(
axisNameSize: 20,
axisNameWidget: Text(
chartName,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
rightTitles: const AxisTitles(
sideTitles: SideTitles(
showTitles: false,
),
),
leftTitles: const AxisTitles(
sideTitles: SideTitles(
showTitles: false,
),
bottomTitles: AxisTitles(
axisNameSize: 20,
axisNameWidget: Text(chartName),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
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(
touchTooltipData: LineTouchTooltipData(
tooltipBgColor: Colors.grey.withOpacity(0.3),
@@ -55,7 +45,7 @@ Widget sharedLineChart(
final index = spot.x.toInt();
final sensorData = sensors[index];
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(),
);
}).toList();
@@ -91,10 +81,13 @@ Widget sharedLineChart(
gridData: const FlGridData(show: false),
borderData: FlBorderData(
border: Border(
top: BorderSide.none,
right: BorderSide.none,
left: BorderSide(color: Colors.black.withOpacity(0.7)),
bottom: BorderSide(color: Colors.black.withOpacity(0.7))),
top: BorderSide.none,
right: BorderSide.none,
left: BorderSide.none,
bottom: BorderSide(
color: Colors.black.withOpacity(0.7),
),
),
),
),
);

View File

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

View File

@@ -82,6 +82,17 @@ class ThemeNotifier extends ChangeNotifier {
AppThemes _currenThemeEnum = AppThemes.LIGHT;
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) {
if (theme == AppThemes.LIGHT) {
_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 '../../feature/devices/device_model.dart';
import '../constant/icon/icon_constants.dart';
import '../shared/model/ward_model.dart';
class DeviceUtils {
@@ -63,13 +64,13 @@ class DeviceUtils {
}
}
if (sensor.name == "7") {
map['sensorVolt'] = "${(sensor.value!) / 1000} V";
map['sensorVolt'] = "${(sensor.value!) / 1000}";
}
if (sensor.name == "8") {
map['sensorTemp'] = "${sensor.value}°C";
map['sensorTemp'] = "${sensor.value}";
}
if (sensor.name == "9") {
map['sensorHum'] = "${sensor.value} %";
map['sensorHum'] = "${sensor.value}";
}
if (sensor.name == "10") {
map['sensorBattery'] = "${sensor.value}";
@@ -270,4 +271,53 @@ class DeviceUtils {
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;
}
}
}