Merged with dev

This commit is contained in:
Abdullah Alassaf
2025-02-02 22:32:15 +03:00
47 changed files with 2075 additions and 712 deletions

View File

@ -0,0 +1,18 @@
<svg width="47" height="37" viewBox="0 0 47 37" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="6" y="1" width="41" height="25" rx="12.5" fill="#E9E9E9"/>
<g filter="url(#filter0_d_6419_4507)">
<circle cx="18.5" cy="13.5" r="10.5" fill="white"/>
</g>
<defs>
<filter id="filter0_d_6419_4507" x="0" y="0" width="37" height="37" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="5"/>
<feGaussianBlur stdDeviation="4"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6419_4507"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6419_4507" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 965 B

View File

@ -0,0 +1,18 @@
<svg width="47" height="37" viewBox="0 0 47 37" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="1" width="41" height="25" rx="12.5" fill="#023DFE" fill-opacity="0.7"/>
<g filter="url(#filter0_d_6689_981)">
<circle cx="28.5" cy="13.5" r="10.5" fill="white"/>
</g>
<defs>
<filter id="filter0_d_6689_981" x="10" y="0" width="37" height="37" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="5"/>
<feGaussianBlur stdDeviation="4"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6689_981"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6689_981" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 975 B

View File

@ -289,12 +289,12 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/url_launcher_ios/ios" :path: ".symlinks/plugins/url_launcher_ios/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
Firebase: 374a441a91ead896215703a674d58cdb3e9d772b Firebase: 374a441a91ead896215703a674d58cdb3e9d772b
firebase_analytics: a5c6ef5a435d22870fe3cfdcb424f390f56ff752 firebase_analytics: 07bd7cfbac54bfcdccf2bb2530f9a65486f7ef3f
firebase_core: 2337982fb78ee4d8d91e608b0a3d4f44346a93c8 firebase_core: feb37e79f775c2bd08dd35e02d83678291317e10
firebase_crashlytics: 3b6a9a9cbdc5ab92afaf9b206e52c79c2321a0d4 firebase_crashlytics: 609a5f6f4a2f5af9e40a68182e0c1be3ca2a02f6
firebase_database: 8ba35f32ce38e53d81ac61fbe12e0e1b9277fe0f firebase_database: adc9efd0b70cdc8d1e6f3c9f6bb054a625c4f45d
FirebaseAnalytics: 7114c698cac995602e3b1b96663473e50d54d6e7 FirebaseAnalytics: 7114c698cac995602e3b1b96663473e50d54d6e7
FirebaseAppCheckInterop: 347aa09a805219a31249b58fc956888e9fcb314b FirebaseAppCheckInterop: 347aa09a805219a31249b58fc956888e9fcb314b
FirebaseCore: 48b0dd707581cf9c1a1220da68223fb0a562afaa FirebaseCore: 48b0dd707581cf9c1a1220da68223fb0a562afaa
@ -307,23 +307,23 @@ SPEC CHECKSUMS:
FirebaseSessions: 9529d14180868e29a8da164b3a729c036204918b FirebaseSessions: 9529d14180868e29a8da164b3a729c036204918b
FirebaseSharedSwift: a4e5dfca3e210633bb3a3dfb94176c019211948b FirebaseSharedSwift: a4e5dfca3e210633bb3a3dfb94176c019211948b
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_secure_storage: 2c2ff13db9e0a5647389bff88b0ecac56e3f3418 flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
GoogleAppMeasurement: 6a9e6317b6a6d810ad03d4a66564ca6c4c5818a3 GoogleAppMeasurement: 6a9e6317b6a6d810ad03d4a66564ca6c4c5818a3
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19 leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
onesignal_flutter: d86795eb74c65854b23169f131b4fb1ca3146659 onesignal_flutter: 5ce68a29861960168e81101cb1bd685d264361de
OneSignalXCFramework: bdf74fdc06888f9466dc21e826fe1549ed143095 OneSignalXCFramework: bdf74fdc06888f9466dc21e826fe1549ed143095
path_provider_foundation: 608fcb11be570ce83519b076ab6a1fffe2474f05 path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
shared_preferences_foundation: 0b09b969fb36da5551c0bc4a2dbd9d0ff9387478 shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
sqflite: c35dad70033b8862124f8337cc994a809fcd9fa3 sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
PODFILE CHECKSUM: deba6d843ff3cf709e6e9051ce6601a587b24105 PODFILE CHECKSUM: deba6d843ff3cf709e6e9051ce6601a587b24105

View File

@ -7,10 +7,10 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:onesignal_flutter/onesignal_flutter.dart'; import 'package:onesignal_flutter/onesignal_flutter.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import 'package:syncrow_app/features/app_layout/model/permission_model.dart';
import 'package:syncrow_app/features/app_layout/model/space_model.dart'; import 'package:syncrow_app/features/app_layout/model/space_model.dart';
import 'package:syncrow_app/features/app_layout/view/widgets/app_bar_home_dropdown.dart'; import 'package:syncrow_app/features/app_layout/view/widgets/app_bar_home_dropdown.dart';
import 'package:syncrow_app/features/auth/model/user_model.dart'; import 'package:syncrow_app/features/auth/model/user_model.dart';
import 'package:syncrow_app/features/dashboard/view/dashboard_view.dart';
import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart';
import 'package:syncrow_app/features/devices/model/subspace_model.dart'; import 'package:syncrow_app/features/devices/model/subspace_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/devices_view_body.dart'; import 'package:syncrow_app/features/devices/view/widgets/devices_view_body.dart';
@ -30,23 +30,30 @@ import 'package:syncrow_app/services/api/profile_api.dart';
import 'package:syncrow_app/services/api/spaces_api.dart'; import 'package:syncrow_app/services/api/spaces_api.dart';
import 'package:syncrow_app/utils/helpers/snack_bar.dart'; import 'package:syncrow_app/utils/helpers/snack_bar.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
part 'home_state.dart'; part 'home_state.dart';
class HomeCubit extends Cubit<HomeState> { class HomeCubit extends Cubit<HomeState> {
HomeCubit._() : super(HomeInitial()) { HomeCubit._() : super(HomeInitial()) {
// checkIfNotificationPermissionGranted(); // checkIfNotificationPermissionGranted();
fetchUserInfo(); fetchUserInfo().then(
(value) {
if (selectedSpace == null) { if (selectedSpace == null) {
fetchUnitsByUserId(); fetchUnitsByUserId();
fetchPermissions();
// .then((value) { // .then((value) {
// if (selectedSpace != null) { // if (selectedSpace != null) {
// fetchRoomsByUnitId(selectedSpace!); // fetchRoomsByUnitId(selectedSpace!);
// } // }
// }); // });
} }
},
);
} }
static UserModel? user; static UserModel? user;
List<PermissionModel>? permissionModel = [];
static HomeCubit? _instance; static HomeCubit? _instance;
static HomeCubit getInstance() { static HomeCubit getInstance() {
// If an instance already exists, return it // If an instance already exists, return it
@ -56,15 +63,88 @@ class HomeCubit extends Cubit<HomeState> {
Future fetchUserInfo() async { Future fetchUserInfo() async {
try { try {
emit(HomeLoading());
var uuid = var uuid =
await const FlutterSecureStorage().read(key: UserModel.userUuidKey); await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
user = await ProfileApi().fetchUserInfo(uuid); user = await ProfileApi().fetchUserInfo(uuid);
emit(HomeUserInfoLoaded(user!)); // Emit state after fetching user info emit(HomeUserInfoLoaded(user!));
} catch (e) { } catch (e) {
return; return;
} }
} }
static bool manageSupSpace = false;
static bool manageScene = false;
static bool manageDeviceLocation = false;
static bool visitorPasswordManagement = false;
String errorMsg = '';
Future<void> fetchPermissions() async {
try {
emit(HomeLoading());
final response = await ProfileApi().fetchPermissions(user!.role!.uuid);
permissionModel = PermissionModel.fromJsonList(response);
hasViewPermission();
emit(PermissionsRoleLoaded(permissionModel!));
} catch (e) {
emit(HomeError(e.toString()));
}
}
void hasViewPermission() {
emit(HomeLoading());
manageSupSpace = hasPermission(
permissionModel!,
'SPACE_MANAGEMENT',
'MANAGE_SPACE',
'ASSIGN_USER_TO_SPACE',
);
manageScene = hasPermission(
permissionModel!,
'AUTOMATION_MANAGEMENT',
'MANAGE_SCENES',
'UPDATE',
);
manageDeviceLocation = hasPermission(
permissionModel!,
'DEVICE_MANAGEMENT',
'MANAGE_DEVICE',
'LOCATION_UPDATE',
);
visitorPasswordManagement = hasPermission(
permissionModel!,
'VISITOR_PASSWORD_MANAGEMENT',
'MANAGE_VISITOR_PASSWORD',
'VIEW',
);
emit(HomePermissionUpdated());
}
bool hasPermission(List<PermissionModel> permissions, String mainTitle,
String subTitle, String finalTitle) {
try {
final mainOption = permissions.firstWhere(
(perm) => perm.title == mainTitle,
);
final subOption = mainOption.subOptions.firstWhere(
(sub) => sub.title == subTitle,
);
if (subOption.subOptions == null) {
return false;
}
final finalOption = subOption.subOptions!.firstWhere(
(finalSub) => finalSub.title == finalTitle,
);
return finalOption.isChecked == true;
} catch (e) {
return false;
}
}
void emitSafe(HomeState newState) { void emitSafe(HomeState newState) {
final cubit = this; final cubit = this;
if (!cubit.isClosed) { if (!cubit.isClosed) {
@ -89,7 +169,7 @@ class HomeCubit extends Cubit<HomeState> {
static HomeCubit get(context) => BlocProvider.of(context); static HomeCubit get(context) => BlocProvider.of(context);
List<SpaceModel>? spaces; List<SpaceModel> spaces = [];
SpaceModel? selectedSpace; SpaceModel? selectedSpace;
@ -182,8 +262,10 @@ class HomeCubit extends Cubit<HomeState> {
if (index == 0) { if (index == 0) {
unselectRoom(); unselectRoom();
} else if (index == 1) {
unselectRoom1();
} else { } else {
selectedRoom = selectedSpace!.subspaces[index - 1]; selectedRoom = selectedSpace!.subspaces[index - 2];
emitSafe(RoomSelected(selectedRoom!)); emitSafe(RoomSelected(selectedRoom!));
} }
} }
@ -197,8 +279,10 @@ class HomeCubit extends Cubit<HomeState> {
if (index <= 0) { if (index <= 0) {
unselectRoom(); unselectRoom();
} else if (index == 1) {
unselectRoom1();
} else { } else {
selectedRoom = selectedSpace!.subspaces[index - 1]; selectedRoom = selectedSpace!.subspaces[index - 2];
emitSafe(RoomSelected(selectedRoom!)); emitSafe(RoomSelected(selectedRoom!));
} }
} }
@ -220,6 +304,23 @@ class HomeCubit extends Cubit<HomeState> {
emitSafe(RoomUnSelected()); emitSafe(RoomUnSelected());
} }
unselectRoom1() {
// selectedRoom = null;
devicesPageController.animateToPage(
1,
duration: duration,
curve: Curves.linear,
);
roomsPageController.animateToPage(
1,
duration: duration,
curve: Curves.linear,
);
emitSafe(RoomUnSelected());
}
//////////////////////////////////////// API //////////////////////////////////////// //////////////////////////////////////// API ////////////////////////////////////////
generateInvitation(SpaceModel unit) async { generateInvitation(SpaceModel unit) async {
try { try {
@ -261,15 +362,16 @@ class HomeCubit extends Cubit<HomeState> {
emitSafe(GetSpacesLoading()); emitSafe(GetSpacesLoading());
try { try {
spaces = await SpacesAPI.getSpacesByUserId(); spaces = await SpacesAPI.getSpacesByUserId();
emitSafe(GetSpacesSuccess(spaces));
} catch (failure) { } catch (failure) {
emitSafe(GetSpacesError("No units found")); emitSafe(GetSpacesError("No units found"));
return; return;
} }
if (spaces != null && spaces!.isNotEmpty) { if (spaces.isNotEmpty) {
selectedSpace = spaces!.first; selectedSpace = spaces.first;
await fetchRoomsByUnitId(selectedSpace!); await fetchRoomsByUnitId(selectedSpace!);
emitSafe(GetSpacesSuccess(spaces!)); emitSafe(GetSpacesSuccess(spaces));
} else { } else {
emitSafe(GetSpacesError("No spaces found")); emitSafe(GetSpacesError("No spaces found"));
} }
@ -298,20 +400,21 @@ class HomeCubit extends Cubit<HomeState> {
await const FlutterSecureStorage().read(key: UserModel.userUuidKey); await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
var res = await SpacesAPI.activationCodeSpace( var res = await SpacesAPI.activationCodeSpace(
activationCode: activationCode, userUuid: uuid); activationCode: activationCode, userUuid: uuid);
if (res['success'] == true) { if (res['success'] == true) {
fetchUserInfo(); fetchUserInfo();
fetchUnitsByUserId(); fetchUnitsByUserId();
} }
emitSafe(GetSpacesSuccess(spaces!)); emitSafe(GetSpacesSuccess(spaces!));
return res['success']; return res['success'];
} on DioException catch (e) { } on DioException catch (e) {
final errorMessage = e.response?.data['error']['message']; final errorMessage = e.response?.data['error']['message'];
emitSafe(ActivationError(errMessage: errorMessage)); errorMsg = e.response?.data['error']['message'];
return false; emitSafe(ActivationError(errMessage: errorMsg));
} catch (e) {
emitSafe(ActivationError(errMessage: e.toString()));
return false; return false;
} }
} }
/////////////////////////////////////// Nav /////////////////////////////////////// /////////////////////////////////////// Nav ///////////////////////////////////////
@ -378,7 +481,8 @@ class HomeCubit extends Cubit<HomeState> {
// ), // ),
// onPressed: () {}, // onPressed: () {},
// ), // ),
IconButton( manageScene
? IconButton(
icon: const Icon( icon: const Icon(
Icons.add, Icons.add,
size: 32, size: 32,
@ -413,7 +517,8 @@ class HomeCubit extends Cubit<HomeState> {
.read<CreateSceneBloc>() .read<CreateSceneBloc>()
.add(const ClearTabToRunSetting()); .add(const ClearTabToRunSetting());
}, },
), )
: const SizedBox(),
// IconButton( // IconButton(
// icon: const Icon( // icon: const Icon(
// Icons.more_vert, // Icons.more_vert,
@ -503,3 +608,28 @@ BottomNavigationBarItem defaultBottomNavBarItem(
label: label, label: label,
); );
} }
// class PermissionUtils {
// // Check if the "VIEW" permission exists in "MANAGE_SUBSPACE"
// static bool hasViewPermission(List<dynamic> permissions) {
// return _hasPermission(permissions, 'MANAGE_SUBSPACE', 'VIEW');
// }
// // Generalized permission checker
// static bool _hasPermission(
// List<dynamic> permissions, String mainTitle, String subTitle) {
// final mainOption = permissions.firstWhere(
// (perm) => perm['title'] == mainTitle,
// orElse: () => null,
// );
// if (mainOption != null) {
// final subOption = mainOption['subOptions'].firstWhere(
// (sub) => sub['title'] == subTitle,
// orElse: () => null,
// );
// return subOption != null && subOption['isChecked'] == true;
// }
// return false;
// }
// }

View File

@ -15,8 +15,8 @@ class HomeError extends HomeState {
class HomeSuccess extends HomeState {} class HomeSuccess extends HomeState {}
///specific states class ActivationSuccess extends HomeState {}
//get spaces
class GetSpacesLoading extends HomeLoading {} class GetSpacesLoading extends HomeLoading {}
class GetSpacesSuccess extends HomeSuccess { class GetSpacesSuccess extends HomeSuccess {
@ -65,9 +65,15 @@ class RoomUnSelected extends HomeState {}
class NavChangePage extends HomeState {} class NavChangePage extends HomeState {}
// Define new state classes class HomePermissionUpdated extends HomeState {}
class HomeUserInfoLoaded extends HomeState { class HomeUserInfoLoaded extends HomeState {
final UserModel user; final UserModel user;
HomeUserInfoLoaded(this.user); HomeUserInfoLoaded(this.user);
} }
class PermissionsRoleLoaded extends HomeState {
final List<PermissionModel> permissionModel;
PermissionsRoleLoaded(this.permissionModel);
}

View File

@ -0,0 +1,39 @@
class PermissionModel {
final String title;
final List<PermissionAttributes> subOptions;
PermissionModel({required this.title, required this.subOptions});
factory PermissionModel.fromJson(Map<String, dynamic> json) {
return PermissionModel(
title: json['title'],
subOptions: (json['subOptions'] as List)
.map((e) => PermissionAttributes.fromJson(e))
.toList(),
);
}
static List<PermissionModel> fromJsonList(List<dynamic> jsonList) {
return jsonList.map((json) => PermissionModel.fromJson(json)).toList();
}
}
class PermissionAttributes {
final String title;
final List<PermissionAttributes>? subOptions;
final bool? isChecked;
PermissionAttributes({required this.title, this.subOptions, this.isChecked});
factory PermissionAttributes.fromJson(Map<String, dynamic> json) {
return PermissionAttributes(
title: json['title'],
isChecked: json['isChecked'],
subOptions: json['subOptions'] != null
? (json['subOptions'] as List)
.map((e) => PermissionAttributes.fromJson(e))
.toList()
: null,
);
}
}

View File

@ -17,7 +17,10 @@ class AppLayout extends StatelessWidget {
child: BlocBuilder<HomeCubit, HomeState>( child: BlocBuilder<HomeCubit, HomeState>(
builder: (context, state) { builder: (context, state) {
return DefaultScaffold( return DefaultScaffold(
appBar: HomeCubit.getInstance().spaces != null ? const DefaultAppBar() : null, appBar: HomeCubit.getInstance().spaces != null &&
HomeCubit.getInstance().spaces!.isNotEmpty
? const DefaultAppBar()
: null,
bottomNavBar: const DefaultNavBar(), bottomNavBar: const DefaultNavBar(),
child: const AppBody(), child: const AppBody(),
); );

View File

@ -35,8 +35,10 @@ class DefaultAppBar extends StatelessWidget implements PreferredSizeWidget {
) )
: null, : null,
), ),
actions: HomeCubit.appBarActions[ actions: HomeCubit.manageScene
HomeCubit.bottomNavItems[HomeCubit.pageIndex].label], ? HomeCubit.appBarActions[
HomeCubit.bottomNavItems[HomeCubit.pageIndex].label]
: null,
)); ));
}, },
); );

View File

@ -221,6 +221,7 @@ class AuthCubit extends Cubit<AuthState> {
List<String> userFullName = fullName.split(' '); List<String> userFullName = fullName.split(' ');
response = await AuthenticationAPI.signUp( response = await AuthenticationAPI.signUp(
model: SignUpModel( model: SignUpModel(
hasAcceptedAppAgreement: true,
email: email.toLowerCase(), email: email.toLowerCase(),
password: signUpPassword, password: signUpPassword,
firstName: userFullName[0], firstName: userFullName[0],

View File

@ -3,10 +3,12 @@ class SignUpModel {
final String password; final String password;
final String firstName; final String firstName;
final String lastName; final String lastName;
final bool hasAcceptedAppAgreement;
SignUpModel( SignUpModel(
{required this.email, {required this.email,
required this.password, required this.password,
required this.hasAcceptedAppAgreement,
required this.firstName, required this.firstName,
required this.lastName}); required this.lastName});
@ -15,7 +17,8 @@ class SignUpModel {
email: json['email'], email: json['email'],
password: json['password'], password: json['password'],
firstName: json['firstName'], firstName: json['firstName'],
lastName: json['lastName']); lastName: json['lastName'],
hasAcceptedAppAgreement: true);
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@ -24,6 +27,7 @@ class SignUpModel {
'password': password, 'password': password,
'firstName': firstName, 'firstName': firstName,
'lastName': lastName, 'lastName': lastName,
"hasAcceptedAppAgreement": hasAcceptedAppAgreement
}; };
} }
} }

View File

@ -15,6 +15,11 @@ class UserModel {
final String? timeZone; final String? timeZone;
final String? regionUuid; final String? regionUuid;
final bool? isAgreementAccepted; final bool? isAgreementAccepted;
final bool? hasAcceptedWebAgreement;
final DateTime? webAgreementAcceptedAt;
final bool? hasAcceptedAppAgreement;
final DateTime? appAgreementAcceptedAt;
final Role? role;
UserModel({ UserModel({
required this.uuid, required this.uuid,
@ -28,7 +33,11 @@ class UserModel {
required this.isAgreementAccepted, required this.isAgreementAccepted,
required this.regionName, required this.regionName,
required this.timeZone, required this.timeZone,
// required this.role, required this.hasAcceptedWebAgreement,
required this.webAgreementAcceptedAt,
required this.hasAcceptedAppAgreement,
required this.appAgreementAcceptedAt,
required this.role,
}); });
factory UserModel.fromJson(Map<String, dynamic> json) { factory UserModel.fromJson(Map<String, dynamic> json) {
@ -44,6 +53,15 @@ class UserModel {
regionName: json['region']?['regionName'], regionName: json['region']?['regionName'],
timeZone: json['timeZone']?['timeZoneOffset'], timeZone: json['timeZone']?['timeZoneOffset'],
regionUuid: json['region']?['uuid'], regionUuid: json['region']?['uuid'],
hasAcceptedWebAgreement: json['hasAcceptedWebAgreement'],
webAgreementAcceptedAt: json['webAgreementAcceptedAt'] != null
? DateTime.parse(json['webAgreementAcceptedAt'])
: null,
hasAcceptedAppAgreement: json['hasAcceptedAppAgreement'],
appAgreementAcceptedAt: json['appAgreementAcceptedAt'] != null
? DateTime.parse(json['appAgreementAcceptedAt'])
: null,
role: json['role'] != null ? Role.fromJson(json['role']) : null,
); );
} }
@ -61,6 +79,15 @@ class UserModel {
regionUuid: null, regionUuid: null,
regionName: tempJson['region']?['regionName'], regionName: tempJson['region']?['regionName'],
timeZone: tempJson['timezone']?['timeZoneOffset'], timeZone: tempJson['timezone']?['timeZoneOffset'],
hasAcceptedWebAgreement: tempJson['hasAcceptedWebAgreement'],
webAgreementAcceptedAt: tempJson['webAgreementAcceptedAt'] != null
? DateTime.parse(tempJson['webAgreementAcceptedAt'])
: null,
hasAcceptedAppAgreement: tempJson['hasAcceptedAppAgreement'],
appAgreementAcceptedAt: tempJson['appAgreementAcceptedAt'] != null
? DateTime.parse(tempJson['appAgreementAcceptedAt'])
: null,
role: tempJson['role'] != null ? Role.fromJson(tempJson['role']) : null,
); );
} }
@ -85,6 +112,43 @@ class UserModel {
'isAgreementAccepted': isAgreementAccepted, 'isAgreementAccepted': isAgreementAccepted,
'regionName': regionName, 'regionName': regionName,
'timeZone': timeZone, 'timeZone': timeZone,
'hasAcceptedWebAgreement': hasAcceptedWebAgreement,
'webAgreementAcceptedAt': webAgreementAcceptedAt?.toIso8601String(),
'hasAcceptedAppAgreement': hasAcceptedAppAgreement,
'appAgreementAcceptedAt': appAgreementAcceptedAt?.toIso8601String(),
'role': role?.toJson(),
};
}
}
class Role {
final String? uuid;
final DateTime? createdAt;
final DateTime? updatedAt;
final String? type;
Role({
required this.uuid,
required this.createdAt,
required this.updatedAt,
required this.type,
});
factory Role.fromJson(Map<String, dynamic> json) {
return Role(
uuid: json['uuid'],
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt']) : null,
updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt']) : null,
type: json['type'],
);
}
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'createdAt': createdAt?.toIso8601String(),
'updatedAt': updatedAt?.toIso8601String(),
'type': type,
}; };
} }
} }

View File

@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart'; import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart';
import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_state.dart'; import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_state.dart';
import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart';
import 'package:syncrow_app/features/devices/model/ac_model.dart'; import 'package:syncrow_app/features/devices/model/ac_model.dart';
import 'package:syncrow_app/features/devices/model/device_control_model.dart'; import 'package:syncrow_app/features/devices/model/device_control_model.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart'; import 'package:syncrow_app/features/devices/model/device_model.dart';
@ -135,6 +136,8 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
ac.acSwitch = acSwitchValue; ac.acSwitch = acSwitchValue;
} }
} }
_setAllAcsTempsAndSwitches(); _setAllAcsTempsAndSwitches();
_emitAcsStatus(emit); _emitAcsStatus(emit);
} else { } else {

View File

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:firebase_database/firebase_database.dart'; import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -112,7 +114,7 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
code: 'presence_state', code: 'presence_state',
); );
recordGroups = response; recordGroups = response;
// print('---${recordGroups.data!.first.eventTime}'); // print('---${jsonEncode(recordGroups.data.first.)}');
emit(FitchData()); emit(FitchData());
} on DioException catch (e) { } on DioException catch (e) {
final errorData = e.response!.data; final errorData = e.response!.data;

View File

@ -31,6 +31,7 @@ class DevicesCubit extends Cubit<DevicesState> {
// Fetch groups based on the selected space ID // Fetch groups based on the selected space ID
await fetchGroups(selectedSpace.id); await fetchGroups(selectedSpace.id);
await fetchAllDevices(selectedSpace);
} }
DevicesCubit._() : super(DevicesInitial()) { DevicesCubit._() : super(DevicesInitial()) {
@ -47,6 +48,7 @@ class DevicesCubit extends Cubit<DevicesState> {
return _instance ??= DevicesCubit._(); return _instance ??= DevicesCubit._();
} }
@override @override
Future<void> close() { Future<void> close() {
_instance = null; _instance = null;
@ -109,17 +111,6 @@ class DevicesCubit extends Cubit<DevicesState> {
} }
// Getter to retrieve all devices from HomeCubit // Getter to retrieve all devices from HomeCubit
List<DeviceModel> get allDevices {
List<DeviceModel> devices = [];
if (HomeCubit.getInstance().selectedSpace != null) {
for (var room in HomeCubit.getInstance().selectedSpace!.subspaces) {
if (room.devices != null) {
devices.addAll(room.devices!);
}
}
}
return devices;
}
// DevicesCategoryModel? get chosenCategory { // DevicesCategoryModel? get chosenCategory {
// for (var category in allCategories!) { // for (var category in allCategories!) {
@ -267,36 +258,36 @@ class DevicesCubit extends Cubit<DevicesState> {
} }
///////////////////////// API CALLS ////////////////////////// ///////////////////////// API CALLS //////////////////////////
deviceControl(DeviceControlModel control, String deviceId) async { // deviceControl(DeviceControlModel control, String deviceId) async {
emitSafe(DeviceControlLoading( // emitSafe(DeviceControlLoading(
code: control.code, // code: control.code,
)); // ));
try { // try {
var response = await DevicesAPI.controlDevice(control, deviceId); // var response = await DevicesAPI.controlDevice(control, deviceId);
if (response['success'] ?? false) { // if (response['success'] ?? false) {
emitSafe(DeviceControlSuccess(code: control.code)); // emitSafe(DeviceControlSuccess(code: control.code));
//this delay is to give tuya server time to update the status // //this delay is to give tuya server time to update the status
// Future.delayed(const Duration(milliseconds: 400), () { // // Future.delayed(const Duration(milliseconds: 400), () {
fetchDevicesStatues( // fetchDevicesStatues(
deviceId, // deviceId,
HomeCubit.getInstance() // HomeCubit.getInstance()
.selectedSpace! // .selectedSpace!
.subspaces // .subspaces
.indexOf(HomeCubit.getInstance().selectedRoom!), // .indexOf(HomeCubit.getInstance().selectedRoom!),
code: control.code); // code: control.code);
// }); // // });
} else { // } else {
emitSafe(DeviceControlError('Failed to control the device')); // emitSafe(DeviceControlError('Failed to control the device'));
} // }
} catch (failure) { // } catch (failure) {
emitSafe(DeviceControlError(failure.toString())); // emitSafe(DeviceControlError(failure.toString()));
return; // return;
} // }
} // }
fetchGroups(String spaceId) async { fetchGroups(String spaceId) async {
emitSafe(DevicesCategoriesLoading()); emitSafe(GetDevicesLoading());
try { try {
allCategories = await DevicesAPI.fetchGroups(spaceId); allCategories = await DevicesAPI.fetchGroups(spaceId);
} catch (e) { } catch (e) {
@ -321,12 +312,15 @@ class DevicesCubit extends Cubit<DevicesState> {
try { try {
HomeCubit.getInstance().selectedSpace!.subspaces[roomIndex].devices = HomeCubit.getInstance().selectedSpace!.subspaces[roomIndex].devices =
await DevicesAPI.getDevicesByRoomId( await DevicesAPI.getDevicesByRoomId(
communityUuid: unit!.community.uuid, spaceUuid: unit.id, roomId: roomId); communityUuid: unit!.community.uuid,
spaceUuid: unit.id,
roomId: roomId);
} catch (e) { } catch (e) {
emitSafe(GetDevicesError(e.toString())); emitSafe(GetDevicesError(e.toString()));
return; return;
} }
final devices = HomeCubit.getInstance().selectedSpace!.subspaces[roomIndex].devices; final devices =
HomeCubit.getInstance().selectedSpace!.subspaces[roomIndex].devices;
emitSafe(GetDevicesSuccess(devices)); emitSafe(GetDevicesSuccess(devices));
//get status for each device //get status for each device
@ -356,8 +350,11 @@ class DevicesCubit extends Cubit<DevicesState> {
emitSafe(GetDeviceStatusError(e.toString())); emitSafe(GetDeviceStatusError(e.toString()));
return; return;
} }
HomeCubit.getInstance().selectedSpace!.subspaces[roomIndex].devices![deviceIndex].status = HomeCubit.getInstance()
statuses; .selectedSpace!
.subspaces[roomIndex]
.devices![deviceIndex]
.status = statuses;
emitSafe(GetDeviceStatusSuccess(code: code)); emitSafe(GetDeviceStatusSuccess(code: code));
} }
@ -406,6 +403,345 @@ class DevicesCubit extends Cubit<DevicesState> {
// emitSafe(LightBrightnessChanged(value)); // emitSafe(LightBrightnessChanged(value));
// } // }
// } // }
// List<DeviceModel> _fetchedDevices = [];
// List<DeviceModel> get allDevices => _fetchedDevices;
List<DeviceModel> allDevices = [];
Future<void> fetchAllDevices(SpaceModel? unit) async {
emitSafe(GetDevicesLoading());
try {
final devices = await DevicesAPI.getAllDevices(
communityUuid: unit!.community.uuid,
spaceUuid: unit.id,
);
allDevices = devices;
emitSafe(GetDevicesSuccess(allDevices));
} catch (e) {
emitSafe(GetDevicesError(e.toString()));
}
}
bool isDeviceOn(DeviceModel device) {
final switchStatuses = device.status.where(
(s) => (s.code?.startsWith('switch') ?? false),
);
final anySwitchFalse = switchStatuses.any((s) => s.value == false);
return !anySwitchFalse;
}
void updateDeviceStatus(String deviceId, bool newToggleStatus) {
emitSafe(GetDevicesLoading());
// Create a fresh copy of the device list.
final updatedDevices = List<DeviceModel>.from(allDevices);
for (int i = 0; i < updatedDevices.length; i++) {
final device = updatedDevices[i];
if (device.uuid == deviceId) {
updatedDevices[i] = DeviceModel(
activeTime: device.activeTime,
localKey: device.localKey,
model: device.model,
name: device.name,
icon: device.icon,
categoryName: device.categoryName,
type: device.type,
isOnline: device.isOnline,
status: device.status, // Make sure to keep the same status list
productName: device.productName,
timeZone: device.timeZone,
updateTime: device.updateTime,
uuid: device.uuid,
productUuid: device.productUuid,
productType: device.productType,
subspace: device.subspace,
toggleStatus: newToggleStatus,
);
break;
}
}
emit(GetDevicesSuccess(updatedDevices));
}
Future<void> threeGangToggle(
DeviceControlModel control, String deviceUuid) async {
emit(SwitchControlLoading(code: control.code));
try {
final deviceIndex = allDevices.indexWhere((d) => d.uuid == deviceUuid);
if (deviceIndex == -1) {
throw Exception('Device not found');
}
final device = allDevices[deviceIndex];
final switches = ['switch_1', 'switch_2', 'switch_3'];
for (final switchCode in switches) {
final statusIndex =
device.status.indexWhere((s) => s.code == switchCode);
if (statusIndex != -1) {
final currentValue = device.status[statusIndex].value ?? false;
final toggledValue = !currentValue;
final controlRequest = DeviceControlModel(
code: switchCode, value: toggledValue, deviceId: deviceUuid);
final response =
await DevicesAPI.controlDevice(controlRequest, deviceUuid);
if (response['success'] != true) {
throw Exception('Failed to toggle $switchCode');
}
device.status[statusIndex].value = toggledValue;
}
}
final anySwitchOff = device.status.any(
(s) => (s.code?.startsWith('switch_') ?? false) && (s.value == false),
);
device.toggleStatus = !anySwitchOff;
allDevices[deviceIndex] = device;
emit(DeviceControlSuccess(code: control.code));
} catch (failure) {
emit(DeviceControlError(failure.toString()));
}
}
Future<void> towGangToggle(
DeviceControlModel control, String deviceUuid) async {
emit(SwitchControlLoading(code: control.code));
try {
final deviceIndex = allDevices.indexWhere((d) => d.uuid == deviceUuid);
if (deviceIndex == -1) {
throw Exception('Device not found');
}
final device = allDevices[deviceIndex];
final switches = [
'switch_1',
'switch_2',
];
for (final switchCode in switches) {
final statusIndex =
device.status.indexWhere((s) => s.code == switchCode);
if (statusIndex != -1) {
final currentValue = device.status[statusIndex].value ?? false;
final toggledValue = !currentValue;
final controlRequest = DeviceControlModel(
code: switchCode, value: toggledValue, deviceId: deviceUuid);
final response =
await DevicesAPI.controlDevice(controlRequest, deviceUuid);
device.status[statusIndex].value = response['result'];
}
}
final anySwitchOff = device.status.any(
(s) => (s.code?.startsWith('switch_') ?? false) && (s.value == false),
);
device.toggleStatus = !anySwitchOff;
allDevices[deviceIndex] = device;
emit(DeviceControlSuccess(code: control.code));
} catch (failure) {
emit(DeviceControlError(failure.toString()));
}
}
Future<void> towGTGangToggle(
DeviceControlModel control, String deviceUuid) async {
emit(SwitchControlLoading(code: control.code));
try {
final deviceIndex = allDevices.indexWhere((d) => d.uuid == deviceUuid);
if (deviceIndex == -1) {
throw Exception('Device not found');
}
final device = allDevices[deviceIndex];
final switches = [
'switch_1',
'switch_2',
];
for (final switchCode in switches) {
final statusIndex =
device.status.indexWhere((s) => s.code == switchCode);
if (statusIndex != -1) {
final currentValue = device.status[statusIndex].value ?? false;
final toggledValue = !currentValue;
final controlRequest = DeviceControlModel(
code: switchCode, value: toggledValue, deviceId: deviceUuid);
final response =
await DevicesAPI.controlDevice(controlRequest, deviceUuid);
device.status[statusIndex].value = response['result'];
}
}
final anySwitchOff = device.status.any(
(s) => (s.code?.startsWith('switch_') ?? false) && (s.value == false),
);
device.toggleStatus = !anySwitchOff;
allDevices[deviceIndex] = device;
emit(DeviceControlSuccess(code: control.code));
} catch (failure) {
emit(DeviceControlError(failure.toString()));
}
}
Future<void> oneGangToggle(
DeviceControlModel control, String deviceUuid) async {
emit(SwitchControlLoading(code: control.code));
try {
final deviceIndex = allDevices.indexWhere((d) => d.uuid == deviceUuid);
if (deviceIndex == -1) {
throw Exception('Device not found');
}
final device = allDevices[deviceIndex];
final statusIndex = device.status.indexWhere((s) => s.code == 'switch_1');
if (statusIndex != -1) {
final currentValue = device.status[statusIndex].value ?? false;
final toggledValue = !currentValue;
final controlRequest = DeviceControlModel(
code: 'switch_1', value: toggledValue, deviceId: deviceUuid);
final response =
await DevicesAPI.controlDevice(controlRequest, deviceUuid);
print(response);
if (response['result'] != true) {
throw Exception('Failed to toggle switch_1');
}
device.status[statusIndex].value = response['result'];
}
final anySwitchOff = device.status.any(
(s) => (s.code?.startsWith('switch_') ?? false) && (s.value == false),
);
device.toggleStatus = !anySwitchOff;
allDevices[deviceIndex] = device;
emit(DeviceControlSuccess(code: control.code));
} catch (failure) {
emit(DeviceControlError(failure.toString()));
}
}
Future<void> oneGTGangToggle(
DeviceControlModel control, String deviceUuid) async {
emit(SwitchControlLoading(code: control.code));
try {
final deviceIndex = allDevices.indexWhere((d) => d.uuid == deviceUuid);
if (deviceIndex == -1) {
throw Exception('Device not found');
}
final device = allDevices[deviceIndex];
final statusIndex = device.status.indexWhere((s) => s.code == 'switch_1');
if (statusIndex != -1) {
final currentValue = device.status[statusIndex].value ?? false;
final toggledValue = !currentValue;
final controlRequest = DeviceControlModel(
code: 'switch_1', value: toggledValue, deviceId: deviceUuid);
final response =
await DevicesAPI.controlDevice(controlRequest, deviceUuid);
if (response['result'] != true) {
throw Exception('Failed to toggle switch_1');
}
device.status[statusIndex].value = response['result'];
}
final anySwitchOff = device.status.any(
(s) => (s.code?.startsWith('switch_') ?? false) && (s.value == false),
);
device.toggleStatus = !anySwitchOff;
allDevices[deviceIndex] = device;
emit(DeviceControlSuccess(code: control.code));
} catch (failure) {
emit(DeviceControlError(failure.toString()));
}
}
Future<void> deviceControl(
DeviceControlModel control, String deviceUuid) async {
emit(SwitchControlLoading(code: control.code));
try {
final response = await DevicesAPI.controlDevice(control, deviceUuid);
if (response['success'] == true) {
final deviceIndex = allDevices.indexWhere((d) => d.uuid == deviceUuid);
if (deviceIndex != -1) {
final device = allDevices[deviceIndex];
final statusIndex =
device.status.indexWhere((s) => s.code == control.code);
if (statusIndex != -1) {
device.status[statusIndex].value = control.value;
}
final anySwitchOff = device.status.any(
(s) =>
(s.code?.startsWith('switch_') ?? false) && (s.value == false),
);
device.toggleStatus = !anySwitchOff;
allDevices[deviceIndex] = device;
}
emit(DeviceControlSuccess(code: control.code));
} else {
emit(DeviceControlError('Failed to control the device'));
}
} catch (failure) {
emit(DeviceControlError(failure.toString()));
}
}
Future<void> changeCurtainSwitch(
DeviceControlModel control, String deviceUuid) async {
emit(SwitchControlLoading(code: control.code));
try {
final deviceIndex = allDevices.indexWhere((d) => d.uuid == deviceUuid);
if (deviceIndex == -1) {
throw Exception('Device not found');
}
final device = allDevices[deviceIndex];
final isOpen = control.value == "open";
final newValue = isOpen ? 0 : 100;
final response = await DevicesAPI.deviceBatchController(
code: 'percent_control',
devicesUuid: [deviceUuid],
value: newValue,
);
if (response['success'] == true) {
final statusIndex =
device.status.indexWhere((s) => s.code == 'percent_control');
if (statusIndex != -1) {
device.status[statusIndex].value = newValue;
}
allDevices[deviceIndex] = device;
emit(DeviceControlSuccess(code: control.code));
} else {
emit(DeviceControlError('Failed to toggle curtain.'));
}
} catch (error) {
emit(DeviceControlError(error.toString()));
}
}
Future<void> changeGarageSwitch(
DeviceControlModel control, String deviceUuid) async {
emit(SwitchControlLoading(code: control.code));
try {
final deviceIndex = allDevices.indexWhere((d) => d.uuid == deviceUuid);
if (deviceIndex == -1) {
throw Exception('Device not found');
}
final device = allDevices[deviceIndex];
final isOpen = control.value == "open";
final newValue = isOpen ? 0 : 100;
final response = await DevicesAPI.deviceBatchController(
code: 'percent_control',
devicesUuid: [deviceUuid],
value: newValue,
);
if (response['success'] == true) {
final statusIndex =
device.status.indexWhere((s) => s.code == 'percent_control');
if (statusIndex != -1) {
device.status[statusIndex].value = newValue;
}
allDevices[deviceIndex] = device;
emit(DeviceControlSuccess(code: control.code));
} else {
emit(DeviceControlError('Failed to toggle curtain.'));
}
} catch (error) {
emit(DeviceControlError(error.toString()));
}
}
} }
enum LightMode { enum LightMode {

View File

@ -34,6 +34,7 @@ class GetDeviceStatusError extends DevicesState {
} }
class GetDevicesLoading extends DevicesState {} class GetDevicesLoading extends DevicesState {}
class RoomLoading extends DevicesState {}
class GetDevicesSuccess extends DevicesState { class GetDevicesSuccess extends DevicesState {
GetDevicesSuccess(this.devices); GetDevicesSuccess(this.devices);
@ -55,10 +56,16 @@ class DeviceSwitchChanged extends DevicesState {}
class DeviceSelected extends DevicesState {} class DeviceSelected extends DevicesState {}
// Device Control // Device Control
class DeviceControlLoading extends DevicesState { // class DeviceControlLoading extends DevicesState {
final String? code; // final String? code;
DeviceControlLoading({this.code}); // DeviceControlLoading({this.code});
// }
class SwitchControlLoading extends DevicesState {
final String? code;
final String? deviceId;
SwitchControlLoading({this.deviceId, this.code});
} }
class DeviceControlSuccess extends DevicesState { class DeviceControlSuccess extends DevicesState {

View File

@ -40,7 +40,8 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
bool createSchedule = false; bool createSchedule = false;
List<ScheduleModel> listSchedule = []; List<ScheduleModel> listSchedule = [];
TwoTouchBloc({required this.twoTouchId, required this.switchCode}) : super(InitialState()) { TwoTouchBloc({required this.twoTouchId, required this.switchCode})
: super(InitialState()) {
on<InitialEvent>(_fetchTwoTouchStatus); on<InitialEvent>(_fetchTwoTouchStatus);
on<TwoTouchUpdated>(_twoTouchUpdated); on<TwoTouchUpdated>(_twoTouchUpdated);
on<ChangeFirstSwitchStatusEvent>(_changeFirstSwitch); on<ChangeFirstSwitchStatusEvent>(_changeFirstSwitch);
@ -71,13 +72,15 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
int selectedTabIndex = 0; int selectedTabIndex = 0;
void toggleSelectedIndex(ToggleSelectedEvent event, Emitter<TwoTouchState> emit) { void toggleSelectedIndex(
ToggleSelectedEvent event, Emitter<TwoTouchState> emit) {
emit(LoadingInitialState()); emit(LoadingInitialState());
selectedTabIndex = event.index; selectedTabIndex = event.index;
emit(ChangeSlidingSegmentState(value: selectedTabIndex)); emit(ChangeSlidingSegmentState(value: selectedTabIndex));
} }
void toggleCreateSchedule(ToggleCreateScheduleEvent event, Emitter<TwoTouchState> emit) { void toggleCreateSchedule(
ToggleCreateScheduleEvent event, Emitter<TwoTouchState> emit) {
emit(LoadingInitialState()); emit(LoadingInitialState());
createSchedule = !createSchedule; createSchedule = !createSchedule;
selectedDays.clear(); selectedDays.clear();
@ -85,7 +88,8 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
emit(UpdateCreateScheduleState(createSchedule)); emit(UpdateCreateScheduleState(createSchedule));
} }
void _fetchTwoTouchStatus(InitialEvent event, Emitter<TwoTouchState> emit) async { void _fetchTwoTouchStatus(
InitialEvent event, Emitter<TwoTouchState> emit) async {
emit(LoadingInitialState()); emit(LoadingInitialState());
try { try {
var response = await DevicesAPI.getDeviceStatus(twoTouchId); var response = await DevicesAPI.getDeviceStatus(twoTouchId);
@ -104,18 +108,21 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
_listenToChanges() { _listenToChanges() {
try { try {
DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$twoTouchId'); DatabaseReference ref =
FirebaseDatabase.instance.ref('device-status/$twoTouchId');
Stream<DatabaseEvent> stream = ref.onValue; Stream<DatabaseEvent> stream = ref.onValue;
stream.listen((DatabaseEvent event) async { stream.listen((DatabaseEvent event) async {
if (_timer != null) { if (_timer != null) {
await Future.delayed(const Duration(seconds: 2)); await Future.delayed(const Duration(seconds: 2));
} }
Map<dynamic, dynamic> usersMap = event.snapshot.value as Map<dynamic, dynamic>; Map<dynamic, dynamic> usersMap =
event.snapshot.value as Map<dynamic, dynamic>;
List<StatusModel> statusList = []; List<StatusModel> statusList = [];
usersMap['status'].forEach((element) { usersMap['status'].forEach((element) {
statusList.add(StatusModel(code: element['code'], value: element['value'])); statusList
.add(StatusModel(code: element['code'], value: element['value']));
}); });
deviceStatus = TwoTouchModel.fromJson(statusList); deviceStatus = TwoTouchModel.fromJson(statusList);
@ -130,7 +137,8 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
emit(UpdateState(twoTouchModel: deviceStatus)); emit(UpdateState(twoTouchModel: deviceStatus));
} }
void _changeFirstSwitch(ChangeFirstSwitchStatusEvent event, Emitter<TwoTouchState> emit) async { void _changeFirstSwitch(
ChangeFirstSwitchStatusEvent event, Emitter<TwoTouchState> emit) async {
emit(LoadingNewSate(twoTouchModel: deviceStatus)); emit(LoadingNewSate(twoTouchModel: deviceStatus));
try { try {
deviceStatus.firstSwitch = !event.value; deviceStatus.firstSwitch = !event.value;
@ -141,7 +149,8 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
_timer = Timer(const Duration(milliseconds: 100), () async { _timer = Timer(const Duration(milliseconds: 100), () async {
final response = await DevicesAPI.controlDevice( final response = await DevicesAPI.controlDevice(
DeviceControlModel(deviceId: twoTouchId, code: 'switch_1', value: !event.value), DeviceControlModel(
deviceId: twoTouchId, code: 'switch_1', value: !event.value),
twoTouchId); twoTouchId);
if (!response['success']) { if (!response['success']) {
@ -153,7 +162,8 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
} }
} }
void _changeSecondSwitch(ChangeSecondSwitchStatusEvent event, Emitter<TwoTouchState> emit) async { void _changeSecondSwitch(
ChangeSecondSwitchStatusEvent event, Emitter<TwoTouchState> emit) async {
emit(LoadingNewSate(twoTouchModel: deviceStatus)); emit(LoadingNewSate(twoTouchModel: deviceStatus));
try { try {
deviceStatus.secondSwitch = !event.value; deviceStatus.secondSwitch = !event.value;
@ -163,7 +173,8 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
} }
_timer = Timer(const Duration(milliseconds: 100), () async { _timer = Timer(const Duration(milliseconds: 100), () async {
final response = await DevicesAPI.controlDevice( final response = await DevicesAPI.controlDevice(
DeviceControlModel(deviceId: twoTouchId, code: 'switch_2', value: !event.value), DeviceControlModel(
deviceId: twoTouchId, code: 'switch_2', value: !event.value),
twoTouchId); twoTouchId);
if (!response['success']) { if (!response['success']) {
@ -186,11 +197,15 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
final response = await Future.wait([ final response = await Future.wait([
DevicesAPI.controlDevice( DevicesAPI.controlDevice(
DeviceControlModel( DeviceControlModel(
deviceId: twoTouchId, code: 'switch_1', value: deviceStatus.firstSwitch), deviceId: twoTouchId,
code: 'switch_1',
value: deviceStatus.firstSwitch),
twoTouchId), twoTouchId),
DevicesAPI.controlDevice( DevicesAPI.controlDevice(
DeviceControlModel( DeviceControlModel(
deviceId: twoTouchId, code: 'switch_2', value: deviceStatus.secondSwitch), deviceId: twoTouchId,
code: 'switch_2',
value: deviceStatus.secondSwitch),
twoTouchId), twoTouchId),
]); ]);
@ -213,11 +228,15 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
final response = await Future.wait([ final response = await Future.wait([
DevicesAPI.controlDevice( DevicesAPI.controlDevice(
DeviceControlModel( DeviceControlModel(
deviceId: twoTouchId, code: 'switch_1', value: deviceStatus.firstSwitch), deviceId: twoTouchId,
code: 'switch_1',
value: deviceStatus.firstSwitch),
twoTouchId), twoTouchId),
DevicesAPI.controlDevice( DevicesAPI.controlDevice(
DeviceControlModel( DeviceControlModel(
deviceId: twoTouchId, code: 'switch_2', value: deviceStatus.secondSwitch), deviceId: twoTouchId,
code: 'switch_2',
value: deviceStatus.secondSwitch),
twoTouchId), twoTouchId),
]); ]);
if (response.every((element) => !element['success'])) { if (response.every((element) => !element['success'])) {
@ -237,8 +256,10 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
groupTwoTouchList[i].firstSwitch = true; groupTwoTouchList[i].firstSwitch = true;
groupTwoTouchList[i].secondSwitch = true; groupTwoTouchList[i].secondSwitch = true;
} }
emit(UpdateGroupState(twoTouchList: groupTwoTouchList, allSwitches: true)); emit(
List<String> allDeviceIds = groupTwoTouchList.map((device) => device.deviceId).toList(); UpdateGroupState(twoTouchList: groupTwoTouchList, allSwitches: true));
List<String> allDeviceIds =
groupTwoTouchList.map((device) => device.deviceId).toList();
final response1 = await DevicesAPI.deviceBatchController( final response1 = await DevicesAPI.deviceBatchController(
code: 'switch_1', code: 'switch_1',
@ -271,9 +292,11 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
groupTwoTouchList[i].secondSwitch = false; groupTwoTouchList[i].secondSwitch = false;
} }
emit(UpdateGroupState(twoTouchList: groupTwoTouchList, allSwitches: false)); emit(UpdateGroupState(
twoTouchList: groupTwoTouchList, allSwitches: false));
List<String> allDeviceIds = groupTwoTouchList.map((device) => device.deviceId).toList(); List<String> allDeviceIds =
groupTwoTouchList.map((device) => device.deviceId).toList();
final response1 = await DevicesAPI.deviceBatchController( final response1 = await DevicesAPI.deviceBatchController(
code: 'switch_1', code: 'switch_1',
@ -298,17 +321,20 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
} }
} }
void _changeSliding(ChangeSlidingSegment event, Emitter<TwoTouchState> emit) async { void _changeSliding(
ChangeSlidingSegment event, Emitter<TwoTouchState> emit) async {
emit(ChangeSlidingSegmentState(value: event.value)); emit(ChangeSlidingSegmentState(value: event.value));
} }
void _setCounterValue(SetCounterValue event, Emitter<TwoTouchState> emit) async { void _setCounterValue(
SetCounterValue event, Emitter<TwoTouchState> emit) async {
emit(LoadingNewSate(twoTouchModel: deviceStatus)); emit(LoadingNewSate(twoTouchModel: deviceStatus));
int seconds = 0; int seconds = 0;
try { try {
seconds = event.duration.inSeconds; seconds = event.duration.inSeconds;
final response = await DevicesAPI.controlDevice( final response = await DevicesAPI.controlDevice(
DeviceControlModel(deviceId: twoTouchId, code: event.deviceCode, value: seconds), DeviceControlModel(
deviceId: twoTouchId, code: event.deviceCode, value: seconds),
twoTouchId); twoTouchId);
if (response['success'] ?? false) { if (response['success'] ?? false) {
@ -333,7 +359,8 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
} }
} }
void _getCounterValue(GetCounterEvent event, Emitter<TwoTouchState> emit) async { void _getCounterValue(
GetCounterEvent event, Emitter<TwoTouchState> emit) async {
emit(LoadingInitialState()); emit(LoadingInitialState());
try { try {
add(GetScheduleEvent()); add(GetScheduleEvent());
@ -441,7 +468,8 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
deviceId: twoTouchId, deviceId: twoTouchId,
); );
List<dynamic> jsonData = response; List<dynamic> jsonData = response;
listSchedule = jsonData.map((item) => ScheduleModel.fromJson(item)).toList(); listSchedule =
jsonData.map((item) => ScheduleModel.fromJson(item)).toList();
emit(InitialState()); emit(InitialState());
} on DioException catch (e) { } on DioException catch (e) {
final errorData = e.response!.data; final errorData = e.response!.data;
@ -452,12 +480,13 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
int? getTimeStampWithoutSeconds(DateTime? dateTime) { int? getTimeStampWithoutSeconds(DateTime? dateTime) {
if (dateTime == null) return null; if (dateTime == null) return null;
DateTime dateTimeWithoutSeconds = DateTime dateTimeWithoutSeconds = DateTime(dateTime.year, dateTime.month,
DateTime(dateTime.year, dateTime.month, dateTime.day, dateTime.hour, dateTime.minute); dateTime.day, dateTime.hour, dateTime.minute);
return dateTimeWithoutSeconds.millisecondsSinceEpoch ~/ 1000; return dateTimeWithoutSeconds.millisecondsSinceEpoch ~/ 1000;
} }
Future toggleRepeat(ToggleScheduleEvent event, Emitter<TwoTouchState> emit) async { Future toggleRepeat(
ToggleScheduleEvent event, Emitter<TwoTouchState> emit) async {
try { try {
emit(LoadingInitialState()); emit(LoadingInitialState());
final response = await DevicesAPI.changeSchedule( final response = await DevicesAPI.changeSchedule(
@ -476,7 +505,8 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
} }
} }
Future deleteSchedule(DeleteScheduleEvent event, Emitter<TwoTouchState> emit) async { Future deleteSchedule(
DeleteScheduleEvent event, Emitter<TwoTouchState> emit) async {
try { try {
emit(LoadingInitialState()); emit(LoadingInitialState());
final response = await DevicesAPI.deleteSchedule( final response = await DevicesAPI.deleteSchedule(
@ -496,7 +526,8 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
} }
} }
void _fetchTwoTouchWizardStatus(InitialWizardEvent event, Emitter<TwoTouchState> emit) async { void _fetchTwoTouchWizardStatus(
InitialWizardEvent event, Emitter<TwoTouchState> emit) async {
emit(LoadingInitialState()); emit(LoadingInitialState());
try { try {
devicesList = []; devicesList = [];
@ -506,7 +537,8 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
HomeCubit.getInstance().selectedSpace?.id ?? '', '2GT'); HomeCubit.getInstance().selectedSpace?.id ?? '', '2GT');
for (int i = 0; i < devicesList.length; i++) { for (int i = 0; i < devicesList.length; i++) {
var response = await DevicesAPI.getDeviceStatus(devicesList[i].uuid ?? ''); var response =
await DevicesAPI.getDeviceStatus(devicesList[i].uuid ?? '');
List<StatusModel> statusModelList = []; List<StatusModel> statusModelList = [];
for (var status in response['status']) { for (var status in response['status']) {
statusModelList.add(StatusModel.fromJson(status)); statusModelList.add(StatusModel.fromJson(status));
@ -529,15 +561,16 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
return true; return true;
}); });
} }
emit(UpdateGroupState(twoTouchList: groupTwoTouchList, allSwitches: allSwitchesOn)); emit(UpdateGroupState(
twoTouchList: groupTwoTouchList, allSwitches: allSwitchesOn));
} catch (e) { } catch (e) {
emit(FailedState(error: e.toString())); emit(FailedState(error: e.toString()));
return; return;
} }
} }
void _changeFirstWizardSwitch( void _changeFirstWizardSwitch(ChangeFirstWizardSwitchStatusEvent event,
ChangeFirstWizardSwitchStatusEvent event, Emitter<TwoTouchState> emit) async { Emitter<TwoTouchState> emit) async {
emit(LoadingNewSate(twoTouchModel: deviceStatus)); emit(LoadingNewSate(twoTouchModel: deviceStatus));
try { try {
bool allSwitchesValue = true; bool allSwitchesValue = true;
@ -550,7 +583,8 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
} }
}); });
emit(UpdateGroupState(twoTouchList: groupTwoTouchList, allSwitches: allSwitchesValue)); emit(UpdateGroupState(
twoTouchList: groupTwoTouchList, allSwitches: allSwitchesValue));
final response = await DevicesAPI.deviceBatchController( final response = await DevicesAPI.deviceBatchController(
code: 'switch_1', code: 'switch_1',
devicesUuid: [event.deviceId], devicesUuid: [event.deviceId],
@ -565,8 +599,8 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
} }
} }
void _changeSecondWizardSwitch( void _changeSecondWizardSwitch(ChangeSecondWizardSwitchStatusEvent event,
ChangeSecondWizardSwitchStatusEvent event, Emitter<TwoTouchState> emit) async { Emitter<TwoTouchState> emit) async {
emit(LoadingNewSate(twoTouchModel: deviceStatus)); emit(LoadingNewSate(twoTouchModel: deviceStatus));
try { try {
bool allSwitchesValue = true; bool allSwitchesValue = true;
@ -579,7 +613,8 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
} }
}); });
emit(UpdateGroupState(twoTouchList: groupTwoTouchList, allSwitches: allSwitchesValue)); emit(UpdateGroupState(
twoTouchList: groupTwoTouchList, allSwitches: allSwitchesValue));
final response = await DevicesAPI.deviceBatchController( final response = await DevicesAPI.deviceBatchController(
code: 'switch_2', code: 'switch_2',
@ -598,7 +633,8 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
String statusSelected = ''; String statusSelected = '';
String optionSelected = ''; String optionSelected = '';
Future<void> _changeStatus(ChangeStatusEvent event, Emitter<TwoTouchState> emit) async { Future<void> _changeStatus(
ChangeStatusEvent event, Emitter<TwoTouchState> emit) async {
try { try {
emit(LoadingInitialState()); emit(LoadingInitialState());
final Map<String, Map<String, String>> controlMap = { final Map<String, Map<String, String>> controlMap = {
@ -627,11 +663,15 @@ class TwoTouchBloc extends Bloc<TwoTouchEvent, TwoTouchState> {
final selectedControl = controlMap[optionSelected]?[statusSelected]; final selectedControl = controlMap[optionSelected]?[statusSelected];
if (selectedControl != null) { if (selectedControl != null) {
await DevicesAPI.controlDevice( await DevicesAPI.controlDevice(
DeviceControlModel(deviceId: twoTouchId, code: optionSelected, value: selectedControl), DeviceControlModel(
deviceId: twoTouchId,
code: optionSelected,
value: selectedControl),
twoTouchId, twoTouchId,
); );
} else { } else {
emit(const FailedState(error: 'Invalid statusSelected or optionSelected')); emit(const FailedState(
error: 'Invalid statusSelected or optionSelected'));
} }
} on DioException catch (e) { } on DioException catch (e) {
final errorData = e.response!.data; final errorData = e.response!.data;

View File

@ -10,6 +10,9 @@ class DeviceModel {
String? model; String? model;
String? name; String? name;
String? icon; String? icon;
String? categoryName;
bool? toggleStatus = false;
String? type; String? type;
bool? isOnline; bool? isOnline;
List<StatusModel> status = []; List<StatusModel> status = [];
@ -21,6 +24,8 @@ class DeviceModel {
DeviceType? productType; DeviceType? productType;
bool isSelected = false; bool isSelected = false;
late List<FunctionModel> functions; late List<FunctionModel> functions;
DeviceSubspace? subspace;
DeviceModel( DeviceModel(
{this.activeTime, {this.activeTime,
this.productUuid, this.productUuid,
@ -34,6 +39,9 @@ class DeviceModel {
this.updateTime, this.updateTime,
this.uuid, this.uuid,
this.productType, this.productType,
this.categoryName,
this.subspace,
this.toggleStatus,
this.icon, this.icon,
this.type}) { this.type}) {
functions = getFunctions(productType!); functions = getFunctions(productType!);
@ -87,11 +95,45 @@ class DeviceModel {
} else { } else {
tempIcon = Assets.assetsIconsLogo; tempIcon = Assets.assetsIconsLogo;
} }
// Step 1: Parse `status` as before
final statusList = (json['status'] as List<dynamic>?)
?.map((st) => StatusModel.fromJson(st as Map<String, dynamic>))
.toList();
// Step 2: Check whether ANY status means "off"
final anyOff = statusList?.any((s) {
final code = s.code;
if (code == null) return false;
// 1) Handle "switch" or "switch_x"
if (code == 'switch' || code.startsWith('switch_')) {
// If it's false, we consider that "off"
return s.value == false;
}
// 2) If code == "control" and value == "stop", consider "off"
if (s.value == 'open') {
return true;
}
// 3) If code == "percent_control" and value == 0, maybe "off"
if (code == 'percent_control' && s.value == 0) {
return true;
}
// Add more conditions for other codes as needed
// Default: if none of the above apply, it's not "off"
return false;
});
// Step 3: Decide final toggleStatus (true = fully "on", false = partially/fully "off")
bool computedToggleStatus = !(anyOff ?? false);
return DeviceModel( return DeviceModel(
icon: tempIcon, icon: tempIcon,
activeTime: json['activeTime'], activeTime: json['activeTime'],
// id: json['id'], categoryName: json['categoryName'],
localKey: json['localKey'], localKey: json['localKey'],
model: json['model'], model: json['model'],
name: json['name'], name: json['name'],
@ -102,8 +144,25 @@ class DeviceModel {
uuid: json['uuid'], uuid: json['uuid'],
productType: type, productType: type,
type: json['productType'], type: json['productType'],
status: [],
productUuid: json['productUuid']); // Use the newly computed toggleStatus:
toggleStatus: computedToggleStatus,
// Or if you prefer to use the value returned by the backend when available:
// toggleStatus: json['toggleStatus'] ?? computedToggleStatus,
status: statusList ?? [],
subspace: json['subspace'] != null
? DeviceSubspace.fromJson(json['subspace'])
: DeviceSubspace(
createdAt: null,
disabled: false,
subspaceName: '',
updatedAt: null,
uuid: '',
),
productUuid: json['productUuid'],
);
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@ -118,9 +177,50 @@ class DeviceModel {
'updateTime': updateTime, 'updateTime': updateTime,
'uuid': uuid, 'uuid': uuid,
'productType': productType, 'productType': productType,
'subspace': subspace?.toJson(), // serialize subspace
}; };
} }
List<FunctionModel> getFunctions(DeviceType type) => List<FunctionModel> getFunctions(DeviceType type) =>
devicesFunctionsMap[productType] ?? []; devicesFunctionsMap[productType] ?? [];
} }
class DeviceSubspace {
String? uuid;
DateTime? createdAt;
DateTime? updatedAt;
String? subspaceName;
bool? disabled;
DeviceSubspace({
this.uuid,
this.createdAt,
this.updatedAt,
this.subspaceName,
this.disabled,
});
factory DeviceSubspace.fromJson(Map<String, dynamic> json) {
return DeviceSubspace(
uuid: json['uuid'],
createdAt: json['createdAt'] != null
? DateTime.tryParse(json['createdAt'])
: null,
updatedAt: json['updatedAt'] != null
? DateTime.tryParse(json['updatedAt'])
: null,
subspaceName: json['subspaceName'],
disabled: json['disabled'],
);
}
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'createdAt': createdAt?.toIso8601String(),
'updatedAt': updatedAt?.toIso8601String(),
'subspaceName': subspaceName,
'disabled': disabled,
};
}
}

View File

@ -38,8 +38,10 @@ class SettingProfilePage extends StatelessWidget {
final _bloc = BlocProvider.of<DeviceSettingBloc>(context); final _bloc = BlocProvider.of<DeviceSettingBloc>(context);
return state is DeviceSettingLoadingState return state is DeviceSettingLoadingState
? const Center( ? const Center(
child: child: DefaultContainer(
DefaultContainer(width: 50, height: 50, child: CircularProgressIndicator()), width: 50,
height: 50,
child: CircularProgressIndicator()),
) )
: RefreshIndicator( : RefreshIndicator(
onRefresh: () async { onRefresh: () async {
@ -55,15 +57,18 @@ class SettingProfilePage extends StatelessWidget {
child: SvgPicture.asset( child: SvgPicture.asset(
Assets.sosHomeIcon, Assets.sosHomeIcon,
fit: BoxFit.fitHeight, fit: BoxFit.fitHeight,
height: MediaQuery.of(context).size.height * 0.13, height:
MediaQuery.of(context).size.height * 0.13,
)) ))
: CircleAvatar( : CircleAvatar(
radius: 55, radius: 55,
backgroundColor: ColorsManager.graysColor, backgroundColor: ColorsManager.graysColor,
child: ClipOval( child: ClipOval(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment:
mainAxisAlignment: MainAxisAlignment.center, CrossAxisAlignment.center,
mainAxisAlignment:
MainAxisAlignment.center,
children: [ children: [
Center( Center(
child: SvgPicture.asset( child: SvgPicture.asset(
@ -71,7 +76,10 @@ class SettingProfilePage extends StatelessWidget {
? Assets.fourSceneIcon ? Assets.fourSceneIcon
: Assets.sixSceneIcon, : Assets.sixSceneIcon,
fit: BoxFit.contain, fit: BoxFit.contain,
height: MediaQuery.of(context).size.height * 0.08, height: MediaQuery.of(context)
.size
.height *
0.08,
), ),
), ),
], ],
@ -89,7 +97,8 @@ class SettingProfilePage extends StatelessWidget {
children: [ children: [
IntrinsicWidth( IntrinsicWidth(
child: ConstrainedBox( child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 200), constraints:
const BoxConstraints(maxWidth: 200),
child: TextFormField( child: TextFormField(
maxLength: 30, maxLength: 30,
style: const TextStyle( style: const TextStyle(
@ -122,7 +131,8 @@ class SettingProfilePage extends StatelessWidget {
Assets.sosEditProfile, Assets.sosEditProfile,
color: Colors.grey, color: Colors.grey,
fit: BoxFit.contain, fit: BoxFit.contain,
height: MediaQuery.of(context).size.height * 0.02, height: MediaQuery.of(context).size.height *
0.02,
), ),
), ),
), ),
@ -141,6 +151,7 @@ class SettingProfilePage extends StatelessWidget {
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: InkWell( child: InkWell(
onTap: () async { onTap: () async {
if (HomeCubit.visitorPasswordManagement) {
bool? val = await Navigator.of(context).push( bool? val = await Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => LocationSettingPage( builder: (context) => LocationSettingPage(
@ -151,6 +162,7 @@ class SettingProfilePage extends StatelessWidget {
if (val != null && val == true) { if (val != null && val == true) {
_bloc.add(const DeviceSettingInitialInfo()); _bloc.add(const DeviceSettingInitialInfo());
} }
}
}, },
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -162,7 +174,8 @@ class SettingProfilePage extends StatelessWidget {
children: [ children: [
SizedBox( SizedBox(
child: BodyMedium( child: BodyMedium(
text: _bloc.deviceInfo.subspace.subspaceName, text: _bloc
.deviceInfo.subspace.subspaceName,
fontColor: ColorsManager.textGray, fontColor: ColorsManager.textGray,
), ),
), ),

View File

@ -1,9 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_bloc.dart'; import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart'; import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart';
import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_state.dart'; import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_state.dart';
import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart';
import 'package:syncrow_app/features/devices/model/ac_model.dart'; import 'package:syncrow_app/features/devices/model/ac_model.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart'; import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/ACs/ac_interface_controls.dart'; import 'package:syncrow_app/features/devices/view/widgets/ACs/ac_interface_controls.dart';
@ -71,6 +73,7 @@ class AcInterface extends StatelessWidget {
onTap: () { onTap: () {
BlocProvider.of<ACsBloc>(context) BlocProvider.of<ACsBloc>(context)
.add(AcSwitch(acSwitch: statusModel.acSwitch)); .add(AcSwitch(acSwitch: statusModel.acSwitch));
}, },
child: SvgPicture.asset(Assets.acSwitchIcon)) child: SvgPicture.asset(Assets.acSwitchIcon))
], ],

View File

@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_bloc.dart'; import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart'; import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart';
import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_state.dart'; import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_state.dart';
import 'package:syncrow_app/features/devices/model/ac_model.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart'; import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/ACs/ac_interface.dart'; import 'package:syncrow_app/features/devices/view/widgets/ACs/ac_interface.dart';
import 'package:syncrow_app/features/devices/view/widgets/ACs/acs_list.dart'; import 'package:syncrow_app/features/devices/view/widgets/ACs/acs_list.dart';
@ -40,6 +41,8 @@ class ACsView extends StatelessWidget {
extendBody: true, extendBody: true,
appBar: deviceModel != null appBar: deviceModel != null
? DeviceAppbar( ? DeviceAppbar(
//BlocProvider.of<ACsBloc>(context).deviceStatus.acSwitch.toString()
value: true,
deviceName: deviceModel!.name!, deviceName: deviceModel!.name!,
deviceUuid: deviceModel!.uuid!, deviceUuid: deviceModel!.uuid!,
) )

View File

@ -0,0 +1,110 @@
import 'package:flutter/material.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/devices/view/widgets/room_page_switch.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
class AllDevices extends StatefulWidget {
const AllDevices({super.key, required this.allDevices});
final List<DeviceModel> allDevices;
@override
_AllDevicesState createState() => _AllDevicesState();
}
class _AllDevicesState extends State<AllDevices> {
final TextEditingController _searchController = TextEditingController();
List<DeviceModel> _filteredDevices = [];
@override
void initState() {
super.initState();
_filteredDevices = widget.allDevices ?? [];
_searchController.addListener(_filterDevices);
}
@override
void dispose() {
_searchController.removeListener(_filterDevices);
_searchController.dispose();
super.dispose();
}
void _filterDevices() {
final query = _searchController.text.toLowerCase();
setState(() {
_filteredDevices = widget.allDevices!
.where((device) => device.name!.toLowerCase().contains(query))
.toList();
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
if (widget.allDevices.isNotEmpty)
TextFormField(
controller: _searchController,
decoration: InputDecoration(
hintText: 'Search',
hintStyle: const TextStyle(
color: ColorsManager.textGray,
fontSize: 16,
fontWeight: FontWeight.w400),
prefixIcon: Container(
padding: const EdgeInsets.all(5.0),
margin: const EdgeInsets.all(10.0),
child: SvgPicture.asset(
Assets.searchIcon,
fit: BoxFit.contain,
),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
),
),
),
_filteredDevices.isNotEmpty
? Expanded(
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
childAspectRatio: 1.5,
),
padding: const EdgeInsets.only(top: 10),
itemCount: _filteredDevices.length,
itemBuilder: (context, index) {
return RoomPageSwitch(
allDevices: _filteredDevices,
isAllDevices: true,
device: _filteredDevices[index]);
},
),
)
: widget.allDevices.isNotEmpty
? const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Text(
'No Results Found',
style: TextStyle(
color: ColorsManager.grayColor,
fontSize: 14,
fontWeight: FontWeight.w400),
)),
],
),
)
: const SizedBox(),
],
);
}
}

View File

@ -250,7 +250,9 @@ class CeilingSensorInterface extends StatelessWidget {
), ),
] else if (button['title'] == 'Space Type') ...[ ] else if (button['title'] == 'Space Type') ...[
Text( Text(
model.spaceType.name.toString(), model.spaceType.name.toString() == "none"
? "Office"
: model.spaceType.name.toString(),
style: const TextStyle(color: Colors.black), style: const TextStyle(color: Colors.black),
), ),
] else ...[ ] else ...[
@ -299,7 +301,13 @@ class CeilingSensorInterface extends StatelessWidget {
); );
if (result != null) { if (result != null) {
bloc.add(ChangeValueEvent( bloc.add(ChangeValueEvent(
type: title.toString(), value: result, code: 'nobody_time')); type: title.toString(),
value: result
.toString()
.toLowerCase()
.replaceAll('sec', 's')
.replaceAll('1hr', '1hour'), // Replacing "sec" with "s"
code: 'nobody_time'));
} }
} else if (title == 'Maximum Distance') { } else if (title == 'Maximum Distance') {
final result = await _showDialog( final result = await _showDialog(

View File

@ -90,9 +90,7 @@ class PresenceRecord extends StatelessWidget {
: Colors.grey, : Colors.grey,
), ),
title: Text( title: Text(
record.value == 'true' record.value.toString(),
? "Opened"
: "Closed",
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 18, fontSize: 18,

View File

@ -106,12 +106,11 @@ class _PresenceSpaceTypeDialogState extends State<PresenceSpaceTypeDialog> {
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: SvgPicture.asset( child: SvgPicture.asset(
icon, icon,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
title, title == "None" ? "Office" : title,
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodySmall .bodySmall

View File

@ -6,6 +6,7 @@ import 'package:syncrow_app/features/devices/bloc/curtain_bloc/curtain_event.dar
import 'package:syncrow_app/features/devices/bloc/curtain_bloc/curtain_state.dart'; import 'package:syncrow_app/features/devices/bloc/curtain_bloc/curtain_state.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart'; import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/curtains/curtain_buttons.dart'; import 'package:syncrow_app/features/devices/view/widgets/curtains/curtain_buttons.dart';
import 'package:syncrow_app/features/devices/view/widgets/device_appbar.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart'; import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/generated/assets.dart';
@ -33,11 +34,17 @@ class CurtainView extends StatelessWidget {
// blindHeight = state.blindHeight; // blindHeight = state.blindHeight;
} }
return DefaultScaffold( return DefaultScaffold(
appBar: DeviceAppbar(
deviceName: curtain!.name!,
deviceUuid: curtain!.uuid!,
),
title: curtain.name, title: curtain.name,
child: state is CurtainLoadingState child: state is CurtainLoadingState
? const Center( ? const Center(
child: child: DefaultContainer(
DefaultContainer(width: 50, height: 50, child: CircularProgressIndicator()), width: 50,
height: 50,
child: CircularProgressIndicator()),
) )
: RefreshIndicator( : RefreshIndicator(
onRefresh: () async { onRefresh: () async {
@ -62,37 +69,56 @@ class CurtainView extends StatelessWidget {
children: [ children: [
SvgPicture.asset( SvgPicture.asset(
Assets.assetsIconsCurtainsIconCurtainHolder, Assets.assetsIconsCurtainsIconCurtainHolder,
width: MediaQuery.sizeOf(context).width * 0.75, width:
MediaQuery.sizeOf(context).width * 0.75,
), ),
SizedBox( SizedBox(
width: MediaQuery.sizeOf(context).width * 0.75, width:
MediaQuery.sizeOf(context).width * 0.75,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [ children: [
SizedBox( SizedBox(
width: MediaQuery.sizeOf(context).width * 0.025, width:
MediaQuery.sizeOf(context).width *
0.025,
), ),
AnimatedContainer( AnimatedContainer(
duration: const Duration(milliseconds: 200), duration:
const Duration(milliseconds: 200),
curve: Curves.linear, curve: Curves.linear,
height: MediaQuery.sizeOf(context).height * 0.35, height: MediaQuery.sizeOf(context)
width: MediaQuery.sizeOf(context).width * 0.35, .height *
0.35,
width:
MediaQuery.sizeOf(context).width *
0.35,
child: Stack( child: Stack(
children: List.generate( children: List.generate(
4, 4,
(index) { (index) {
double spacing = curtainWidth / 7.5; double spacing =
double leftMostPosition = index * spacing; curtainWidth / 7.5;
double leftMostPosition =
index * spacing;
return AnimatedPositioned( return AnimatedPositioned(
duration: const Duration(milliseconds: 200), duration: const Duration(
milliseconds: 200),
curve: Curves.linear, curve: Curves.linear,
left: leftMostPosition, left: leftMostPosition,
child: SizedBox( child: SizedBox(
height: height: MediaQuery.sizeOf(
MediaQuery.sizeOf(context).height * 0.35, context)
width: MediaQuery.sizeOf(context).width * 0.08, .height *
0.35,
width: MediaQuery.sizeOf(
context)
.width *
0.08,
child: SvgPicture.asset( child: SvgPicture.asset(
Assets.assetsIconsCurtainsIconVerticalBlade, Assets
.assetsIconsCurtainsIconVerticalBlade,
fit: BoxFit.fill, fit: BoxFit.fill,
), ),
), ),
@ -102,23 +128,37 @@ class CurtainView extends StatelessWidget {
), ),
), ),
AnimatedContainer( AnimatedContainer(
duration: const Duration(milliseconds: 200), duration:
const Duration(milliseconds: 200),
curve: Curves.linear, curve: Curves.linear,
height: MediaQuery.sizeOf(context).height * 0.35, height: MediaQuery.sizeOf(context)
width: MediaQuery.sizeOf(context).width * 0.35, .height *
0.35,
width:
MediaQuery.sizeOf(context).width *
0.35,
child: Stack( child: Stack(
children: List.generate( children: List.generate(
4, 4,
(index) { (index) {
double spacing = curtainWidth / 7.5; double spacing =
double rightMostPosition = index * spacing; curtainWidth / 7.5;
double rightMostPosition =
index * spacing;
return AnimatedPositioned( return AnimatedPositioned(
duration: const Duration(milliseconds: 200), duration: const Duration(
milliseconds: 200),
curve: Curves.linear, curve: Curves.linear,
right: rightMostPosition, right: rightMostPosition,
child: SizedBox( child: SizedBox(
height: MediaQuery.sizeOf(context).height * 0.35, height: MediaQuery.sizeOf(
width: MediaQuery.sizeOf(context).width * 0.08, context)
.height *
0.35,
width: MediaQuery.sizeOf(
context)
.width *
0.08,
child: SvgPicture.asset( child: SvgPicture.asset(
Assets.rightVerticalBlade, Assets.rightVerticalBlade,
fit: BoxFit.fill, fit: BoxFit.fill,
@ -130,7 +170,9 @@ class CurtainView extends StatelessWidget {
), ),
), ),
SizedBox( SizedBox(
width: MediaQuery.sizeOf(context).width * 0.025, width:
MediaQuery.sizeOf(context).width *
0.025,
), ),
], ],
), ),

View File

@ -1,3 +1,4 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
@ -6,14 +7,27 @@ import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
class DeviceAppbar extends StatelessWidget implements PreferredSizeWidget { class DeviceAppbar extends StatelessWidget implements PreferredSizeWidget {
final String deviceName; final String deviceName;
final String deviceUuid; final String deviceUuid;
final bool? value;
final double appBarHeight = 56.0; final double appBarHeight = 56.0;
final void Function()? onPressed; final void Function()? onPressed;
const DeviceAppbar( const DeviceAppbar(
{super.key, required this.deviceName, required this.deviceUuid, this.onPressed}); {super.key,
required this.deviceName,
this.value,
required this.deviceUuid,
this.onPressed});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AppBar( return AppBar(
leading: IconButton(
onPressed: () {
Navigator.of(context).pop(value ?? true);
},
icon: Icon(
Platform.isIOS ? Icons.arrow_back_ios : Icons.arrow_back,
)),
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
centerTitle: true, centerTitle: true,
title: BodyLarge( title: BodyLarge(

View File

@ -1,12 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/devices/view/widgets/all_devices.dart';
import 'package:syncrow_app/features/devices/view/widgets/room_page.dart'; import 'package:syncrow_app/features/devices/view/widgets/room_page.dart';
import 'package:syncrow_app/features/devices/view/widgets/rooms_slider.dart'; import 'package:syncrow_app/features/devices/view/widgets/rooms_slider.dart';
import 'package:syncrow_app/features/devices/view/widgets/wizard_page.dart'; import 'package:syncrow_app/features/devices/view/widgets/wizard_page.dart';
import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_bloc.dart';
import 'package:syncrow_app/features/scene/view/scene_view.dart'; import 'package:syncrow_app/features/scene/view/scene_view.dart';
import 'package:syncrow_app/features/shared_widgets/create_unit.dart'; import 'package:syncrow_app/features/shared_widgets/create_unit.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
@ -14,35 +14,68 @@ import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.da
import 'package:syncrow_app/utils/context_extension.dart'; import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; import 'package:syncrow_app/utils/resource_manager/strings_manager.dart';
class DevicesViewPage extends StatelessWidget { class DevicesViewBody extends StatelessWidget {
const DevicesViewPage({super.key}); const DevicesViewBody({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocBuilder<HomeCubit, HomeState>(
create: (context) => SceneBloc(), // Initialize your SceneBloc here builder: (context, homeState) {
child: DevicesViewBody(), final homeCubit = HomeCubit.getInstance();
// Handle state priority: Errors first
if (homeState is ActivationError) {
return const CreateUnitWidget();
}
// Handle loading states
if (homeState is GetSpacesLoading || homeState is HomeLoading) {
return const Center(child: CircularProgressIndicator());
}
// Handle error states
if (homeState is GetSpacesError) {
return const CreateUnitWidget();
}
// Handle success states
if (homeState is GetSpacesSuccess ||
homeState is RoomUnSelected ||
homeState is RoomSelected ||
homeState is NavChangePage) {
// Show empty state if no spaces
if (homeCubit.spaces.isEmpty) {
return const CreateUnitWidget();
}
return BlocBuilder<DevicesCubit, DevicesState>(
builder: (context, devicesState) {
// Devices loading states
if (devicesState is DevicesLoading ||
devicesState is DevicesCategoriesLoading ||
devicesState is GetDevicesLoading) {
return const Center(child: CircularProgressIndicator());
}
// Devices error state
if (devicesState is GetDevicesError) {
return Center(child: BodyLarge(text: devicesState.errorMsg));
}
// Main content for both GetSpacesSuccess and RoomUnSelected
return _buildMainContent(context, homeCubit);
},
); );
} }
// Fallback for unknown states
return const Center(child: BodyLarge(text: ''));
},
);
} }
class DevicesViewBody extends StatelessWidget { Widget _buildMainContent(BuildContext context, HomeCubit homeCubit) {
const DevicesViewBody({ final devicesCubit = context.read<DevicesCubit>();
super.key,
});
@override return Column(
Widget build(BuildContext context) {
return BlocBuilder<DevicesCubit, DevicesState>(
builder: (context, state) {
if (state is DevicesLoading ||
state is GetDevicesLoading ||
state is DevicesCategoriesLoading) {
return const Center(child: CircularProgressIndicator());
} else {
return HomeCubit.getInstance().spaces?.isEmpty ?? true
? const CreateUnitWidget()
: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
@ -50,53 +83,52 @@ class DevicesViewBody extends StatelessWidget {
children: [ children: [
TitleMedium( TitleMedium(
text: StringsManager.devices, text: StringsManager.devices,
style: context.titleMedium.copyWith( style: context.titleMedium.copyWith(fontSize: 25),
fontSize: 25,
),
), ),
], ],
), ),
SizedBox( SizedBox(
height: MediaQuery.of(context).size.height * 0.1, height: MediaQuery.of(context).size.height * 0.1,
child: const SceneView( child: const SceneView(pageType: true),
pageType: true,
)),
const SizedBox(
height: 20,
), ),
const SizedBox(height: 20),
const RoomsSlider(), const RoomsSlider(),
const SizedBox( const SizedBox(height: 10),
height: 10,
),
Expanded( Expanded(
child: PageView( child: PageView(
controller: HomeCubit.getInstance().devicesPageController, controller: homeCubit.devicesPageController,
onPageChanged: (index) { onPageChanged: (index) {
HomeCubit.getInstance().devicesPageChanged(index); homeCubit.devicesPageChanged(index);
if (index == 0) {
devicesCubit.fetchAllDevices(homeCubit.selectedSpace);
}
}, },
children: [ children: [
WizardPage( AllDevices(
groupsList: DevicesCubit.getInstance().allCategories ?? [], allDevices: devicesCubit.allDevices,
),
WizardPage(
groupsList: devicesCubit.allCategories ?? [],
),
if (homeCubit.selectedSpace != null)
...homeCubit.selectedSpace!.subspaces.map(
(room) => RoomPage(room: room),
), ),
if (HomeCubit.getInstance().selectedSpace != null)
...HomeCubit.getInstance().selectedSpace!.subspaces.map(
(room) {
return RoomPage(
room: room,
);
},
)
], ],
), ),
), ),
HomeCubit.getInstance().selectedSpace != null _buildPageIndicator(homeCubit),
],
);
}
Widget _buildPageIndicator(HomeCubit homeCubit) {
return homeCubit.selectedSpace != null
? Padding( ? Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(vertical: 7),
vertical: 7,
),
child: SmoothPageIndicator( child: SmoothPageIndicator(
controller: HomeCubit.getInstance().devicesPageController, controller: homeCubit.devicesPageController,
count: HomeCubit.getInstance().selectedSpace!.subspaces.length + 1, count: homeCubit.selectedSpace!.subspaces.length + 2,
effect: const WormEffect( effect: const WormEffect(
paintStyle: PaintingStyle.stroke, paintStyle: PaintingStyle.stroke,
dotHeight: 8, dotHeight: 8,
@ -106,11 +138,6 @@ class DevicesViewBody extends StatelessWidget {
) )
: const Center( : const Center(
child: BodyLarge(text: 'No Home Found'), child: BodyLarge(text: 'No Home Found'),
),
],
);
}
},
); );
} }
} }

View File

@ -5,6 +5,7 @@ import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_b
import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_event.dart'; import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_event.dart';
import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_state.dart'; import 'package:syncrow_app/features/devices/bloc/garage_door_bloc/garage_door_state.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart'; import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/device_appbar.dart';
import 'package:syncrow_app/features/devices/view/widgets/garage_door/garage_preferences_settings.dart'; import 'package:syncrow_app/features/devices/view/widgets/garage_door/garage_preferences_settings.dart';
import 'package:syncrow_app/features/devices/view/widgets/garage_door/garage_records_screen.dart'; import 'package:syncrow_app/features/devices/view/widgets/garage_door/garage_records_screen.dart';
import 'package:syncrow_app/features/devices/view/widgets/garage_door/schedule_garage_screen.dart'; import 'package:syncrow_app/features/devices/view/widgets/garage_door/schedule_garage_screen.dart';
@ -22,6 +23,10 @@ class GarageDoorScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DefaultScaffold( return DefaultScaffold(
appBar: DeviceAppbar(
deviceName: device!.name!,
deviceUuid: device!.uuid!,
),
title: 'Garage Door Opener', title: 'Garage Door Opener',
child: BlocProvider( child: BlocProvider(
create: (context) => GarageDoorBloc(GDId: device?.uuid ?? '') create: (context) => GarageDoorBloc(GDId: device?.uuid ?? '')

View File

@ -5,6 +5,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart'; import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/6_scene_switch/six_scene_screen.dart'; import 'package:syncrow_app/features/devices/view/widgets/6_scene_switch/six_scene_screen.dart';
@ -28,23 +29,31 @@ import 'package:syncrow_app/features/devices/view/widgets/smart_door/door_interf
import 'package:syncrow_app/features/devices/view/widgets/three_gang/three_gang_interface.dart'; import 'package:syncrow_app/features/devices/view/widgets/three_gang/three_gang_interface.dart';
import 'package:syncrow_app/features/devices/view/widgets/water_heater/water_heater_page.dart'; import 'package:syncrow_app/features/devices/view/widgets/water_heater/water_heater_page.dart';
import 'package:syncrow_app/features/devices/view/widgets/water_leak/water_leak_screen.dart'; import 'package:syncrow_app/features/devices/view/widgets/water_leak/water_leak_screen.dart';
import 'package:syncrow_app/features/shared_widgets/custom_switch.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart'; import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/utils/context_extension.dart'; import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/helpers/custom_page_route.dart'; import 'package:syncrow_app/utils/helpers/custom_page_route.dart';
import 'package:syncrow_app/utils/resource_manager/constants.dart'; import 'package:syncrow_app/utils/resource_manager/constants.dart';
class RoomPageSwitch extends StatelessWidget { class RoomPageSwitch extends StatelessWidget {
const RoomPageSwitch({ const RoomPageSwitch(
super.key, {super.key,
required this.device, required this.device,
}); this.isAllDevices = false,
this.allDevices});
final DeviceModel device; final DeviceModel device;
final List<DeviceModel>? allDevices;
final bool isAllDevices;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
showDeviceInterface(device, context); showDeviceInterface(
device: device,
context: context,
isAllDevices: isAllDevices,
allDevices: allDevices);
}, },
child: DefaultContainer( child: DefaultContainer(
padding: const EdgeInsets.all(15), padding: const EdgeInsets.all(15),
@ -60,14 +69,19 @@ class RoomPageSwitch extends StatelessWidget {
device.icon!, device.icon!,
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
// CustomSwitch( isAllDevices
// device: device, ? CustomSwitch(
// ), device: device,
)
: const SizedBox(),
], ],
), ),
Flexible( Flexible(
child: FittedBox( child: FittedBox(
child: Text( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
device.name ?? "", device.name ?? "",
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
maxLines: 2, maxLines: 2,
@ -77,6 +91,17 @@ class RoomPageSwitch extends StatelessWidget {
color: Colors.grey, color: Colors.grey,
), ),
), ),
Text(
device.subspace!.subspaceName ?? '',
overflow: TextOverflow.ellipsis,
style: context.bodySmall.copyWith(
fontWeight: FontWeight.w400,
fontSize: 10,
color: Colors.grey,
),
),
],
),
), ),
), ),
], ],
@ -89,14 +114,23 @@ class RoomPageSwitch extends StatelessWidget {
/// Shows the device interface based on the product type of the device. /// Shows the device interface based on the product type of the device.
/// ///
/// The [device] parameter represents the device model. /// The [device] parameter represents the device model.
void showDeviceInterface(DeviceModel device, BuildContext context) { Future<void> showDeviceInterface(
{required DeviceModel device,
required BuildContext context,
required isAllDevices,
List<DeviceModel>? allDevices}) async {
final devicesCubit = context.read<DevicesCubit>();
switch (device.productType) { switch (device.productType) {
case DeviceType.AC: case DeviceType.AC:
Navigator.push( var value = await Navigator.push(
context, context,
PageRouteBuilder( PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => pageBuilder: (context, animation1, animation2) =>
ACsView(deviceModel: device))); ACsView(deviceModel: device)));
if (value && isAllDevices) {
devicesCubit.fetchAllDevices(HomeCubit.getInstance().selectedSpace);
}
// navigateToInterface(ACsView(deviceModel: device), context); // navigateToInterface(ACsView(deviceModel: device), context);
break; break;
case DeviceType.WallSensor: case DeviceType.WallSensor:
@ -116,12 +150,15 @@ void showDeviceInterface(DeviceModel device, BuildContext context) {
// navigateToInterface(CeilingSensorInterface(ceilingSensor: device), context); // navigateToInterface(CeilingSensorInterface(ceilingSensor: device), context);
break; break;
case DeviceType.Curtain: case DeviceType.Curtain:
Navigator.push( var value = await Navigator.push(
context, context,
PageRouteBuilder( PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => CurtainView( pageBuilder: (context, animation1, animation2) => CurtainView(
curtain: device, curtain: device,
))); )));
if (value && isAllDevices) {
devicesCubit.fetchAllDevices(HomeCubit.getInstance().selectedSpace);
}
break; break;
case DeviceType.Blind: case DeviceType.Blind:
break; break;
@ -143,30 +180,43 @@ void showDeviceInterface(DeviceModel device, BuildContext context) {
case DeviceType.LightBulb: case DeviceType.LightBulb:
navigateToInterface(LightInterface(light: device), context); navigateToInterface(LightInterface(light: device), context);
case DeviceType.OneGang: case DeviceType.OneGang:
Navigator.push( var value = await Navigator.push(
context, context,
PageRouteBuilder( PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => pageBuilder: (context, animation1, animation2) =>
OneGangInterface(gangSwitch: device))); OneGangInterface(gangSwitch: device)));
if (value && isAllDevices) {
devicesCubit.fetchAllDevices(HomeCubit.getInstance().selectedSpace);
}
case DeviceType.TwoGang: case DeviceType.TwoGang:
Navigator.push( var value = await Navigator.push(
context, context,
PageRouteBuilder( PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => pageBuilder: (context, animation1, animation2) =>
TwoGangInterface(gangSwitch: device))); TwoGangInterface(gangSwitch: device)));
if (value && isAllDevices) {
devicesCubit.fetchAllDevices(HomeCubit.getInstance().selectedSpace);
}
case DeviceType.ThreeGang: case DeviceType.ThreeGang:
Navigator.push( var value = await Navigator.push(
context, context,
PageRouteBuilder( PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => pageBuilder: (context, animation1, animation2) =>
ThreeGangInterface(gangSwitch: device))); ThreeGangInterface(gangSwitch: device)));
if (value && isAllDevices) {
devicesCubit.fetchAllDevices(HomeCubit.getInstance().selectedSpace);
}
case DeviceType.WH: case DeviceType.WH:
Navigator.push( var value = await Navigator.push(
context, context,
PageRouteBuilder( PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => pageBuilder: (context, animation1, animation2) =>
WaterHeaterPage(device: device))); WaterHeaterPage(device: device)));
if (value && isAllDevices) {
devicesCubit.fetchAllDevices(HomeCubit.getInstance().selectedSpace);
}
case DeviceType.DS: case DeviceType.DS:
Navigator.push( Navigator.push(
context, context,
@ -182,31 +232,43 @@ void showDeviceInterface(DeviceModel device, BuildContext context) {
PowerClampPage(device: device))); PowerClampPage(device: device)));
case DeviceType.OneTouch: case DeviceType.OneTouch:
Navigator.push( var value = await Navigator.push(
context, context,
PageRouteBuilder( PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => pageBuilder: (context, animation1, animation2) =>
OneTouchScreen(device: device))); OneTouchScreen(device: device)));
if (value && isAllDevices) {
devicesCubit.fetchAllDevices(HomeCubit.getInstance().selectedSpace);
}
case DeviceType.TowTouch: case DeviceType.TowTouch:
Navigator.push( var value = await Navigator.push(
context, context,
PageRouteBuilder( PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => pageBuilder: (context, animation1, animation2) =>
TwoTouchInterface(touchSwitch: device))); TwoTouchInterface(touchSwitch: device)));
if (value && isAllDevices) {
devicesCubit.fetchAllDevices(HomeCubit.getInstance().selectedSpace);
}
case DeviceType.ThreeTouch: case DeviceType.ThreeTouch:
Navigator.push( var value = await Navigator.push(
context, context,
PageRouteBuilder( PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => pageBuilder: (context, animation1, animation2) =>
ThreeTouchInterface(touchSwitch: device))); ThreeTouchInterface(touchSwitch: device)));
if (value && isAllDevices) {
devicesCubit.fetchAllDevices(HomeCubit.getInstance().selectedSpace);
}
case DeviceType.GarageDoor: case DeviceType.GarageDoor:
Navigator.push( var value = await Navigator.push(
context, context,
PageRouteBuilder( PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => pageBuilder: (context, animation1, animation2) =>
GarageDoorScreen(device: device))); GarageDoorScreen(device: device)));
if (value && isAllDevices) {
devicesCubit.fetchAllDevices(HomeCubit.getInstance().selectedSpace);
}
case DeviceType.WaterLeak: case DeviceType.WaterLeak:
Navigator.push( Navigator.push(
@ -216,18 +278,24 @@ void showDeviceInterface(DeviceModel device, BuildContext context) {
WaterLeakScreen(device: device))); WaterLeakScreen(device: device)));
case DeviceType.SixScene: case DeviceType.SixScene:
Navigator.push( var value = await Navigator.push(
context, context,
PageRouteBuilder( PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => pageBuilder: (context, animation1, animation2) =>
SixSceneScreen(device: device))); SixSceneScreen(device: device)));
if (value && isAllDevices) {
devicesCubit.fetchAllDevices(HomeCubit.getInstance().selectedSpace);
}
case DeviceType.FourScene: case DeviceType.FourScene:
Navigator.push( var value = await Navigator.push(
context, context,
PageRouteBuilder( PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => pageBuilder: (context, animation1, animation2) =>
FourSceneScreen(device: device))); FourSceneScreen(device: device)));
if (value && isAllDevices) {
devicesCubit.fetchAllDevices(HomeCubit.getInstance().selectedSpace);
}
case DeviceType.SOS: case DeviceType.SOS:
Navigator.push( Navigator.push(

View File

@ -23,6 +23,21 @@ class RoomsSlider extends StatelessWidget {
HomeCubit.getInstance().roomSliderPageChanged(index); HomeCubit.getInstance().roomSliderPageChanged(index);
}, },
children: [ children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: InkWell(
onTap: () {
HomeCubit.getInstance().unselectRoom();
},
child: TitleMedium(
text: 'All Devices',
style: context.titleMedium.copyWith(
fontSize: 25,
color: ColorsManager.textPrimaryColor,
),
),
),
),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: InkWell( child: InkWell(
@ -43,7 +58,10 @@ class RoomsSlider extends StatelessWidget {
(room) => InkWell( (room) => InkWell(
onTap: () { onTap: () {
HomeCubit.getInstance().roomSliderPageChanged( HomeCubit.getInstance().roomSliderPageChanged(
HomeCubit.getInstance().selectedSpace!.subspaces.indexOf(room)); HomeCubit.getInstance()
.selectedSpace!
.subspaces
.indexOf(room));
}, },
child: TitleMedium( child: TitleMedium(
text: room.name!, text: room.name!,
@ -51,7 +69,8 @@ class RoomsSlider extends StatelessWidget {
fontSize: 25, fontSize: 25,
color: HomeCubit.getInstance().selectedRoom == room color: HomeCubit.getInstance().selectedRoom == room
? ColorsManager.textPrimaryColor ? ColorsManager.textPrimaryColor
: ColorsManager.textPrimaryColor.withOpacity(.2), : ColorsManager.textPrimaryColor
.withOpacity(.2),
), ),
), ),
), ),

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/devices/view/widgets/smart_door/members_management_view.dart'; import 'package:syncrow_app/features/devices/view/widgets/smart_door/members_management_view.dart';
import 'package:syncrow_app/features/devices/view/widgets/smart_door/smart_linkage_view.dart'; import 'package:syncrow_app/features/devices/view/widgets/smart_door/smart_linkage_view.dart';
import 'package:syncrow_app/features/devices/view/widgets/smart_door/temporary_password_page.dart'; import 'package:syncrow_app/features/devices/view/widgets/smart_door/temporary_password_page.dart';
@ -14,6 +15,8 @@ class DoorLockGrid extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final buttons = doorLockButtons(val: uuid);
return GridView.builder( return GridView.builder(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
@ -23,14 +26,15 @@ class DoorLockGrid extends StatelessWidget {
crossAxisSpacing: 10, crossAxisSpacing: 10,
childAspectRatio: 1.75 / 1, childAspectRatio: 1.75 / 1,
), ),
itemCount: 4, itemCount: buttons.length,
itemBuilder: (context, index) => DefaultContainer( itemBuilder: (context, index) => DefaultContainer(
onTap: () { onTap: () {
//TODO: remove checking after adding the pages //TODO: remove checking after adding the pages
doorLockButtons()[index]['page'] != null doorLockButtons()[index]['page'] != null
? Navigator.of(context).push( ? Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => doorLockButtons(val: uuid)[index]['page'] as Widget, builder: (context) =>
doorLockButtons(val: uuid)[index]['page'] as Widget,
), ),
) )
: null; : null;
@ -75,6 +79,7 @@ List<Map<String, dynamic>> doorLockButtons({val}) => [
'image': Assets.assetsIconsDoorlockAssetsMembersManagement, 'image': Assets.assetsIconsDoorlockAssetsMembersManagement,
'page': const MembersManagementView(), 'page': const MembersManagementView(),
}, },
if (HomeCubit.manageDeviceLocation)
{ {
'title': 'Temporary Password', 'title': 'Temporary Password',
'image': Assets.assetsIconsDoorlockAssetsTemporaryPassword, 'image': Assets.assetsIconsDoorlockAssetsTemporaryPassword,

View File

@ -1,5 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/two_touch_bloc/two_touch_bloc.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart'; import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/device_appbar.dart'; import 'package:syncrow_app/features/devices/view/widgets/device_appbar.dart';
import 'package:syncrow_app/features/devices/view/widgets/two_touch/two_touch_screen.dart'; import 'package:syncrow_app/features/devices/view/widgets/two_touch/two_touch_screen.dart';
@ -26,6 +28,7 @@ class TwoTouchInterface extends StatelessWidget {
extendBody: true, extendBody: true,
appBar: touchSwitch != null appBar: touchSwitch != null
? DeviceAppbar( ? DeviceAppbar(
value: true,
deviceName: touchSwitch!.name!, deviceName: touchSwitch!.name!,
deviceUuid: touchSwitch!.uuid!, deviceUuid: touchSwitch!.uuid!,
) )

View File

@ -1,5 +1,15 @@
import 'dart:ui';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/menu/bloc/privacy_policy.dart';
import 'package:syncrow_app/features/menu/bloc/user_agreement.dart';
import 'package:syncrow_app/features/menu/view/widgets/join_home/join_home_view.dart';
import 'package:syncrow_app/features/menu/view/widgets/manage_home/manage_home_view.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/view/securty_view.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/services/api/profile_api.dart'; import 'package:syncrow_app/services/api/profile_api.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
part 'menu_state.dart'; part 'menu_state.dart';
@ -32,4 +42,143 @@ class MenuCubit extends Cubit<MenuState> {
emit(MenuError(error.toString())); emit(MenuError(error.toString()));
} }
} }
List<Map<String, Object>> menuSections = [
//Home Management
{
'title': 'Home Management',
'color': ColorsManager.primaryColor,
'buttons': [
// {
// 'title': 'Create a Unit',
// 'Icon': Assets.assetsIconsMenuIconsHomeManagementIconsCreateHome,
// 'page': const CreateUnitView()
// },
{
'title': 'Join a Unit',
'Icon': Assets.assetsIconsMenuIconsHomeManagementIconsJoinAHome,
'page': const JoinHomeView()
},
if (HomeCubit.manageSupSpace)
{
'title': 'Manage Your Units',
'Icon':
Assets.assetsIconsMenuIconsHomeManagementIconsManageYourHome,
'page': const ManageHomeView()
},
],
},
//General Settings
// {
// 'title': 'General Settings',
// 'color': const Color(0xFF023DFE),
// 'buttons': [
// {
// 'title': 'Voice Assistant',
// 'Icon': Assets.assetsIconsMenuIconsGeneralSettingsIconsVoiceAssistant,
// 'page': null
// },
// {
// 'title': 'Temperature unit',
// 'Icon': Assets.assetsIconsMenuIconsGeneralSettingsIconsTemperatureUnit,
// 'page': null
// },
// {
// 'title': 'Touch tone on panel',
// 'Icon': Assets.assetsIconsMenuIconsGeneralSettingsIconsTouchTone,
// 'page': null
// },
// {
// 'title': 'Language',
// 'Icon': Assets.assetsIconsMenuIconsGeneralSettingsIconsLanguage,
// 'page': null
// },
// {
// 'title': 'Network Diagnosis',
// 'Icon': Assets.assetsIconsMenuIconsGeneralSettingsIconsNetworkDiagnosis,
// 'page': null
// },
// {
// 'title': 'Clear Cache',
// 'Icon': Assets.assetsIconsMenuIconsGeneralSettingsIconsClearCach,
// 'page': null
// },
// ],
// },
// //Messages Center
// {
// 'title': 'Messages Center',
// 'color': const Color(0xFF0088FF),
// 'buttons': [
// {
// 'title': 'Alerts',
// 'Icon': Assets.assetsIconsMenuIconsMessagesCenterIconsAlerts,
// 'page': null
// },
// {
// 'title': 'Messages',
// 'Icon': Assets.assetsIconsMenuIconsMessagesCenterIconsMessages,
// 'page': null
// },
// {
// 'title': 'FAQs',
// 'Icon': Assets.assetsIconsMenuIconsMessagesCenterIconsFAQs,
// 'page': null
// },
// {
// 'title': 'Help & Feedback',
// 'Icon': Assets.assetsIconsMenuIconsMessagesCenterIconsHelpAndFeedback,
// 'page': null
// },
// ],
// },
//Security And Privacy
{
'title': 'Security And Privacy',
'color': const Color(0xFF8AB9FF),
'buttons': [
{
'title': 'Security',
'Icon': Assets.assetsIconsMenuIconsSecurityAndPrivacyIconsSecurty,
'page': const SecurtyView()
},
// {
// 'title': 'Privacy',
// 'Icon': Assets.assetsIconsMenuIconsSecurityAndPrivacyIconsPrivacy,
// 'page': const PrivacyView()
// },
],
},
//Legal Information
{
'title': 'Legal Information',
'color': const Color(0xFF001B72),
'buttons': [
{
'title': 'About',
'Icon': Assets.assetsIconsMenuIconsLeagalInfoIconsAbout,
'page': null
},
{
'title': 'Privacy Policy',
'Icon': Assets.assetsIconsMenuIconsLeagalInfoIconsPrivacyPolicy,
'page': const PrivacyPolicy()
},
{
'title': 'User Agreement',
'Icon': Assets.assetsIconsMenuIconsLeagalInfoIconsUserAgreement,
'page': const UserAgreement()
},
],
},
];
Future<void> fetchMenuSections() async {
emit(MenuLoading());
try {
emit(MenuItemsLoaded(menuSections));
} catch (e) {
emit(MenuError(e.toString()));
}
}
} }

View File

@ -11,6 +11,11 @@ class MenuLoaded extends MenuState {
MenuLoaded(this.userAgreementHtml); MenuLoaded(this.userAgreementHtml);
} }
class MenuItemsLoaded extends MenuState {
final List<Map<String, Object>> menuSections;
MenuItemsLoaded(this.menuSections);
}
class MenuError extends MenuState { class MenuError extends MenuState {
final String message; final String message;

View File

@ -1,43 +1,35 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart'; import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart';
import 'package:syncrow_app/features/menu/bloc/menu_cubit.dart'; import 'package:syncrow_app/features/menu/bloc/menu_cubit.dart';
import 'package:syncrow_app/features/menu/view/widgets/menu_list.dart'; import 'package:syncrow_app/features/menu/view/widgets/menu_list.dart';
import 'package:syncrow_app/features/menu/view/widgets/profile/profile_tab.dart'; import 'package:syncrow_app/features/menu/view/widgets/profile/profile_tab.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart'; import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/utils/context_extension.dart'; import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/constants.dart';
class MenuView extends StatelessWidget { class MenuView extends StatelessWidget {
const MenuView({super.key}); const MenuView({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (BuildContext context) => MenuCubit(), create: (BuildContext context) => MenuCubit()..fetchMenuSections(),
child: BlocBuilder<MenuCubit, MenuState>( child: BlocBuilder<MenuCubit, MenuState>(
builder: (context, state) { builder: (context, menuState) {
return BlocBuilder<AuthCubit, AuthState>( if (menuState is MenuLoading) {
builder: (context, state) { return const Center(child: CircularProgressIndicator());
} else if (menuState is MenuError) {
return Center(child: Text(menuState.message));
} else if (menuState is MenuItemsLoaded) {
final sections = menuState.menuSections;
return SingleChildScrollView( return SingleChildScrollView(
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
child: Column( child: Column(
children: [ children: [
const ProfileTab(), const ProfileTab(),
for (var section in menuSections) for (var section in sections) MenuList(section: section),
MenuList( const SizedBox(height: 15),
section: section,
),
const SizedBox(
height: 15,
),
BodyMedium(text: dotenv.env['ENV_NAME'] ?? ''),
const SizedBox(
height: 15,
),
InkWell( InkWell(
onTap: () { onTap: () {
AuthCubit.get(context).logout(); AuthCubit.get(context).logout();
@ -58,12 +50,13 @@ class MenuView extends StatelessWidget {
), ),
], ],
), ),
) ),
], ],
), ),
); );
}, }
); // Fallback in case no states match
return const SizedBox.shrink();
}, },
), ),
); );

View File

@ -3,10 +3,12 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart'; import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/context_extension.dart'; import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/helpers/snack_bar.dart'; import 'package:syncrow_app/utils/helpers/snack_bar.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
class JoinHomeView extends StatelessWidget { class JoinHomeView extends StatelessWidget {
const JoinHomeView({super.key}); const JoinHomeView({super.key});
@ -57,14 +59,93 @@ class JoinHomeView extends StatelessWidget {
'Please enter the invitation code'); 'Please enter the invitation code');
return; return;
} }
if (await HomeCubit.getInstance().activationCode(textEditingController.text)) { if (await HomeCubit.getInstance()
.activationCode(textEditingController.text)) {
await HomeCubit.getInstance().fetchUnitsByUserId(); await HomeCubit.getInstance().fetchUnitsByUserId();
CustomSnackBar.displaySnackBar('Done successfully'); CustomSnackBar.displaySnackBar('Done successfully');
Navigator.of(context).pop(); Navigator.of(context).pop();
} else { } else {
CustomSnackBar.displaySnackBar('Wrong code!'); // CustomSnackBar.displaySnackBar('Wrong code!');
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
contentPadding: EdgeInsets.zero,
content: SizedBox(
height: MediaQuery.of(context).size.height * 0.2,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const SizedBox(
height: 10,
),
const BodyLarge(
text: "Warning",
fontWeight: FontWeight.w700,
fontColor: ColorsManager.red,
fontSize: 16,
),
const Padding(
padding:
EdgeInsets.only(left: 15, right: 15),
child: Divider(
color: ColorsManager.textGray,
),
),
Padding(
padding: const EdgeInsets.only(
left: 15,
right: 20,
top: 15,
bottom: 20),
child: Column(
children: [
Center(
child: Text(
HomeCubit.getInstance().errorMsg,
textAlign: TextAlign.center,
)),
],
),
),
Expanded(
child: Container(
decoration: const BoxDecoration(
border: Border(
top: BorderSide(
color: ColorsManager.textGray,
width: 1.0,
),
)),
child: InkWell(
onTap: () {
Navigator.of(context).pop();
},
child: Padding(
padding: const EdgeInsets.only(
top: 5, bottom: 5),
child: Center(
child: Text(
'Ok',
style: TextStyle(
color: ColorsManager
.switchButton
.withOpacity(0.6),
fontSize: 14,
fontWeight:
FontWeight.w400),
),
),
)),
),
)
],
),
),
);
},
);
} }
}, },
icon: const Icon( icon: const Icon(

View File

@ -16,10 +16,12 @@ class SceneRoomsTabBarDevicesView extends StatefulWidget {
const SceneRoomsTabBarDevicesView({super.key}); const SceneRoomsTabBarDevicesView({super.key});
@override @override
State<SceneRoomsTabBarDevicesView> createState() => _SceneRoomsTabBarDevicesViewState(); State<SceneRoomsTabBarDevicesView> createState() =>
_SceneRoomsTabBarDevicesViewState();
} }
class _SceneRoomsTabBarDevicesViewState extends State<SceneRoomsTabBarDevicesView> class _SceneRoomsTabBarDevicesViewState
extends State<SceneRoomsTabBarDevicesView>
with SingleTickerProviderStateMixin { with SingleTickerProviderStateMixin {
late final TabController _tabController; late final TabController _tabController;
List<SubSpaceModel>? rooms = []; List<SubSpaceModel>? rooms = [];
@ -35,14 +37,15 @@ class _SceneRoomsTabBarDevicesViewState extends State<SceneRoomsTabBarDevicesVie
0, 0,
SubSpaceModel( SubSpaceModel(
name: 'All Devices', name: 'All Devices',
devices: DevicesCubit.getInstance().allDevices, devices: context.read<DevicesCubit>().allDevices,
id: '-1', id: '-1',
), ),
); );
} }
} }
_tabController = TabController(length: rooms!.length, vsync: this, initialIndex: 0); _tabController =
TabController(length: rooms!.length, vsync: this, initialIndex: 0);
_tabController.addListener(_handleTabSwitched); _tabController.addListener(_handleTabSwitched);
super.initState(); super.initState();
} }
@ -52,8 +55,10 @@ class _SceneRoomsTabBarDevicesViewState extends State<SceneRoomsTabBarDevicesVie
final value = _tabController.index; final value = _tabController.index;
/// select tab /// select tab
context.read<TabBarBloc>().add( context.read<TabBarBloc>().add(TabChanged(
TabChanged(selectedIndex: value, roomId: rooms?[value].id ?? '', unit: selectedSpace)); selectedIndex: value,
roomId: rooms?[value].id ?? '',
unit: selectedSpace));
return; return;
} }
} }

View File

@ -36,17 +36,20 @@ class SceneItem extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DefaultContainer( return DefaultContainer(
onTap: () { onTap: () {
context.read<SmartSceneSelectBloc>().add(const SmartSceneClearEvent()); if (HomeCubit.manageScene) {
context
.read<SmartSceneSelectBloc>()
.add(const SmartSceneClearEvent());
if (disablePlayButton == false) { if (disablePlayButton == false) {
BlocProvider.of<CreateSceneBloc>(context) BlocProvider.of<CreateSceneBloc>(context).add(
.add(FetchSceneTasksEvent(sceneId: scene.id, isAutomation: false)); FetchSceneTasksEvent(sceneId: scene.id, isAutomation: false));
/// the state to set the scene type must be after the fetch /// the state to set the scene type must be after the fetch
BlocProvider.of<CreateSceneBloc>(context) BlocProvider.of<CreateSceneBloc>(context)
.add(const SceneTypeEvent(CreateSceneEnum.tabToRun)); .add(const SceneTypeEvent(CreateSceneEnum.tabToRun));
} else { } else {
BlocProvider.of<CreateSceneBloc>(context) BlocProvider.of<CreateSceneBloc>(context).add(
.add(FetchSceneTasksEvent(sceneId: scene.id, isAutomation: true)); FetchSceneTasksEvent(sceneId: scene.id, isAutomation: true));
/// the state to set the scene type must be after the fetch /// the state to set the scene type must be after the fetch
BlocProvider.of<CreateSceneBloc>(context) BlocProvider.of<CreateSceneBloc>(context)
@ -64,6 +67,7 @@ class SceneItem extends StatelessWidget {
sceneName: scene.name, sceneName: scene.name,
), ),
); );
}
}, },
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -79,8 +83,11 @@ class SceneItem extends StatelessWidget {
height: 32, height: 32,
width: 32, width: 32,
fit: BoxFit.fill, fit: BoxFit.fill,
errorBuilder: (context, error, stackTrace) => errorBuilder: (context, error, stackTrace) => Image.asset(
Image.asset(Assets.assetsIconsLogo, height: 32, width: 32, fit: BoxFit.fill), Assets.assetsIconsLogo,
height: 32,
width: 32,
fit: BoxFit.fill),
), ),
if (disablePlayButton || scene.iconInBytes.isEmpty) if (disablePlayButton || scene.iconInBytes.isEmpty)
SvgPicture.asset( SvgPicture.asset(
@ -93,7 +100,9 @@ class SceneItem extends StatelessWidget {
? IconButton( ? IconButton(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
onPressed: () { onPressed: () {
context.read<SceneBloc>().add(SceneTrigger(scene.id, scene.name)); context
.read<SceneBloc>()
.add(SceneTrigger(scene.id, scene.name));
}, },
icon: isLoading icon: isLoading
? const Center( ? const Center(
@ -115,10 +124,14 @@ class SceneItem extends StatelessWidget {
activeColor: ColorsManager.primaryColor, activeColor: ColorsManager.primaryColor,
value: scene.status == 'enable' ? true : false, value: scene.status == 'enable' ? true : false,
onChanged: (value) { onChanged: (value) {
context.read<SceneBloc>().add(UpdateAutomationStatus( context.read<SceneBloc>().add(
automationStatusUpdate: AutomationStatusUpdate( UpdateAutomationStatus(
automationStatusUpdate:
AutomationStatusUpdate(
isEnable: value, isEnable: value,
spaceUuid: HomeCubit.getInstance().selectedSpace!.id), spaceUuid: HomeCubit.getInstance()
.selectedSpace!
.id),
automationId: scene.id)); automationId: scene.id));
}, },
), ),

View File

@ -16,7 +16,8 @@ class CreateUnitWidget extends StatelessWidget {
TextEditingController textEditingController = TextEditingController(); TextEditingController textEditingController = TextEditingController();
return BlocConsumer<HomeCubit, HomeState>( return BlocConsumer<HomeCubit, HomeState>(
listener: (context, state) { listener: (context, state) {
if (state is ActivationError) {} // if (state is ActivationError) {
// }
}, },
builder: (context, state) { builder: (context, state) {
return SingleChildScrollView( return SingleChildScrollView(
@ -96,10 +97,11 @@ class CreateUnitWidget extends StatelessWidget {
textEditingController.text)) { textEditingController.text)) {
CustomSnackBar.displaySnackBar( CustomSnackBar.displaySnackBar(
'Done successfully'); 'Done successfully');
Future.delayed(
const Duration(milliseconds: 500),
() {
Navigator.of(context).pop(); Navigator.of(context).pop();
} else { });
CustomSnackBar.displaySnackBar(
'Wrong code!');
} }
}, },
icon: const Icon( icon: const Icon(

View File

@ -1,81 +1,146 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart'; import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart';
import 'package:syncrow_app/features/devices/model/device_control_model.dart'; import 'package:syncrow_app/features/devices/model/device_control_model.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart'; import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:collection/collection.dart';
class CustomSwitch extends StatelessWidget { class CustomSwitch extends StatefulWidget {
const CustomSwitch({super.key, required this.device}); const CustomSwitch({Key? key, required this.device}) : super(key: key);
final DeviceModel device; final DeviceModel device;
@override
_CustomSwitchState createState() => _CustomSwitchState();
}
bool isCurtainOpen(DeviceModel device) {
// Find the status with code == 'percent_control'
final curtainStatus = device.status.firstWhere(
(s) => s.code == 'percent_control',
// orElse: () => null,
);
// We consider it "on/open" if percent_control == 100
return curtainStatus.value >= 20;
}
class _CustomSwitchState extends State<CustomSwitch> {
bool _isLoading = false;
bool isDeviceOn(DeviceModel device) {
// If it's a curtain, check percent_control
if (device.type == "CUR") {
return isCurtainOpen(device);
}
// Otherwise, default to your existing switch logic
final switchStatuses = device.status.where(
(s) => (s.code?.startsWith('switch') ?? false),
);
final anySwitchFalse = switchStatuses.any((s) => s.value == false);
return !anySwitchFalse;
}
Widget _buildLoadingIndicator() {
return const SizedBox(
width: 45,
height: 28,
child: Center(
child: SizedBox(
width: 22,
height: 22,
child: CircularProgressIndicator(
strokeWidth: 2,
color: ColorsManager.primaryColor,
),
),
),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<DevicesCubit, DevicesState>( final isSwitch = widget.device.categoryName == 'Switch';
builder: (context, state) { if (!isSwitch &&
bool? status; widget.device.type != "AC" &&
if (device.status.isNotEmpty) { widget.device.type != "GD" &&
status = device.status widget.device.type != "CUR") {
.firstWhereOrNull((status) => status.code == "switch") return const SizedBox();
?.value;
} }
return status == null final isOn = isDeviceOn(widget.device);
? const SizedBox()
: GestureDetector( return GestureDetector(
onTap: () { onTap: () async {
DevicesCubit.getInstance().deviceControl( setState(() {
DeviceControlModel( _isLoading = true;
deviceId: device.uuid, });
code: device.status
.firstWhere((status) => status.code == "switch") final newValue = !isOn;
.code, String code;
value: !device.status dynamic controlValue;
.firstWhere((status) => status.code == "switch")
.value!, if (widget.device.type == "AC") {
), code = "switch";
device.uuid!, controlValue = newValue;
} else if (widget.device.type == "CUR") {
code = "percent_control";
controlValue = newValue ? "close" : "open";
} else {
code = "switch_1";
controlValue = newValue;
}
final control = DeviceControlModel(
code: code,
value: controlValue,
deviceId: widget.device.uuid!,
); );
try {
if (widget.device.type == "CUR") {
await context
.read<DevicesCubit>()
.changeCurtainSwitch(control, widget.device.uuid!);
} else if (widget.device.type == "1GT") {
await context
.read<DevicesCubit>()
.oneGTGangToggle(control, widget.device.uuid!);
} else if (widget.device.type == "2GT") {
await context
.read<DevicesCubit>()
.towGTGangToggle(control, widget.device.uuid!);
} else if (widget.device.type == "3G") {
await context
.read<DevicesCubit>()
.threeGangToggle(control, widget.device.uuid!);
} else if (widget.device.type == "2G") {
await context
.read<DevicesCubit>()
.towGangToggle(control, widget.device.uuid!);
} else if (widget.device.type == "1G") {
await context
.read<DevicesCubit>()
.oneGangToggle(control, widget.device.uuid!);
} else {
await context
.read<DevicesCubit>()
.deviceControl(control, widget.device.uuid!);
}
} finally {
setState(() {
_isLoading = false;
});
}
}, },
child: Container( child: _isLoading
width: 45.0, ? _buildLoadingIndicator()
height: 28.0, : SizedBox(
decoration: BoxDecoration( child: SvgPicture.asset(
borderRadius: BorderRadius.circular(24.0), isOn ? Assets.toggleSwitchSmall : Assets.offToggleSwitchSmall,
color: status
? ColorsManager.primaryColor
: const Color(0xFFD9D9D9)),
child: Center(
child: Container(
width: 40.0,
height: 23.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.0),
color: Colors.white,
),
child: Padding(
padding: const EdgeInsets.all(2.0),
child: Container(
alignment: status
? Alignment.centerRight
: Alignment.centerLeft,
child: Container(
width: 20.0,
height: 20.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: status
? ColorsManager.primaryColor
: Colors.grey,
),
),
),
),
),
), ),
), ),
); );
},
);
} }
} }

View File

@ -1139,4 +1139,8 @@ class Assets {
static const String office = 'assets/icons/office.svg'; static const String office = 'assets/icons/office.svg';
static const String parlour = 'assets/icons/parlour.svg'; static const String parlour = 'assets/icons/parlour.svg';
static const String grid = 'assets/images/grid.svg'; static const String grid = 'assets/images/grid.svg';
static const String toggleSwitchSmall = 'assets/icons/toggleSwitchSmall.svg';
static const String offToggleSwitchSmall = 'assets/icons/offToggleSwitchSmall.svg';
} }

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart'; import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart';
import 'package:syncrow_app/features/devices/bloc/devices_cubit.dart';
import 'package:syncrow_app/features/menu/bloc/menu_cubit.dart'; import 'package:syncrow_app/features/menu/bloc/menu_cubit.dart';
import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_bloc.dart'; import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_bloc.dart';
import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart';
@ -27,6 +28,9 @@ class MyApp extends StatelessWidget {
Constants.bottomNavBarHeightPercentage; Constants.bottomNavBarHeightPercentage;
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider<DevicesCubit>(
create: (context) => DevicesCubit.getInstance(),
),
BlocProvider(create: (context) => AuthCubit()), BlocProvider(create: (context) => AuthCubit()),
BlocProvider( BlocProvider(
create: (context) => EffectPeriodBloc(), create: (context) => EffectPeriodBloc(),
@ -35,7 +39,7 @@ class MyApp extends StatelessWidget {
BlocProvider(create: (context) => CreateSceneBloc()), BlocProvider(create: (context) => CreateSceneBloc()),
BlocProvider(create: (context) => SceneBloc()), BlocProvider(create: (context) => SceneBloc()),
BlocProvider(create: (context) => ProfileBloc()), BlocProvider(create: (context) => ProfileBloc()),
BlocProvider(create: (context) => MenuCubit()), BlocProvider(create: (context) => MenuCubit()), //DevicesCubit
], ],
child: MaterialApp( child: MaterialApp(
navigatorKey: NavigationService.navigatorKey, navigatorKey: NavigationService.navigatorKey,

View File

@ -225,4 +225,7 @@ abstract class ApiEndpoints {
static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}'; static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}';
static const String terms = '/terms'; static const String terms = '/terms';
static const String policy = '/policy'; static const String policy = '/policy';
static const String getPermission = '/permission/{roleUuid}';
static const String getAllDevices =
'/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/devices';
} }

View File

@ -51,11 +51,14 @@ class DevicesAPI {
static Future<Map<String, dynamic>> controlDevice( static Future<Map<String, dynamic>> controlDevice(
DeviceControlModel controlModel, String deviceId) async { DeviceControlModel controlModel, String deviceId) async {
try { try {
print('object-*/-*/-*/${controlModel.toJson()}');
final response = await _httpService.post( final response = await _httpService.post(
path: ApiEndpoints.controlDevice.replaceAll('{deviceUuid}', deviceId), path: ApiEndpoints.controlDevice.replaceAll('{deviceUuid}', deviceId),
body: controlModel.toJson(), body: controlModel.toJson(),
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
print('object-*/-*/-*/${json}');
return json; return json;
}, },
); );
@ -88,7 +91,8 @@ class DevicesAPI {
static Future<Map<String, dynamic>> getDeviceStatus(String deviceId) async { static Future<Map<String, dynamic>> getDeviceStatus(String deviceId) async {
final response = await _httpService.get( final response = await _httpService.get(
path: ApiEndpoints.deviceFunctionsStatus.replaceAll('{deviceUuid}', deviceId), path: ApiEndpoints.deviceFunctionsStatus
.replaceAll('{deviceUuid}', deviceId),
showServerMessage: false, showServerMessage: false,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return json; return json;
@ -97,7 +101,8 @@ class DevicesAPI {
return response; return response;
} }
static Future<Map<String, dynamic>> getPowerClampStatus(String deviceId) async { static Future<Map<String, dynamic>> getPowerClampStatus(
String deviceId) async {
final response = await _httpService.get( final response = await _httpService.get(
path: ApiEndpoints.powerClamp.replaceAll('{powerClampUuid}', deviceId), path: ApiEndpoints.powerClamp.replaceAll('{powerClampUuid}', deviceId),
showServerMessage: false, showServerMessage: false,
@ -109,7 +114,9 @@ class DevicesAPI {
} }
static Future<Map<String, dynamic>> renamePass( static Future<Map<String, dynamic>> renamePass(
{required String name, required String doorLockUuid, required String passwordId}) async { {required String name,
required String doorLockUuid,
required String passwordId}) async {
final response = await _httpService.put( final response = await _httpService.put(
path: ApiEndpoints.renamePassword path: ApiEndpoints.renamePassword
.replaceAll('{doorLockUuid}', doorLockUuid) .replaceAll('{doorLockUuid}', doorLockUuid)
@ -144,7 +151,8 @@ class DevicesAPI {
return response; return response;
} }
static Future getSceneBySwitchName({String? deviceId, String? switchName}) async { static Future getSceneBySwitchName(
{String? deviceId, String? switchName}) async {
final response = await _httpService.get( final response = await _httpService.get(
path: ApiEndpoints.fourSceneByName path: ApiEndpoints.fourSceneByName
.replaceAll('{deviceUuid}', deviceId!) .replaceAll('{deviceUuid}', deviceId!)
@ -165,7 +173,11 @@ class DevicesAPI {
final response = await _httpService.post( final response = await _httpService.post(
path: ApiEndpoints.deviceScene.replaceAll('{deviceUuid}', deviceId!), path: ApiEndpoints.deviceScene.replaceAll('{deviceUuid}', deviceId!),
body: jsonEncode( body: jsonEncode(
{"switchName": switchName, "sceneUuid": sceneUuid, "spaceUuid": spaceUuid}, {
"switchName": switchName,
"sceneUuid": sceneUuid,
"spaceUuid": spaceUuid
},
), ),
showServerMessage: false, showServerMessage: false,
expectedResponseModel: (json) { expectedResponseModel: (json) {
@ -185,7 +197,8 @@ class DevicesAPI {
return response; return response;
} }
static Future<List<DeviceModel>> getDeviceByGroupName(String unitId, String groupName) async { static Future<List<DeviceModel>> getDeviceByGroupName(
String unitId, String groupName) async {
final response = await _httpService.get( final response = await _httpService.get(
path: ApiEndpoints.devicesByGroupName path: ApiEndpoints.devicesByGroupName
.replaceAll('{unitUuid}', unitId) .replaceAll('{unitUuid}', unitId)
@ -230,7 +243,9 @@ class DevicesAPI {
if (json == null || json.isEmpty || json == []) { if (json == null || json.isEmpty || json == []) {
return <DeviceModel>[]; return <DeviceModel>[];
} }
return data.map<DeviceModel>((device) => DeviceModel.fromJson(device)).toList(); return data
.map<DeviceModel>((device) => DeviceModel.fromJson(device))
.toList();
}, },
); );
@ -242,7 +257,8 @@ class DevicesAPI {
} }
} }
static Future<List<DeviceModel>> getDevicesByGatewayId(String gatewayId) async { static Future<List<DeviceModel>> getDevicesByGatewayId(
String gatewayId) async {
final response = await _httpService.get( final response = await _httpService.get(
path: ApiEndpoints.gatewayApi.replaceAll('{gatewayUuid}', gatewayId), path: ApiEndpoints.gatewayApi.replaceAll('{gatewayUuid}', gatewayId),
showServerMessage: false, showServerMessage: false,
@ -264,7 +280,8 @@ class DevicesAPI {
String deviceId, String deviceId,
) async { ) async {
final response = await _httpService.get( final response = await _httpService.get(
path: ApiEndpoints.getTemporaryPassword.replaceAll('{doorLockUuid}', deviceId), path: ApiEndpoints.getTemporaryPassword
.replaceAll('{doorLockUuid}', deviceId),
showServerMessage: false, showServerMessage: false,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return json; return json;
@ -275,7 +292,8 @@ class DevicesAPI {
static Future getOneTimePasswords(String deviceId) async { static Future getOneTimePasswords(String deviceId) async {
final response = await _httpService.get( final response = await _httpService.get(
path: ApiEndpoints.getOneTimeTemporaryPassword.replaceAll('{doorLockUuid}', deviceId), path: ApiEndpoints.getOneTimeTemporaryPassword
.replaceAll('{doorLockUuid}', deviceId),
showServerMessage: false, showServerMessage: false,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return json; return json;
@ -286,7 +304,8 @@ class DevicesAPI {
static Future getTimeLimitPasswords(String deviceId) async { static Future getTimeLimitPasswords(String deviceId) async {
final response = await _httpService.get( final response = await _httpService.get(
path: ApiEndpoints.getMultipleTimeTemporaryPassword.replaceAll('{doorLockUuid}', deviceId), path: ApiEndpoints.getMultipleTimeTemporaryPassword
.replaceAll('{doorLockUuid}', deviceId),
showServerMessage: false, showServerMessage: false,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return json; return json;
@ -311,10 +330,12 @@ class DevicesAPI {
"invalidTime": invalidTime, "invalidTime": invalidTime,
}; };
if (scheduleList != null) { if (scheduleList != null) {
body["scheduleList"] = scheduleList.map((schedule) => schedule.toJson()).toList(); body["scheduleList"] =
scheduleList.map((schedule) => schedule.toJson()).toList();
} }
final response = await _httpService.post( final response = await _httpService.post(
path: ApiEndpoints.addTemporaryPassword.replaceAll('{doorLockUuid}', deviceId), path: ApiEndpoints.addTemporaryPassword
.replaceAll('{doorLockUuid}', deviceId),
body: body, body: body,
showServerMessage: false, showServerMessage: false,
expectedResponseModel: (json) => json, expectedResponseModel: (json) => json,
@ -325,7 +346,8 @@ class DevicesAPI {
static Future generateOneTimePassword({deviceId}) async { static Future generateOneTimePassword({deviceId}) async {
try { try {
final response = await _httpService.post( final response = await _httpService.post(
path: ApiEndpoints.addOneTimeTemporaryPassword.replaceAll('{doorLockUuid}', deviceId), path: ApiEndpoints.addOneTimeTemporaryPassword
.replaceAll('{doorLockUuid}', deviceId),
showServerMessage: false, showServerMessage: false,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return json; return json;
@ -337,10 +359,12 @@ class DevicesAPI {
} }
} }
static Future generateMultiTimePassword({deviceId, effectiveTime, invalidTime}) async { static Future generateMultiTimePassword(
{deviceId, effectiveTime, invalidTime}) async {
try { try {
final response = await _httpService.post( final response = await _httpService.post(
path: ApiEndpoints.addMultipleTimeTemporaryPassword.replaceAll('{doorLockUuid}', deviceId), path: ApiEndpoints.addMultipleTimeTemporaryPassword
.replaceAll('{doorLockUuid}', deviceId),
showServerMessage: true, showServerMessage: true,
body: {"effectiveTime": effectiveTime, "invalidTime": invalidTime}, body: {"effectiveTime": effectiveTime, "invalidTime": invalidTime},
expectedResponseModel: (json) { expectedResponseModel: (json) {
@ -530,7 +554,9 @@ class DevicesAPI {
String code, String code,
) async { ) async {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.getDeviceLogs.replaceAll('{uuid}', uuid).replaceAll('{code}', code), path: ApiEndpoints.getDeviceLogs
.replaceAll('{uuid}', uuid)
.replaceAll('{code}', code),
showServerMessage: false, showServerMessage: false,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return DeviceReport.fromJson(json); return DeviceReport.fromJson(json);
@ -538,4 +564,45 @@ class DevicesAPI {
); );
return response; return response;
} }
static Future<List<DeviceModel>> getAllDevices({
required String communityUuid,
required String spaceUuid,
}) async {
print('communityUuid=$communityUuid');
print('spaceUuid=$spaceUuid');
print('projectUuid=${TempConst.projectId}');
try {
final String path = ApiEndpoints.getAllDevices
.replaceAll('{communityUuid}', communityUuid)
.replaceAll('{spaceUuid}', spaceUuid)
.replaceAll('{projectUuid}', TempConst.projectId);
final response = await _httpService.get(
path: path,
showServerMessage: false,
expectedResponseModel: (json) {
print('response-*/-*/$json');
final data = json['data'];
if (data == null || data.isEmpty) {
return <DeviceModel>[];
}
if (json == null || json.isEmpty || json == []) {
return <DeviceModel>[];
}
return data
.map<DeviceModel>((device) => DeviceModel.fromJson(device))
.toList();
},
);
return response;
} catch (e) {
// Log the error if needed
// Return an empty list in case of error
return <DeviceModel>[];
}
}
} }

View File

@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/auth/model/user_model.dart'; import 'package:syncrow_app/features/auth/model/user_model.dart';
import 'package:syncrow_app/features/menu/model/region_model.dart'; import 'package:syncrow_app/features/menu/model/region_model.dart';
@ -95,6 +94,16 @@ class ProfileApi {
return response; return response;
} }
Future fetchPermissions(roleId) async {
final response = await _httpService.get(
path: ApiEndpoints.getPermission.replaceAll('{roleUuid}', roleId!),
showServerMessage: true,
expectedResponseModel: (json) {
return json;
});
return response;
}
static Future<List<RegionModel>> fetchRegion() async { static Future<List<RegionModel>> fetchRegion() async {
final response = await _httpService.get( final response = await _httpService.get(
path: ApiEndpoints.getRegion, path: ApiEndpoints.getRegion,

View File

@ -1,5 +1,6 @@
//ignore_for_file: constant_identifier_names //ignore_for_file: constant_identifier_names
import 'dart:ui'; import 'dart:ui';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/devices/model/function_model.dart'; import 'package:syncrow_app/features/devices/model/function_model.dart';
import 'package:syncrow_app/features/menu/bloc/privacy_policy.dart'; import 'package:syncrow_app/features/menu/bloc/privacy_policy.dart';
import 'package:syncrow_app/features/menu/bloc/user_agreement.dart'; import 'package:syncrow_app/features/menu/bloc/user_agreement.dart';
@ -677,134 +678,6 @@ K? getNextItem<K, V>(Map<K, V> map, V value) {
return null; return null;
} }
List<Map<String, Object>> menuSections = [
//Home Management
{
'title': 'Home Management',
'color': ColorsManager.primaryColor,
'buttons': [
// {
// 'title': 'Create a Unit',
// 'Icon': Assets.assetsIconsMenuIconsHomeManagementIconsCreateHome,
// 'page': const CreateUnitView()
// },
{
'title': 'Join a Unit',
'Icon': Assets.assetsIconsMenuIconsHomeManagementIconsJoinAHome,
'page': const JoinHomeView()
},
{
'title': 'Manage Your Units',
'Icon': Assets.assetsIconsMenuIconsHomeManagementIconsManageYourHome,
'page': const ManageHomeView()
},
],
},
//General Settings
// {
// 'title': 'General Settings',
// 'color': const Color(0xFF023DFE),
// 'buttons': [
// {
// 'title': 'Voice Assistant',
// 'Icon': Assets.assetsIconsMenuIconsGeneralSettingsIconsVoiceAssistant,
// 'page': null
// },
// {
// 'title': 'Temperature unit',
// 'Icon': Assets.assetsIconsMenuIconsGeneralSettingsIconsTemperatureUnit,
// 'page': null
// },
// {
// 'title': 'Touch tone on panel',
// 'Icon': Assets.assetsIconsMenuIconsGeneralSettingsIconsTouchTone,
// 'page': null
// },
// {
// 'title': 'Language',
// 'Icon': Assets.assetsIconsMenuIconsGeneralSettingsIconsLanguage,
// 'page': null
// },
// {
// 'title': 'Network Diagnosis',
// 'Icon': Assets.assetsIconsMenuIconsGeneralSettingsIconsNetworkDiagnosis,
// 'page': null
// },
// {
// 'title': 'Clear Cache',
// 'Icon': Assets.assetsIconsMenuIconsGeneralSettingsIconsClearCach,
// 'page': null
// },
// ],
// },
// //Messages Center
// {
// 'title': 'Messages Center',
// 'color': const Color(0xFF0088FF),
// 'buttons': [
// {
// 'title': 'Alerts',
// 'Icon': Assets.assetsIconsMenuIconsMessagesCenterIconsAlerts,
// 'page': null
// },
// {
// 'title': 'Messages',
// 'Icon': Assets.assetsIconsMenuIconsMessagesCenterIconsMessages,
// 'page': null
// },
// {
// 'title': 'FAQs',
// 'Icon': Assets.assetsIconsMenuIconsMessagesCenterIconsFAQs,
// 'page': null
// },
// {
// 'title': 'Help & Feedback',
// 'Icon': Assets.assetsIconsMenuIconsMessagesCenterIconsHelpAndFeedback,
// 'page': null
// },
// ],
// },
//Security And Privacy
{
'title': 'Security And Privacy',
'color': const Color(0xFF8AB9FF),
'buttons': [
{
'title': 'Security',
'Icon': Assets.assetsIconsMenuIconsSecurityAndPrivacyIconsSecurty,
'page': const SecurtyView()
},
// {
// 'title': 'Privacy',
// 'Icon': Assets.assetsIconsMenuIconsSecurityAndPrivacyIconsPrivacy,
// 'page': const PrivacyView()
// },
],
},
//Legal Information
{
'title': 'Legal Information',
'color': const Color(0xFF001B72),
'buttons': [
{
'title': 'About',
'Icon': Assets.assetsIconsMenuIconsLeagalInfoIconsAbout,
'page': null
},
{
'title': 'Privacy Policy',
'Icon': Assets.assetsIconsMenuIconsLeagalInfoIconsPrivacyPolicy,
'page': const PrivacyPolicy()
},
{
'title': 'User Agreement',
'Icon': Assets.assetsIconsMenuIconsLeagalInfoIconsUserAgreement,
'page': const UserAgreement()
},
],
},
];
enum MemberRole { enum MemberRole {
FamilyMember, FamilyMember,
OtherMember, OtherMember,

View File

@ -5,7 +5,7 @@ description: This is the mobile application project, developed with Flutter for
# pub.dev using `flutter pub publish`. This is preferred for private packages. # pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: "none" # Remove this line if you wish to publish to pub.dev publish_to: "none" # Remove this line if you wish to publish to pub.dev
version: 1.0.16+53 version: 1.0.18+55
environment: environment:
sdk: ">=3.0.6 <4.0.0" sdk: ">=3.0.6 <4.0.0"