Merge branch 'dev' of https://github.com/SyncrowIOT/web into feat/refactoring

This commit is contained in:
hannathkadher
2024-10-31 09:26:23 +04:00
49 changed files with 2973 additions and 326 deletions
+110 -48
View File
@@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/access_management/bloc/access_state.dart';
import 'package:syncrow_web/pages/access_management/model/password_model.dart';
import 'package:syncrow_web/pages/common/hour_picker_dialog.dart';
import 'package:syncrow_web/services/access_mang_api.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/snack_bar.dart';
@@ -26,7 +27,8 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
List<PasswordModel> filteredData = [];
List<PasswordModel> data = [];
Future<void> _onFetchTableData(FetchTableData event, Emitter<AccessState> emit) async {
Future<void> _onFetchTableData(
FetchTableData event, Emitter<AccessState> emit) async {
try {
emit(AccessLoaded());
data = await AccessMangApi().fetchVisitorPassword();
@@ -39,19 +41,28 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
}
void updateTabsCount() {
int toBeEffectiveCount =
data.where((item) => item.passwordStatus.value == 'To be effective').length;
int effectiveCount = data.where((item) => item.passwordStatus.value == 'Effective').length;
int expiredCount = data.where((item) => item.passwordStatus.value == 'Expired').length;
int toBeEffectiveCount = data
.where((item) => item.passwordStatus.value == 'To be effective')
.length;
int effectiveCount =
data.where((item) => item.passwordStatus.value == 'Effective').length;
int expiredCount =
data.where((item) => item.passwordStatus.value == 'Expired').length;
tabs[1] = 'To Be Effective ($toBeEffectiveCount)';
tabs[2] = 'Effective ($effectiveCount)';
tabs[3] = 'Expired ($expiredCount)';
}
int selectedIndex = 0;
final List<String> tabs = ['All', 'To Be Effective (0)', 'Effective (0)', 'Expired'];
final List<String> tabs = [
'All',
'To Be Effective (0)',
'Effective (0)',
'Expired'
];
Future selectFilterTap(TabChangedEvent event, Emitter<AccessState> emit) async {
Future selectFilterTap(
TabChangedEvent event, Emitter<AccessState> emit) async {
try {
emit(AccessLoaded());
selectedIndex = event.selectedIndex;
@@ -73,6 +84,23 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
initialDate: DateTime.now(),
firstDate: DateTime.now().add(const Duration(days: -5095)),
lastDate: DateTime.now().add(const Duration(days: 2095)),
builder: (BuildContext context, Widget? child) {
return Theme(
data: ThemeData.light().copyWith(
colorScheme: ColorScheme.light(
primary: ColorsManager.blackColor,
onPrimary: Colors.white,
onSurface: ColorsManager.grayColor,
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: Colors.blue,
),
),
),
child: child!,
);
},
);
if (picked != null) {
final TimeOfDay? timePicked = await showHourPicker(
@@ -88,16 +116,20 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
timePicked.hour,
timePicked.minute,
);
final int selectedTimestamp = selectedDateTime.millisecondsSinceEpoch ~/ 1000;
final int selectedTimestamp =
selectedDateTime.millisecondsSinceEpoch ~/ 1000;
if (event.isStart) {
if (expirationTimeTimeStamp != null && selectedTimestamp > expirationTimeTimeStamp!) {
CustomSnackBar.displaySnackBar('Effective Time cannot be later than Expiration Time.');
if (expirationTimeTimeStamp != null &&
selectedTimestamp > expirationTimeTimeStamp!) {
CustomSnackBar.displaySnackBar(
'Effective Time cannot be later than Expiration Time.');
} else {
startTime = selectedDateTime.toString().split('.').first;
effectiveTimeTimeStamp = selectedTimestamp;
}
} else {
if (effectiveTimeTimeStamp != null && selectedTimestamp < effectiveTimeTimeStamp!) {
if (effectiveTimeTimeStamp != null &&
selectedTimestamp < effectiveTimeTimeStamp!) {
CustomSnackBar.displaySnackBar(
'Expiration Time cannot be earlier than Effective Time.');
} else {
@@ -110,7 +142,8 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
emit(ChangeTimeState());
}
Future<void> _filterData(FilterDataEvent event, Emitter<AccessState> emit) async {
Future<void> _filterData(
FilterDataEvent event, Emitter<AccessState> emit) async {
emit(AccessLoaded());
try {
// Convert search text to lower case for case-insensitive search
@@ -119,29 +152,40 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
filteredData = data.where((item) {
bool matchesCriteria = true;
// Convert timestamp to DateTime and extract date component
DateTime effectiveDate =
DateTime.fromMillisecondsSinceEpoch(int.parse(item.effectiveTime.toString()) * 1000)
.toUtc()
.toLocal();
DateTime invalidDate =
DateTime.fromMillisecondsSinceEpoch(int.parse(item.invalidTime.toString()) * 1000)
.toUtc()
.toLocal();
DateTime effectiveDateAndTime = DateTime(effectiveDate.year, effectiveDate.month,
effectiveDate.day, effectiveDate.hour, effectiveDate.minute);
DateTime invalidDateAndTime = DateTime(invalidDate.year, invalidDate.month, invalidDate.day,
invalidDate.hour, invalidDate.minute);
DateTime effectiveDate = DateTime.fromMillisecondsSinceEpoch(
int.parse(item.effectiveTime.toString()) * 1000)
.toUtc()
.toLocal();
DateTime invalidDate = DateTime.fromMillisecondsSinceEpoch(
int.parse(item.invalidTime.toString()) * 1000)
.toUtc()
.toLocal();
DateTime effectiveDateAndTime = DateTime(
effectiveDate.year,
effectiveDate.month,
effectiveDate.day,
effectiveDate.hour,
effectiveDate.minute);
DateTime invalidDateAndTime = DateTime(
invalidDate.year,
invalidDate.month,
invalidDate.day,
invalidDate.hour,
invalidDate.minute);
// Filter by password name, making the search case-insensitive
if (searchText.isNotEmpty) {
final bool matchesName = item.passwordName.toString().toLowerCase().contains(searchText);
final bool matchesName =
item.passwordName.toString().toLowerCase().contains(searchText);
if (!matchesName) {
matchesCriteria = false;
}
}
if (searchEmailText.isNotEmpty) {
final bool matchesName =
item.authorizerEmail.toString().toLowerCase().contains(searchEmailText);
final bool matchesName = item.authorizerEmail
.toString()
.toLowerCase()
.contains(searchEmailText);
if (!matchesName) {
matchesCriteria = false;
}
@@ -149,9 +193,11 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
// Filter by start date only
if (event.startTime != null && event.endTime == null) {
DateTime startDateTime =
DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000).toUtc().toLocal();
startDateTime = DateTime(startDateTime.year, startDateTime.month, startDateTime.day,
startDateTime.hour, startDateTime.minute);
DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000)
.toUtc()
.toLocal();
startDateTime = DateTime(startDateTime.year, startDateTime.month,
startDateTime.day, startDateTime.hour, startDateTime.minute);
if (effectiveDateAndTime.isBefore(startDateTime)) {
matchesCriteria = false;
}
@@ -159,9 +205,11 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
// Filter by end date only
if (event.endTime != null && event.startTime == null) {
DateTime startDateTime =
DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000).toUtc().toLocal();
startDateTime = DateTime(startDateTime.year, startDateTime.month, startDateTime.day,
startDateTime.hour, startDateTime.minute);
DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000)
.toUtc()
.toLocal();
startDateTime = DateTime(startDateTime.year, startDateTime.month,
startDateTime.day, startDateTime.hour, startDateTime.minute);
if (invalidDateAndTime.isAfter(startDateTime)) {
matchesCriteria = false;
}
@@ -170,13 +218,17 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
// Filter by both start date and end date
if (event.startTime != null && event.endTime != null) {
DateTime startDateTime =
DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000).toUtc().toLocal();
DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000)
.toUtc()
.toLocal();
DateTime endDateTime =
DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000).toUtc().toLocal();
startDateTime = DateTime(startDateTime.year, startDateTime.month, startDateTime.day,
startDateTime.hour, startDateTime.minute);
endDateTime = DateTime(endDateTime.year, endDateTime.month, endDateTime.day,
endDateTime.hour, endDateTime.minute);
DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000)
.toUtc()
.toLocal();
startDateTime = DateTime(startDateTime.year, startDateTime.month,
startDateTime.day, startDateTime.hour, startDateTime.minute);
endDateTime = DateTime(endDateTime.year, endDateTime.month,
endDateTime.day, endDateTime.hour, endDateTime.minute);
if (effectiveDateAndTime.isBefore(startDateTime) ||
invalidDateAndTime.isAfter(endDateTime)) {
matchesCriteria = false;
@@ -184,11 +236,14 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
}
// Filter by selected tab index
if (event.selectedTabIndex == 1 && item.passwordStatus.value != 'To be effective') {
if (event.selectedTabIndex == 1 &&
item.passwordStatus.value != 'To be effective') {
matchesCriteria = false;
} else if (event.selectedTabIndex == 2 && item.passwordStatus.value != 'Effective') {
} else if (event.selectedTabIndex == 2 &&
item.passwordStatus.value != 'Effective') {
matchesCriteria = false;
} else if (event.selectedTabIndex == 3 && item.passwordStatus.value != 'Expired') {
} else if (event.selectedTabIndex == 3 &&
item.passwordStatus.value != 'Expired') {
matchesCriteria = false;
}
@@ -214,12 +269,14 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
}
String timestampToDate(dynamic timestamp) {
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(int.parse(timestamp) * 1000);
DateTime dateTime =
DateTime.fromMillisecondsSinceEpoch(int.parse(timestamp) * 1000);
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')}";
}
Future<void> onTabChanged(TabChangedEvent event, Emitter<AccessState> emit) async {
Future<void> onTabChanged(
TabChangedEvent event, Emitter<AccessState> emit) async {
try {
emit(AccessLoaded());
selectedIndex = event.selectedIndex;
@@ -228,14 +285,19 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
filteredData = data;
break;
case 1: // To Be Effective
filteredData =
data.where((item) => item.passwordStatus.value == "To Be Effective").toList();
filteredData = data
.where((item) => item.passwordStatus.value == "To Be Effective")
.toList();
break;
case 2: // Effective
filteredData = data.where((item) => item.passwordStatus.value == "Effective").toList();
filteredData = data
.where((item) => item.passwordStatus.value == "Effective")
.toList();
break;
case 3: // Expired
filteredData = data.where((item) => item.passwordStatus.value == "Expired").toList();
filteredData = data
.where((item) => item.passwordStatus.value == "Expired")
.toList();
break;
default:
filteredData = data;
@@ -15,8 +15,8 @@ import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/utils/style.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
@@ -27,7 +27,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
final isLargeScreen = isLargeScreenSize(context);
final isSmallScreen = isSmallScreenSize(context);
final isHalfMediumScreen = isHafMediumScreenSize(context);
final padding = isLargeScreen ? const EdgeInsets.all(30) : const EdgeInsets.all(15);
final padding =
isLargeScreen ? const EdgeInsets.all(30) : const EdgeInsets.all(15);
return WebScaffold(
enableMenuSidebar: false,
@@ -37,23 +38,10 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
style: Theme.of(context).textTheme.headlineLarge,
),
),
centerBody: Wrap(
children: [
Padding(
padding: EdgeInsets.only(left: MediaQuery.of(context).size.width * 0.09),
child: Align(
alignment: Alignment.bottomLeft,
child: Text(
'Physical Access',
style: Theme.of(context).textTheme.headlineMedium!.copyWith(color: Colors.white),
),
),
),
],
),
rightBody: const NavigateHomeGridView(),
scaffoldBody: BlocProvider(
create: (BuildContext context) => AccessBloc()..add(FetchTableData()),
create: (BuildContext context) =>
AccessBloc()..add(FetchTableData()),
child: BlocConsumer<AccessBloc, AccessState>(
listener: (context, state) {},
builder: (context, state) {
@@ -107,11 +95,14 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
return [
item.passwordName,
item.passwordType.value,
accessBloc.timestampToDate(item.effectiveTime),
accessBloc.timestampToDate(item.invalidTime),
accessBloc
.timestampToDate(item.effectiveTime),
accessBloc
.timestampToDate(item.invalidTime),
item.deviceName.toString(),
item.authorizerEmail.toString(),
accessBloc.timestampToDate(item.invalidTime),
accessBloc
.timestampToDate(item.invalidTime),
item.passwordStatus.value,
];
}).toList(),
@@ -122,7 +113,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
})));
}
Wrap _buildVisitorAdminPasswords(BuildContext context, AccessBloc accessBloc) {
Wrap _buildVisitorAdminPasswords(
BuildContext context, AccessBloc accessBloc) {
return Wrap(
spacing: 10,
runSpacing: 10,
@@ -147,8 +139,9 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
},
borderRadius: 8,
child: Text(
'+ Create Visitor Password ',
style: context.textTheme.titleSmall!.copyWith(color: Colors.white, fontSize: 12),
'Create Visitor Password ',
style: context.textTheme.titleSmall!
.copyWith(color: Colors.white, fontSize: 12),
)),
),
Container(
@@ -160,7 +153,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
backgroundColor: ColorsManager.whiteColors,
child: Text(
'Admin Password',
style: context.textTheme.titleSmall!.copyWith(color: Colors.black, fontSize: 12),
style: context.textTheme.titleSmall!
.copyWith(color: Colors.black, fontSize: 12),
)),
),
],
@@ -183,6 +177,16 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
isRequired: false,
textFieldName: 'Name',
description: '',
onSubmitted: (value) {
accessBloc.add(FilterDataEvent(
emailAuthorizer:
accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
},
),
),
const SizedBox(width: 15),
@@ -194,6 +198,16 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
isRequired: false,
textFieldName: 'Authorizer',
description: '',
onSubmitted: (value) {
accessBloc.add(FilterDataEvent(
emailAuthorizer:
accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
},
),
),
const SizedBox(width: 15),
@@ -218,7 +232,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
onSearch: () {
accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex,
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
@@ -239,12 +254,21 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
SizedBox(
width: 300,
child: CustomWebTextField(
controller: accessBloc.passwordName,
isRequired: true,
height: 40,
textFieldName: 'Name',
description: '',
),
controller: accessBloc.passwordName,
isRequired: true,
height: 40,
textFieldName: 'Name',
description: '',
onSubmitted: (value) {
accessBloc.add(FilterDataEvent(
emailAuthorizer:
accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
}),
),
DateTimeWebWidget(
icon: Assets.calendarIcon,
@@ -264,7 +288,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
onSearch: () {
accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex,
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
+29 -13
View File
@@ -31,7 +31,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
////////////////////////////// forget password //////////////////////////////////
final TextEditingController forgetEmailController = TextEditingController();
final TextEditingController forgetPasswordController = TextEditingController();
final TextEditingController forgetPasswordController =
TextEditingController();
final TextEditingController forgetOtp = TextEditingController();
final forgetFormKey = GlobalKey<FormState>();
final forgetEmailKey = GlobalKey<FormState>();
@@ -48,7 +49,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
return;
}
_remainingTime = 1;
add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false));
add(UpdateTimerEvent(
remainingTime: _remainingTime, isButtonEnabled: false));
try {
forgetEmailValidate = '';
_remainingTime = (await AuthenticationAPI.sendOtp(
@@ -84,7 +86,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
_timer?.cancel();
add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true));
} else {
add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false));
add(UpdateTimerEvent(
remainingTime: _remainingTime, isButtonEnabled: false));
}
});
}
@@ -94,21 +97,25 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
}
Future<void> changePassword(ChangePasswordEvent event, Emitter<AuthState> emit) async {
Future<void> changePassword(
ChangePasswordEvent event, Emitter<AuthState> emit) async {
emit(LoadingForgetState());
try {
var response = await AuthenticationAPI.verifyOtp(
email: forgetEmailController.text, otpCode: forgetOtp.text);
if (response == true) {
await AuthenticationAPI.forgetPassword(
password: forgetPasswordController.text, email: forgetEmailController.text);
otpCode: forgetOtp.text,
password: forgetPasswordController.text,
email: forgetEmailController.text);
_timer?.cancel();
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
emit(SuccessForgetState());
}
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['error']['message'] ?? 'something went wrong';
String errorMessage =
errorData['error']['message'] ?? 'something went wrong';
validate = errorMessage;
emit(AuthInitialState());
}
@@ -122,7 +129,9 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
}
void _onUpdateTimer(UpdateTimerEvent event, Emitter<AuthState> emit) {
emit(TimerState(isButtonEnabled: event.isButtonEnabled, remainingTime: event.remainingTime));
emit(TimerState(
isButtonEnabled: event.isButtonEnabled,
remainingTime: event.remainingTime));
}
///////////////////////////////////// login /////////////////////////////////////
@@ -154,7 +163,9 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
token = await AuthenticationAPI.loginWithEmail(
model: LoginWithEmailModel(
email: event.username, password: event.password, regionUuid: event.regionUuid),
email: event.username,
password: event.password,
regionUuid: event.regionUuid),
);
} catch (failure) {
validate = 'Invalid Credentials!';
@@ -164,7 +175,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
if (token.accessTokenIsNotEmpty) {
FlutterSecureStorage storage = const FlutterSecureStorage();
await storage.write(key: Token.loginAccessTokenKey, value: token.accessToken);
await storage.write(
key: Token.loginAccessTokenKey, value: token.accessToken);
const FlutterSecureStorage().write(
key: UserModel.userUuidKey,
value: Token.decodeToken(token.accessToken)['uuid'].toString());
@@ -322,12 +334,14 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
static Future<String> getTokenAndValidate() async {
try {
const storage = FlutterSecureStorage();
final firstLaunch =
await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true;
final firstLaunch = await SharedPreferencesHelper.readBoolFromSP(
StringsManager.firstLaunch) ??
true;
if (firstLaunch) {
storage.deleteAll();
}
await SharedPreferencesHelper.saveBoolToSP(StringsManager.firstLaunch, false);
await SharedPreferencesHelper.saveBoolToSP(
StringsManager.firstLaunch, false);
final value = await storage.read(key: Token.loginAccessTokenKey) ?? '';
if (value.isEmpty) {
return 'Token not found';
@@ -380,7 +394,9 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
final String formattedTime = [
if (days > 0) '${days}d', // Append 'd' for days
if (days > 0 || hours > 0)
hours.toString().padLeft(2, '0'), // Show hours if there are days or hours
hours
.toString()
.padLeft(2, '0'), // Show hours if there are days or hours
minutes.toString().padLeft(2, '0'),
seconds.toString().padLeft(2, '0'),
].join(':');
+7 -1
View File
@@ -26,9 +26,12 @@ class CurtainToggle extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
height: 10,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipOval(
child: Container(
@@ -41,6 +44,9 @@ class CurtainToggle extends StatelessWidget {
),
),
),
const SizedBox(
width: 20,
),
SizedBox(
height: 20,
width: 35,
+77 -59
View File
@@ -4,6 +4,7 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class DynamicTable extends StatefulWidget {
final List<String> headers;
@@ -45,6 +46,8 @@ class DynamicTable extends StatefulWidget {
class _DynamicTableState extends State<DynamicTable> {
late List<bool> _selectedRows;
bool _selectAll = false;
final ScrollController _verticalScrollController = ScrollController();
final ScrollController _horizontalScrollController = ScrollController();
@override
void initState() {
@@ -102,68 +105,81 @@ class _DynamicTableState extends State<DynamicTable> {
Widget build(BuildContext context) {
return Container(
decoration: widget.cellDecoration,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
width: widget.size.width,
child: Column(
children: [
Container(
decoration: widget.headerDecoration ?? BoxDecoration(color: Colors.grey[200]),
child: Row(
child: Scrollbar(
controller: _verticalScrollController,
thumbVisibility: true,
trackVisibility: true,
child: Scrollbar(
controller: _horizontalScrollController,
thumbVisibility: false,
trackVisibility: false,
notificationPredicate: (notif) => notif.depth == 1,
child: SingleChildScrollView(
controller: _verticalScrollController,
child: SingleChildScrollView(
controller: _horizontalScrollController,
scrollDirection: Axis.horizontal,
child: SizedBox(
width: widget.size.width,
child: Column(
children: [
if (widget.withCheckBox) _buildSelectAllCheckbox(),
...widget.headers.map((header) => _buildTableHeaderCell(header)),
],
),
),
widget.isEmpty
? Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
Container(
decoration: widget.headerDecoration ??
const BoxDecoration(
color: ColorsManager.boxColor,
),
child: Row(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
if (widget.withCheckBox) _buildSelectAllCheckbox(),
...List.generate(widget.headers.length, (index) {
return _buildTableHeaderCell(widget.headers[index], index);
})
//...widget.headers.map((header) => _buildTableHeaderCell(header)),
],
),
),
widget.isEmpty
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(Assets.emptyTable),
const SizedBox(
height: 15,
Column(
children: [
SvgPicture.asset(Assets.emptyTable),
const SizedBox(
height: 15,
),
Text(
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),
)
],
),
],
)
: Column(
children: List.generate(widget.data.length, (index) {
final row = widget.data[index];
return Row(
children: [
if (widget.withCheckBox) _buildRowCheckbox(index, widget.size.height * 0.08),
...row.map((cell) => _buildTableCell(cell.toString(), widget.size.height * 0.08)),
],
);
}),
),
],
),
)
: Expanded(
child: Container(
color: Colors.white,
child: ListView.builder(
shrinkWrap: true,
itemCount: widget.data.length,
itemBuilder: (context, index) {
final row = widget.data[index];
return Row(
children: [
if (widget.withCheckBox) _buildRowCheckbox(index, widget.size.height * 0.10),
...row.map((cell) => _buildTableCell(cell.toString(), widget.size.height * 0.10)),
],
);
},
),
),
),
],
],
),
),
),
),
),
),
@@ -173,7 +189,6 @@ class _DynamicTableState extends State<DynamicTable> {
Widget _buildSelectAllCheckbox() {
return Container(
width: 50,
padding: const EdgeInsets.all(8.0),
decoration: const BoxDecoration(
border: Border.symmetric(
vertical: BorderSide(color: ColorsManager.boxDivider),
@@ -198,6 +213,7 @@ class _DynamicTableState extends State<DynamicTable> {
width: 1.0,
),
),
color: ColorsManager.whiteColors,
),
alignment: Alignment.centerLeft,
child: Center(
@@ -211,7 +227,7 @@ class _DynamicTableState extends State<DynamicTable> {
);
}
Widget _buildTableHeaderCell(String title) {
Widget _buildTableHeaderCell(String title, int index) {
return Expanded(
child: Container(
decoration: const BoxDecoration(
@@ -219,15 +235,16 @@ class _DynamicTableState extends State<DynamicTable> {
vertical: BorderSide(color: ColorsManager.boxDivider),
),
),
constraints: const BoxConstraints.expand(height: 40),
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.all(8.0),
padding: EdgeInsets.symmetric(horizontal: index == widget.headers.length - 1 ? 12 : 8.0, vertical: 4),
child: Text(
title,
style: const TextStyle(
style: context.textTheme.titleSmall!.copyWith(
color: ColorsManager.grayColor,
fontSize: 12,
fontWeight: FontWeight.w400,
fontSize: 13,
color: Color(0xFF999999),
),
maxLines: 2,
),
@@ -276,6 +293,7 @@ class _DynamicTableState extends State<DynamicTable> {
width: 1.0,
),
),
color: Colors.white,
),
alignment: Alignment.centerLeft,
child: Text(
@@ -286,7 +304,7 @@ class _DynamicTableState extends State<DynamicTable> {
: (batteryLevel != null && batteryLevel > 20)
? ColorsManager.green
: statusColor,
fontSize: 10,
fontSize: 13,
fontWeight: FontWeight.w400),
maxLines: 2,
),
+4 -1
View File
@@ -81,7 +81,10 @@ class DateTimeWebWidget extends StatelessWidget {
const SizedBox(
width: 30,
),
const Icon(Icons.arrow_right_alt),
const Icon(
Icons.arrow_right_alt,
color: ColorsManager.grayColor,
),
const SizedBox(
width: 30,
),
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class StatefulTextField extends StatefulWidget {
const StatefulTextField(
@@ -25,13 +26,15 @@ class StatefulTextField extends StatefulWidget {
class _StatefulTextFieldState extends State<StatefulTextField> {
@override
Widget build(BuildContext context) {
return CustomTextField(
title: widget.title,
controller: widget.controller,
hintText: widget.hintText,
width: widget.width,
elevation: widget.elevation,
onSubmittedFun: widget.onSubmitted);
return Container(
child: CustomTextField(
title: widget.title,
controller: widget.controller,
hintText: widget.hintText,
width: widget.width,
elevation: widget.elevation,
onSubmittedFun: widget.onSubmitted),
);
}
}
@@ -73,17 +76,20 @@ class CustomTextField extends StatelessWidget {
child: Container(
width: width,
height: 45,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
decoration: containerDecoration,
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(8),
// ),
child: TextFormField(
controller: controller,
style: const TextStyle(color: Colors.black),
decoration: InputDecoration(
hintText: hintText,
hintStyle: const TextStyle(fontSize: 12),
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
contentPadding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
border: InputBorder.none,
),
onFieldSubmitted: (_) {
@@ -13,6 +13,7 @@ class CustomWebTextField extends StatelessWidget {
this.validator,
this.hintText,
this.height,
this.onSubmitted,
});
final bool isRequired;
@@ -22,6 +23,7 @@ class CustomWebTextField extends StatelessWidget {
final String? Function(String?)? validator;
final String? hintText;
final double? height;
final ValueChanged<String>? onSubmitted;
@override
Widget build(BuildContext context) {
@@ -32,23 +34,25 @@ class CustomWebTextField extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
if (isRequired)
Text('* ',
Row(
children: [
if (isRequired)
Text(
'* ',
style: Theme.of(context)
.textTheme.bodyMedium!
.textTheme
.bodyMedium!
.copyWith(color: Colors.red),
),
Text(
textFieldName,
style: Theme.of(context)
.textTheme.bodySmall!
.copyWith(color: Colors.black, fontSize: 13),
),
],
),
Text(
textFieldName,
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: Colors.black, fontSize: 13),
),
],
),
const SizedBox(
width: 10,
),
@@ -68,17 +72,7 @@ class CustomWebTextField extends StatelessWidget {
),
Container(
height: height ?? 35,
decoration: containerDecoration.copyWith(
color: const Color(0xFFF5F6F7),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.3),
spreadRadius: 2,
blurRadius: 3,
offset: const Offset(1, 1), // changes position of shadow
),
]
),
decoration: containerDecoration,
child: TextFormField(
validator: validator,
controller: controller,
@@ -88,6 +82,7 @@ class CustomWebTextField extends StatelessWidget {
hintStyle: context.textTheme.titleSmall!
.copyWith(color: Colors.grey, fontSize: 12),
hintText: hintText ?? 'Please enter'),
onFieldSubmitted: onSubmitted,
),
),
],
@@ -40,7 +40,7 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
: 1,
mainAxisExtent: 140,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
mainAxisSpacing: 16,
),
children: [
ToggleWidget(
@@ -81,6 +81,7 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
padding: const EdgeInsets.all(0),
onPressed: () {},
icon: const Icon(
Icons.remove,
@@ -108,6 +109,7 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
),
Text('m', style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor)),
IconButton(
padding: const EdgeInsets.all(0),
onPressed: () {},
icon: const Icon(
Icons.add,
@@ -127,7 +129,7 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
deviceId: device.uuid!,
code: 'child_lock',
value: state.status.childLock,
label: 'Child Lock',
label: 'Lock',
icon: state.status.childLock ? Assets.acLock : Assets.unlock,
onChange: (value) {
context.read<AcBloc>().add(
@@ -18,6 +18,8 @@ import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_do
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_clamp_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/view/smart_power_device_control.dart';
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart';
@@ -94,6 +96,10 @@ mixin RouteControlsBasedCode {
return WaterLeakView(
deviceId: device.uuid!,
);
case 'PC':
return SmartPowerDeviceControl(
deviceId: device.uuid!,
);
default:
return const SizedBox();
}
@@ -224,6 +230,13 @@ mixin RouteControlsBasedCode {
.map((e) => e.uuid!)
.toList(),
);
case 'PC':
return PowerClampBatchControlView(
deviceIds: devices
.where((e) => (e.productType == 'PC'))
.map((e) => e.uuid!)
.toList(),
);
default:
return const SizedBox();
}
@@ -8,7 +8,6 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/format_date_time.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/utils/style.dart';
@@ -146,7 +145,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
.add(SelectDevice(selectedDevice));
},
withCheckBox: true,
size: context.screenSize,
size: MediaQuery.of(context).size,
uuidIndex: 2,
headers: const [
'Device Name',
@@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/utils/style.dart';
class DeviceSearchFilters extends StatefulWidget {
const DeviceSearchFilters({super.key});
@@ -12,7 +13,8 @@ class DeviceSearchFilters extends StatefulWidget {
State<DeviceSearchFilters> createState() => _DeviceSearchFiltersState();
}
class _DeviceSearchFiltersState extends State<DeviceSearchFilters> with HelperResponsiveLayout {
class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
with HelperResponsiveLayout {
final TextEditingController communityController = TextEditingController();
final TextEditingController unitNameController = TextEditingController();
final TextEditingController productNameController = TextEditingController();
@@ -34,7 +36,8 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters> with HelperRe
const SizedBox(width: 20),
_buildSearchField("Space Name", unitNameController, 200),
const SizedBox(width: 20),
_buildSearchField("Device Name / Product Name", productNameController, 300),
_buildSearchField(
"Device Name / Product Name", productNameController, 300),
const SizedBox(width: 20),
_buildSearchResetButtons(),
],
@@ -59,19 +62,22 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters> with HelperRe
);
}
Widget _buildSearchField(String title, TextEditingController controller, double width) {
return StatefulTextField(
title: title,
width: width,
elevation: 2,
controller: controller,
onSubmitted: () {
context.read<DeviceManagementBloc>().add(SearchDevices(
productName: productNameController.text,
unitName: unitNameController.text,
community: communityController.text,
searchField: true));
},
Widget _buildSearchField(
String title, TextEditingController controller, double width) {
return Container(
child: StatefulTextField(
title: title,
width: width,
elevation: 2,
controller: controller,
onSubmitted: () {
context.read<DeviceManagementBloc>().add(SearchDevices(
productName: productNameController.text,
unitName: unitNameController.text,
community: communityController.text,
searchField: true));
},
),
);
}
@@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/device_managment/gateway/bloc/gate_way_bloc.da
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
import 'package:syncrow_web/pages/visitor_password/model/device_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class GateWayControlsView extends StatelessWidget with HelperResponsiveLayout {
@@ -25,25 +26,61 @@ class GateWayControlsView extends StatelessWidget with HelperResponsiveLayout {
if (state is GatewayLoadingState) {
return const Center(child: CircularProgressIndicator());
} else if (state is UpdateGatewayState) {
return GridView.builder(
padding: const EdgeInsets.symmetric(horizontal: 50),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isLarge || isExtraLarge
? 3
: isMedium
? 2
: 1,
mainAxisExtent: 140,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
),
itemCount: state.list.length,
itemBuilder: (context, index) {
final device = state.list[index];
return _DeviceItem(device: device);
},
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 50),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Bluetooth Devices:",
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.grayColor,
),
),
const SizedBox(height: 12),
Text(
"No devices found",
style: context.textTheme.bodySmall!.copyWith(
color: ColorsManager.blackColor,
),
),
const SizedBox(height: 30),
Text(
"ZigBee Devices:",
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.grayColor,
),
),
],
),
),
const SizedBox(height: 12),
GridView.builder(
padding: const EdgeInsets.symmetric(horizontal: 50),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isLarge || isExtraLarge
? 3
: isMedium
? 2
: 1,
mainAxisExtent: 140,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
),
itemCount: state.list.length,
itemBuilder: (context, index) {
final device = state.list[index];
return _DeviceItem(device: device);
},
),
],
);
} else {
return const Center(child: Text('Error fetching status'));
@@ -0,0 +1,792 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_event.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_state.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/device_event.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_batch_model.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_chart.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
class SmartPowerBloc extends Bloc<SmartPowerEvent, SmartPowerState> {
SmartPowerBloc({required this.deviceId}) : super(SmartPowerInitial()) {
on<SmartPowerFetchDeviceEvent>(_onFetchDeviceStatus);
on<SmartPowerArrowPressedEvent>(_onArrowPressed);
on<SmartPowerFetchBatchEvent>(_onFetchBatchStatus);
on<SmartPowerPageChangedEvent>(_onPageChanged);
on<PowerBatchControlEvent>(_onBatchControl);
on<FilterRecordsByDateEvent>(_filterRecordsByDate);
on<SelectDateEvent>(checkDayMonthYearSelected);
on<SmartPowerFactoryReset>(_onFactoryReset);
}
late PowerClampModel deviceStatus;
late PowerClampBatchModel deviceBatchStatus;
final String deviceId;
Timer? _timer;
List<Map<String, dynamic>> phaseData = [];
int currentPage = 0;
List<EventDevice> record = [
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:15:43'),
value: '2286'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:15:35'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:15:29'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:15:25'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:15:21'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:15:17'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:15:07'),
value: '2286'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:14:47'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:14:40'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:14:23'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2024-10-23 11:14:13'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:15:43'),
value: '2286'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:15:35'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:15:29'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:15:25'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:15:21'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:15:17'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:15:07'),
value: '2286'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:14:47'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:14:40'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:14:23'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-10-23 11:14:13'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:15:43'),
value: '2286'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:15:35'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:15:29'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:15:25'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:15:21'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:15:17'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:15:07'),
value: '2286'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:14:47'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:14:40'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:14:23'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-23 11:14:13'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-11 11:15:43'),
value: '2286'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-11 11:15:35'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-12 11:15:29'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-13 11:15:25'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-14 11:15:21'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-15 11:15:17'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-16 11:15:07'),
value: '2286'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-17 11:14:47'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-18 11:14:40'),
value: '2284'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-19 11:14:23'),
value: '2285'),
EventDevice(
code: 'VoltageA',
eventTime: DateTime.parse('2023-02-20 11:14:13'),
value: '2284'),
];
FutureOr<void> _onFetchDeviceStatus(
SmartPowerFetchDeviceEvent event, Emitter<SmartPowerState> emit) async {
emit(SmartPowerLoading());
try {
var status =
await DevicesManagementApi().getPowerClampInfo(event.deviceId);
deviceStatus = PowerClampModel.fromJson(status);
phaseData = [
{
'name': 'Phase A',
'voltage': '${deviceStatus.status.phaseA.dataPoints[0].value / 10} V',
'current': '${deviceStatus.status.phaseA.dataPoints[1].value / 10} A',
'activePower': '${deviceStatus.status.phaseA.dataPoints[2].value} W',
'powerFactor': '${deviceStatus.status.phaseA.dataPoints[3].value}',
},
{
'name': 'Phase B',
'voltage': '${deviceStatus.status.phaseB.dataPoints[0].value / 10} V',
'current': '${deviceStatus.status.phaseB.dataPoints[1].value / 10} A',
'activePower': '${deviceStatus.status.phaseB.dataPoints[2].value} W',
'powerFactor': '${deviceStatus.status.phaseB.dataPoints[3].value}',
},
{
'name': 'Phase C',
'voltage': '${deviceStatus.status.phaseC.dataPoints[0].value / 10} V',
'current': '${deviceStatus.status.phaseC.dataPoints[1].value / 10} A',
'activePower': '${deviceStatus.status.phaseC.dataPoints[2].value} W',
'powerFactor': '${deviceStatus.status.phaseC.dataPoints[3].value}',
},
];
emit(GetDeviceStatus());
} catch (e) {
emit(SmartPowerError(e.toString()));
}
}
FutureOr<void> _onArrowPressed(
SmartPowerArrowPressedEvent event, Emitter<SmartPowerState> emit) {
currentPage = (currentPage + event.direction + 4) % 4;
emit(SmartPowerStatusLoaded(deviceStatus, currentPage));
emit(GetDeviceStatus());
}
FutureOr<void> _onPageChanged(
SmartPowerPageChangedEvent event, Emitter<SmartPowerState> emit) {
currentPage = event.page;
emit(SmartPowerStatusLoaded(deviceStatus, currentPage));
emit(GetDeviceStatus());
}
Future<void> _onFactoryReset(
SmartPowerFactoryReset event, Emitter<SmartPowerState> emit) async {
emit(SmartPowerLoading());
try {
final response = await DevicesManagementApi().factoryReset(
event.factoryReset,
event.deviceId,
);
if (response) {
emit(SmartPowerInitial());
} else {
emit(SmartPowerError('Factory reset failed'));
}
} catch (e) {
emit(SmartPowerError(e.toString()));
}
}
Future<void> _onBatchControl(
PowerBatchControlEvent event, Emitter<SmartPowerState> emit) async {
final oldValue = deviceStatus.status;
_updateLocalValue(event.code, event.value);
// emit(WaterLeakBatchStatusLoadedState(deviceStatus!));
await _runDebounce(
deviceId: event.deviceIds,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: true,
);
}
Future<void> _onFetchBatchStatus(
SmartPowerFetchBatchEvent event, Emitter<SmartPowerState> emit) async {
emit(SmartPowerLoading());
try {
final response =
await DevicesManagementApi().getPowerStatus(event.devicesIds);
PowerClampBatchModel deviceStatus =
PowerClampBatchModel.fromJson(response);
emit(SmartPowerLoadBatchControll(deviceStatus));
} catch (e) {
debugPrint('=========error====$e');
emit(SmartPowerError(e.toString()));
}
}
Future<void> _runDebounce({
required dynamic deviceId,
required String code,
required dynamic value,
required dynamic oldValue,
required Emitter<SmartPowerState> emit,
required bool isBatch,
}) async {
late String id;
if (deviceId is List) {
id = deviceId.first;
} else {
id = deviceId;
}
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(const Duration(milliseconds: 500), () async {
try {
late bool response;
if (isBatch) {
response = await DevicesManagementApi()
.deviceBatchControl(deviceId, code, value);
} else {
response = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: value));
}
if (!response) {
_revertValueAndEmit(id, code, oldValue, emit);
}
} catch (e) {
_revertValueAndEmit(id, code, oldValue, emit);
}
});
}
void _updateLocalValue(String code, dynamic value) {
if (code == 'watersensor_state') {
deviceStatus = deviceStatus.copyWith(statusPower: value);
} else if (code == 'battery_percentage') {
deviceStatus = deviceStatus.copyWith(statusPower: value);
}
}
void _revertValueAndEmit(String deviceId, String code, dynamic oldValue,
Emitter<SmartPowerState> emit) {
_updateLocalValue(code, oldValue);
emit(SmartPowerLoadBatchControll(deviceBatchStatus));
}
@override
Future<void> close() {
_timer?.cancel();
return super.close();
}
List<EventDevice> filteredRecords = [];
int currentIndex = 0;
final List<String> views = ['Day', 'Month', 'Year'];
Widget dateSwitcher() {
void switchView(int direction) {
currentIndex = (currentIndex + direction + views.length) % views.length;
}
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: const Icon(Icons.arrow_left),
onPressed: () {
setState(() {
switchView(-1);
});
},
),
Text(
views[currentIndex],
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500),
),
IconButton(
icon: const Icon(Icons.arrow_right),
onPressed: () {
setState(() {
switchView(1);
});
},
),
],
);
},
);
}
Future<DateTime?> selectMonthAndYear(BuildContext context) async {
int selectedYear = DateTime.now().year;
int selectedMonth = DateTime.now().month;
FixedExtentScrollController yearController =
FixedExtentScrollController(initialItem: selectedYear - 1905);
FixedExtentScrollController monthController =
FixedExtentScrollController(initialItem: selectedMonth - 1);
return await showDialog<DateTime>(
context: context,
builder: (BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
),
height: 350,
width: 350,
child: Column(
children: [
const Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'Select Month and Year',
style:
TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
),
const Divider(),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Spacer(),
Expanded(
child: ListWheelScrollView.useDelegate(
controller: yearController,
overAndUnderCenterOpacity: 0.2,
itemExtent: 50,
onSelectedItemChanged: (index) {
selectedYear = 1905 + index;
},
childDelegate: ListWheelChildBuilderDelegate(
builder: (context, index) {
return Center(
child: Text(
(1905 + index).toString(),
style: const TextStyle(fontSize: 18),
),
);
},
childCount: 200,
),
),
),
Expanded(
flex: 2,
child: ListWheelScrollView.useDelegate(
controller: monthController,
overAndUnderCenterOpacity: 0.2,
itemExtent: 50,
onSelectedItemChanged: (index) {
selectedMonth = index + 1;
},
childDelegate: ListWheelChildBuilderDelegate(
builder: (context, index) {
return Center(
child: Text(
DateFormat.MMMM()
.format(DateTime(0, index + 1)),
style: const TextStyle(fontSize: 18),
),
);
},
childCount: 12,
),
),
),
const Spacer(),
],
),
),
const Divider(),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
child: const Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: const Text('OK'),
onPressed: () {
final selectedDateTime =
DateTime(selectedYear, selectedMonth);
Navigator.of(context).pop(selectedDateTime);
},
),
],
),
),
],
),
),
],
);
},
);
}
Future<DateTime?> selectYear(BuildContext context) async {
int selectedYear = DateTime.now().year;
FixedExtentScrollController yearController =
FixedExtentScrollController(initialItem: selectedYear - 1905);
return await showDialog<DateTime>(
context: context,
builder: (BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
),
height: 350,
width: 350,
child: Column(
children: [
const Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'Select Year',
style:
TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
),
const Divider(),
Expanded(
child: ListWheelScrollView.useDelegate(
controller: yearController,
overAndUnderCenterOpacity: 0.2,
itemExtent: 50,
onSelectedItemChanged: (index) {
selectedYear = 1905 + index;
},
childDelegate: ListWheelChildBuilderDelegate(
builder: (context, index) {
return Center(
child: Text(
(1905 + index).toString(),
style: const TextStyle(fontSize: 18),
),
);
},
childCount: 200,
),
),
),
const Divider(),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
child: const Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: const Text('OK'),
onPressed: () {
final selectedDateTime = DateTime(selectedYear);
Navigator.of(context).pop(selectedDateTime);
},
),
],
),
),
],
),
),
],
);
},
);
}
Future<DateTime?> dayMonthYearPicker({
required BuildContext context,
}) async {
DateTime selectedDate = DateTime.now();
return await showDialog<DateTime>(
context: context,
builder: (BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 350,
width: 350,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
),
child: Column(
children: [
Expanded(
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.date,
initialDateTime: DateTime.now(),
minimumYear: 1900,
maximumYear: DateTime.now().year,
onDateTimeChanged: (DateTime newDateTime) {
selectedDate = newDateTime;
},
),
),
const Divider(),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
child: const Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: const Text('OK'),
onPressed: () {
Navigator.of(context).pop(selectedDate);
},
),
],
),
),
],
),
),
],
);
},
);
}
DateTime? dateTime = DateTime.now();
String formattedDate = DateFormat('yyyy/MM/dd').format(DateTime.now());
void checkDayMonthYearSelected(
SelectDateEvent event, Emitter<SmartPowerState> emit) async {
Future<DateTime?> Function(BuildContext context)? dateSelector;
String dateFormat;
switch (currentIndex) {
case 0:
dateSelector = (context) {
return dayMonthYearPicker(context: context);
};
dateFormat = 'yyyy/MM/dd';
break;
case 1:
dateSelector = (context) {
return selectMonthAndYear(context);
};
dateFormat = 'yyyy-MM';
break;
case 2:
dateSelector = (context) {
return selectYear(context);
};
dateFormat = 'yyyy';
break;
default:
return;
}
Future.delayed(const Duration(milliseconds: 500), () {
emit(FakeState());
});
// Use the selected picker
await dateSelector(event.context).then((newDate) {
if (newDate.toString() == 'null') {
emit(GetDeviceStatus());
} else {
dateTime = newDate;
add(FilterRecordsByDateEvent(
selectedDate: newDate!,
viewType: views[currentIndex],
));
}
// formattedDate = newDate.toString();
});
emit(FilterRecordsState(filteredRecords: energyDataList));
}
List<EnergyData> energyDataList = [];
void _filterRecordsByDate(
FilterRecordsByDateEvent event, Emitter<SmartPowerState> emit) {
// emit(SmartPowerLoading());
if (event.viewType == 'Year') {
formattedDate = event.selectedDate.year.toString();
filteredRecords = record
.where((record) => record.eventTime!.year == event.selectedDate.year)
.toList();
} else if (event.viewType == 'Month') {
formattedDate =
"${event.selectedDate.year.toString()}-${getMonthShortName(event.selectedDate.month)}";
filteredRecords = record
.where((record) =>
record.eventTime!.year == event.selectedDate.year &&
record.eventTime!.month == event.selectedDate.month)
.toList();
} else if (event.viewType == 'Day') {
formattedDate =
"${event.selectedDate.year.toString()}-${getMonthShortName(event.selectedDate.month)}-${event.selectedDate.day}";
filteredRecords = record
.where((record) =>
record.eventTime!.year == event.selectedDate.year &&
record.eventTime!.month == event.selectedDate.month &&
record.eventTime!.day == event.selectedDate.day)
.toList();
}
selectDateRange();
energyDataList = filteredRecords.map((eventDevice) {
return EnergyData(
event.viewType == 'Year'
? getMonthShortName(
int.tryParse(DateFormat('MM').format(eventDevice.eventTime!))!)
: event.viewType == 'Month'
? DateFormat('yyyy/MM/dd').format(eventDevice.eventTime!)
: DateFormat('HH:mm:ss').format(eventDevice.eventTime!),
double.parse(eventDevice.value!),
);
}).toList();
emit(FilterRecordsState(filteredRecords: energyDataList));
}
String getMonthShortName(int month) {
final date = DateTime(0, month);
return DateFormat.MMM().format(date);
}
String endChartDate = '';
void selectDateRange() async {
DateTime startDate = dateTime!;
DateTime endDate = DateTime(startDate.year, startDate.month + 1, 1)
.subtract(Duration(days: 1));
String formattedEndDate = DateFormat('dd/MM/yyyy').format(endDate);
endChartDate = ' - $formattedEndDate';
}
}
@@ -0,0 +1,115 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
class SmartPowerEvent extends Equatable {
@override
List<Object?> get props => [];
}
class SmartPowerFetchDeviceEvent extends SmartPowerEvent {
final String deviceId;
SmartPowerFetchDeviceEvent(this.deviceId);
@override
List<Object> get props => [deviceId];
}
class SmartPowerControl extends SmartPowerEvent {
final String deviceId;
final String code;
final bool value;
SmartPowerControl(
{required this.deviceId, required this.code, required this.value});
@override
List<Object> get props => [deviceId, code, value];
}
class SmartPowerFetchBatchEvent extends SmartPowerEvent {
final List<String> devicesIds;
SmartPowerFetchBatchEvent(this.devicesIds);
@override
List<Object> get props => [devicesIds];
}
class SmartPowerBatchControl extends SmartPowerEvent {
final List<String> devicesIds;
final String code;
final bool value;
SmartPowerBatchControl(
{required this.devicesIds, required this.code, required this.value});
@override
List<Object> get props => [devicesIds, code, value];
}
class SmartPowerFactoryReset extends SmartPowerEvent {
final String deviceId;
final FactoryResetModel factoryReset;
SmartPowerFactoryReset({required this.deviceId, required this.factoryReset});
@override
List<Object> get props => [deviceId, factoryReset];
}
class PageChangedEvent extends SmartPowerEvent {
final int newPage;
PageChangedEvent(this.newPage);
}
class PageArrowPressedEvent extends SmartPowerEvent {
final int direction;
PageArrowPressedEvent(this.direction);
}
class SmartPowerArrowPressedEvent extends SmartPowerEvent {
final int direction;
SmartPowerArrowPressedEvent(this.direction);
}
class SmartPowerPageChangedEvent extends SmartPowerEvent {
final int page;
SmartPowerPageChangedEvent(this.page);
}
class SelectDateEvent extends SmartPowerEvent {
BuildContext context;
SelectDateEvent({required this.context});
}
class FilterRecordsByDateEvent extends SmartPowerEvent {
final DateTime selectedDate;
final String viewType; // 'Day', 'Month', 'Year'
FilterRecordsByDateEvent(
{required this.selectedDate, required this.viewType});
}
class FetchPowerClampBatchStatusEvent extends SmartPowerEvent {
final List<String> deviceIds;
FetchPowerClampBatchStatusEvent(this.deviceIds);
@override
List<Object> get props => [deviceIds];
}class PowerBatchControlEvent extends SmartPowerEvent {
final List<String> deviceIds;
final String code;
final dynamic value;
PowerBatchControlEvent({
required this.deviceIds,
required this.code,
required this.value,
});
@override
List<Object> get props => [deviceIds, code, value];
}
@@ -0,0 +1,77 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_batch_model.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_chart.dart';
class SmartPowerState extends Equatable {
@override
List<Object?> get props => [];
}
class SmartPowerInitial extends SmartPowerState {}
class SmartPowerLoading extends SmartPowerState {}
class GetDeviceStatus extends SmartPowerState {}
//GetDeviceStatus
class SmartPowerLoadBatchControll extends SmartPowerState {
final PowerClampBatchModel status;
SmartPowerLoadBatchControll(this.status);
@override
List<Object> get props => [status];
}
class DateSelectedState extends SmartPowerState {}
class FakeState extends SmartPowerState {}
class SmartPowerStatusLoaded extends SmartPowerState {
final PowerClampModel deviceStatus;
final int currentPage;
SmartPowerStatusLoaded(this.deviceStatus, this.currentPage);
}
class SmartPowerError extends SmartPowerState {
final String message;
SmartPowerError(this.message);
@override
List<Object> get props => [message];
}
class SmartPowerControlError extends SmartPowerState {
final String message;
SmartPowerControlError(this.message);
@override
List<Object> get props => [message];
}
class SmartPowerBatchControlError extends SmartPowerState {
final String message;
SmartPowerBatchControlError(this.message);
@override
List<Object> get props => [message];
}
class SmartPowerBatchStatusLoaded extends SmartPowerState {
final List<String> status;
SmartPowerBatchStatusLoaded(this.status);
@override
List<Object> get props => [status];
}
class FilterRecordsState extends SmartPowerState {
final List<EnergyData> filteredRecords;
FilterRecordsState({required this.filteredRecords});
}
@@ -0,0 +1,23 @@
class EventDevice {
final String? code;
final DateTime? eventTime;
final String? value;
EventDevice({
this.code,
this.eventTime,
this.value,
});
EventDevice.fromJson(Map<String, dynamic> json)
: code = json['code'] as String?,
eventTime = json['eventTime'] ,
value = json['value'] as String?;
Map<String, dynamic> toJson() => {
'code': code,
'eventTime': eventTime,
'value': value,
};
}
@@ -0,0 +1,49 @@
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
abstract class PowerClampModel1 {
String get productUuid;
String get productType;
}
class PowerClampBatchModel extends PowerClampModel1 {
@override
final String productUuid;
@override
final String productType;
final List<Status> status;
PowerClampBatchModel({
required this.productUuid,
required this.productType,
required this.status,
});
factory PowerClampBatchModel.fromJson(Map<String, dynamic> json) {
String productUuid = json['productUuid'] ?? '';
String productType = json['productType'] ?? '';
List<Status> statusList = [];
if (json['status'] != null && json['status'] is List) {
statusList =
(json['status'] as List).map((e) => Status.fromJson(e)).toList();
}
return PowerClampBatchModel(
productUuid: productUuid,
productType: productType,
status: statusList,
);
}
PowerClampBatchModel copyWith({
String? productUuid,
String? productType,
List<Status>? status,
}) {
return PowerClampBatchModel(
productUuid: productUuid ?? this.productUuid,
productType: productType ?? this.productType,
status: status ?? this.status,
);
}
}
@@ -0,0 +1,98 @@
// PowerClampModel class to represent the response
class PowerClampModel {
String productUuid;
String productType;
PowerStatus status;
PowerClampModel({
required this.productUuid,
required this.productType,
required this.status,
});
factory PowerClampModel.fromJson(Map<String, dynamic> json) {
return PowerClampModel(
productUuid: json['productUuid'],
productType: json['productType'],
status: PowerStatus.fromJson(json['status']),
);
}
PowerClampModel copyWith({
String? productUuid,
String? productType,
PowerStatus? statusPower,
}) {
return PowerClampModel(
productUuid: productUuid ?? this.productUuid,
productType: productType ?? this.productType,
status: statusPower ?? this.status,
);
}
}
class PowerStatus {
Phase phaseA;
Phase phaseB;
Phase phaseC;
Phase general;
PowerStatus({
required this.phaseA,
required this.phaseB,
required this.phaseC,
required this.general,
});
factory PowerStatus.fromJson(Map<String, dynamic> json) {
return PowerStatus(
phaseA: Phase.fromJson(json['phaseA']),
phaseB: Phase.fromJson(json['phaseB']),
phaseC: Phase.fromJson(json['phaseC']),
general: Phase.fromJson(json['general']
// List<DataPoint>.from(
// json['general'].map((x) => DataPoint.fromJson(x))),
));
}
}
class Phase {
List<DataPoint> dataPoints;
Phase({required this.dataPoints});
factory Phase.fromJson(List<dynamic> json) {
return Phase(
dataPoints: json.map((x) => DataPoint.fromJson(x)).toList(),
);
}
}
class DataPoint {
dynamic code;
dynamic customName;
dynamic dpId;
dynamic time;
dynamic type;
dynamic value;
DataPoint({
required this.code,
required this.customName,
required this.dpId,
required this.time,
required this.type,
required this.value,
});
factory DataPoint.fromJson(Map<String, dynamic> json) {
return DataPoint(
code: json['code'],
customName: json['customName'],
dpId: json['dpId'],
time: json['time'],
type: json['type'],
value: json['value'],
);
}
}
@@ -0,0 +1,124 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_info_card.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class PhaseWidget extends StatefulWidget {
final List<Map<String, dynamic>> phaseData;
PhaseWidget({
required this.phaseData,
});
@override
_PhaseWidgetState createState() => _PhaseWidgetState();
}
class _PhaseWidgetState extends State<PhaseWidget> {
int _selectedPhaseIndex = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(height: 10),
Row(
children: List.generate(widget.phaseData.length, (index) {
return InkWell(
onTap: () {
setState(() {
_selectedPhaseIndex = index;
});
},
child: Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
child: Text(
widget.phaseData[index]['name'],
style: TextStyle(
fontWeight: FontWeight.bold,
color: _selectedPhaseIndex == index
? Colors.black
: Colors.grey,
),
),
),
);
}),
),
SizedBox(height: 10),
_selectedPhaseIndex == 0
? phase(
totalActive: widget.phaseData[0]['activePower'] ?? '0',
totalCurrent: widget.phaseData[0]['current'] ?? '0',
totalFactor: widget.phaseData[0]['powerFactor'] ?? '0',
totalVoltage: widget.phaseData[0]['voltage'] ?? '0',
)
: _selectedPhaseIndex == 1
? phase(
totalActive: widget.phaseData[1]['activePower'] ?? '0',
totalCurrent: widget.phaseData[1]['current'] ?? '0',
totalFactor: widget.phaseData[1]['powerFactor'] ?? '0',
totalVoltage: widget.phaseData[1]['voltage'] ?? '0',
)
: phase(
totalActive: widget.phaseData[2]['activePower'] ?? '0',
totalCurrent: widget.phaseData[2]['current'] ?? '0',
totalFactor: widget.phaseData[2]['powerFactor'] ?? '0',
totalVoltage: widget.phaseData[2]['voltage'] ?? '0',
),
],
);
}
}
class phase extends StatelessWidget {
const phase({
super.key,
required this.totalVoltage,
required this.totalCurrent,
required this.totalActive,
required this.totalFactor,
});
final String totalVoltage;
final String totalCurrent;
final String totalActive;
final String totalFactor;
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
PowerClampInfoCard(
iconPath: Assets.voltageIcon,
title: 'Voltage',
value: totalVoltage,
unit: '',
),
PowerClampInfoCard(
iconPath: Assets.voltMeterIcon,
title: 'Current',
value: totalCurrent,
unit: '',
),
PowerClampInfoCard(
iconPath: Assets.powerActiveIcon,
title: 'Active Power',
value: totalActive,
unit: '',
),
PowerClampInfoCard(
iconPath: Assets.speedoMeter,
title: 'Power Factor',
value: totalFactor,
unit: '',
),
],
),
],
),
);
}
}
@@ -0,0 +1,281 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class EnergyConsumptionPage extends StatefulWidget {
final List<dynamic> chartData;
final double totalConsumption;
final String date;
final String formattedDate;
final Widget widget;
final Function()? onTap;
EnergyConsumptionPage({
required this.chartData,
required this.totalConsumption,
required this.date,
required this.widget,
required this.onTap,
required this.formattedDate,
});
@override
_EnergyConsumptionPageState createState() => _EnergyConsumptionPageState();
}
class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
late List<dynamic> _chartData;
@override
void initState() {
_chartData = widget.chartData;
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
color: ColorsManager.whiteColors,
child: Column(
children: [
const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Total Consumption',
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 20,
),
),
Text(
'8623.20 kWh',
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 20,
),
),
],
),
const Row(
children: [
Text(
'Energy consumption',
style: TextStyle(
color: ColorsManager.grayColor,
fontWeight: FontWeight.w700,
fontSize: 12,
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.formattedDate,
style: const TextStyle(
color: ColorsManager.grayColor,
fontWeight: FontWeight.w400,
fontSize: 8,
),
),
const Text(
'1000.00 kWh',
style: TextStyle(
color: ColorsManager.grayColor,
fontWeight: FontWeight.w400,
fontSize: 8,
),
),
],
),
Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 10),
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.11,
child: LineChart(
LineChartData(
lineTouchData: LineTouchData(
handleBuiltInTouches: true,
touchSpotThreshold: 2,
getTouchLineEnd: (barData, spotIndex) {
return 10.0;
},
touchTooltipData: LineTouchTooltipData(
getTooltipColor: (touchTooltipItem) => Colors.white,
tooltipRoundedRadius: 10.0,
tooltipPadding: const EdgeInsets.all(8.0),
tooltipBorder: const BorderSide(
color: ColorsManager.grayColor, width: 1),
getTooltipItems: (List<LineBarSpot> touchedSpots) {
return touchedSpots.map((spot) {
return LineTooltipItem(
'${spot.x},\n ${spot.y.toStringAsFixed(2)} kWh',
const TextStyle(
color: Colors.blue,
fontWeight: FontWeight.bold,
fontSize: 12,
),
);
}).toList();
},
)),
titlesData: FlTitlesData(
bottomTitles: const AxisTitles(
sideTitles: SideTitles(
showTitles: false,
),
),
leftTitles: const AxisTitles(
sideTitles: SideTitles(
showTitles: false,
),
),
rightTitles: const AxisTitles(
sideTitles: SideTitles(
showTitles: false,
),
),
topTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: false,
reservedSize: 70,
getTitlesWidget: (value, meta) {
int index = value.toInt();
if (index >= 0 && index < _chartData.length) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: RotatedBox(
quarterTurns: -1,
child: Text(_chartData[index].time,
style: TextStyle(fontSize: 10)),
),
);
}
return const SizedBox.shrink();
},
),
),
),
gridData: FlGridData(
show: true,
drawVerticalLine: true,
horizontalInterval: 1,
verticalInterval: 1,
getDrawingVerticalLine: (value) {
return FlLine(
color: Colors.grey.withOpacity(0.2),
dashArray: [8, 8],
strokeWidth: 1,
);
},
getDrawingHorizontalLine: (value) {
return FlLine(
color: Colors.grey.withOpacity(0.2),
dashArray: [5, 5],
strokeWidth: 1,
);
},
drawHorizontalLine: false,
),
lineBarsData: [
LineChartBarData(
preventCurveOvershootingThreshold: 0.1,
curveSmoothness: 0.5,
preventCurveOverShooting: true,
aboveBarData: BarAreaData(),
spots: _chartData
.asMap()
.entries
.map((entry) => FlSpot(entry.key.toDouble(),
entry.value.consumption))
.toList(),
isCurved: true,
color: ColorsManager.primaryColor.withOpacity(0.6),
show: true,
shadow: const Shadow(color: Colors.black12),
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
colors: [
ColorsManager.primaryColor.withOpacity(0.5),
Colors.blue.withOpacity(0.1),
],
begin: Alignment.center,
end: Alignment.bottomCenter,
),
),
dotData: const FlDotData(
show: false,
),
isStrokeCapRound: true,
barWidth: 2,
),
],
borderData: FlBorderData(
show: false,
border: Border.all(
color: Color(0xff023DFE).withOpacity(0.7),
width: 10,
),
),
),
),
),
),
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
color: ColorsManager.graysColor,
borderRadius: BorderRadius.circular(10),
),
child: Container(child: widget.widget),
),
),
const SizedBox(
width: 20,
),
Expanded(
child: Container(
padding: const EdgeInsets.all(5.0),
decoration: BoxDecoration(
color: ColorsManager.graysColor,
borderRadius: BorderRadius.circular(10),
),
child: InkWell(
onTap: widget.onTap,
child: Center(
child: SizedBox(
child: Padding(
padding: const EdgeInsets.all(5),
child: Text(widget.date),
),
),
),
),
),
),
],
),
)
],
),
],
),
);
}
}
class EnergyData {
EnergyData(this.time, this.consumption);
final String time;
final double consumption;
}
@@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_bloc.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_event.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_state.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_batch_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class PowerClampBatchControlView extends StatelessWidget
with HelperResponsiveLayout {
final List<String> deviceIds;
const PowerClampBatchControlView({Key? key, required this.deviceIds})
: super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => SmartPowerBloc(deviceId: deviceIds.first)
..add(SmartPowerFetchBatchEvent(deviceIds)),
child: BlocBuilder<SmartPowerBloc, SmartPowerState>(
builder: (context, state) {
if (state is SmartPowerLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is SmartPowerLoadBatchControll) {
return _buildStatusControls(context, state.status);
} else if (state is SmartPowerError) {
return Center(child: Text('Error: ${state.message}'));
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
);
}
Widget _buildStatusControls(
BuildContext context, PowerClampBatchModel status) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 170,
// height: 140,
child: FirmwareUpdateWidget(deviceId: deviceIds.first, version: 2)),
const SizedBox(
width: 12,
),
SizedBox(
width: 170,
height: 140,
child: FactoryResetWidget(
callFactoryReset: () {
context.read<SmartPowerBloc>().add(SmartPowerFactoryReset(
deviceId: deviceIds.first,
factoryReset: FactoryResetModel(devicesUuid: deviceIds)));
},
),
),
],
);
}
}
@@ -0,0 +1,81 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class PowerClampInfoCard extends StatelessWidget {
final String iconPath;
final String title;
final String value;
final String unit;
const PowerClampInfoCard({
Key? key,
required this.iconPath,
required this.title,
required this.value,
required this.unit,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Expanded(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 6),
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(20),
),
height: 55,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
const SizedBox(
width: 16,
),
SvgPicture.asset(
iconPath,
fit: BoxFit.fill,
),
const SizedBox(
width: 18,
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
title,
style: const TextStyle(
fontSize: 8,
fontWeight: FontWeight.w400,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
value,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
),
),
Text(
unit,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
),
),
],
),
],
)
],
),
),
);
}
}
@@ -0,0 +1,279 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_bloc.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_event.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_state.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/view/phase_widget.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_chart.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_info_card.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
//Smart Power Clamp
class SmartPowerDeviceControl extends StatelessWidget
with HelperResponsiveLayout {
final String deviceId;
const SmartPowerDeviceControl({super.key, required this.deviceId});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => SmartPowerBloc(deviceId: deviceId)
..add(SmartPowerFetchDeviceEvent(deviceId)),
child: BlocBuilder<SmartPowerBloc, SmartPowerState>(
builder: (context, state) {
final _blocProvider = BlocProvider.of<SmartPowerBloc>(context);
if (state is SmartPowerLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is FakeState) {
return _buildStatusControls(
currentPage: _blocProvider.currentPage,
context: context,
blocProvider: _blocProvider,
);
} else if (state is GetDeviceStatus) {
return _buildStatusControls(
currentPage: _blocProvider.currentPage,
context: context,
blocProvider: _blocProvider,
);
} else if (state is FilterRecordsState) {
return _buildStatusControls(
currentPage: _blocProvider.currentPage,
context: context,
blocProvider: _blocProvider,
);
}
return const Center(child: CircularProgressIndicator());
// }
},
),
);
}
Widget _buildStatusControls({
required BuildContext context,
required SmartPowerBloc blocProvider,
required int currentPage,
}) {
PageController _pageController = PageController(initialPage: currentPage);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 50),
child: DeviceControlsContainer(
child: Column(
children: [
const Row(
children: [
Text(
'Live',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.w700,
color: ColorsManager.textPrimaryColor),
),
],
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
PowerClampInfoCard(
iconPath: Assets.powerActiveIcon,
title: 'Active',
value: blocProvider
.deviceStatus.status.general.dataPoints[2].value
.toString(),
unit: '',
),
PowerClampInfoCard(
iconPath: Assets.voltMeterIcon,
title: 'Current',
value: blocProvider
.deviceStatus.status.general.dataPoints[1].value
.toString(),
unit: ' A',
),
PowerClampInfoCard(
iconPath: Assets.frequencyIcon,
title: 'Frequency',
value: blocProvider
.deviceStatus.status.general.dataPoints[4].value
.toString(),
unit: ' Hz',
),
],
),
),
PhaseWidget(
phaseData: blocProvider.phaseData,
),
const SizedBox(
height: 10,
),
Container(
padding: const EdgeInsets.only(
top: 10,
left: 20,
right: 20,
bottom: 10,
),
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(20),
),
height: 300,
child: Column(
children: [
Container(
decoration: BoxDecoration(
color: ColorsManager.graysColor,
borderRadius: BorderRadius.circular(20),
),
height: 50,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: const Icon(Icons.arrow_left),
onPressed: () {
blocProvider.add(SmartPowerArrowPressedEvent(-1));
_pageController.previousPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
),
Text(
currentPage == 0
? 'Total'
: currentPage == 1
? 'Phase A'
: currentPage == 2
? 'Phase B'
: 'Phase C',
style: const TextStyle(fontSize: 18),
),
IconButton(
icon: const Icon(Icons.arrow_right),
onPressed: () {
blocProvider.add(SmartPowerArrowPressedEvent(1));
_pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
),
],
),
),
const SizedBox(
height: 5,
),
Expanded(
flex: 2,
child: PageView(
controller: _pageController,
onPageChanged: (int page) {
blocProvider.add(SmartPowerPageChangedEvent(page));
},
physics: const NeverScrollableScrollPhysics(),
children: [
EnergyConsumptionPage(
formattedDate:
'${blocProvider.dateTime!.day}/${blocProvider.dateTime!.month}/${blocProvider.dateTime!.year} ${blocProvider.endChartDate}',
onTap: () {
blocProvider.add(SelectDateEvent(context: context));
blocProvider.add(FilterRecordsByDateEvent(
selectedDate: blocProvider.dateTime!,
viewType: blocProvider
.views[blocProvider.currentIndex]));
},
widget: blocProvider.dateSwitcher(),
chartData: blocProvider.energyDataList.isNotEmpty
? blocProvider.energyDataList
: [
EnergyData('12:00 AM', 4.0),
EnergyData('01:00 AM', 3.5),
EnergyData('02:00 AM', 3.8),
EnergyData('03:00 AM', 3.2),
EnergyData('04:00 AM', 4.0),
EnergyData('05:00 AM', 3.4),
EnergyData('06:00 AM', 3.2),
EnergyData('07:00 AM', 3.5),
EnergyData('08:00 AM', 3.8),
EnergyData('09:00 AM', 3.6),
EnergyData('10:00 AM', 3.9),
EnergyData('11:00 AM', 4.0),
],
totalConsumption: 10000,
date: blocProvider.formattedDate,
),
EnergyConsumptionPage(
formattedDate:
'${blocProvider.dateTime!.day}/${blocProvider.dateTime!.month}/${blocProvider.dateTime!.year} ${blocProvider.endChartDate}',
onTap: () {
blocProvider.add(SelectDateEvent(context: context));
},
widget: blocProvider.dateSwitcher(),
chartData: blocProvider.energyDataList.isNotEmpty
? blocProvider.energyDataList
: [
EnergyData('12:00 AM', 4.0),
EnergyData('01:00 AM', 3.5),
EnergyData('02:00 AM', 3.8),
EnergyData('03:00 AM', 3.2),
EnergyData('04:00 AM', 4.0),
EnergyData('05:00 AM', 3.4),
EnergyData('06:00 AM', 3.2),
EnergyData('07:00 AM', 3.5),
EnergyData('08:00 AM', 3.8),
EnergyData('09:00 AM', 3.6),
EnergyData('10:00 AM', 3.9),
EnergyData('11:00 AM', 4.0),
],
totalConsumption: 10000,
date: blocProvider.formattedDate,
),
EnergyConsumptionPage(
formattedDate:
'${blocProvider.dateTime!.day}/${blocProvider.dateTime!.month}/${blocProvider.dateTime!.year} ${blocProvider.endChartDate}',
onTap: () {
blocProvider.add(SelectDateEvent(context: context));
},
widget: blocProvider.dateSwitcher(),
chartData: blocProvider.energyDataList.isNotEmpty
? blocProvider.energyDataList
: [
EnergyData('12:00 AM', 4.0),
EnergyData('01:00 AM', 6.5),
EnergyData('02:00 AM', 3.8),
EnergyData('03:00 AM', 3.2),
EnergyData('04:00 AM', 6.0),
EnergyData('05:00 AM', 3.4),
EnergyData('06:00 AM', 5.2),
EnergyData('07:00 AM', 3.5),
EnergyData('08:00 AM', 3.8),
EnergyData('09:00 AM', 5.6),
EnergyData('10:00 AM', 6.9),
EnergyData('11:00 AM', 6.0),
],
totalConsumption: 10000,
date: blocProvider.formattedDate,
),
],
),
),
],
),
),
],
),
),
);
}
}
@@ -52,6 +52,7 @@ class _FactoryResetWidgetState extends State<FactoryResetWidget> {
child: DefaultButton(
height: 20,
elevation: 0,
padding: 0,
onPressed: _toggleConfirmation,
backgroundColor: ColorsManager.greyColor,
child: Text(
@@ -69,14 +70,16 @@ class _FactoryResetWidgetState extends State<FactoryResetWidget> {
child: DefaultButton(
height: 20,
elevation: 0,
padding: 0,
onPressed: widget.callFactoryReset,
backgroundColor: ColorsManager.red,
child: Text(
'Reset',
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.whiteColors,
fontWeight: FontWeight.w400,
fontSize: 12),
color: ColorsManager.whiteColors,
fontWeight: FontWeight.w400,
fontSize: 12,
),
),
),
),
@@ -7,7 +7,8 @@ import 'package:syncrow_web/pages/device_managment/all_devices/helper/route_cont
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class DeviceBatchControlDialog extends StatelessWidget with RouteControlsBasedCode {
class DeviceBatchControlDialog extends StatelessWidget
with RouteControlsBasedCode {
final List<AllDevicesModel> devices;
const DeviceBatchControlDialog({super.key, required this.devices});
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/helper/route_controls_based_code.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/format_date_time.dart';
@@ -31,7 +32,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
children: [
const SizedBox(),
Text(
device.productName ?? 'Device Control',
getBatchDialogName(device),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 22,
@@ -64,7 +65,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
),
const SizedBox(height: 20),
_buildDeviceInfoSection(),
const SizedBox(height: 20),
//const SizedBox(height: 20),
//// BUILD DEVICE CONTROLS
///
//// ROUTE TO SPECIFIC CONTROL VIEW BASED ON DEVICE CATEGORY
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class DeviceControlsContainer extends StatelessWidget {
const DeviceControlsContainer({required this.child, this.padding, super.key});
@@ -8,21 +7,21 @@ class DeviceControlsContainer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
color: ColorsManager.greyColor.withOpacity(0.2),
// boxShadow: <BoxShadow>[
// BoxShadow(
// color: ColorsManager.blackColor.withOpacity(0.05),
// blurRadius: 6.0,
// offset: const Offset(0, 5),
// spreadRadius: 0)
// ],
),
padding: EdgeInsets.all(padding ?? 12),
child: child,
elevation: 3,
surfaceTintColor: Colors.transparent,
child: Container(
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(20),
),
padding:
EdgeInsets.symmetric(vertical: padding ?? 10, horizontal: padding ?? 16), //EdgeInsets.all(padding ?? 12),
child: child,
),
);
}
}
+2 -1
View File
@@ -18,10 +18,11 @@ class AuthenticationAPI {
static Future forgetPassword({
required var email,
required var password,
required var otpCode,
}) async {
final response = await HTTPService().post(
path: ApiEndpoints.forgetPassword,
body: {"email": email, "password": password},
body: {"email": email, "password": password,"otpCode": otpCode},
showServerMessage: true,
expectedResponseModel: (json) {});
return response;
+41
View File
@@ -1,3 +1,4 @@
import 'dart:core';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
@@ -50,6 +51,22 @@ class DevicesManagementApi {
}
}
Future getPowerClampInfo(String deviceId) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.powerClamp.replaceAll('{powerClampUuid}', deviceId),
showServerMessage: true,
expectedResponseModel: (json) {
return json;
},
);
return response;
} catch (e) {
debugPrint('Error fetching $e');
return {};
}
}
//deviceControl
Future<bool> deviceControl(String uuid, Status status) async {
try {
@@ -179,6 +196,30 @@ class DevicesManagementApi {
}
}
getPowerStatus(List<String> uuids) async {
try {
final queryParameters = {
'devicesUuid': uuids.join(','),
};
final response = await HTTPService().get(
path: ApiEndpoints.getBatchStatus,
queryParameters: queryParameters,
showServerMessage: true,
expectedResponseModel: (json) {
return json;
},
);
return response;
} catch (e) {
debugPrint('Error fetching $e');
return DeviceStatus(
productUuid: '',
productType: '',
status: [],
);
}
}
Future<bool> addScheduleRecord(
ScheduleEntry sendSchedule, String uuid) async {
try {
+2 -1
View File
@@ -42,4 +42,5 @@ abstract class ColorsManager {
static const Color textGreen = Color(0xFF008905);
static const Color yaGreen = Color(0xFFFFBF44);
}
//0036E6
//background: #background: #5D5D5D;
+3 -1
View File
@@ -47,5 +47,7 @@ abstract class ApiEndpoints {
static const String updateScheduleByDeviceId =
'/schedule/enable/{deviceUuid}';
static const String factoryReset = '/device/factory/reset/{deviceUuid}';
static const String factoryReset = '/device/factory/reset/{deviceUuid}';
static const String powerClamp =
'/device/{powerClampUuid}/power-clamp/status';
}
+30 -6
View File
@@ -60,23 +60,29 @@ class Assets {
static const String nobodyTime = "assets/icons/nobody_time.svg";
// Automation functions
static const String tempPasswordUnlock = "assets/icons/automation_functions/temp_password_unlock.svg";
static const String doorlockNormalOpen = "assets/icons/automation_functions/doorlock_normal_open.svg";
static const String tempPasswordUnlock =
"assets/icons/automation_functions/temp_password_unlock.svg";
static const String doorlockNormalOpen =
"assets/icons/automation_functions/doorlock_normal_open.svg";
static const String doorbell = "assets/icons/automation_functions/doorbell.svg";
static const String remoteUnlockViaApp = "assets/icons/automation_functions/remote_unlock_via_app.svg";
static const String remoteUnlockViaApp =
"assets/icons/automation_functions/remote_unlock_via_app.svg";
static const String doubleLock = "assets/icons/automation_functions/double_lock.svg";
static const String selfTestResult = "assets/icons/automation_functions/self_test_result.svg";
static const String lockAlarm = "assets/icons/automation_functions/lock_alarm.svg";
static const String presenceState = "assets/icons/automation_functions/presence_state.svg";
static const String currentTemp = "assets/icons/automation_functions/current_temp.svg";
static const String presence = "assets/icons/automation_functions/presence.svg";
static const String residualElectricity = "assets/icons/automation_functions/residual_electricity.svg";
static const String residualElectricity =
"assets/icons/automation_functions/residual_electricity.svg";
static const String hijackAlarm = "assets/icons/automation_functions/hijack_alarm.svg";
static const String passwordUnlock = "assets/icons/automation_functions/password_unlock.svg";
static const String remoteUnlockRequest = "assets/icons/automation_functions/remote_unlock_req.svg";
static const String remoteUnlockRequest =
"assets/icons/automation_functions/remote_unlock_req.svg";
static const String cardUnlock = "assets/icons/automation_functions/card_unlock.svg";
static const String motion = "assets/icons/automation_functions/motion.svg";
static const String fingerprintUnlock = "assets/icons/automation_functions/fingerprint_unlock.svg";
static const String fingerprintUnlock =
"assets/icons/automation_functions/fingerprint_unlock.svg";
// Presence Sensor Assets
static const String sensorMotionIcon = "assets/icons/sensor_motion_ic.svg";
@@ -168,4 +174,22 @@ class Assets {
//assets/icons/2gang.svg
static const String twoGang = 'assets/icons/2gang.svg';
static const String frequencyIcon = "assets/icons/frequency_icon.svg";
static const String voltMeterIcon = "assets/icons/volt_meter_icon.svg";
static const String powerActiveIcon = "assets/icons/power_active_icon.svg";
static const String searchIcon = "assets/icons/search_icon.svg";
static const String voltageIcon = "assets/icons/voltage_icon.svg";
static const String speedoMeter = "assets/icons/speedo_meter.svg";
//assets/icons/account_setting.svg
static const String accountSetting = 'assets/icons/account_setting.svg';
//assets/icons/settings.svg
static const String settings = 'assets/icons/settings.svg';
//assets/icons/sign_out.svg
static const String signOut = 'assets/icons/sign_out.svg';
//assets/icons/logo_grey.svg
static const String logoGrey = 'assets/icons/logo-grey.svg';
}
+4 -5
View File
@@ -33,12 +33,11 @@ InputDecoration? textBoxDecoration({bool suffixIcon = false}) =>
BoxDecoration containerDecoration = BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 5,
blurRadius: 8,
offset: const Offset(0, 3), // changes position of shadow
color: Colors.grey.withOpacity(0.3),
spreadRadius: 2,
blurRadius: 4,
offset: const Offset(0, 5), // changes position of shadow
),
],
color: ColorsManager.boxColor,
borderRadius: const BorderRadius.all(Radius.circular(10)));
+1
View File
@@ -3,6 +3,7 @@ import 'package:syncrow_web/utils/color_manager.dart';
final myTheme = ThemeData(
fontFamily: 'Aftika',
useMaterial3: true,
textTheme: const TextTheme(
bodySmall: TextStyle(
fontSize: 13,
+239
View File
@@ -0,0 +1,239 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/pages/auth/model/user_model.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class UserDropdownMenu extends StatefulWidget {
const UserDropdownMenu({super.key, required this.user});
final UserModel? user;
@override
_UserDropdownMenuState createState() => _UserDropdownMenuState();
}
class _UserDropdownMenuState extends State<UserDropdownMenu> {
bool _isDropdownOpen = false;
void _toggleDropdown() {
setState(() {
_isDropdownOpen = !_isDropdownOpen;
});
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
onTap: () async {
_toggleDropdown();
await _showPopupMenu(context);
setState(() {
_isDropdownOpen = false;
});
},
child: Transform.rotate(
angle: _isDropdownOpen ? -1.5708 : 1.5708,
child: const Icon(
Icons.arrow_forward_ios,
color: Colors.white,
size: 16,
),
),
),
],
);
}
Future<void> _showPopupMenu(BuildContext context) async {
final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox;
final RelativeRect position = RelativeRect.fromRect(
Rect.fromLTRB(
overlay.size.width,
75,
0,
overlay.size.height,
),
Offset.zero & overlay.size,
);
await showMenu(
context: context,
position: position,
color: ColorsManager.whiteColors,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(10),
bottomLeft: Radius.circular(10),
),
),
items: <PopupMenuEntry>[
PopupMenuItem(
onTap: () {},
child: ListTile(
leading: SvgPicture.asset(Assets.accountSetting),
title: Text(
"Account Settings",
style: context.textTheme.bodyMedium,
),
),
),
PopupMenuItem(
onTap: () {},
child: ListTile(
leading: SvgPicture.asset(Assets.settings),
title: Text(
"Settings",
style: context.textTheme.bodyMedium,
),
),
),
PopupMenuItem(
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) {
final size = MediaQuery.of(context).size;
return AlertDialog(
alignment: Alignment.center,
content: SizedBox(
height: 200,
width: 400,
child: Padding(
padding: const EdgeInsets.only(top: 24, left: 24, right: 24),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Image.asset(
Assets.blackLogo,
height: 40,
width: 200,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text(
'Log out of your Syncrow account',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Colors.black,
),
),
),
const SizedBox(
height: 16,
),
Row(
children: [
SizedBox.square(
dimension: 80,
child: CircleAvatar(
backgroundColor: ColorsManager.whiteColors,
child: SizedBox.square(
dimension: 78,
child: SvgPicture.asset(
Assets.logoGrey,
fit: BoxFit.fitHeight,
height: 80,
),
),
),
),
const SizedBox(
width: 16,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${widget.user?.firstName ?? ''} ${widget.user?.lastName}',
style: Theme.of(context).textTheme.titleMedium!.copyWith(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
Text(
' ${widget.user?.email}',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Colors.black,
),
),
],
),
),
],
),
],
),
),
),
actionsAlignment: MainAxisAlignment.center,
actions: [
SizedBox(
width: 200,
child: GestureDetector(
onTap: () {
context.pop();
},
child: DefaultButton(
backgroundColor: ColorsManager.boxColor,
elevation: 1,
child: Text(
'Cancel',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 12,
color: Colors.black,
),
),
),
),
),
const SizedBox(
height: 10,
),
GestureDetector(
onTap: () {
AuthBloc.logout();
context.go(RoutesConst.auth);
},
child: SizedBox(
width: 200,
child: DefaultButton(
elevation: 1,
child: Text(
'Logout',
style:
Theme.of(context).textTheme.bodyMedium!.copyWith(fontSize: 12, color: Colors.white),
),
),
),
),
]);
},
);
},
child: ListTile(
leading: SvgPicture.asset(Assets.signOut),
title: Text(
"Log Out",
style: context.textTheme.bodyMedium,
),
),
),
],
).then((value) {
setState(() {
_isDropdownOpen = false;
});
});
}
}
+62 -63
View File
@@ -1,14 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/common/custom_dialog.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_state.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/utils/user_drop_down_menu.dart';
class WebAppBar extends StatefulWidget {
final Widget? title;
@@ -58,15 +56,15 @@ class _WebAppBarState extends State<WebAppBar> with HelperResponsiveLayout {
if (widget.rightBody != null) widget.rightBody!,
Row(
children: [
const SizedBox.square(
SizedBox.square(
dimension: 40,
child: CircleAvatar(
backgroundColor: Colors.white,
backgroundColor: ColorsManager.whiteColors,
child: SizedBox.square(
dimension: 35,
child: CircleAvatar(
backgroundColor: Colors.grey,
child: FlutterLogo(),
child: SvgPicture.asset(
Assets.logoGrey,
fit: BoxFit.cover,
),
),
),
@@ -112,15 +110,15 @@ class _WebAppBarState extends State<WebAppBar> with HelperResponsiveLayout {
const SizedBox(
width: 10,
),
const SizedBox.square(
SizedBox.square(
dimension: 40,
child: CircleAvatar(
backgroundColor: Colors.white,
backgroundColor: ColorsManager.whiteColors,
child: SizedBox.square(
dimension: 35,
child: CircleAvatar(
backgroundColor: Colors.grey,
child: FlutterLogo(),
child: SvgPicture.asset(
Assets.logoGrey,
fit: BoxFit.cover,
),
),
),
@@ -136,54 +134,55 @@ class _WebAppBarState extends State<WebAppBar> with HelperResponsiveLayout {
const SizedBox(
width: 10,
),
GestureDetector(
onTap: () {
showCustomDialog(
context: context,
barrierDismissible: true,
title: 'Logout',
message: 'Are you sure you want to logout?',
actions: [
GestureDetector(
onTap: () {
AuthBloc.logout();
context.go(RoutesConst.auth);
},
child: DefaultButton(
child: Text(
'Ok',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(fontSize: 12, color: Colors.white),
),
),
),
const SizedBox(
height: 10,
),
GestureDetector(
onTap: () {
context.pop();
},
child: DefaultButton(
child: Text(
'Cancel',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(fontSize: 12, color: Colors.white),
),
),
),
],
);
},
child: const Icon(
Icons.logout,
color: ColorsManager.whiteColors,
),
)
UserDropdownMenu(user: user),
// GestureDetector(
// onTap: () {
// showCustomDialog(
// context: context,
// barrierDismissible: true,
// title: 'Logout',
// message: 'Are you sure you want to logout?',
// actions: [
// GestureDetector(
// onTap: () {
// AuthBloc.logout();
// context.go(RoutesConst.auth);
// },
// child: DefaultButton(
// child: Text(
// 'Ok',
// style: Theme.of(context)
// .textTheme
// .bodyMedium!
// .copyWith(fontSize: 12, color: Colors.white),
// ),
// ),
// ),
// const SizedBox(
// height: 10,
// ),
// GestureDetector(
// onTap: () {
// context.pop();
// },
// child: DefaultButton(
// child: Text(
// 'Cancel',
// style: Theme.of(context)
// .textTheme
// .bodyMedium!
// .copyWith(fontSize: 12, color: Colors.white),
// ),
// ),
// ),
// ],
// );
// },
// child: const Icon(
// Icons.logout,
// color: ColorsManager.whiteColors,
// ),
// )
],
),
],