Implemented home management and user invitation flows

This commit is contained in:
Abdullah Alassaf
2024-06-05 03:42:00 +03:00
parent 6a9c2967d2
commit 631ba53d7a
29 changed files with 968 additions and 288 deletions
+43 -6
View File
@@ -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<HomeState> {
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<HomeState> {
changeSelectedSpace(SpaceModel space) {
selectedSpace = space;
emitSafe(SpaceSelected(space));
fetchRoomsByUnitId(space);
}
roomSliderPageChanged(int index) {
@@ -193,6 +197,39 @@ class HomeCubit extends Cubit<HomeState> {
}
//////////////////////////////////////// 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<bool> joinAUnit(String code) async {
try {
var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey) ?? '';
Map<String, String> 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<HomeState> {
if (spaces != null && spaces!.isNotEmpty) {
selectedSpace = spaces!.first;
await fetchRoomsByUnitId(selectedSpace!);
emitSafe(GetSpacesSuccess(spaces!));
// fetchRoomsByUnitId(selectedSpace!);
} else {
emitSafe(GetSpacesError("No spaces found"));
}
@@ -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],
));
},
);
+9 -2
View File
@@ -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<AuthState> {
@@ -26,6 +26,7 @@ class AuthCubit extends Cubit<AuthState> {
final loginFormKey = GlobalKey<FormState>();
final signUpFormKey = GlobalKey<FormState>();
bool isPasswordVisible = false;
bool showValidationMessage = false;
static GlobalKey<FormState> formKey = GlobalKey<FormState>();
@@ -58,7 +59,7 @@ class AuthCubit extends Cubit<AuthState> {
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<AuthState> {
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(),
+7 -5
View File
@@ -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();
@@ -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();
@@ -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<CreateUnitEvent, CreateUnitState> {
List<String> rooms = [];
@@ -45,11 +47,16 @@ class CreateUnitBloc extends Bloc<CreateUnitEvent, CreateUnitState> {
void _save(SaveRooms event, Emitter<CreateUnitState> 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<CreateUnitEvent, CreateUnitState> {
}
// }
}
await HomeCubit.getInstance().fetchUnitsByUserId();
emit(RoomsSavedSuccessfully());
} catch (_) {
emit(const ErrorState(message: 'Something went wrong'));
@@ -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<ManageUnitEvent, ManageUnitState> {
List<DeviceModel> allDevices = [];
String unitName = '';
ManageUnitBloc() : super(InitialState()) {
on<FetchRoomsEvent>(_fetchRoomsAndDevices);
on<FetchDevicesByRoomIdEvent>(_fetchDevicesByRoomId);
on<AssignRoomEvent>(_assignDevice);
on<AddNewRoom>(_addNewRoom);
}
void _fetchRoomsAndDevices(FetchRoomsEvent event, Emitter<ManageUnitState> 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<ManageUnitState> emit) async {
try {
Map<String, bool> roomDevicesId = {};
emit(LoadingState());
final devicesList = await DevicesAPI.getDevicesByRoomId(event.roomId);
allDevices = await HomeManagementAPI.fetchDevicesByUserId();
List<String> 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<ManageUnitState> emit) async {
try {
Map<String, bool> roomDevicesId = {};
emit(LoadingState());
Map<String, String> body = {"deviceUuid": event.deviceId, "roomUuid": event.roomId};
await HomeManagementAPI.assignDeviceToRoom(body);
final devicesList = await DevicesAPI.getDevicesByRoomId(event.roomId);
List<String> 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<ManageUnitState> emit) async {
Map<String, String> 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;
}
}
}
@@ -0,0 +1,50 @@
import 'package:equatable/equatable.dart';
abstract class ManageUnitEvent extends Equatable {
const ManageUnitEvent();
@override
List<Object> get props => [];
}
class InitialEvent extends ManageUnitEvent {}
class LoadingEvent extends ManageUnitEvent {}
class FetchRoomsEvent extends ManageUnitEvent {
final String unitId;
const FetchRoomsEvent({required this.unitId});
@override
List<Object> get props => [unitId];
}
class FetchDevicesByRoomIdEvent extends ManageUnitEvent {
final String roomId;
const FetchDevicesByRoomIdEvent({required this.roomId});
@override
List<Object> get props => [roomId];
}
class AddNewRoom extends ManageUnitEvent {
final String roomName;
final String unitId;
const AddNewRoom({required this.roomName, required this.unitId});
@override
List<Object> get props => [roomName, unitId];
}
class AssignRoomEvent extends ManageUnitEvent {
final String roomId;
final String deviceId;
const AssignRoomEvent({required this.roomId, required this.deviceId});
@override
List<Object> get props => [roomId];
}
@@ -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<Object> get props => [];
}
class InitialState extends ManageUnitState {}
class LoadingState extends ManageUnitState {}
class FetchRoomsState extends ManageUnitState {
final List<RoomModel> roomsList;
final List<DeviceModel> devicesList;
const FetchRoomsState({required this.devicesList, required this.roomsList});
@override
List<Object> get props => [devicesList];
}
class FetchDeviceByRoomIdState extends ManageUnitState {
final List<DeviceModel> roomDevices;
final List<DeviceModel> allDevices;
final Map<String, bool> roomDevicesId;
final String roomId;
const FetchDeviceByRoomIdState(
{required this.roomDevices,
required this.allDevices,
required this.roomDevicesId,
required this.roomId});
@override
List<Object> get props => [roomDevices, allDevices, roomDevicesId, roomId];
}
class AssignRoomState extends ManageUnitState {}
class ErrorState extends ManageUnitState {
final String message;
const ErrorState({required this.message});
@override
List<Object> get props => [message];
}
@@ -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<CreateUnitBloc, CreateUnitState>(listener: (context, state) {
if (state is RoomsSavedSuccessfully) {
CustomSnackBar.displaySnackBar('Saved successfully');
NavigationService.navigatorKey.currentState!.pushNamedAndRemoveUntil(
Routes.homeRoute,
(Route route) => false,
);
}
child: BlocConsumer<CreateUnitBloc, CreateUnitState>(
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<CreateUnitBloc>(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<CreateUnitBloc>(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<CreateUnitBloc>(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<CreateUnitBloc>(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<CreateUnitBloc>(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,
),
),
),
],
),
),
],
),
),
],
),
),
),
],
),
),
),
);
}),
);
},
),
);
}
}
@@ -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,
),
@@ -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<ManageUnitBloc, ManageUnitState>(
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<ManageUnitBloc>(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());
}));
}
}
@@ -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: [
@@ -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<ManageUnitBloc, ManageUnitState>(
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<ManageUnitBloc>(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<ManageUnitBloc>(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,
),
),
),
],
),
),
],
),
)
],
),
),
);
}));
}
}
@@ -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(
+3
View File
@@ -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";
+1 -1
View File
@@ -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';
@@ -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';
}
+40
View File
@@ -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<List<DeviceModel>> fetchDevicesByUserId() async {
var storage = const FlutterSecureStorage();
var userId = await storage.read(key: UserModel.userUuidKey) ?? '';
List<DeviceModel> 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<Map<String, dynamic>> assignDeviceToRoom(Map<String, String> body) async {
try {
final response = await _httpService.put(
path: ApiEndpoints.assignDeviceToRoom,
body: body,
expectedResponseModel: (json) {
return json;
},
);
return response;
} catch (e) {
rethrow;
}
}
}
+17
View File
@@ -79,6 +79,23 @@ class HTTPService {
}
}
Future<T> put<T>(
{required String path,
Map<String, dynamic>? 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<T> download<T>(
{required String path,
required String savePath,
+24 -2
View File
@@ -9,8 +9,7 @@ class SpacesAPI {
static final HTTPService _httpService = HTTPService();
static Future<List<SpaceModel>> 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<String> 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<bool> joinUnit(
Map<String, String> body,
) async {
final response = await _httpService.post(
path: ApiEndpoints.verifyInvitationCode,
showServerMessage: false,
body: body,
expectedResponseModel: (json) => json['success'],
);
return response;
}
}
+1 -1
View File
@@ -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';