Compare commits

..

23 Commits

Author SHA1 Message Date
0936ca8b95 Merged with dev and solved conflicts 2024-09-03 12:18:17 +03:00
b7c6232bdd Merge pull request #11 from SyncrowIOT/aug_bug_fixes
Aug bug fixes
2024-09-03 11:48:17 +03:00
3a254bce4d delete dynamic password 2024-09-03 09:51:55 +03:00
439ce85117 delete dynamic password 2024-09-03 09:50:36 +03:00
a100b5c40e login Enhancements and add search to forget password 2024-09-03 09:12:01 +03:00
6ba0a70289 login Enhancements and add search to forget password 2024-09-03 09:03:01 +03:00
8130acc392 login Enhancements and add search to forget password 2024-09-03 08:58:48 +03:00
2339417c8c space type connection 2024-09-02 22:36:11 +03:00
3017a5d1f6 fixed report table 2024-09-02 21:23:31 +03:00
7bbae2d332 fix selection from dynamic table 2024-09-02 19:26:52 +03:00
4cf9d4c2f2 push 2024-09-02 17:37:40 +03:00
418f5d5406 login Enhancements 2024-09-02 16:59:25 +03:00
00bce2d0ab fix pugs 2024-09-02 14:52:06 +03:00
ddcdd4891a forget password changes & bugs number 10 & 15-18 2024-09-02 10:16:28 +03:00
3ff6937116 Merge pull request #8 from SyncrowIOT/device_mgmt_bugs
Device mgmt bugs
2024-09-01 10:03:18 +03:00
3287e43a5f Fixed login design 2024-09-01 09:49:24 +03:00
36c2412234 handle login, home page responsiveness 2024-08-31 17:24:43 +03:00
dbe65bffff access management design revamp, responsiveness and buttons 2024-08-31 15:35:17 +03:00
6e183dba9f Bug fixes 2024-08-29 16:23:25 +03:00
95b1e2c932 fixes bugs and re add checkboxes theme 2024-08-29 12:55:37 +03:00
5f8957c9c1 push bug fixes 2024-08-28 21:02:51 +03:00
adf19818e7 push fixes 2024-08-28 20:52:09 +03:00
cfc8d4cf2f Merge pull request #7 from SyncrowIOT/device_view
Device view
2024-08-28 15:01:08 +03:00
66 changed files with 1742 additions and 1363 deletions

View File

@ -1 +0,0 @@

View File

@ -7,29 +7,35 @@ import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.da
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/services/locator.dart';
import 'package:syncrow_web/utils/app_routes.dart'; import 'package:syncrow_web/utils/app_routes.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart'; import 'package:syncrow_web/utils/constants/routes_const.dart';
import 'package:syncrow_web/utils/theme/theme.dart';
Future<void> main() async { Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
initialSetup(); initialSetup();
String checkToken = await AuthBloc.getTokenAndValidate(); runApp(MyApp());
GoRouter router = GoRouter(
initialLocation: checkToken == 'Success' ? RoutesConst.home : RoutesConst.auth,
routes: AppRoutes.getRoutes(),
);
runApp(MyApp(
router: router,
));
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
final GoRouter router; MyApp({
const MyApp({
super.key, super.key,
required this.router,
}); });
final GoRouter _router = GoRouter(
initialLocation: RoutesConst.auth,
routes: AppRoutes.getRoutes(),
redirect: (context, state) async {
String checkToken = await AuthBloc.getTokenAndValidate();
final loggedIn = checkToken == 'Success';
final goingToLogin = state.uri.toString() == RoutesConst.auth;
if (!loggedIn && !goingToLogin) return RoutesConst.auth;
if (loggedIn && goingToLogin) return RoutesConst.home;
return null;
},
);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiBlocProvider( return MultiBlocProvider(
@ -40,7 +46,7 @@ class MyApp extends StatelessWidget {
) )
], ],
child: MaterialApp.router( child: MaterialApp.router(
debugShowCheckedModeBanner: false, // Hide debug banner debugShowCheckedModeBanner: false,
scrollBehavior: const MaterialScrollBehavior().copyWith( scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: { dragDevices: {
PointerDeviceKind.mouse, PointerDeviceKind.mouse,
@ -49,26 +55,8 @@ class MyApp extends StatelessWidget {
PointerDeviceKind.unknown, PointerDeviceKind.unknown,
}, },
), ),
theme: myTheme,
theme: ThemeData( routerConfig: _router,
fontFamily: 'Aftika',
textTheme: const TextTheme(
bodySmall: TextStyle(
fontSize: 13, color: ColorsManager.whiteColors, fontWeight: FontWeight.bold),
bodyMedium: TextStyle(color: Colors.black87, fontSize: 14),
bodyLarge: TextStyle(fontSize: 16, color: Colors.white),
headlineSmall: TextStyle(color: Colors.black87, fontSize: 18),
headlineMedium: TextStyle(color: Colors.black87, fontSize: 20),
headlineLarge: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
),
routeInformationProvider: router.routeInformationProvider,
routerDelegate: router.routerDelegate,
routeInformationParser: router.routeInformationParser,
)); ));
} }
} }

View File

@ -4,7 +4,6 @@ import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_state.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_state.dart';
import 'package:syncrow_web/pages/access_management/model/password_model.dart'; import 'package:syncrow_web/pages/access_management/model/password_model.dart';
import 'package:syncrow_web/services/access_mang_api.dart'; import 'package:syncrow_web/services/access_mang_api.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart'; import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/snack_bar.dart'; import 'package:syncrow_web/utils/snack_bar.dart';
@ -108,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;
} }
@ -136,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;
@ -146,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;
@ -156,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)) {
@ -184,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

@ -1,65 +1,57 @@
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:go_router/go_router.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_state.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_state.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart';
import 'package:syncrow_web/pages/common/custom_table.dart'; import 'package:syncrow_web/pages/common/custom_table.dart';
import 'package:syncrow_web/pages/common/date_time_widget.dart'; import 'package:syncrow_web/pages/common/date_time_widget.dart';
import 'package:syncrow_web/pages/common/filter/filter_widget.dart';
import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart'; import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart'; import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart'; import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart';
class AccessManagementPage extends StatelessWidget { class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
const AccessManagementPage({super.key}); const AccessManagementPage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size; final isLargeScreen = isLargeScreenSize(context);
final isSmallScreen = isSmallScreenSize(context);
final isHalfMediumScreen = isHafMediumScreenSize(context);
final padding = isLargeScreen ? const EdgeInsets.all(30) : const EdgeInsets.all(15);
return WebScaffold( return WebScaffold(
enableMenuSideba: false, enableMenuSideba: false,
appBarTitle: Row( appBarTitle: FittedBox(
child: Text(
'Access Management',
style: Theme.of(context).textTheme.headlineLarge,
),
),
centerBody: Wrap(
children: [ children: [
Text( Padding(
'Access Management', padding: EdgeInsets.only(left: MediaQuery.of(context).size.width * 0.09),
style: Theme.of(context).textTheme.headlineLarge, child: Align(
) alignment: Alignment.bottomLeft,
child: Text(
'Physical Access',
style: Theme.of(context).textTheme.headlineMedium!.copyWith(color: Colors.white),
),
),
),
], ],
), ),
appBarBody: [ rightBody: const NavigateHomeGridView(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Physical Access',
style: Theme.of(context).textTheme.headlineMedium!.copyWith(color: Colors.white),
),
Row(
children: [
InkWell(
onTap: () {
context.go(RoutesConst.home);
},
child: SvgPicture.asset(
height: 20,
width: 20,
Assets.grid,
),
),
const SizedBox(
width: 10,
)
],
),
],
),
],
scaffoldBody: BlocProvider( scaffoldBody: BlocProvider(
create: (BuildContext context) => AccessBloc()..add(FetchTableData()), create: (BuildContext context) => AccessBloc()..add(FetchTableData()),
child: BlocConsumer<AccessBloc, AccessState>( child: BlocConsumer<AccessBloc, AccessState>(
@ -67,194 +59,39 @@ class AccessManagementPage extends StatelessWidget {
builder: (context, state) { builder: (context, state) {
final accessBloc = BlocProvider.of<AccessBloc>(context); final accessBloc = BlocProvider.of<AccessBloc>(context);
final filteredData = accessBloc.filteredData; final filteredData = accessBloc.filteredData;
return state is AccessLoaded return state is AccessLoaded
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: Container( : Container(
padding: EdgeInsets.all(30), padding: padding,
height: size.height, height: MediaQuery.of(context).size.height,
width: size.width, width: MediaQuery.of(context).size.width,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( FilterWidget(
decoration: containerDecoration, size: MediaQuery.of(context).size,
height: size.height * 0.05, tabs: accessBloc.tabs,
child: Flexible( selectedIndex: accessBloc.selectedIndex,
child: ListView.builder( onTabChanged: (index) {
scrollDirection: Axis.horizontal, accessBloc.add(TabChangedEvent(index));
itemCount: BlocProvider.of<AccessBloc>(context).tabs.length, },
shrinkWrap: true,
itemBuilder: (context, index) {
final isSelected = index ==
BlocProvider.of<AccessBloc>(context).selectedIndex;
return InkWell(
onTap: () {
BlocProvider.of<AccessBloc>(context)
.add(TabChangedEvent(index));
},
child: Container(
decoration: BoxDecoration(
color: ColorsManager.boxColor,
border: Border.all(
color: isSelected ? Colors.blue : Colors.transparent,
width: 2.0,
),
borderRadius: index == 0
? const BorderRadius.only(
topLeft: Radius.circular(10),
bottomLeft: Radius.circular(10))
: index == 3
? const BorderRadius.only(
topRight: Radius.circular(10),
bottomRight: Radius.circular(10))
: null,
),
padding: const EdgeInsets.only(left: 10, right: 10),
child: Center(
child: Text(
BlocProvider.of<AccessBloc>(context).tabs[index],
style: TextStyle(
color: isSelected ? Colors.blue : Colors.black,
),
),
),
),
);
},
),
),
),
const SizedBox(
height: 20,
),
Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
textBaseline: TextBaseline.ideographic,
children: [
Container(
width: size.width * 0.15,
child: CustomWebTextField(
controller: accessBloc.passwordName,
isRequired: true,
textFieldName: 'Name',
description: '',
),
),
const SizedBox(
width: 15,
),
DateTimeWebWidget(
icon: Assets.calendarIcon,
isRequired: false,
title: 'Access Time',
size: size,
endTime: () {
accessBloc.add(SelectTime(context: context, isStart: false));
},
startTime: () {
accessBloc.add(SelectTime(context: context, isStart: true));
},
firstString: BlocProvider.of<AccessBloc>(context).startTime,
secondString: BlocProvider.of<AccessBloc>(context).endTime,
),
const SizedBox(
width: 15,
),
SizedBox(
width: size.width * 0.06,
child: Container(
decoration: containerDecoration,
child: DefaultButton(
onPressed: () {
accessBloc.add(FilterDataEvent(
selectedTabIndex: BlocProvider.of<AccessBloc>(
context)
.selectedIndex, // Pass the selected tab index
passwordName:
accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
},
borderRadius: 9,
child: const Text('Search'))),
),
const SizedBox(
width: 10,
),
SizedBox(
width: size.width * 0.06,
child: Container(
decoration: containerDecoration,
child: DefaultButton(
onPressed: () {
accessBloc.add(ResetSearch());
},
backgroundColor: ColorsManager.whiteColors,
borderRadius: 9,
child: Text(
'Reset',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: Colors.black),
),
),
),
),
],
),
const SizedBox(
height: 20,
),
Wrap(
children: [
Container(
width: size.width * 0.15,
decoration: containerDecoration,
child: DefaultButton(
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return const VisitorPasswordDialog();
},
).then((v) {
if (v != null) {
accessBloc.add(FetchTableData());
}
});
},
borderRadius: 8,
child: const Text('+ Create Visitor Password ')),
),
const SizedBox(
width: 10,
),
Container(
width: size.width * 0.12,
decoration: containerDecoration,
child: DefaultButton(
borderRadius: 8,
backgroundColor: ColorsManager.whiteColors,
child: Text(
'Admin Password',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: Colors.black),
)))
],
),
const SizedBox(
height: 20,
), ),
const SizedBox(height: 20),
if (isSmallScreen || isHalfMediumScreen)
_buildSmallSearchFilters(context, accessBloc)
else
_buildNormalSearchWidgets(context, accessBloc),
const SizedBox(height: 20),
_buildVisitorAdminPasswords(context, accessBloc),
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: size, size: MediaQuery.of(context).size,
cellDecoration: containerDecoration, cellDecoration: containerDecoration,
headers: const [ headers: const [
'Name', 'Name',
@ -267,7 +104,7 @@ class AccessManagementPage extends StatelessWidget {
], ],
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(),
@ -276,12 +113,150 @@ class AccessManagementPage extends StatelessWidget {
item.passwordStatus.value, item.passwordStatus.value,
]; ];
}).toList(), }).toList(),
) )),
// : const Center(child: CircularProgressIndicator()),
)
], ],
), ),
); );
}))); })));
} }
Wrap _buildVisitorAdminPasswords(BuildContext context, AccessBloc accessBloc) {
return Wrap(
spacing: 10,
runSpacing: 10,
children: [
Container(
width: 205,
height: 42,
decoration: containerDecoration,
child: DefaultButton(
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return const VisitorPasswordDialog();
},
).then((v) {
if (v != null) {
accessBloc.add(FetchTableData());
}
});
},
borderRadius: 8,
child: Text(
'+ Create Visitor Password ',
style: context.textTheme.titleSmall!.copyWith(color: Colors.white, fontSize: 12),
)),
),
Container(
width: 133,
height: 42,
decoration: containerDecoration,
child: DefaultButton(
borderRadius: 8,
backgroundColor: ColorsManager.whiteColors,
child: Text(
'Admin Password',
style: context.textTheme.titleSmall!.copyWith(color: Colors.black, fontSize: 12),
)),
),
],
);
}
Row _buildNormalSearchWidgets(BuildContext context, AccessBloc accessBloc) {
return Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
textBaseline: TextBaseline.ideographic,
children: [
SizedBox(
width: 250,
child: CustomWebTextField(
controller: accessBloc.passwordName,
height: 43,
isRequired: false,
textFieldName: 'Name',
description: '',
),
),
const SizedBox(width: 15),
SizedBox(
child: DateTimeWebWidget(
icon: Assets.calendarIcon,
isRequired: false,
title: 'Access Time',
size: MediaQuery.of(context).size,
endTime: () {
accessBloc.add(SelectTime(context: context, isStart: false));
},
startTime: () {
accessBloc.add(SelectTime(context: context, isStart: true));
},
firstString: BlocProvider.of<AccessBloc>(context).startTime,
secondString: BlocProvider.of<AccessBloc>(context).endTime,
),
),
const SizedBox(width: 15),
SearchResetButtons(
onSearch: () {
accessBloc.add(FilterDataEvent(
selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
},
onReset: () {
accessBloc.add(ResetSearch());
},
),
],
);
}
Widget _buildSmallSearchFilters(BuildContext context, AccessBloc accessBloc) {
return Wrap(
spacing: 20,
runSpacing: 10,
children: [
SizedBox(
width: 300,
child: CustomWebTextField(
controller: accessBloc.passwordName,
isRequired: true,
height: 40,
textFieldName: 'Name',
description: '',
),
),
DateTimeWebWidget(
icon: Assets.calendarIcon,
isRequired: false,
title: 'Access Time',
size: MediaQuery.of(context).size,
endTime: () {
accessBloc.add(SelectTime(context: context, isStart: false));
},
startTime: () {
accessBloc.add(SelectTime(context: context, isStart: true));
},
firstString: BlocProvider.of<AccessBloc>(context).startTime,
secondString: BlocProvider.of<AccessBloc>(context).endTime,
),
SearchResetButtons(
onSearch: () {
accessBloc.add(FilterDataEvent(
selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
},
onReset: () {
accessBloc.add(ResetSearch());
},
),
],
);
}
} }

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';
@ -135,6 +140,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(const LoginFailure(error: 'Something went wrong')); emit(const LoginFailure(error: 'Something went wrong'));
return; return;
} }
token = await AuthenticationAPI.loginWithEmail( token = await AuthenticationAPI.loginWithEmail(
model: LoginWithEmailModel( model: LoginWithEmailModel(
email: event.username, email: event.username,
@ -143,10 +149,10 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
); );
} catch (failure) { } catch (failure) {
validate = 'Invalid Credentials!'; validate = 'Invalid Credentials!';
emit(AuthInitialState()); emit(const LoginFailure(error: 'Invalid Credentials!'));
// emit(const LoginFailure(error: 'Something went wrong'));
return; return;
} }
if (token.accessTokenIsNotEmpty) { if (token.accessTokenIsNotEmpty) {
FlutterSecureStorage storage = const FlutterSecureStorage(); FlutterSecureStorage storage = const FlutterSecureStorage();
await storage.write( await storage.write(
@ -155,9 +161,9 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
key: UserModel.userUuidKey, key: UserModel.userUuidKey,
value: Token.decodeToken(token.accessToken)['uuid'].toString()); value: Token.decodeToken(token.accessToken)['uuid'].toString());
user = UserModel.fromToken(token); user = UserModel.fromToken(token);
debugPrint(token.accessToken);
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

@ -1,5 +1,4 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
abstract class AuthEvent extends Equatable { abstract class AuthEvent extends Equatable {
const AuthEvent(); const AuthEvent();
@ -47,13 +46,16 @@ class StopTimerEvent extends AuthEvent {}
class UpdateTimerEvent extends AuthEvent { class UpdateTimerEvent extends AuthEvent {
final int remainingTime; final int remainingTime;
final bool isButtonEnabled; final bool isButtonEnabled;
const UpdateTimerEvent( const UpdateTimerEvent({required this.remainingTime, required this.isButtonEnabled});
{required this.remainingTime, required this.isButtonEnabled});
} }
class ChangePasswordEvent extends AuthEvent {} class ChangePasswordEvent extends AuthEvent {
class SendOtpEvent extends AuthEvent {} }
class SendOtpEvent extends AuthEvent {
}
class PasswordVisibleEvent extends AuthEvent { class PasswordVisibleEvent extends AuthEvent {
final bool? newValue; final bool? newValue;

View File

@ -56,8 +56,7 @@ class TimerState extends AuthState {
final bool isButtonEnabled; final bool isButtonEnabled;
final int remainingTime; final int remainingTime;
const TimerState( const TimerState({required this.isButtonEnabled, required this.remainingTime});
{required this.isButtonEnabled, required this.remainingTime});
@override @override
List<Object> get props => [isButtonEnabled, remainingTime]; List<Object> get props => [isButtonEnabled, remainingTime];
@ -65,12 +64,12 @@ class TimerState extends AuthState {
class AuthError extends AuthState { class AuthError extends AuthState {
final String message; final String message;
String? code; final String? code;
AuthError({required this.message, this.code}); const AuthError({required this.message, this.code});
} }
class AuthTokenError extends AuthError { class AuthTokenError extends AuthError {
AuthTokenError({required super.message, super.code}); const AuthTokenError({required super.message, super.code});
} }
class AuthSuccess extends AuthState {} class AuthSuccess extends AuthState {}

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/auth/view/forget_password_mobile_page.dart';
import 'package:syncrow_web/pages/auth/view/forget_password_web_page.dart'; import 'package:syncrow_web/pages/auth/view/forget_password_web_page.dart';
import 'package:syncrow_web/utils/responsive_layout.dart'; import 'package:syncrow_web/utils/responsive_layout.dart';
@ -9,7 +8,6 @@ class ForgetPasswordPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const ResponsiveLayout( return const ResponsiveLayout(
desktopBody: ForgetPasswordWebPage(), desktopBody: ForgetPasswordWebPage(), mobileBody: ForgetPasswordWebPage());
mobileBody: ForgetPasswordWebPage());
} }
} }

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,8 +1,8 @@
import 'dart:ui';
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';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_event.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_event.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_state.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_state.dart';
@ -11,7 +11,7 @@ import 'package:syncrow_web/pages/auth/view/forget_password_page.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/pages/home/view/home_page.dart'; import 'package:syncrow_web/utils/constants/routes_const.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
class LoginMobilePage extends StatelessWidget { class LoginMobilePage extends StatelessWidget {
@ -25,10 +25,7 @@ class LoginMobilePage extends StatelessWidget {
listener: (context, state) { listener: (context, state) {
if (state is LoginSuccess) { if (state is LoginSuccess) {
// Navigate to home screen after successful login // Navigate to home screen after successful login
Navigator.pushReplacement( context.go(RoutesConst.home, extra: {'clearHistory': true});
context,
MaterialPageRoute(builder: (context) => HomePage()),
);
} else if (state is LoginFailure) { } else if (state is LoginFailure) {
// Show error message // Show error message
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
@ -52,8 +49,6 @@ class LoginMobilePage extends StatelessWidget {
Widget _buildLoginForm(BuildContext context) { Widget _buildLoginForm(BuildContext context) {
final loginBloc = BlocProvider.of<AuthBloc>(context); final loginBloc = BlocProvider.of<AuthBloc>(context);
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
return Center( return Center(
child: Stack( child: Stack(
children: [ children: [
@ -102,11 +97,8 @@ class LoginMobilePage extends StatelessWidget {
padding: EdgeInsets.all(20), padding: EdgeInsets.all(20),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1), color: Colors.white.withOpacity(0.1),
borderRadius: borderRadius: const BorderRadius.all(Radius.circular(30)),
const BorderRadius.all(Radius.circular(30)), border: Border.all(color: ColorsManager.graysColor.withOpacity(0.2))),
border: Border.all(
color:
ColorsManager.graysColor.withOpacity(0.2))),
child: Form( child: Form(
key: loginBloc.loginFormKey, key: loginBloc.loginFormKey,
child: Column( child: Column(
@ -117,9 +109,7 @@ class LoginMobilePage extends StatelessWidget {
const Text( const Text(
'Login', 'Login',
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
fontSize: 24,
fontWeight: FontWeight.bold),
), ),
const SizedBox(height: 30), const SizedBox(height: 30),
Column( Column(
@ -148,8 +138,7 @@ class LoginMobilePage extends StatelessWidget {
), ),
isDense: true, isDense: true,
style: const TextStyle(color: Colors.black), style: const TextStyle(color: Colors.black),
items: loginBloc.regionList! items: loginBloc.regionList!.map((RegionModel region) {
.map((RegionModel region) {
return DropdownMenuItem<String>( return DropdownMenuItem<String>(
value: region.name, value: region.name,
child: Text(region.name), child: Text(region.name),
@ -173,8 +162,8 @@ class LoginMobilePage extends StatelessWidget {
child: TextFormField( child: TextFormField(
validator: loginBloc.validateEmail, validator: loginBloc.validateEmail,
controller: loginBloc.loginEmailController, controller: loginBloc.loginEmailController,
decoration: textBoxDecoration()! decoration:
.copyWith(hintText: 'Enter your email'), textBoxDecoration()!.copyWith(hintText: 'Enter your email'),
style: const TextStyle(color: Colors.black), style: const TextStyle(color: Colors.black),
), ),
), ),
@ -194,8 +183,7 @@ class LoginMobilePage extends StatelessWidget {
validator: loginBloc.validatePassword, validator: loginBloc.validatePassword,
obscureText: loginBloc.obscureText, obscureText: loginBloc.obscureText,
keyboardType: TextInputType.visiblePassword, keyboardType: TextInputType.visiblePassword,
controller: controller: loginBloc.loginPasswordController,
loginBloc.loginPasswordController,
decoration: textBoxDecoration()!.copyWith( decoration: textBoxDecoration()!.copyWith(
hintText: 'At least 8 characters', hintText: 'At least 8 characters',
), ),
@ -213,16 +201,13 @@ class LoginMobilePage extends StatelessWidget {
children: [ children: [
InkWell( InkWell(
onTap: () { onTap: () {
Navigator.of(context) Navigator.of(context).push(MaterialPageRoute(
.push(MaterialPageRoute( builder: (context) => const ForgetPasswordPage(),
builder: (context) =>
const ForgetPasswordPage(),
)); ));
}, },
child: Text( child: Text(
"Forgot Password?", "Forgot Password?",
style: style: Theme.of(context).textTheme.bodySmall,
Theme.of(context).textTheme.bodySmall,
), ),
), ),
], ],
@ -233,15 +218,13 @@ class LoginMobilePage extends StatelessWidget {
Transform.scale( Transform.scale(
scale: 1.2, // Adjust the scale as needed scale: 1.2, // Adjust the scale as needed
child: Checkbox( child: Checkbox(
fillColor: MaterialStateProperty.all<Color>( fillColor: MaterialStateProperty.all<Color>(Colors.white),
Colors.white),
activeColor: Colors.white, activeColor: Colors.white,
value: loginBloc.isChecked, value: loginBloc.isChecked,
checkColor: Colors.black, checkColor: Colors.black,
shape: const CircleBorder(), shape: const CircleBorder(),
onChanged: (bool? newValue) { onChanged: (bool? newValue) {
loginBloc.add( loginBloc.add(CheckBoxEvent(newValue: newValue));
CheckBoxEvent(newValue: newValue));
}, },
), ),
), ),
@ -250,37 +233,30 @@ class LoginMobilePage extends StatelessWidget {
child: RichText( child: RichText(
text: TextSpan( text: TextSpan(
text: 'Agree to ', text: 'Agree to ',
style: style: const TextStyle(color: Colors.white),
const TextStyle(color: Colors.white),
children: [ children: [
TextSpan( TextSpan(
text: '(Terms of Service)', text: '(Terms of Service)',
style: const TextStyle( style: const TextStyle(color: Colors.black),
color: Colors.black),
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () { ..onTap = () {
loginBloc.launchURL( loginBloc.launchURL('https://example.com/terms');
'https://example.com/terms');
}, },
), ),
TextSpan( TextSpan(
text: ' (Legal Statement)', text: ' (Legal Statement)',
style: const TextStyle( style: const TextStyle(color: Colors.black),
color: Colors.black),
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () { ..onTap = () {
loginBloc.launchURL( loginBloc.launchURL('https://example.com/legal');
'https://example.com/legal');
}, },
), ),
TextSpan( TextSpan(
text: ' (Privacy Statement)', text: ' (Privacy Statement)',
style: const TextStyle( style: const TextStyle(color: Colors.black),
color: Colors.black),
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () { ..onTap = () {
loginBloc.launchURL( loginBloc.launchURL('https://example.com/privacy');
'https://example.com/privacy');
}, },
), ),
], ],
@ -297,15 +273,12 @@ class LoginMobilePage extends StatelessWidget {
: ColorsManager.grayColor, : ColorsManager.grayColor,
child: const Text('Sign in'), child: const Text('Sign in'),
onPressed: () { onPressed: () {
if (loginBloc.loginFormKey.currentState! if (loginBloc.loginFormKey.currentState!.validate()) {
.validate()) {
loginBloc.add( loginBloc.add(
LoginButtonPressed( LoginButtonPressed(
regionUuid: '', regionUuid: '',
username: username: loginBloc.loginEmailController.text,
loginBloc.loginEmailController.text, password: loginBloc.loginPasswordController.text,
password: loginBloc
.loginPasswordController.text,
), ),
); );
} }
@ -320,8 +293,7 @@ class LoginMobilePage extends StatelessWidget {
Flexible( Flexible(
child: Text( child: Text(
"Don't you have an account? ", "Don't you have an account? ",
style: TextStyle( style: TextStyle(color: Colors.white, fontSize: 13),
color: Colors.white, fontSize: 13),
)), )),
Text( Text(
"Sign up", "Sign up",

View File

@ -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';
@ -13,6 +14,7 @@ import 'package:syncrow_web/pages/common/first_layer.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart'; import 'package:syncrow_web/utils/constants/routes_const.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
class LoginWebPage extends StatefulWidget { class LoginWebPage extends StatefulWidget {
@ -22,7 +24,8 @@ class LoginWebPage extends StatefulWidget {
State<LoginWebPage> createState() => _LoginWebPageState(); State<LoginWebPage> createState() => _LoginWebPageState();
} }
class _LoginWebPageState extends State<LoginWebPage> { class _LoginWebPageState extends State<LoginWebPage>
with HelperResponsiveLayout {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -31,7 +34,7 @@ class _LoginWebPageState extends State<LoginWebPage> {
child: BlocConsumer<AuthBloc, AuthState>( child: BlocConsumer<AuthBloc, AuthState>(
listener: (context, state) { listener: (context, state) {
if (state is LoginSuccess) { if (state is LoginSuccess) {
context.go(RoutesConst.home); GoRouter.of(context).go(RoutesConst.home);
} else if (state is LoginFailure) { } else if (state is LoginFailure) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
@ -50,11 +53,15 @@ class _LoginWebPageState extends State<LoginWebPage> {
Widget _buildLoginForm(BuildContext context, AuthState state) { Widget _buildLoginForm(BuildContext context, AuthState state) {
final loginBloc = BlocProvider.of<AuthBloc>(context); final loginBloc = BlocProvider.of<AuthBloc>(context);
final isSmallScreen = isSmallScreenSize(context);
final isMediumScreen = isMediumScreenSize(context);
Size size = MediaQuery.of(context).size; Size size = MediaQuery.of(context).size;
late ScrollController _scrollController; late ScrollController _scrollController;
_scrollController = ScrollController(); _scrollController = ScrollController();
void _scrollToCenter() { void _scrollToCenter() {
final 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),
@ -65,6 +72,7 @@ class _LoginWebPageState extends State<LoginWebPage> {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToCenter(); _scrollToCenter();
}); });
return Stack( return Stack(
children: [ children: [
FirstLayer( FirstLayer(
@ -74,363 +82,419 @@ class _LoginWebPageState extends State<LoginWebPage> {
shrinkWrap: true, shrinkWrap: true,
children: [ children: [
Container( Container(
width: 400,
padding: EdgeInsets.all(size.width * 0.02), padding: EdgeInsets.all(size.width * 0.02),
margin: EdgeInsets.all(size.width * 0.09), margin: EdgeInsets.all(size.width * 0.05),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black.withOpacity(0.3), color: Colors.black.withOpacity(0.3),
borderRadius: const BorderRadius.all(Radius.circular(20)), borderRadius: const BorderRadius.all(Radius.circular(20)),
), ),
child: Center( child: Center(
child: Row( child: isSmallScreen || isMediumScreen
crossAxisAlignment: CrossAxisAlignment.center, ? SizedBox(
mainAxisAlignment: MainAxisAlignment.center, width: 400,
children: [ child: Column(
const Spacer(), // For small screens
Expanded( crossAxisAlignment: CrossAxisAlignment.center,
flex: 3, children: [
child: SvgPicture.asset( SizedBox(
Assets.loginLogo, width: 300,
), child: SvgPicture.asset(
), Assets.loginLogo,
const Spacer(),
Expanded(
flex: 3,
child: Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius: const BorderRadius.all(Radius.circular(30)),
border: Border.all(
color: ColorsManager.graysColor.withOpacity(0.2))),
child: Form(
key: loginBloc.loginFormKey,
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: size.width * 0.02,
vertical: size.width * 0.003),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(height: 40),
Text('Login',
style: Theme.of(context).textTheme.headlineLarge),
SizedBox(height: size.height * 0.03),
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,
),
SizedBox(
child: DropdownButtonFormField<String>(
padding: EdgeInsets.zero,
value: loginBloc.regionList!.any((region) =>
region.id == loginBloc.regionUuid)
? loginBloc.regionUuid
: null,
validator: loginBloc.validateRegion,
icon: const Icon(
Icons.keyboard_arrow_down_outlined,
),
decoration: textBoxDecoration()!.copyWith(
errorStyle: const TextStyle(height: 0),
hintText: null,
),
hint: SizedBox(
width: size.width * 0.12,
child: Align(
alignment: Alignment.centerLeft,
child: Text(
'Select your region/country',
textAlign: TextAlign.center,
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.w400),
overflow: TextOverflow.ellipsis,
),
),
),
isDense: true,
style: const TextStyle(color: Colors.black),
items:
loginBloc.regionList!.map((RegionModel region) {
return DropdownMenuItem<String>(
value: region.id,
child: SizedBox(
width: size.width * 0.08,
child: Text(region.name)),
);
}).toList(),
onChanged: (String? value) {
loginBloc.add(CheckEnableEvent());
loginBloc.add(SelectRegionEvent(val: value!));
},
),
)
],
),
const SizedBox(height: 20.0),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Email",
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
fontSize: 14, fontWeight: FontWeight.w400),
),
const SizedBox(
height: 10,
),
SizedBox(
child: TextFormField(
onChanged: (value) {
loginBloc.add(CheckEnableEvent());
// print(loginBloc.checkEnable());
},
validator: loginBloc.loginValidateEmail,
controller: loginBloc.loginEmailController,
decoration: textBoxDecoration()!.copyWith(
errorStyle: const TextStyle(
height: 0), // Hide the error text space
hintText: 'Enter your email address',
hintStyle: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.w400)),
style: const TextStyle(color: Colors.black),
),
),
],
),
const SizedBox(height: 20.0),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Password",
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
fontSize: 14, fontWeight: FontWeight.w400),
),
const SizedBox(
height: 10,
),
SizedBox(
child: TextFormField(
onChanged: (value) {
loginBloc.add(CheckEnableEvent());
},
validator: loginBloc.validatePassword,
obscureText: loginBloc.obscureText,
keyboardType: TextInputType.visiblePassword,
controller: loginBloc.loginPasswordController,
decoration: textBoxDecoration()!.copyWith(
hintText: 'At least 8 characters',
hintStyle: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.w400),
suffixIcon: IconButton(
onPressed: () {
loginBloc.add(PasswordVisibleEvent(
newValue: loginBloc.obscureText));
},
icon: SizedBox(
child: SvgPicture.asset(
loginBloc.obscureText
? Assets.visiblePassword
: Assets.invisiblePassword,
height: 15,
width: 15,
),
),
),
errorStyle: const TextStyle(
height: 0), // Hide the error text space
),
style: const TextStyle(color: Colors.black),
),
),
],
),
const SizedBox(
height: 20,
),
SizedBox(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
InkWell(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) =>
const ForgetPasswordPage(),
));
},
child: Text(
"Forgot Password?",
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.w400),
),
),
],
),
),
const SizedBox(
height: 20,
),
Row(
children: [
Transform.scale(
scale: 1.2, // Adjust the scale as needed
child: Checkbox(
fillColor:
MaterialStateProperty.all<Color>(Colors.white),
activeColor: Colors.white,
value: loginBloc.isChecked,
checkColor: Colors.black,
shape: const CircleBorder(),
onChanged: (bool? newValue) {
loginBloc.add(CheckBoxEvent(newValue: newValue));
},
),
),
SizedBox(
width: size.width * 0.14,
child: RichText(
text: TextSpan(
text: 'Agree to ',
style: const TextStyle(color: Colors.white),
children: [
TextSpan(
text: '(Terms of Service)',
style: const TextStyle(
color: Colors.black,
),
recognizer: TapGestureRecognizer()
..onTap = () {
loginBloc.launchURL(
'https://example.com/terms');
},
),
TextSpan(
text: ' (Legal Statement)',
style: const TextStyle(color: Colors.black),
recognizer: TapGestureRecognizer()
..onTap = () {
loginBloc.launchURL(
'https://example.com/legal');
},
),
TextSpan(
text: ' (Privacy Statement)',
style: const TextStyle(color: Colors.black),
recognizer: TapGestureRecognizer()
..onTap = () {
loginBloc.launchURL(
'https://example.com/privacy');
},
),
],
),
),
),
],
),
const SizedBox(height: 20.0),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: size.width * 0.2,
child: DefaultButton(
enabled: loginBloc.checkValidate,
child: Text('Sign in',
style: Theme.of(context)
.textTheme
.labelLarge!
.copyWith(
fontSize: 14,
color: loginBloc.checkValidate
? ColorsManager.whiteColors
: ColorsManager.whiteColors
.withOpacity(0.2),
)),
onPressed: () {
if (loginBloc.loginFormKey.currentState!
.validate()) {
loginBloc.add(LoginButtonPressed(
regionUuid: loginBloc.regionUuid,
username: loginBloc.loginEmailController.text,
password:
loginBloc.loginPasswordController.text,
));
} else {
loginBloc.add(ChangeValidateEvent());
}
},
),
),
],
),
const SizedBox(height: 15.0),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
child: Text(
loginBloc.validate,
style: const TextStyle(
fontWeight: FontWeight.w700,
color: ColorsManager.red),
),
)
],
)
],
),
), ),
))), ),
const Spacer(), const SizedBox(height: 20),
], _buildLoginFormFields(context, loginBloc, size),
), ],
),
)
: Row(
// For larger screens
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Spacer(),
Expanded(
flex: 2,
child: SvgPicture.asset(
Assets.loginLogo,
),
),
const Spacer(),
Expanded(
flex: 2,
child: _buildLoginFormFields(
context, loginBloc, size),
),
const Spacer(),
],
),
), ),
), ),
], ],
), ),
), ),
), ),
if (state is AuthLoading) const Center(child: CircularProgressIndicator()) if (state is AuthLoading)
const Center(child: CircularProgressIndicator())
],
);
}
Widget _buildLoginFormFields(
BuildContext context, AuthBloc loginBloc, Size size) {
return Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius: const BorderRadius.all(Radius.circular(30)),
border: Border.all(color: ColorsManager.graysColor.withOpacity(0.2)),
),
child: Form(
key: loginBloc.loginFormKey,
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: size.width * 0.02, vertical: size.width * 0.003),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(height: 40),
Text('Login', style: Theme.of(context).textTheme.headlineLarge),
SizedBox(height: size.height * 0.03),
_buildDropdownField(context, loginBloc, size),
const SizedBox(height: 20.0),
_buildEmailField(context, loginBloc),
const SizedBox(height: 20.0),
_buildPasswordField(context, loginBloc),
const SizedBox(height: 20),
_buildForgotPassword(context),
const SizedBox(height: 20),
_buildCheckbox(context, loginBloc, size),
const SizedBox(height: 20.0),
_buildSignInButton(context, loginBloc, size),
const SizedBox(height: 15.0),
_buildValidationMessage(loginBloc),
],
),
),
),
);
}
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(CheckEnableEvent());
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();
}
},
),
),
),
],
);
}
Widget _buildEmailField(BuildContext context, AuthBloc loginBloc) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Email",
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(fontSize: 14, fontWeight: FontWeight.w400),
),
const SizedBox(height: 10),
SizedBox(
child: TextFormField(
onChanged: (value) {
loginBloc.add(CheckEnableEvent());
},
validator: loginBloc.loginValidateEmail,
controller: loginBloc.loginEmailController,
decoration: textBoxDecoration()!.copyWith(
errorStyle: const TextStyle(height: 0),
hintText: 'Enter your email address',
hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.w400)),
style: const TextStyle(color: Colors.black),
),
),
],
);
}
Widget _buildPasswordField(BuildContext context, AuthBloc loginBloc) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Password",
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(fontSize: 14, fontWeight: FontWeight.w400),
),
const SizedBox(height: 10),
SizedBox(
child: TextFormField(
onChanged: (value) {
loginBloc.add(CheckEnableEvent());
},
validator: loginBloc.validatePassword,
obscureText: loginBloc.obscureText,
keyboardType: TextInputType.visiblePassword,
controller: loginBloc.loginPasswordController,
decoration: textBoxDecoration()!.copyWith(
hintText: 'At least 8 characters',
hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor, fontWeight: FontWeight.w400),
suffixIcon: IconButton(
onPressed: () {
loginBloc.add(
PasswordVisibleEvent(newValue: loginBloc.obscureText));
},
icon: SizedBox(
child: SvgPicture.asset(
loginBloc.obscureText
? Assets.visiblePassword
: Assets.invisiblePassword,
height: 15,
width: 15,
),
),
),
errorStyle: const TextStyle(height: 0),
),
style: const TextStyle(color: Colors.black),
),
),
],
);
}
Widget _buildForgotPassword(BuildContext context) {
return SizedBox(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
InkWell(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const ForgetPasswordPage(),
));
},
child: Text(
"Forgot Password?",
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.w400),
),
),
],
),
);
}
Widget _buildCheckbox(BuildContext context, AuthBloc loginBloc, Size size) {
return Row(
children: [
Transform.scale(
scale: 1.2,
child: Checkbox(
fillColor: MaterialStateProperty.all<Color>(Colors.white),
activeColor: Colors.white,
value: loginBloc.isChecked,
checkColor: Colors.black,
shape: const CircleBorder(),
onChanged: (bool? newValue) {
loginBloc.add(CheckBoxEvent(newValue: newValue));
},
),
),
SizedBox(
width: 220,
child: RichText(
text: TextSpan(
text: 'Agree to ',
style: const TextStyle(color: Colors.white),
children: [
TextSpan(
text: '(Terms of Service)',
style: const TextStyle(color: Colors.black),
recognizer: TapGestureRecognizer()
..onTap = () {
loginBloc.launchURL('https://example.com/terms');
},
),
TextSpan(
text: ' (Legal Statement)',
style: const TextStyle(color: Colors.black),
recognizer: TapGestureRecognizer()
..onTap = () {
loginBloc.launchURL('https://example.com/legal');
},
),
TextSpan(
text: ' (Privacy Statement)',
style: const TextStyle(color: Colors.black),
recognizer: TapGestureRecognizer()
..onTap = () {
loginBloc.launchURL('https://example.com/privacy');
},
),
],
),
),
),
],
);
}
Widget _buildSignInButton(
BuildContext context, AuthBloc loginBloc, Size size) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: size.width * 0.2,
child: DefaultButton(
enabled: loginBloc.checkValidate,
child: Text('Sign in',
style: Theme.of(context).textTheme.labelLarge!.copyWith(
fontSize: 14,
color: loginBloc.checkValidate
? ColorsManager.whiteColors
: ColorsManager.whiteColors.withOpacity(0.2),
)),
onPressed: () {
if (loginBloc.loginFormKey.currentState!.validate()) {
loginBloc.add(LoginButtonPressed(
regionUuid: loginBloc.regionUuid,
username: loginBloc.loginEmailController.text,
password: loginBloc.loginPasswordController.text,
));
} else {
loginBloc.add(ChangeValidateEvent());
}
},
),
),
],
);
}
Widget _buildValidationMessage(AuthBloc loginBloc) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
child: Text(
loginBloc.validate,
style: const TextStyle(
fontWeight: FontWeight.w700, color: ColorsManager.red),
),
)
], ],
); );
} }

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
@ -23,14 +24,18 @@ class SearchResetButtons extends StatelessWidget {
const SizedBox(height: 25), const SizedBox(height: 25),
Center( Center(
child: Container( child: Container(
height: 43, height: 42,
width: 100, width: 100,
decoration: containerDecoration, decoration: containerDecoration,
child: Center( child: Center(
child: DefaultButton( child: DefaultButton(
onPressed: onSearch, onPressed: onSearch,
borderRadius: 9, borderRadius: 9,
child: const Text('Search'), child: Text(
'Search',
style: context.textTheme.titleSmall!
.copyWith(color: Colors.white, fontSize: 12),
),
), ),
), ),
), ),
@ -44,21 +49,19 @@ class SearchResetButtons extends StatelessWidget {
const SizedBox(height: 25), const SizedBox(height: 25),
Center( Center(
child: Container( child: Container(
height: 43, height: 42,
width: 100, width: 100,
decoration: containerDecoration, decoration: containerDecoration,
child: Center( child: Center(
child: DefaultButton( child: DefaultButton(
backgroundColor: ColorsManager.whiteColors, backgroundColor: ColorsManager.whiteColors,
borderRadius: 9, borderRadius: 9,
onPressed: onReset,
child: Text( child: Text(
'Reset', 'Reset',
style: Theme.of(context) style: context.textTheme.titleSmall!
.textTheme .copyWith(color: Colors.black, fontSize: 12),
.bodySmall!
.copyWith(color: Colors.black),
), ),
onPressed: onReset,
), ),
), ),
), ),

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(
@ -65,11 +98,13 @@ class _DynamicTableState extends State<DynamicTable> {
child: Column( child: Column(
children: [ children: [
Container( Container(
decoration: widget.headerDecoration ?? BoxDecoration(color: Colors.grey[200]), decoration: widget.headerDecoration ??
BoxDecoration(color: Colors.grey[200]),
child: Row( child: Row(
children: [ children: [
if (widget.withCheckBox) _buildSelectAllCheckbox(), if (widget.withCheckBox) _buildSelectAllCheckbox(),
...widget.headers.map((header) => _buildTableHeaderCell(header)).toList(), ...widget.headers
.map((header) => _buildTableHeaderCell(header)),
], ],
), ),
), ),
@ -89,11 +124,13 @@ 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!
.copyWith(color: ColorsManager.grayColor), .copyWith(
color: ColorsManager.grayColor),
) )
], ],
), ),
@ -113,11 +150,11 @@ class _DynamicTableState extends State<DynamicTable> {
return Row( return Row(
children: [ children: [
if (widget.withCheckBox) if (widget.withCheckBox)
_buildRowCheckbox(index, widget.size.height * 0.10), _buildRowCheckbox(
...row index, widget.size.height * 0.10),
.map((cell) => ...row.map((cell) => _buildTableCell(
_buildTableCell(cell.toString(), widget.size.height * 0.10)) cell.toString(),
.toList(), widget.size.height * 0.10)),
], ],
); );
}, },
@ -142,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,
), ),
); );
} }
@ -190,6 +227,7 @@ class _DynamicTableState extends State<DynamicTable> {
fontSize: 13, fontSize: 13,
color: Color(0xFF999999), color: Color(0xFF999999),
), ),
maxLines: 2,
), ),
), ),
), ),
@ -222,7 +260,7 @@ class _DynamicTableState extends State<DynamicTable> {
statusColor = ColorsManager.red; statusColor = ColorsManager.red;
break; break;
default: default:
statusColor = Colors.black; // Default color statusColor = Colors.black;
} }
return Expanded( return Expanded(
@ -241,11 +279,14 @@ class _DynamicTableState extends State<DynamicTable> {
child: Text( child: Text(
content, content,
style: TextStyle( style: TextStyle(
color: batteryLevel != null && batteryLevel < 20 color: (batteryLevel != null && batteryLevel < 20)
? ColorsManager.red ? ColorsManager.red
: statusColor, // Use the passed color or default to black : (batteryLevel != null && batteryLevel > 20)
? ColorsManager.green
: statusColor,
fontSize: 10, fontSize: 10,
fontWeight: FontWeight.w400), fontWeight: FontWeight.w400),
maxLines: 2,
), ),
), ),
); );

View File

@ -36,7 +36,10 @@ class DateTimeWebWidget extends StatelessWidget {
if (isRequired) if (isRequired)
Text( Text(
'* ', '* ',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: Colors.red), style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: Colors.red),
), ),
Text( Text(
title, title,
@ -51,8 +54,9 @@ class DateTimeWebWidget extends StatelessWidget {
height: 8, height: 8,
), ),
Container( Container(
height: size.height * 0.055, // height: size.height * 0.056,
padding: const EdgeInsets.only(top: 10, bottom: 10, right: 30, left: 10), padding:
const EdgeInsets.only(top: 10, bottom: 10, right: 30, left: 10),
decoration: containerDecoration, decoration: containerDecoration,
child: FittedBox( child: FittedBox(
child: Column( child: Column(
@ -65,10 +69,13 @@ class DateTimeWebWidget extends StatelessWidget {
child: FittedBox( child: FittedBox(
child: Text( child: Text(
firstString, firstString,
style: Theme.of(context).textTheme.bodySmall!.copyWith( style: Theme.of(context)
color: ColorsManager.grayColor, .textTheme
fontSize: 12, .bodySmall!
fontWeight: FontWeight.w400), .copyWith(
color: ColorsManager.grayColor,
fontSize: 12,
fontWeight: FontWeight.w400),
), ),
)), )),
const SizedBox( const SizedBox(
@ -83,10 +90,13 @@ class DateTimeWebWidget extends StatelessWidget {
child: FittedBox( child: FittedBox(
child: Text( child: Text(
secondString, secondString,
style: Theme.of(context).textTheme.bodySmall!.copyWith( style: Theme.of(context)
color: ColorsManager.grayColor, .textTheme
fontSize: 12, .bodySmall!
fontWeight: FontWeight.w400), .copyWith(
color: ColorsManager.grayColor,
fontSize: 12,
fontWeight: FontWeight.w400),
), ),
)), )),
const SizedBox( const SizedBox(

View File

@ -20,7 +20,7 @@ class FilterWidget extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
decoration: containerDecoration, decoration: containerDecoration,
height: size.height * 0.05, height: 40,
child: ListView.builder( child: ListView.builder(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemCount: tabs.length, itemCount: tabs.length,

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/core/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
class StatefulTextField extends StatefulWidget { class StatefulTextField extends StatefulWidget {
const StatefulTextField({ const StatefulTextField({
@ -8,7 +8,7 @@ class StatefulTextField extends StatefulWidget {
this.hintText = 'Please enter', this.hintText = 'Please enter',
required this.width, required this.width,
this.elevation = 0, this.elevation = 0,
required this.controller, // Add the controller required this.controller,
}); });
final String title; final String title;
@ -59,6 +59,7 @@ class CustomTextField extends StatelessWidget {
Text( Text(
title, title,
style: context.textTheme.bodyMedium!.copyWith( style: context.textTheme.bodyMedium!.copyWith(
fontSize: 13,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: const Color(0xff000000), color: const Color(0xff000000),
), ),
@ -69,6 +70,7 @@ class CustomTextField extends StatelessWidget {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
child: Container( child: Container(
width: width, width: width,
height: 45,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
@ -78,6 +80,7 @@ class CustomTextField extends StatelessWidget {
style: const TextStyle(color: Colors.black), style: const TextStyle(color: Colors.black),
decoration: InputDecoration( decoration: InputDecoration(
hintText: hintText, hintText: hintText,
hintStyle: const TextStyle(fontSize: 12),
contentPadding: contentPadding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 10), const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
border: InputBorder.none, border: InputBorder.none,

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';
class CustomWebTextField extends StatelessWidget { class CustomWebTextField extends StatelessWidget {
@ -11,6 +12,7 @@ class CustomWebTextField extends StatelessWidget {
this.description, this.description,
this.validator, this.validator,
this.hintText, this.hintText,
this.height,
}); });
final bool isRequired; final bool isRequired;
@ -19,6 +21,7 @@ class CustomWebTextField extends StatelessWidget {
final TextEditingController? controller; final TextEditingController? controller;
final String? Function(String?)? validator; final String? Function(String?)? validator;
final String? hintText; final String? hintText;
final double? height;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -29,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),
), ),
], ],
@ -66,23 +67,26 @@ class CustomWebTextField extends StatelessWidget {
height: 7, height: 7,
), ),
Container( Container(
decoration: containerDecoration height: height ?? 35,
.copyWith(color: const Color(0xFFF5F6F7), boxShadow: [ decoration: containerDecoration.copyWith(
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,
style: const TextStyle(color: Colors.black), style: const TextStyle(color: Colors.black),
decoration: textBoxDecoration()!.copyWith( decoration: textBoxDecoration()!.copyWith(
errorStyle: errorStyle: const TextStyle(height: 0),
const TextStyle(height: 0), // Hide the error text space hintStyle: context.textTheme.titleSmall!
.copyWith(color: Colors.grey, fontSize: 12),
hintText: hintText ?? 'Please enter'), hintText: hintText ?? 'Please enter'),
), ),
), ),

View File

@ -18,6 +18,7 @@ class AcDeviceControl extends StatelessWidget with HelperResponsiveLayout {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context); final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context); final isMedium = isMediumScreenSize(context);
return BlocProvider( return BlocProvider(
@ -31,7 +32,7 @@ class AcDeviceControl extends StatelessWidget with HelperResponsiveLayout {
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isLarge crossAxisCount: isLarge || isExtraLarge
? 3 ? 3
: isMedium : isMedium
? 2 ? 2
@ -74,7 +75,7 @@ class AcDeviceControl extends StatelessWidget with HelperResponsiveLayout {
} else if (state is AcsLoadingState) { } else if (state is AcsLoadingState) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} else { } else {
return const Center(child: Text('Error fetching status')); return const Center(child: Text('Error fetching status'));
} }
}, },
), ),

View File

@ -116,16 +116,20 @@ class _CurrentTempState extends State<CurrentTemp> {
description: '°C', description: '°C',
descriptionColor: ColorsManager.dialogBlueTitle, descriptionColor: ColorsManager.dialogBlueTitle,
onIncrement: () { onIncrement: () {
setState(() { if (_adjustedValue < 30) {
_adjustedValue++; setState(() {
}); _adjustedValue++;
_onValueChanged(_adjustedValue); });
_onValueChanged(_adjustedValue);
}
}, },
onDecrement: () { onDecrement: () {
setState(() { if (_adjustedValue > 20) {
_adjustedValue--; setState(() {
}); _adjustedValue--;
_onValueChanged(_adjustedValue); });
_onValueChanged(_adjustedValue);
}
}), }),
], ],
), ),

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,13 +159,13 @@ class DeviceManagementBloc
false); false);
return matchesCommunity && matchesUnit && matchesProductName; return matchesCommunity && matchesUnit && matchesProductName;
}).toList(); }).toList();
emit(DeviceManagementFiltered( emit(DeviceManagementFiltered(
filteredDevices: filteredDevices, filteredDevices: filteredDevices,
selectedIndex: _selectedIndex, selectedIndex: 0,
onlineCount: _onlineCount, onlineCount: _onlineCount,
offlineCount: _offlineCount, offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount, lowBatteryCount: _lowBatteryCount,
selectedDevice: null,
)); ));
} }
} }

View File

@ -5,7 +5,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart';
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_status_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_status_view.dart';
import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dart'; import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dart';
import 'package:syncrow_web/pages/device_managment/living_room_switch/view/living_room_device_control.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_device_control.dart';
import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart';
mixin RouteControlsBasedCode { mixin RouteControlsBasedCode {

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart'; import 'package:syncrow_web/web_layout/web_scaffold.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';
@ -13,10 +14,13 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
return BlocProvider( return BlocProvider(
create: (context) => DeviceManagementBloc()..add(FetchDevices()), create: (context) => DeviceManagementBloc()..add(FetchDevices()),
child: WebScaffold( child: WebScaffold(
appBarTitle: Text( appBarTitle: FittedBox(
'Device Management', child: Text(
style: Theme.of(context).textTheme.headlineLarge, 'Device Management',
style: Theme.of(context).textTheme.headlineLarge,
),
), ),
rightBody: const NavigateHomeGridView(),
enableMenuSideba: isLargeScreenSize(context), enableMenuSideba: isLargeScreenSize(context),
scaffoldBody: BlocBuilder<DeviceManagementBloc, DeviceManagementState>( scaffoldBody: BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
builder: (context, state) { builder: (context, state) {
@ -30,7 +34,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
return DeviceManagementBody(devices: devices); return DeviceManagementBody(devices: devices);
} else { } else {
return const Center(child: Text('No Devices Found')); return const Center(child: Text('Error fetching Devices'));
} }
}, },
), ),

View File

@ -1,6 +1,6 @@
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:syncrow_web/core/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/common/custom_table.dart'; import 'package:syncrow_web/pages/common/custom_table.dart';
import 'package:syncrow_web/pages/common/filter/filter_widget.dart'; import 'package:syncrow_web/pages/common/filter/filter_widget.dart';
@ -42,79 +42,81 @@ 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 = [
'All (${devices.length})', 'All',
'Online ($onlineCount)', 'Online ($onlineCount)',
'Offline ($offlineCount)', 'Offline ($offlineCount)',
'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: 43, decoration: containerDecoration,
width: isSmallScreenSize(context) ? double.infinity : 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
color: isControlButtonEnabled : Colors.grey,
? 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];
@ -124,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',
@ -144,7 +147,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
device.room?.name ?? '', device.room?.name ?? '',
device.batteryLevel != null device.batteryLevel != null
? '${device.batteryLevel}%' ? '${device.batteryLevel}%'
: '', : '-',
formatDateTime(DateTime.fromMillisecondsSinceEpoch( formatDateTime(DateTime.fromMillisecondsSinceEpoch(
(device.createTime ?? 0) * 1000)), (device.createTime ?? 0) * 1000)),
device.online == true ? 'Online' : 'Offline', device.online == true ? 'Online' : 'Offline',
@ -152,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

@ -28,7 +28,7 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return isLargeScreenSize(context) return isExtraLargeScreenSize(context)
? Row( ? Row(
children: [ children: [
_buildSearchField("Community", communityController, 200), _buildSearchField("Community", communityController, 200),

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,6 +10,7 @@ class CeilingSensorModel {
String bodyMovement; String bodyMovement;
String noBodyTime; String noBodyTime;
int maxDistance; int maxDistance;
SpaceTypes spaceType;
CeilingSensorModel({ CeilingSensorModel({
required this.presenceState, required this.presenceState,
@ -20,6 +21,7 @@ class CeilingSensorModel {
required this.bodyMovement, required this.bodyMovement,
required this.noBodyTime, required this.noBodyTime,
required this.maxDistance, required this.maxDistance,
required this.spaceType,
}); });
factory CeilingSensorModel.fromJson(List<Status> jsonList) { factory CeilingSensorModel.fromJson(List<Status> jsonList) {
@ -31,6 +33,7 @@ class CeilingSensorModel {
String _bodyMovement = 'none'; String _bodyMovement = 'none';
String _noBodyTime = 'none'; String _noBodyTime = 'none';
int _maxDis = 0; int _maxDis = 0;
SpaceTypes _spaceType = SpaceTypes.none;
try { try {
for (var status in jsonList) { for (var status in jsonList) {
@ -38,17 +41,26 @@ class CeilingSensorModel {
case 'presence_state': case 'presence_state':
_presenceState = status.value ?? 'none'; _presenceState = status.value ?? 'none';
break; break;
case 'scene':
_spaceType = getSpaceType(status.value ?? 'none');
break;
case 'sensitivity': case 'sensitivity':
_sensitivity = status.value is int ? status.value : int.tryParse(status.value ?? '1') ?? 1; _sensitivity = status.value is int
? status.value
: int.tryParse(status.value ?? '1') ?? 1;
break; break;
case 'checking_result': case 'checking_result':
_checkingResult = status.value ?? ''; _checkingResult = status.value ?? '';
break; break;
case 'presence_range': case 'presence_range':
_presenceRange = status.value is int ? status.value : int.tryParse(status.value ?? '0') ?? 0; _presenceRange = status.value is int
? status.value
: int.tryParse(status.value ?? '0') ?? 0;
break; break;
case 'sports_para': case 'sports_para':
_sportsPara = status.value is int ? status.value : int.tryParse(status.value ?? '0') ?? 0; _sportsPara = status.value is int
? status.value
: int.tryParse(status.value ?? '0') ?? 0;
break; break;
case 'body_movement': case 'body_movement':
_bodyMovement = status.value ?? ''; _bodyMovement = status.value ?? '';
@ -57,7 +69,9 @@ class CeilingSensorModel {
_noBodyTime = status.value ?? 'none'; _noBodyTime = status.value ?? 'none';
break; break;
case 'moving_max_dis': case 'moving_max_dis':
_maxDis = status.value is int ? status.value : int.tryParse(status.value ?? '0') ?? 0; _maxDis = status.value is int
? status.value
: int.tryParse(status.value ?? '0') ?? 0;
break; break;
} }
} }
@ -74,6 +88,31 @@ class CeilingSensorModel {
bodyMovement: _bodyMovement, bodyMovement: _bodyMovement,
noBodyTime: _noBodyTime, noBodyTime: _noBodyTime,
maxDistance: _maxDis, maxDistance: _maxDis,
spaceType: _spaceType,
); );
} }
} }
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

@ -24,6 +24,7 @@ class CeilingSensorControls extends StatelessWidget
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context); final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context); final isMedium = isMediumScreenSize(context);
return BlocProvider( return BlocProvider(
@ -35,8 +36,8 @@ class CeilingSensorControls extends StatelessWidget
state is CeilingReportsLoadingState) { state is CeilingReportsLoadingState) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} else if (state is CeilingUpdateState) { } else if (state is CeilingUpdateState) {
return _buildGridView( return _buildGridView(context, state.ceilingSensorModel,
context, state.ceilingSensorModel, isLarge, isMedium); isExtraLarge, isLarge, isMedium);
} else if (state is CeilingReportsState) { } else if (state is CeilingReportsState) {
return ReportsTable( return ReportsTable(
report: state.deviceReport, report: state.deviceReport,
@ -58,7 +59,8 @@ class CeilingSensorControls extends StatelessWidget
); );
} else if (state is CeilingReportsFailedState) { } else if (state is CeilingReportsFailedState) {
final model = context.read<CeilingSensorBloc>().deviceStatus; final model = context.read<CeilingSensorBloc>().deviceStatus;
return _buildGridView(context, model, 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'));
}, },
@ -67,13 +69,13 @@ class CeilingSensorControls extends StatelessWidget
} }
Widget _buildGridView(BuildContext context, CeilingSensorModel model, Widget _buildGridView(BuildContext context, CeilingSensorModel model,
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,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isLarge crossAxisCount: isLarge || isExtraLarge
? 3 ? 3
: isMedium : isMedium
? 2 ? 2
@ -96,15 +98,15 @@ class CeilingSensorControls extends StatelessWidget
postfix: 'm', postfix: 'm',
description: 'Detection Range', description: 'Detection Range',
), ),
const PresenceSpaceType( PresenceSpaceType(
listOfIcons: [
Assets.office,
Assets.parlour,
Assets.dyi,
Assets.bathroom,
Assets.bedroom,
],
description: 'Space Type', description: 'Space Type',
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(),
@ -138,7 +140,7 @@ class CeilingSensorControls extends StatelessWidget
PresenceNoBodyTime( PresenceNoBodyTime(
value: model.noBodyTime, value: model.noBodyTime,
title: 'Nobody Time:', title: 'Nobody Time:',
// description: 'hr', description: '',
action: (String value) => context.read<CeilingSensorBloc>().add( action: (String value) => context.read<CeilingSensorBloc>().add(
CeilingChangeValueEvent( CeilingChangeValueEvent(
code: 'nobody_time', code: 'nobody_time',

View File

@ -34,9 +34,9 @@ class DoorLockView extends StatelessWidget {
} else if (state is UpdateState) { } else if (state is UpdateState) {
return _buildStatusControls(context, state.smartDoorModel); return _buildStatusControls(context, state.smartDoorModel);
} else if (state is DoorLockControlError) { } else if (state is DoorLockControlError) {
return Center(child: Text(state.message)); return const SizedBox();
} else { } else {
return const Center(child: CircularProgressIndicator()); return const Center(child: Text('Error fetching status'));
} }
}, },
), ),

View File

@ -14,6 +14,7 @@ class GateWayControls extends StatelessWidget with HelperResponsiveLayout {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context); final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context); final isMedium = isMediumScreenSize(context);
@ -29,7 +30,7 @@ class GateWayControls extends StatelessWidget with HelperResponsiveLayout {
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isLarge crossAxisCount: isLarge || isExtraLarge
? 3 ? 3
: isMedium : isMedium
? 2 ? 2
@ -45,7 +46,7 @@ class GateWayControls extends StatelessWidget with HelperResponsiveLayout {
}, },
); );
} else { } else {
return const Center(child: Text('Error fetching devices')); return const Center(child: Text('Error fetching status'));
} }
}, },
), ),

View File

@ -1,7 +1,7 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/core/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/helper/route_controls_based_code.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/helper/route_controls_based_code.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';

View File

@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart';
class NavigateHomeGridView extends StatelessWidget {
const NavigateHomeGridView({
super.key,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
InkWell(
onTap: () {
context.go(RoutesConst.home);
},
child: SvgPicture.asset(
height: 20,
width: 20,
Assets.grid,
),
),
const SizedBox(
width: 10,
)
],
);
}
}

View File

@ -1,21 +1,33 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/core/extension/build_context_x.dart'; import 'package:syncrow_web/utils/constants/assets.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.action,
}); });
final List<String> listOfIcons;
final String description; final String description;
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,
@ -35,13 +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) => SvgPicture.asset( final icon = entry.value;
final spaceType = entry.key;
return GestureDetector(
onTap: () => action(spaceType.name),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
border: Border.all(
color: value == spaceType
? ColorsManager.blueColor
: Colors.transparent,
),
),
child: SvgPicture.asset(
icon, icon,
width: 40, width: 40,
height: 40, height: 40,
)), ),
], ),
);
}).toList(),
), ),
], ],
), ),

View File

@ -23,6 +23,8 @@ class PresenceNoBodyTime extends StatefulWidget {
class _PresenceUpdateDataState extends State<PresenceNoBodyTime> { class _PresenceUpdateDataState extends State<PresenceNoBodyTime> {
late String _currentValue; late String _currentValue;
late String _numericValue;
late String _unit;
final List<String> nobodyTimeRange = [ final List<String> nobodyTimeRange = [
'none', 'none',
@ -40,29 +42,45 @@ class _PresenceUpdateDataState extends State<PresenceNoBodyTime> {
void initState() { void initState() {
super.initState(); super.initState();
_currentValue = widget.value; _currentValue = widget.value;
_numericValue = _extractNumericValue(_currentValue);
_unit = _extractUnit(_currentValue);
}
String _extractNumericValue(String value) {
if (value == 'none') return '0';
return value.replaceAll(RegExp(r'[a-zA-Z]'), '').trim();
}
String _extractUnit(String value) {
if (value == 'none') return '';
if (value.endsWith('s')) return 's';
if (value.endsWith('min')) return 'min';
if (value.endsWith('hour')) return 'hr';
return '';
} }
void _onValueChanged(String newValue) { void _onValueChanged(String newValue) {
setState(() {
_currentValue = newValue;
_numericValue = _extractNumericValue(newValue);
_unit = _extractUnit(newValue);
});
widget.action(newValue); widget.action(newValue);
} }
void _incrementValue() { void _incrementValue() {
int currentIndex = nobodyTimeRange.indexOf(_currentValue); int currentIndex = nobodyTimeRange.indexOf(_currentValue);
if (currentIndex < nobodyTimeRange.length - 1) { if (currentIndex < nobodyTimeRange.length - 1) {
setState(() { String newValue = nobodyTimeRange[currentIndex + 1];
_currentValue = nobodyTimeRange[currentIndex + 1]; _onValueChanged(newValue);
});
_onValueChanged(_currentValue);
} }
} }
void _decrementValue() { void _decrementValue() {
int currentIndex = nobodyTimeRange.indexOf(_currentValue); int currentIndex = nobodyTimeRange.indexOf(_currentValue);
if (currentIndex > 0) { if (currentIndex > 0) {
setState(() { String newValue = nobodyTimeRange[currentIndex - 1];
_currentValue = nobodyTimeRange[currentIndex - 1]; _onValueChanged(newValue);
});
_onValueChanged(_currentValue);
} }
} }
@ -81,11 +99,12 @@ class _PresenceUpdateDataState extends State<PresenceNoBodyTime> {
fontSize: 10), fontSize: 10),
), ),
IncrementDecrementWidget( IncrementDecrementWidget(
value: _currentValue, value: _numericValue,
description: widget.description ?? '', description: _unit,
descriptionColor: ColorsManager.blackColor, descriptionColor: ColorsManager.blackColor,
onIncrement: _incrementValue, onIncrement: _incrementValue,
onDecrement: _decrementValue), onDecrement: _decrementValue,
),
], ],
), ),
); );

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

@ -4,7 +4,7 @@ import 'dart:async';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/living_room_switch/models/living_room_model.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart';
import 'package:syncrow_web/services/devices_mang_api.dart'; import 'package:syncrow_web/services/devices_mang_api.dart';
part 'living_room_event.dart'; part 'living_room_event.dart';
@ -24,18 +24,15 @@ class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
LivingRoomFetchDeviceStatus event, Emitter<LivingRoomState> emit) async { LivingRoomFetchDeviceStatus event, Emitter<LivingRoomState> emit) async {
emit(LivingRoomDeviceStatusLoading()); emit(LivingRoomDeviceStatusLoading());
try { try {
final status = final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
await DevicesManagementApi().getDeviceStatus(event.deviceId); deviceStatus = LivingRoomStatusModel.fromJson(event.deviceId, status.status);
deviceStatus =
LivingRoomStatusModel.fromJson(event.deviceId, status.status);
emit(LivingRoomDeviceStatusLoaded(deviceStatus)); emit(LivingRoomDeviceStatusLoaded(deviceStatus));
} catch (e) { } catch (e) {
emit(LivingRoomDeviceManagementError(e.toString())); emit(LivingRoomDeviceManagementError(e.toString()));
} }
} }
FutureOr<void> _livingRoomControl( FutureOr<void> _livingRoomControl(LivingRoomControl event, Emitter<LivingRoomState> emit) async {
LivingRoomControl event, Emitter<LivingRoomState> emit) async {
final oldValue = _getValueByCode(event.code); final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value); _updateLocalValue(event.code, event.value);
@ -63,8 +60,8 @@ class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
} }
_timer = Timer(const Duration(seconds: 1), () async { _timer = Timer(const Duration(seconds: 1), () async {
try { try {
final response = await DevicesManagementApi() final response =
.deviceControl(deviceId, Status(code: code, value: value)); await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value));
if (!response) { if (!response) {
_revertValueAndEmit(deviceId, code, oldValue, emit); _revertValueAndEmit(deviceId, code, oldValue, emit);
} }
@ -74,8 +71,8 @@ class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
}); });
} }
void _revertValueAndEmit(String deviceId, String code, dynamic oldValue, void _revertValueAndEmit(
Emitter<LivingRoomState> emit) { String deviceId, String code, dynamic oldValue, Emitter<LivingRoomState> emit) {
_updateLocalValue(code, oldValue); _updateLocalValue(code, oldValue);
emit(LivingRoomDeviceStatusLoaded(deviceStatus)); emit(LivingRoomDeviceStatusLoaded(deviceStatus));
emit(const LivingRoomControlError('Failed to control the device.')); emit(const LivingRoomControlError('Failed to control the device.'));

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/living_room_switch/widgets/cieling_light.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/cieling_light.dart';
import 'package:syncrow_web/pages/device_managment/living_room_switch/widgets/spot_light.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/spot_light.dart';
import 'package:syncrow_web/pages/device_managment/living_room_switch/widgets/wall_light.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/wall_light.dart';
mixin LivingRoomHelper { mixin LivingRoomHelper {
Widget livingRoomControlWidgets( Widget livingRoomControlWidgets(
@ -18,4 +18,3 @@ mixin LivingRoomHelper {
} }
} }
} }

View File

@ -1,12 +1,11 @@
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:syncrow_web/pages/device_managment/living_room_switch/bloc/living_room_bloc.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart';
import 'package:syncrow_web/pages/device_managment/living_room_switch/models/living_room_model.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart';
import 'package:syncrow_web/pages/device_managment/living_room_switch/widgets/living_toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.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 LivingRoomDeviceControl extends StatelessWidget class LivingRoomDeviceControl extends StatelessWidget with HelperResponsiveLayout {
with HelperResponsiveLayout {
final String deviceId; final String deviceId;
const LivingRoomDeviceControl({super.key, required this.deviceId}); const LivingRoomDeviceControl({super.key, required this.deviceId});
@ -14,17 +13,16 @@ class LivingRoomDeviceControl extends StatelessWidget
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => LivingRoomBloc(deviceId: deviceId) create: (context) =>
..add(LivingRoomFetchDeviceStatus(deviceId)), LivingRoomBloc(deviceId: deviceId)..add(LivingRoomFetchDeviceStatus(deviceId)),
child: BlocBuilder<LivingRoomBloc, LivingRoomState>( child: BlocBuilder<LivingRoomBloc, LivingRoomState>(
builder: (context, state) { builder: (context, state) {
if (state is LivingRoomDeviceStatusLoading) { if (state is LivingRoomDeviceStatusLoading) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} else if (state is LivingRoomDeviceStatusLoaded) { } else if (state is LivingRoomDeviceStatusLoaded) {
return _buildStatusControls(context, state.status); return _buildStatusControls(context, state.status);
} else if (state is LivingRoomDeviceManagementError || } else if (state is LivingRoomDeviceManagementError || state is LivingRoomControlError) {
state is LivingRoomControlError) { return const Center(child: Text('Error fetching status'));
return Center(child: Text(state.toString()));
} else { } else {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
@ -33,8 +31,8 @@ class LivingRoomDeviceControl extends StatelessWidget
); );
} }
Widget _buildStatusControls( Widget _buildStatusControls(BuildContext context, LivingRoomStatusModel status) {
BuildContext context, LivingRoomStatusModel status) { final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context); final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context); final isMedium = isMediumScreenSize(context);
return GridView( return GridView(
@ -42,7 +40,7 @@ class LivingRoomDeviceControl extends StatelessWidget
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isLarge crossAxisCount: isLarge || isExtraLarge
? 3 ? 3
: isMedium : isMedium
? 2 ? 2

View File

@ -2,16 +2,12 @@ import 'package:flutter/cupertino.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/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/device_managment/living_room_switch/bloc/living_room_bloc.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
class CeilingLight extends StatelessWidget { class CeilingLight extends StatelessWidget {
const CeilingLight( const CeilingLight({super.key, required this.value, required this.code, required this.deviceId});
{super.key,
required this.value,
required this.code,
required this.deviceId});
final bool value; final bool value;
final String code; final String code;

View File

@ -2,7 +2,7 @@ import 'package:flutter/cupertino.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';
import 'package:syncrow_web/pages/device_managment/living_room_switch/bloc/living_room_bloc.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';

View File

@ -2,16 +2,12 @@ import 'package:flutter/cupertino.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/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/device_managment/living_room_switch/bloc/living_room_bloc.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
class SpotLight extends StatelessWidget { class SpotLight extends StatelessWidget {
const SpotLight( const SpotLight({super.key, required this.value, required this.code, required this.deviceId});
{super.key,
required this.value,
required this.code,
required this.deviceId});
final bool value; final bool value;
final String code; final String code;

View File

@ -2,16 +2,12 @@ import 'package:flutter/cupertino.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/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/device_managment/living_room_switch/bloc/living_room_bloc.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
class WallLight extends StatelessWidget { class WallLight extends StatelessWidget {
const WallLight( const WallLight({super.key, required this.value, required this.code, required this.deviceId});
{super.key,
required this.value,
required this.code,
required this.deviceId});
final bool value; final bool value;
final String code; final String code;

View File

@ -21,6 +21,7 @@ class WallSensorControls extends StatelessWidget with HelperResponsiveLayout {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context); final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context); final isMedium = isMediumScreenSize(context);
return BlocProvider( return BlocProvider(
@ -32,11 +33,13 @@ class WallSensorControls extends StatelessWidget with HelperResponsiveLayout {
state is DeviceReportsLoadingState) { 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( return _buildGridView(context, state.wallSensorModel, isExtraLarge,
context, state.wallSensorModel, isLarge, isMedium); 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());
@ -51,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, 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'));
}, },
@ -60,13 +64,13 @@ class WallSensorControls extends StatelessWidget with HelperResponsiveLayout {
} }
Widget _buildGridView(BuildContext context, WallSensorModel model, Widget _buildGridView(BuildContext context, WallSensorModel model,
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,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isLarge crossAxisCount: isLarge || isExtraLarge
? 3 ? 3
: isMedium : isMedium
? 2 ? 2

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,11 +39,12 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
emit(HomeUpdateTree(graph: graph, builder: builder)); emit(HomeUpdateTree(graph: graph, builder: builder));
} }
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(HomeUserInfoLoaded(user!)); // Emit state after fetching user info 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,6 +1,5 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:graphview/GraphView.dart'; import 'package:graphview/GraphView.dart';
import 'package:syncrow_web/pages/auth/model/user_model.dart';
abstract class HomeState extends Equatable { abstract class HomeState extends Equatable {
const HomeState(); const HomeState();
@ -25,9 +24,3 @@ class HomeUpdateTree extends HomeState {
@override @override
List<Object> get props => [graph, builder]; List<Object> get props => [graph, builder];
} }
class HomeUserInfoLoaded extends HomeState {
final UserModel user;
HomeUserInfoLoaded(this.user);
}

View File

@ -1,13 +1,31 @@
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/responsive_layout.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class HomePage extends StatelessWidget { 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) {
return ResponsiveLayout(desktopBody: HomeWebPage(), mobileBody: HomeMobilePage()); final isSmallScreen = isSmallScreenSize(context);
final isMediumScreen = isMediumScreenSize(context);
return isSmallScreen || isMediumScreen
? HomeMobilePage()
: const HomeWebPage();
} }
} }

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_state.dart';
import 'package:syncrow_web/pages/home/view/home_card.dart'; import 'package:syncrow_web/pages/home/view/home_card.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
@ -9,68 +10,77 @@ 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;
return WebScaffold( return PopScope(
enableMenuSideba: false, canPop: false,
appBarTitle: Row( onPopInvoked: (didPop) => false,
children: [ child: WebScaffold(
SvgPicture.asset( enableMenuSideba: false,
Assets.loginLogo, appBarTitle: Row(
width: 150, children: [
), SvgPicture.asset(
], Assets.loginLogo,
), width: 150,
scaffoldBody: BlocProvider( ),
create: (context) => HomeBloc(), ],
child: SizedBox(
height: size.height,
width: size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: size.height * 0.05),
const Text(
'ACCESS YOUR APPS',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w700),
),
const SizedBox(height: 30),
Expanded(
flex: 4,
child: SizedBox(
height: size.height * 0.6,
width: size.width * 0.68,
child: GridView.builder(
itemCount: 8,
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0,
childAspectRatio: 1.5,
),
itemBuilder: (context, index) {
return HomeCard(
index: index,
active: homeItems[index]['active'],
name: homeItems[index]['title'],
img: homeItems[index]['icon'],
onTap: () {},
);
},
),
),
),
],
),
), ),
scaffoldBody: BlocConsumer<HomeBloc, HomeState>(
listener: (context, state) {},
builder: (context, state) {
final homeBloc = BlocProvider.of<HomeBloc>(context);
return SizedBox(
height: size.height,
width: size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: size.height * 0.05),
const Text(
'ACCESS YOUR APPS',
style:
TextStyle(fontSize: 20, fontWeight: FontWeight.w700),
),
const SizedBox(height: 30),
Expanded(
flex: 4,
child: SizedBox(
height: size.height * 0.6,
width: size.width * 0.68,
child: GridView.builder(
itemCount: 8,
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0,
childAspectRatio: 1.5,
),
itemBuilder: (context, index) {
return HomeCard(
index: index,
active: homeItems[index]['active'],
name: homeItems[index]['title'],
img: homeItems[index]['icon'],
onTap: () =>
homeBloc.homeItems[index].onPress(context),
);
},
),
),
),
],
),
);
}),
), ),
); );
} }
dynamic homeItems = [ final dynamic homeItems = [
{ {
'title': 'Access', 'title': 'Access',
'icon': Assets.accessIcon, 'icon': Assets.accessIcon,

View File

@ -8,31 +8,28 @@ import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart';
class HomeWebPage extends StatelessWidget { class HomeWebPage extends StatelessWidget {
HomeWebPage({super.key}); const HomeWebPage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size; Size size = MediaQuery.of(context).size;
return PopScope( return PopScope(
canPop: false, canPop: false,
onPopInvoked: (didPop) => false, onPopInvoked: (didPop) => false,
child: child: BlocConsumer<HomeBloc, HomeState>(
WebScaffold( listener: (BuildContext context, state) {},
enableMenuSideba: false, builder: (context, state) {
appBarTitle: Row( final homeBloc = BlocProvider.of<HomeBloc>(context);
children: [ return WebScaffold(
SvgPicture.asset( enableMenuSideba: false,
Assets.loginLogo, appBarTitle: Row(
width: 150, children: [
), SvgPicture.asset(
], Assets.loginLogo,
), width: 150,
scaffoldBody: BlocProvider( ),
create: (context) => HomeBloc(), ],
child: BlocConsumer<HomeBloc, HomeState>( ),
listener: (BuildContext context, state) {}, scaffoldBody: SizedBox(
builder: (context, state) {
final homeBloc = BlocProvider.of<HomeBloc>(context);
return SizedBox(
height: size.height, height: size.height,
width: size.width, width: size.width,
child: Column( child: Column(
@ -55,8 +52,7 @@ class HomeWebPage extends StatelessWidget {
width: size.width * 0.68, width: size.width * 0.68,
child: GridView.builder( child: GridView.builder(
itemCount: 8, itemCount: 8,
gridDelegate: gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4, crossAxisCount: 4,
crossAxisSpacing: 20.0, crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0, mainAxisSpacing: 20.0,
@ -68,8 +64,7 @@ class HomeWebPage extends StatelessWidget {
active: homeBloc.homeItems[index].active!, active: homeBloc.homeItems[index].active!,
name: homeBloc.homeItems[index].title!, name: homeBloc.homeItems[index].title!,
img: homeBloc.homeItems[index].icon!, img: homeBloc.homeItems[index].icon!,
onTap: () => onTap: () => homeBloc.homeItems[index].onPress(context),
homeBloc.homeItems[index].onPress(context),
); );
}, },
), ),
@ -77,9 +72,9 @@ class HomeWebPage extends StatelessWidget {
), ),
], ],
), ),
); ),
}, );
), },
))); ));
} }
} }

View File

@ -205,7 +205,6 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
} on DioException catch (e) { } on DioException catch (e) {
final errorData = e.response!.data; final errorData = e.response!.data;
String errorMessage = errorData['message']; String errorMessage = errorData['message'];
print('errorMessage==$errorData');
emit(FailedState(errorMessage.toString())); emit(FailedState(errorMessage.toString()));
} }
} }
@ -220,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,
), ),
], ],
@ -239,7 +238,6 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
} on DioException catch (e) { } on DioException catch (e) {
final errorData = e.response!.data; final errorData = e.response!.data;
String errorMessage = errorData['message']; String errorMessage = errorData['message'];
print('errorMessage==$errorData');
emit(FailedState(errorMessage.toString())); emit(FailedState(errorMessage.toString()));
} }
} }
@ -260,7 +258,6 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
} on DioException catch (e) { } on DioException catch (e) {
final errorData = e.response!.data; final errorData = e.response!.data;
String errorMessage = errorData['message']; String errorMessage = errorData['message'];
print('errorMessage==$errorData');
emit(FailedState(errorMessage.toString())); emit(FailedState(errorMessage.toString()));
} }
} }
@ -285,7 +282,6 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
} on DioException catch (e) { } on DioException catch (e) {
final errorData = e.response!.data; final errorData = e.response!.data;
String errorMessage = errorData['message']; String errorMessage = errorData['message'];
print('errorMessage==$errorData');
emit(FailedState(errorMessage.toString())); emit(FailedState(errorMessage.toString()));
} }
} }
@ -322,7 +318,7 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
void filterDevices() { void filterDevices() {
final deviceName = deviceNameController.text.toLowerCase(); final deviceName = deviceNameController.text.toLowerCase();
final deviceId = deviceIdController.text.toLowerCase(); final deviceId = deviceIdController.text.toLowerCase();
final unitName = unitNameController.text.toLowerCase(); // final unitName = unitNameController.text.toLowerCase();
final filteredData = data.where((device) { final filteredData = data.where((device) {
final matchesDeviceName = device.name.toLowerCase().contains(deviceName); final matchesDeviceName = device.name.toLowerCase().contains(deviceName);
final matchesDeviceId = device.uuid.toLowerCase().contains(deviceId); final matchesDeviceId = device.uuid.toLowerCase().contains(deviceId);
@ -332,7 +328,6 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
add(UpdateFilteredDevicesEvent(filteredData)); add(UpdateFilteredDevicesEvent(filteredData));
} }
@override
Stream<VisitorPasswordState> mapEventToState(VisitorPasswordEvent event) async* { Stream<VisitorPasswordState> mapEventToState(VisitorPasswordEvent event) async* {
if (event is FetchDevice) { if (event is FetchDevice) {
} else if (event is UpdateFilteredDevicesEvent) { } else if (event is UpdateFilteredDevicesEvent) {
@ -417,7 +412,6 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
DateTime dateTime = inputFormat.parse(dateTimeString); DateTime dateTime = inputFormat.parse(dateTimeString);
return dateTime; return dateTime;
} catch (e) { } catch (e) {
print("Error parsing date: $e");
return null; return null;
} }
} }
@ -454,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,10 +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' &&
print('objectobjectobjectobject'); visitorBloc.accessTypeSelected == 'Offline Password') {
setPasswordFunction(context, size, visitorBloc); if (visitorBloc.expirationTime != 'End Time' &&
} else { 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) {
@ -555,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(
@ -564,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',
@ -724,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 [];
} }
} }
@ -123,13 +121,6 @@ class AccessMangApi {
String? effectiveTime, String? effectiveTime,
String? invalidTime, String? invalidTime,
List<String>? devicesUuid}) async { List<String>? devicesUuid}) async {
print(jsonEncode({
"email": email,
"devicesUuid": devicesUuid,
"passwordName": passwordName,
"effectiveTime": effectiveTime,
"invalidTime": invalidTime,
}));
final response = await HTTPService().post( final response = await HTTPService().post(
path: ApiEndpoints.sendOffLineMultipleTime, path: ApiEndpoints.sendOffLineMultipleTime,
body: jsonEncode({ body: jsonEncode({

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

@ -1,5 +1,5 @@
class RoutesConst { class RoutesConst {
static const String auth = '/'; static const String auth = '/auth';
static const String home = '/home'; static const String home = '/home';
static const String visitorPassword = '/visitor-password'; static const String visitorPassword = '/visitor-password';
static const String accessManagementPage = '/access-management-page'; static const String accessManagementPage = '/access-management-page';

View File

@ -2,15 +2,25 @@ import 'package:flutter/material.dart';
mixin HelperResponsiveLayout { mixin HelperResponsiveLayout {
bool isSmallScreenSize(BuildContext context) { bool isSmallScreenSize(BuildContext context) {
return MediaQuery.of(context).size.width < 700; return MediaQuery.of(context).size.width < 600;
} }
bool isMediumScreenSize(BuildContext context) { bool isMediumScreenSize(BuildContext context) {
return MediaQuery.of(context).size.width >= 700 && return MediaQuery.of(context).size.width >= 600 &&
MediaQuery.of(context).size.width < 1400; MediaQuery.of(context).size.width < 1024;
}
bool isHafMediumScreenSize(BuildContext context) {
return MediaQuery.of(context).size.width >= 600 / 1.3 &&
MediaQuery.of(context).size.width < 1024 / 1.3;
} }
bool isLargeScreenSize(BuildContext context) { bool isLargeScreenSize(BuildContext context) {
return MediaQuery.of(context).size.width >= 1400; return MediaQuery.of(context).size.width >= 1024 &&
MediaQuery.of(context).size.width < 1440;
}
bool isExtraLargeScreenSize(BuildContext context) {
return MediaQuery.of(context).size.width >= 1440;
} }
} }

View File

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
final myTheme = ThemeData(
fontFamily: 'Aftika',
textTheme: const TextTheme(
bodySmall: TextStyle(
fontSize: 13,
color: ColorsManager.whiteColors,
fontWeight: FontWeight.bold),
bodyMedium: TextStyle(color: Colors.black87, fontSize: 14),
bodyLarge: TextStyle(fontSize: 16, color: Colors.white),
headlineSmall: TextStyle(color: Colors.black87, fontSize: 18),
headlineMedium: TextStyle(color: Colors.black87, fontSize: 20),
headlineLarge: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
colorScheme: ColorScheme.fromSeed(
seedColor: ColorsManager.blueColor,
primary: ColorsManager.blueColor,
onSurface: Colors.grey.shade400,
),
switchTheme: SwitchThemeData(
thumbColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return ColorsManager.blueColor;
}
return ColorsManager.whiteColors;
}),
trackColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return ColorsManager.blueColor.withOpacity(0.5);
}
return ColorsManager.whiteColors;
}),
),
checkboxTheme: CheckboxThemeData(
fillColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return ColorsManager.blueColor;
}
return Colors.grey.shade200;
}),
checkColor: WidgetStateProperty.all(Colors.white),
side: const BorderSide(color: ColorsManager.whiteColors),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
),
);

View File

@ -3,63 +3,125 @@ 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_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_state.dart'; import 'package:syncrow_web/pages/home/bloc/home_state.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class WebAppBar extends StatelessWidget { class WebAppBar extends StatelessWidget with HelperResponsiveLayout {
final Widget? title; final Widget? title;
final List<Widget>? body; final Widget? centerBody;
const WebAppBar({super.key, this.title, this.body}); final Widget? rightBody;
const WebAppBar({super.key, this.title, this.centerBody, this.rightBody});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext 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: 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: Row( child: isSmallScreen || isHalfMediumScreen
mainAxisAlignment: MainAxisAlignment.spaceBetween, ? Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
Expanded( children: [
child: title!, if (title != null)
), Align(
if (body != null) alignment: Alignment.centerLeft,
Expanded( child: title!,
flex: 2, ),
child: Wrap( if (centerBody != null)
spacing: 15, // Adjust the spacing as needed Padding(
children: body!, padding: const EdgeInsets.only(top: 8.0),
), child: centerBody,
), ),
Row( if (rightBody != null || user != null)
children: [ Row(
const SizedBox( mainAxisAlignment: MainAxisAlignment.spaceBetween,
width: 10, children: [
), if (rightBody != null) rightBody!,
const SizedBox.square( Row(
dimension: 40, children: [
child: CircleAvatar( const SizedBox.square(
backgroundColor: Colors.white, dimension: 40,
child: SizedBox.square( child: CircleAvatar(
dimension: 35, backgroundColor: Colors.white,
child: CircleAvatar( child: SizedBox.square(
backgroundColor: Colors.grey, dimension: 35,
child: FlutterLogo(), child: CircleAvatar(
), backgroundColor: Colors.grey,
child: FlutterLogo(),
),
),
),
),
const SizedBox(
width: 10,
),
if (user != null)
Text(
'${user.firstName} ${user.lastName}',
style: Theme.of(context).textTheme.bodyLarge,
),
],
),
],
),
],
)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Row(
children: [
title!,
if (centerBody != null)
Padding(
padding: const EdgeInsets.only(left: 80),
child: centerBody!,
),
],
), ),
), ),
), Row(
const SizedBox( mainAxisSize: MainAxisSize.min,
width: 10, children: [
), if (rightBody != null)
if (HomeBloc.user != null) Align(
Text( alignment: Alignment.centerRight,
'${HomeBloc.user!.firstName.toString()} ${HomeBloc.user!.lastName.toString()} ', child: rightBody,
style: Theme.of(context).textTheme.bodyLarge, ),
const SizedBox(
width: 10,
),
const SizedBox.square(
dimension: 40,
child: CircleAvatar(
backgroundColor: Colors.white,
child: SizedBox.square(
dimension: 35,
child: CircleAvatar(
backgroundColor: Colors.grey,
child: FlutterLogo(),
),
),
),
),
const SizedBox(
width: 10,
),
if (user != null)
Text(
'${user.firstName} ${user.lastName}',
style: Theme.of(context).textTheme.bodyLarge,
),
],
), ),
], ],
) ),
],
),
); );
}); });
} }

View File

@ -8,14 +8,17 @@ import 'menu_sidebar.dart';
class WebScaffold extends StatelessWidget with HelperResponsiveLayout { class WebScaffold extends StatelessWidget with HelperResponsiveLayout {
final bool enableMenuSideba; final bool enableMenuSideba;
final Widget? appBarTitle; final Widget? appBarTitle;
final List<Widget>? appBarBody; final Widget? centerBody;
final Widget? rightBody;
final Widget? scaffoldBody; final Widget? scaffoldBody;
const WebScaffold( const WebScaffold({
{super.key, super.key,
this.appBarTitle, this.appBarTitle,
this.appBarBody, this.centerBody,
this.scaffoldBody, this.rightBody,
this.enableMenuSideba = true}); this.scaffoldBody,
this.enableMenuSideba = true,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isSmall = isSmallScreenSize(context); final isSmall = isSmallScreenSize(context);
@ -40,7 +43,8 @@ class WebScaffold extends StatelessWidget with HelperResponsiveLayout {
opacity: 0.7, opacity: 0.7,
child: WebAppBar( child: WebAppBar(
title: appBarTitle, title: appBarTitle,
body: appBarBody, centerBody: centerBody,
rightBody: rightBody,
)), )),
Expanded( Expanded(
child: Row( child: Row(

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: