Compare commits

..

15 Commits

30 changed files with 762 additions and 563 deletions

View File

@ -107,26 +107,28 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
Future<void> _filterData(FilterDataEvent event, Emitter<AccessState> emit) async { Future<void> _filterData(FilterDataEvent event, Emitter<AccessState> emit) async {
emit(AccessLoaded()); emit(AccessLoaded());
try { try {
// Convert search text to lower case for case-insensitive search
final searchText = event.passwordName?.toLowerCase() ?? '';
filteredData = data.where((item) { filteredData = data.where((item) {
bool matchesCriteria = true; bool matchesCriteria = true;
// Convert timestamp to DateTime and extract date component // Convert timestamp to DateTime and extract date component
DateTime effectiveDate = DateTime effectiveDate =
DateTime.fromMillisecondsSinceEpoch(int.parse(item.effectiveTime.toString()) * 1000) DateTime.fromMillisecondsSinceEpoch(int.parse(item.effectiveTime.toString()) * 1000)
.toUtc() .toUtc()
.toLocal(); .toLocal();
DateTime invalidDate = DateTime invalidDate =
DateTime.fromMillisecondsSinceEpoch(int.parse(item.invalidTime.toString()) * 1000) DateTime.fromMillisecondsSinceEpoch(int.parse(item.invalidTime.toString()) * 1000)
.toUtc() .toUtc()
.toLocal(); .toLocal();
DateTime effectiveDateOnly = DateTime effectiveDateOnly =
DateTime(effectiveDate.year, effectiveDate.month, effectiveDate.day); DateTime(effectiveDate.year, effectiveDate.month, effectiveDate.day);
DateTime invalidDateOnly = DateTime(invalidDate.year, invalidDate.month, invalidDate.day); DateTime invalidDateOnly = DateTime(invalidDate.year, invalidDate.month, invalidDate.day);
// Filter by password name // Filter by password name, making the search case-insensitive
if (event.passwordName != null && event.passwordName!.isNotEmpty) { if (searchText.isNotEmpty) {
final bool matchesName = final bool matchesName = item.passwordName.toString().toLowerCase().contains(searchText);
item.passwordName != null && item.passwordName.contains(event.passwordName);
if (!matchesName) { if (!matchesName) {
matchesCriteria = false; matchesCriteria = false;
} }
@ -135,7 +137,7 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
// Filter by start date only // Filter by start date only
if (event.startTime != null && event.endTime == null) { if (event.startTime != null && event.endTime == null) {
DateTime startDateOnly = DateTime startDateOnly =
DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000).toUtc().toLocal(); DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000).toUtc().toLocal();
startDateOnly = DateTime(startDateOnly.year, startDateOnly.month, startDateOnly.day); startDateOnly = DateTime(startDateOnly.year, startDateOnly.month, startDateOnly.day);
if (effectiveDateOnly.isBefore(startDateOnly)) { if (effectiveDateOnly.isBefore(startDateOnly)) {
matchesCriteria = false; matchesCriteria = false;
@ -145,7 +147,7 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
// Filter by end date only // Filter by end date only
if (event.endTime != null && event.startTime == null) { if (event.endTime != null && event.startTime == null) {
DateTime endDateOnly = DateTime endDateOnly =
DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000).toUtc().toLocal(); DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000).toUtc().toLocal();
endDateOnly = DateTime(endDateOnly.year, endDateOnly.month, endDateOnly.day); endDateOnly = DateTime(endDateOnly.year, endDateOnly.month, endDateOnly.day);
if (invalidDateOnly.isAfter(endDateOnly)) { if (invalidDateOnly.isAfter(endDateOnly)) {
matchesCriteria = false; matchesCriteria = false;
@ -155,9 +157,9 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
// Filter by both start date and end date // Filter by both start date and end date
if (event.startTime != null && event.endTime != null) { if (event.startTime != null && event.endTime != null) {
DateTime startDateOnly = DateTime startDateOnly =
DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000).toUtc().toLocal(); DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000).toUtc().toLocal();
DateTime endDateOnly = DateTime endDateOnly =
DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000).toUtc().toLocal(); DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000).toUtc().toLocal();
startDateOnly = DateTime(startDateOnly.year, startDateOnly.month, startDateOnly.day); startDateOnly = DateTime(startDateOnly.year, startDateOnly.month, startDateOnly.day);
endDateOnly = DateTime(endDateOnly.year, endDateOnly.month, endDateOnly.day); endDateOnly = DateTime(endDateOnly.year, endDateOnly.month, endDateOnly.day);
if (effectiveDateOnly.isBefore(startDateOnly) || invalidDateOnly.isAfter(endDateOnly)) { if (effectiveDateOnly.isBefore(startDateOnly) || invalidDateOnly.isAfter(endDateOnly)) {
@ -183,6 +185,8 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
} }
} }
resetSearch(ResetSearch event, Emitter<AccessState> emit) async { resetSearch(ResetSearch event, Emitter<AccessState> emit) async {
emit(AccessLoaded()); emit(AccessLoaded());
startTime = 'Start Time'; startTime = 'Start Time';

View File

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

View File

@ -27,8 +27,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
final isLargeScreen = isLargeScreenSize(context); final isLargeScreen = isLargeScreenSize(context);
final isSmallScreen = isSmallScreenSize(context); final isSmallScreen = isSmallScreenSize(context);
final isHalfMediumScreen = isHafMediumScreenSize(context); final isHalfMediumScreen = isHafMediumScreenSize(context);
final padding = final padding = isLargeScreen ? const EdgeInsets.all(30) : const EdgeInsets.all(15);
isLargeScreen ? const EdgeInsets.all(30) : const EdgeInsets.all(15);
return WebScaffold( return WebScaffold(
enableMenuSideba: false, enableMenuSideba: false,
@ -38,17 +37,23 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
style: Theme.of(context).textTheme.headlineLarge, style: Theme.of(context).textTheme.headlineLarge,
), ),
), ),
centerBody: Text( centerBody: Wrap(
'Physical Access', children: [
style: Theme.of(context) Padding(
.textTheme padding: EdgeInsets.only(left: MediaQuery.of(context).size.width * 0.09),
.headlineMedium! child: Align(
.copyWith(color: Colors.white), alignment: Alignment.bottomLeft,
child: Text(
'Physical Access',
style: Theme.of(context).textTheme.headlineMedium!.copyWith(color: Colors.white),
),
),
),
],
), ),
rightBody: const NavigateHomeGridView(), rightBody: const NavigateHomeGridView(),
scaffoldBody: BlocProvider( scaffoldBody: BlocProvider(
create: (BuildContext context) => create: (BuildContext context) => AccessBloc()..add(FetchTableData()),
AccessBloc()..add(FetchTableData()),
child: BlocConsumer<AccessBloc, AccessState>( child: BlocConsumer<AccessBloc, AccessState>(
listener: (context, state) {}, listener: (context, state) {},
builder: (context, state) { builder: (context, state) {
@ -82,6 +87,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
const SizedBox(height: 20), const SizedBox(height: 20),
Expanded( Expanded(
child: DynamicTable( child: DynamicTable(
uuidIndex: 1,
withSelectAll: true,
isEmpty: filteredData.isEmpty, isEmpty: filteredData.isEmpty,
withCheckBox: false, withCheckBox: false,
size: MediaQuery.of(context).size, size: MediaQuery.of(context).size,
@ -97,7 +104,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
], ],
data: filteredData.map((item) { data: filteredData.map((item) {
return [ return [
item.passwordName.toString(), item.passwordName,
item.passwordType.value, item.passwordType.value,
('${accessBloc.timestampToDate(item.effectiveTime)} - ${accessBloc.timestampToDate(item.invalidTime)}'), ('${accessBloc.timestampToDate(item.effectiveTime)} - ${accessBloc.timestampToDate(item.invalidTime)}'),
item.deviceUuid.toString(), item.deviceUuid.toString(),
@ -113,8 +120,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
}))); })));
} }
Wrap _buildVisitorAdminPasswords( Wrap _buildVisitorAdminPasswords(BuildContext context, AccessBloc accessBloc) {
BuildContext context, AccessBloc accessBloc) {
return Wrap( return Wrap(
spacing: 10, spacing: 10,
runSpacing: 10, runSpacing: 10,
@ -140,8 +146,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
borderRadius: 8, borderRadius: 8,
child: Text( child: Text(
'+ Create Visitor Password ', '+ Create Visitor Password ',
style: context.textTheme.titleSmall! style: context.textTheme.titleSmall!.copyWith(color: Colors.white, fontSize: 12),
.copyWith(color: Colors.white, fontSize: 12),
)), )),
), ),
Container( Container(
@ -153,8 +158,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
backgroundColor: ColorsManager.whiteColors, backgroundColor: ColorsManager.whiteColors,
child: Text( child: Text(
'Admin Password', 'Admin Password',
style: context.textTheme.titleSmall! style: context.textTheme.titleSmall!.copyWith(color: Colors.black, fontSize: 12),
.copyWith(color: Colors.black, fontSize: 12),
)), )),
), ),
], ],
@ -171,15 +175,14 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
width: 250, width: 250,
child: CustomWebTextField( child: CustomWebTextField(
controller: accessBloc.passwordName, controller: accessBloc.passwordName,
height: 38, height: 43,
isRequired: true, isRequired: false,
textFieldName: 'Name', textFieldName: 'Name',
description: '', description: '',
), ),
), ),
const SizedBox(width: 15), const SizedBox(width: 15),
SizedBox( SizedBox(
height: 70,
child: DateTimeWebWidget( child: DateTimeWebWidget(
icon: Assets.calendarIcon, icon: Assets.calendarIcon,
isRequired: false, isRequired: false,
@ -199,8 +202,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
SearchResetButtons( SearchResetButtons(
onSearch: () { onSearch: () {
accessBloc.add(FilterDataEvent( accessBloc.add(FilterDataEvent(
selectedTabIndex: selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex,
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(), passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp, startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp)); endTime: accessBloc.expirationTimeTimeStamp));
@ -245,8 +247,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
SearchResetButtons( SearchResetButtons(
onSearch: () { onSearch: () {
accessBloc.add(FilterDataEvent( accessBloc.add(FilterDataEvent(
selectedTabIndex: selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex,
BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(), passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp, startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp)); endTime: accessBloc.expirationTimeTimeStamp));

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
@ -81,21 +82,25 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
_timer?.cancel(); _timer?.cancel();
emit(const TimerState(isButtonEnabled: true, remainingTime: 0)); emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
emit(SuccessForgetState()); emit(SuccessForgetState());
} else if (response == "You entered wrong otp") {
forgetValidate = 'Wrong one time password.';
emit(AuthInitialState());
} else if (response == "OTP expired") {
forgetValidate = 'One time password has been expired.';
emit(AuthInitialState());
} }
} catch (failure) {
// forgetValidate='Invalid Credentials!';
emit(AuthInitialState());
// emit(FailureForgetState(error: failure.toString()));
} }
on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
if(errorMessage=='this email is not registered'){
validate='Invalid Credentials!';
emit(AuthInitialState());
}else if (errorMessage == "You entered wrong otp") {
forgetValidate = 'Wrong one time password.';
emit(AuthInitialState());
} else if (errorMessage == "OTP expired") {
forgetValidate = 'One time password has been expired.';
emit(AuthInitialState());
}
}
} }
//925207
String? validateCode(String? value) { String? validateCode(String? value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Code is required'; return 'Code is required';
@ -158,6 +163,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
user = UserModel.fromToken(token); user = UserModel.fromToken(token);
loginEmailController.clear(); loginEmailController.clear();
loginPasswordController.clear(); loginPasswordController.clear();
debugPrint("token " + token.accessToken);
emit(LoginSuccess()); emit(LoginSuccess());
} else { } else {
emit(const LoginFailure(error: 'Something went wrong')); emit(const LoginFailure(error: 'Something went wrong'));
@ -177,15 +183,15 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(LoginInitial()); emit(LoginInitial());
} }
checkOtpCode( // checkOtpCode(
ChangePasswordEvent event, // ChangePasswordEvent event,
Emitter<AuthState> emit, // Emitter<AuthState> emit,
) async { // ) async {
emit(LoadingForgetState()); // emit(LoadingForgetState());
await AuthenticationAPI.verifyOtp( // await AuthenticationAPI.verifyOtp(
email: forgetEmailController.text, otpCode: forgetOtp.text); // email: forgetEmailController.text, otpCode: forgetOtp.text);
emit(SuccessForgetState()); // emit(SuccessForgetState());
} // }
void _passwordVisible(PasswordVisibleEvent event, Emitter<AuthState> emit) { void _passwordVisible(PasswordVisibleEvent event, Emitter<AuthState> emit) {
emit(AuthLoading()); emit(AuthLoading());

View File

@ -49,9 +49,13 @@ class UpdateTimerEvent extends AuthEvent {
const UpdateTimerEvent({required this.remainingTime, required this.isButtonEnabled}); const UpdateTimerEvent({required this.remainingTime, required this.isButtonEnabled});
} }
class ChangePasswordEvent extends AuthEvent {} class ChangePasswordEvent extends AuthEvent {
class SendOtpEvent extends AuthEvent {} }
class SendOtpEvent extends AuthEvent {
}
class PasswordVisibleEvent extends AuthEvent { class PasswordVisibleEvent extends AuthEvent {
final bool? newValue; final bool? newValue;

View File

@ -1,3 +1,4 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
@ -148,44 +149,7 @@ class ForgetPasswordWebPage extends StatelessWidget {
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
SizedBox( SizedBox(
child: DropdownButtonFormField<String>( child: _buildDropdownField(context, forgetBloc, size)
validator: forgetBloc.validateRegion,
icon: const Icon(
Icons.keyboard_arrow_down_outlined,
),
decoration:
textBoxDecoration()!.copyWith(
hintText: null,
),
hint: SizedBox(
width: size.width * 0.12,
child: const Align(
alignment: Alignment.centerLeft,
child: Text(
'Select your region/country',
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
),
),
),
isDense: true,
style: const TextStyle(
color: Colors.black),
items: forgetBloc.regionList!
.map((RegionModel region) {
return DropdownMenuItem<String>(
value: region.id,
child: SizedBox(
width: size.width * 0.06,
child: Text(region.name)),
);
}).toList(),
onChanged: (String? value) {
forgetBloc.add(SelectRegionEvent(
val: value!,
));
},
),
) )
], ],
), ),
@ -208,11 +172,12 @@ class ForgetPasswordWebPage extends StatelessWidget {
SizedBox( SizedBox(
child: TextFormField( child: TextFormField(
validator: forgetBloc.validateEmail, validator: forgetBloc.validateEmail,
controller:
forgetBloc.forgetEmailController,
decoration: textBoxDecoration()! decoration: textBoxDecoration()!
.copyWith( .copyWith(
hintText: 'Enter your email'), hintText: 'Enter your email',
hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.w400)),
style: const TextStyle( style: const TextStyle(
color: Colors.black), color: Colors.black),
), ),
@ -244,13 +209,15 @@ class ForgetPasswordWebPage extends StatelessWidget {
decoration: decoration:
textBoxDecoration()!.copyWith( textBoxDecoration()!.copyWith(
hintText: 'Enter Code', hintText: 'Enter Code',
hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.w400),
suffixIcon: SizedBox( suffixIcon: SizedBox(
width: 100, width: 100,
child: Center( child: Center(
child: InkWell( child: InkWell(
onTap: state is TimerState && onTap: state is TimerState &&
!state !state.isButtonEnabled &&
.isButtonEnabled &&
state.remainingTime != state.remainingTime !=
1 1
? null ? null
@ -261,10 +228,8 @@ class ForgetPasswordWebPage extends StatelessWidget {
child: Text( child: Text(
'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime != 1 ? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}', 'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime != 1 ? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}',
style: TextStyle( style: TextStyle(
color: state color: state is TimerState &&
is TimerState && !state.isButtonEnabled
!state
.isButtonEnabled
? Colors.grey ? Colors.grey
: ColorsManager : ColorsManager
.btnColor, .btnColor,
@ -278,8 +243,7 @@ class ForgetPasswordWebPage extends StatelessWidget {
color: Colors.black), color: Colors.black),
), ),
), ),
if (forgetBloc.forgetValidate != if (forgetBloc.forgetValidate != '') // Check if there is a validation message
'') // Check if there is a validation message
Padding( Padding(
padding: padding:
const EdgeInsets.only(top: 8.0), const EdgeInsets.only(top: 8.0),
@ -311,18 +275,16 @@ class ForgetPasswordWebPage extends StatelessWidget {
const SizedBox(height: 10), const SizedBox(height: 10),
SizedBox( SizedBox(
child: TextFormField( child: TextFormField(
validator: validator: forgetBloc.passwordValidator,
forgetBloc.passwordValidator, keyboardType: TextInputType.visiblePassword,
keyboardType: controller: forgetBloc.forgetPasswordController,
TextInputType.visiblePassword, decoration: textBoxDecoration()!.copyWith(
controller: forgetBloc
.forgetPasswordController,
decoration:
textBoxDecoration()!.copyWith(
hintText: 'At least 8 characters', hintText: 'At least 8 characters',
hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.w400),
), ),
style: const TextStyle( style: const TextStyle(color: Colors.black),
color: Colors.black),
), ),
), ),
], ],
@ -346,8 +308,7 @@ class ForgetPasswordWebPage extends StatelessWidget {
if (forgetBloc if (forgetBloc
.forgetFormKey.currentState! .forgetFormKey.currentState!
.validate()) { .validate()) {
forgetBloc forgetBloc.add(ChangePasswordEvent());
.add(ChangePasswordEvent());
} }
}, },
), ),
@ -355,12 +316,14 @@ class ForgetPasswordWebPage extends StatelessWidget {
], ],
), ),
const SizedBox(height: 10.0), const SizedBox(height: 10.0),
SizedBox( Center(
child: Text( child: SizedBox(
forgetBloc.validate, child: Text(
style: const TextStyle( forgetBloc.validate,
fontWeight: FontWeight.w700, style: const TextStyle(
color: ColorsManager.red), fontWeight: FontWeight.w700,
color: ColorsManager.red),
),
), ),
), ),
SizedBox( SizedBox(
@ -410,4 +373,109 @@ class ForgetPasswordWebPage extends StatelessWidget {
), ),
)); ));
} }
Widget _buildDropdownField(
BuildContext context, AuthBloc loginBloc, Size size) {
final TextEditingController textEditingController = TextEditingController();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Country/Region",
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontSize: 14,
fontWeight: FontWeight.w400,
),
),
const SizedBox(height: 10),
Container(
height: 50,
decoration: const BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.all(Radius.circular(8)),
),
width: size.width * 0.9,
child: DropdownButtonHideUnderline(
child: DropdownButton2<String>(
style: TextStyle(color: Colors.black),
isExpanded: true,
hint: Text(
'Select your region/country',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.w400,
),
overflow: TextOverflow.ellipsis,
),
items: loginBloc.regionList!.map((RegionModel region) {
return DropdownMenuItem<String>(
value: region.id, // Use region.id as the value
child: Text(
region.name,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
);
}).toList(),
value: loginBloc.regionList!.any(
(region) => region.id == loginBloc.regionUuid,)
? loginBloc.regionUuid
: null,
onChanged: (String? value) {
if (value != null) {
loginBloc.add(SelectRegionEvent(
val: value,
));
}
},
buttonStyleData: const ButtonStyleData(
padding: EdgeInsets.symmetric(horizontal: 16),
height: 40,
width: double.infinity,
),
dropdownStyleData: DropdownStyleData(
maxHeight: size.height * 0.70,
),
menuItemStyleData: const MenuItemStyleData(
height: 40,
),
dropdownSearchData: DropdownSearchData(
searchController: textEditingController,
searchInnerWidgetHeight: 50,
searchInnerWidget: Container(
height: 50,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: TextFormField(
style: const TextStyle(color: Colors.black),
controller: textEditingController,
decoration: textBoxDecoration()!.copyWith(
errorStyle: const TextStyle(height: 0),
contentPadding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 10,
),
),
),
),
searchMatchFn: (item, searchValue) {
// Use the item's child text (region name) for searching.
final regionName = (item.child as Text).data?.toLowerCase() ?? '';
final search = searchValue.toLowerCase().trim();
// Debugging print statement to ensure values are captured correctly.
// Return true if the region name contains the search term.
return regionName.contains(search);
},
),
onMenuStateChange: (isOpen) {
if (!isOpen) {
textEditingController.clear();
}
},
),
),
),
],
);
}
} }

View File

@ -1,3 +1,4 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -23,7 +24,8 @@ class LoginWebPage extends StatefulWidget {
State<LoginWebPage> createState() => _LoginWebPageState(); State<LoginWebPage> createState() => _LoginWebPageState();
} }
class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout { class _LoginWebPageState extends State<LoginWebPage>
with HelperResponsiveLayout {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -58,7 +60,8 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
_scrollController = ScrollController(); _scrollController = ScrollController();
void _scrollToCenter() { void _scrollToCenter() {
final double middlePosition = _scrollController.position.maxScrollExtent / 2; final double middlePosition =
_scrollController.position.maxScrollExtent / 2;
_scrollController.animateTo( _scrollController.animateTo(
middlePosition, middlePosition,
duration: const Duration(seconds: 1), duration: const Duration(seconds: 1),
@ -120,7 +123,8 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
const Spacer(), const Spacer(),
Expanded( Expanded(
flex: 2, flex: 2,
child: _buildLoginFormFields(context, loginBloc, size), child: _buildLoginFormFields(
context, loginBloc, size),
), ),
const Spacer(), const Spacer(),
], ],
@ -131,12 +135,14 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
), ),
), ),
), ),
if (state is AuthLoading) const Center(child: CircularProgressIndicator()) if (state is AuthLoading)
const Center(child: CircularProgressIndicator())
], ],
); );
} }
Widget _buildLoginFormFields(BuildContext context, AuthBloc loginBloc, Size size) { Widget _buildLoginFormFields(
BuildContext context, AuthBloc loginBloc, Size size) {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1), color: Colors.white.withOpacity(0.1),
@ -146,8 +152,8 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
child: Form( child: Form(
key: loginBloc.loginFormKey, key: loginBloc.loginFormKey,
child: Padding( child: Padding(
padding: padding: EdgeInsets.symmetric(
EdgeInsets.symmetric(horizontal: size.width * 0.02, vertical: size.width * 0.003), horizontal: size.width * 0.02, vertical: size.width * 0.003),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -175,88 +181,112 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
); );
} }
Widget _buildDropdownField(BuildContext context, AuthBloc loginBloc, Size size) {
Widget _buildDropdownField(
BuildContext context, AuthBloc loginBloc, Size size) {
final TextEditingController textEditingController = TextEditingController();
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Text( Text(
"Country/Region", "Country/Region",
style: Theme.of(context) style: Theme.of(context).textTheme.bodySmall!.copyWith(
.textTheme fontSize: 14,
.bodySmall! fontWeight: FontWeight.w400,
.copyWith(fontSize: 14, fontWeight: FontWeight.w400), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
SizedBox( Container(
width: size.width * 0.8, height: 50,
child: LayoutBuilder( decoration: const BoxDecoration(
builder: (context, constraints) { color: ColorsManager.boxColor,
return DropdownButtonFormField<String>( borderRadius: BorderRadius.all(Radius.circular(8)),
value: loginBloc.regionList!.any((region) => region.id == loginBloc.regionUuid) ),
? loginBloc.regionUuid width: size.width * 0.9,
: null, child: DropdownButtonHideUnderline(
validator: loginBloc.validateRegion, child: DropdownButton2<String>(
icon: const Icon( style: TextStyle(color: Colors.black),
Icons.keyboard_arrow_down_outlined, isExpanded: true,
size: 20, hint: Text(
'Select your region/country',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.w400,
), ),
decoration: textBoxDecoration()!.copyWith( overflow: TextOverflow.ellipsis,
errorStyle: const TextStyle(height: 0), ),
contentPadding: const EdgeInsets.symmetric( items: loginBloc.regionList!.map((RegionModel region) {
vertical: 12, return DropdownMenuItem<String>(
horizontal: 10, value: region.id, // Use region.id as the value
child: Text(
region.name,
overflow: TextOverflow.ellipsis,
maxLines: 1,
), ),
), );
hint: Text( }).toList(),
'Select your region/country', value: loginBloc.regionList!.any(
style: Theme.of(context) (region) => region.id == loginBloc.regionUuid,)
.textTheme ? loginBloc.regionUuid
.bodySmall! : null,
.copyWith(color: ColorsManager.grayColor, fontWeight: FontWeight.w400), onChanged: (String? value) {
overflow: TextOverflow.ellipsis, if (value != null) {
), loginBloc.add(CheckEnableEvent());
isDense: true, loginBloc.add(SelectRegionEvent(val: value));
style: const TextStyle(color: Colors.black), }
items: loginBloc.regionList!.map((RegionModel region) { },
return DropdownMenuItem<String>( buttonStyleData: const ButtonStyleData(
value: region.id, padding: EdgeInsets.symmetric(horizontal: 16),
child: Container( height: 40,
constraints: BoxConstraints(maxWidth: constraints.maxWidth - 40), width: double.infinity,
child: Text( ),
region.name, dropdownStyleData: DropdownStyleData(
overflow: TextOverflow.ellipsis, maxHeight: size.height * 0.70,
maxLines: 1, ),
menuItemStyleData: const MenuItemStyleData(
height: 40,
),
dropdownSearchData: DropdownSearchData(
searchController: textEditingController,
searchInnerWidgetHeight: 50,
searchInnerWidget: Container(
height: 50,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: TextFormField(
style: const TextStyle(color: Colors.black),
controller: textEditingController,
decoration: textBoxDecoration()!.copyWith(
errorStyle: const TextStyle(height: 0),
contentPadding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 10,
), ),
), ),
); ),
}).toList(), ),
onChanged: (String? value) { searchMatchFn: (item, searchValue) {
loginBloc.add(CheckEnableEvent()); // Use the item's child text (region name) for searching.
loginBloc.add(SelectRegionEvent(val: value!)); final regionName = (item.child as Text).data?.toLowerCase() ?? '';
final search = searchValue.toLowerCase().trim();
// Debugging print statement to ensure values are captured correctly.
// Return true if the region name contains the search term.
return regionName.contains(search);
}, },
dropdownColor: Colors.white, ),
menuMaxHeight: size.height * 0.45, onMenuStateChange: (isOpen) {
selectedItemBuilder: (context) { if (!isOpen) {
return loginBloc.regionList!.map<Widget>((region) { textEditingController.clear();
return Container( }
constraints: BoxConstraints(maxWidth: constraints.maxWidth - 40), },
child: Text( ),
region.name,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
);
}).toList();
},
);
},
), ),
), ),
], ],
); );
} }
Widget _buildEmailField(BuildContext context, AuthBloc loginBloc) { Widget _buildEmailField(BuildContext context, AuthBloc loginBloc) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -280,10 +310,9 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
decoration: textBoxDecoration()!.copyWith( decoration: textBoxDecoration()!.copyWith(
errorStyle: const TextStyle(height: 0), errorStyle: const TextStyle(height: 0),
hintText: 'Enter your email address', hintText: 'Enter your email address',
hintStyle: Theme.of(context) hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith(
.textTheme color: ColorsManager.grayColor,
.bodySmall! fontWeight: FontWeight.w400)),
.copyWith(color: ColorsManager.grayColor, fontWeight: FontWeight.w400)),
style: const TextStyle(color: Colors.black), style: const TextStyle(color: Colors.black),
), ),
), ),
@ -315,17 +344,18 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
controller: loginBloc.loginPasswordController, controller: loginBloc.loginPasswordController,
decoration: textBoxDecoration()!.copyWith( decoration: textBoxDecoration()!.copyWith(
hintText: 'At least 8 characters', hintText: 'At least 8 characters',
hintStyle: Theme.of(context) hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith(
.textTheme color: ColorsManager.grayColor, fontWeight: FontWeight.w400),
.bodySmall!
.copyWith(color: ColorsManager.grayColor, fontWeight: FontWeight.w400),
suffixIcon: IconButton( suffixIcon: IconButton(
onPressed: () { onPressed: () {
loginBloc.add(PasswordVisibleEvent(newValue: loginBloc.obscureText)); loginBloc.add(
PasswordVisibleEvent(newValue: loginBloc.obscureText));
}, },
icon: SizedBox( icon: SizedBox(
child: SvgPicture.asset( child: SvgPicture.asset(
loginBloc.obscureText ? Assets.visiblePassword : Assets.invisiblePassword, loginBloc.obscureText
? Assets.visiblePassword
: Assets.invisiblePassword,
height: 15, height: 15,
width: 15, width: 15,
), ),
@ -353,10 +383,10 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
}, },
child: Text( child: Text(
"Forgot Password?", "Forgot Password?",
style: Theme.of(context) style: Theme.of(context).textTheme.bodySmall!.copyWith(
.textTheme color: Colors.black,
.bodySmall! fontSize: 14,
.copyWith(color: Colors.black, fontSize: 14, fontWeight: FontWeight.w400), fontWeight: FontWeight.w400),
), ),
), ),
], ],
@ -419,7 +449,8 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
); );
} }
Widget _buildSignInButton(BuildContext context, AuthBloc loginBloc, Size size) { Widget _buildSignInButton(
BuildContext context, AuthBloc loginBloc, Size size) {
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -460,7 +491,8 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
SizedBox( SizedBox(
child: Text( child: Text(
loginBloc.validate, loginBloc.validate,
style: const TextStyle(fontWeight: FontWeight.w700, color: ColorsManager.red), style: const TextStyle(
fontWeight: FontWeight.w700, color: ColorsManager.red),
), ),
) )
], ],

View File

@ -5,27 +5,33 @@ import 'package:syncrow_web/utils/constants/assets.dart';
class DynamicTable extends StatefulWidget { class DynamicTable extends StatefulWidget {
final List<String> headers; final List<String> headers;
final String? tableName;
final List<List<dynamic>> data; final List<List<dynamic>> data;
final BoxDecoration? headerDecoration; final BoxDecoration? headerDecoration;
final BoxDecoration? cellDecoration; final BoxDecoration? cellDecoration;
final Size size; final Size size;
final bool withCheckBox; final bool withCheckBox;
final bool withSelectAll;
final bool isEmpty; final bool isEmpty;
final void Function(bool?)? selectAll; final void Function(bool?)? selectAll;
final void Function(int, bool, dynamic)? onRowSelected; final void Function(int, bool, dynamic)? onRowSelected;
final List<String>? initialSelectedIds; final List<String>? initialSelectedIds;
final int uuidIndex;
const DynamicTable({ const DynamicTable({
super.key, super.key,
required this.headers, required this.headers,
required this.data, required this.data,
required this.size, required this.size,
this.tableName,
required this.isEmpty, required this.isEmpty,
required this.withCheckBox, required this.withCheckBox,
required this.withSelectAll,
this.headerDecoration, this.headerDecoration,
this.cellDecoration, this.cellDecoration,
this.selectAll, this.selectAll,
this.onRowSelected, this.onRowSelected,
this.initialSelectedIds, this.initialSelectedIds,
required this.uuidIndex,
}); });
@override @override
@ -34,13 +40,29 @@ class DynamicTable extends StatefulWidget {
class _DynamicTableState extends State<DynamicTable> { class _DynamicTableState extends State<DynamicTable> {
late List<bool> _selected; late List<bool> _selected;
bool _selectAll = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_initializeSelection();
}
@override
void didUpdateWidget(DynamicTable oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.data != widget.data) {
_initializeSelection();
}
}
void _initializeSelection() {
_selected = List<bool>.generate(widget.data.length, (index) { _selected = List<bool>.generate(widget.data.length, (index) {
// Check if the initialSelectedIds contains the deviceUuid
// uuidIndex is the index of the column containing the deviceUuid
final deviceUuid = widget.data[index][widget.uuidIndex];
return widget.initialSelectedIds != null && return widget.initialSelectedIds != null &&
widget.initialSelectedIds!.contains(widget.data[index][1]); widget.initialSelectedIds!.contains(deviceUuid);
}); });
} }
@ -54,6 +76,17 @@ class _DynamicTableState extends State<DynamicTable> {
}); });
} }
void _toggleSelectAll(bool? value) {
setState(() {
_selectAll = value ?? false;
_selected = List<bool>.filled(widget.data.length, _selectAll);
if (widget.selectAll != null) {
widget.selectAll!(_selectAll);
}
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
@ -71,8 +104,7 @@ class _DynamicTableState extends State<DynamicTable> {
children: [ children: [
if (widget.withCheckBox) _buildSelectAllCheckbox(), if (widget.withCheckBox) _buildSelectAllCheckbox(),
...widget.headers ...widget.headers
.map((header) => _buildTableHeaderCell(header)) .map((header) => _buildTableHeaderCell(header)),
.toList(),
], ],
), ),
), ),
@ -92,7 +124,8 @@ class _DynamicTableState extends State<DynamicTable> {
height: 15, height: 15,
), ),
Text( Text(
'No Devices', // no password
widget.tableName=='AccessManagement'? 'No Password ' : 'No Devices',
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodySmall! .bodySmall!
@ -119,11 +152,9 @@ class _DynamicTableState extends State<DynamicTable> {
if (widget.withCheckBox) if (widget.withCheckBox)
_buildRowCheckbox( _buildRowCheckbox(
index, widget.size.height * 0.10), index, widget.size.height * 0.10),
...row ...row.map((cell) => _buildTableCell(
.map((cell) => _buildTableCell( cell.toString(),
cell.toString(), widget.size.height * 0.10)),
widget.size.height * 0.10))
.toList(),
], ],
); );
}, },
@ -148,7 +179,7 @@ class _DynamicTableState extends State<DynamicTable> {
), ),
child: Checkbox( child: Checkbox(
value: _selected.every((element) => element == true), value: _selected.every((element) => element == true),
onChanged: null, onChanged:widget.withSelectAll?_toggleSelectAll:null,
), ),
); );
} }

View File

@ -32,21 +32,19 @@ class CustomWebTextField extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
if (isRequired)
Row( Row(
children: [ children: [
Text( if (isRequired)
'* ', Text('* ',
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme.bodyMedium!
.bodyMedium!
.copyWith(color: Colors.red), .copyWith(color: Colors.red),
), ),
Text( Text(
textFieldName, textFieldName,
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme.bodySmall!
.bodySmall!
.copyWith(color: Colors.black, fontSize: 13), .copyWith(color: Colors.black, fontSize: 13),
), ),
], ],
@ -70,15 +68,17 @@ class CustomWebTextField extends StatelessWidget {
), ),
Container( Container(
height: height ?? 35, height: height ?? 35,
decoration: containerDecoration decoration: containerDecoration.copyWith(
.copyWith(color: const Color(0xFFF5F6F7), boxShadow: [ color: const Color(0xFFF5F6F7),
boxShadow: [
BoxShadow( BoxShadow(
color: Colors.grey.withOpacity(0.3), color: Colors.grey.withOpacity(0.3),
spreadRadius: 2, spreadRadius: 2,
blurRadius: 3, blurRadius: 3,
offset: const Offset(1, 1), // changes position of shadow offset: const Offset(1, 1), // changes position of shadow
), ),
]), ]
),
child: TextFormField( child: TextFormField(
validator: validator, validator: validator,
controller: controller, controller: controller,

View File

@ -28,14 +28,16 @@ class DeviceManagementBloc
emit(DeviceManagementLoading()); emit(DeviceManagementLoading());
try { try {
final devices = await DevicesManagementApi().fetchDevices(); final devices = await DevicesManagementApi().fetchDevices();
_selectedDevices.clear();
_devices = devices; _devices = devices;
_calculateDeviceCounts(); _calculateDeviceCounts();
emit(DeviceManagementLoaded( emit(DeviceManagementLoaded(
devices: devices, devices: devices,
selectedIndex: _selectedIndex, selectedIndex: 0,
onlineCount: _onlineCount, onlineCount: _onlineCount,
offlineCount: _offlineCount, offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount, lowBatteryCount: _lowBatteryCount,
selectedDevice: null,
)); ));
} catch (e) { } catch (e) {
emit(DeviceManagementInitial()); emit(DeviceManagementInitial());
@ -63,6 +65,8 @@ class DeviceManagementBloc
onlineCount: _onlineCount, onlineCount: _onlineCount,
offlineCount: _offlineCount, offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount, lowBatteryCount: _lowBatteryCount,
selectedDevice:
_selectedDevices.isNotEmpty ? _selectedDevices.first : null,
)); ));
} }
} }
@ -75,8 +79,10 @@ class DeviceManagementBloc
void _onSelectDevice( void _onSelectDevice(
SelectDevice event, Emitter<DeviceManagementState> emit) { SelectDevice event, Emitter<DeviceManagementState> emit) {
if (_selectedDevices.contains(event.selectedDevice)) { final selectedUuid = event.selectedDevice.uuid;
_selectedDevices.remove(event.selectedDevice);
if (_selectedDevices.any((device) => device.uuid == selectedUuid)) {
_selectedDevices.removeWhere((device) => device.uuid == selectedUuid);
} else { } else {
_selectedDevices.add(event.selectedDevice); _selectedDevices.add(event.selectedDevice);
} }
@ -129,6 +135,9 @@ class DeviceManagementBloc
void _onSearchDevices( void _onSearchDevices(
SearchDevices event, Emitter<DeviceManagementState> emit) { SearchDevices event, Emitter<DeviceManagementState> emit) {
if (_devices.isNotEmpty) { if (_devices.isNotEmpty) {
_selectedDevices.clear();
_selectedIndex = 0;
final filteredDevices = _devices.where((device) { final filteredDevices = _devices.where((device) {
final matchesCommunity = event.community == null || final matchesCommunity = event.community == null ||
event.community!.isEmpty || event.community!.isEmpty ||
@ -150,7 +159,6 @@ class DeviceManagementBloc
false); false);
return matchesCommunity && matchesUnit && matchesProductName; return matchesCommunity && matchesUnit && matchesProductName;
}).toList(); }).toList();
_selectedDevices = [];
emit(DeviceManagementFiltered( emit(DeviceManagementFiltered(
filteredDevices: filteredDevices, filteredDevices: filteredDevices,
selectedIndex: 0, selectedIndex: 0,

View File

@ -42,6 +42,10 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
offlineCount = state.offlineCount; offlineCount = state.offlineCount;
lowBatteryCount = state.lowBatteryCount; lowBatteryCount = state.lowBatteryCount;
isControlButtonEnabled = state.selectedDevice != null; isControlButtonEnabled = state.selectedDevice != null;
} else if (state is DeviceManagementInitial) {
devicesToShow = [];
selectedIndex = 0;
isControlButtonEnabled = false;
} }
final tabs = [ final tabs = [
@ -51,71 +55,68 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
'Low Battery ($lowBatteryCount)', 'Low Battery ($lowBatteryCount)',
]; ];
return CustomScrollView( return Column(
slivers: [ children: [
SliverToBoxAdapter( Container(
child: Container( padding: isLargeScreenSize(context)
padding: isLargeScreenSize(context) ? const EdgeInsets.all(30)
? const EdgeInsets.all(30) : const EdgeInsets.all(15),
: const EdgeInsets.all(15), child: Column(
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ FilterWidget(
FilterWidget( size: MediaQuery.of(context).size,
size: MediaQuery.of(context).size, tabs: tabs,
tabs: tabs, selectedIndex: selectedIndex,
selectedIndex: selectedIndex, onTabChanged: (index) {
onTabChanged: (index) { context.read<DeviceManagementBloc>()
context .add(SelectedFilterChanged(index));
.read<DeviceManagementBloc>() },
.add(SelectedFilterChanged(index)); ),
}, const SizedBox(height: 20),
), const DeviceSearchFilters(),
const SizedBox(height: 20), const SizedBox(height: 12),
const DeviceSearchFilters(), Container(
const SizedBox(height: 12), height: 45,
Container( width: 100,
height: 45, decoration: containerDecoration,
width: 100, child: Center(
decoration: containerDecoration, child: DefaultButton(
child: Center( onPressed: isControlButtonEnabled
child: DefaultButton( ? () {
onPressed: isControlButtonEnabled final selectedDevice = context
? () { .read<DeviceManagementBloc>()
final selectedDevice = context .selectedDevices.first;
.read<DeviceManagementBloc>() showDialog(
.selectedDevices context: context,
.first; builder: (context) => DeviceControlDialog(
showDialog( device: selectedDevice),
context: context, );
builder: (context) => DeviceControlDialog( }
device: selectedDevice), : null,
); borderRadius: 9,
} child: Text(
: null, 'Control',
borderRadius: 9, style: TextStyle(
child: Text( fontSize: 12,
'Control', color: isControlButtonEnabled
style: TextStyle( ? Colors.white
fontSize: 12, : Colors.grey,
color: isControlButtonEnabled
? Colors.white
: Colors.grey,
),
), ),
), ),
), ),
), ),
], ),
), ],
), ),
), ),
SliverFillRemaining( Expanded(
child: Padding( child: Padding(
padding: isLargeScreenSize(context) padding: isLargeScreenSize(context)
? const EdgeInsets.all(30) ? const EdgeInsets.all(30)
: const EdgeInsets.all(15), : const EdgeInsets.all(15),
child: DynamicTable( child: DynamicTable(
withSelectAll: false,
cellDecoration: containerDecoration, cellDecoration: containerDecoration,
onRowSelected: (index, isSelected, row) { onRowSelected: (index, isSelected, row) {
final selectedDevice = devicesToShow[index]; final selectedDevice = devicesToShow[index];
@ -125,6 +126,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
}, },
withCheckBox: true, withCheckBox: true,
size: context.screenSize, size: context.screenSize,
uuidIndex: 2,
headers: const [ headers: const [
'Device Name', 'Device Name',
'Product Name', 'Product Name',
@ -153,10 +155,15 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
(device.updateTime ?? 0) * 1000)), (device.updateTime ?? 0) * 1000)),
]; ];
}).toList(), }).toList(),
initialSelectedIds: context
.read<DeviceManagementBloc>()
.selectedDevices
.map((device) => device.uuid!)
.toList(),
isEmpty: devicesToShow.isEmpty, isEmpty: devicesToShow.isEmpty,
), ),
), ),
), )
], ],
); );
}, },

View File

@ -62,16 +62,19 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
deviceStatus.noBodyTime = event.value; deviceStatus.noBodyTime = event.value;
} else if (event.code == 'moving_max_dis') { } else if (event.code == 'moving_max_dis') {
deviceStatus.maxDistance = event.value; deviceStatus.maxDistance = event.value;
} else if (event.code == 'scene') {
deviceStatus.spaceType = getSpaceType(event.value);
} }
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
await _runDeBouncer( await _runDeBouncer(
deviceId: deviceId, code: event.code, value: event.value); deviceId: deviceId, code: event.code, value: event.value, emit: emit);
} }
_runDeBouncer({ _runDeBouncer({
required String deviceId, required String deviceId,
required String code, required String code,
required dynamic value, required dynamic value,
required Emitter<CeilingSensorState> emit,
}) { }) {
if (_timer != null) { if (_timer != null) {
_timer!.cancel(); _timer!.cancel();
@ -84,6 +87,11 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
if (!response) { if (!response) {
add(CeilingInitialEvent()); add(CeilingInitialEvent());
} }
if (response == true && code == 'scene') {
emit(CeilingLoadingInitialState());
await Future.delayed(const Duration(seconds: 1));
add(CeilingInitialEvent());
}
} catch (_) { } catch (_) {
await Future.delayed(const Duration(milliseconds: 500)); await Future.delayed(const Duration(milliseconds: 500));
add(CeilingInitialEvent()); add(CeilingInitialEvent());

View File

@ -10,7 +10,7 @@ class CeilingSensorModel {
String bodyMovement; String bodyMovement;
String noBodyTime; String noBodyTime;
int maxDistance; int maxDistance;
String spaceType; SpaceTypes spaceType;
CeilingSensorModel({ CeilingSensorModel({
required this.presenceState, required this.presenceState,
@ -33,7 +33,7 @@ class CeilingSensorModel {
String _bodyMovement = 'none'; String _bodyMovement = 'none';
String _noBodyTime = 'none'; String _noBodyTime = 'none';
int _maxDis = 0; int _maxDis = 0;
String _spaceType = 'none'; SpaceTypes _spaceType = SpaceTypes.none;
try { try {
for (var status in jsonList) { for (var status in jsonList) {
@ -42,7 +42,7 @@ class CeilingSensorModel {
_presenceState = status.value ?? 'none'; _presenceState = status.value ?? 'none';
break; break;
case 'scene': case 'scene':
_spaceType = status.value ?? 'none'; _spaceType = getSpaceType(status.value ?? 'none');
break; break;
case 'sensitivity': case 'sensitivity':
_sensitivity = status.value is int _sensitivity = status.value is int
@ -92,3 +92,27 @@ class CeilingSensorModel {
); );
} }
} }
enum SpaceTypes {
none,
parlour,
area,
toilet,
bedroom,
}
SpaceTypes getSpaceType(String value) {
switch (value) {
case 'parlour':
return SpaceTypes.parlour;
case 'area':
return SpaceTypes.area;
case 'toilet':
return SpaceTypes.toilet;
case 'bedroom':
return SpaceTypes.bedroom;
case 'none':
default:
return SpaceTypes.none;
}
}

View File

@ -99,15 +99,14 @@ class CeilingSensorControls extends StatelessWidget
description: 'Detection Range', description: 'Detection Range',
), ),
PresenceSpaceType( PresenceSpaceType(
listOfIcons: const [
Assets.office,
Assets.parlour,
Assets.dyi,
Assets.bathroom,
Assets.bedroom,
],
description: 'Space Type', description: 'Space Type',
value: model.spaceType, value: model.spaceType,
action: (String value) => context.read<CeilingSensorBloc>().add(
CeilingChangeValueEvent(
code: 'scene',
value: value,
),
),
), ),
PresenceUpdateData( PresenceUpdateData(
value: model.sensitivity.toDouble(), value: model.sensitivity.toDouble(),

View File

@ -1,24 +1,33 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.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/color_manager.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart';
class PresenceSpaceType extends StatelessWidget { class PresenceSpaceType extends StatelessWidget {
const PresenceSpaceType({ const PresenceSpaceType({
super.key, super.key,
required this.listOfIcons,
required this.description, required this.description,
required this.value, required this.value,
required this.action,
}); });
final List<String> listOfIcons;
final String description; final String description;
final String value; final SpaceTypes value;
final void Function(String value) action;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Map<SpaceTypes, String> spaceTypeIcons = {
SpaceTypes.none: Assets.office,
SpaceTypes.parlour: Assets.parlour,
SpaceTypes.area: Assets.dyi,
SpaceTypes.toilet: Assets.bathroom,
SpaceTypes.bedroom: Assets.bedroom,
};
return DeviceControlsContainer( return DeviceControlsContainer(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -38,23 +47,28 @@ class PresenceSpaceType extends StatelessWidget {
Wrap( Wrap(
runSpacing: 8, runSpacing: 8,
spacing: 16, spacing: 16,
children: [ children: spaceTypeIcons.entries.map((entry) {
...listOfIcons.map((icon) => Container( final icon = entry.value;
decoration: BoxDecoration( final spaceType = entry.key;
borderRadius: BorderRadius.circular(100), return GestureDetector(
border: Border.all( onTap: () => action(spaceType.name),
color: icon.contains(value) child: Container(
? ColorsManager.blueColor decoration: BoxDecoration(
: Colors.transparent, borderRadius: BorderRadius.circular(100),
), border: Border.all(
color: value == spaceType
? ColorsManager.blueColor
: Colors.transparent,
), ),
child: SvgPicture.asset( ),
icon, child: SvgPicture.asset(
width: 40, icon,
height: 40, width: 40,
), height: 40,
)), ),
], ),
);
}).toList(),
), ),
], ],
), ),

View File

@ -6,6 +6,8 @@ import 'package:syncrow_web/pages/device_managment/shared/table/table_header.dar
class ReportsTable extends StatelessWidget { class ReportsTable extends StatelessWidget {
final DeviceReport report; final DeviceReport report;
final String? thirdColumnTitle;
final String? thirdColumnDescription;
final Function(int index) onRowTap; final Function(int index) onRowTap;
final VoidCallback onClose; final VoidCallback onClose;
@ -14,6 +16,8 @@ class ReportsTable extends StatelessWidget {
required this.report, required this.report,
required this.onRowTap, required this.onRowTap,
required this.onClose, required this.onClose,
this.thirdColumnTitle,
this.thirdColumnDescription,
}); });
@override @override
@ -32,33 +36,34 @@ class ReportsTable extends StatelessWidget {
children: [ children: [
TableRow( TableRow(
decoration: BoxDecoration(color: Colors.grey.shade200), decoration: BoxDecoration(color: Colors.grey.shade200),
children: const [ children: [
TableHeader(title: 'Date'), const TableHeader(title: 'Date'),
TableHeader(title: 'Time'), const TableHeader(title: 'Time'),
TableHeader(title: 'Status'), TableHeader(title: thirdColumnTitle ?? 'Status'),
], ],
), ),
...report.data!.asMap().entries.map((entry) { if (report.data != null)
int index = entry.key; ...report.data!.asMap().entries.map((entry) {
DeviceEvent data = entry.value; int index = entry.key;
DeviceEvent data = entry.value;
// Parse eventTime into Date and Time // Parse eventTime into Date and Time
DateTime eventDateTime = DateTime eventDateTime =
DateTime.fromMillisecondsSinceEpoch(data.eventTime!); DateTime.fromMillisecondsSinceEpoch(data.eventTime!);
String date = DateFormat('dd/MM/yyyy').format(eventDateTime); String date = DateFormat('dd/MM/yyyy').format(eventDateTime);
String time = DateFormat('HH:mm').format(eventDateTime); String time = DateFormat('HH:mm').format(eventDateTime);
return TableRow( return TableRow(
children: [ children: [
TableCellWidget(value: date), TableCellWidget(value: date),
TableCellWidget(value: time), TableCellWidget(value: time),
TableCellWidget( TableCellWidget(
value: data.value!, value: '${data.value!} $thirdColumnDescription',
onTap: () => onRowTap(index), onTap: () => onRowTap(index),
), ),
], ],
); );
}).toList(), }),
], ],
), ),
), ),

View File

@ -25,16 +25,21 @@ class WallSensorControls extends StatelessWidget with HelperResponsiveLayout {
final isLarge = isLargeScreenSize(context); final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context); final isMedium = isMediumScreenSize(context);
return BlocProvider( return BlocProvider(
create: (context) => WallSensorBloc(deviceId: device.uuid!)..add(WallSensorInitialEvent()), create: (context) =>
WallSensorBloc(deviceId: device.uuid!)..add(WallSensorInitialEvent()),
child: BlocBuilder<WallSensorBloc, WallSensorState>( child: BlocBuilder<WallSensorBloc, WallSensorState>(
builder: (context, state) { builder: (context, state) {
if (state is WallSensorLoadingInitialState || state is DeviceReportsLoadingState) { if (state is WallSensorLoadingInitialState ||
state is DeviceReportsLoadingState) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} else if (state is WallSensorUpdateState) { } else if (state is WallSensorUpdateState) {
return _buildGridView(context, state.wallSensorModel, isExtraLarge, isLarge, isMedium); return _buildGridView(context, state.wallSensorModel, isExtraLarge,
isLarge, isMedium);
} else if (state is DeviceReportsState) { } else if (state is DeviceReportsState) {
return ReportsTable( return ReportsTable(
report: state.deviceReport, report: state.deviceReport,
thirdColumnTitle: "Value",
thirdColumnDescription: "Lux",
onRowTap: (index) {}, onRowTap: (index) {},
onClose: () { onClose: () {
context.read<WallSensorBloc>().add(BackToGridViewEvent()); context.read<WallSensorBloc>().add(BackToGridViewEvent());
@ -49,7 +54,8 @@ class WallSensorControls extends StatelessWidget with HelperResponsiveLayout {
); );
} else if (state is DeviceReportsFailedState) { } else if (state is DeviceReportsFailedState) {
final model = context.read<WallSensorBloc>().deviceStatus; final model = context.read<WallSensorBloc>().deviceStatus;
return _buildGridView(context, model, isExtraLarge, isLarge, isMedium); return _buildGridView(
context, model, isExtraLarge, isLarge, isMedium);
} }
return const Center(child: Text('Error fetching status')); return const Center(child: Text('Error fetching status'));
}, },
@ -57,8 +63,8 @@ class WallSensorControls extends StatelessWidget with HelperResponsiveLayout {
); );
} }
Widget _buildGridView( Widget _buildGridView(BuildContext context, WallSensorModel model,
BuildContext context, WallSensorModel model, bool isExtraLarge, bool isLarge, bool isMedium) { bool isExtraLarge, bool isLarge, bool isMedium) {
return GridView( return GridView(
padding: const EdgeInsets.symmetric(horizontal: 50), padding: const EdgeInsets.symmetric(horizontal: 50),
shrinkWrap: true, shrinkWrap: true,
@ -127,10 +133,11 @@ class WallSensorControls extends StatelessWidget with HelperResponsiveLayout {
maxValue: 10000, maxValue: 10000,
steps: 1, steps: 1,
description: 'hr', description: 'hr',
action: (int value) => context.read<WallSensorBloc>().add(WallSensorChangeValueEvent( action: (int value) =>
code: 'no_one_time', context.read<WallSensorBloc>().add(WallSensorChangeValueEvent(
value: value, code: 'no_one_time',
))), value: value,
))),
PresenceUpdateData( PresenceUpdateData(
value: model.farDetection.toDouble(), value: model.farDetection.toDouble(),
title: 'Far Detection:', title: 'Far Detection:',
@ -147,9 +154,8 @@ class WallSensorControls extends StatelessWidget with HelperResponsiveLayout {
), ),
GestureDetector( GestureDetector(
onTap: () { onTap: () {
context context.read<WallSensorBloc>().add(GetDeviceReportsEvent(
.read<WallSensorBloc>() code: 'illuminance_value', deviceUuid: device.uuid!));
.add(GetDeviceReportsEvent(code: 'illuminance_value', deviceUuid: device.uuid!));
}, },
child: const PresenceStaticWidget( child: const PresenceStaticWidget(
icon: Assets.illuminanceRecordIcon, icon: Assets.illuminanceRecordIcon,
@ -158,9 +164,8 @@ class WallSensorControls extends StatelessWidget with HelperResponsiveLayout {
), ),
GestureDetector( GestureDetector(
onTap: () { onTap: () {
context context.read<WallSensorBloc>().add(GetDeviceReportsEvent(
.read<WallSensorBloc>() code: 'presence_state', deviceUuid: device.uuid!));
.add(GetDeviceReportsEvent(code: 'presence_state', deviceUuid: device.uuid!));
}, },
child: const PresenceStaticWidget( child: const PresenceStaticWidget(
icon: Assets.presenceRecordIcon, icon: Assets.presenceRecordIcon,

View File

@ -16,11 +16,11 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
final BuchheimWalkerConfiguration builder = BuchheimWalkerConfiguration(); final BuchheimWalkerConfiguration builder = BuchheimWalkerConfiguration();
List<Node> sourcesList = []; List<Node> sourcesList = [];
List<Node> destinationsList = []; List<Node> destinationsList = [];
static UserModel? user; UserModel? user;
HomeBloc() : super((HomeInitial())) { HomeBloc() : super((HomeInitial())) {
on<CreateNewNode>(_createNode); on<CreateNewNode>(_createNode);
fetchUserInfo(); on<FetchUserInfo>(_fetchUserInfo);
} }
void _createNode(CreateNewNode event, Emitter<HomeState> emit) async { void _createNode(CreateNewNode event, Emitter<HomeState> emit) async {
@ -39,10 +39,12 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
emit(HomeUpdateTree(graph: graph, builder: builder)); emit(HomeUpdateTree(graph: graph, builder: builder));
} }
static Future fetchUserInfo() async { Future _fetchUserInfo(FetchUserInfo event, Emitter<HomeState> emit) async {
try { try {
var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey); var uuid =
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
user = await HomeApi().fetchUserInfo(uuid); user = await HomeApi().fetchUserInfo(uuid);
emit(HomeInitial());
} catch (e) { } catch (e) {
return; return;
} }

View File

@ -17,3 +17,7 @@ class CreateNewNode extends HomeEvent {
@override @override
List<Object> get props => [sourceNode, destinationNode]; List<Object> get props => [sourceNode, destinationNode];
} }
class FetchUserInfo extends HomeEvent {
const FetchUserInfo();
}

View File

@ -1,11 +1,25 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/home/view/home_page_mobile.dart'; import 'package:syncrow_web/pages/home/view/home_page_mobile.dart';
import 'package:syncrow_web/pages/home/view/home_page_web.dart'; import 'package:syncrow_web/pages/home/view/home_page_web.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class HomePage extends StatelessWidget with HelperResponsiveLayout { class HomePage extends StatefulWidget {
const HomePage({super.key}); const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with HelperResponsiveLayout {
@override
void initState() {
super.initState();
context.read<HomeBloc>().add(const FetchUserInfo());
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isSmallScreen = isSmallScreenSize(context); final isSmallScreen = isSmallScreenSize(context);

View File

@ -10,6 +10,7 @@ import 'package:syncrow_web/web_layout/web_scaffold.dart';
class HomeMobilePage extends StatelessWidget { class HomeMobilePage extends StatelessWidget {
HomeMobilePage({super.key}); HomeMobilePage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size; Size size = MediaQuery.of(context).size;
@ -79,7 +80,7 @@ class HomeMobilePage extends StatelessWidget {
); );
} }
dynamic homeItems = [ final dynamic homeItems = [
{ {
'title': 'Access', 'title': 'Access',
'icon': Assets.accessIcon, 'icon': Assets.accessIcon,

View File

@ -219,8 +219,8 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
scheduleList: [ scheduleList: [
if (repeat) if (repeat)
Schedule( Schedule(
effectiveTime: getTimeFromDateTimeString(expirationTime), effectiveTime: getTimeFromDateTimeString(effectiveTime),
invalidTime: getTimeFromDateTimeString(effectiveTime).toString(), invalidTime: getTimeFromDateTimeString(expirationTime).toString(),
workingDay: selectedDays, workingDay: selectedDays,
), ),
], ],
@ -448,6 +448,7 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
<Widget>[ <Widget>[
TextButton( TextButton(
onPressed: () { onPressed: () {
Navigator.of(context).pop(true); Navigator.of(context).pop(true);
}, },
child: const Text('OK'), child: const Text('OK'),

View File

@ -35,12 +35,12 @@ class AddDeviceDialog extends StatelessWidget {
backgroundColor: Colors.white, backgroundColor: Colors.white,
title: Text( title: Text(
'Add Accessible Device', 'Add Accessible Device',
style: Theme.of(context).textTheme.headlineLarge!.copyWith( style: Theme.of(context)
fontWeight: FontWeight.w400, .textTheme
fontSize: 24, .headlineLarge!
color: Colors.black), .copyWith(fontWeight: FontWeight.w400, fontSize: 24, color: Colors.black),
), ),
content: Container( content: SizedBox(
height: MediaQuery.of(context).size.height / 1.7, height: MediaQuery.of(context).size.height / 1.7,
width: MediaQuery.of(context).size.width / 2, width: MediaQuery.of(context).size.width / 2,
child: Padding( child: Padding(
@ -68,17 +68,14 @@ class AddDeviceDialog extends StatelessWidget {
), ),
Text( Text(
'Only online accessible devices can be added', 'Only online accessible devices can be added',
style: Theme.of(context) style: Theme.of(context).textTheme.bodySmall!.copyWith(
.textTheme fontWeight: FontWeight.w400,
.bodySmall! fontSize: 12,
.copyWith( color: ColorsManager.grayColor),
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.grayColor),
), ),
], ],
)), )),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
const SizedBox( const SizedBox(
@ -93,7 +90,7 @@ class AddDeviceDialog extends StatelessWidget {
flex: 4, flex: 4,
child: CustomWebTextField( child: CustomWebTextField(
controller: visitorBloc.deviceNameController, controller: visitorBloc.deviceNameController,
isRequired: true, isRequired: false,
textFieldName: 'Device Name', textFieldName: 'Device Name',
description: '', description: '',
), ),
@ -103,7 +100,7 @@ class AddDeviceDialog extends StatelessWidget {
flex: 4, flex: 4,
child: CustomWebTextField( child: CustomWebTextField(
controller: visitorBloc.deviceIdController, controller: visitorBloc.deviceIdController,
isRequired: true, isRequired: false,
textFieldName: 'Device ID', textFieldName: 'Device ID',
description: '', description: '',
), ),
@ -113,7 +110,7 @@ class AddDeviceDialog extends StatelessWidget {
flex: 4, flex: 4,
child: CustomWebTextField( child: CustomWebTextField(
controller: visitorBloc.unitNameController, controller: visitorBloc.unitNameController,
isRequired: true, isRequired: false,
textFieldName: 'Unit Name', textFieldName: 'Unit Name',
description: '', description: '',
), ),
@ -155,8 +152,7 @@ class AddDeviceDialog extends StatelessWidget {
visitorBloc.deviceNameController.clear(); visitorBloc.deviceNameController.clear();
visitorBloc.deviceIdController.clear(); visitorBloc.deviceIdController.clear();
visitorBloc.unitNameController.clear(); visitorBloc.unitNameController.clear();
visitorBloc.add( visitorBloc.add(FetchDevice()); // Reset to original list
FetchDevice()); // Reset to original list
}, },
), ),
), ),
@ -168,14 +164,15 @@ class AddDeviceDialog extends StatelessWidget {
flex: 3, flex: 3,
child: state is TableLoaded child: state is TableLoaded
? DynamicTable( ? DynamicTable(
uuidIndex: 1,
withSelectAll: true,
initialSelectedIds: selectedDeviceIds, initialSelectedIds: selectedDeviceIds,
cellDecoration: containerDecoration, cellDecoration: containerDecoration,
isEmpty: visitorBloc.data.isEmpty, isEmpty: visitorBloc.data.isEmpty,
selectAll: (p0) { selectAll: (p0) {
visitorBloc.selectedDeviceIds.clear(); visitorBloc.selectedDeviceIds.clear();
for (var item in state.data) { for (var item in state.data) {
visitorBloc visitorBloc.add(SelectDeviceEvent(item.uuid));
.add(SelectDeviceEvent(item.uuid));
} }
}, },
onRowSelected: (index, isSelected, row) { onRowSelected: (index, isSelected, row) {

View File

@ -87,7 +87,8 @@ class VisitorPasswordDialog extends StatelessWidget {
], ],
)) ))
.then((v) { .then((v) {
Navigator.of(context).pop(); Navigator.of(context).pop(true);
}); });
} else if (state is FailedState) { } else if (state is FailedState) {
visitorBloc.stateDialog( visitorBloc.stateDialog(
@ -211,28 +212,28 @@ class VisitorPasswordDialog extends StatelessWidget {
}, },
), ),
), ),
SizedBox( // SizedBox(
width: size.width * 0.12, // width: size.width * 0.12,
child: RadioListTile<String>( // child: RadioListTile<String>(
contentPadding: EdgeInsets.zero, // contentPadding: EdgeInsets.zero,
title: Text( // title: Text(
'Dynamic Password', // 'Dynamic Password',
style: text, // style: text,
), // ),
value: 'Dynamic Password', // value: 'Dynamic Password',
groupValue: (state is PasswordTypeSelected) // groupValue: (state is PasswordTypeSelected)
? state.selectedType // ? state.selectedType
: visitorBloc.accessTypeSelected, // : visitorBloc.accessTypeSelected,
onChanged: (String? value) { // onChanged: (String? value) {
if (value != null) { // if (value != null) {
context // context
.read<VisitorPasswordBloc>() // .read<VisitorPasswordBloc>()
.add(SelectPasswordType(value)); // .add(SelectPasswordType(value));
visitorBloc.usageFrequencySelected = ''; // visitorBloc.usageFrequencySelected = '';
} // }
}, // },
), // ),
), // ),
], ],
)), )),
const Spacer( const Spacer(
@ -256,14 +257,14 @@ class VisitorPasswordDialog extends StatelessWidget {
color: ColorsManager.grayColor, color: ColorsManager.grayColor,
fontSize: 9), fontSize: 9),
), ),
if (visitorBloc.accessTypeSelected == 'Dynamic Password') // if (visitorBloc.accessTypeSelected == 'Dynamic Password')
Text( // Text(
'Quick and short-acting password, only valid within 5 minutes after creation, the system randomly generates a digital password.', // 'Quick and short-acting password, only valid within 5 minutes after creation, the system randomly generates a digital password.',
style: Theme.of(context).textTheme.bodySmall!.copyWith( // style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.w400, // fontWeight: FontWeight.w400,
color: ColorsManager.grayColor, // color: ColorsManager.grayColor,
fontSize: 9), // fontSize: 9),
), // ),
const SizedBox( const SizedBox(
height: 20, height: 20,
) )
@ -323,8 +324,7 @@ class VisitorPasswordDialog extends StatelessWidget {
: visitorBloc.usageFrequencySelected, : visitorBloc.usageFrequencySelected,
onChanged: (String? value) { onChanged: (String? value) {
if (value != null) { if (value != null) {
context context.read<VisitorPasswordBloc>()
.read<VisitorPasswordBloc>()
.add(SelectUsageFrequency(value)); .add(SelectUsageFrequency(value));
} }
}, },
@ -344,7 +344,7 @@ class VisitorPasswordDialog extends StatelessWidget {
if (visitorBloc.usageFrequencySelected == 'One-Time' && if (visitorBloc.usageFrequencySelected == 'One-Time' &&
visitorBloc.accessTypeSelected == 'Offline Password') visitorBloc.accessTypeSelected == 'Offline Password')
Text( Text(
'Within the validity period, there is no limit to the number of times each device can be unlocked.', 'Within the validity period, each device can be unlocked only once, and the maximum validity period is 6 hours',
style: Theme.of(context).textTheme.bodySmall!.copyWith( style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor, fontSize: 9), color: ColorsManager.grayColor, fontSize: 9),
), ),
@ -380,11 +380,9 @@ class VisitorPasswordDialog extends StatelessWidget {
endTime: () { endTime: () {
if (visitorBloc.usageFrequencySelected == 'Periodic' && if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Offline Password') { visitorBloc.accessTypeSelected == 'Offline Password') {
visitorBloc.add( visitorBloc.add(SelectTimeEvent(context: context, isEffective: false));
SelectTimeEvent(context: context, isEffective: false));
} else { } else {
visitorBloc.add(SelectTimeVisitorPassword( visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: false, isRepeat: false));
context: context, isStart: false, isRepeat: false));
} }
}, },
startTime: () { startTime: () {
@ -398,13 +396,11 @@ class VisitorPasswordDialog extends StatelessWidget {
} }
}, },
firstString: (visitorBloc.usageFrequencySelected == firstString: (visitorBloc.usageFrequencySelected ==
'Periodic' && 'Periodic' && visitorBloc.accessTypeSelected == 'Offline Password')
visitorBloc.accessTypeSelected == 'Offline Password')
? visitorBloc.effectiveTime ? visitorBloc.effectiveTime
: visitorBloc.startTimeAccess.toString(), : visitorBloc.startTimeAccess.toString(),
secondString: (visitorBloc.usageFrequencySelected == secondString: (visitorBloc.usageFrequencySelected ==
'Periodic' && 'Periodic' && visitorBloc.accessTypeSelected == 'Offline Password')
visitorBloc.accessTypeSelected == 'Offline Password')
? visitorBloc.expirationTime ? visitorBloc.expirationTime
: visitorBloc.endTimeAccess.toString(), : visitorBloc.endTimeAccess.toString(),
icon: Assets.calendarIcon), icon: Assets.calendarIcon),
@ -528,9 +524,20 @@ class VisitorPasswordDialog extends StatelessWidget {
if (visitorBloc.usageFrequencySelected == 'One-Time' && if (visitorBloc.usageFrequencySelected == 'One-Time' &&
visitorBloc.accessTypeSelected == 'Offline Password') { visitorBloc.accessTypeSelected == 'Offline Password') {
setPasswordFunction(context, size, visitorBloc); setPasswordFunction(context, size, visitorBloc);
} else if (visitorBloc.accessTypeSelected == 'Dynamic Password') { } else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
setPasswordFunction(context, size, visitorBloc); visitorBloc.accessTypeSelected == 'Offline Password') {
} else { if (visitorBloc.expirationTime != 'End Time' &&
visitorBloc.effectiveTime != 'Start Time' ) {
setPasswordFunction(context, size, visitorBloc);
}else{
visitorBloc.stateDialog(
context: context,
message: 'Please select Access Period to continue',
title: 'Access Period');
}
} else if(
visitorBloc.endTimeAccess.toString()!='End Time'
&&visitorBloc.startTimeAccess.toString()!='Start Time') {
if (visitorBloc.effectiveTimeTimeStamp != null && if (visitorBloc.effectiveTimeTimeStamp != null &&
visitorBloc.expirationTimeTimeStamp != null) { visitorBloc.expirationTimeTimeStamp != null) {
if (isRepeat == true) { if (isRepeat == true) {
@ -554,6 +561,11 @@ class VisitorPasswordDialog extends StatelessWidget {
message: 'Please select Access Period to continue', message: 'Please select Access Period to continue',
title: 'Access Period'); title: 'Access Period');
} }
}else{
visitorBloc.stateDialog(
context: context,
message: 'Please select Access Period to continue',
title: 'Access Period');
} }
} else { } else {
visitorBloc.stateDialog( visitorBloc.stateDialog(
@ -563,57 +575,6 @@ class VisitorPasswordDialog extends StatelessWidget {
} }
} }
}, },
// onPressed: () {
// if (visitorBloc.forgetFormKey.currentState!.validate()) {
// if (visitorBloc.selectedDevices.isNotEmpty) {
// switch (visitorBloc.usageFrequencySelected) {
// case 'One-Time':
// if (visitorBloc.accessTypeSelected == 'Offline Password') {
// setPasswordFunction(context, size, visitorBloc);
// } else {
// visitorBloc.stateDialog(
// context: context,
// message: 'Invalid combination of Access Type and Usage Frequency.',
// title: 'Error',
// );
// }
// break;
// default:
// if (visitorBloc.effectiveTimeTimeStamp != null && visitorBloc.expirationTimeTimeStamp != null) {
// if (isRepeat) {
// if (visitorBloc.expirationTime != 'End Time' &&
// visitorBloc.effectiveTime != 'Start Time' &&
// visitorBloc.selectedDays.isNotEmpty) {
// setPasswordFunction(context, size, visitorBloc);
// } else {
// visitorBloc.stateDialog(
// context: context,
// message: 'Please select days and fill start time and end time to continue',
// title: 'Access Period',
// );
// }
// } else {
// setPasswordFunction(context, size, visitorBloc);
// }
// } else {
// visitorBloc.stateDialog(
// context: context,
// message: 'Please select Access Period to continue',
// title: 'Access Period',
// );
// }
// break;
// }
// } else {
// visitorBloc.stateDialog(
// context: context,
// message: 'Please select devices to continue',
// title: 'Select Devices',
// );
// }
// }
// },
borderRadius: 8, borderRadius: 8,
child: Text( child: Text(
'Ok', 'Ok',
@ -723,39 +684,39 @@ class VisitorPasswordDialog extends StatelessWidget {
borderRadius: 8, borderRadius: 8,
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(context);
if (visitorBloc.accessTypeSelected == 'Dynamic Password') { if (visitorBloc.usageFrequencySelected == 'One-Time' &&
} else { visitorBloc.accessTypeSelected == 'Online Password') {
if (visitorBloc.usageFrequencySelected == 'One-Time' && visitorBloc.add(OnlineOneTimePasswordEvent(
visitorBloc.accessTypeSelected == 'Online Password') { context: context,
visitorBloc.add(OnlineOneTimePasswordEvent( passwordName: visitorBloc.userNameController.text,
context: context, email: visitorBloc.emailController.text,
passwordName: visitorBloc.userNameController.text, ));
email: visitorBloc.emailController.text, }
)); else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
} else if (visitorBloc.usageFrequencySelected == 'Periodic' && visitorBloc.accessTypeSelected == 'Online Password') {
visitorBloc.accessTypeSelected == 'Online Password') { visitorBloc.add(OnlineMultipleTimePasswordEvent(
visitorBloc.add(OnlineMultipleTimePasswordEvent( passwordName: visitorBloc.userNameController.text,
passwordName: visitorBloc.userNameController.text, email: visitorBloc.emailController.text,
email: visitorBloc.emailController.text, effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(),
effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(), invalidTime: visitorBloc.expirationTimeTimeStamp.toString(),
invalidTime: visitorBloc.expirationTimeTimeStamp.toString(), ));
)); }
} else if (visitorBloc.usageFrequencySelected == 'One-Time' && else if (visitorBloc.usageFrequencySelected == 'One-Time' &&
visitorBloc.accessTypeSelected == 'Offline Password') { visitorBloc.accessTypeSelected == 'Offline Password') {
visitorBloc.add(OfflineOneTimePasswordEvent( visitorBloc.add(OfflineOneTimePasswordEvent(
context: context, context: context,
passwordName: visitorBloc.userNameController.text, passwordName: visitorBloc.userNameController.text,
email: visitorBloc.emailController.text, email: visitorBloc.emailController.text,
)); ));
} else if (visitorBloc.usageFrequencySelected == 'Periodic' && }
visitorBloc.accessTypeSelected == 'Offline Password') { else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.add(OfflineMultipleTimePasswordEvent( visitorBloc.accessTypeSelected == 'Offline Password') {
passwordName: visitorBloc.userNameController.text, visitorBloc.add(OfflineMultipleTimePasswordEvent(
email: visitorBloc.emailController.text, passwordName: visitorBloc.userNameController.text,
effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(), email: visitorBloc.emailController.text,
invalidTime: visitorBloc.expirationTimeTimeStamp.toString(), effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(),
)); invalidTime: visitorBloc.expirationTimeTimeStamp.toString(),
} ));
} }
}, },
child: Text( child: Text(

View File

@ -22,7 +22,6 @@ class AccessMangApi {
); );
return response; return response;
} catch (e) { } catch (e) {
debugPrint('Error fetching visitor passwords: $e');
return []; return [];
} }
} }
@ -42,7 +41,6 @@ class AccessMangApi {
); );
return response; return response;
} catch (e) { } catch (e) {
debugPrint('Error fetching $e');
return []; return [];
} }
} }

View File

@ -25,7 +25,8 @@ class AuthenticationAPI {
path: ApiEndpoints.forgetPassword, path: ApiEndpoints.forgetPassword,
body: {"email": email, "password": password}, body: {"email": email, "password": password},
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) {}); expectedResponseModel: (json) {
});
return response; return response;
} }
@ -52,21 +53,17 @@ class AuthenticationAPI {
return cooldown; return cooldown;
} }
} else { } else {
debugPrint('Error: ${e.response!.statusCode} - ${e.response!.statusMessage}');
return 1; return 1;
} }
} else { } else {
debugPrint('Error: ${e.message}');
return 1; return 1;
} }
} catch (e) { } catch (e) {
debugPrint('Unexpected Error: $e');
return 1; return 1;
} }
} }
static Future verifyOtp({required String email, required String otpCode}) async { static Future verifyOtp({required String email, required String otpCode}) async {
try {
final response = await HTTPService().post( final response = await HTTPService().post(
path: ApiEndpoints.verifyOtp, path: ApiEndpoints.verifyOtp,
body: {"email": email, "type": "PASSWORD", "otpCode": otpCode}, body: {"email": email, "type": "PASSWORD", "otpCode": otpCode},
@ -79,17 +76,7 @@ class AuthenticationAPI {
} }
}); });
return response; return response;
} on DioException catch (e) {
if (e.response != null) {
if (e.response!.statusCode == 400) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
return errorMessage;
}
} else {
debugPrint('Error: ${e.message}');
}
}
} }
static Future<List<RegionModel>> fetchRegion() async { static Future<List<RegionModel>> fetchRegion() async {

View File

@ -14,7 +14,7 @@ class Assets {
static const String google = "assets/images/google.svg"; static const String google = "assets/images/google.svg";
static const String facebook = "assets/images/facebook.svg"; static const String facebook = "assets/images/facebook.svg";
static const String invisiblePassword = "assets/images/Password_invisible.svg"; static const String invisiblePassword = "assets/images/Password_invisible.svg";
static const String visiblePassword = "assets/images/Password_visible.svg"; static const String visiblePassword = "assets/images/password_visible.svg";
static const String accessIcon = "assets/images/access_icon.svg"; static const String accessIcon = "assets/images/access_icon.svg";
static const String spaseManagementIcon = "assets/images/spase_management_icon.svg"; static const String spaseManagementIcon = "assets/images/spase_management_icon.svg";
static const String devicesIcon = "assets/images/devices_icon.svg"; static const String devicesIcon = "assets/images/devices_icon.svg";

View File

@ -15,13 +15,14 @@ class WebAppBar extends StatelessWidget with HelperResponsiveLayout {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool isSmallScreen = isSmallScreenSize(context); bool isSmallScreen = isSmallScreenSize(context);
bool isHalfMediumScreen = isHafMediumScreenSize(context);
return BlocBuilder<HomeBloc, HomeState>(builder: (context, state) { return BlocBuilder<HomeBloc, HomeState>(builder: (context, state) {
final user = context.read<HomeBloc>().user;
return Container( return Container(
height: isSmallScreen ? 130 : 100, height: (isSmallScreen || isHalfMediumScreen) ? 130 : 100,
decoration: const BoxDecoration(color: ColorsManager.secondaryColor), decoration: const BoxDecoration(color: ColorsManager.secondaryColor),
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: isSmallScreen child: isSmallScreen || isHalfMediumScreen
? Column( ? Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -31,14 +32,11 @@ class WebAppBar extends StatelessWidget with HelperResponsiveLayout {
child: title!, child: title!,
), ),
if (centerBody != null) if (centerBody != null)
Align( Padding(
alignment: Alignment.centerLeft, padding: const EdgeInsets.only(top: 8.0),
child: Padding( child: centerBody,
padding: const EdgeInsets.only(top: 8.0),
child: centerBody,
),
), ),
if (rightBody != null || HomeBloc.user != null) if (rightBody != null || user != null)
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@ -61,9 +59,9 @@ class WebAppBar extends StatelessWidget with HelperResponsiveLayout {
const SizedBox( const SizedBox(
width: 10, width: 10,
), ),
if (HomeBloc.user != null) if (user != null)
Text( Text(
'${HomeBloc.user!.firstName} ${HomeBloc.user!.lastName}', '${user.firstName} ${user.lastName}',
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
), ),
], ],
@ -76,16 +74,18 @@ class WebAppBar extends StatelessWidget with HelperResponsiveLayout {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Align( Expanded(
alignment: Alignment.centerLeft, child: Row(
child: title!, children: [
), title!,
if (centerBody != null) if (centerBody != null)
Expanded( Padding(
child: Center( padding: const EdgeInsets.only(left: 80),
child: centerBody, child: centerBody!,
), ),
],
), ),
),
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -113,9 +113,9 @@ class WebAppBar extends StatelessWidget with HelperResponsiveLayout {
const SizedBox( const SizedBox(
width: 10, width: 10,
), ),
if (HomeBloc.user != null) if (user != null)
Text( Text(
'${HomeBloc.user!.firstName} ${HomeBloc.user!.lastName}', '${user.firstName} ${user.lastName}',
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
), ),
], ],

View File

@ -89,6 +89,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.1" version: "1.0.1"
dropdown_button2:
dependency: "direct main"
description:
name: dropdown_button2
sha256: b0fe8d49a030315e9eef6c7ac84ca964250155a6224d491c1365061bc974a9e1
url: "https://pub.dev"
source: hosted
version: "2.3.9"
dropdown_search:
dependency: "direct main"
description:
name: dropdown_search
sha256: "55106e8290acaa97ed15bea1fdad82c3cf0c248dd410e651f5a8ac6870f783ab"
url: "https://pub.dev"
source: hosted
version: "5.0.6"
equatable: equatable:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -43,9 +43,11 @@ dependencies:
get_it: ^7.6.7 get_it: ^7.6.7
flutter_secure_storage: ^9.2.2 flutter_secure_storage: ^9.2.2
shared_preferences: ^2.3.0 shared_preferences: ^2.3.0
dropdown_button2: ^2.3.9
data_table_2: ^2.5.15 data_table_2: ^2.5.15
go_router: go_router:
intl: ^0.19.0 intl: ^0.19.0
dropdown_search: ^5.0.6
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: