Merged with dev

This commit is contained in:
Abdullah Alassaf
2025-02-06 00:57:29 +03:00
317 changed files with 19032 additions and 3484 deletions
+138
View File
@@ -0,0 +1,138 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class DialogDropdown extends StatefulWidget {
final List<String> items;
final ValueChanged<String> onSelected;
final String? selectedValue;
const DialogDropdown({
Key? key,
required this.items,
required this.onSelected,
this.selectedValue,
}) : super(key: key);
@override
_DialogDropdownState createState() => _DialogDropdownState();
}
class _DialogDropdownState extends State<DialogDropdown> {
bool _isOpen = false;
late OverlayEntry _overlayEntry;
@override
void initState() {
super.initState();
}
void _toggleDropdown() {
if (_isOpen) {
_closeDropdown();
} else {
_openDropdown();
}
}
void _openDropdown() {
_overlayEntry = _createOverlayEntry();
Overlay.of(context).insert(_overlayEntry);
_isOpen = true;
}
void _closeDropdown() {
_overlayEntry.remove();
_isOpen = false;
}
OverlayEntry _createOverlayEntry() {
final renderBox = context.findRenderObject() as RenderBox;
final size = renderBox.size;
final offset = renderBox.localToGlobal(Offset.zero);
return OverlayEntry(
builder: (context) {
return GestureDetector(
onTap: () {
_closeDropdown();
},
behavior: HitTestBehavior.translucent,
child: Stack(
children: [
Positioned(
left: offset.dx,
top: offset.dy + size.height,
width: size.width,
child: Material(
elevation: 4.0,
child: Container(
color: ColorsManager.whiteColors,
constraints: const BoxConstraints(
maxHeight: 200.0, // Set max height for dropdown
),
child: ListView.builder(
shrinkWrap: true,
itemCount: widget.items.length,
itemBuilder: (context, index) {
final item = widget.items[index];
return Container(
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
color: ColorsManager.lightGrayBorderColor,
width: 1.0,
),
),
),
child: ListTile(
title: Text(
item,
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
color: ColorsManager.textPrimaryColor,
),
),
onTap: () {
widget.onSelected(item);
_closeDropdown();
},
),
);
},
),
),
),
),
],
),
);
},
);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _toggleDropdown,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
decoration: BoxDecoration(
border: Border.all(color: ColorsManager.transparentColor),
borderRadius: BorderRadius.circular(8.0),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.selectedValue ?? 'Select an item',
style: Theme.of(context).textTheme.bodyMedium,
),
const Icon(Icons.arrow_drop_down),
],
),
),
);
}
}
+160
View File
@@ -0,0 +1,160 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class DialogTextfieldDropdown extends StatefulWidget {
final List<String> items;
final ValueChanged<String> onSelected;
final String? initialValue;
const DialogTextfieldDropdown({
Key? key,
required this.items,
required this.onSelected,
this.initialValue,
}) : super(key: key);
@override
_DialogTextfieldDropdownState createState() =>
_DialogTextfieldDropdownState();
}
class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
bool _isOpen = false;
late OverlayEntry _overlayEntry;
final TextEditingController _controller = TextEditingController();
late List<String> _filteredItems; // Filtered items list
@override
void initState() {
super.initState();
_controller.text = widget.initialValue ?? 'Select Tag';
_filteredItems = List.from(widget.items); // Initialize filtered items
}
void _toggleDropdown() {
if (_isOpen) {
_closeDropdown();
} else {
_openDropdown();
}
}
void _openDropdown() {
_overlayEntry = _createOverlayEntry();
Overlay.of(context).insert(_overlayEntry);
_isOpen = true;
}
void _closeDropdown() {
_overlayEntry.remove();
_isOpen = false;
}
OverlayEntry _createOverlayEntry() {
final renderBox = context.findRenderObject() as RenderBox;
final size = renderBox.size;
final offset = renderBox.localToGlobal(Offset.zero);
return OverlayEntry(
builder: (context) {
return GestureDetector(
onTap: () {
_closeDropdown();
},
behavior: HitTestBehavior.translucent,
child: Stack(
children: [
Positioned(
left: offset.dx,
top: offset.dy + size.height,
width: size.width,
child: Material(
elevation: 4.0,
child: Container(
color: ColorsManager.whiteColors,
constraints: const BoxConstraints(
maxHeight: 200.0,
),
child: ListView.builder(
shrinkWrap: true,
itemCount: _filteredItems.length,
itemBuilder: (context, index) {
final item = _filteredItems[index];
return Container(
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
color: ColorsManager.lightGrayBorderColor,
width: 1.0,
),
),
),
child: ListTile(
title: Text(item,
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
color: ColorsManager.textPrimaryColor)),
onTap: () {
_controller.text = item;
widget.onSelected(item);
setState(() {
_filteredItems
.remove(item); // Remove selected item
});
_closeDropdown();
},
),
);
},
),
),
),
),
],
),
);
},
);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _toggleDropdown,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
decoration: BoxDecoration(
border: Border.all(color: ColorsManager.transparentColor),
borderRadius: BorderRadius.circular(8.0),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: TextFormField(
controller: _controller,
onChanged: (value) {
setState(() {
_filteredItems = widget.items
.where((item) =>
item.toLowerCase().contains(value.toLowerCase()))
.toList(); // Filter items dynamically
});
widget.onSelected(value);
},
style: Theme.of(context).textTheme.bodyMedium,
decoration: const InputDecoration(
hintText: 'Enter or Select tag',
border: InputBorder.none,
),
),
),
const Icon(Icons.arrow_drop_down),
],
),
),
);
}
}
+39
View File
@@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class EditChip extends StatelessWidget {
final String label;
final VoidCallback onTap;
final Color labelColor;
final Color backgroundColor;
final Color borderColor;
final double borderRadius;
const EditChip({
Key? key,
this.label = 'Edit',
required this.onTap,
this.labelColor = ColorsManager.spaceColor,
this.backgroundColor = ColorsManager.whiteColors,
this.borderColor = ColorsManager.spaceColor,
this.borderRadius = 16.0,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Chip(
label: Text(
label,
style: Theme.of(context).textTheme.bodySmall!.copyWith(color: labelColor)
),
backgroundColor: backgroundColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(borderRadius),
side: BorderSide(color: borderColor),
),
),
);
}
}
@@ -46,14 +46,13 @@ class CustomSearchBar extends StatelessWidget {
filled: true,
fillColor: ColorsManager.textFieldGreyColor,
hintText: hintText,
hintStyle: TextStyle(
color: Color(0xB2999999),
fontSize: 12,
fontFamily: 'Aftika',
fontWeight: FontWeight.w400,
height: 0,
letterSpacing: -0.24,
),
hintStyle: Theme.of(context).textTheme.bodyLarge!.copyWith(
color: ColorsManager.lightGrayColor,
fontSize: 12,
fontWeight: FontWeight.w400,
height: 0,
letterSpacing: -0.24,
),
suffixIcon: Padding(
padding: const EdgeInsets.only(right: 16),
child: SvgPicture.asset(
+25
View File
@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
class SpacesSideTree extends StatefulWidget {
final List<CommunityModel> communities;
final String? selectedSpaceUuid;
const SpacesSideTree({
super.key,
required this.communities,
this.selectedSpaceUuid,
});
@override
State<SpacesSideTree> createState() => _SpacesSideTreeState();
}
class _SpacesSideTreeState extends State<SpacesSideTree> {
String _searchQuery = '';
String? _selectedSpaceUuid;
String? _selectedId;
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}
+8 -5
View File
@@ -6,7 +6,9 @@ import 'package:go_router/go_router.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/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
import 'package:syncrow_web/services/locator.dart';
import 'package:syncrow_web/utils/app_routes.dart';
@@ -15,8 +17,7 @@ import 'package:syncrow_web/utils/theme/theme.dart';
Future<void> main() async {
try {
const environment =
String.fromEnvironment('FLAVOR', defaultValue: 'development');
const environment = String.fromEnvironment('FLAVOR', defaultValue: 'development');
await dotenv.load(fileName: '.env.$environment');
WidgetsFlutterBinding.ensureInitialized();
initialSetup();
@@ -48,14 +49,16 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(),
),
BlocProvider<RoutineBloc>(
create: (context) => RoutineBloc(),
),
BlocProvider<SpaceTreeBloc>(
create: (context) => SpaceTreeBloc()..add(InitialEvent()),
),
],
child: MaterialApp.router(
debugShowCheckedModeBanner: false,
@@ -11,7 +11,7 @@ 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/color_manager.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
@@ -27,8 +27,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
final isLargeScreen = isLargeScreenSize(context);
final isSmallScreen = isSmallScreenSize(context);
final isHalfMediumScreen = isHafMediumScreenSize(context);
final padding =
isLargeScreen ? const EdgeInsets.all(30) : const EdgeInsets.all(15);
final padding = isLargeScreen ? const EdgeInsets.all(30) : const EdgeInsets.all(15);
return WebScaffold(
enableMenuSidebar: false,
@@ -40,8 +39,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
),
rightBody: const NavigateHomeGridView(),
scaffoldBody: BlocProvider(
create: (BuildContext context) =>
AccessBloc()..add(FetchTableData()),
create: (BuildContext context) => AccessBloc()..add(FetchTableData()),
child: BlocConsumer<AccessBloc, AccessState>(
listener: (context, state) {},
builder: (context, state) {
@@ -95,14 +93,11 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
return [
item.passwordName,
item.passwordType.value,
accessBloc
.timestampToDate(item.effectiveTime),
accessBloc
.timestampToDate(item.invalidTime),
accessBloc.timestampToDate(item.effectiveTime),
accessBloc.timestampToDate(item.invalidTime),
item.deviceName.toString(),
item.authorizerEmail.toString(),
accessBloc
.timestampToDate(item.invalidTime),
accessBloc.timestampToDate(item.invalidTime),
item.passwordStatus.value,
];
}).toList(),
@@ -113,8 +108,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
})));
}
Wrap _buildVisitorAdminPasswords(
BuildContext context, AccessBloc accessBloc) {
Wrap _buildVisitorAdminPasswords(BuildContext context, AccessBloc accessBloc) {
return Wrap(
spacing: 10,
runSpacing: 10,
@@ -140,23 +134,22 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
borderRadius: 8,
child: Text(
'Create Visitor Password ',
style: context.textTheme.titleSmall!
.copyWith(color: Colors.white, fontSize: 12),
)),
),
Container(
width: 133,
height: 42,
decoration: containerDecoration,
child: DefaultButton(
borderRadius: 8,
backgroundColor: ColorsManager.whiteColors,
child: Text(
'Admin Password',
style: context.textTheme.titleSmall!
.copyWith(color: Colors.black, fontSize: 12),
style: context.textTheme.titleSmall!.copyWith(color: Colors.white, fontSize: 12),
)),
),
// Container(
// width: 133,
// height: 42,
// decoration: containerDecoration,
// child: DefaultButton(
// borderRadius: 8,
// backgroundColor: ColorsManager.whiteColors,
// child: Text(
// 'Admin Password',
// style: context.textTheme.titleSmall!
// .copyWith(color: Colors.black, fontSize: 12),
// )),
// ),
],
);
}
@@ -179,10 +172,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
description: '',
onSubmitted: (value) {
accessBloc.add(FilterDataEvent(
emailAuthorizer:
accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
@@ -200,10 +191,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
description: '',
onSubmitted: (value) {
accessBloc.add(FilterDataEvent(
emailAuthorizer:
accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
@@ -232,8 +221,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
onSearch: () {
accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
@@ -261,10 +249,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
description: '',
onSubmitted: (value) {
accessBloc.add(FilterDataEvent(
emailAuthorizer:
accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
@@ -288,8 +274,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
onSearch: () {
accessBloc.add(FilterDataEvent(
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
selectedTabIndex:
BlocProvider.of<AccessBloc>(context).selectedIndex,
selectedTabIndex: BlocProvider.of<AccessBloc>(context).selectedIndex,
passwordName: accessBloc.passwordName.text.toLowerCase(),
startTime: accessBloc.effectiveTimeTimeStamp,
endTime: accessBloc.expirationTimeTimeStamp));
+18 -11
View File
@@ -54,7 +54,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
try {
forgetEmailValidate = '';
_remainingTime = (await AuthenticationAPI.sendOtp(
email: forgetEmailController.text, regionUuid: regionUuid))!;
email: forgetEmailController.text,
))!;
} on DioException catch (e) {
if (e.response!.statusCode == 400) {
final errorData = e.response!.data;
@@ -138,7 +139,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
final TextEditingController loginEmailController = TextEditingController();
final TextEditingController loginPasswordController = TextEditingController();
final loginFormKey = GlobalKey<FormState>();
bool isChecked = false;
bool isChecked = true;
bool obscureText = true;
String newPassword = '';
String maskedEmail = '';
@@ -146,7 +147,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
String validate = '';
String forgetValidate = '';
String forgetEmailValidate = '';
String regionUuid = '';
// String regionUuid = '';
static Token token = Token.emptyConstructor();
static UserModel? user;
bool showValidationMessage = false;
@@ -163,16 +164,23 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
token = await AuthenticationAPI.loginWithEmail(
model: LoginWithEmailModel(
email: event.username,
password: event.password,
regionUuid: event.regionUuid),
email: event.username,
password: event.password,
),
);
} catch (failure) {
validate = 'Invalid Credentials!';
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['error']['message'];
if (errorMessage == "Access denied for web platform") {
validate = errorMessage;
} else {
validate = 'Invalid Credentials!';
}
emit(LoginInitial());
return;
}
if (token.accessTokenIsNotEmpty) {
FlutterSecureStorage storage = const FlutterSecureStorage();
await storage.write(
@@ -376,7 +384,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
Future selectRegion(SelectRegionEvent event, Emitter<AuthState> emit) async {
try {
emit(AuthLoading());
regionUuid = event.val;
// regionUuid = event.val;
add(CheckEnableEvent());
emit(AuthInitialState());
} catch (e) {
@@ -411,8 +419,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(AuthLoading());
checkValidate = isChecked == true &&
loginPasswordController.text.isNotEmpty &&
loginEmailController.text.isNotEmpty &&
regionUuid != '';
loginEmailController.text.isNotEmpty;
emit(LoginInitial());
return checkValidate;
}
+5 -9
View File
@@ -10,16 +10,16 @@ abstract class AuthEvent extends Equatable {
class LoginButtonPressed extends AuthEvent {
final String username;
final String password;
final String regionUuid;
// final String regionUuid;
const LoginButtonPressed({
required this.username,
required this.password,
required this.regionUuid,
// required this.regionUuid,
});
@override
List<Object> get props => [username, password, regionUuid];
List<Object> get props => [username, password];
}
class CheckBoxEvent extends AuthEvent {
@@ -49,13 +49,9 @@ class UpdateTimerEvent extends AuthEvent {
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;
@@ -1,19 +1,19 @@
class LoginWithEmailModel {
final String email;
final String password;
final String regionUuid;
// final String regionUuid;
LoginWithEmailModel({
required this.email,
required this.password,
required this.regionUuid,
// required this.regionUuid,
});
factory LoginWithEmailModel.fromJson(Map<String, dynamic> json) {
return LoginWithEmailModel(
email: json['email'],
password: json['password'],
regionUuid: json['regionUuid'],
// regionUuid: json['regionUuid'],
);
}
@@ -21,7 +21,9 @@ class LoginWithEmailModel {
return {
'email': email,
'password': password,
'regionUuid': regionUuid,
"platform": "web"
// 'regionUuid': regionUuid,
};
}
}
//[email protected]
+38
View File
@@ -10,6 +10,10 @@ class UserModel {
final String? phoneNumber;
final bool? isEmailVerified;
final bool? isAgreementAccepted;
final bool? hasAcceptedWebAgreement;
final DateTime? webAgreementAcceptedAt;
final UserRole? role;
UserModel({
required this.uuid,
required this.email,
@@ -19,6 +23,9 @@ class UserModel {
required this.phoneNumber,
required this.isEmailVerified,
required this.isAgreementAccepted,
required this.hasAcceptedWebAgreement,
required this.webAgreementAcceptedAt,
required this.role,
});
factory UserModel.fromJson(Map<String, dynamic> json) {
@@ -31,6 +38,11 @@ class UserModel {
phoneNumber: json['phoneNumber'],
isEmailVerified: json['isEmailVerified'],
isAgreementAccepted: json['isAgreementAccepted'],
hasAcceptedWebAgreement: json['hasAcceptedWebAgreement'],
webAgreementAcceptedAt: json['webAgreementAcceptedAt'] != null
? DateTime.parse(json['webAgreementAcceptedAt'])
: null,
role: json['role'] != null ? UserRole.fromJson(json['role']) : null,
);
}
@@ -41,6 +53,9 @@ class UserModel {
Map<String, dynamic> tempJson = Token.decodeToken(token.accessToken);
return UserModel(
hasAcceptedWebAgreement: null,
role: null,
webAgreementAcceptedAt: null,
uuid: tempJson['uuid'].toString(),
email: tempJson['email'],
firstName: null,
@@ -65,3 +80,26 @@ class UserModel {
};
}
}
class UserRole {
final String uuid;
final DateTime createdAt;
final DateTime updatedAt;
final String type;
UserRole({
required this.uuid,
required this.createdAt,
required this.updatedAt,
required this.type,
});
factory UserRole.fromJson(Map<String, dynamic> json) {
return UserRole(
uuid: json['uuid'],
createdAt: DateTime.parse(json['createdAt']),
updatedAt: DateTime.parse(json['updatedAt']),
type: json['type'],
);
}
}
@@ -124,18 +124,18 @@ class ForgetPasswordWebPage extends StatelessWidget {
.copyWith(fontSize: 14, fontWeight: FontWeight.w400),
),
const SizedBox(height: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
const SizedBox(height: 10),
Form(
key: forgetBloc.forgetRegionKey,
child: SizedBox(
child:
_buildDropdownField(context, forgetBloc, size)))
],
),
// Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.start,
// children: [
// const SizedBox(height: 10),
// Form(
// key: forgetBloc.forgetRegionKey,
// child: SizedBox(
// child:
// _buildDropdownField(context, forgetBloc, size)))
// ],
// ),
const SizedBox(height: 20),
Form(
key: forgetBloc.forgetEmailKey,
@@ -459,9 +459,9 @@ class ForgetPasswordWebPage extends StatelessWidget {
),
);
}).toList(),
value: loginBloc.regionList!.any((region) => region.id == loginBloc.regionUuid)
? loginBloc.regionUuid
: null,
// value: loginBloc.regionList!.any((region) => region.id == loginBloc.regionUuid)
// ? loginBloc.regionUuid
// : null,
onChanged: (String? value) {
if (value != null) {
loginBloc.add(SelectRegionEvent(val: value));
+43 -41
View File
@@ -6,7 +6,7 @@ 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';
import 'package:syncrow_web/pages/auth/model/region_model.dart';
// import 'package:syncrow_web/pages/auth/model/region_model.dart';
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';
@@ -112,44 +112,44 @@ class LoginMobilePage extends StatelessWidget {
color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 30),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Country/Region",
style: Theme.of(context).textTheme.bodySmall,
),
SizedBox(
child: DropdownButtonFormField<String>(
validator: loginBloc.validateRegion,
icon: const Icon(
Icons.keyboard_arrow_down_outlined,
),
decoration: textBoxDecoration()!.copyWith(
hintText: null,
),
hint: const Align(
alignment: Alignment.centerLeft,
child: Text(
'Select your region/country',
textAlign: TextAlign.center,
),
),
isDense: true,
style: const TextStyle(color: Colors.black),
items: loginBloc.regionList!.map((RegionModel region) {
return DropdownMenuItem<String>(
value: region.name,
child: Text(region.name),
);
}).toList(),
onChanged: (String? value) {},
),
)
],
),
const SizedBox(height: 20.0),
// Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.start,
// children: [
// Text(
// "Country/Region",
// style: Theme.of(context).textTheme.bodySmall,
// ),
// SizedBox(
// child: DropdownButtonFormField<String>(
// validator: loginBloc.validateRegion,
// icon: const Icon(
// Icons.keyboard_arrow_down_outlined,
// ),
// decoration: textBoxDecoration()!.copyWith(
// hintText: null,
// ),
// hint: const Align(
// alignment: Alignment.centerLeft,
// child: Text(
// 'Select your region/country',
// textAlign: TextAlign.center,
// ),
// ),
// isDense: true,
// style: const TextStyle(color: Colors.black),
// items: loginBloc.regionList!.map((RegionModel region) {
// return DropdownMenuItem<String>(
// value: region.name,
// child: Text(region.name),
// );
// }).toList(),
// onChanged: (String? value) {},
// ),
// )
// ],
// ),
// const SizedBox(height: 20.0),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
@@ -207,7 +207,10 @@ class LoginMobilePage extends StatelessWidget {
},
child: Text(
"Forgot Password?",
style: Theme.of(context).textTheme.bodySmall,
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: ColorsManager.blackColor),
),
),
],
@@ -276,7 +279,6 @@ class LoginMobilePage extends StatelessWidget {
if (loginBloc.loginFormKey.currentState!.validate()) {
loginBloc.add(
LoginButtonPressed(
regionUuid: '',
username: loginBloc.loginEmailController.text,
password: loginBloc.loginPasswordController.text,
),
+10 -10
View File
@@ -155,16 +155,16 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
children: <Widget>[
const SizedBox(height: 40),
Text('Login', style: Theme.of(context).textTheme.headlineLarge),
SizedBox(height: size.height * 0.03),
_buildDropdownField(context, loginBloc, size),
// SizedBox(height: size.height * 0.03),
// _buildDropdownField(context, loginBloc, size),
const SizedBox(height: 20.0),
_buildEmailField(context, loginBloc),
const SizedBox(height: 20.0),
_buildPasswordField(context, loginBloc),
const SizedBox(height: 20),
_buildForgotPassword(context),
const SizedBox(height: 20),
_buildCheckbox(context, loginBloc, size),
// const SizedBox(height: 20),
// _buildCheckbox(context, loginBloc, size),
const SizedBox(height: 20.0),
_buildSignInButton(context, loginBloc, size),
const SizedBox(height: 15.0),
@@ -219,11 +219,11 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
),
);
}).toList(),
value: loginBloc.regionList!.any(
(region) => region.id == loginBloc.regionUuid,
)
? loginBloc.regionUuid
: null,
// value: loginBloc.regionList!.any(
// (region) => region.id == loginBloc.regionUuid,
// )
// ? loginBloc.regionUuid
// : null,
onChanged: (String? value) {
if (value != null) {
loginBloc.add(CheckEnableEvent());
@@ -462,7 +462,7 @@ class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout
onPressed: () {
if (loginBloc.loginFormKey.currentState!.validate()) {
loginBloc.add(LoginButtonPressed(
regionUuid: loginBloc.regionUuid,
// regionUuid: loginBloc.regionUuid,
username: loginBloc.loginEmailController.text,
password: loginBloc.loginPasswordController.text,
));
+8 -3
View File
@@ -19,12 +19,14 @@ class DefaultButton extends StatelessWidget {
this.padding,
this.borderColor,
this.elevation,
this.borderWidth = 1.0,
});
final void Function()? onPressed;
final Widget child;
final double? height;
final bool isSecondary;
final double? borderRadius;
final double borderWidth;
final bool enabled;
final double? padding;
final bool isDone;
@@ -66,13 +68,16 @@ class DefaultButton extends StatelessWidget {
}),
shape: WidgetStateProperty.all(
RoundedRectangleBorder(
side: BorderSide(color: borderColor ?? Colors.transparent),
side: BorderSide(
color: borderColor ?? Colors.transparent,
width: borderWidth,
),
borderRadius: BorderRadius.circular(borderRadius ?? 20),
),
),
fixedSize: height != null
? WidgetStateProperty.all(Size.fromHeight(height!))
: null,
? WidgetStateProperty.all(Size.fromHeight(height!))
: null,
padding: WidgetStateProperty.all(
EdgeInsets.all(padding ?? 10),
),
+1 -1
View File
@@ -12,7 +12,7 @@ Future<void> showCustomDialog({
double? iconWidth,
VoidCallback? onOkPressed,
bool barrierDismissible = false,
required List<Widget> actions,
List<Widget>? actions,
}) {
return showDialog(
context: context,
@@ -8,7 +8,7 @@ import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/ba
import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
@@ -26,7 +26,8 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
return BlocProvider(
create: (context) => AcBloc(deviceId: devicesIds.first)..add(AcFetchBatchStatusEvent(devicesIds)),
create: (context) =>
AcBloc(deviceId: devicesIds.first)..add(AcFetchBatchStatusEvent(devicesIds)),
child: BlocBuilder<AcBloc, AcsState>(
builder: (context, state) {
if (state is ACStatusLoaded) {
@@ -98,7 +99,8 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo
),
Text(
'h',
style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor),
style:
context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor),
),
Text(
'30',
@@ -107,7 +109,9 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo
fontWeight: FontWeight.bold,
),
),
Text('m', style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor)),
Text('m',
style: context.textTheme.bodySmall!
.copyWith(color: ColorsManager.blackColor)),
IconButton(
onPressed: () {},
icon: const Icon(
@@ -138,7 +142,7 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo
));
},
),
FirmwareUpdateWidget(deviceId: devicesIds.first, version: 5),
// FirmwareUpdateWidget(deviceId: devicesIds.first, version: 5),
FactoryResetWidget(
callFactoryReset: () {
context.read<AcBloc>().add(AcFactoryResetEvent(
@@ -1,13 +1,14 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
part 'device_managment_event.dart';
part 'device_managment_state.dart';
class DeviceManagementBloc
extends Bloc<DeviceManagementEvent, DeviceManagementState> {
class DeviceManagementBloc extends Bloc<DeviceManagementEvent, DeviceManagementState> {
int _selectedIndex = 0;
List<AllDevicesModel> _devices = [];
int _onlineCount = 0;
@@ -30,11 +31,23 @@ class DeviceManagementBloc
on<UpdateSelection>(_onUpdateSelection);
}
Future<void> _onFetchDevices(
FetchDevices event, Emitter<DeviceManagementState> emit) async {
Future<void> _onFetchDevices(FetchDevices event, Emitter<DeviceManagementState> emit) async {
emit(DeviceManagementLoading());
try {
final devices = await DevicesManagementApi().fetchDevices();
List<AllDevicesModel> devices = [];
_devices.clear();
var spaceBloc = event.context.read<SpaceTreeBloc>();
if (spaceBloc.state.selectedCommunities.isEmpty) {
devices = await DevicesManagementApi().fetchDevices('', '');
} else {
for (var community in spaceBloc.state.selectedCommunities) {
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[community] ?? [];
for (var space in spacesList) {
devices.addAll(await DevicesManagementApi().fetchDevices(community, space));
}
}
}
_selectedDevices.clear();
_devices = devices;
_filteredDevices = devices;
@@ -53,8 +66,7 @@ class DeviceManagementBloc
}
}
void _onFilterDevices(
FilterDevices event, Emitter<DeviceManagementState> emit) async {
void _onFilterDevices(FilterDevices event, Emitter<DeviceManagementState> emit) async {
if (_devices.isNotEmpty) {
_filteredDevices = List.from(_devices.where((device) {
switch (event.filter) {
@@ -85,8 +97,7 @@ class DeviceManagementBloc
}
}
Future<void> _onResetFilters(
ResetFilters event, Emitter<DeviceManagementState> emit) async {
Future<void> _onResetFilters(ResetFilters event, Emitter<DeviceManagementState> emit) async {
currentProductName = '';
_selectedDevices.clear();
_filteredDevices = List.from(_devices);
@@ -102,8 +113,7 @@ class DeviceManagementBloc
));
}
void _onResetSelectedDevices(
ResetSelectedDevices event, Emitter<DeviceManagementState> emit) {
void _onResetSelectedDevices(ResetSelectedDevices event, Emitter<DeviceManagementState> emit) {
_selectedDevices.clear();
if (state is DeviceManagementLoaded) {
@@ -129,14 +139,12 @@ class DeviceManagementBloc
}
}
void _onSelectedFilterChanged(
SelectedFilterChanged event, Emitter<DeviceManagementState> emit) {
void _onSelectedFilterChanged(SelectedFilterChanged event, Emitter<DeviceManagementState> emit) {
_selectedIndex = event.selectedIndex;
add(FilterDevices(_getFilterFromIndex(_selectedIndex)));
}
void _onSelectDevice(
SelectDevice event, Emitter<DeviceManagementState> emit) {
void _onSelectDevice(SelectDevice event, Emitter<DeviceManagementState> emit) {
final selectedUuid = event.selectedDevice.uuid;
if (_selectedDevices.any((device) => device.uuid == selectedUuid)) {
@@ -147,8 +155,7 @@ class DeviceManagementBloc
List<AllDevicesModel> clonedSelectedDevices = List.from(_selectedDevices);
bool isControlButtonEnabled =
_checkIfControlButtonEnabled(clonedSelectedDevices);
bool isControlButtonEnabled = _checkIfControlButtonEnabled(clonedSelectedDevices);
if (state is DeviceManagementLoaded) {
emit(DeviceManagementLoaded(
@@ -157,8 +164,7 @@ class DeviceManagementBloc
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
selectedDevice:
clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null,
selectedDevice: clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null,
isControlButtonEnabled: isControlButtonEnabled,
));
} else if (state is DeviceManagementFiltered) {
@@ -168,15 +174,13 @@ class DeviceManagementBloc
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
selectedDevice:
clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null,
selectedDevice: clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null,
isControlButtonEnabled: isControlButtonEnabled,
));
}
}
void _onUpdateSelection(
UpdateSelection event, Emitter<DeviceManagementState> emit) {
void _onUpdateSelection(UpdateSelection event, Emitter<DeviceManagementState> emit) {
List<AllDevicesModel> selectedDevices = [];
List<AllDevicesModel> devicesToSelectFrom = [];
@@ -219,8 +223,7 @@ class DeviceManagementBloc
bool _checkIfControlButtonEnabled(List<AllDevicesModel> selectedDevices) {
if (selectedDevices.length > 1) {
final productTypes =
selectedDevices.map((device) => device.productType).toSet();
final productTypes = selectedDevices.map((device) => device.productType).toSet();
return productTypes.length == 1;
} else if (selectedDevices.length == 1) {
return true;
@@ -231,10 +234,8 @@ class DeviceManagementBloc
void _calculateDeviceCounts() {
_onlineCount = _devices.where((device) => device.online == true).length;
_offlineCount = _devices.where((device) => device.online == false).length;
_lowBatteryCount = _devices
.where((device) =>
device.batteryLevel != null && device.batteryLevel! < 20)
.length;
_lowBatteryCount =
_devices.where((device) => device.batteryLevel != null && device.batteryLevel! < 20).length;
}
String _getFilterFromIndex(int index) {
@@ -250,8 +251,7 @@ class DeviceManagementBloc
}
}
void _onSearchDevices(
SearchDevices event, Emitter<DeviceManagementState> emit) {
void _onSearchDevices(SearchDevices event, Emitter<DeviceManagementState> emit) {
if ((event.community == null || event.community!.isEmpty) &&
(event.unitName == null || event.unitName!.isEmpty) &&
(event.productName == null || event.productName!.isEmpty)) {
@@ -280,33 +280,22 @@ class DeviceManagementBloc
final filteredDevices = devicesToSearch.where((device) {
final matchesCommunity = event.community == null ||
event.community!.isEmpty ||
(device.community?.name
?.toLowerCase()
.contains(event.community!.toLowerCase()) ??
(device.community?.name?.toLowerCase().contains(event.community!.toLowerCase()) ??
false);
final matchesUnit = event.unitName == null ||
event.unitName!.isEmpty ||
(device.spaces != null &&
device.spaces!.isNotEmpty &&
device.spaces![0].spaceName
!.toLowerCase()
.contains(event.unitName!.toLowerCase()));
device.spaces![0].spaceName!.toLowerCase().contains(event.unitName!.toLowerCase()));
final matchesProductName = event.productName == null ||
event.productName!.isEmpty ||
(device.name
?.toLowerCase()
.contains(event.productName!.toLowerCase()) ??
false);
(device.name?.toLowerCase().contains(event.productName!.toLowerCase()) ?? false);
final matchesDeviceName = event.productName == null ||
event.productName!.isEmpty ||
(device.categoryName
?.toLowerCase()
.contains(event.productName!.toLowerCase()) ??
(device.categoryName?.toLowerCase().contains(event.productName!.toLowerCase()) ??
false);
return matchesCommunity &&
matchesUnit &&
(matchesProductName || matchesDeviceName);
return matchesCommunity && matchesUnit && (matchesProductName || matchesDeviceName);
}).toList();
emit(DeviceManagementFiltered(
@@ -7,7 +7,15 @@ abstract class DeviceManagementEvent extends Equatable {
List<Object?> get props => [];
}
class FetchDevices extends DeviceManagementEvent {}
class FetchDevices extends DeviceManagementEvent {
// final Map<String, List<String>> selectedCommunitiesSpaces;
// final String spaceId;
final BuildContext context;
const FetchDevices(this.context);
@override
List<Object?> get props => [context];
}
class FilterDevices extends DeviceManagementEvent {
final String filter;
@@ -0,0 +1,47 @@
class DeviceSubspace {
final String uuid;
final DateTime? createdAt;
final DateTime? updatedAt;
final String subspaceName;
final bool disabled;
DeviceSubspace({
required this.uuid,
this.createdAt,
this.updatedAt,
required this.subspaceName,
required this.disabled,
});
factory DeviceSubspace.fromJson(Map<String, dynamic> json) {
return DeviceSubspace(
uuid: json['uuid'] as String,
createdAt: json['createdAt'] != null
? DateTime.tryParse(json['createdAt'].toString())
: null,
updatedAt: json['updatedAt'] != null
? DateTime.tryParse(json['updatedAt'].toString())
: null,
subspaceName: json['subspaceName'] as String,
disabled: json['disabled'] as bool,
);
}
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'createdAt': createdAt?.toIso8601String(),
'updatedAt': updatedAt?.toIso8601String(),
'subspaceName': subspaceName,
'disabled': disabled,
};
}
static List<DeviceSubspace> listFromJson(List<dynamic> jsonList) {
return jsonList.map((json) => DeviceSubspace.fromJson(json)).toList();
}
static List<Map<String, dynamic>> listToJson(List<DeviceSubspace> subspaces) {
return subspaces.map((subspace) => subspace.toJson()).toList();
}
}
@@ -1,12 +1,13 @@
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_community.model.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_space_model.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_subspace.model.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart';
import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart';
import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/three_gang_switch/three_gang_switch.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/two_gang_switch/two_gang_switch.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/enum/device_types.dart';
@@ -47,6 +48,7 @@ class AllDevicesModel {
*/
DevicesModelRoom? room;
DeviceSubspace? subspace;
DevicesModelUnit? unit;
DeviceCommunityModel? community;
String? productUuid;
@@ -77,6 +79,7 @@ class AllDevicesModel {
AllDevicesModel({
this.room,
this.subspace,
this.unit,
this.community,
this.productUuid,
@@ -110,6 +113,9 @@ class AllDevicesModel {
room = (json['room'] != null && (json['room'] is Map))
? DevicesModelRoom.fromJson(json['room'])
: null;
subspace = (json['subspace'] != null && (json['subspace'] is Map))
? DeviceSubspace.fromJson(json['subspace'])
: null;
unit = (json['unit'] != null && (json['unit'] is Map))
? DevicesModelUnit.fromJson(json['unit'])
: null;
@@ -142,9 +148,7 @@ class AllDevicesModel {
productName = json['productName']?.toString();
if (json['spaces'] != null && json['spaces'] is List) {
spaces = (json['spaces'] as List)
.map((space) => DeviceSpaceModel.fromJson(space))
.toList();
spaces = (json['spaces'] as List).map((space) => DeviceSpaceModel.fromJson(space)).toList();
}
}
@@ -192,8 +196,7 @@ SOS
String tempIcon = '';
if (type == DeviceType.LightBulb) {
tempIcon = Assets.lightBulb;
} else if (type == DeviceType.CeilingSensor ||
type == DeviceType.WallSensor) {
} else if (type == DeviceType.CeilingSensor || type == DeviceType.WallSensor) {
tempIcon = Assets.sensors;
} else if (type == DeviceType.AC) {
tempIcon = Assets.ac;
@@ -248,34 +251,25 @@ SOS
case '1G':
return [
OneGangSwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
OneGangCountdownFunction(
deviceId: uuid ?? '', deviceName: name ?? ''),
OneGangCountdownFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
];
case '2G':
return [
TwoGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangCountdown1Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangCountdown2Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangCountdown1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
TwoGangCountdown2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
];
case '3G':
return [
ThreeGangSwitch1Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangSwitch2Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangSwitch3Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangCountdown1Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangCountdown2Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangCountdown3Function(
deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangSwitch1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangSwitch2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangSwitch3Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangCountdown1Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangCountdown2Function(deviceId: uuid ?? '', deviceName: name ?? ''),
ThreeGangCountdown3Function(deviceId: uuid ?? '', deviceName: name ?? ''),
];
default:
@@ -288,6 +282,9 @@ SOS
if (room != null) {
data['room'] = room!.toJson();
}
if (subspace != null) {
data['subspace'] = subspace!.toJson();
}
if (unit != null) {
data['unit'] = unit!.toJson();
}
@@ -330,6 +327,7 @@ SOS
return other is AllDevicesModel &&
other.room == room &&
other.subspace == subspace &&
other.unit == unit &&
other.productUuid == productUuid &&
other.productType == productType &&
@@ -360,6 +358,7 @@ SOS
@override
int get hashCode {
return room.hashCode ^
subspace.hashCode ^
unit.hashCode ^
productUuid.hashCode ^
productType.hashCode ^
@@ -3,9 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_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/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart';
import 'package:syncrow_web/pages/routiens/view/routines_view.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart';
import 'package:syncrow_web/pages/routines/view/routines_view.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@@ -19,7 +19,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => DeviceManagementBloc()..add(FetchDevices()),
create: (context) => DeviceManagementBloc()..add(FetchDevices(context)),
),
],
child: WebScaffold(
@@ -80,7 +80,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
return BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
builder: (context, deviceState) {
if (deviceState is DeviceManagementLoading) {
return const Center(child: CircularProgressIndicator());
return const DeviceManagementBody(devices: []);
} else if (deviceState is DeviceManagementLoaded) {
return DeviceManagementBody(devices: deviceState.devices);
} else if (deviceState is DeviceManagementFiltered) {
@@ -8,6 +8,8 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
import 'package:syncrow_web/utils/format_date_time.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/utils/style.dart';
@@ -59,118 +61,153 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
final buttonLabel = (selectedDevices.length > 1) ? 'Batch Control' : 'Control';
return Column(
return Row(
children: [
Container(
padding: isLargeScreenSize(context) ? const EdgeInsets.all(30) : const EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FilterWidget(
size: MediaQuery.of(context).size,
tabs: tabs,
selectedIndex: selectedIndex,
onTabChanged: (index) {
context.read<DeviceManagementBloc>().add(SelectedFilterChanged(index));
},
),
const SizedBox(height: 20),
const DeviceSearchFilters(),
const SizedBox(height: 12),
Container(
height: 45,
width: 125,
decoration: containerDecoration,
child: Center(
child: DefaultButton(
onPressed: isControlButtonEnabled
? () {
if (selectedDevices.length == 1) {
showDialog(
context: context,
builder: (context) => DeviceControlDialog(
device: selectedDevices.first,
),
);
} else if (selectedDevices.length > 1) {
final productTypes = selectedDevices.map((device) => device.productType).toSet();
if (productTypes.length == 1) {
showDialog(
context: context,
builder: (context) => DeviceBatchControlDialog(
devices: selectedDevices,
Expanded(child: SpaceTreeView(
onSelect: () {
context.read<DeviceManagementBloc>().add(FetchDevices(context));
},
)),
Expanded(
flex: 4,
child: state is DeviceManagementLoading
? const Center(child: CircularProgressIndicator())
: Column(
children: [
Container(
padding: isLargeScreenSize(context)
? const EdgeInsets.all(30)
: const EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FilterWidget(
size: MediaQuery.of(context).size,
tabs: tabs,
selectedIndex: selectedIndex,
onTabChanged: (index) {
context
.read<DeviceManagementBloc>()
.add(SelectedFilterChanged(index));
},
),
const SizedBox(height: 20),
const DeviceSearchFilters(),
const SizedBox(height: 12),
Container(
height: 45,
width: 125,
decoration: containerDecoration,
child: Center(
child: DefaultButton(
onPressed: isControlButtonEnabled
? () {
if (selectedDevices.length == 1) {
showDialog(
context: context,
builder: (context) => DeviceControlDialog(
device: selectedDevices.first,
),
);
} else if (selectedDevices.length > 1) {
final productTypes = selectedDevices
.map((device) => device.productType)
.toSet();
if (productTypes.length == 1) {
showDialog(
context: context,
builder: (context) => DeviceBatchControlDialog(
devices: selectedDevices,
),
);
}
}
}
: null,
borderRadius: 9,
child: Text(
buttonLabel,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: isControlButtonEnabled ? Colors.white : Colors.grey,
),
);
}
}
}
: null,
borderRadius: 9,
child: Text(
buttonLabel,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: isControlButtonEnabled ? Colors.white : Colors.grey,
),
),
),
),
],
),
),
),
),
),
],
),
),
Expanded(
child: Padding(
padding: isLargeScreenSize(context) ? const EdgeInsets.all(30) : const EdgeInsets.all(15),
child: DynamicTable(
withSelectAll: true,
cellDecoration: containerDecoration,
onRowSelected: (index, isSelected, row) {
final selectedDevice = devicesToShow[index];
context.read<DeviceManagementBloc>().add(SelectDevice(selectedDevice));
},
withCheckBox: true,
size: MediaQuery.of(context).size,
uuidIndex: 2,
headers: const [
'Device Name',
'Product Name',
'Device ID',
'Space Name',
'location',
'Battery Level',
'Installation Date and Time',
'Status',
'Last Offline Date and Time',
],
data: devicesToShow.map((device) {
final combinedSpaceNames = device.spaces != null
? device.spaces!.map((space) => space.spaceName).join(' > ') +
(device.community != null ? ' > ${device.community!.name}' : '')
: (device.community != null ? device.community!.name : '');
Expanded(
child: Padding(
padding: isLargeScreenSize(context)
? const EdgeInsets.all(30)
: const EdgeInsets.all(15),
child: DynamicTable(
withSelectAll: true,
cellDecoration: containerDecoration,
onRowSelected: (index, isSelected, row) {
final selectedDevice = devicesToShow[index];
context
.read<DeviceManagementBloc>()
.add(SelectDevice(selectedDevice));
},
withCheckBox: true,
size: MediaQuery.of(context).size,
uuidIndex: 2,
headers: const [
'Device Name',
'Product Name',
'Device ID',
'Space Name',
'location',
'Battery Level',
'Installation Date and Time',
'Status',
'Last Offline Date and Time',
],
data: devicesToShow.map((device) {
final combinedSpaceNames = device.spaces != null
? device.spaces!.map((space) => space.spaceName).join(' > ') +
(device.community != null
? ' > ${device.community!.name}'
: '')
: (device.community != null ? device.community!.name : '');
return [
device.name ?? '',
device.productName ?? '',
device.uuid ?? '',
(device.spaces != null && device.spaces!.isNotEmpty) ? device.spaces![0].spaceName : '',
combinedSpaceNames,
device.batteryLevel != null ? '${device.batteryLevel}%' : '-',
formatDateTime(DateTime.fromMillisecondsSinceEpoch((device.createTime ?? 0) * 1000)),
device.online == true ? 'Online' : 'Offline',
formatDateTime(DateTime.fromMillisecondsSinceEpoch((device.updateTime ?? 0) * 1000)),
];
}).toList(),
onSelectionChanged: (selectedRows) {
context.read<DeviceManagementBloc>().add(UpdateSelection(selectedRows));
},
initialSelectedIds:
context.read<DeviceManagementBloc>().selectedDevices.map((device) => device.uuid!).toList(),
isEmpty: devicesToShow.isEmpty,
),
),
)
return [
device.name ?? '',
device.productName ?? '',
device.uuid ?? '',
(device.spaces != null && device.spaces!.isNotEmpty)
? device.spaces![0].spaceName
: '',
combinedSpaceNames,
device.batteryLevel != null ? '${device.batteryLevel}%' : '-',
formatDateTime(DateTime.fromMillisecondsSinceEpoch(
(device.createTime ?? 0) * 1000)),
device.online == true ? 'Online' : 'Offline',
formatDateTime(DateTime.fromMillisecondsSinceEpoch(
(device.updateTime ?? 0) * 1000)),
];
}).toList(),
onSelectionChanged: (selectedRows) {
context
.read<DeviceManagementBloc>()
.add(UpdateSelection(selectedRows));
},
initialSelectedIds: context
.read<DeviceManagementBloc>()
.selectedDevices
.map((device) => device.uuid!)
.toList(),
isEmpty: devicesToShow.isEmpty,
),
),
)
],
),
),
],
);
},
@@ -12,8 +12,7 @@ class DeviceSearchFilters extends StatefulWidget {
State<DeviceSearchFilters> createState() => _DeviceSearchFiltersState();
}
class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
with HelperResponsiveLayout {
class _DeviceSearchFiltersState extends State<DeviceSearchFilters> with HelperResponsiveLayout {
final TextEditingController communityController = TextEditingController();
final TextEditingController unitNameController = TextEditingController();
final TextEditingController productNameController = TextEditingController();
@@ -27,8 +26,7 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
const SizedBox(width: 20),
_buildSearchField("Space Name", unitNameController, 200),
const SizedBox(width: 20),
_buildSearchField(
"Device Name / Product Name", productNameController, 300),
_buildSearchField("Device Name / Product Name", productNameController, 300),
const SizedBox(width: 20),
_buildSearchResetButtons(),
],
@@ -53,8 +51,7 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
);
}
Widget _buildSearchField(
String title, TextEditingController controller, double width) {
Widget _buildSearchField(String title, TextEditingController controller, double width) {
return Container(
child: StatefulTextField(
title: title,
@@ -88,7 +85,7 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
productNameController.clear();
context.read<DeviceManagementBloc>()
..add(ResetFilters())
..add(FetchDevices());
..add(FetchDevices(context));
},
);
}
@@ -6,7 +6,7 @@ import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_e
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_space_type.dart';
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart';
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presense_nobody_time.dart';
@@ -110,7 +110,7 @@ class CeilingSensorBatchControlView extends StatelessWidget with HelperResponsiv
),
),
),
FirmwareUpdateWidget(deviceId: devicesIds.first, version: 4),
// FirmwareUpdateWidget(deviceId: devicesIds.first, version: 4),
FactoryResetWidget(
callFactoryReset: () {
context.read<CeilingSensorBloc>().add(
@@ -6,11 +6,10 @@ import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_bloc.dar
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart';
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class CurtainBatchStatusView extends StatelessWidget
with HelperResponsiveLayout {
class CurtainBatchStatusView extends StatelessWidget with HelperResponsiveLayout {
const CurtainBatchStatusView({super.key, required this.devicesIds});
final List<String> devicesIds;
@@ -18,8 +17,8 @@ class CurtainBatchStatusView extends StatelessWidget
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => CurtainBloc(deviceId: devicesIds.first)
..add(CurtainFetchBatchStatus(devicesIds)),
create: (context) =>
CurtainBloc(deviceId: devicesIds.first)..add(CurtainFetchBatchStatus(devicesIds)),
child: BlocBuilder<CurtainBloc, CurtainState>(
builder: (context, state) {
if (state is CurtainStatusLoading) {
@@ -68,7 +67,7 @@ class CurtainBatchStatusView extends StatelessWidget
));
},
),
FirmwareUpdateWidget(deviceId: devicesIds.first, version: 5),
// FirmwareUpdateWidget(deviceId: devicesIds.first, version: 5),
FactoryResetWidget(
callFactoryReset: () {
context.read<CurtainBloc>().add(
@@ -4,11 +4,10 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_re
import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_bloc.dart';
import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_event.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class DoorLockBatchControlView extends StatelessWidget
with HelperResponsiveLayout {
class DoorLockBatchControlView extends StatelessWidget with HelperResponsiveLayout {
const DoorLockBatchControlView({super.key, required this.devicesIds});
final List<String> devicesIds;
@@ -18,17 +17,17 @@ class DoorLockBatchControlView extends StatelessWidget
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 170,
height: 140,
child: FirmwareUpdateWidget(
deviceId: devicesIds.first,
version: 12,
),
),
const SizedBox(
width: 12,
),
// SizedBox(
// width: 170,
// height: 140,
// child: FirmwareUpdateWidget(
// deviceId: devicesIds.first,
// version: 12,
// ),
// ),
// const SizedBox(
// width: 12,
// ),
SizedBox(
width: 170,
height: 140,
@@ -6,17 +6,15 @@ import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class GarageDoorBatchControlView extends StatelessWidget
with HelperResponsiveLayout {
class GarageDoorBatchControlView extends StatelessWidget with HelperResponsiveLayout {
final List<String> deviceIds;
const GarageDoorBatchControlView({Key? key, required this.deviceIds})
: super(key: key);
const GarageDoorBatchControlView({Key? key, required this.deviceIds}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -39,8 +37,7 @@ class GarageDoorBatchControlView extends StatelessWidget
);
}
Widget _buildStatusControls(
BuildContext context, GarageDoorStatusModel status) {
Widget _buildStatusControls(BuildContext context, GarageDoorStatusModel status) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
@@ -75,10 +72,10 @@ class GarageDoorBatchControlView extends StatelessWidget
);
},
),
FirmwareUpdateWidget(
deviceId: deviceIds.first,
version: 12,
),
// FirmwareUpdateWidget(
// deviceId: deviceIds.first,
// version: 12,
// ),
FactoryResetWidget(
callFactoryReset: () {
context.read<GarageDoorBloc>().add(
@@ -3,11 +3,10 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/gateway/bloc/gate_way_bloc.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class GatewayBatchControlView extends StatelessWidget
with HelperResponsiveLayout {
class GatewayBatchControlView extends StatelessWidget with HelperResponsiveLayout {
const GatewayBatchControlView({super.key, required this.gatewayIds});
final List<String> gatewayIds;
@@ -24,14 +23,13 @@ class GatewayBatchControlView extends StatelessWidget
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 170,
height: 140,
child: FirmwareUpdateWidget(
deviceId: gatewayIds.first, version: 2)),
const SizedBox(
width: 12,
),
// SizedBox(
// width: 170,
// height: 140,
// child: FirmwareUpdateWidget(deviceId: gatewayIds.first, version: 2)),
// const SizedBox(
// width: 12,
// ),
SizedBox(
width: 170,
height: 140,
@@ -40,8 +38,7 @@ class GatewayBatchControlView extends StatelessWidget
context.read<GateWayBloc>().add(
GateWayFactoryReset(
deviceId: gatewayIds.first,
factoryReset:
FactoryResetModel(devicesUuid: gatewayIds),
factoryReset: FactoryResetModel(devicesUuid: gatewayIds),
),
);
},
@@ -4,7 +4,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_re
import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart';
import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
class MainDoorSensorBatchView extends StatelessWidget {
const MainDoorSensorBatchView({super.key, required this.devicesIds});
@@ -16,17 +16,17 @@ class MainDoorSensorBatchView extends StatelessWidget {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 170,
height: 140,
child: FirmwareUpdateWidget(
deviceId: devicesIds.first,
version: 12,
),
),
const SizedBox(
width: 12,
),
// SizedBox(
// width: 170,
// height: 140,
// child: FirmwareUpdateWidget(
// deviceId: devicesIds.first,
// version: 12,
// ),
// ),
// const SizedBox(
// width: 12,
// ),
SizedBox(
width: 170,
height: 140,
@@ -4,16 +4,14 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_re
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart';
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class OneGangGlassSwitchBatchControlView extends StatelessWidget
with HelperResponsiveLayout {
class OneGangGlassSwitchBatchControlView extends StatelessWidget with HelperResponsiveLayout {
final List<String> deviceIds;
const OneGangGlassSwitchBatchControlView(
{required this.deviceIds, super.key});
const OneGangGlassSwitchBatchControlView({required this.deviceIds, super.key});
@override
Widget build(BuildContext context) {
@@ -36,8 +34,7 @@ class OneGangGlassSwitchBatchControlView extends StatelessWidget
);
}
Widget _buildStatusControls(
BuildContext context, OneGangGlassStatusModel status) {
Widget _buildStatusControls(BuildContext context, OneGangGlassStatusModel status) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
@@ -71,10 +68,10 @@ class OneGangGlassSwitchBatchControlView extends StatelessWidget
);
},
),
FirmwareUpdateWidget(
deviceId: deviceIds.first,
version: 12,
),
// FirmwareUpdateWidget(
// deviceId: deviceIds.first,
// version: 12,
// ),
FactoryResetWidget(
callFactoryReset: () {
context.read<OneGangGlassSwitchBloc>().add(
@@ -6,12 +6,11 @@ import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_lig
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class WallLightBatchControlView extends StatelessWidget
with HelperResponsiveLayout {
class WallLightBatchControlView extends StatelessWidget with HelperResponsiveLayout {
const WallLightBatchControlView({super.key, required this.deviceIds});
final List<String> deviceIds;
@@ -27,8 +26,7 @@ class WallLightBatchControlView extends StatelessWidget
return const Center(child: CircularProgressIndicator());
} else if (state is WallLightSwitchStatusLoaded) {
return _buildStatusControls(context, state.status);
} else if (state is WallLightSwitchError ||
state is WallLightSwitchControlError) {
} else if (state is WallLightSwitchError || state is WallLightSwitchControlError) {
return const Center(child: Text('Error fetching status'));
} else {
return const Center(child: CircularProgressIndicator());
@@ -38,8 +36,7 @@ class WallLightBatchControlView extends StatelessWidget
);
}
Widget _buildStatusControls(
BuildContext context, WallLightStatusModel status) {
Widget _buildStatusControls(BuildContext context, WallLightStatusModel status) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
@@ -74,15 +71,14 @@ class WallLightBatchControlView extends StatelessWidget
);
},
),
FirmwareUpdateWidget(
deviceId: deviceIds.first,
version: 12,
),
// FirmwareUpdateWidget(
// deviceId: deviceIds.first,
// version: 12,
// ),
FactoryResetWidget(
callFactoryReset: () {
context.read<WallLightSwitchBloc>().add(WallLightFactoryReset(
deviceId: status.uuid,
factoryReset: FactoryResetModel(devicesUuid: deviceIds)));
deviceId: status.uuid, factoryReset: FactoryResetModel(devicesUuid: deviceIds)));
},
),
],
@@ -6,21 +6,19 @@ import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_
import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_state.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_batch_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class PowerClampBatchControlView extends StatelessWidget
with HelperResponsiveLayout {
class PowerClampBatchControlView extends StatelessWidget with HelperResponsiveLayout {
final List<String> deviceIds;
const PowerClampBatchControlView({Key? key, required this.deviceIds})
: super(key: key);
const PowerClampBatchControlView({Key? key, required this.deviceIds}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => SmartPowerBloc(deviceId: deviceIds.first)
..add(SmartPowerFetchBatchEvent(deviceIds)),
create: (context) =>
SmartPowerBloc(deviceId: deviceIds.first)..add(SmartPowerFetchBatchEvent(deviceIds)),
child: BlocBuilder<SmartPowerBloc, SmartPowerState>(
builder: (context, state) {
if (state is SmartPowerLoading) {
@@ -37,18 +35,17 @@ class PowerClampBatchControlView extends StatelessWidget
);
}
Widget _buildStatusControls(
BuildContext context, PowerClampBatchModel status) {
Widget _buildStatusControls(BuildContext context, PowerClampBatchModel status) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 170,
// height: 140,
child: FirmwareUpdateWidget(deviceId: deviceIds.first, version: 2)),
const SizedBox(
width: 12,
),
// SizedBox(
// width: 170,
// // height: 140,
// child: FirmwareUpdateWidget(deviceId: deviceIds.first, version: 2)),
// const SizedBox(
// width: 12,
// ),
SizedBox(
width: 170,
height: 140,
@@ -1,128 +1,128 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
// import 'package:flutter/material.dart';
// import 'package:flutter_svg/flutter_svg.dart';
// import 'package:syncrow_web/pages/common/buttons/default_button.dart';
// import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
// import 'package:syncrow_web/utils/color_manager.dart';
// import 'package:syncrow_web/utils/constants/assets.dart';
// import 'package:syncrow_web/utils/extension/build_context_x.dart';
class FirmwareUpdateWidget extends StatefulWidget {
const FirmwareUpdateWidget({super.key, required this.deviceId, required this.version});
// class FirmwareUpdateWidget extends StatefulWidget {
// const FirmwareUpdateWidget({super.key, required this.deviceId, required this.version});
final String deviceId;
final int version;
// final String deviceId;
// final int version;
@override
State<FirmwareUpdateWidget> createState() => _FirmwareUpdateWidgetState();
}
// @override
// State<FirmwareUpdateWidget> createState() => _FirmwareUpdateWidgetState();
// }
class _FirmwareUpdateWidgetState extends State<FirmwareUpdateWidget> {
bool _showConfirmation = false;
// class _FirmwareUpdateWidgetState extends State<FirmwareUpdateWidget> {
// bool _showConfirmation = false;
void _toggleConfirmation() {
setState(() {
_showConfirmation = !_showConfirmation;
});
}
// void _toggleConfirmation() {
// setState(() {
// _showConfirmation = !_showConfirmation;
// });
// }
@override
Widget build(BuildContext context) {
return DeviceControlsContainer(
child: _showConfirmation
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
children: [
Text(
'Firmware Update',
style: context.textTheme.titleMedium!.copyWith(
fontWeight: FontWeight.bold,
color: ColorsManager.blackColor,
),
),
Text(
'Are you sure?',
style: context.textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor,
),
),
],
),
Row(
children: [
Flexible(
child: DefaultButton(
height: 20,
elevation: 0,
padding: 0,
onPressed: _toggleConfirmation,
backgroundColor: ColorsManager.greyColor,
child: Text(
'Cancel',
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.blackColor,
fontWeight: FontWeight.w400,
fontSize: 12,
),
),
),
),
const SizedBox(width: 8),
Flexible(
child: DefaultButton(
height: 20,
elevation: 0,
padding: 0,
onPressed: () {
_toggleConfirmation();
},
backgroundColor: ColorsManager.primaryColor,
child: Text(
'Update',
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.whiteColors,
fontWeight: FontWeight.w400,
fontSize: 12,
),
),
),
),
],
),
],
)
: GestureDetector(
onTap: _toggleConfirmation,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ClipOval(
child: Container(
color: ColorsManager.whiteColors,
height: 60,
width: 60,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: SvgPicture.asset(
Assets.firmware,
fit: BoxFit.cover,
),
),
),
),
Text(
'Firmware Update',
style: context.textTheme.titleMedium!.copyWith(
fontWeight: FontWeight.w400,
color: ColorsManager.blackColor,
),
),
],
),
),
);
}
}
// @override
// Widget build(BuildContext context) {
// return DeviceControlsContainer(
// child: _showConfirmation
// ? Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Column(
// children: [
// Text(
// 'Firmware Update',
// style: context.textTheme.titleMedium!.copyWith(
// fontWeight: FontWeight.bold,
// color: ColorsManager.blackColor,
// ),
// ),
// Text(
// 'Are you sure?',
// style: context.textTheme.bodySmall!.copyWith(
// color: ColorsManager.grayColor,
// ),
// ),
// ],
// ),
// Row(
// children: [
// Flexible(
// child: DefaultButton(
// height: 20,
// elevation: 0,
// padding: 0,
// onPressed: _toggleConfirmation,
// backgroundColor: ColorsManager.greyColor,
// child: Text(
// 'Cancel',
// style: context.textTheme.bodyMedium!.copyWith(
// color: ColorsManager.blackColor,
// fontWeight: FontWeight.w400,
// fontSize: 12,
// ),
// ),
// ),
// ),
// const SizedBox(width: 8),
// Flexible(
// child: DefaultButton(
// height: 20,
// elevation: 0,
// padding: 0,
// onPressed: () {
// _toggleConfirmation();
// },
// backgroundColor: ColorsManager.primaryColor,
// child: Text(
// 'Update',
// style: context.textTheme.bodyMedium!.copyWith(
// color: ColorsManager.whiteColors,
// fontWeight: FontWeight.w400,
// fontSize: 12,
// ),
// ),
// ),
// ),
// ],
// ),
// ],
// )
// : GestureDetector(
// onTap: _toggleConfirmation,
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// ClipOval(
// child: Container(
// color: ColorsManager.whiteColors,
// height: 60,
// width: 60,
// child: Padding(
// padding: const EdgeInsets.all(12.0),
// child: SvgPicture.asset(
// Assets.firmware,
// fit: BoxFit.cover,
// ),
// ),
// ),
// ),
// Text(
// 'Firmware Update',
// style: context.textTheme.titleMedium!.copyWith(
// fontWeight: FontWeight.w400,
// color: ColorsManager.blackColor,
// ),
// ),
// ],
// ),
// ),
// );
// }
// }
@@ -95,8 +95,9 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
]),
TableRow(
children: [
_buildInfoRow('Space Name:', device.unit?.name ?? 'N/A'),
_buildInfoRow('Room:', device.room?.name ?? 'N/A'),
_buildInfoRow('Space Name:',
device.spaces?.firstOrNull?.spaceName ?? 'N/A'),
_buildInfoRow('Room:', device.subspace?.subspaceName ?? 'N/A'),
],
),
TableRow(
@@ -111,9 +112,13 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
),
_buildInfoRow(
'Battery Level:',
device.batteryLevel != null ? '${device.batteryLevel ?? 0}%' : "-",
device.batteryLevel != null
? '${device.batteryLevel ?? 0}%'
: "-",
statusColor: device.batteryLevel != null
? (device.batteryLevel! < 20 ? ColorsManager.red : ColorsManager.green)
? (device.batteryLevel! < 20
? ColorsManager.red
: ColorsManager.green)
: null,
),
],
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/sos/bloc/sos_device_bloc.dart';
class SOSBatchControlView extends StatelessWidget {
@@ -18,20 +17,21 @@ class SOSBatchControlView extends StatelessWidget {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 170,
// height: 140,
child: FirmwareUpdateWidget(deviceId: deviceIds.first, version: 2)),
const SizedBox(
width: 12,
),
// SizedBox(
// width: 170,
// // height: 140,
// child: FirmwareUpdateWidget(deviceId: deviceIds.first, version: 2)),
// const SizedBox(
// width: 12,
// ),
SizedBox(
width: 170,
height: 140,
child: FactoryResetWidget(
callFactoryReset: () {
context.read<SosDeviceBloc>().add(
SosFactoryReset(deviceId: deviceIds.first, factoryReset: FactoryResetModel(devicesUuid: deviceIds)));
context.read<SosDeviceBloc>().add(SosFactoryReset(
deviceId: deviceIds.first,
factoryReset: FactoryResetModel(devicesUuid: deviceIds)));
},
),
),
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart';
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/models/three_gang_glass_switch.dart';
@@ -98,10 +98,10 @@ class ThreeGangGlassSwitchBatchControlView extends StatelessWidget with HelperRe
);
},
),
FirmwareUpdateWidget(
deviceId: deviceIds.first,
version: 12, // adjust the version according to your requirement
),
// FirmwareUpdateWidget(
// deviceId: deviceIds.first,
// version: 12, // adjust the version according to your requirement
// ),
FactoryResetWidget(
callFactoryReset: () {
context.read<ThreeGangGlassSwitchBloc>().add(
@@ -2,14 +2,13 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.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/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class LivingRoomBatchControlsView extends StatelessWidget
with HelperResponsiveLayout {
class LivingRoomBatchControlsView extends StatelessWidget with HelperResponsiveLayout {
const LivingRoomBatchControlsView({super.key, required this.deviceIds});
final List<String> deviceIds;
@@ -17,16 +16,15 @@ class LivingRoomBatchControlsView extends StatelessWidget
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LivingRoomBloc(deviceId: deviceIds.first)
..add(LivingRoomFetchBatchEvent(deviceIds)),
create: (context) =>
LivingRoomBloc(deviceId: deviceIds.first)..add(LivingRoomFetchBatchEvent(deviceIds)),
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) {
} else if (state is LivingRoomDeviceManagementError || state is LivingRoomControlError) {
return const Center(child: Text('Error fetching status'));
} else {
return const Center(child: CircularProgressIndicator());
@@ -36,8 +34,7 @@ class LivingRoomBatchControlsView 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);
@@ -102,10 +99,10 @@ class LivingRoomBatchControlsView extends StatelessWidget
);
},
),
FirmwareUpdateWidget(
deviceId: deviceIds.first,
version: 12,
),
// FirmwareUpdateWidget(
// deviceId: deviceIds.first,
// version: 12,
// ),
FactoryResetWidget(callFactoryReset: () {
context.read<LivingRoomBloc>().add(
LivingRoomFactoryResetEvent(
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart';
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart';
@@ -16,8 +16,8 @@ class TwoGangGlassSwitchBatchControlView extends StatelessWidget with HelperResp
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) =>
TwoGangGlassSwitchBloc(deviceId: deviceIds.first)..add(TwoGangGlassSwitchFetchBatchStatusEvent(deviceIds)),
create: (context) => TwoGangGlassSwitchBloc(deviceId: deviceIds.first)
..add(TwoGangGlassSwitchFetchBatchStatusEvent(deviceIds)),
child: BlocBuilder<TwoGangGlassSwitchBloc, TwoGangGlassSwitchState>(
builder: (context, state) {
if (state is TwoGangGlassSwitchLoading) {
@@ -83,10 +83,10 @@ class TwoGangGlassSwitchBatchControlView extends StatelessWidget with HelperResp
);
},
),
FirmwareUpdateWidget(
deviceId: deviceIds.first,
version: 12, // adjust the version according to your requirement
),
// FirmwareUpdateWidget(
// deviceId: deviceIds.first,
// version: 12, // adjust the version according to your requirement
// ),
FactoryResetWidget(
callFactoryReset: () {
context.read<TwoGangGlassSwitchBloc>().add(
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart';
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart';
@@ -10,8 +10,7 @@ import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang
import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class TwoGangBatchControlView extends StatelessWidget
with HelperResponsiveLayout {
class TwoGangBatchControlView extends StatelessWidget with HelperResponsiveLayout {
const TwoGangBatchControlView({super.key, required this.deviceIds});
final List<String> deviceIds;
@@ -27,8 +26,7 @@ class TwoGangBatchControlView extends StatelessWidget
return const Center(child: CircularProgressIndicator());
} else if (state is TwoGangSwitchStatusLoaded) {
return _buildStatusControls(context, state.status);
} else if (state is TwoGangSwitchError ||
state is TwoGangSwitchControlError) {
} else if (state is TwoGangSwitchError || state is TwoGangSwitchControlError) {
return const Center(child: Text('Error fetching status'));
} else {
return const Center(child: CircularProgressIndicator());
@@ -84,10 +82,10 @@ class TwoGangBatchControlView extends StatelessWidget
));
},
),
FirmwareUpdateWidget(
deviceId: deviceIds.first,
version: 12,
),
// FirmwareUpdateWidget(
// deviceId: deviceIds.first,
// version: 12,
// ),
FactoryResetWidget(callFactoryReset: () {
context.read<TwoGangSwitchBloc>().add(
TwoGangFactoryReset(
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart';
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_bloc.dart';
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_event.dart';
@@ -113,7 +113,7 @@ class WallSensorBatchControlView extends StatelessWidget with HelperResponsiveLa
),
),
),
FirmwareUpdateWidget(deviceId: devicesIds.first, version: 2),
// FirmwareUpdateWidget(deviceId: devicesIds.first, version: 2),
FactoryResetWidget(
callFactoryReset: () {
context.read<WallSensorBloc>().add(
@@ -2,15 +2,14 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class WaterHEaterBatchControlView extends StatelessWidget
with HelperResponsiveLayout {
class WaterHEaterBatchControlView extends StatelessWidget with HelperResponsiveLayout {
const WaterHEaterBatchControlView({super.key, required this.deviceIds});
final List<String> deviceIds;
@@ -18,8 +17,8 @@ class WaterHEaterBatchControlView extends StatelessWidget
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => WaterHeaterBloc()
..add(FetchWaterHeaterBatchStatusEvent(devicesUuid: deviceIds)),
create: (context) =>
WaterHeaterBloc()..add(FetchWaterHeaterBatchStatusEvent(devicesUuid: deviceIds)),
child: BlocBuilder<WaterHeaterBloc, WaterHeaterState>(
builder: (context, state) {
if (state is WaterHeaterLoadingState) {
@@ -36,8 +35,7 @@ class WaterHEaterBatchControlView extends StatelessWidget
);
}
Widget _buildStatusControls(
BuildContext context, WaterHeaterStatusModel status) {
Widget _buildStatusControls(BuildContext context, WaterHeaterStatusModel status) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
@@ -73,10 +71,10 @@ class WaterHEaterBatchControlView extends StatelessWidget
);
},
),
FirmwareUpdateWidget(
deviceId: deviceIds.first,
version: 12,
),
// FirmwareUpdateWidget(
// deviceId: deviceIds.first,
// version: 12,
// ),
FactoryResetWidget(
callFactoryReset: () {},
),
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/water_leak/bloc/water_leak_bloc.dart';
import 'package:syncrow_web/pages/device_managment/water_leak/bloc/water_leak_event.dart';
@@ -10,18 +10,16 @@ import 'package:syncrow_web/pages/device_managment/water_leak/bloc/water_leak_st
import 'package:syncrow_web/pages/device_managment/water_leak/model/water_leak_status_model.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class WaterLeakBatchControlView extends StatelessWidget
with HelperResponsiveLayout {
class WaterLeakBatchControlView extends StatelessWidget with HelperResponsiveLayout {
final List<String> deviceIds;
const WaterLeakBatchControlView({Key? key, required this.deviceIds})
: super(key: key);
const WaterLeakBatchControlView({Key? key, required this.deviceIds}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => WaterLeakBloc(deviceIds.first)
..add(FetchWaterLeakBatchStatusEvent(deviceIds)),
create: (context) =>
WaterLeakBloc(deviceIds.first)..add(FetchWaterLeakBatchStatusEvent(deviceIds)),
child: BlocBuilder<WaterLeakBloc, WaterLeakState>(
builder: (context, state) {
if (state is WaterLeakLoadingState) {
@@ -38,18 +36,17 @@ class WaterLeakBatchControlView extends StatelessWidget
);
}
Widget _buildStatusControls(
BuildContext context, WaterLeakStatusModel status) {
Widget _buildStatusControls(BuildContext context, WaterLeakStatusModel status) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 170,
height: 140,
child: FirmwareUpdateWidget(deviceId: deviceIds.first, version: 2)),
const SizedBox(
width: 12,
),
// SizedBox(
// width: 170,
// height: 140,
// child: FirmwareUpdateWidget(deviceId: deviceIds.first, version: 2)),
// const SizedBox(
// width: 12,
// ),
SizedBox(
width: 170,
height: 140,
+105 -57
View File
@@ -1,55 +1,103 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:go_router/go_router.dart';
import 'package:graphview/GraphView.dart';
// import 'package:graphview/GraphView.dart';
import 'package:syncrow_web/pages/auth/model/user_model.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/home/bloc/home_state.dart';
import 'package:syncrow_web/pages/home/home_model/home_item_model.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/services/home_api.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';
class HomeBloc extends Bloc<HomeEvent, HomeState> {
final Graph graph = Graph()..isTree = true;
final BuchheimWalkerConfiguration builder = BuchheimWalkerConfiguration();
List<Node> sourcesList = [];
List<Node> destinationsList = [];
// final Graph graph = Graph()..isTree = true;
// final BuchheimWalkerConfiguration builder = BuchheimWalkerConfiguration();
// List<Node> sourcesList = [];
// List<Node> destinationsList = [];
UserModel? user;
String terms = '';
String policy = '';
HomeBloc() : super((HomeInitial())) {
on<CreateNewNode>(_createNode);
// on<CreateNewNode>(_createNode);
on<FetchUserInfo>(_fetchUserInfo);
on<FetchTermEvent>(_fetchTerms);
on<FetchPolicyEvent>(_fetchPolicy);
on<ConfirmUserAgreementEvent>(_confirmUserAgreement);
}
void _createNode(CreateNewNode event, Emitter<HomeState> emit) async {
emit(HomeInitial());
sourcesList.add(event.sourceNode);
destinationsList.add(event.destinationNode);
for (int i = 0; i < sourcesList.length; i++) {
graph.addEdge(sourcesList[i], destinationsList[i]);
}
// void _createNode(CreateNewNode event, Emitter<HomeState> emit) async {
// emit(HomeInitial());
// sourcesList.add(event.sourceNode);
// destinationsList.add(event.destinationNode);
// for (int i = 0; i < sourcesList.length; i++) {
// graph.addEdge(sourcesList[i], destinationsList[i]);
// }
builder
..siblingSeparation = (100)
..levelSeparation = (150)
..subtreeSeparation = (150)
..orientation = (BuchheimWalkerConfiguration.ORIENTATION_TOP_BOTTOM);
emit(HomeUpdateTree(graph: graph, builder: builder));
}
// builder
// ..siblingSeparation = (100)
// ..levelSeparation = (150)
// ..subtreeSeparation = (150)
// ..orientation = (BuchheimWalkerConfiguration.ORIENTATION_TOP_BOTTOM);
// emit(HomeUpdateTree(graph: graph, builder: builder));
// }
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);
add(FetchTermEvent());
add(FetchPolicyEvent());
emit(HomeInitial());
} catch (e) {
return;
}
}
Future _fetchTerms(FetchTermEvent event, Emitter<HomeState> emit) async {
try {
emit(LoadingHome());
terms = await HomeApi().fetchTerms();
emit(HomeInitial());
// emit(PolicyAgreement());
} catch (e) {
return;
}
}
Future _fetchPolicy(FetchPolicyEvent event, Emitter<HomeState> emit) async {
try {
emit(LoadingHome());
policy = await HomeApi().fetchPolicy();
debugPrint("Fetched policy: $policy");
// Emit a state to trigger the UI update
emit(HomeInitial());
} catch (e) {
debugPrint("Error fetching policy: $e");
return;
}
}
Future _confirmUserAgreement(
ConfirmUserAgreementEvent event, Emitter<HomeState> emit) async {
try {
emit(LoadingHome());
var uuid =
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
policy = await HomeApi().confirmUserAgreements(uuid);
emit(PolicyAgreement());
} catch (e) {
return;
}
}
// static Future fetchUserInfo() async {
// try {
// var uuid =
@@ -90,40 +138,40 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
},
color: ColorsManager.primaryColor,
),
HomeItemModel(
title: 'Move in',
icon: Assets.moveinIcon,
active: false,
onPress: (context) {},
color: ColorsManager.primaryColor,
),
HomeItemModel(
title: 'Construction',
icon: Assets.constructionIcon,
active: false,
onPress: (context) {},
color: ColorsManager.primaryColor,
),
HomeItemModel(
title: 'Energy',
icon: Assets.energyIcon,
active: false,
onPress: (context) {},
color: ColorsManager.slidingBlueColor.withOpacity(0.2),
),
HomeItemModel(
title: 'Integrations',
icon: Assets.integrationsIcon,
active: false,
onPress: (context) {},
color: ColorsManager.slidingBlueColor.withOpacity(0.2),
),
HomeItemModel(
title: 'Asset',
icon: Assets.assetIcon,
active: false,
onPress: (context) {},
color: ColorsManager.slidingBlueColor.withOpacity(0.2),
),
// HomeItemModel(
// title: 'Move in',
// icon: Assets.moveinIcon,
// active: false,
// onPress: (context) {},
// color: ColorsManager.primaryColor,
// ),
// HomeItemModel(
// title: 'Construction',
// icon: Assets.constructionIcon,
// active: false,
// onPress: (context) {},
// color: ColorsManager.primaryColor,
// ),
// HomeItemModel(
// title: 'Energy',
// icon: Assets.energyIcon,
// active: false,
// onPress: (context) {},
// color: ColorsManager.slidingBlueColor.withOpacity(0.2),
// ),
// HomeItemModel(
// title: 'Integrations',
// icon: Assets.integrationsIcon,
// active: false,
// onPress: (context) {},
// color: ColorsManager.slidingBlueColor.withOpacity(0.2),
// ),
// HomeItemModel(
// title: 'Asset',
// icon: Assets.assetIcon,
// active: false,
// onPress: (context) {},
// color: ColorsManager.slidingBlueColor.withOpacity(0.2),
// ),
];
}
+16 -10
View File
@@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart';
import 'package:graphview/GraphView.dart';
// import 'package:graphview/GraphView.dart';
abstract class HomeEvent extends Equatable {
const HomeEvent();
@@ -8,16 +8,22 @@ abstract class HomeEvent extends Equatable {
List<Object> get props => [];
}
class CreateNewNode extends HomeEvent {
final Node sourceNode;
final Node destinationNode;
const CreateNewNode(
{required this.sourceNode, required this.destinationNode});
// class CreateNewNode extends HomeEvent {
// final Node sourceNode;
// final Node destinationNode;
// const CreateNewNode(
// {required this.sourceNode, required this.destinationNode});
@override
List<Object> get props => [sourceNode, destinationNode];
}
// @override
// List<Object> get props => [sourceNode, destinationNode];
// }
class FetchUserInfo extends HomeEvent {
const FetchUserInfo();
}
}
class FetchTermEvent extends HomeEvent {}
class FetchPolicyEvent extends HomeEvent {}
class ConfirmUserAgreementEvent extends HomeEvent {}
+18 -12
View File
@@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart';
import 'package:graphview/GraphView.dart';
// import 'package:graphview/GraphView.dart';
abstract class HomeState extends Equatable {
const HomeState();
@@ -8,19 +8,25 @@ abstract class HomeState extends Equatable {
List<Object> get props => [];
}
class LoadingHome extends HomeState {}
class HomeInitial extends HomeState {}
class HomeCounterState extends HomeState {
final int counter;
const HomeCounterState(this.counter);
}
class TermsAgreement extends HomeState {}
class HomeUpdateTree extends HomeState {
final Graph graph;
final BuchheimWalkerConfiguration builder;
class PolicyAgreement extends HomeState {}
const HomeUpdateTree({required this.graph, required this.builder});
// class HomeCounterState extends HomeState {
// final int counter;
// const HomeCounterState(this.counter);
// }
@override
List<Object> get props => [graph, builder];
}
// class HomeUpdateTree extends HomeState {
// final Graph graph;
// final BuchheimWalkerConfiguration builder;
// const HomeUpdateTree({required this.graph, required this.builder});
// @override
// List<Object> get props => [graph, builder];
// }
@@ -0,0 +1,178 @@
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart';
import 'package:url_launcher/url_launcher.dart';
class AgreementAndPrivacyDialog extends StatefulWidget {
final String terms;
final String policy;
const AgreementAndPrivacyDialog({
super.key,
required this.terms,
required this.policy,
});
@override
_AgreementAndPrivacyDialogState createState() =>
_AgreementAndPrivacyDialogState();
}
class _AgreementAndPrivacyDialogState extends State<AgreementAndPrivacyDialog> {
final ScrollController _scrollController = ScrollController();
bool _isAtEnd = false;
int _currentPage = 1;
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
WidgetsBinding.instance
.addPostFrameCallback((_) => _checkScrollRequirement());
}
void _checkScrollRequirement() {
final scrollPosition = _scrollController.position;
if (scrollPosition.maxScrollExtent <= 0) {
setState(() {
_isAtEnd = true;
});
}
}
@override
void dispose() {
_scrollController.removeListener(_onScroll);
_scrollController.dispose();
super.dispose();
}
void _onScroll() {
if (_scrollController.position.atEdge) {
final isAtBottom = _scrollController.position.pixels ==
_scrollController.position.maxScrollExtent;
if (isAtBottom && !_isAtEnd) {
setState(() {
_isAtEnd = true;
});
}
}
}
String get _dialogTitle =>
_currentPage == 1 ? 'User Agreement' : 'Privacy Policy';
String get _dialogContent => _currentPage == 1 ? widget.terms : widget.policy;
final String staticText =
'<h5 style="color: #FF5722;">If you cancel you will be logged out.</h5>';
Widget _buildScrollableContent() {
return Container(
padding: const EdgeInsets.all(40),
width: MediaQuery.of(context).size.width * 0.8,
height: MediaQuery.of(context).size.height * 0.75,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
child: Scrollbar(
thumbVisibility: true,
trackVisibility: true,
interactive: true,
controller: _scrollController,
child: SingleChildScrollView(
controller: _scrollController,
padding: const EdgeInsets.all(25),
child: Html(
data: "$_dialogContent $staticText",
onLinkTap: (url, attributes, element) async {
if (url != null) {
final uri = Uri.parse(url);
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
},
style: {
"body": Style(
fontSize: FontSize(14),
color: Colors.black87,
lineHeight: LineHeight(1.5),
),
},
),
),
),
);
}
Widget _buildActionButton() {
final String buttonText = _currentPage == 2 ? "I Agree" : "Next";
return InkWell(
onTap: _isAtEnd
? () {
if (_currentPage == 1) {
setState(() {
_currentPage = 2;
_isAtEnd = false;
_scrollController.jumpTo(0);
WidgetsBinding.instance
.addPostFrameCallback((_) => _checkScrollRequirement());
});
} else {
Navigator.of(context).pop(true);
}
}
: null,
child: Text(
buttonText,
style: TextStyle(
color: _isAtEnd ? ColorsManager.secondaryColor : Colors.grey,
),
),
);
}
@override
Widget build(BuildContext context) {
return Dialog(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
_dialogTitle,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: ColorsManager.secondaryColor,
),
),
),
const Divider(),
_buildScrollableContent(),
const Divider(),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
InkWell(
onTap: () {
AuthBloc.logout();
context.go(RoutesConst.auth);
},
child: const Text("Cancel"),
),
_buildActionButton(),
],
),
),
],
),
);
}
}
+32 -35
View File
@@ -41,8 +41,7 @@ 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(
@@ -51,9 +50,8 @@ class HomeMobilePage extends StatelessWidget {
height: size.height * 0.6,
width: size.width * 0.68,
child: GridView.builder(
itemCount: 8,
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
itemCount: 3,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0,
@@ -65,8 +63,7 @@ class HomeMobilePage extends StatelessWidget {
active: homeItems[index]['active'],
name: homeItems[index]['title'],
img: homeItems[index]['icon'],
onTap: () =>
homeBloc.homeItems[index].onPress(context),
onTap: () => homeBloc.homeItems[index].onPress(context),
);
},
),
@@ -97,33 +94,33 @@ class HomeMobilePage extends StatelessWidget {
'icon': Assets.devicesIcon,
'active': true,
},
{
'title': 'Move in',
'icon': Assets.moveinIcon,
'active': false,
},
{
'title': 'Construction',
'icon': Assets.constructionIcon,
'active': false,
},
{
'title': 'Energy',
'icon': Assets.energyIcon,
'color': ColorsManager.slidingBlueColor.withOpacity(0.2),
'active': false,
},
{
'title': 'Integrations',
'icon': Assets.integrationsIcon,
'color': ColorsManager.slidingBlueColor.withOpacity(0.2),
'active': false,
},
{
'title': 'Asset',
'icon': Assets.assetIcon,
'color': ColorsManager.slidingBlueColor.withOpacity(0.2),
'active': false,
},
// {
// 'title': 'Move in',
// 'icon': Assets.moveinIcon,
// 'active': false,
// },
// {
// 'title': 'Construction',
// 'icon': Assets.constructionIcon,
// 'active': false,
// },
// {
// 'title': 'Energy',
// 'icon': Assets.energyIcon,
// 'color': ColorsManager.slidingBlueColor.withOpacity(0.2),
// 'active': false,
// },
// {
// 'title': 'Integrations',
// 'icon': Assets.integrationsIcon,
// 'color': ColorsManager.slidingBlueColor.withOpacity(0.2),
// 'active': false,
// },
// {
// 'title': 'Asset',
// 'icon': Assets.assetIcon,
// 'color': ColorsManager.slidingBlueColor.withOpacity(0.2),
// 'active': false,
// },
];
}
+114 -60
View File
@@ -1,80 +1,134 @@
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_event.dart';
import 'package:syncrow_web/pages/home/view/agreement_and_privacy_dialog.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/constants/assets.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
class HomeWebPage extends StatelessWidget {
class HomeWebPage extends StatefulWidget {
const HomeWebPage({super.key});
@override
State<HomeWebPage> createState() => _HomeWebPageState();
}
class _HomeWebPageState extends State<HomeWebPage> {
// Flag to track whether the dialog is already shown.
bool _dialogShown = false;
@override
void initState() {
super.initState();
final homeBloc = BlocProvider.of<HomeBloc>(context);
homeBloc.add(FetchUserInfo());
}
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
final homeBloc = BlocProvider.of<HomeBloc>(context);
return PopScope(
canPop: false,
onPopInvoked: (didPop) => false,
child: BlocConsumer<HomeBloc, HomeState>(
listener: (BuildContext context, state) {},
builder: (context, state) {
final homeBloc = BlocProvider.of<HomeBloc>(context);
return WebScaffold(
enableMenuSidebar: false,
appBarTitle: Row(
canPop: false,
onPopInvoked: (didPop) => false,
child: BlocConsumer<HomeBloc, HomeState>(
listener: (BuildContext context, state) {
print('state=$state');
if (state is HomeInitial) {
if (homeBloc.user!.hasAcceptedWebAgreement == false &&
!_dialogShown) {
_dialogShown =
true; // Set the flag to true to indicate the dialog is showing.
Future.delayed(const Duration(seconds: 1), () {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AgreementAndPrivacyDialog(
terms: homeBloc.terms,
policy: homeBloc.policy,
);
},
).then((v) {
_dialogShown = false;
if (v != null) {
homeBloc.add(ConfirmUserAgreementEvent());
homeBloc.add(const FetchUserInfo());
}
});
});
}
}
},
builder: (context, state) {
return WebScaffold(
enableMenuSidebar: false,
appBarTitle: Row(
children: [
SvgPicture.asset(
Assets.loginLogo,
width: 150,
),
],
),
scaffoldBody: SizedBox(
height: size.height,
width: size.width,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
Assets.loginLogo,
width: 150,
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: size.height * 0.1),
Text(
'ACCESS YOUR APPS',
style: Theme.of(context)
.textTheme
.headlineLarge!
.copyWith(color: Colors.black, fontSize: 40),
),
const SizedBox(height: 30),
Expanded(
flex: 4,
child: SizedBox(
height: size.height * 0.6,
width: size.width * 0.68,
child: GridView.builder(
itemCount: 3, // Change this count if needed.
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // Adjust as needed.
crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0,
childAspectRatio: 1.5,
),
itemBuilder: (context, index) {
return HomeCard(
index: index,
active: homeBloc.homeItems[index].active!,
name: homeBloc.homeItems[index].title!,
img: homeBloc.homeItems[index].icon!,
onTap: () =>
homeBloc.homeItems[index].onPress(context),
);
},
),
),
),
],
),
],
),
scaffoldBody: SizedBox(
height: size.height,
width: size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: size.height * 0.1),
Text(
'ACCESS YOUR APPS',
style: Theme.of(context)
.textTheme
.headlineLarge!
.copyWith(color: Colors.black, fontSize: 40),
),
const SizedBox(height: 30),
Expanded(
flex: 4,
child: SizedBox(
height: size.height * 0.6,
width: size.width * 0.68,
child: GridView.builder(
itemCount: 8,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0,
childAspectRatio: 1.5,
),
itemBuilder: (context, index) {
return HomeCard(
index: index,
active: homeBloc.homeItems[index].active!,
name: homeBloc.homeItems[index].title!,
img: homeBloc.homeItems[index].icon!,
onTap: () => homeBloc.homeItems[index].onPress(context),
);
},
),
),
),
],
),
),
);
},
));
),
);
},
),
);
}
}
+181 -181
View File
@@ -1,185 +1,185 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:graphview/GraphView.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/bloc/home_state.dart';
// import 'package:flutter/material.dart';
// import 'package:flutter_bloc/flutter_bloc.dart';
// import 'package:graphview/GraphView.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/bloc/home_state.dart';
class TreeWidget extends StatelessWidget {
const TreeWidget({super.key});
// class TreeWidget extends StatelessWidget {
// const TreeWidget({super.key});
@override
Widget build(BuildContext context) {
// final HomeBloc homeBloc = BlocProvider.of<HomeBloc>(context);
String firstNodeName = '';
String secondNodeName = '';
// @override
// Widget build(BuildContext context) {
// // final HomeBloc homeBloc = BlocProvider.of<HomeBloc>(context);
// String firstNodeName = '';
// String secondNodeName = '';
return SafeArea(
child: Container(
padding: const EdgeInsets.all(24),
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
alignment: AlignmentDirectional.center,
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
BlocBuilder<HomeBloc, HomeState>(builder: (context, state) {
if (state is HomeInitial) {
return Wrap(
children: [
SizedBox(
width: 100,
child: TextFormField(
decoration: const InputDecoration(
labelText: "Subtree separation"),
onChanged: (text) {
firstNodeName = text;
},
),
),
const SizedBox(
width: 8,
),
Container(
width: 100,
child: TextFormField(
decoration: InputDecoration(labelText: "Node Name"),
onChanged: (text) {
secondNodeName = text;
},
),
),
ElevatedButton(
onPressed: () {
final node1 = Node.Id(firstNodeName);
final node2 = Node.Id(secondNodeName);
context.read<HomeBloc>().add(CreateNewNode(
sourceNode: node1, destinationNode: node2));
},
child: Text("Add"),
)
],
);
}
if (state is HomeUpdateTree) {
return Expanded(
child: InteractiveViewer(
constrained: false,
boundaryMargin: const EdgeInsets.all(100),
minScale: 0.01,
maxScale: 5.6,
child: GraphView(
graph: state.graph,
algorithm: BuchheimWalkerAlgorithm(
state.builder, TreeEdgeRenderer(state.builder)),
paint: Paint()
..color = Colors.green
..strokeWidth = 1
..style = PaintingStyle.stroke,
builder: (Node node) {
// I can decide what widget should be shown here based on the id
var nodeName = node.key!.value;
return rectangleWidget(nodeName, node, context);
},
)),
);
} else {
return Container();
}
})
],
),
),
);
}
}
// return SafeArea(
// child: Container(
// padding: const EdgeInsets.all(24),
// width: MediaQuery.sizeOf(context).width,
// height: MediaQuery.sizeOf(context).height,
// alignment: AlignmentDirectional.center,
// child: Column(
// mainAxisSize: MainAxisSize.max,
// crossAxisAlignment: CrossAxisAlignment.center,
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// BlocBuilder<HomeBloc, HomeState>(builder: (context, state) {
// if (state is HomeInitial) {
// return Wrap(
// children: [
// SizedBox(
// width: 100,
// child: TextFormField(
// decoration: const InputDecoration(
// labelText: "Subtree separation"),
// onChanged: (text) {
// firstNodeName = text;
// },
// ),
// ),
// const SizedBox(
// width: 8,
// ),
// Container(
// width: 100,
// child: TextFormField(
// decoration: InputDecoration(labelText: "Node Name"),
// onChanged: (text) {
// secondNodeName = text;
// },
// ),
// ),
// ElevatedButton(
// onPressed: () {
// final node1 = Node.Id(firstNodeName);
// final node2 = Node.Id(secondNodeName);
// context.read<HomeBloc>().add(CreateNewNode(
// sourceNode: node1, destinationNode: node2));
// },
// child: Text("Add"),
// )
// ],
// );
// }
// if (state is HomeUpdateTree) {
// return Expanded(
// child: InteractiveViewer(
// constrained: false,
// boundaryMargin: const EdgeInsets.all(100),
// minScale: 0.01,
// maxScale: 5.6,
// child: GraphView(
// graph: state.graph,
// algorithm: BuchheimWalkerAlgorithm(
// state.builder, TreeEdgeRenderer(state.builder)),
// paint: Paint()
// ..color = Colors.green
// ..strokeWidth = 1
// ..style = PaintingStyle.stroke,
// builder: (Node node) {
// // I can decide what widget should be shown here based on the id
// var nodeName = node.key!.value;
// return rectangleWidget(nodeName, node, context);
// },
// )),
// );
// } else {
// return Container();
// }
// })
// ],
// ),
// ),
// );
// }
// }
Widget rectangleWidget(String text, Node node, BuildContext blocContext) {
String nodeName = '';
return InkWell(
onTap: () {
showDialog(
context: blocContext,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Add a child'),
content: TextField(
decoration:
const InputDecoration(hintText: 'Enter your text here'),
onChanged: (value) {
nodeName = value;
},
),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('Close'),
),
TextButton(
onPressed: () {
if (nodeName.isNotEmpty) {
final newNode = Node.Id(nodeName);
blocContext.read<HomeBloc>().add(CreateNewNode(
sourceNode: node, destinationNode: newNode));
}
Navigator.of(context).pop();
},
child: Text('Add'),
),
],
);
},
);
},
child: Container(
width: MediaQuery.of(blocContext).size.width * 0.2,
margin: EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0),
padding: EdgeInsets.all(20.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10.0),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3), // changes position of shadow
),
],
),
child: Row(
children: [
const SizedBox(
child: Icon(
Icons.location_on,
color: Colors.blue,
size: 40.0,
),
),
const SizedBox(width: 10.0),
SizedBox(
child: Text(
text,
style: const TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.bold,
),
),
),
const Spacer(),
Container(
child: const Icon(
Icons.add_circle_outline,
color: Colors.grey,
size: 24.0,
),
),
],
),
),
);
}
// Widget rectangleWidget(String text, Node node, BuildContext blocContext) {
// String nodeName = '';
// return InkWell(
// onTap: () {
// showDialog(
// context: blocContext,
// builder: (BuildContext context) {
// return AlertDialog(
// title: const Text('Add a child'),
// content: TextField(
// decoration:
// const InputDecoration(hintText: 'Enter your text here'),
// onChanged: (value) {
// nodeName = value;
// },
// ),
// actions: <Widget>[
// TextButton(
// onPressed: () {
// Navigator.of(context).pop();
// },
// child: Text('Close'),
// ),
// TextButton(
// onPressed: () {
// if (nodeName.isNotEmpty) {
// final newNode = Node.Id(nodeName);
// blocContext.read<HomeBloc>().add(CreateNewNode(
// sourceNode: node, destinationNode: newNode));
// }
// Navigator.of(context).pop();
// },
// child: Text('Add'),
// ),
// ],
// );
// },
// );
// },
// child: Container(
// width: MediaQuery.of(blocContext).size.width * 0.2,
// margin: EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0),
// padding: EdgeInsets.all(20.0),
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(10.0),
// boxShadow: [
// BoxShadow(
// color: Colors.grey.withOpacity(0.5),
// spreadRadius: 2,
// blurRadius: 5,
// offset: Offset(0, 3), // changes position of shadow
// ),
// ],
// ),
// child: Row(
// children: [
// const SizedBox(
// child: Icon(
// Icons.location_on,
// color: Colors.blue,
// size: 40.0,
// ),
// ),
// const SizedBox(width: 10.0),
// SizedBox(
// child: Text(
// text,
// style: const TextStyle(
// fontSize: 24.0,
// fontWeight: FontWeight.bold,
// ),
// ),
// ),
// const Spacer(),
// Container(
// child: const Icon(
// Icons.add_circle_outline,
// color: Colors.grey,
// size: 24.0,
// ),
// ),
// ],
// ),
// ),
// );
// }
@@ -0,0 +1,42 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_state.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/role_model.dart';
class RolesPermissionBloc
extends Bloc<RolesPermissionEvent, RolesPermissionState> {
RolesPermissionBloc() : super(RolesInitial()) {
on<GetRoles>(_getRoles);
on<ChangeTapSelected>(changeTapSelected);
}
List<RoleModel> roleModel = [];
FutureOr<void> _getRoles(
GetRoles event, Emitter<RolesPermissionState> emit) async {
emit(UsersLoadingState());
try {
roleModel = [
RoleModel(roleId: '1', roleImage: '', roleName: 'Admin'),
RoleModel(roleId: '2', roleImage: '', roleName: 'Security'),
RoleModel(roleId: '2', roleImage: '', roleName: 'Reception'),
];
emit(UsersLoadedState());
} catch (e) {
emit(ErrorState(e.toString()));
}
}
bool tapSelect = true;
changeTapSelected(
ChangeTapSelected event, Emitter<RolesPermissionState> emit) {
try {
emit(RolesLoadingState());
tapSelect = event.selected;
emit(ChangeTapStatus(select: !tapSelect));
} catch (e) {
emit(ErrorState(e.toString()));
}
}
}
@@ -0,0 +1,41 @@
import 'package:equatable/equatable.dart';
sealed class RolesPermissionEvent extends Equatable {
const RolesPermissionEvent();
}
class GetRoles extends RolesPermissionEvent {
const GetRoles();
@override
List<Object?> get props => [];
}
class GetBatchStatus extends RolesPermissionEvent {
final List<String> uuids;
const GetBatchStatus(this.uuids);
@override
List<Object?> get props => [uuids];
}
class GetDeviceRecords extends RolesPermissionEvent {
final String uuid;
const GetDeviceRecords(this.uuid);
@override
List<Object?> get props => [uuid];
}
class GetDeviceAutomationRecords extends RolesPermissionEvent {
final String uuid;
const GetDeviceAutomationRecords(this.uuid);
@override
List<Object?> get props => [uuid];
}
class ChangeTapSelected extends RolesPermissionEvent {
final bool selected;
const ChangeTapSelected(this.selected);
@override
List<Object?> get props => [selected];
}
@@ -0,0 +1,79 @@
import 'package:equatable/equatable.dart';
sealed class RolesPermissionState extends Equatable {
const RolesPermissionState();
}
final class RolesInitial extends RolesPermissionState {
@override
List<Object> get props => [];
}
final class RolesLoadingState extends RolesPermissionState {
@override
List<Object> get props => [];
}
final class UsersLoadingState extends RolesPermissionState {
@override
List<Object> get props => [];
}
final class RolesLoadedState extends RolesPermissionState {
@override
List<Object> get props => [];
}
final class UsersLoadedState extends RolesPermissionState {
@override
List<Object> get props => [];
}
final class ErrorState extends RolesPermissionState {
final String message;
const ErrorState(this.message);
@override
List<Object> get props => [message];
}
/// report state
final class SosReportLoadingState extends RolesPermissionState {
@override
List<Object> get props => [];
}
final class RolesErrorState extends RolesPermissionState {
final String message;
const RolesErrorState(this.message);
@override
List<Object> get props => [message];
}
/// automation reports
final class SosAutomationReportLoadingState extends RolesPermissionState {
@override
List<Object> get props => [];
}
final class SosAutomationReportErrorState extends RolesPermissionState {
final String message;
const SosAutomationReportErrorState(this.message);
@override
List<Object> get props => [message];
}
final class ChangeTapStatus extends RolesPermissionState {
final bool select;
const ChangeTapStatus({required this.select});
@override
List<Object> get props => [select];
}
@@ -0,0 +1,267 @@
// import 'dart:convert';
// // Model for Space
// class UserSpaceModel {
// final String uuid;
// final DateTime createdAt;
// final DateTime updatedAt;
// UserSpaceModel({
// required this.uuid,
// required this.createdAt,
// required this.updatedAt,
// });
// factory UserSpaceModel.fromJson(Map<String, dynamic> json) {
// return UserSpaceModel(
// uuid: json['uuid'],
// createdAt: DateTime.parse(json['createdAt']),
// updatedAt: DateTime.parse(json['updatedAt']),
// );
// }
// Map<String, dynamic> toJson() {
// return {
// 'uuid': uuid,
// 'createdAt': createdAt.toIso8601String(),
// 'updatedAt': updatedAt.toIso8601String(),
// };
// }
// }
// // Model for User
// class EditUserModel {
// final String uuid;
// final DateTime createdAt;
// final dynamic email;
// final dynamic? jobTitle;
// final dynamic status;
// final String firstName;
// final String lastName;
// final String? phoneNumber;
// final bool isEnabled;
// final dynamic invitedBy;
// final dynamic roleType;
// final List<UserSpaceModel> spaces;
// final String createdDate;
// final String createdTime;
// EditUserModel({
// required this.uuid,
// required this.createdAt,
// required this.email,
// this.jobTitle,
// required this.status,
// required this.firstName,
// required this.lastName,
// this.phoneNumber,
// required this.isEnabled,
// required this.invitedBy,
// required this.roleType,
// required this.spaces,
// required this.createdDate,
// required this.createdTime,
// });
// factory EditUserModel.fromJson(Map<String, dynamic> json) {
// var spacesList = (json['spaces'] as List)
// .map((spaceJson) => UserSpaceModel.fromJson(spaceJson))
// .toList();
// return EditUserModel(
// uuid: json['uuid'],
// createdAt: DateTime.parse(json['createdAt']),
// email: json['email'],
// jobTitle: json['jobTitle'],
// status: json['status'],
// firstName: json['firstName'],
// lastName: json['lastName'],
// phoneNumber: json['phoneNumber'],
// isEnabled: json['isEnabled'],
// invitedBy: json['invitedBy'],
// roleType: json['roleType'],
// spaces: spacesList,
// createdDate: json['createdDate'],
// createdTime: json['createdTime'],
// );
// }
// Map<String, dynamic> toJson() {
// return {
// 'uuid': uuid,
// 'createdAt': createdAt.toIso8601String(),
// 'email': email,
// 'jobTitle': jobTitle,
// 'status': status,
// 'firstName': firstName,
// 'lastName': lastName,
// 'phoneNumber': phoneNumber,
// 'isEnabled': isEnabled,
// 'invitedBy': invitedBy,
// 'roleType': roleType,
// 'spaces': spaces.map((space) => space.toJson()).toList(),
// 'createdDate': createdDate,
// 'createdTime': createdTime,
// };
// }
// }
class UserProjectResponse {
final int statusCode;
final String message;
final EditUserModel data;
final bool success;
UserProjectResponse({
required this.statusCode,
required this.message,
required this.data,
required this.success,
});
/// Create a [UserProjectResponse] from JSON data
factory UserProjectResponse.fromJson(Map<String, dynamic> json) {
return UserProjectResponse(
statusCode: json['statusCode'] as int,
message: json['message'] as String,
data: EditUserModel.fromJson(json['data'] as Map<String, dynamic>),
success: json['success'] as bool,
);
}
/// Convert the [UserProjectResponse] to JSON
Map<String, dynamic> toJson() {
return {
'statusCode': statusCode,
'message': message,
'data': data.toJson(),
'success': success,
};
}
}
class EditUserModel {
final String uuid;
final String firstName;
final String lastName;
final String email;
final String createdDate; // e.g. "1/3/2025"
final String createdTime; // e.g. "8:41:43 AM"
final String status; // e.g. "invited"
final String invitedBy; // e.g. "SUPER_ADMIN"
final String? phoneNumber; // can be empty
final String? jobTitle; // can be empty
final String roleType; // e.g. "ADMIN"
final List<UserSpaceModel> spaces;
EditUserModel({
required this.uuid,
required this.firstName,
required this.lastName,
required this.email,
required this.createdDate,
required this.createdTime,
required this.status,
required this.invitedBy,
required this.phoneNumber,
required this.jobTitle,
required this.roleType,
required this.spaces,
});
/// Create a [UserData] from JSON data
factory EditUserModel.fromJson(Map<String, dynamic> json) {
return EditUserModel(
uuid: json['uuid'] as String,
firstName: json['firstName'] as String,
lastName: json['lastName'] as String,
email: json['email'] as String,
createdDate: json['createdDate'] as String,
createdTime: json['createdTime'] as String,
status: json['status'] as String,
invitedBy: json['invitedBy'] as String,
phoneNumber: json['phoneNumber'] ?? '',
jobTitle: json['jobTitle'] ?? '',
roleType: json['roleType'] as String,
spaces: (json['spaces'] as List<dynamic>)
.map((e) => UserSpaceModel.fromJson(e as Map<String, dynamic>))
.toList(),
);
}
/// Convert the [UserData] to JSON
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'firstName': firstName,
'lastName': lastName,
'email': email,
'createdDate': createdDate,
'createdTime': createdTime,
'status': status,
'invitedBy': invitedBy,
'phoneNumber': phoneNumber,
'jobTitle': jobTitle,
'roleType': roleType,
'spaces': spaces.map((e) => e.toJson()).toList(),
};
}
}
class UserSpaceModel {
final String uuid;
final String createdAt; // e.g. "2024-11-04T07:20:35.940Z"
final String updatedAt; // e.g. "2024-11-28T18:47:29.736Z"
final dynamic spaceTuyaUuid;
final dynamic spaceName;
final dynamic invitationCode;
final bool disabled;
final double x;
final double y;
final String icon;
UserSpaceModel({
required this.uuid,
required this.createdAt,
required this.updatedAt,
required this.spaceTuyaUuid,
required this.spaceName,
required this.invitationCode,
required this.disabled,
required this.x,
required this.y,
required this.icon,
});
/// Create a [UserSpaceModel] from JSON data
factory UserSpaceModel.fromJson(Map<String, dynamic> json) {
return UserSpaceModel(
uuid: json['uuid'] as String,
createdAt: json['createdAt'] as String,
updatedAt: json['updatedAt'] as String,
spaceTuyaUuid: json['spaceTuyaUuid'] as String?,
spaceName: json['spaceName'] as String,
invitationCode: json['invitationCode'] as String?,
disabled: json['disabled'] as bool,
x: (json['x'] as num).toDouble(),
y: (json['y'] as num).toDouble(),
icon: json['icon'] as String,
);
}
/// Convert the [UserSpaceModel] to JSON
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'createdAt': createdAt,
'updatedAt': updatedAt,
'spaceTuyaUuid': spaceTuyaUuid,
'spaceName': spaceName,
'invitationCode': invitationCode,
'disabled': disabled,
'x': x,
'y': y,
'icon': icon,
};
}
}
@@ -0,0 +1,6 @@
class RoleModel {
String? roleId;
String? roleName;
String? roleImage;
RoleModel({this.roleId, this.roleName, this.roleImage});
}
@@ -0,0 +1,22 @@
class RoleTypeModel {
final String uuid;
final String createdAt;
final String updatedAt;
final String type;
RoleTypeModel({
required this.uuid,
required this.createdAt,
required this.updatedAt,
required this.type,
});
factory RoleTypeModel.fromJson(Map<String, dynamic> json) {
return RoleTypeModel(
uuid: json['uuid'],
createdAt: json['createdAt'],
updatedAt: json['updatedAt'],
type: json['type'].toString().toLowerCase().replaceAll("_", " "),
);
}
}
@@ -0,0 +1,52 @@
class RolesUserModel {
final String uuid;
final DateTime createdAt;
final String email;
final dynamic firstName;
final dynamic lastName;
final dynamic roleType;
final dynamic status;
final bool isEnabled;
final String invitedBy;
final dynamic phoneNumber;
final dynamic jobTitle;
final dynamic createdDate;
final dynamic createdTime;
RolesUserModel({
required this.uuid,
required this.createdAt,
required this.email,
required this.firstName,
required this.lastName,
required this.roleType,
required this.status,
required this.isEnabled,
required this.invitedBy,
this.phoneNumber,
this.jobTitle,
required this.createdDate,
required this.createdTime,
});
factory RolesUserModel.fromJson(Map<String, dynamic> json) {
return RolesUserModel(
uuid: json['uuid'],
createdAt: DateTime.parse(json['createdAt']),
email: json['email'],
firstName: json['firstName'],
lastName: json['lastName'],
roleType: json['roleType'].toString().toLowerCase().replaceAll("_", " "),
status: json['status'],
isEnabled: json['isEnabled'],
invitedBy:
json['invitedBy'].toString().toLowerCase().replaceAll("_", " "),
phoneNumber: json['phoneNumber'],
jobTitle: json['jobTitle'] == null || json['jobTitle'] == " "
? "_"
: json['jobTitle'],
createdDate: json['createdDate'],
createdTime: json['createdTime'],
);
}
}
@@ -0,0 +1,603 @@
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/custom_dialog.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/edit_user_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/services/space_mana_api.dart';
import 'package:syncrow_web/services/user_permission.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class UsersBloc extends Bloc<UsersEvent, UsersState> {
UsersBloc() : super(UsersInitial()) {
on<CheckStepStatus>(isCompleteBasicsFun);
on<LoadCommunityAndSpacesEvent>(_onLoadCommunityAndSpaces);
on<SearchAnode>(searchTreeNode);
on<CheckSpacesStepStatus>(isCompleteSpacesFun);
on<RoleEvent>(_getRolePermission);
on<PermissionEvent>(_getPermissions);
on<SearchPermission>(searchRolePermission);
on<SendInviteUsers>(_sendInvitUser);
on<ValidateBasicsStep>(_validateBasicsStep);
on<CheckRoleStepStatus>(isCompleteRoleFun);
on<CheckEmailEvent>(checkEmail);
on<GetUserByIdEvent>(getUserById);
on<ToggleNodeExpansion>(_onToggleNodeExpansion);
on<ToggleNodeCheck>(_onToggleNodeCheck);
on<EditInviteUsers>(_editInviteUser);
}
void _validateBasicsStep(ValidateBasicsStep event, Emitter<UsersState> emit) {
if (formKey.currentState?.validate() ?? false) {
emit(const BasicsStepValidState());
} else {
emit(const BasicsStepInvalidState());
}
}
String roleSelected = '';
final formKey = GlobalKey<FormState>();
final TextEditingController firstNameController = TextEditingController();
final TextEditingController lastNameController = TextEditingController();
final TextEditingController emailController = TextEditingController();
final TextEditingController phoneController = TextEditingController();
final TextEditingController jobTitleController = TextEditingController();
final TextEditingController roleSearchController = TextEditingController();
bool? isCompleteBasics;
bool? isCompleteRolePermissions;
bool? isCompleteSpaces;
int numberBasics = 0;
int numberSpaces = 0;
int numberRole = 0;
void isCompleteSpacesFun(
CheckSpacesStepStatus event, Emitter<UsersState> emit) {
emit(UsersLoadingState());
List<String> selectedIds = getSelectedIds(updatedCommunities);
isCompleteSpaces = selectedIds.isNotEmpty;
emit(ChangeStatusSteps());
}
void isCompleteRoleFun(CheckRoleStepStatus event, Emitter<UsersState> emit) {
emit(UsersLoadingState());
isCompleteRolePermissions = roleSelected != '';
emit(ChangeStatusSteps());
}
Future<List<SpaceModel>> _fetchSpacesForCommunity(
String communityUuid) async {
return await CommunitySpaceManagementApi().getSpaceHierarchy(communityUuid);
}
List<TreeNode> updatedCommunities = [];
List<TreeNode> spacesNodes = [];
List<String> communityIds = [];
_onLoadCommunityAndSpaces(
LoadCommunityAndSpacesEvent event, Emitter<UsersState> emit) async {
try {
emit(UsersLoadingState());
List<CommunityModel> communities =
await CommunitySpaceManagementApi().fetchCommunities();
communityIds = communities.map((community) => community.uuid).toList();
updatedCommunities = await Future.wait(
communities.map((community) async {
List<SpaceModel> spaces =
await _fetchSpacesForCommunity(community.uuid);
spacesNodes = _buildTreeNodes(spaces);
return TreeNode(
uuid: community.uuid,
title: community.name,
children: spacesNodes,
isChecked: false,
isHighlighted: false,
isExpanded: true,
);
}).toList(),
);
originalCommunities = updatedCommunities;
emit(const SpacesLoadedState());
} catch (e) {
emit(ErrorState('Error loading communities and spaces: $e'));
}
}
// This variable holds the full original list.
List<TreeNode> originalCommunities = [];
// This variable holds the working list that may be filtered.
// Build tree nodes from your data model.
List<TreeNode> _buildTreeNodes(List<SpaceModel> spaces) {
return spaces.map((space) {
List<TreeNode> childNodes =
space.children.isNotEmpty ? _buildTreeNodes(space.children) : [];
return TreeNode(
uuid: space.uuid!,
title: space.name,
isChecked: false,
isHighlighted: false,
isExpanded: childNodes.isNotEmpty,
children: childNodes,
);
}).toList();
}
// Optional helper method to deep clone a TreeNode.
TreeNode _cloneNode(TreeNode node) {
return TreeNode(
uuid: node.uuid,
title: node.title,
isChecked: node.isChecked,
isHighlighted: node.isHighlighted,
isExpanded: node.isExpanded,
children: node.children.map(_cloneNode).toList(),
);
}
// Clone an entire list of tree nodes.
List<TreeNode> _cloneNodes(List<TreeNode> nodes) {
return nodes.map(_cloneNode).toList();
}
// Your search event handler.
void searchTreeNode(SearchAnode event, Emitter<UsersState> emit) {
emit(UsersLoadingState());
// If the search term is empty, restore the original list.
if (event.searchTerm!.isEmpty) {
// Clear any highlights on the restored copy.
updatedCommunities = _cloneNodes(originalCommunities);
_clearHighlights(updatedCommunities);
} else {
// Start with a fresh clone of the original tree.
List<TreeNode> freshClone = _cloneNodes(originalCommunities);
_searchAndHighlightNodes(freshClone, event.searchTerm!);
updatedCommunities = _filterNodes(freshClone, event.searchTerm!);
}
emit(ChangeStatusSteps());
}
void _clearHighlights(List<TreeNode> nodes) {
for (var node in nodes) {
node.isHighlighted = false;
if (node.children.isNotEmpty) {
_clearHighlights(node.children);
}
}
}
bool _searchAndHighlightNodes(List<TreeNode> nodes, String searchTerm) {
bool anyMatch = false;
for (var node in nodes) {
bool isMatch =
node.title.toLowerCase().contains(searchTerm.toLowerCase());
bool childMatch = _searchAndHighlightNodes(node.children, searchTerm);
node.isHighlighted = isMatch || childMatch;
anyMatch = anyMatch || node.isHighlighted;
}
return anyMatch;
}
List<TreeNode> _filterNodes(List<TreeNode> nodes, String searchTerm) {
List<TreeNode> filteredNodes = [];
for (var node in nodes) {
bool isMatch =
node.title.toLowerCase().contains(searchTerm.toLowerCase());
List<TreeNode> filteredChildren = _filterNodes(node.children, searchTerm);
if (isMatch || filteredChildren.isNotEmpty) {
node.isHighlighted = isMatch;
node.children = filteredChildren;
filteredNodes.add(node);
}
}
return filteredNodes;
}
// List<TreeNode> _buildTreeNodes(List<SpaceModel> spaces) {
// return spaces.map((space) {
// List<TreeNode> childNodes =
// space.children.isNotEmpty ? _buildTreeNodes(space.children) : [];
// return TreeNode(
// uuid: space.uuid!,
// title: space.name,
// isChecked: false,
// isHighlighted: false,
// isExpanded: childNodes.isNotEmpty,
// children: childNodes,
// );
// }).toList();
// }
// void searchTreeNode(SearchAnode event, Emitter<UsersState> emit) {
// emit(UsersLoadingState());
// if (event.searchTerm!.isEmpty) {
// _clearHighlights(updatedCommunities);
// } else {
// _searchAndHighlightNodes(updatedCommunities, event.searchTerm!);
// updatedCommunities = _filterNodes(updatedCommunities, event.searchTerm!);
// }
// emit(ChangeStatusSteps());
// }
// void _clearHighlights(List<TreeNode> nodes) {
// for (var node in nodes) {
// node.isHighlighted = false;
// if (node.children.isNotEmpty) {
// _clearHighlights(node.children);
// }
// }
// }
// bool _searchAndHighlightNodes(List<TreeNode> nodes, String searchTerm) {
// bool anyMatch = false;
// for (var node in nodes) {
// bool isMatch =
// node.title.toLowerCase().contains(searchTerm.toLowerCase());
// bool childMatch = _searchAndHighlightNodes(node.children, searchTerm);
// node.isHighlighted = isMatch || childMatch;
// anyMatch = anyMatch || node.isHighlighted;
// }
// return anyMatch;
// }
// List<TreeNode> _filterNodes(List<TreeNode> nodes, String searchTerm) {
// List<TreeNode> filteredNodes = [];
// for (var node in nodes) {
// // Check if the current node's title contains the search term.
// bool isMatch =
// node.title.toLowerCase().contains(searchTerm.toLowerCase());
// // Recursively filter the children.
// List<TreeNode> filteredChildren = _filterNodes(node.children, searchTerm);
// // If the current node is a match or any of its children are, include it.
// if (isMatch || filteredChildren.isNotEmpty) {
// // Optionally, update any properties (like isHighlighted) if you still need them.
// node.isHighlighted = isMatch;
// // Replace the children with the filtered ones.
// node.children = filteredChildren;
// filteredNodes.add(node);
// }
// }
// return filteredNodes;
// }
List<String> selectedIds = [];
List<String> getSelectedIds(List<TreeNode> nodes) {
List<String> selectedIds = [];
for (var node in nodes) {
if (node.isChecked) {
selectedIds.add(node.uuid);
}
if (node.children.isNotEmpty) {
selectedIds.addAll(getSelectedIds(node.children));
}
}
return selectedIds;
}
List<RoleTypeModel> roles = [];
List<PermissionOption> permissions = [];
_getRolePermission(RoleEvent event, Emitter<UsersState> emit) async {
try {
emit(UsersLoadingState());
roles = await UserPermissionApi().fetchRoles();
emit(RolePermissionInitial());
} catch (e) {
emit(ErrorState('Error loading communities and spaces: $e'));
}
}
_getPermissions(PermissionEvent event, Emitter<UsersState> emit) async {
try {
emit(UsersLoadingState());
permissions = await UserPermissionApi().fetchPermission(
event.roleUuid == "" ? roles.first.uuid : event.roleUuid);
roleSelected = event.roleUuid!;
emit(RolePermissionInitial());
} catch (e) {
emit(ErrorState('Error loading communities and spaces: $e'));
}
}
bool _searchRolePermission(List<PermissionOption> nodes, String searchTerm) {
bool anyMatch = false;
for (var node in nodes) {
bool isMatch =
node.title.toLowerCase().contains(searchTerm.toLowerCase());
bool childMatch = _searchRolePermission(node.subOptions, searchTerm);
node.isHighlighted = isMatch || childMatch;
anyMatch = anyMatch || node.isHighlighted;
}
return anyMatch;
}
void _sendInvitUser(SendInviteUsers event, Emitter<UsersState> emit) async {
try {
emit(UsersLoadingState());
List<String> selectedIds = getSelectedIds(updatedCommunities)
.where((id) => !communityIds.contains(id))
.toList();
bool res = await UserPermissionApi().sendInviteUser(
email: emailController.text,
firstName: firstNameController.text,
jobTitle: jobTitleController.text,
lastName: lastNameController.text,
phoneNumber: phoneController.text,
roleUuid: roleSelected,
spaceUuids: selectedIds,
);
if (res) {
showCustomDialog(
barrierDismissible: false,
context: event.context,
message: "The invite was sent successfully.",
iconPath: Assets.deviceNoteIcon,
title: "Invite Success",
dialogHeight: MediaQuery.of(event.context).size.height * 0.3,
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(event.context).pop(true);
Navigator.of(event.context).pop(true);
},
child: const Text('OK'),
),
],
);
} else {
emit(const ErrorState('Failed to send invite.'));
}
emit(SaveState());
} catch (e) {
emit(ErrorState('Failed to send invite: ${e.toString()}'));
}
}
_editInviteUser(EditInviteUsers event, Emitter<UsersState> emit) async {
try {
emit(UsersLoadingState());
List<String> selectedIds = getSelectedIds(updatedCommunities)
.where((id) => !communityIds.contains(id))
.toList();
bool res = await UserPermissionApi().editInviteUser(
userId: event.userId,
firstName: firstNameController.text,
jobTitle: jobTitleController.text,
lastName: lastNameController.text,
phoneNumber: phoneController.text,
roleUuid: roleSelected,
spaceUuids: selectedIds,
);
if (res == true) {
showCustomDialog(
barrierDismissible: false,
context: event.context,
message: "The invite was sent successfully.",
iconPath: Assets.deviceNoteIcon,
title: "Invite Success",
dialogHeight: MediaQuery.of(event.context).size.height * 0.3,
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(event.context).pop(true);
Navigator.of(event.context).pop(true);
},
child: const Text('OK'),
),
],
).then(
(value) {},
);
} else {
emit(const ErrorState('Failed to send invite.'));
}
emit(SaveState());
} catch (e) {
emit(ErrorState('Failed to send invite: ${e.toString()}'));
}
}
void searchRolePermission(SearchPermission event, Emitter<UsersState> emit) {
emit(UsersLoadingState());
if (event.searchTerm!.isEmpty) {
_clearHighlightsRolePermission(permissions);
} else {
_searchRolePermission(permissions, event.searchTerm!);
}
emit(ChangeStatusSteps());
}
String checkEmailValid = '';
Future<void> checkEmail(
CheckEmailEvent event, Emitter<UsersState> emit) async {
emit(UsersLoadingState());
String? res = await UserPermissionApi().checkEmail(
emailController.text,
);
checkEmailValid = res!;
emit(ChangeStatusSteps());
}
bool isCompleteBasicsFun(CheckStepStatus event, Emitter<UsersState> emit) {
emit(UsersLoadingState());
if (event.isEditUser == false) {
add(const CheckEmailEvent());
final emailRegex = RegExp(
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
);
bool isEmailValid = emailRegex.hasMatch(emailController.text);
bool isEmailServerValid = checkEmailValid == 'Valid email';
isCompleteBasics = firstNameController.text.isNotEmpty &&
lastNameController.text.isNotEmpty &&
emailController.text.isNotEmpty &&
isEmailValid &&
isEmailServerValid;
} else {
isCompleteBasics = firstNameController.text.isNotEmpty &&
lastNameController.text.isNotEmpty;
}
emit(ChangeStatusSteps());
emit(ValidateBasics());
return isCompleteBasics!;
}
void _clearHighlightsRolePermission(List<PermissionOption> nodes) {
for (var node in nodes) {
node.isHighlighted = false;
if (node.subOptions.isNotEmpty) {
_clearHighlightsRolePermission(node.subOptions);
}
}
}
EditUserModel? res = EditUserModel(
spaces: [],
jobTitle: '',
phoneNumber: '',
uuid: '',
email: '',
firstName: '',
lastName: '',
roleType: '',
status: '',
invitedBy: '',
createdDate: '',
createdTime: '');
Future<void> getUserById(
GetUserByIdEvent event,
Emitter<UsersState> emit,
) async {
emit(UsersLoadingState());
try {
if (event.uuid?.isNotEmpty ?? false) {
final res = await UserPermissionApi().fetchUserById(event.uuid);
if (res != null) {
// Populate the text controllers
firstNameController.text = res.firstName;
lastNameController.text = res.lastName;
emailController.text = res.email;
phoneController.text = res.phoneNumber ?? '';
jobTitleController.text = res.jobTitle ?? '';
res.roleType;
if (updatedCommunities.isNotEmpty) {
// Create a list of UUIDs to mark
final uuidsToMark = res.spaces.map((space) => space.uuid).toList();
// Print all IDs and mark nodes in updatedCommunities
debugPrint('Printing and marking nodes in updatedCommunities:');
_printAndMarkNodes(updatedCommunities, uuidsToMark);
}
final roleId = roles
.firstWhere((element) =>
element.type ==
res.roleType.toString().toLowerCase().replaceAll("_", " "))
.uuid;
debugPrint('Role ID: $roleId');
roleSelected = roleId;
add(PermissionEvent(roleUuid: roleSelected));
emit(ChangeStatusSteps());
} else {}
} else {}
} catch (_) {}
}
void _printAndMarkNodes(List<TreeNode> nodes, List<String> uuidsToMark,
[int level = 0]) {
for (final node in nodes) {
if (uuidsToMark.contains(node.uuid)) {
node.isChecked = true;
debugPrint(
'${' ' * level}MATCH FOUND: Node ID: ${node.uuid}, Title: ${node.title} is marked as checked.');
} else {
debugPrint(
'${' ' * level}Node ID: ${node.uuid}, Title: ${node.title}');
}
if (node.children.isNotEmpty) {
_printAndMarkNodes(node.children, uuidsToMark, level + 1);
}
}
}
void _onToggleNodeExpansion(
ToggleNodeExpansion event,
Emitter<UsersState> emit,
) {
emit(UsersLoadingState());
event.node.isExpanded = !event.node.isExpanded;
emit(ChangeStatusSteps());
}
void _onToggleNodeCheck(
ToggleNodeCheck event,
Emitter<UsersState> emit,
) {
emit(UsersLoadingState());
//Toggle node's checked state
event.node.isChecked = !event.node.isChecked;
debugPrint(
'Node toggled. ID: ${event.node.uuid}, isChecked: ${event.node.isChecked}',
);
// Update children and parent
_updateChildrenCheckStatus(event.node, event.node.isChecked);
_updateParentCheckStatus(event.node);
// Finally, emit a new state
emit(ChangeStatusSteps());
}
void _updateChildrenCheckStatus(TreeNode node, bool isChecked) {
for (var child in node.children) {
child.isChecked = isChecked;
_updateChildrenCheckStatus(child, isChecked);
}
}
void _updateParentCheckStatus(TreeNode node) {
TreeNode? parent = _findParent(updatedCommunities, node);
if (parent != null) {
parent.isChecked = _areAllChildrenChecked(parent);
_updateParentCheckStatus(parent);
}
}
bool _areAllChildrenChecked(TreeNode node) {
return node.children.isNotEmpty &&
node.children.every((child) =>
child.isChecked &&
(child.children.isEmpty || _areAllChildrenChecked(child)));
}
TreeNode? _findParent(List<TreeNode> nodes, TreeNode target) {
for (var node in nodes) {
if (node.children.contains(target)) {
return node;
}
final parent = _findParent(node.children, target);
if (parent != null) {
return parent;
}
}
return null;
}
}
@@ -0,0 +1,176 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart';
sealed class UsersEvent extends Equatable {
const UsersEvent();
}
class SendInviteUsers extends UsersEvent {
final BuildContext context;
const SendInviteUsers({required this.context});
@override
List<Object?> get props => [context];
}
class EditInviteUsers extends UsersEvent {
final BuildContext context;
final String userId;
const EditInviteUsers({required this.context, required this.userId});
@override
List<Object?> get props => [context, userId];
}
class CheckSpacesStepStatus extends UsersEvent {
const CheckSpacesStepStatus();
@override
List<Object?> get props => [];
}
class CheckRoleStepStatus extends UsersEvent {
const CheckRoleStepStatus();
@override
List<Object?> get props => [];
}
class LoadCommunityAndSpacesEvent extends UsersEvent {
const LoadCommunityAndSpacesEvent();
@override
List<Object?> get props => [];
}
class RoleEvent extends UsersEvent {
const RoleEvent();
@override
List<Object?> get props => [];
}
class PermissionEvent extends UsersEvent {
final String? roleUuid;
const PermissionEvent({this.roleUuid = ""});
@override
List<Object?> get props => [roleUuid];
}
class GetBatchStatus extends UsersEvent {
final List<String> uuids;
const GetBatchStatus(this.uuids);
@override
List<Object?> get props => [uuids];
}
//isEditUser:widget.userId!=''? false:true
class CheckStepStatus extends UsersEvent {
final int? steps;
final bool? isEditUser;
const CheckStepStatus({this.steps, required this.isEditUser});
@override
List<Object?> get props => [steps];
}
class SearchAnode extends UsersEvent {
final List<TreeNode>? nodes;
final String? searchTerm;
const SearchAnode({this.nodes, this.searchTerm});
@override
List<Object?> get props => [nodes, searchTerm];
}
class SearchPermission extends UsersEvent {
final List<PermissionOption>? nodes;
final String? searchTerm;
const SearchPermission({this.nodes, this.searchTerm});
@override
List<Object?> get props => [nodes, searchTerm];
}
class SelectedId extends UsersEvent {
final List<TreeNode>? nodes;
const SelectedId({
this.nodes,
});
@override
List<Object?> get props => [nodes];
}
class ValidateBasicsStep extends UsersEvent {
const ValidateBasicsStep();
@override
List<Object?> get props => [];
}
class CheckEmailEvent extends UsersEvent {
const CheckEmailEvent();
@override
List<Object?> get props => [];
}
class GetUserByIdEvent extends UsersEvent {
final String? uuid;
const GetUserByIdEvent({this.uuid});
@override
List<Object?> get props => [uuid];
}
class ToggleNodeExpansion extends UsersEvent {
final TreeNode node;
const ToggleNodeExpansion({required this.node});
@override
List<Object?> get props => [node];
}
class UpdateNodeCheckStatus extends UsersEvent {
final TreeNode node;
const UpdateNodeCheckStatus({required this.node});
@override
List<Object?> get props => [node];
}
class ToggleNodeHighlightEvent extends UsersEvent {
final TreeNode node;
const ToggleNodeHighlightEvent(this.node);
@override
List<Object?> get props => [node];
}
class ExpandAllNodesEvent extends UsersEvent {
@override
List<Object?> get props => [];
}
class CollapseAllNodesEvent extends UsersEvent {
@override
List<Object?> get props => [];
}
class ClearSelectionsEvent extends UsersEvent {
@override
List<Object?> get props => [];
}
class ToggleNodeCheckEvent extends UsersEvent {
final TreeNode node;
const ToggleNodeCheckEvent(this.node);
@override
List<Object?> get props => [];
}
class ToggleNodeCheck extends UsersEvent {
final TreeNode node;
const ToggleNodeCheck(this.node);
@override
List<Object?> get props => [];
}
class EditUserEvent extends UsersEvent {
const EditUserEvent();
@override
List<Object?> get props => [];
}
@@ -0,0 +1,91 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart';
sealed class UsersState extends Equatable {
const UsersState();
}
final class UsersInitial extends UsersState {
@override
List<Object> get props => [];
}
final class RolePermissionInitial extends UsersState {
@override
List<Object> get props => [];
}
final class ChangeStatusSteps extends UsersState {
@override
List<Object> get props => [];
}
final class UsersLoadingState extends UsersState {
@override
List<Object> get props => [];
}
final class SaveState extends UsersState {
@override
List<Object> get props => [];
}
final class SpacesLoadedState extends UsersState {
const SpacesLoadedState();
@override
List<Object> get props => [];
}
final class ErrorState extends UsersState {
final String message;
const ErrorState(this.message);
@override
List<Object> get props => [message];
}
final class RolesErrorState extends UsersState {
final String message;
const RolesErrorState(this.message);
@override
List<Object> get props => [message];
}
/// automation reports
final class ChangeTapStatus extends UsersState {
final bool select;
const ChangeTapStatus({required this.select});
@override
List<Object> get props => [select];
}
final class BasicsStepValidState extends UsersState {
const BasicsStepValidState();
@override
List<Object> get props => [];
}
class BasicsStepInvalidState extends UsersState {
const BasicsStepInvalidState();
@override
List<Object> get props => [];
}
final class ValidateBasics extends UsersState {
@override
List<Object> get props => [];
}
class UsersLoadedState extends UsersState {
final List<TreeNode> updatedCommunities;
const UsersLoadedState({required this.updatedCommunities});
@override
List<Object> get props => [];
}
@@ -0,0 +1,42 @@
class PermissionOption {
String id;
String title;
bool isChecked;
bool isHighlighted;
List<PermissionOption> subOptions;
PermissionOption({
required this.id,
required this.title,
this.isChecked = false,
this.isHighlighted = false,
this.subOptions = const [],
});
factory PermissionOption.fromJson(Map<String, dynamic> json) {
return PermissionOption(
id: json['id'] ?? '',
title: json['title'] != null
? json['title'].toString().toLowerCase().replaceAll("_", " ")
: '',
isChecked: json['isChecked'] ?? false,
isHighlighted: json['isHighlighted'] ?? false,
subOptions: (json['subOptions'] as List?)
?.map((sub) => PermissionOption.fromJson(sub))
.toList() ??
[],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'isChecked': isChecked,
'isHighlighted': isHighlighted,
'subOptions': subOptions.map((sub) => sub.toJson()).toList(),
};
}
}
enum CheckState { none, some, all }
@@ -0,0 +1,17 @@
class TreeNode {
String uuid;
String title;
bool isChecked;
bool isHighlighted;
bool isExpanded;
List<TreeNode> children;
TreeNode({
required this.uuid,
required this.title,
this.isChecked = false,
this.isHighlighted = false,
this.isExpanded = false,
this.children = const [],
});
}
@@ -0,0 +1,364 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class AddNewUserDialog extends StatefulWidget {
const AddNewUserDialog({super.key});
@override
_AddNewUserDialogState createState() => _AddNewUserDialogState();
}
class _AddNewUserDialogState extends State<AddNewUserDialog> {
int currentStep = 1;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => UsersBloc()
..add(const LoadCommunityAndSpacesEvent())
..add(const RoleEvent()),
child: BlocConsumer<UsersBloc, UsersState>(listener: (context, state) {
// print('88888==${state}');
// if (state is SpacesLoadedState) {
// print('object');
// _blocRole.add(const CheckRoleStepStatus());
// }
}, builder: (context, state) {
final _blocRole = BlocProvider.of<UsersBloc>(context);
return Dialog(
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(20))),
width: 900,
child: Column(
children: [
// Title
const Padding(
padding: EdgeInsets.all(8.0),
child: SizedBox(
child: Text(
"Add New User",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: ColorsManager.secondaryColor),
),
),
),
const Divider(),
Expanded(
child: Row(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildStep1Indicator(1, "Basics", _blocRole),
_buildStep2Indicator(2, "Spaces", _blocRole),
_buildStep3Indicator(
3, "Role & Permissions", _blocRole),
],
),
),
),
Container(
width: 1,
color: ColorsManager.grayBorder,
),
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 10),
Expanded(
child: _getFormContent(),
),
const SizedBox(height: 20),
],
),
),
),
],
),
),
const Divider(),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
InkWell(
onTap: () {
Navigator.of(context).pop();
},
child: const Text("Cancel"),
),
InkWell(
onTap: () {
_blocRole.add(const CheckEmailEvent());
setState(() {
if (currentStep < 3) {
currentStep++;
if (currentStep == 2) {
_blocRole.add(
const CheckStepStatus(isEditUser: false));
} else if (currentStep == 3) {
_blocRole.add(const CheckSpacesStepStatus());
}
} else {
_blocRole.add(SendInviteUsers(context: context));
}
});
},
child: Text(
currentStep < 3 ? "Next" : "Save",
style: TextStyle(
color: (_blocRole.isCompleteSpaces == false ||
_blocRole.isCompleteBasics == false ||
_blocRole.isCompleteRolePermissions ==
false) &&
currentStep == 3
? ColorsManager.grayColor
: ColorsManager.secondaryColor),
),
),
],
),
),
],
),
));
}));
}
Widget _getFormContent() {
switch (currentStep) {
case 1:
return const BasicsView(
userId: '',
);
case 2:
return const SpacesAccessView();
case 3:
return const RolesAndPermission();
default:
return Container();
}
}
int step3 = 0;
Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) {
return GestureDetector(
onTap: () {
setState(() {
bloc.add(const CheckSpacesStepStatus());
currentStep = step;
Future.delayed(const Duration(milliseconds: 500), () {
bloc.add(const ValidateBasicsStep());
});
});
if (step3 == 3) {
bloc.add(const CheckRoleStepStatus());
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: [
SvgPicture.asset(
currentStep == step
? Assets.currentProcessIcon
: bloc.isCompleteBasics == false
? Assets.wrongProcessIcon
: bloc.isCompleteBasics == true
? Assets.completeProcessIcon
: Assets.uncomplete_ProcessIcon,
width: 25,
height: 25,
),
const SizedBox(width: 10),
Text(
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
if (step != 3)
Padding(
padding: const EdgeInsets.all(5.0),
child: Padding(
padding: const EdgeInsets.only(left: 12),
child: Container(
height: 60,
width: 1,
color: Colors.grey,
),
),
)
],
),
);
}
Widget _buildStep2Indicator(int step, String label, UsersBloc bloc) {
return GestureDetector(
onTap: () {
setState(() {
bloc.add(const CheckStepStatus(isEditUser: false));
currentStep = step;
Future.delayed(const Duration(milliseconds: 500), () {
bloc.add(const CheckStepStatus(isEditUser: false));
});
if (step3 == 3) {
Future.delayed(const Duration(seconds: 1), () {
bloc.add(const CheckRoleStepStatus());
});
}
});
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: [
SvgPicture.asset(
currentStep == step
? Assets.currentProcessIcon
: bloc.isCompleteSpaces == false
? Assets.wrongProcessIcon
: bloc.isCompleteSpaces == true
? Assets.completeProcessIcon
: Assets.uncomplete_ProcessIcon,
width: 25,
height: 25,
),
const SizedBox(width: 10),
Text(
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
if (step != 3)
Padding(
padding: const EdgeInsets.all(5.0),
child: Padding(
padding: const EdgeInsets.only(left: 12),
child: Container(
height: 60,
width: 1,
color: Colors.grey,
),
),
)
],
),
);
}
Widget _buildStep3Indicator(int step, String label, UsersBloc bloc) {
return GestureDetector(
onTap: () {
setState(() {
currentStep = step;
step3 = step;
bloc.add(const CheckSpacesStepStatus());
bloc.add(CheckStepStatus(isEditUser: false));
});
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: [
SvgPicture.asset(
currentStep == step
? Assets.currentProcessIcon
: bloc.isCompleteRolePermissions == false
? Assets.wrongProcessIcon
: bloc.isCompleteRolePermissions == true
? Assets.completeProcessIcon
: Assets.uncomplete_ProcessIcon,
width: 25,
height: 25,
),
const SizedBox(width: 10),
Text(
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
if (step != 3)
Padding(
padding: const EdgeInsets.all(5.0),
child: Padding(
padding: const EdgeInsets.only(left: 12),
child: Container(
height: 60,
width: 1,
color: Colors.grey,
),
),
)
],
),
);
}
}
@@ -0,0 +1,342 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl_phone_field/countries.dart';
import 'package:intl_phone_field/country_picker_dialog.dart';
import 'package:intl_phone_field/intl_phone_field.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.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 BasicsView extends StatelessWidget {
final String? userId;
const BasicsView({super.key, this.userId = ''});
@override
Widget build(BuildContext context) {
return BlocBuilder<UsersBloc, UsersState>(builder: (context, state) {
final _blocRole = BlocProvider.of<UsersBloc>(context);
if (state is BasicsStepInvalidState) {
_blocRole.formKey.currentState?.validate();
}
return Form(
key: _blocRole.formKey,
child: ListView(
shrinkWrap: true,
children: [
Text(
'Set up the basics',
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 20,
color: Colors.black),
),
const SizedBox(
height: 50,
),
Text(
'To get started, fill out some basic information about who you’re adding as a user.',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
),
),
const SizedBox(
height: 35,
),
Row(
children: [
Flexible(
child: SizedBox(
// width: MediaQuery.of(context).size.width * 0.18,
height: MediaQuery.of(context).size.width * 0.08,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
child: Row(
children: [
const Text(
" * ",
style: TextStyle(
color: ColorsManager.red,
fontWeight: FontWeight.w900,
fontSize: 15,
),
),
Text(
'First Name',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 13,
),
),
],
)),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
style: const TextStyle(
color: ColorsManager.blackColor),
// onChanged: (value) {
// Future.delayed(const Duration(milliseconds: 200),
// () {
// _blocRole.add(const ValidateBasicsStep());
// });
// },
controller: _blocRole.firstNameController,
decoration: inputTextFormDeco(
hintText: "Enter first name",
).copyWith(
hintStyle: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter first name';
}
return null;
},
),
),
],
),
),
),
const SizedBox(width: 10),
Flexible(
child: SizedBox(
// width: MediaQuery.of(context).size.width * 0.18,
height: MediaQuery.of(context).size.width * 0.08,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
child: Row(
children: [
const Text(
" * ",
style: TextStyle(
color: ColorsManager.red,
fontWeight: FontWeight.w900,
fontSize: 15,
),
),
Text('Last Name',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 13,
)),
],
)),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
// onChanged: (value) {
// Future.delayed(const Duration(milliseconds: 200),
// () {
// _blocRole.add(ValidateBasicsStep());
// });
// },
controller: _blocRole.lastNameController,
style: const TextStyle(color: Colors.black),
decoration:
inputTextFormDeco(hintText: "Enter last name")
.copyWith(
hintStyle: context.textTheme.bodyMedium
?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray)),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter last name';
}
return null;
},
),
),
],
),
),
),
],
),
const SizedBox(height: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
child: Row(
children: [
const Text(
" * ",
style: TextStyle(
color: ColorsManager.red,
fontWeight: FontWeight.w900,
fontSize: 15,
),
),
Text(
'Email Address',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 13,
),
),
],
)),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
enabled: userId != '' ? false : true,
// onChanged: (value) {
// Future.delayed(const Duration(milliseconds: 200), () {
// _blocRole.add(CheckStepStatus(
// isEditUser: userId != '' ? false : true));
// _blocRole.add(ValidateBasicsStep());
// });
// },
controller: _blocRole.emailController,
style: const TextStyle(color: ColorsManager.blackColor),
decoration: inputTextFormDeco(hintText: "[email protected]")
.copyWith(
hintStyle: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter Email Address';
}
final emailRegex = RegExp(
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
);
if (!emailRegex.hasMatch(value)) {
return 'Enter a valid Email Address';
}
if (_blocRole.checkEmailValid != "Valid email") {
return _blocRole.checkEmailValid;
}
// return null;
},
),
),
],
),
const SizedBox(height: 10),
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
child: Row(
children: [
Text(
'Mobile Number',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 13,
),
),
],
)),
IntlPhoneField(
pickerDialogStyle: PickerDialogStyle(),
dropdownIconPosition: IconPosition.leading,
disableLengthCheck: true,
dropdownTextStyle:
const TextStyle(color: ColorsManager.blackColor),
textInputAction: TextInputAction.done,
decoration: inputTextFormDeco(
hintText: "05x xxx xxxx",
).copyWith(
hintStyle: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray),
),
initialCountryCode: 'AE',
countries: const [
Country(
name: "United Arab Emirates",
nameTranslations: {
"en": "United Arab Emirates",
"ar": "الإمارات العربية المتحدة",
},
flag: "🇦🇪",
code: "AE",
dialCode: "971",
minLength: 9,
maxLength: 9,
),
Country(
name: "Saudi Arabia",
nameTranslations: {
"en": "Saudi Arabia",
"ar": "السعودية",
},
flag: "🇸🇦",
code: "SA",
dialCode: "966",
minLength: 9,
maxLength: 9,
),
],
style: const TextStyle(color: Colors.black),
controller: _blocRole.phoneController,
)
],
),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
child: Row(
children: [
Text(
'Job Title',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 13,
),
),
],
)),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: _blocRole.jobTitleController,
style:
const TextStyle(color: ColorsManager.blackColor),
decoration: inputTextFormDeco(
hintText: "Job Title (Optional)")
.copyWith(
hintStyle: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray),
),
keyboardType: TextInputType.phone,
),
),
],
),
),
],
),
const SizedBox(height: 20),
],
),
);
});
}
}
@@ -0,0 +1,146 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class TreeView extends StatelessWidget {
final String? userId;
const TreeView({
super.key,
this.userId,
});
@override
Widget build(BuildContext context) {
final _blocRole = BlocProvider.of<UsersBloc>(context);
debugPrint('TreeView constructed with userId = $userId');
return BlocProvider(
create: (_) => UsersBloc(),
// ..add(const LoadCommunityAndSpacesEvent()),
child: BlocConsumer<UsersBloc, UsersState>(
listener: (context, state) {
// if (state is SpacesLoadedState) {
// _blocRole.add(GetUserByIdEvent(uuid: userId));
// }
},
builder: (context, state) {
if (state is UsersLoadingState) {
return const Center(child: CircularProgressIndicator());
}
return SingleChildScrollView(
child: _buildTree(_blocRole.updatedCommunities, _blocRole),
);
},
),
);
}
Widget _buildTree(
List<TreeNode> nodes,
UsersBloc bloc, {
int level = 0,
}) {
return Column(
children: nodes.map((node) {
return Container(
color: node.isHighlighted ? Colors.blue.shade50 : Colors.transparent,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: [
/// Checkbox (GestureDetector)
GestureDetector(
onTap: () {
bloc.add(ToggleNodeCheck(node));
},
child: Image.asset(
_getCheckBoxImage(node),
width: 20,
height: 20,
),
),
const SizedBox(width: 15),
Expanded(
child: Padding(
padding: EdgeInsets.only(left: level * 10.0),
child: Row(
children: [
GestureDetector(
onTap: () {
bloc.add(ToggleNodeExpansion(node: node));
},
child: node.children.isNotEmpty
? SvgPicture.asset(
node.isExpanded
? Assets.arrowDown
: Assets.arrowForward,
fit: BoxFit.none,
)
: const SizedBox(width: 16),
),
const SizedBox(width: 20),
Text(
node.title,
style: TextStyle(
fontSize: 16,
color: node.isHighlighted
? ColorsManager.blackColor
: ColorsManager.textGray,
),
),
],
),
),
),
],
),
),
if (node.isExpanded)
_buildTree(
node.children,
bloc,
level: level + 1,
),
],
),
);
}).toList(),
);
}
String _getCheckBoxImage(TreeNode node) {
if (node.children.isEmpty) {
return node.isChecked ? Assets.CheckBoxChecked : Assets.emptyBox;
}
if (_areAllChildrenChecked(node)) {
return Assets.CheckBoxChecked;
} else if (_areSomeChildrenChecked(node)) {
return Assets.rectangleCheckBox;
} else {
return Assets.emptyBox;
}
}
bool _areAllChildrenChecked(TreeNode node) {
return node.children.isNotEmpty &&
node.children.every((child) =>
child.isChecked &&
(child.children.isEmpty || _areAllChildrenChecked(child)));
}
bool _areSomeChildrenChecked(TreeNode node) {
return node.children.isNotEmpty &&
node.children.any((child) =>
child.isChecked ||
(child.children.isNotEmpty && _areSomeChildrenChecked(child)));
}
}
@@ -0,0 +1,143 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class DeleteUserDialog extends StatefulWidget {
final Function()? onTapDelete;
DeleteUserDialog({super.key, this.onTapDelete});
@override
_DeleteUserDialogState createState() => _DeleteUserDialogState();
}
class _DeleteUserDialogState extends State<DeleteUserDialog> {
bool isLoading = false;
bool _isDisposed = false;
@override
void dispose() {
_isDisposed = true;
super.dispose();
}
@override
Widget build(BuildContext context) {
return Dialog(
child: Container(
height: 160,
width: 200,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(20))),
child: Column(
children: [
const Padding(
padding: EdgeInsets.all(8.0),
child: SizedBox(
child: Text(
"Delete User",
style: TextStyle(
color: ColorsManager.red,
fontSize: 18,
fontWeight: FontWeight.bold),
),
),
),
const Padding(
padding: EdgeInsets.only(
left: 25,
right: 25,
),
child: Divider(),
),
const Expanded(
child: Padding(
padding: EdgeInsets.only(left: 25, right: 25, top: 10, bottom: 10),
child: Text(
"Are you sure you want to delete this user?",
textAlign: TextAlign.center,
),
)),
Row(
children: [
Expanded(
child: InkWell(
onTap: () {
Navigator.of(context).pop(false); // Return false if canceled
},
child: Container(
padding: const EdgeInsets.all(10),
decoration: const BoxDecoration(
border: Border(
right: BorderSide(
color: ColorsManager.grayBorder,
width: 0.5,
),
top: BorderSide(
color: ColorsManager.grayBorder,
width: 1,
),
),
),
child: const Center(child: Text('Cancel'))),
)),
Expanded(
child: InkWell(
onTap: isLoading
? null
: () async {
setState(() {
isLoading = true;
});
try {
if (widget.onTapDelete != null) {
await widget.onTapDelete!();
}
} finally {
if (!_isDisposed) {
setState(() {
isLoading = false;
});
}
}
Navigator.of(context).pop(true);
},
child: Container(
padding: const EdgeInsets.all(10),
decoration: const BoxDecoration(
border: Border(
left: BorderSide(
color: ColorsManager.grayBorder,
width: 0.5,
),
top: BorderSide(
color: ColorsManager.grayBorder,
width: 1,
),
),
),
child: Center(
child: isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
color: ColorsManager.red,
strokeWidth: 2.0,
),
)
: const Text(
'Delete',
style: TextStyle(
color: ColorsManager.red,
),
))),
)),
],
)
],
),
));
}
}
@@ -0,0 +1,364 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class EditUserDialog extends StatefulWidget {
final String? userId;
const EditUserDialog({super.key, this.userId});
@override
_EditUserDialogState createState() => _EditUserDialogState();
}
class _EditUserDialogState extends State<EditUserDialog> {
int currentStep = 1;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => UsersBloc()
..add(const LoadCommunityAndSpacesEvent())
..add(const RoleEvent())
..add(GetUserByIdEvent(uuid: widget.userId)),
child: BlocConsumer<UsersBloc, UsersState>(listener: (context, state) {
if (state is SpacesLoadedState) {
BlocProvider.of<UsersBloc>(context)
.add(GetUserByIdEvent(uuid: widget.userId));
}
}, builder: (context, state) {
final _blocRole = BlocProvider.of<UsersBloc>(context);
return Dialog(
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(20))),
width: 900,
child: Column(
children: [
// Title
const Padding(
padding: EdgeInsets.all(8.0),
child: SizedBox(
child: Text(
"Edit User",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: ColorsManager.secondaryColor),
),
),
),
const Divider(),
Expanded(
child: Row(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildStep1Indicator(1, "Basics", _blocRole),
_buildStep2Indicator(2, "Spaces", _blocRole),
_buildStep3Indicator(
3, "Role & Permissions", _blocRole),
],
),
),
),
Container(
width: 1,
color: ColorsManager.grayBorder,
),
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 10),
Expanded(
child: _getFormContent(widget.userId),
),
const SizedBox(height: 20),
],
),
),
),
],
),
),
const Divider(),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
InkWell(
onTap: () {
Navigator.of(context).pop();
},
child: const Text("Cancel"),
),
InkWell(
onTap: () {
// _blocRole.add(const CheckEmailEvent());
setState(() {
if (currentStep < 3) {
currentStep++;
if (currentStep == 2) {
_blocRole
.add(CheckStepStatus(isEditUser: true));
} else if (currentStep == 3) {
_blocRole.add(const CheckSpacesStepStatus());
}
} else {
_blocRole.add(EditInviteUsers(
context: context,
userId: widget.userId!));
}
});
},
child: Text(
currentStep < 3 ? "Next" : "Save",
style: TextStyle(
color: (_blocRole.isCompleteSpaces == false ||
_blocRole.isCompleteBasics == false ||
_blocRole.isCompleteRolePermissions ==
false) &&
currentStep == 3
? ColorsManager.grayColor
: ColorsManager.secondaryColor),
),
),
],
),
),
],
),
));
}));
}
Widget _getFormContent(userid) {
switch (currentStep) {
case 1:
return BasicsView(
userId: userid,
);
case 2:
return SpacesAccessView(
userId: userid,
);
case 3:
return const RolesAndPermission();
default:
return Container();
}
}
int step3 = 0;
Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) {
return GestureDetector(
onTap: () {
setState(() {
bloc.add(const CheckSpacesStepStatus());
currentStep = step;
Future.delayed(const Duration(milliseconds: 500), () {
bloc.add(ValidateBasicsStep());
});
});
if (step3 == 3) {
bloc.add(const CheckRoleStepStatus());
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: [
SvgPicture.asset(
currentStep == step
? Assets.currentProcessIcon
: bloc.isCompleteBasics == false
? Assets.wrongProcessIcon
: bloc.isCompleteBasics == true
? Assets.completeProcessIcon
: Assets.uncomplete_ProcessIcon,
width: 25,
height: 25,
),
const SizedBox(width: 10),
Text(
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
if (step != 3)
Padding(
padding: const EdgeInsets.all(5.0),
child: Padding(
padding: const EdgeInsets.only(left: 12),
child: Container(
height: 60,
width: 1,
color: Colors.grey,
),
),
)
],
),
);
}
Widget _buildStep2Indicator(int step, String label, UsersBloc bloc) {
return GestureDetector(
onTap: () {
setState(() {
currentStep = step;
bloc.add(CheckStepStatus(isEditUser: true));
if (step3 == 3) {
bloc.add(const CheckRoleStepStatus());
}
});
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: [
SvgPicture.asset(
currentStep == step
? Assets.currentProcessIcon
: bloc.isCompleteSpaces == false
? Assets.wrongProcessIcon
: bloc.isCompleteSpaces == true
? Assets.completeProcessIcon
: Assets.uncomplete_ProcessIcon,
width: 25,
height: 25,
),
const SizedBox(width: 10),
Text(
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
if (step != 3)
Padding(
padding: const EdgeInsets.all(5.0),
child: Padding(
padding: const EdgeInsets.only(left: 12),
child: Container(
height: 60,
width: 1,
color: Colors.grey,
),
),
)
],
),
);
}
Widget _buildStep3Indicator(int step, String label, UsersBloc bloc) {
return GestureDetector(
onTap: () {
setState(() {
currentStep = step;
step3 = step;
bloc.add(const CheckSpacesStepStatus());
bloc.add(CheckStepStatus(isEditUser: true));
});
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: [
SvgPicture.asset(
currentStep == step
? Assets.currentProcessIcon
: bloc.isCompleteRolePermissions == false
? Assets.wrongProcessIcon
: bloc.isCompleteRolePermissions == true
? Assets.completeProcessIcon
: Assets.uncomplete_ProcessIcon,
width: 25,
height: 25,
),
const SizedBox(width: 10),
Text(
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
if (step != 3)
Padding(
padding: const EdgeInsets.all(5.0),
child: Padding(
padding: const EdgeInsets.only(left: 12),
child: Container(
height: 60,
width: 1,
color: Colors.grey,
),
),
)
],
),
);
}
}
@@ -0,0 +1,295 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class PermissionManagement extends StatefulWidget {
final UsersBloc? bloc;
const PermissionManagement({Key? key, this.bloc}) : super(key: key);
@override
_PermissionManagementState createState() => _PermissionManagementState();
}
class _PermissionManagementState extends State<PermissionManagement> {
void toggleOptionById(String id) {
setState(() {
for (var mainOption in widget.bloc!.permissions) {
if (mainOption.id == id) {
final isChecked =
checkifOneOfthemChecked(mainOption) == CheckState.all;
mainOption.isChecked = !isChecked;
for (var subOption in mainOption.subOptions) {
subOption.isChecked = !isChecked;
for (var child in subOption.subOptions) {
child.isChecked = !isChecked;
}
}
return;
}
for (var subOption in mainOption.subOptions) {
if (subOption.id == id) {
subOption.isChecked = !subOption.isChecked;
for (var child in subOption.subOptions) {
child.isChecked = subOption.isChecked;
}
mainOption.isChecked =
mainOption.subOptions.every((sub) => sub.isChecked);
return;
}
for (var child in subOption.subOptions) {
if (child.id == id) {
child.isChecked = !child.isChecked;
subOption.isChecked =
subOption.subOptions.every((child) => child.isChecked);
mainOption.isChecked =
mainOption.subOptions.every((sub) => sub.isChecked);
return;
}
}
}
}
});
}
CheckState checkifOneOfthemChecked(PermissionOption mainOption) {
bool allSelected = true;
bool someSelected = false;
for (var subOption in mainOption.subOptions) {
if (subOption.isChecked) {
someSelected = true;
} else {
allSelected = false;
}
for (var child in subOption.subOptions) {
if (child.isChecked) {
someSelected = true;
} else {
allSelected = false;
}
}
}
if (allSelected) {
return CheckState.all;
} else if (someSelected) {
return CheckState.some;
} else {
return CheckState.none;
}
}
@override
Widget build(BuildContext context) {
return ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: widget.bloc!.permissions.length,
itemBuilder: (context, index) {
final option = widget.bloc!.permissions[index];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
InkWell(
// onTap: () => toggleOptionById(option.id),
child: Builder(
builder: (context) {
final checkState = checkifOneOfthemChecked(option);
if (checkState == CheckState.all) {
return Image.asset(
Assets.CheckBoxChecked,
width: 20,
height: 20,
);
} else if (checkState == CheckState.some) {
return Image.asset(
Assets.rectangleCheckBox,
width: 20,
height: 20,
);
} else {
return Image.asset(
Assets.emptyBox,
width: 20,
height: 20,
);
}
},
),
),
const SizedBox(width: 8),
Text(
' ${option.title.isNotEmpty ? option.title[0].toUpperCase() : ''}${option.title.substring(1)}',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 12,
color: ColorsManager.blackColor),
),
],
),
const SizedBox(
height: 10,
),
...option.subOptions.map((subOption) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color: option.isHighlighted
? Colors.blue.shade50
: Colors.white,
child: Row(
children: [
InkWell(
// onTap: () => toggleOptionById(subOption.id),
child: Builder(
builder: (context) {
final checkState =
checkifOneOfthemChecked(PermissionOption(
id: subOption.id,
title: subOption.title,
subOptions: [subOption],
));
if (checkState == CheckState.all) {
return Image.asset(
Assets.CheckBoxChecked,
width: 20,
height: 20,
);
} else if (checkState == CheckState.some) {
return Image.asset(
Assets.rectangleCheckBox,
width: 20,
height: 20,
);
} else {
return Image.asset(
Assets.emptyBox,
width: 20,
height: 20,
);
}
},
),
),
const SizedBox(width: 8),
Text(
' ${subOption.title.isNotEmpty ? subOption.title[0].toUpperCase() : ''}${subOption.title.substring(1)}',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 12,
color: ColorsManager.lightGreyColor),
),
],
),
),
Padding(
padding: const EdgeInsets.only(left: 50.0),
child: GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 2.0,
crossAxisSpacing: 0.2,
childAspectRatio: 5,
),
itemCount: subOption.subOptions.length,
itemBuilder: (context, index) {
final child = subOption.subOptions[index];
return Container(
color: option.isHighlighted
? Colors.blue.shade50
: Colors.white,
child: Row(
children: [
Builder(
builder: (context) {
final checkState =
checkifOneOfthemChecked(PermissionOption(
id: child.id,
title: child.title,
subOptions: [child],
));
if (checkState == CheckState.all) {
return Image.asset(
Assets.CheckBoxChecked,
width: 20,
height: 20,
);
} else if (checkState == CheckState.some) {
return Image.asset(
Assets.rectangleCheckBox,
width: 20,
height: 20,
);
} else {
return Image.asset(
Assets.emptyBox,
width: 20,
height: 20,
);
}
},
),
const SizedBox(width: 8),
Text(
' ${child.title.isNotEmpty ? child.title[0].toUpperCase() : ''}${child.title.substring(1)}',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.lightGreyColor),
),
],
),
);
},
),
)
],
);
}).toList(),
],
);
},
);
}
}
// Container(
// height: 50,
// width: 120,
// child: CheckboxListTile(
// activeColor: ColorsManager.dialogBlueTitle,
// selectedTileColor: child.isHighlighted
// ? Colors.blue.shade50
// : Colors.white,
// dense: true,
// controlAffinity:
// ListTileControlAffinity.leading,
// title: Text(
// child.title,
// style: context.textTheme.bodyMedium?.copyWith(
// fontWeight: FontWeight.w400,
// fontSize: 12,
// color: ColorsManager.lightGreyColor),
// ),
// value: child.isChecked,
// onChanged: (value) =>
// toggleOptionById(child.id),
// enabled: false,
// ),
// ),
@@ -0,0 +1,162 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart';
Future<void> showPopUpFilterMenu({
required BuildContext context,
required Function(String value) onSortAtoZ,
required Function(String value) onSortZtoA,
Function()? cancelButton,
required Map<String, bool> checkboxStates,
required RelativeRect position,
Function()? onOkPressed,
List<String>? list,
String? isSelected,
}) async {
await showMenu(
context: context,
position: position,
color: ColorsManager.whiteColors,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
),
items: <PopupMenuEntry>[
PopupMenuItem(
enabled: false,
child: StatefulBuilder(
builder: (context, setState) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
child: ListTile(
onTap: () {
setState(() {
if (isSelected == 'Asc') {
isSelected = null;
onSortAtoZ.call('');
} else {
onSortAtoZ.call('Asc');
isSelected = 'Asc';
}
});
},
leading: Image.asset(
Assets.AtoZIcon,
width: 25,
),
title: Text(
"Sort A to Z",
style: TextStyle(
color: isSelected == "Asc"
? ColorsManager.blackColor
: ColorsManager.grayColor),
),
),
),
ListTile(
onTap: () {
setState(() {
if (isSelected == 'Desc') {
isSelected = null;
onSortZtoA.call('');
} else {
onSortZtoA.call('Desc');
isSelected = 'Desc';
}
});
},
leading: Image.asset(
Assets.ZtoAIcon,
width: 25,
),
title: Text(
"Sort Z to A",
style: TextStyle(
color: isSelected == "Desc"
? ColorsManager.blackColor
: ColorsManager.grayColor),
),
),
const Divider(),
const Text(
"Filter by ",
style: TextStyle(fontWeight: FontWeight.bold),
),
Container(
decoration: containerDecoration.copyWith(
boxShadow: [],
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10),
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10))),
padding: const EdgeInsets.all(10),
height: 200,
width: 400,
child: Container(
padding: const EdgeInsets.all(10),
color: Colors.white,
child: ListView.builder(
itemCount: list?.length ?? 0,
itemBuilder: (context, index) {
final item = list![index];
return Row(
children: [
Checkbox(
value: checkboxStates[item],
onChanged: (bool? newValue) {
checkboxStates[item] = newValue ?? false;
(context as Element).markNeedsBuild();
},
),
Text(
item,
style: TextStyle(color: ColorsManager.grayColor),
),
],
);
},
),
),
),
const SizedBox(
height: 10,
),
const Divider(),
const SizedBox(
height: 10,
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () {
Navigator.of(context).pop(); // Close the menu
},
child: const Text("Cancel"),
),
GestureDetector(
onTap: onOkPressed,
child: const Text(
"OK",
style: TextStyle(
color: ColorsManager.spaceColor,
),
),
),
],
),
const SizedBox(
height: 10,
),
],
);
},
),
),
],
);
}
@@ -0,0 +1,117 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/style.dart';
class RoleDropdown extends StatefulWidget {
final UsersBloc? bloc;
const RoleDropdown({super.key, this.bloc});
@override
_RoleDropdownState createState() => _RoleDropdownState();
}
class _RoleDropdownState extends State<RoleDropdown> {
late String selectedRole;
@override
void initState() {
super.initState();
selectedRole = widget.bloc!.roleSelected;
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Text(
" * ",
style: TextStyle(
color: ColorsManager.red,
fontWeight: FontWeight.w900,
fontSize: 15,
),
),
Text(
"Role",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: Colors.black,
),
),
],
),
const SizedBox(height: 8),
SizedBox(
child: DropdownButtonFormField<String>(
dropdownColor: ColorsManager.whiteColors,
// alignment: Alignment.,
focusColor: Colors.white,
autofocus: true,
value: selectedRole.isNotEmpty ? selectedRole : null,
items: widget.bloc!.roles.map((role) {
return DropdownMenuItem<String>(
value: role.uuid,
child: Text(
' ${role.type.isNotEmpty ? role.type[0].toUpperCase() : ''}${role.type.substring(1)}',
),
);
}).toList(),
onChanged: (value) {
setState(() {
selectedRole = value!;
});
widget.bloc!.roleSelected = selectedRole;
widget.bloc!
.add(PermissionEvent(roleUuid: widget.bloc!.roleSelected));
},
icon: const SizedBox.shrink(),
borderRadius: const BorderRadius.all(Radius.circular(10)),
hint: const Padding(
padding: EdgeInsets.only(left: 10),
child: Text(
"Please Select",
style: TextStyle(
color: ColorsManager.textGray,
),
),
),
decoration: inputTextFormDeco().copyWith(
contentPadding: EdgeInsets.zero,
suffixIcon: Container(
padding: EdgeInsets.zero,
width: 70,
height: 45,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: const BorderRadius.only(
bottomRight: Radius.circular(10),
topRight: Radius.circular(10),
),
border: Border.all(
color: ColorsManager.textGray,
width: 1.0,
),
),
child: const Center(
child: Icon(
Icons.keyboard_arrow_down,
color: ColorsManager.textGray,
),
),
),
),
),
),
],
),
);
}
}
@@ -0,0 +1,125 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class RolesAndPermission extends StatelessWidget {
const RolesAndPermission({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<UsersBloc, UsersState>(builder: (context, state) {
final _blocRole = BlocProvider.of<UsersBloc>(context);
return Container(
color: Colors.white,
child: Form(
key: _blocRole.formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'Role & Permissions',
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 20,
color: Colors.black),
),
const SizedBox(
height: 15,
),
SizedBox(
width: 350,
height: 100,
child: RoleDropdown(
bloc: _blocRole,
)),
const SizedBox(height: 10),
Expanded(
child: SizedBox(
child: Column(
children: [
Expanded(
flex: 2,
child: Container(
decoration: const BoxDecoration(
color: ColorsManager.circleRolesBackground,
borderRadius: BorderRadius.only(
topRight: Radius.circular(20),
topLeft: Radius.circular(20)),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(20)),
border: Border.all(
color: ColorsManager.grayBorder)),
child: TextFormField(
style:
const TextStyle(color: Colors.black),
controller:
_blocRole.roleSearchController,
onChanged: (value) {
_blocRole.add(SearchPermission(
nodes: _blocRole.permissions,
searchTerm: value));
},
decoration: textBoxDecoration(radios: 20)!
.copyWith(
fillColor: Colors.white,
suffixIcon: Padding(
padding:
const EdgeInsets.only(right: 16),
child: SvgPicture.asset(
Assets.textFieldSearch,
width: 24,
height: 24,
),
),
hintStyle: context.textTheme.bodyMedium
?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray),
),
),
),
),
],
),
),
),
),
Expanded(
flex: 7,
child: Container(
color: ColorsManager.circleRolesBackground,
padding: const EdgeInsets.all(8.0),
child: Container(
color: ColorsManager.whiteColors,
child: PermissionManagement(
bloc: _blocRole,
))))
],
),
),
),
],
),
),
);
});
}
}
@@ -0,0 +1,122 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class SpacesAccessView extends StatelessWidget {
final String? userId;
const SpacesAccessView({super.key, this.userId = ''});
@override
Widget build(BuildContext context) {
return BlocBuilder<UsersBloc, UsersState>(builder: (context, state) {
final _blocRole = BlocProvider.of<UsersBloc>(context);
return Container(
color: Colors.white,
child: Form(
key: _blocRole.formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'Spaces access',
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 20,
color: Colors.black),
),
const SizedBox(
height: 35,
),
const SizedBox(
child: Text(
'Select the spaces you would like to grant access to for the user you are adding'),
),
const SizedBox(
height: 25,
),
Expanded(
child: SizedBox(
child: Column(
children: [
Expanded(
flex: 2,
child: Container(
decoration: const BoxDecoration(
color: ColorsManager.circleRolesBackground,
borderRadius: BorderRadius.only(
topRight: Radius.circular(20),
topLeft: Radius.circular(20)),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(20)),
border: Border.all(
color: ColorsManager.grayBorder)),
child: TextFormField(
style:
const TextStyle(color: Colors.black),
// controller: _blocRole.firstNameController,
onChanged: (value) {
_blocRole.add(SearchAnode(
nodes: _blocRole.updatedCommunities,
searchTerm: value));
},
decoration: textBoxDecoration(radios: 20)!
.copyWith(
fillColor: Colors.white,
suffixIcon: Padding(
padding:
const EdgeInsets.only(right: 16),
child: SvgPicture.asset(
Assets.textFieldSearch,
width: 24,
height: 24,
),
),
hintStyle: context.textTheme.bodyMedium
?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray),
),
),
),
),
],
),
),
),
),
Expanded(
flex: 7,
child: Container(
color: ColorsManager.circleRolesBackground,
padding: const EdgeInsets.all(8.0),
child: Container(
color: ColorsManager.whiteColors,
child: TreeView(userId: userId))))
],
),
),
),
],
),
),
);
});
}
}
@@ -0,0 +1,417 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart';
import 'package:syncrow_web/services/user_permission.dart';
class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
UserTableBloc() : super(TableInitial()) {
on<GetUsers>(_getUsers);
on<ChangeUserStatus>(_changeUserStatus);
on<SortUsersByNameAsc>(_toggleSortUsersByNameAsc);
on<SortUsersByNameDesc>(_toggleSortUsersByNameDesc);
on<DateOldestToNewestEvent>(_toggleSortUsersByDateOldestToNewest);
on<DateNewestToOldestEvent>(_toggleSortUsersByDateNewestToOldest);
on<SearchUsers>(_searchUsers);
on<ChangePage>(_handlePageChange);
on<FilterUsersByRoleEvent>(_filterUsersByRole);
on<FilterUsersByJobEvent>(_filterUsersByJobTitle);
on<FilterUsersByCreatedEvent>(_filterUsersByCreated);
on<FilterUsersByDeActevateEvent>(_filterUserStatus);
on<DeleteUserEvent>(_deleteUser);
on<FilterClearEvent>(_filterClear);
}
int itemsPerPage = 20;
int currentPage = 1;
List<RolesUserModel> users = [];
List<RolesUserModel> initialUsers = [];
List<RolesUserModel> totalUsersCount = [];
String currentSortOrder = '';
String currentSortJopTitle = '';
String currentSortRole = '';
String currentSortCreatedDate = '';
String currentSortStatus = '';
String currentSortCreatedBy = '';
String currentSortOrderDate = '';
List<String> roleTypes = [];
List<String> jobTitle = [];
List<String> createdBy = [];
List<String> status = ['active', 'invited', 'disabled'];
Future<void> _getUsers(GetUsers event, Emitter<UserTableState> emit) async {
emit(UsersLoadingState());
try {
roleTypes.clear();
jobTitle.clear();
createdBy.clear();
users = await UserPermissionApi().fetchUsers();
users.sort((a, b) {
final dateA = _parseDateTime(a.createdDate);
final dateB = _parseDateTime(b.createdDate);
return dateB.compareTo(dateA);
});
for (var user in users) {
roleTypes.add(user.roleType.toString());
}
for (var user in users) {
jobTitle.add(user.jobTitle.toString());
}
for (var user in users) {
createdBy.add(user.invitedBy.toString());
}
initialUsers = List.from(users);
roleTypes = roleTypes.toSet().toList();
jobTitle = jobTitle.toSet().toList();
createdBy = createdBy.toSet().toList();
_handlePageChange(ChangePage(1), emit);
totalUsersCount = initialUsers;
add(ChangePage(currentPage));
emit(UsersLoadedState(users: users));
} catch (e) {
emit(ErrorState(e.toString()));
}
}
Future<void> _deleteUser(
DeleteUserEvent event, Emitter<UserTableState> emit) async {
emit(UsersLoadingState());
try {
bool res = await UserPermissionApi().deleteUserById(event.userId);
if (res == true) {
Navigator.of(event.context).pop(true);
} else {
emit(const ErrorState('Something error'));
}
emit(UsersLoadedState(users: users));
} catch (e) {
emit(ErrorState(e.toString()));
}
}
Future<void> _changeUserStatus(
ChangeUserStatus event, Emitter<UserTableState> emit) async {
try {
emit(UsersLoadingState());
bool res = await UserPermissionApi().changeUserStatusById(
event.userId, event.newStatus == "disabled" ? false : true);
if (res == true) {
add(const GetUsers());
}
emit(UsersLoadedState(users: users));
} catch (e) {
emit(ErrorState(e.toString()));
}
}
void _toggleSortUsersByNameAsc(
SortUsersByNameAsc event, Emitter<UserTableState> emit) {
selectedRoles.clear();
selectedJobTitles.clear();
selectedCreatedBy.clear();
selectedStatuses.clear();
if (currentSortOrder == "Asc") {
emit(UsersLoadingState());
currentSortOrder = "";
users = List.from(users);
} else {
emit(UsersLoadingState());
currentSortOrder = "Asc";
users.sort((a, b) => a.firstName
.toString()
.toLowerCase()
.compareTo(b.firstName.toString().toLowerCase()));
}
currentSortJopTitle = '';
currentSortCreatedDate = '';
currentSortStatus = '';
currentSortCreatedBy = '';
emit(UsersLoadedState(users: users));
}
void _toggleSortUsersByNameDesc(
SortUsersByNameDesc event, Emitter<UserTableState> emit) {
selectedRoles.clear();
selectedJobTitles.clear();
selectedCreatedBy.clear();
selectedStatuses.clear();
if (currentSortOrder == "Desc") {
emit(UsersLoadingState());
currentSortOrder = "";
users = List.from(initialUsers);
} else {
emit(UsersLoadingState());
currentSortOrder = "Desc";
users.sort((a, b) => b.firstName!.compareTo(a.firstName!));
}
currentSortJopTitle = '';
currentSortCreatedDate = '';
currentSortStatus = '';
currentSortCreatedBy = '';
emit(UsersLoadedState(users: users));
}
void _toggleSortUsersByDateNewestToOldest(
DateNewestToOldestEvent event, Emitter<UserTableState> emit) {
selectedRoles.clear();
selectedJobTitles.clear();
selectedCreatedBy.clear();
selectedStatuses.clear();
if (currentSortOrderDate == "NewestToOldest") {
emit(UsersLoadingState());
currentSortOrder = "";
currentSortOrderDate = "";
users = List.from(initialUsers);
emit(UsersLoadedState(users: users));
} else {
emit(UsersLoadingState());
currentSortOrder = "NewestToOldest";
users.sort((a, b) {
final dateA = _parseDateTime(a.createdDate);
final dateB = _parseDateTime(b.createdDate);
return dateB.compareTo(dateA);
});
emit(UsersLoadedState(users: users));
}
}
void _toggleSortUsersByDateOldestToNewest(
DateOldestToNewestEvent event, Emitter<UserTableState> emit) {
selectedRoles.clear();
selectedJobTitles.clear();
selectedCreatedBy.clear();
selectedStatuses.clear();
if (currentSortOrderDate == "OldestToNewest") {
emit(UsersLoadingState());
currentSortOrder = "";
currentSortOrderDate = "";
users = List.from(initialUsers);
emit(UsersLoadedState(users: users));
} else {
emit(UsersLoadingState());
users.sort((a, b) {
final dateA = _parseDateTime(a.createdDate);
final dateB = _parseDateTime(b.createdDate);
return dateA.compareTo(dateB);
});
currentSortOrder = "OldestToNewest";
emit(UsersLoadedState(users: users));
}
}
DateTime _parseDateTime(String date) {
try {
final dateParts = date.split('/');
final day = int.parse(dateParts[0]);
final month = int.parse(dateParts[1]);
final year = int.parse(dateParts[2]);
return DateTime(year, month, day);
} catch (e) {
throw FormatException('Invalid date or time format: $date ');
}
}
Future<void> _searchUsers(
SearchUsers event, Emitter<UserTableState> emit) async {
try {
emit(TableSearch());
final query = event.query.toLowerCase();
final filteredUsers = initialUsers.where((user) {
final fullName = "${user.firstName} ${user.lastName}".toLowerCase();
final email = user.email.toLowerCase();
return fullName.contains(query) || email.contains(query);
}).toList();
emit(UsersLoadedState(users: filteredUsers));
} catch (e) {
emit(ErrorState(e.toString()));
}
}
void _paginateUsers(
int pageNumber, int itemsPerPage, Emitter<UserTableState> emit) {
final startIndex = (pageNumber - 1) * itemsPerPage;
final endIndex = startIndex + itemsPerPage;
if (startIndex >= users.length) {
emit(const UsersLoadedState(users: []));
return;
}
final paginatedUsers = users.sublist(
startIndex,
endIndex > users.length ? users.length : endIndex,
);
emit(UsersLoadedState(users: paginatedUsers));
}
void _handlePageChange(ChangePage event, Emitter<UserTableState> emit) {
currentPage = event.pageNumber;
const itemsPerPage = 20;
final startIndex = (event.pageNumber - 1) * itemsPerPage;
final endIndex = startIndex + itemsPerPage;
if (startIndex >= users.length) {
emit(const UsersLoadedState(users: []));
return;
}
final paginatedUsers = users.sublist(
startIndex,
endIndex > users.length ? users.length : endIndex,
);
emit(UsersLoadedState(users: paginatedUsers));
}
Set<String> selectedRoles = {};
Set<String> selectedJobTitles = {};
Set<String> selectedCreatedBy = {};
Set<String> selectedStatuses = {};
void _filterUsersByRole(
FilterUsersByRoleEvent event, Emitter<UserTableState> emit) {
selectedRoles = event.selectedRoles!.toSet();
final filteredUsers = initialUsers.where((user) {
if (selectedRoles.isEmpty) return true;
return selectedRoles.contains(user.roleType);
}).toList();
if (event.sortOrder == "Asc") {
currentSortOrder = "Asc";
filteredUsers.sort((a, b) => a.firstName
.toString()
.toLowerCase()
.compareTo(b.firstName.toString().toLowerCase()));
} else if (event.sortOrder == "Desc") {
currentSortOrder = "Desc";
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
} else {}
currentSortOrder = "";
currentSortCreatedDate = '';
currentSortStatus = '';
currentSortCreatedBy = '';
currentSortJopTitle = '';
currentSortOrderDate = "";
totalUsersCount = filteredUsers;
emit(UsersLoadedState(users: filteredUsers));
}
void _filterUsersByJobTitle(
FilterUsersByJobEvent event, Emitter<UserTableState> emit) {
selectedJobTitles = event.selectedJob!.toSet();
emit(UsersLoadingState());
final filteredUsers = initialUsers.where((user) {
if (selectedJobTitles.isEmpty) return true;
return selectedJobTitles.contains(user.jobTitle);
}).toList();
if (event.sortOrder == "Asc") {
currentSortOrder = "Asc";
filteredUsers.sort((a, b) => a.firstName
.toString()
.toLowerCase()
.compareTo(b.firstName.toString().toLowerCase()));
} else if (event.sortOrder == "Desc") {
currentSortOrder = "Desc";
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
} else {}
currentSortOrder = "";
currentSortCreatedDate = '';
currentSortStatus = '';
currentSortCreatedBy = '';
currentSortRole = '';
currentSortOrderDate = "";
totalUsersCount = filteredUsers;
emit(UsersLoadedState(users: filteredUsers));
}
void _filterUsersByCreated(
FilterUsersByCreatedEvent event, Emitter<UserTableState> emit) {
selectedCreatedBy = event.selectedCreatedBy!.toSet();
final filteredUsers = initialUsers.where((user) {
if (selectedCreatedBy.isEmpty) return true;
return selectedCreatedBy.contains(user.invitedBy);
}).toList();
if (event.sortOrder == "Asc") {
currentSortOrder = "Asc";
filteredUsers.sort((a, b) => a.firstName
.toString()
.toLowerCase()
.compareTo(b.firstName.toString().toLowerCase()));
} else if (event.sortOrder == "Desc") {
currentSortOrder = "Desc";
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
} else {}
currentSortOrder = '';
currentSortRole = '';
currentSortCreatedDate = '';
currentSortStatus = '';
currentSortOrderDate = "";
totalUsersCount = filteredUsers;
emit(UsersLoadedState(users: filteredUsers));
}
void _filterUserStatus(
FilterUsersByDeActevateEvent event, Emitter<UserTableState> emit) {
selectedStatuses = event.selectedActivate!.toSet();
final filteredUsers = initialUsers.where((user) {
if (selectedStatuses.isEmpty) return true;
return selectedStatuses.any((status) {
final userStatus = user.status?.toLowerCase() ?? '';
switch (status.toLowerCase()) {
case 'active':
return user.isEnabled == true && userStatus != 'invited';
case 'disabled':
return user.isEnabled == false;
case 'invited':
return userStatus == 'invited';
default:
return false;
}
});
}).toList();
if (event.sortOrder == "Asc") {
currentSortOrder = "Asc";
filteredUsers.sort((a, b) => a.firstName
.toString()
.toLowerCase()
.compareTo(b.firstName.toString().toLowerCase()));
} else if (event.sortOrder == "Desc") {
currentSortOrder = "Desc";
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
totalUsersCount = filteredUsers;
} else {}
currentSortOrder = '';
currentSortRole = '';
currentSortCreatedDate = '';
currentSortCreatedBy = '';
currentSortOrderDate = "";
emit(UsersLoadedState(users: filteredUsers));
}
void _resetAllFilters(Emitter<UserTableState> emit) {
selectedRoles.clear();
selectedJobTitles.clear();
selectedCreatedBy.clear();
selectedStatuses.clear();
emit(UsersLoadedState(users: initialUsers));
}
void _filterClear(FilterClearEvent event, Emitter<UserTableState> emit) {
selectedRoles.clear();
selectedJobTitles.clear();
selectedCreatedBy.clear();
selectedStatuses.clear();
emit(UsersLoadedState(users: initialUsers));
}
}
@@ -0,0 +1,138 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';
sealed class UserTableEvent extends Equatable {
const UserTableEvent();
}
class GetRoles extends UserTableEvent {
const GetRoles();
@override
List<Object?> get props => [];
}
class GetUsers extends UserTableEvent {
const GetUsers();
@override
List<Object?> get props => [];
}
class ChangeUserStatus extends UserTableEvent {
final String userId;
final String newStatus;
const ChangeUserStatus({required this.userId, required this.newStatus});
@override
List<Object?> get props => [userId, newStatus];
}
class SortUsersByNameAsc extends UserTableEvent {
const SortUsersByNameAsc();
@override
List<Object?> get props => [];
}
class SortUsersByNameDesc extends UserTableEvent {
const SortUsersByNameDesc();
@override
List<Object?> get props => [];
}
class StoreUsersEvent extends UserTableEvent {
const StoreUsersEvent();
@override
List<Object?> get props => [];
}
class DateNewestToOldestEvent extends UserTableEvent {
const DateNewestToOldestEvent();
@override
List<Object?> get props => [];
}
class DateOldestToNewestEvent extends UserTableEvent {
const DateOldestToNewestEvent();
@override
List<Object?> get props => [];
}
class SearchUsers extends UserTableEvent {
final String query;
SearchUsers(this.query);
@override
List<Object?> get props => [];
}
class ChangePage extends UserTableEvent {
final int pageNumber;
ChangePage(this.pageNumber);
@override
List<Object> get props => [pageNumber];
}
class DeleteUserEvent extends UserTableEvent {
final String userId;
final BuildContext context;
const DeleteUserEvent(this.userId, this.context);
@override
List<Object> get props => [userId, context];
}
class FilterUsersByRoleEvent extends UserTableEvent {
final List<String>? selectedRoles;
final String? sortOrder;
const FilterUsersByRoleEvent({this.selectedRoles, this.sortOrder});
List<Object?> get props => [selectedRoles, sortOrder];
}
class FilterUsersByJobEvent extends UserTableEvent {
final List<String>? selectedJob;
final String? sortOrder;
const FilterUsersByJobEvent({this.selectedJob, this.sortOrder});
List<Object?> get props => [selectedJob, sortOrder];
}
class FilterUsersByCreatedEvent extends UserTableEvent {
final List<String>? selectedCreatedBy;
final String? sortOrder;
const FilterUsersByCreatedEvent({this.selectedCreatedBy, this.sortOrder});
List<Object?> get props => [selectedCreatedBy, sortOrder];
}
class FilterUsersByDeActevateEvent extends UserTableEvent {
final List<String>? selectedActivate;
final String? sortOrder;
const FilterUsersByDeActevateEvent({this.selectedActivate, this.sortOrder});
List<Object?> get props => [selectedActivate, sortOrder];
}
class FilterOptionsEvent extends UserTableEvent {
final String query;
final List<String> fullOptions;
FilterOptionsEvent({required this.query, required this.fullOptions});
@override
List<Object?> get props => [query, fullOptions];
}
class FilterClearEvent extends UserTableEvent {
FilterClearEvent();
@override
List<Object?> get props => [];
}
@@ -0,0 +1,94 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart';
sealed class UserTableState extends Equatable {
const UserTableState();
}
final class TableInitial extends UserTableState {
@override
List<Object> get props => [];
}
final class TableSearch extends UserTableState {
@override
List<Object> get props => [];
}
final class RolesLoadingState extends UserTableState {
@override
List<Object> get props => [];
}
final class UsersLoadingState extends UserTableState {
@override
List<Object> get props => [];
}
final class RolesLoadedState extends UserTableState {
@override
List<Object> get props => [];
}
final class UsersLoadedState extends UserTableState {
final List<RolesUserModel> users;
const UsersLoadedState({required this.users});
@override
List<Object> get props => [users];
}
final class ErrorState extends UserTableState {
final String message;
const ErrorState(this.message);
@override
List<Object> get props => [message];
}
/// report state
final class SosReportLoadingState extends UserTableState {
@override
List<Object> get props => [];
}
final class RolesErrorState extends UserTableState {
final String message;
const RolesErrorState(this.message);
@override
List<Object> get props => [message];
}
/// automation reports
final class SosAutomationReportLoadingState extends UserTableState {
@override
List<Object> get props => [];
}
final class SosAutomationReportErrorState extends UserTableState {
final String message;
const SosAutomationReportErrorState(this.message);
@override
List<Object> get props => [message];
}
final class ChangeTapStatus extends UserTableState {
final bool select;
const ChangeTapStatus({required this.select});
@override
List<Object> get props => [select];
}
class FilterOptionsState extends UserTableState {
final List<String> filteredOptions;
const FilterOptionsState(this.filteredOptions);
@override
List<Object> get props => [filteredOptions];
}
@@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
Future<void> showDateFilterMenu({
required BuildContext context,
Function()? aToZTap,
Function()? zToaTap,
String? isSelected,
}) async {
final RenderBox overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
final RelativeRect position = RelativeRect.fromRect(
Rect.fromLTRB(
overlay.size.width / 3,
240,
0,
overlay.size.height,
),
Offset.zero & overlay.size,
);
await showMenu(
context: context,
position: position,
color: ColorsManager.whiteColors,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(10),
bottomLeft: Radius.circular(10),
),
),
items: <PopupMenuEntry>[
PopupMenuItem(
onTap: aToZTap,
child: ListTile(
leading: Image.asset(
Assets.AtoZIcon,
width: 25,
),
title: Text(
"Sort from newest to oldest",
style: TextStyle(
color: isSelected == "NewestToOldest"
? Colors.black
: Colors.blueGrey),
),
),
),
PopupMenuItem(
onTap: zToaTap,
child: ListTile(
leading: Image.asset(
Assets.ZtoAIcon,
width: 25,
),
title: Text(
"Sort from oldest to newest",
style: TextStyle(
color: isSelected == "OldestToNewest"
? Colors.black
: Colors.blueGrey),
),
),
),
],
).then((value) {});
}
@@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
Future<void> showDeActivateFilterMenu({
required BuildContext context,
Function()? aToZTap,
Function()? zToaTap,
String? isSelected,
}) async {
final RenderBox overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
final RelativeRect position = RelativeRect.fromRect(
Rect.fromLTRB(
overlay.size.width / 2,
240,
0,
overlay.size.height,
),
Offset.zero & overlay.size,
);
await showMenu(
context: context,
position: position,
color: ColorsManager.whiteColors,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(10),
bottomLeft: Radius.circular(10),
),
),
items: <PopupMenuEntry>[
PopupMenuItem(
onTap: aToZTap,
child: ListTile(
leading: Image.asset(
Assets.AtoZIcon,
width: 25,
),
title: Text(
"Sort A to Z",
style: TextStyle(
color: isSelected == "NewestToOldest"
? Colors.black
: Colors.blueGrey),
),
),
),
PopupMenuItem(
onTap: zToaTap,
child: ListTile(
leading: Image.asset(
Assets.ZtoAIcon,
width: 25,
),
title: Text(
"Sort Z to A",
style: TextStyle(
color: isSelected == "OldestToNewest"
? Colors.black
: Colors.blueGrey),
),
),
),
],
).then((value) {});
}
@@ -0,0 +1,64 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
Future<void> showNameMenu({
required BuildContext context,
Function()? aToZTap,
Function()? zToaTap,
String? isSelected,
}) async {
final RenderBox overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
final RelativeRect position = RelativeRect.fromRect(
Rect.fromLTRB(
overlay.size.width / 35,
240,
0,
overlay.size.height,
),
Offset.zero & overlay.size,
);
await showMenu(
context: context,
position: position,
color: ColorsManager.whiteColors,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(10),
bottomLeft: Radius.circular(10),
),
),
items: <PopupMenuEntry>[
PopupMenuItem(
onTap: aToZTap,
child: ListTile(
leading: Image.asset(
Assets.AtoZIcon,
width: 25,
),
title: Text(
"Sort A to Z",
style: TextStyle(
color: isSelected == "Asc" ? Colors.black : Colors.blueGrey),
),
),
),
PopupMenuItem(
onTap: zToaTap,
child: ListTile(
leading: Image.asset(
Assets.ZtoAIcon,
width: 25,
),
title: Text(
"Sort Z to A",
style: TextStyle(
color: isSelected == "Desc" ? Colors.black : Colors.blueGrey),
),
),
),
],
).then((value) {});
}
@@ -0,0 +1,264 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart';
class _HeaderColumn extends StatelessWidget {
final String title;
final double width;
final bool showFilter;
final VoidCallback? onFilter;
final Function(double) onResize;
const _HeaderColumn({
required this.title,
required this.width,
required this.showFilter,
required this.onResize,
this.onFilter,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return MouseRegion(
cursor: SystemMouseCursors.resizeColumn,
child: GestureDetector(
onHorizontalDragUpdate: (details) => onResize(details.delta.dx),
child: Container(
width: width,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: const BoxDecoration(
border: Border(right: BorderSide(color: ColorsManager.boxDivider)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontWeight: FontWeight.w400,
fontSize: 13,
color: ColorsManager.grayColor,
),
),
),
if (showFilter)
IconButton(
icon: SvgPicture.asset(Assets.filterTableIcon),
onPressed: onFilter,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
],
),
),
),
);
}
}
class _TableRow extends StatelessWidget {
final List<Widget> cells;
final List<double> columnWidths;
final bool isLast;
const _TableRow({
required this.cells,
required this.columnWidths,
required this.isLast,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
children: [
for (int i = 0; i < cells.length; i++)
Container(
width: columnWidths[i],
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
// decoration: BoxDecoration(
// border: Border(
// right: BorderSide(color: ColorsManager.boxDivider),
// ),
// ),
child: cells[i],
),
],
),
if (!isLast)
Divider(
height: 1,
thickness: 1,
color: ColorsManager.boxDivider,
),
],
);
}
}
//===========================================================================
class DynamicTableScreen extends StatefulWidget {
final List<String> titles;
final List<List<Widget>> rows;
final void Function(int columnIndex)? onFilter;
const DynamicTableScreen({
required this.titles,
required this.rows,
required this.onFilter,
Key? key,
}) : super(key: key);
@override
_DynamicTableScreenState createState() => _DynamicTableScreenState();
}
class _DynamicTableScreenState extends State<DynamicTableScreen> {
late List<double> columnWidths;
final double _minColumnWidth = 100.0;
final double _maxColumnWidth = 300.0;
final double _dividerWidth = 1.0;
double _lastAvailableWidth = 0;
@override
void initState() {
super.initState();
columnWidths = List.filled(widget.titles.length, _minColumnWidth);
}
void _handleColumnResize(int index, double delta) {
setState(() {
double newWidth = columnWidths[index] + delta;
newWidth = newWidth.clamp(_minColumnWidth, _maxColumnWidth);
double actualDelta = newWidth - columnWidths[index];
if (actualDelta == 0) return;
int nextIndex = (index + 1) % columnWidths.length;
columnWidths[index] = newWidth;
columnWidths[nextIndex] = (columnWidths[nextIndex] - actualDelta)
.clamp(_minColumnWidth, _maxColumnWidth);
});
}
Widget _buildHeader() {
return Container(
decoration: containerDecoration.copyWith(
color: ColorsManager.circleRolesBackground,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15),
),
),
child: Row(
children: [
for (int i = 0; i < widget.titles.length; i++)
_HeaderColumn(
title: widget.titles[i],
width: columnWidths[i],
showFilter: i != 1 && i != 9 && i != 8 && i != 5,
onFilter: () => widget.onFilter?.call(i),
onResize: (delta) => _handleColumnResize(i, delta),
),
],
),
);
}
Widget _buildBody() {
if (widget.rows.isEmpty) {
return SizedBox(
height: 300,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(Assets.emptyTable),
const SizedBox(height: 15),
const Text(
'No Users',
style: TextStyle(
color: ColorsManager.lightGrayColor,
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
],
),
),
);
}
return Container(
decoration: containerDecoration.copyWith(
color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(15),
bottomRight: Radius.circular(15),
),
),
child: Column(
children: [
for (int rowIndex = 0; rowIndex < widget.rows.length; rowIndex++)
_TableRow(
cells: widget.rows[rowIndex],
columnWidths: columnWidths,
isLast: rowIndex == widget.rows.length - 1,
),
],
),
);
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final availableWidth = constraints.maxWidth;
final totalDividersWidth = (widget.titles.length - 1) * _dividerWidth;
if (_lastAvailableWidth != availableWidth) {
final equalWidth =
(availableWidth - totalDividersWidth) / widget.titles.length;
final clampedWidth =
equalWidth.clamp(_minColumnWidth, _maxColumnWidth);
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
columnWidths = List.filled(widget.titles.length, clampedWidth);
_lastAvailableWidth = availableWidth;
});
});
}
final totalTableWidth =
columnWidths.fold(0.0, (sum, w) => sum + w) + totalDividersWidth;
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Container(
width: totalTableWidth,
decoration: containerDecoration.copyWith(
color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(),
_buildBody(),
],
),
),
);
},
);
}
}
@@ -0,0 +1,540 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:number_pagination/number_pagination.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/name_filter.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/user_table.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class UsersPage extends StatelessWidget {
UsersPage({super.key});
@override
Widget build(BuildContext context) {
final TextEditingController searchController = TextEditingController();
Widget actionButton({required String title, required Function()? onTap}) {
return InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.only(left: 8, right: 8),
child: Text(
title,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: title == "Delete"
? ColorsManager.red
: ColorsManager.spaceColor,
fontWeight: FontWeight.w400,
),
),
),
);
}
Widget status({required String status}) {
return Center(
child: Container(
padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(20)),
color: status == "invited"
? ColorsManager.invitedOrange.withOpacity(0.5)
: status == "active"
? ColorsManager.activeGreen.withOpacity(0.5)
: ColorsManager.disabledPink.withOpacity(0.5),
),
child: Padding(
padding:
const EdgeInsets.only(left: 10, right: 10, bottom: 5, top: 5),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
status,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: status == "invited"
? ColorsManager.invitedOrangeText
: status == "active"
? ColorsManager.activeGreenText
: ColorsManager.disabledRedText,
fontWeight: FontWeight.w400,
),
),
],
),
),
),
);
}
Widget changeIconStatus(
{required String userId,
required String status,
required Function()? onTap}) {
return Center(
child: InkWell(
onTap: onTap,
child: Padding(
padding:
const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5),
child: SvgPicture.asset(
status == "invited"
? Assets.invitedIcon
: status == "active"
? Assets.activeUser
: Assets.deActiveUser,
height: 35,
),
),
),
);
}
return BlocBuilder<UserTableBloc, UserTableState>(
builder: (context, state) {
final screenSize = MediaQuery.of(context).size;
final _blocRole = BlocProvider.of<UserTableBloc>(context);
if (state is UsersLoadingState) {
return const Center(child: CircularProgressIndicator());
} else if (state is UsersLoadedState) {
return Padding(
padding: const EdgeInsets.all(20),
child: Align(
alignment: Alignment.topCenter,
child: ListView(
shrinkWrap: true,
children: [
Row(
children: [
Container(
decoration: containerDecoration.copyWith(
borderRadius: const BorderRadius.all(
Radius.circular(20),
),
),
width: screenSize.width * 0.4,
child: TextFormField(
controller: searchController,
onChanged: (value) {
final bloc = context.read<UserTableBloc>();
bloc.add(FilterClearEvent());
bloc.add(SearchUsers(value));
if (value == '') {
bloc.add(ChangePage(1));
}
},
style: const TextStyle(color: Colors.black),
decoration: textBoxDecoration(radios: 15)!.copyWith(
fillColor: ColorsManager.whiteColors,
errorStyle: const TextStyle(height: 0),
hintStyle: context.textTheme.titleSmall?.copyWith(
color: Colors.grey,
fontSize: 12,
),
hintText: 'Search',
suffixIcon: SizedBox(
child: SvgPicture.asset(
Assets.searchIconUser,
fit: BoxFit.none,
),
),
),
),
),
const SizedBox(width: 20),
InkWell(
onTap: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return const AddNewUserDialog();
},
).then((v) {
if (v != null) {
_blocRole.add(const GetUsers());
}
});
},
child: Container(
decoration: containerWhiteDecoration,
width: screenSize.width * 0.18,
height: 50,
child: const Center(
child: Text(
'Add New User',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: ColorsManager.blueColor,
),
),
),
),
),
],
),
const SizedBox(height: 25),
DynamicTableScreen(
onFilter: (columnIndex) {
if (columnIndex == 0) {
showNameMenu(
context: context,
isSelected: _blocRole.currentSortOrder,
aToZTap: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameAsc());
},
zToaTap: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameDesc());
},
);
}
if (columnIndex == 2) {
final Map<String, bool> checkboxStates = {
for (var item in _blocRole.jobTitle)
item: _blocRole.selectedJobTitles.contains(item),
};
final RenderBox overlay = Overlay.of(context)
.context
.findRenderObject() as RenderBox;
showPopUpFilterMenu(
position: RelativeRect.fromLTRB(
overlay.size.width / 5.3,
240,
overlay.size.width / 4,
0,
),
list: _blocRole.jobTitle,
context: context,
checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortJopTitle,
onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries
.where((entry) => entry.value)
.map((entry) => entry.key)
.toList();
Navigator.of(context).pop();
_blocRole.add(FilterUsersByJobEvent(
selectedJob: selectedItems,
sortOrder: _blocRole.currentSortJopTitle,
));
},
onSortAtoZ: (v) {
_blocRole.currentSortJopTitle = v;
},
onSortZtoA: (v) {
_blocRole.currentSortJopTitle = v;
},
);
}
if (columnIndex == 3) {
final Map<String, bool> checkboxStates = {
for (var item in _blocRole.roleTypes)
item: _blocRole.selectedRoles.contains(item),
};
final RenderBox overlay = Overlay.of(context)
.context
.findRenderObject() as RenderBox;
showPopUpFilterMenu(
position: RelativeRect.fromLTRB(
overlay.size.width / 4,
240,
overlay.size.width / 4,
0,
),
list: _blocRole.roleTypes,
context: context,
checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortRole,
onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries
.where((entry) => entry.value)
.map((entry) => entry.key)
.toList();
Navigator.of(context).pop();
context.read<UserTableBloc>().add(
FilterUsersByRoleEvent(
selectedRoles: selectedItems,
sortOrder: _blocRole.currentSortRole));
},
onSortAtoZ: (v) {
_blocRole.currentSortRole = v;
},
onSortZtoA: (v) {
_blocRole.currentSortRole = v;
},
);
}
if (columnIndex == 4) {
showDateFilterMenu(
context: context,
isSelected: _blocRole.currentSortOrder,
aToZTap: () {
context
.read<UserTableBloc>()
.add(const DateNewestToOldestEvent());
},
zToaTap: () {
context
.read<UserTableBloc>()
.add(const DateOldestToNewestEvent());
},
);
}
if (columnIndex == 6) {
final Map<String, bool> checkboxStates = {
for (var item in _blocRole.createdBy)
item: _blocRole.selectedCreatedBy.contains(item),
};
final RenderBox overlay = Overlay.of(context)
.context
.findRenderObject() as RenderBox;
showPopUpFilterMenu(
position: RelativeRect.fromLTRB(
overlay.size.width / 1,
240,
overlay.size.width / 4,
0,
),
list: _blocRole.createdBy,
context: context,
checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortCreatedBy,
onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries
.where((entry) => entry.value)
.map((entry) => entry.key)
.toList();
Navigator.of(context).pop();
_blocRole.add(FilterUsersByCreatedEvent(
selectedCreatedBy: selectedItems,
sortOrder: _blocRole.currentSortCreatedBy));
},
onSortAtoZ: (v) {
_blocRole.currentSortCreatedBy = v;
},
onSortZtoA: (v) {
_blocRole.currentSortCreatedBy = v;
},
);
}
if (columnIndex == 7) {
final Map<String, bool> checkboxStates = {
for (var item in _blocRole.status)
item: _blocRole.selectedStatuses.contains(item),
};
final RenderBox overlay = Overlay.of(context)
.context
.findRenderObject() as RenderBox;
showPopUpFilterMenu(
position: RelativeRect.fromLTRB(
overlay.size.width / 0,
240,
overlay.size.width / 5,
0,
),
list: _blocRole.status,
context: context,
checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortStatus,
onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries
.where((entry) => entry.value)
.map((entry) => entry.key)
.toList();
Navigator.of(context).pop();
_blocRole.add(FilterUsersByDeActevateEvent(
selectedActivate: selectedItems,
sortOrder: _blocRole.currentSortStatus));
},
onSortAtoZ: (v) {
_blocRole.currentSortStatus = v;
},
onSortZtoA: (v) {
_blocRole.currentSortStatus = v;
},
);
}
if (columnIndex == 8) {
showDeActivateFilterMenu(
context: context,
isSelected: _blocRole.currentSortOrderDate,
aToZTap: () {
context
.read<UserTableBloc>()
.add(const DateNewestToOldestEvent());
},
zToaTap: () {
context
.read<UserTableBloc>()
.add(const DateOldestToNewestEvent());
},
);
}
},
titles: const [
"Full Name",
"Email Address",
"Job Title",
"Role",
"Creation Date",
"Creation Time",
"Created By",
"Status",
"De/Activate",
"Action"
],
rows: state.users.map((user) {
return [
Text('${user.firstName} ${user.lastName}'),
Text(user.email),
Text(user.jobTitle),
Text(user.roleType ?? ''),
Text(user.createdDate ?? ''),
Text(user.createdTime ?? ''),
Text(user.invitedBy),
status(
status: user.isEnabled == false
? 'disabled'
: user.status,
),
changeIconStatus(
status: user.isEnabled == false
? 'disabled'
: user.status,
userId: user.uuid,
onTap: user.status != "invited"
? () {
context.read<UserTableBloc>().add(
ChangeUserStatus(
userId: user.uuid,
newStatus: user.isEnabled == false
? 'disabled'
: user.status));
}
: null,
),
Row(
children: [
actionButton(
title: "Edit",
onTap: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return EditUserDialog(userId: user.uuid);
},
).then((v) {
if (v != null) {
if (v != null) {
_blocRole.add(const GetUsers());
}
}
});
},
),
actionButton(
title: "Delete",
onTap: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return DeleteUserDialog(
onTapDelete: () async {
try {
_blocRole.add(DeleteUserEvent(
user.uuid, context));
await Future.delayed(
const Duration(seconds: 2));
return true;
} catch (e) {
return false;
}
});
},
).then((v) {
if (v != null) {
_blocRole.add(const GetUsers());
}
});
},
),
],
),
];
}).toList(),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SizedBox(
width: 500,
child: NumberPagination(
visiblePagesCount: 4,
buttonRadius: 10,
selectedButtonColor: ColorsManager.secondaryColor,
buttonUnSelectedBorderColor:
ColorsManager.grayBorder,
lastPageIcon:
const Icon(Icons.keyboard_double_arrow_right),
firstPageIcon:
const Icon(Icons.keyboard_double_arrow_left),
totalPages: (_blocRole.totalUsersCount.length /
_blocRole.itemsPerPage)
.ceil(),
currentPage: _blocRole.currentPage,
onPageChanged: (int pageNumber) {
context
.read<UserTableBloc>()
.add(ChangePage(pageNumber));
},
),
),
],
),
),
],
),
),
);
} else if (state is ErrorState) {
return Center(child: Text(state.message));
} else {
return const Center(child: Text('No data available.'));
}
},
);
}
}
@@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class RoleCard extends StatelessWidget {
final String name;
const RoleCard({super.key, required this.name});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: ColorsManager.whiteColors, // Card background color
borderRadius: BorderRadius.circular(20), // Rounded corners
boxShadow: [
BoxShadow(
color: ColorsManager.blackColor.withOpacity(0.2), // Shadow color
blurRadius: 20, // Spread of the shadow
offset: const Offset(2, 2), // No directional bias
spreadRadius: 1, // Ensures the shadow is more noticeable
),
],
),
child: Container(
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(20),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
const CircleAvatar(
backgroundColor: ColorsManager.neutralGray,
radius: 65,
child: CircleAvatar(
backgroundColor: ColorsManager.circleRolesBackground,
radius: 62,
child: Icon(
Icons.admin_panel_settings,
size: 40,
color: Colors.blue,
),
),
),
Text(
name,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
],
),
),
);
}
}
@@ -0,0 +1,94 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_state.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/users_page.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
class RolesAndPermissionPage extends StatelessWidget {
const RolesAndPermissionPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => RolesPermissionBloc(),
child: BlocConsumer<RolesPermissionBloc, RolesPermissionState>(
listener: (context, state) {},
builder: (context, state) {
final _blocRole = BlocProvider.of<RolesPermissionBloc>(context);
return state is RolesLoadingState
? const Center(child: CircularProgressIndicator())
: WebScaffold(
enableMenuSidebar: false,
appBarTitle: FittedBox(
child: Text(
'Roles & Permissions',
style: Theme.of(context).textTheme.headlineLarge,
),
),
rightBody: const NavigateHomeGridView(),
centerBody: Row(
children: [
// TextButton(
// style: TextButton.styleFrom(
// backgroundColor: null,
// ),
// onPressed: () {
// _blocRole.add(const ChangeTapSelected(true));
// },
// child: Text(
// 'Roles',
// style: context.textTheme.titleMedium?.copyWith(
// color: (_blocRole.tapSelect == true)
// ? ColorsManager.whiteColors
// : ColorsManager.grayColor,
// fontWeight: (_blocRole.tapSelect == true)
// ? FontWeight.w700
// : FontWeight.w400,
// ),
// ),
// ),
TextButton(
style: TextButton.styleFrom(
backgroundColor: null,
),
onPressed: () {
// _blocRole.add(const ChangeTapSelected(false));
},
child: Text(
'Users',
style: context.textTheme.titleMedium?.copyWith(
color: (_blocRole.tapSelect == true)
? ColorsManager.whiteColors
: ColorsManager.grayColor,
fontWeight: (_blocRole.tapSelect == true)
? FontWeight.w700
: FontWeight.w400,
),
),
),
],
),
scaffoldBody: BlocProvider<UserTableBloc>(
create: (context) => UserTableBloc()..add(const GetUsers()),
child: UsersPage(),
)
// _blocRole.tapSelect == false
// ? UsersPage(
// blocRole: _blocRole,
// )
// : RolesPage(
// blocRole: _blocRole,
// )
);
},
),
);
}
}
@@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/view/role_card.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class RolesPage extends StatelessWidget {
final RolesPermissionBloc blocRole;
const RolesPage({super.key, required this.blocRole});
@override
Widget build(BuildContext context) {
final TextEditingController searchController = TextEditingController();
double screenWidth = MediaQuery.of(context).size.width;
int crossAxisCount = (screenWidth ~/ 200).clamp(1, 6);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: containerDecoration.copyWith(
borderRadius: const BorderRadius.all(
Radius.circular(20),
),
),
width: 250,
child: TextFormField(
controller: searchController,
style: const TextStyle(color: Colors.black),
decoration: textBoxDecoration(radios: 15)!.copyWith(
fillColor: ColorsManager.whiteColors,
errorStyle: const TextStyle(height: 0),
hintStyle: context.textTheme.titleSmall?.copyWith(
color: Colors.grey,
fontSize: 12,
),
hintText: 'Search',
suffixIcon: SvgPicture.asset(Assets.searchIconUser)),
),
),
Expanded(
child: GridView.builder(
padding: const EdgeInsets.all(10),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 2 / 2.5,
),
itemCount: blocRole.roleModel.length ?? 0,
itemBuilder: (context, index) {
final role = blocRole.roleModel[index];
if (role == null) {
return const SizedBox.shrink();
}
return RoleCard(
name: role.roleName ?? 'Unknown',
);
},
),
),
],
);
}
}
@@ -1,85 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/ac_dialog.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/one_gang_switch_dialog.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/three_gang_switch_dialog.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/two_gang_switch_dialog.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
class DeviceDialogHelper {
static Future<Map<String, dynamic>?> showDeviceDialog(
BuildContext context,
Map<String, dynamic> data, {
required bool removeComparetors,
}) async {
final functions = data['functions'] as List<DeviceFunction>;
try {
final result = await _getDialogForDeviceType(
context,
data['productType'],
data,
functions,
removeComparetors: removeComparetors,
);
if (result != null) {
return result;
}
} catch (e) {
debugPrint('Error: $e');
}
return null;
}
static Future<Map<String, dynamic>?> _getDialogForDeviceType(
BuildContext context,
String productType,
Map<String, dynamic> data,
List<DeviceFunction> functions,
{required bool removeComparetors}) async {
final routineBloc = context.read<RoutineBloc>();
final deviceSelectedFunctions =
routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? [];
switch (productType) {
case 'AC':
return ACHelper.showACFunctionsDialog(
context,
functions,
data['device'],
deviceSelectedFunctions,
data['uniqueCustomId'],
removeComparetors);
case '1G':
return OneGangSwitchHelper.showSwitchFunctionsDialog(
context,
functions,
data['device'],
deviceSelectedFunctions,
data['uniqueCustomId'],
removeComparetors);
case '2G':
return TwoGangSwitchHelper.showSwitchFunctionsDialog(
context,
functions,
data['device'],
deviceSelectedFunctions,
data['uniqueCustomId'],
removeComparetors);
case '3G':
return ThreeGangSwitchHelper.showSwitchFunctionsDialog(
context,
functions,
data['device'],
deviceSelectedFunctions,
data['uniqueCustomId'],
removeComparetors);
default:
return null;
}
}
}
@@ -1,69 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart';
import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart';
import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/routine_view_card.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class RoutinesView extends StatefulWidget {
const RoutinesView({super.key});
@override
State<RoutinesView> createState() => _RoutinesViewState();
}
class _RoutinesViewState extends State<RoutinesView> {
@override
void initState() {
super.initState();
context.read<RoutineBloc>().add(FetchDevicesInRoutine());
}
@override
Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) {
if (state.createRoutineView) {
return const CreateNewRoutineView();
}
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Create New Routines",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(
height: 10,
),
RoutineViewCard(
onTap: () {
context.read<RoutineBloc>().add(
(ResetRoutineState()),
);
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(createRoutineView: true),
);
},
icon: Icons.add,
textString: '',
),
const SizedBox(
height: 15,
),
const Expanded(child: FetchRoutineScenesAutomation()),
],
),
);
},
);
}
}
@@ -1,143 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/routine_view_card.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class FetchRoutineScenesAutomation extends StatefulWidget {
const FetchRoutineScenesAutomation({super.key});
@override
State<FetchRoutineScenesAutomation> createState() => _FetchRoutineScenesState();
}
class _FetchRoutineScenesState extends State<FetchRoutineScenesAutomation>
with HelperResponsiveLayout {
@override
void initState() {
super.initState();
context.read<RoutineBloc>()
..add(const LoadScenes(spaceId, communityId))
..add(const LoadAutomation(spaceId));
}
@override
Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) {
return state.isLoading
? const Center(
child: CircularProgressIndicator(),
)
: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Scenes (Tab to Run)",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
if (state.scenes.isEmpty)
Text(
"No scenes found",
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.grayColor,
),
),
if (state.scenes.isNotEmpty)
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: isSmallScreenSize(context) ? 160 : 170,
),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: state.scenes.length,
itemBuilder: (context, index) => Padding(
padding: EdgeInsets.only(
right: isSmallScreenSize(context) ? 4.0 : 8.0,
),
child: RoutineViewCard(
onTap: () {
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(createRoutineView: true),
);
context.read<RoutineBloc>().add(
GetSceneDetails(
sceneId: state.scenes[index].id,
isTabToRun: true,
isUpdate: true,
),
);
},
textString: state.scenes[index].name,
icon: state.scenes[index].icon ?? Assets.logoHorizontal,
isFromScenes: true,
iconInBytes: state.scenes[index].iconInBytes,
),
),
),
),
const SizedBox(height: 15),
Text(
"Automations",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
if (state.automations.isEmpty)
Text(
"No automations found",
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.grayColor,
),
),
if (state.automations.isNotEmpty)
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: isSmallScreenSize(context) ? 160 : 170,
),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: state.automations.length,
itemBuilder: (context, index) => Padding(
padding: EdgeInsets.only(
right: isSmallScreenSize(context) ? 4.0 : 8.0,
),
child: RoutineViewCard(
onTap: () {
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(createRoutineView: true),
);
context.read<RoutineBloc>().add(
GetAutomationDetails(
automationId: state.automations[index].id,
isAutomation: true,
isUpdate: true),
);
},
textString: state.automations[index].name,
icon: state.automations[index].icon ?? Assets.automation,
),
),
),
),
],
),
),
);
},
);
}
}
@@ -1,7 +1,7 @@
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart';
import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_event.dart';
import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_state.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
class EffectPeriodBloc extends Bloc<EffectPeriodEvent, EffectPeriodState> {
@@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart';
import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
abstract class EffectPeriodEvent extends Equatable {
@@ -2,7 +2,7 @@ import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
part 'functions_bloc_event.dart';
part 'functions_bloc_state.dart';
@@ -26,8 +26,7 @@ class FunctionBloc extends Bloc<FunctionBlocEvent, FunctionBlocState> {
functionCode: event.functionData.functionCode,
operationName: event.functionData.operationName,
value: event.functionData.value ?? existingData.value,
valueDescription: event.functionData.valueDescription ??
existingData.valueDescription,
valueDescription: event.functionData.valueDescription ?? existingData.valueDescription,
condition: event.functionData.condition ?? existingData.condition,
);
} else {
@@ -59,10 +58,8 @@ class FunctionBloc extends Bloc<FunctionBlocEvent, FunctionBlocState> {
);
}
FutureOr<void> _onSelectFunction(
SelectFunction event, Emitter<FunctionBlocState> emit) {
FutureOr<void> _onSelectFunction(SelectFunction event, Emitter<FunctionBlocState> emit) {
emit(state.copyWith(
selectedFunction: event.functionCode,
selectedOperationName: event.operationName));
selectedFunction: event.functionCode, selectedOperationName: event.operationName));
}
}
@@ -4,12 +4,12 @@ import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart';
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart';
import 'package:syncrow_web/pages/routiens/models/delay/delay_fucntions.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/models/routine_details_model.dart';
import 'package:syncrow_web/pages/routiens/models/routine_model.dart';
import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart';
import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_scene_model.dart';
import 'package:syncrow_web/pages/routines/models/delay/delay_fucntions.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/routine_details_model.dart';
import 'package:syncrow_web/pages/routines/models/routine_model.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
import 'package:syncrow_web/services/routines_api.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
@@ -19,8 +19,8 @@ import 'package:uuid/uuid.dart';
part 'routine_event.dart';
part 'routine_state.dart';
const spaceId = '9cb9da1f-adbe-4688-bf55-29e7584007a7';
const communityId = '68dd94cf-0240-4ccf-8c26-df506246a0dd';
String spaceId = '9cb9da1f-adbe-4688-bf55-29e7584007a7';
String communityId = '68dd94cf-0240-4ccf-8c26-df506246a0dd';
class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
RoutineBloc() : super(const RoutineState()) {
@@ -57,8 +57,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
emit(state.copyWith(routineTab: event.isRoutineTab, createRoutineView: false));
add(ResetRoutineState());
if (event.isRoutineTab) {
add(const LoadScenes(spaceId, communityId));
add(const LoadAutomation(spaceId));
add(LoadScenes(spaceId, communityId));
add(LoadAutomation(spaceId));
}
}
@@ -156,18 +156,25 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
emit(state.copyWith(isLoading: true, errorMessage: null));
try {
final scenes = await SceneApi.getScenesByUnitId(event.unitId, event.communityId);
spaceId = event.spaceId;
communityId = event.communityId;
List<ScenesModel> scenes = [];
if (communityId.isNotEmpty && spaceId.isNotEmpty) {
scenes = await SceneApi.getScenes(event.spaceId, event.communityId);
}
emit(state.copyWith(
scenes: scenes,
isLoading: false,
));
} catch (e) {
emit(state.copyWith(
isLoading: false,
loadScenesErrorMessage: 'Failed to load scenes',
errorMessage: '',
loadAutomationErrorMessage: '',
));
isLoading: false,
loadScenesErrorMessage: 'Failed to load scenes',
errorMessage: '',
loadAutomationErrorMessage: '',
scenes: []));
}
}
@@ -175,27 +182,22 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
emit(state.copyWith(isLoading: true, errorMessage: null));
try {
final automations = await SceneApi.getAutomationByUnitId(event.unitId);
if (automations.isNotEmpty) {
emit(state.copyWith(
automations: automations,
isLoading: false,
));
} else {
emit(state.copyWith(
spaceId = event.spaceId;
List<ScenesModel> automations = [];
if (spaceId.isNotEmpty) {
automations = await SceneApi.getAutomation(event.spaceId);
}
emit(state.copyWith(
automations: automations,
isLoading: false,
));
} catch (e) {
emit(state.copyWith(
isLoading: false,
loadAutomationErrorMessage: 'Failed to load automations',
errorMessage: '',
loadScenesErrorMessage: '',
));
}
} catch (e) {
emit(state.copyWith(
isLoading: false,
loadAutomationErrorMessage: 'Failed to load automations',
errorMessage: '',
loadScenesErrorMessage: '',
));
automations: []));
}
}
@@ -290,8 +292,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final result = await SceneApi.createScene(createSceneModel);
if (result['success']) {
add(ResetRoutineState());
add(const LoadScenes(spaceId, communityId));
add(const LoadAutomation(spaceId));
add(LoadScenes(spaceId, communityId));
add(LoadAutomation(spaceId));
} else {
emit(state.copyWith(
isLoading: false,
@@ -419,8 +421,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final result = await SceneApi.createAutomation(createAutomationModel);
if (result['success']) {
add(ResetRoutineState());
add(const LoadAutomation(spaceId));
add(const LoadScenes(spaceId, communityId));
add(LoadAutomation(spaceId));
add(LoadScenes(spaceId, communityId));
} else {
emit(state.copyWith(
isLoading: false,
@@ -758,6 +760,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
thenItems: [],
selectedFunctions: {},
scenes: [],
sceneId: '',
automationId: '',
automations: [],
isLoading: false,
errorMessage: '',
@@ -783,8 +787,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
SceneApi.deleteAutomation(unitUuid: spaceId, automationId: state.automationId ?? '');
}
add(const LoadScenes(spaceId, communityId));
add(const LoadAutomation(spaceId));
add(LoadScenes(spaceId, communityId));
add(LoadAutomation(spaceId));
add(ResetRoutineState());
emit(state.copyWith(isLoading: false, createRoutineView: false));
} catch (e) {
@@ -812,7 +816,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
FutureOr<void> _fetchDevices(FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true));
try {
final devices = await DevicesManagementApi().fetchDevices();
final devices = await DevicesManagementApi().fetchDevices('', '');
emit(state.copyWith(isLoading: false, devices: devices));
} catch (e) {
@@ -890,8 +894,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final result = await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
if (result['success']) {
add(ResetRoutineState());
add(const LoadScenes(spaceId, communityId));
add(const LoadAutomation(spaceId));
add(LoadScenes(spaceId, communityId));
add(LoadAutomation(spaceId));
} else {
emit(state.copyWith(
isLoading: false,
@@ -1019,8 +1023,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (result['success']) {
add(ResetRoutineState());
add(const LoadAutomation(spaceId));
add(const LoadScenes(spaceId, communityId));
add(LoadAutomation(spaceId));
add(LoadScenes(spaceId, communityId));
} else {
emit(state.copyWith(
isLoading: false,
@@ -1050,7 +1054,9 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final automationDetails = await SceneApi.getAutomationDetails(event.automationId);
final Map<String, Map<String, dynamic>> deviceCards = {};
final Map<String, Map<String, dynamic>> deviceIfCards = {};
final Map<String, Map<String, dynamic>> deviceThenCards = {};
final Map<String, List<DeviceFunctionData>> updatedFunctions =
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
@@ -1066,8 +1072,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final deviceId = condition.entityId;
if (!deviceCards.containsKey(deviceId)) {
deviceCards[deviceId] = {
if (!deviceIfCards.containsKey(deviceId)) {
deviceIfCards[deviceId] = {
'entityId': condition.entityId,
'deviceId': condition.entityId,
'uniqueCustomId': const Uuid().v4(),
@@ -1080,7 +1086,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
};
}
final cardData = deviceCards[deviceId]!;
final cardData = deviceIfCards[deviceId]!;
final uniqueCustomId = cardData['uniqueCustomId'].toString();
if (!updatedFunctions.containsKey(uniqueCustomId)) {
@@ -1117,8 +1123,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final deviceId =
action.actionExecutor == 'delay' ? '${action.entityId}_delay' : action.entityId;
if (!deviceCards.containsKey(deviceId)) {
deviceCards[deviceId] = {
if (!deviceThenCards.containsKey(deviceId)) {
deviceThenCards[deviceId] = {
'entityId': action.entityId,
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'uniqueCustomId': const Uuid().v4(),
@@ -1140,10 +1146,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
: action.type == 'automation'
? 'automation'
: 'action',
'icon': action.icon ?? ''
};
}
final cardData = deviceCards[deviceId]!;
final cardData = deviceThenCards[deviceId]!;
final uniqueCustomId = cardData['uniqueCustomId'].toString();
if (!updatedFunctions.containsKey(uniqueCustomId)) {
@@ -1192,8 +1199,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
final ifItems = deviceCards.values.where((card) => card['type'] == 'condition').toList();
final thenItems = deviceCards.values
final ifItems = deviceIfCards.values.where((card) => card['type'] == 'condition').toList();
final thenItems = deviceThenCards.values
.where((card) =>
card['type'] == 'action' || card['type'] == 'automation' || card['type'] == 'scene')
.toList();
@@ -27,22 +27,22 @@ class AddToThenContainer extends RoutineEvent {
}
class LoadScenes extends RoutineEvent {
final String unitId;
final String spaceId;
final String communityId;
const LoadScenes(this.unitId, this.communityId);
const LoadScenes(this.spaceId, this.communityId);
@override
List<Object> get props => [unitId, communityId];
List<Object> get props => [spaceId, communityId];
}
class LoadAutomation extends RoutineEvent {
final String unitId;
final String spaceId;
const LoadAutomation(this.unitId);
const LoadAutomation(this.spaceId);
@override
List<Object> get props => [unitId];
List<Object> get props => [spaceId];
}
class AddFunctionToRoutine extends RoutineEvent {
@@ -1,7 +1,7 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_event.dart';
import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_state.dart';
import 'package:syncrow_web/pages/routiens/models/icon_model.dart';
import 'package:syncrow_web/pages/routines/bloc/setting_bloc/setting_event.dart';
import 'package:syncrow_web/pages/routines/bloc/setting_bloc/setting_state.dart';
import 'package:syncrow_web/pages/routines/models/icon_model.dart';
import 'package:syncrow_web/services/routines_api.dart';
class SettingBloc extends Bloc<SettingEvent, SettingState> {
@@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/routiens/models/icon_model.dart';
import 'package:syncrow_web/pages/routines/models/icon_model.dart';
abstract class SettingState extends Equatable {
const SettingState();

Some files were not shown because too many files have changed in this diff Show More