mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-11-26 18:34:56 +00:00
Merge branch 'dev' of https://github.com/SyncrowIOT/web into feature/space-management
This commit is contained in:
@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_state.dart';
|
||||
import 'package:syncrow_web/pages/access_management/model/password_model.dart';
|
||||
import 'package:syncrow_web/pages/common/hour_picker_dialog.dart';
|
||||
import 'package:syncrow_web/services/access_mang_api.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/app_enum.dart';
|
||||
@ -11,22 +12,23 @@ import 'package:syncrow_web/utils/snack_bar.dart';
|
||||
class AccessBloc extends Bloc<AccessEvent, AccessState> {
|
||||
AccessBloc() : super((AccessInitial())) {
|
||||
on<FetchTableData>(_onFetchTableData);
|
||||
// on<TabChangedEvent>(selectFilterTap);
|
||||
on<SelectTime>(selectTime);
|
||||
on<FilterDataEvent>(_filterData);
|
||||
on<ResetSearch>(resetSearch);
|
||||
on<TabChangedEvent>(onTabChanged);
|
||||
}
|
||||
|
||||
String startTime = 'Start Date';
|
||||
String endTime = 'End Date';
|
||||
|
||||
int? effectiveTimeTimeStamp;
|
||||
int? expirationTimeTimeStamp;
|
||||
TextEditingController passwordName = TextEditingController();
|
||||
TextEditingController emailAuthorizer = TextEditingController();
|
||||
List<PasswordModel> filteredData = [];
|
||||
List<PasswordModel> data = [];
|
||||
|
||||
Future<void> _onFetchTableData(FetchTableData event, Emitter<AccessState> emit) async {
|
||||
Future<void> _onFetchTableData(
|
||||
FetchTableData event, Emitter<AccessState> emit) async {
|
||||
try {
|
||||
emit(AccessLoaded());
|
||||
data = await AccessMangApi().fetchVisitorPassword();
|
||||
@ -39,19 +41,28 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
|
||||
}
|
||||
|
||||
void updateTabsCount() {
|
||||
int toBeEffectiveCount =
|
||||
data.where((item) => item.passwordStatus.value == 'To be effective').length;
|
||||
int effectiveCount = data.where((item) => item.passwordStatus.value == 'Effective').length;
|
||||
int expiredCount = data.where((item) => item.passwordStatus.value == 'Expired').length;
|
||||
int toBeEffectiveCount = data
|
||||
.where((item) => item.passwordStatus.value == 'To be effective')
|
||||
.length;
|
||||
int effectiveCount =
|
||||
data.where((item) => item.passwordStatus.value == 'Effective').length;
|
||||
int expiredCount =
|
||||
data.where((item) => item.passwordStatus.value == 'Expired').length;
|
||||
tabs[1] = 'To Be Effective ($toBeEffectiveCount)';
|
||||
tabs[2] = 'Effective ($effectiveCount)';
|
||||
tabs[3] = 'Expired ($expiredCount)';
|
||||
}
|
||||
|
||||
int selectedIndex = 0;
|
||||
final List<String> tabs = ['All', 'To Be Effective (0)', 'Effective (0)', 'Expired'];
|
||||
final List<String> tabs = [
|
||||
'All',
|
||||
'To Be Effective (0)',
|
||||
'Effective (0)',
|
||||
'Expired'
|
||||
];
|
||||
|
||||
Future selectFilterTap(TabChangedEvent event, Emitter<AccessState> emit) async {
|
||||
Future selectFilterTap(
|
||||
TabChangedEvent event, Emitter<AccessState> emit) async {
|
||||
try {
|
||||
emit(AccessLoaded());
|
||||
selectedIndex = event.selectedIndex;
|
||||
@ -63,115 +74,176 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> selectTime(SelectTime event, Emitter<AccessState> emit) async {
|
||||
Future<void> selectTime(
|
||||
SelectTime event,
|
||||
Emitter<AccessState> emit,
|
||||
) async {
|
||||
emit(AccessLoaded());
|
||||
final DateTime? picked = await showDatePicker(
|
||||
context: event.context,
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime(2015, 8),
|
||||
lastDate: DateTime(2101),
|
||||
firstDate: DateTime.now().add(const Duration(days: -5095)),
|
||||
lastDate: DateTime.now().add(const Duration(days: 2095)),
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
return Theme(
|
||||
data: ThemeData.light().copyWith(
|
||||
colorScheme: ColorScheme.light(
|
||||
primary: ColorsManager.blackColor,
|
||||
onPrimary: Colors.white,
|
||||
onSurface: ColorsManager.grayColor,
|
||||
),
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.blue,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
);
|
||||
if (picked != null) {
|
||||
final selectedDateTime = DateTime(
|
||||
picked.year,
|
||||
picked.month,
|
||||
picked.day,
|
||||
final TimeOfDay? timePicked = await showHourPicker(
|
||||
context: event.context,
|
||||
initialTime: TimeOfDay.now(),
|
||||
);
|
||||
final selectedTimestamp = DateTime(
|
||||
selectedDateTime.year,
|
||||
selectedDateTime.month,
|
||||
selectedDateTime.day,
|
||||
selectedDateTime.hour,
|
||||
selectedDateTime.minute,
|
||||
).millisecondsSinceEpoch ~/
|
||||
1000; // Divide by 1000 to remove milliseconds
|
||||
if (event.isStart) {
|
||||
if (expirationTimeTimeStamp != null && selectedTimestamp > expirationTimeTimeStamp!) {
|
||||
CustomSnackBar.displaySnackBar('Effective Time cannot be later than Expiration Time.');
|
||||
|
||||
if (timePicked != null) {
|
||||
final DateTime selectedDateTime = DateTime(
|
||||
picked.year,
|
||||
picked.month,
|
||||
picked.day,
|
||||
timePicked.hour,
|
||||
timePicked.minute,
|
||||
);
|
||||
final int selectedTimestamp =
|
||||
selectedDateTime.millisecondsSinceEpoch ~/ 1000;
|
||||
if (event.isStart) {
|
||||
if (expirationTimeTimeStamp != null &&
|
||||
selectedTimestamp > expirationTimeTimeStamp!) {
|
||||
CustomSnackBar.displaySnackBar(
|
||||
'Effective Time cannot be later than Expiration Time.');
|
||||
} else {
|
||||
startTime = selectedDateTime.toString().split('.').first;
|
||||
effectiveTimeTimeStamp = selectedTimestamp;
|
||||
}
|
||||
} else {
|
||||
startTime =
|
||||
selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds
|
||||
effectiveTimeTimeStamp = selectedTimestamp;
|
||||
}
|
||||
} else {
|
||||
if (effectiveTimeTimeStamp != null && selectedTimestamp < effectiveTimeTimeStamp!) {
|
||||
CustomSnackBar.displaySnackBar('Expiration Time cannot be earlier than Effective Time.');
|
||||
} else {
|
||||
endTime = selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds
|
||||
expirationTimeTimeStamp = selectedTimestamp;
|
||||
if (effectiveTimeTimeStamp != null &&
|
||||
selectedTimestamp < effectiveTimeTimeStamp!) {
|
||||
CustomSnackBar.displaySnackBar(
|
||||
'Expiration Time cannot be earlier than Effective Time.');
|
||||
} else {
|
||||
endTime = selectedDateTime.toString().split('.').first;
|
||||
expirationTimeTimeStamp = selectedTimestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
emit(ChangeTimeState());
|
||||
}
|
||||
|
||||
Future<void> _filterData(FilterDataEvent event, Emitter<AccessState> emit) async {
|
||||
Future<void> _filterData(
|
||||
FilterDataEvent event, Emitter<AccessState> emit) async {
|
||||
emit(AccessLoaded());
|
||||
try {
|
||||
// Convert search text to lower case for case-insensitive search
|
||||
final searchText = event.passwordName?.toLowerCase() ?? '';
|
||||
final searchEmailText = event.emailAuthorizer?.toLowerCase() ?? '';
|
||||
filteredData = data.where((item) {
|
||||
bool matchesCriteria = true;
|
||||
|
||||
// Convert timestamp to DateTime and extract date component
|
||||
DateTime effectiveDate =
|
||||
DateTime.fromMillisecondsSinceEpoch(int.parse(item.effectiveTime.toString()) * 1000)
|
||||
.toUtc()
|
||||
.toLocal();
|
||||
DateTime invalidDate =
|
||||
DateTime.fromMillisecondsSinceEpoch(int.parse(item.invalidTime.toString()) * 1000)
|
||||
.toUtc()
|
||||
.toLocal();
|
||||
DateTime effectiveDateOnly =
|
||||
DateTime(effectiveDate.year, effectiveDate.month, effectiveDate.day);
|
||||
DateTime invalidDateOnly = DateTime(invalidDate.year, invalidDate.month, invalidDate.day);
|
||||
DateTime effectiveDate = DateTime.fromMillisecondsSinceEpoch(
|
||||
int.parse(item.effectiveTime.toString()) * 1000)
|
||||
.toUtc()
|
||||
.toLocal();
|
||||
DateTime invalidDate = DateTime.fromMillisecondsSinceEpoch(
|
||||
int.parse(item.invalidTime.toString()) * 1000)
|
||||
.toUtc()
|
||||
.toLocal();
|
||||
DateTime effectiveDateAndTime = DateTime(
|
||||
effectiveDate.year,
|
||||
effectiveDate.month,
|
||||
effectiveDate.day,
|
||||
effectiveDate.hour,
|
||||
effectiveDate.minute);
|
||||
DateTime invalidDateAndTime = DateTime(
|
||||
invalidDate.year,
|
||||
invalidDate.month,
|
||||
invalidDate.day,
|
||||
invalidDate.hour,
|
||||
invalidDate.minute);
|
||||
|
||||
// Filter by password name
|
||||
if (event.passwordName != null && event.passwordName!.isNotEmpty) {
|
||||
// Filter by password name, making the search case-insensitive
|
||||
if (searchText.isNotEmpty) {
|
||||
final bool matchesName =
|
||||
item.passwordName != null && item.passwordName.contains(event.passwordName);
|
||||
item.passwordName.toString().toLowerCase().contains(searchText);
|
||||
if (!matchesName) {
|
||||
matchesCriteria = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by start date only
|
||||
if (event.startTime != null && event.endTime == null) {
|
||||
DateTime startDateOnly =
|
||||
DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000).toUtc().toLocal();
|
||||
startDateOnly = DateTime(startDateOnly.year, startDateOnly.month, startDateOnly.day);
|
||||
if (effectiveDateOnly.isBefore(startDateOnly)) {
|
||||
if (searchEmailText.isNotEmpty) {
|
||||
final bool matchesName = item.authorizerEmail
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.contains(searchEmailText);
|
||||
if (!matchesName) {
|
||||
matchesCriteria = false;
|
||||
}
|
||||
}
|
||||
// Filter by start date only
|
||||
if (event.startTime != null && event.endTime == null) {
|
||||
DateTime startDateTime =
|
||||
DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000)
|
||||
.toUtc()
|
||||
.toLocal();
|
||||
startDateTime = DateTime(startDateTime.year, startDateTime.month,
|
||||
startDateTime.day, startDateTime.hour, startDateTime.minute);
|
||||
if (effectiveDateAndTime.isBefore(startDateTime)) {
|
||||
matchesCriteria = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by end date only
|
||||
if (event.endTime != null && event.startTime == null) {
|
||||
DateTime endDateOnly =
|
||||
DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000).toUtc().toLocal();
|
||||
endDateOnly = DateTime(endDateOnly.year, endDateOnly.month, endDateOnly.day);
|
||||
if (invalidDateOnly.isAfter(endDateOnly)) {
|
||||
DateTime startDateTime =
|
||||
DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000)
|
||||
.toUtc()
|
||||
.toLocal();
|
||||
startDateTime = DateTime(startDateTime.year, startDateTime.month,
|
||||
startDateTime.day, startDateTime.hour, startDateTime.minute);
|
||||
if (invalidDateAndTime.isAfter(startDateTime)) {
|
||||
matchesCriteria = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by both start date and end date
|
||||
if (event.startTime != null && event.endTime != null) {
|
||||
DateTime startDateOnly =
|
||||
DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000).toUtc().toLocal();
|
||||
DateTime endDateOnly =
|
||||
DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000).toUtc().toLocal();
|
||||
startDateOnly = DateTime(startDateOnly.year, startDateOnly.month, startDateOnly.day);
|
||||
endDateOnly = DateTime(endDateOnly.year, endDateOnly.month, endDateOnly.day);
|
||||
if (effectiveDateOnly.isBefore(startDateOnly) || invalidDateOnly.isAfter(endDateOnly)) {
|
||||
DateTime startDateTime =
|
||||
DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000)
|
||||
.toUtc()
|
||||
.toLocal();
|
||||
DateTime endDateTime =
|
||||
DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000)
|
||||
.toUtc()
|
||||
.toLocal();
|
||||
startDateTime = DateTime(startDateTime.year, startDateTime.month,
|
||||
startDateTime.day, startDateTime.hour, startDateTime.minute);
|
||||
endDateTime = DateTime(endDateTime.year, endDateTime.month,
|
||||
endDateTime.day, endDateTime.hour, endDateTime.minute);
|
||||
if (effectiveDateAndTime.isBefore(startDateTime) ||
|
||||
invalidDateAndTime.isAfter(endDateTime)) {
|
||||
matchesCriteria = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by selected tab index
|
||||
if (event.selectedTabIndex == 1 && item.passwordStatus.value != 'To be effective') {
|
||||
if (event.selectedTabIndex == 1 &&
|
||||
item.passwordStatus.value != 'To be effective') {
|
||||
matchesCriteria = false;
|
||||
} else if (event.selectedTabIndex == 2 && item.passwordStatus.value != 'Effective') {
|
||||
} else if (event.selectedTabIndex == 2 &&
|
||||
item.passwordStatus.value != 'Effective') {
|
||||
matchesCriteria = false;
|
||||
} else if (event.selectedTabIndex == 3 && item.passwordStatus.value != 'Expired') {
|
||||
} else if (event.selectedTabIndex == 3 &&
|
||||
item.passwordStatus.value != 'Expired') {
|
||||
matchesCriteria = false;
|
||||
}
|
||||
|
||||
@ -189,6 +261,7 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
|
||||
startTime = 'Start Time';
|
||||
endTime = 'End Time';
|
||||
passwordName.clear();
|
||||
emailAuthorizer.clear();
|
||||
selectedIndex = 0;
|
||||
effectiveTimeTimeStamp = null;
|
||||
expirationTimeTimeStamp = null;
|
||||
@ -196,11 +269,14 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
|
||||
}
|
||||
|
||||
String timestampToDate(dynamic timestamp) {
|
||||
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(int.parse(timestamp) * 1000);
|
||||
return "${dateTime.year}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.day.toString().padLeft(2, '0')}";
|
||||
DateTime dateTime =
|
||||
DateTime.fromMillisecondsSinceEpoch(int.parse(timestamp) * 1000);
|
||||
return "${dateTime.year}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.day.toString().padLeft(2, '0')} "
|
||||
" ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}";
|
||||
}
|
||||
|
||||
Future<void> onTabChanged(TabChangedEvent event, Emitter<AccessState> emit) async {
|
||||
Future<void> onTabChanged(
|
||||
TabChangedEvent event, Emitter<AccessState> emit) async {
|
||||
try {
|
||||
emit(AccessLoaded());
|
||||
selectedIndex = event.selectedIndex;
|
||||
@ -209,14 +285,19 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
|
||||
filteredData = data;
|
||||
break;
|
||||
case 1: // To Be Effective
|
||||
filteredData =
|
||||
data.where((item) => item.passwordStatus.value == "To Be Effective").toList();
|
||||
filteredData = data
|
||||
.where((item) => item.passwordStatus.value == "To Be Effective")
|
||||
.toList();
|
||||
break;
|
||||
case 2: // Effective
|
||||
filteredData = data.where((item) => item.passwordStatus.value == "Effective").toList();
|
||||
filteredData = data
|
||||
.where((item) => item.passwordStatus.value == "Effective")
|
||||
.toList();
|
||||
break;
|
||||
case 3: // Expired
|
||||
filteredData = data.where((item) => item.passwordStatus.value == "Expired").toList();
|
||||
filteredData = data
|
||||
.where((item) => item.passwordStatus.value == "Expired")
|
||||
.toList();
|
||||
break;
|
||||
default:
|
||||
filteredData = data;
|
||||
@ -224,6 +305,7 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
|
||||
add(FilterDataEvent(
|
||||
selectedTabIndex: selectedIndex,
|
||||
passwordName: passwordName.text.toLowerCase(),
|
||||
emailAuthorizer: emailAuthorizer.text.toLowerCase(),
|
||||
startTime: effectiveTimeTimeStamp,
|
||||
endTime: expirationTimeTimeStamp));
|
||||
emit(TableLoaded(filteredData));
|
||||
|
||||
@ -28,12 +28,14 @@ class SelectTime extends AccessEvent {
|
||||
|
||||
class FilterDataEvent extends AccessEvent {
|
||||
final String? passwordName;
|
||||
final String? emailAuthorizer;
|
||||
final int? startTime;
|
||||
final int? endTime;
|
||||
final int selectedTabIndex; // Add this field
|
||||
|
||||
const FilterDataEvent({
|
||||
this.passwordName,
|
||||
this.emailAuthorizer,
|
||||
this.startTime,
|
||||
this.endTime,
|
||||
required this.selectedTabIndex, // Initialize this field
|
||||
|
||||
@ -6,10 +6,13 @@ class PasswordModel {
|
||||
final dynamic effectiveTime;
|
||||
final dynamic passwordCreated;
|
||||
final dynamic createdTime;
|
||||
final dynamic passwordName; // New field
|
||||
final dynamic passwordName;
|
||||
final AccessStatus passwordStatus;
|
||||
final AccessType passwordType;
|
||||
final dynamic deviceUuid;
|
||||
final dynamic authorizerEmail;
|
||||
final dynamic authorizerDate;
|
||||
final dynamic deviceName;
|
||||
|
||||
PasswordModel({
|
||||
this.passwordId,
|
||||
@ -17,10 +20,13 @@ class PasswordModel {
|
||||
this.effectiveTime,
|
||||
this.passwordCreated,
|
||||
this.createdTime,
|
||||
this.passwordName, // New field
|
||||
this.passwordName,
|
||||
required this.passwordStatus,
|
||||
required this.passwordType,
|
||||
this.deviceUuid,
|
||||
this.authorizerEmail,
|
||||
this.authorizerDate,
|
||||
this.deviceName,
|
||||
});
|
||||
|
||||
factory PasswordModel.fromJson(Map<String, dynamic> json) {
|
||||
@ -30,10 +36,13 @@ class PasswordModel {
|
||||
effectiveTime: json['effectiveTime'],
|
||||
passwordCreated: json['passwordCreated'],
|
||||
createdTime: json['createdTime'],
|
||||
passwordName: json['passwordName'] ?? 'No name', // New field
|
||||
passwordName: json['passwordName']??'No Name',
|
||||
passwordStatus: AccessStatusExtension.fromString(json['passwordStatus']),
|
||||
passwordType: AccessTypeExtension.fromString(json['passwordType']),
|
||||
deviceUuid: json['deviceUuid'],
|
||||
authorizerEmail: json['authorizerEmail'],
|
||||
authorizerDate: json['authorizerDate'],
|
||||
deviceName: json['deviceName'],
|
||||
);
|
||||
}
|
||||
|
||||
@ -44,10 +53,13 @@ class PasswordModel {
|
||||
'effectiveTime': effectiveTime,
|
||||
'passwordCreated': passwordCreated,
|
||||
'createdTime': createdTime,
|
||||
'passwodName': passwordName, // New field
|
||||
'passwordName': passwordName, // New field
|
||||
'passwordStatus': passwordStatus,
|
||||
'passwordType': passwordType,
|
||||
'deviceUuid': deviceUuid,
|
||||
'authorizerEmail': authorizerEmail,
|
||||
'authorizerDate': authorizerDate,
|
||||
'deviceName': deviceName,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,67 +1,47 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_state.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart';
|
||||
import 'package:syncrow_web/pages/common/custom_table.dart';
|
||||
import 'package:syncrow_web/pages/common/date_time_widget.dart';
|
||||
import 'package:syncrow_web/pages/common/filter/filter_widget.dart';
|
||||
import 'package:syncrow_web/pages/common/text_field/custom_web_textfield.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/constants/app_enum.dart';
|
||||
import 'package:syncrow_web/utils/constants/routes_const.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
||||
|
||||
class AccessManagementPage extends StatelessWidget {
|
||||
class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout {
|
||||
const AccessManagementPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Size size = MediaQuery.of(context).size;
|
||||
final isLargeScreen = isLargeScreenSize(context);
|
||||
final isSmallScreen = isSmallScreenSize(context);
|
||||
final isHalfMediumScreen = isHafMediumScreenSize(context);
|
||||
final padding =
|
||||
isLargeScreen ? const EdgeInsets.all(30) : const EdgeInsets.all(15);
|
||||
|
||||
return WebScaffold(
|
||||
enableMenuSideba: false,
|
||||
appBarTitle: Row(
|
||||
children: [
|
||||
Text(
|
||||
'Access Management',
|
||||
style: Theme.of(context).textTheme.headlineLarge,
|
||||
)
|
||||
],
|
||||
),
|
||||
appBarBody: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Physical Access',
|
||||
style: Theme.of(context).textTheme.headlineMedium!.copyWith(color: Colors.white),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () {
|
||||
context.go(RoutesConst.home);
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
height: 20,
|
||||
width: 20,
|
||||
Assets.grid,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
enableMenuSidebar: false,
|
||||
appBarTitle: FittedBox(
|
||||
child: Text(
|
||||
'Access Management',
|
||||
style: Theme.of(context).textTheme.headlineLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
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) {
|
||||
@ -70,196 +50,42 @@ class AccessManagementPage extends StatelessWidget {
|
||||
return state is AccessLoaded
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Container(
|
||||
padding: EdgeInsets.all(30),
|
||||
height: size.height,
|
||||
width: size.width,
|
||||
padding: padding,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
decoration: containerDecoration,
|
||||
height: size.height * 0.05,
|
||||
child: Flexible(
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: BlocProvider.of<AccessBloc>(context).tabs.length,
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
final isSelected = index ==
|
||||
BlocProvider.of<AccessBloc>(context).selectedIndex;
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
BlocProvider.of<AccessBloc>(context)
|
||||
.add(TabChangedEvent(index));
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.boxColor,
|
||||
border: Border.all(
|
||||
color: isSelected ? Colors.blue : Colors.transparent,
|
||||
width: 2.0,
|
||||
),
|
||||
borderRadius: index == 0
|
||||
? const BorderRadius.only(
|
||||
topLeft: Radius.circular(10),
|
||||
bottomLeft: Radius.circular(10))
|
||||
: index == 3
|
||||
? const BorderRadius.only(
|
||||
topRight: Radius.circular(10),
|
||||
bottomRight: Radius.circular(10))
|
||||
: null,
|
||||
),
|
||||
padding: const EdgeInsets.only(left: 10, right: 10),
|
||||
child: Center(
|
||||
child: Text(
|
||||
BlocProvider.of<AccessBloc>(context).tabs[index],
|
||||
style: TextStyle(
|
||||
color: isSelected ? Colors.blue : Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
textBaseline: TextBaseline.ideographic,
|
||||
children: [
|
||||
Container(
|
||||
width: size.width * 0.15,
|
||||
child: CustomWebTextField(
|
||||
controller: accessBloc.passwordName,
|
||||
isRequired: true,
|
||||
textFieldName: 'Name',
|
||||
description: '',
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 15,
|
||||
),
|
||||
DateTimeWebWidget(
|
||||
icon: Assets.calendarIcon,
|
||||
isRequired: false,
|
||||
title: 'Access Time',
|
||||
size: size,
|
||||
endTime: () {
|
||||
accessBloc.add(SelectTime(context: context, isStart: false));
|
||||
},
|
||||
startTime: () {
|
||||
accessBloc.add(SelectTime(context: context, isStart: true));
|
||||
},
|
||||
firstString: BlocProvider.of<AccessBloc>(context).startTime,
|
||||
secondString: BlocProvider.of<AccessBloc>(context).endTime,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 15,
|
||||
),
|
||||
SizedBox(
|
||||
width: size.width * 0.06,
|
||||
child: Container(
|
||||
decoration: containerDecoration,
|
||||
child: DefaultButton(
|
||||
onPressed: () {
|
||||
accessBloc.add(FilterDataEvent(
|
||||
selectedTabIndex: BlocProvider.of<AccessBloc>(
|
||||
context)
|
||||
.selectedIndex, // Pass the selected tab index
|
||||
passwordName:
|
||||
accessBloc.passwordName.text.toLowerCase(),
|
||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
||||
endTime: accessBloc.expirationTimeTimeStamp));
|
||||
},
|
||||
borderRadius: 9,
|
||||
child: const Text('Search'))),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
SizedBox(
|
||||
width: size.width * 0.06,
|
||||
child: Container(
|
||||
decoration: containerDecoration,
|
||||
child: DefaultButton(
|
||||
onPressed: () {
|
||||
accessBloc.add(ResetSearch());
|
||||
},
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
borderRadius: 9,
|
||||
child: Text(
|
||||
'Reset',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: Colors.black),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Wrap(
|
||||
children: [
|
||||
Container(
|
||||
width: size.width * 0.15,
|
||||
decoration: containerDecoration,
|
||||
child: DefaultButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return const VisitorPasswordDialog();
|
||||
},
|
||||
).then((v) {
|
||||
if (v != null) {
|
||||
accessBloc.add(FetchTableData());
|
||||
}
|
||||
});
|
||||
},
|
||||
borderRadius: 8,
|
||||
child: const Text('+ Create Visitor Password ')),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Container(
|
||||
width: size.width * 0.12,
|
||||
decoration: containerDecoration,
|
||||
child: DefaultButton(
|
||||
borderRadius: 8,
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
child: Text(
|
||||
'Admin Password',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: Colors.black),
|
||||
)))
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
FilterWidget(
|
||||
size: MediaQuery.of(context).size,
|
||||
tabs: accessBloc.tabs,
|
||||
selectedIndex: accessBloc.selectedIndex,
|
||||
onTabChanged: (index) {
|
||||
accessBloc.add(TabChangedEvent(index));
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
if (isSmallScreen || isHalfMediumScreen)
|
||||
_buildSmallSearchFilters(context, accessBloc)
|
||||
else
|
||||
_buildNormalSearchWidgets(context, accessBloc),
|
||||
const SizedBox(height: 20),
|
||||
_buildVisitorAdminPasswords(context, accessBloc),
|
||||
const SizedBox(height: 20),
|
||||
Expanded(
|
||||
child: DynamicTable(
|
||||
tableName: 'AccessManagement',
|
||||
uuidIndex: 1,
|
||||
withSelectAll: true,
|
||||
isEmpty: filteredData.isEmpty,
|
||||
withCheckBox: false,
|
||||
size: size,
|
||||
size: MediaQuery.of(context).size,
|
||||
cellDecoration: containerDecoration,
|
||||
headers: const [
|
||||
'Name',
|
||||
'Access Type',
|
||||
'Access Period',
|
||||
'Access Start',
|
||||
'Access End',
|
||||
'Accessible Device',
|
||||
'Authorizer',
|
||||
'Authorization Date & Time',
|
||||
@ -267,21 +93,212 @@ class AccessManagementPage extends StatelessWidget {
|
||||
],
|
||||
data: filteredData.map((item) {
|
||||
return [
|
||||
item.passwordName.toString(),
|
||||
item.passwordName,
|
||||
item.passwordType.value,
|
||||
('${accessBloc.timestampToDate(item.effectiveTime)} - ${accessBloc.timestampToDate(item.invalidTime)}'),
|
||||
item.deviceUuid.toString(),
|
||||
'',
|
||||
'',
|
||||
accessBloc
|
||||
.timestampToDate(item.effectiveTime),
|
||||
accessBloc
|
||||
.timestampToDate(item.invalidTime),
|
||||
item.deviceName.toString(),
|
||||
item.authorizerEmail.toString(),
|
||||
accessBloc
|
||||
.timestampToDate(item.invalidTime),
|
||||
item.passwordStatus.value,
|
||||
];
|
||||
}).toList(),
|
||||
)
|
||||
// : const Center(child: CircularProgressIndicator()),
|
||||
)
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
})));
|
||||
}
|
||||
|
||||
Wrap _buildVisitorAdminPasswords(
|
||||
BuildContext context, AccessBloc accessBloc) {
|
||||
return Wrap(
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
children: [
|
||||
Container(
|
||||
width: 205,
|
||||
height: 42,
|
||||
decoration: containerDecoration,
|
||||
child: DefaultButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return const VisitorPasswordDialog();
|
||||
},
|
||||
).then((v) {
|
||||
if (v != null) {
|
||||
accessBloc.add(FetchTableData());
|
||||
}
|
||||
});
|
||||
},
|
||||
borderRadius: 8,
|
||||
child: Text(
|
||||
'Create Visitor Password ',
|
||||
style: context.textTheme.titleSmall!
|
||||
.copyWith(color: Colors.white, fontSize: 12),
|
||||
)),
|
||||
),
|
||||
Container(
|
||||
width: 133,
|
||||
height: 42,
|
||||
decoration: containerDecoration,
|
||||
child: DefaultButton(
|
||||
borderRadius: 8,
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
child: Text(
|
||||
'Admin Password',
|
||||
style: context.textTheme.titleSmall!
|
||||
.copyWith(color: Colors.black, fontSize: 12),
|
||||
)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Row _buildNormalSearchWidgets(BuildContext context, AccessBloc accessBloc) {
|
||||
// TimeOfDay _selectedTime = TimeOfDay.now();
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
textBaseline: TextBaseline.ideographic,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 250,
|
||||
child: CustomWebTextField(
|
||||
controller: accessBloc.passwordName,
|
||||
height: 43,
|
||||
isRequired: false,
|
||||
textFieldName: 'Name',
|
||||
description: '',
|
||||
onSubmitted: (value) {
|
||||
accessBloc.add(FilterDataEvent(
|
||||
emailAuthorizer:
|
||||
accessBloc.emailAuthorizer.text.toLowerCase(),
|
||||
selectedTabIndex:
|
||||
BlocProvider.of<AccessBloc>(context).selectedIndex,
|
||||
passwordName: accessBloc.passwordName.text.toLowerCase(),
|
||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
||||
endTime: accessBloc.expirationTimeTimeStamp));
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
SizedBox(
|
||||
width: 250,
|
||||
child: CustomWebTextField(
|
||||
controller: accessBloc.emailAuthorizer,
|
||||
height: 43,
|
||||
isRequired: false,
|
||||
textFieldName: 'Authorizer',
|
||||
description: '',
|
||||
onSubmitted: (value) {
|
||||
accessBloc.add(FilterDataEvent(
|
||||
emailAuthorizer:
|
||||
accessBloc.emailAuthorizer.text.toLowerCase(),
|
||||
selectedTabIndex:
|
||||
BlocProvider.of<AccessBloc>(context).selectedIndex,
|
||||
passwordName: accessBloc.passwordName.text.toLowerCase(),
|
||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
||||
endTime: accessBloc.expirationTimeTimeStamp));
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
SizedBox(
|
||||
child: DateTimeWebWidget(
|
||||
icon: Assets.calendarIcon,
|
||||
isRequired: false,
|
||||
title: 'Access Time',
|
||||
size: MediaQuery.of(context).size,
|
||||
endTime: () {
|
||||
accessBloc.add(SelectTime(context: context, isStart: false));
|
||||
},
|
||||
startTime: () {
|
||||
accessBloc.add(SelectTime(context: context, isStart: true));
|
||||
},
|
||||
firstString: BlocProvider.of<AccessBloc>(context).startTime,
|
||||
secondString: BlocProvider.of<AccessBloc>(context).endTime,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
SearchResetButtons(
|
||||
onSearch: () {
|
||||
accessBloc.add(FilterDataEvent(
|
||||
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
|
||||
selectedTabIndex:
|
||||
BlocProvider.of<AccessBloc>(context).selectedIndex,
|
||||
passwordName: accessBloc.passwordName.text.toLowerCase(),
|
||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
||||
endTime: accessBloc.expirationTimeTimeStamp));
|
||||
},
|
||||
onReset: () {
|
||||
accessBloc.add(ResetSearch());
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSmallSearchFilters(BuildContext context, AccessBloc accessBloc) {
|
||||
return Wrap(
|
||||
spacing: 20,
|
||||
runSpacing: 10,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 300,
|
||||
child: CustomWebTextField(
|
||||
controller: accessBloc.passwordName,
|
||||
isRequired: true,
|
||||
height: 40,
|
||||
textFieldName: 'Name',
|
||||
description: '',
|
||||
onSubmitted: (value) {
|
||||
accessBloc.add(FilterDataEvent(
|
||||
emailAuthorizer:
|
||||
accessBloc.emailAuthorizer.text.toLowerCase(),
|
||||
selectedTabIndex:
|
||||
BlocProvider.of<AccessBloc>(context).selectedIndex,
|
||||
passwordName: accessBloc.passwordName.text.toLowerCase(),
|
||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
||||
endTime: accessBloc.expirationTimeTimeStamp));
|
||||
}),
|
||||
),
|
||||
DateTimeWebWidget(
|
||||
icon: Assets.calendarIcon,
|
||||
isRequired: false,
|
||||
title: 'Access Time',
|
||||
size: MediaQuery.of(context).size,
|
||||
endTime: () {
|
||||
accessBloc.add(SelectTime(context: context, isStart: false));
|
||||
},
|
||||
startTime: () {
|
||||
accessBloc.add(SelectTime(context: context, isStart: true));
|
||||
},
|
||||
firstString: BlocProvider.of<AccessBloc>(context).startTime,
|
||||
secondString: BlocProvider.of<AccessBloc>(context).endTime,
|
||||
),
|
||||
SearchResetButtons(
|
||||
onSearch: () {
|
||||
accessBloc.add(FilterDataEvent(
|
||||
emailAuthorizer: accessBloc.emailAuthorizer.text.toLowerCase(),
|
||||
selectedTabIndex:
|
||||
BlocProvider.of<AccessBloc>(context).selectedIndex,
|
||||
passwordName: accessBloc.passwordName.text.toLowerCase(),
|
||||
startTime: accessBloc.effectiveTimeTimeStamp,
|
||||
endTime: accessBloc.expirationTimeTimeStamp));
|
||||
},
|
||||
onReset: () {
|
||||
accessBloc.add(ResetSearch());
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
@ -34,14 +35,15 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
TextEditingController();
|
||||
final TextEditingController forgetOtp = TextEditingController();
|
||||
final forgetFormKey = GlobalKey<FormState>();
|
||||
final forgetEmailKey = GlobalKey<FormState>();
|
||||
final forgetRegionKey = GlobalKey<FormState>();
|
||||
late bool checkValidate = false;
|
||||
|
||||
Timer? _timer;
|
||||
int _remainingTime = 0;
|
||||
List<RegionModel>? regionList = [RegionModel(name: 'name', id: 'id')];
|
||||
|
||||
Future<void> _onStartTimer(
|
||||
StartTimerEvent event, Emitter<AuthState> emit) async {
|
||||
Future _onStartTimer(StartTimerEvent event, Emitter<AuthState> emit) async {
|
||||
if (_validateInputs(emit)) return;
|
||||
if (_timer != null && _timer!.isActive) {
|
||||
return;
|
||||
@ -49,8 +51,35 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
_remainingTime = 1;
|
||||
add(UpdateTimerEvent(
|
||||
remainingTime: _remainingTime, isButtonEnabled: false));
|
||||
_remainingTime = (await AuthenticationAPI.sendOtp(
|
||||
email: forgetEmailController.text, regionUuid: regionUuid))!;
|
||||
try {
|
||||
forgetEmailValidate = '';
|
||||
_remainingTime = (await AuthenticationAPI.sendOtp(
|
||||
email: forgetEmailController.text, regionUuid: regionUuid))!;
|
||||
} on DioException catch (e) {
|
||||
if (e.response!.statusCode == 400) {
|
||||
final errorData = e.response!.data;
|
||||
String errorMessage = errorData['message'];
|
||||
if (errorMessage == 'User not found') {
|
||||
validate = 'Invalid Credential';
|
||||
emit(AuthInitialState());
|
||||
return 1;
|
||||
} else {
|
||||
validate = '';
|
||||
_remainingTime = errorData['data']['cooldown'] ?? 1;
|
||||
emit(AuthInitialState());
|
||||
}
|
||||
} else {
|
||||
emit(AuthInitialState());
|
||||
|
||||
return 1;
|
||||
}
|
||||
emit(AuthInitialState());
|
||||
} catch (e) {
|
||||
emit(AuthInitialState());
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
_remainingTime--;
|
||||
if (_remainingTime <= 0) {
|
||||
@ -70,32 +99,28 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
|
||||
Future<void> changePassword(
|
||||
ChangePasswordEvent event, Emitter<AuthState> emit) async {
|
||||
emit(LoadingForgetState());
|
||||
try {
|
||||
emit(LoadingForgetState());
|
||||
var response = await AuthenticationAPI.verifyOtp(
|
||||
email: forgetEmailController.text, otpCode: forgetOtp.text);
|
||||
if (response == true) {
|
||||
await AuthenticationAPI.forgetPassword(
|
||||
otpCode: forgetOtp.text,
|
||||
password: forgetPasswordController.text,
|
||||
email: forgetEmailController.text);
|
||||
_timer?.cancel();
|
||||
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
||||
emit(SuccessForgetState());
|
||||
} else if (response == "You entered wrong otp") {
|
||||
forgetValidate = 'Wrong one time password.';
|
||||
emit(AuthInitialState());
|
||||
} else if (response == "OTP expired") {
|
||||
forgetValidate = 'One time password has been expired.';
|
||||
emit(AuthInitialState());
|
||||
}
|
||||
} catch (failure) {
|
||||
// forgetValidate='Invalid Credentials!';
|
||||
} on DioException catch (e) {
|
||||
final errorData = e.response!.data;
|
||||
String errorMessage =
|
||||
errorData['error']['message'] ?? 'something went wrong';
|
||||
validate = errorMessage;
|
||||
emit(AuthInitialState());
|
||||
// emit(FailureForgetState(error: failure.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
//925207
|
||||
String? validateCode(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Code is required';
|
||||
@ -120,6 +145,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
String otpCode = '';
|
||||
String validate = '';
|
||||
String forgetValidate = '';
|
||||
String forgetEmailValidate = '';
|
||||
String regionUuid = '';
|
||||
static Token token = Token.emptyConstructor();
|
||||
static UserModel? user;
|
||||
@ -127,7 +153,6 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
|
||||
void _login(LoginButtonPressed event, Emitter<AuthState> emit) async {
|
||||
emit(AuthLoading());
|
||||
|
||||
if (isChecked) {
|
||||
try {
|
||||
if (event.username.isEmpty || event.password.isEmpty) {
|
||||
@ -135,6 +160,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
emit(const LoginFailure(error: 'Something went wrong'));
|
||||
return;
|
||||
}
|
||||
|
||||
token = await AuthenticationAPI.loginWithEmail(
|
||||
model: LoginWithEmailModel(
|
||||
email: event.username,
|
||||
@ -143,10 +169,10 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
);
|
||||
} catch (failure) {
|
||||
validate = 'Invalid Credentials!';
|
||||
emit(AuthInitialState());
|
||||
// emit(const LoginFailure(error: 'Something went wrong'));
|
||||
emit(LoginInitial());
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.accessTokenIsNotEmpty) {
|
||||
FlutterSecureStorage storage = const FlutterSecureStorage();
|
||||
await storage.write(
|
||||
@ -155,9 +181,9 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
key: UserModel.userUuidKey,
|
||||
value: Token.decodeToken(token.accessToken)['uuid'].toString());
|
||||
user = UserModel.fromToken(token);
|
||||
debugPrint(token.accessToken);
|
||||
loginEmailController.clear();
|
||||
loginPasswordController.clear();
|
||||
debugPrint("token " + token.accessToken);
|
||||
emit(LoginSuccess());
|
||||
} else {
|
||||
emit(const LoginFailure(error: 'Something went wrong'));
|
||||
@ -177,15 +203,15 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
emit(LoginInitial());
|
||||
}
|
||||
|
||||
checkOtpCode(
|
||||
ChangePasswordEvent event,
|
||||
Emitter<AuthState> emit,
|
||||
) async {
|
||||
emit(LoadingForgetState());
|
||||
await AuthenticationAPI.verifyOtp(
|
||||
email: forgetEmailController.text, otpCode: forgetOtp.text);
|
||||
emit(SuccessForgetState());
|
||||
}
|
||||
// checkOtpCode(
|
||||
// ChangePasswordEvent event,
|
||||
// Emitter<AuthState> emit,
|
||||
// ) async {
|
||||
// emit(LoadingForgetState());
|
||||
// await AuthenticationAPI.verifyOtp(
|
||||
// email: forgetEmailController.text, otpCode: forgetOtp.text);
|
||||
// emit(SuccessForgetState());
|
||||
// }
|
||||
|
||||
void _passwordVisible(PasswordVisibleEvent event, Emitter<AuthState> emit) {
|
||||
emit(AuthLoading());
|
||||
@ -208,9 +234,10 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
return 'Email is required';
|
||||
} else if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
|
||||
return 'Enter a valid email address';
|
||||
} else if (regionUuid == '') {
|
||||
return 'Please select your region';
|
||||
}
|
||||
// else if (regionUuid == '') {
|
||||
// return 'Please select your region';
|
||||
// }
|
||||
validate = '';
|
||||
return null;
|
||||
}
|
||||
@ -226,7 +253,9 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
emit(LoadingForgetState());
|
||||
final nameError = validateEmail(forgetEmailController.text);
|
||||
if (nameError != null) {
|
||||
emit(FailureForgetState(error: nameError));
|
||||
forgetEmailValidate = nameError;
|
||||
emit(AuthInitialState());
|
||||
// emit(FailureForgetState(error: nameError));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -348,6 +377,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
try {
|
||||
emit(AuthLoading());
|
||||
regionUuid = event.val;
|
||||
add(CheckEnableEvent());
|
||||
emit(AuthInitialState());
|
||||
} catch (e) {
|
||||
emit(FailureForgetState(error: e.toString()));
|
||||
@ -404,4 +434,9 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
forgetValidate = '';
|
||||
emit(LoginInitial());
|
||||
}
|
||||
|
||||
static logout() {
|
||||
const storage = FlutterSecureStorage();
|
||||
storage.deleteAll();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class AuthEvent extends Equatable {
|
||||
const AuthEvent();
|
||||
@ -47,13 +46,16 @@ class StopTimerEvent extends AuthEvent {}
|
||||
class UpdateTimerEvent extends AuthEvent {
|
||||
final int remainingTime;
|
||||
final bool isButtonEnabled;
|
||||
const UpdateTimerEvent(
|
||||
{required this.remainingTime, required this.isButtonEnabled});
|
||||
const UpdateTimerEvent({required this.remainingTime, required this.isButtonEnabled});
|
||||
}
|
||||
|
||||
class ChangePasswordEvent extends AuthEvent {}
|
||||
class ChangePasswordEvent extends AuthEvent {
|
||||
|
||||
class SendOtpEvent extends AuthEvent {}
|
||||
}
|
||||
|
||||
class SendOtpEvent extends AuthEvent {
|
||||
|
||||
}
|
||||
|
||||
class PasswordVisibleEvent extends AuthEvent {
|
||||
final bool? newValue;
|
||||
|
||||
@ -56,8 +56,7 @@ class TimerState extends AuthState {
|
||||
final bool isButtonEnabled;
|
||||
final int remainingTime;
|
||||
|
||||
const TimerState(
|
||||
{required this.isButtonEnabled, required this.remainingTime});
|
||||
const TimerState({required this.isButtonEnabled, required this.remainingTime});
|
||||
|
||||
@override
|
||||
List<Object> get props => [isButtonEnabled, remainingTime];
|
||||
@ -65,12 +64,12 @@ class TimerState extends AuthState {
|
||||
|
||||
class AuthError extends AuthState {
|
||||
final String message;
|
||||
String? code;
|
||||
AuthError({required this.message, this.code});
|
||||
final String? code;
|
||||
const AuthError({required this.message, this.code});
|
||||
}
|
||||
|
||||
class AuthTokenError extends AuthError {
|
||||
AuthTokenError({required super.message, super.code});
|
||||
const AuthTokenError({required super.message, super.code});
|
||||
}
|
||||
|
||||
class AuthSuccess extends AuthState {}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/auth/view/forget_password_mobile_page.dart';
|
||||
import 'package:syncrow_web/pages/auth/view/forget_password_web_page.dart';
|
||||
import 'package:syncrow_web/utils/responsive_layout.dart';
|
||||
|
||||
@ -9,7 +8,6 @@ class ForgetPasswordPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const ResponsiveLayout(
|
||||
desktopBody: ForgetPasswordWebPage(),
|
||||
mobileBody: ForgetPasswordWebPage());
|
||||
desktopBody: ForgetPasswordWebPage(), mobileBody: ForgetPasswordWebPage());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
@ -47,8 +48,7 @@ class ForgetPasswordWebPage extends StatelessWidget {
|
||||
late ScrollController _scrollController;
|
||||
_scrollController = ScrollController();
|
||||
void _scrollToCenter() {
|
||||
final double middlePosition =
|
||||
_scrollController.position.maxScrollExtent / 2;
|
||||
final double middlePosition = _scrollController.position.maxScrollExtent / 2;
|
||||
_scrollController.animateTo(
|
||||
middlePosition,
|
||||
duration: const Duration(seconds: 1),
|
||||
@ -65,8 +65,7 @@ class ForgetPasswordWebPage extends StatelessWidget {
|
||||
second: Center(
|
||||
child: Stack(
|
||||
children: [
|
||||
if (state is AuthLoading)
|
||||
const Center(child: CircularProgressIndicator()),
|
||||
if (state is AuthLoading) const Center(child: CircularProgressIndicator()),
|
||||
ListView(
|
||||
shrinkWrap: true,
|
||||
controller: _scrollController,
|
||||
@ -96,21 +95,16 @@ class ForgetPasswordWebPage extends StatelessWidget {
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.1),
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(30)),
|
||||
border: Border.all(
|
||||
color:
|
||||
ColorsManager.graysColor.withOpacity(0.2)),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(30)),
|
||||
border: Border.all(color: ColorsManager.graysColor.withOpacity(0.2)),
|
||||
),
|
||||
child: Form(
|
||||
key: forgetBloc.forgetFormKey,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: size.width * 0.02,
|
||||
vertical: size.width * 0.003),
|
||||
horizontal: size.width * 0.02, vertical: size.width * 0.003),
|
||||
child: Column(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceEvenly,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 10),
|
||||
@ -127,102 +121,55 @@ class ForgetPasswordWebPage extends StatelessWidget {
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400),
|
||||
.copyWith(fontSize: 14, fontWeight: FontWeight.w400),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Country/Region",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
child: DropdownButtonFormField<String>(
|
||||
validator: forgetBloc.validateRegion,
|
||||
icon: const Icon(
|
||||
Icons.keyboard_arrow_down_outlined,
|
||||
),
|
||||
decoration:
|
||||
textBoxDecoration()!.copyWith(
|
||||
hintText: null,
|
||||
),
|
||||
hint: SizedBox(
|
||||
width: size.width * 0.12,
|
||||
child: const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Select your region/country',
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
isDense: true,
|
||||
style: const TextStyle(
|
||||
color: Colors.black),
|
||||
items: forgetBloc.regionList!
|
||||
.map((RegionModel region) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: region.id,
|
||||
child: SizedBox(
|
||||
width: size.width * 0.06,
|
||||
child: Text(region.name)),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (String? value) {
|
||||
forgetBloc.add(SelectRegionEvent(
|
||||
val: value!,
|
||||
));
|
||||
},
|
||||
),
|
||||
)
|
||||
Form(
|
||||
key: forgetBloc.forgetRegionKey,
|
||||
child: SizedBox(
|
||||
child:
|
||||
_buildDropdownField(context, forgetBloc, size)))
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Account",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
validator: forgetBloc.validateEmail,
|
||||
controller:
|
||||
forgetBloc.forgetEmailController,
|
||||
decoration: textBoxDecoration()!
|
||||
.copyWith(
|
||||
hintText: 'Enter your email'),
|
||||
style: const TextStyle(
|
||||
color: Colors.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Form(
|
||||
key: forgetBloc.forgetEmailKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Account",
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontSize: 14, fontWeight: FontWeight.w400),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
controller: forgetBloc.forgetEmailController,
|
||||
validator: forgetBloc.validateEmail,
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
hintText: 'Enter your email',
|
||||
hintStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontWeight: FontWeight.w400),
|
||||
),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
const SizedBox(height: 20.0),
|
||||
Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
@ -230,59 +177,64 @@ class ForgetPasswordWebPage extends StatelessWidget {
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400),
|
||||
.copyWith(fontSize: 14, fontWeight: FontWeight.w400),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
validator: forgetBloc.validateCode,
|
||||
keyboardType:
|
||||
TextInputType.visiblePassword,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller: forgetBloc.forgetOtp,
|
||||
decoration:
|
||||
textBoxDecoration()!.copyWith(
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
hintText: 'Enter Code',
|
||||
hintStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontWeight: FontWeight.w400),
|
||||
suffixIcon: SizedBox(
|
||||
width: 100,
|
||||
child: Center(
|
||||
child: InkWell(
|
||||
onTap: state is TimerState &&
|
||||
!state
|
||||
.isButtonEnabled &&
|
||||
state.remainingTime !=
|
||||
1
|
||||
!state.isButtonEnabled &&
|
||||
state.remainingTime != 1
|
||||
? null
|
||||
: () {
|
||||
forgetBloc.add(
|
||||
StartTimerEvent());
|
||||
if (forgetBloc
|
||||
.forgetEmailKey.currentState!
|
||||
.validate() ||
|
||||
forgetBloc
|
||||
.forgetRegionKey.currentState!
|
||||
.validate()) {
|
||||
if (forgetBloc
|
||||
.forgetRegionKey.currentState!
|
||||
.validate()) {
|
||||
forgetBloc.add(StartTimerEvent());
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime != 1 ? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}',
|
||||
style: TextStyle(
|
||||
color: state
|
||||
is TimerState &&
|
||||
!state
|
||||
.isButtonEnabled
|
||||
color: state is TimerState &&
|
||||
!state.isButtonEnabled
|
||||
? Colors.grey
|
||||
: ColorsManager
|
||||
.btnColor,
|
||||
: ColorsManager.btnColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
style: const TextStyle(
|
||||
color: Colors.black),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
if (forgetBloc.forgetValidate !=
|
||||
'') // Check if there is a validation message
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 8.0),
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
forgetBloc.forgetValidate,
|
||||
style: const TextStyle(
|
||||
@ -295,8 +247,7 @@ class ForgetPasswordWebPage extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
@ -304,25 +255,40 @@ class ForgetPasswordWebPage extends StatelessWidget {
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400),
|
||||
.copyWith(fontSize: 14, fontWeight: FontWeight.w400),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
validator:
|
||||
forgetBloc.passwordValidator,
|
||||
keyboardType:
|
||||
TextInputType.visiblePassword,
|
||||
controller: forgetBloc
|
||||
.forgetPasswordController,
|
||||
decoration:
|
||||
textBoxDecoration()!.copyWith(
|
||||
obscureText: forgetBloc.obscureText,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
validator: forgetBloc.passwordValidator,
|
||||
controller: forgetBloc.forgetPasswordController,
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () {
|
||||
forgetBloc.add(PasswordVisibleEvent(
|
||||
newValue: forgetBloc.obscureText));
|
||||
},
|
||||
icon: SizedBox(
|
||||
child: SvgPicture.asset(
|
||||
forgetBloc.obscureText
|
||||
? Assets.visiblePassword
|
||||
: Assets.invisiblePassword,
|
||||
height: 15,
|
||||
width: 15,
|
||||
),
|
||||
),
|
||||
),
|
||||
hintText: 'At least 8 characters',
|
||||
hintStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontWeight: FontWeight.w400),
|
||||
),
|
||||
style: const TextStyle(
|
||||
color: Colors.black),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -332,22 +298,24 @@ class ForgetPasswordWebPage extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
Row(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: size.width * 0.2,
|
||||
child: DefaultButton(
|
||||
backgroundColor:
|
||||
ColorsManager.btnColor,
|
||||
backgroundColor: ColorsManager.btnColor,
|
||||
child: const Text('Submit'),
|
||||
onPressed: () {
|
||||
if (forgetBloc
|
||||
.forgetFormKey.currentState!
|
||||
.validate()) {
|
||||
forgetBloc
|
||||
.add(ChangePasswordEvent());
|
||||
if (forgetBloc.forgetFormKey.currentState!.validate() ||
|
||||
forgetBloc.forgetEmailKey.currentState!
|
||||
.validate()) {
|
||||
if (forgetBloc.forgetEmailKey.currentState!
|
||||
.validate() &&
|
||||
forgetBloc.forgetFormKey.currentState!
|
||||
.validate()) {
|
||||
forgetBloc.add(ChangePasswordEvent());
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
@ -355,41 +323,37 @@ class ForgetPasswordWebPage extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10.0),
|
||||
SizedBox(
|
||||
child: Text(
|
||||
forgetBloc.validate,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: ColorsManager.red),
|
||||
Center(
|
||||
child: SizedBox(
|
||||
child: Text(
|
||||
forgetBloc.validate,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w700, color: ColorsManager.red),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
SizedBox(
|
||||
width: size.width * 0.2,
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Flexible(
|
||||
child: Text(
|
||||
"Do you have an account? ",
|
||||
style: TextStyle(color: Colors.white),
|
||||
)),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
forgetBloc.add(StopTimerEvent());
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Flexible(
|
||||
child: Text(
|
||||
"Sign in",
|
||||
)),
|
||||
),
|
||||
],
|
||||
child: Center(
|
||||
child: Wrap(
|
||||
children: [
|
||||
const Text(
|
||||
"Do you have an account? ",
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
forgetBloc.add(StopTimerEvent());
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text(
|
||||
"Sign in",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15.0),
|
||||
@ -410,4 +374,152 @@ class ForgetPasswordWebPage extends StatelessWidget {
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildDropdownField(BuildContext context, AuthBloc loginBloc, Size size) {
|
||||
final TextEditingController textEditingController = TextEditingController();
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Country/Region",
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
width: size.width * 0.9,
|
||||
child: FormField<String>(
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please select a country/region';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
builder: (FormFieldState<String> field) {
|
||||
return InputDecorator(
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 2, vertical: 10),
|
||||
errorText: field.errorText,
|
||||
filled: true, // Ensure the dropdown is filled with the background color
|
||||
fillColor: ColorsManager.boxColor, // Match the dropdown container color
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
borderSide: BorderSide(
|
||||
color: field.hasError ? Colors.red : Colors.transparent,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
borderSide: BorderSide(
|
||||
color: field.hasError ? Colors.red : ColorsManager.grayColor,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
borderSide: BorderSide(
|
||||
color: field.hasError ? Colors.red : ColorsManager.grayColor,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
borderSide: const BorderSide(
|
||||
color: Colors.red,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<String>(
|
||||
isExpanded: true,
|
||||
hint: Text(
|
||||
'Select your region/country',
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
items: loginBloc.regionList!.map((RegionModel region) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: region.id,
|
||||
child: Text(
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
region.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
value: loginBloc.regionList!.any((region) => region.id == loginBloc.regionUuid)
|
||||
? loginBloc.regionUuid
|
||||
: null,
|
||||
onChanged: (String? value) {
|
||||
if (value != null) {
|
||||
loginBloc.add(SelectRegionEvent(val: value));
|
||||
field.didChange(value); // Notify the form field of the change
|
||||
}
|
||||
},
|
||||
buttonStyleData: const ButtonStyleData(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
height: 40,
|
||||
width: double.infinity,
|
||||
),
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
maxHeight: size.height * 0.70,
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager
|
||||
.boxColor, // Match dropdown background color to the container
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
height: 40,
|
||||
),
|
||||
dropdownSearchData: DropdownSearchData(
|
||||
searchController: textEditingController,
|
||||
searchInnerWidgetHeight: 50,
|
||||
searchInnerWidget: Container(
|
||||
height: 50,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: TextFormField(
|
||||
style: const TextStyle(color: Colors.black),
|
||||
controller: textEditingController,
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
errorStyle: const TextStyle(height: 0),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 12,
|
||||
horizontal: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
searchMatchFn: (item, searchValue) {
|
||||
final regionName = (item.child as Text).data?.toLowerCase() ?? '';
|
||||
final search = searchValue.toLowerCase().trim();
|
||||
return regionName.contains(search);
|
||||
},
|
||||
),
|
||||
onMenuStateChange: (isOpen) {
|
||||
if (!isOpen) {
|
||||
textEditingController.clear();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_event.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_state.dart';
|
||||
@ -11,7 +11,7 @@ import 'package:syncrow_web/pages/auth/view/forget_password_page.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/pages/home/view/home_page.dart';
|
||||
import 'package:syncrow_web/utils/constants/routes_const.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class LoginMobilePage extends StatelessWidget {
|
||||
@ -25,10 +25,7 @@ class LoginMobilePage extends StatelessWidget {
|
||||
listener: (context, state) {
|
||||
if (state is LoginSuccess) {
|
||||
// Navigate to home screen after successful login
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => HomePage()),
|
||||
);
|
||||
context.go(RoutesConst.home, extra: {'clearHistory': true});
|
||||
} else if (state is LoginFailure) {
|
||||
// Show error message
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
@ -52,8 +49,6 @@ class LoginMobilePage extends StatelessWidget {
|
||||
|
||||
Widget _buildLoginForm(BuildContext context) {
|
||||
final loginBloc = BlocProvider.of<AuthBloc>(context);
|
||||
final TextEditingController _usernameController = TextEditingController();
|
||||
final TextEditingController _passwordController = TextEditingController();
|
||||
return Center(
|
||||
child: Stack(
|
||||
children: [
|
||||
@ -102,11 +97,8 @@ class LoginMobilePage extends StatelessWidget {
|
||||
padding: EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.1),
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(30)),
|
||||
border: Border.all(
|
||||
color:
|
||||
ColorsManager.graysColor.withOpacity(0.2))),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(30)),
|
||||
border: Border.all(color: ColorsManager.graysColor.withOpacity(0.2))),
|
||||
child: Form(
|
||||
key: loginBloc.loginFormKey,
|
||||
child: Column(
|
||||
@ -117,9 +109,7 @@ class LoginMobilePage extends StatelessWidget {
|
||||
const Text(
|
||||
'Login',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold),
|
||||
color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Column(
|
||||
@ -148,8 +138,7 @@ class LoginMobilePage extends StatelessWidget {
|
||||
),
|
||||
isDense: true,
|
||||
style: const TextStyle(color: Colors.black),
|
||||
items: loginBloc.regionList!
|
||||
.map((RegionModel region) {
|
||||
items: loginBloc.regionList!.map((RegionModel region) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: region.name,
|
||||
child: Text(region.name),
|
||||
@ -173,8 +162,8 @@ class LoginMobilePage extends StatelessWidget {
|
||||
child: TextFormField(
|
||||
validator: loginBloc.validateEmail,
|
||||
controller: loginBloc.loginEmailController,
|
||||
decoration: textBoxDecoration()!
|
||||
.copyWith(hintText: 'Enter your email'),
|
||||
decoration:
|
||||
textBoxDecoration()!.copyWith(hintText: 'Enter your email'),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
@ -194,8 +183,7 @@ class LoginMobilePage extends StatelessWidget {
|
||||
validator: loginBloc.validatePassword,
|
||||
obscureText: loginBloc.obscureText,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller:
|
||||
loginBloc.loginPasswordController,
|
||||
controller: loginBloc.loginPasswordController,
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
hintText: 'At least 8 characters',
|
||||
),
|
||||
@ -213,16 +201,13 @@ class LoginMobilePage extends StatelessWidget {
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
const ForgetPasswordPage(),
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) => const ForgetPasswordPage(),
|
||||
));
|
||||
},
|
||||
child: Text(
|
||||
"Forgot Password?",
|
||||
style:
|
||||
Theme.of(context).textTheme.bodySmall,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -233,15 +218,13 @@ class LoginMobilePage extends StatelessWidget {
|
||||
Transform.scale(
|
||||
scale: 1.2, // Adjust the scale as needed
|
||||
child: Checkbox(
|
||||
fillColor: MaterialStateProperty.all<Color>(
|
||||
Colors.white),
|
||||
fillColor: MaterialStateProperty.all<Color>(Colors.white),
|
||||
activeColor: Colors.white,
|
||||
value: loginBloc.isChecked,
|
||||
checkColor: Colors.black,
|
||||
shape: const CircleBorder(),
|
||||
onChanged: (bool? newValue) {
|
||||
loginBloc.add(
|
||||
CheckBoxEvent(newValue: newValue));
|
||||
loginBloc.add(CheckBoxEvent(newValue: newValue));
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -250,37 +233,30 @@ class LoginMobilePage extends StatelessWidget {
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: 'Agree to ',
|
||||
style:
|
||||
const TextStyle(color: Colors.white),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '(Terms of Service)',
|
||||
style: const TextStyle(
|
||||
color: Colors.black),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
loginBloc.launchURL(
|
||||
'https://example.com/terms');
|
||||
loginBloc.launchURL('https://example.com/terms');
|
||||
},
|
||||
),
|
||||
TextSpan(
|
||||
text: ' (Legal Statement)',
|
||||
style: const TextStyle(
|
||||
color: Colors.black),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
loginBloc.launchURL(
|
||||
'https://example.com/legal');
|
||||
loginBloc.launchURL('https://example.com/legal');
|
||||
},
|
||||
),
|
||||
TextSpan(
|
||||
text: ' (Privacy Statement)',
|
||||
style: const TextStyle(
|
||||
color: Colors.black),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
loginBloc.launchURL(
|
||||
'https://example.com/privacy');
|
||||
loginBloc.launchURL('https://example.com/privacy');
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -297,15 +273,12 @@ class LoginMobilePage extends StatelessWidget {
|
||||
: ColorsManager.grayColor,
|
||||
child: const Text('Sign in'),
|
||||
onPressed: () {
|
||||
if (loginBloc.loginFormKey.currentState!
|
||||
.validate()) {
|
||||
if (loginBloc.loginFormKey.currentState!.validate()) {
|
||||
loginBloc.add(
|
||||
LoginButtonPressed(
|
||||
regionUuid: '',
|
||||
username:
|
||||
loginBloc.loginEmailController.text,
|
||||
password: loginBloc
|
||||
.loginPasswordController.text,
|
||||
username: loginBloc.loginEmailController.text,
|
||||
password: loginBloc.loginPasswordController.text,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -320,8 +293,7 @@ class LoginMobilePage extends StatelessWidget {
|
||||
Flexible(
|
||||
child: Text(
|
||||
"Don't you have an account? ",
|
||||
style: TextStyle(
|
||||
color: Colors.white, fontSize: 13),
|
||||
style: TextStyle(color: Colors.white, fontSize: 13),
|
||||
)),
|
||||
Text(
|
||||
"Sign up",
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@ -13,6 +14,7 @@ import 'package:syncrow_web/pages/common/first_layer.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/constants/routes_const.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class LoginWebPage extends StatefulWidget {
|
||||
@ -22,7 +24,7 @@ class LoginWebPage extends StatefulWidget {
|
||||
State<LoginWebPage> createState() => _LoginWebPageState();
|
||||
}
|
||||
|
||||
class _LoginWebPageState extends State<LoginWebPage> {
|
||||
class _LoginWebPageState extends State<LoginWebPage> with HelperResponsiveLayout {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -31,7 +33,7 @@ class _LoginWebPageState extends State<LoginWebPage> {
|
||||
child: BlocConsumer<AuthBloc, AuthState>(
|
||||
listener: (context, state) {
|
||||
if (state is LoginSuccess) {
|
||||
context.go(RoutesConst.home);
|
||||
GoRouter.of(context).go(RoutesConst.home);
|
||||
} else if (state is LoginFailure) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
@ -50,9 +52,12 @@ class _LoginWebPageState extends State<LoginWebPage> {
|
||||
|
||||
Widget _buildLoginForm(BuildContext context, AuthState state) {
|
||||
final loginBloc = BlocProvider.of<AuthBloc>(context);
|
||||
final isSmallScreen = isSmallScreenSize(context);
|
||||
final isMediumScreen = isMediumScreenSize(context);
|
||||
Size size = MediaQuery.of(context).size;
|
||||
late ScrollController _scrollController;
|
||||
_scrollController = ScrollController();
|
||||
|
||||
void _scrollToCenter() {
|
||||
final double middlePosition = _scrollController.position.maxScrollExtent / 2;
|
||||
_scrollController.animateTo(
|
||||
@ -65,6 +70,7 @@ class _LoginWebPageState extends State<LoginWebPage> {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollToCenter();
|
||||
});
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
FirstLayer(
|
||||
@ -74,356 +80,52 @@ class _LoginWebPageState extends State<LoginWebPage> {
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
Container(
|
||||
width: 400,
|
||||
padding: EdgeInsets.all(size.width * 0.02),
|
||||
margin: EdgeInsets.all(size.width * 0.09),
|
||||
margin: EdgeInsets.all(size.width * 0.05),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
child: Center(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Spacer(),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: SvgPicture.asset(
|
||||
Assets.loginLogo,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.1),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(30)),
|
||||
border: Border.all(
|
||||
color: ColorsManager.graysColor.withOpacity(0.2))),
|
||||
child: Form(
|
||||
key: loginBloc.loginFormKey,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: size.width * 0.02,
|
||||
vertical: size.width * 0.003),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 40),
|
||||
Text('Login',
|
||||
style: Theme.of(context).textTheme.headlineLarge),
|
||||
SizedBox(height: size.height * 0.03),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Country/Region",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
fontSize: 14, fontWeight: FontWeight.w400),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
SizedBox(
|
||||
child: DropdownButtonFormField<String>(
|
||||
padding: EdgeInsets.zero,
|
||||
value: loginBloc.regionList!.any((region) =>
|
||||
region.id == loginBloc.regionUuid)
|
||||
? loginBloc.regionUuid
|
||||
: null,
|
||||
validator: loginBloc.validateRegion,
|
||||
icon: const Icon(
|
||||
Icons.keyboard_arrow_down_outlined,
|
||||
),
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
errorStyle: const TextStyle(height: 0),
|
||||
hintText: null,
|
||||
),
|
||||
hint: SizedBox(
|
||||
width: size.width * 0.12,
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Select your region/country',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontWeight: FontWeight.w400),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
isDense: true,
|
||||
style: const TextStyle(color: Colors.black),
|
||||
items:
|
||||
loginBloc.regionList!.map((RegionModel region) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: region.id,
|
||||
child: SizedBox(
|
||||
width: size.width * 0.08,
|
||||
child: Text(region.name)),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (String? value) {
|
||||
loginBloc.add(CheckEnableEvent());
|
||||
loginBloc.add(SelectRegionEvent(val: value!));
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Email",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
fontSize: 14, fontWeight: FontWeight.w400),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
onChanged: (value) {
|
||||
loginBloc.add(CheckEnableEvent());
|
||||
// print(loginBloc.checkEnable());
|
||||
},
|
||||
validator: loginBloc.loginValidateEmail,
|
||||
controller: loginBloc.loginEmailController,
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
errorStyle: const TextStyle(
|
||||
height: 0), // Hide the error text space
|
||||
hintText: 'Enter your email address',
|
||||
hintStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontWeight: FontWeight.w400)),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Password",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
fontSize: 14, fontWeight: FontWeight.w400),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
onChanged: (value) {
|
||||
loginBloc.add(CheckEnableEvent());
|
||||
},
|
||||
validator: loginBloc.validatePassword,
|
||||
obscureText: loginBloc.obscureText,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller: loginBloc.loginPasswordController,
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
hintText: 'At least 8 characters',
|
||||
hintStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontWeight: FontWeight.w400),
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () {
|
||||
loginBloc.add(PasswordVisibleEvent(
|
||||
newValue: loginBloc.obscureText));
|
||||
},
|
||||
icon: SizedBox(
|
||||
child: SvgPicture.asset(
|
||||
loginBloc.obscureText
|
||||
? Assets.visiblePassword
|
||||
: Assets.invisiblePassword,
|
||||
height: 15,
|
||||
width: 15,
|
||||
),
|
||||
),
|
||||
),
|
||||
errorStyle: const TextStyle(
|
||||
height: 0), // Hide the error text space
|
||||
),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
SizedBox(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
const ForgetPasswordPage(),
|
||||
));
|
||||
},
|
||||
child: Text(
|
||||
"Forgot Password?",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
color: Colors.black,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Transform.scale(
|
||||
scale: 1.2, // Adjust the scale as needed
|
||||
child: Checkbox(
|
||||
fillColor:
|
||||
MaterialStateProperty.all<Color>(Colors.white),
|
||||
activeColor: Colors.white,
|
||||
value: loginBloc.isChecked,
|
||||
checkColor: Colors.black,
|
||||
shape: const CircleBorder(),
|
||||
onChanged: (bool? newValue) {
|
||||
loginBloc.add(CheckBoxEvent(newValue: newValue));
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: size.width * 0.14,
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: 'Agree to ',
|
||||
style: const TextStyle(color: Colors.white),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '(Terms of Service)',
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
loginBloc.launchURL(
|
||||
'https://example.com/terms');
|
||||
},
|
||||
),
|
||||
TextSpan(
|
||||
text: ' (Legal Statement)',
|
||||
style: const TextStyle(color: Colors.black),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
loginBloc.launchURL(
|
||||
'https://example.com/legal');
|
||||
},
|
||||
),
|
||||
TextSpan(
|
||||
text: ' (Privacy Statement)',
|
||||
style: const TextStyle(color: Colors.black),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
loginBloc.launchURL(
|
||||
'https://example.com/privacy');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: size.width * 0.2,
|
||||
child: DefaultButton(
|
||||
enabled: loginBloc.checkValidate,
|
||||
child: Text('Sign in',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge!
|
||||
.copyWith(
|
||||
fontSize: 14,
|
||||
color: loginBloc.checkValidate
|
||||
? ColorsManager.whiteColors
|
||||
: ColorsManager.whiteColors
|
||||
.withOpacity(0.2),
|
||||
)),
|
||||
onPressed: () {
|
||||
if (loginBloc.loginFormKey.currentState!
|
||||
.validate()) {
|
||||
loginBloc.add(LoginButtonPressed(
|
||||
regionUuid: loginBloc.regionUuid,
|
||||
username: loginBloc.loginEmailController.text,
|
||||
password:
|
||||
loginBloc.loginPasswordController.text,
|
||||
));
|
||||
} else {
|
||||
loginBloc.add(ChangeValidateEvent());
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 15.0),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
child: Text(
|
||||
loginBloc.validate,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: ColorsManager.red),
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
child: isSmallScreen || isMediumScreen
|
||||
? SizedBox(
|
||||
width: 400,
|
||||
child: Column(
|
||||
// For small screens
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 300,
|
||||
child: SvgPicture.asset(
|
||||
Assets.loginLogo,
|
||||
),
|
||||
))),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
_buildLoginFormFields(context, loginBloc, size),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
// For larger screens
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Spacer(),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: SvgPicture.asset(
|
||||
Assets.loginLogo,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: _buildLoginFormFields(context, loginBloc, size),
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -434,4 +136,358 @@ class _LoginWebPageState extends State<LoginWebPage> {
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoginFormFields(BuildContext context, AuthBloc loginBloc, Size size) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.1),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(30)),
|
||||
border: Border.all(color: ColorsManager.graysColor.withOpacity(0.2)),
|
||||
),
|
||||
child: Form(
|
||||
key: loginBloc.loginFormKey,
|
||||
child: Padding(
|
||||
padding:
|
||||
EdgeInsets.symmetric(horizontal: size.width * 0.02, vertical: size.width * 0.003),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 40),
|
||||
Text('Login', style: Theme.of(context).textTheme.headlineLarge),
|
||||
SizedBox(height: size.height * 0.03),
|
||||
_buildDropdownField(context, loginBloc, size),
|
||||
const SizedBox(height: 20.0),
|
||||
_buildEmailField(context, loginBloc),
|
||||
const SizedBox(height: 20.0),
|
||||
_buildPasswordField(context, loginBloc),
|
||||
const SizedBox(height: 20),
|
||||
_buildForgotPassword(context),
|
||||
const SizedBox(height: 20),
|
||||
_buildCheckbox(context, loginBloc, size),
|
||||
const SizedBox(height: 20.0),
|
||||
_buildSignInButton(context, loginBloc, size),
|
||||
const SizedBox(height: 15.0),
|
||||
_buildValidationMessage(loginBloc),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDropdownField(BuildContext context, AuthBloc loginBloc, Size size) {
|
||||
final TextEditingController textEditingController = TextEditingController();
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Country/Region",
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Container(
|
||||
height: 50,
|
||||
decoration: const BoxDecoration(
|
||||
color: ColorsManager.boxColor,
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
width: size.width * 0.9,
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<String>(
|
||||
style: TextStyle(color: Colors.black),
|
||||
isExpanded: true,
|
||||
hint: Text(
|
||||
'Select your region/country',
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
items: loginBloc.regionList!.map((RegionModel region) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: region.id, // Use region.id as the value
|
||||
child: Text(
|
||||
region.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
value: loginBloc.regionList!.any(
|
||||
(region) => region.id == loginBloc.regionUuid,
|
||||
)
|
||||
? loginBloc.regionUuid
|
||||
: null,
|
||||
onChanged: (String? value) {
|
||||
if (value != null) {
|
||||
loginBloc.add(CheckEnableEvent());
|
||||
loginBloc.add(SelectRegionEvent(val: value));
|
||||
}
|
||||
},
|
||||
buttonStyleData: const ButtonStyleData(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
height: 40,
|
||||
width: double.infinity,
|
||||
),
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
maxHeight: size.height * 0.70,
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
height: 40,
|
||||
),
|
||||
dropdownSearchData: DropdownSearchData(
|
||||
searchController: textEditingController,
|
||||
searchInnerWidgetHeight: 50,
|
||||
searchInnerWidget: Container(
|
||||
height: 50,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: TextFormField(
|
||||
style: const TextStyle(color: Colors.black),
|
||||
controller: textEditingController,
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
errorStyle: const TextStyle(height: 0),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 12,
|
||||
horizontal: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
searchMatchFn: (item, searchValue) {
|
||||
// Use the item's child text (region name) for searching.
|
||||
final regionName = (item.child as Text).data?.toLowerCase() ?? '';
|
||||
final search = searchValue.toLowerCase().trim();
|
||||
// Debugging print statement to ensure values are captured correctly.
|
||||
// Return true if the region name contains the search term.
|
||||
return regionName.contains(search);
|
||||
},
|
||||
),
|
||||
onMenuStateChange: (isOpen) {
|
||||
if (!isOpen) {
|
||||
textEditingController.clear();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmailField(BuildContext context, AuthBloc loginBloc) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Email",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(fontSize: 14, fontWeight: FontWeight.w400),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
onChanged: (value) {
|
||||
loginBloc.add(CheckEnableEvent());
|
||||
},
|
||||
validator: loginBloc.loginValidateEmail,
|
||||
controller: loginBloc.loginEmailController,
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
errorStyle: const TextStyle(height: 0),
|
||||
hintText: 'Enter your email address',
|
||||
hintStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: ColorsManager.grayColor, fontWeight: FontWeight.w400)),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPasswordField(BuildContext context, AuthBloc loginBloc) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Password",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(fontSize: 14, fontWeight: FontWeight.w400),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
SizedBox(
|
||||
child: TextFormField(
|
||||
onChanged: (value) {
|
||||
loginBloc.add(CheckEnableEvent());
|
||||
},
|
||||
validator: loginBloc.validatePassword,
|
||||
obscureText: loginBloc.obscureText,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller: loginBloc.loginPasswordController,
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
hintText: 'At least 8 characters',
|
||||
hintStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: ColorsManager.grayColor, fontWeight: FontWeight.w400),
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () {
|
||||
loginBloc.add(PasswordVisibleEvent(newValue: loginBloc.obscureText));
|
||||
},
|
||||
icon: SizedBox(
|
||||
child: SvgPicture.asset(
|
||||
loginBloc.obscureText ? Assets.visiblePassword : Assets.invisiblePassword,
|
||||
height: 15,
|
||||
width: 15,
|
||||
),
|
||||
),
|
||||
),
|
||||
errorStyle: const TextStyle(height: 0),
|
||||
),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildForgotPassword(BuildContext context) {
|
||||
return SizedBox(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) => const ForgetPasswordPage(),
|
||||
));
|
||||
},
|
||||
child: Text(
|
||||
"Forgot Password?",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: Colors.black, fontSize: 14, fontWeight: FontWeight.w400),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCheckbox(BuildContext context, AuthBloc loginBloc, Size size) {
|
||||
return Row(
|
||||
children: [
|
||||
Transform.scale(
|
||||
scale: 1.2,
|
||||
child: Checkbox(
|
||||
fillColor: MaterialStateProperty.all<Color>(Colors.white),
|
||||
activeColor: Colors.white,
|
||||
value: loginBloc.isChecked,
|
||||
checkColor: Colors.black,
|
||||
shape: const CircleBorder(),
|
||||
onChanged: (bool? newValue) {
|
||||
loginBloc.add(CheckBoxEvent(newValue: newValue));
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: 'Agree to ',
|
||||
style: const TextStyle(color: Colors.white),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '(Terms of Service)',
|
||||
style: const TextStyle(color: Colors.black),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
loginBloc.launchURL('https://example.com/terms');
|
||||
},
|
||||
),
|
||||
TextSpan(
|
||||
text: ' (Legal Statement)',
|
||||
style: const TextStyle(color: Colors.black),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
loginBloc.launchURL('https://example.com/legal');
|
||||
},
|
||||
),
|
||||
TextSpan(
|
||||
text: ' (Privacy Statement)',
|
||||
style: const TextStyle(color: Colors.black),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
loginBloc.launchURL('https://example.com/privacy');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSignInButton(BuildContext context, AuthBloc loginBloc, Size size) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: size.width * 0.2,
|
||||
child: DefaultButton(
|
||||
enabled: loginBloc.checkValidate,
|
||||
child: Text('Sign in',
|
||||
style: Theme.of(context).textTheme.labelLarge!.copyWith(
|
||||
fontSize: 14,
|
||||
color: loginBloc.checkValidate
|
||||
? ColorsManager.whiteColors
|
||||
: ColorsManager.whiteColors.withOpacity(0.2),
|
||||
)),
|
||||
onPressed: () {
|
||||
if (loginBloc.loginFormKey.currentState!.validate()) {
|
||||
loginBloc.add(LoginButtonPressed(
|
||||
regionUuid: loginBloc.regionUuid,
|
||||
username: loginBloc.loginEmailController.text,
|
||||
password: loginBloc.loginPasswordController.text,
|
||||
));
|
||||
} else {
|
||||
loginBloc.add(ChangeValidateEvent());
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildValidationMessage(AuthBloc loginBloc) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
child: Text(
|
||||
loginBloc.validate,
|
||||
style: const TextStyle(fontWeight: FontWeight.w700, color: ColorsManager.red),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
304
lib/pages/common/access_device_table.dart
Normal file
304
lib/pages/common/access_device_table.dart
Normal file
@ -0,0 +1,304 @@
|
||||
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';
|
||||
|
||||
class AccessDeviceTable extends StatefulWidget {
|
||||
final List<String> headers;
|
||||
final String? tableName;
|
||||
final List<List<dynamic>> data;
|
||||
final BoxDecoration? headerDecoration;
|
||||
final BoxDecoration? cellDecoration;
|
||||
final Size size;
|
||||
final bool withCheckBox;
|
||||
final bool withSelectAll;
|
||||
final bool isEmpty;
|
||||
final void Function(bool?)? selectAll;
|
||||
final void Function(int, bool, dynamic)? onRowSelected;
|
||||
final List<String>? initialSelectedIds;
|
||||
final int uuidIndex;
|
||||
const AccessDeviceTable({
|
||||
super.key,
|
||||
required this.headers,
|
||||
required this.data,
|
||||
required this.size,
|
||||
this.tableName,
|
||||
required this.isEmpty,
|
||||
required this.withCheckBox,
|
||||
required this.withSelectAll,
|
||||
this.headerDecoration,
|
||||
this.cellDecoration,
|
||||
this.selectAll,
|
||||
this.onRowSelected,
|
||||
this.initialSelectedIds,
|
||||
required this.uuidIndex,
|
||||
});
|
||||
|
||||
@override
|
||||
_DynamicTableState createState() => _DynamicTableState();
|
||||
}
|
||||
|
||||
class _DynamicTableState extends State<AccessDeviceTable> {
|
||||
late List<bool> _selected;
|
||||
bool _selectAll = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeSelection();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(AccessDeviceTable oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.data != widget.data) {
|
||||
_initializeSelection();
|
||||
}
|
||||
}
|
||||
|
||||
void _initializeSelection() {
|
||||
if (widget.data.isEmpty) {
|
||||
_selected = [];
|
||||
_selectAll = false;
|
||||
} else {
|
||||
_selected = List<bool>.generate(widget.data.length, (index) {
|
||||
// Check if the initialSelectedIds contains the deviceUuid
|
||||
// uuidIndex is the index of the column containing the deviceUuid
|
||||
final deviceUuid = widget.data[index][widget.uuidIndex];
|
||||
return widget.initialSelectedIds != null &&
|
||||
widget.initialSelectedIds!.contains(deviceUuid);
|
||||
});
|
||||
_selectAll = _selected.every((element) => element == true);
|
||||
}
|
||||
}
|
||||
|
||||
void _toggleRowSelection(int index) {
|
||||
setState(() {
|
||||
_selected[index] = !_selected[index];
|
||||
|
||||
if (widget.onRowSelected != null) {
|
||||
widget.onRowSelected!(index, _selected[index], widget.data[index]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _toggleSelectAll(bool? value) {
|
||||
setState(() {
|
||||
_selectAll = value ?? false;
|
||||
_selected = List<bool>.filled(widget.data.length, _selectAll);
|
||||
if (widget.selectAll != null) {
|
||||
widget.selectAll!(_selectAll);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: widget.cellDecoration,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: SizedBox(
|
||||
width: widget.size.width,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
decoration: widget.headerDecoration ??
|
||||
BoxDecoration(color: Colors.grey[200]),
|
||||
child: Row(
|
||||
children: [
|
||||
if (widget.withCheckBox) _buildSelectAllCheckbox(),
|
||||
...widget.headers
|
||||
.map((header) => _buildTableHeaderCell(header)),
|
||||
],
|
||||
),
|
||||
),
|
||||
widget.isEmpty
|
||||
? Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
SvgPicture.asset(Assets.emptyTable),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
Text(
|
||||
// no password
|
||||
widget.tableName == 'AccessManagement'
|
||||
? 'No Password '
|
||||
: 'No Devices',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
color: ColorsManager.grayColor),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Expanded(
|
||||
child: Container(
|
||||
color: Colors.white,
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: widget.data.length,
|
||||
itemBuilder: (context, index) {
|
||||
final row = widget.data[index];
|
||||
return Row(
|
||||
children: [
|
||||
if (widget.withCheckBox)
|
||||
_buildRowCheckbox(
|
||||
index, widget.size.height * 0.10),
|
||||
...row.map((cell) => _buildTableCell(
|
||||
cell.toString(),
|
||||
widget.size.height * 0.10)),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSelectAllCheckbox() {
|
||||
return Container(
|
||||
width: 50,
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(color: ColorsManager.boxDivider),
|
||||
),
|
||||
),
|
||||
child: Checkbox(
|
||||
value: widget.data.isNotEmpty &&
|
||||
_selected.every((element) => element == true),
|
||||
onChanged: widget.withSelectAll && widget.data.isNotEmpty
|
||||
? _toggleSelectAll
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRowCheckbox(int index, double size) {
|
||||
return Container(
|
||||
width: 50,
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
height: size,
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: ColorsManager.boxDivider,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Center(
|
||||
child: Checkbox(
|
||||
value: _selected[index],
|
||||
onChanged: (bool? value) {
|
||||
_toggleRowSelection(index);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTableHeaderCell(String title) {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(color: ColorsManager.boxDivider),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 13,
|
||||
color: Color(0xFF999999),
|
||||
),
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTableCell(String content, double size) {
|
||||
bool isBatteryLevel = content.endsWith('%');
|
||||
double? batteryLevel;
|
||||
|
||||
if (isBatteryLevel) {
|
||||
batteryLevel = double.tryParse(content.replaceAll('%', '').trim());
|
||||
}
|
||||
|
||||
Color? statusColor;
|
||||
switch (content) {
|
||||
case 'Effective':
|
||||
statusColor = ColorsManager.textGreen;
|
||||
break;
|
||||
case 'Expired':
|
||||
statusColor = ColorsManager.red;
|
||||
break;
|
||||
case 'To be effective':
|
||||
statusColor = ColorsManager.yaGreen;
|
||||
break;
|
||||
case 'Online':
|
||||
statusColor = ColorsManager.green;
|
||||
break;
|
||||
case 'Offline':
|
||||
statusColor = ColorsManager.red;
|
||||
break;
|
||||
default:
|
||||
statusColor = Colors.black;
|
||||
}
|
||||
|
||||
return Expanded(
|
||||
child: Container(
|
||||
height: size,
|
||||
padding: const EdgeInsets.all(5.0),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: ColorsManager.boxDivider,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
content,
|
||||
style: TextStyle(
|
||||
color: (batteryLevel != null && batteryLevel < 20)
|
||||
? ColorsManager.red
|
||||
: (batteryLevel != null && batteryLevel > 20)
|
||||
? ColorsManager.green
|
||||
: statusColor,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w400),
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -16,8 +16,9 @@ class DefaultButton extends StatelessWidget {
|
||||
this.foregroundColor,
|
||||
this.borderRadius,
|
||||
this.height = 40,
|
||||
this.width = 140,
|
||||
this.padding,
|
||||
this.borderColor,
|
||||
this.elevation,
|
||||
});
|
||||
final void Function()? onPressed;
|
||||
final Widget child;
|
||||
@ -32,7 +33,8 @@ class DefaultButton extends StatelessWidget {
|
||||
final ButtonStyle? customButtonStyle;
|
||||
final Color? backgroundColor;
|
||||
final Color? foregroundColor;
|
||||
final double? width;
|
||||
final Color? borderColor;
|
||||
final double? elevation;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -42,38 +44,42 @@ class DefaultButton extends StatelessWidget {
|
||||
? null
|
||||
: customButtonStyle ??
|
||||
ButtonStyle(
|
||||
fixedSize: WidgetStateProperty.all(Size(width ?? 50, height ?? 40)), // Set button height
|
||||
textStyle: MaterialStateProperty.all(
|
||||
textStyle: WidgetStateProperty.all(
|
||||
customTextStyle ??
|
||||
Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontSize: 13,
|
||||
color: foregroundColor,
|
||||
fontWeight: FontWeight.normal),
|
||||
),
|
||||
foregroundColor: MaterialStateProperty.all(
|
||||
foregroundColor: WidgetStateProperty.all(
|
||||
isSecondary
|
||||
? Colors.black
|
||||
: enabled
|
||||
? foregroundColor ?? Colors.white
|
||||
: Colors.black,
|
||||
),
|
||||
backgroundColor: MaterialStateProperty.resolveWith<Color>(
|
||||
(Set<MaterialState> states) {
|
||||
backgroundColor: WidgetStateProperty.resolveWith<Color>(
|
||||
(Set<WidgetState> states) {
|
||||
return enabled
|
||||
? backgroundColor ?? ColorsManager.primaryColor
|
||||
: Colors.black.withOpacity(0.2);
|
||||
}),
|
||||
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
|
||||
shape: WidgetStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(borderRadius ?? 10),
|
||||
side: BorderSide(color: borderColor ?? Colors.transparent),
|
||||
borderRadius: BorderRadius.circular(borderRadius ?? 20),
|
||||
),
|
||||
),
|
||||
padding: MaterialStateProperty.all(
|
||||
EdgeInsets.all(padding ?? 10),
|
||||
),
|
||||
minimumSize: MaterialStateProperty.all(
|
||||
fixedSize: WidgetStateProperty.all(
|
||||
const Size.fromHeight(50),
|
||||
),
|
||||
padding: WidgetStateProperty.all(
|
||||
EdgeInsets.all(padding ?? 10),
|
||||
),
|
||||
minimumSize: WidgetStateProperty.all(
|
||||
const Size.fromHeight(50),
|
||||
),
|
||||
elevation: WidgetStateProperty.all(elevation ?? 0),
|
||||
),
|
||||
child: SizedBox(
|
||||
height: height ?? 50,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
@ -23,14 +24,18 @@ class SearchResetButtons extends StatelessWidget {
|
||||
const SizedBox(height: 25),
|
||||
Center(
|
||||
child: Container(
|
||||
height: 43,
|
||||
height: 42,
|
||||
width: 100,
|
||||
decoration: containerDecoration,
|
||||
child: Center(
|
||||
child: DefaultButton(
|
||||
onPressed: onSearch,
|
||||
borderRadius: 9,
|
||||
child: const Text('Search'),
|
||||
child: Text(
|
||||
'Search',
|
||||
style: context.textTheme.titleSmall!
|
||||
.copyWith(color: Colors.white, fontSize: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -44,21 +49,19 @@ class SearchResetButtons extends StatelessWidget {
|
||||
const SizedBox(height: 25),
|
||||
Center(
|
||||
child: Container(
|
||||
height: 43,
|
||||
height: 42,
|
||||
width: 100,
|
||||
decoration: containerDecoration,
|
||||
child: Center(
|
||||
child: DefaultButton(
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
borderRadius: 9,
|
||||
onPressed: onReset,
|
||||
child: Text(
|
||||
'Reset',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: Colors.black),
|
||||
style: context.textTheme.titleSmall!
|
||||
.copyWith(color: Colors.black, fontSize: 12),
|
||||
),
|
||||
onPressed: onReset,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
73
lib/pages/common/curtain_toggle.dart
Normal file
73
lib/pages/common/curtain_toggle.dart
Normal file
@ -0,0 +1,73 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_svg/flutter_svg.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';
|
||||
|
||||
class CurtainToggle extends StatelessWidget {
|
||||
final bool value;
|
||||
final String code;
|
||||
final String deviceId;
|
||||
final String label;
|
||||
final Null Function(dynamic value) onChanged;
|
||||
|
||||
const CurtainToggle({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.code,
|
||||
required this.deviceId,
|
||||
required this.label,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DeviceControlsContainer(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClipOval(
|
||||
child: Container(
|
||||
color: ColorsManager.whiteColors,
|
||||
child: SvgPicture.asset(
|
||||
Assets.curtainIcon,
|
||||
width: 60,
|
||||
height: 60,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
width: 35,
|
||||
child: CupertinoSwitch(
|
||||
value: value,
|
||||
activeColor: ColorsManager.dialogBlueTitle,
|
||||
onChanged: onChanged,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
Future<void> showCustomDialog({
|
||||
required BuildContext context,
|
||||
@ -13,7 +12,7 @@ Future<void> showCustomDialog({
|
||||
double? iconWidth,
|
||||
VoidCallback? onOkPressed,
|
||||
bool barrierDismissible = false,
|
||||
required actions,
|
||||
required List<Widget> actions,
|
||||
}) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
@ -21,59 +20,43 @@ Future<void> showCustomDialog({
|
||||
builder: (BuildContext context) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
return AlertDialog(
|
||||
alignment: Alignment.center,
|
||||
content: SizedBox(
|
||||
height: dialogHeight ?? size.height * 0.15,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (iconPath != null)
|
||||
SvgPicture.asset(
|
||||
iconPath,
|
||||
height: iconHeight ?? 35,
|
||||
width: iconWidth ?? 35,
|
||||
),
|
||||
if (title != null)
|
||||
alignment: Alignment.center,
|
||||
content: SizedBox(
|
||||
height: dialogHeight ?? size.height * 0.15,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (iconPath != null)
|
||||
SvgPicture.asset(
|
||||
iconPath,
|
||||
height: iconHeight ?? 35,
|
||||
width: iconWidth ?? 35,
|
||||
),
|
||||
if (title != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
title,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineLarge!
|
||||
.copyWith(fontSize: 20, fontWeight: FontWeight.w400, color: Colors.black),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.headlineLarge!.copyWith(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Colors.black),
|
||||
message,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: Colors.black),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
message,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(color: Colors.black),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
if(widget!=null)
|
||||
Expanded(child:widget)
|
||||
],
|
||||
),
|
||||
),
|
||||
actionsAlignment: MainAxisAlignment.center,
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: onOkPressed ?? () => Navigator.of(context).pop(),
|
||||
child: Text(
|
||||
'OK',
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize: 16),
|
||||
if (widget != null) Expanded(child: widget)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
actionsAlignment: MainAxisAlignment.center,
|
||||
actions: actions);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,31 +1,42 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.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 DynamicTable extends StatefulWidget {
|
||||
final List<String> headers;
|
||||
final String? tableName;
|
||||
final List<List<dynamic>> data;
|
||||
final BoxDecoration? headerDecoration;
|
||||
final BoxDecoration? cellDecoration;
|
||||
final Size size;
|
||||
final bool withCheckBox;
|
||||
final bool withSelectAll;
|
||||
final bool isEmpty;
|
||||
final void Function(bool?)? selectAll;
|
||||
final void Function(int, bool, dynamic)? onRowSelected;
|
||||
final List<String>? initialSelectedIds;
|
||||
final int uuidIndex;
|
||||
final Function(dynamic selectedRows)? onSelectionChanged;
|
||||
const DynamicTable({
|
||||
super.key,
|
||||
required this.headers,
|
||||
required this.data,
|
||||
required this.size,
|
||||
this.tableName,
|
||||
required this.isEmpty,
|
||||
required this.withCheckBox,
|
||||
required this.withSelectAll,
|
||||
this.headerDecoration,
|
||||
this.cellDecoration,
|
||||
this.selectAll,
|
||||
this.onRowSelected,
|
||||
this.initialSelectedIds,
|
||||
required this.uuidIndex,
|
||||
this.onSelectionChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -33,98 +44,142 @@ class DynamicTable extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DynamicTableState extends State<DynamicTable> {
|
||||
late List<bool> _selected;
|
||||
late List<bool> _selectedRows;
|
||||
bool _selectAll = false;
|
||||
final ScrollController _verticalScrollController = ScrollController();
|
||||
final ScrollController _horizontalScrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selected = List<bool>.generate(widget.data.length, (index) {
|
||||
return widget.initialSelectedIds != null &&
|
||||
widget.initialSelectedIds!.contains(widget.data[index][1]);
|
||||
});
|
||||
_initializeSelection();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(DynamicTable oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (!_compareListOfLists(oldWidget.data, widget.data)) {
|
||||
_initializeSelection();
|
||||
}
|
||||
}
|
||||
|
||||
bool _compareListOfLists(List<List<dynamic>> oldList, List<List<dynamic>> newList) {
|
||||
// Check if the old and new lists are the same
|
||||
if (oldList.length != newList.length) return false;
|
||||
|
||||
for (int i = 0; i < oldList.length; i++) {
|
||||
if (oldList[i].length != newList[i].length) return false;
|
||||
|
||||
for (int j = 0; j < oldList[i].length; j++) {
|
||||
if (oldList[i][j] != newList[i][j]) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void _initializeSelection() {
|
||||
_selectedRows = List<bool>.filled(widget.data.length, false);
|
||||
_selectAll = false;
|
||||
}
|
||||
|
||||
void _toggleRowSelection(int index) {
|
||||
setState(() {
|
||||
_selected[index] = !_selected[index];
|
||||
|
||||
if (widget.onRowSelected != null) {
|
||||
widget.onRowSelected!(index, _selected[index], widget.data[index]);
|
||||
}
|
||||
_selectedRows[index] = !_selectedRows[index];
|
||||
_selectAll = _selectedRows.every((isSelected) => isSelected);
|
||||
});
|
||||
widget.onSelectionChanged?.call(_selectedRows);
|
||||
context.read<DeviceManagementBloc>().add(UpdateSelection(_selectedRows));
|
||||
}
|
||||
|
||||
void _toggleSelectAll(bool? value) {
|
||||
setState(() {
|
||||
_selectAll = value ?? false;
|
||||
_selectedRows = List<bool>.filled(widget.data.length, _selectAll);
|
||||
});
|
||||
widget.onSelectionChanged?.call(_selectedRows);
|
||||
context.read<DeviceManagementBloc>().add(UpdateSelection(_selectedRows));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: widget.cellDecoration,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: SizedBox(
|
||||
width: widget.size.width,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
decoration: widget.headerDecoration ?? BoxDecoration(color: Colors.grey[200]),
|
||||
child: Row(
|
||||
child: Scrollbar(
|
||||
controller: _verticalScrollController,
|
||||
thumbVisibility: true,
|
||||
trackVisibility: true,
|
||||
child: Scrollbar(
|
||||
controller: _horizontalScrollController,
|
||||
thumbVisibility: false,
|
||||
trackVisibility: false,
|
||||
notificationPredicate: (notif) => notif.depth == 1,
|
||||
child: SingleChildScrollView(
|
||||
controller: _verticalScrollController,
|
||||
child: SingleChildScrollView(
|
||||
controller: _horizontalScrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: SizedBox(
|
||||
width: widget.size.width,
|
||||
child: Column(
|
||||
children: [
|
||||
if (widget.withCheckBox) _buildSelectAllCheckbox(),
|
||||
...widget.headers.map((header) => _buildTableHeaderCell(header)).toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
widget.isEmpty
|
||||
? Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
Container(
|
||||
decoration: widget.headerDecoration ??
|
||||
const BoxDecoration(
|
||||
color: ColorsManager.boxColor,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
if (widget.withCheckBox) _buildSelectAllCheckbox(),
|
||||
...List.generate(widget.headers.length, (index) {
|
||||
return _buildTableHeaderCell(widget.headers[index], index);
|
||||
})
|
||||
//...widget.headers.map((header) => _buildTableHeaderCell(header)),
|
||||
],
|
||||
),
|
||||
),
|
||||
widget.isEmpty
|
||||
? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SvgPicture.asset(Assets.emptyTable),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
Column(
|
||||
children: [
|
||||
SvgPicture.asset(Assets.emptyTable),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
Text(
|
||||
widget.tableName == 'AccessManagement' ? 'No Password ' : 'No Devices',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: ColorsManager.grayColor),
|
||||
)
|
||||
],
|
||||
),
|
||||
Text(
|
||||
'No Devices',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: ColorsManager.grayColor),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
: Column(
|
||||
children: List.generate(widget.data.length, (index) {
|
||||
final row = widget.data[index];
|
||||
return Row(
|
||||
children: [
|
||||
if (widget.withCheckBox) _buildRowCheckbox(index, widget.size.height * 0.08),
|
||||
...row.map((cell) => _buildTableCell(cell.toString(), widget.size.height * 0.08)),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Expanded(
|
||||
child: Container(
|
||||
color: Colors.white,
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: widget.data.length,
|
||||
itemBuilder: (context, index) {
|
||||
final row = widget.data[index];
|
||||
return Row(
|
||||
children: [
|
||||
if (widget.withCheckBox)
|
||||
_buildRowCheckbox(index, widget.size.height * 0.10),
|
||||
...row
|
||||
.map((cell) =>
|
||||
_buildTableCell(cell.toString(), widget.size.height * 0.10))
|
||||
.toList(),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -134,15 +189,14 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
Widget _buildSelectAllCheckbox() {
|
||||
return Container(
|
||||
width: 50,
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border.symmetric(
|
||||
vertical: BorderSide(color: ColorsManager.boxDivider),
|
||||
),
|
||||
),
|
||||
child: Checkbox(
|
||||
value: _selected.every((element) => element == true),
|
||||
onChanged: null,
|
||||
value: _selectAll,
|
||||
onChanged: widget.withSelectAll && widget.data.isNotEmpty ? _toggleSelectAll : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -159,11 +213,12 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
color: ColorsManager.whiteColors,
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Center(
|
||||
child: Checkbox(
|
||||
value: _selected[index],
|
||||
value: _selectedRows[index],
|
||||
onChanged: (bool? value) {
|
||||
_toggleRowSelection(index);
|
||||
},
|
||||
@ -172,7 +227,7 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTableHeaderCell(String title) {
|
||||
Widget _buildTableHeaderCell(String title, int index) {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
@ -180,16 +235,18 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
vertical: BorderSide(color: ColorsManager.boxDivider),
|
||||
),
|
||||
),
|
||||
constraints: const BoxConstraints.expand(height: 40),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
padding: EdgeInsets.symmetric(horizontal: index == widget.headers.length - 1 ? 12 : 8.0, vertical: 4),
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
style: context.textTheme.titleSmall!.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 13,
|
||||
color: Color(0xFF999999),
|
||||
),
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -222,7 +279,7 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
statusColor = ColorsManager.red;
|
||||
break;
|
||||
default:
|
||||
statusColor = Colors.black; // Default color
|
||||
statusColor = Colors.black;
|
||||
}
|
||||
|
||||
return Expanded(
|
||||
@ -236,16 +293,20 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
color: Colors.white,
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
content,
|
||||
style: TextStyle(
|
||||
color: batteryLevel != null && batteryLevel < 20
|
||||
color: (batteryLevel != null && batteryLevel < 20)
|
||||
? ColorsManager.red
|
||||
: statusColor, // Use the passed color or default to black
|
||||
fontSize: 10,
|
||||
: (batteryLevel != null && batteryLevel > 20)
|
||||
? ColorsManager.green
|
||||
: statusColor,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w400),
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -36,7 +36,10 @@ class DateTimeWebWidget extends StatelessWidget {
|
||||
if (isRequired)
|
||||
Text(
|
||||
'* ',
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(color: Colors.red),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(color: Colors.red),
|
||||
),
|
||||
Text(
|
||||
title,
|
||||
@ -51,8 +54,9 @@ class DateTimeWebWidget extends StatelessWidget {
|
||||
height: 8,
|
||||
),
|
||||
Container(
|
||||
height: size.height * 0.055,
|
||||
padding: const EdgeInsets.only(top: 10, bottom: 10, right: 30, left: 10),
|
||||
// height: size.height * 0.056,
|
||||
padding:
|
||||
const EdgeInsets.only(top: 10, bottom: 10, right: 30, left: 10),
|
||||
decoration: containerDecoration,
|
||||
child: FittedBox(
|
||||
child: Column(
|
||||
@ -65,16 +69,22 @@ class DateTimeWebWidget extends StatelessWidget {
|
||||
child: FittedBox(
|
||||
child: Text(
|
||||
firstString,
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400),
|
||||
),
|
||||
)),
|
||||
const SizedBox(
|
||||
width: 30,
|
||||
),
|
||||
const Icon(Icons.arrow_right_alt),
|
||||
const Icon(
|
||||
Icons.arrow_right_alt,
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 30,
|
||||
),
|
||||
@ -83,10 +93,13 @@ class DateTimeWebWidget extends StatelessWidget {
|
||||
child: FittedBox(
|
||||
child: Text(
|
||||
secondString,
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400),
|
||||
),
|
||||
)),
|
||||
const SizedBox(
|
||||
|
||||
@ -20,7 +20,7 @@ class FilterWidget extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: containerDecoration,
|
||||
height: size.height * 0.05,
|
||||
height: 40,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: tabs.length,
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HourPickerDialog extends StatefulWidget {
|
||||
final TimeOfDay initialTime;
|
||||
|
||||
const HourPickerDialog({super.key, required this.initialTime});
|
||||
|
||||
@override
|
||||
@ -9,70 +12,50 @@ class HourPickerDialog extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _HourPickerDialogState extends State<HourPickerDialog> {
|
||||
late int _selectedHour;
|
||||
bool _isPm = false;
|
||||
late String selectedHour;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selectedHour = widget.initialTime.hour > 12
|
||||
? widget.initialTime.hour - 12
|
||||
: widget.initialTime.hour;
|
||||
_isPm = widget.initialTime.period == DayPeriod.pm;
|
||||
// Initialize the selectedHour with the initial time passed to the dialog
|
||||
selectedHour = widget.initialTime.hour.toString().padLeft(2, '0') + ':00';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Select Hour'),
|
||||
content: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
DropdownButton<int>(
|
||||
value: _selectedHour,
|
||||
items: List.generate(12, (index) {
|
||||
int displayHour = index + 1;
|
||||
return DropdownMenuItem(
|
||||
value: displayHour,
|
||||
child: Text(displayHour.toString()),
|
||||
);
|
||||
}),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_selectedHour = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
SizedBox(width: 16.0),
|
||||
DropdownButton<bool>(
|
||||
value: _isPm,
|
||||
items: const [
|
||||
DropdownMenuItem(
|
||||
value: false,
|
||||
child: Text('AM'),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: true,
|
||||
child: Text('PM'),
|
||||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_isPm = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
content: DropdownButton<String>(
|
||||
value: selectedHour, // Show the currently selected hour
|
||||
items: List.generate(24, (index) {
|
||||
String hour = index.toString().padLeft(2, '0');
|
||||
return DropdownMenuItem<String>(
|
||||
value: '$hour:00',
|
||||
child: Text('$hour:00'),
|
||||
);
|
||||
}),
|
||||
onChanged: (String? newValue) {
|
||||
if (newValue != null) {
|
||||
setState(() {
|
||||
selectedHour = newValue; // Update the selected hour without closing the dialog
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(null),
|
||||
onPressed: () => Navigator.of(context).pop(null), // Close the dialog without selection
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
int hour = _isPm ? _selectedHour + 12 : _selectedHour;
|
||||
Navigator.of(context).pop(TimeOfDay(hour: hour, minute: 0));
|
||||
// Close the dialog and return the selected time
|
||||
Navigator.of(context).pop(
|
||||
TimeOfDay(
|
||||
hour: int.parse(selectedHour.split(':')[0]),
|
||||
minute: 0,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('OK'),
|
||||
),
|
||||
@ -86,6 +69,7 @@ Future<TimeOfDay?> showHourPicker({
|
||||
required TimeOfDay initialTime,
|
||||
}) {
|
||||
return showDialog<TimeOfDay>(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (context) => HourPickerDialog(initialTime: initialTime),
|
||||
);
|
||||
|
||||
@ -1,21 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/core/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class StatefulTextField extends StatefulWidget {
|
||||
const StatefulTextField({
|
||||
super.key,
|
||||
required this.title,
|
||||
this.hintText = 'Please enter',
|
||||
required this.width,
|
||||
this.elevation = 0,
|
||||
required this.controller, // Add the controller
|
||||
});
|
||||
const StatefulTextField(
|
||||
{super.key,
|
||||
required this.title,
|
||||
this.hintText = 'Please enter',
|
||||
required this.width,
|
||||
this.elevation = 0,
|
||||
required this.controller,
|
||||
this.onSubmitted});
|
||||
|
||||
final String title;
|
||||
final String hintText;
|
||||
final double width;
|
||||
final double elevation;
|
||||
final TextEditingController controller;
|
||||
final TextEditingController controller;
|
||||
final Function? onSubmitted;
|
||||
|
||||
@override
|
||||
State<StatefulTextField> createState() => _StatefulTextFieldState();
|
||||
@ -24,31 +26,34 @@ class StatefulTextField extends StatefulWidget {
|
||||
class _StatefulTextFieldState extends State<StatefulTextField> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CustomTextField(
|
||||
title: widget.title,
|
||||
controller: widget.controller,
|
||||
hintText: widget.hintText,
|
||||
width: widget.width,
|
||||
elevation: widget.elevation,
|
||||
return Container(
|
||||
child: CustomTextField(
|
||||
title: widget.title,
|
||||
controller: widget.controller,
|
||||
hintText: widget.hintText,
|
||||
width: widget.width,
|
||||
elevation: widget.elevation,
|
||||
onSubmittedFun: widget.onSubmitted),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomTextField extends StatelessWidget {
|
||||
const CustomTextField({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.controller,
|
||||
this.hintText = 'Please enter',
|
||||
required this.width,
|
||||
this.elevation = 0,
|
||||
});
|
||||
const CustomTextField(
|
||||
{super.key,
|
||||
required this.title,
|
||||
required this.controller,
|
||||
this.hintText = 'Please enter',
|
||||
required this.width,
|
||||
this.elevation = 0,
|
||||
this.onSubmittedFun});
|
||||
|
||||
final String title;
|
||||
final TextEditingController controller;
|
||||
final String hintText;
|
||||
final double width;
|
||||
final double elevation;
|
||||
final Function? onSubmittedFun;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -59,6 +64,7 @@ class CustomTextField extends StatelessWidget {
|
||||
Text(
|
||||
title,
|
||||
style: context.textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: const Color(0xff000000),
|
||||
),
|
||||
@ -69,19 +75,26 @@ class CustomTextField extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
width: width,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
height: 45,
|
||||
decoration: containerDecoration,
|
||||
|
||||
// decoration: BoxDecoration(
|
||||
// color: Colors.white,
|
||||
// borderRadius: BorderRadius.circular(8),
|
||||
// ),
|
||||
child: TextFormField(
|
||||
controller: controller,
|
||||
style: const TextStyle(color: Colors.black),
|
||||
decoration: InputDecoration(
|
||||
hintText: hintText,
|
||||
hintStyle: const TextStyle(fontSize: 12),
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onFieldSubmitted: (_) {
|
||||
onSubmittedFun!();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class CustomWebTextField extends StatelessWidget {
|
||||
@ -11,6 +12,8 @@ class CustomWebTextField extends StatelessWidget {
|
||||
this.description,
|
||||
this.validator,
|
||||
this.hintText,
|
||||
this.height,
|
||||
this.onSubmitted,
|
||||
});
|
||||
|
||||
final bool isRequired;
|
||||
@ -19,6 +22,8 @@ class CustomWebTextField extends StatelessWidget {
|
||||
final TextEditingController? controller;
|
||||
final String? Function(String?)? validator;
|
||||
final String? hintText;
|
||||
final double? height;
|
||||
final ValueChanged<String>? onSubmitted;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -29,9 +34,9 @@ class CustomWebTextField extends StatelessWidget {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (isRequired)
|
||||
Row(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
if (isRequired)
|
||||
Text(
|
||||
'* ',
|
||||
style: Theme.of(context)
|
||||
@ -39,15 +44,15 @@ class CustomWebTextField extends StatelessWidget {
|
||||
.bodyMedium!
|
||||
.copyWith(color: Colors.red),
|
||||
),
|
||||
Text(
|
||||
textFieldName,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: Colors.black, fontSize: 13),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
textFieldName,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: Colors.black, fontSize: 13),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
@ -66,24 +71,18 @@ class CustomWebTextField extends StatelessWidget {
|
||||
height: 7,
|
||||
),
|
||||
Container(
|
||||
decoration: containerDecoration
|
||||
.copyWith(color: const Color(0xFFF5F6F7), boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.3),
|
||||
spreadRadius: 2,
|
||||
blurRadius: 3,
|
||||
offset: const Offset(1, 1), // changes position of shadow
|
||||
),
|
||||
]),
|
||||
height: height ?? 35,
|
||||
decoration: containerDecoration,
|
||||
child: TextFormField(
|
||||
validator: validator,
|
||||
controller: controller,
|
||||
style: const TextStyle(color: Colors.black),
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
errorStyle:
|
||||
const TextStyle(height: 0), // Hide the error text space
|
||||
|
||||
errorStyle: const TextStyle(height: 0),
|
||||
hintStyle: context.textTheme.titleSmall!
|
||||
.copyWith(color: Colors.grey, fontSize: 12),
|
||||
hintText: hintText ?? 'Please enter'),
|
||||
onFieldSubmitted: onSubmitted,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@ -14,12 +14,15 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
Timer? _timer;
|
||||
|
||||
AcBloc({required this.deviceId}) : super(AcsInitialState()) {
|
||||
on<AcFetchDeviceStatus>(_onFetchAcStatus);
|
||||
on<AcControl>(_onAcControl);
|
||||
on<AcFetchDeviceStatusEvent>(_onFetchAcStatus);
|
||||
on<AcFetchBatchStatusEvent>(_onFetchAcBatchStatus);
|
||||
on<AcControlEvent>(_onAcControl);
|
||||
on<AcBatchControlEvent>(_onAcBatchControl);
|
||||
on<AcFactoryResetEvent>(_onFactoryReset);
|
||||
}
|
||||
|
||||
FutureOr<void> _onFetchAcStatus(
|
||||
AcFetchDeviceStatus event, Emitter<AcsState> emit) async {
|
||||
AcFetchDeviceStatusEvent event, Emitter<AcsState> emit) async {
|
||||
emit(AcsLoadingState());
|
||||
try {
|
||||
final status =
|
||||
@ -31,7 +34,8 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onAcControl(AcControl event, Emitter<AcsState> emit) async {
|
||||
FutureOr<void> _onAcControl(
|
||||
AcControlEvent event, Emitter<AcsState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
_updateLocalValue(event.code, event.value, emit);
|
||||
@ -39,6 +43,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
emit(ACStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
isBatch: false,
|
||||
deviceId: event.deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
@ -48,27 +53,43 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
}
|
||||
|
||||
Future<void> _runDebounce({
|
||||
required String deviceId,
|
||||
required dynamic deviceId,
|
||||
required String code,
|
||||
required dynamic value,
|
||||
required dynamic oldValue,
|
||||
required Emitter<AcsState> emit,
|
||||
required bool isBatch,
|
||||
}) async {
|
||||
late String id;
|
||||
|
||||
if (deviceId is List) {
|
||||
id = deviceId.first;
|
||||
} else {
|
||||
id = deviceId;
|
||||
}
|
||||
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
_timer = Timer(const Duration(seconds: 1), () async {
|
||||
try {
|
||||
final response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: value));
|
||||
late bool response;
|
||||
if (isBatch) {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceBatchControl(deviceId, code, value);
|
||||
} else {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: value));
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
_revertValueAndEmit(deviceId, code, oldValue, emit);
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e is DioException && e.response != null) {
|
||||
debugPrint('Error response: ${e.response?.data}');
|
||||
}
|
||||
_revertValueAndEmit(deviceId, code, oldValue, emit);
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -77,7 +98,6 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
String deviceId, String code, dynamic oldValue, Emitter<AcsState> emit) {
|
||||
_updateLocalValue(code, oldValue, emit);
|
||||
emit(ACStatusLoaded(deviceStatus));
|
||||
emit(const AcsFailedState(error: 'Failed to control the device.'));
|
||||
}
|
||||
|
||||
void _updateLocalValue(String code, dynamic value, Emitter<AcsState> emit) {
|
||||
@ -133,4 +153,54 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onFetchAcBatchStatus(
|
||||
AcFetchBatchStatusEvent event, Emitter<AcsState> emit) async {
|
||||
emit(AcsLoadingState());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
deviceStatus =
|
||||
AcStatusModel.fromJson(event.devicesIds.first, status.status);
|
||||
emit(ACStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(AcsFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onAcBatchControl(
|
||||
AcBatchControlEvent event, Emitter<AcsState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
_updateLocalValue(event.code, event.value, emit);
|
||||
|
||||
emit(ACStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
isBatch: true,
|
||||
deviceId: event.devicesIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
);
|
||||
}
|
||||
|
||||
FutureOr<void> _onFactoryReset(
|
||||
AcFactoryResetEvent event, Emitter<AcsState> emit) async {
|
||||
emit(AcsLoadingState());
|
||||
try {
|
||||
final response = await DevicesManagementApi().factoryReset(
|
||||
event.factoryResetModel,
|
||||
event.deviceId,
|
||||
);
|
||||
if (!response) {
|
||||
emit(const AcsFailedState(error: 'Failed'));
|
||||
} else {
|
||||
add(AcFetchDeviceStatusEvent(event.deviceId));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(AcsFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
|
||||
sealed class AcsEvent extends Equatable {
|
||||
const AcsEvent();
|
||||
@ -7,21 +8,30 @@ sealed class AcsEvent extends Equatable {
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class AcFetchDeviceStatus extends AcsEvent {
|
||||
class AcFetchDeviceStatusEvent extends AcsEvent {
|
||||
final String deviceId;
|
||||
|
||||
const AcFetchDeviceStatus(this.deviceId);
|
||||
const AcFetchDeviceStatusEvent(this.deviceId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId];
|
||||
}
|
||||
|
||||
class AcControl extends AcsEvent {
|
||||
class AcFetchBatchStatusEvent extends AcsEvent {
|
||||
final List<String> devicesIds;
|
||||
|
||||
const AcFetchBatchStatusEvent(this.devicesIds);
|
||||
|
||||
@override
|
||||
List<Object> get props => [devicesIds];
|
||||
}
|
||||
|
||||
class AcControlEvent extends AcsEvent {
|
||||
final String deviceId;
|
||||
final String code;
|
||||
final dynamic value;
|
||||
|
||||
const AcControl({
|
||||
const AcControlEvent({
|
||||
required this.deviceId,
|
||||
required this.code,
|
||||
required this.value,
|
||||
@ -30,3 +40,31 @@ class AcControl extends AcsEvent {
|
||||
@override
|
||||
List<Object> get props => [deviceId, code, value];
|
||||
}
|
||||
|
||||
class AcBatchControlEvent extends AcsEvent {
|
||||
final List<String> devicesIds;
|
||||
final String code;
|
||||
final dynamic value;
|
||||
|
||||
const AcBatchControlEvent({
|
||||
required this.devicesIds,
|
||||
required this.code,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [devicesIds, code, value];
|
||||
}
|
||||
|
||||
class AcFactoryResetEvent extends AcsEvent {
|
||||
final String deviceId;
|
||||
final FactoryResetModel factoryResetModel;
|
||||
|
||||
const AcFactoryResetEvent({
|
||||
required this.deviceId,
|
||||
required this.factoryResetModel,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId, factoryResetModel];
|
||||
}
|
||||
|
||||
@ -22,6 +22,16 @@ class ACStatusLoaded extends AcsState {
|
||||
List<Object> get props => [status, timestamp];
|
||||
}
|
||||
|
||||
class AcBatchStatusLoaded extends AcsState {
|
||||
final AcStatusModel status;
|
||||
final DateTime timestamp;
|
||||
|
||||
AcBatchStatusLoaded(this.status) : timestamp = DateTime.now();
|
||||
|
||||
@override
|
||||
List<Object> get props => [status, timestamp];
|
||||
}
|
||||
|
||||
class AcsFailedState extends AcsState {
|
||||
final String error;
|
||||
|
||||
|
||||
@ -40,16 +40,16 @@ class AcStatusModel {
|
||||
acSwitch = status.value ?? false;
|
||||
break;
|
||||
case 'mode':
|
||||
mode = status.value ?? 'cold'; // default to 'cold' if null
|
||||
mode = status.value ?? 'cold';
|
||||
break;
|
||||
case 'temp_set':
|
||||
tempSet = status.value ?? 210; // default value if null
|
||||
tempSet = status.value ?? 210;
|
||||
break;
|
||||
case 'temp_current':
|
||||
currentTemp = status.value ?? 210; // default value if null
|
||||
currentTemp = status.value ?? 210;
|
||||
break;
|
||||
case 'level':
|
||||
fanSpeeds = status.value ?? 'low'; // default value if null
|
||||
fanSpeeds = status.value ?? 'low';
|
||||
break;
|
||||
case 'child_lock':
|
||||
childLock = status.value ?? false;
|
||||
|
||||
161
lib/pages/device_managment/ac/view/ac_device_batch_control.dart
Normal file
161
lib/pages/device_managment/ac/view/ac_device_batch_control.dart
Normal file
@ -0,0 +1,161 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart';
|
||||
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/toggle_widget.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 AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayout {
|
||||
const AcDeviceBatchControlView({super.key, required this.devicesIds});
|
||||
|
||||
final List<String> devicesIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return BlocProvider(
|
||||
create: (context) => AcBloc(deviceId: devicesIds.first)..add(AcFetchBatchStatusEvent(devicesIds)),
|
||||
child: BlocBuilder<AcBloc, AcsState>(
|
||||
builder: (context, state) {
|
||||
if (state is ACStatusLoaded) {
|
||||
return GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge || isExtraLarge
|
||||
? 3
|
||||
: isMedium
|
||||
? 2
|
||||
: 1,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
children: [
|
||||
ToggleWidget(
|
||||
deviceId: devicesIds.first,
|
||||
code: 'switch',
|
||||
value: state.status.acSwitch,
|
||||
label: 'ThermoState',
|
||||
icon: Assets.ac,
|
||||
onChange: (value) {
|
||||
context.read<AcBloc>().add(AcBatchControlEvent(
|
||||
devicesIds: devicesIds,
|
||||
code: 'switch',
|
||||
value: value,
|
||||
));
|
||||
},
|
||||
),
|
||||
BatchCurrentTemp(
|
||||
currentTemp: state.status.currentTemp,
|
||||
tempSet: state.status.tempSet,
|
||||
code: 'temp_set',
|
||||
devicesIds: devicesIds,
|
||||
isBatch: true,
|
||||
),
|
||||
BatchAcMode(
|
||||
value: state.status.acMode,
|
||||
code: 'mode',
|
||||
devicesIds: devicesIds,
|
||||
),
|
||||
BatchFanSpeedControl(
|
||||
value: state.status.acFanSpeed,
|
||||
code: 'level',
|
||||
devicesIds: devicesIds,
|
||||
),
|
||||
ToggleWidget(
|
||||
label: '',
|
||||
labelWidget: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(
|
||||
Icons.remove,
|
||||
size: 28,
|
||||
color: ColorsManager.greyColor,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'06',
|
||||
style: context.textTheme.titleLarge!.copyWith(
|
||||
color: ColorsManager.dialogBlueTitle,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'h',
|
||||
style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor),
|
||||
),
|
||||
Text(
|
||||
'30',
|
||||
style: context.textTheme.titleLarge!.copyWith(
|
||||
color: ColorsManager.dialogBlueTitle,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text('m', style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor)),
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(
|
||||
Icons.add,
|
||||
size: 28,
|
||||
color: ColorsManager.greyColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
value: false,
|
||||
code: 'ac_schedule',
|
||||
deviceId: devicesIds.first,
|
||||
icon: Assets.acSchedule,
|
||||
onChange: (value) {},
|
||||
),
|
||||
ToggleWidget(
|
||||
deviceId: devicesIds.first,
|
||||
code: 'child_lock',
|
||||
value: state.status.childLock,
|
||||
label: 'Child Lock',
|
||||
icon: state.status.childLock ? Assets.acLock : Assets.unlock,
|
||||
onChange: (value) {
|
||||
context.read<AcBloc>().add(AcBatchControlEvent(
|
||||
devicesIds: devicesIds,
|
||||
code: 'child_lock',
|
||||
value: value,
|
||||
));
|
||||
},
|
||||
),
|
||||
FirmwareUpdateWidget(deviceId: devicesIds.first, version: 5),
|
||||
FactoryResetWidget(
|
||||
callFactoryReset: () {
|
||||
context.read<AcBloc>().add(AcFactoryResetEvent(
|
||||
deviceId: state.status.uuid,
|
||||
factoryResetModel: FactoryResetModel(devicesUuid: devicesIds),
|
||||
));
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
} else if (state is AcsLoadingState) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else {
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -4,25 +4,27 @@ import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/view/control_list/ac_mode.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/view/control_list/ac_toggle.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/view/control_list/current_temp.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/view/control_list/fan_speed.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.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';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class AcDeviceControl extends StatelessWidget with HelperResponsiveLayout {
|
||||
const AcDeviceControl({super.key, required this.device});
|
||||
class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
|
||||
const AcDeviceControlsView({super.key, required this.device});
|
||||
|
||||
final AllDevicesModel device;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return BlocProvider(
|
||||
create: (context) => AcBloc(deviceId: device.uuid!)
|
||||
..add(AcFetchDeviceStatus(device.uuid!)),
|
||||
create: (context) => AcBloc(deviceId: device.uuid!)..add(AcFetchDeviceStatusEvent(device.uuid!)),
|
||||
child: BlocBuilder<AcBloc, AcsState>(
|
||||
builder: (context, state) {
|
||||
if (state is ACStatusLoaded) {
|
||||
@ -31,20 +33,31 @@ class AcDeviceControl extends StatelessWidget with HelperResponsiveLayout {
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge
|
||||
crossAxisCount: isLarge || isExtraLarge
|
||||
? 3
|
||||
: isMedium
|
||||
? 2
|
||||
: 1,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
mainAxisSpacing: 16,
|
||||
),
|
||||
children: [
|
||||
AcToggle(
|
||||
ToggleWidget(
|
||||
label: 'Thermostat',
|
||||
value: state.status.acSwitch,
|
||||
code: 'switch',
|
||||
deviceId: device.uuid!,
|
||||
icon: Assets.ac,
|
||||
onChange: (value) {
|
||||
context.read<AcBloc>().add(
|
||||
AcControlEvent(
|
||||
deviceId: device.uuid!,
|
||||
code: 'switch',
|
||||
value: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
CurrentTemp(
|
||||
currentTemp: state.status.currentTemp,
|
||||
@ -62,12 +75,71 @@ class AcDeviceControl extends StatelessWidget with HelperResponsiveLayout {
|
||||
code: 'level',
|
||||
deviceId: device.uuid!,
|
||||
),
|
||||
AcToggle(
|
||||
value: state.status.childLock,
|
||||
code: 'child_lock',
|
||||
ToggleWidget(
|
||||
label: '',
|
||||
labelWidget: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
IconButton(
|
||||
padding: const EdgeInsets.all(0),
|
||||
onPressed: () {},
|
||||
icon: const Icon(
|
||||
Icons.remove,
|
||||
size: 28,
|
||||
color: ColorsManager.greyColor,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'06',
|
||||
style: context.textTheme.titleLarge!.copyWith(
|
||||
color: ColorsManager.dialogBlueTitle,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'h',
|
||||
style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor),
|
||||
),
|
||||
Text(
|
||||
'30',
|
||||
style: context.textTheme.titleLarge!.copyWith(
|
||||
color: ColorsManager.dialogBlueTitle,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text('m', style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor)),
|
||||
IconButton(
|
||||
padding: const EdgeInsets.all(0),
|
||||
onPressed: () {},
|
||||
icon: const Icon(
|
||||
Icons.add,
|
||||
size: 28,
|
||||
color: ColorsManager.greyColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
value: false,
|
||||
code: 'ac_schedule',
|
||||
deviceId: device.uuid!,
|
||||
description: 'Child Lock',
|
||||
icon: Assets.childLock,
|
||||
icon: Assets.acSchedule,
|
||||
onChange: (value) {},
|
||||
),
|
||||
ToggleWidget(
|
||||
deviceId: device.uuid!,
|
||||
code: 'child_lock',
|
||||
value: state.status.childLock,
|
||||
label: 'Lock',
|
||||
icon: state.status.childLock ? Assets.acLock : Assets.unlock,
|
||||
onChange: (value) {
|
||||
context.read<AcBloc>().add(
|
||||
AcControlEvent(
|
||||
deviceId: device.uuid!,
|
||||
code: 'child_lock',
|
||||
value: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@ -0,0 +1,73 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.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';
|
||||
|
||||
class BatchAcMode extends StatelessWidget {
|
||||
const BatchAcMode({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.code,
|
||||
required this.devicesIds,
|
||||
});
|
||||
|
||||
final TempModes value;
|
||||
final String code;
|
||||
final List<String> devicesIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DeviceControlsContainer(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
_buildIconContainer(context, TempModes.cold, Assets.freezing, value == TempModes.cold),
|
||||
_buildIconContainer(context, TempModes.hot, Assets.acSun, value == TempModes.hot),
|
||||
_buildIconContainer(context, TempModes.wind, Assets.acAirConditioner, value == TempModes.wind),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIconContainer(BuildContext context, TempModes mode, String assetPath, bool isSelected) {
|
||||
return Flexible(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.read<AcBloc>().add(
|
||||
AcBatchControlEvent(
|
||||
devicesIds: devicesIds,
|
||||
code: code,
|
||||
value: mode.name,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: ColorsManager.whiteColors,
|
||||
border: Border.all(
|
||||
color: isSelected ? Colors.blue : Colors.transparent,
|
||||
width: 2.0,
|
||||
),
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 4),
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: ClipOval(
|
||||
child: SvgPicture.asset(
|
||||
assetPath,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,132 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/celciuse_symbol.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/increament_decreament.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class BatchCurrentTemp extends StatefulWidget {
|
||||
const BatchCurrentTemp({
|
||||
super.key,
|
||||
required this.code,
|
||||
required this.devicesIds,
|
||||
required this.currentTemp,
|
||||
required this.tempSet,
|
||||
this.isBatch,
|
||||
});
|
||||
|
||||
final String code;
|
||||
final List<String> devicesIds;
|
||||
final int currentTemp;
|
||||
final int tempSet;
|
||||
final bool? isBatch;
|
||||
|
||||
@override
|
||||
State<BatchCurrentTemp> createState() => _CurrentTempState();
|
||||
}
|
||||
|
||||
class _CurrentTempState extends State<BatchCurrentTemp> {
|
||||
late double _adjustedValue;
|
||||
Timer? _debounce;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_adjustedValue = _initialAdjustedValue(widget.tempSet);
|
||||
}
|
||||
|
||||
double _initialAdjustedValue(dynamic value) {
|
||||
if (value is int || value is double) {
|
||||
double doubleValue = value.toDouble();
|
||||
return doubleValue > 99 ? doubleValue / 10 : doubleValue;
|
||||
} else {
|
||||
throw ArgumentError('Invalid value type: Expected int or double');
|
||||
}
|
||||
}
|
||||
|
||||
void _onValueChanged(double newValue) {
|
||||
if (_debounce?.isActive ?? false) {
|
||||
_debounce?.cancel();
|
||||
}
|
||||
_debounce = Timer(const Duration(milliseconds: 500), () {
|
||||
context.read<AcBloc>().add(
|
||||
AcBatchControlEvent(
|
||||
devicesIds: widget.devicesIds,
|
||||
code: widget.code,
|
||||
value: (newValue * 10).toInt(),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_debounce?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DeviceControlsContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
widget.isBatch == true
|
||||
? Text(
|
||||
'Set Temperature',
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.grey),
|
||||
)
|
||||
: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Current Temperature',
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.grey),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
(widget.currentTemp > 99 ? widget.currentTemp / 10 : widget.currentTemp).toString(),
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.grey),
|
||||
),
|
||||
const CelsiusSymbol(
|
||||
color: Colors.grey,
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
IncrementDecrementWidget(
|
||||
value: _adjustedValue.toString(),
|
||||
description: '°C',
|
||||
descriptionColor: ColorsManager.dialogBlueTitle,
|
||||
onIncrement: () {
|
||||
if (_adjustedValue < 30) {
|
||||
setState(() {
|
||||
_adjustedValue = _adjustedValue + 0.5;
|
||||
});
|
||||
_onValueChanged(_adjustedValue);
|
||||
}
|
||||
},
|
||||
onDecrement: () {
|
||||
if (_adjustedValue > 20) {
|
||||
setState(() {
|
||||
_adjustedValue = _adjustedValue - 0.5;
|
||||
});
|
||||
_onValueChanged(_adjustedValue);
|
||||
}
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.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';
|
||||
|
||||
class BatchFanSpeedControl extends StatelessWidget {
|
||||
const BatchFanSpeedControl({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.code,
|
||||
required this.devicesIds,
|
||||
});
|
||||
|
||||
final FanSpeeds value;
|
||||
final String code;
|
||||
final List<String> devicesIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DeviceControlsContainer(
|
||||
padding: 8,
|
||||
child: Column(
|
||||
children: [
|
||||
Wrap(
|
||||
runSpacing: 8,
|
||||
spacing: 8,
|
||||
children: [
|
||||
_buildIconContainer(context, FanSpeeds.auto, Assets.acFanAuto, value == FanSpeeds.auto),
|
||||
_buildIconContainer(context, FanSpeeds.low, Assets.acFanLow, value == FanSpeeds.low),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
runSpacing: 8,
|
||||
spacing: 8,
|
||||
children: [
|
||||
_buildIconContainer(context, FanSpeeds.middle, Assets.acFanMiddle, value == FanSpeeds.middle),
|
||||
_buildIconContainer(context, FanSpeeds.high, Assets.acFanHigh, value == FanSpeeds.high),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIconContainer(BuildContext context, FanSpeeds speed, String assetPath, bool isSelected) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
context.read<AcBloc>().add(
|
||||
AcBatchControlEvent(
|
||||
devicesIds: devicesIds,
|
||||
code: code,
|
||||
value: speed.name,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: ColorsManager.whiteColors,
|
||||
border: Border.all(
|
||||
color: isSelected ? Colors.blue : Colors.transparent,
|
||||
width: 2.0,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: ClipOval(
|
||||
child: SvgPicture.asset(
|
||||
assetPath,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.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';
|
||||
|
||||
class AcMode extends StatelessWidget {
|
||||
const AcMode({
|
||||
@ -21,35 +22,25 @@ class AcMode extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: ColorsManager.greyColor.withOpacity(0.2),
|
||||
border: Border.all(color: ColorsManager.boxDivider),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
return DeviceControlsContainer(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
_buildIconContainer(context, TempModes.cold, Assets.freezing,
|
||||
value == TempModes.cold),
|
||||
_buildIconContainer(
|
||||
context, TempModes.hot, Assets.acSun, value == TempModes.hot),
|
||||
_buildIconContainer(context, TempModes.wind, Assets.acAirConditioner,
|
||||
value == TempModes.wind),
|
||||
_buildIconContainer(context, TempModes.cold, Assets.freezing, value == TempModes.cold),
|
||||
_buildIconContainer(context, TempModes.hot, Assets.acSun, value == TempModes.hot),
|
||||
_buildIconContainer(context, TempModes.wind, Assets.acAirConditioner, value == TempModes.wind),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIconContainer(
|
||||
BuildContext context, TempModes mode, String assetPath, bool isSelected) {
|
||||
Widget _buildIconContainer(BuildContext context, TempModes mode, String assetPath, bool isSelected) {
|
||||
return Flexible(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.read<AcBloc>().add(
|
||||
AcControl(
|
||||
AcControlEvent(
|
||||
deviceId: deviceId,
|
||||
code: code,
|
||||
value: mode.name,
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.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';
|
||||
|
||||
@ -25,13 +25,7 @@ class AcToggle extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: ColorsManager.greyColor.withOpacity(0.2),
|
||||
border: Border.all(color: ColorsManager.boxDivider),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
return DeviceControlsContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -39,16 +33,21 @@ class AcToggle extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClipOval(
|
||||
child: Container(
|
||||
color: ColorsManager.whiteColors,
|
||||
child: SvgPicture.asset(
|
||||
icon ?? Assets.acDevice,
|
||||
width: 60,
|
||||
height: 60,
|
||||
fit: BoxFit.cover,
|
||||
Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: ColorsManager.whiteColors,
|
||||
),
|
||||
)),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: ClipOval(
|
||||
child: SvgPicture.asset(
|
||||
icon ?? Assets.lightPulp,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
width: 35,
|
||||
@ -57,7 +56,7 @@ class AcToggle extends StatelessWidget {
|
||||
value: value,
|
||||
onChanged: (newValue) {
|
||||
context.read<AcBloc>().add(
|
||||
AcControl(
|
||||
AcControlEvent(
|
||||
deviceId: deviceId,
|
||||
code: code,
|
||||
value: newValue,
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/celciuse_symbol.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/increament_decreament.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
||||
|
||||
class CurrentTemp extends StatefulWidget {
|
||||
const CurrentTemp({
|
||||
@ -50,7 +52,7 @@ class _CurrentTempState extends State<CurrentTemp> {
|
||||
}
|
||||
_debounce = Timer(const Duration(milliseconds: 500), () {
|
||||
context.read<AcBloc>().add(
|
||||
AcControl(
|
||||
AcControlEvent(
|
||||
deviceId: widget.deviceId,
|
||||
code: widget.code,
|
||||
value: (newValue * 10).toInt(),
|
||||
@ -67,13 +69,7 @@ class _CurrentTempState extends State<CurrentTemp> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: ColorsManager.greyColor.withOpacity(0.2),
|
||||
border: Border.all(color: ColorsManager.boxDivider),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
return DeviceControlsContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -83,10 +79,7 @@ class _CurrentTempState extends State<CurrentTemp> {
|
||||
children: [
|
||||
Text(
|
||||
'Current Temperature',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: Colors.grey),
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.grey),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
@ -94,14 +87,8 @@ class _CurrentTempState extends State<CurrentTemp> {
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
(widget.currentTemp > 99
|
||||
? widget.currentTemp / 10
|
||||
: widget.currentTemp)
|
||||
.toString(),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: Colors.grey),
|
||||
(widget.currentTemp > 99 ? widget.currentTemp / 10 : widget.currentTemp).toString(),
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.grey),
|
||||
),
|
||||
const CelsiusSymbol(
|
||||
color: Colors.grey,
|
||||
@ -116,16 +103,20 @@ class _CurrentTempState extends State<CurrentTemp> {
|
||||
description: '°C',
|
||||
descriptionColor: ColorsManager.dialogBlueTitle,
|
||||
onIncrement: () {
|
||||
setState(() {
|
||||
_adjustedValue++;
|
||||
});
|
||||
_onValueChanged(_adjustedValue);
|
||||
if (_adjustedValue < 30) {
|
||||
setState(() {
|
||||
_adjustedValue = _adjustedValue + 0.5;
|
||||
});
|
||||
_onValueChanged(_adjustedValue);
|
||||
}
|
||||
},
|
||||
onDecrement: () {
|
||||
setState(() {
|
||||
_adjustedValue--;
|
||||
});
|
||||
_onValueChanged(_adjustedValue);
|
||||
if (_adjustedValue > 20) {
|
||||
setState(() {
|
||||
_adjustedValue = _adjustedValue - 0.5;
|
||||
});
|
||||
_onValueChanged(_adjustedValue);
|
||||
}
|
||||
}),
|
||||
],
|
||||
),
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.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';
|
||||
|
||||
class FanSpeedControl extends StatelessWidget {
|
||||
const FanSpeedControl({
|
||||
@ -21,33 +22,24 @@ class FanSpeedControl extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: ColorsManager.greyColor.withOpacity(0.2),
|
||||
border: Border.all(color: ColorsManager.boxDivider),
|
||||
),
|
||||
padding: const EdgeInsets.all(8),
|
||||
return DeviceControlsContainer(
|
||||
child: Column(
|
||||
children: [
|
||||
Wrap(
|
||||
runSpacing: 8,
|
||||
spacing: 8,
|
||||
children: [
|
||||
_buildIconContainer(context, FanSpeeds.auto, Assets.acFanAuto,
|
||||
value == FanSpeeds.auto),
|
||||
_buildIconContainer(context, FanSpeeds.low, Assets.acFanLow,
|
||||
value == FanSpeeds.low),
|
||||
_buildIconContainer(context, FanSpeeds.auto, Assets.acFanAuto, value == FanSpeeds.auto),
|
||||
_buildIconContainer(context, FanSpeeds.low, Assets.acFanLow, value == FanSpeeds.low),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
runSpacing: 8,
|
||||
spacing: 8,
|
||||
children: [
|
||||
_buildIconContainer(context, FanSpeeds.middle, Assets.acFanMiddle,
|
||||
value == FanSpeeds.middle),
|
||||
_buildIconContainer(context, FanSpeeds.high, Assets.acFanHigh,
|
||||
value == FanSpeeds.high),
|
||||
_buildIconContainer(context, FanSpeeds.middle, Assets.acFanMiddle, value == FanSpeeds.middle),
|
||||
_buildIconContainer(context, FanSpeeds.high, Assets.acFanHigh, value == FanSpeeds.high),
|
||||
],
|
||||
)
|
||||
],
|
||||
@ -55,12 +47,11 @@ class FanSpeedControl extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIconContainer(BuildContext context, FanSpeeds speed,
|
||||
String assetPath, bool isSelected) {
|
||||
Widget _buildIconContainer(BuildContext context, FanSpeeds speed, String assetPath, bool isSelected) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
context.read<AcBloc>().add(
|
||||
AcControl(
|
||||
AcControlEvent(
|
||||
deviceId: deviceId,
|
||||
code: code,
|
||||
value: speed.name,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.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/services/devices_mang_api.dart';
|
||||
|
||||
@ -14,6 +14,10 @@ class DeviceManagementBloc
|
||||
int _offlineCount = 0;
|
||||
int _lowBatteryCount = 0;
|
||||
List<AllDevicesModel> _selectedDevices = [];
|
||||
List<AllDevicesModel> _filteredDevices = [];
|
||||
String currentProductName = '';
|
||||
String? currentCommunity;
|
||||
String? currentUnitName;
|
||||
|
||||
DeviceManagementBloc() : super(DeviceManagementInitial()) {
|
||||
on<FetchDevices>(_onFetchDevices);
|
||||
@ -21,6 +25,9 @@ class DeviceManagementBloc
|
||||
on<SelectedFilterChanged>(_onSelectedFilterChanged);
|
||||
on<SearchDevices>(_onSearchDevices);
|
||||
on<SelectDevice>(_onSelectDevice);
|
||||
on<ResetFilters>(_onResetFilters);
|
||||
on<ResetSelectedDevices>(_onResetSelectedDevices);
|
||||
on<UpdateSelection>(_onUpdateSelection);
|
||||
}
|
||||
|
||||
Future<void> _onFetchDevices(
|
||||
@ -28,14 +35,18 @@ class DeviceManagementBloc
|
||||
emit(DeviceManagementLoading());
|
||||
try {
|
||||
final devices = await DevicesManagementApi().fetchDevices();
|
||||
_selectedDevices.clear();
|
||||
_devices = devices;
|
||||
_filteredDevices = devices;
|
||||
_calculateDeviceCounts();
|
||||
emit(DeviceManagementLoaded(
|
||||
devices: devices,
|
||||
selectedIndex: _selectedIndex,
|
||||
selectedIndex: 0,
|
||||
onlineCount: _onlineCount,
|
||||
offlineCount: _offlineCount,
|
||||
lowBatteryCount: _lowBatteryCount,
|
||||
selectedDevice: null,
|
||||
isControlButtonEnabled: false,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(DeviceManagementInitial());
|
||||
@ -43,9 +54,9 @@ class DeviceManagementBloc
|
||||
}
|
||||
|
||||
void _onFilterDevices(
|
||||
FilterDevices event, Emitter<DeviceManagementState> emit) {
|
||||
FilterDevices event, Emitter<DeviceManagementState> emit) async {
|
||||
if (_devices.isNotEmpty) {
|
||||
final filteredDevices = _devices.where((device) {
|
||||
_filteredDevices = List.from(_devices.where((device) {
|
||||
switch (event.filter) {
|
||||
case 'Online':
|
||||
return device.online == true;
|
||||
@ -56,13 +67,64 @@ class DeviceManagementBloc
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}).toList();
|
||||
}).toList());
|
||||
|
||||
emit(DeviceManagementFiltered(
|
||||
filteredDevices: filteredDevices,
|
||||
filteredDevices: _filteredDevices,
|
||||
selectedIndex: _selectedIndex,
|
||||
onlineCount: _onlineCount,
|
||||
offlineCount: _offlineCount,
|
||||
lowBatteryCount: _lowBatteryCount,
|
||||
selectedDevice: _selectedDevices.isNotEmpty ? _selectedDevices : null,
|
||||
isControlButtonEnabled: _selectedDevices.isNotEmpty,
|
||||
));
|
||||
|
||||
if (currentProductName.isNotEmpty) {
|
||||
add(SearchDevices(productName: currentProductName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onResetFilters(
|
||||
ResetFilters event, Emitter<DeviceManagementState> emit) async {
|
||||
currentProductName = '';
|
||||
_selectedDevices.clear();
|
||||
_filteredDevices = List.from(_devices);
|
||||
_selectedIndex = 0;
|
||||
emit(DeviceManagementLoaded(
|
||||
devices: _devices,
|
||||
selectedIndex: 0,
|
||||
onlineCount: _onlineCount,
|
||||
offlineCount: _offlineCount,
|
||||
lowBatteryCount: _lowBatteryCount,
|
||||
selectedDevice: null,
|
||||
isControlButtonEnabled: false,
|
||||
));
|
||||
}
|
||||
|
||||
void _onResetSelectedDevices(
|
||||
ResetSelectedDevices event, Emitter<DeviceManagementState> emit) {
|
||||
_selectedDevices.clear();
|
||||
|
||||
if (state is DeviceManagementLoaded) {
|
||||
emit(DeviceManagementLoaded(
|
||||
devices: _devices,
|
||||
selectedIndex: _selectedIndex,
|
||||
onlineCount: _onlineCount,
|
||||
offlineCount: _offlineCount,
|
||||
lowBatteryCount: _lowBatteryCount,
|
||||
selectedDevice: null,
|
||||
isControlButtonEnabled: false,
|
||||
));
|
||||
} else if (state is DeviceManagementFiltered) {
|
||||
emit(DeviceManagementFiltered(
|
||||
filteredDevices: (state as DeviceManagementFiltered).filteredDevices,
|
||||
selectedIndex: _selectedIndex,
|
||||
onlineCount: _onlineCount,
|
||||
offlineCount: _offlineCount,
|
||||
lowBatteryCount: _lowBatteryCount,
|
||||
selectedDevice: null,
|
||||
isControlButtonEnabled: false,
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -75,13 +137,18 @@ class DeviceManagementBloc
|
||||
|
||||
void _onSelectDevice(
|
||||
SelectDevice event, Emitter<DeviceManagementState> emit) {
|
||||
if (_selectedDevices.contains(event.selectedDevice)) {
|
||||
_selectedDevices.remove(event.selectedDevice);
|
||||
final selectedUuid = event.selectedDevice.uuid;
|
||||
|
||||
if (_selectedDevices.any((device) => device.uuid == selectedUuid)) {
|
||||
_selectedDevices.removeWhere((device) => device.uuid == selectedUuid);
|
||||
} else {
|
||||
_selectedDevices.add(event.selectedDevice);
|
||||
}
|
||||
|
||||
bool isControlButtonEnabled = _selectedDevices.length == 1;
|
||||
List<AllDevicesModel> clonedSelectedDevices = List.from(_selectedDevices);
|
||||
|
||||
bool isControlButtonEnabled =
|
||||
_checkIfControlButtonEnabled(clonedSelectedDevices);
|
||||
|
||||
if (state is DeviceManagementLoaded) {
|
||||
emit(DeviceManagementLoaded(
|
||||
@ -90,7 +157,9 @@ class DeviceManagementBloc
|
||||
onlineCount: _onlineCount,
|
||||
offlineCount: _offlineCount,
|
||||
lowBatteryCount: _lowBatteryCount,
|
||||
selectedDevice: isControlButtonEnabled ? _selectedDevices.first : null,
|
||||
selectedDevice:
|
||||
clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null,
|
||||
isControlButtonEnabled: isControlButtonEnabled,
|
||||
));
|
||||
} else if (state is DeviceManagementFiltered) {
|
||||
emit(DeviceManagementFiltered(
|
||||
@ -99,11 +168,66 @@ class DeviceManagementBloc
|
||||
onlineCount: _onlineCount,
|
||||
offlineCount: _offlineCount,
|
||||
lowBatteryCount: _lowBatteryCount,
|
||||
selectedDevice: isControlButtonEnabled ? _selectedDevices.first : null,
|
||||
selectedDevice:
|
||||
clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null,
|
||||
isControlButtonEnabled: isControlButtonEnabled,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void _onUpdateSelection(
|
||||
UpdateSelection event, Emitter<DeviceManagementState> emit) {
|
||||
List<AllDevicesModel> selectedDevices = [];
|
||||
List<AllDevicesModel> devicesToSelectFrom = [];
|
||||
|
||||
if (state is DeviceManagementLoaded) {
|
||||
devicesToSelectFrom = (state as DeviceManagementLoaded).devices;
|
||||
} else if (state is DeviceManagementFiltered) {
|
||||
devicesToSelectFrom = (state as DeviceManagementFiltered).filteredDevices;
|
||||
}
|
||||
|
||||
for (int i = 0; i < event.selectedRows.length; i++) {
|
||||
if (event.selectedRows[i]) {
|
||||
selectedDevices.add(devicesToSelectFrom[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (state is DeviceManagementLoaded) {
|
||||
final loadedState = state as DeviceManagementLoaded;
|
||||
emit(DeviceManagementLoaded(
|
||||
devices: loadedState.devices,
|
||||
selectedIndex: loadedState.selectedIndex,
|
||||
onlineCount: loadedState.onlineCount,
|
||||
offlineCount: loadedState.offlineCount,
|
||||
lowBatteryCount: loadedState.lowBatteryCount,
|
||||
selectedDevice: selectedDevices,
|
||||
isControlButtonEnabled: _checkIfControlButtonEnabled(selectedDevices),
|
||||
));
|
||||
} else if (state is DeviceManagementFiltered) {
|
||||
final filteredState = state as DeviceManagementFiltered;
|
||||
emit(DeviceManagementFiltered(
|
||||
filteredDevices: filteredState.filteredDevices,
|
||||
selectedIndex: filteredState.selectedIndex,
|
||||
onlineCount: filteredState.onlineCount,
|
||||
offlineCount: filteredState.offlineCount,
|
||||
lowBatteryCount: filteredState.lowBatteryCount,
|
||||
selectedDevice: selectedDevices,
|
||||
isControlButtonEnabled: _checkIfControlButtonEnabled(selectedDevices),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
bool _checkIfControlButtonEnabled(List<AllDevicesModel> selectedDevices) {
|
||||
if (selectedDevices.length > 1) {
|
||||
final productTypes =
|
||||
selectedDevices.map((device) => device.productType).toSet();
|
||||
return productTypes.length == 1;
|
||||
} else if (selectedDevices.length == 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void _calculateDeviceCounts() {
|
||||
_onlineCount = _devices.where((device) => device.online == true).length;
|
||||
_offlineCount = _devices.where((device) => device.online == false).length;
|
||||
@ -128,27 +252,61 @@ class DeviceManagementBloc
|
||||
|
||||
void _onSearchDevices(
|
||||
SearchDevices event, Emitter<DeviceManagementState> emit) {
|
||||
if (_devices.isNotEmpty) {
|
||||
final filteredDevices = _devices.where((device) {
|
||||
if ((event.community == null || event.community!.isEmpty) &&
|
||||
(event.unitName == null || event.unitName!.isEmpty) &&
|
||||
(event.productName == null || event.productName!.isEmpty)) {
|
||||
currentProductName = '';
|
||||
if (state is DeviceManagementFiltered) {
|
||||
add(FilterDevices(_getFilterFromIndex(_selectedIndex)));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.productName == currentProductName &&
|
||||
event.community == currentCommunity &&
|
||||
event.unitName == currentUnitName &&
|
||||
event.searchField) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentProductName = event.productName ?? '';
|
||||
currentCommunity = event.community;
|
||||
currentUnitName = event.unitName;
|
||||
|
||||
List<AllDevicesModel> devicesToSearch = _filteredDevices;
|
||||
|
||||
if (devicesToSearch.isNotEmpty) {
|
||||
final filteredDevices = devicesToSearch.where((device) {
|
||||
final matchesCommunity = event.community == null ||
|
||||
event.community!.isEmpty ||
|
||||
(device.room?.name
|
||||
(device.community?.name
|
||||
?.toLowerCase()
|
||||
.contains(event.community!.toLowerCase()) ??
|
||||
false);
|
||||
final matchesUnit = event.unitName == null ||
|
||||
event.unitName!.isEmpty ||
|
||||
(device.unit?.name
|
||||
?.toLowerCase()
|
||||
.contains(event.unitName!.toLowerCase()) ??
|
||||
false);
|
||||
(device.spaces != null &&
|
||||
device.spaces!.isNotEmpty &&
|
||||
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);
|
||||
return matchesCommunity && matchesUnit && matchesProductName;
|
||||
final matchesDeviceName = event.productName == null ||
|
||||
event.productName!.isEmpty ||
|
||||
(device.categoryName
|
||||
?.toLowerCase()
|
||||
.contains(event.productName!.toLowerCase()) ??
|
||||
false);
|
||||
|
||||
return matchesCommunity &&
|
||||
matchesUnit &&
|
||||
(matchesProductName || matchesDeviceName);
|
||||
}).toList();
|
||||
|
||||
emit(DeviceManagementFiltered(
|
||||
@ -157,6 +315,8 @@ class DeviceManagementBloc
|
||||
onlineCount: _onlineCount,
|
||||
offlineCount: _offlineCount,
|
||||
lowBatteryCount: _lowBatteryCount,
|
||||
selectedDevice: null,
|
||||
isControlButtonEnabled: false,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,11 +31,13 @@ class SearchDevices extends DeviceManagementEvent {
|
||||
final String? community;
|
||||
final String? unitName;
|
||||
final String? productName;
|
||||
final bool searchField;
|
||||
|
||||
const SearchDevices({
|
||||
this.community,
|
||||
this.unitName,
|
||||
this.productName,
|
||||
this.searchField = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -50,3 +52,13 @@ class SelectDevice extends DeviceManagementEvent {
|
||||
@override
|
||||
List<Object?> get props => [selectedDevice];
|
||||
}
|
||||
|
||||
class ResetFilters extends DeviceManagementEvent {}
|
||||
|
||||
class ResetSelectedDevices extends DeviceManagementEvent {}
|
||||
|
||||
class UpdateSelection extends DeviceManagementEvent {
|
||||
final List<bool> selectedRows;
|
||||
|
||||
const UpdateSelection(this.selectedRows);
|
||||
}
|
||||
|
||||
@ -17,7 +17,8 @@ class DeviceManagementLoaded extends DeviceManagementState {
|
||||
final int onlineCount;
|
||||
final int offlineCount;
|
||||
final int lowBatteryCount;
|
||||
final AllDevicesModel? selectedDevice;
|
||||
final List<AllDevicesModel>? selectedDevice;
|
||||
final bool isControlButtonEnabled;
|
||||
|
||||
const DeviceManagementLoaded({
|
||||
required this.devices,
|
||||
@ -26,6 +27,7 @@ class DeviceManagementLoaded extends DeviceManagementState {
|
||||
required this.offlineCount,
|
||||
required this.lowBatteryCount,
|
||||
this.selectedDevice,
|
||||
required this.isControlButtonEnabled,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -35,7 +37,8 @@ class DeviceManagementLoaded extends DeviceManagementState {
|
||||
onlineCount,
|
||||
offlineCount,
|
||||
lowBatteryCount,
|
||||
selectedDevice
|
||||
selectedDevice,
|
||||
isControlButtonEnabled
|
||||
];
|
||||
}
|
||||
|
||||
@ -45,7 +48,8 @@ class DeviceManagementFiltered extends DeviceManagementState {
|
||||
final int onlineCount;
|
||||
final int offlineCount;
|
||||
final int lowBatteryCount;
|
||||
final AllDevicesModel? selectedDevice;
|
||||
final List<AllDevicesModel>? selectedDevice;
|
||||
final bool isControlButtonEnabled;
|
||||
|
||||
const DeviceManagementFiltered({
|
||||
required this.filteredDevices,
|
||||
@ -54,6 +58,7 @@ class DeviceManagementFiltered extends DeviceManagementState {
|
||||
required this.offlineCount,
|
||||
required this.lowBatteryCount,
|
||||
this.selectedDevice,
|
||||
required this.isControlButtonEnabled,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -63,7 +68,8 @@ class DeviceManagementFiltered extends DeviceManagementState {
|
||||
onlineCount,
|
||||
offlineCount,
|
||||
lowBatteryCount,
|
||||
selectedDevice
|
||||
selectedDevice,
|
||||
isControlButtonEnabled
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -1,34 +1,199 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/view/ac_device_batch_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/view/ac_device_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_status_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_status_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_batch_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/living_room_switch/view/living_room_device_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_sensor_batch_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_clamp_batch_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/power_clamp/view/smart_power_device_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/sos/view/sos_batch_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/sos/view/sos_device_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_batch_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_device_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_batch_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/water_heater/view/water_heater_batch_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/water_heater/view/water_heater_device_control.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/water_leak/view/water_leak_batch_control_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/water_leak/view/water_leak_control_view.dart';
|
||||
|
||||
import '../../one_g_glass_switch/view/one_gang_glass_switch_control_view.dart';
|
||||
|
||||
mixin RouteControlsBasedCode {
|
||||
Widget routeControlsWidgets({required AllDevicesModel device}) {
|
||||
switch (device.productType) {
|
||||
case '1G':
|
||||
return WallLightDeviceControl(
|
||||
deviceId: device.uuid!,
|
||||
);
|
||||
case '2G':
|
||||
return TwoGangDeviceControlView(
|
||||
deviceId: device.uuid!,
|
||||
);
|
||||
case '3G':
|
||||
return LivingRoomDeviceControl(
|
||||
return LivingRoomDeviceControlsView(
|
||||
deviceId: device.uuid!,
|
||||
);
|
||||
case '1GT':
|
||||
return OneGangGlassSwitchControlView(
|
||||
deviceId: device.uuid!,
|
||||
);
|
||||
case '2GT':
|
||||
return TwoGangGlassSwitchControlView(
|
||||
deviceId: device.uuid!,
|
||||
);
|
||||
case '3GT':
|
||||
return ThreeGangGlassSwitchControlView(
|
||||
deviceId: device.uuid!,
|
||||
);
|
||||
case 'GW':
|
||||
return GateWayControls(
|
||||
return GateWayControlsView(
|
||||
gatewayId: device.uuid!,
|
||||
);
|
||||
case 'DL':
|
||||
return DoorLockView(device: device);
|
||||
return DoorLockControlsView(device: device);
|
||||
case 'WPS':
|
||||
return WallSensorControls(device: device);
|
||||
return WallSensorControlsView(device: device);
|
||||
case 'CPS':
|
||||
return CeilingSensorControls(
|
||||
return CeilingSensorControlsView(
|
||||
device: device,
|
||||
);
|
||||
case 'CUR':
|
||||
return CurtainStatusControlsView(
|
||||
deviceId: device.uuid!,
|
||||
);
|
||||
case 'AC':
|
||||
return AcDeviceControl(device: device);
|
||||
return AcDeviceControlsView(device: device);
|
||||
case 'WH':
|
||||
return WaterHeaterDeviceControlView(
|
||||
device: device,
|
||||
);
|
||||
case 'DS':
|
||||
return MainDoorSensorControlView(device: device);
|
||||
case 'GD':
|
||||
return GarageDoorControlView(
|
||||
deviceId: device.uuid!,
|
||||
);
|
||||
case 'WL':
|
||||
return WaterLeakView(
|
||||
deviceId: device.uuid!,
|
||||
);
|
||||
case 'PC':
|
||||
return SmartPowerDeviceControl(
|
||||
deviceId: device.uuid!,
|
||||
);
|
||||
case 'SOS':
|
||||
return SosDeviceControlsView(device: device);
|
||||
default:
|
||||
return const SizedBox();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
3G:
|
||||
1G:
|
||||
2G:
|
||||
GW:
|
||||
DL:
|
||||
WPS:
|
||||
CPS:
|
||||
AC:
|
||||
CUR:
|
||||
WH:
|
||||
DS:
|
||||
*/
|
||||
|
||||
Widget routeBatchControlsWidgets({required List<AllDevicesModel> devices}) {
|
||||
switch (devices.first.productType) {
|
||||
case '1G':
|
||||
return WallLightBatchControlView(
|
||||
deviceIds: devices.where((e) => (e.productType == '1G')).map((e) => e.uuid!).toList(),
|
||||
);
|
||||
case '2G':
|
||||
return TwoGangBatchControlView(
|
||||
deviceIds: devices.where((e) => (e.productType == '2G')).map((e) => e.uuid!).toList(),
|
||||
);
|
||||
case '3G':
|
||||
return LivingRoomBatchControlsView(
|
||||
deviceIds: devices.where((e) => (e.productType == '3G')).map((e) => e.uuid!).toList(),
|
||||
);
|
||||
case '1GT':
|
||||
return OneGangGlassSwitchBatchControlView(
|
||||
deviceIds: devices.where((e) => (e.productType == '1GT')).map((e) => e.uuid!).toList(),
|
||||
);
|
||||
case '2GT':
|
||||
return TwoGangGlassSwitchBatchControlView(
|
||||
deviceIds: devices.where((e) => (e.productType == '2GT')).map((e) => e.uuid!).toList(),
|
||||
);
|
||||
case '3GT':
|
||||
return ThreeGangGlassSwitchBatchControlView(
|
||||
deviceIds: devices.where((e) => (e.productType == '3GT')).map((e) => e.uuid!).toList(),
|
||||
);
|
||||
case 'GW':
|
||||
return GatewayBatchControlView(
|
||||
gatewayIds: devices.where((e) => (e.productType == 'GW')).map((e) => e.uuid!).toList(),
|
||||
);
|
||||
case 'DL':
|
||||
return DoorLockBatchControlView(
|
||||
devicesIds: devices.where((e) => (e.productType == 'DL')).map((e) => e.uuid!).toList());
|
||||
case 'WPS':
|
||||
return WallSensorBatchControlView(
|
||||
devicesIds: devices.where((e) => (e.productType == 'WPS')).map((e) => e.uuid!).toList());
|
||||
case 'CPS':
|
||||
return CeilingSensorBatchControlView(
|
||||
devicesIds: devices.where((e) => (e.productType == 'CPS')).map((e) => e.uuid!).toList(),
|
||||
);
|
||||
case 'CUR':
|
||||
return CurtainBatchStatusView(
|
||||
devicesIds: devices.where((e) => (e.productType == 'CUR')).map((e) => e.uuid!).toList(),
|
||||
);
|
||||
case 'AC':
|
||||
return AcDeviceBatchControlView(
|
||||
devicesIds: devices.where((e) => (e.productType == 'AC')).map((e) => e.uuid!).toList());
|
||||
case 'WH':
|
||||
return WaterHEaterBatchControlView(
|
||||
deviceIds: devices.where((e) => (e.productType == 'WH')).map((e) => e.uuid!).toList(),
|
||||
);
|
||||
case 'DS':
|
||||
return MainDoorSensorBatchView(
|
||||
devicesIds: devices.where((e) => (e.productType == 'DS')).map((e) => e.uuid!).toList(),
|
||||
);
|
||||
case 'GD':
|
||||
return GarageDoorBatchControlView(
|
||||
deviceIds: devices.where((e) => (e.productType == 'GD')).map((e) => e.uuid!).toList(),
|
||||
);
|
||||
case 'WL':
|
||||
return WaterLeakBatchControlView(
|
||||
deviceIds: devices.where((e) => (e.productType == 'WL')).map((e) => e.uuid!).toList(),
|
||||
);
|
||||
case 'PC':
|
||||
return PowerClampBatchControlView(
|
||||
deviceIds: devices.where((e) => (e.productType == 'PC')).map((e) => e.uuid!).toList(),
|
||||
);
|
||||
case 'SOS':
|
||||
return SOSBatchControlView(
|
||||
deviceIds: devices.where((e) => (e.productType == 'SOS')).map((e) => e.uuid!).toList(),
|
||||
);
|
||||
default:
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
class DeviceCommunityModel {
|
||||
String? uuid;
|
||||
String? name;
|
||||
|
||||
DeviceCommunityModel({this.uuid, this.name});
|
||||
|
||||
DeviceCommunityModel.fromJson(Map<String, dynamic> json) {
|
||||
uuid = json['uuid']?.toString();
|
||||
name = json['name']?.toString();
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['uuid'] = uuid;
|
||||
data['name'] = name;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@ -13,11 +13,15 @@ class DeviceReport {
|
||||
|
||||
DeviceReport.fromJson(Map<String, dynamic> json)
|
||||
: deviceUuid = json['deviceUuid'] as String?,
|
||||
startTime = json['startTime'] as int?,
|
||||
endTime = json['endTime'] as int?,
|
||||
data = (json['data'] as List<dynamic>?)
|
||||
?.map((e) => DeviceEvent.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
startTime = int.tryParse(json['startTime'].toString()) ??
|
||||
json['startTime'] as int?,
|
||||
endTime =
|
||||
int.tryParse(json['endTime'].toString()) ?? json['endTime'] as int?,
|
||||
data = json['data'] != null
|
||||
? (json['data'] as List<dynamic>?)
|
||||
?.map((e) => DeviceEvent.fromJson(e as Map<String, dynamic>))
|
||||
.toList()
|
||||
: [];
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'deviceUuid': deviceUuid,
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
class DeviceSpaceModel {
|
||||
String? uuid;
|
||||
String? spaceName;
|
||||
|
||||
DeviceSpaceModel({this.uuid, this.spaceName});
|
||||
|
||||
DeviceSpaceModel.fromJson(Map<String, dynamic> json) {
|
||||
uuid = json['uuid']?.toString();
|
||||
spaceName = json['spaceName']?.toString();
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['uuid'] = uuid;
|
||||
data['spaceName'] = spaceName;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,8 @@
|
||||
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/room.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class AllDevicesModel {
|
||||
/*
|
||||
@ -39,6 +42,7 @@ class AllDevicesModel {
|
||||
|
||||
DevicesModelRoom? room;
|
||||
DevicesModelUnit? unit;
|
||||
DeviceCommunityModel? community;
|
||||
String? productUuid;
|
||||
String? productType;
|
||||
String? permissionType;
|
||||
@ -62,10 +66,13 @@ class AllDevicesModel {
|
||||
int? updateTime;
|
||||
String? uuid;
|
||||
int? batteryLevel;
|
||||
String? productName;
|
||||
List<DeviceSpaceModel>? spaces;
|
||||
|
||||
AllDevicesModel({
|
||||
this.room,
|
||||
this.unit,
|
||||
this.community,
|
||||
this.productUuid,
|
||||
this.productType,
|
||||
this.permissionType,
|
||||
@ -89,6 +96,8 @@ class AllDevicesModel {
|
||||
this.updateTime,
|
||||
this.uuid,
|
||||
this.batteryLevel,
|
||||
this.productName,
|
||||
this.spaces,
|
||||
});
|
||||
AllDevicesModel.fromJson(Map<String, dynamic> json) {
|
||||
room = (json['room'] != null && (json['room'] is Map))
|
||||
@ -97,6 +106,9 @@ class AllDevicesModel {
|
||||
unit = (json['unit'] != null && (json['unit'] is Map))
|
||||
? DevicesModelUnit.fromJson(json['unit'])
|
||||
: null;
|
||||
community = (json['community'] != null && (json['community'] is Map))
|
||||
? DeviceCommunityModel.fromJson(json['community'])
|
||||
: null;
|
||||
productUuid = json['productUuid']?.toString();
|
||||
productType = json['productType']?.toString();
|
||||
permissionType = json['permissionType']?.toString();
|
||||
@ -105,7 +117,7 @@ class AllDevicesModel {
|
||||
categoryName = json['categoryName']?.toString();
|
||||
createTime = int.tryParse(json['createTime']?.toString() ?? '');
|
||||
gatewayId = json['gatewayId']?.toString();
|
||||
icon = json['icon']?.toString();
|
||||
icon = json['icon'] ?? _getDefaultIcon(productType);
|
||||
ip = json['ip']?.toString();
|
||||
lat = json['lat']?.toString();
|
||||
localKey = json['localKey']?.toString();
|
||||
@ -119,8 +131,43 @@ class AllDevicesModel {
|
||||
timeZone = json['timeZone']?.toString();
|
||||
updateTime = int.tryParse(json['updateTime']?.toString() ?? '');
|
||||
uuid = json['uuid']?.toString();
|
||||
batteryLevel = int.tryParse(json['batteryLevel']?.toString() ?? '');
|
||||
batteryLevel = int.tryParse(json['battery']?.toString() ?? '');
|
||||
productName = json['productName']?.toString();
|
||||
if (json['spaces'] != null && json['spaces'] is List) {
|
||||
spaces = (json['spaces'] as List)
|
||||
.map((space) => DeviceSpaceModel.fromJson(space))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
String _getDefaultIcon(String? productType) {
|
||||
switch (productType) {
|
||||
case 'LightBulb':
|
||||
return Assets.lightBulb;
|
||||
case 'CeilingSensor':
|
||||
case 'WallSensor':
|
||||
return Assets.sensors;
|
||||
case 'AC':
|
||||
return Assets.ac;
|
||||
case 'DoorLock':
|
||||
return Assets.doorLock;
|
||||
case 'Curtain':
|
||||
return Assets.curtain;
|
||||
case '3G':
|
||||
case '2G':
|
||||
case '1G':
|
||||
return Assets.gangSwitch;
|
||||
case 'Gateway':
|
||||
return Assets.gateway;
|
||||
case 'WH':
|
||||
return Assets.blackLogo;
|
||||
case 'DS':
|
||||
return Assets.sensors;
|
||||
default:
|
||||
return Assets.logo;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (room != null) {
|
||||
@ -129,6 +176,9 @@ class AllDevicesModel {
|
||||
if (unit != null) {
|
||||
data['unit'] = unit!.toJson();
|
||||
}
|
||||
if (community != null) {
|
||||
data['community'] = community!.toJson();
|
||||
}
|
||||
data['productUuid'] = productUuid;
|
||||
data['productType'] = productType;
|
||||
data['permissionType'] = permissionType;
|
||||
@ -151,7 +201,74 @@ class AllDevicesModel {
|
||||
data['timeZone'] = timeZone;
|
||||
data['updateTime'] = updateTime;
|
||||
data['uuid'] = uuid;
|
||||
data['batteryLevel'] = batteryLevel;
|
||||
data['battery'] = batteryLevel;
|
||||
data['productName'] = productName;
|
||||
if (spaces != null) {
|
||||
data['spaces'] = spaces!.map((space) => space.toJson()).toList();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is AllDevicesModel &&
|
||||
other.room == room &&
|
||||
other.unit == unit &&
|
||||
other.productUuid == productUuid &&
|
||||
other.productType == productType &&
|
||||
other.permissionType == permissionType &&
|
||||
other.activeTime == activeTime &&
|
||||
other.category == category &&
|
||||
other.categoryName == categoryName &&
|
||||
other.createTime == createTime &&
|
||||
other.gatewayId == gatewayId &&
|
||||
other.icon == icon &&
|
||||
other.ip == ip &&
|
||||
other.lat == lat &&
|
||||
other.localKey == localKey &&
|
||||
other.lon == lon &&
|
||||
other.model == model &&
|
||||
other.name == name &&
|
||||
other.nodeId == nodeId &&
|
||||
other.online == online &&
|
||||
other.ownerId == ownerId &&
|
||||
other.sub == sub &&
|
||||
other.timeZone == timeZone &&
|
||||
other.updateTime == updateTime &&
|
||||
other.uuid == uuid &&
|
||||
other.productName == productName &&
|
||||
other.batteryLevel == batteryLevel;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return room.hashCode ^
|
||||
unit.hashCode ^
|
||||
productUuid.hashCode ^
|
||||
productType.hashCode ^
|
||||
permissionType.hashCode ^
|
||||
activeTime.hashCode ^
|
||||
category.hashCode ^
|
||||
categoryName.hashCode ^
|
||||
createTime.hashCode ^
|
||||
gatewayId.hashCode ^
|
||||
icon.hashCode ^
|
||||
ip.hashCode ^
|
||||
lat.hashCode ^
|
||||
localKey.hashCode ^
|
||||
lon.hashCode ^
|
||||
model.hashCode ^
|
||||
name.hashCode ^
|
||||
nodeId.hashCode ^
|
||||
online.hashCode ^
|
||||
ownerId.hashCode ^
|
||||
sub.hashCode ^
|
||||
timeZone.hashCode ^
|
||||
updateTime.hashCode ^
|
||||
uuid.hashCode ^
|
||||
productName.hashCode ^
|
||||
batteryLevel.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,55 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class FactoryResetModel {
|
||||
final List<String> devicesUuid;
|
||||
|
||||
FactoryResetModel({
|
||||
required this.devicesUuid,
|
||||
});
|
||||
|
||||
factory FactoryResetModel.fromJson(Map<String, dynamic> json) {
|
||||
return FactoryResetModel(
|
||||
devicesUuid: List<String>.from(json['devicesUuid']),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'devicesUuid': devicesUuid,
|
||||
};
|
||||
}
|
||||
|
||||
FactoryResetModel copyWith({
|
||||
List<String>? devicesUuid,
|
||||
}) {
|
||||
return FactoryResetModel(
|
||||
devicesUuid: devicesUuid ?? this.devicesUuid,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'devicesUuid': devicesUuid,
|
||||
};
|
||||
}
|
||||
|
||||
factory FactoryResetModel.fromMap(Map<String, dynamic> map) {
|
||||
return FactoryResetModel(
|
||||
devicesUuid: List<String>.from(map['devicesUuid']),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'FactoryReset(devicesUuid: $devicesUuid)';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is FactoryResetModel &&
|
||||
listEquals(other.devicesUuid, devicesUuid);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => devicesUuid.hashCode;
|
||||
}
|
||||
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
|
||||
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
@ -13,24 +14,25 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
|
||||
return BlocProvider(
|
||||
create: (context) => DeviceManagementBloc()..add(FetchDevices()),
|
||||
child: WebScaffold(
|
||||
appBarTitle: Text(
|
||||
'Device Management',
|
||||
style: Theme.of(context).textTheme.headlineLarge,
|
||||
appBarTitle: FittedBox(
|
||||
child: Text(
|
||||
'Device Management',
|
||||
style: Theme.of(context).textTheme.headlineLarge,
|
||||
),
|
||||
),
|
||||
enableMenuSideba: isLargeScreenSize(context),
|
||||
rightBody: const NavigateHomeGridView(),
|
||||
scaffoldBody: BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
|
||||
builder: (context, state) {
|
||||
if (state is DeviceManagementLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is DeviceManagementLoaded ||
|
||||
state is DeviceManagementFiltered) {
|
||||
} else if (state is DeviceManagementLoaded || state is DeviceManagementFiltered) {
|
||||
final devices = state is DeviceManagementLoaded
|
||||
? state.devices
|
||||
: (state as DeviceManagementFiltered).filteredDevices;
|
||||
|
||||
return DeviceManagementBody(devices: devices);
|
||||
} else {
|
||||
return const Center(child: Text('No Devices Found'));
|
||||
return const Center(child: Text('Error fetching Devices'));
|
||||
}
|
||||
},
|
||||
),
|
||||
@ -38,3 +40,6 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/core/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||
import 'package:syncrow_web/pages/common/custom_table.dart';
|
||||
import 'package:syncrow_web/pages/common/filter/filter_widget.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart';
|
||||
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/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';
|
||||
@ -27,6 +27,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
||||
int offlineCount = 0;
|
||||
int lowBatteryCount = 0;
|
||||
bool isControlButtonEnabled = false;
|
||||
List<AllDevicesModel> selectedDevices = [];
|
||||
|
||||
if (state is DeviceManagementLoaded) {
|
||||
devicesToShow = state.devices;
|
||||
@ -34,87 +35,108 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
||||
onlineCount = state.onlineCount;
|
||||
offlineCount = state.offlineCount;
|
||||
lowBatteryCount = state.lowBatteryCount;
|
||||
isControlButtonEnabled = state.selectedDevice != null;
|
||||
isControlButtonEnabled = state.isControlButtonEnabled;
|
||||
selectedDevices = state.selectedDevice ?? [];
|
||||
} else if (state is DeviceManagementFiltered) {
|
||||
devicesToShow = state.filteredDevices;
|
||||
selectedIndex = state.selectedIndex;
|
||||
onlineCount = state.onlineCount;
|
||||
offlineCount = state.offlineCount;
|
||||
lowBatteryCount = state.lowBatteryCount;
|
||||
isControlButtonEnabled = state.selectedDevice != null;
|
||||
isControlButtonEnabled = state.isControlButtonEnabled;
|
||||
selectedDevices = state.selectedDevice ?? [];
|
||||
} else if (state is DeviceManagementInitial) {
|
||||
devicesToShow = [];
|
||||
selectedIndex = 0;
|
||||
isControlButtonEnabled = false;
|
||||
}
|
||||
|
||||
final tabs = [
|
||||
'All (${devices.length})',
|
||||
'All',
|
||||
'Online ($onlineCount)',
|
||||
'Offline ($offlineCount)',
|
||||
'Low Battery ($lowBatteryCount)',
|
||||
];
|
||||
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
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: 43,
|
||||
width: isSmallScreenSize(context) ? double.infinity : 100,
|
||||
decoration: containerDecoration,
|
||||
child: Center(
|
||||
child: DefaultButton(
|
||||
onPressed: isControlButtonEnabled
|
||||
? () {
|
||||
final selectedDevice = context
|
||||
.read<DeviceManagementBloc>()
|
||||
.selectedDevices
|
||||
.first;
|
||||
final buttonLabel =
|
||||
(selectedDevices.length > 1) ? 'Batch Control' : 'Control';
|
||||
|
||||
return 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: selectedDevice),
|
||||
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(
|
||||
'Control',
|
||||
style: TextStyle(
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverFillRemaining(
|
||||
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];
|
||||
@ -123,28 +145,42 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
||||
.add(SelectDevice(selectedDevice));
|
||||
},
|
||||
withCheckBox: true,
|
||||
size: context.screenSize,
|
||||
size: MediaQuery.of(context).size,
|
||||
uuidIndex: 2,
|
||||
headers: const [
|
||||
'Device Name',
|
||||
'Product Name',
|
||||
'Device ID',
|
||||
'Unit Name',
|
||||
'Room',
|
||||
'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.categoryName ?? '',
|
||||
device.name ?? '',
|
||||
device.productName ?? '',
|
||||
device.uuid ?? '',
|
||||
device.unit?.name ?? '',
|
||||
device.room?.name ?? '',
|
||||
(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',
|
||||
@ -152,10 +188,20 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
||||
(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,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class DeviceSearchFilters extends StatefulWidget {
|
||||
const DeviceSearchFilters({super.key});
|
||||
@ -28,12 +29,12 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return isLargeScreenSize(context)
|
||||
return isExtraLargeScreenSize(context)
|
||||
? Row(
|
||||
children: [
|
||||
_buildSearchField("Community", communityController, 200),
|
||||
const SizedBox(width: 20),
|
||||
_buildSearchField("Unit Name", unitNameController, 200),
|
||||
_buildSearchField("Space Name", unitNameController, 200),
|
||||
const SizedBox(width: 20),
|
||||
_buildSearchField(
|
||||
"Device Name / Product Name", productNameController, 300),
|
||||
@ -45,10 +46,17 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
|
||||
spacing: 20,
|
||||
runSpacing: 10,
|
||||
children: [
|
||||
_buildSearchField("Community", communityController, 200),
|
||||
_buildSearchField("Unit Name", unitNameController, 200),
|
||||
_buildSearchField(
|
||||
"Device Name / Product Name", productNameController, 300),
|
||||
"Community",
|
||||
communityController,
|
||||
200,
|
||||
),
|
||||
_buildSearchField("Space Name", unitNameController, 200),
|
||||
_buildSearchField(
|
||||
"Device Name / Product Name",
|
||||
productNameController,
|
||||
300,
|
||||
),
|
||||
_buildSearchResetButtons(),
|
||||
],
|
||||
);
|
||||
@ -56,11 +64,20 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
|
||||
|
||||
Widget _buildSearchField(
|
||||
String title, TextEditingController controller, double width) {
|
||||
return StatefulTextField(
|
||||
title: title,
|
||||
width: width,
|
||||
elevation: 2,
|
||||
controller: controller,
|
||||
return Container(
|
||||
child: StatefulTextField(
|
||||
title: title,
|
||||
width: width,
|
||||
elevation: 2,
|
||||
controller: controller,
|
||||
onSubmitted: () {
|
||||
context.read<DeviceManagementBloc>().add(SearchDevices(
|
||||
productName: productNameController.text,
|
||||
unitName: unitNameController.text,
|
||||
community: communityController.text,
|
||||
searchField: true));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -68,16 +85,18 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
|
||||
return SearchResetButtons(
|
||||
onSearch: () {
|
||||
context.read<DeviceManagementBloc>().add(SearchDevices(
|
||||
community: communityController.text,
|
||||
unitName: unitNameController.text,
|
||||
productName: productNameController.text,
|
||||
));
|
||||
community: communityController.text,
|
||||
unitName: unitNameController.text,
|
||||
productName: productNameController.text,
|
||||
searchField: true));
|
||||
},
|
||||
onReset: () {
|
||||
communityController.clear();
|
||||
unitNameController.clear();
|
||||
productNameController.clear();
|
||||
context.read<DeviceManagementBloc>().add(FetchDevices());
|
||||
context.read<DeviceManagementBloc>()
|
||||
..add(ResetFilters())
|
||||
..add(FetchDevices());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,123 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/help_description.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
|
||||
class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
|
||||
final String deviceId;
|
||||
late CeilingSensorModel deviceStatus;
|
||||
Timer? _timer;
|
||||
|
||||
CeilingSensorBloc({required this.deviceId}) : super(CeilingInitialState()) {
|
||||
on<CeilingInitialEvent>(_fetchCeilingSensorStatus);
|
||||
on<CeilingChangeValueEvent>(_changeValue);
|
||||
on<GetCeilingDeviceReportsEvent>(_getDeviceReports);
|
||||
on<ShowCeilingDescriptionEvent>(_showDescription);
|
||||
on<BackToCeilingGridViewEvent>(_backToGridView);
|
||||
}
|
||||
|
||||
void _fetchCeilingSensorStatus(
|
||||
CeilingInitialEvent event, Emitter<CeilingSensorState> emit) async {
|
||||
emit(CeilingLoadingInitialState());
|
||||
try {
|
||||
var response = await DevicesManagementApi().getDeviceStatus(deviceId);
|
||||
deviceStatus = CeilingSensorModel.fromJson(response.status);
|
||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||
// _listenToChanges();
|
||||
} catch (e) {
|
||||
emit(CeilingFailedState(error: e.toString()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// _listenToChanges() {
|
||||
// try {
|
||||
// DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
// Stream<DatabaseEvent> stream = ref.onValue;
|
||||
|
||||
// stream.listen((DatabaseEvent event) {
|
||||
// Map<dynamic, dynamic> usersMap = event.snapshot.value as Map<dynamic, dynamic>;
|
||||
// List<StatusModel> statusList = [];
|
||||
|
||||
// usersMap['status'].forEach((element) {
|
||||
// statusList.add(StatusModel(code: element['code'], value: element['value']));
|
||||
// });
|
||||
|
||||
// deviceStatus = WallSensorModel.fromJson(statusList);
|
||||
// add(WallSensorUpdatedEvent());
|
||||
// });
|
||||
// } catch (_) {}
|
||||
// }
|
||||
|
||||
void _changeValue(
|
||||
CeilingChangeValueEvent event, Emitter<CeilingSensorState> emit) async {
|
||||
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
|
||||
if (event.code == 'sensitivity') {
|
||||
deviceStatus.sensitivity = event.value;
|
||||
} else if (event.code == 'none_body_time') {
|
||||
deviceStatus.noBodyTime = event.value;
|
||||
} else if (event.code == 'moving_max_dis') {
|
||||
deviceStatus.maxDistance = event.value;
|
||||
}
|
||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||
await _runDeBouncer(
|
||||
deviceId: deviceId, code: event.code, value: event.value);
|
||||
}
|
||||
|
||||
_runDeBouncer({
|
||||
required String deviceId,
|
||||
required String code,
|
||||
required dynamic value,
|
||||
}) {
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
_timer = Timer(const Duration(seconds: 1), () async {
|
||||
try {
|
||||
final response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: value));
|
||||
|
||||
if (!response) {
|
||||
add(CeilingInitialEvent());
|
||||
}
|
||||
} catch (_) {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
add(CeilingInitialEvent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
FutureOr<void> _getDeviceReports(GetCeilingDeviceReportsEvent event,
|
||||
Emitter<CeilingSensorState> emit) async {
|
||||
if (event.code.isEmpty) {
|
||||
emit(ShowCeilingDescriptionState(description: reportString));
|
||||
return;
|
||||
} else {
|
||||
emit(CeilingReportsLoadingState());
|
||||
|
||||
try {
|
||||
await DevicesManagementApi.getDeviceReports(deviceId, event.code)
|
||||
.then((value) {
|
||||
emit(CeilingReportsState(deviceReport: value));
|
||||
});
|
||||
} catch (e) {
|
||||
emit(CeilingReportsFailedState(error: e.toString()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _showDescription(
|
||||
ShowCeilingDescriptionEvent event, Emitter<CeilingSensorState> emit) {
|
||||
emit(ShowCeilingDescriptionState(description: event.description));
|
||||
}
|
||||
|
||||
void _backToGridView(
|
||||
BackToCeilingGridViewEvent event, Emitter<CeilingSensorState> emit) {
|
||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||
}
|
||||
}
|
||||
206
lib/pages/device_managment/ceiling_sensor/bloc/ceiling_bloc.dart
Normal file
206
lib/pages/device_managment/ceiling_sensor/bloc/ceiling_bloc.dart
Normal file
@ -0,0 +1,206 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart';
|
||||
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/ceiling_sensor/model/help_description.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
|
||||
class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
|
||||
final String deviceId;
|
||||
late CeilingSensorModel deviceStatus;
|
||||
Timer? _timer;
|
||||
|
||||
CeilingSensorBloc({required this.deviceId}) : super(CeilingInitialState()) {
|
||||
on<CeilingInitialEvent>(_fetchCeilingSensorStatus);
|
||||
on<CeilingFetchDeviceStatusEvent>(_fetchCeilingSensorBatchControl);
|
||||
on<CeilingChangeValueEvent>(_changeValue);
|
||||
on<CeilingBatchControlEvent>(_onBatchControl);
|
||||
on<GetCeilingDeviceReportsEvent>(_getDeviceReports);
|
||||
on<ShowCeilingDescriptionEvent>(_showDescription);
|
||||
on<BackToCeilingGridViewEvent>(_backToGridView);
|
||||
on<CeilingFactoryResetEvent>(_onFactoryReset);
|
||||
}
|
||||
|
||||
void _fetchCeilingSensorStatus(
|
||||
CeilingInitialEvent event, Emitter<CeilingSensorState> emit) async {
|
||||
emit(CeilingLoadingInitialState());
|
||||
try {
|
||||
var response = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
deviceStatus = CeilingSensorModel.fromJson(response.status);
|
||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||
// _listenToChanges();
|
||||
} catch (e) {
|
||||
emit(CeilingFailedState(error: e.toString()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// _listenToChanges() {
|
||||
// try {
|
||||
// DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
// Stream<DatabaseEvent> stream = ref.onValue;
|
||||
|
||||
// stream.listen((DatabaseEvent event) {
|
||||
// Map<dynamic, dynamic> usersMap = event.snapshot.value as Map<dynamic, dynamic>;
|
||||
// List<StatusModel> statusList = [];
|
||||
|
||||
// usersMap['status'].forEach((element) {
|
||||
// statusList.add(StatusModel(code: element['code'], value: element['value']));
|
||||
// });
|
||||
|
||||
// deviceStatus = WallSensorModel.fromJson(statusList);
|
||||
// add(WallSensorUpdatedEvent());
|
||||
// });
|
||||
// } catch (_) {}
|
||||
// }
|
||||
|
||||
void _changeValue(CeilingChangeValueEvent event, Emitter<CeilingSensorState> emit) async {
|
||||
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
|
||||
if (event.code == 'sensitivity') {
|
||||
deviceStatus.sensitivity = event.value;
|
||||
} else if (event.code == 'none_body_time') {
|
||||
deviceStatus.noBodyTime = event.value;
|
||||
} else if (event.code == 'moving_max_dis') {
|
||||
deviceStatus.maxDistance = event.value;
|
||||
} else if (event.code == 'scene') {
|
||||
deviceStatus.spaceType = getSpaceType(event.value);
|
||||
}
|
||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||
await _runDeBouncer(
|
||||
deviceId: deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
emit: emit,
|
||||
isBatch: false,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onBatchControl(
|
||||
CeilingBatchControlEvent event, Emitter<CeilingSensorState> emit) async {
|
||||
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
|
||||
if (event.code == 'sensitivity') {
|
||||
deviceStatus.sensitivity = event.value;
|
||||
} else if (event.code == 'none_body_time') {
|
||||
deviceStatus.noBodyTime = event.value;
|
||||
} else if (event.code == 'moving_max_dis') {
|
||||
deviceStatus.maxDistance = event.value;
|
||||
} else if (event.code == 'scene') {
|
||||
deviceStatus.spaceType = getSpaceType(event.value);
|
||||
}
|
||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||
await _runDeBouncer(
|
||||
deviceId: event.deviceIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
emit: emit,
|
||||
isBatch: true,
|
||||
);
|
||||
}
|
||||
|
||||
_runDeBouncer({
|
||||
required dynamic deviceId,
|
||||
required String code,
|
||||
required dynamic value,
|
||||
required Emitter<CeilingSensorState> emit,
|
||||
required bool isBatch,
|
||||
}) {
|
||||
late String id;
|
||||
|
||||
if (deviceId is List) {
|
||||
id = deviceId.first;
|
||||
} else {
|
||||
id = deviceId;
|
||||
}
|
||||
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
_timer = Timer(const Duration(seconds: 1), () async {
|
||||
try {
|
||||
late bool response;
|
||||
if (isBatch) {
|
||||
response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value);
|
||||
} else {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: value));
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
add(CeilingInitialEvent(id));
|
||||
}
|
||||
if (response == true && code == 'scene') {
|
||||
emit(CeilingLoadingInitialState());
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
add(CeilingInitialEvent(id));
|
||||
}
|
||||
} catch (_) {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
add(CeilingInitialEvent(id));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
FutureOr<void> _getDeviceReports(
|
||||
GetCeilingDeviceReportsEvent event, Emitter<CeilingSensorState> emit) async {
|
||||
if (event.code.isEmpty) {
|
||||
emit(ShowCeilingDescriptionState(description: reportString));
|
||||
return;
|
||||
} else {
|
||||
emit(CeilingReportsLoadingState());
|
||||
// final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch;
|
||||
// final to = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
try {
|
||||
// await DevicesManagementApi.getDeviceReportsByDate(deviceId, event.code, from.toString(), to.toString())
|
||||
await DevicesManagementApi.getDeviceReports(deviceId, event.code).then((value) {
|
||||
emit(CeilingReportsState(deviceReport: value));
|
||||
});
|
||||
} catch (e) {
|
||||
emit(CeilingReportsFailedState(error: e.toString()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _showDescription(ShowCeilingDescriptionEvent event, Emitter<CeilingSensorState> emit) {
|
||||
emit(ShowCeilingDescriptionState(description: event.description));
|
||||
}
|
||||
|
||||
void _backToGridView(BackToCeilingGridViewEvent event, Emitter<CeilingSensorState> emit) {
|
||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||
}
|
||||
|
||||
FutureOr<void> _fetchCeilingSensorBatchControl(
|
||||
CeilingFetchDeviceStatusEvent event, Emitter<CeilingSensorState> emit) async {
|
||||
emit(CeilingLoadingInitialState());
|
||||
try {
|
||||
var response = await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
deviceStatus = CeilingSensorModel.fromJson(response.status);
|
||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||
} catch (e) {
|
||||
emit(CeilingFailedState(error: e.toString()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onFactoryReset(
|
||||
CeilingFactoryResetEvent event, Emitter<CeilingSensorState> emit) async {
|
||||
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
|
||||
try {
|
||||
final response = await DevicesManagementApi().factoryReset(
|
||||
event.factoryResetModel,
|
||||
event.devicesId,
|
||||
);
|
||||
if (!response) {
|
||||
emit(const CeilingFailedState(error: 'Failed'));
|
||||
} else {
|
||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(CeilingFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
|
||||
abstract class CeilingSensorEvent extends Equatable {
|
||||
const CeilingSensorEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class CeilingInitialEvent extends CeilingSensorEvent {
|
||||
final String deviceId;
|
||||
const CeilingInitialEvent(this.deviceId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId];
|
||||
}
|
||||
|
||||
class CeilingFetchDeviceStatusEvent extends CeilingSensorEvent {
|
||||
final List<String> devicesIds;
|
||||
|
||||
const CeilingFetchDeviceStatusEvent(this.devicesIds);
|
||||
|
||||
@override
|
||||
List<Object> get props => [devicesIds];
|
||||
}
|
||||
|
||||
class CeilingBatchControlEvent extends CeilingSensorEvent {
|
||||
final List<String> deviceIds;
|
||||
final String code;
|
||||
final dynamic value;
|
||||
|
||||
const CeilingBatchControlEvent({
|
||||
required this.deviceIds,
|
||||
required this.code,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceIds, code, value];
|
||||
}
|
||||
|
||||
class CeilingChangeValueEvent extends CeilingSensorEvent {
|
||||
final dynamic value;
|
||||
final String code;
|
||||
const CeilingChangeValueEvent({required this.value, required this.code});
|
||||
|
||||
@override
|
||||
List<Object> get props => [value, code];
|
||||
}
|
||||
|
||||
class GetCeilingDeviceReportsEvent extends CeilingSensorEvent {
|
||||
final String code;
|
||||
final String deviceUuid;
|
||||
|
||||
const GetCeilingDeviceReportsEvent(
|
||||
{required this.code, required this.deviceUuid});
|
||||
|
||||
@override
|
||||
List<Object> get props => [code, deviceUuid];
|
||||
}
|
||||
|
||||
class ShowCeilingDescriptionEvent extends CeilingSensorEvent {
|
||||
final String description;
|
||||
|
||||
const ShowCeilingDescriptionEvent({required this.description});
|
||||
|
||||
@override
|
||||
List<Object> get props => [description];
|
||||
}
|
||||
|
||||
class BackToCeilingGridViewEvent extends CeilingSensorEvent {}
|
||||
|
||||
class CeilingFactoryResetEvent extends CeilingSensorEvent {
|
||||
final String devicesId;
|
||||
final FactoryResetModel factoryResetModel;
|
||||
|
||||
const CeilingFactoryResetEvent({
|
||||
required this.devicesId,
|
||||
required this.factoryResetModel,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [devicesId, factoryResetModel];
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class CeilingSensorEvent extends Equatable {
|
||||
const CeilingSensorEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class CeilingInitialEvent extends CeilingSensorEvent {}
|
||||
|
||||
class CeilingChangeValueEvent extends CeilingSensorEvent {
|
||||
final dynamic value;
|
||||
final String code;
|
||||
const CeilingChangeValueEvent({required this.value, required this.code});
|
||||
|
||||
@override
|
||||
List<Object> get props => [value, code];
|
||||
}
|
||||
|
||||
class GetCeilingDeviceReportsEvent extends CeilingSensorEvent {
|
||||
final String code;
|
||||
final String deviceUuid;
|
||||
|
||||
const GetCeilingDeviceReportsEvent(
|
||||
{required this.code, required this.deviceUuid});
|
||||
|
||||
@override
|
||||
List<Object> get props => [code, deviceUuid];
|
||||
}
|
||||
|
||||
class ShowCeilingDescriptionEvent extends CeilingSensorEvent {
|
||||
final String description;
|
||||
|
||||
const ShowCeilingDescriptionEvent({required this.description});
|
||||
|
||||
@override
|
||||
List<Object> get props => [description];
|
||||
}
|
||||
|
||||
class BackToCeilingGridViewEvent extends CeilingSensorEvent {}
|
||||
@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
|
||||
class CeilingSensorModel {
|
||||
@ -10,6 +11,7 @@ class CeilingSensorModel {
|
||||
String bodyMovement;
|
||||
String noBodyTime;
|
||||
int maxDistance;
|
||||
SpaceTypes spaceType;
|
||||
|
||||
CeilingSensorModel({
|
||||
required this.presenceState,
|
||||
@ -20,6 +22,7 @@ class CeilingSensorModel {
|
||||
required this.bodyMovement,
|
||||
required this.noBodyTime,
|
||||
required this.maxDistance,
|
||||
required this.spaceType,
|
||||
});
|
||||
|
||||
factory CeilingSensorModel.fromJson(List<Status> jsonList) {
|
||||
@ -31,6 +34,7 @@ class CeilingSensorModel {
|
||||
String _bodyMovement = 'none';
|
||||
String _noBodyTime = 'none';
|
||||
int _maxDis = 0;
|
||||
SpaceTypes _spaceType = SpaceTypes.none;
|
||||
|
||||
try {
|
||||
for (var status in jsonList) {
|
||||
@ -38,17 +42,23 @@ class CeilingSensorModel {
|
||||
case 'presence_state':
|
||||
_presenceState = status.value ?? 'none';
|
||||
break;
|
||||
case 'scene':
|
||||
_spaceType = getSpaceType(status.value ?? 'none');
|
||||
break;
|
||||
case 'sensitivity':
|
||||
_sensitivity = status.value is int ? status.value : int.tryParse(status.value ?? '1') ?? 1;
|
||||
_sensitivity =
|
||||
status.value is int ? status.value : int.tryParse(status.value ?? '1') ?? 1;
|
||||
break;
|
||||
case 'checking_result':
|
||||
_checkingResult = status.value ?? '';
|
||||
break;
|
||||
case 'presence_range':
|
||||
_presenceRange = status.value is int ? status.value : int.tryParse(status.value ?? '0') ?? 0;
|
||||
_presenceRange =
|
||||
status.value is int ? status.value : int.tryParse(status.value ?? '0') ?? 0;
|
||||
break;
|
||||
case 'sports_para':
|
||||
_sportsPara = status.value is int ? status.value : int.tryParse(status.value ?? '0') ?? 0;
|
||||
_sportsPara =
|
||||
status.value is int ? status.value : int.tryParse(status.value ?? '0') ?? 0;
|
||||
break;
|
||||
case 'body_movement':
|
||||
_bodyMovement = status.value ?? '';
|
||||
@ -74,6 +84,55 @@ class CeilingSensorModel {
|
||||
bodyMovement: _bodyMovement,
|
||||
noBodyTime: _noBodyTime,
|
||||
maxDistance: _maxDis,
|
||||
spaceType: _spaceType,
|
||||
);
|
||||
}
|
||||
|
||||
CeilingSensorModel copyWith({
|
||||
String? presenceState,
|
||||
int? sensitivity,
|
||||
String? checkingResult,
|
||||
int? presenceRange,
|
||||
int? sportsPara,
|
||||
String? bodyMovement,
|
||||
String? noBodyTime,
|
||||
int? maxDistance,
|
||||
SpaceTypes? spaceType,
|
||||
}) {
|
||||
return CeilingSensorModel(
|
||||
presenceState: presenceState ?? this.presenceState,
|
||||
sensitivity: sensitivity ?? this.sensitivity,
|
||||
checkingResult: checkingResult ?? this.checkingResult,
|
||||
presenceRange: presenceRange ?? this.presenceRange,
|
||||
sportsPara: sportsPara ?? this.sportsPara,
|
||||
bodyMovement: bodyMovement ?? this.bodyMovement,
|
||||
noBodyTime: noBodyTime ?? this.noBodyTime,
|
||||
maxDistance: maxDistance ?? this.maxDistance,
|
||||
spaceType: spaceType ?? this.spaceType,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum SpaceTypes {
|
||||
none,
|
||||
parlour,
|
||||
area,
|
||||
toilet,
|
||||
bedroom,
|
||||
}
|
||||
|
||||
SpaceTypes getSpaceType(String value) {
|
||||
switch (value) {
|
||||
case 'parlour':
|
||||
return SpaceTypes.parlour;
|
||||
case 'area':
|
||||
return SpaceTypes.area;
|
||||
case 'toilet':
|
||||
return SpaceTypes.toilet;
|
||||
case 'bedroom':
|
||||
return SpaceTypes.bedroom;
|
||||
case 'none':
|
||||
default:
|
||||
return SpaceTypes.none;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,127 @@
|
||||
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/ceiling_sensor/bloc/ceiling_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart';
|
||||
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/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';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class CeilingSensorBatchControlView extends StatelessWidget with HelperResponsiveLayout {
|
||||
const CeilingSensorBatchControlView({super.key, required this.devicesIds});
|
||||
|
||||
final List<String> devicesIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return BlocProvider(
|
||||
create: (context) => CeilingSensorBloc(deviceId: devicesIds.first)
|
||||
..add(CeilingFetchDeviceStatusEvent(devicesIds)),
|
||||
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
|
||||
builder: (context, state) {
|
||||
if (state is CeilingLoadingInitialState || state is CeilingReportsLoadingState) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is CeilingUpdateState) {
|
||||
return _buildGridView(
|
||||
context, state.ceilingSensorModel, isExtraLarge, isLarge, isMedium);
|
||||
}
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGridView(BuildContext context, CeilingSensorModel model, bool isExtraLarge,
|
||||
bool isLarge, bool isMedium) {
|
||||
return GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge || isExtraLarge
|
||||
? 3
|
||||
: isMedium
|
||||
? 2
|
||||
: 1,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
children: [
|
||||
PresenceSpaceType(
|
||||
description: 'Space Type',
|
||||
value: model.spaceType,
|
||||
action: (String value) => context.read<CeilingSensorBloc>().add(
|
||||
CeilingBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: 'scene',
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: model.sensitivity.toDouble(),
|
||||
title: 'Sensitivity:',
|
||||
minValue: 1,
|
||||
maxValue: 10,
|
||||
steps: 1,
|
||||
action: (int value) {
|
||||
context.read<CeilingSensorBloc>().add(
|
||||
CeilingBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: 'sensitivity',
|
||||
value: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: model.maxDistance.toDouble(),
|
||||
title: 'Maximum Distance:',
|
||||
minValue: 0,
|
||||
maxValue: 500,
|
||||
steps: 50,
|
||||
description: 'm',
|
||||
action: (int value) => context.read<CeilingSensorBloc>().add(
|
||||
CeilingBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: 'moving_max_dis',
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceNoBodyTime(
|
||||
value: model.noBodyTime,
|
||||
title: 'Nobody Time:',
|
||||
description: '',
|
||||
action: (String value) => context.read<CeilingSensorBloc>().add(
|
||||
CeilingBatchControlEvent(
|
||||
deviceIds: devicesIds,
|
||||
code: 'nobody_time',
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
FirmwareUpdateWidget(deviceId: devicesIds.first, version: 4),
|
||||
FactoryResetWidget(
|
||||
callFactoryReset: () {
|
||||
context.read<CeilingSensorBloc>().add(
|
||||
CeilingFactoryResetEvent(
|
||||
devicesId: devicesIds.first,
|
||||
factoryResetModel: FactoryResetModel(devicesUuid: devicesIds),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
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/device_managment/ceiling_sensor/bloc/bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart';
|
||||
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/sensors_widgets/presence_display_data.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_space_type.dart';
|
||||
@ -16,49 +16,44 @@ import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dar
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class CeilingSensorControls extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const CeilingSensorControls({super.key, required this.device});
|
||||
class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLayout {
|
||||
const CeilingSensorControlsView({super.key, required this.device});
|
||||
|
||||
final AllDevicesModel device;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return BlocProvider(
|
||||
create: (context) => CeilingSensorBloc(deviceId: device.uuid ?? '')
|
||||
..add(CeilingInitialEvent()),
|
||||
..add(CeilingInitialEvent(device.uuid ?? '')),
|
||||
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
|
||||
builder: (context, state) {
|
||||
if (state is CeilingLoadingInitialState ||
|
||||
state is CeilingReportsLoadingState) {
|
||||
if (state is CeilingLoadingInitialState || state is CeilingReportsLoadingState) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is CeilingUpdateState) {
|
||||
return _buildGridView(
|
||||
context, state.ceilingSensorModel, isLarge, isMedium);
|
||||
context, state.ceilingSensorModel, isExtraLarge, isLarge, isMedium);
|
||||
} else if (state is CeilingReportsState) {
|
||||
return ReportsTable(
|
||||
report: state.deviceReport,
|
||||
onRowTap: (index) {},
|
||||
onClose: () {
|
||||
context
|
||||
.read<CeilingSensorBloc>()
|
||||
.add(BackToCeilingGridViewEvent());
|
||||
context.read<CeilingSensorBloc>().add(BackToCeilingGridViewEvent());
|
||||
},
|
||||
);
|
||||
} else if (state is ShowCeilingDescriptionState) {
|
||||
return DescriptionView(
|
||||
description: state.description,
|
||||
onClose: () {
|
||||
context
|
||||
.read<CeilingSensorBloc>()
|
||||
.add(BackToCeilingGridViewEvent());
|
||||
context.read<CeilingSensorBloc>().add(BackToCeilingGridViewEvent());
|
||||
},
|
||||
);
|
||||
} else if (state is CeilingReportsFailedState) {
|
||||
final model = context.read<CeilingSensorBloc>().deviceStatus;
|
||||
return _buildGridView(context, model, isLarge, isMedium);
|
||||
return _buildGridView(context, model, isExtraLarge, isLarge, isMedium);
|
||||
}
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
},
|
||||
@ -66,14 +61,14 @@ class CeilingSensorControls extends StatelessWidget
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGridView(BuildContext context, CeilingSensorModel model,
|
||||
Widget _buildGridView(BuildContext context, CeilingSensorModel model, bool isExtraLarge,
|
||||
bool isLarge, bool isMedium) {
|
||||
return GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge
|
||||
crossAxisCount: isLarge || isExtraLarge
|
||||
? 3
|
||||
: isMedium
|
||||
? 2
|
||||
@ -96,21 +91,21 @@ class CeilingSensorControls extends StatelessWidget
|
||||
postfix: 'm',
|
||||
description: 'Detection Range',
|
||||
),
|
||||
const PresenceSpaceType(
|
||||
listOfIcons: [
|
||||
Assets.office,
|
||||
Assets.parlour,
|
||||
Assets.dyi,
|
||||
Assets.bathroom,
|
||||
Assets.bedroom,
|
||||
],
|
||||
PresenceSpaceType(
|
||||
description: 'Space Type',
|
||||
value: model.spaceType,
|
||||
action: (String value) => context.read<CeilingSensorBloc>().add(
|
||||
CeilingChangeValueEvent(
|
||||
code: 'scene',
|
||||
value: value,
|
||||
),
|
||||
),
|
||||
),
|
||||
PresenceUpdateData(
|
||||
value: model.sensitivity.toDouble(),
|
||||
title: 'Sensitivity:',
|
||||
minValue: 1,
|
||||
maxValue: 5,
|
||||
maxValue: 10,
|
||||
steps: 1,
|
||||
action: (int value) {
|
||||
context.read<CeilingSensorBloc>().add(
|
||||
@ -138,7 +133,7 @@ class CeilingSensorControls extends StatelessWidget
|
||||
PresenceNoBodyTime(
|
||||
value: model.noBodyTime,
|
||||
title: 'Nobody Time:',
|
||||
// description: 'hr',
|
||||
description: '',
|
||||
action: (String value) => context.read<CeilingSensorBloc>().add(
|
||||
CeilingChangeValueEvent(
|
||||
code: 'nobody_time',
|
||||
@ -148,8 +143,8 @@ class CeilingSensorControls extends StatelessWidget
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.read<CeilingSensorBloc>().add(GetCeilingDeviceReportsEvent(
|
||||
code: 'presence_state', deviceUuid: device.uuid!));
|
||||
context.read<CeilingSensorBloc>().add(
|
||||
GetCeilingDeviceReportsEvent(code: 'presence_state', deviceUuid: device.uuid!));
|
||||
},
|
||||
child: const PresenceStaticWidget(
|
||||
icon: Assets.illuminanceRecordIcon,
|
||||
@ -158,8 +153,9 @@ class CeilingSensorControls extends StatelessWidget
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.read<CeilingSensorBloc>().add(GetCeilingDeviceReportsEvent(
|
||||
code: '', deviceUuid: device.uuid!));
|
||||
context
|
||||
.read<CeilingSensorBloc>()
|
||||
.add(GetCeilingDeviceReportsEvent(code: '', deviceUuid: device.uuid!));
|
||||
},
|
||||
child: const PresenceStaticWidget(
|
||||
icon: Assets.helpDescriptionIcon,
|
||||
|
||||
161
lib/pages/device_managment/curtain/bloc/curtain_bloc.dart
Normal file
161
lib/pages/device_managment/curtain/bloc/curtain_bloc.dart
Normal file
@ -0,0 +1,161 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
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/services/devices_mang_api.dart';
|
||||
|
||||
class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
|
||||
late bool deviceStatus;
|
||||
final String deviceId;
|
||||
Timer? _timer;
|
||||
|
||||
CurtainBloc({required this.deviceId}) : super(CurtainInitial()) {
|
||||
on<CurtainFetchDeviceStatus>(_onFetchDeviceStatus);
|
||||
on<CurtainFetchBatchStatus>(_onFetchBatchStatus);
|
||||
on<CurtainControl>(_onCurtainControl);
|
||||
on<CurtainBatchControl>(_onCurtainBatchControl);
|
||||
on<CurtainFactoryReset>(_onFactoryReset);
|
||||
}
|
||||
|
||||
FutureOr<void> _onFetchDeviceStatus(
|
||||
CurtainFetchDeviceStatus event, Emitter<CurtainState> emit) async {
|
||||
emit(CurtainStatusLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
|
||||
deviceStatus = _checkStatus(status.status[0].value);
|
||||
|
||||
emit(CurtainStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(CurtainError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onCurtainControl(
|
||||
CurtainControl event, Emitter<CurtainState> emit) async {
|
||||
final oldValue = deviceStatus;
|
||||
|
||||
_updateLocalValue(event.value, emit);
|
||||
|
||||
emit(CurtainStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: false,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _runDebounce({
|
||||
required dynamic deviceId,
|
||||
required String code,
|
||||
required bool value,
|
||||
required bool oldValue,
|
||||
required Emitter<CurtainState> emit,
|
||||
required bool isBatch,
|
||||
}) async {
|
||||
late String id;
|
||||
|
||||
if (deviceId is List) {
|
||||
id = deviceId.first;
|
||||
} else {
|
||||
id = deviceId;
|
||||
}
|
||||
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
_timer = Timer(const Duration(seconds: 1), () async {
|
||||
try {
|
||||
final controlValue = value ? 'open' : 'close';
|
||||
|
||||
late bool response;
|
||||
if (isBatch) {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceBatchControl(deviceId, code, controlValue);
|
||||
} else {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: controlValue));
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
_revertValueAndEmit(id, oldValue, emit);
|
||||
}
|
||||
} catch (e) {
|
||||
_revertValueAndEmit(id, oldValue, emit);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _revertValueAndEmit(
|
||||
String deviceId, bool oldValue, Emitter<CurtainState> emit) {
|
||||
_updateLocalValue(oldValue, emit);
|
||||
emit(CurtainStatusLoaded(deviceStatus));
|
||||
emit(const CurtainControlError('Failed to control the device.'));
|
||||
}
|
||||
|
||||
void _updateLocalValue(bool value, Emitter<CurtainState> emit) {
|
||||
deviceStatus = value;
|
||||
emit(CurtainStatusLoaded(deviceStatus));
|
||||
}
|
||||
|
||||
bool _checkStatus(String command) {
|
||||
return command.toLowerCase() == 'open';
|
||||
}
|
||||
|
||||
FutureOr<void> _onFetchBatchStatus(
|
||||
CurtainFetchBatchStatus event, Emitter<CurtainState> emit) async {
|
||||
emit(CurtainStatusLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
|
||||
deviceStatus = _checkStatus(status.status[0].value);
|
||||
|
||||
emit(CurtainStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(CurtainError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onCurtainBatchControl(
|
||||
CurtainBatchControl event, Emitter<CurtainState> emit) async {
|
||||
final oldValue = deviceStatus;
|
||||
|
||||
_updateLocalValue(event.value, emit);
|
||||
|
||||
emit(CurtainStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.devicesIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: true,
|
||||
);
|
||||
}
|
||||
|
||||
FutureOr<void> _onFactoryReset(
|
||||
CurtainFactoryReset event, Emitter<CurtainState> emit) async {
|
||||
emit(CurtainStatusLoading());
|
||||
try {
|
||||
final response = await DevicesManagementApi().factoryReset(
|
||||
event.factoryReset,
|
||||
event.deviceId,
|
||||
);
|
||||
if (!response) {
|
||||
emit(const CurtainControlError('Failed'));
|
||||
} else {
|
||||
add(CurtainFetchDeviceStatus(event.deviceId));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(CurtainControlError(e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
62
lib/pages/device_managment/curtain/bloc/curtain_event.dart
Normal file
62
lib/pages/device_managment/curtain/bloc/curtain_event.dart
Normal file
@ -0,0 +1,62 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
|
||||
sealed class CurtainEvent extends Equatable {
|
||||
const CurtainEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class CurtainFetchDeviceStatus extends CurtainEvent {
|
||||
final String deviceId;
|
||||
|
||||
const CurtainFetchDeviceStatus(this.deviceId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId];
|
||||
}
|
||||
|
||||
class CurtainFetchBatchStatus extends CurtainEvent {
|
||||
final List<String> devicesIds;
|
||||
|
||||
const CurtainFetchBatchStatus(this.devicesIds);
|
||||
|
||||
@override
|
||||
List<Object> get props => [devicesIds];
|
||||
}
|
||||
|
||||
class CurtainControl extends CurtainEvent {
|
||||
final String deviceId;
|
||||
final String code;
|
||||
final bool value;
|
||||
|
||||
const CurtainControl(
|
||||
{required this.deviceId, required this.code, required this.value});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId, code, value];
|
||||
}
|
||||
|
||||
class CurtainBatchControl extends CurtainEvent {
|
||||
final List<String> devicesIds;
|
||||
final String code;
|
||||
final bool value;
|
||||
|
||||
const CurtainBatchControl(
|
||||
{required this.devicesIds, required this.code, required this.value});
|
||||
|
||||
@override
|
||||
List<Object> get props => [devicesIds, code, value];
|
||||
}
|
||||
|
||||
class CurtainFactoryReset extends CurtainEvent {
|
||||
final String deviceId;
|
||||
final FactoryResetModel factoryReset;
|
||||
|
||||
const CurtainFactoryReset(
|
||||
{required this.deviceId, required this.factoryReset});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId, factoryReset];
|
||||
}
|
||||
40
lib/pages/device_managment/curtain/bloc/curtain_state.dart
Normal file
40
lib/pages/device_managment/curtain/bloc/curtain_state.dart
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
sealed class CurtainState extends Equatable {
|
||||
const CurtainState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class CurtainInitial extends CurtainState {}
|
||||
|
||||
class CurtainStatusLoading extends CurtainState {}
|
||||
|
||||
class CurtainStatusLoaded extends CurtainState {
|
||||
final bool status;
|
||||
|
||||
const CurtainStatusLoaded(this.status);
|
||||
|
||||
@override
|
||||
List<Object> get props => [status];
|
||||
}
|
||||
|
||||
class CurtainError extends CurtainState {
|
||||
final String message;
|
||||
|
||||
const CurtainError(this.message);
|
||||
|
||||
@override
|
||||
List<Object> get props => [message];
|
||||
}
|
||||
|
||||
class CurtainControlError extends CurtainState {
|
||||
final String message;
|
||||
|
||||
const CurtainControlError(this.message);
|
||||
|
||||
@override
|
||||
List<Object> get props => [message];
|
||||
}
|
||||
32
lib/pages/device_managment/curtain/model/curtain_model.dart
Normal file
32
lib/pages/device_managment/curtain/model/curtain_model.dart
Normal file
@ -0,0 +1,32 @@
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
|
||||
class CurtainModel {
|
||||
final String productUuid;
|
||||
final String productType;
|
||||
final List<Status> status;
|
||||
|
||||
CurtainModel({
|
||||
required this.productUuid,
|
||||
required this.productType,
|
||||
required this.status,
|
||||
});
|
||||
|
||||
factory CurtainModel.fromJson(dynamic json) {
|
||||
var statusList = json['status'] as List;
|
||||
List<Status> status = statusList.map((i) => Status.fromJson(i)).toList();
|
||||
|
||||
return CurtainModel(
|
||||
productUuid: json['productUuid'],
|
||||
productType: json['productType'],
|
||||
status: status,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'productUuid': productUuid,
|
||||
'productType': productType,
|
||||
'status': status.map((s) => s.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/common/curtain_toggle.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_bloc.dart';
|
||||
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/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class CurtainBatchStatusView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const CurtainBatchStatusView({super.key, required this.devicesIds});
|
||||
|
||||
final List<String> devicesIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => CurtainBloc(deviceId: devicesIds.first)
|
||||
..add(CurtainFetchBatchStatus(devicesIds)),
|
||||
child: BlocBuilder<CurtainBloc, CurtainState>(
|
||||
builder: (context, state) {
|
||||
if (state is CurtainStatusLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is CurtainStatusLoaded) {
|
||||
return _buildStatusControls(context, state.status);
|
||||
} else if (state is CurtainError || state is CurtainControlError) {
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
} else {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusControls(BuildContext context, bool status) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge || isExtraLarge
|
||||
? 3
|
||||
: isMedium
|
||||
? 2
|
||||
: 1,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
children: [
|
||||
CurtainToggle(
|
||||
value: status,
|
||||
code: 'control',
|
||||
deviceId: devicesIds.first,
|
||||
label: 'Curtains',
|
||||
onChanged: (value) {
|
||||
context.read<CurtainBloc>().add(CurtainBatchControl(
|
||||
devicesIds: devicesIds,
|
||||
code: 'control',
|
||||
value: value,
|
||||
));
|
||||
},
|
||||
),
|
||||
FirmwareUpdateWidget(deviceId: devicesIds.first, version: 5),
|
||||
FactoryResetWidget(
|
||||
callFactoryReset: () {
|
||||
context.read<CurtainBloc>().add(
|
||||
CurtainFactoryReset(
|
||||
deviceId: devicesIds.first,
|
||||
factoryReset: FactoryResetModel(devicesUuid: devicesIds),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/common/curtain_toggle.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_bloc.dart';
|
||||
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/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class CurtainStatusControlsView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
final String deviceId;
|
||||
|
||||
const CurtainStatusControlsView({super.key, required this.deviceId});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => CurtainBloc(deviceId: deviceId)
|
||||
..add(CurtainFetchDeviceStatus(deviceId)),
|
||||
child: BlocBuilder<CurtainBloc, CurtainState>(
|
||||
builder: (context, state) {
|
||||
if (state is CurtainStatusLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is CurtainStatusLoaded) {
|
||||
return _buildStatusControls(context, state.status);
|
||||
} else if (state is CurtainError || state is CurtainControlError) {
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
} else {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusControls(BuildContext context, bool status) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge || isExtraLarge
|
||||
? 3
|
||||
: isMedium
|
||||
? 2
|
||||
: 1,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
children: [
|
||||
const SizedBox.shrink(),
|
||||
CurtainToggle(
|
||||
value: status,
|
||||
code: 'control',
|
||||
deviceId: deviceId,
|
||||
label: 'Curtains',
|
||||
onChanged: (value) {
|
||||
context.read<CurtainBloc>().add(
|
||||
CurtainControl(
|
||||
deviceId: deviceId,
|
||||
code: 'control',
|
||||
value: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox.shrink(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -17,6 +17,7 @@ class DoorLockBloc extends Bloc<DoorLockEvent, DoorLockState> {
|
||||
on<DoorLockFetchStatus>(_onFetchDeviceStatus);
|
||||
//on<DoorLockControl>(_onDoorLockControl);
|
||||
on<UpdateLockEvent>(_updateLock);
|
||||
on<DoorLockFactoryReset>(_onFactoryReset);
|
||||
}
|
||||
|
||||
FutureOr<void> _onFetchDeviceStatus(
|
||||
@ -113,4 +114,22 @@ class DoorLockBloc extends Bloc<DoorLockEvent, DoorLockState> {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onFactoryReset(
|
||||
DoorLockFactoryReset event, Emitter<DoorLockState> emit) async {
|
||||
emit(DoorLockStatusLoading());
|
||||
try {
|
||||
final response = await DevicesManagementApi().factoryReset(
|
||||
event.factoryReset,
|
||||
event.deviceId,
|
||||
);
|
||||
if (!response) {
|
||||
emit(const DoorLockControlError('Failed'));
|
||||
} else {
|
||||
add(DoorLockFetchStatus(event.deviceId));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(DoorLockControlError(e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
|
||||
sealed class DoorLockEvent extends Equatable {
|
||||
const DoorLockEvent();
|
||||
@ -37,3 +38,16 @@ class UpdateLockEvent extends DoorLockEvent {
|
||||
@override
|
||||
List<Object> get props => [value];
|
||||
}
|
||||
|
||||
class DoorLockFactoryReset extends DoorLockEvent {
|
||||
final String deviceId;
|
||||
final FactoryResetModel factoryReset;
|
||||
|
||||
const DoorLockFactoryReset({
|
||||
required this.deviceId,
|
||||
required this.factoryReset,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId, factoryReset];
|
||||
}
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
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/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/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class DoorLockBatchControlView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const DoorLockBatchControlView({super.key, required this.devicesIds});
|
||||
|
||||
final List<String> devicesIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
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: FactoryResetWidget(
|
||||
callFactoryReset: () {
|
||||
BlocProvider.of<DoorLockBloc>(context).add(
|
||||
DoorLockFactoryReset(
|
||||
deviceId: devicesIds.first,
|
||||
factoryReset: FactoryResetModel(devicesUuid: devicesIds),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -7,10 +7,10 @@ import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_stat
|
||||
import 'package:syncrow_web/pages/device_managment/door_lock/models/door_lock_status_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/door_lock/widget/door_button.dart';
|
||||
|
||||
class DoorLockView extends StatelessWidget {
|
||||
class DoorLockControlsView extends StatelessWidget {
|
||||
final AllDevicesModel device;
|
||||
|
||||
const DoorLockView({super.key, required this.device});
|
||||
const DoorLockControlsView({super.key, required this.device});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -34,9 +34,9 @@ class DoorLockView extends StatelessWidget {
|
||||
} else if (state is UpdateState) {
|
||||
return _buildStatusControls(context, state.smartDoorModel);
|
||||
} else if (state is DoorLockControlError) {
|
||||
return Center(child: Text(state.message));
|
||||
return const SizedBox();
|
||||
} else {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
}
|
||||
},
|
||||
),
|
||||
@ -0,0 +1,423 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart';
|
||||
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/water_heater/models/schedule_entry.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
import 'package:syncrow_web/utils/format_date_time.dart';
|
||||
|
||||
class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
|
||||
final String deviceId;
|
||||
late GarageDoorStatusModel deviceStatus;
|
||||
Timer? _timer;
|
||||
|
||||
GarageDoorBloc({required this.deviceId}) : super(GarageDoorInitialState()) {
|
||||
on<GarageDoorInitialEvent>(_fetchGarageDoorStatus);
|
||||
on<GarageDoorControlEvent>(_garageDoorControlEvent);
|
||||
on<AddGarageDoorScheduleEvent>(_addSchedule);
|
||||
on<UpdateGarageDoorScheduleEvent>(_updateSchedule);
|
||||
on<DeleteGarageDoorScheduleEvent>(_deleteSchedule);
|
||||
on<FetchGarageDoorSchedulesEvent>(_fetchSchedules);
|
||||
on<IncreaseGarageDoorDelayEvent>(_increaseDelay);
|
||||
on<DecreaseGarageDoorDelayEvent>(_decreaseDelay);
|
||||
on<FetchGarageDoorRecordsEvent>(_fetchRecords);
|
||||
on<GarageDoorUpdatedEvent>(_handleUpdate);
|
||||
on<UpdateSelectedTimeEvent>(_updateSelectedTime);
|
||||
on<UpdateSelectedDayEvent>(_updateSelectedDay);
|
||||
on<UpdateFunctionOnEvent>(_updateFunctionOn);
|
||||
on<InitializeAddScheduleEvent>(_initializeAddSchedule);
|
||||
on<BackToGarageDoorGridViewEvent>(_backToGridView);
|
||||
on<UpdateCountdownAlarmEvent>(_onUpdateCountdownAlarm);
|
||||
on<UpdateTrTimeConEvent>(_onUpdateTrTimeCon);
|
||||
on<GarageDoorBatchControlEvent>(_onBatchControl);
|
||||
on<GarageDoorFetchBatchStatusEvent>(_onFetchBatchStatus);
|
||||
on<GarageDoorFactoryResetEvent>(_onFactoryReset);
|
||||
on<EditGarageDoorScheduleEvent>(_onEditSchedule);
|
||||
}
|
||||
|
||||
void _fetchGarageDoorStatus(GarageDoorInitialEvent event, Emitter<GarageDoorState> emit) async {
|
||||
emit(GarageDoorLoadingState());
|
||||
try {
|
||||
var response = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
deviceStatus = GarageDoorStatusModel.fromJson(deviceId, response.status);
|
||||
emit(GarageDoorLoadedState(status: deviceStatus));
|
||||
} catch (e) {
|
||||
emit(GarageDoorErrorState(message: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFetchBatchStatus(GarageDoorFetchBatchStatusEvent event, Emitter<GarageDoorState> emit) async {
|
||||
emit(GarageDoorLoadingState());
|
||||
try {
|
||||
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
|
||||
deviceStatus = GarageDoorStatusModel.fromJson(event.deviceIds.first, status.status);
|
||||
emit(GarageDoorBatchStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(GarageDoorBatchControlError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _addSchedule(AddGarageDoorScheduleEvent event, Emitter<GarageDoorState> emit) async {
|
||||
try {
|
||||
ScheduleEntry newSchedule = ScheduleEntry(
|
||||
category: event.category,
|
||||
time: formatTimeOfDayToISO(event.time),
|
||||
function: Status(code: 'switch_1', value: event.functionOn),
|
||||
days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
|
||||
);
|
||||
bool success = await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId);
|
||||
if (success) {
|
||||
add(FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1'));
|
||||
} else {
|
||||
emit(GarageDoorLoadedState(status: deviceStatus));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(GarageDoorLoadedState(status: deviceStatus));
|
||||
}
|
||||
}
|
||||
|
||||
void _onUpdateCountdownAlarm(UpdateCountdownAlarmEvent event, Emitter<GarageDoorState> emit) {
|
||||
final currentState = state;
|
||||
if (currentState is GarageDoorLoadedState) {
|
||||
emit(currentState.copyWith(
|
||||
status: currentState.status.copyWith(countdownAlarm: event.countdownAlarm),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void _onUpdateTrTimeCon(UpdateTrTimeConEvent event, Emitter<GarageDoorState> emit) {
|
||||
final currentState = state;
|
||||
if (currentState is GarageDoorLoadedState) {
|
||||
emit(currentState.copyWith(
|
||||
status: currentState.status.copyWith(trTimeCon: event.trTimeCon),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _updateSchedule(UpdateGarageDoorScheduleEvent event, Emitter<GarageDoorState> emit) async {
|
||||
try {
|
||||
final updatedSchedules = deviceStatus.schedules?.map((schedule) {
|
||||
if (schedule.scheduleId == event.scheduleId) {
|
||||
return schedule.copyWith(
|
||||
function: Status(code: 'switch_1', value: event.functionOn),
|
||||
enable: event.enable,
|
||||
);
|
||||
}
|
||||
return schedule;
|
||||
}).toList();
|
||||
bool success = await DevicesManagementApi().updateScheduleRecord(
|
||||
enable: event.enable,
|
||||
uuid: deviceStatus.uuid,
|
||||
scheduleId: event.scheduleId,
|
||||
);
|
||||
if (success) {
|
||||
deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules);
|
||||
emit(GarageDoorLoadedState(status: deviceStatus));
|
||||
} else {
|
||||
emit(GarageDoorLoadedState(status: deviceStatus));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(GarageDoorLoadedState(status: deviceStatus));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _deleteSchedule(DeleteGarageDoorScheduleEvent event, Emitter<GarageDoorState> emit) async {
|
||||
try {
|
||||
bool success = await DevicesManagementApi().deleteScheduleRecord(deviceStatus.uuid, event.scheduleId);
|
||||
if (success) {
|
||||
final updatedSchedules =
|
||||
deviceStatus.schedules?.where((schedule) => schedule.scheduleId != event.scheduleId).toList();
|
||||
deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules);
|
||||
emit(GarageDoorLoadedState(status: deviceStatus));
|
||||
} else {
|
||||
emit(GarageDoorLoadedState(status: deviceStatus));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(GarageDoorLoadedState(status: deviceStatus));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _fetchSchedules(FetchGarageDoorSchedulesEvent event, Emitter<GarageDoorState> emit) async {
|
||||
emit(ScheduleGarageLoadingState());
|
||||
try {
|
||||
List<ScheduleModel> schedules =
|
||||
await DevicesManagementApi().getDeviceSchedules(deviceStatus.uuid, event.category);
|
||||
deviceStatus = deviceStatus.copyWith(schedules: schedules);
|
||||
emit(
|
||||
GarageDoorLoadedState(
|
||||
status: deviceStatus,
|
||||
scheduleMode: ScheduleModes.schedule,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
GarageDoorLoadedState(
|
||||
status: deviceStatus,
|
||||
scheduleMode: ScheduleModes.schedule,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _updateSelectedTime(UpdateSelectedTimeEvent event, Emitter<GarageDoorState> emit) async {
|
||||
final currentState = state;
|
||||
if (currentState is GarageDoorLoadedState) {
|
||||
emit(currentState.copyWith(selectedTime: event.selectedTime));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _updateSelectedDay(UpdateSelectedDayEvent event, Emitter<GarageDoorState> emit) async {
|
||||
final currentState = state;
|
||||
if (currentState is GarageDoorLoadedState) {
|
||||
List<bool> updatedDays = List.from(currentState.selectedDays);
|
||||
updatedDays[event.dayIndex] = event.isSelected;
|
||||
emit(currentState.copyWith(selectedDays: updatedDays, selectedTime: currentState.selectedTime));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _updateFunctionOn(UpdateFunctionOnEvent event, Emitter<GarageDoorState> emit) async {
|
||||
final currentState = state;
|
||||
if (currentState is GarageDoorLoadedState) {
|
||||
emit(currentState.copyWith(functionOn: event.functionOn, selectedTime: currentState.selectedTime));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _initializeAddSchedule(InitializeAddScheduleEvent event, Emitter<GarageDoorState> emit) async {
|
||||
final currentState = state;
|
||||
if (currentState is GarageDoorLoadedState) {
|
||||
emit(currentState.copyWith(
|
||||
selectedTime: event.selectedTime,
|
||||
selectedDays: event.selectedDays,
|
||||
functionOn: event.functionOn,
|
||||
isEditing: event.isEditing,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _fetchRecords(FetchGarageDoorRecordsEvent event, Emitter<GarageDoorState> emit) async {
|
||||
emit(GarageDoorReportsLoadingState());
|
||||
try {
|
||||
final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch;
|
||||
final to = DateTime.now().millisecondsSinceEpoch;
|
||||
final DeviceReport records =
|
||||
await DevicesManagementApi.getDeviceReportsByDate(event.deviceId, 'switch_1', from.toString(), to.toString());
|
||||
emit(GarageDoorReportsState(deviceReport: records));
|
||||
} catch (e) {
|
||||
emit(GarageDoorReportsFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onBatchControl(GarageDoorBatchControlEvent event, Emitter<GarageDoorState> emit) async {
|
||||
final oldValue = event.code == 'switch_1' ? deviceStatus.switch1 : false;
|
||||
|
||||
_updateLocalValue(event.code, event.value);
|
||||
emit(GarageDoorBatchStatusLoaded(deviceStatus));
|
||||
|
||||
final success = await _runDeBouncer(
|
||||
deviceId: event.deviceIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: true,
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
_revertValue(event.code, oldValue, emit);
|
||||
}
|
||||
}
|
||||
|
||||
void _backToGridView(BackToGarageDoorGridViewEvent event, Emitter<GarageDoorState> emit) {
|
||||
emit(GarageDoorLoadedState(status: deviceStatus));
|
||||
}
|
||||
|
||||
void _handleUpdate(GarageDoorUpdatedEvent event, Emitter<GarageDoorState> emit) {
|
||||
emit(GarageDoorLoadedState(status: deviceStatus));
|
||||
}
|
||||
|
||||
Future<bool> _runDeBouncer({
|
||||
required dynamic deviceId,
|
||||
required String code,
|
||||
required dynamic value,
|
||||
required dynamic oldValue,
|
||||
required Emitter<GarageDoorState> emit,
|
||||
required bool isBatch,
|
||||
}) async {
|
||||
try {
|
||||
late bool status;
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
if (isBatch) {
|
||||
status = await DevicesManagementApi().deviceBatchControl(deviceId, code, value);
|
||||
} else {
|
||||
status = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value));
|
||||
}
|
||||
|
||||
if (!status) {
|
||||
_revertValue(code, oldValue, emit);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
_revertValue(code, oldValue, emit);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFactoryReset(GarageDoorFactoryResetEvent event, Emitter<GarageDoorState> emit) async {
|
||||
emit(GarageDoorLoadingState());
|
||||
try {
|
||||
final response = await DevicesManagementApi().factoryReset(event.factoryReset, event.deviceId);
|
||||
if (!response) {
|
||||
emit(const GarageDoorErrorState(message: 'Failed to reset device'));
|
||||
} else {
|
||||
emit(GarageDoorLoadedState(status: deviceStatus));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(GarageDoorErrorState(message: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void _increaseDelay(IncreaseGarageDoorDelayEvent event, Emitter<GarageDoorState> emit) async {
|
||||
// if (deviceStatus.countdown1 != 0) {
|
||||
try {
|
||||
deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay + Duration(minutes: 10));
|
||||
emit(GarageDoorLoadedState(status: deviceStatus));
|
||||
add(GarageDoorControlEvent(deviceId: event.deviceId, value: deviceStatus.delay.inSeconds, code: 'countdown_1'));
|
||||
} catch (e) {
|
||||
emit(GarageDoorErrorState(message: e.toString()));
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
void _decreaseDelay(DecreaseGarageDoorDelayEvent event, Emitter<GarageDoorState> emit) async {
|
||||
// if (deviceStatus.countdown1 != 0) {
|
||||
try {
|
||||
if (deviceStatus.delay.inMinutes > 10) {
|
||||
deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay - Duration(minutes: 10));
|
||||
}
|
||||
emit(GarageDoorLoadedState(status: deviceStatus));
|
||||
add(GarageDoorControlEvent(deviceId: event.deviceId, value: deviceStatus.delay.inSeconds, code: 'countdown_1'));
|
||||
} catch (e) {
|
||||
emit(GarageDoorErrorState(message: e.toString()));
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
void _garageDoorControlEvent(GarageDoorControlEvent event, Emitter<GarageDoorState> emit) async {
|
||||
final oldValue = event.code == 'countdown_1' ? deviceStatus.countdown1 : deviceStatus.switch1;
|
||||
_updateLocalValue(event.code, event.value);
|
||||
emit(GarageDoorLoadedState(status: deviceStatus));
|
||||
final success = await _runDeBouncer(
|
||||
deviceId: event.deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: false,
|
||||
);
|
||||
if (!success) {
|
||||
_revertValue(event.code, oldValue, emit);
|
||||
}
|
||||
}
|
||||
|
||||
void _revertValue(String code, dynamic oldValue, Emitter<GarageDoorState> emit) {
|
||||
switch (code) {
|
||||
case 'switch_1':
|
||||
if (oldValue is bool) {
|
||||
deviceStatus = deviceStatus.copyWith(switch1: oldValue);
|
||||
}
|
||||
break;
|
||||
case 'countdown_1':
|
||||
if (oldValue is int) {
|
||||
deviceStatus = deviceStatus.copyWith(countdown1: oldValue, delay: Duration(seconds: oldValue));
|
||||
}
|
||||
break;
|
||||
// Add other cases if needed
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (state is GarageDoorLoadedState) {
|
||||
final currentState = state as GarageDoorLoadedState;
|
||||
emit(currentState.copyWith(status: deviceStatus));
|
||||
}
|
||||
}
|
||||
|
||||
void _updateLocalValue(String code, dynamic value) {
|
||||
switch (code) {
|
||||
case 'switch_1':
|
||||
if (value is bool) {
|
||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||
}
|
||||
break;
|
||||
case 'countdown_1':
|
||||
if (value is int) {
|
||||
deviceStatus = deviceStatus.copyWith(countdown1: value, delay: Duration(seconds: value));
|
||||
}
|
||||
break;
|
||||
case 'countdown_alarm':
|
||||
if (value is int) {
|
||||
deviceStatus = deviceStatus.copyWith(countdownAlarm: value);
|
||||
}
|
||||
break;
|
||||
case 'tr_timecon':
|
||||
if (value is int) {
|
||||
deviceStatus = deviceStatus.copyWith(trTimeCon: value);
|
||||
}
|
||||
break;
|
||||
case 'door_state_1':
|
||||
if (value is String) {
|
||||
deviceStatus = deviceStatus.copyWith(doorState1: value);
|
||||
}
|
||||
break;
|
||||
case 'door_control_1':
|
||||
if (value is String) {
|
||||
deviceStatus = deviceStatus.copyWith(doorControl1: value);
|
||||
}
|
||||
break;
|
||||
case 'voice_control_1':
|
||||
if (value is bool) {
|
||||
deviceStatus = deviceStatus.copyWith(voiceControl1: value);
|
||||
}
|
||||
break;
|
||||
case 'door_contact_state':
|
||||
if (value is bool) {
|
||||
deviceStatus = deviceStatus.copyWith(doorContactState: value);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_timer?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
FutureOr<void> _onEditSchedule(EditGarageDoorScheduleEvent event, Emitter<GarageDoorState> emit) async {
|
||||
try {
|
||||
ScheduleEntry newSchedule = ScheduleEntry(
|
||||
scheduleId: event.scheduleId,
|
||||
category: event.category,
|
||||
time: formatTimeOfDayToISO(event.time),
|
||||
function: Status(code: 'switch_1', value: event.functionOn),
|
||||
days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
|
||||
);
|
||||
bool success = await DevicesManagementApi().editScheduleRecord(deviceId, newSchedule);
|
||||
if (success) {
|
||||
add(FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1'));
|
||||
} else {
|
||||
emit(GarageDoorLoadedState(status: deviceStatus));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(GarageDoorLoadedState(status: deviceStatus));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,234 @@
|
||||
// lib/pages/device_managment/garage_door/bloc/event.dart
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
|
||||
abstract class GarageDoorEvent extends Equatable {
|
||||
const GarageDoorEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class GarageDoorInitialEvent extends GarageDoorEvent {
|
||||
final String deviceId;
|
||||
|
||||
const GarageDoorInitialEvent(this.deviceId);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [deviceId];
|
||||
}
|
||||
|
||||
class GarageDoorControlEvent extends GarageDoorEvent {
|
||||
final String deviceId;
|
||||
final dynamic value;
|
||||
final String code;
|
||||
|
||||
const GarageDoorControlEvent({required this.deviceId, required this.value, required this.code});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [deviceId, value];
|
||||
}
|
||||
|
||||
class AddGarageDoorScheduleEvent extends GarageDoorEvent {
|
||||
final String category;
|
||||
final TimeOfDay time;
|
||||
final bool functionOn;
|
||||
final List<bool> selectedDays;
|
||||
|
||||
const AddGarageDoorScheduleEvent({
|
||||
required this.category,
|
||||
required this.time,
|
||||
required this.functionOn,
|
||||
required this.selectedDays,
|
||||
});
|
||||
}
|
||||
|
||||
class EditGarageDoorScheduleEvent extends GarageDoorEvent {
|
||||
final String scheduleId;
|
||||
final String category;
|
||||
final TimeOfDay time;
|
||||
final bool functionOn;
|
||||
final List<bool> selectedDays;
|
||||
|
||||
const EditGarageDoorScheduleEvent({
|
||||
required this.scheduleId,
|
||||
required this.category,
|
||||
required this.time,
|
||||
required this.functionOn,
|
||||
required this.selectedDays,
|
||||
});
|
||||
}
|
||||
|
||||
class UpdateGarageDoorScheduleEvent extends GarageDoorEvent {
|
||||
final String deviceId;
|
||||
final String scheduleId;
|
||||
final bool enable;
|
||||
final bool functionOn;
|
||||
final int index;
|
||||
|
||||
const UpdateGarageDoorScheduleEvent({
|
||||
required this.deviceId,
|
||||
required this.scheduleId,
|
||||
required this.enable,
|
||||
required this.functionOn,
|
||||
required this.index,
|
||||
});
|
||||
}
|
||||
|
||||
class DeleteGarageDoorScheduleEvent extends GarageDoorEvent {
|
||||
final String deviceId;
|
||||
final String scheduleId;
|
||||
final int index;
|
||||
|
||||
const DeleteGarageDoorScheduleEvent({
|
||||
required this.deviceId,
|
||||
required this.scheduleId,
|
||||
required this.index,
|
||||
});
|
||||
}
|
||||
|
||||
class FetchGarageDoorSchedulesEvent extends GarageDoorEvent {
|
||||
final String deviceId;
|
||||
final String category;
|
||||
|
||||
const FetchGarageDoorSchedulesEvent({
|
||||
required this.deviceId,
|
||||
required this.category,
|
||||
});
|
||||
}
|
||||
|
||||
class IncreaseGarageDoorDelayEvent extends GarageDoorEvent {
|
||||
final String deviceId;
|
||||
|
||||
const IncreaseGarageDoorDelayEvent({required this.deviceId});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [deviceId];
|
||||
}
|
||||
|
||||
class DecreaseGarageDoorDelayEvent extends GarageDoorEvent {
|
||||
final String deviceId;
|
||||
|
||||
const DecreaseGarageDoorDelayEvent({required this.deviceId});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [deviceId];
|
||||
}
|
||||
|
||||
class FetchGarageDoorRecordsEvent extends GarageDoorEvent {
|
||||
final String deviceId;
|
||||
final String code;
|
||||
|
||||
const FetchGarageDoorRecordsEvent({required this.deviceId, required this.code});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [deviceId, code];
|
||||
}
|
||||
|
||||
class BackToGarageDoorGridViewEvent extends GarageDoorEvent {}
|
||||
|
||||
class GarageDoorUpdatedEvent extends GarageDoorEvent {}
|
||||
|
||||
class UpdateSelectedTimeEvent extends GarageDoorEvent {
|
||||
final TimeOfDay? selectedTime;
|
||||
|
||||
const UpdateSelectedTimeEvent(this.selectedTime);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [selectedTime];
|
||||
}
|
||||
|
||||
class UpdateSelectedDayEvent extends GarageDoorEvent {
|
||||
final int dayIndex;
|
||||
final bool isSelected;
|
||||
|
||||
const UpdateSelectedDayEvent(this.dayIndex, this.isSelected);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [dayIndex, isSelected];
|
||||
}
|
||||
|
||||
class UpdateFunctionOnEvent extends GarageDoorEvent {
|
||||
final bool functionOn;
|
||||
|
||||
const UpdateFunctionOnEvent({required this.functionOn});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [functionOn];
|
||||
}
|
||||
|
||||
class InitializeAddScheduleEvent extends GarageDoorEvent {
|
||||
final TimeOfDay? selectedTime;
|
||||
final List<bool> selectedDays;
|
||||
final bool functionOn;
|
||||
final bool isEditing;
|
||||
final int? index;
|
||||
|
||||
const InitializeAddScheduleEvent({
|
||||
required this.selectedTime,
|
||||
required this.selectedDays,
|
||||
required this.functionOn,
|
||||
required this.isEditing,
|
||||
this.index,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
selectedTime,
|
||||
selectedDays,
|
||||
functionOn,
|
||||
isEditing,
|
||||
index,
|
||||
];
|
||||
}
|
||||
|
||||
class UpdateCountdownAlarmEvent extends GarageDoorEvent {
|
||||
final int countdownAlarm;
|
||||
|
||||
const UpdateCountdownAlarmEvent(this.countdownAlarm);
|
||||
}
|
||||
|
||||
class UpdateTrTimeConEvent extends GarageDoorEvent {
|
||||
final int trTimeCon;
|
||||
|
||||
const UpdateTrTimeConEvent(this.trTimeCon);
|
||||
}
|
||||
|
||||
class GarageDoorBatchControlEvent extends GarageDoorEvent {
|
||||
final List<String> deviceIds;
|
||||
final String code;
|
||||
final bool value;
|
||||
|
||||
const GarageDoorBatchControlEvent({
|
||||
required this.deviceIds,
|
||||
required this.code,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [deviceIds, code, value];
|
||||
}
|
||||
|
||||
class GarageDoorFetchBatchStatusEvent extends GarageDoorEvent {
|
||||
final List<String> deviceIds;
|
||||
|
||||
const GarageDoorFetchBatchStatusEvent(this.deviceIds);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [deviceIds];
|
||||
}
|
||||
|
||||
class GarageDoorFactoryResetEvent extends GarageDoorEvent {
|
||||
final FactoryResetModel factoryReset;
|
||||
final String deviceId;
|
||||
|
||||
const GarageDoorFactoryResetEvent({
|
||||
required this.factoryReset,
|
||||
required this.deviceId,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [factoryReset, deviceId];
|
||||
}
|
||||
@ -0,0 +1,141 @@
|
||||
// lib/pages/device_managment/garage_door/bloc/state.dart
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
|
||||
|
||||
abstract class GarageDoorState extends Equatable {
|
||||
const GarageDoorState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class GarageDoorInitialState extends GarageDoorState {}
|
||||
|
||||
class GarageDoorLoadingState extends GarageDoorState {}
|
||||
|
||||
class GarageDoorLoadedState extends GarageDoorState {
|
||||
final GarageDoorStatusModel status;
|
||||
final Duration? delay;
|
||||
final DeviceReport? records;
|
||||
final List<bool> selectedDays;
|
||||
final TimeOfDay? selectedTime;
|
||||
final bool functionOn;
|
||||
final bool isEditing;
|
||||
final ScheduleModes? scheduleMode;
|
||||
|
||||
const GarageDoorLoadedState({
|
||||
required this.status,
|
||||
this.delay,
|
||||
this.records,
|
||||
this.selectedDays = const [false, false, false, false, false, false, false],
|
||||
this.selectedTime,
|
||||
this.functionOn = false,
|
||||
this.isEditing = false,
|
||||
this.scheduleMode = ScheduleModes.schedule,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
status,
|
||||
delay,
|
||||
records,
|
||||
selectedDays,
|
||||
selectedTime,
|
||||
functionOn,
|
||||
isEditing,
|
||||
scheduleMode,
|
||||
];
|
||||
|
||||
GarageDoorLoadedState copyWith({
|
||||
GarageDoorStatusModel? status,
|
||||
Duration? delay,
|
||||
DeviceReport? records,
|
||||
List<bool>? selectedDays,
|
||||
TimeOfDay? selectedTime,
|
||||
bool? functionOn,
|
||||
bool? isEditing,
|
||||
ScheduleModes? scheduleMode,
|
||||
}) {
|
||||
return GarageDoorLoadedState(
|
||||
status: status ?? this.status,
|
||||
delay: delay ?? this.delay,
|
||||
records: records ?? this.records,
|
||||
selectedDays: selectedDays ?? this.selectedDays,
|
||||
selectedTime: selectedTime,
|
||||
functionOn: functionOn ?? this.functionOn,
|
||||
isEditing: isEditing ?? this.isEditing,
|
||||
scheduleMode: scheduleMode ?? this.scheduleMode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GarageDoorErrorState extends GarageDoorState {
|
||||
final String message;
|
||||
|
||||
const GarageDoorErrorState({required this.message});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
|
||||
class GarageDoorLoadingNewState extends GarageDoorState {
|
||||
final GarageDoorStatusModel garageDoorModel;
|
||||
|
||||
const GarageDoorLoadingNewState({required this.garageDoorModel});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [garageDoorModel];
|
||||
}
|
||||
|
||||
class GarageDoorReportsLoadingState extends GarageDoorState {}
|
||||
|
||||
class GarageDoorReportsFailedState extends GarageDoorState {
|
||||
final String error;
|
||||
|
||||
const GarageDoorReportsFailedState({required this.error});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [error];
|
||||
}
|
||||
|
||||
class GarageDoorReportsState extends GarageDoorState {
|
||||
final DeviceReport deviceReport;
|
||||
|
||||
const GarageDoorReportsState({required this.deviceReport});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [deviceReport];
|
||||
}
|
||||
|
||||
class ShowGarageDoorDescriptionState extends GarageDoorState {
|
||||
final String description;
|
||||
|
||||
const ShowGarageDoorDescriptionState({required this.description});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [description];
|
||||
}
|
||||
|
||||
class ScheduleGarageLoadingState extends GarageDoorState {}
|
||||
|
||||
class GarageDoorBatchStatusLoaded extends GarageDoorState {
|
||||
final GarageDoorStatusModel status;
|
||||
|
||||
const GarageDoorBatchStatusLoaded(this.status);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status];
|
||||
}
|
||||
|
||||
class GarageDoorBatchControlError extends GarageDoorState {
|
||||
final String message;
|
||||
|
||||
const GarageDoorBatchControlError(this.message);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
@ -0,0 +1,385 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_display_data.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class GarageDoorDialogHelper {
|
||||
static void showAddGarageDoorScheduleDialog(BuildContext context,
|
||||
{ScheduleModel? schedule, int? index, bool? isEdit}) {
|
||||
final bloc = context.read<GarageDoorBloc>();
|
||||
|
||||
if (schedule == null) {
|
||||
bloc.add((const UpdateSelectedTimeEvent(null)));
|
||||
bloc.add(InitializeAddScheduleEvent(
|
||||
selectedTime: null,
|
||||
selectedDays: List.filled(7, false),
|
||||
functionOn: false,
|
||||
isEditing: false,
|
||||
));
|
||||
} else {
|
||||
final time = _convertStringToTimeOfDay(schedule.time);
|
||||
final selectedDays = _convertDaysStringToBooleans(schedule.days);
|
||||
|
||||
bloc.add(InitializeAddScheduleEvent(
|
||||
selectedTime: time,
|
||||
selectedDays: selectedDays,
|
||||
functionOn: schedule.function.value,
|
||||
isEditing: true,
|
||||
index: index,
|
||||
));
|
||||
}
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) {
|
||||
return BlocProvider.value(
|
||||
value: bloc,
|
||||
child: BlocBuilder<GarageDoorBloc, GarageDoorState>(
|
||||
builder: (context, state) {
|
||||
if (state is GarageDoorLoadedState) {
|
||||
return AlertDialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const SizedBox(),
|
||||
Text(
|
||||
'Scheduling',
|
||||
style: context.textTheme.titleLarge!.copyWith(
|
||||
color: ColorsManager.dialogBlueTitle,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
SizedBox(
|
||||
width: 150,
|
||||
height: 40,
|
||||
child: DefaultButton(
|
||||
padding: 8,
|
||||
backgroundColor: ColorsManager.boxColor,
|
||||
borderRadius: 15,
|
||||
onPressed: () async {
|
||||
TimeOfDay? time = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: state.selectedTime ?? TimeOfDay.now(),
|
||||
builder: (context, child) {
|
||||
return Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: ColorsManager.primaryColor,
|
||||
),
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
);
|
||||
if (time != null) {
|
||||
bloc.add(UpdateSelectedTimeEvent(time));
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
state.selectedTime == null ? 'Time' : state.selectedTime!.format(context),
|
||||
style: context.textTheme.bodySmall!.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
),
|
||||
const Icon(
|
||||
Icons.access_time,
|
||||
color: ColorsManager.grayColor,
|
||||
size: 18,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildDayCheckboxes(context, state.selectedDays, isEdit: isEdit),
|
||||
const SizedBox(height: 16),
|
||||
_buildFunctionSwitch(context, state.functionOn, isEdit),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: DefaultButton(
|
||||
height: 40,
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
backgroundColor: ColorsManager.boxColor,
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: DefaultButton(
|
||||
height: 40,
|
||||
onPressed: () {
|
||||
if (state.selectedTime != null) {
|
||||
if (state.isEditing && index != null) {
|
||||
bloc.add(EditGarageDoorScheduleEvent(
|
||||
scheduleId: schedule?.scheduleId ?? '',
|
||||
category: 'switch_1',
|
||||
time: state.selectedTime!,
|
||||
selectedDays: state.selectedDays,
|
||||
functionOn: state.functionOn,
|
||||
));
|
||||
} else {
|
||||
bloc.add(AddGarageDoorScheduleEvent(
|
||||
category: 'switch_1',
|
||||
time: state.selectedTime!,
|
||||
selectedDays: state.selectedDays,
|
||||
functionOn: state.functionOn,
|
||||
));
|
||||
}
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
backgroundColor: ColorsManager.primaryColor,
|
||||
child: const Text('Save'),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static TimeOfDay _convertStringToTimeOfDay(String timeString) {
|
||||
final regex = RegExp(r'^(\d{2}):(\d{2})$');
|
||||
final match = regex.firstMatch(timeString);
|
||||
if (match != null) {
|
||||
final hour = int.parse(match.group(1)!);
|
||||
final minute = int.parse(match.group(2)!);
|
||||
return TimeOfDay(hour: hour, minute: minute);
|
||||
} else {
|
||||
throw const FormatException('Invalid time format');
|
||||
}
|
||||
}
|
||||
|
||||
static List<bool> _convertDaysStringToBooleans(List<String> selectedDays) {
|
||||
final daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||
List<bool> daysBoolean = List.filled(7, false);
|
||||
|
||||
for (int i = 0; i < daysOfWeek.length; i++) {
|
||||
if (selectedDays.contains(daysOfWeek[i])) {
|
||||
daysBoolean[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return daysBoolean;
|
||||
}
|
||||
|
||||
static Widget _buildDayCheckboxes(BuildContext context, List<bool> selectedDays, {bool? isEdit}) {
|
||||
final dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||
|
||||
return Row(
|
||||
children: List.generate(7, (index) {
|
||||
return Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: selectedDays[index],
|
||||
onChanged: (bool? value) {
|
||||
context.read<GarageDoorBloc>().add(UpdateSelectedDayEvent(index, value!));
|
||||
},
|
||||
),
|
||||
Text(dayLabels[index]),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
static Widget _buildFunctionSwitch(BuildContext context, bool isOn, bool? isEdit) {
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
'Function:',
|
||||
style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.grayColor),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Radio<bool>(
|
||||
value: true,
|
||||
groupValue: isOn,
|
||||
onChanged: (bool? value) {
|
||||
context.read<GarageDoorBloc>().add(const UpdateFunctionOnEvent(functionOn: true));
|
||||
},
|
||||
),
|
||||
const Text('On'),
|
||||
const SizedBox(width: 10),
|
||||
Radio<bool>(
|
||||
value: false,
|
||||
groupValue: isOn,
|
||||
onChanged: (bool? value) {
|
||||
context.read<GarageDoorBloc>().add(const UpdateFunctionOnEvent(functionOn: false));
|
||||
},
|
||||
),
|
||||
const Text('Off'),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static void showPreferencesDialog(BuildContext context) {
|
||||
final bloc = context.read<GarageDoorBloc>();
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) {
|
||||
return BlocProvider.value(
|
||||
value: bloc,
|
||||
child: BlocBuilder<GarageDoorBloc, GarageDoorState>(
|
||||
builder: (context, state) {
|
||||
if (state is GarageDoorLoadedState) {
|
||||
return AlertDialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const SizedBox(),
|
||||
|
||||
/// The dialog is closed when the user taps on the close button or when the
|
||||
/// [GarageDoorBloc] state changes.
|
||||
Text(
|
||||
'Preferences',
|
||||
style: context.textTheme.titleLarge!.copyWith(
|
||||
color: ColorsManager.dialogBlueTitle,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
children: [
|
||||
const SizedBox(width: 24),
|
||||
SizedBox(
|
||||
width: 190,
|
||||
height: 150,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.customAlertDialog(
|
||||
alertBody: TimeOutAlarmDialogBody(bloc),
|
||||
title: 'Time Out Alarm',
|
||||
onConfirm: () {
|
||||
final updatedState = context.read<GarageDoorBloc>().state;
|
||||
if (updatedState is GarageDoorLoadedState) {
|
||||
context.read<GarageDoorBloc>().add(
|
||||
GarageDoorControlEvent(
|
||||
deviceId: updatedState.status.uuid,
|
||||
code: 'countdown_alarm',
|
||||
value: updatedState.status.countdownAlarm,
|
||||
),
|
||||
);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
});
|
||||
},
|
||||
child: ToggleWidget(
|
||||
icon: "-1",
|
||||
value: state.status.doorState1 == "close_time_alarm" ? false : true,
|
||||
code: 'door_state_1',
|
||||
deviceId: bloc.deviceId,
|
||||
label: 'Alarm when door is open',
|
||||
onChange: (value) {
|
||||
context.read<GarageDoorBloc>().add(
|
||||
GarageDoorControlEvent(
|
||||
deviceId: bloc.deviceId,
|
||||
code: 'door_state_1',
|
||||
value: state.status.doorState1 == "close_time_alarm"
|
||||
? "unclosed_time"
|
||||
: "close_time_alarm",
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
SizedBox(
|
||||
width: 190,
|
||||
height: 150,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.customAlertDialog(
|
||||
alertBody: OpeningAndClosingTimeDialogBody(
|
||||
bloc: bloc,
|
||||
onDurationChanged: (newDuration) {
|
||||
context.read<GarageDoorBloc>().add(
|
||||
UpdateTrTimeConEvent(newDuration),
|
||||
);
|
||||
},
|
||||
),
|
||||
title: 'Opening and Closing Time',
|
||||
onConfirm: () {
|
||||
final updatedState = context.read<GarageDoorBloc>().state;
|
||||
if (updatedState is GarageDoorLoadedState) {
|
||||
context.read<GarageDoorBloc>().add(
|
||||
GarageDoorControlEvent(
|
||||
deviceId: updatedState.status.uuid,
|
||||
code: 'tr_timecon',
|
||||
value: updatedState.status.trTimeCon,
|
||||
),
|
||||
);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
});
|
||||
},
|
||||
child: PresenceDisplayValue(
|
||||
value: state.status.trTimeCon.toString(),
|
||||
postfix: 'sec',
|
||||
description: 'Opening & Closing Time',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 24),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,123 @@
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart';
|
||||
|
||||
class GarageDoorStatusModel {
|
||||
final String uuid;
|
||||
final bool switch1;
|
||||
final int countdown1;
|
||||
final bool doorContactState;
|
||||
final int trTimeCon;
|
||||
final int countdownAlarm;
|
||||
final String doorControl1;
|
||||
final bool voiceControl1;
|
||||
final String doorState1;
|
||||
// final bool isOpen;
|
||||
final Duration delay;
|
||||
final List<ScheduleModel>? schedules; // Add schedules field
|
||||
|
||||
GarageDoorStatusModel({
|
||||
required this.uuid,
|
||||
required this.switch1,
|
||||
required this.countdown1,
|
||||
required this.doorContactState,
|
||||
required this.trTimeCon,
|
||||
required this.countdownAlarm,
|
||||
required this.doorControl1,
|
||||
required this.voiceControl1,
|
||||
required this.doorState1,
|
||||
// required this.isOpen,
|
||||
required this.delay,
|
||||
required this.schedules, // Initialize schedules
|
||||
});
|
||||
|
||||
factory GarageDoorStatusModel.fromJson(String id, List<Status> jsonList) {
|
||||
late bool switch1;
|
||||
late int countdown1;
|
||||
late bool doorContactState;
|
||||
late int trTimeCon;
|
||||
late int countdownAlarm;
|
||||
late String doorControl1;
|
||||
late bool voiceControl1;
|
||||
late String doorState1;
|
||||
List<ScheduleModel> schedules = []; // Initialize schedules
|
||||
|
||||
for (var status in jsonList) {
|
||||
switch (status.code) {
|
||||
case 'switch_1':
|
||||
switch1 = status.value ?? false;
|
||||
break;
|
||||
case 'countdown_1':
|
||||
countdown1 = status.value ?? 0;
|
||||
break;
|
||||
case 'doorcontact_state':
|
||||
doorContactState = status.value ?? false;
|
||||
break;
|
||||
case 'tr_timecon':
|
||||
trTimeCon = status.value ?? 0;
|
||||
break;
|
||||
case 'countdown_alarm':
|
||||
countdownAlarm = status.value ?? 0;
|
||||
break;
|
||||
case 'door_control_1':
|
||||
doorControl1 = status.value ?? 'closed';
|
||||
break;
|
||||
case 'voice_control_1':
|
||||
voiceControl1 = status.value ?? false;
|
||||
break;
|
||||
case 'door_state_1':
|
||||
doorState1 = status.value ?? 'close_time_alarm';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return GarageDoorStatusModel(
|
||||
uuid: id,
|
||||
switch1: switch1,
|
||||
countdown1: countdown1,
|
||||
doorContactState: doorContactState,
|
||||
trTimeCon: trTimeCon,
|
||||
countdownAlarm: countdownAlarm,
|
||||
doorControl1: doorControl1,
|
||||
voiceControl1: voiceControl1,
|
||||
doorState1: doorState1,
|
||||
// isOpen: doorState1 == 'open' ? true : false,
|
||||
delay: Duration(seconds: countdown1),
|
||||
schedules: schedules, // Assign schedules
|
||||
);
|
||||
}
|
||||
|
||||
GarageDoorStatusModel copyWith({
|
||||
String? uuid,
|
||||
bool? switch1,
|
||||
int? countdown1,
|
||||
bool? doorContactState,
|
||||
int? trTimeCon,
|
||||
int? countdownAlarm,
|
||||
String? doorControl1,
|
||||
bool? voiceControl1,
|
||||
String? doorState1,
|
||||
// bool? isOpen,
|
||||
Duration? delay,
|
||||
List<ScheduleModel>? schedules, // Add schedules to copyWith
|
||||
}) {
|
||||
return GarageDoorStatusModel(
|
||||
uuid: uuid ?? this.uuid,
|
||||
switch1: switch1 ?? this.switch1,
|
||||
countdown1: countdown1 ?? this.countdown1,
|
||||
doorContactState: doorContactState ?? this.doorContactState,
|
||||
trTimeCon: trTimeCon ?? this.trTimeCon,
|
||||
countdownAlarm: countdownAlarm ?? this.countdownAlarm,
|
||||
doorControl1: doorControl1 ?? this.doorControl1,
|
||||
voiceControl1: voiceControl1 ?? this.voiceControl1,
|
||||
doorState1: doorState1 ?? this.doorState1,
|
||||
// isOpen: isOpen ?? this.isOpen,
|
||||
delay: delay ?? this.delay,
|
||||
schedules: schedules ?? this.schedules, // Copy schedules
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'GarageDoorStatusModel(uuid: $uuid, switch1: $switch1, countdown1: $countdown1, doorContactState: $doorContactState, trTimeCon: $trTimeCon, countdownAlarm: $countdownAlarm, doorControl1: $doorControl1, voiceControl1: $voiceControl1, doorState1: $doorState1, delay: $delay, schedules: $schedules)';
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,95 @@
|
||||
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/garage_door/bloc/garage_door_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart';
|
||||
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/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 {
|
||||
final List<String> deviceIds;
|
||||
|
||||
const GarageDoorBatchControlView({Key? key, required this.deviceIds})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => GarageDoorBloc(deviceId: deviceIds.first)
|
||||
..add(GarageDoorFetchBatchStatusEvent(deviceIds)),
|
||||
child: BlocBuilder<GarageDoorBloc, GarageDoorState>(
|
||||
builder: (context, state) {
|
||||
if (state is GarageDoorLoadingState) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is GarageDoorBatchStatusLoaded) {
|
||||
return _buildStatusControls(context, state.status);
|
||||
} else if (state is GarageDoorBatchControlError) {
|
||||
return Center(child: Text('Error: ${state.message}'));
|
||||
} else {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusControls(
|
||||
BuildContext context, GarageDoorStatusModel status) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge || isExtraLarge
|
||||
? 3
|
||||
: isMedium
|
||||
? 2
|
||||
: 1,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
children: [
|
||||
ToggleWidget(
|
||||
value: status.switch1,
|
||||
code: 'switch_1',
|
||||
deviceId: deviceIds.first,
|
||||
label: 'Garage Door',
|
||||
icon: status.switch1 ? Assets.openedDoor : Assets.closedDoor,
|
||||
onChange: (value) {
|
||||
context.read<GarageDoorBloc>().add(
|
||||
GarageDoorBatchControlEvent(
|
||||
deviceIds: deviceIds,
|
||||
code: 'switch_1',
|
||||
value: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
FirmwareUpdateWidget(
|
||||
deviceId: deviceIds.first,
|
||||
version: 12,
|
||||
),
|
||||
FactoryResetWidget(
|
||||
callFactoryReset: () {
|
||||
context.read<GarageDoorBloc>().add(
|
||||
GarageDoorFactoryResetEvent(
|
||||
deviceId: deviceIds.first,
|
||||
factoryReset: FactoryResetModel(devicesUuid: deviceIds),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,188 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/helper/garage_door_helper.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/icon_name_status_container.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/table/report_table.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';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout {
|
||||
final String deviceId;
|
||||
|
||||
const GarageDoorControlView({required this.deviceId, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => GarageDoorBloc(deviceId: deviceId)..add(GarageDoorInitialEvent(deviceId)),
|
||||
child: BlocBuilder<GarageDoorBloc, GarageDoorState>(
|
||||
builder: (context, state) {
|
||||
if (state is GarageDoorLoadingState) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is GarageDoorReportsState) {
|
||||
return ReportsTable(
|
||||
report: state.deviceReport,
|
||||
hideValueShowDescription: true,
|
||||
garageDoorSensor: true,
|
||||
onRowTap: (index) {},
|
||||
onClose: () {
|
||||
context.read<GarageDoorBloc>().add(BackToGarageDoorGridViewEvent());
|
||||
},
|
||||
);
|
||||
} else if (state is GarageDoorLoadedState) {
|
||||
return _buildControlView(context, state.status);
|
||||
} else if (state is GarageDoorErrorState) {
|
||||
return Center(child: Text('Error: ${state.message}'));
|
||||
}
|
||||
return const Center(child: Text('Unknown state'));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildControlView(BuildContext context, GarageDoorStatusModel status) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
|
||||
return GridView(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge || isExtraLarge
|
||||
? 3
|
||||
: isMedium
|
||||
? 2
|
||||
: 1,
|
||||
childAspectRatio: 1.5,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
children: [
|
||||
IconNameStatusContainer(
|
||||
isFullIcon: false,
|
||||
name: status.switch1 ? 'Opened' : 'Closed',
|
||||
icon: status.switch1 ? Assets.openedDoor : Assets.closedDoor,
|
||||
onTap: () {
|
||||
context.read<GarageDoorBloc>().add(
|
||||
GarageDoorControlEvent(deviceId: status.uuid, value: !status.switch1, code: 'switch_1'),
|
||||
);
|
||||
},
|
||||
status: status.switch1,
|
||||
textColor: ColorsManager.blackColor,
|
||||
),
|
||||
IconNameStatusContainer(
|
||||
onTap: () {
|
||||
context.read<GarageDoorBloc>().add(
|
||||
FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1'),
|
||||
);
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => BlocProvider.value(
|
||||
value: BlocProvider.of<GarageDoorBloc>(context),
|
||||
child: BuildGarageDoorScheduleView(status: status),
|
||||
));
|
||||
},
|
||||
name: 'Scheduling',
|
||||
icon: Assets.acSchedule,
|
||||
status: false,
|
||||
textColor: ColorsManager.blackColor,
|
||||
isFullIcon: false,
|
||||
),
|
||||
ToggleWidget(
|
||||
label: '',
|
||||
labelWidget: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
context.read<GarageDoorBloc>().add(DecreaseGarageDoorDelayEvent(deviceId: status.uuid));
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.remove,
|
||||
size: 28,
|
||||
color: ColorsManager.greyColor,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
Text(
|
||||
status.delay.inHours.toString().padLeft(2, '0'),
|
||||
style: context.textTheme.titleLarge!.copyWith(
|
||||
color: ColorsManager.dialogBlueTitle,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'h',
|
||||
style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor),
|
||||
),
|
||||
Text(
|
||||
(status.delay.inMinutes % 60).toString().padLeft(2, '0'),
|
||||
style: context.textTheme.titleLarge!.copyWith(
|
||||
color: ColorsManager.dialogBlueTitle,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'm',
|
||||
style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
context.read<GarageDoorBloc>().add(IncreaseGarageDoorDelayEvent(deviceId: status.uuid));
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.add,
|
||||
size: 28,
|
||||
color: ColorsManager.greyColor,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
],
|
||||
),
|
||||
value: status.countdown1 != 0 ? true : false,
|
||||
code: 'countdown_1',
|
||||
deviceId: status.uuid,
|
||||
icon: Assets.doorDelay,
|
||||
onChange: (value) {
|
||||
context.read<GarageDoorBloc>().add(
|
||||
GarageDoorControlEvent(
|
||||
deviceId: status.uuid, value: value ? status.delay.inSeconds : 0, code: 'countdown_1'),
|
||||
);
|
||||
},
|
||||
),
|
||||
IconNameStatusContainer(
|
||||
isFullIcon: false,
|
||||
name: 'Records',
|
||||
icon: Assets.records,
|
||||
onTap: () {
|
||||
context.read<GarageDoorBloc>().add(FetchGarageDoorRecordsEvent(code: 'switch_1', deviceId: status.uuid));
|
||||
},
|
||||
status: false,
|
||||
textColor: ColorsManager.blackColor,
|
||||
),
|
||||
IconNameStatusContainer(
|
||||
isFullIcon: false,
|
||||
name: 'Preferences',
|
||||
icon: Assets.preferences,
|
||||
onTap: () {
|
||||
GarageDoorDialogHelper.showPreferencesDialog(context);
|
||||
},
|
||||
status: false,
|
||||
textColor: ColorsManager.blackColor,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/widgets/seconds_picker.dart';
|
||||
|
||||
class OpeningAndClosingTimeDialogBody extends StatefulWidget {
|
||||
final ValueChanged<int> onDurationChanged;
|
||||
final GarageDoorBloc bloc;
|
||||
|
||||
OpeningAndClosingTimeDialogBody({
|
||||
required this.onDurationChanged,
|
||||
required this.bloc,
|
||||
});
|
||||
|
||||
@override
|
||||
_OpeningAndClosingTimeDialogBodyState createState() =>
|
||||
_OpeningAndClosingTimeDialogBodyState();
|
||||
}
|
||||
|
||||
class _OpeningAndClosingTimeDialogBodyState
|
||||
extends State<OpeningAndClosingTimeDialogBody> {
|
||||
late int durationInSeconds;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
|
||||
final currentState = widget.bloc.state;
|
||||
if (currentState is GarageDoorLoadedState) {
|
||||
setState(() {
|
||||
durationInSeconds = currentState.status.trTimeCon;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 120,
|
||||
color: Colors.white,
|
||||
child: SecondsPicker(
|
||||
initialSeconds: durationInSeconds,
|
||||
onSecondsChanged: (newSeconds) {
|
||||
setState(() {
|
||||
durationInSeconds = newSeconds;
|
||||
});
|
||||
widget.onDurationChanged(newSeconds);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,212 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/helper/garage_door_helper.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_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';
|
||||
import 'package:syncrow_web/utils/format_date_time.dart';
|
||||
|
||||
class ScheduleGarageTableWidget extends StatelessWidget {
|
||||
final GarageDoorLoadedState state;
|
||||
|
||||
const ScheduleGarageTableWidget({
|
||||
super.key,
|
||||
required this.state,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Table(
|
||||
border: TableBorder.all(
|
||||
color: ColorsManager.graysColor,
|
||||
borderRadius: const BorderRadius.only(topLeft: Radius.circular(20), topRight: Radius.circular(20)),
|
||||
),
|
||||
children: [
|
||||
TableRow(
|
||||
decoration: const BoxDecoration(
|
||||
color: ColorsManager.boxColor,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
children: [
|
||||
_buildTableHeader('Active'),
|
||||
_buildTableHeader('Days'),
|
||||
_buildTableHeader('Time'),
|
||||
_buildTableHeader('Function'),
|
||||
_buildTableHeader('Action'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
BlocBuilder<GarageDoorBloc, GarageDoorState>(
|
||||
builder: (context, state) {
|
||||
if (state is ScheduleGarageLoadingState) {
|
||||
return const SizedBox(height: 200, child: Center(child: CircularProgressIndicator()));
|
||||
}
|
||||
if (state is GarageDoorLoadedState && state.status.schedules?.isEmpty == true) {
|
||||
return _buildEmptyState(context);
|
||||
} else if (state is GarageDoorLoadedState) {
|
||||
return Container(
|
||||
height: 200,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: ColorsManager.graysColor),
|
||||
borderRadius:
|
||||
const BorderRadius.only(bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)),
|
||||
),
|
||||
child: _buildTableBody(state, context));
|
||||
}
|
||||
return const SizedBox(
|
||||
height: 200,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState(BuildContext context) {
|
||||
return Container(
|
||||
height: 200,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: ColorsManager.graysColor),
|
||||
borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)),
|
||||
),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SvgPicture.asset(Assets.emptyRecords, width: 40, height: 40),
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
'No schedules added yet',
|
||||
style: context.textTheme.bodySmall!.copyWith(
|
||||
fontSize: 13,
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTableBody(GarageDoorLoadedState state, BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 200,
|
||||
child: SingleChildScrollView(
|
||||
child: Table(
|
||||
border: TableBorder.all(color: ColorsManager.graysColor),
|
||||
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
||||
children: [
|
||||
if (state.status.schedules != null)
|
||||
for (int i = 0; i < state.status.schedules!.length; i++)
|
||||
_buildScheduleRow(state.status.schedules![i], i, context, state),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTableHeader(String label) {
|
||||
return TableCell(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TableRow _buildScheduleRow(ScheduleModel schedule, int index, BuildContext context, GarageDoorLoadedState state) {
|
||||
return TableRow(
|
||||
children: [
|
||||
Center(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.read<GarageDoorBloc>().add(UpdateGarageDoorScheduleEvent(
|
||||
index: index,
|
||||
enable: !schedule.enable,
|
||||
scheduleId: schedule.scheduleId,
|
||||
deviceId: state.status.uuid,
|
||||
functionOn: schedule.function.value,
|
||||
));
|
||||
},
|
||||
child: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: schedule.enable
|
||||
? const Icon(Icons.radio_button_checked, color: ColorsManager.blueColor)
|
||||
: const Icon(
|
||||
Icons.radio_button_unchecked,
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(child: Text(_getSelectedDays(ScheduleModel.parseSelectedDays(schedule.days)))),
|
||||
Center(child: Text(formatIsoStringToTime(schedule.time, context))),
|
||||
Center(child: Text(schedule.function.value ? 'On' : 'Off')),
|
||||
Center(
|
||||
child: Wrap(
|
||||
runAlignment: WrapAlignment.center,
|
||||
children: [
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(padding: EdgeInsets.zero),
|
||||
onPressed: () {
|
||||
GarageDoorDialogHelper.showAddGarageDoorScheduleDialog(context,
|
||||
schedule: schedule, index: index, isEdit: true);
|
||||
},
|
||||
child: Text(
|
||||
'Edit',
|
||||
style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blueColor),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(padding: EdgeInsets.zero),
|
||||
onPressed: () {
|
||||
context.read<GarageDoorBloc>().add(DeleteGarageDoorScheduleEvent(
|
||||
index: index,
|
||||
scheduleId: schedule.scheduleId,
|
||||
deviceId: state.status.uuid,
|
||||
));
|
||||
},
|
||||
child: Text(
|
||||
'Delete',
|
||||
style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blueColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String _getSelectedDays(List<bool> selectedDays) {
|
||||
final days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||
List<String> selectedDaysStr = [];
|
||||
for (int i = 0; i < selectedDays.length; i++) {
|
||||
if (selectedDays[i]) {
|
||||
selectedDaysStr.add(days[i]);
|
||||
}
|
||||
}
|
||||
return selectedDaysStr.join(', ');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class ScheduleGarageHeader extends StatelessWidget {
|
||||
const ScheduleGarageHeader({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const SizedBox(),
|
||||
Text(
|
||||
'Scheduling',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 22,
|
||||
color: ColorsManager.dialogBlueTitle,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 25,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Colors.grey,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
child: IconButton(
|
||||
padding: const EdgeInsets.all(1),
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: Colors.grey,
|
||||
size: 18,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule__garage_table.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class ScheduleGarageManagementUI extends StatelessWidget {
|
||||
final GarageDoorLoadedState state;
|
||||
final Function onAddSchedule;
|
||||
|
||||
const ScheduleGarageManagementUI({
|
||||
super.key,
|
||||
required this.state,
|
||||
required this.onAddSchedule,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 170,
|
||||
height: 40,
|
||||
child: DefaultButton(
|
||||
borderColor: ColorsManager.boxColor,
|
||||
padding: 2,
|
||||
backgroundColor: ColorsManager.graysColor,
|
||||
borderRadius: 15,
|
||||
onPressed: () => onAddSchedule(),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.add, color: ColorsManager.primaryColor),
|
||||
Text(
|
||||
' Add new schedule',
|
||||
style: context.textTheme.bodySmall!.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ScheduleGarageTableWidget(state: state),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class ScheduleGarageModeButtons extends StatelessWidget {
|
||||
final VoidCallback onSave;
|
||||
|
||||
const ScheduleGarageModeButtons({
|
||||
super.key,
|
||||
required this.onSave,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Expanded(
|
||||
child: DefaultButton(
|
||||
height: 40,
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
backgroundColor: ColorsManager.boxColor,
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
Expanded(
|
||||
child: DefaultButton(
|
||||
height: 40,
|
||||
onPressed: onSave,
|
||||
backgroundColor: ColorsManager.primaryColor,
|
||||
child: const Text('Save'),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class ScheduleGarageDoorModeSelector extends StatelessWidget {
|
||||
final GarageDoorLoadedState state;
|
||||
|
||||
const ScheduleGarageDoorModeSelector({super.key, required this.state});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Type:',
|
||||
style: context.textTheme.bodySmall!.copyWith(
|
||||
fontSize: 13,
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildRadioTile(context, 'Schedule', ScheduleModes.schedule, state),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRadioTile(BuildContext context, String label, ScheduleModes mode, GarageDoorLoadedState state) {
|
||||
return Flexible(
|
||||
child: ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(
|
||||
label,
|
||||
style: context.textTheme.bodySmall!.copyWith(
|
||||
fontSize: 13,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
leading: Radio<ScheduleModes>(
|
||||
value: mode,
|
||||
groupValue: state.scheduleMode,
|
||||
onChanged: (ScheduleModes? value) {
|
||||
if (value != null) {
|
||||
if (value == ScheduleModes.schedule) {
|
||||
context.read<GarageDoorBloc>().add(
|
||||
FetchGarageDoorSchedulesEvent(
|
||||
category: 'switch_1',
|
||||
deviceId: state.status.uuid,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,96 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/helper/garage_door_helper.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_header.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_managment_ui.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart';
|
||||
|
||||
class BuildGarageDoorScheduleView extends StatefulWidget {
|
||||
const BuildGarageDoorScheduleView({super.key, required this.status});
|
||||
|
||||
final GarageDoorStatusModel status;
|
||||
|
||||
@override
|
||||
State<BuildGarageDoorScheduleView> createState() => _BuildScheduleViewState();
|
||||
}
|
||||
|
||||
class _BuildScheduleViewState extends State<BuildGarageDoorScheduleView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bloc = BlocProvider.of<GarageDoorBloc>(context);
|
||||
|
||||
return BlocProvider.value(
|
||||
value: bloc,
|
||||
child: Dialog(
|
||||
backgroundColor: Colors.white,
|
||||
insetPadding: const EdgeInsets.all(20),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 700,
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 40.0, vertical: 20),
|
||||
child: BlocBuilder<GarageDoorBloc, GarageDoorState>(
|
||||
builder: (context, state) {
|
||||
if (state is GarageDoorLoadedState) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const ScheduleGarageHeader(),
|
||||
const SizedBox(height: 20),
|
||||
ScheduleGarageManagementUI(
|
||||
state: state,
|
||||
onAddSchedule: () {
|
||||
GarageDoorDialogHelper
|
||||
.showAddGarageDoorScheduleDialog(context,
|
||||
schedule: null, index: null, isEdit: false);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ScheduleGarageModeButtons(
|
||||
onSave: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
if (state is ScheduleGarageLoadingState) {
|
||||
return SizedBox(
|
||||
height: 200,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const ScheduleGarageHeader(),
|
||||
const SizedBox(
|
||||
height: 50,
|
||||
),
|
||||
const Center(child: CircularProgressIndicator()),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
ScheduleGarageModeButtons(
|
||||
onSave: () {},
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
return const SizedBox(
|
||||
height: 200,
|
||||
child: ScheduleGarageHeader(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SecondsPicker extends StatefulWidget {
|
||||
final int initialSeconds;
|
||||
final ValueChanged<int> onSecondsChanged;
|
||||
|
||||
SecondsPicker({
|
||||
required this.initialSeconds,
|
||||
required this.onSecondsChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
_SecondsPickerState createState() => _SecondsPickerState();
|
||||
}
|
||||
|
||||
class _SecondsPickerState extends State<SecondsPicker> {
|
||||
late FixedExtentScrollController _scrollController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController = FixedExtentScrollController(
|
||||
initialItem: widget.initialSeconds,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 120,
|
||||
color: Colors.white,
|
||||
child: ListWheelScrollView.useDelegate(
|
||||
controller: _scrollController,
|
||||
itemExtent: 48,
|
||||
onSelectedItemChanged: (index) {
|
||||
widget.onSecondsChanged(index);
|
||||
},
|
||||
physics: const FixedExtentScrollPhysics(),
|
||||
childDelegate: ListWheelChildBuilderDelegate(
|
||||
builder: (context, index) {
|
||||
return Center(
|
||||
child: Text(
|
||||
'$index sec',
|
||||
style: const TextStyle(fontSize: 24),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart';
|
||||
|
||||
class TimeOutAlarmDialogBody extends StatefulWidget {
|
||||
TimeOutAlarmDialogBody(this.bloc);
|
||||
final GarageDoorBloc bloc;
|
||||
|
||||
@override
|
||||
_TimeOutAlarmDialogBodyState createState() => _TimeOutAlarmDialogBodyState();
|
||||
}
|
||||
|
||||
class _TimeOutAlarmDialogBodyState extends State<TimeOutAlarmDialogBody> {
|
||||
int durationInSeconds = 0;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
|
||||
final currentState = widget.bloc.state;
|
||||
if (currentState is GarageDoorLoadedState) {
|
||||
if (currentState.status.countdownAlarm != 0) {
|
||||
setState(() {
|
||||
durationInSeconds = currentState.status.countdownAlarm;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 120,
|
||||
color: Colors.white,
|
||||
child: CupertinoTimerPicker(
|
||||
itemExtent: 120,
|
||||
mode: CupertinoTimerPickerMode.hm,
|
||||
initialTimerDuration: Duration(seconds: durationInSeconds),
|
||||
onTimerDurationChanged: (newDuration) {
|
||||
widget.bloc.add(
|
||||
UpdateCountdownAlarmEvent(newDuration.inSeconds),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/model/device_model.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
|
||||
@ -12,14 +13,13 @@ class GateWayBloc extends Bloc<GateWayEvent, GateWayState> {
|
||||
GateWayBloc() : super(GateWayInitial()) {
|
||||
on<GateWayFetch>((event, emit) {});
|
||||
on<GatWayById>(_getGatWayById);
|
||||
on<GateWayFactoryReset>(_onFactoryReset);
|
||||
}
|
||||
|
||||
FutureOr<void> _getGatWayById(
|
||||
GatWayById event, Emitter<GateWayState> emit) async {
|
||||
FutureOr<void> _getGatWayById(GatWayById event, Emitter<GateWayState> emit) async {
|
||||
emit(GatewayLoadingState());
|
||||
try {
|
||||
List<DeviceModel> devicesList =
|
||||
await DevicesManagementApi.getDevicesByGatewayId(event.getWayId);
|
||||
List<DeviceModel> devicesList = await DevicesManagementApi.getDevicesByGatewayId(event.getWayId);
|
||||
|
||||
emit(UpdateGatewayState(list: devicesList));
|
||||
} catch (e) {
|
||||
@ -27,4 +27,21 @@ class GateWayBloc extends Bloc<GateWayEvent, GateWayState> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onFactoryReset(GateWayFactoryReset event, Emitter<GateWayState> emit) async {
|
||||
emit(GatewayLoadingState());
|
||||
try {
|
||||
final response = await DevicesManagementApi().factoryReset(
|
||||
event.factoryReset,
|
||||
event.deviceId,
|
||||
);
|
||||
if (!response) {
|
||||
emit(const ErrorState(message: 'Failed'));
|
||||
} else {
|
||||
add(GatWayById(event.deviceId));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(ErrorState(message: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,3 +18,15 @@ class GatWayById extends GateWayEvent {
|
||||
final String getWayId;
|
||||
const GatWayById(this.getWayId);
|
||||
}
|
||||
|
||||
class GateWayFactoryReset extends GateWayEvent {
|
||||
final String deviceId;
|
||||
final FactoryResetModel factoryReset;
|
||||
const GateWayFactoryReset({
|
||||
required this.deviceId,
|
||||
required this.factoryReset,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId, factoryReset];
|
||||
}
|
||||
|
||||
@ -0,0 +1,59 @@
|
||||
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/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/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class GatewayBatchControlView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
const GatewayBatchControlView({super.key, required this.gatewayIds});
|
||||
|
||||
final List<String> gatewayIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => GateWayBloc()..add(GatWayById(gatewayIds.first)),
|
||||
child: BlocBuilder<GateWayBloc, GateWayState>(
|
||||
builder: (context, state) {
|
||||
if (state is GatewayLoadingState) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is UpdateGatewayState) {
|
||||
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: FactoryResetWidget(
|
||||
callFactoryReset: () {
|
||||
context.read<GateWayBloc>().add(
|
||||
GateWayFactoryReset(
|
||||
deviceId: gatewayIds.first,
|
||||
factoryReset:
|
||||
FactoryResetModel(devicesUuid: gatewayIds),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -5,15 +5,17 @@ import 'package:syncrow_web/pages/device_managment/gateway/bloc/gate_way_bloc.da
|
||||
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/model/device_model.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';
|
||||
|
||||
class GateWayControls extends StatelessWidget with HelperResponsiveLayout {
|
||||
const GateWayControls({super.key, required this.gatewayId});
|
||||
class GateWayControlsView extends StatelessWidget with HelperResponsiveLayout {
|
||||
const GateWayControlsView({super.key, required this.gatewayId});
|
||||
|
||||
final String gatewayId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
|
||||
@ -24,28 +26,64 @@ class GateWayControls extends StatelessWidget with HelperResponsiveLayout {
|
||||
if (state is GatewayLoadingState) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is UpdateGatewayState) {
|
||||
return GridView.builder(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge
|
||||
? 3
|
||||
: isMedium
|
||||
? 2
|
||||
: 1,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
itemCount: state.list.length,
|
||||
itemBuilder: (context, index) {
|
||||
final device = state.list[index];
|
||||
return _DeviceItem(device: device);
|
||||
},
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
"Bluetooth Devices:",
|
||||
style: context.textTheme.bodyMedium!.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
"No devices found",
|
||||
style: context.textTheme.bodySmall!.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Text(
|
||||
"ZigBee Devices:",
|
||||
style: context.textTheme.bodyMedium!.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
GridView.builder(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge || isExtraLarge
|
||||
? 3
|
||||
: isMedium
|
||||
? 2
|
||||
: 1,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
itemCount: state.list.length,
|
||||
itemBuilder: (context, index) {
|
||||
final device = state.list[index];
|
||||
return _DeviceItem(device: device);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const Center(child: Text('Error fetching devices'));
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
}
|
||||
},
|
||||
),
|
||||
@ -66,26 +104,23 @@ class _DeviceItem extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 60,
|
||||
ClipOval(
|
||||
child: Container(
|
||||
height: 60,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: ColorsManager.whiteColors,
|
||||
width: 60,
|
||||
padding: const EdgeInsets.all(8),
|
||||
color: ColorsManager.whiteColors,
|
||||
child: SvgPicture.asset(
|
||||
device.icon ?? 'assets/icons/gateway.svg',
|
||||
width: 35,
|
||||
height: 35,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 4),
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: ClipOval(
|
||||
child: SvgPicture.asset(
|
||||
device.icon,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
const Spacer(),
|
||||
Text(
|
||||
device.name ?? 'Unknown Device',
|
||||
textAlign: TextAlign.center,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
part of 'living_room_bloc.dart';
|
||||
|
||||
sealed class LivingRoomEvent extends Equatable {
|
||||
const LivingRoomEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LivingRoomFetchDeviceStatus extends LivingRoomEvent {
|
||||
final String deviceId;
|
||||
|
||||
const LivingRoomFetchDeviceStatus(this.deviceId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId];
|
||||
}
|
||||
|
||||
class LivingRoomControl extends LivingRoomEvent {
|
||||
final String deviceId;
|
||||
final String code;
|
||||
final bool value;
|
||||
|
||||
const LivingRoomControl(
|
||||
{required this.deviceId, required this.code, required this.value});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId, code, value];
|
||||
}
|
||||
@ -1,80 +0,0 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/living_room_switch/bloc/living_room_bloc.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class ToggleWidget extends StatelessWidget {
|
||||
final bool value;
|
||||
final String code;
|
||||
final String deviceId;
|
||||
final String label;
|
||||
|
||||
const ToggleWidget({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.code,
|
||||
required this.deviceId,
|
||||
required this.label,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: ColorsManager.greyColor.withOpacity(0.2),
|
||||
border: Border.all(color: ColorsManager.boxDivider),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
ClipOval(
|
||||
child: Container(
|
||||
color: ColorsManager.whiteColors,
|
||||
child: SvgPicture.asset(
|
||||
Assets.lightPulp,
|
||||
width: 60,
|
||||
height: 60,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
)),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
width: 35,
|
||||
child: CupertinoSwitch(
|
||||
value: value,
|
||||
activeColor: ColorsManager.dialogBlueTitle,
|
||||
onChanged: (newValue) {
|
||||
context.read<LivingRoomBloc>().add(
|
||||
LivingRoomControl(
|
||||
deviceId: deviceId,
|
||||
code: code,
|
||||
value: newValue,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,159 @@
|
||||
import 'dart:async';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/main_door_sensor/models/main_door_status_model.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
|
||||
class MainDoorSensorBloc
|
||||
extends Bloc<MainDoorSensorEvent, MainDoorSensorState> {
|
||||
MainDoorSensorBloc() : super(MainDoorSensorInitial()) {
|
||||
on<MainDoorSensorFetchDeviceEvent>(_onFetchDeviceStatus);
|
||||
on<MainDoorSensorControl>(_onControl);
|
||||
on<MainDoorSensorFetchBatchEvent>(_onFetchBatchStatus);
|
||||
on<MainDoorSensorReportsEvent>(_fetchReports);
|
||||
on<MainDoorSensorFactoryReset>(_factoryReset);
|
||||
}
|
||||
|
||||
late MainDoorSensorStatusModel deviceStatus;
|
||||
Timer? _timer;
|
||||
|
||||
FutureOr<void> _onFetchDeviceStatus(MainDoorSensorFetchDeviceEvent event,
|
||||
Emitter<MainDoorSensorState> emit) async {
|
||||
emit(MainDoorSensorLoadingState());
|
||||
try {
|
||||
final status = await DevicesManagementApi()
|
||||
.getDeviceStatus(event.deviceId)
|
||||
.then((value) => value.status);
|
||||
|
||||
deviceStatus = MainDoorSensorStatusModel.fromJson(event.deviceId, status);
|
||||
emit(MainDoorSensorDeviceStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(MainDoorSensorFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onControl(
|
||||
MainDoorSensorControl event, Emitter<MainDoorSensorState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
_updateLocalValue(event.code, event.value);
|
||||
|
||||
emit(MainDoorSensorDeviceStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _runDebounce({
|
||||
required String deviceId,
|
||||
required String code,
|
||||
required bool value,
|
||||
required bool oldValue,
|
||||
required Emitter<MainDoorSensorState> emit,
|
||||
}) async {
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
|
||||
_timer = Timer(const Duration(milliseconds: 500), () async {
|
||||
try {
|
||||
final response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: value));
|
||||
|
||||
if (!response) {
|
||||
_revertValueAndEmit(deviceId, code, oldValue, emit);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e is DioException && e.response != null) {
|
||||
debugPrint('Error response: ${e.response?.data}');
|
||||
}
|
||||
_revertValueAndEmit(deviceId, code, oldValue, emit);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _revertValueAndEmit(String deviceId, String code, bool oldValue,
|
||||
Emitter<MainDoorSensorState> emit) {
|
||||
_updateLocalValue(code, oldValue);
|
||||
emit(MainDoorSensorDeviceStatusLoaded(deviceStatus));
|
||||
}
|
||||
|
||||
void _updateLocalValue(String code, bool value) {
|
||||
switch (code) {
|
||||
case 'doorcontact_state':
|
||||
deviceStatus = deviceStatus.copyWith(doorContactState: value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool _getValueByCode(String code) {
|
||||
switch (code) {
|
||||
case 'doorcontact_state':
|
||||
return deviceStatus.doorContactState;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch batch status for multiple devices (if needed)
|
||||
FutureOr<void> _onFetchBatchStatus(MainDoorSensorFetchBatchEvent event,
|
||||
Emitter<MainDoorSensorState> emit) async {
|
||||
emit(MainDoorSensorLoadingState());
|
||||
try {
|
||||
// final batchStatus =
|
||||
// await DevicesManagementApi().getBatchDeviceStatus(event.deviceIds);
|
||||
// Assuming you need to update multiple devices status here
|
||||
// You might need a list or map of MainDoorSensorStatusModel for batch processing
|
||||
// emit(MainDoorSensorBatchStatusLoaded(batchStatus));
|
||||
} catch (e) {
|
||||
emit(MainDoorSensorBatchFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _fetchReports(MainDoorSensorReportsEvent event,
|
||||
Emitter<MainDoorSensorState> emit) async {
|
||||
emit(MainDoorSensorLoadingState());
|
||||
try {
|
||||
final reports = await DevicesManagementApi.getDeviceReportsByDate(
|
||||
event.deviceId, event.code, event.from, event.to);
|
||||
emit(MainDoorSensorReportLoaded(reports));
|
||||
} catch (e) {
|
||||
emit(MainDoorSensorFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_timer?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
FutureOr<void> _factoryReset(MainDoorSensorFactoryReset event,
|
||||
Emitter<MainDoorSensorState> emit) async {
|
||||
emit(MainDoorSensorLoadingState());
|
||||
try {
|
||||
final response = await DevicesManagementApi().factoryReset(
|
||||
event.factoryReset,
|
||||
event.deviceId,
|
||||
);
|
||||
if (!response) {
|
||||
emit(MainDoorSensorFailedState(error: 'Failed'));
|
||||
} else {
|
||||
add(MainDoorSensorFetchDeviceEvent(event.deviceId));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(MainDoorSensorFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
import '../../all_devices/models/factory_reset_model.dart';
|
||||
|
||||
class MainDoorSensorEvent extends Equatable {
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class MainDoorSensorFetchDeviceEvent extends MainDoorSensorEvent {
|
||||
final String deviceId;
|
||||
|
||||
MainDoorSensorFetchDeviceEvent(this.deviceId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId];
|
||||
}
|
||||
|
||||
class MainDoorSensorFetchBatchEvent extends MainDoorSensorEvent {
|
||||
final String deviceId;
|
||||
|
||||
MainDoorSensorFetchBatchEvent(this.deviceId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId];
|
||||
}
|
||||
|
||||
class MainDoorSensorControl extends MainDoorSensorEvent {
|
||||
final String deviceId;
|
||||
final String code;
|
||||
final bool value;
|
||||
|
||||
MainDoorSensorControl(
|
||||
{required this.deviceId, required this.code, required this.value});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId, code, value];
|
||||
}
|
||||
|
||||
class MainDoorSensorBatchControl extends MainDoorSensorEvent {
|
||||
final List<String> deviceId;
|
||||
final String code;
|
||||
final bool value;
|
||||
|
||||
MainDoorSensorBatchControl(
|
||||
{required this.deviceId, required this.code, required this.value});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId, code, value];
|
||||
}
|
||||
|
||||
class MainDoorSensorReportsEvent extends MainDoorSensorEvent {
|
||||
final String deviceId;
|
||||
final String code;
|
||||
final String from;
|
||||
final String to;
|
||||
@override
|
||||
List<Object> get props => [deviceId, code, from, to];
|
||||
|
||||
MainDoorSensorReportsEvent(
|
||||
{required this.deviceId,
|
||||
required this.code,
|
||||
required this.from,
|
||||
required this.to});
|
||||
}
|
||||
|
||||
class MainDoorSensorFactoryReset extends MainDoorSensorEvent {
|
||||
final String deviceId;
|
||||
final FactoryResetModel factoryReset;
|
||||
|
||||
MainDoorSensorFactoryReset(
|
||||
{required this.deviceId, required this.factoryReset});
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/main_door_sensor/models/main_door_status_model.dart';
|
||||
|
||||
class MainDoorSensorState extends Equatable {
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class MainDoorSensorInitial extends MainDoorSensorState {}
|
||||
|
||||
class MainDoorSensorLoadingState extends MainDoorSensorState {}
|
||||
|
||||
class MainDoorSensorDeviceStatusLoaded extends MainDoorSensorState {
|
||||
final MainDoorSensorStatusModel status;
|
||||
|
||||
MainDoorSensorDeviceStatusLoaded(this.status);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status];
|
||||
}
|
||||
|
||||
class MainDoorSensorFailedState extends MainDoorSensorState {
|
||||
final String error;
|
||||
|
||||
MainDoorSensorFailedState({required this.error});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [error];
|
||||
}
|
||||
|
||||
class MainDoorSensorBatchFailedState extends MainDoorSensorState {
|
||||
final String error;
|
||||
|
||||
MainDoorSensorBatchFailedState({required this.error});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [error];
|
||||
}
|
||||
|
||||
class MainDoorSensorBatchStatusLoaded extends MainDoorSensorState {
|
||||
final List<MainDoorSensorStatusModel> status;
|
||||
|
||||
MainDoorSensorBatchStatusLoaded(this.status);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status];
|
||||
}
|
||||
|
||||
class MainDoorSensorReportLoaded extends MainDoorSensorState {
|
||||
final DeviceReport deviceReport;
|
||||
|
||||
MainDoorSensorReportLoaded(this.deviceReport);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [deviceReport];
|
||||
}
|
||||
|
||||
class MainDoorSensorReportsLoadingState extends MainDoorSensorState {}
|
||||
|
||||
class MainDoorSensorReportsFailedState extends MainDoorSensorState {
|
||||
final String error;
|
||||
|
||||
MainDoorSensorReportsFailedState({required this.error});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [error];
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
|
||||
class MainDoorSensorStatusModel {
|
||||
final String uuid;
|
||||
final bool doorContactState;
|
||||
final int batteryPercentage;
|
||||
|
||||
MainDoorSensorStatusModel({
|
||||
required this.uuid,
|
||||
required this.doorContactState,
|
||||
required this.batteryPercentage,
|
||||
});
|
||||
|
||||
factory MainDoorSensorStatusModel.fromJson(String id, List<Status> jsonList) {
|
||||
late bool doorContactState = false;
|
||||
late int batteryPercentage = 0;
|
||||
|
||||
for (var status in jsonList) {
|
||||
switch (status.code) {
|
||||
case 'doorcontact_state':
|
||||
doorContactState = status.value ?? false;
|
||||
break;
|
||||
case 'battery_percentage':
|
||||
batteryPercentage = status.value ?? 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return MainDoorSensorStatusModel(
|
||||
uuid: id,
|
||||
doorContactState: doorContactState,
|
||||
batteryPercentage: batteryPercentage,
|
||||
);
|
||||
}
|
||||
|
||||
MainDoorSensorStatusModel copyWith({
|
||||
String? uuid,
|
||||
bool? doorContactState,
|
||||
int? batteryPercentage,
|
||||
}) {
|
||||
return MainDoorSensorStatusModel(
|
||||
uuid: uuid ?? this.uuid,
|
||||
doorContactState: doorContactState ?? this.doorContactState,
|
||||
batteryPercentage: batteryPercentage ?? this.batteryPercentage,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,113 @@
|
||||
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/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/main_door_sensor/bloc/main_door_sensor_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/main_door_sensor/models/main_door_status_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/main_door_sensor/widgets/notification_dialog.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/icon_name_status_container.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class MainDoorSensorControlView extends StatelessWidget with HelperResponsiveLayout {
|
||||
const MainDoorSensorControlView({super.key, required this.device});
|
||||
|
||||
final AllDevicesModel device;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => MainDoorSensorBloc()..add(MainDoorSensorFetchDeviceEvent(device.uuid!)),
|
||||
child: BlocBuilder<MainDoorSensorBloc, MainDoorSensorState>(
|
||||
builder: (context, state) {
|
||||
if (state is MainDoorSensorLoadingState || state is MainDoorSensorReportsLoadingState) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is MainDoorSensorDeviceStatusLoaded) {
|
||||
return _buildStatusControls(context, state.status);
|
||||
} else if (state is MainDoorSensorReportLoaded) {
|
||||
return ReportsTable(
|
||||
report: state.deviceReport,
|
||||
onRowTap: (index) {},
|
||||
onClose: () {
|
||||
context.read<MainDoorSensorBloc>().add(MainDoorSensorFetchDeviceEvent(device.uuid!));
|
||||
},
|
||||
hideValueShowDescription: true,
|
||||
mainDoorSensor: true,
|
||||
);
|
||||
} else if (state is MainDoorSensorFailedState || state is MainDoorSensorBatchFailedState) {
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
} else {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildStatusControls(BuildContext context, MainDoorSensorStatusModel status) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge || isExtraLarge
|
||||
? 3
|
||||
: isMedium
|
||||
? 2
|
||||
: 1,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
children: [
|
||||
IconNameStatusContainer(
|
||||
isFullIcon: true,
|
||||
name: status.doorContactState ? 'Open' : 'Close',
|
||||
icon: Assets.openCloseDoor,
|
||||
onTap: () {},
|
||||
status: status.doorContactState,
|
||||
textColor: status.doorContactState ? ColorsManager.red : ColorsManager.blackColor,
|
||||
paddingAmount: 8,
|
||||
),
|
||||
IconNameStatusContainer(
|
||||
isFullIcon: true,
|
||||
name: 'Open/Close\nRecord',
|
||||
icon: Assets.openCloseRecords,
|
||||
onTap: () {
|
||||
final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch;
|
||||
final to = DateTime.now().millisecondsSinceEpoch;
|
||||
context.read<MainDoorSensorBloc>().add(
|
||||
MainDoorSensorReportsEvent(
|
||||
deviceId: device.uuid!,
|
||||
code: 'doorcontact_state',
|
||||
from: from.toString(),
|
||||
to: to.toString(),
|
||||
),
|
||||
);
|
||||
},
|
||||
status: false,
|
||||
textColor: ColorsManager.blackColor,
|
||||
),
|
||||
IconNameStatusContainer(
|
||||
isFullIcon: false,
|
||||
name: 'Notifications\nSettings',
|
||||
icon: Assets.mainDoorNotifi,
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => const NotificationDialog(),
|
||||
);
|
||||
},
|
||||
status: false,
|
||||
textColor: ColorsManager.blackColor,
|
||||
paddingAmount: 14,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
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/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';
|
||||
|
||||
class MainDoorSensorBatchView extends StatelessWidget {
|
||||
const MainDoorSensorBatchView({super.key, required this.devicesIds});
|
||||
|
||||
final List<String> devicesIds;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
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: FactoryResetWidget(
|
||||
callFactoryReset: () {
|
||||
BlocProvider.of<MainDoorSensorBloc>(context).add(
|
||||
MainDoorSensorFactoryReset(
|
||||
deviceId: devicesIds.first,
|
||||
factoryReset: FactoryResetModel(devicesUuid: devicesIds),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class NotificationDialog extends StatefulWidget {
|
||||
const NotificationDialog({super.key});
|
||||
|
||||
@override
|
||||
State<NotificationDialog> createState() => _NotificationDialogState();
|
||||
}
|
||||
|
||||
class _NotificationDialogState extends State<NotificationDialog> {
|
||||
bool isLowBatteryNotificationEnabled = true;
|
||||
bool isClosingRemindersEnabled = true;
|
||||
bool isDoorAlarmEnabled = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
backgroundColor: Colors.white,
|
||||
insetPadding: const EdgeInsets.all(20),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 660,
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const SizedBox(),
|
||||
Text(
|
||||
'Notification Settings',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 22,
|
||||
color: ColorsManager.dialogBlueTitle,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 25,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Colors.grey,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.all(1),
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: Colors.grey,
|
||||
size: 18,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 170,
|
||||
height: 135,
|
||||
child: ToggleWidget(
|
||||
value: isLowBatteryNotificationEnabled,
|
||||
code: 'notification',
|
||||
deviceId: '',
|
||||
label: 'Low Battery',
|
||||
onChange: (v) {
|
||||
setState(() {
|
||||
isLowBatteryNotificationEnabled = v;
|
||||
});
|
||||
},
|
||||
icon: '-1',
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 170,
|
||||
height: 135,
|
||||
child: ToggleWidget(
|
||||
value: isClosingRemindersEnabled,
|
||||
code: 'notification',
|
||||
deviceId: '',
|
||||
label: 'Closing\nReminders',
|
||||
onChange: (v) {
|
||||
setState(() {
|
||||
isClosingRemindersEnabled = v;
|
||||
});
|
||||
},
|
||||
icon: '-1',
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 170,
|
||||
height: 135,
|
||||
child: ToggleWidget(
|
||||
value: isDoorAlarmEnabled,
|
||||
code: 'notification',
|
||||
deviceId: '',
|
||||
label: 'Door Alarm',
|
||||
onChange: (v) {
|
||||
setState(() {
|
||||
isDoorAlarmEnabled = v;
|
||||
});
|
||||
},
|
||||
icon: '-1',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,159 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
|
||||
part 'one_gang_glass_switch_event.dart';
|
||||
part 'one_gang_glass_switch_state.dart';
|
||||
|
||||
class OneGangGlassSwitchBloc extends Bloc<OneGangGlassSwitchEvent, OneGangGlassSwitchState> {
|
||||
OneGangGlassStatusModel deviceStatus;
|
||||
Timer? _timer;
|
||||
|
||||
OneGangGlassSwitchBloc({required String deviceId})
|
||||
: deviceStatus = OneGangGlassStatusModel(uuid: deviceId, switch1: false, countDown: 0),
|
||||
super(OneGangGlassSwitchInitial()) {
|
||||
on<OneGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
|
||||
on<OneGangGlassSwitchControl>(_onControl);
|
||||
on<OneGangGlassSwitchBatchControl>(_onBatchControl);
|
||||
on<OneGangGlassSwitchFetchBatchStatusEvent>(_onFetchBatchStatus);
|
||||
on<OneGangGlassFactoryResetEvent>(_onFactoryReset);
|
||||
}
|
||||
|
||||
Future<void> _onFetchDeviceStatus(
|
||||
OneGangGlassSwitchFetchDeviceEvent event, Emitter<OneGangGlassSwitchState> emit) async {
|
||||
emit(OneGangGlassSwitchLoading());
|
||||
try {
|
||||
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceId, status.status);
|
||||
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(OneGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onControl(OneGangGlassSwitchControl event, Emitter<OneGangGlassSwitchState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
_updateLocalValue(event.code, event.value);
|
||||
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: false,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onFactoryReset(OneGangGlassFactoryResetEvent event, Emitter<OneGangGlassSwitchState> emit) async {
|
||||
emit(OneGangGlassSwitchLoading());
|
||||
try {
|
||||
final response = await DevicesManagementApi().factoryReset(event.factoryReset, event.deviceId);
|
||||
if (!response) {
|
||||
emit(OneGangGlassSwitchError('Failed to reset device'));
|
||||
} else {
|
||||
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(OneGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onBatchControl(OneGangGlassSwitchBatchControl event, Emitter<OneGangGlassSwitchState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
_updateLocalValue(event.code, event.value);
|
||||
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.deviceIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: true,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onFetchBatchStatus(
|
||||
OneGangGlassSwitchFetchBatchStatusEvent event, Emitter<OneGangGlassSwitchState> emit) async {
|
||||
emit(OneGangGlassSwitchLoading());
|
||||
try {
|
||||
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
|
||||
deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceIds.first, status.status);
|
||||
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(OneGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _runDebounce({
|
||||
required dynamic deviceId,
|
||||
required String code,
|
||||
required bool value,
|
||||
required bool oldValue,
|
||||
required Emitter<OneGangGlassSwitchState> emit,
|
||||
required bool isBatch,
|
||||
}) async {
|
||||
late String id;
|
||||
if (deviceId is List) {
|
||||
id = deviceId.first;
|
||||
} else {
|
||||
id = deviceId;
|
||||
}
|
||||
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
|
||||
_timer = Timer(const Duration(milliseconds: 500), () async {
|
||||
try {
|
||||
late bool response;
|
||||
if (isBatch) {
|
||||
response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value);
|
||||
} else {
|
||||
response = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value));
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
} catch (e) {
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _revertValueAndEmit(String deviceId, String code, bool oldValue, Emitter<OneGangGlassSwitchState> emit) {
|
||||
_updateLocalValue(code, oldValue);
|
||||
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
}
|
||||
|
||||
void _updateLocalValue(String code, bool value) {
|
||||
if (code == 'switch_1') {
|
||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||
}
|
||||
}
|
||||
|
||||
bool _getValueByCode(String code) {
|
||||
switch (code) {
|
||||
case 'switch_1':
|
||||
return deviceStatus.switch1;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_timer?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
part of 'one_gang_glass_switch_bloc.dart';
|
||||
|
||||
@immutable
|
||||
abstract class OneGangGlassSwitchEvent {}
|
||||
|
||||
class OneGangGlassSwitchFetchDeviceEvent extends OneGangGlassSwitchEvent {
|
||||
final String deviceId;
|
||||
|
||||
OneGangGlassSwitchFetchDeviceEvent(this.deviceId);
|
||||
}
|
||||
|
||||
class OneGangGlassSwitchControl extends OneGangGlassSwitchEvent {
|
||||
final String deviceId;
|
||||
final String code;
|
||||
final bool value;
|
||||
|
||||
OneGangGlassSwitchControl({
|
||||
required this.deviceId,
|
||||
required this.code,
|
||||
required this.value,
|
||||
});
|
||||
}
|
||||
|
||||
class OneGangGlassSwitchBatchControl extends OneGangGlassSwitchEvent {
|
||||
final List<String> deviceIds;
|
||||
final String code;
|
||||
final bool value;
|
||||
|
||||
OneGangGlassSwitchBatchControl({
|
||||
required this.deviceIds,
|
||||
required this.code,
|
||||
required this.value,
|
||||
});
|
||||
}
|
||||
|
||||
class OneGangGlassSwitchFetchBatchStatusEvent extends OneGangGlassSwitchEvent {
|
||||
final List<String> deviceIds;
|
||||
|
||||
OneGangGlassSwitchFetchBatchStatusEvent(this.deviceIds);
|
||||
}
|
||||
|
||||
class OneGangGlassFactoryResetEvent extends OneGangGlassSwitchEvent {
|
||||
final FactoryResetModel factoryReset;
|
||||
final String deviceId;
|
||||
|
||||
OneGangGlassFactoryResetEvent({
|
||||
required this.factoryReset,
|
||||
required this.deviceId,
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
part of 'one_gang_glass_switch_bloc.dart';
|
||||
|
||||
@immutable
|
||||
abstract class OneGangGlassSwitchState {}
|
||||
|
||||
class OneGangGlassSwitchInitial extends OneGangGlassSwitchState {}
|
||||
|
||||
class OneGangGlassSwitchLoading extends OneGangGlassSwitchState {}
|
||||
|
||||
class OneGangGlassSwitchStatusLoaded extends OneGangGlassSwitchState {
|
||||
final OneGangGlassStatusModel status;
|
||||
|
||||
OneGangGlassSwitchStatusLoaded(this.status);
|
||||
}
|
||||
|
||||
class OneGangGlassSwitchError extends OneGangGlassSwitchState {
|
||||
final String message;
|
||||
|
||||
OneGangGlassSwitchError(this.message);
|
||||
}
|
||||
|
||||
class OneGangGlassSwitchBatchStatusLoaded extends OneGangGlassSwitchState {
|
||||
final OneGangGlassStatusModel status;
|
||||
|
||||
OneGangGlassSwitchBatchStatusLoaded(this.status);
|
||||
}
|
||||
|
||||
class OneGangGlassSwitchBatchControlError extends OneGangGlassSwitchState {
|
||||
final String message;
|
||||
|
||||
OneGangGlassSwitchBatchControlError(this.message);
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
|
||||
class OneGangGlassStatusModel {
|
||||
final String uuid;
|
||||
final bool switch1;
|
||||
final int countDown;
|
||||
|
||||
OneGangGlassStatusModel({
|
||||
required this.uuid,
|
||||
required this.switch1,
|
||||
required this.countDown,
|
||||
});
|
||||
|
||||
factory OneGangGlassStatusModel.fromJson(String id, List<Status> jsonList) {
|
||||
late bool switch1;
|
||||
late int countDown;
|
||||
|
||||
for (var status in jsonList) {
|
||||
switch (status.code) {
|
||||
case 'switch_1':
|
||||
switch1 = status.value ?? false;
|
||||
break;
|
||||
case 'countdown_1':
|
||||
countDown = status.value ?? 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return OneGangGlassStatusModel(
|
||||
uuid: id,
|
||||
switch1: switch1,
|
||||
countDown: countDown,
|
||||
);
|
||||
}
|
||||
|
||||
OneGangGlassStatusModel copyWith({
|
||||
String? uuid,
|
||||
bool? switch1,
|
||||
int? countDown,
|
||||
}) {
|
||||
return OneGangGlassStatusModel(
|
||||
uuid: uuid ?? this.uuid,
|
||||
switch1: switch1 ?? this.switch1,
|
||||
countDown: countDown ?? this.countDown,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'OneGangGlassStatusModel(uuid: $uuid, switch1: $switch1, countDown: $countDown)';
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
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/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/toggle_widget.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class OneGangGlassSwitchBatchControlView extends StatelessWidget
|
||||
with HelperResponsiveLayout {
|
||||
final List<String> deviceIds;
|
||||
|
||||
const OneGangGlassSwitchBatchControlView(
|
||||
{required this.deviceIds, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => OneGangGlassSwitchBloc(deviceId: deviceIds.first)
|
||||
..add(OneGangGlassSwitchFetchBatchStatusEvent(deviceIds)),
|
||||
child: BlocBuilder<OneGangGlassSwitchBloc, OneGangGlassSwitchState>(
|
||||
builder: (context, state) {
|
||||
if (state is OneGangGlassSwitchLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is OneGangGlassSwitchStatusLoaded) {
|
||||
return _buildStatusControls(context, state.status);
|
||||
} else if (state is OneGangGlassSwitchError) {
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
} else {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusControls(
|
||||
BuildContext context, OneGangGlassStatusModel status) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge || isExtraLarge
|
||||
? 3
|
||||
: isMedium
|
||||
? 2
|
||||
: 1,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
children: [
|
||||
ToggleWidget(
|
||||
value: status.switch1,
|
||||
code: 'switch_1',
|
||||
deviceId: deviceIds.first,
|
||||
label: 'Wall Light',
|
||||
onChange: (value) {
|
||||
context.read<OneGangGlassSwitchBloc>().add(
|
||||
OneGangGlassSwitchBatchControl(
|
||||
deviceIds: deviceIds,
|
||||
code: 'switch_1',
|
||||
value: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
FirmwareUpdateWidget(
|
||||
deviceId: deviceIds.first,
|
||||
version: 12,
|
||||
),
|
||||
FactoryResetWidget(
|
||||
callFactoryReset: () {
|
||||
context.read<OneGangGlassSwitchBloc>().add(
|
||||
OneGangGlassFactoryResetEvent(
|
||||
factoryReset: FactoryResetModel(devicesUuid: deviceIds),
|
||||
deviceId: deviceIds.first,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
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/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 OneGangGlassSwitchControlView extends StatelessWidget with HelperResponsiveLayout {
|
||||
final String deviceId;
|
||||
|
||||
const OneGangGlassSwitchControlView({required this.deviceId, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
OneGangGlassSwitchBloc(deviceId: deviceId)..add(OneGangGlassSwitchFetchDeviceEvent(deviceId)),
|
||||
child: BlocBuilder<OneGangGlassSwitchBloc, OneGangGlassSwitchState>(
|
||||
builder: (context, state) {
|
||||
if (state is OneGangGlassSwitchLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is OneGangGlassSwitchStatusLoaded) {
|
||||
return _buildStatusControls(context, state.status);
|
||||
} else if (state is OneGangGlassSwitchError) {
|
||||
return const Center(child: Text('Error fetching status'));
|
||||
} else {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusControls(BuildContext context, OneGangGlassStatusModel status) {
|
||||
final isExtraLarge = isExtraLargeScreenSize(context);
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return GridView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: isLarge || isExtraLarge
|
||||
? 3
|
||||
: isMedium
|
||||
? 2
|
||||
: 1,
|
||||
mainAxisExtent: 140,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
children: [
|
||||
ToggleWidget(
|
||||
value: status.switch1,
|
||||
code: 'switch_1',
|
||||
deviceId: deviceId,
|
||||
label: "Wall Light",
|
||||
onChange: (value) {
|
||||
context.read<OneGangGlassSwitchBloc>().add(
|
||||
OneGangGlassSwitchControl(
|
||||
deviceId: deviceId,
|
||||
code: 'switch_1',
|
||||
value: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
ToggleWidget(
|
||||
value: false,
|
||||
code: '',
|
||||
deviceId: deviceId,
|
||||
label: 'Preferences',
|
||||
icon: Assets.preferences,
|
||||
onChange: (value) {},
|
||||
showToggle: false,
|
||||
),
|
||||
ToggleWidget(
|
||||
value: false,
|
||||
code: '',
|
||||
deviceId: deviceId,
|
||||
label: 'Scheduling',
|
||||
icon: Assets.scheduling,
|
||||
onChange: (value) {},
|
||||
showToggle: false,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user