refactor(ui): update DetailDevice screen layout
This commit is contained in:
30
lib/product/shared/shared_curve.dart
Normal file
30
lib/product/shared/shared_curve.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
127
lib/product/shared/shared_language_switch.dart
Normal file
127
lib/product/shared/shared_language_switch.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
212
lib/product/shared/shared_light_dark_switch.dart
Normal file
212
lib/product/shared/shared_light_dark_switch.dart
Normal 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,
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user