mirror of
https://github.com/SyncrowIOT/syncrow-app.git
synced 2025-07-15 17:47:28 +00:00
643 lines
19 KiB
Dart
643 lines
19 KiB
Dart
import 'dart:io';
|
|
import 'package:dio/dio.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
import 'package:flutter_svg/flutter_svg.dart';
|
|
import 'package:onesignal_flutter/onesignal_flutter.dart';
|
|
import 'package:permission_handler/permission_handler.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/view/widgets/app_bar_home_dropdown.dart';
|
|
import 'package:syncrow_app/features/auth/model/project_model.dart';
|
|
import 'package:syncrow_app/features/auth/model/user_model.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/view/widgets/devices_view_body.dart';
|
|
import 'package:syncrow_app/features/menu/view/menu_view.dart';
|
|
import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart';
|
|
import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_bloc.dart';
|
|
import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_event.dart';
|
|
import 'package:syncrow_app/features/scene/bloc/smart_scene/smart_scene_select_dart_bloc.dart';
|
|
import 'package:syncrow_app/features/scene/enum/create_scene_enum.dart';
|
|
import 'package:syncrow_app/features/scene/model/scene_settings_route_arguments.dart';
|
|
import 'package:syncrow_app/features/scene/view/routines_view.dart';
|
|
import 'package:syncrow_app/generated/assets.dart';
|
|
import 'package:syncrow_app/navigation/navigation_service.dart';
|
|
import 'package:syncrow_app/navigation/routing_constants.dart';
|
|
import 'package:syncrow_app/services/api/devices_api.dart';
|
|
import 'package:syncrow_app/services/api/profile_api.dart';
|
|
import 'package:syncrow_app/services/api/spaces_api.dart';
|
|
import 'package:syncrow_app/utils/constants/temp_const.dart';
|
|
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
|
|
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
|
|
part 'home_state.dart';
|
|
|
|
class HomeCubit extends Cubit<HomeState> {
|
|
HomeCubit._() : super(HomeInitial()) {
|
|
// checkIfNotificationPermissionGranted();
|
|
fetchUserInfo().then(
|
|
(value) {
|
|
if (selectedSpace == null) {
|
|
fetchUnitsByUserId();
|
|
fetchPermissions();
|
|
|
|
// .then((value) {
|
|
// if (selectedSpace != null) {
|
|
// fetchRoomsByUnitId(selectedSpace!);
|
|
// }
|
|
// });
|
|
}
|
|
},
|
|
);
|
|
}
|
|
static UserModel? user;
|
|
|
|
List<PermissionModel>? permissionModel = [];
|
|
|
|
static HomeCubit? _instance;
|
|
static HomeCubit getInstance() {
|
|
// If an instance already exists, return it
|
|
_instance ??= HomeCubit._();
|
|
return _instance!;
|
|
}
|
|
|
|
Future fetchUserInfo() async {
|
|
try {
|
|
emit(HomeLoading());
|
|
var uuid =
|
|
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
|
|
user = await ProfileApi().fetchUserInfo(uuid);
|
|
project = user?.project;
|
|
|
|
emit(HomeUserInfoLoaded(user!));
|
|
} catch (e) {
|
|
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) {
|
|
final cubit = this;
|
|
if (!cubit.isClosed) {
|
|
cubit.emit(newState);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> close() {
|
|
_instance = null;
|
|
selectedSpace = null;
|
|
selectedRoom = null;
|
|
pageIndex = 0;
|
|
// OneSignal.User.pushSubscription
|
|
// .removeObserver((stateChanges) => oneSignalSubscriptionObserver);
|
|
// OneSignal.Notifications.removePermissionObserver(
|
|
// (permission) => oneSignalPermissionObserver);
|
|
// OneSignal.Notifications.removeClickListener(
|
|
// (event) => oneSignalClickListenerObserver);
|
|
return super.close();
|
|
}
|
|
|
|
static HomeCubit get(context) => BlocProvider.of(context);
|
|
|
|
List<SpaceModel> spaces = [];
|
|
|
|
SpaceModel? selectedSpace;
|
|
|
|
SubSpaceModel? selectedRoom;
|
|
|
|
Project? project;
|
|
|
|
PageController devicesPageController = PageController();
|
|
|
|
PageController roomsPageController = PageController();
|
|
|
|
var duration = const Duration(milliseconds: 300);
|
|
|
|
// void oneSignalPermissionObserver;
|
|
// void oneSignalSubscriptionObserver;
|
|
// void oneSignalClickListenerObserver;
|
|
|
|
// selectSpace(SpaceModel space) async {
|
|
// selectedSpace = space;
|
|
// emit(SpaceSelected(space));
|
|
// }
|
|
|
|
checkIfNotificationPermissionGranted() async {
|
|
try {
|
|
OneSignal.initialize('762350c9-1e5d-4d95-a648-16d4dc8a25e1');
|
|
|
|
//Show native push notification dialog
|
|
if (Platform.isIOS) {
|
|
await OneSignal.Notifications.permissionNative();
|
|
} else {
|
|
await OneSignal.Notifications.requestPermission(true);
|
|
}
|
|
|
|
if (await Permission.notification.isGranted == false) {
|
|
return;
|
|
}
|
|
|
|
var userUuid =
|
|
await const FlutterSecureStorage().read(key: UserModel.userUuidKey) ??
|
|
'';
|
|
if (userUuid.isNotEmpty) {
|
|
await OneSignal.login(userUuid);
|
|
}
|
|
//Enable push notifications
|
|
await OneSignal.User.pushSubscription.optIn();
|
|
|
|
// //this function will be called once a user is subscribed
|
|
// oneSignalSubscriptionObserver =
|
|
// OneSignal.User.pushSubscription.addObserver((state) async {
|
|
// if (state.current.optedIn) {
|
|
// await _sendSubscriptionId();
|
|
// }
|
|
// });
|
|
|
|
// // Send the player id when a user allows notifications
|
|
// oneSignalPermissionObserver =
|
|
// OneSignal.Notifications.addPermissionObserver((state) async {
|
|
// await _sendSubscriptionId();
|
|
// });
|
|
|
|
// //check if the player id is sent, if not send it again
|
|
// await _sendSubscriptionId();
|
|
|
|
// oneSignalClickListenerObserver =
|
|
// OneSignal.Notifications.addClickListener((event) async {
|
|
// //Once the user clicks on the notification
|
|
// });
|
|
} catch (err) {
|
|
debugPrint("******* Error");
|
|
debugPrint(err.toString());
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
_sendSubscriptionId() async {
|
|
// String? subscriptionId = OneSignal.User.pushSubscription.id ?? '';
|
|
//TODO send the subscription id to BE
|
|
}
|
|
|
|
changeSelectedSpace(SpaceModel space) {
|
|
selectedSpace = space;
|
|
emitSafe(SpaceSelected(space));
|
|
fetchRoomsByUnitId(space);
|
|
}
|
|
|
|
roomSliderPageChanged(int index) {
|
|
devicesPageController.animateToPage(
|
|
index,
|
|
duration: duration,
|
|
curve: Curves.linear,
|
|
);
|
|
|
|
if (index == 0) {
|
|
unselectRoom();
|
|
} else if (index == 1) {
|
|
unselectRoom1();
|
|
} else {
|
|
selectedRoom = selectedSpace!.subspaces[index - 2];
|
|
emitSafe(RoomSelected(selectedRoom!));
|
|
}
|
|
}
|
|
|
|
devicesPageChanged(int index) {
|
|
roomsPageController.animateToPage(
|
|
index,
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.linear,
|
|
);
|
|
|
|
if (index <= 0) {
|
|
unselectRoom();
|
|
} else if (index == 1) {
|
|
unselectRoom1();
|
|
} else {
|
|
selectedRoom = selectedSpace!.subspaces[index - 2];
|
|
emitSafe(RoomSelected(selectedRoom!));
|
|
}
|
|
}
|
|
|
|
unselectRoom() {
|
|
// selectedRoom = null;
|
|
devicesPageController.animateToPage(
|
|
0,
|
|
duration: duration,
|
|
curve: Curves.linear,
|
|
);
|
|
|
|
roomsPageController.animateToPage(
|
|
0,
|
|
duration: duration,
|
|
curve: Curves.linear,
|
|
);
|
|
|
|
emitSafe(RoomUnSelected());
|
|
}
|
|
|
|
unselectRoom1() {
|
|
// selectedRoom = null;
|
|
devicesPageController.animateToPage(
|
|
1,
|
|
duration: duration,
|
|
curve: Curves.linear,
|
|
);
|
|
|
|
roomsPageController.animateToPage(
|
|
1,
|
|
duration: duration,
|
|
curve: Curves.linear,
|
|
);
|
|
|
|
emitSafe(RoomUnSelected());
|
|
}
|
|
|
|
//////////////////////////////////////// API ////////////////////////////////////////
|
|
generateInvitation(SpaceModel unit) async {
|
|
try {
|
|
final invitationCode = await SpacesAPI.generateInvitationCode(unit.id,
|
|
unit.community.uuid, project?.uuid ?? TempConst.projectIdDev);
|
|
if (invitationCode.isNotEmpty) {
|
|
Share.share('The invitation code is $invitationCode');
|
|
CustomSnackBar.displaySnackBar(
|
|
'Invitation code generated successfully the code is: $invitationCode');
|
|
} else {
|
|
CustomSnackBar.displaySnackBar('Please try again!');
|
|
}
|
|
} catch (failure) {
|
|
CustomSnackBar.displaySnackBar('Something went wrong');
|
|
return;
|
|
}
|
|
}
|
|
|
|
Future<bool> joinAUnit(String code) async {
|
|
try {
|
|
var userUuid =
|
|
await const FlutterSecureStorage().read(key: UserModel.userUuidKey) ??
|
|
'';
|
|
Map<String, String> body = {'inviteCode': code};
|
|
|
|
final success = await SpacesAPI.joinUnit(userUuid, body);
|
|
if (success) {
|
|
await fetchUnitsByUserId();
|
|
CustomSnackBar.displaySnackBar('Done successfully');
|
|
}
|
|
return true;
|
|
} catch (failure) {
|
|
CustomSnackBar.displaySnackBar('Something went wrong');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
fetchUnitsByUserId() async {
|
|
emitSafe(GetSpacesLoading());
|
|
try {
|
|
spaces = await SpacesAPI.getSpacesByUserId();
|
|
emitSafe(GetSpacesSuccess(spaces));
|
|
} catch (failure) {
|
|
emitSafe(GetSpacesError("No units found"));
|
|
return;
|
|
}
|
|
|
|
if (spaces.isNotEmpty) {
|
|
selectedSpace = spaces.first;
|
|
await fetchRoomsByUnitId(selectedSpace!);
|
|
emitSafe(GetSpacesSuccess(spaces));
|
|
} else {
|
|
emitSafe(GetSpacesError("No spaces found"));
|
|
}
|
|
}
|
|
|
|
fetchRoomsByUnitId(SpaceModel space) async {
|
|
emitSafe(GetSpaceRoomsLoading());
|
|
try {
|
|
space.subspaces = await SpacesAPI.getSubSpaceBySpaceId(
|
|
space.community.uuid,
|
|
space.id,
|
|
project?.uuid ?? TempConst.projectIdDev);
|
|
} catch (failure) {
|
|
emitSafe(GetSpaceRoomsError(failure.toString()));
|
|
return;
|
|
}
|
|
if (space.subspaces.isNotEmpty) {
|
|
emitSafe(GetSpaceRoomsSuccess(space.subspaces));
|
|
} else {
|
|
emitSafe(GetSpaceRoomsError("No rooms found"));
|
|
}
|
|
}
|
|
|
|
activationCode(activationCode) async {
|
|
try {
|
|
emitSafe(GetSpaceRoomsLoading());
|
|
var uuid =
|
|
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
|
|
var res = await SpacesAPI.activationCodeSpace(
|
|
activationCode: activationCode, userUuid: uuid);
|
|
|
|
if (res['success'] == true) {
|
|
fetchUserInfo();
|
|
fetchUnitsByUserId();
|
|
}
|
|
emitSafe(GetSpacesSuccess(spaces!));
|
|
|
|
return res['success'];
|
|
} on DioException catch (e) {
|
|
final errorMessage = e.response?.data['error']['message'];
|
|
errorMsg = e.response?.data['error']['message'];
|
|
emitSafe(ActivationError(errMessage: errorMsg));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////// Nav ///////////////////////////////////////
|
|
|
|
static int pageIndex = 0;
|
|
|
|
static Map<String, List<Widget>> appBarActions = {
|
|
// 'Dashboard': [
|
|
// // IconButton(
|
|
// // icon: const Icon(
|
|
// // Icons.add,
|
|
// // size: 25,
|
|
// // ),
|
|
// // style: ButtonStyle(
|
|
// // foregroundColor: WidgetStateProperty.all(ColorsManager.textPrimaryColor),
|
|
// // ),
|
|
// // onPressed: () {
|
|
// // Navigator.push(
|
|
// // NavigationService.navigatorKey.currentContext!,
|
|
// // CustomPageRoute(
|
|
// // builder: (context) => CurtainView(
|
|
// // curtain: DeviceModel(
|
|
// // name: "Curtain",
|
|
// // status: [StatusModel(code: "awd", value: 1)],
|
|
// // productType: DeviceType.Curtain,
|
|
// // ),
|
|
// // ),
|
|
// // ),
|
|
// // );
|
|
// // },
|
|
// // ),
|
|
// ],
|
|
'Devices': [
|
|
//TODO: to be checked
|
|
// IconButton(
|
|
// icon: const Icon(
|
|
// Icons.add,
|
|
// size: 25,
|
|
// ),
|
|
// style: ButtonStyle(
|
|
// foregroundColor:
|
|
// MaterialStateProperty.all(ColorsManager.textPrimaryColor),
|
|
// ),
|
|
// onPressed: () {},
|
|
// ),
|
|
// IconButton(
|
|
// icon: const Icon(
|
|
// Icons.more_vert,
|
|
// size: 25,
|
|
// ),
|
|
// style: ButtonStyle(
|
|
// foregroundColor:
|
|
// MaterialStateProperty.all(ColorsManager.textPrimaryColor),
|
|
// ),
|
|
// onPressed: () {},
|
|
// ),
|
|
],
|
|
'Routine': [
|
|
// IconButton(
|
|
// icon: Image.asset(
|
|
// Assets.assetsIconsFilter,
|
|
// height: 20,
|
|
// width: 20,
|
|
// ),
|
|
// onPressed: () {},
|
|
// ),
|
|
manageScene
|
|
? IconButton(
|
|
icon: const Icon(
|
|
Icons.add,
|
|
size: 32,
|
|
),
|
|
style: ButtonStyle(
|
|
foregroundColor:
|
|
WidgetStateProperty.all(ColorsManager.textPrimaryColor),
|
|
),
|
|
onPressed: () {
|
|
Navigator.pushNamed(
|
|
NavigationService.navigatorKey.currentContext!,
|
|
Routes.sceneTasksRoute,
|
|
arguments: SceneSettingsRouteArguments(
|
|
sceneType: '',
|
|
sceneId: '',
|
|
sceneName: '',
|
|
),
|
|
);
|
|
NavigationService.navigatorKey.currentContext!
|
|
.read<CreateSceneBloc>()
|
|
.add(const ClearTaskListEvent());
|
|
NavigationService.navigatorKey.currentContext!
|
|
.read<CreateSceneBloc>()
|
|
.add(const SceneTypeEvent(CreateSceneEnum.none));
|
|
NavigationService.navigatorKey.currentContext!
|
|
.read<SmartSceneSelectBloc>()
|
|
.add(const SmartSceneClearEvent());
|
|
BlocProvider.of<EffectPeriodBloc>(
|
|
NavigationService.navigatorKey.currentState!.context)
|
|
.add(ResetEffectivePeriod());
|
|
NavigationService.navigatorKey.currentContext!
|
|
.read<CreateSceneBloc>()
|
|
.add(const ClearTabToRunSetting());
|
|
},
|
|
)
|
|
: const SizedBox(),
|
|
// IconButton(
|
|
// icon: const Icon(
|
|
// Icons.more_vert,
|
|
// size: 28,
|
|
// ),
|
|
// style: ButtonStyle(
|
|
// foregroundColor:
|
|
// WidgetStateProperty.all(ColorsManager.textPrimaryColor),
|
|
// ),
|
|
// onPressed: () {},
|
|
// ),
|
|
],
|
|
'Menu': [
|
|
// IconButton(
|
|
// icon: SvgPicture.asset(
|
|
// Assets.assetsIconsScan,
|
|
// height: 20,
|
|
// width: 20,
|
|
// ),
|
|
// onPressed: () {},
|
|
// ),
|
|
],
|
|
};
|
|
|
|
static Map<String, Widget?> appBarLeading = {
|
|
// 'Dashboard': const AppBarHomeDropdown(),
|
|
'Devices': const AppBarHomeDropdown(),
|
|
'Routine': const AppBarHomeDropdown(),
|
|
'Menu': Padding(
|
|
padding: const EdgeInsets.only(left: 15),
|
|
child: Image.asset(
|
|
Assets.assetsImagesLogoHorizontal,
|
|
height: 15,
|
|
width: 100,
|
|
fit: BoxFit.scaleDown,
|
|
),
|
|
),
|
|
};
|
|
|
|
static var bottomNavItems = [
|
|
// defaultBottomNavBarItem(icon: Assets.assetsIconsDashboard, label: 'Dashboard'),
|
|
// defaultBottomNavBarItem(icon: Assets.assetsIconslayout, label: 'Layout'),
|
|
defaultBottomNavBarItem(icon: Assets.assetsIconsDevices, label: 'Devices'),
|
|
defaultBottomNavBarItem(icon: Assets.assetsIconsRoutines, label: 'Routine'),
|
|
defaultBottomNavBarItem(icon: Assets.assetsIconsMenu, label: 'Menu'),
|
|
];
|
|
|
|
final List<Widget> pages = [
|
|
// const DashboardView(),
|
|
// const LayoutPage(),
|
|
BlocProvider(
|
|
create: (context) => DevicesCubit.getInstance(),
|
|
child: const DevicesViewBody(),
|
|
),
|
|
const RoutinesView(),
|
|
const MenuView(),
|
|
];
|
|
|
|
void updatePageIndex(int index) {
|
|
pageIndex = index;
|
|
|
|
emitSafe(NavChangePage());
|
|
}
|
|
|
|
void updateDevice(String deviceId) async {
|
|
try {
|
|
final response = await DevicesAPI.firmwareDevice(
|
|
deviceId: deviceId, firmwareVersion: '0');
|
|
if (response['success'] ?? false) {
|
|
CustomSnackBar.displaySnackBar('No updates available');
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
}
|
|
|
|
BottomNavigationBarItem defaultBottomNavBarItem(
|
|
{required String icon, required String label}) {
|
|
return BottomNavigationBarItem(
|
|
icon: SvgPicture.asset(icon),
|
|
activeIcon: SvgPicture.asset(
|
|
icon.replaceAll('.svg', '-fill.svg'),
|
|
colorFilter: const ColorFilter.mode(
|
|
ColorsManager.primaryColor,
|
|
BlendMode.srcIn,
|
|
),
|
|
),
|
|
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;
|
|
// }
|
|
// }
|