Revert "formatted all files."

This reverts commit 04250ebc98.
This commit is contained in:
Faris Armoush
2025-06-12 16:04:49 +03:00
parent 218f43bacb
commit c642ba2644
473 changed files with 4335 additions and 5417 deletions

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class DialogDropdown extends StatefulWidget { class DialogDropdown extends StatefulWidget {
final List<String> items; final List<String> items;
@ -8,14 +7,14 @@ class DialogDropdown extends StatefulWidget {
final String? selectedValue; final String? selectedValue;
const DialogDropdown({ const DialogDropdown({
super.key, Key? key,
required this.items, required this.items,
required this.onSelected, required this.onSelected,
this.selectedValue, this.selectedValue,
}); }) : super(key: key);
@override @override
State<DialogDropdown> createState() => _DialogDropdownState(); _DialogDropdownState createState() => _DialogDropdownState();
} }
class _DialogDropdownState extends State<DialogDropdown> { class _DialogDropdownState extends State<DialogDropdown> {
@ -47,14 +46,16 @@ class _DialogDropdownState extends State<DialogDropdown> {
} }
OverlayEntry _createOverlayEntry() { OverlayEntry _createOverlayEntry() {
final renderBox = context.findRenderObject()! as RenderBox; final renderBox = context.findRenderObject() as RenderBox;
final size = renderBox.size; final size = renderBox.size;
final offset = renderBox.localToGlobal(Offset.zero); final offset = renderBox.localToGlobal(Offset.zero);
return OverlayEntry( return OverlayEntry(
builder: (context) { builder: (context) {
return GestureDetector( return GestureDetector(
onTap: _closeDropdown, onTap: () {
_closeDropdown();
},
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
child: Stack( child: Stack(
children: [ children: [
@ -86,9 +87,12 @@ class _DialogDropdownState extends State<DialogDropdown> {
child: ListTile( child: ListTile(
title: Text( title: Text(
item, item,
style: context.textTheme.bodyMedium?.copyWith( style: Theme.of(context)
color: ColorsManager.textPrimaryColor, .textTheme
), .bodyMedium
?.copyWith(
color: ColorsManager.textPrimaryColor,
),
), ),
onTap: () { onTap: () {
widget.onSelected(item); widget.onSelected(item);

View File

@ -10,25 +10,24 @@ class EditChip extends StatelessWidget {
final double borderRadius; final double borderRadius;
const EditChip({ const EditChip({
super.key, Key? key,
this.label = 'Edit', this.label = 'Edit',
required this.onTap, required this.onTap,
this.labelColor = ColorsManager.spaceColor, this.labelColor = ColorsManager.spaceColor,
this.backgroundColor = ColorsManager.whiteColors, this.backgroundColor = ColorsManager.whiteColors,
this.borderColor = ColorsManager.spaceColor, this.borderColor = ColorsManager.spaceColor,
this.borderRadius = 16.0, this.borderRadius = 16.0,
}); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
child: Chip( child: Chip(
label: Text(label, label: Text(
style: Theme.of(context) label,
.textTheme style: Theme.of(context).textTheme.bodySmall!.copyWith(color: labelColor)
.bodySmall! ),
.copyWith(color: labelColor)),
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(borderRadius), borderRadius: BorderRadius.circular(borderRadius),

View File

@ -9,12 +9,12 @@ class TagDialogTextfieldDropdown extends StatefulWidget {
final String product; final String product;
const TagDialogTextfieldDropdown({ const TagDialogTextfieldDropdown({
super.key, Key? key,
required this.items, required this.items,
required this.onSelected, required this.onSelected,
this.initialValue, this.initialValue,
required this.product, required this.product,
}); }) : super(key: key);
@override @override
_DialogTextfieldDropdownState createState() => _DialogTextfieldDropdownState createState() =>
@ -79,7 +79,7 @@ class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
} }
OverlayEntry _createOverlayEntry() { OverlayEntry _createOverlayEntry() {
final renderBox = context.findRenderObject()! as RenderBox; final renderBox = context.findRenderObject() as RenderBox;
final size = renderBox.size; final size = renderBox.size;
final offset = renderBox.localToGlobal(Offset.zero); final offset = renderBox.localToGlobal(Offset.zero);

View File

@ -10,8 +10,7 @@ class CustomExpansionTile extends StatefulWidget {
final ValueChanged<bool>? onExpansionChanged; // Notify when expansion changes final ValueChanged<bool>? onExpansionChanged; // Notify when expansion changes
final VoidCallback? onItemSelected; // Callback for selecting the item final VoidCallback? onItemSelected; // Callback for selecting the item
const CustomExpansionTile({ CustomExpansionTile({
super.key,
required this.title, required this.title,
this.children, this.children,
this.initiallyExpanded = false, this.initiallyExpanded = false,

View File

@ -7,7 +7,7 @@ class CustomSearchBar extends StatefulWidget {
final TextEditingController? controller; final TextEditingController? controller;
final String hintText; final String hintText;
final String? searchQuery; final String? searchQuery;
final void Function(String)? onSearchChanged; final Function(String)? onSearchChanged; // Callback for search input changes
const CustomSearchBar({ const CustomSearchBar({
super.key, super.key,
@ -37,7 +37,7 @@ class _CustomSearchBarState extends State<CustomSearchBar> {
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.2), color: Colors.black.withOpacity(0.2),
spreadRadius: 0, spreadRadius: 0,
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 4), offset: const Offset(0, 4),
@ -57,7 +57,7 @@ class _CustomSearchBarState extends State<CustomSearchBar> {
style: const TextStyle( style: const TextStyle(
color: Colors.black, color: Colors.black,
), ),
onChanged: widget.onSearchChanged, onChanged: widget.onSearchChanged, // Call the callback on text change
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
fillColor: ColorsManager.textFieldGreyColor, fillColor: ColorsManager.textFieldGreyColor,

View File

@ -1,8 +1,7 @@
// File generated by FlutterFire CLI. // File generated by FlutterFire CLI.
// ignore_for_file: type=lint // ignore_for_file: type=lint
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart' import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform;
show defaultTargetPlatform, kIsWeb, TargetPlatform;
/// Default [FirebaseOptions] for use with your Firebase apps. /// Default [FirebaseOptions] for use with your Firebase apps.
/// ///

View File

@ -40,7 +40,7 @@ class MyApp extends StatelessWidget {
initialLocation: RoutesConst.auth, initialLocation: RoutesConst.auth,
routes: AppRoutes.getRoutes(), routes: AppRoutes.getRoutes(),
redirect: (context, state) async { redirect: (context, state) async {
final checkToken = await AuthBloc.getTokenAndValidate(); String checkToken = await AuthBloc.getTokenAndValidate();
final loggedIn = checkToken == 'Success'; final loggedIn = checkToken == 'Success';
final goingToLogin = state.uri.toString() == RoutesConst.auth; final goingToLogin = state.uri.toString() == RoutesConst.auth;

View File

@ -21,8 +21,7 @@ import 'package:syncrow_web/utils/theme/theme.dart';
Future<void> main() async { Future<void> main() async {
try { try {
const environment = const environment = String.fromEnvironment('FLAVOR', defaultValue: 'development');
String.fromEnvironment('FLAVOR', defaultValue: 'development');
await dotenv.load(fileName: '.env.$environment'); await dotenv.load(fileName: '.env.$environment');
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp( await Firebase.initializeApp(
@ -40,7 +39,7 @@ class MyApp extends StatelessWidget {
initialLocation: RoutesConst.auth, initialLocation: RoutesConst.auth,
routes: AppRoutes.getRoutes(), routes: AppRoutes.getRoutes(),
redirect: (context, state) async { redirect: (context, state) async {
final checkToken = await AuthBloc.getTokenAndValidate(); String checkToken = await AuthBloc.getTokenAndValidate();
final loggedIn = checkToken == 'Success'; final loggedIn = checkToken == 'Success';
final goingToLogin = state.uri.toString() == RoutesConst.auth; final goingToLogin = state.uri.toString() == RoutesConst.auth;
@ -58,8 +57,7 @@ class MyApp extends StatelessWidget {
BlocProvider<CreateRoutineBloc>( BlocProvider<CreateRoutineBloc>(
create: (context) => CreateRoutineBloc(), create: (context) => CreateRoutineBloc(),
), ),
BlocProvider( BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider<VisitorPasswordBloc>( BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(), create: (context) => VisitorPasswordBloc(),
), ),

View File

@ -21,8 +21,7 @@ import 'package:syncrow_web/utils/theme/theme.dart';
Future<void> main() async { Future<void> main() async {
try { try {
const environment = const environment = String.fromEnvironment('FLAVOR', defaultValue: 'staging');
String.fromEnvironment('FLAVOR', defaultValue: 'staging');
await dotenv.load(fileName: '.env.$environment'); await dotenv.load(fileName: '.env.$environment');
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp( await Firebase.initializeApp(
@ -40,7 +39,7 @@ class MyApp extends StatelessWidget {
initialLocation: RoutesConst.auth, initialLocation: RoutesConst.auth,
routes: AppRoutes.getRoutes(), routes: AppRoutes.getRoutes(),
redirect: (context, state) async { redirect: (context, state) async {
final checkToken = await AuthBloc.getTokenAndValidate(); String checkToken = await AuthBloc.getTokenAndValidate();
final loggedIn = checkToken == 'Success'; final loggedIn = checkToken == 'Success';
final goingToLogin = state.uri.toString() == RoutesConst.auth; final goingToLogin = state.uri.toString() == RoutesConst.auth;
@ -58,8 +57,7 @@ class MyApp extends StatelessWidget {
BlocProvider<CreateRoutineBloc>( BlocProvider<CreateRoutineBloc>(
create: (context) => CreateRoutineBloc(), create: (context) => CreateRoutineBloc(),
), ),
BlocProvider( BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider<VisitorPasswordBloc>( BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(), create: (context) => VisitorPasswordBloc(),
), ),

View File

@ -11,7 +11,7 @@ import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/snack_bar.dart'; import 'package:syncrow_web/utils/snack_bar.dart';
class AccessBloc extends Bloc<AccessEvent, AccessState> { class AccessBloc extends Bloc<AccessEvent, AccessState> {
AccessBloc() : super(AccessInitial()) { AccessBloc() : super((AccessInitial())) {
on<FetchTableData>(_onFetchTableData); on<FetchTableData>(_onFetchTableData);
on<SelectTime>(selectTime); on<SelectTime>(selectTime);
on<FilterDataEvent>(_filterData); on<FilterDataEvent>(_filterData);
@ -43,12 +43,12 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
} }
void updateTabsCount() { void updateTabsCount() {
final toBeEffectiveCount = data int toBeEffectiveCount = data
.where((item) => item.passwordStatus.value == 'To be effective') .where((item) => item.passwordStatus.value == 'To be effective')
.length; .length;
final effectiveCount = int effectiveCount =
data.where((item) => item.passwordStatus.value == 'Effective').length; data.where((item) => item.passwordStatus.value == 'Effective').length;
final expiredCount = int expiredCount =
data.where((item) => item.passwordStatus.value == 'Expired').length; data.where((item) => item.passwordStatus.value == 'Expired').length;
tabs[1] = 'To Be Effective ($toBeEffectiveCount)'; tabs[1] = 'To Be Effective ($toBeEffectiveCount)';
tabs[2] = 'Effective ($effectiveCount)'; tabs[2] = 'Effective ($effectiveCount)';
@ -81,7 +81,7 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
Emitter<AccessState> emit, Emitter<AccessState> emit,
) async { ) async {
emit(AccessLoaded()); emit(AccessLoaded());
final picked = await showDatePicker( final DateTime? picked = await showDatePicker(
context: event.context, context: event.context,
initialDate: DateTime.now(), initialDate: DateTime.now(),
firstDate: DateTime.now().add(const Duration(days: -5095)), firstDate: DateTime.now().add(const Duration(days: -5095)),
@ -89,7 +89,7 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
builder: (BuildContext context, Widget? child) { builder: (BuildContext context, Widget? child) {
return Theme( return Theme(
data: ThemeData.light().copyWith( data: ThemeData.light().copyWith(
colorScheme: const ColorScheme.light( colorScheme: ColorScheme.light(
primary: ColorsManager.blackColor, primary: ColorsManager.blackColor,
onPrimary: Colors.white, onPrimary: Colors.white,
onSurface: ColorsManager.grayColor, onSurface: ColorsManager.grayColor,
@ -105,20 +105,20 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
}, },
); );
if (picked != null) { if (picked != null) {
final timePicked = await showHourPicker( final TimeOfDay? timePicked = await showHourPicker(
context: event.context, context: event.context,
initialTime: TimeOfDay.now(), initialTime: TimeOfDay.now(),
); );
if (timePicked != null) { if (timePicked != null) {
final selectedDateTime = DateTime( final DateTime selectedDateTime = DateTime(
picked.year, picked.year,
picked.month, picked.month,
picked.day, picked.day,
timePicked.hour, timePicked.hour,
timePicked.minute, timePicked.minute,
); );
final selectedTimestamp = final int selectedTimestamp =
selectedDateTime.millisecondsSinceEpoch ~/ 1000; selectedDateTime.millisecondsSinceEpoch ~/ 1000;
if (event.isStart) { if (event.isStart) {
if (expirationTimeTimeStamp != null && if (expirationTimeTimeStamp != null &&
@ -152,35 +152,39 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
final searchText = event.passwordName?.toLowerCase() ?? ''; final searchText = event.passwordName?.toLowerCase() ?? '';
final searchEmailText = event.emailAuthorizer?.toLowerCase() ?? ''; final searchEmailText = event.emailAuthorizer?.toLowerCase() ?? '';
filteredData = data.where((item) { filteredData = data.where((item) {
var matchesCriteria = true; bool matchesCriteria = true;
// Convert timestamp to DateTime and extract date component // Convert timestamp to DateTime and extract date component
final effectiveDate = DateTime.fromMillisecondsSinceEpoch( DateTime effectiveDate = DateTime.fromMillisecondsSinceEpoch(
int.parse(item.effectiveTime.toString()) * 1000) int.parse(item.effectiveTime.toString()) * 1000)
.toUtc() .toUtc()
.toLocal(); .toLocal();
final invalidDate = DateTime.fromMillisecondsSinceEpoch( DateTime invalidDate = DateTime.fromMillisecondsSinceEpoch(
int.parse(item.invalidTime.toString()) * 1000) int.parse(item.invalidTime.toString()) * 1000)
.toUtc() .toUtc()
.toLocal(); .toLocal();
final effectiveDateAndTime = DateTime( DateTime effectiveDateAndTime = DateTime(
effectiveDate.year, effectiveDate.year,
effectiveDate.month, effectiveDate.month,
effectiveDate.day, effectiveDate.day,
effectiveDate.hour, effectiveDate.hour,
effectiveDate.minute); effectiveDate.minute);
final invalidDateAndTime = DateTime(invalidDate.year, invalidDate.month, DateTime invalidDateAndTime = DateTime(
invalidDate.day, invalidDate.hour, invalidDate.minute); invalidDate.year,
invalidDate.month,
invalidDate.day,
invalidDate.hour,
invalidDate.minute);
// Filter by password name, making the search case-insensitive // Filter by password name, making the search case-insensitive
if (searchText.isNotEmpty) { if (searchText.isNotEmpty) {
final matchesName = final bool matchesName =
item.passwordName.toString().toLowerCase().contains(searchText); item.passwordName.toString().toLowerCase().contains(searchText);
if (!matchesName) { if (!matchesName) {
matchesCriteria = false; matchesCriteria = false;
} }
} }
if (searchEmailText.isNotEmpty) { if (searchEmailText.isNotEmpty) {
final matchesName = item.authorizerEmail final bool matchesName = item.authorizerEmail
.toString() .toString()
.toLowerCase() .toLowerCase()
.contains(searchEmailText); .contains(searchEmailText);
@ -190,7 +194,7 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
} }
// Filter by start date only // Filter by start date only
if (event.startTime != null && event.endTime == null) { if (event.startTime != null && event.endTime == null) {
var startDateTime = DateTime startDateTime =
DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000) DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000)
.toUtc() .toUtc()
.toLocal(); .toLocal();
@ -202,7 +206,7 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
} }
// Filter by end date only // Filter by end date only
if (event.endTime != null && event.startTime == null) { if (event.endTime != null && event.startTime == null) {
var startDateTime = DateTime startDateTime =
DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000) DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000)
.toUtc() .toUtc()
.toLocal(); .toLocal();
@ -215,11 +219,11 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
// Filter by both start date and end date // Filter by both start date and end date
if (event.startTime != null && event.endTime != null) { if (event.startTime != null && event.endTime != null) {
var startDateTime = DateTime startDateTime =
DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000) DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000)
.toUtc() .toUtc()
.toLocal(); .toLocal();
var endDateTime = DateTime endDateTime =
DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000) DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000)
.toUtc() .toUtc()
.toLocal(); .toLocal();
@ -254,7 +258,7 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
} }
} }
Future<void> resetSearch(ResetSearch event, Emitter<AccessState> emit) async { resetSearch(ResetSearch event, Emitter<AccessState> emit) async {
emit(AccessLoaded()); emit(AccessLoaded());
startTime = 'Start Time'; startTime = 'Start Time';
endTime = 'End Time'; endTime = 'End Time';
@ -268,7 +272,7 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
} }
String timestampToDate(dynamic timestamp) { String timestampToDate(dynamic timestamp) {
final dateTime = DateTime dateTime =
DateTime.fromMillisecondsSinceEpoch(int.parse(timestamp) * 1000); DateTime.fromMillisecondsSinceEpoch(int.parse(timestamp) * 1000);
return "${dateTime.year}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.day.toString().padLeft(2, '0')} " return "${dateTime.year}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.day.toString().padLeft(2, '0')} "
" ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}"; " ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}";
@ -285,17 +289,17 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
break; break;
case 1: // To Be Effective case 1: // To Be Effective
filteredData = data filteredData = data
.where((item) => item.passwordStatus.value == 'To Be Effective') .where((item) => item.passwordStatus.value == "To Be Effective")
.toList(); .toList();
break; break;
case 2: // Effective case 2: // Effective
filteredData = data filteredData = data
.where((item) => item.passwordStatus.value == 'Effective') .where((item) => item.passwordStatus.value == "Effective")
.toList(); .toList();
break; break;
case 3: // Expired case 3: // Expired
filteredData = data filteredData = data
.where((item) => item.passwordStatus.value == 'Expired') .where((item) => item.passwordStatus.value == "Expired")
.toList(); .toList();
break; break;
default: default:

View File

@ -15,7 +15,7 @@ class AccessLoaded extends AccessState {}
class FailedState extends AccessState { class FailedState extends AccessState {
final String message; final String message;
const FailedState(this.message); FailedState(this.message);
@override @override
List<Object> get props => [message]; List<Object> get props => [message];

View File

@ -36,7 +36,7 @@ class PasswordModel {
effectiveTime: json['effectiveTime'], effectiveTime: json['effectiveTime'],
passwordCreated: json['passwordCreated'], passwordCreated: json['passwordCreated'],
createdTime: json['createdTime'], createdTime: json['createdTime'],
passwordName: json['passwordName'] ?? 'No Name', passwordName: json['passwordName']??'No Name',
passwordStatus: AccessStatusExtension.fromString(json['passwordStatus']), passwordStatus: AccessStatusExtension.fromString(json['passwordStatus']),
passwordType: AccessTypeExtension.fromString(json['passwordType']), passwordType: AccessTypeExtension.fromString(json['passwordType']),
deviceUuid: json['deviceUuid'], deviceUuid: json['deviceUuid'],

View File

@ -1,3 +1,5 @@
import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class DashedBorderPainter extends CustomPainter { class DashedBorderPainter extends CustomPainter {
@ -18,28 +20,27 @@ class DashedBorderPainter extends CustomPainter {
..strokeWidth = 0.5 ..strokeWidth = 0.5
..style = PaintingStyle.stroke; ..style = PaintingStyle.stroke;
final topPath = Path() final Path topPath = Path()
..moveTo(0, 0) ..moveTo(0, 0)
..lineTo(size.width, 0); ..lineTo(size.width, 0);
final bottomPath = Path() final Path bottomPath = Path()
..moveTo(0, size.height) ..moveTo(0, size.height)
..lineTo(size.width, size.height); ..lineTo(size.width, size.height);
final dashedTopPath = _createDashedPath(topPath, dashWidth, dashSpace); final dashedTopPath = _createDashedPath(topPath, dashWidth, dashSpace);
final dashedBottomPath = final dashedBottomPath = _createDashedPath(bottomPath, dashWidth, dashSpace);
_createDashedPath(bottomPath, dashWidth, dashSpace);
canvas.drawPath(dashedTopPath, paint); canvas.drawPath(dashedTopPath, paint);
canvas.drawPath(dashedBottomPath, paint); canvas.drawPath(dashedBottomPath, paint);
} }
Path _createDashedPath(Path source, double dashWidth, double dashSpace) { Path _createDashedPath(Path source, double dashWidth, double dashSpace) {
final dashedPath = Path(); final Path dashedPath = Path();
for (final pathMetric in source.computeMetrics()) { for (PathMetric pathMetric in source.computeMetrics()) {
var distance = 0.0; double distance = 0.0;
while (distance < pathMetric.length) { while (distance < pathMetric.length) {
final nextDistance = distance + dashWidth; final double nextDistance = distance + dashWidth;
dashedPath.addPath( dashedPath.addPath(
pathMetric.extractPath(distance, nextDistance), pathMetric.extractPath(distance, nextDistance),
Offset.zero, Offset.zero,

View File

@ -16,4 +16,4 @@ extension GetMonthNameFromNumber on num {
_ => 'N/A' _ => 'N/A'
}; };
} }
} }

View File

@ -15,8 +15,7 @@ class AirQualityDataModel extends Equatable {
return AirQualityDataModel( return AirQualityDataModel(
date: DateTime.parse(json['date'] as String), date: DateTime.parse(json['date'] as String),
data: (json['data'] as List<dynamic>) data: (json['data'] as List<dynamic>)
.map((e) => .map((e) => AirQualityPercentageData.fromJson(e as Map<String, dynamic>))
AirQualityPercentageData.fromJson(e as Map<String, dynamic>))
.toList(), .toList(),
); );
} }
@ -47,7 +46,7 @@ class AirQualityPercentageData extends Equatable {
factory AirQualityPercentageData.fromJson(Map<String, dynamic> json) { factory AirQualityPercentageData.fromJson(Map<String, dynamic> json) {
return AirQualityPercentageData( return AirQualityPercentageData(
type: json['type'] as String? ?? '', type: json['type'] as String? ?? '',
name: json['name'] as String? ?? '', name: json['name'] as String? ?? '',
percentage: (json['percentage'] as num?)?.toDouble() ?? 0, percentage: (json['percentage'] as num?)?.toDouble() ?? 0,
); );

View File

@ -36,14 +36,11 @@ class AnalyticsDevice {
deviceTuyaUuid: json['deviceTuyaUuid'] as String?, deviceTuyaUuid: json['deviceTuyaUuid'] as String?,
isActive: json['isActive'] as bool?, isActive: json['isActive'] as bool?,
productDevice: json['productDevice'] != null productDevice: json['productDevice'] != null
? ProductDevice.fromJson( ? ProductDevice.fromJson(json['productDevice'] as Map<String, dynamic>)
json['productDevice'] as Map<String, dynamic>)
: null, : null,
spaceUuid: json['spaceUuid'] as String?, spaceUuid: json['spaceUuid'] as String?,
latitude: latitude: json['lat'] != null ? double.parse(json['lat'] as String) : null,
json['lat'] != null ? double.parse(json['lat'] as String) : null, longitude: json['lon'] != null ? double.parse(json['lon'] as String) : null,
longitude:
json['lon'] != null ? double.parse(json['lon'] as String) : null,
); );
} }
} }

View File

@ -15,8 +15,7 @@ class Occupacy extends Equatable {
factory Occupacy.fromJson(Map<String, dynamic> json) { factory Occupacy.fromJson(Map<String, dynamic> json) {
return Occupacy( return Occupacy(
date: date: DateTime.parse(json['event_date'] as String? ?? '${DateTime.now()}'),
DateTime.parse(json['event_date'] as String? ?? '${DateTime.now()}'),
occupancy: (json['occupancy_percentage'] ?? 0).toString(), occupancy: (json['occupancy_percentage'] ?? 0).toString(),
spaceUuid: json['space_uuid'] as String? ?? '', spaceUuid: json['space_uuid'] as String? ?? '',
occupiedSeconds: json['occupied_seconds'] as int? ?? 0, occupiedSeconds: json['occupied_seconds'] as int? ?? 0,

View File

@ -19,8 +19,7 @@ class OccupancyHeatMapModel extends Equatable {
eventDate: DateTime.parse( eventDate: DateTime.parse(
json['event_date'] as String? ?? '${DateTime.now()}', json['event_date'] as String? ?? '${DateTime.now()}',
), ),
countTotalPresenceDetected: countTotalPresenceDetected: json['count_total_presence_detected'] as int? ?? 0,
json['count_total_presence_detected'] as int? ?? 0,
); );
} }

View File

@ -33,8 +33,7 @@ class AirQualityDistributionBloc
state.copyWith( state.copyWith(
status: AirQualityDistributionStatus.success, status: AirQualityDistributionStatus.success,
chartData: result, chartData: result,
filteredChartData: filteredChartData: _arrangeChartDataByType(result, state.selectedAqiType),
_arrangeChartDataByType(result, state.selectedAqiType),
), ),
); );
} catch (e) { } catch (e) {
@ -62,8 +61,7 @@ class AirQualityDistributionBloc
emit( emit(
state.copyWith( state.copyWith(
selectedAqiType: event.aqiType, selectedAqiType: event.aqiType,
filteredChartData: filteredChartData: _arrangeChartDataByType(state.chartData, event.aqiType),
_arrangeChartDataByType(state.chartData, event.aqiType),
), ),
); );
} }

View File

@ -7,8 +7,7 @@ import 'package:syncrow_web/pages/analytics/services/device_location/device_loca
part 'device_location_event.dart'; part 'device_location_event.dart';
part 'device_location_state.dart'; part 'device_location_state.dart';
class DeviceLocationBloc class DeviceLocationBloc extends Bloc<DeviceLocationEvent, DeviceLocationState> {
extends Bloc<DeviceLocationEvent, DeviceLocationState> {
DeviceLocationBloc( DeviceLocationBloc(
this._deviceLocationService, this._deviceLocationService,
) : super(const DeviceLocationState()) { ) : super(const DeviceLocationState()) {

View File

@ -53,8 +53,7 @@ class RangeOfAqiBloc extends Bloc<RangeOfAqiEvent, RangeOfAqiState> {
emit( emit(
state.copyWith( state.copyWith(
selectedAqiType: event.aqiType, selectedAqiType: event.aqiType,
filteredRangeOfAqi: filteredRangeOfAqi: _arrangeChartDataByType(state.rangeOfAqi, event.aqiType),
_arrangeChartDataByType(state.rangeOfAqi, event.aqiType),
), ),
); );
} }

View File

@ -105,8 +105,7 @@ abstract final class RangeOfAqiChartsHelper {
tooltipRoundedRadius: 16, tooltipRoundedRadius: 16,
showOnTopOfTheChartBoxArea: false, showOnTopOfTheChartBoxArea: false,
tooltipPadding: const EdgeInsets.all(8), tooltipPadding: const EdgeInsets.all(8),
getTooltipItems: (touchedSpots) => getTooltipItems: (touchedSpots) => RangeOfAqiChartsHelper.getTooltipItems(
RangeOfAqiChartsHelper.getTooltipItems(
touchedSpots, touchedSpots,
chartData, chartData,
), ),

View File

@ -81,8 +81,7 @@ class AqiDeviceInfo extends StatelessWidget {
aqiLevel: status aqiLevel: status
.firstWhere( .firstWhere(
(e) => e.code == 'air_quality_index', (e) => e.code == 'air_quality_index',
orElse: () => orElse: () => Status(code: 'air_quality_index', value: ''),
Status(code: 'air_quality_index', value: ''),
) )
.value .value
.toString(), .toString(),

View File

@ -36,25 +36,23 @@ class AqiDistributionChart extends StatelessWidget {
); );
} }
List<BarChartGroupData> _buildBarGroups( List<BarChartGroupData> _buildBarGroups(List<AirQualityDataModel> sortedData) {
List<AirQualityDataModel> sortedData) {
return List.generate(sortedData.length, (index) { return List.generate(sortedData.length, (index) {
final data = sortedData[index]; final data = sortedData[index];
final stackItems = <BarChartRodData>[]; final stackItems = <BarChartRodData>[];
double currentY = 0; double currentY = 0;
var isFirstElement = true; bool isFirstElement = true;
// Sort data by type to ensure consistent order // Sort data by type to ensure consistent order
final sortedPercentageData = final sortedPercentageData = List<AirQualityPercentageData>.from(data.data)
List<AirQualityPercentageData>.from(data.data) ..sort((a, b) => a.type.compareTo(b.type));
..sort((a, b) => a.type.compareTo(b.type));
for (final percentageData in sortedPercentageData) { for (final percentageData in sortedPercentageData) {
stackItems.add( stackItems.add(
BarChartRodData( BarChartRodData(
fromY: currentY, fromY: currentY,
toY: currentY + percentageData.percentage, toY: currentY + percentageData.percentage ,
color: AirQualityDataModel.metricColors[percentageData.name], color: AirQualityDataModel.metricColors[percentageData.name]!,
borderRadius: isFirstElement borderRadius: isFirstElement
? const BorderRadius.only( ? const BorderRadius.only(
topLeft: Radius.circular(22), topLeft: Radius.circular(22),
@ -86,9 +84,9 @@ class AqiDistributionChart extends StatelessWidget {
tooltipRoundedRadius: 16, tooltipRoundedRadius: 16,
tooltipPadding: const EdgeInsets.all(8), tooltipPadding: const EdgeInsets.all(8),
getTooltipItem: (group, groupIndex, rod, rodIndex) { getTooltipItem: (group, groupIndex, rod, rodIndex) {
final data = chartData[group.x]; final data = chartData[group.x.toInt()];
final children = <TextSpan>[]; final List<TextSpan> children = [];
final textStyle = context.textTheme.bodySmall?.copyWith( final textStyle = context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
@ -96,9 +94,8 @@ class AqiDistributionChart extends StatelessWidget {
); );
// Sort data by type to ensure consistent order // Sort data by type to ensure consistent order
final sortedPercentageData = final sortedPercentageData = List<AirQualityPercentageData>.from(data.data)
List<AirQualityPercentageData>.from(data.data) ..sort((a, b) => a.type.compareTo(b.type));
..sort((a, b) => a.type.compareTo(b.type));
for (final percentageData in sortedPercentageData) { for (final percentageData in sortedPercentageData) {
children.add(TextSpan( children.add(TextSpan(

View File

@ -49,7 +49,7 @@ class AqiSubValueWidget extends StatelessWidget {
int _getActiveSegmentByRange(double value, (double min, double max) range) { int _getActiveSegmentByRange(double value, (double min, double max) range) {
final ranges = _getRangesForValue(range); final ranges = _getRangesForValue(range);
for (var i = 0; i < ranges.length; i++) { for (int i = 0; i < ranges.length; i++) {
if (value <= ranges[i].max) return i; if (value <= ranges[i].max) return i;
} }
return ranges.length - 1; return ranges.length - 1;

View File

@ -29,8 +29,7 @@ class AqiTypeDropdown extends StatefulWidget {
class _AqiTypeDropdownState extends State<AqiTypeDropdown> { class _AqiTypeDropdownState extends State<AqiTypeDropdown> {
AqiType? _selectedItem = AqiType.aqi; AqiType? _selectedItem = AqiType.aqi;
void _updateSelectedItem(AqiType? item) => void _updateSelectedItem(AqiType? item) => setState(() => _selectedItem = item);
setState(() => _selectedItem = item);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -63,7 +63,7 @@ class RangeOfAqiChart extends StatelessWidget {
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.bottomCenter, begin: Alignment.bottomCenter,
end: Alignment.topCenter, end: Alignment.topCenter,
stops: const [0.0, 0.2, 0.4, 0.6, 0.8, 1.0], stops: [0.0, 0.2, 0.4, 0.6, 0.8, 1.0],
colors: RangeOfAqiChartsHelper.gradientData.map((e) { colors: RangeOfAqiChartsHelper.gradientData.map((e) {
final (color, _) = e; final (color, _) = e;
return color.withValues(alpha: 0.6); return color.withValues(alpha: 0.6);
@ -99,8 +99,7 @@ class RangeOfAqiChart extends StatelessWidget {
}) { }) {
const invisibleDot = FlDotData(show: false); const invisibleDot = FlDotData(show: false);
return LineChartBarData( return LineChartBarData(
spots: spots: List.generate(values.length, (i) => FlSpot(i.toDouble(), values[i])),
List.generate(values.length, (i) => FlSpot(i.toDouble(), values[i])),
isCurved: true, isCurved: true,
color: color, color: color,
barWidth: 4, barWidth: 4,

View File

@ -32,8 +32,7 @@ class RangeOfAqiChartBox extends StatelessWidget {
const SizedBox(height: 10), const SizedBox(height: 10),
const Divider(), const Divider(),
const SizedBox(height: 20), const SizedBox(height: 20),
Expanded( Expanded(child: RangeOfAqiChart(chartData: state.filteredRangeOfAqi)),
child: RangeOfAqiChart(chartData: state.filteredRangeOfAqi)),
], ],
), ),
); );

View File

@ -8,8 +8,7 @@ sealed class AnalyticsDevicesEvent extends Equatable {
} }
final class LoadAnalyticsDevicesEvent extends AnalyticsDevicesEvent { final class LoadAnalyticsDevicesEvent extends AnalyticsDevicesEvent {
const LoadAnalyticsDevicesEvent( const LoadAnalyticsDevicesEvent({required this.param, required this.onSuccess});
{required this.param, required this.onSuccess});
final GetAnalyticsDevicesParam param; final GetAnalyticsDevicesParam param;
final void Function(AnalyticsDevice device) onSuccess; final void Function(AnalyticsDevice device) onSuccess;

View File

@ -7,7 +7,7 @@ sealed class AnalyticsTabEvent extends Equatable {
List<Object> get props => []; List<Object> get props => [];
} }
class UpdateAnalyticsTabEvent extends AnalyticsTabEvent { class UpdateAnalyticsTabEvent extends AnalyticsTabEvent {
const UpdateAnalyticsTabEvent(this.analyticsTab); const UpdateAnalyticsTabEvent(this.analyticsTab);
final AnalyticsPageTab analyticsTab; final AnalyticsPageTab analyticsTab;

View File

@ -8,8 +8,7 @@ import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
final class AirQualityDataLoadingStrategy final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
implements AnalyticsDataLoadingStrategy {
@override @override
void onCommunitySelected( void onCommunitySelected(
BuildContext context, BuildContext context,
@ -26,8 +25,7 @@ final class AirQualityDataLoadingStrategy
SpaceModel space, SpaceModel space,
) { ) {
final spaceTreeBloc = context.read<SpaceTreeBloc>(); final spaceTreeBloc = context.read<SpaceTreeBloc>();
final isSpaceSelected = final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
final hasSelectedSpaces = spaceTreeBloc.state.selectedSpaces.isNotEmpty; final hasSelectedSpaces = spaceTreeBloc.state.selectedSpaces.isNotEmpty;
if (hasSelectedSpaces) clearData(context); if (hasSelectedSpaces) clearData(context);
@ -36,7 +34,7 @@ final class AirQualityDataLoadingStrategy
spaceTreeBloc spaceTreeBloc
..add(const SpaceTreeClearSelectionEvent()) ..add(const SpaceTreeClearSelectionEvent())
..add(OnSpaceSelected(community, space.uuid ?? '', const [])); ..add(OnSpaceSelected(community, space.uuid ?? '', []));
FetchAirQualityDataHelper.loadAirQualityData( FetchAirQualityDataHelper.loadAirQualityData(
context, context,

View File

@ -8,8 +8,7 @@ abstract final class AnalyticsDataLoadingStrategyFactory {
const AnalyticsDataLoadingStrategyFactory._(); const AnalyticsDataLoadingStrategyFactory._();
static AnalyticsDataLoadingStrategy getStrategy(AnalyticsPageTab tab) { static AnalyticsDataLoadingStrategy getStrategy(AnalyticsPageTab tab) {
return switch (tab) { return switch (tab) {
AnalyticsPageTab.energyManagement => AnalyticsPageTab.energyManagement => EnergyManagementDataLoadingStrategy(),
EnergyManagementDataLoadingStrategy(),
AnalyticsPageTab.occupancy => OccupancyDataLoadingStrategy(), AnalyticsPageTab.occupancy => OccupancyDataLoadingStrategy(),
AnalyticsPageTab.airQuality => AirQualityDataLoadingStrategy(), AnalyticsPageTab.airQuality => AirQualityDataLoadingStrategy(),
}; };

View File

@ -7,8 +7,7 @@ import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
class EnergyManagementDataLoadingStrategy class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
implements AnalyticsDataLoadingStrategy {
@override @override
void onCommunitySelected( void onCommunitySelected(
BuildContext context, BuildContext context,
@ -32,8 +31,7 @@ class EnergyManagementDataLoadingStrategy
SpaceModel space, SpaceModel space,
) { ) {
final spaceTreeBloc = context.read<SpaceTreeBloc>(); final spaceTreeBloc = context.read<SpaceTreeBloc>();
final isSpaceSelected = final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
final hasSelectedSpaces = spaceTreeBloc.state.selectedSpaces.isNotEmpty; final hasSelectedSpaces = spaceTreeBloc.state.selectedSpaces.isNotEmpty;
if (isSpaceSelected) { if (isSpaceSelected) {

View File

@ -24,8 +24,7 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
SpaceModel space, SpaceModel space,
) { ) {
final spaceTreeBloc = context.read<SpaceTreeBloc>(); final spaceTreeBloc = context.read<SpaceTreeBloc>();
final isSpaceSelected = final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
final hasSelectedSpaces = spaceTreeBloc.state.selectedSpaces.isNotEmpty; final hasSelectedSpaces = spaceTreeBloc.state.selectedSpaces.isNotEmpty;
if (hasSelectedSpaces) clearData(context); if (hasSelectedSpaces) clearData(context);
@ -34,7 +33,7 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
spaceTreeBloc spaceTreeBloc
..add(const SpaceTreeClearSelectionEvent()) ..add(const SpaceTreeClearSelectionEvent())
..add(OnSpaceSelected(community, space.uuid ?? '', const [])); ..add(OnSpaceSelected(community, space.uuid ?? '', []));
FetchOccupancyDataHelper.loadOccupancyData( FetchOccupancyDataHelper.loadOccupancyData(
context, context,

View File

@ -10,8 +10,7 @@ class AnalyticsCommunitiesSidebar extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final selectedTab = context.watch<AnalyticsTabBloc>().state; final selectedTab = context.watch<AnalyticsTabBloc>().state;
final strategy = final strategy = AnalyticsDataLoadingStrategyFactory.getStrategy(selectedTab);
AnalyticsDataLoadingStrategyFactory.getStrategy(selectedTab);
return Expanded( return Expanded(
child: AnalyticsSpaceTreeView( child: AnalyticsSpaceTreeView(

View File

@ -20,7 +20,7 @@ class AnalyticsDateFilterButton extends StatefulWidget {
final void Function(DateTime)? onDateSelected; final void Function(DateTime)? onDateSelected;
final DatePickerType datePickerType; final DatePickerType datePickerType;
static final Color _color = ColorsManager.blackColor.withValues(alpha: 0.8); static final _color = ColorsManager.blackColor.withValues(alpha: 0.8);
@override @override
State<AnalyticsDateFilterButton> createState() => State<AnalyticsDateFilterButton> createState() =>

View File

@ -21,8 +21,8 @@ class AnalyticsPageTabButton extends StatelessWidget {
onPressed: () { onPressed: () {
AnalyticsDataLoadingStrategyFactory.getStrategy(tab).clearData(context); AnalyticsDataLoadingStrategyFactory.getStrategy(tab).clearData(context);
context.read<AnalyticsTabBloc>().add( context.read<AnalyticsTabBloc>().add(
UpdateAnalyticsTabEvent(tab), UpdateAnalyticsTabEvent(tab),
); );
}, },
child: Text( child: Text(
tab.title, tab.title,
@ -33,9 +33,8 @@ class AnalyticsPageTabButton extends StatelessWidget {
style: TextStyle( style: TextStyle(
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w400, fontWeight: isSelected ? FontWeight.w700 : FontWeight.w400,
fontSize: 16, fontSize: 16,
color: isSelected color:
? ColorsManager.slidingBlueColor isSelected ? ColorsManager.slidingBlueColor : ColorsManager.textGray,
: ColorsManager.textGray,
), ),
), ),
); );

View File

@ -21,18 +21,18 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
int? _selectedMonth; int? _selectedMonth;
static const _monthNames = [ static const _monthNames = [
'January', "January",
'February', "February",
'March', "March",
'April', "April",
'May', "May",
'June', "June",
'July', "July",
'August', "August",
'September', "September",
'October', "October",
'November', "November",
'December', "December",
]; ];
@override @override
@ -189,19 +189,14 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
final isFutureMonth = isCurrentYear && index > currentDate.month - 1; final isFutureMonth = isCurrentYear && index > currentDate.month - 1;
return InkWell( return InkWell(
onTap: isFutureMonth onTap: isFutureMonth ? null : () => setState(() => _selectedMonth = index),
? null
: () => setState(() => _selectedMonth = index),
child: DecoratedBox( child: DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFEDF2F7), color: const Color(0xFFEDF2F7),
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: topLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero,
index % 3 == 0 ? const Radius.circular(16) : Radius.zero, bottomLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero,
bottomLeft: topRight: index % 3 == 2 ? const Radius.circular(16) : Radius.zero,
index % 3 == 0 ? const Radius.circular(16) : Radius.zero,
topRight:
index % 3 == 2 ? const Radius.circular(16) : Radius.zero,
bottomRight: bottomRight:
index % 3 == 2 ? const Radius.circular(16) : Radius.zero, index % 3 == 2 ? const Radius.circular(16) : Radius.zero,
), ),

View File

@ -53,8 +53,7 @@ class _AnalyticsSpaceTreeViewState extends State<AnalyticsSpaceTreeView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>( return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
builder: (context, state) {
final communities = state.searchQuery.isNotEmpty final communities = state.searchQuery.isNotEmpty
? state.filteredCommunity ? state.filteredCommunity
: state.communityList; : state.communityList;
@ -77,10 +76,9 @@ class _AnalyticsSpaceTreeViewState extends State<AnalyticsSpaceTreeView> {
), ),
), ),
CustomSearchBar( CustomSearchBar(
onSearchChanged: (query) => onSearchChanged: (query) => context.read<SpaceTreeBloc>().add(
context.read<SpaceTreeBloc>().add( SearchQueryEvent(query),
SearchQueryEvent(query), ),
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Expanded( Expanded(
@ -115,8 +113,7 @@ class _AnalyticsSpaceTreeViewState extends State<AnalyticsSpaceTreeView> {
isExpanded: state.expandedCommunities.contains( isExpanded: state.expandedCommunities.contains(
communities[index].uuid, communities[index].uuid,
), ),
onItemSelected: () => onItemSelected: () => widget.onSelectCommunity?.call(
widget.onSelectCommunity?.call(
communities[index], communities[index],
communities[index].spaces, communities[index].spaces,
), ),
@ -124,8 +121,8 @@ class _AnalyticsSpaceTreeViewState extends State<AnalyticsSpaceTreeView> {
(space) { (space) {
return CustomExpansionTileSpaceTree( return CustomExpansionTileSpaceTree(
title: space.name, title: space.name,
isExpanded: state.expandedSpaces isExpanded:
.contains(space.uuid), state.expandedSpaces.contains(space.uuid),
onItemSelected: () => onItemSelected: () =>
widget.onSelectSpace?.call( widget.onSelectSpace?.call(
communities[index], communities[index],
@ -156,8 +153,7 @@ class _AnalyticsSpaceTreeViewState extends State<AnalyticsSpaceTreeView> {
}, },
), ),
), ),
if (state.paginationIsLoading) if (state.paginationIsLoading) const CircularProgressIndicator(),
const CircularProgressIndicator(),
], ],
), ),
); );

View File

@ -19,9 +19,9 @@ class YearPickerWidget extends StatefulWidget {
class _YearPickerWidgetState extends State<YearPickerWidget> { class _YearPickerWidgetState extends State<YearPickerWidget> {
late int _currentYear; late int _currentYear;
static final List<int> years = List.generate( static final years = List.generate(
DateTime.now().year - (DateTime.now().year - 5) + 1, DateTime.now().year - (DateTime.now().year - 5) + 1,
(index) => 2020 + index, (index) => (2020 + index),
).where((year) => year <= DateTime.now().year).toList(); ).where((year) => year <= DateTime.now().year).toList();
@override @override
@ -123,12 +123,9 @@ class _YearPickerWidgetState extends State<YearPickerWidget> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFEDF2F7), color: const Color(0xFFEDF2F7),
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: topLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero,
index % 3 == 0 ? const Radius.circular(16) : Radius.zero, bottomLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero,
bottomLeft: topRight: index % 3 == 2 ? const Radius.circular(16) : Radius.zero,
index % 3 == 0 ? const Radius.circular(16) : Radius.zero,
topRight:
index % 3 == 2 ? const Radius.circular(16) : Radius.zero,
bottomRight: bottomRight:
index % 3 == 2 ? const Radius.circular(16) : Radius.zero, index % 3 == 2 ? const Radius.circular(16) : Radius.zero,
), ),

View File

@ -8,15 +8,13 @@ import 'package:syncrow_web/services/api/api_exception.dart';
part 'energy_consumption_by_phases_event.dart'; part 'energy_consumption_by_phases_event.dart';
part 'energy_consumption_by_phases_state.dart'; part 'energy_consumption_by_phases_state.dart';
class EnergyConsumptionByPhasesBloc extends Bloc<EnergyConsumptionByPhasesEvent, class EnergyConsumptionByPhasesBloc
EnergyConsumptionByPhasesState> { extends Bloc<EnergyConsumptionByPhasesEvent, EnergyConsumptionByPhasesState> {
EnergyConsumptionByPhasesBloc( EnergyConsumptionByPhasesBloc(
this._energyConsumptionByPhasesService, this._energyConsumptionByPhasesService,
) : super(const EnergyConsumptionByPhasesState()) { ) : super(const EnergyConsumptionByPhasesState()) {
on<LoadEnergyConsumptionByPhasesEvent>( on<LoadEnergyConsumptionByPhasesEvent>(_onLoadEnergyConsumptionByPhasesEvent);
_onLoadEnergyConsumptionByPhasesEvent); on<ClearEnergyConsumptionByPhasesEvent>(_onClearEnergyConsumptionByPhasesEvent);
on<ClearEnergyConsumptionByPhasesEvent>(
_onClearEnergyConsumptionByPhasesEvent);
} }
final EnergyConsumptionByPhasesService _energyConsumptionByPhasesService; final EnergyConsumptionByPhasesService _energyConsumptionByPhasesService;
@ -27,8 +25,7 @@ class EnergyConsumptionByPhasesBloc extends Bloc<EnergyConsumptionByPhasesEvent,
) async { ) async {
emit(state.copyWith(status: EnergyConsumptionByPhasesStatus.loading)); emit(state.copyWith(status: EnergyConsumptionByPhasesStatus.loading));
try { try {
final chartData = final chartData = await _energyConsumptionByPhasesService.load(event.param);
await _energyConsumptionByPhasesService.load(event.param);
emit( emit(
state.copyWith( state.copyWith(
status: EnergyConsumptionByPhasesStatus.loaded, status: EnergyConsumptionByPhasesStatus.loaded,
@ -52,7 +49,7 @@ class EnergyConsumptionByPhasesBloc extends Bloc<EnergyConsumptionByPhasesEvent,
} }
} }
Future<void> _onClearEnergyConsumptionByPhasesEvent( void _onClearEnergyConsumptionByPhasesEvent(
ClearEnergyConsumptionByPhasesEvent event, ClearEnergyConsumptionByPhasesEvent event,
Emitter<EnergyConsumptionByPhasesState> emit, Emitter<EnergyConsumptionByPhasesState> emit,
) async { ) async {

View File

@ -7,8 +7,7 @@ sealed class EnergyConsumptionByPhasesEvent extends Equatable {
List<Object> get props => []; List<Object> get props => [];
} }
class LoadEnergyConsumptionByPhasesEvent class LoadEnergyConsumptionByPhasesEvent extends EnergyConsumptionByPhasesEvent {
extends EnergyConsumptionByPhasesEvent {
const LoadEnergyConsumptionByPhasesEvent({ const LoadEnergyConsumptionByPhasesEvent({
required this.param, required this.param,
}); });
@ -19,7 +18,6 @@ class LoadEnergyConsumptionByPhasesEvent
List<Object> get props => [param]; List<Object> get props => [param];
} }
final class ClearEnergyConsumptionByPhasesEvent final class ClearEnergyConsumptionByPhasesEvent extends EnergyConsumptionByPhasesEvent {
extends EnergyConsumptionByPhasesEvent {
const ClearEnergyConsumptionByPhasesEvent(); const ClearEnergyConsumptionByPhasesEvent();
} }

View File

@ -8,13 +8,12 @@ import 'package:syncrow_web/services/api/api_exception.dart';
part 'energy_consumption_per_device_event.dart'; part 'energy_consumption_per_device_event.dart';
part 'energy_consumption_per_device_state.dart'; part 'energy_consumption_per_device_state.dart';
class EnergyConsumptionPerDeviceBloc extends Bloc< class EnergyConsumptionPerDeviceBloc
EnergyConsumptionPerDeviceEvent, EnergyConsumptionPerDeviceState> { extends Bloc<EnergyConsumptionPerDeviceEvent, EnergyConsumptionPerDeviceState> {
EnergyConsumptionPerDeviceBloc( EnergyConsumptionPerDeviceBloc(
this._energyConsumptionPerDeviceService, this._energyConsumptionPerDeviceService,
) : super(const EnergyConsumptionPerDeviceState()) { ) : super(const EnergyConsumptionPerDeviceState()) {
on<LoadEnergyConsumptionPerDeviceEvent>( on<LoadEnergyConsumptionPerDeviceEvent>(_onLoadEnergyConsumptionPerDeviceEvent);
_onLoadEnergyConsumptionPerDeviceEvent);
on<ClearEnergyConsumptionPerDeviceEvent>( on<ClearEnergyConsumptionPerDeviceEvent>(
_onClearEnergyConsumptionPerDeviceEvent); _onClearEnergyConsumptionPerDeviceEvent);
} }
@ -27,8 +26,7 @@ class EnergyConsumptionPerDeviceBloc extends Bloc<
) async { ) async {
emit(state.copyWith(status: EnergyConsumptionPerDeviceStatus.loading)); emit(state.copyWith(status: EnergyConsumptionPerDeviceStatus.loading));
try { try {
final chartData = final chartData = await _energyConsumptionPerDeviceService.load(event.param);
await _energyConsumptionPerDeviceService.load(event.param);
emit( emit(
state.copyWith( state.copyWith(
status: EnergyConsumptionPerDeviceStatus.loaded, status: EnergyConsumptionPerDeviceStatus.loaded,
@ -52,7 +50,7 @@ class EnergyConsumptionPerDeviceBloc extends Bloc<
} }
} }
Future<void> _onClearEnergyConsumptionPerDeviceEvent( void _onClearEnergyConsumptionPerDeviceEvent(
ClearEnergyConsumptionPerDeviceEvent event, ClearEnergyConsumptionPerDeviceEvent event,
Emitter<EnergyConsumptionPerDeviceState> emit, Emitter<EnergyConsumptionPerDeviceState> emit,
) async { ) async {

View File

@ -8,8 +8,7 @@ import 'package:syncrow_web/services/api/api_exception.dart';
part 'power_clamp_info_event.dart'; part 'power_clamp_info_event.dart';
part 'power_clamp_info_state.dart'; part 'power_clamp_info_state.dart';
class PowerClampInfoBloc class PowerClampInfoBloc extends Bloc<PowerClampInfoEvent, PowerClampInfoState> {
extends Bloc<PowerClampInfoEvent, PowerClampInfoState> {
PowerClampInfoBloc( PowerClampInfoBloc(
this._powerClampInfoService, this._powerClampInfoService,
) : super(const PowerClampInfoState()) { ) : super(const PowerClampInfoState()) {
@ -26,8 +25,7 @@ class PowerClampInfoBloc
) async { ) async {
emit(state.copyWith(status: PowerClampInfoStatus.loading)); emit(state.copyWith(status: PowerClampInfoStatus.loading));
try { try {
final powerClampModel = final powerClampModel = await _powerClampInfoService.getInfo(event.deviceId);
await _powerClampInfoService.getInfo(event.deviceId);
emit( emit(
state.copyWith( state.copyWith(
status: PowerClampInfoStatus.loaded, status: PowerClampInfoStatus.loaded,
@ -51,7 +49,7 @@ class PowerClampInfoBloc
} }
} }
Future<void> _onUpdatePowerClampStatusEvent( void _onUpdatePowerClampStatusEvent(
UpdatePowerClampStatusEvent event, UpdatePowerClampStatusEvent event,
Emitter<PowerClampInfoState> emit, Emitter<PowerClampInfoState> emit,
) async { ) async {

View File

@ -16,6 +16,7 @@ final class LoadPowerClampInfoEvent extends PowerClampInfoEvent {
List<Object> get props => [deviceId]; List<Object> get props => [deviceId];
} }
final class UpdatePowerClampStatusEvent extends PowerClampInfoEvent { final class UpdatePowerClampStatusEvent extends PowerClampInfoEvent {
const UpdatePowerClampStatusEvent(this.statusList); const UpdatePowerClampStatusEvent(this.statusList);
@ -27,4 +28,4 @@ final class UpdatePowerClampStatusEvent extends PowerClampInfoEvent {
final class ClearPowerClampInfoEvent extends PowerClampInfoEvent { final class ClearPowerClampInfoEvent extends PowerClampInfoEvent {
const ClearPowerClampInfoEvent(); const ClearPowerClampInfoEvent();
} }

View File

@ -24,4 +24,4 @@ class _RealtimeDeviceChangesUpdated extends RealtimeDeviceChangesEvent {
final List<Status> deviceStatusList; final List<Status> deviceStatusList;
const _RealtimeDeviceChangesUpdated(this.deviceStatusList); const _RealtimeDeviceChangesUpdated(this.deviceStatusList);
} }

View File

@ -49,7 +49,7 @@ class TotalEnergyConsumptionBloc
} }
} }
Future<void> _onClearTotalEnergyConsumptionEvent( void _onClearTotalEnergyConsumptionEvent(
ClearTotalEnergyConsumptionEvent event, ClearTotalEnergyConsumptionEvent event,
Emitter<TotalEnergyConsumptionState> emit, Emitter<TotalEnergyConsumptionState> emit,
) async { ) async {

View File

@ -7,8 +7,7 @@ sealed class TotalEnergyConsumptionEvent extends Equatable {
List<Object?> get props => []; List<Object?> get props => [];
} }
final class TotalEnergyConsumptionLoadEvent final class TotalEnergyConsumptionLoadEvent extends TotalEnergyConsumptionEvent {
extends TotalEnergyConsumptionEvent {
const TotalEnergyConsumptionLoadEvent({required this.param}); const TotalEnergyConsumptionLoadEvent({required this.param});
final GetTotalEnergyConsumptionParam param; final GetTotalEnergyConsumptionParam param;
@ -17,7 +16,6 @@ final class TotalEnergyConsumptionLoadEvent
List<Object?> get props => [param]; List<Object?> get props => [param];
} }
final class ClearTotalEnergyConsumptionEvent final class ClearTotalEnergyConsumptionEvent extends TotalEnergyConsumptionEvent {
extends TotalEnergyConsumptionEvent {
const ClearTotalEnergyConsumptionEvent(); const ClearTotalEnergyConsumptionEvent();
} }

View File

@ -69,8 +69,7 @@ abstract final class EnergyManagementChartsHelper {
return labels.where((element) => element.isNotEmpty).join(', '); return labels.where((element) => element.isNotEmpty).join(', ');
} }
static List<LineTooltipItem?> getTooltipItems( static List<LineTooltipItem?> getTooltipItems(List<LineBarSpot> touchedSpots) {
List<LineBarSpot> touchedSpots) {
return touchedSpots.map((spot) { return touchedSpots.map((spot) {
return LineTooltipItem( return LineTooltipItem(
getToolTipLabel(spot.x, spot.y), getToolTipLabel(spot.x, spot.y),
@ -86,8 +85,7 @@ abstract final class EnergyManagementChartsHelper {
static LineTouchTooltipData lineTouchTooltipData() { static LineTouchTooltipData lineTouchTooltipData() {
return LineTouchTooltipData( return LineTouchTooltipData(
getTooltipColor: (touchTooltipItem) => ColorsManager.whiteColors, getTooltipColor: (touchTooltipItem) => ColorsManager.whiteColors,
tooltipBorder: tooltipBorder: const BorderSide(color: ColorsManager.semiTransparentBlack),
const BorderSide(color: ColorsManager.semiTransparentBlack),
tooltipRoundedRadius: 16, tooltipRoundedRadius: 16,
showOnTopOfTheChartBoxArea: false, showOnTopOfTheChartBoxArea: false,
tooltipPadding: const EdgeInsets.all(8), tooltipPadding: const EdgeInsets.all(8),

View File

@ -122,8 +122,7 @@ abstract final class FetchEnergyManagementDataHelper {
final selectedDevice = getSelectedDevice(context); final selectedDevice = getSelectedDevice(context);
context.read<RealtimeDeviceChangesBloc>().add( context.read<RealtimeDeviceChangesBloc>().add(
RealtimeDeviceChangesStarted( RealtimeDeviceChangesStarted(deviceUuid ?? selectedDevice?.uuid ?? ''),
deviceUuid ?? selectedDevice?.uuid ?? ''),
); );
} }

View File

@ -51,8 +51,7 @@ class AnalyticsEnergyManagementView extends StatelessWidget {
spacing: 20, spacing: 20,
children: [ children: [
Expanded(child: TotalEnergyConsumptionChartBox()), Expanded(child: TotalEnergyConsumptionChartBox()),
Expanded( Expanded(child: EnergyConsumptionPerDeviceChartBox()),
child: EnergyConsumptionPerDeviceChartBox()),
], ],
), ),
), ),

View File

@ -52,8 +52,7 @@ class AnalyticsDeviceDropdown extends StatelessWidget {
); );
} }
Widget _buildDevicesDropdown( Widget _buildDevicesDropdown(BuildContext context, AnalyticsDevicesState state) {
BuildContext context, AnalyticsDevicesState state) {
final spaceUuid = state.selectedDevice?.spaceUuid; final spaceUuid = state.selectedDevice?.spaceUuid;
return DropdownButton<AnalyticsDevice?>( return DropdownButton<AnalyticsDevice?>(
value: state.selectedDevice, value: state.selectedDevice,

View File

@ -18,6 +18,7 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BarChart( return BarChart(
BarChartData( BarChartData(
gridData: EnergyManagementChartsHelper.gridData().copyWith( gridData: EnergyManagementChartsHelper.gridData().copyWith(
checkToShowHorizontalLine: (value) => true, checkToShowHorizontalLine: (value) => true,
horizontalInterval: 250, horizontalInterval: 250,
@ -99,11 +100,11 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
}) { }) {
final data = energyData; final data = energyData;
final date = DateFormat('dd/MM/yyyy').format(data[group.x].date); final date = DateFormat('dd/MM/yyyy').format(data[group.x.toInt()].date);
final phaseA = data[group.x].energyConsumedA; final phaseA = data[group.x.toInt()].energyConsumedA;
final phaseB = data[group.x].energyConsumedB; final phaseB = data[group.x.toInt()].energyConsumedB;
final phaseC = data[group.x].energyConsumedC; final phaseC = data[group.x.toInt()].energyConsumedC;
final total = data[group.x].energyConsumedKw; final total = data[group.x.toInt()].energyConsumedKw;
return BarTooltipItem( return BarTooltipItem(
'$date\n', '$date\n',

View File

@ -22,8 +22,7 @@ class EnergyConsumptionByPhasesChartBox extends StatelessWidget {
children: [ children: [
AnalyticsErrorWidget(state.errorMessage), AnalyticsErrorWidget(state.errorMessage),
EnergyConsumptionByPhasesTitle( EnergyConsumptionByPhasesTitle(
isLoading: isLoading: state.status == EnergyConsumptionByPhasesStatus.loading,
state.status == EnergyConsumptionByPhasesStatus.loading,
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
Expanded( Expanded(

View File

@ -17,6 +17,7 @@ class EnergyConsumptionPerDeviceChart extends StatelessWidget {
context, context,
leftTitlesInterval: 250, leftTitlesInterval: 250,
), ),
gridData: EnergyManagementChartsHelper.gridData().copyWith( gridData: EnergyManagementChartsHelper.gridData().copyWith(
checkToShowHorizontalLine: (value) => true, checkToShowHorizontalLine: (value) => true,
horizontalInterval: 250, horizontalInterval: 250,

View File

@ -46,8 +46,7 @@ class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
flex: 2, flex: 2,
child: EnergyConsumptionPerDeviceDevicesList( child: EnergyConsumptionPerDeviceDevicesList(
chartData: state.chartData, chartData: state.chartData,
devices: devices: context.watch<AnalyticsDevicesBloc>().state.devices,
context.watch<AnalyticsDevicesBloc>().state.devices,
), ),
), ),
], ],
@ -56,8 +55,7 @@ class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
const Divider(height: 0), const Divider(height: 0),
const SizedBox(height: 20), const SizedBox(height: 20),
Expanded( Expanded(
child: child: EnergyConsumptionPerDeviceChart(chartData: state.chartData),
EnergyConsumptionPerDeviceChart(chartData: state.chartData),
), ),
], ],
), ),

View File

@ -43,14 +43,11 @@ class PowerClampEnergyDataWidget extends StatelessWidget {
title: 'Smart Power Clamp', title: 'Smart Power Clamp',
showSpaceUuidInDevicesDropdown: true, showSpaceUuidInDevicesDropdown: true,
onChanged: (device) { onChanged: (device) {
FetchEnergyManagementDataHelper FetchEnergyManagementDataHelper.loadEnergyConsumptionByPhases(
.loadEnergyConsumptionByPhases(
context, context,
powerClampUuid: device.uuid, powerClampUuid: device.uuid,
selectedDate: context selectedDate:
.read<AnalyticsDatePickerBloc>() context.read<AnalyticsDatePickerBloc>().state.monthlyDate,
.state
.monthlyDate,
); );
}, },
), ),
@ -94,8 +91,7 @@ class PowerClampEnergyDataWidget extends StatelessWidget {
), ),
), ),
const SizedBox(height: 14), const SizedBox(height: 14),
const Expanded( const Expanded(flex: 3, child: EnergyConsumptionByPhasesChartBox()),
flex: 3, child: EnergyConsumptionByPhasesChartBox()),
], ],
), ),
); );

View File

@ -140,9 +140,9 @@ class PowerClampPhasesDataWidget extends StatelessWidget {
String _formatCurrentValue(String? value) { String _formatCurrentValue(String? value) {
if (value == null) return '--'; if (value == null) return '--';
var str = value; String str = value;
if (str.isEmpty || str == '--') return '--'; if (str.isEmpty || str == '--') return '--';
str = str.replaceAll(RegExp('[^0-9]'), ''); str = str.replaceAll(RegExp(r'[^0-9]'), '');
if (str.isEmpty) return '--'; if (str.isEmpty) return '--';
if (str.length == 1) return '${str[0]}.0'; if (str.length == 1) return '${str[0]}.0';
return '${str[0]}.${str.substring(1)}'; return '${str[0]}.${str.substring(1)}';
@ -150,9 +150,9 @@ class PowerClampPhasesDataWidget extends StatelessWidget {
String _formatPowerFactor(String? value) { String _formatPowerFactor(String? value) {
if (value == null) return '--'; if (value == null) return '--';
var str = value; String str = value;
if (str.isEmpty || str == '--') return '--'; if (str.isEmpty || str == '--') return '--';
str = str.replaceAll(RegExp('[^0-9]'), ''); str = str.replaceAll(RegExp(r'[^0-9]'), '');
if (str.isEmpty) return '--'; if (str.isEmpty) return '--';
final intValue = int.tryParse(str); final intValue = int.tryParse(str);
if (intValue == null) return '--'; if (intValue == null) return '--';
@ -162,9 +162,9 @@ class PowerClampPhasesDataWidget extends StatelessWidget {
String _formatVoltage(String? value) { String _formatVoltage(String? value) {
if (value == null) return '--'; if (value == null) return '--';
var str = value; String str = value;
if (str.isEmpty || str == '--') return '--'; if (str.isEmpty || str == '--') return '--';
str = str.replaceAll(RegExp('[^0-9]'), ''); str = str.replaceAll(RegExp(r'[^0-9]'), '');
if (str.isEmpty) return '--'; if (str.isEmpty) return '--';
if (str.length == 1) return '0.${str[0]}'; if (str.length == 1) return '0.${str[0]}';
return '${str.substring(0, str.length - 1)}.${str.substring(str.length - 1)}'; return '${str.substring(0, str.length - 1)}.${str.substring(str.length - 1)}';

View File

@ -29,6 +29,7 @@ class TotalEnergyConsumptionChart extends StatelessWidget {
), ),
duration: Duration.zero, duration: Duration.zero,
curve: Curves.easeIn, curve: Curves.easeIn,
), ),
); );
} }

View File

@ -25,8 +25,7 @@ class TotalEnergyConsumptionChartBox extends StatelessWidget {
Row( Row(
children: [ children: [
ChartsLoadingWidget( ChartsLoadingWidget(
isLoading: isLoading: state.status == TotalEnergyConsumptionStatus.loading,
state.status == TotalEnergyConsumptionStatus.loading,
), ),
const Expanded( const Expanded(
flex: 3, flex: 3,

View File

@ -23,11 +23,9 @@ class OccupancyBloc extends Bloc<OccupancyEvent, OccupancyState> {
emit(state.copyWith(status: OccupancyStatus.loading)); emit(state.copyWith(status: OccupancyStatus.loading));
try { try {
final chartData = await _occupacyService.load(event.param); final chartData = await _occupacyService.load(event.param);
emit( emit(state.copyWith(chartData: chartData, status: OccupancyStatus.loaded));
state.copyWith(chartData: chartData, status: OccupancyStatus.loaded));
} on APIException catch (e) { } on APIException catch (e) {
emit(state.copyWith( emit(state.copyWith(status: OccupancyStatus.failure, errorMessage: e.message));
status: OccupancyStatus.failure, errorMessage: e.message));
} catch (e) { } catch (e) {
emit(state.copyWith(status: OccupancyStatus.failure, errorMessage: '$e')); emit(state.copyWith(status: OccupancyStatus.failure, errorMessage: '$e'));
} }

View File

@ -25,18 +25,15 @@ abstract final class FetchOccupancyDataHelper {
final datePickerState = context.read<AnalyticsDatePickerBloc>().state; final datePickerState = context.read<AnalyticsDatePickerBloc>().state;
loadAnalyticsDevices(context, loadAnalyticsDevices(context, communityUuid: communityId, spaceUuid: spaceId);
communityUuid: communityId, spaceUuid: spaceId); final selectedDevice = context.read<AnalyticsDevicesBloc>().state.selectedDevice;
final selectedDevice =
context.read<AnalyticsDevicesBloc>().state.selectedDevice;
loadOccupancyChartData( loadOccupancyChartData(
context, context,
spaceUuid: spaceId, spaceUuid: spaceId,
date: datePickerState.monthlyDate, date: datePickerState.monthlyDate,
); );
loadHeatMapData(context, loadHeatMapData(context, spaceUuid: spaceId, year: datePickerState.yearlyDate);
spaceUuid: spaceId, year: datePickerState.yearlyDate);
if (selectedDevice case final AnalyticsDevice device) { if (selectedDevice case final AnalyticsDevice device) {
context.read<RealtimeDeviceChangesBloc>() context.read<RealtimeDeviceChangesBloc>()
@ -67,8 +64,7 @@ abstract final class FetchOccupancyDataHelper {
context.read<OccupancyBloc>().add( context.read<OccupancyBloc>().add(
LoadOccupancyEvent( LoadOccupancyEvent(
GetOccupancyParam( GetOccupancyParam(
monthDate: monthDate: '${date.year}-${date.month.toString().padLeft(2, '0')}',
'${date.year}-${date.month.toString().padLeft(2, '0')}',
spaceUuid: spaceUuid, spaceUuid: spaceUuid,
), ),
), ),

View File

@ -20,12 +20,9 @@ class AnalyticsOccupancyView extends StatelessWidget {
child: Column( child: Column(
spacing: 32, spacing: 32,
children: [ children: [
SizedBox( SizedBox(height: height * 0.46, child: const OccupancyEndSideBar()),
height: height * 0.46, child: const OccupancyEndSideBar()), SizedBox(height: height * 0.5, child: const OccupancyChartBox()),
SizedBox( SizedBox(height: height * 0.5, child: const OccupancyHeatMapBox()),
height: height * 0.5, child: const OccupancyChartBox()),
SizedBox(
height: height * 0.5, child: const OccupancyHeatMapBox()),
], ],
), ),
); );

View File

@ -41,8 +41,7 @@ class OccupancyChart extends StatelessWidget {
barRods: [ barRods: [
BarChartRodData( BarChartRodData(
toY: 100.0, toY: 100.0,
fromY: fromY: occupancyValue == 0 ? occupancyValue : occupancyValue + 2.5,
occupancyValue == 0 ? occupancyValue : occupancyValue + 2.5,
color: ColorsManager.graysColor, color: ColorsManager.graysColor,
width: _chartWidth, width: _chartWidth,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
@ -89,8 +88,8 @@ class OccupancyChart extends StatelessWidget {
}) { }) {
final data = chartData; final data = chartData;
final occupancyValue = double.parse(data[group.x].occupancy); final occupancyValue = double.parse(data[group.x.toInt()].occupancy);
final percentage = '${occupancyValue.toStringAsFixed(0)}%'; final percentage = '${(occupancyValue).toStringAsFixed(0)}%';
return BarTooltipItem( return BarTooltipItem(
percentage, percentage,
@ -117,7 +116,7 @@ class OccupancyChart extends StatelessWidget {
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
child: Text( child: Text(
'${value.toStringAsFixed(0)}%', '${(value).toStringAsFixed(0)}%',
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
fontSize: 12, fontSize: 12,
color: ColorsManager.greyColor, color: ColorsManager.greyColor,

View File

@ -44,15 +44,13 @@ class OccupancyChartBox extends StatelessWidget {
child: AnalyticsDateFilterButton( child: AnalyticsDateFilterButton(
onDateSelected: (DateTime value) { onDateSelected: (DateTime value) {
context.read<AnalyticsDatePickerBloc>().add( context.read<AnalyticsDatePickerBloc>().add(
UpdateAnalyticsDatePickerEvent( UpdateAnalyticsDatePickerEvent(montlyDate: value),
montlyDate: value),
); );
if (spaceTreeState.selectedSpaces.isNotEmpty) { if (spaceTreeState.selectedSpaces.isNotEmpty) {
FetchOccupancyDataHelper.loadOccupancyChartData( FetchOccupancyDataHelper.loadOccupancyChartData(
context, context,
spaceUuid: spaceUuid:
spaceTreeState.selectedSpaces.firstOrNull ?? spaceTreeState.selectedSpaces.firstOrNull ?? '',
'',
date: value, date: value,
); );
} }

View File

@ -44,15 +44,13 @@ class OccupancyHeatMapBox extends StatelessWidget {
child: AnalyticsDateFilterButton( child: AnalyticsDateFilterButton(
onDateSelected: (DateTime value) { onDateSelected: (DateTime value) {
context.read<AnalyticsDatePickerBloc>().add( context.read<AnalyticsDatePickerBloc>().add(
UpdateAnalyticsDatePickerEvent( UpdateAnalyticsDatePickerEvent(yearlyDate: value),
yearlyDate: value),
); );
if (spaceTreeState.selectedSpaces.isNotEmpty) { if (spaceTreeState.selectedSpaces.isNotEmpty) {
FetchOccupancyDataHelper.loadHeatMapData( FetchOccupancyDataHelper.loadHeatMapData(
context, context,
spaceUuid: spaceUuid:
spaceTreeState.selectedSpaces.firstOrNull ?? spaceTreeState.selectedSpaces.firstOrNull ?? '',
'',
year: value, year: value,
); );
} }

View File

@ -28,11 +28,11 @@ class OccupancyPainter extends CustomPainter {
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
final fillPaint = Paint(); final Paint fillPaint = Paint();
final borderPaint = Paint() final Paint borderPaint = Paint()
..color = ColorsManager.grayBorder.withValues(alpha: 0.4) ..color = ColorsManager.grayBorder.withValues(alpha: 0.4)
..style = PaintingStyle.stroke; ..style = PaintingStyle.stroke;
final hoveredBorderPaint = Paint() final Paint hoveredBorderPaint = Paint()
..color = Colors.black ..color = Colors.black
..style = PaintingStyle.stroke ..style = PaintingStyle.stroke
..strokeWidth = 1.5; ..strokeWidth = 1.5;
@ -66,24 +66,24 @@ class OccupancyPainter extends CustomPainter {
); );
canvas.drawLine(Offset(x, y), Offset(x, y + cellSize), borderPaint); canvas.drawLine(Offset(x, y), Offset(x, y + cellSize), borderPaint);
canvas.drawLine(Offset(x + cellSize, y), canvas.drawLine(Offset(x + cellSize, y), Offset(x + cellSize, y + cellSize),
Offset(x + cellSize, y + cellSize), borderPaint); borderPaint);
} }
} }
} }
void _drawDashedLine(Canvas canvas, Offset start, Offset end, Paint paint) { void _drawDashedLine(Canvas canvas, Offset start, Offset end, Paint paint) {
const dashWidth = 2.0; const double dashWidth = 2.0;
const dashSpace = 4.0; const double dashSpace = 4.0;
final totalLength = (end - start).distance; final double totalLength = (end - start).distance;
final direction = (end - start) / (end - start).distance; final Offset direction = (end - start) / (end - start).distance;
var currentLength = 0.0; double currentLength = 0.0;
while (currentLength < totalLength) { while (currentLength < totalLength) {
final dashStart = start + direction * currentLength; final Offset dashStart = start + direction * currentLength;
final nextLength = currentLength + dashWidth; final double nextLength = currentLength + dashWidth;
final dashEnd = start + final Offset dashEnd =
direction * (nextLength < totalLength ? nextLength : totalLength); start + direction * (nextLength < totalLength ? nextLength : totalLength);
canvas.drawLine(dashStart, dashEnd, paint); canvas.drawLine(dashStart, dashEnd, paint);
currentLength = nextLength + dashSpace; currentLength = nextLength + dashSpace;
} }

View File

@ -5,8 +5,7 @@ import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart'; import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/air_quality_distribution_service.dart'; import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/air_quality_distribution_service.dart';
class FakeAirQualityDistributionService class FakeAirQualityDistributionService implements AirQualityDistributionService {
implements AirQualityDistributionService {
final _random = Random(); final _random = Random();
@override @override
@ -44,6 +43,7 @@ class FakeAirQualityDistributionService
name: 'poor', name: 'poor',
percentage: nonNullValues[2], percentage: nonNullValues[2],
type: AqiType.hcho.code, type: AqiType.hcho.code,
), ),
AirQualityPercentageData( AirQualityPercentageData(
name: 'unhealthy', name: 'unhealthy',
@ -71,7 +71,7 @@ class FakeAirQualityDistributionService
List<bool> nullMask, List<bool> nullMask,
) { ) {
double nonNullSum = 0; double nonNullSum = 0;
for (var i = 0; i < originalValues.length; i++) { for (int i = 0; i < originalValues.length; i++) {
if (!nullMask[i]) { if (!nullMask[i]) {
nonNullSum += originalValues[i]; nonNullSum += originalValues[i];
} }

View File

@ -3,8 +3,7 @@ import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/air_quality_distribution_service.dart'; import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/air_quality_distribution_service.dart';
import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/services/api/http_service.dart';
class RemoteAirQualityDistributionService class RemoteAirQualityDistributionService implements AirQualityDistributionService {
implements AirQualityDistributionService {
RemoteAirQualityDistributionService(this._httpService); RemoteAirQualityDistributionService(this._httpService);
final HTTPService _httpService; final HTTPService _httpService;

View File

@ -16,8 +16,7 @@ class AnalyticsDevicesServiceDelegate implements AnalyticsDevicesService {
GetAnalyticsDevicesParam param, GetAnalyticsDevicesParam param,
) { ) {
return switch (param.requestType) { return switch (param.requestType) {
AnalyticsDeviceRequestType.occupancy => AnalyticsDeviceRequestType.occupancy => _occupancyService.getDevices(param),
_occupancyService.getDevices(param),
AnalyticsDeviceRequestType.energyManagement => AnalyticsDeviceRequestType.energyManagement =>
_energyManagementService.getDevices(param), _energyManagementService.getDevices(param),
}; };

View File

@ -14,8 +14,7 @@ final class RemoteEnergyManagementAnalyticsDevicesService
static const _defaultErrorMessage = 'Failed to load analytics devices'; static const _defaultErrorMessage = 'Failed to load analytics devices';
@override @override
Future<List<AnalyticsDevice>> getDevices( Future<List<AnalyticsDevice>> getDevices(GetAnalyticsDevicesParam param) async {
GetAnalyticsDevicesParam param) async {
try { try {
final response = await _httpService.get( final response = await _httpService.get(
path: '/devices-space-community/recursive-child', path: '/devices-space-community/recursive-child',
@ -38,8 +37,7 @@ final class RemoteEnergyManagementAnalyticsDevicesService
final message = e.response?.data as Map<String, dynamic>?; final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?; final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? ''; final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
[_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage); throw APIException(formattedErrorMessage);
} catch (e) { } catch (e) {
throw APIException('$_defaultErrorMessage: $e'); throw APIException('$_defaultErrorMessage: $e');

View File

@ -6,8 +6,7 @@ import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/services/api/api_exception.dart'; import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/services/api/http_service.dart';
class RemoteOccupancyAnalyticsDevicesService class RemoteOccupancyAnalyticsDevicesService implements AnalyticsDevicesService {
implements AnalyticsDevicesService {
const RemoteOccupancyAnalyticsDevicesService(this._httpService); const RemoteOccupancyAnalyticsDevicesService(this._httpService);
final HTTPService _httpService; final HTTPService _httpService;
@ -15,8 +14,7 @@ class RemoteOccupancyAnalyticsDevicesService
static const _defaultErrorMessage = 'Failed to load analytics devices'; static const _defaultErrorMessage = 'Failed to load analytics devices';
@override @override
Future<List<AnalyticsDevice>> getDevices( Future<List<AnalyticsDevice>> getDevices(GetAnalyticsDevicesParam param) async {
GetAnalyticsDevicesParam param) async {
try { try {
final requests = await Future.wait<List<AnalyticsDevice>>( final requests = await Future.wait<List<AnalyticsDevice>>(
param.deviceTypes.map((e) { param.deviceTypes.map((e) {
@ -36,18 +34,15 @@ class RemoteOccupancyAnalyticsDevicesService
final message = e.response?.data as Map<String, dynamic>?; final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?; final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? ''; final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
[_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage); throw APIException(formattedErrorMessage);
} catch (e) { } catch (e) {
final formattedErrorMessage = final formattedErrorMessage = [_defaultErrorMessage, e.toString()].join(': ');
[_defaultErrorMessage, e.toString()].join(': ');
throw APIException(formattedErrorMessage); throw APIException(formattedErrorMessage);
} }
} }
Future<List<AnalyticsDevice>> _makeRequest( Future<List<AnalyticsDevice>> _makeRequest(GetAnalyticsDevicesParam param) async {
GetAnalyticsDevicesParam param) async {
try { try {
final projectUuid = await ProjectManager.getProjectUUID(); final projectUuid = await ProjectManager.getProjectUUID();
@ -74,8 +69,7 @@ class RemoteOccupancyAnalyticsDevicesService
final message = e.response?.data as Map<String, dynamic>?; final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?; final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? ''; final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
[_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage); throw APIException(formattedErrorMessage);
} catch (e) { } catch (e) {
throw APIException('$_defaultErrorMessage: $e'); throw APIException('$_defaultErrorMessage: $e');

View File

@ -34,7 +34,7 @@ class DeviceLocationDetailsServiceDecorator implements DeviceLocationService {
return deviceLocationInfo; return deviceLocationInfo;
} catch (e) { } catch (e) {
throw Exception('Failed to load device location info: $e'); throw Exception('Failed to load device location info: ${e.toString()}');
} }
} }
} }

View File

@ -11,8 +11,7 @@ final class RemoteEnergyConsumptionByPhasesService
final HTTPService _httpService; final HTTPService _httpService;
static const _defaultErrorMessage = static const _defaultErrorMessage = 'Failed to load energy consumption per phase';
'Failed to load energy consumption per phase';
@override @override
Future<List<PhasesEnergyConsumption>> load( Future<List<PhasesEnergyConsumption>> load(
@ -37,8 +36,7 @@ final class RemoteEnergyConsumptionByPhasesService
final message = e.response?.data as Map<String, dynamic>?; final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?; final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? ''; final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
[_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage); throw APIException(formattedErrorMessage);
} catch (e) { } catch (e) {
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': '); final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');

View File

@ -13,8 +13,7 @@ class RemoteEnergyConsumptionPerDeviceService
final HTTPService _httpService; final HTTPService _httpService;
static const _defaultErrorMessage = static const _defaultErrorMessage = 'Failed to load energy consumption per device';
'Failed to load energy consumption per device';
@override @override
Future<List<DeviceEnergyDataModel>> load( Future<List<DeviceEnergyDataModel>> load(
@ -32,8 +31,7 @@ class RemoteEnergyConsumptionPerDeviceService
final message = e.response?.data as Map<String, dynamic>?; final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?; final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? ''; final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
[_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage); throw APIException(formattedErrorMessage);
} catch (e) { } catch (e) {
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': '); final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
@ -61,8 +59,7 @@ abstract final class _EnergyConsumptionPerDeviceMapper {
final energyJson = data as Map<String, dynamic>; final energyJson = data as Map<String, dynamic>;
return EnergyDataModel( return EnergyDataModel(
date: DateTime.parse(energyJson['date'] as String), date: DateTime.parse(energyJson['date'] as String),
value: value: double.parse(energyJson['total_energy_consumed_kw'] as String),
double.parse(energyJson['total_energy_consumed_kw'] as String),
); );
}).toList(), }).toList(),
); );

View File

@ -33,8 +33,7 @@ final class RemoteOccupancyService implements OccupacyService {
final message = e.response?.data as Map<String, dynamic>?; final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?; final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? ''; final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
[_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage); throw APIException(formattedErrorMessage);
} catch (e) { } catch (e) {
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': '); final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');

View File

@ -13,8 +13,7 @@ final class RemoteOccupancyHeatMapService implements OccupancyHeatMapService {
static const _defaultErrorMessage = 'Failed to load occupancy heat map'; static const _defaultErrorMessage = 'Failed to load occupancy heat map';
@override @override
Future<List<OccupancyHeatMapModel>> load( Future<List<OccupancyHeatMapModel>> load(GetOccupancyHeatMapParam param) async {
GetOccupancyHeatMapParam param) async {
try { try {
final response = await _httpService.get( final response = await _httpService.get(
path: '/occupancy/heat-map/space/${param.spaceUuid}', path: '/occupancy/heat-map/space/${param.spaceUuid}',
@ -25,8 +24,7 @@ final class RemoteOccupancyHeatMapService implements OccupancyHeatMapService {
final dailyData = json['data'] as List<dynamic>? ?? <dynamic>[]; final dailyData = json['data'] as List<dynamic>? ?? <dynamic>[];
final result = dailyData.map( final result = dailyData.map(
(json) => (json) => OccupancyHeatMapModel.fromJson(json as Map<String, dynamic>),
OccupancyHeatMapModel.fromJson(json as Map<String, dynamic>),
); );
return result.toList(); return result.toList();
@ -38,8 +36,7 @@ final class RemoteOccupancyHeatMapService implements OccupancyHeatMapService {
final message = e.response?.data as Map<String, dynamic>?; final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?; final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? ''; final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
[_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage); throw APIException(formattedErrorMessage);
} catch (e) { } catch (e) {
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': '); final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');

View File

@ -28,8 +28,7 @@ final class RemotePowerClampInfoService implements PowerClampInfoService {
final message = e.response?.data as Map<String, dynamic>?; final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?; final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? ''; final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
[_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage); throw APIException(formattedErrorMessage);
} catch (e) { } catch (e) {
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': '); final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');

View File

@ -6,7 +6,7 @@ import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_s
class FakeRangeOfAqiService implements RangeOfAqiService { class FakeRangeOfAqiService implements RangeOfAqiService {
@override @override
Future<List<RangeOfAqi>> load(GetRangeOfAqiParam param) async { Future<List<RangeOfAqi>> load(GetRangeOfAqiParam param) async {
return Future.delayed(const Duration(milliseconds: 800), () { return await Future.delayed(const Duration(milliseconds: 800), () {
final random = DateTime.now().millisecondsSinceEpoch; final random = DateTime.now().millisecondsSinceEpoch;
return List.generate(30, (index) { return List.generate(30, (index) {
@ -19,20 +19,14 @@ class FakeRangeOfAqiService implements RangeOfAqiService {
final avg = (min + avgDelta).clamp(0.0, 301.0); final avg = (min + avgDelta).clamp(0.0, 301.0);
final max = (avg + maxDelta).clamp(0.0, 301.0); final max = (avg + maxDelta).clamp(0.0, 301.0);
return RangeOfAqi( return RangeOfAqi(
data: [ data: [
RangeOfAqiValue( RangeOfAqiValue(type: AqiType.aqi.code, min: min, average: avg, max: max),
type: AqiType.aqi.code, min: min, average: avg, max: max), RangeOfAqiValue(type: AqiType.pm25.code, min: min, average: avg, max: max),
RangeOfAqiValue( RangeOfAqiValue(type: AqiType.pm10.code, min: min, average: avg, max: max),
type: AqiType.pm25.code, min: min, average: avg, max: max), RangeOfAqiValue(type: AqiType.hcho.code, min: min, average: avg, max: max),
RangeOfAqiValue( RangeOfAqiValue(type: AqiType.tvoc.code, min: min, average: avg, max: max),
type: AqiType.pm10.code, min: min, average: avg, max: max), RangeOfAqiValue(type: AqiType.co2.code, min: min, average: avg, max: max),
RangeOfAqiValue(
type: AqiType.hcho.code, min: min, average: avg, max: max),
RangeOfAqiValue(
type: AqiType.tvoc.code, min: min, average: avg, max: max),
RangeOfAqiValue(
type: AqiType.co2.code, min: min, average: avg, max: max),
], ],
date: date, date: date,
); );

View File

@ -3,4 +3,4 @@ import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
abstract interface class RangeOfAqiService { abstract interface class RangeOfAqiService {
Future<List<RangeOfAqi>> load(GetRangeOfAqiParam param); Future<List<RangeOfAqi>> load(GetRangeOfAqiParam param);
} }

View File

@ -2,4 +2,4 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/device_sta
abstract interface class RealtimeDeviceService { abstract interface class RealtimeDeviceService {
Stream<List<Status>> subscribe(String deviceId); Stream<List<Status>> subscribe(String deviceId);
} }

View File

@ -5,8 +5,7 @@ import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/to
import 'package:syncrow_web/services/api/api_exception.dart'; import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/services/api/http_service.dart';
class RemoteTotalEnergyConsumptionService class RemoteTotalEnergyConsumptionService implements TotalEnergyConsumptionService {
implements TotalEnergyConsumptionService {
const RemoteTotalEnergyConsumptionService(this._httpService); const RemoteTotalEnergyConsumptionService(this._httpService);
final HTTPService _httpService; final HTTPService _httpService;
@ -30,8 +29,7 @@ class RemoteTotalEnergyConsumptionService
final message = e.response?.data as Map<String, dynamic>?; final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?; final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? ''; final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
[_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage); throw APIException(formattedErrorMessage);
} catch (e) { } catch (e) {
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': '); final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');

View File

@ -75,8 +75,7 @@ class AnalyticsSidebarHeader extends StatelessWidget {
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
SelectableText( SelectableText(
context.watch<AnalyticsDevicesBloc>().state.selectedDevice?.uuid ?? context.watch<AnalyticsDevicesBloc>().state.selectedDevice?.uuid ?? 'N/A',
'N/A',
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,

View File

@ -36,8 +36,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
////////////////////////////// forget password ////////////////////////////////// ////////////////////////////// forget password //////////////////////////////////
final TextEditingController forgetEmailController = TextEditingController(); final TextEditingController forgetEmailController = TextEditingController();
final TextEditingController forgetPasswordController = final TextEditingController forgetPasswordController = TextEditingController();
TextEditingController();
final TextEditingController forgetOtp = TextEditingController(); final TextEditingController forgetOtp = TextEditingController();
final forgetFormKey = GlobalKey<FormState>(); final forgetFormKey = GlobalKey<FormState>();
final forgetEmailKey = GlobalKey<FormState>(); final forgetEmailKey = GlobalKey<FormState>();
@ -54,8 +53,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
return; return;
} }
_remainingTime = 1; _remainingTime = 1;
add(UpdateTimerEvent( add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false));
remainingTime: _remainingTime, isButtonEnabled: false));
try { try {
forgetEmailValidate = ''; forgetEmailValidate = '';
_remainingTime = (await AuthenticationAPI.sendOtp( _remainingTime = (await AuthenticationAPI.sendOtp(
@ -64,7 +62,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
} on DioException catch (e) { } on DioException catch (e) {
if (e.response!.statusCode == 400) { if (e.response!.statusCode == 400) {
final errorData = e.response!.data; final errorData = e.response!.data;
final String errorMessage = errorData['message']; String errorMessage = errorData['message'];
if (errorMessage == 'User not found') { if (errorMessage == 'User not found') {
validate = 'Invalid Credential'; validate = 'Invalid Credential';
emit(AuthInitialState()); emit(AuthInitialState());
@ -92,8 +90,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
_timer?.cancel(); _timer?.cancel();
add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true)); add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true));
} else { } else {
add(UpdateTimerEvent( add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false));
remainingTime: _remainingTime, isButtonEnabled: false));
} }
}); });
} }
@ -103,11 +100,11 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(const TimerState(isButtonEnabled: true, remainingTime: 0)); emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
} }
Future<void> changePassword( Future<void> changePassword(
ChangePasswordEvent event, Emitter<AuthState> emit) async { ChangePasswordEvent event, Emitter<AuthState> emit) async {
emit(LoadingForgetState()); emit(LoadingForgetState());
try { try {
final response = await AuthenticationAPI.verifyOtp( var response = await AuthenticationAPI.verifyOtp(
email: forgetEmailController.text, otpCode: forgetOtp.text); email: forgetEmailController.text, otpCode: forgetOtp.text);
if (response == true) { if (response == true) {
await AuthenticationAPI.forgetPassword( await AuthenticationAPI.forgetPassword(
@ -125,6 +122,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
} }
} }
String? validateCode(String? value) { String? validateCode(String? value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Code is required'; return 'Code is required';
@ -133,9 +131,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
} }
void _onUpdateTimer(UpdateTimerEvent event, Emitter<AuthState> emit) { void _onUpdateTimer(UpdateTimerEvent event, Emitter<AuthState> emit) {
emit(TimerState( emit(TimerState(isButtonEnabled: event.isButtonEnabled, remainingTime: event.remainingTime));
isButtonEnabled: event.isButtonEnabled,
remainingTime: event.remainingTime));
} }
///////////////////////////////////// login ///////////////////////////////////// ///////////////////////////////////// login /////////////////////////////////////
@ -155,7 +151,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
static UserModel? user; static UserModel? user;
bool showValidationMessage = false; bool showValidationMessage = false;
Future<void> _login(LoginButtonPressed event, Emitter<AuthState> emit) async {
void _login(LoginButtonPressed event, Emitter<AuthState> emit) async {
emit(AuthLoading()); emit(AuthLoading());
if (isChecked) { if (isChecked) {
try { try {
@ -182,7 +179,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
} }
if (token.accessTokenIsNotEmpty) { if (token.accessTokenIsNotEmpty) {
const storage = FlutterSecureStorage(); FlutterSecureStorage storage = const FlutterSecureStorage();
await storage.write( await storage.write(
key: Token.loginAccessTokenKey, value: token.accessToken); key: Token.loginAccessTokenKey, value: token.accessToken);
const FlutterSecureStorage().write( const FlutterSecureStorage().write(
@ -200,7 +197,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
} }
} }
void checkBoxToggle(
checkBoxToggle(
CheckBoxEvent event, CheckBoxEvent event,
Emitter<AuthState> emit, Emitter<AuthState> emit,
) { ) {
@ -279,12 +277,12 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Please enter your password'; return 'Please enter your password';
} }
final validationErrors = <String>[]; List<String> validationErrors = [];
if (!RegExp('^(?=.*[a-z])').hasMatch(value)) { if (!RegExp(r'^(?=.*[a-z])').hasMatch(value)) {
validationErrors.add(' - one lowercase letter'); validationErrors.add(' - one lowercase letter');
} }
if (!RegExp('^(?=.*[A-Z])').hasMatch(value)) { if (!RegExp(r'^(?=.*[A-Z])').hasMatch(value)) {
validationErrors.add(' - one uppercase letter'); validationErrors.add(' - one uppercase letter');
} }
if (!RegExp(r'^(?=.*\d)').hasMatch(value)) { if (!RegExp(r'^(?=.*\d)').hasMatch(value)) {
@ -306,7 +304,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
String? fullNameValidator(String? value) { String? fullNameValidator(String? value) {
if (value == null) return 'Full name is required'; if (value == null) return 'Full name is required';
final withoutExtraSpaces = value.replaceAll(RegExp(r'\s+'), ' ').trim(); final withoutExtraSpaces = value.replaceAll(RegExp(r"\s+"), ' ').trim();
if (withoutExtraSpaces.length < 2 || withoutExtraSpaces.length > 30) { if (withoutExtraSpaces.length < 2 || withoutExtraSpaces.length > 30) {
return 'Full name must be between 2 and 30 characters long'; return 'Full name must be between 2 and 30 characters long';
} }
@ -341,14 +339,12 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
static Future<String> getTokenAndValidate() async { static Future<String> getTokenAndValidate() async {
try { try {
const storage = FlutterSecureStorage(); const storage = FlutterSecureStorage();
final firstLaunch = await SharedPreferencesHelper.readBoolFromSP( final firstLaunch =
StringsManager.firstLaunch) ?? await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true;
true;
if (firstLaunch) { if (firstLaunch) {
storage.deleteAll(); storage.deleteAll();
} }
await SharedPreferencesHelper.saveBoolToSP( await SharedPreferencesHelper.saveBoolToSP(StringsManager.firstLaunch, false);
StringsManager.firstLaunch, false);
final value = await storage.read(key: Token.loginAccessTokenKey) ?? ''; final value = await storage.read(key: Token.loginAccessTokenKey) ?? '';
if (value.isEmpty) { if (value.isEmpty) {
return 'Token not found'; return 'Token not found';
@ -370,8 +366,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
} }
} }
Future<void> _fetchRegion( void _fetchRegion(RegionInitialEvent event, Emitter<AuthState> emit) async {
RegionInitialEvent event, Emitter<AuthState> emit) async {
try { try {
emit(AuthLoading()); emit(AuthLoading());
regionList = await AuthenticationAPI.fetchRegion(); regionList = await AuthenticationAPI.fetchRegion();
@ -394,17 +389,15 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
} }
String formattedTime(int time) { String formattedTime(int time) {
final days = (time / 86400).floor(); // 86400 seconds in a day final int days = (time / 86400).floor(); // 86400 seconds in a day
final hours = ((time % 86400) / 3600).floor(); final int hours = ((time % 86400) / 3600).floor();
final minutes = (((time % 86400) % 3600) / 60).floor(); final int minutes = (((time % 86400) % 3600) / 60).floor();
final seconds = ((time % 86400) % 3600) % 60; final int seconds = (((time % 86400) % 3600) % 60).floor();
final formattedTime = [ final String formattedTime = [
if (days > 0) '${days}d', // Append 'd' for days if (days > 0) '${days}d', // Append 'd' for days
if (days > 0 || hours > 0) if (days > 0 || hours > 0)
hours hours.toString().padLeft(2, '0'), // Show hours if there are days or hours
.toString()
.padLeft(2, '0'), // Show hours if there are days or hours
minutes.toString().padLeft(2, '0'), minutes.toString().padLeft(2, '0'),
seconds.toString().padLeft(2, '0'), seconds.toString().padLeft(2, '0'),
].join(':'); ].join(':');
@ -424,7 +417,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
return checkValidate; return checkValidate;
} }
void changeValidate( changeValidate(
ChangeValidateEvent event, ChangeValidateEvent event,
Emitter<AuthState> emit, Emitter<AuthState> emit,
) { ) {
@ -433,7 +426,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(LoginInitial()); emit(LoginInitial());
} }
void changeForgetValidate( changeForgetValidate(
ChangeValidateEvent event, ChangeValidateEvent event,
Emitter<AuthState> emit, Emitter<AuthState> emit,
) { ) {

View File

@ -46,8 +46,7 @@ class StopTimerEvent extends AuthEvent {}
class UpdateTimerEvent extends AuthEvent { class UpdateTimerEvent extends AuthEvent {
final int remainingTime; final int remainingTime;
final bool isButtonEnabled; final bool isButtonEnabled;
const UpdateTimerEvent( const UpdateTimerEvent({required this.remainingTime, required this.isButtonEnabled});
{required this.remainingTime, required this.isButtonEnabled});
} }
class ChangePasswordEvent extends AuthEvent {} class ChangePasswordEvent extends AuthEvent {}

View File

@ -56,8 +56,7 @@ class TimerState extends AuthState {
final bool isButtonEnabled; final bool isButtonEnabled;
final int remainingTime; final int remainingTime;
const TimerState( const TimerState({required this.isButtonEnabled, required this.remainingTime});
{required this.isButtonEnabled, required this.remainingTime});
@override @override
List<Object> get props => [isButtonEnabled, remainingTime]; List<Object> get props => [isButtonEnabled, remainingTime];
@ -81,7 +80,7 @@ class TimerUpdated extends AuthState {
final String formattedTime; final String formattedTime;
final bool isButtonEnabled; final bool isButtonEnabled;
const TimerUpdated({ TimerUpdated({
required this.formattedTime, required this.formattedTime,
required this.isButtonEnabled, required this.isButtonEnabled,
}); });

View File

@ -21,9 +21,9 @@ class LoginWithEmailModel {
return { return {
'email': email, 'email': email,
'password': password, 'password': password,
'platform': 'web' "platform": "web"
// 'regionUuid': regionUuid, // 'regionUuid': regionUuid,
}; };
} }
} }
//tst@tst.com //tst@tst.com

View File

@ -11,14 +11,6 @@ class Token {
final int iat; final int iat;
final int exp; final int exp;
Token(
this.accessToken,
this.refreshToken,
this.sessionId,
this.iat,
this.exp,
);
Token.emptyConstructor() Token.emptyConstructor()
: accessToken = '', : accessToken = '',
refreshToken = '', refreshToken = '',
@ -32,6 +24,14 @@ class Token {
bool get isNotEmpty => accessToken.isNotEmpty && refreshToken.isNotEmpty; bool get isNotEmpty => accessToken.isNotEmpty && refreshToken.isNotEmpty;
Token(
this.accessToken,
this.refreshToken,
this.sessionId,
this.iat,
this.exp,
);
Token.refreshToken(this.refreshToken) Token.refreshToken(this.refreshToken)
: accessToken = '', : accessToken = '',
sessionId = '', sessionId = '',
@ -40,7 +40,7 @@ class Token {
factory Token.fromJson(Map<String, dynamic> json) { factory Token.fromJson(Map<String, dynamic> json) {
//save token to secure storage //save token to secure storage
const storage = FlutterSecureStorage(); var storage = const FlutterSecureStorage();
storage.write( storage.write(
key: loginAccessTokenKey, value: json[loginAccessTokenKey] ?? ''); key: loginAccessTokenKey, value: json[loginAccessTokenKey] ?? '');
storage.write( storage.write(

View File

@ -55,21 +55,22 @@ class UserModel {
//from token //from token
factory UserModel.fromToken(Token token) { factory UserModel.fromToken(Token token) {
final tempJson = Token.decodeToken(token.accessToken); Map<String, dynamic> tempJson = Token.decodeToken(token.accessToken);
return UserModel( return UserModel(
hasAcceptedWebAgreement: null, hasAcceptedWebAgreement: null,
role: null, role: null,
webAgreementAcceptedAt: null, webAgreementAcceptedAt: null,
uuid: tempJson['uuid'].toString(), uuid: tempJson['uuid'].toString(),
email: tempJson['email'], email: tempJson['email'],
firstName: null, firstName: null,
lastName: null, lastName: null,
photoUrl: null, photoUrl: null,
phoneNumber: null, phoneNumber: null,
isEmailVerified: null, isEmailVerified: null,
isAgreementAccepted: null, isAgreementAccepted: null,
project: null); project: null
);
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {

View File

@ -36,18 +36,20 @@ class ForgetPasswordWebPage extends StatelessWidget {
); );
} }
}, },
builder: _buildForm, builder: (context, state) {
return _buildForm(context, state);
},
), ),
), ),
); );
} }
Widget _buildForm(BuildContext context, AuthState state) { Widget _buildForm(BuildContext context, AuthState state) {
late ScrollController scrollController; late ScrollController _scrollController;
scrollController = ScrollController(); _scrollController = ScrollController();
void scrollToCenter() { void _scrollToCenter() {
final middlePosition = scrollController.position.maxScrollExtent / 2; final double middlePosition = _scrollController.position.maxScrollExtent / 2;
scrollController.animateTo( _scrollController.animateTo(
middlePosition, middlePosition,
duration: const Duration(seconds: 1), duration: const Duration(seconds: 1),
curve: Curves.easeInOut, curve: Curves.easeInOut,
@ -55,25 +57,24 @@ class ForgetPasswordWebPage extends StatelessWidget {
} }
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
scrollToCenter(); _scrollToCenter();
}); });
final forgetBloc = BlocProvider.of<AuthBloc>(context); final forgetBloc = BlocProvider.of<AuthBloc>(context);
final size = MediaQuery.of(context).size; Size size = MediaQuery.of(context).size;
return FirstLayer( return FirstLayer(
second: Center( second: Center(
child: Stack( child: Stack(
children: [ children: [
if (state is AuthLoading) if (state is AuthLoading) const Center(child: CircularProgressIndicator()),
const Center(child: CircularProgressIndicator()),
ListView( ListView(
shrinkWrap: true, shrinkWrap: true,
controller: scrollController, controller: _scrollController,
children: [ children: [
Container( Container(
padding: EdgeInsets.all(size.width * 0.02), padding: EdgeInsets.all(size.width * 0.02),
margin: EdgeInsets.all(size.width * 0.09), margin: EdgeInsets.all(size.width * 0.09),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.3), color: Colors.black.withOpacity(0.3),
borderRadius: const BorderRadius.all(Radius.circular(20)), borderRadius: const BorderRadius.all(Radius.circular(20)),
), ),
child: Center( child: Center(
@ -93,22 +94,17 @@ class ForgetPasswordWebPage extends StatelessWidget {
flex: 3, flex: 3,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.1), color: Colors.white.withOpacity(0.1),
borderRadius: borderRadius: const BorderRadius.all(Radius.circular(30)),
const BorderRadius.all(Radius.circular(30)), border: Border.all(color: ColorsManager.graysColor.withOpacity(0.2)),
border: Border.all(
color: ColorsManager.graysColor
.withValues(alpha: 0.2)),
), ),
child: Form( child: Form(
key: forgetBloc.forgetFormKey, key: forgetBloc.forgetFormKey,
child: Padding( child: Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: size.width * 0.02, horizontal: size.width * 0.02, vertical: size.width * 0.003),
vertical: size.width * 0.003),
child: Column( child: Column(
mainAxisAlignment: mainAxisAlignment: MainAxisAlignment.spaceEvenly,
MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
const SizedBox(height: 10), const SizedBox(height: 10),
@ -125,9 +121,7 @@ class ForgetPasswordWebPage extends StatelessWidget {
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodySmall! .bodySmall!
.copyWith( .copyWith(fontSize: 14, fontWeight: FontWeight.w400),
fontSize: 14,
fontWeight: FontWeight.w400),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
// Column( // Column(
@ -146,90 +140,69 @@ class ForgetPasswordWebPage extends StatelessWidget {
Form( Form(
key: forgetBloc.forgetEmailKey, key: forgetBloc.forgetEmailKey,
child: Column( child: Column(
crossAxisAlignment: crossAxisAlignment: CrossAxisAlignment.start,
CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisAlignment:
MainAxisAlignment.start,
children: [ children: [
Text( Text(
'Account', "Account",
style: Theme.of(context) style: Theme.of(context).textTheme.bodySmall!.copyWith(
.textTheme fontSize: 14, fontWeight: FontWeight.w400),
.bodySmall!
.copyWith(
fontSize: 14,
fontWeight:
FontWeight.w400),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
SizedBox( SizedBox(
child: TextFormField( child: TextFormField(
controller: forgetBloc controller: forgetBloc.forgetEmailController,
.forgetEmailController, validator: forgetBloc.validateEmail,
validator: decoration: textBoxDecoration()!.copyWith(
forgetBloc.validateEmail,
decoration:
textBoxDecoration()!.copyWith(
hintText: 'Enter your email', hintText: 'Enter your email',
hintStyle: Theme.of(context) hintStyle: Theme.of(context)
.textTheme .textTheme
.bodySmall! .bodySmall!
.copyWith( .copyWith(
color: ColorsManager color: ColorsManager.grayColor,
.grayColor, fontWeight: FontWeight.w400),
fontWeight:
FontWeight.w400),
), ),
style: const TextStyle( style: const TextStyle(color: Colors.black),
color: Colors.black),
), ),
), ),
], ],
)), )),
const SizedBox(height: 20.0), const SizedBox(height: 20.0),
Column( Column(
crossAxisAlignment: crossAxisAlignment: CrossAxisAlignment.start,
CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Text( Text(
'One Time Password', "One Time Password",
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodySmall! .bodySmall!
.copyWith( .copyWith(fontSize: 14, fontWeight: FontWeight.w400),
fontSize: 14,
fontWeight: FontWeight.w400),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
SizedBox( SizedBox(
child: TextFormField( child: TextFormField(
validator: forgetBloc.validateCode, validator: forgetBloc.validateCode,
keyboardType: keyboardType: TextInputType.visiblePassword,
TextInputType.visiblePassword,
controller: forgetBloc.forgetOtp, controller: forgetBloc.forgetOtp,
decoration: decoration: textBoxDecoration()!.copyWith(
textBoxDecoration()!.copyWith(
hintText: 'Enter Code', hintText: 'Enter Code',
hintStyle: Theme.of(context) hintStyle: Theme.of(context)
.textTheme .textTheme
.bodySmall! .bodySmall!
.copyWith( .copyWith(
color: color: ColorsManager.grayColor,
ColorsManager.grayColor, fontWeight: FontWeight.w400),
fontWeight:
FontWeight.w400),
suffixIcon: SizedBox( suffixIcon: SizedBox(
width: 100, width: 100,
child: Center( child: Center(
child: InkWell( child: InkWell(
onTap: state is TimerState && onTap: state is TimerState &&
!state !state.isButtonEnabled &&
.isButtonEnabled && state.remainingTime != 1
state.remainingTime !=
1
? null ? null
: () { :
() {
if (forgetBloc if (forgetBloc
.forgetEmailKey .forgetEmailKey
.currentState! .currentState!
@ -238,31 +211,27 @@ class ForgetPasswordWebPage extends StatelessWidget {
StartTimerEvent()); StartTimerEvent());
} }
}, },
child: Text( child: Text(
'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime != 1 ? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}', 'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime != 1 ? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}',
style: TextStyle( style: TextStyle(
color: state color: state is TimerState &&
is TimerState && !state.isButtonEnabled
!state
.isButtonEnabled
? Colors.grey ? Colors.grey
: ColorsManager : ColorsManager.btnColor,
.btnColor,
), ),
), ),
), ),
), ),
), ),
), ),
style: const TextStyle( style: const TextStyle(color: Colors.black),
color: Colors.black),
), ),
), ),
if (forgetBloc.forgetValidate != if (forgetBloc.forgetValidate !=
'') // Check if there is a validation message '') // Check if there is a validation message
Padding( Padding(
padding: padding: const EdgeInsets.only(top: 8.0),
const EdgeInsets.only(top: 8.0),
child: Text( child: Text(
forgetBloc.forgetValidate, forgetBloc.forgetValidate,
style: const TextStyle( style: const TextStyle(
@ -275,44 +244,34 @@ class ForgetPasswordWebPage extends StatelessWidget {
), ),
const SizedBox(height: 20.0), const SizedBox(height: 20.0),
Column( Column(
crossAxisAlignment: crossAxisAlignment: CrossAxisAlignment.start,
CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Text( Text(
'Password', "Password",
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodySmall! .bodySmall!
.copyWith( .copyWith(fontSize: 14, fontWeight: FontWeight.w400),
fontSize: 14,
fontWeight: FontWeight.w400),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
SizedBox( SizedBox(
child: TextFormField( child: TextFormField(
obscureText: forgetBloc.obscureText, obscureText: forgetBloc.obscureText,
keyboardType: keyboardType: TextInputType.visiblePassword,
TextInputType.visiblePassword, validator: forgetBloc.passwordValidator,
validator: controller: forgetBloc.forgetPasswordController,
forgetBloc.passwordValidator, decoration: textBoxDecoration()!.copyWith(
controller: forgetBloc
.forgetPasswordController,
decoration:
textBoxDecoration()!.copyWith(
suffixIcon: IconButton( suffixIcon: IconButton(
onPressed: () { onPressed: () {
forgetBloc.add( forgetBloc.add(PasswordVisibleEvent(
PasswordVisibleEvent( newValue: forgetBloc.obscureText));
newValue: forgetBloc
.obscureText));
}, },
icon: SizedBox( icon: SizedBox(
child: SvgPicture.asset( child: SvgPicture.asset(
forgetBloc.obscureText forgetBloc.obscureText
? Assets.visiblePassword ? Assets.visiblePassword
: Assets : Assets.invisiblePassword,
.invisiblePassword,
height: 15, height: 15,
width: 15, width: 15,
), ),
@ -323,13 +282,10 @@ class ForgetPasswordWebPage extends StatelessWidget {
.textTheme .textTheme
.bodySmall! .bodySmall!
.copyWith( .copyWith(
color: color: ColorsManager.grayColor,
ColorsManager.grayColor, fontWeight: FontWeight.w400),
fontWeight:
FontWeight.w400),
), ),
style: const TextStyle( style: const TextStyle(color: Colors.black),
color: Colors.black),
), ),
), ),
], ],
@ -339,31 +295,23 @@ class ForgetPasswordWebPage extends StatelessWidget {
), ),
const SizedBox(height: 20.0), const SizedBox(height: 20.0),
Row( Row(
crossAxisAlignment: crossAxisAlignment: CrossAxisAlignment.center,
CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
SizedBox( SizedBox(
width: size.width * 0.2, width: size.width * 0.2,
child: DefaultButton( child: DefaultButton(
backgroundColor: backgroundColor: ColorsManager.btnColor,
ColorsManager.btnColor,
child: const Text('Submit'), child: const Text('Submit'),
onPressed: () { onPressed: () {
if (forgetBloc if (forgetBloc.forgetFormKey.currentState!.validate() ||
.forgetFormKey.currentState! forgetBloc.forgetEmailKey.currentState!
.validate() ||
forgetBloc.forgetEmailKey
.currentState!
.validate()) { .validate()) {
if (forgetBloc.forgetEmailKey if (forgetBloc.forgetEmailKey.currentState!
.currentState!
.validate() && .validate() &&
forgetBloc.forgetFormKey forgetBloc.forgetFormKey.currentState!
.currentState!
.validate()) { .validate()) {
forgetBloc forgetBloc.add(ChangePasswordEvent());
.add(ChangePasswordEvent());
} }
} }
}, },
@ -377,8 +325,7 @@ class ForgetPasswordWebPage extends StatelessWidget {
child: Text( child: Text(
forgetBloc.validate, forgetBloc.validate,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700, color: ColorsManager.red),
color: ColorsManager.red),
), ),
), ),
), ),
@ -390,9 +337,8 @@ class ForgetPasswordWebPage extends StatelessWidget {
child: Wrap( child: Wrap(
children: [ children: [
const Text( const Text(
'Do you have an account? ', "Do you have an account? ",
style: style: TextStyle(color: Colors.white),
TextStyle(color: Colors.white),
), ),
InkWell( InkWell(
onTap: () { onTap: () {
@ -400,7 +346,7 @@ class ForgetPasswordWebPage extends StatelessWidget {
Navigator.pop(context); Navigator.pop(context);
}, },
child: const Text( child: const Text(
'Sign in', "Sign in",
), ),
), ),
], ],
@ -426,15 +372,14 @@ class ForgetPasswordWebPage extends StatelessWidget {
)); ));
} }
Widget _buildDropdownField( Widget _buildDropdownField(BuildContext context, AuthBloc loginBloc, Size size) {
BuildContext context, AuthBloc loginBloc, Size size) { final TextEditingController textEditingController = TextEditingController();
final textEditingController = TextEditingController();
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Text( Text(
'Country/Region', "Country/Region",
style: Theme.of(context).textTheme.bodySmall!.copyWith( style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@ -453,13 +398,10 @@ class ForgetPasswordWebPage extends StatelessWidget {
builder: (FormFieldState<String> field) { builder: (FormFieldState<String> field) {
return InputDecorator( return InputDecorator(
decoration: InputDecoration( decoration: InputDecoration(
contentPadding: contentPadding: const EdgeInsets.symmetric(horizontal: 2, vertical: 10),
const EdgeInsets.symmetric(horizontal: 2, vertical: 10),
errorText: field.errorText, errorText: field.errorText,
filled: filled: true, // Ensure the dropdown is filled with the background color
true, // Ensure the dropdown is filled with the background color fillColor: ColorsManager.boxColor, // Match the dropdown container color
fillColor: ColorsManager
.boxColor, // Match the dropdown container color
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0), borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide( borderSide: BorderSide(
@ -470,16 +412,14 @@ class ForgetPasswordWebPage extends StatelessWidget {
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0), borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide( borderSide: BorderSide(
color: color: field.hasError ? Colors.red : ColorsManager.grayColor,
field.hasError ? Colors.red : ColorsManager.grayColor,
width: 1.5, width: 1.5,
), ),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0), borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide( borderSide: BorderSide(
color: color: field.hasError ? Colors.red : ColorsManager.grayColor,
field.hasError ? Colors.red : ColorsManager.grayColor,
width: 1.5, width: 1.5,
), ),
), ),
@ -506,11 +446,10 @@ class ForgetPasswordWebPage extends StatelessWidget {
return DropdownMenuItem<String>( return DropdownMenuItem<String>(
value: region.id, value: region.id,
child: Text( child: Text(
style: style: Theme.of(context).textTheme.bodyMedium!.copyWith(
Theme.of(context).textTheme.bodyMedium!.copyWith( fontSize: 14,
fontSize: 14, fontWeight: FontWeight.w400,
fontWeight: FontWeight.w400, ),
),
region.name, region.name,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
maxLines: 1, maxLines: 1,
@ -523,8 +462,7 @@ class ForgetPasswordWebPage extends StatelessWidget {
onChanged: (String? value) { onChanged: (String? value) {
if (value != null) { if (value != null) {
loginBloc.add(SelectRegionEvent(val: value)); loginBloc.add(SelectRegionEvent(val: value));
field.didChange( field.didChange(value); // Notify the form field of the change
value); // Notify the form field of the change
} }
}, },
buttonStyleData: const ButtonStyleData( buttonStyleData: const ButtonStyleData(
@ -548,8 +486,7 @@ class ForgetPasswordWebPage extends StatelessWidget {
searchInnerWidgetHeight: 50, searchInnerWidgetHeight: 50,
searchInnerWidget: Container( searchInnerWidget: Container(
height: 50, height: 50,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
horizontal: 8, vertical: 4),
child: TextFormField( child: TextFormField(
style: const TextStyle(color: Colors.black), style: const TextStyle(color: Colors.black),
controller: textEditingController, controller: textEditingController,
@ -563,8 +500,7 @@ class ForgetPasswordWebPage extends StatelessWidget {
), ),
), ),
searchMatchFn: (item, searchValue) { searchMatchFn: (item, searchValue) {
final regionName = final regionName = (item.child as Text).data?.toLowerCase() ?? '';
(item.child as Text).data?.toLowerCase() ?? '';
final search = searchValue.toLowerCase().trim(); final search = searchValue.toLowerCase().trim();
return regionName.contains(search); return regionName.contains(search);
}, },

View File

@ -79,7 +79,7 @@ class LoginMobilePage extends StatelessWidget {
child: Container( child: Container(
margin: const EdgeInsets.all(50), margin: const EdgeInsets.all(50),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.3), color: Colors.black.withOpacity(0.3),
borderRadius: const BorderRadius.all(Radius.circular(20))), borderRadius: const BorderRadius.all(Radius.circular(20))),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -93,15 +93,12 @@ class LoginMobilePage extends StatelessWidget {
), ),
), ),
Container( Container(
margin: const EdgeInsets.all(30), margin: EdgeInsets.all(30),
padding: const EdgeInsets.all(20), padding: EdgeInsets.all(20),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.1), color: Colors.white.withOpacity(0.1),
borderRadius: borderRadius: const BorderRadius.all(Radius.circular(30)),
const BorderRadius.all(Radius.circular(30)), border: Border.all(color: ColorsManager.graysColor.withOpacity(0.2))),
border: Border.all(
color: ColorsManager.graysColor
.withValues(alpha: 0.2))),
child: Form( child: Form(
key: loginBloc.loginFormKey, key: loginBloc.loginFormKey,
child: Column( child: Column(
@ -112,9 +109,7 @@ class LoginMobilePage extends StatelessWidget {
const Text( const Text(
'Login', 'Login',
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
fontSize: 24,
fontWeight: FontWeight.bold),
), ),
const SizedBox(height: 30), const SizedBox(height: 30),
// Column( // Column(
@ -160,15 +155,15 @@ class LoginMobilePage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Text( Text(
'Email', "Email",
style: Theme.of(context).textTheme.bodySmall, style: Theme.of(context).textTheme.bodySmall,
), ),
SizedBox( SizedBox(
child: TextFormField( child: TextFormField(
validator: loginBloc.validateEmail, validator: loginBloc.validateEmail,
controller: loginBloc.loginEmailController, controller: loginBloc.loginEmailController,
decoration: textBoxDecoration()! decoration:
.copyWith(hintText: 'Enter your email'), textBoxDecoration()!.copyWith(hintText: 'Enter your email'),
style: const TextStyle(color: Colors.black), style: const TextStyle(color: Colors.black),
), ),
), ),
@ -180,7 +175,7 @@ class LoginMobilePage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Text( Text(
'Password', "Password",
style: Theme.of(context).textTheme.bodySmall, style: Theme.of(context).textTheme.bodySmall,
), ),
SizedBox( SizedBox(
@ -188,8 +183,7 @@ class LoginMobilePage extends StatelessWidget {
validator: loginBloc.validatePassword, validator: loginBloc.validatePassword,
obscureText: loginBloc.obscureText, obscureText: loginBloc.obscureText,
keyboardType: TextInputType.visiblePassword, keyboardType: TextInputType.visiblePassword,
controller: controller: loginBloc.loginPasswordController,
loginBloc.loginPasswordController,
decoration: textBoxDecoration()!.copyWith( decoration: textBoxDecoration()!.copyWith(
hintText: 'At least 8 characters', hintText: 'At least 8 characters',
), ),
@ -207,19 +201,16 @@ class LoginMobilePage extends StatelessWidget {
children: [ children: [
InkWell( InkWell(
onTap: () { onTap: () {
Navigator.of(context) Navigator.of(context).push(MaterialPageRoute(
.push(MaterialPageRoute( builder: (context) => const ForgetPasswordPage(),
builder: (context) =>
const ForgetPasswordPage(),
)); ));
}, },
child: Text( child: Text(
'Forgot Password?', "Forgot Password?",
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodySmall! .bodySmall!
.copyWith( .copyWith(color: ColorsManager.blackColor),
color: ColorsManager.blackColor),
), ),
), ),
], ],
@ -230,15 +221,13 @@ class LoginMobilePage extends StatelessWidget {
Transform.scale( Transform.scale(
scale: 1.2, // Adjust the scale as needed scale: 1.2, // Adjust the scale as needed
child: Checkbox( child: Checkbox(
fillColor: WidgetStateProperty.all<Color>( fillColor: MaterialStateProperty.all<Color>(Colors.white),
Colors.white),
activeColor: Colors.white, activeColor: Colors.white,
value: loginBloc.isChecked, value: loginBloc.isChecked,
checkColor: Colors.black, checkColor: Colors.black,
shape: const CircleBorder(), shape: const CircleBorder(),
onChanged: (bool? newValue) { onChanged: (bool? newValue) {
loginBloc.add( loginBloc.add(CheckBoxEvent(newValue: newValue));
CheckBoxEvent(newValue: newValue));
}, },
), ),
), ),
@ -247,37 +236,30 @@ class LoginMobilePage extends StatelessWidget {
child: RichText( child: RichText(
text: TextSpan( text: TextSpan(
text: 'Agree to ', text: 'Agree to ',
style: style: const TextStyle(color: Colors.white),
const TextStyle(color: Colors.white),
children: [ children: [
TextSpan( TextSpan(
text: '(Terms of Service)', text: '(Terms of Service)',
style: const TextStyle( style: const TextStyle(color: Colors.black),
color: Colors.black),
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () { ..onTap = () {
loginBloc.launchURL( loginBloc.launchURL('https://example.com/terms');
'https://example.com/terms');
}, },
), ),
TextSpan( TextSpan(
text: ' (Legal Statement)', text: ' (Legal Statement)',
style: const TextStyle( style: const TextStyle(color: Colors.black),
color: Colors.black),
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () { ..onTap = () {
loginBloc.launchURL( loginBloc.launchURL('https://example.com/legal');
'https://example.com/legal');
}, },
), ),
TextSpan( TextSpan(
text: ' (Privacy Statement)', text: ' (Privacy Statement)',
style: const TextStyle( style: const TextStyle(color: Colors.black),
color: Colors.black),
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () { ..onTap = () {
loginBloc.launchURL( loginBloc.launchURL('https://example.com/privacy');
'https://example.com/privacy');
}, },
), ),
], ],
@ -294,14 +276,11 @@ class LoginMobilePage extends StatelessWidget {
: ColorsManager.grayColor, : ColorsManager.grayColor,
child: const Text('Sign in'), child: const Text('Sign in'),
onPressed: () { onPressed: () {
if (loginBloc.loginFormKey.currentState! if (loginBloc.loginFormKey.currentState!.validate()) {
.validate()) {
loginBloc.add( loginBloc.add(
LoginButtonPressed( LoginButtonPressed(
username: username: loginBloc.loginEmailController.text,
loginBloc.loginEmailController.text, password: loginBloc.loginPasswordController.text,
password: loginBloc
.loginPasswordController.text,
), ),
); );
} }
@ -316,11 +295,10 @@ class LoginMobilePage extends StatelessWidget {
Flexible( Flexible(
child: Text( child: Text(
"Don't you have an account? ", "Don't you have an account? ",
style: TextStyle( style: TextStyle(color: Colors.white, fontSize: 13),
color: Colors.white, fontSize: 13),
)), )),
Text( Text(
'Sign up', "Sign up",
), ),
], ],
), ),

View File

@ -24,8 +24,7 @@ class LoginWebPage extends StatefulWidget {
State<LoginWebPage> createState() => _LoginWebPageState(); State<LoginWebPage> createState() => _LoginWebPageState();
} }
class _LoginWebPageState extends State<LoginWebPage> class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout {
with HelperResponsiveLayout {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -43,7 +42,9 @@ class _LoginWebPageState extends State<LoginWebPage>
); );
} }
}, },
builder: _buildLoginForm, builder: (context, state) {
return _buildLoginForm(context, state);
},
), ),
), ),
); );
@ -53,12 +54,12 @@ class _LoginWebPageState extends State<LoginWebPage>
final loginBloc = BlocProvider.of<AuthBloc>(context); final loginBloc = BlocProvider.of<AuthBloc>(context);
final isSmallScreen = isSmallScreenSize(context); final isSmallScreen = isSmallScreenSize(context);
final isMediumScreen = isMediumScreenSize(context); final isMediumScreen = isMediumScreenSize(context);
final size = MediaQuery.of(context).size; Size size = MediaQuery.of(context).size;
late ScrollController scrollController; late ScrollController scrollController;
scrollController = ScrollController(); scrollController = ScrollController();
void scrollToCenter() { void scrollToCenter() {
final middlePosition = scrollController.position.maxScrollExtent / 2; final double middlePosition = scrollController.position.maxScrollExtent / 2;
scrollController.animateTo( scrollController.animateTo(
middlePosition, middlePosition,
duration: const Duration(seconds: 1), duration: const Duration(seconds: 1),
@ -83,7 +84,7 @@ class _LoginWebPageState extends State<LoginWebPage>
padding: EdgeInsets.all(size.width * 0.02), padding: EdgeInsets.all(size.width * 0.02),
margin: EdgeInsets.all(size.width * 0.05), margin: EdgeInsets.all(size.width * 0.05),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.3), color: Colors.black.withOpacity(0.3),
borderRadius: const BorderRadius.all(Radius.circular(20)), borderRadius: const BorderRadius.all(Radius.circular(20)),
), ),
child: Center( child: Center(
@ -120,8 +121,7 @@ class _LoginWebPageState extends State<LoginWebPage>
const Spacer(), const Spacer(),
Expanded( Expanded(
flex: 2, flex: 2,
child: _buildLoginFormFields( child: _buildLoginFormFields(context, loginBloc, size),
context, loginBloc, size),
), ),
const Spacer(), const Spacer(),
], ],
@ -132,26 +132,23 @@ class _LoginWebPageState extends State<LoginWebPage>
), ),
), ),
), ),
if (state is AuthLoading) if (state is AuthLoading) const Center(child: CircularProgressIndicator())
const Center(child: CircularProgressIndicator())
], ],
); );
} }
Widget _buildLoginFormFields( Widget _buildLoginFormFields(BuildContext context, AuthBloc loginBloc, Size size) {
BuildContext context, AuthBloc loginBloc, Size size) {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.1), color: Colors.white.withOpacity(0.1),
borderRadius: const BorderRadius.all(Radius.circular(30)), borderRadius: const BorderRadius.all(Radius.circular(30)),
border: border: Border.all(color: ColorsManager.graysColor.withOpacity(0.2)),
Border.all(color: ColorsManager.graysColor.withValues(alpha: 0.2)),
), ),
child: Form( child: Form(
key: loginBloc.loginFormKey, key: loginBloc.loginFormKey,
child: Padding( child: Padding(
padding: EdgeInsets.symmetric( padding:
horizontal: size.width * 0.02, vertical: size.width * 0.003), EdgeInsets.symmetric(horizontal: size.width * 0.02, vertical: size.width * 0.003),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -179,15 +176,14 @@ class _LoginWebPageState extends State<LoginWebPage>
); );
} }
Widget _buildDropdownField( Widget _buildDropdownField(BuildContext context, AuthBloc loginBloc, Size size) {
BuildContext context, AuthBloc loginBloc, Size size) { final TextEditingController textEditingController = TextEditingController();
final textEditingController = TextEditingController();
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Text( Text(
'Country/Region', "Country/Region",
style: Theme.of(context).textTheme.bodySmall!.copyWith( style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@ -250,8 +246,7 @@ class _LoginWebPageState extends State<LoginWebPage>
searchInnerWidgetHeight: 50, searchInnerWidgetHeight: 50,
searchInnerWidget: Container( searchInnerWidget: Container(
height: 50, height: 50,
padding: padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: TextFormField( child: TextFormField(
style: const TextStyle(color: Colors.black), style: const TextStyle(color: Colors.black),
controller: textEditingController, controller: textEditingController,
@ -266,8 +261,7 @@ class _LoginWebPageState extends State<LoginWebPage>
), ),
searchMatchFn: (item, searchValue) { searchMatchFn: (item, searchValue) {
// Use the item's child text (region name) for searching. // Use the item's child text (region name) for searching.
final regionName = final regionName = (item.child as Text).data?.toLowerCase() ?? '';
(item.child as Text).data?.toLowerCase() ?? '';
final search = searchValue.toLowerCase().trim(); final search = searchValue.toLowerCase().trim();
// Debugging print statement to ensure values are captured correctly. // Debugging print statement to ensure values are captured correctly.
// Return true if the region name contains the search term. // Return true if the region name contains the search term.
@ -292,7 +286,7 @@ class _LoginWebPageState extends State<LoginWebPage>
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Text( Text(
'Email', "Email",
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodySmall! .bodySmall!
@ -309,9 +303,10 @@ class _LoginWebPageState extends State<LoginWebPage>
decoration: textBoxDecoration()!.copyWith( decoration: textBoxDecoration()!.copyWith(
errorStyle: const TextStyle(height: 0), errorStyle: const TextStyle(height: 0),
hintText: 'Enter your email address', hintText: 'Enter your email address',
hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith( hintStyle: Theme.of(context)
color: ColorsManager.grayColor, .textTheme
fontWeight: FontWeight.w400)), .bodySmall!
.copyWith(color: ColorsManager.grayColor, fontWeight: FontWeight.w400)),
style: const TextStyle(color: Colors.black), style: const TextStyle(color: Colors.black),
), ),
), ),
@ -325,7 +320,7 @@ class _LoginWebPageState extends State<LoginWebPage>
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Text( Text(
'Password', "Password",
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodySmall! .bodySmall!
@ -343,7 +338,7 @@ class _LoginWebPageState extends State<LoginWebPage>
controller: loginBloc.loginPasswordController, controller: loginBloc.loginPasswordController,
onFieldSubmitted: (value) { onFieldSubmitted: (value) {
if (loginBloc.loginFormKey.currentState!.validate()) { if (loginBloc.loginFormKey.currentState!.validate()) {
loginBloc.add(LoginButtonPressed( loginBloc.add(LoginButtonPressed(
username: loginBloc.loginEmailController.text, username: loginBloc.loginEmailController.text,
password: value, password: value,
)); ));
@ -353,18 +348,17 @@ class _LoginWebPageState extends State<LoginWebPage>
}, },
decoration: textBoxDecoration()!.copyWith( decoration: textBoxDecoration()!.copyWith(
hintText: 'At least 8 characters', hintText: 'At least 8 characters',
hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith( hintStyle: Theme.of(context)
color: ColorsManager.grayColor, fontWeight: FontWeight.w400), .textTheme
.bodySmall!
.copyWith(color: ColorsManager.grayColor, fontWeight: FontWeight.w400),
suffixIcon: IconButton( suffixIcon: IconButton(
onPressed: () { onPressed: () {
loginBloc.add( loginBloc.add(PasswordVisibleEvent(newValue: loginBloc.obscureText));
PasswordVisibleEvent(newValue: loginBloc.obscureText));
}, },
icon: SizedBox( icon: SizedBox(
child: SvgPicture.asset( child: SvgPicture.asset(
loginBloc.obscureText loginBloc.obscureText ? Assets.visiblePassword : Assets.invisiblePassword,
? Assets.visiblePassword
: Assets.invisiblePassword,
height: 15, height: 15,
width: 15, width: 15,
), ),
@ -391,11 +385,11 @@ class _LoginWebPageState extends State<LoginWebPage>
)); ));
}, },
child: Text( child: Text(
'Forgot Password?', "Forgot Password?",
style: Theme.of(context).textTheme.bodySmall!.copyWith( style: Theme.of(context)
color: Colors.black, .textTheme
fontSize: 14, .bodySmall!
fontWeight: FontWeight.w400), .copyWith(color: Colors.black, fontSize: 14, fontWeight: FontWeight.w400),
), ),
), ),
], ],
@ -459,8 +453,7 @@ class _LoginWebPageState extends State<LoginWebPage>
); );
} }
Widget _buildSignInButton( Widget _buildSignInButton(BuildContext context, AuthBloc loginBloc, Size size) {
BuildContext context, AuthBloc loginBloc, Size size) {
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -474,7 +467,7 @@ class _LoginWebPageState extends State<LoginWebPage>
fontSize: 14, fontSize: 14,
color: loginBloc.checkValidate color: loginBloc.checkValidate
? ColorsManager.whiteColors ? ColorsManager.whiteColors
: ColorsManager.whiteColors.withValues(alpha: 0.2), : ColorsManager.whiteColors.withOpacity(0.2),
)), )),
onPressed: () { onPressed: () {
if (loginBloc.loginFormKey.currentState!.validate()) { if (loginBloc.loginFormKey.currentState!.validate()) {
@ -501,8 +494,7 @@ class _LoginWebPageState extends State<LoginWebPage>
SizedBox( SizedBox(
child: Text( child: Text(
loginBloc.validate, loginBloc.validate,
style: const TextStyle( style: const TextStyle(fontWeight: FontWeight.w700, color: ColorsManager.red),
fontWeight: FontWeight.w700, color: ColorsManager.red),
), ),
) )
], ],

View File

@ -108,64 +108,66 @@ class _DynamicTableState extends State<AccessDeviceTable> {
child: Row( child: Row(
children: [ children: [
if (widget.withCheckBox) _buildSelectAllCheckbox(), if (widget.withCheckBox) _buildSelectAllCheckbox(),
...widget.headers.map(_buildTableHeaderCell), ...widget.headers
.map((header) => _buildTableHeaderCell(header)),
], ],
), ),
), ),
if (widget.isEmpty) widget.isEmpty
Expanded( ? Expanded(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Column( Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
SvgPicture.asset(Assets.emptyTable), Column(
const SizedBox( children: [
height: 15, SvgPicture.asset(Assets.emptyTable),
const SizedBox(
height: 15,
),
Text(
// no password
widget.tableName == 'AccessManagement'
? 'No Password '
: 'No Devices',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.grayColor),
)
],
), ),
Text(
// no password
widget.tableName == 'AccessManagement'
? 'No Password '
: 'No Devices',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: ColorsManager.grayColor),
)
], ],
), ),
], ],
), ),
], )
), : Expanded(
) child: Container(
else color: Colors.white,
Expanded( child: ListView.builder(
child: ColoredBox( shrinkWrap: true,
color: Colors.white, itemCount: widget.data.length,
child: ListView.builder( itemBuilder: (context, index) {
shrinkWrap: true, final row = widget.data[index];
itemCount: widget.data.length, return Row(
itemBuilder: (context, index) { children: [
final row = widget.data[index]; if (widget.withCheckBox)
return Row( _buildRowCheckbox(
children: [ index, widget.size.height * 0.10),
if (widget.withCheckBox) ...row.map((cell) => _buildTableCell(
_buildRowCheckbox( cell.toString(),
index, widget.size.height * 0.10), widget.size.height * 0.10)),
...row.map((cell) => _buildTableCell( ],
cell.toString(), widget.size.height * 0.10)), );
], },
); ),
}, ),
), ),
),
),
], ],
), ),
), ),
@ -243,7 +245,7 @@ class _DynamicTableState extends State<AccessDeviceTable> {
} }
Widget _buildTableCell(String content, double size) { Widget _buildTableCell(String content, double size) {
final isBatteryLevel = content.endsWith('%'); bool isBatteryLevel = content.endsWith('%');
double? batteryLevel; double? batteryLevel;
if (isBatteryLevel) { if (isBatteryLevel) {

View File

@ -22,21 +22,17 @@ class CancelButton extends StatelessWidget {
return ElevatedButton( return ElevatedButton(
onPressed: onPressed, onPressed: onPressed,
style: ButtonStyle( style: ButtonStyle(
backgroundColor: backgroundColor: WidgetStateProperty.all(ColorsManager.boxColor), // White background
WidgetStateProperty.all(ColorsManager.boxColor), // White background foregroundColor: WidgetStateProperty.all(Colors.black), // Black text color
foregroundColor:
WidgetStateProperty.all(Colors.black), // Black text color
shape: WidgetStateProperty.all<RoundedRectangleBorder>( shape: WidgetStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder( RoundedRectangleBorder(
borderRadius: BorderRadius.circular(borderRadius ?? 10), borderRadius: BorderRadius.circular(borderRadius ?? 10),
side: side: const BorderSide(color: ColorsManager.boxColor), // Black border
const BorderSide(color: ColorsManager.boxColor), // Black border
), ),
), ),
fixedSize: WidgetStateProperty.all( fixedSize: WidgetStateProperty.all(Size(width ?? 50, height ?? 40)), // Set button height
Size(width ?? 50, height ?? 40)), // Set button height
), ),
child: Text(label), // Dynamic label child: Text(label), // Dynamic label
); );
} }
} }

View File

@ -64,7 +64,7 @@ class DefaultButton extends StatelessWidget {
(Set<WidgetState> states) { (Set<WidgetState> states) {
return enabled return enabled
? backgroundColor ?? ColorsManager.primaryColor ? backgroundColor ?? ColorsManager.primaryColor
: Colors.black.withValues(alpha: 0.2); : Colors.black.withOpacity(0.2);
}), }),
shape: WidgetStateProperty.all( shape: WidgetStateProperty.all(
RoundedRectangleBorder( RoundedRectangleBorder(

View File

@ -34,7 +34,7 @@ class CurtainToggle extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ClipOval( ClipOval(
child: ColoredBox( child: Container(
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
child: SvgPicture.asset( child: SvgPicture.asset(
Assets.curtainIcon, Assets.curtainIcon,
@ -52,7 +52,7 @@ class CurtainToggle extends StatelessWidget {
width: 35, width: 35,
child: CupertinoSwitch( child: CupertinoSwitch(
value: value, value: value,
activeTrackColor: ColorsManager.dialogBlueTitle, activeColor: ColorsManager.dialogBlueTitle,
onChanged: onChanged, onChanged: onChanged,
), ),
), ),

View File

@ -12,7 +12,7 @@ Future<void> showCustomDialog({
double? iconWidth, double? iconWidth,
VoidCallback? onOkPressed, VoidCallback? onOkPressed,
bool barrierDismissible = false, bool barrierDismissible = false,
List<Widget>? actions, List<Widget>? actions,
}) { }) {
return showDialog( return showDialog(
context: context, context: context,
@ -40,20 +40,14 @@ Future<void> showCustomDialog({
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.headlineLarge! .headlineLarge!
.copyWith( .copyWith(fontSize: 20, fontWeight: FontWeight.w400, color: Colors.black),
fontSize: 20,
fontWeight: FontWeight.w400,
color: Colors.black),
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.only(top: 8.0), padding: const EdgeInsets.only(top: 8.0),
child: Text( child: Text(
message, message,
style: Theme.of(context) style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: Colors.black),
.textTheme
.bodyMedium!
.copyWith(color: Colors.black),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),

View File

@ -20,8 +20,8 @@ class DynamicTable extends StatefulWidget {
final void Function(int, bool, dynamic)? onRowSelected; final void Function(int, bool, dynamic)? onRowSelected;
final List<String>? initialSelectedIds; final List<String>? initialSelectedIds;
final int uuidIndex; final int uuidIndex;
final void Function(dynamic selectedRows)? onSelectionChanged; final Function(dynamic selectedRows)? onSelectionChanged;
final void Function(int rowIndex)? onSettingsPressed; final Function(int rowIndex)? onSettingsPressed;
const DynamicTable({ const DynamicTable({
super.key, super.key,
required this.headers, required this.headers,
@ -79,10 +79,10 @@ class _DynamicTableState extends State<DynamicTable> {
// Check if the old and new lists are the same // Check if the old and new lists are the same
if (oldList.length != newList.length) return false; if (oldList.length != newList.length) return false;
for (var i = 0; i < oldList.length; i++) { for (int i = 0; i < oldList.length; i++) {
if (oldList[i].length != newList[i].length) return false; if (oldList[i].length != newList[i].length) return false;
for (var j = 0; j < oldList[i].length; j++) { for (int j = 0; j < oldList[i].length; j++) {
if (oldList[i][j] != newList[i][j]) return false; if (oldList[i][j] != newList[i][j]) return false;
} }
} }
@ -162,7 +162,7 @@ class _DynamicTableState extends State<DynamicTable> {
child: SingleChildScrollView( child: SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
controller: _horizontalBodyScrollController, controller: _horizontalBodyScrollController,
child: ColoredBox( child: Container(
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
child: SizedBox( child: SizedBox(
width: widget.size.width, width: widget.size.width,
@ -184,7 +184,7 @@ class _DynamicTableState extends State<DynamicTable> {
rowIndex: rowIndex, rowIndex: rowIndex,
columnIndex: entry.key, columnIndex: entry.key,
); );
}), }).toList(),
], ],
); );
}), }),
@ -304,13 +304,13 @@ class _DynamicTableState extends State<DynamicTable> {
required int rowIndex, required int rowIndex,
required int columnIndex, required int columnIndex,
}) { }) {
final isBatteryLevel = content.endsWith('%'); bool isBatteryLevel = content.endsWith('%');
double? batteryLevel; double? batteryLevel;
if (isBatteryLevel) { if (isBatteryLevel) {
batteryLevel = double.tryParse(content.replaceAll('%', '').trim()); batteryLevel = double.tryParse(content.replaceAll('%', '').trim());
} }
final isSettingsColumn = widget.headers[columnIndex] == 'Settings'; bool isSettingsColumn = widget.headers[columnIndex] == 'Settings';
if (isSettingsColumn) { if (isSettingsColumn) {
return buildSettingsIcon( return buildSettingsIcon(
@ -404,7 +404,7 @@ class _DynamicTableState extends State<DynamicTable> {
borderRadius: BorderRadius.circular(height / 2), borderRadius: BorderRadius.circular(height / 2),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.17), color: Colors.black.withOpacity(0.17),
blurRadius: 14, blurRadius: 14,
offset: const Offset(0, 4), offset: const Offset(0, 4),
), ),
@ -416,10 +416,11 @@ class _DynamicTableState extends State<DynamicTable> {
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Center( child: Center(
child: SvgPicture.asset( child: SvgPicture.asset(
Assets.settings, Assets.settings, // ضع المسار الصحيح هنا
width: 40, width: 40,
height: 22, height: 22,
color: ColorsManager.primaryColor, color: ColorsManager
.primaryColor, // نفس لون الأيقونة في الصورة
), ),
), ),
), ),

View File

@ -36,7 +36,7 @@ class FilterWidget extends StatelessWidget {
color: ColorsManager.boxColor, color: ColorsManager.boxColor,
border: Border.all( border: Border.all(
color: isSelected color: isSelected
? ColorsManager.blueColor.withValues(alpha: 0.8) ? ColorsManager.blueColor.withOpacity(0.8)
: Colors.transparent, : Colors.transparent,
width: 2.0, width: 2.0,
), ),
@ -48,7 +48,7 @@ class FilterWidget extends StatelessWidget {
tabs[index], tabs[index],
style: TextStyle( style: TextStyle(
color: isSelected color: isSelected
? ColorsManager.blueColor.withValues(alpha: 0.8) ? ColorsManager.blueColor.withOpacity(0.8)
: Colors.black, : Colors.black,
), ),
), ),

View File

@ -1,3 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class HourPickerDialog extends StatefulWidget { class HourPickerDialog extends StatefulWidget {
@ -16,7 +18,7 @@ class _HourPickerDialogState extends State<HourPickerDialog> {
void initState() { void initState() {
super.initState(); super.initState();
// Initialize the selectedHour with the initial time passed to the dialog // Initialize the selectedHour with the initial time passed to the dialog
selectedHour = '${widget.initialTime.hour.toString().padLeft(2, '0')}:00'; selectedHour = widget.initialTime.hour.toString().padLeft(2, '0') + ':00';
} }
@override @override
@ -26,7 +28,7 @@ class _HourPickerDialogState extends State<HourPickerDialog> {
content: DropdownButton<String>( content: DropdownButton<String>(
value: selectedHour, // Show the currently selected hour value: selectedHour, // Show the currently selected hour
items: List.generate(24, (index) { items: List.generate(24, (index) {
final hour = index.toString().padLeft(2, '0'); String hour = index.toString().padLeft(2, '0');
return DropdownMenuItem<String>( return DropdownMenuItem<String>(
value: '$hour:00', value: '$hour:00',
child: Text('$hour:00'), child: Text('$hour:00'),
@ -35,16 +37,14 @@ class _HourPickerDialogState extends State<HourPickerDialog> {
onChanged: (String? newValue) { onChanged: (String? newValue) {
if (newValue != null) { if (newValue != null) {
setState(() { setState(() {
selectedHour = selectedHour = newValue; // Update the selected hour without closing the dialog
newValue; // Update the selected hour without closing the dialog
}); });
} }
}, },
), ),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.of(context) onPressed: () => Navigator.of(context).pop(null), // Close the dialog without selection
.pop(null), // Close the dialog without selection
child: const Text('Cancel'), child: const Text('Cancel'),
), ),
TextButton( TextButton(

Some files were not shown because too many files have changed in this diff Show More