diff --git a/android/app/build.gradle b/android/app/build.gradle index c6a9a9e..4a14eae 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -27,7 +27,7 @@ if (flutterVersionName == null) { } android { - namespace "com.example.syncrow.app" + namespace "com.example.syncrow_application" compileSdkVersion 34 ndkVersion flutter.ndkVersion @@ -46,7 +46,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.syncrow.app" + applicationId "com.example.syncrow_application" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. minSdkVersion 21 diff --git a/android/app/google-services.json b/android/app/google-services.json index 0c568c5..b304aae 100644 --- a/android/app/google-services.json +++ b/android/app/google-services.json @@ -9,7 +9,7 @@ "client_info": { "mobilesdk_app_id": "1:427332280600:android:550f67441246cb1a0c7e6d", "android_client_info": { - "package_name": "com.example.syncrow.app" + "package_name": "com.example.syncrow_application" } }, "oauth_client": [], diff --git a/android/app/src/main/kotlin/com/example/syncrow_app/MainActivity.kt b/android/app/src/main/kotlin/com/example/syncrow_app/MainActivity.kt index ec5d9a5..1e03aee 100644 --- a/android/app/src/main/kotlin/com/example/syncrow_app/MainActivity.kt +++ b/android/app/src/main/kotlin/com/example/syncrow_app/MainActivity.kt @@ -1,4 +1,4 @@ -package com.example.syncrow.app +package com.example.syncrow_application import io.flutter.embedding.android.FlutterActivity diff --git a/assets/icons/blue_checkbox_ic.svg b/assets/icons/blue_checkbox_ic.svg new file mode 100644 index 0000000..46d0160 --- /dev/null +++ b/assets/icons/blue_checkbox_ic.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/empty_checkbox_ic.svg b/assets/icons/empty_checkbox_ic.svg new file mode 100644 index 0000000..34522c8 --- /dev/null +++ b/assets/icons/empty_checkbox_ic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/features/app_layout/bloc/home_cubit.dart b/lib/features/app_layout/bloc/home_cubit.dart index 13d33e8..48021cc 100644 --- a/lib/features/app_layout/bloc/home_cubit.dart +++ b/lib/features/app_layout/bloc/home_cubit.dart @@ -6,6 +6,7 @@ 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/space_model.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'; @@ -22,6 +23,7 @@ import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/navigation/navigation_service.dart'; import 'package:syncrow_app/services/api/spaces_api.dart'; import 'package:syncrow_app/utils/helpers/custom_page_route.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/constants.dart'; @@ -31,11 +33,12 @@ class HomeCubit extends Cubit { HomeCubit._() : super(HomeInitial()) { checkIfNotificationPermissionGranted(); if (selectedSpace == null) { - fetchUnitsByUserId().then((value) { - if (selectedSpace != null) { - fetchRoomsByUnitId(selectedSpace!); - } - }); + fetchUnitsByUserId(); + // .then((value) { + // if (selectedSpace != null) { + // fetchRoomsByUnitId(selectedSpace!); + // } + // }); } } @@ -143,6 +146,7 @@ class HomeCubit extends Cubit { changeSelectedSpace(SpaceModel space) { selectedSpace = space; emitSafe(SpaceSelected(space)); + fetchRoomsByUnitId(space); } roomSliderPageChanged(int index) { @@ -193,6 +197,39 @@ class HomeCubit extends Cubit { } //////////////////////////////////////// API //////////////////////////////////////// + generateInvitation(String unitId) async { + try { + final invitationCode = await SpacesAPI.generateInvitationCode(unitId); + 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 joinAUnit(String code) async { + try { + var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey) ?? ''; + Map body = {'userUuid': uuid, 'inviteCode': code}; + + final success = await SpacesAPI.joinUnit(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 { @@ -204,8 +241,8 @@ class HomeCubit extends Cubit { if (spaces != null && spaces!.isNotEmpty) { selectedSpace = spaces!.first; + await fetchRoomsByUnitId(selectedSpace!); emitSafe(GetSpacesSuccess(spaces!)); - // fetchRoomsByUnitId(selectedSpace!); } else { emitSafe(GetSpacesError("No spaces found")); } diff --git a/lib/features/app_layout/view/widgets/default_app_bar.dart b/lib/features/app_layout/view/widgets/default_app_bar.dart index 6c67536..332ef19 100644 --- a/lib/features/app_layout/view/widgets/default_app_bar.dart +++ b/lib/features/app_layout/view/widgets/default_app_bar.dart @@ -20,11 +20,9 @@ class DefaultAppBar extends StatelessWidget implements PreferredSizeWidget { leadingWidth: 150, toolbarHeight: Constants.appBarHeight, leading: HomeCubit.getInstance().spaces!.isNotEmpty - ? HomeCubit.appBarLeading[ - HomeCubit.bottomNavItems[HomeCubit.pageIndex].label] + ? HomeCubit.appBarLeading[HomeCubit.bottomNavItems[HomeCubit.pageIndex].label] : null, - actions: HomeCubit.appBarActions[ - HomeCubit.bottomNavItems[HomeCubit.pageIndex].label], + // actions: HomeCubit.appBarActions[HomeCubit.bottomNavItems[HomeCubit.pageIndex].label], )); }, ); diff --git a/lib/features/auth/bloc/auth_cubit.dart b/lib/features/auth/bloc/auth_cubit.dart index c62bc08..2d3f9b1 100644 --- a/lib/features/auth/bloc/auth_cubit.dart +++ b/lib/features/auth/bloc/auth_cubit.dart @@ -1,4 +1,3 @@ -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'; @@ -9,6 +8,7 @@ import 'package:syncrow_app/features/auth/model/user_model.dart'; import 'package:syncrow_app/navigation/navigation_service.dart'; import 'package:syncrow_app/navigation/routing_constants.dart'; import 'package:syncrow_app/services/api/authentication_api.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; part 'auth_state.dart'; class AuthCubit extends Cubit { @@ -26,6 +26,7 @@ class AuthCubit extends Cubit { final loginFormKey = GlobalKey(); final signUpFormKey = GlobalKey(); bool isPasswordVisible = false; + bool showValidationMessage = false; static GlobalKey formKey = GlobalKey(); @@ -58,7 +59,7 @@ class AuthCubit extends Cubit { if (value.isNotEmpty) { if (!RegExp(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$') .hasMatch(value)) { - return 'Password must contain at least one uppercase letter, one lowercase letter, one number and one special character'; + return 'Password must contain at least:\n - one uppercase letter.\n - one lowercase letter.\n - one number. \n - special character'; } } } @@ -147,6 +148,12 @@ class AuthCubit extends Cubit { login() async { emit(AuthLoginLoading()); try { + if (emailController.text.isEmpty || passwordController.text.isEmpty) { + CustomSnackBar.displaySnackBar('Please enter your credentials'); + emit(AuthLoginError(message: 'Something went wrong')); + return; + } + token = await AuthenticationAPI.loginWithEmail( model: LoginWithEmailModel( email: emailController.text.toLowerCase(), diff --git a/lib/features/auth/view/sign_up_view.dart b/lib/features/auth/view/sign_up_view.dart index e6f27c9..aa5d71e 100644 --- a/lib/features/auth/view/sign_up_view.dart +++ b/lib/features/auth/view/sign_up_view.dart @@ -86,7 +86,7 @@ class SignUpView extends StatelessWidget { ), Form( key: formKey, - autovalidateMode: AutovalidateMode.disabled, + // autovalidateMode: AutovalidateMode.disabled, child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -96,14 +96,15 @@ class SignUpView extends StatelessWidget { fontColor: Colors.white, ), TextFormField( - autovalidateMode: AutovalidateMode.disabled, + // autovalidateMode: AutovalidateMode.disabled, textInputAction: TextInputAction.done, keyboardType: TextInputType.name, scrollPadding: EdgeInsets.zero, autocorrect: false, autofillHints: const [AutofillHints.name], // controller: AuthCubit.get(context).fullNameController, - validator: AuthCubit.get(context).fullNameValidator, + validator: (value) => + AuthCubit.get(context).fullNameValidator(value), onTapOutside: (event) { FocusScope.of(context).unfocus(); }, @@ -119,7 +120,7 @@ class SignUpView extends StatelessWidget { fontColor: Colors.white, ), TextFormField( - autovalidateMode: AutovalidateMode.disabled, + // autovalidateMode: AutovalidateMode.disabled, textInputAction: TextInputAction.done, keyboardType: TextInputType.text, scrollPadding: EdgeInsets.zero, @@ -141,7 +142,7 @@ class SignUpView extends StatelessWidget { fontColor: Colors.white, ), TextFormField( - autovalidateMode: AutovalidateMode.disabled, + // autovalidateMode: AutovalidateMode.disabled, textInputAction: TextInputAction.done, keyboardType: TextInputType.text, scrollPadding: EdgeInsets.zero, @@ -199,6 +200,7 @@ class SignUpView extends StatelessWidget { 'Sign up', ), onPressed: () { + AuthCubit.get(context).showValidationMessage = true; if (formKey.currentState!.validate()) { if ((state is! AuthLoading)) { AuthCubit.get(context).signUp(); diff --git a/lib/features/auth/view/widgets/login_form.dart b/lib/features/auth/view/widgets/login_form.dart index 26e6844..5432a4d 100644 --- a/lib/features/auth/view/widgets/login_form.dart +++ b/lib/features/auth/view/widgets/login_form.dart @@ -65,7 +65,7 @@ class LoginForm extends StatelessWidget { if (state is AuthTokenError && !pressed) { return null; } - return AuthCubit.get(context).passwordValidator(value); + // return AuthCubit.get(context).passwordValidator(value); }, onTapOutside: (event) { FocusScope.of(context).unfocus(); diff --git a/lib/features/menu/bloc/create_unit_bloc/create_unit_bloc.dart b/lib/features/menu/bloc/create_unit_bloc/create_unit_bloc.dart index 5a00e20..e31fd2b 100644 --- a/lib/features/menu/bloc/create_unit_bloc/create_unit_bloc.dart +++ b/lib/features/menu/bloc/create_unit_bloc/create_unit_bloc.dart @@ -1,9 +1,11 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.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/menu/bloc/create_unit_bloc/create_unit_event.dart'; import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_state.dart'; import 'package:syncrow_app/services/api/home_creation_api.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; class CreateUnitBloc extends Bloc { List rooms = []; @@ -45,11 +47,16 @@ class CreateUnitBloc extends Bloc { void _save(SaveRooms event, Emitter emit) async { try { - emit(LoadingState()); - if (rooms.isEmpty) { - const ErrorState(message: 'Please add at least one room!'); + if (event.unitName.isEmpty) { + CustomSnackBar.displaySnackBar('Please enter the unit name'); return; } + if (rooms.isEmpty) { + CustomSnackBar.displaySnackBar('Please add at least one room!'); + return; + } + + emit(LoadingState()); var storage = const FlutterSecureStorage(); var userId = await storage.read(key: UserModel.userUuidKey) ?? ''; @@ -83,6 +90,7 @@ class CreateUnitBloc extends Bloc { } // } } + await HomeCubit.getInstance().fetchUnitsByUserId(); emit(RoomsSavedSuccessfully()); } catch (_) { emit(const ErrorState(message: 'Something went wrong')); diff --git a/lib/features/menu/bloc/manage_unit_bloc/manage_unit_bloc.dart b/lib/features/menu/bloc/manage_unit_bloc/manage_unit_bloc.dart new file mode 100644 index 0000000..bc85498 --- /dev/null +++ b/lib/features/menu/bloc/manage_unit_bloc/manage_unit_bloc.dart @@ -0,0 +1,112 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/menu/bloc/manage_unit_bloc/manage_unit_event.dart'; +import 'package:syncrow_app/features/menu/bloc/manage_unit_bloc/manage_unit_state.dart'; +import 'package:syncrow_app/services/api/devices_api.dart'; +import 'package:syncrow_app/services/api/home_creation_api.dart'; +import 'package:syncrow_app/services/api/home_management_api.dart'; +import 'package:syncrow_app/services/api/spaces_api.dart'; + +class ManageUnitBloc extends Bloc { + List allDevices = []; + String unitName = ''; + + ManageUnitBloc() : super(InitialState()) { + on(_fetchRoomsAndDevices); + on(_fetchDevicesByRoomId); + on(_assignDevice); + on(_addNewRoom); + } + + void _fetchRoomsAndDevices(FetchRoomsEvent event, Emitter emit) async { + try { + emit(LoadingState()); + final roomsList = await SpacesAPI.getRoomsBySpaceId(event.unitId); + emit(FetchRoomsState(devicesList: allDevices, roomsList: roomsList)); + } catch (e) { + emit(const ErrorState(message: 'Something went wrong')); + return; + } + } + + void _fetchDevicesByRoomId(FetchDevicesByRoomIdEvent event, Emitter emit) async { + try { + Map roomDevicesId = {}; + emit(LoadingState()); + final devicesList = await DevicesAPI.getDevicesByRoomId(event.roomId); + allDevices = await HomeManagementAPI.fetchDevicesByUserId(); + + List allDevicesIds = []; + + allDevices.forEach((element) { + allDevicesIds.add(element.uuid!); + }); + + devicesList.forEach((e) { + if (allDevicesIds.contains(e.uuid!)) { + roomDevicesId[e.uuid!] = true; + } else { + roomDevicesId[e.uuid!] = false; + } + }); + emit(FetchDeviceByRoomIdState( + roomDevices: devicesList, + allDevices: allDevices, + roomDevicesId: roomDevicesId, + roomId: event.roomId)); + } catch (e) { + emit(const ErrorState(message: 'Something went wrong')); + return; + } + } + + void _assignDevice(AssignRoomEvent event, Emitter emit) async { + try { + Map roomDevicesId = {}; + emit(LoadingState()); + Map body = {"deviceUuid": event.deviceId, "roomUuid": event.roomId}; + await HomeManagementAPI.assignDeviceToRoom(body); + final devicesList = await DevicesAPI.getDevicesByRoomId(event.roomId); + + List allDevicesIds = []; + + allDevices.forEach((element) { + allDevicesIds.add(element.uuid!); + }); + + devicesList.forEach((e) { + if (allDevicesIds.contains(e.uuid!)) { + roomDevicesId[e.uuid!] = true; + } else { + roomDevicesId[e.uuid!] = false; + } + }); + emit(FetchDeviceByRoomIdState( + roomDevices: devicesList, + allDevices: allDevices, + roomDevicesId: roomDevicesId, + roomId: event.roomId)); + } catch (e) { + emit(const ErrorState(message: 'Something went wrong')); + return; + } + } + + _addNewRoom(AddNewRoom event, Emitter emit) async { + Map body = {'roomName': event.roomName, 'unitUuid': event.unitId}; + try { + emit(LoadingState()); + final response = await HomeCreation.createRoom(body); + if (response['data']['uuid'] != '') { + final roomsList = await SpacesAPI.getRoomsBySpaceId(event.unitId); + allDevices = await HomeManagementAPI.fetchDevicesByUserId(); + emit(FetchRoomsState(devicesList: allDevices, roomsList: roomsList)); + } else { + emit(const ErrorState(message: 'Something went wrong')); + } + } catch (_) { + emit(const ErrorState(message: 'Something went wrong')); + return; + } + } +} diff --git a/lib/features/menu/bloc/manage_unit_bloc/manage_unit_event.dart b/lib/features/menu/bloc/manage_unit_bloc/manage_unit_event.dart new file mode 100644 index 0000000..03c0722 --- /dev/null +++ b/lib/features/menu/bloc/manage_unit_bloc/manage_unit_event.dart @@ -0,0 +1,50 @@ +import 'package:equatable/equatable.dart'; + +abstract class ManageUnitEvent extends Equatable { + const ManageUnitEvent(); + + @override + List get props => []; +} + +class InitialEvent extends ManageUnitEvent {} + +class LoadingEvent extends ManageUnitEvent {} + +class FetchRoomsEvent extends ManageUnitEvent { + final String unitId; + + const FetchRoomsEvent({required this.unitId}); + + @override + List get props => [unitId]; +} + +class FetchDevicesByRoomIdEvent extends ManageUnitEvent { + final String roomId; + + const FetchDevicesByRoomIdEvent({required this.roomId}); + + @override + List get props => [roomId]; +} + +class AddNewRoom extends ManageUnitEvent { + final String roomName; + final String unitId; + + const AddNewRoom({required this.roomName, required this.unitId}); + + @override + List get props => [roomName, unitId]; +} + +class AssignRoomEvent extends ManageUnitEvent { + final String roomId; + final String deviceId; + + const AssignRoomEvent({required this.roomId, required this.deviceId}); + + @override + List get props => [roomId]; +} diff --git a/lib/features/menu/bloc/manage_unit_bloc/manage_unit_state.dart b/lib/features/menu/bloc/manage_unit_bloc/manage_unit_state.dart new file mode 100644 index 0000000..56236a5 --- /dev/null +++ b/lib/features/menu/bloc/manage_unit_bloc/manage_unit_state.dart @@ -0,0 +1,51 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/room_model.dart'; + +abstract class ManageUnitState extends Equatable { + const ManageUnitState(); + + @override + List get props => []; +} + +class InitialState extends ManageUnitState {} + +class LoadingState extends ManageUnitState {} + +class FetchRoomsState extends ManageUnitState { + final List roomsList; + final List devicesList; + + const FetchRoomsState({required this.devicesList, required this.roomsList}); + + @override + List get props => [devicesList]; +} + +class FetchDeviceByRoomIdState extends ManageUnitState { + final List roomDevices; + final List allDevices; + final Map roomDevicesId; + final String roomId; + + const FetchDeviceByRoomIdState( + {required this.roomDevices, + required this.allDevices, + required this.roomDevicesId, + required this.roomId}); + + @override + List get props => [roomDevices, allDevices, roomDevicesId, roomId]; +} + +class AssignRoomState extends ManageUnitState {} + +class ErrorState extends ManageUnitState { + final String message; + + const ErrorState({required this.message}); + + @override + List get props => [message]; +} diff --git a/lib/features/menu/view/widgets/create_home/create_home_view.dart b/lib/features/menu/view/widgets/create_home/create_home_view.dart index 2480e2e..201f80b 100644 --- a/lib/features/menu/view/widgets/create_home/create_home_view.dart +++ b/lib/features/menu/view/widgets/create_home/create_home_view.dart @@ -1,7 +1,7 @@ -// ignore_for_file: unnecessary_const import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_bloc.dart'; import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_event.dart'; import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_state.dart'; @@ -23,245 +23,257 @@ class CreateUnitView extends StatelessWidget { @override Widget build(BuildContext context) { String unitName = ''; - String location = ''; TextEditingController textEditingController = TextEditingController(); + return BlocProvider( create: (context) => CreateUnitBloc(), - child: BlocConsumer(listener: (context, state) { - if (state is RoomsSavedSuccessfully) { - CustomSnackBar.displaySnackBar('Saved successfully'); - NavigationService.navigatorKey.currentState!.pushNamedAndRemoveUntil( - Routes.homeRoute, - (Route route) => false, - ); - } + child: BlocConsumer( + listener: (context, state) { + if (state is RoomsSavedSuccessfully) { + CustomSnackBar.displaySnackBar('Unit created successfully'); + Navigator.of(context).pop(); + } - if (state is ErrorState) { - CustomSnackBar.displaySnackBar(state.message); - } - }, builder: (context, state) { - textEditingController.text = ''; - return DefaultScaffold( - actions: [ - TextButton( - onPressed: () { - if (unitName.isNotEmpty) { + if (state is ErrorState) { + CustomSnackBar.displaySnackBar(state.message); + } + }, + builder: (context, state) { + textEditingController.text = ''; + return DefaultScaffold( + actions: [ + TextButton( + onPressed: () { BlocProvider.of(context).add(SaveRooms( - communityName: 'Community Test', - buildingName: 'Building Test', - floorName: 'Floor Test', - unitName: unitName)); - } else { - CustomSnackBar.displaySnackBar('Please enter the unit name'); - } - }, - child: const BodyLarge( - text: 'Save', - fontWeight: FontWeight.bold, + communityName: 'Community Test', + buildingName: 'Building Test', + floorName: 'Floor Test', + unitName: unitName, + )); + }, + child: const BodyLarge( + text: 'Save', + fontWeight: FontWeight.bold, + ), ), - ), - ], - title: 'Create a Unit', - child: Container( - padding: const EdgeInsets.symmetric(vertical: 40), - width: MediaQuery.sizeOf(context).width, - height: MediaQuery.sizeOf(context).height, - child: SingleChildScrollView( - child: Column( - children: [ - //Home Info - DefaultContainer( - padding: const EdgeInsets.symmetric( - horizontal: 25, - vertical: 5, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const BodyMedium(text: 'Unit Name'), - Flexible( - child: TextField( - textAlign: TextAlign.end, - onChanged: (value) { - unitName = value; - }, - decoration: InputDecoration( - hintText: 'Enter Name', - hintStyle: context.bodyMedium.copyWith(color: Colors.grey), - border: InputBorder.none, - ), - ), + ], + title: 'Create a Unit', + child: Container( + padding: const EdgeInsets.symmetric(vertical: 40), + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + child: state is LoadingState + ? const Center( + child: CircularProgressIndicator(), + ) + : SingleChildScrollView( + child: Column( + children: [ + // Home Info + DefaultContainer( + padding: const EdgeInsets.symmetric( + horizontal: 25, + vertical: 5, ), - ], - ), - const Divider( - color: ColorsManager.greyColor, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const BodyMedium(text: 'Location '), - Flexible( - child: TextField( - textAlign: TextAlign.end, - onChanged: (value) { - location = value; - }, - decoration: InputDecoration( - hintText: 'Set', - hintStyle: context.bodyMedium.copyWith(color: Colors.grey), - border: InputBorder.none, - ), - ), - ), - ], - ), - ], - ), - ), - - //Rooms Info - const SizedBox(height: 10), - const Row( - children: [ - SizedBox( - width: 25, - ), - BodySmall( - text: 'Spaces', - fontWeight: FontWeight.bold, - ), - ], - ), - const SizedBox( - height: 4, - ), - DefaultContainer( - padding: const EdgeInsets.symmetric( - horizontal: 25, - vertical: 5, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - if (state is CreateRoomState) - if (state.roomList.isNotEmpty) - Column( + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, children: [ - SizedBox( - height: (state.roomList.length) * 55, - child: ListView.separated( - physics: - const NeverScrollableScrollPhysics(), // Disable scrolling - separatorBuilder: (context, index) => const Divider( - color: ColorsManager.greyColor, - ), - itemCount: state.roomList.length, - itemBuilder: (context, index) { - return Dismissible( - key: Key(state.roomList[index]), - background: Container( - padding: const EdgeInsets.only(right: 10), - alignment: AlignmentDirectional.centerEnd, - decoration: ShapeDecoration( - color: const Color(0xFFFF0000), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(index == 0 ? 20 : 0), - topRight: Radius.circular(index == 0 ? 20 : 0), - ), - ), - ), - child: SvgPicture.asset( - Assets.assetsDeleteIcon, - width: 20, - height: 22, - ), - ), - direction: DismissDirection.endToStart, - onDismissed: (direction) { - String removedUnit = state.roomList[index]; - - BlocProvider.of(context) - .add(RemoveRoomEvent(roomName: removedUnit)); - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('$removedUnit removed')), - ); + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const BodyMedium(text: 'Unit Name'), + Flexible( + child: TextField( + textAlign: TextAlign.end, + onChanged: (value) { + unitName = value; }, - child: Container( - height: 50, - padding: const EdgeInsets.symmetric(vertical: 16), - child: Text( - state.roomList[index], - style: const TextStyle( - color: Color(0xFF5D5D5D), - fontSize: 15, - fontWeight: FontWeight.w400, - ), - ), + decoration: InputDecoration( + hintText: 'Enter Name', + hintStyle: + context.bodyMedium.copyWith(color: Colors.grey), + border: InputBorder.none, ), - ); - }, - ), + ), + ), + ], ), const Divider( color: ColorsManager.greyColor, ), - ], - ), - Row( - children: [ - TextButton( - onPressed: () { - if (textEditingController.text.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Please add the room name')), - ); - return; - } - BlocProvider.of(context) - .add(CreateRoomEvent(roomName: textEditingController.text)); - }, - child: const Row( - mainAxisAlignment: MainAxisAlignment.start, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - BodyMedium( - text: 'Add Space', - fontColor: ColorsManager.primaryColor, + const BodyMedium(text: 'Location '), + Flexible( + child: TextField( + textAlign: TextAlign.end, + onChanged: (value) {}, + decoration: InputDecoration( + hintText: 'Set', + hintStyle: + context.bodyMedium.copyWith(color: Colors.grey), + border: InputBorder.none, + ), + ), ), ], - )), - Flexible( - child: TextField( - textAlign: TextAlign.end, - controller: textEditingController, - onChanged: (value) { - textEditingController.text = value; - }, - decoration: InputDecoration( - hintText: 'Set', - hintStyle: context.bodyMedium.copyWith(color: Colors.grey), - border: InputBorder.none, ), + ], + ), + ), + + // Rooms Info + const SizedBox(height: 10), + const Row( + children: [ + SizedBox( + width: 25, + ), + BodySmall( + text: 'Spaces', + fontWeight: FontWeight.bold, + ), + ], + ), + const SizedBox( + height: 4, + ), + Container( + // margin: EdgeInsets.zero, + // margin: const EdgeInsets.symmetric( + // horizontal: 25, + // vertical: 5, + // ), + decoration: const ShapeDecoration( + color: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20)), ), ), - ], - ) - ], + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + if (state is CreateRoomState) + if (state.roomList.isNotEmpty) + Column( + children: [ + ListView.separated( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + separatorBuilder: (context, index) => const Divider( + color: ColorsManager.greyColor, + ), + itemCount: state.roomList.length, + itemBuilder: (context, index) { + return Dismissible( + key: Key(state.roomList[index]), + background: Container( + padding: const EdgeInsets.only(right: 10), + alignment: AlignmentDirectional.centerEnd, + decoration: ShapeDecoration( + color: const Color(0xFFFF0000), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(index == 0 ? 20 : 0), + topRight: + Radius.circular(index == 0 ? 20 : 0), + ), + ), + ), + child: SvgPicture.asset( + Assets.assetsDeleteIcon, + width: 20, + height: 22, + ), + ), + direction: DismissDirection.endToStart, + onDismissed: (direction) { + String removedUnit = state.roomList[index]; + + BlocProvider.of(context) + .add(RemoveRoomEvent(roomName: removedUnit)); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('$removedUnit removed')), + ); + }, + child: Container( + height: 50, + padding: const EdgeInsets.symmetric( + vertical: 16, horizontal: 25), + child: Text( + state.roomList[index], + style: const TextStyle( + color: Color(0xFF5D5D5D), + fontSize: 15, + fontWeight: FontWeight.w400, + ), + ), + ), + ); + }, + ), + const Divider( + color: ColorsManager.greyColor, + ), + ], + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 5), + child: Row( + children: [ + TextButton( + onPressed: () { + if (textEditingController.text.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Please add the room name')), + ); + return; + } + BlocProvider.of(context).add( + CreateRoomEvent( + roomName: textEditingController.text)); + textEditingController.clear(); + }, + child: const Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + BodyMedium( + text: 'Add custom Space', + fontColor: ColorsManager.primaryColor, + ), + ], + ), + ), + Flexible( + child: TextField( + textAlign: TextAlign.end, + controller: textEditingController, + decoration: InputDecoration( + hintText: 'Set', + hintStyle: + context.bodyMedium.copyWith(color: Colors.grey), + border: InputBorder.none, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), ), - ), - ], - ), ), - ), - ); - }), + ); + }, + ), ); } } diff --git a/lib/features/menu/view/widgets/join_home/join_home_view.dart b/lib/features/menu/view/widgets/join_home/join_home_view.dart index f860f31..69952c9 100644 --- a/lib/features/menu/view/widgets/join_home/join_home_view.dart +++ b/lib/features/menu/view/widgets/join_home/join_home_view.dart @@ -1,16 +1,19 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.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_scaffold.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; class JoinHomeView extends StatelessWidget { const JoinHomeView({super.key}); @override Widget build(BuildContext context) { + TextEditingController textEditingController = TextEditingController(); return DefaultScaffold( title: "Join a Home", child: Column( @@ -38,16 +41,27 @@ class JoinHomeView extends StatelessWidget { children: [ Flexible( child: TextField( + controller: textEditingController, decoration: InputDecoration( hintText: 'Invitatoin code', - hintStyle: - context.bodyMedium.copyWith(color: Colors.grey), + hintStyle: context.bodyMedium.copyWith(color: Colors.grey), border: InputBorder.none, ), ), ), IconButton( - onPressed: () {}, + onPressed: () async { + if (textEditingController.text.isEmpty) { + CustomSnackBar.displaySnackBar('Please enter the invitation code'); + return; + } + if (await HomeCubit.getInstance().joinAUnit(textEditingController.text)) { + CustomSnackBar.displaySnackBar('Done successfully'); + Navigator.of(context).pop(); + } else { + CustomSnackBar.displaySnackBar('Wrong code!'); + } + }, icon: const Icon( Icons.arrow_right_alt, ), diff --git a/lib/features/menu/view/widgets/manage_home/assign_devices.dart b/lib/features/menu/view/widgets/manage_home/assign_devices.dart new file mode 100644 index 0000000..9372129 --- /dev/null +++ b/lib/features/menu/view/widgets/manage_home/assign_devices.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_app/features/menu/bloc/manage_unit_bloc/manage_unit_bloc.dart'; +import 'package:syncrow_app/features/menu/bloc/manage_unit_bloc/manage_unit_event.dart'; +import 'package:syncrow_app/features/menu/bloc/manage_unit_bloc/manage_unit_state.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/generated/assets.dart'; + +class AssignDeviceView extends StatelessWidget { + final String unitId; + final String roomId; + const AssignDeviceView({super.key, required this.unitId, required this.roomId}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => ManageUnitBloc()..add(FetchDevicesByRoomIdEvent(roomId: roomId)), + child: BlocConsumer( + listener: (context, state) {}, + builder: (context, state) { + return DefaultScaffold( + title: 'Space Setting', + child: state is LoadingState + ? const Center(child: RefreshProgressIndicator()) + : state is FetchDeviceByRoomIdState + ? Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + child: GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 10.0, + mainAxisSpacing: 10.0, + childAspectRatio: 1.0, + ), + itemCount: state.allDevices.length, + itemBuilder: (context, index) { + return Container( + width: MediaQuery.sizeOf(context).width, + padding: const EdgeInsets.all(16), + decoration: ShapeDecoration( + color: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SvgPicture.asset( + state.allDevices[index].icon!, + fit: BoxFit.contain, + ), + GestureDetector( + onTap: () { + if (state.roomDevicesId[ + state.allDevices[index].uuid!] ?? + false == false) { + BlocProvider.of(context).add( + AssignRoomEvent( + deviceId: + state.allDevices[index].uuid ?? + '', + roomId: roomId)); + } + }, + child: SvgPicture.asset( + state.roomDevicesId[ + state.allDevices[index].uuid!] ?? + false + ? Assets.blueCheckboxIcon + : Assets.emptyCheckboxIcon, + width: 22, + height: 22, + )) + ], + ), + Text(state.allDevices[index].name ?? ''), + ], + ), + ); + }), + ) + : Container()); + })); + } +} diff --git a/lib/features/menu/view/widgets/manage_home/home_settings.dart b/lib/features/menu/view/widgets/manage_home/home_settings.dart index cb6373d..5b4ce6a 100644 --- a/lib/features/menu/view/widgets/manage_home/home_settings.dart +++ b/lib/features/menu/view/widgets/manage_home/home_settings.dart @@ -1,11 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; import 'package:syncrow_app/features/app_layout/model/space_model.dart'; +import 'package:syncrow_app/features/menu/view/widgets/manage_home/room_screen.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/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_small.dart'; import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/helpers/custom_page_route.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; class HomeSettingsView extends StatelessWidget { @@ -38,8 +42,7 @@ class HomeSettingsView extends StatelessWidget { textAlign: TextAlign.end, decoration: InputDecoration( hintText: 'Enter Name', - hintStyle: - context.bodyMedium.copyWith(color: Colors.grey), + hintStyle: context.bodyMedium.copyWith(color: Colors.grey), border: InputBorder.none, ), ), @@ -52,16 +55,27 @@ class HomeSettingsView extends StatelessWidget { height: 1, color: ColorsManager.greyColor, ), - const Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - BodyMedium(text: 'Rooms'), - Icon( - Icons.arrow_forward_ios, - color: ColorsManager.greyColor, - size: 15, - ) - ], + Container( + width: MediaQuery.sizeOf(context).width, + child: GestureDetector( + onTap: () { + Navigator.of(context).push(CustomPageRoute( + builder: (context) => RoomsView( + unitId: space?.id ?? '', + ))); + }, + child: const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodyMedium(text: 'Rooms'), + Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ) + ], + ), + ), ), //Divider Container( @@ -78,14 +92,39 @@ class HomeSettingsView extends StatelessWidget { textAlign: TextAlign.end, decoration: InputDecoration( hintText: 'Set', - hintStyle: - context.bodyMedium.copyWith(color: Colors.grey), + hintStyle: context.bodyMedium.copyWith(color: Colors.grey), border: InputBorder.none, ), ), ), ], ), + //Divider + Container( + margin: const EdgeInsets.only(bottom: 10), + height: 1, + color: ColorsManager.greyColor, + ), + Container( + width: MediaQuery.sizeOf(context).width, + padding: const EdgeInsets.only(bottom: 10), + child: GestureDetector( + onTap: () async { + await HomeCubit.getInstance().generateInvitation(space?.id ?? ''); + }, + child: const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodyMedium(text: 'Invite a member'), + Icon( + Icons.arrow_forward_ios, + color: ColorsManager.greyColor, + size: 15, + ) + ], + ), + ), + ), ], ), ), @@ -104,14 +143,13 @@ class HomeSettingsView extends StatelessWidget { crossAxisCount: 2, crossAxisSpacing: 10, ), - itemCount: 4, + itemCount: 2, itemBuilder: (context, index) => Stack( alignment: Alignment.topCenter, children: [ DefaultContainer( margin: const EdgeInsets.only(top: 20), - padding: - const EdgeInsets.symmetric(vertical: 15, horizontal: 40), + padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 40), child: Column( mainAxisSize: MainAxisSize.min, children: [ diff --git a/lib/features/menu/view/widgets/manage_home/room_screen.dart b/lib/features/menu/view/widgets/manage_home/room_screen.dart new file mode 100644 index 0000000..f4947c6 --- /dev/null +++ b/lib/features/menu/view/widgets/manage_home/room_screen.dart @@ -0,0 +1,137 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/menu/bloc/manage_unit_bloc/manage_unit_bloc.dart'; +import 'package:syncrow_app/features/menu/bloc/manage_unit_bloc/manage_unit_event.dart'; +import 'package:syncrow_app/features/menu/bloc/manage_unit_bloc/manage_unit_state.dart'; +import 'package:syncrow_app/features/menu/view/widgets/manage_home/assign_devices.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.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/helpers/snack_bar.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class RoomsView extends StatelessWidget { + final String unitId; + const RoomsView({super.key, required this.unitId}); + + @override + Widget build(BuildContext context) { + TextEditingController textEditingController = TextEditingController(); + return BlocProvider( + create: (context) => ManageUnitBloc()..add(FetchRoomsEvent(unitId: unitId)), + child: BlocConsumer( + listener: (context, state) {}, + builder: (context, state) { + return DefaultScaffold( + title: 'Space Management', + child: state is LoadingState + ? const Center(child: RefreshProgressIndicator()) + : Container( + margin: const EdgeInsets.only(top: 32), + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + child: Column( + children: [ + if (state is FetchRoomsState) + Container( + decoration: const ShapeDecoration( + color: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20)), + ), + ), + child: Column( + children: [ + if (state.roomsList.isNotEmpty) + ListView.separated( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + separatorBuilder: (context, index) => const Divider( + color: ColorsManager.greyColor, + ), + itemCount: state.roomsList.length, + itemBuilder: (context, index) { + return GestureDetector( + onTap: () { + // BlocProvider.of(context).add( + // FetchDevicesByRoomIdEvent(roomId: roomId)); + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => AssignDeviceView( + roomId: state.roomsList[index].id ?? '', + unitId: unitId, + )), + ); + }, + child: Container( + height: 50, + padding: const EdgeInsets.symmetric( + vertical: 16, horizontal: 25), + child: Text( + state.roomsList[index].name ?? '', + style: const TextStyle( + color: Color(0xFF5D5D5D), + fontSize: 15, + fontWeight: FontWeight.w400, + ), + ), + ), + ); + }, + ), + const Divider( + color: ColorsManager.greyColor, + ), + Container( + padding: + const EdgeInsets.symmetric(horizontal: 25, vertical: 5), + child: Row( + children: [ + TextButton( + onPressed: () { + if (textEditingController.text.isEmpty) { + CustomSnackBar.displaySnackBar( + 'Please add the room name'); + return; + } + BlocProvider.of(context).add( + AddNewRoom( + roomName: textEditingController.text, + unitId: unitId)); + textEditingController.clear(); + }, + child: const Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + BodyMedium( + text: 'Add Space', + fontColor: ColorsManager.primaryColor, + ), + ], + ), + ), + Flexible( + child: TextField( + textAlign: TextAlign.end, + controller: textEditingController, + decoration: InputDecoration( + hintText: 'Set', + hintStyle: + context.bodyMedium.copyWith(color: Colors.grey), + border: InputBorder.none, + ), + ), + ), + ], + ), + ), + ], + ), + ) + ], + ), + ), + ); + })); + } +} diff --git a/lib/features/shared_widgets/default_scaffold.dart b/lib/features/shared_widgets/default_scaffold.dart index 78e3fbc..ec8026b 100644 --- a/lib/features/shared_widgets/default_scaffold.dart +++ b/lib/features/shared_widgets/default_scaffold.dart @@ -8,12 +8,7 @@ import 'package:syncrow_app/utils/resource_manager/font_manager.dart'; class DefaultScaffold extends StatelessWidget { const DefaultScaffold( - {super.key, - required this.child, - this.title, - this.actions, - this.appBar, - this.bottomNavBar}); + {super.key, required this.child, this.title, this.actions, this.appBar, this.bottomNavBar}); final Widget child; final String? title; @@ -46,8 +41,7 @@ class DefaultScaffold extends StatelessWidget { body: Container( width: MediaQuery.sizeOf(context).width, height: MediaQuery.sizeOf(context).height, - padding: - const EdgeInsets.symmetric(horizontal: Constants.defaultPadding), + padding: const EdgeInsets.symmetric(horizontal: Constants.defaultPadding), decoration: const BoxDecoration( image: DecorationImage( image: AssetImage( diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart index 997516e..b7e5fde 100644 --- a/lib/generated/assets.dart +++ b/lib/generated/assets.dart @@ -647,6 +647,9 @@ class Assets { static const String assetsDeleteIcon = "assets/icons/delete_room_ic.svg"; + static const String blueCheckboxIcon = "assets/icons/blue_checkbox_ic.svg"; + static const String emptyCheckboxIcon = "assets/icons/empty_checkbox_ic.svg"; + /// Assets for assetsImagesAutomation /// assets/images/automation.jpg static const String assetsImagesAutomation = "assets/images/automation.jpg"; diff --git a/lib/navigation/router.dart b/lib/navigation/router.dart index 46370b7..73d2347 100644 --- a/lib/navigation/router.dart +++ b/lib/navigation/router.dart @@ -6,7 +6,7 @@ import 'package:syncrow_app/features/auth/view/sign_up_view.dart'; import 'package:syncrow_app/features/dashboard/view/dashboard_view.dart'; import 'package:syncrow_app/features/layout/view/layout_view.dart'; import 'package:syncrow_app/features/menu/view/menu_view.dart'; -import 'package:syncrow_app/features/menu/view/widgets/home%20management/create_home_view.dart'; +import 'package:syncrow_app/features/menu/view/widgets/create_home/create_home_view.dart'; import 'package:syncrow_app/features/menu/view/widgets/profile/profile_view.dart'; import 'package:syncrow_app/features/scene/view/scene_view.dart'; import 'package:syncrow_app/features/splash/view/splash_view.dart'; diff --git a/lib/services/api/api_links_endpoints.dart b/lib/services/api/api_links_endpoints.dart index 6281ac9..ebf8c89 100644 --- a/lib/services/api/api_links_endpoints.dart +++ b/lib/services/api/api_links_endpoints.dart @@ -57,6 +57,9 @@ abstract class ApiEndpoints { static const String unitChild = '$baseUrl/unit/child/'; static const String unitParent = '$baseUrl/unit/parent/{unitUuid}'; static const String unitUser = '$baseUrl/unit/user/'; + static const String invitationCode = '$baseUrl/unit/{unitUuid}/invitation-code'; + static const String verifyInvitationCode = '$baseUrl/unit/user/verify-code'; + //PUT static const String renameUnit = '$baseUrl/unit/rename/{unitUuid}'; @@ -87,6 +90,8 @@ abstract class ApiEndpoints { static const String addDeviceToRoom = '$baseUrl/device/room'; static const String addDeviceToGroup = '$baseUrl/device/group'; static const String controlDevice = '$baseUrl/device/{deviceUuid}/control'; + static const String getDevicesByUserId = '$baseUrl/device/user/{userId}'; + //GET static const String deviceByRoom = '$baseUrl/device/room'; static const String deviceByUuid = '$baseUrl/device/{deviceUuid}'; @@ -100,4 +105,6 @@ abstract class ApiEndpoints { static const String devicePermissionList = '$baseUrl/device-permission/list'; //PUT static const String editDevicePermission = '$baseUrl/device-permission/edit/{userId}'; + + static const String assignDeviceToRoom = '$baseUrl/device/room'; } diff --git a/lib/services/api/home_management_api.dart b/lib/services/api/home_management_api.dart new file mode 100644 index 0000000..78923ec --- /dev/null +++ b/lib/services/api/home_management_api.dart @@ -0,0 +1,40 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:syncrow_app/features/auth/model/user_model.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/services/api/api_links_endpoints.dart'; +import 'package:syncrow_app/services/api/http_service.dart'; + +class HomeManagementAPI { + static final HTTPService _httpService = HTTPService(); + + static Future> fetchDevicesByUserId() async { + var storage = const FlutterSecureStorage(); + var userId = await storage.read(key: UserModel.userUuidKey) ?? ''; + + List list = []; + await _httpService.get( + path: ApiEndpoints.getDevicesByUserId.replaceAll("{userId}", userId), + showServerMessage: false, + expectedResponseModel: (json) { + json.forEach((value) { + list.add(DeviceModel.fromJson(value)); + }); + }); + return list; + } + + static Future> assignDeviceToRoom(Map body) async { + try { + final response = await _httpService.put( + path: ApiEndpoints.assignDeviceToRoom, + body: body, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } +} diff --git a/lib/services/api/http_service.dart b/lib/services/api/http_service.dart index e9fb883..36e8e71 100644 --- a/lib/services/api/http_service.dart +++ b/lib/services/api/http_service.dart @@ -79,6 +79,23 @@ class HTTPService { } } + Future put( + {required String path, + Map? queryParameters, + dynamic body, + required T Function(dynamic) expectedResponseModel}) async { + try { + final response = await client.put( + path, + data: body, + queryParameters: queryParameters, + ); + return expectedResponseModel(response.data); + } catch (error) { + rethrow; + } + } + Future download( {required String path, required String savePath, diff --git a/lib/services/api/spaces_api.dart b/lib/services/api/spaces_api.dart index 7d6f476..bd02075 100644 --- a/lib/services/api/spaces_api.dart +++ b/lib/services/api/spaces_api.dart @@ -9,8 +9,7 @@ class SpacesAPI { static final HTTPService _httpService = HTTPService(); static Future> getUnitsByUserId() async { - var uuid = - await const FlutterSecureStorage().read(key: UserModel.userUuidKey); + var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey); final response = await _httpService.get( path: "${ApiEndpoints.unitUser}$uuid", showServerMessage: false, @@ -34,4 +33,27 @@ class SpacesAPI { ); return response; } + + static Future generateInvitationCode( + String unitId, + ) async { + final response = await _httpService.get( + path: ApiEndpoints.invitationCode.replaceAll('{unitUuid}', unitId), + showServerMessage: false, + expectedResponseModel: (json) => json['invitationCode'], + ); + return response; + } + + static Future joinUnit( + Map body, + ) async { + final response = await _httpService.post( + path: ApiEndpoints.verifyInvitationCode, + showServerMessage: false, + body: body, + expectedResponseModel: (json) => json['success'], + ); + return response; + } } diff --git a/lib/utils/resource_manager/constants.dart b/lib/utils/resource_manager/constants.dart index 7eebe46..9e0563f 100644 --- a/lib/utils/resource_manager/constants.dart +++ b/lib/utils/resource_manager/constants.dart @@ -2,7 +2,7 @@ import 'dart:ui'; import 'package:syncrow_app/features/devices/model/function_model.dart'; -import 'package:syncrow_app/features/menu/view/widgets/home%20management/create_home_view.dart'; +import 'package:syncrow_app/features/menu/view/widgets/create_home/create_home_view.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/privacy/privacy_view.dart'; diff --git a/pubspec.lock b/pubspec.lock index 718c9d8..eefb7dd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -89,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" + url: "https://pub.dev" + source: hosted + version: "0.3.4+1" crypto: dependency: transitive description: @@ -197,10 +205,10 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: c8e1d59385eee98de63c92f961d2a7062c5d9a65e7f45bdc7f1b0b205aab2492 + sha256: "22fcb352744908224fc7be3caae254836099786acfe5df6e9fe901e9c2575a41" url: "https://pub.dev" source: hosted - version: "2.11.5" + version: "2.17.1" firebase_crashlytics: dependency: "direct main" description: @@ -377,10 +385,10 @@ packages: dependency: transitive description: name: http - sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" http_parser: dependency: transitive description: @@ -461,6 +469,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.11.0" + mime: + dependency: transitive + description: + name: mime + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + url: "https://pub.dev" + source: hosted + version: "1.0.5" nested: dependency: transitive description: @@ -645,6 +661,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.27.7" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: ef3489a969683c4f3d0239010cc8b7a2a46543a8d139e111c06c558875083544 + url: "https://pub.dev" + source: hosted + version: "9.0.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "0f9e4418835d1b2c3ae78fdb918251959106cefdbc4dd43526e182f80e82f6d4" + url: "https://pub.dev" + source: hosted + version: "4.0.0" shared_preferences: dependency: "direct main" description: @@ -689,10 +721,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.3.0" shared_preferences_windows: dependency: transitive description: @@ -862,10 +894,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b + sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.3.1" url_launcher_windows: dependency: transitive description: @@ -926,10 +958,10 @@ packages: dependency: transitive description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.5.1" win32: dependency: transitive description: @@ -955,5 +987,5 @@ packages: source: hosted version: "6.5.0" sdks: - dart: ">=3.2.0 <4.0.0" - flutter: ">=3.16.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" diff --git a/pubspec.yaml b/pubspec.yaml index dd0430a..c60bd0c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ description: This is the mobile application project, developed with Flutter for # 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 -version: 1.0.1+2 +version: 1.0.2+4 environment: sdk: ">=3.0.6 <4.0.0" @@ -41,6 +41,7 @@ dependencies: onesignal_flutter: ^5.2.0 permission_handler: ^11.3.1 pin_code_fields: ^8.0.1 + share_plus: ^9.0.0 dev_dependencies: flutter_test: