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

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,
),
),
),