diff --git a/android/build.gradle b/android/build.gradle index 5107ad1..1683e4e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.8.20' + ext.kotlin_version = '1.8.22' repositories { google() mavenCentral() diff --git a/android/settings.gradle b/android/settings.gradle index 44e62bc..a42444d 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.2.1" apply false + id "org.jetbrains.kotlin.android" version "1.8.22" apply false +} + +include ":app" diff --git a/lib/bloc/devices_manager_bloc.dart b/lib/bloc/devices_manager_bloc.dart index 71ebf49..95254d0 100644 --- a/lib/bloc/devices_manager_bloc.dart +++ b/lib/bloc/devices_manager_bloc.dart @@ -1,10 +1,9 @@ import 'dart:async'; import 'dart:convert'; -import 'package:sfm_app/product/constant/app/app_constants.dart'; - import '../feature/devices/device_model.dart'; import '../product/base/bloc/base_bloc.dart'; +import '../product/constant/app/app_constants.dart'; import '../product/services/api_services.dart'; import '../product/utils/device_utils.dart'; @@ -26,11 +25,57 @@ class DevicesManagerBloc extends BlocBase { Stream>> get streamDeviceByState => deviceByState.stream; + final tagStates = StreamController>.broadcast(); + StreamSink> get sinkTagStates => tagStates.sink; + Stream> get streamTagStates => tagStates.stream; + @override void dispose() {} - void getDevice() async { - String body = await apiServices.getOwnerDevices(); + // void getDevice() async { + // String body = await apiServices.getOwnerDevices(); + // Map> deviceByState = { + // ApplicationConstants.OFFLINE_STATE: [], + // ApplicationConstants.NORMAL_STATE: [], + // ApplicationConstants.WARNING_STATE: [], + // ApplicationConstants.INPROGRESS_STATE: [], + // ApplicationConstants.ERROR_STATE: [], + // }; + // if (body.isNotEmpty) { + // final data = jsonDecode(body); + // List items = data['items']; + // List originalDevices = Device.fromJsonDynamicList(items); + // List devices = + // DeviceUtils.instance.sortDeviceByState(originalDevices); + // for (var device in devices) { + // String stateKey; + // switch (device.state) { + // case -1: + // stateKey = ApplicationConstants.OFFLINE_STATE; + // break; + // case 0: + // stateKey = ApplicationConstants.NORMAL_STATE; + // break; + // case 1: + // stateKey = ApplicationConstants.WARNING_STATE; + // break; + // case 2: + // stateKey = ApplicationConstants.INPROGRESS_STATE; + // break; + // default: + // stateKey = ApplicationConstants.ERROR_STATE; + // break; + // } + // deviceByState[stateKey]!.add(device); + // } + // sinkAllDevices.add(devices); + // sinkDeviceByState.add(deviceByState); + // } + // } + + void getDeviceByState(int state) async { + sinkTagStates.add([state]); + Map> deviceByState = { ApplicationConstants.OFFLINE_STATE: [], ApplicationConstants.NORMAL_STATE: [], @@ -39,35 +84,49 @@ class DevicesManagerBloc extends BlocBase { ApplicationConstants.ERROR_STATE: [], }; + List devices = []; + String body; + + if (state != -2) { + body = + await apiServices.getOwnerDevieByState({"state": state.toString()}); + } else { + body = await apiServices.getOwnerDevices(); + } + if (body.isNotEmpty) { final data = jsonDecode(body); List items = data['items']; List originalDevices = Device.fromJsonDynamicList(items); - List devices = - DeviceUtils.instance.sortDeviceByState(originalDevices); - for (var device in devices) { - String stateKey; - switch (device.state) { - case -1: - stateKey = ApplicationConstants.OFFLINE_STATE; - break; - case 0: - stateKey = ApplicationConstants.NORMAL_STATE; - break; - case 1: - stateKey = ApplicationConstants.WARNING_STATE; - break; - case 2: - stateKey = ApplicationConstants.INPROGRESS_STATE; - break; - default: - stateKey = ApplicationConstants.ERROR_STATE; - break; + + devices = (state != -2) + ? DeviceUtils.instance.sortDeviceAZByName(originalDevices) + : DeviceUtils.instance.sortDeviceByState(originalDevices); + + if (state == -2) { + for (var device in originalDevices) { + String stateKey = _getStateKey(device.state!); + deviceByState[stateKey]!.add(device); } - deviceByState[stateKey]!.add(device); + sinkDeviceByState.add(deviceByState); } - sinkAllDevices.add(devices); - sinkDeviceByState.add(deviceByState); + } + + sinkAllDevices.add(devices); + } + + String _getStateKey(int state) { + switch (state) { + case -1: + return ApplicationConstants.OFFLINE_STATE; + case 0: + return ApplicationConstants.NORMAL_STATE; + case 1: + return ApplicationConstants.WARNING_STATE; + case 2: + return ApplicationConstants.INPROGRESS_STATE; + default: + return ApplicationConstants.ERROR_STATE; } } } diff --git a/lib/bloc/main_bloc.dart b/lib/bloc/main_bloc.dart index a13d607..a0df4bc 100644 --- a/lib/bloc/main_bloc.dart +++ b/lib/bloc/main_bloc.dart @@ -1,19 +1,22 @@ import 'dart:async'; +import 'dart:convert'; import 'package:flutter/material.dart'; -import 'package:sfm_app/product/base/bloc/base_bloc.dart'; +import '../product/base/bloc/base_bloc.dart'; +import '../product/services/api_services.dart'; import '../feature/bell/bell_model.dart'; +import '../feature/settings/profile/profile_model.dart'; class MainBloc extends BlocBase { + APIServices apiServices = APIServices(); final bellBloc = StreamController.broadcast(); StreamSink get sinkBellBloc => bellBloc.sink; Stream get streamBellBloc => bellBloc.stream; - final language = StreamController.broadcast(); StreamSink get sinkLanguage => language.sink; Stream get streamLanguage => language.stream; - + final theme = StreamController.broadcast(); StreamSink get sinkTheme => theme.sink; Stream get streamTheme => theme.stream; @@ -26,6 +29,16 @@ class MainBloc extends BlocBase { StreamSink get sinkIsVNIcon => isVNIcon.sink; Stream get streamIsVNIcon => isVNIcon.stream; + final userProfile = StreamController.broadcast(); + StreamSink get sinkUserProfile => userProfile.sink; + Stream get streamUserProfile => userProfile.stream; + @override void dispose() {} + + void getUserProfile() async { + String data = await apiServices.getUserDetail(); + User user = User.fromJson(jsonDecode(data)); + sinkUserProfile.add(user); + } } diff --git a/lib/feature/bell/bell_screen.dart b/lib/feature/bell/bell_screen.dart index 6cb2006..ba96a43 100644 --- a/lib/feature/bell/bell_screen.dart +++ b/lib/feature/bell/bell_screen.dart @@ -92,27 +92,41 @@ class _BellScreenState extends State { }, child: Column( children: [ - ListTile( - title: Text( - getBellEvent( - context, - bellSnapshot.data![index] - .itemDetail!.sourceName!, - bellSnapshot - .data![index].eventType!, - bellSnapshot.data![index] - .itemDetail!.targetName!, - ), - overflow: TextOverflow.ellipsis, - maxLines: 3, - style: - const TextStyle(fontSize: 15), + Container( + decoration: BoxDecoration( + color: bellSnapshot + .data![index].status! == + 1 + ? Theme.of(context) + .appBarTheme + .backgroundColor + : Theme.of(context) + .colorScheme + .outlineVariant, ), - trailing: Text( - timeAgo( - context, - bellSnapshot - .data![index].createdAt!, + child: ListTile( + // style: ListTileS, + title: Text( + getBellEvent( + context, + bellSnapshot.data![index] + .itemDetail!.sourceName!, + bellSnapshot + .data![index].eventType!, + bellSnapshot.data![index] + .itemDetail!.targetName!, + ), + overflow: TextOverflow.ellipsis, + maxLines: 3, + style: + const TextStyle(fontSize: 15), + ), + trailing: Text( + timeAgo( + context, + bellSnapshot + .data![index].createdAt!, + ), ), ), ), @@ -176,26 +190,6 @@ class _BellScreenState extends State { check = false; } - bool checkStatus(List bells) { - for (var bell in bells) { - if (bell.status == 0) { - return false; - } - } - return true; - } - - Future colorByTheme(int status) async { - String theme = await apiServices.checkTheme(); - if (theme == AppThemes.LIGHT.name && status == 1) { - return Colors.white; - } else if (theme == AppThemes.DARK.name && status == 1) { - return Colors.black; - } else { - return const Color.fromARGB(255, 90, 175, 214); - } - } - String timeAgo(BuildContext context, DateTime dateTime) { final duration = DateTime.now().difference(dateTime); diff --git a/lib/feature/devices/add_new_device_widget.dart b/lib/feature/devices/add_new_device_widget.dart index 6c0f436..a5de72b 100644 --- a/lib/feature/devices/add_new_device_widget.dart +++ b/lib/feature/devices/add_new_device_widget.dart @@ -1,6 +1,7 @@ // ignore_for_file: use_build_context_synchronously import 'package:flutter/material.dart'; +import 'package:sfm_app/product/shared/shared_snack_bar.dart'; import '../../product/utils/response_status_utils.dart'; import '../../product/constant/enums/role_enums.dart'; import '../../product/services/api_services.dart'; @@ -22,8 +23,11 @@ addNewDevice(BuildContext context, String role) async { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('${appLocalization(context).add_device_title}: ', - style: context.titleMediumTextStyle), + Text( + '${appLocalization(context).add_device_title}: ', + style: + const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), Container( alignment: Alignment.centerRight, child: IconButton( @@ -57,15 +61,28 @@ addNewDevice(BuildContext context, String role) async { appLocalization(context).input_name_device_hintText), ) : const SizedBox.shrink(), + SizedBox( + height: context.lowValue, + ), Center( - child: TextButton( - onPressed: () async { - String extID = extIDController.text; - String deviceName = deviceNameController.text; + child: FilledButton.tonal( + onPressed: () async { + String extID = extIDController.text; + String deviceName = deviceNameController.text; + if (extID == "") { + showNoIconTopSnackBar( + context, + appLocalization(context).notification_enter_all_inf, + Colors.orange, + Colors.white); + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + } else { addDevices(context, role, extID, deviceName); ScaffoldMessenger.of(context).hideCurrentSnackBar(); - }, - child: Text(appLocalization(context).add_button_content)), + } + }, + child: Text(appLocalization(context).add_button_content), + ), ) ], ), diff --git a/lib/feature/devices/devices_manager_screen.dart b/lib/feature/devices/devices_manager_screen.dart index 3dee5ba..64aa869 100644 --- a/lib/feature/devices/devices_manager_screen.dart +++ b/lib/feature/devices/devices_manager_screen.dart @@ -29,17 +29,17 @@ class _DevicesManagerScreenState extends State { APIServices apiServices = APIServices(); List devices = []; Timer? getAllDevicesTimer; - + List tags = []; @override void initState() { super.initState(); devicesManagerBloc = BlocProvider.of(context); getUserRole(); - // devicesManagerBloc.getDevice(); - // getAllOwnerDevices(); - // const duration = Duration(seconds: 10); - // getAllDevicesTimer = - // Timer.periodic(duration, (Timer t) => devicesManagerBloc.getDevice()); + const duration = Duration(seconds: 10); + getAllDevicesTimer = Timer.periodic( + duration, + (Timer t) => devicesManagerBloc.getDeviceByState(-2), + ); } @override @@ -52,144 +52,179 @@ class _DevicesManagerScreenState extends State { Widget build(BuildContext context) { return Scaffold( // backgroundColor: Colors.grey.withValues(alpha: 0.6), - body: SafeArea( - child: StreamBuilder>( - stream: devicesManagerBloc.streamAllDevices, - initialData: devices, - builder: (context, allDeviceSnapshot) { - if (allDeviceSnapshot.data?.isEmpty ?? devices.isEmpty) { - devicesManagerBloc.getDevice(); - return const Center(child: CircularProgressIndicator()); - } else { - return SingleChildScrollView( - child: Column( - children: [ - StreamBuilder( - stream: devicesManagerBloc.streamUserRole, - initialData: role, - builder: (context, roleSnapshot) { - return PaginatedDataTable( - header: Center( - child: Text( - appLocalization(context) - .paginated_data_table_title, - style: context.titleLargeTextStyle, + body: StreamBuilder>( + stream: devicesManagerBloc.streamTagStates, + builder: (context, tagSnapshot) { + return SafeArea( + child: StreamBuilder>( + stream: devicesManagerBloc.streamAllDevices, + initialData: devices, + builder: (context, allDeviceSnapshot) { + if (allDeviceSnapshot.data?.isEmpty ?? devices.isEmpty) { + devicesManagerBloc + .getDeviceByState(tagSnapshot.data?[0] ?? -2); + return const Center(child: CircularProgressIndicator()); + } else { + if (tagSnapshot.data!.isNotEmpty) {} + return SingleChildScrollView( + child: Column( + children: [ + if (tagSnapshot.hasData && + tagSnapshot.data!.isNotEmpty && + tagSnapshot.data![0] != -2) + TagState( + state: tagSnapshot.data![0], + devicesManagerBloc: devicesManagerBloc, ), - ), - columns: [ - if (roleSnapshot.data == RoleEnums.ADMIN.name || - roleSnapshot.data == RoleEnums.USER.name) - DataColumn( - label: Center( - child: Text(appLocalization(context) - .paginated_data_table_column_action), - ), - ), - DataColumn( - label: Center( - child: Text(appLocalization(context) - .paginated_data_table_column_deviceName), - ), - ), - DataColumn( - label: Center( - child: Text(appLocalization(context) - .paginated_data_table_column_deviceStatus), - ), - ), - DataColumn( - label: Center( - child: Text(appLocalization(context) - .paginated_data_table_column_deviceBaterry), - ), - ), - DataColumn( - label: Center( - child: Text(appLocalization(context) - .paginated_data_table_column_deviceSignal), - ), - ), - DataColumn( - label: Center( - child: Text(appLocalization(context) - .paginated_data_table_column_deviceTemperature), - ), - ), - DataColumn( - label: Center( - child: Text(appLocalization(context) - .paginated_data_table_column_deviceHump), - ), - ), - DataColumn( - label: Center( - child: Text(appLocalization(context) - .paginated_data_table_column_devicePower), - ), - ), - ], - onPageChanged: (int pageIndex) { - // log('Chuyen page: $pageIndex'); - }, - rowsPerPage: 5, - actions: [ - if (roleSnapshot.data == RoleEnums.USER.name || - roleSnapshot.data == RoleEnums.ADMIN.name) - IconButton( - style: ButtonStyle( - backgroundColor: - WidgetStateProperty.all( - Colors.green), - iconColor: WidgetStateProperty.all( - Colors.white)), - onPressed: () { - ScaffoldMessenger.of(context) - .clearSnackBars(); - addNewDevice( - context, roleSnapshot.data ?? role); + SizedBox(height: context.lowValue), + StreamBuilder( + stream: devicesManagerBloc.streamUserRole, + initialData: role, + builder: (context, roleSnapshot) { + return CardTheme( + color: Theme.of(context).colorScheme.onPrimary, + shadowColor: + Theme.of(context).colorScheme.onPrimary, + child: PaginatedDataTable( + headingRowHeight: 30, + columnSpacing: 30, + horizontalMargin: 10, + header: Center( + child: Text( + appLocalization(context) + .paginated_data_table_title, + style: context.headlineMediumTextStyle, + ), + ), + columns: [ + if (roleSnapshot.data == + RoleEnums.ADMIN.name || + roleSnapshot.data == + RoleEnums.USER.name) + DataColumn( + label: Center( + child: Text(appLocalization(context) + .paginated_data_table_column_deviceName), + ), + ), + DataColumn( + label: Center( + child: Text(appLocalization(context) + .paginated_data_table_column_deviceStatus), + ), + ), + DataColumn( + label: Center( + child: Text(appLocalization(context) + .paginated_data_table_column_deviceBaterry), + ), + ), + DataColumn( + label: Center( + child: Text(appLocalization(context) + .paginated_data_table_column_deviceSignal), + ), + ), + DataColumn( + label: Center( + child: Text(appLocalization(context) + .paginated_data_table_column_deviceTemperature), + ), + ), + DataColumn( + label: Center( + child: Text(appLocalization(context) + .paginated_data_table_column_deviceHump), + ), + ), + DataColumn( + label: Center( + child: Text(appLocalization(context) + .paginated_data_table_column_devicePower), + ), + ), + DataColumn( + label: Center( + child: Text(appLocalization(context) + .paginated_data_table_column_action), + ), + ), + ], + onPageChanged: (int pageIndex) { + // log('Chuyen page: $pageIndex'); }, - icon: IconConstants.instance - .getMaterialIcon(Icons.add)) - ], - source: DeviceSource( - devices: allDeviceSnapshot.data ?? devices, - context: context, - devicesBloc: devicesManagerBloc, - role: role, + rowsPerPage: + (allDeviceSnapshot.data?.length ?? 0) < 6 + ? (allDeviceSnapshot.data?.length ?? + 0) + : 5, + actions: [ + if (roleSnapshot.data == + RoleEnums.USER.name || + roleSnapshot.data == + RoleEnums.ADMIN.name) + IconButton( + style: ButtonStyle( + backgroundColor: + WidgetStateProperty.all( + Colors.green), + iconColor: + WidgetStateProperty.all( + Colors.white, + ), + ), + onPressed: () { + ScaffoldMessenger.of(context) + .clearSnackBars(); + addNewDevice(context, + roleSnapshot.data ?? role); + }, + icon: IconConstants.instance + .getMaterialIcon(Icons.add)) + ], + source: DeviceSource( + devices: allDeviceSnapshot.data ?? devices, + context: context, + devicesBloc: devicesManagerBloc, + role: role, + ), + ), + ); + }, ), - ); - }, - ), - SizedBox(height: context.lowValue), - Text( - appLocalization(context).overview_message, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, + SizedBox(height: context.lowValue), + Text( + appLocalization(context).overview_message, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + StreamBuilder>>( + stream: devicesManagerBloc.streamDeviceByState, + builder: (context, devicesByStateSnapshot) { + if (devicesByStateSnapshot.data == null) { + devicesManagerBloc.getDeviceByState( + tagSnapshot.data?[0] ?? -2); + return const Center( + child: CircularProgressIndicator()); + } else { + return SharedPieChart( + deviceByState: + devicesByStateSnapshot.data ?? {}, + devicesManagerBloc: devicesManagerBloc, + ); + } + }, + ), + ], ), - ), - SizedBox(height: context.lowValue), - StreamBuilder>>( - stream: devicesManagerBloc.streamDeviceByState, - builder: (context, devicesByStateSnapshot) { - if (devicesByStateSnapshot.data == null) { - devicesManagerBloc.getDevice(); - return const Center( - child: CircularProgressIndicator()); - } else { - return SharedPieChart( - deviceByState: devicesByStateSnapshot.data ?? {}); - } - }, - ), - SizedBox(height: context.mediumValue), - ], - ), - ); - } - }, - ), - ), + ); + } + }, + ), + ); + }), ); } @@ -205,11 +240,13 @@ class DeviceSource extends DataTableSource { List devices; final DevicesManagerBloc devicesBloc; final BuildContext context; - DeviceSource( - {required this.devices, - required this.context, - required this.devicesBloc, - required this.role}); + + DeviceSource({ + required this.devices, + required this.context, + required this.devicesBloc, + required this.role, + }); @override DataRow? getRow(int index) { if (index >= devices.length) { @@ -221,88 +258,72 @@ class DeviceSource extends DataTableSource { String deviceState = DeviceUtils.instance.checkStateDevice(context, device.state!); return DataRow.byIndex( - // color: getTableRowColor(device.state!), + // color: WidgetStateProperty.all(rowColor), index: index, cells: [ if (role == RoleEnums.USER.name || role == RoleEnums.ADMIN.name) DataCell( - Center( - child: Row( - children: [ - IconButton( - // style: ButtonStyle(), - hoverColor: Colors.black, - onPressed: () { - context.pushNamed(AppRoutes.DEVICE_UPDATE.name, - pathParameters: {'thingID': device.thingId!}); - }, - icon: const Icon(Icons.build, color: Colors.blue)), - IconButton( - onPressed: () async { - handleDeleteDevice(context, device.thingId!, role); - }, - icon: const Icon(Icons.delete, color: Colors.red)), - ], - ), - ), - ), + Text(device.name!, + style: TextStyle( + color: DeviceUtils.instance + .getTableRowColor(device.state!))), onTap: () { + context.pushNamed(AppRoutes.DEVICE_DETAIL.name, + pathParameters: {'thingID': device.thingId!}); + }), DataCell( - Text(device.name!, + Text(deviceState, style: TextStyle( color: DeviceUtils.instance .getTableRowColor(device.state!))), onTap: () { - // log(device.thingId.toString()); context.pushNamed(AppRoutes.DEVICE_DETAIL.name, pathParameters: {'thingID': device.thingId!}); }), DataCell( - Center( - child: Text(deviceState, - style: TextStyle( - color: DeviceUtils.instance - .getTableRowColor(device.state!)))), onTap: () { - // log(device.thingId.toString()); - context.pushNamed(AppRoutes.DEVICE_DETAIL.name, - pathParameters: {'thingID': device.thingId!}); - }), - DataCell( - Center( - child: Center( - child: Text(sensorMap['sensorBattery'] + "%", - style: TextStyle( - color: DeviceUtils.instance - .getTableRowColor(device.state!))))), + Text(sensorMap['sensorBattery'] + "%", + style: TextStyle( + color: DeviceUtils.instance.getTableRowColor(device.state!))), onTap: () => context.pushNamed(AppRoutes.DEVICE_DETAIL.name, pathParameters: {'thingID': device.thingId!}), ), DataCell( - Center( - child: Center( - child: Text(sensorMap['sensorCsq'], - style: TextStyle( - color: DeviceUtils.instance - .getTableRowColor(device.state!))))), + Text(sensorMap['sensorCsq'], + style: TextStyle( + color: DeviceUtils.instance.getTableRowColor(device.state!))), + ), + DataCell( + Text("${sensorMap['sensorTemp']}°C", + style: TextStyle( + color: DeviceUtils.instance.getTableRowColor(device.state!))), + ), + DataCell( + Text("${sensorMap['sensorHum']}%", + style: TextStyle( + color: DeviceUtils.instance.getTableRowColor(device.state!))), + ), + DataCell( + Text("${sensorMap['sensorVolt']}V", + style: TextStyle( + color: DeviceUtils.instance.getTableRowColor(device.state!))), ), DataCell( Center( - child: Text(sensorMap['sensorTemp'], - style: TextStyle( - color: DeviceUtils.instance - .getTableRowColor(device.state!)))), - ), - DataCell( - Center( - child: Text(sensorMap['sensorHum'], - style: TextStyle( - color: DeviceUtils.instance - .getTableRowColor(device.state!)))), - ), - DataCell( - Center( - child: Text(sensorMap['sensorVolt'], - style: TextStyle( - color: DeviceUtils.instance - .getTableRowColor(device.state!)))), + child: Row( + children: [ + IconButton( + hoverColor: Colors.black, + onPressed: () { + context.pushNamed(AppRoutes.DEVICE_UPDATE.name, + pathParameters: {'thingID': device.thingId!}); + }, + icon: const Icon(Icons.build, color: Colors.blue)), + IconButton( + onPressed: () async { + handleDeleteDevice(context, device.thingId!, role); + }, + icon: const Icon(Icons.delete, color: Colors.red)), + ], + ), + ), ), ], ); @@ -317,3 +338,44 @@ class DeviceSource extends DataTableSource { @override int get selectedRowCount => 0; } + +class TagState extends StatelessWidget { + const TagState({ + super.key, + required this.state, + required this.devicesManagerBloc, + }); + + final int state; + final DevicesManagerBloc devicesManagerBloc; + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(context.lowValue), + height: context.mediumValue, + width: context.dynamicWidth(0.35), + decoration: BoxDecoration( + color: DeviceUtils.instance.getTableRowColor(state), + borderRadius: BorderRadius.circular(context.mediumValue), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + DeviceUtils.instance.checkStateDevice(context, state), + style: const TextStyle(color: Colors.white), + ), + GestureDetector( + onTap: () { + devicesManagerBloc.getDeviceByState(-2); + }, + child: const Icon( + Icons.close, + size: 20, + ), + ), + ], + ), + ); + } +} diff --git a/lib/feature/home/home_screen.dart b/lib/feature/home/home_screen.dart index f59574a..e317aad 100644 --- a/lib/feature/home/home_screen.dart +++ b/lib/feature/home/home_screen.dart @@ -47,8 +47,8 @@ class _HomeScreenState extends State { void initState() { super.initState(); homeBloc = BlocProvider.of(context); - const duration = Duration(seconds: 20); getOwnerAndJoinedDevices(); + const duration = Duration(seconds: 20); getAllDevicesTimer = Timer.periodic(duration, (Timer t) => getOwnerAndJoinedDevices()); } diff --git a/lib/feature/main/main_screen.dart b/lib/feature/main/main_screen.dart index 5eb7a39..86538aa 100644 --- a/lib/feature/main/main_screen.dart +++ b/lib/feature/main/main_screen.dart @@ -8,6 +8,8 @@ import 'package:go_router/go_router.dart'; // import 'package:persistent_bottom_nav_bar_v2/persistent-tab-view.dart'; import 'package:badges/badges.dart' as badges; import 'package:persistent_bottom_nav_bar/persistent_bottom_nav_bar.dart'; +import '../settings/profile/profile_model.dart'; +import '../../product/extention/context_extention.dart'; import '../../bloc/home_bloc.dart'; import '../../product/constant/app/app_constants.dart'; import '../../product/constant/enums/app_route_enums.dart'; @@ -75,6 +77,7 @@ class _MainScreenState extends State with WidgetsBindingObserver { WidgetsBinding.instance.addObserver(this); initialCheck(); getBellNotification(); + mainBloc.getUserProfile(); } @override @@ -176,36 +179,21 @@ class _MainScreenState extends State with WidgetsBindingObserver { builder: (context, themeModeSnapshot) { return Scaffold( appBar: AppBar( + title: StreamBuilder( + stream: mainBloc.streamUserProfile, + builder: (context, userSnapshot) { + return Row( + children: [ + IconConstants.instance.getMaterialIcon(Icons.person), + SizedBox( + width: context.lowValue, + ), + Text(userSnapshot.data?.name ?? "") + ], + ); + }), 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, - // ), IconButton( onPressed: () async { ThemeData newTheme = await ThemeServices().changeTheme( @@ -337,11 +325,9 @@ class _MainScreenState extends State with WidgetsBindingObserver { controller: controller, screens: _buildScreens(), items: _navBarsItems(), - // confineInSafeArea: true, handleAndroidBackButtonPress: true, resizeToAvoidBottomInset: true, stateManagement: true, - // hideNavigationBarWhenKeyboardShows: true, backgroundColor: themeModeSnapshot.data! ? Colors.white : Colors.black, decoration: NavBarDecoration( @@ -349,17 +335,18 @@ class _MainScreenState extends State with WidgetsBindingObserver { colorBehindNavBar: themeModeSnapshot.data! ? Colors.white : Colors.black, ), - // 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, + animationSettings: const NavBarAnimationSettings( + navBarItemAnimation: ItemAnimationSettings( + duration: Duration(milliseconds: 200), + curve: Curves.bounceInOut, + ), + screenTransitionAnimation: ScreenTransitionAnimationSettings( + animateTabTransition: true, + curve: Curves.bounceInOut, + duration: Duration(milliseconds: 200), + ), + ), + navBarStyle: NavBarStyle.style13, ), ); }, diff --git a/lib/product/extention/context_extention.dart b/lib/product/extention/context_extention.dart index de531d9..aabe49f 100644 --- a/lib/product/extention/context_extention.dart +++ b/lib/product/extention/context_extention.dart @@ -71,7 +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 dynamicMilliSecondDuration(int milliseconds) => Duration(milliseconds: milliseconds); + Duration dynamicMilliSecondDuration(int milliseconds) => + Duration(milliseconds: milliseconds); Duration dynamicMinutesDuration(int minutes) => Duration(minutes: minutes); } diff --git a/lib/product/network/network_manager.dart b/lib/product/network/network_manager.dart index afbfd8f..79046a0 100644 --- a/lib/product/network/network_manager.dart +++ b/lib/product/network/network_manager.dart @@ -36,7 +36,7 @@ class NetworkManager { /// string if the request fails Future getDataFromServer(String path) async { final url = Uri.https(ApplicationConstants.DOMAIN, path); - log("GET url: $url"); + log("[${DateTime.now().toLocal().toString().split(' ')[1]}] GET url: $url"); final headers = await getHeaders(); final response = await http.get(url, headers: headers); if (response.statusCode == StatusCodeConstants.OK || @@ -61,7 +61,7 @@ class NetworkManager { Future getDataFromServerWithParams( String path, Map params) async { final url = Uri.https(ApplicationConstants.DOMAIN, path, params); - log("GET Params url: $url"); + log("[${DateTime.now().toLocal().toString().split(' ')[1]}] GET Params url: $url"); final headers = await getHeaders(); final response = await http.get(url, headers: headers); if (response.statusCode == StatusCodeConstants.CREATED || @@ -78,7 +78,7 @@ class NetworkManager { /// to be sent. Returns the HTTP status code of the response. Future createDataInServer(String path, Map body) async { final url = Uri.https(ApplicationConstants.DOMAIN, path); - log("POST url: $url"); + log("[${DateTime.now().toLocal().toString().split(' ')[1]}] POST url: $url"); final headers = await getHeaders(); final response = await http.post(url, headers: headers, body: jsonEncode(body)); @@ -91,7 +91,7 @@ class NetworkManager { /// to be updated. Returns the HTTP status code of the response. Future updateDataInServer(String path, Map body) async { final url = Uri.https(ApplicationConstants.DOMAIN, path); - log("PUT url: $url"); + log("[${DateTime.now().toLocal().toString().split(' ')[1]}] PUT url: $url"); final headers = await getHeaders(); final response = await http.put(url, headers: headers, body: jsonEncode(body)); @@ -106,7 +106,7 @@ class NetworkManager { /// failure or an error. Future deleteDataInServer(String path) async { final url = Uri.https(ApplicationConstants.DOMAIN, path); - log("DELETE url: $url"); + log("[${DateTime.now().toLocal().toString().split(' ')[1]}] DELETE url: $url"); final headers = await getHeaders(); final response = await http.delete(url, headers: headers); return response.statusCode; diff --git a/lib/product/services/api_services.dart b/lib/product/services/api_services.dart index 0d6c3be..e292d71 100644 --- a/lib/product/services/api_services.dart +++ b/lib/product/services/api_services.dart @@ -271,6 +271,12 @@ class APIServices { return data; } + Future getOwnerDevieByState(Map params) async { + String? data = await NetworkManager.instance! + .getDataFromServerWithParams(APIPathConstants.DEVICE_PATH, params); + return data; + } + Future createDeviceByAdmin(Map body) async { int? statusCode = await NetworkManager.instance! .createDataInServer(APIPathConstants.DEVICE_PATH, body); diff --git a/lib/product/shared/shared_pie_chart.dart b/lib/product/shared/shared_pie_chart.dart index 877bf70..d0184d6 100644 --- a/lib/product/shared/shared_pie_chart.dart +++ b/lib/product/shared/shared_pie_chart.dart @@ -1,5 +1,8 @@ +import 'dart:developer'; + import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; +import 'package:sfm_app/bloc/devices_manager_bloc.dart'; import 'package:sfm_app/feature/devices/device_model.dart'; import 'package:sfm_app/product/extention/context_extention.dart'; import 'package:sfm_app/product/services/language_services.dart'; @@ -7,15 +10,17 @@ import 'package:sfm_app/product/services/language_services.dart'; import '../constant/app/app_constants.dart'; class SharedPieChart extends StatelessWidget { - const SharedPieChart({ - super.key, - required this.deviceByState, - }); + const SharedPieChart( + {super.key, + required this.deviceByState, + required this.devicesManagerBloc}); final Map> deviceByState; + final DevicesManagerBloc devicesManagerBloc; @override Widget build(BuildContext context) { + int touchedIndex = -1; TextStyle titleStyle = const TextStyle( color: Colors.white, fontSize: 20, @@ -31,56 +36,98 @@ class SharedPieChart extends StatelessWidget { deviceByState[ApplicationConstants.INPROGRESS_STATE]?.length ?? 0; int errorCount = deviceByState[ApplicationConstants.ERROR_STATE]?.length ?? 0; - return Padding( - padding: context.paddingLowHorizontal, + return AspectRatio( + aspectRatio: 1.5, child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Container( - width: context.dynamicWidth(0.5), - height: context.dynamicHeight(0.3), - padding: const EdgeInsets.all(10), - child: PieChart( - PieChartData( - sections: [ - PieChartSectionData( - color: Colors.grey, - value: offlineCount.toDouble(), - title: offlineCount.toString(), - radius: context.dynamicWidth(0.2), - titleStyle: titleStyle, + children: [ + const SizedBox( + height: 18, + ), + Expanded( + child: AspectRatio( + aspectRatio: 1, + child: PieChart( + PieChartData( + pieTouchData: PieTouchData( + touchCallback: (FlTouchEvent event, pieTouchResponse) { + if (!event.isInterestedForInteractions || + pieTouchResponse == null || + pieTouchResponse.touchedSection == null) { + touchedIndex = -1; + return; + } + int newTouchedIndex = + pieTouchResponse.touchedSection!.touchedSectionIndex; + + // Chỉ gọi updateDevicesOnTapPieChart nếu touchedIndex thay đổi + if (newTouchedIndex != touchedIndex) { + touchedIndex = newTouchedIndex; + log("TouchedIndex: $touchedIndex"); + log("Event: ${event.isInterestedForInteractions}"); + + Future.delayed( + context.lowDuration, + () => updateDevicesOnTapPieChart(newTouchedIndex), + ); + } + // touchedIndex = + // pieTouchResponse.touchedSection!.touchedSectionIndex; + // // log("TouchedIndex: $touchedIndex"); + // // log("Event: ${event.isInterestedForInteractions}"); + // // int currentTouchedIndex = touchedIndex; + + // if (currentTouchedIndex != touchedIndex) { + // touchedIndex = currentTouchedIndex; + // log("TouchedIndex: $touchedIndex"); + // log("Event: ${event.isInterestedForInteractions}"); + + // Future.delayed( + // context.lowDuration, + // () => updateDevicesOnTapPieChart(touchedIndex), + // ); + // } + }, ), - PieChartSectionData( - color: Colors.green, - value: normalCount.toDouble(), - title: normalCount.toString(), - radius: context.dynamicWidth(0.2), - titleStyle: titleStyle, - ), - PieChartSectionData( - color: Colors.red, - value: warningCount.toDouble(), - title: warningCount.toString(), - radius: context.dynamicWidth(0.2), - titleStyle: titleStyle, - ), - PieChartSectionData( - color: Colors.yellow, - value: inProgressCount.toDouble(), - title: inProgressCount.toString(), - 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.2), - titleStyle: titleStyle, - ), - ], - centerSpaceRadius: context.dynamicWidth(0.1), - sectionsSpace: 2, + sections: [ + PieChartSectionData( + color: Colors.grey, + value: offlineCount.toDouble(), + title: offlineCount.toString(), + radius: context.dynamicWidth(0.2), + titleStyle: titleStyle, + ), + PieChartSectionData( + color: Colors.green, + value: normalCount.toDouble(), + title: normalCount.toString(), + radius: context.dynamicWidth(0.2), + titleStyle: titleStyle, + ), + PieChartSectionData( + color: Colors.red, + value: warningCount.toDouble(), + title: warningCount.toString(), + radius: context.dynamicWidth(0.2), + titleStyle: titleStyle, + ), + PieChartSectionData( + color: Colors.yellow, + value: inProgressCount.toDouble(), + title: inProgressCount.toString(), + 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.2), + titleStyle: titleStyle, + ), + ], + centerSpaceRadius: context.dynamicWidth(0.05), + sectionsSpace: 2, + ), ), ), ), @@ -88,52 +135,88 @@ class SharedPieChart extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Indicator( - color: Colors.grey, - text: appLocalization(context).inactive_devices_message, - isSquare: true, - ), - SizedBox( - height: context.lowValue, - ), - Indicator( - color: Colors.green, - text: appLocalization(context).active_devices_message, - isSquare: true, - ), - SizedBox( - height: context.lowValue, - ), - Indicator( - color: Colors.red, - text: appLocalization(context).warning_devices_message, - isSquare: true, - ), - SizedBox( - height: context.lowValue, - ), - Indicator( - color: Colors.yellow, - text: appLocalization(context).in_progress_message, - isSquare: true, - ), - SizedBox( - height: context.lowValue, - ), - Indicator( - color: Colors.black, - text: appLocalization(context).error_message_uppercase, - isSquare: true, - ), - // const SizedBox( - // height: 18, - // ), + if (offlineCount != 0) + Column( + children: [ + Indicator( + color: Colors.grey, + text: appLocalization(context).inactive_devices_message, + isSquare: true, + ), + SizedBox( + height: context.lowValue, + ), + ], + ), + if (normalCount != 0) + Column( + children: [ + Indicator( + color: Colors.green, + text: appLocalization(context).active_devices_message, + isSquare: true, + ), + SizedBox( + height: context.lowValue, + ), + ], + ), + if (warningCount != 0) + Column( + children: [ + Indicator( + color: Colors.red, + text: appLocalization(context).warning_devices_message, + isSquare: true, + ), + SizedBox( + height: context.lowValue, + ), + ], + ), + if (inProgressCount != 0) + Column( + children: [ + Indicator( + color: Colors.yellow, + text: appLocalization(context).in_progress_message, + isSquare: true, + ), + SizedBox( + height: context.lowValue, + ), + ], + ), + if (errorCount != 0) + Indicator( + color: Colors.black, + text: appLocalization(context).error_message_uppercase, + isSquare: true, + ), ], ), + const SizedBox( + width: 10, + ), ], ), ); } + + void updateDevicesOnTapPieChart(int touchedIndex) { + log("Update PieChart On Tap Function"); + if (touchedIndex == 0) { + devicesManagerBloc.getDeviceByState(-1); + } else if (touchedIndex == 1) { + devicesManagerBloc.getDeviceByState(0); + } else if (touchedIndex == 2) { + devicesManagerBloc.getDeviceByState(1); + } else if (touchedIndex == 3) { + devicesManagerBloc.getDeviceByState(2); + } else { + devicesManagerBloc.getDeviceByState(-2); + } + } } class Indicator extends StatelessWidget { diff --git a/lib/product/theme/app_theme_dark.dart b/lib/product/theme/app_theme_dark.dart index 2a55f08..d01bb4c 100644 --- a/lib/product/theme/app_theme_dark.dart +++ b/lib/product/theme/app_theme_dark.dart @@ -16,6 +16,7 @@ class AppThemeDark extends AppTheme { useMaterial3: true, scheme: FlexScheme.flutterDash, subThemesData: const FlexSubThemesData( + snackBarBackgroundSchemeColor: SchemeColor.black, inputDecoratorRadius: 30, interactionEffects: true, tintedDisabledControls: true, @@ -30,24 +31,4 @@ class AppThemeDark extends AppTheme { ), visualDensity: FlexColorScheme.comfortablePlatformDensity, ); - - // ThemeData.dark().copyWith( - // useMaterial3: true, - // colorScheme: _buildColorScheme, - // ); - - // ColorScheme get _buildColorScheme => FlexColorScheme.dark().toScheme; - // ColorScheme( - // brightness: Brightness.dark, - // primary: Colors.blue.shade900, - // onPrimary: Colors.blue, - // secondary: Colors.white, - // onSecondary: Colors.white70, - // error: Colors.red, - // onError: Colors.orange, - // background: Colors.black, - // onBackground: Colors.white70, - // surface: Colors.grey, - // onSurface: Colors.white, - // ); } diff --git a/lib/product/theme/app_theme_light.dart b/lib/product/theme/app_theme_light.dart index 92e5675..926a48d 100644 --- a/lib/product/theme/app_theme_light.dart +++ b/lib/product/theme/app_theme_light.dart @@ -17,6 +17,7 @@ class AppThemeLight extends AppTheme { scheme: FlexScheme.flutterDash, bottomAppBarElevation: 20.0, subThemesData: const FlexSubThemesData( + snackBarBackgroundSchemeColor: SchemeColor.surfaceBright, inputDecoratorRadius: 30, interactionEffects: true, tintedDisabledControls: true, diff --git a/lib/product/utils/device_utils.dart b/lib/product/utils/device_utils.dart index 83f7646..342e20f 100644 --- a/lib/product/utils/device_utils.dart +++ b/lib/product/utils/device_utils.dart @@ -129,7 +129,7 @@ class DeviceUtils { message = appLocalization(context).in_progress_message; } else if (state == 3) { message = appLocalization(context).in_progress_message; - } + } else if (state == -2) {} return message; } @@ -144,6 +144,10 @@ class DeviceUtils { return sortedDevices; } + List sortDeviceAZByName(List devices) { + return devices..sort((a, b) => (a.name ?? '').compareTo(b.name ?? '')); + } + Color getTableRowColor(int state) { if (state == 1) { return Colors.red; @@ -251,12 +255,14 @@ class DeviceUtils { } IconData getSignalIcon(BuildContext context, String signal) { - if (signal == appLocalization(context).gf_weak_signal_message) { + if (signal == appLocalization(context).low_message_uppercase) { return Icons.signal_cellular_alt_1_bar; - } else if (signal == appLocalization(context).gf_moderate_signal_message) { + } else if (signal == appLocalization(context).moderate_message_uppercase) { return Icons.signal_cellular_alt_2_bar; - } else { + } else if (signal == appLocalization(context).good_message_uppercase) { return Icons.signal_cellular_alt; + } else { + return Icons.signal_cellular_connected_no_internet_4_bar_rounded; } } @@ -312,12 +318,12 @@ class DeviceUtils { } 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 { + if (signal == appLocalization(context).good_message_uppercase) { return Colors.green; + } else if (signal == appLocalization(context).moderate_message_uppercase) { + return Colors.orangeAccent; + } else { + return Colors.red; } } }