Merge pull request #9 from SyncrowIOT/fix_bugs

Fix bugs and login Enhancements
This commit is contained in:
Abdullah
2024-09-03 12:20:54 +03:00
committed by GitHub
17 changed files with 539 additions and 451 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) {
@ -83,6 +88,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
Expanded( Expanded(
child: DynamicTable( child: DynamicTable(
uuidIndex: 1, uuidIndex: 1,
withSelectAll: true,
isEmpty: filteredData.isEmpty, isEmpty: filteredData.isEmpty,
withCheckBox: false, withCheckBox: false,
size: MediaQuery.of(context).size, size: MediaQuery.of(context).size,
@ -98,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(),
@ -114,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,
@ -141,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(
@ -154,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),
)), )),
), ),
], ],
@ -172,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,
@ -200,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));
@ -246,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';
@ -178,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,11 +5,13 @@ 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;
@ -20,8 +22,10 @@ class DynamicTable extends StatefulWidget {
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,
@ -36,6 +40,7 @@ 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() {
@ -71,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(
@ -108,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!
@ -162,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

@ -55,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];

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) {
@ -184,7 +181,6 @@ class AddDeviceDialog extends StatelessWidget {
}, },
withCheckBox: true, withCheckBox: true,
size: size * 0.5, size: size * 0.5,
uuidIndex: 1,
headers: const [ headers: const [
'Device Name', 'Device Name',
'Device ID', 'Device ID',

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

@ -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: