Compare commits

..

33 Commits

Author SHA1 Message Date
bf6df377f6 update branch from dev 2024-09-03 13:48:29 +03:00
18605f52a0 Merge remote-tracking branch 'origin' into aug_bug_fixes2 2024-09-03 13:46:42 +03:00
5a291ce9f8 Changed the default env. file 2024-09-03 13:42:10 +03:00
25b9457043 Added env files and dotenv package 2024-09-03 13:41:07 +03:00
040e678b1e handle Lux description for illumnance sensor 2024-09-03 13:37:33 +03:00
7a32ad7878 staging deployed 2024-09-03 13:00:09 +03:00
ea256c4f51 ci: add Azure Static Web Apps workflow file
on-behalf-of: @Azure opensource@microsoft.com
2024-09-03 12:55:30 +03:00
ece37fc3c1 Merge pull request #9 from SyncrowIOT/fix_bugs
Fix bugs and login Enhancements
2024-09-03 12:20:54 +03:00
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
cf80251f82 Merge pull request #10 from SyncrowIOT/aug_bug_fixes
Aug bug fixes
2024-09-03 11:46:07 +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
4a252b38a8 Merge pull request #6 from SyncrowIOT/device_view
Device view
2024-08-28 14:57:54 +03:00
75 changed files with 1915 additions and 1410 deletions

2
.env.development Normal file
View File

@ -0,0 +1,2 @@
ENV_NAME=development
BASE_URL=https://syncrow-dev.azurewebsites.net

2
.env.production Normal file
View File

@ -0,0 +1,2 @@
ENV_NAME=production
BASE_URL=https://syncrow-staging.azurewebsites.net

2
.env.staging Normal file
View File

@ -0,0 +1,2 @@
ENV_NAME=staging
BASE_URL=https://syncrow-staging.azurewebsites.net

View File

@ -0,0 +1,60 @@
name: Azure Static Web Apps CI/CD
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, closed]
branches:
- main
jobs:
build_and_deploy_job:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
runs-on: ubuntu-latest
name: Build and Deploy Job
steps:
- name: Checkout Code
uses: actions/checkout@v3
with:
submodules: true
lfs: false
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.22.2' # Specify the Flutter version you want to use
- name: Install dependencies
run: flutter pub get
- name: Build Flutter Web App
run: flutter build web
- name: Build And Deploy
id: builddeploy
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_ZEALOUS_MUSHROOM_0D31A3303 }}
repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
action: "upload"
###### Repository/Build Configurations - These values can be configured to match your app requirements. ######
# For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
app_location: "/build/web" # App source code path
api_location: "" # Api source code path - optional
output_location: "/build/web" # Built app content directory - optional
###### End of Repository/Build Configurations ######
close_pull_request_job:
if: github.event_name == 'pull_request' && github.event.action == 'closed'
runs-on: ubuntu-latest
name: Close Pull Request Job
steps:
- name: Close Pull Request
id: closepullrequest
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_ZEALOUS_MUSHROOM_0D31A3303 }}
action: "close"

61
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,61 @@
{
"configurations": [
{
"name": "DEVELOPMENT",
"request": "launch",
"type": "dart",
"args": [
"--dart-define",
"FLAVOR=development"
],
"flutterMode": "debug"
},{
"name": "STAGING",
"request": "launch",
"type": "dart",
"args": [
"--dart-define",
"FLAVOR=staging"
],
"flutterMode": "debug"
},{
"name": "PRODUCTION",
"request": "launch",
"type": "dart",
"args": [
"--dart-define",
"FLAVOR=production"
],
"flutterMode": "debug"
},
]
}

View File

@ -1 +0,0 @@

View File

@ -1,35 +1,46 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/services/locator.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/theme/theme.dart';
Future<void> main() async {
try {
const environment = String.fromEnvironment('FLAVOR', defaultValue: 'development');
await dotenv.load(fileName: '.env.$environment');
WidgetsFlutterBinding.ensureInitialized();
initialSetup();
String checkToken = await AuthBloc.getTokenAndValidate();
GoRouter router = GoRouter(
initialLocation: checkToken == 'Success' ? RoutesConst.home : RoutesConst.auth,
routes: AppRoutes.getRoutes(),
);
runApp(MyApp(
router: router,
));
} catch (_) {}
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final GoRouter router;
const MyApp({
MyApp({
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
Widget build(BuildContext context) {
return MultiBlocProvider(
@ -40,7 +51,7 @@ class MyApp extends StatelessWidget {
)
],
child: MaterialApp.router(
debugShowCheckedModeBanner: false, // Hide debug banner
debugShowCheckedModeBanner: false,
scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
@ -49,26 +60,8 @@ class MyApp extends StatelessWidget {
PointerDeviceKind.unknown,
},
),
theme: 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,
),
),
),
routeInformationProvider: router.routeInformationProvider,
routerDelegate: router.routerDelegate,
routeInformationParser: router.routeInformationParser,
theme: myTheme,
routerConfig: _router,
));
}
}

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/model/password_model.dart';
import 'package:syncrow_web/services/access_mang_api.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/snack_bar.dart';
@ -108,6 +107,9 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
Future<void> _filterData(FilterDataEvent event, Emitter<AccessState> emit) async {
emit(AccessLoaded());
try {
// Convert search text to lower case for case-insensitive search
final searchText = event.passwordName?.toLowerCase() ?? '';
filteredData = data.where((item) {
bool matchesCriteria = true;
@ -124,10 +126,9 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
DateTime(effectiveDate.year, effectiveDate.month, effectiveDate.day);
DateTime invalidDateOnly = DateTime(invalidDate.year, invalidDate.month, invalidDate.day);
// Filter by password name
if (event.passwordName != null && event.passwordName!.isNotEmpty) {
final bool matchesName =
item.passwordName != null && item.passwordName.contains(event.passwordName);
// Filter by password name, making the search case-insensitive
if (searchText.isNotEmpty) {
final bool matchesName = item.passwordName.toString().toLowerCase().contains(searchText);
if (!matchesName) {
matchesCriteria = false;
}
@ -184,6 +185,8 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
}
}
resetSearch(ResetSearch event, Emitter<AccessState> emit) async {
emit(AccessLoaded());
startTime = 'Start Time';

View File

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

View File

@ -1,65 +1,57 @@
import 'package:flutter/material.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_event.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/search_reset_buttons.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/filter/filter_widget.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/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/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/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
class AccessManagementPage extends StatelessWidget {
class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
const AccessManagementPage({super.key});
@override
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(
enableMenuSideba: false,
appBarTitle: Row(
children: [
Text(
appBarTitle: FittedBox(
child: Text(
'Access Management',
style: Theme.of(context).textTheme.headlineLarge,
)
],
),
appBarBody: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
),
centerBody: Wrap(
children: [
Text(
Padding(
padding: EdgeInsets.only(left: MediaQuery.of(context).size.width * 0.09),
child: Align(
alignment: Alignment.bottomLeft,
child: Text(
'Physical Access',
style: Theme.of(context).textTheme.headlineMedium!.copyWith(color: Colors.white),
),
Row(
children: [
InkWell(
onTap: () {
context.go(RoutesConst.home);
},
child: SvgPicture.asset(
height: 20,
width: 20,
Assets.grid,
),
),
const SizedBox(
width: 10,
)
],
),
],
),
],
rightBody: const NavigateHomeGridView(),
scaffoldBody: BlocProvider(
create: (BuildContext context) => AccessBloc()..add(FetchTableData()),
child: BlocConsumer<AccessBloc, AccessState>(
@ -67,150 +59,75 @@ class AccessManagementPage extends StatelessWidget {
builder: (context, state) {
final accessBloc = BlocProvider.of<AccessBloc>(context);
final filteredData = accessBloc.filteredData;
return state is AccessLoaded
? const Center(child: CircularProgressIndicator())
: Container(
padding: EdgeInsets.all(30),
height: size.height,
width: size.width,
padding: padding,
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: containerDecoration,
height: size.height * 0.05,
child: Flexible(
child: ListView.builder(
scrollDirection: Axis.horizontal,
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,
),
),
),
),
);
FilterWidget(
size: MediaQuery.of(context).size,
tabs: accessBloc.tabs,
selectedIndex: accessBloc.selectedIndex,
onTabChanged: (index) {
accessBloc.add(TabChangedEvent(index));
},
),
),
),
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),
if (isSmallScreen || isHalfMediumScreen)
_buildSmallSearchFilters(context, accessBloc)
else
_buildNormalSearchWidgets(context, accessBloc),
const SizedBox(height: 20),
_buildVisitorAdminPasswords(context, accessBloc),
const SizedBox(height: 20),
Expanded(
child: DynamicTable(
uuidIndex: 1,
withSelectAll: true,
isEmpty: filteredData.isEmpty,
withCheckBox: false,
size: MediaQuery.of(context).size,
cellDecoration: containerDecoration,
headers: const [
'Name',
'Access Type',
'Access Period',
'Accessible Device',
'Authorizer',
'Authorization Date & Time',
'Access Status'
],
data: filteredData.map((item) {
return [
item.passwordName,
item.passwordType.value,
('${accessBloc.timestampToDate(item.effectiveTime)} - ${accessBloc.timestampToDate(item.invalidTime)}'),
item.deviceUuid.toString(),
'',
'',
item.passwordStatus.value,
];
}).toList(),
)),
],
),
const SizedBox(
height: 20,
),
Wrap(
);
})));
}
Wrap _buildVisitorAdminPasswords(BuildContext context, AccessBloc accessBloc) {
return Wrap(
spacing: 10,
runSpacing: 10,
children: [
Container(
width: size.width * 0.15,
width: 205,
height: 42,
decoration: containerDecoration,
child: DefaultButton(
onPressed: () {
@ -227,61 +144,119 @@ class AccessManagementPage extends StatelessWidget {
});
},
borderRadius: 8,
child: const Text('+ Create Visitor Password ')),
),
const SizedBox(
width: 10,
child: Text(
'+ Create Visitor Password ',
style: context.textTheme.titleSmall!.copyWith(color: Colors.white, fontSize: 12),
)),
),
Container(
width: size.width * 0.12,
width: 133,
height: 42,
decoration: containerDecoration,
child: DefaultButton(
borderRadius: 8,
backgroundColor: ColorsManager.whiteColors,
child: Text(
'Admin Password',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: Colors.black),
)))
],
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());
},
),
const SizedBox(
height: 20,
),
Expanded(
child: DynamicTable(
isEmpty: filteredData.isEmpty,
withCheckBox: false,
size: size,
cellDecoration: containerDecoration,
headers: const [
'Name',
'Access Type',
'Access Period',
'Accessible Device',
'Authorizer',
'Authorization Date & Time',
'Access Status'
],
data: filteredData.map((item) {
return [
item.passwordName.toString(),
item.passwordType.value,
('${accessBloc.timestampToDate(item.effectiveTime)} - ${accessBloc.timestampToDate(item.invalidTime)}'),
item.deviceUuid.toString(),
'',
'',
item.passwordStatus.value,
];
}).toList(),
)
// : const Center(child: CircularProgressIndicator()),
)
],
),
);
})));
}
}

View File

@ -1,4 +1,5 @@
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
@ -81,21 +82,25 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
_timer?.cancel();
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
emit(SuccessForgetState());
} else if (response == "You entered wrong otp") {
}
}
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 (response == "OTP expired") {
} else if (errorMessage == "OTP expired") {
forgetValidate = 'One time password has been expired.';
emit(AuthInitialState());
}
} catch (failure) {
// forgetValidate='Invalid Credentials!';
emit(AuthInitialState());
// emit(FailureForgetState(error: failure.toString()));
}
}
//925207
String? validateCode(String? value) {
if (value == null || value.isEmpty) {
return 'Code is required';
@ -135,6 +140,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(const LoginFailure(error: 'Something went wrong'));
return;
}
token = await AuthenticationAPI.loginWithEmail(
model: LoginWithEmailModel(
email: event.username,
@ -143,10 +149,10 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
);
} catch (failure) {
validate = 'Invalid Credentials!';
emit(AuthInitialState());
// emit(const LoginFailure(error: 'Something went wrong'));
emit(const LoginFailure(error: 'Invalid Credentials!'));
return;
}
if (token.accessTokenIsNotEmpty) {
FlutterSecureStorage storage = const FlutterSecureStorage();
await storage.write(
@ -155,9 +161,9 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
key: UserModel.userUuidKey,
value: Token.decodeToken(token.accessToken)['uuid'].toString());
user = UserModel.fromToken(token);
debugPrint(token.accessToken);
loginEmailController.clear();
loginPasswordController.clear();
debugPrint("token " + token.accessToken);
emit(LoginSuccess());
} else {
emit(const LoginFailure(error: 'Something went wrong'));
@ -177,15 +183,15 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(LoginInitial());
}
checkOtpCode(
ChangePasswordEvent event,
Emitter<AuthState> emit,
) async {
emit(LoadingForgetState());
await AuthenticationAPI.verifyOtp(
email: forgetEmailController.text, otpCode: forgetOtp.text);
emit(SuccessForgetState());
}
// checkOtpCode(
// ChangePasswordEvent event,
// Emitter<AuthState> emit,
// ) async {
// emit(LoadingForgetState());
// await AuthenticationAPI.verifyOtp(
// email: forgetEmailController.text, otpCode: forgetOtp.text);
// emit(SuccessForgetState());
// }
void _passwordVisible(PasswordVisibleEvent event, Emitter<AuthState> emit) {
emit(AuthLoading());

View File

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

View File

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

View File

@ -1,5 +1,4 @@
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/utils/responsive_layout.dart';
@ -9,7 +8,6 @@ class ForgetPasswordPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const ResponsiveLayout(
desktopBody: ForgetPasswordWebPage(),
mobileBody: ForgetPasswordWebPage());
desktopBody: ForgetPasswordWebPage(), mobileBody: ForgetPasswordWebPage());
}
}

View File

@ -1,3 +1,4 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
@ -148,44 +149,7 @@ class ForgetPasswordWebPage extends StatelessWidget {
),
const SizedBox(height: 10),
SizedBox(
child: DropdownButtonFormField<String>(
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!,
));
},
),
child: _buildDropdownField(context, forgetBloc, size)
)
],
),
@ -208,11 +172,12 @@ class ForgetPasswordWebPage extends StatelessWidget {
SizedBox(
child: TextFormField(
validator: forgetBloc.validateEmail,
controller:
forgetBloc.forgetEmailController,
decoration: textBoxDecoration()!
.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(
color: Colors.black),
),
@ -244,13 +209,15 @@ class ForgetPasswordWebPage extends StatelessWidget {
decoration:
textBoxDecoration()!.copyWith(
hintText: 'Enter Code',
hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.w400),
suffixIcon: SizedBox(
width: 100,
child: Center(
child: InkWell(
onTap: state is TimerState &&
!state
.isButtonEnabled &&
!state.isButtonEnabled &&
state.remainingTime !=
1
? null
@ -261,10 +228,8 @@ class ForgetPasswordWebPage extends StatelessWidget {
child: Text(
'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime != 1 ? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}',
style: TextStyle(
color: state
is TimerState &&
!state
.isButtonEnabled
color: state is TimerState &&
!state.isButtonEnabled
? Colors.grey
: ColorsManager
.btnColor,
@ -278,8 +243,7 @@ class ForgetPasswordWebPage extends StatelessWidget {
color: Colors.black),
),
),
if (forgetBloc.forgetValidate !=
'') // Check if there is a validation message
if (forgetBloc.forgetValidate != '') // Check if there is a validation message
Padding(
padding:
const EdgeInsets.only(top: 8.0),
@ -311,18 +275,16 @@ class ForgetPasswordWebPage extends StatelessWidget {
const SizedBox(height: 10),
SizedBox(
child: TextFormField(
validator:
forgetBloc.passwordValidator,
keyboardType:
TextInputType.visiblePassword,
controller: forgetBloc
.forgetPasswordController,
decoration:
textBoxDecoration()!.copyWith(
validator: forgetBloc.passwordValidator,
keyboardType: TextInputType.visiblePassword,
controller: forgetBloc.forgetPasswordController,
decoration: textBoxDecoration()!.copyWith(
hintText: 'At least 8 characters',
hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.w400),
),
style: const TextStyle(
color: Colors.black),
style: const TextStyle(color: Colors.black),
),
),
],
@ -346,8 +308,7 @@ class ForgetPasswordWebPage extends StatelessWidget {
if (forgetBloc
.forgetFormKey.currentState!
.validate()) {
forgetBloc
.add(ChangePasswordEvent());
forgetBloc.add(ChangePasswordEvent());
}
},
),
@ -355,7 +316,8 @@ class ForgetPasswordWebPage extends StatelessWidget {
],
),
const SizedBox(height: 10.0),
SizedBox(
Center(
child: SizedBox(
child: Text(
forgetBloc.validate,
style: const TextStyle(
@ -363,6 +325,7 @@ class ForgetPasswordWebPage extends StatelessWidget {
color: ColorsManager.red),
),
),
),
SizedBox(
height: 10,
),
@ -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/material.dart';
import 'package:flutter_bloc/flutter_bloc.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_event.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/utils/color_manager.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';
class LoginMobilePage extends StatelessWidget {
@ -25,10 +25,7 @@ class LoginMobilePage extends StatelessWidget {
listener: (context, state) {
if (state is LoginSuccess) {
// Navigate to home screen after successful login
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => HomePage()),
);
context.go(RoutesConst.home, extra: {'clearHistory': true});
} else if (state is LoginFailure) {
// Show error message
ScaffoldMessenger.of(context).showSnackBar(
@ -52,8 +49,6 @@ class LoginMobilePage extends StatelessWidget {
Widget _buildLoginForm(BuildContext context) {
final loginBloc = BlocProvider.of<AuthBloc>(context);
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
return Center(
child: Stack(
children: [
@ -102,11 +97,8 @@ class LoginMobilePage extends StatelessWidget {
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius:
const BorderRadius.all(Radius.circular(30)),
border: Border.all(
color:
ColorsManager.graysColor.withOpacity(0.2))),
borderRadius: const BorderRadius.all(Radius.circular(30)),
border: Border.all(color: ColorsManager.graysColor.withOpacity(0.2))),
child: Form(
key: loginBloc.loginFormKey,
child: Column(
@ -117,9 +109,7 @@ class LoginMobilePage extends StatelessWidget {
const Text(
'Login',
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold),
color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 30),
Column(
@ -148,8 +138,7 @@ class LoginMobilePage extends StatelessWidget {
),
isDense: true,
style: const TextStyle(color: Colors.black),
items: loginBloc.regionList!
.map((RegionModel region) {
items: loginBloc.regionList!.map((RegionModel region) {
return DropdownMenuItem<String>(
value: region.name,
child: Text(region.name),
@ -173,8 +162,8 @@ class LoginMobilePage extends StatelessWidget {
child: TextFormField(
validator: loginBloc.validateEmail,
controller: loginBloc.loginEmailController,
decoration: textBoxDecoration()!
.copyWith(hintText: 'Enter your email'),
decoration:
textBoxDecoration()!.copyWith(hintText: 'Enter your email'),
style: const TextStyle(color: Colors.black),
),
),
@ -194,8 +183,7 @@ class LoginMobilePage extends StatelessWidget {
validator: loginBloc.validatePassword,
obscureText: loginBloc.obscureText,
keyboardType: TextInputType.visiblePassword,
controller:
loginBloc.loginPasswordController,
controller: loginBloc.loginPasswordController,
decoration: textBoxDecoration()!.copyWith(
hintText: 'At least 8 characters',
),
@ -213,16 +201,13 @@ class LoginMobilePage extends StatelessWidget {
children: [
InkWell(
onTap: () {
Navigator.of(context)
.push(MaterialPageRoute(
builder: (context) =>
const ForgetPasswordPage(),
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const ForgetPasswordPage(),
));
},
child: Text(
"Forgot Password?",
style:
Theme.of(context).textTheme.bodySmall,
style: Theme.of(context).textTheme.bodySmall,
),
),
],
@ -233,15 +218,13 @@ class LoginMobilePage extends StatelessWidget {
Transform.scale(
scale: 1.2, // Adjust the scale as needed
child: Checkbox(
fillColor: MaterialStateProperty.all<Color>(
Colors.white),
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));
loginBloc.add(CheckBoxEvent(newValue: newValue));
},
),
),
@ -250,37 +233,30 @@ class LoginMobilePage extends StatelessWidget {
child: RichText(
text: TextSpan(
text: 'Agree to ',
style:
const TextStyle(color: Colors.white),
style: const TextStyle(color: Colors.white),
children: [
TextSpan(
text: '(Terms of Service)',
style: const TextStyle(
color: Colors.black),
style: const TextStyle(color: Colors.black),
recognizer: TapGestureRecognizer()
..onTap = () {
loginBloc.launchURL(
'https://example.com/terms');
loginBloc.launchURL('https://example.com/terms');
},
),
TextSpan(
text: ' (Legal Statement)',
style: const TextStyle(
color: Colors.black),
style: const TextStyle(color: Colors.black),
recognizer: TapGestureRecognizer()
..onTap = () {
loginBloc.launchURL(
'https://example.com/legal');
loginBloc.launchURL('https://example.com/legal');
},
),
TextSpan(
text: ' (Privacy Statement)',
style: const TextStyle(
color: Colors.black),
style: const TextStyle(color: Colors.black),
recognizer: TapGestureRecognizer()
..onTap = () {
loginBloc.launchURL(
'https://example.com/privacy');
loginBloc.launchURL('https://example.com/privacy');
},
),
],
@ -297,15 +273,12 @@ class LoginMobilePage extends StatelessWidget {
: ColorsManager.grayColor,
child: const Text('Sign in'),
onPressed: () {
if (loginBloc.loginFormKey.currentState!
.validate()) {
if (loginBloc.loginFormKey.currentState!.validate()) {
loginBloc.add(
LoginButtonPressed(
regionUuid: '',
username:
loginBloc.loginEmailController.text,
password: loginBloc
.loginPasswordController.text,
username: loginBloc.loginEmailController.text,
password: loginBloc.loginPasswordController.text,
),
);
}
@ -320,8 +293,7 @@ class LoginMobilePage extends StatelessWidget {
Flexible(
child: Text(
"Don't you have an account? ",
style: TextStyle(
color: Colors.white, fontSize: 13),
style: TextStyle(color: Colors.white, fontSize: 13),
)),
Text(
"Sign up",

View File

@ -1,3 +1,4 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.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/constants/assets.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';
class LoginWebPage extends StatefulWidget {
@ -22,7 +24,8 @@ class LoginWebPage extends StatefulWidget {
State<LoginWebPage> createState() => _LoginWebPageState();
}
class _LoginWebPageState extends State<LoginWebPage> {
class _LoginWebPageState extends State<LoginWebPage>
with HelperResponsiveLayout {
@override
Widget build(BuildContext context) {
return Scaffold(
@ -31,7 +34,7 @@ class _LoginWebPageState extends State<LoginWebPage> {
child: BlocConsumer<AuthBloc, AuthState>(
listener: (context, state) {
if (state is LoginSuccess) {
context.go(RoutesConst.home);
GoRouter.of(context).go(RoutesConst.home);
} else if (state is LoginFailure) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
@ -50,11 +53,15 @@ class _LoginWebPageState extends State<LoginWebPage> {
Widget _buildLoginForm(BuildContext context, AuthState state) {
final loginBloc = BlocProvider.of<AuthBloc>(context);
final isSmallScreen = isSmallScreenSize(context);
final isMediumScreen = isMediumScreenSize(context);
Size size = MediaQuery.of(context).size;
late ScrollController _scrollController;
_scrollController = ScrollController();
void _scrollToCenter() {
final double middlePosition = _scrollController.position.maxScrollExtent / 2;
final double middlePosition =
_scrollController.position.maxScrollExtent / 2;
_scrollController.animateTo(
middlePosition,
duration: const Duration(seconds: 1),
@ -65,6 +72,7 @@ class _LoginWebPageState extends State<LoginWebPage> {
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToCenter();
});
return Stack(
children: [
FirstLayer(
@ -74,115 +82,213 @@ class _LoginWebPageState extends State<LoginWebPage> {
shrinkWrap: true,
children: [
Container(
width: 400,
padding: EdgeInsets.all(size.width * 0.02),
margin: EdgeInsets.all(size.width * 0.09),
margin: EdgeInsets.all(size.width * 0.05),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.3),
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
child: Center(
child: Row(
child: isSmallScreen || isMediumScreen
? SizedBox(
width: 400,
child: Column(
// For small screens
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 300,
child: SvgPicture.asset(
Assets.loginLogo,
),
),
const SizedBox(height: 20),
_buildLoginFormFields(context, loginBloc, size),
],
),
)
: Row(
// For larger screens
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Spacer(),
Expanded(
flex: 3,
flex: 2,
child: SvgPicture.asset(
Assets.loginLogo,
),
),
const Spacer(),
Expanded(
flex: 3,
child: Container(
flex: 2,
child: _buildLoginFormFields(
context, loginBloc, size),
),
const Spacer(),
],
),
),
),
],
),
),
),
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))),
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),
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),
Text('Login', style: Theme.of(context).textTheme.headlineLarge),
SizedBox(height: size.height * 0.03),
Column(
_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),
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,
const SizedBox(height: 10),
Container(
height: 50,
decoration: const BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.all(Radius.circular(8)),
),
decoration: textBoxDecoration()!.copyWith(
errorStyle: const TextStyle(height: 0),
hintText: null,
),
hint: SizedBox(
width: size.width * 0.12,
child: Align(
alignment: Alignment.centerLeft,
child: Text(
width: size.width * 0.9,
child: DropdownButtonHideUnderline(
child: DropdownButton2<String>(
style: TextStyle(color: Colors.black),
isExpanded: true,
hint: Text(
'Select your region/country',
textAlign: TextAlign.center,
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.w400),
fontWeight: FontWeight.w400,
),
overflow: TextOverflow.ellipsis,
),
),
),
isDense: true,
style: const TextStyle(color: Colors.black),
items:
loginBloc.regionList!.map((RegionModel region) {
items: loginBloc.regionList!.map((RegionModel region) {
return DropdownMenuItem<String>(
value: region.id,
child: SizedBox(
width: size.width * 0.08,
child: Text(region.name)),
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!));
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();
}
},
),
const SizedBox(height: 20.0),
Column(
),
),
],
);
}
Widget _buildEmailField(BuildContext context, AuthBloc loginBloc) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
@ -191,37 +297,31 @@ class _LoginWebPageState extends State<LoginWebPage> {
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
fontSize: 14, fontWeight: FontWeight.w400),
),
const SizedBox(
height: 10,
.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
errorStyle: const TextStyle(height: 0),
hintText: 'Enter your email address',
hintStyle: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
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(
);
}
Widget _buildPasswordField(BuildContext context, AuthBloc loginBloc) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
@ -230,12 +330,9 @@ class _LoginWebPageState extends State<LoginWebPage> {
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
fontSize: 14, fontWeight: FontWeight.w400),
),
const SizedBox(
height: 10,
.copyWith(fontSize: 14, fontWeight: FontWeight.w400),
),
const SizedBox(height: 10),
SizedBox(
child: TextFormField(
onChanged: (value) {
@ -247,16 +344,12 @@ class _LoginWebPageState extends State<LoginWebPage> {
controller: loginBloc.loginPasswordController,
decoration: textBoxDecoration()!.copyWith(
hintText: 'At least 8 characters',
hintStyle: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.w400),
hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor, fontWeight: FontWeight.w400),
suffixIcon: IconButton(
onPressed: () {
loginBloc.add(PasswordVisibleEvent(
newValue: loginBloc.obscureText));
loginBloc.add(
PasswordVisibleEvent(newValue: loginBloc.obscureText));
},
icon: SizedBox(
child: SvgPicture.asset(
@ -268,34 +361,29 @@ class _LoginWebPageState extends State<LoginWebPage> {
),
),
),
errorStyle: const TextStyle(
height: 0), // Hide the error text space
errorStyle: const TextStyle(height: 0),
),
style: const TextStyle(color: Colors.black),
),
),
],
),
const SizedBox(
height: 20,
),
SizedBox(
);
}
Widget _buildForgotPassword(BuildContext context) {
return SizedBox(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
InkWell(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) =>
const ForgetPasswordPage(),
builder: (context) => const ForgetPasswordPage(),
));
},
child: Text(
"Forgot Password?",
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.w400),
@ -303,17 +391,16 @@ class _LoginWebPageState extends State<LoginWebPage> {
),
],
),
),
const SizedBox(
height: 20,
),
Row(
);
}
Widget _buildCheckbox(BuildContext context, AuthBloc loginBloc, Size size) {
return Row(
children: [
Transform.scale(
scale: 1.2, // Adjust the scale as needed
scale: 1.2,
child: Checkbox(
fillColor:
MaterialStateProperty.all<Color>(Colors.white),
fillColor: MaterialStateProperty.all<Color>(Colors.white),
activeColor: Colors.white,
value: loginBloc.isChecked,
checkColor: Colors.black,
@ -324,7 +411,7 @@ class _LoginWebPageState extends State<LoginWebPage> {
),
),
SizedBox(
width: size.width * 0.14,
width: 220,
child: RichText(
text: TextSpan(
text: 'Agree to ',
@ -332,13 +419,10 @@ class _LoginWebPageState extends State<LoginWebPage> {
children: [
TextSpan(
text: '(Terms of Service)',
style: const TextStyle(
color: Colors.black,
),
style: const TextStyle(color: Colors.black),
recognizer: TapGestureRecognizer()
..onTap = () {
loginBloc.launchURL(
'https://example.com/terms');
loginBloc.launchURL('https://example.com/terms');
},
),
TextSpan(
@ -346,8 +430,7 @@ class _LoginWebPageState extends State<LoginWebPage> {
style: const TextStyle(color: Colors.black),
recognizer: TapGestureRecognizer()
..onTap = () {
loginBloc.launchURL(
'https://example.com/legal');
loginBloc.launchURL('https://example.com/legal');
},
),
TextSpan(
@ -355,8 +438,7 @@ class _LoginWebPageState extends State<LoginWebPage> {
style: const TextStyle(color: Colors.black),
recognizer: TapGestureRecognizer()
..onTap = () {
loginBloc.launchURL(
'https://example.com/privacy');
loginBloc.launchURL('https://example.com/privacy');
},
),
],
@ -364,9 +446,12 @@ class _LoginWebPageState extends State<LoginWebPage> {
),
),
],
),
const SizedBox(height: 20.0),
Row(
);
}
Widget _buildSignInButton(
BuildContext context, AuthBloc loginBloc, Size size) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
@ -375,24 +460,18 @@ class _LoginWebPageState extends State<LoginWebPage> {
child: DefaultButton(
enabled: loginBloc.checkValidate,
child: Text('Sign in',
style: Theme.of(context)
.textTheme
.labelLarge!
.copyWith(
style: Theme.of(context).textTheme.labelLarge!.copyWith(
fontSize: 14,
color: loginBloc.checkValidate
? ColorsManager.whiteColors
: ColorsManager.whiteColors
.withOpacity(0.2),
: ColorsManager.whiteColors.withOpacity(0.2),
)),
onPressed: () {
if (loginBloc.loginFormKey.currentState!
.validate()) {
if (loginBloc.loginFormKey.currentState!.validate()) {
loginBloc.add(LoginButtonPressed(
regionUuid: loginBloc.regionUuid,
username: loginBloc.loginEmailController.text,
password:
loginBloc.loginPasswordController.text,
password: loginBloc.loginPasswordController.text,
));
} else {
loginBloc.add(ChangeValidateEvent());
@ -401,9 +480,11 @@ class _LoginWebPageState extends State<LoginWebPage> {
),
),
],
),
const SizedBox(height: 15.0),
Row(
);
}
Widget _buildValidationMessage(AuthBloc loginBloc) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
@ -411,27 +492,10 @@ class _LoginWebPageState extends State<LoginWebPage> {
child: Text(
loginBloc.validate,
style: const TextStyle(
fontWeight: FontWeight.w700,
color: ColorsManager.red),
fontWeight: FontWeight.w700, color: ColorsManager.red),
),
)
],
)
],
),
),
))),
const Spacer(),
],
),
),
),
],
),
),
),
if (state is AuthLoading) const Center(child: CircularProgressIndicator())
],
);
}
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.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/color_manager.dart';
@ -23,14 +24,18 @@ class SearchResetButtons extends StatelessWidget {
const SizedBox(height: 25),
Center(
child: Container(
height: 43,
height: 42,
width: 100,
decoration: containerDecoration,
child: Center(
child: DefaultButton(
onPressed: onSearch,
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),
Center(
child: Container(
height: 43,
height: 42,
width: 100,
decoration: containerDecoration,
child: Center(
child: DefaultButton(
backgroundColor: ColorsManager.whiteColors,
borderRadius: 9,
onPressed: onReset,
child: Text(
'Reset',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: Colors.black),
style: context.textTheme.titleSmall!
.copyWith(color: Colors.black, fontSize: 12),
),
onPressed: onReset,
),
),
),

View File

@ -5,27 +5,33 @@ import 'package:syncrow_web/utils/constants/assets.dart';
class DynamicTable extends StatefulWidget {
final List<String> headers;
final String? tableName;
final List<List<dynamic>> data;
final BoxDecoration? headerDecoration;
final BoxDecoration? cellDecoration;
final Size size;
final bool withCheckBox;
final bool withSelectAll;
final bool isEmpty;
final void Function(bool?)? selectAll;
final void Function(int, bool, dynamic)? onRowSelected;
final List<String>? initialSelectedIds;
final int uuidIndex;
const DynamicTable({
super.key,
required this.headers,
required this.data,
required this.size,
this.tableName,
required this.isEmpty,
required this.withCheckBox,
required this.withSelectAll,
this.headerDecoration,
this.cellDecoration,
this.selectAll,
this.onRowSelected,
this.initialSelectedIds,
required this.uuidIndex,
});
@override
@ -34,13 +40,29 @@ class DynamicTable extends StatefulWidget {
class _DynamicTableState extends State<DynamicTable> {
late List<bool> _selected;
bool _selectAll = false;
@override
void 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) {
// 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 &&
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
Widget build(BuildContext context) {
return Container(
@ -65,11 +98,13 @@ class _DynamicTableState extends State<DynamicTable> {
child: Column(
children: [
Container(
decoration: widget.headerDecoration ?? BoxDecoration(color: Colors.grey[200]),
decoration: widget.headerDecoration ??
BoxDecoration(color: Colors.grey[200]),
child: Row(
children: [
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,
),
Text(
'No Devices',
// no password
widget.tableName=='AccessManagement'? 'No Password ' : 'No Devices',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: ColorsManager.grayColor),
.copyWith(
color: ColorsManager.grayColor),
)
],
),
@ -113,11 +150,11 @@ class _DynamicTableState extends State<DynamicTable> {
return Row(
children: [
if (widget.withCheckBox)
_buildRowCheckbox(index, widget.size.height * 0.10),
...row
.map((cell) =>
_buildTableCell(cell.toString(), widget.size.height * 0.10))
.toList(),
_buildRowCheckbox(
index, widget.size.height * 0.10),
...row.map((cell) => _buildTableCell(
cell.toString(),
widget.size.height * 0.10)),
],
);
},
@ -142,7 +179,7 @@ class _DynamicTableState extends State<DynamicTable> {
),
child: Checkbox(
value: _selected.every((element) => element == true),
onChanged: null,
onChanged:widget.withSelectAll?_toggleSelectAll:null,
),
);
}
@ -190,6 +227,7 @@ class _DynamicTableState extends State<DynamicTable> {
fontSize: 13,
color: Color(0xFF999999),
),
maxLines: 2,
),
),
),
@ -222,7 +260,7 @@ class _DynamicTableState extends State<DynamicTable> {
statusColor = ColorsManager.red;
break;
default:
statusColor = Colors.black; // Default color
statusColor = Colors.black;
}
return Expanded(
@ -241,11 +279,14 @@ class _DynamicTableState extends State<DynamicTable> {
child: Text(
content,
style: TextStyle(
color: batteryLevel != null && batteryLevel < 20
color: (batteryLevel != null && batteryLevel < 20)
? ColorsManager.red
: statusColor, // Use the passed color or default to black
: (batteryLevel != null && batteryLevel > 20)
? ColorsManager.green
: statusColor,
fontSize: 10,
fontWeight: FontWeight.w400),
maxLines: 2,
),
),
);

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.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';
class CustomWebTextField extends StatelessWidget {
@ -11,6 +12,7 @@ class CustomWebTextField extends StatelessWidget {
this.description,
this.validator,
this.hintText,
this.height,
});
final bool isRequired;
@ -19,6 +21,7 @@ class CustomWebTextField extends StatelessWidget {
final TextEditingController? controller;
final String? Function(String?)? validator;
final String? hintText;
final double? height;
@override
Widget build(BuildContext context) {
@ -29,21 +32,19 @@ class CustomWebTextField extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (isRequired)
Row(
children: [
Text(
'* ',
if (isRequired)
Text('* ',
style: Theme.of(context)
.textTheme
.bodyMedium!
.textTheme.bodyMedium!
.copyWith(color: Colors.red),
),
Text(
textFieldName,
style: Theme.of(context)
.textTheme
.bodySmall!
.textTheme.bodySmall!
.copyWith(color: Colors.black, fontSize: 13),
),
],
@ -66,23 +67,26 @@ class CustomWebTextField extends StatelessWidget {
height: 7,
),
Container(
decoration: containerDecoration
.copyWith(color: const Color(0xFFF5F6F7), boxShadow: [
height: height ?? 35,
decoration: containerDecoration.copyWith(
color: const Color(0xFFF5F6F7),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.3),
spreadRadius: 2,
blurRadius: 3,
offset: const Offset(1, 1), // changes position of shadow
),
]),
]
),
child: TextFormField(
validator: validator,
controller: controller,
style: const TextStyle(color: Colors.black),
decoration: textBoxDecoration()!.copyWith(
errorStyle:
const TextStyle(height: 0), // Hide the error text space
errorStyle: const TextStyle(height: 0),
hintStyle: context.textTheme.titleSmall!
.copyWith(color: Colors.grey, fontSize: 12),
hintText: hintText ?? 'Please enter'),
),
),

View File

@ -18,6 +18,7 @@ class AcDeviceControl extends StatelessWidget with HelperResponsiveLayout {
@override
Widget build(BuildContext context) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
return BlocProvider(
@ -31,7 +32,7 @@ class AcDeviceControl extends StatelessWidget with HelperResponsiveLayout {
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isLarge
crossAxisCount: isLarge || isExtraLarge
? 3
: isMedium
? 2

View File

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

View File

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

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.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/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/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -13,10 +14,13 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
return BlocProvider(
create: (context) => DeviceManagementBloc()..add(FetchDevices()),
child: WebScaffold(
appBarTitle: Text(
appBarTitle: FittedBox(
child: Text(
'Device Management',
style: Theme.of(context).textTheme.headlineLarge,
),
),
rightBody: const NavigateHomeGridView(),
enableMenuSideba: isLargeScreenSize(context),
scaffoldBody: BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
builder: (context, state) {
@ -30,7 +34,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
return DeviceManagementBody(devices: devices);
} 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_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/custom_table.dart';
import 'package:syncrow_web/pages/common/filter/filter_widget.dart';
@ -42,19 +42,22 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
offlineCount = state.offlineCount;
lowBatteryCount = state.lowBatteryCount;
isControlButtonEnabled = state.selectedDevice != null;
} else if (state is DeviceManagementInitial) {
devicesToShow = [];
selectedIndex = 0;
isControlButtonEnabled = false;
}
final tabs = [
'All (${devices.length})',
'All',
'Online ($onlineCount)',
'Offline ($offlineCount)',
'Low Battery ($lowBatteryCount)',
];
return CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Container(
return Column(
children: [
Container(
padding: isLargeScreenSize(context)
? const EdgeInsets.all(30)
: const EdgeInsets.all(15),
@ -66,8 +69,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
tabs: tabs,
selectedIndex: selectedIndex,
onTabChanged: (index) {
context
.read<DeviceManagementBloc>()
context.read<DeviceManagementBloc>()
.add(SelectedFilterChanged(index));
},
),
@ -75,8 +77,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
const DeviceSearchFilters(),
const SizedBox(height: 12),
Container(
height: 43,
width: isSmallScreenSize(context) ? double.infinity : 100,
height: 45,
width: 100,
decoration: containerDecoration,
child: Center(
child: DefaultButton(
@ -84,8 +86,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
? () {
final selectedDevice = context
.read<DeviceManagementBloc>()
.selectedDevices
.first;
.selectedDevices.first;
showDialog(
context: context,
builder: (context) => DeviceControlDialog(
@ -97,6 +98,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
child: Text(
'Control',
style: TextStyle(
fontSize: 12,
color: isControlButtonEnabled
? Colors.white
: Colors.grey,
@ -108,13 +110,13 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
],
),
),
),
SliverFillRemaining(
Expanded(
child: Padding(
padding: isLargeScreenSize(context)
? const EdgeInsets.all(30)
: const EdgeInsets.all(15),
child: DynamicTable(
withSelectAll: false,
cellDecoration: containerDecoration,
onRowSelected: (index, isSelected, row) {
final selectedDevice = devicesToShow[index];
@ -124,6 +126,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
},
withCheckBox: true,
size: context.screenSize,
uuidIndex: 2,
headers: const [
'Device Name',
'Product Name',
@ -144,7 +147,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
device.room?.name ?? '',
device.batteryLevel != null
? '${device.batteryLevel}%'
: '',
: '-',
formatDateTime(DateTime.fromMillisecondsSinceEpoch(
(device.createTime ?? 0) * 1000)),
device.online == true ? 'Online' : 'Offline',
@ -152,10 +155,15 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
(device.updateTime ?? 0) * 1000)),
];
}).toList(),
initialSelectedIds: context
.read<DeviceManagementBloc>()
.selectedDevices
.map((device) => device.uuid!)
.toList(),
isEmpty: devicesToShow.isEmpty,
),
),
),
)
],
);
},

View File

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

View File

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

View File

@ -10,6 +10,7 @@ class CeilingSensorModel {
String bodyMovement;
String noBodyTime;
int maxDistance;
SpaceTypes spaceType;
CeilingSensorModel({
required this.presenceState,
@ -20,6 +21,7 @@ class CeilingSensorModel {
required this.bodyMovement,
required this.noBodyTime,
required this.maxDistance,
required this.spaceType,
});
factory CeilingSensorModel.fromJson(List<Status> jsonList) {
@ -31,6 +33,7 @@ class CeilingSensorModel {
String _bodyMovement = 'none';
String _noBodyTime = 'none';
int _maxDis = 0;
SpaceTypes _spaceType = SpaceTypes.none;
try {
for (var status in jsonList) {
@ -38,17 +41,26 @@ class CeilingSensorModel {
case 'presence_state':
_presenceState = status.value ?? 'none';
break;
case 'scene':
_spaceType = getSpaceType(status.value ?? 'none');
break;
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;
case 'checking_result':
_checkingResult = status.value ?? '';
break;
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;
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;
case 'body_movement':
_bodyMovement = status.value ?? '';
@ -57,7 +69,9 @@ class CeilingSensorModel {
_noBodyTime = status.value ?? 'none';
break;
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;
}
}
@ -74,6 +88,31 @@ class CeilingSensorModel {
bodyMovement: _bodyMovement,
noBodyTime: _noBodyTime,
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
Widget build(BuildContext context) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
return BlocProvider(
@ -35,8 +36,8 @@ class CeilingSensorControls extends StatelessWidget
state is CeilingReportsLoadingState) {
return const Center(child: CircularProgressIndicator());
} else if (state is CeilingUpdateState) {
return _buildGridView(
context, state.ceilingSensorModel, isLarge, isMedium);
return _buildGridView(context, state.ceilingSensorModel,
isExtraLarge, isLarge, isMedium);
} else if (state is CeilingReportsState) {
return ReportsTable(
report: state.deviceReport,
@ -58,7 +59,8 @@ class CeilingSensorControls extends StatelessWidget
);
} else if (state is CeilingReportsFailedState) {
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'));
},
@ -67,13 +69,13 @@ class CeilingSensorControls extends StatelessWidget
}
Widget _buildGridView(BuildContext context, CeilingSensorModel model,
bool isLarge, bool isMedium) {
bool isExtraLarge, bool isLarge, bool isMedium) {
return GridView(
padding: const EdgeInsets.symmetric(horizontal: 50),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isLarge
crossAxisCount: isLarge || isExtraLarge
? 3
: isMedium
? 2
@ -96,15 +98,15 @@ class CeilingSensorControls extends StatelessWidget
postfix: 'm',
description: 'Detection Range',
),
const PresenceSpaceType(
listOfIcons: [
Assets.office,
Assets.parlour,
Assets.dyi,
Assets.bathroom,
Assets.bedroom,
],
PresenceSpaceType(
description: 'Space Type',
value: model.spaceType,
action: (String value) => context.read<CeilingSensorBloc>().add(
CeilingChangeValueEvent(
code: 'scene',
value: value,
),
),
),
PresenceUpdateData(
value: model.sensitivity.toDouble(),
@ -138,7 +140,7 @@ class CeilingSensorControls extends StatelessWidget
PresenceNoBodyTime(
value: model.noBodyTime,
title: 'Nobody Time:',
// description: 'hr',
description: '',
action: (String value) => context.read<CeilingSensorBloc>().add(
CeilingChangeValueEvent(
code: 'nobody_time',

View File

@ -34,9 +34,9 @@ class DoorLockView extends StatelessWidget {
} else if (state is UpdateState) {
return _buildStatusControls(context, state.smartDoorModel);
} else if (state is DoorLockControlError) {
return Center(child: Text(state.message));
return const SizedBox();
} 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
Widget build(BuildContext context) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
@ -29,7 +30,7 @@ class GateWayControls extends StatelessWidget with HelperResponsiveLayout {
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isLarge
crossAxisCount: isLarge || isExtraLarge
? 3
: isMedium
? 2
@ -45,7 +46,7 @@ class GateWayControls extends StatelessWidget with HelperResponsiveLayout {
},
);
} 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/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/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: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/utils/color_manager.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart';
class PresenceSpaceType extends StatelessWidget {
const PresenceSpaceType({
super.key,
required this.listOfIcons,
required this.description,
required this.value,
required this.action,
});
final List<String> listOfIcons;
final String description;
final SpaceTypes value;
final void Function(String value) action;
@override
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(
child: Column(
mainAxisSize: MainAxisSize.min,
@ -35,13 +47,28 @@ class PresenceSpaceType extends StatelessWidget {
Wrap(
runSpacing: 8,
spacing: 16,
children: [
...listOfIcons.map((icon) => SvgPicture.asset(
children: spaceTypeIcons.entries.map((entry) {
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,
width: 40,
height: 40,
)),
],
),
),
);
}).toList(),
),
],
),

View File

@ -23,6 +23,8 @@ class PresenceNoBodyTime extends StatefulWidget {
class _PresenceUpdateDataState extends State<PresenceNoBodyTime> {
late String _currentValue;
late String _numericValue;
late String _unit;
final List<String> nobodyTimeRange = [
'none',
@ -40,29 +42,45 @@ class _PresenceUpdateDataState extends State<PresenceNoBodyTime> {
void initState() {
super.initState();
_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) {
setState(() {
_currentValue = newValue;
_numericValue = _extractNumericValue(newValue);
_unit = _extractUnit(newValue);
});
widget.action(newValue);
}
void _incrementValue() {
int currentIndex = nobodyTimeRange.indexOf(_currentValue);
if (currentIndex < nobodyTimeRange.length - 1) {
setState(() {
_currentValue = nobodyTimeRange[currentIndex + 1];
});
_onValueChanged(_currentValue);
String newValue = nobodyTimeRange[currentIndex + 1];
_onValueChanged(newValue);
}
}
void _decrementValue() {
int currentIndex = nobodyTimeRange.indexOf(_currentValue);
if (currentIndex > 0) {
setState(() {
_currentValue = nobodyTimeRange[currentIndex - 1];
});
_onValueChanged(_currentValue);
String newValue = nobodyTimeRange[currentIndex - 1];
_onValueChanged(newValue);
}
}
@ -81,11 +99,12 @@ class _PresenceUpdateDataState extends State<PresenceNoBodyTime> {
fontSize: 10),
),
IncrementDecrementWidget(
value: _currentValue,
description: widget.description ?? '',
value: _numericValue,
description: _unit,
descriptionColor: ColorsManager.blackColor,
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 {
final DeviceReport report;
final String? thirdColumnTitle;
final String? thirdColumnDescription;
final Function(int index) onRowTap;
final VoidCallback onClose;
@ -14,6 +16,8 @@ class ReportsTable extends StatelessWidget {
required this.report,
required this.onRowTap,
required this.onClose,
this.thirdColumnTitle,
this.thirdColumnDescription,
});
@override
@ -32,12 +36,13 @@ class ReportsTable extends StatelessWidget {
children: [
TableRow(
decoration: BoxDecoration(color: Colors.grey.shade200),
children: const [
TableHeader(title: 'Date'),
TableHeader(title: 'Time'),
TableHeader(title: 'Status'),
children: [
const TableHeader(title: 'Date'),
const TableHeader(title: 'Time'),
TableHeader(title: thirdColumnTitle ?? 'Status'),
],
),
if (report.data != null)
...report.data!.asMap().entries.map((entry) {
int index = entry.key;
DeviceEvent data = entry.value;
@ -53,12 +58,12 @@ class ReportsTable extends StatelessWidget {
TableCellWidget(value: date),
TableCellWidget(value: time),
TableCellWidget(
value: data.value!,
value: '${data.value!} ${thirdColumnDescription ?? ''}',
onTap: () => onRowTap(index),
),
],
);
}).toList(),
}),
],
),
),

View File

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

View File

@ -1,7 +1,7 @@
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/living_room_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/cieling_light.dart';
import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/spot_light.dart';
import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/wall_light.dart';
mixin LivingRoomHelper {
Widget livingRoomControlWidgets(
@ -18,4 +18,3 @@ mixin LivingRoomHelper {
}
}
}

View File

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

View File

@ -2,16 +2,12 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/constants/assets.dart';
class CeilingLight extends StatelessWidget {
const CeilingLight(
{super.key,
required this.value,
required this.code,
required this.deviceId});
const CeilingLight({super.key, required this.value, required this.code, required this.deviceId});
final bool value;
final String code;

View File

@ -2,7 +2,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/constants/assets.dart';

View File

@ -2,16 +2,12 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/constants/assets.dart';
class SpotLight extends StatelessWidget {
const SpotLight(
{super.key,
required this.value,
required this.code,
required this.deviceId});
const SpotLight({super.key, required this.value, required this.code, required this.deviceId});
final bool value;
final String code;

View File

@ -2,16 +2,12 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/constants/assets.dart';
class WallLight extends StatelessWidget {
const WallLight(
{super.key,
required this.value,
required this.code,
required this.deviceId});
const WallLight({super.key, required this.value, required this.code, required this.deviceId});
final bool value;
final String code;

View File

@ -99,7 +99,7 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
try {
await DevicesManagementApi.getDeviceReports(deviceId, event.code)
.then((value) {
emit(DeviceReportsState(deviceReport: value));
emit(DeviceReportsState(deviceReport: value, code:event.code));
});
} catch (e) {
emit(DeviceReportsFailedState(error: e.toString()));

View File

@ -42,7 +42,8 @@ class DeviceReportsLoadingState extends WallSensorState {}
class DeviceReportsState extends WallSensorState {
final DeviceReport deviceReport;
const DeviceReportsState({required this.deviceReport});
final String code;
const DeviceReportsState({required this.deviceReport, required this.code});
}
class DeviceReportsFailedState extends WallSensorState {

View File

@ -21,6 +21,7 @@ class WallSensorControls extends StatelessWidget with HelperResponsiveLayout {
@override
Widget build(BuildContext context) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
return BlocProvider(
@ -32,11 +33,15 @@ class WallSensorControls extends StatelessWidget with HelperResponsiveLayout {
state is DeviceReportsLoadingState) {
return const Center(child: CircularProgressIndicator());
} else if (state is WallSensorUpdateState) {
return _buildGridView(
context, state.wallSensorModel, isLarge, isMedium);
return _buildGridView(context, state.wallSensorModel, isExtraLarge,
isLarge, isMedium);
} else if (state is DeviceReportsState) {
return ReportsTable(
report: state.deviceReport,
thirdColumnTitle:
state.code == 'illuminance_value' ? "Value" : 'Status',
thirdColumnDescription:
state.code == 'illuminance_value' ? "Lux" : null,
onRowTap: (index) {},
onClose: () {
context.read<WallSensorBloc>().add(BackToGridViewEvent());
@ -51,7 +56,8 @@ class WallSensorControls extends StatelessWidget with HelperResponsiveLayout {
);
} else if (state is DeviceReportsFailedState) {
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'));
},
@ -60,13 +66,13 @@ class WallSensorControls extends StatelessWidget with HelperResponsiveLayout {
}
Widget _buildGridView(BuildContext context, WallSensorModel model,
bool isLarge, bool isMedium) {
bool isExtraLarge, bool isLarge, bool isMedium) {
return GridView(
padding: const EdgeInsets.symmetric(horizontal: 50),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isLarge
crossAxisCount: isLarge || isExtraLarge
? 3
: isMedium
? 2

View File

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

View File

@ -17,3 +17,7 @@ class CreateNewNode extends HomeEvent {
@override
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:graphview/GraphView.dart';
import 'package:syncrow_web/pages/auth/model/user_model.dart';
abstract class HomeState extends Equatable {
const HomeState();
@ -25,9 +24,3 @@ class HomeUpdateTree extends HomeState {
@override
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_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_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});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with HelperResponsiveLayout {
@override
void initState() {
super.initState();
context.read<HomeBloc>().add(const FetchUserInfo());
}
@override
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_svg/svg.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_state.dart';
import 'package:syncrow_web/pages/home/view/home_card.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
@ -9,10 +10,14 @@ import 'package:syncrow_web/web_layout/web_scaffold.dart';
class HomeMobilePage extends StatelessWidget {
HomeMobilePage({super.key});
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return WebScaffold(
return PopScope(
canPop: false,
onPopInvoked: (didPop) => false,
child: WebScaffold(
enableMenuSideba: false,
appBarTitle: Row(
children: [
@ -22,9 +27,11 @@ class HomeMobilePage extends StatelessWidget {
),
],
),
scaffoldBody: BlocProvider(
create: (context) => HomeBloc(),
child: SizedBox(
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(
@ -34,7 +41,8 @@ class HomeMobilePage extends StatelessWidget {
SizedBox(height: size.height * 0.05),
const Text(
'ACCESS YOUR APPS',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w700),
style:
TextStyle(fontSize: 20, fontWeight: FontWeight.w700),
),
const SizedBox(height: 30),
Expanded(
@ -57,7 +65,8 @@ class HomeMobilePage extends StatelessWidget {
active: homeItems[index]['active'],
name: homeItems[index]['title'],
img: homeItems[index]['icon'],
onTap: () {},
onTap: () =>
homeBloc.homeItems[index].onPress(context),
);
},
),
@ -65,12 +74,13 @@ class HomeMobilePage extends StatelessWidget {
),
],
),
),
);
}),
),
);
}
dynamic homeItems = [
final dynamic homeItems = [
{
'title': 'Access',
'icon': Assets.accessIcon,

View File

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

View File

@ -1,16 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class SpaseManagementicon extends StatelessWidget {
const SpaseManagementicon({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(),
);
}
}

View File

@ -205,7 +205,6 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
print('errorMessage==$errorData');
emit(FailedState(errorMessage.toString()));
}
}
@ -220,8 +219,8 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
scheduleList: [
if (repeat)
Schedule(
effectiveTime: getTimeFromDateTimeString(expirationTime),
invalidTime: getTimeFromDateTimeString(effectiveTime).toString(),
effectiveTime: getTimeFromDateTimeString(effectiveTime),
invalidTime: getTimeFromDateTimeString(expirationTime).toString(),
workingDay: selectedDays,
),
],
@ -239,7 +238,6 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
print('errorMessage==$errorData');
emit(FailedState(errorMessage.toString()));
}
}
@ -260,7 +258,6 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
print('errorMessage==$errorData');
emit(FailedState(errorMessage.toString()));
}
}
@ -285,7 +282,6 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
print('errorMessage==$errorData');
emit(FailedState(errorMessage.toString()));
}
}
@ -322,7 +318,7 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
void filterDevices() {
final deviceName = deviceNameController.text.toLowerCase();
final deviceId = deviceIdController.text.toLowerCase();
final unitName = unitNameController.text.toLowerCase();
// final unitName = unitNameController.text.toLowerCase();
final filteredData = data.where((device) {
final matchesDeviceName = device.name.toLowerCase().contains(deviceName);
final matchesDeviceId = device.uuid.toLowerCase().contains(deviceId);
@ -332,7 +328,6 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
add(UpdateFilteredDevicesEvent(filteredData));
}
@override
Stream<VisitorPasswordState> mapEventToState(VisitorPasswordEvent event) async* {
if (event is FetchDevice) {
} else if (event is UpdateFilteredDevicesEvent) {
@ -417,7 +412,6 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
DateTime dateTime = inputFormat.parse(dateTimeString);
return dateTime;
} catch (e) {
print("Error parsing date: $e");
return null;
}
}
@ -454,6 +448,7 @@ class VisitorPasswordBloc extends Bloc<VisitorPasswordEvent, VisitorPasswordStat
<Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: const Text('OK'),

View File

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

View File

@ -87,7 +87,8 @@ class VisitorPasswordDialog extends StatelessWidget {
],
))
.then((v) {
Navigator.of(context).pop();
Navigator.of(context).pop(true);
});
} else if (state is FailedState) {
visitorBloc.stateDialog(
@ -211,28 +212,28 @@ class VisitorPasswordDialog extends StatelessWidget {
},
),
),
SizedBox(
width: size.width * 0.12,
child: RadioListTile<String>(
contentPadding: EdgeInsets.zero,
title: Text(
'Dynamic Password',
style: text,
),
value: 'Dynamic Password',
groupValue: (state is PasswordTypeSelected)
? state.selectedType
: visitorBloc.accessTypeSelected,
onChanged: (String? value) {
if (value != null) {
context
.read<VisitorPasswordBloc>()
.add(SelectPasswordType(value));
visitorBloc.usageFrequencySelected = '';
}
},
),
),
// SizedBox(
// width: size.width * 0.12,
// child: RadioListTile<String>(
// contentPadding: EdgeInsets.zero,
// title: Text(
// 'Dynamic Password',
// style: text,
// ),
// value: 'Dynamic Password',
// groupValue: (state is PasswordTypeSelected)
// ? state.selectedType
// : visitorBloc.accessTypeSelected,
// onChanged: (String? value) {
// if (value != null) {
// context
// .read<VisitorPasswordBloc>()
// .add(SelectPasswordType(value));
// visitorBloc.usageFrequencySelected = '';
// }
// },
// ),
// ),
],
)),
const Spacer(
@ -256,14 +257,14 @@ class VisitorPasswordDialog extends StatelessWidget {
color: ColorsManager.grayColor,
fontSize: 9),
),
if (visitorBloc.accessTypeSelected == 'Dynamic Password')
Text(
'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(
fontWeight: FontWeight.w400,
color: ColorsManager.grayColor,
fontSize: 9),
),
// if (visitorBloc.accessTypeSelected == 'Dynamic Password')
// Text(
// '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(
// fontWeight: FontWeight.w400,
// color: ColorsManager.grayColor,
// fontSize: 9),
// ),
const SizedBox(
height: 20,
)
@ -323,8 +324,7 @@ class VisitorPasswordDialog extends StatelessWidget {
: visitorBloc.usageFrequencySelected,
onChanged: (String? value) {
if (value != null) {
context
.read<VisitorPasswordBloc>()
context.read<VisitorPasswordBloc>()
.add(SelectUsageFrequency(value));
}
},
@ -344,7 +344,7 @@ class VisitorPasswordDialog extends StatelessWidget {
if (visitorBloc.usageFrequencySelected == 'One-Time' &&
visitorBloc.accessTypeSelected == 'Offline Password')
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(
color: ColorsManager.grayColor, fontSize: 9),
),
@ -380,11 +380,9 @@ class VisitorPasswordDialog extends StatelessWidget {
endTime: () {
if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Offline Password') {
visitorBloc.add(
SelectTimeEvent(context: context, isEffective: false));
visitorBloc.add(SelectTimeEvent(context: context, isEffective: false));
} else {
visitorBloc.add(SelectTimeVisitorPassword(
context: context, isStart: false, isRepeat: false));
visitorBloc.add(SelectTimeVisitorPassword(context: context, isStart: false, isRepeat: false));
}
},
startTime: () {
@ -398,13 +396,11 @@ class VisitorPasswordDialog extends StatelessWidget {
}
},
firstString: (visitorBloc.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected == 'Offline Password')
'Periodic' && visitorBloc.accessTypeSelected == 'Offline Password')
? visitorBloc.effectiveTime
: visitorBloc.startTimeAccess.toString(),
secondString: (visitorBloc.usageFrequencySelected ==
'Periodic' &&
visitorBloc.accessTypeSelected == 'Offline Password')
'Periodic' && visitorBloc.accessTypeSelected == 'Offline Password')
? visitorBloc.expirationTime
: visitorBloc.endTimeAccess.toString(),
icon: Assets.calendarIcon),
@ -528,10 +524,20 @@ class VisitorPasswordDialog extends StatelessWidget {
if (visitorBloc.usageFrequencySelected == 'One-Time' &&
visitorBloc.accessTypeSelected == 'Offline Password') {
setPasswordFunction(context, size, visitorBloc);
} else if (visitorBloc.accessTypeSelected == 'Dynamic Password') {
print('objectobjectobjectobject');
} else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Offline Password') {
if (visitorBloc.expirationTime != 'End Time' &&
visitorBloc.effectiveTime != 'Start Time' ) {
setPasswordFunction(context, size, visitorBloc);
} else {
}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 &&
visitorBloc.expirationTimeTimeStamp != null) {
if (isRepeat == true) {
@ -555,6 +561,11 @@ class VisitorPasswordDialog extends StatelessWidget {
message: 'Please select Access Period to continue',
title: 'Access Period');
}
}else{
visitorBloc.stateDialog(
context: context,
message: 'Please select Access Period to continue',
title: 'Access Period');
}
} else {
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,
child: Text(
'Ok',
@ -724,8 +684,6 @@ class VisitorPasswordDialog extends StatelessWidget {
borderRadius: 8,
onPressed: () {
Navigator.pop(context);
if (visitorBloc.accessTypeSelected == 'Dynamic Password') {
} else {
if (visitorBloc.usageFrequencySelected == 'One-Time' &&
visitorBloc.accessTypeSelected == 'Online Password') {
visitorBloc.add(OnlineOneTimePasswordEvent(
@ -733,7 +691,8 @@ class VisitorPasswordDialog extends StatelessWidget {
passwordName: visitorBloc.userNameController.text,
email: visitorBloc.emailController.text,
));
} else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
}
else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Online Password') {
visitorBloc.add(OnlineMultipleTimePasswordEvent(
passwordName: visitorBloc.userNameController.text,
@ -741,14 +700,16 @@ class VisitorPasswordDialog extends StatelessWidget {
effectiveTime: visitorBloc.effectiveTimeTimeStamp.toString(),
invalidTime: visitorBloc.expirationTimeTimeStamp.toString(),
));
} else if (visitorBloc.usageFrequencySelected == 'One-Time' &&
}
else if (visitorBloc.usageFrequencySelected == 'One-Time' &&
visitorBloc.accessTypeSelected == 'Offline Password') {
visitorBloc.add(OfflineOneTimePasswordEvent(
context: context,
passwordName: visitorBloc.userNameController.text,
email: visitorBloc.emailController.text,
));
} else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
}
else if (visitorBloc.usageFrequencySelected == 'Periodic' &&
visitorBloc.accessTypeSelected == 'Offline Password') {
visitorBloc.add(OfflineMultipleTimePasswordEvent(
passwordName: visitorBloc.userNameController.text,
@ -757,7 +718,6 @@ class VisitorPasswordDialog extends StatelessWidget {
invalidTime: visitorBloc.expirationTimeTimeStamp.toString(),
));
}
}
},
child: Text(
'Ok',

View File

@ -22,7 +22,6 @@ class AccessMangApi {
);
return response;
} catch (e) {
debugPrint('Error fetching visitor passwords: $e');
return [];
}
}
@ -42,7 +41,6 @@ class AccessMangApi {
);
return response;
} catch (e) {
debugPrint('Error fetching $e');
return [];
}
}
@ -123,13 +121,6 @@ class AccessMangApi {
String? effectiveTime,
String? invalidTime,
List<String>? devicesUuid}) async {
print(jsonEncode({
"email": email,
"devicesUuid": devicesUuid,
"passwordName": passwordName,
"effectiveTime": effectiveTime,
"invalidTime": invalidTime,
}));
final response = await HTTPService().post(
path: ApiEndpoints.sendOffLineMultipleTime,
body: jsonEncode({

View File

@ -25,7 +25,8 @@ class AuthenticationAPI {
path: ApiEndpoints.forgetPassword,
body: {"email": email, "password": password},
showServerMessage: true,
expectedResponseModel: (json) {});
expectedResponseModel: (json) {
});
return response;
}
@ -52,21 +53,17 @@ class AuthenticationAPI {
return cooldown;
}
} else {
debugPrint('Error: ${e.response!.statusCode} - ${e.response!.statusMessage}');
return 1;
}
} else {
debugPrint('Error: ${e.message}');
return 1;
}
} catch (e) {
debugPrint('Unexpected Error: $e');
return 1;
}
}
static Future verifyOtp({required String email, required String otpCode}) async {
try {
final response = await HTTPService().post(
path: ApiEndpoints.verifyOtp,
body: {"email": email, "type": "PASSWORD", "otpCode": otpCode},
@ -79,17 +76,7 @@ class AuthenticationAPI {
}
});
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 {

View File

@ -1,41 +1,35 @@
abstract class ApiEndpoints {
static const String baseUrl = 'https://syncrow-dev.azurewebsites.net';
// static const String baseUrl = 'http://100.107.182.63:4001'; //Localhost
//https://syncrow-staging.azurewebsites.net
////////////////////////////////////// Authentication ///////////////////////////////
static const String signUp = '$baseUrl/authentication/user/signup';
static const String login = '$baseUrl/authentication/user/login';
static const String forgetPassword =
'$baseUrl/authentication/user/forget-password';
static const String sendOtp = '$baseUrl/authentication/user/send-otp';
static const String verifyOtp = '$baseUrl/authentication/user/verify-otp';
static const String getRegion = '$baseUrl/region';
static const String visitorPassword = '$baseUrl/visitor-password';
static const String getDevices = '$baseUrl/visitor-password/devices';
import 'package:flutter_dotenv/flutter_dotenv.dart';
static const String sendOnlineOneTime =
'$baseUrl/visitor-password/temporary-password/online/one-time';
abstract class ApiEndpoints {
static String baseUrl = dotenv.env['BASE_URL'] ?? '';
static const String signUp = '/authentication/user/signup';
static const String login = '/authentication/user/login';
static const String forgetPassword = '/authentication/user/forget-password';
static const String sendOtp = '/authentication/user/send-otp';
static const String verifyOtp = '/authentication/user/verify-otp';
static const String getRegion = '/region';
static const String visitorPassword = '/visitor-password';
static const String getDevices = '/visitor-password/devices';
static const String sendOnlineOneTime = '/visitor-password/temporary-password/online/one-time';
static const String sendOnlineMultipleTime =
'$baseUrl/visitor-password/temporary-password/online/multiple-time';
'/visitor-password/temporary-password/online/multiple-time';
//offline Password
static const String sendOffLineOneTime =
'$baseUrl/visitor-password/temporary-password/offline/one-time';
static const String sendOffLineOneTime = '/visitor-password/temporary-password/offline/one-time';
static const String sendOffLineMultipleTime =
'$baseUrl/visitor-password/temporary-password/offline/multiple-time';
'/visitor-password/temporary-password/offline/multiple-time';
static const String getUser = '$baseUrl/user/{userUuid}';
static const String getUser = '/user/{userUuid}';
////// Devices Management ////////////////
static const String getAllDevices = '$baseUrl/device';
static const String getDeviceStatus =
'$baseUrl/device/{uuid}/functions/status';
static const String getAllDevices = '/device';
static const String getDeviceStatus = '/device/{uuid}/functions/status';
static const String deviceControl = '$baseUrl/device/{uuid}/control';
static const String deviceControl = '/device/{uuid}/control';
static const String gatewayApi = '/device/gateway/{gatewayUuid}/devices';
static const String openDoorLock = '/door-lock/open/{doorLockUuid}';
static const String getDeviceLogs =
'$baseUrl/device/report-logs/{uuid}?code={code}';
static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}';
}

View File

@ -14,7 +14,7 @@ class Assets {
static const String google = "assets/images/google.svg";
static const String facebook = "assets/images/facebook.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 spaseManagementIcon = "assets/images/spase_management_icon.svg";
static const String devicesIcon = "assets/images/devices_icon.svg";

View File

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

View File

@ -2,15 +2,25 @@ import 'package:flutter/material.dart';
mixin HelperResponsiveLayout {
bool isSmallScreenSize(BuildContext context) {
return MediaQuery.of(context).size.width < 700;
return MediaQuery.of(context).size.width < 600;
}
bool isMediumScreenSize(BuildContext context) {
return MediaQuery.of(context).size.width >= 700 &&
MediaQuery.of(context).size.width < 1400;
return MediaQuery.of(context).size.width >= 600 &&
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) {
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,35 +3,97 @@ 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_state.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 List<Widget>? body;
const WebAppBar({super.key, this.title, this.body});
final Widget? centerBody;
final Widget? rightBody;
const WebAppBar({super.key, this.title, this.centerBody, this.rightBody});
@override
Widget build(BuildContext context) {
bool isSmallScreen = isSmallScreenSize(context);
bool isHalfMediumScreen = isHafMediumScreenSize(context);
return BlocBuilder<HomeBloc, HomeState>(builder: (context, state) {
final user = context.read<HomeBloc>().user;
return Container(
height: 100,
height: (isSmallScreen || isHalfMediumScreen) ? 130 : 100,
decoration: const BoxDecoration(color: ColorsManager.secondaryColor),
padding: const EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
child: isSmallScreen || isHalfMediumScreen
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
if (title != null)
Align(
alignment: Alignment.centerLeft,
child: title!,
),
if (body != null)
if (centerBody != null)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: centerBody,
),
if (rightBody != null || user != null)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (rightBody != null) rightBody!,
Row(
children: [
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,
),
],
),
],
),
],
)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
flex: 2,
child: Wrap(
spacing: 15, // Adjust the spacing as needed
children: body!,
child: Row(
children: [
title!,
if (centerBody != null)
Padding(
padding: const EdgeInsets.only(left: 80),
child: centerBody!,
),
],
),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
if (rightBody != null)
Align(
alignment: Alignment.centerRight,
child: rightBody,
),
const SizedBox(
width: 10,
),
@ -51,13 +113,13 @@ class WebAppBar extends StatelessWidget {
const SizedBox(
width: 10,
),
if (HomeBloc.user != null)
if (user != null)
Text(
'${HomeBloc.user!.firstName.toString()} ${HomeBloc.user!.lastName.toString()} ',
'${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 {
final bool enableMenuSideba;
final Widget? appBarTitle;
final List<Widget>? appBarBody;
final Widget? centerBody;
final Widget? rightBody;
final Widget? scaffoldBody;
const WebScaffold(
{super.key,
const WebScaffold({
super.key,
this.appBarTitle,
this.appBarBody,
this.centerBody,
this.rightBody,
this.scaffoldBody,
this.enableMenuSideba = true});
this.enableMenuSideba = true,
});
@override
Widget build(BuildContext context) {
final isSmall = isSmallScreenSize(context);
@ -40,7 +43,8 @@ class WebScaffold extends StatelessWidget with HelperResponsiveLayout {
opacity: 0.7,
child: WebAppBar(
title: appBarTitle,
body: appBarBody,
centerBody: centerBody,
rightBody: rightBody,
)),
Expanded(
child: Row(

View File

@ -89,6 +89,22 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct main"
description:
@ -134,6 +150,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.1.5"
flutter_dotenv:
dependency: "direct main"
description:
name: flutter_dotenv
sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77"
url: "https://pub.dev"
source: hosted
version: "5.1.0"
flutter_lints:
dependency: "direct dev"
description:

View File

@ -43,9 +43,13 @@ dependencies:
get_it: ^7.6.7
flutter_secure_storage: ^9.2.2
shared_preferences: ^2.3.0
dropdown_button2: ^2.3.9
data_table_2: ^2.5.15
go_router:
intl: ^0.19.0
dropdown_search: ^5.0.6
flutter_dotenv: ^5.1.0
dev_dependencies:
flutter_test:
@ -75,6 +79,9 @@ flutter:
- assets/icons/
- assets/images/
- assets/
- .env.development
- .env.staging
- .env.production
# An image asset can refer to one or more resolution-specific "variants", see