diff --git a/assets/icons/empty-battery.png b/assets/icons/empty-battery.png new file mode 100644 index 0000000..2d73be3 Binary files /dev/null and b/assets/icons/empty-battery.png differ diff --git a/assets/icons/full-battery.png b/assets/icons/full-battery.png new file mode 100644 index 0000000..b8fbb0c Binary files /dev/null and b/assets/icons/full-battery.png differ diff --git a/assets/icons/half-battery.png b/assets/icons/half-battery.png new file mode 100644 index 0000000..4421596 Binary files /dev/null and b/assets/icons/half-battery.png differ diff --git a/assets/icons/humidity.png b/assets/icons/humidity.png new file mode 100644 index 0000000..bf31dfd Binary files /dev/null and b/assets/icons/humidity.png differ diff --git a/assets/icons/low-battery.png b/assets/icons/low-battery.png new file mode 100644 index 0000000..23d48f3 Binary files /dev/null and b/assets/icons/low-battery.png differ diff --git a/assets/icons/signal.png b/assets/icons/signal.png new file mode 100644 index 0000000..cd653c2 Binary files /dev/null and b/assets/icons/signal.png differ diff --git a/assets/icons/smoke-detector.png b/assets/icons/smoke-detector.png new file mode 100644 index 0000000..ed2ba41 Binary files /dev/null and b/assets/icons/smoke-detector.png differ diff --git a/assets/icons/temperature.png b/assets/icons/temperature.png new file mode 100644 index 0000000..bc361cf Binary files /dev/null and b/assets/icons/temperature.png differ diff --git a/assets/icons/volt.png b/assets/icons/volt.png new file mode 100644 index 0000000..507de0d Binary files /dev/null and b/assets/icons/volt.png differ diff --git a/assets/images/smoke-detector.png b/assets/images/smoke-detector.png new file mode 100644 index 0000000..9a75903 Binary files /dev/null and b/assets/images/smoke-detector.png differ diff --git a/lib/feature/devices/device_detail/device_detail_bloc.dart b/lib/feature/devices/device_detail/device_detail_bloc.dart index dd5bec4..86823b5 100644 --- a/lib/feature/devices/device_detail/device_detail_bloc.dart +++ b/lib/feature/devices/device_detail/device_detail_bloc.dart @@ -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); diff --git a/lib/feature/devices/device_detail/device_detail_screen.dart b/lib/feature/devices/device_detail/device_detail_screen.dart index d320509..f9aa4b8 100644 --- a/lib/feature/devices/device_detail/device_detail_screen.dart +++ b/lib/feature/devices/device_detail/device_detail_screen.dart @@ -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 { 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 { 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( 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 { 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: [ + 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>( - 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: [ + 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( - 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: [ + 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>( + 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( + 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, + ), + ) + ], ), ), - ], - ), + ), + ], ), ), ); diff --git a/lib/feature/devices/devices_manager_screen.dart b/lib/feature/devices/devices_manager_screen.dart index feda0ec..704fdf1 100644 --- a/lib/feature/devices/devices_manager_screen.dart +++ b/lib/feature/devices/devices_manager_screen.dart @@ -63,7 +63,6 @@ class _DevicesManagerScreenState extends State { } else { return SingleChildScrollView( child: Column( - // mainAxisSize: MainAxisSize.min, children: [ StreamBuilder( stream: devicesManagerBloc.streamUserRole, diff --git a/lib/feature/main/main_bloc.dart b/lib/feature/main/main_bloc.dart index 9ae5707..3cea41d 100644 --- a/lib/feature/main/main_bloc.dart +++ b/lib/feature/main/main_bloc.dart @@ -10,13 +10,6 @@ class MainBloc extends BlocBase { StreamSink get sinkBellBloc => bellBloc.sink; Stream get streamBellBloc => bellBloc.stream; - final title = StreamController.broadcast(); - StreamSink get sinkTitle => title.sink; - Stream get streamTitle => title.stream; - - final role = StreamController.broadcast(); - StreamSink get sinkRole => role.sink; - Stream get streamRole => role.stream; final language = StreamController.broadcast(); StreamSink get sinkLanguage => language.sink; @@ -30,10 +23,6 @@ class MainBloc extends BlocBase { StreamSink get sinkIsVNIcon => isVNIcon.sink; Stream get streamIsVNIcon => isVNIcon.stream; - final currentPageIndex = StreamController.broadcast(); - StreamSink get sinkCurrentPageIndex => currentPageIndex.sink; - Stream get streamCurrentPageIndex => currentPageIndex.stream; - @override void dispose() {} } diff --git a/lib/feature/main/main_screen.dart b/lib/feature/main/main_screen.dart index dd56f92..02ff8aa 100644 --- a/lib/feature/main/main_screen.dart +++ b/lib/feature/main/main_screen.dart @@ -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 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 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 with WidgetsBindingObserver { @override Widget build(BuildContext context) { ThemeNotifier themeNotifier = context.watch(); - checkSelectedIndex(currentPageIndex); - - List 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 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 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 modBody = [ - BlocProvider(child: const HomeScreen(), blocBuilder: () => HomeBloc()), - BlocProvider( - child: const DevicesManagerScreen(), - blocBuilder: () => DevicesManagerBloc()), - BlocProvider( - child: const MapScreen(), - blocBuilder: () => MapBloc(), - ), - ]; - - return StreamBuilder( - stream: mainBloc.streamRole, - initialData: role, - builder: (context, roleSnapshot) { - return StreamBuilder( - stream: mainBloc.streamCurrentPageIndex, - initialData: currentPageIndex, - builder: (context, indexSnapshot) { - return Scaffold( - appBar: AppBar( - backgroundColor: Colors.transparent, - // centerTitle: true, - // title: StreamBuilder( - // stream: mainBloc.streamTitle, - // initialData: titlePage, - // builder: (context, titleSnapshot) { - // return Text( - // titleSnapshot.data ?? ApplicationConstants.APP_NAME, - // ); - // }, - // ), - actions: [ - StreamBuilder( - 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( - 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( - 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( + // 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( + 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( + 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( + 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 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); - } - } } diff --git a/lib/main.dart b/lib/main.dart index fd17086..155aa19 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -38,7 +38,6 @@ class _MyAppState extends State { 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 { void initState() { super.initState(); mainBloc = BlocProvider.of(context); - // themeNotifier = Provider.of(context, listen: false); - // ThemeNotifier().loadThemeFromPreferences(); - // log("ThemeKey1: ${LocaleManager.instance.getStringValue(PreferencesKeys.THEME)}"); - // log("Date: ${DateTime.parse("2024-11-16T07:17:36.785Z")}"); } @override diff --git a/lib/product/extention/context_extention.dart b/lib/product/extention/context_extention.dart index 75eaaab..de531d9 100644 --- a/lib/product/extention/context_extention.dart +++ b/lib/product/extention/context_extention.dart @@ -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!; } - diff --git a/lib/product/lang/l10n/app_en.arb b/lib/product/lang/l10n/app_en.arb index 959e94a..c70ce35 100644 --- a/lib/product/lang/l10n/app_en.arb +++ b/lib/product/lang/l10n/app_en.arb @@ -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", diff --git a/lib/product/lang/l10n/app_vi.arb b/lib/product/lang/l10n/app_vi.arb index a97bcf3..ab21783 100644 --- a/lib/product/lang/l10n/app_vi.arb +++ b/lib/product/lang/l10n/app_vi.arb @@ -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", diff --git a/lib/product/shared/shared_curve.dart b/lib/product/shared/shared_curve.dart new file mode 100644 index 0000000..1cb886b --- /dev/null +++ b/lib/product/shared/shared_curve.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +class CuveEdgesCustom extends CustomClipper { + @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; + } +} diff --git a/lib/product/shared/shared_language_switch.dart b/lib/product/shared/shared_language_switch.dart new file mode 100644 index 0000000..da540b8 --- /dev/null +++ b/lib/product/shared/shared_language_switch.dart @@ -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? onChanged; + + @override + State createState() => _LanguageSwitchState(); +} + +class _LanguageSwitchState extends State { + @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, + ), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/product/shared/shared_light_dark_switch.dart b/lib/product/shared/shared_light_dark_switch.dart new file mode 100644 index 0000000..3059e03 --- /dev/null +++ b/lib/product/shared/shared_light_dark_switch.dart @@ -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? onChanged; + + @override + State createState() => _LightDarkSwitchState(); +} + +class _LightDarkSwitchState extends State { + @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, + // ), + // ), + ], + ); + } +} diff --git a/lib/product/shared/shared_line_chart.dart b/lib/product/shared/shared_line_chart.dart index 865d272..23d8a96 100644 --- a/lib/product/shared/shared_line_chart.dart +++ b/lib/product/shared/shared_line_chart.dart @@ -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 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 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), + ), + ), ), ), ); diff --git a/lib/product/shared/shared_pie_chart.dart b/lib/product/shared/shared_pie_chart.dart index 4b5c2b0..877bf70 100644 --- a/lib/product/shared/shared_pie_chart.dart +++ b/lib/product/shared/shared_pie_chart.dart @@ -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, ), ), ), diff --git a/lib/product/theme/theme_notifier.dart b/lib/product/theme/theme_notifier.dart index 0e35247..00351ef 100644 --- a/lib/product/theme/theme_notifier.dart +++ b/lib/product/theme/theme_notifier.dart @@ -82,6 +82,17 @@ class ThemeNotifier extends ChangeNotifier { AppThemes _currenThemeEnum = AppThemes.LIGHT; AppThemes get currenThemeEnum => _currenThemeEnum; + Future 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; diff --git a/lib/product/utils/device_utils.dart b/lib/product/utils/device_utils.dart index fb31386..83f7646 100644 --- a/lib/product/utils/device_utils.dart +++ b/lib/product/utils/device_utils.dart @@ -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 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; + } + } }