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

View File

@ -27,7 +27,7 @@ if (flutterVersionName == null) {
} }
android { android {
namespace "com.example.syncrow.app" namespace "com.example.syncrow_application"
compileSdkVersion 34 compileSdkVersion 34
ndkVersion flutter.ndkVersion ndkVersion flutter.ndkVersion
@ -46,7 +46,7 @@ android {
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // 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. // 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. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion 21 minSdkVersion 21

View File

@ -9,7 +9,7 @@
"client_info": { "client_info": {
"mobilesdk_app_id": "1:427332280600:android:550f67441246cb1a0c7e6d", "mobilesdk_app_id": "1:427332280600:android:550f67441246cb1a0c7e6d",
"android_client_info": { "android_client_info": {
"package_name": "com.example.syncrow.app" "package_name": "com.example.syncrow_application"
} }
}, },
"oauth_client": [], "oauth_client": [],

View File

@ -1,4 +1,4 @@
package com.example.syncrow.app package com.example.syncrow_application
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity

View File

@ -0,0 +1,3 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 20.3799C5.52381 20.3799 1 15.8561 1 10.3799C1 4.90369 5.51613 0.379883 10.9923 0.379883C16.4685 0.379883 21 4.90369 21 10.3799C21 15.8561 16.4762 20.3799 11 20.3799ZM9.77112 15.4029C10.0553 15.4029 10.3011 15.2724 10.4854 14.9882L15.6467 6.90062C15.7619 6.73165 15.8618 6.52428 15.8618 6.33994C15.8618 5.93288 15.5008 5.67942 15.1244 5.67942C14.9017 5.67942 14.679 5.80999 14.51 6.07113L9.73272 13.6671L7.19048 10.4797C6.99078 10.2263 6.78341 10.1341 6.53763 10.1341C6.16129 10.1341 5.84639 10.4336 5.84639 10.8407C5.84639 11.0327 5.9232 11.2401 6.05376 11.4014L9.01843 14.9959C9.25653 15.2877 9.48694 15.4029 9.77112 15.4029Z" fill="#023DFE" fill-opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 820 B

View File

@ -0,0 +1,3 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="20.62" height="20.62" rx="10.31" stroke="#AEAEB2"/>
</svg>

After

Width:  |  Height:  |  Size: 184 B

View File

@ -6,6 +6,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:onesignal_flutter/onesignal_flutter.dart'; import 'package:onesignal_flutter/onesignal_flutter.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:share_plus/share_plus.dart';
import 'package:syncrow_app/features/app_layout/model/space_model.dart'; import 'package:syncrow_app/features/app_layout/model/space_model.dart';
import 'package:syncrow_app/features/app_layout/view/widgets/app_bar_home_dropdown.dart'; import 'package:syncrow_app/features/app_layout/view/widgets/app_bar_home_dropdown.dart';
import 'package:syncrow_app/features/auth/model/user_model.dart'; import 'package:syncrow_app/features/auth/model/user_model.dart';
@ -22,6 +23,7 @@ import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/navigation/navigation_service.dart'; import 'package:syncrow_app/navigation/navigation_service.dart';
import 'package:syncrow_app/services/api/spaces_api.dart'; import 'package:syncrow_app/services/api/spaces_api.dart';
import 'package:syncrow_app/utils/helpers/custom_page_route.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/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/constants.dart'; import 'package:syncrow_app/utils/resource_manager/constants.dart';
@ -31,11 +33,12 @@ class HomeCubit extends Cubit<HomeState> {
HomeCubit._() : super(HomeInitial()) { HomeCubit._() : super(HomeInitial()) {
checkIfNotificationPermissionGranted(); checkIfNotificationPermissionGranted();
if (selectedSpace == null) { if (selectedSpace == null) {
fetchUnitsByUserId().then((value) { fetchUnitsByUserId();
if (selectedSpace != null) { // .then((value) {
fetchRoomsByUnitId(selectedSpace!); // if (selectedSpace != null) {
} // fetchRoomsByUnitId(selectedSpace!);
}); // }
// });
} }
} }
@ -143,6 +146,7 @@ class HomeCubit extends Cubit<HomeState> {
changeSelectedSpace(SpaceModel space) { changeSelectedSpace(SpaceModel space) {
selectedSpace = space; selectedSpace = space;
emitSafe(SpaceSelected(space)); emitSafe(SpaceSelected(space));
fetchRoomsByUnitId(space);
} }
roomSliderPageChanged(int index) { roomSliderPageChanged(int index) {
@ -193,6 +197,39 @@ class HomeCubit extends Cubit<HomeState> {
} }
//////////////////////////////////////// API //////////////////////////////////////// //////////////////////////////////////// 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 { fetchUnitsByUserId() async {
emitSafe(GetSpacesLoading()); emitSafe(GetSpacesLoading());
try { try {
@ -204,8 +241,8 @@ class HomeCubit extends Cubit<HomeState> {
if (spaces != null && spaces!.isNotEmpty) { if (spaces != null && spaces!.isNotEmpty) {
selectedSpace = spaces!.first; selectedSpace = spaces!.first;
await fetchRoomsByUnitId(selectedSpace!);
emitSafe(GetSpacesSuccess(spaces!)); emitSafe(GetSpacesSuccess(spaces!));
// fetchRoomsByUnitId(selectedSpace!);
} else { } else {
emitSafe(GetSpacesError("No spaces found")); emitSafe(GetSpacesError("No spaces found"));
} }

View File

@ -20,11 +20,9 @@ class DefaultAppBar extends StatelessWidget implements PreferredSizeWidget {
leadingWidth: 150, leadingWidth: 150,
toolbarHeight: Constants.appBarHeight, toolbarHeight: Constants.appBarHeight,
leading: HomeCubit.getInstance().spaces!.isNotEmpty leading: HomeCubit.getInstance().spaces!.isNotEmpty
? HomeCubit.appBarLeading[ ? HomeCubit.appBarLeading[HomeCubit.bottomNavItems[HomeCubit.pageIndex].label]
HomeCubit.bottomNavItems[HomeCubit.pageIndex].label]
: null, : null,
actions: HomeCubit.appBarActions[ // actions: HomeCubit.appBarActions[HomeCubit.bottomNavItems[HomeCubit.pageIndex].label],
HomeCubit.bottomNavItems[HomeCubit.pageIndex].label],
)); ));
}, },
); );

View File

@ -1,4 +1,3 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.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/navigation_service.dart';
import 'package:syncrow_app/navigation/routing_constants.dart'; import 'package:syncrow_app/navigation/routing_constants.dart';
import 'package:syncrow_app/services/api/authentication_api.dart'; import 'package:syncrow_app/services/api/authentication_api.dart';
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
part 'auth_state.dart'; part 'auth_state.dart';
class AuthCubit extends Cubit<AuthState> { class AuthCubit extends Cubit<AuthState> {
@ -26,6 +26,7 @@ class AuthCubit extends Cubit<AuthState> {
final loginFormKey = GlobalKey<FormState>(); final loginFormKey = GlobalKey<FormState>();
final signUpFormKey = GlobalKey<FormState>(); final signUpFormKey = GlobalKey<FormState>();
bool isPasswordVisible = false; bool isPasswordVisible = false;
bool showValidationMessage = false;
static GlobalKey<FormState> formKey = GlobalKey<FormState>(); static GlobalKey<FormState> formKey = GlobalKey<FormState>();
@ -58,7 +59,7 @@ class AuthCubit extends Cubit<AuthState> {
if (value.isNotEmpty) { if (value.isNotEmpty) {
if (!RegExp(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$') if (!RegExp(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$')
.hasMatch(value)) { .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 { login() async {
emit(AuthLoginLoading()); emit(AuthLoginLoading());
try { 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( token = await AuthenticationAPI.loginWithEmail(
model: LoginWithEmailModel( model: LoginWithEmailModel(
email: emailController.text.toLowerCase(), email: emailController.text.toLowerCase(),

View File

@ -86,7 +86,7 @@ class SignUpView extends StatelessWidget {
), ),
Form( Form(
key: formKey, key: formKey,
autovalidateMode: AutovalidateMode.disabled, // autovalidateMode: AutovalidateMode.disabled,
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -96,14 +96,15 @@ class SignUpView extends StatelessWidget {
fontColor: Colors.white, fontColor: Colors.white,
), ),
TextFormField( TextFormField(
autovalidateMode: AutovalidateMode.disabled, // autovalidateMode: AutovalidateMode.disabled,
textInputAction: TextInputAction.done, textInputAction: TextInputAction.done,
keyboardType: TextInputType.name, keyboardType: TextInputType.name,
scrollPadding: EdgeInsets.zero, scrollPadding: EdgeInsets.zero,
autocorrect: false, autocorrect: false,
autofillHints: const [AutofillHints.name], autofillHints: const [AutofillHints.name],
// controller: AuthCubit.get(context).fullNameController, // controller: AuthCubit.get(context).fullNameController,
validator: AuthCubit.get(context).fullNameValidator, validator: (value) =>
AuthCubit.get(context).fullNameValidator(value),
onTapOutside: (event) { onTapOutside: (event) {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
}, },
@ -119,7 +120,7 @@ class SignUpView extends StatelessWidget {
fontColor: Colors.white, fontColor: Colors.white,
), ),
TextFormField( TextFormField(
autovalidateMode: AutovalidateMode.disabled, // autovalidateMode: AutovalidateMode.disabled,
textInputAction: TextInputAction.done, textInputAction: TextInputAction.done,
keyboardType: TextInputType.text, keyboardType: TextInputType.text,
scrollPadding: EdgeInsets.zero, scrollPadding: EdgeInsets.zero,
@ -141,7 +142,7 @@ class SignUpView extends StatelessWidget {
fontColor: Colors.white, fontColor: Colors.white,
), ),
TextFormField( TextFormField(
autovalidateMode: AutovalidateMode.disabled, // autovalidateMode: AutovalidateMode.disabled,
textInputAction: TextInputAction.done, textInputAction: TextInputAction.done,
keyboardType: TextInputType.text, keyboardType: TextInputType.text,
scrollPadding: EdgeInsets.zero, scrollPadding: EdgeInsets.zero,
@ -199,6 +200,7 @@ class SignUpView extends StatelessWidget {
'Sign up', 'Sign up',
), ),
onPressed: () { onPressed: () {
AuthCubit.get(context).showValidationMessage = true;
if (formKey.currentState!.validate()) { if (formKey.currentState!.validate()) {
if ((state is! AuthLoading)) { if ((state is! AuthLoading)) {
AuthCubit.get(context).signUp(); AuthCubit.get(context).signUp();

View File

@ -65,7 +65,7 @@ class LoginForm extends StatelessWidget {
if (state is AuthTokenError && !pressed) { if (state is AuthTokenError && !pressed) {
return null; return null;
} }
return AuthCubit.get(context).passwordValidator(value); // return AuthCubit.get(context).passwordValidator(value);
}, },
onTapOutside: (event) { onTapOutside: (event) {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();

View File

@ -1,9 +1,11 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.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/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_event.dart';
import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_state.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/services/api/home_creation_api.dart';
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
class CreateUnitBloc extends Bloc<CreateUnitEvent, CreateUnitState> { class CreateUnitBloc extends Bloc<CreateUnitEvent, CreateUnitState> {
List<String> rooms = []; List<String> rooms = [];
@ -45,11 +47,16 @@ class CreateUnitBloc extends Bloc<CreateUnitEvent, CreateUnitState> {
void _save(SaveRooms event, Emitter<CreateUnitState> emit) async { void _save(SaveRooms event, Emitter<CreateUnitState> emit) async {
try { try {
emit(LoadingState()); if (event.unitName.isEmpty) {
if (rooms.isEmpty) { CustomSnackBar.displaySnackBar('Please enter the unit name');
const ErrorState(message: 'Please add at least one room!');
return; return;
} }
if (rooms.isEmpty) {
CustomSnackBar.displaySnackBar('Please add at least one room!');
return;
}
emit(LoadingState());
var storage = const FlutterSecureStorage(); var storage = const FlutterSecureStorage();
var userId = await storage.read(key: UserModel.userUuidKey) ?? ''; var userId = await storage.read(key: UserModel.userUuidKey) ?? '';
@ -83,6 +90,7 @@ class CreateUnitBloc extends Bloc<CreateUnitEvent, CreateUnitState> {
} }
// } // }
} }
await HomeCubit.getInstance().fetchUnitsByUserId();
emit(RoomsSavedSuccessfully()); emit(RoomsSavedSuccessfully());
} catch (_) { } catch (_) {
emit(const ErrorState(message: 'Something went wrong')); emit(const ErrorState(message: 'Something went wrong'));

View File

@ -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;
}
}
}

View File

@ -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];
}

View File

@ -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];
}

View File

@ -1,7 +1,7 @@
// ignore_for_file: unnecessary_const
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_bloc.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_event.dart';
import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_state.dart'; import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_state.dart';
@ -23,37 +23,33 @@ class CreateUnitView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String unitName = ''; String unitName = '';
String location = '';
TextEditingController textEditingController = TextEditingController(); TextEditingController textEditingController = TextEditingController();
return BlocProvider( return BlocProvider(
create: (context) => CreateUnitBloc(), create: (context) => CreateUnitBloc(),
child: BlocConsumer<CreateUnitBloc, CreateUnitState>(listener: (context, state) { child: BlocConsumer<CreateUnitBloc, CreateUnitState>(
listener: (context, state) {
if (state is RoomsSavedSuccessfully) { if (state is RoomsSavedSuccessfully) {
CustomSnackBar.displaySnackBar('Saved successfully'); CustomSnackBar.displaySnackBar('Unit created successfully');
NavigationService.navigatorKey.currentState!.pushNamedAndRemoveUntil( Navigator.of(context).pop();
Routes.homeRoute,
(Route route) => false,
);
} }
if (state is ErrorState) { if (state is ErrorState) {
CustomSnackBar.displaySnackBar(state.message); CustomSnackBar.displaySnackBar(state.message);
} }
}, builder: (context, state) { },
builder: (context, state) {
textEditingController.text = ''; textEditingController.text = '';
return DefaultScaffold( return DefaultScaffold(
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
if (unitName.isNotEmpty) {
BlocProvider.of<CreateUnitBloc>(context).add(SaveRooms( BlocProvider.of<CreateUnitBloc>(context).add(SaveRooms(
communityName: 'Community Test', communityName: 'Community Test',
buildingName: 'Building Test', buildingName: 'Building Test',
floorName: 'Floor Test', floorName: 'Floor Test',
unitName: unitName)); unitName: unitName,
} else { ));
CustomSnackBar.displaySnackBar('Please enter the unit name');
}
}, },
child: const BodyLarge( child: const BodyLarge(
text: 'Save', text: 'Save',
@ -66,7 +62,11 @@ class CreateUnitView extends StatelessWidget {
padding: const EdgeInsets.symmetric(vertical: 40), padding: const EdgeInsets.symmetric(vertical: 40),
width: MediaQuery.sizeOf(context).width, width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height, height: MediaQuery.sizeOf(context).height,
child: SingleChildScrollView( child: state is LoadingState
? const Center(
child: CircularProgressIndicator(),
)
: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
// Home Info // Home Info
@ -91,7 +91,8 @@ class CreateUnitView extends StatelessWidget {
}, },
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Enter Name', hintText: 'Enter Name',
hintStyle: context.bodyMedium.copyWith(color: Colors.grey), hintStyle:
context.bodyMedium.copyWith(color: Colors.grey),
border: InputBorder.none, border: InputBorder.none,
), ),
), ),
@ -108,12 +109,11 @@ class CreateUnitView extends StatelessWidget {
Flexible( Flexible(
child: TextField( child: TextField(
textAlign: TextAlign.end, textAlign: TextAlign.end,
onChanged: (value) { onChanged: (value) {},
location = value;
},
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Set', hintText: 'Set',
hintStyle: context.bodyMedium.copyWith(color: Colors.grey), hintStyle:
context.bodyMedium.copyWith(color: Colors.grey),
border: InputBorder.none, border: InputBorder.none,
), ),
), ),
@ -140,10 +140,17 @@ class CreateUnitView extends StatelessWidget {
const SizedBox( const SizedBox(
height: 4, height: 4,
), ),
DefaultContainer( Container(
padding: const EdgeInsets.symmetric( // margin: EdgeInsets.zero,
horizontal: 25, // margin: const EdgeInsets.symmetric(
vertical: 5, // horizontal: 25,
// vertical: 5,
// ),
decoration: const ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),
),
), ),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -153,11 +160,9 @@ class CreateUnitView extends StatelessWidget {
if (state.roomList.isNotEmpty) if (state.roomList.isNotEmpty)
Column( Column(
children: [ children: [
SizedBox( ListView.separated(
height: (state.roomList.length) * 55, physics: const NeverScrollableScrollPhysics(),
child: ListView.separated( shrinkWrap: true,
physics:
const NeverScrollableScrollPhysics(), // Disable scrolling
separatorBuilder: (context, index) => const Divider( separatorBuilder: (context, index) => const Divider(
color: ColorsManager.greyColor, color: ColorsManager.greyColor,
), ),
@ -173,7 +178,8 @@ class CreateUnitView extends StatelessWidget {
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: Radius.circular(index == 0 ? 20 : 0), topLeft: Radius.circular(index == 0 ? 20 : 0),
topRight: Radius.circular(index == 0 ? 20 : 0), topRight:
Radius.circular(index == 0 ? 20 : 0),
), ),
), ),
), ),
@ -196,7 +202,8 @@ class CreateUnitView extends StatelessWidget {
}, },
child: Container( child: Container(
height: 50, height: 50,
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(
vertical: 16, horizontal: 25),
child: Text( child: Text(
state.roomList[index], state.roomList[index],
style: const TextStyle( style: const TextStyle(
@ -209,50 +216,54 @@ class CreateUnitView extends StatelessWidget {
); );
}, },
), ),
),
const Divider( const Divider(
color: ColorsManager.greyColor, color: ColorsManager.greyColor,
), ),
], ],
), ),
Row( Container(
padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 5),
child: Row(
children: [ children: [
TextButton( TextButton(
onPressed: () { onPressed: () {
if (textEditingController.text.isEmpty) { if (textEditingController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Please add the room name')), const SnackBar(
content: Text('Please add the room name')),
); );
return; return;
} }
BlocProvider.of<CreateUnitBloc>(context) BlocProvider.of<CreateUnitBloc>(context).add(
.add(CreateRoomEvent(roomName: textEditingController.text)); CreateRoomEvent(
roomName: textEditingController.text));
textEditingController.clear();
}, },
child: const Row( child: const Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
BodyMedium( BodyMedium(
text: 'Add Space', text: 'Add custom Space',
fontColor: ColorsManager.primaryColor, fontColor: ColorsManager.primaryColor,
), ),
], ],
)), ),
),
Flexible( Flexible(
child: TextField( child: TextField(
textAlign: TextAlign.end, textAlign: TextAlign.end,
controller: textEditingController, controller: textEditingController,
onChanged: (value) {
textEditingController.text = value;
},
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Set', hintText: 'Set',
hintStyle: context.bodyMedium.copyWith(color: Colors.grey), hintStyle:
context.bodyMedium.copyWith(color: Colors.grey),
border: InputBorder.none, border: InputBorder.none,
), ),
), ),
), ),
], ],
) ),
),
], ],
), ),
), ),
@ -261,7 +272,8 @@ class CreateUnitView extends StatelessWidget {
), ),
), ),
); );
}), },
),
); );
} }
} }

View File

@ -1,16 +1,19 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart'; import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/context_extension.dart'; import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
class JoinHomeView extends StatelessWidget { class JoinHomeView extends StatelessWidget {
const JoinHomeView({super.key}); const JoinHomeView({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
TextEditingController textEditingController = TextEditingController();
return DefaultScaffold( return DefaultScaffold(
title: "Join a Home", title: "Join a Home",
child: Column( child: Column(
@ -38,16 +41,27 @@ class JoinHomeView extends StatelessWidget {
children: [ children: [
Flexible( Flexible(
child: TextField( child: TextField(
controller: textEditingController,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Invitatoin code', hintText: 'Invitatoin code',
hintStyle: hintStyle: context.bodyMedium.copyWith(color: Colors.grey),
context.bodyMedium.copyWith(color: Colors.grey),
border: InputBorder.none, border: InputBorder.none,
), ),
), ),
), ),
IconButton( 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( icon: const Icon(
Icons.arrow_right_alt, Icons.arrow_right_alt,
), ),

View File

@ -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());
}));
}
}

View File

@ -1,11 +1,15 @@
import 'package:flutter/material.dart'; 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/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_container.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.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/context_extension.dart';
import 'package:syncrow_app/utils/helpers/custom_page_route.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
class HomeSettingsView extends StatelessWidget { class HomeSettingsView extends StatelessWidget {
@ -38,8 +42,7 @@ class HomeSettingsView extends StatelessWidget {
textAlign: TextAlign.end, textAlign: TextAlign.end,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Enter Name', hintText: 'Enter Name',
hintStyle: hintStyle: context.bodyMedium.copyWith(color: Colors.grey),
context.bodyMedium.copyWith(color: Colors.grey),
border: InputBorder.none, border: InputBorder.none,
), ),
), ),
@ -52,7 +55,16 @@ class HomeSettingsView extends StatelessWidget {
height: 1, height: 1,
color: ColorsManager.greyColor, color: ColorsManager.greyColor,
), ),
const Row( 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, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
BodyMedium(text: 'Rooms'), BodyMedium(text: 'Rooms'),
@ -63,6 +75,8 @@ class HomeSettingsView extends StatelessWidget {
) )
], ],
), ),
),
),
//Divider //Divider
Container( Container(
height: 1, height: 1,
@ -78,14 +92,39 @@ class HomeSettingsView extends StatelessWidget {
textAlign: TextAlign.end, textAlign: TextAlign.end,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Set', hintText: 'Set',
hintStyle: hintStyle: context.bodyMedium.copyWith(color: Colors.grey),
context.bodyMedium.copyWith(color: Colors.grey),
border: InputBorder.none, 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, crossAxisCount: 2,
crossAxisSpacing: 10, crossAxisSpacing: 10,
), ),
itemCount: 4, itemCount: 2,
itemBuilder: (context, index) => Stack( itemBuilder: (context, index) => Stack(
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
children: [ children: [
DefaultContainer( DefaultContainer(
margin: const EdgeInsets.only(top: 20), margin: const EdgeInsets.only(top: 20),
padding: padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 40),
const EdgeInsets.symmetric(vertical: 15, horizontal: 40),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [

View File

@ -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,
),
),
),
],
),
),
],
),
)
],
),
),
);
}));
}
}

View File

@ -8,12 +8,7 @@ import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
class DefaultScaffold extends StatelessWidget { class DefaultScaffold extends StatelessWidget {
const DefaultScaffold( const DefaultScaffold(
{super.key, {super.key, required this.child, this.title, this.actions, this.appBar, this.bottomNavBar});
required this.child,
this.title,
this.actions,
this.appBar,
this.bottomNavBar});
final Widget child; final Widget child;
final String? title; final String? title;
@ -46,8 +41,7 @@ class DefaultScaffold extends StatelessWidget {
body: Container( body: Container(
width: MediaQuery.sizeOf(context).width, width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height, height: MediaQuery.sizeOf(context).height,
padding: padding: const EdgeInsets.symmetric(horizontal: Constants.defaultPadding),
const EdgeInsets.symmetric(horizontal: Constants.defaultPadding),
decoration: const BoxDecoration( decoration: const BoxDecoration(
image: DecorationImage( image: DecorationImage(
image: AssetImage( image: AssetImage(

View File

@ -647,6 +647,9 @@ class Assets {
static const String assetsDeleteIcon = "assets/icons/delete_room_ic.svg"; 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 for assetsImagesAutomation
/// assets/images/automation.jpg /// assets/images/automation.jpg
static const String assetsImagesAutomation = "assets/images/automation.jpg"; static const String assetsImagesAutomation = "assets/images/automation.jpg";

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/dashboard/view/dashboard_view.dart';
import 'package:syncrow_app/features/layout/view/layout_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/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/menu/view/widgets/profile/profile_view.dart';
import 'package:syncrow_app/features/scene/view/scene_view.dart'; import 'package:syncrow_app/features/scene/view/scene_view.dart';
import 'package:syncrow_app/features/splash/view/splash_view.dart'; import 'package:syncrow_app/features/splash/view/splash_view.dart';

View File

@ -57,6 +57,9 @@ abstract class ApiEndpoints {
static const String unitChild = '$baseUrl/unit/child/'; static const String unitChild = '$baseUrl/unit/child/';
static const String unitParent = '$baseUrl/unit/parent/{unitUuid}'; static const String unitParent = '$baseUrl/unit/parent/{unitUuid}';
static const String unitUser = '$baseUrl/unit/user/'; 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 //PUT
static const String renameUnit = '$baseUrl/unit/rename/{unitUuid}'; 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 addDeviceToRoom = '$baseUrl/device/room';
static const String addDeviceToGroup = '$baseUrl/device/group'; static const String addDeviceToGroup = '$baseUrl/device/group';
static const String controlDevice = '$baseUrl/device/{deviceUuid}/control'; static const String controlDevice = '$baseUrl/device/{deviceUuid}/control';
static const String getDevicesByUserId = '$baseUrl/device/user/{userId}';
//GET //GET
static const String deviceByRoom = '$baseUrl/device/room'; static const String deviceByRoom = '$baseUrl/device/room';
static const String deviceByUuid = '$baseUrl/device/{deviceUuid}'; static const String deviceByUuid = '$baseUrl/device/{deviceUuid}';
@ -100,4 +105,6 @@ abstract class ApiEndpoints {
static const String devicePermissionList = '$baseUrl/device-permission/list'; static const String devicePermissionList = '$baseUrl/device-permission/list';
//PUT //PUT
static const String editDevicePermission = '$baseUrl/device-permission/edit/{userId}'; static const String editDevicePermission = '$baseUrl/device-permission/edit/{userId}';
static const String assignDeviceToRoom = '$baseUrl/device/room';
} }

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;
}
}
}

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>( Future<T> download<T>(
{required String path, {required String path,
required String savePath, required String savePath,

View File

@ -9,8 +9,7 @@ class SpacesAPI {
static final HTTPService _httpService = HTTPService(); static final HTTPService _httpService = HTTPService();
static Future<List<SpaceModel>> getUnitsByUserId() async { static Future<List<SpaceModel>> getUnitsByUserId() async {
var uuid = var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
final response = await _httpService.get( final response = await _httpService.get(
path: "${ApiEndpoints.unitUser}$uuid", path: "${ApiEndpoints.unitUser}$uuid",
showServerMessage: false, showServerMessage: false,
@ -34,4 +33,27 @@ class SpacesAPI {
); );
return response; 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;
}
} }

View File

@ -2,7 +2,7 @@
import 'dart:ui'; import 'dart:ui';
import 'package:syncrow_app/features/devices/model/function_model.dart'; import 'package:syncrow_app/features/devices/model/function_model.dart';
import 'package:syncrow_app/features/menu/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/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/manage_home/manage_home_view.dart';
import 'package:syncrow_app/features/menu/view/widgets/privacy/privacy_view.dart'; import 'package:syncrow_app/features/menu/view/widgets/privacy/privacy_view.dart';

View File

@ -89,6 +89,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.18.0" 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: crypto:
dependency: transitive dependency: transitive
description: description:
@ -197,10 +205,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: firebase_core_web name: firebase_core_web
sha256: c8e1d59385eee98de63c92f961d2a7062c5d9a65e7f45bdc7f1b0b205aab2492 sha256: "22fcb352744908224fc7be3caae254836099786acfe5df6e9fe901e9c2575a41"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.11.5" version: "2.17.1"
firebase_crashlytics: firebase_crashlytics:
dependency: "direct main" dependency: "direct main"
description: description:
@ -377,10 +385,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: http name: http
sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
@ -461,6 +469,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.0" version: "1.11.0"
mime:
dependency: transitive
description:
name: mime
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
url: "https://pub.dev"
source: hosted
version: "1.0.5"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@ -645,6 +661,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.27.7" 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: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
@ -689,10 +721,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_web name: shared_preferences_web
sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.3.0"
shared_preferences_windows: shared_preferences_windows:
dependency: transitive dependency: transitive
description: description:
@ -862,10 +894,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_web name: url_launcher_web
sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.3" version: "2.3.1"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
@ -926,10 +958,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.0" version: "0.5.1"
win32: win32:
dependency: transitive dependency: transitive
description: description:
@ -955,5 +987,5 @@ packages:
source: hosted source: hosted
version: "6.5.0" version: "6.5.0"
sdks: sdks:
dart: ">=3.2.0 <4.0.0" dart: ">=3.3.0 <4.0.0"
flutter: ">=3.16.0" flutter: ">=3.19.0"

View File

@ -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. # pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: "none" # Remove this line if you wish to publish to pub.dev publish_to: "none" # Remove this line if you wish to publish to pub.dev
version: 1.0.1+2 version: 1.0.2+4
environment: environment:
sdk: ">=3.0.6 <4.0.0" sdk: ">=3.0.6 <4.0.0"
@ -41,6 +41,7 @@ dependencies:
onesignal_flutter: ^5.2.0 onesignal_flutter: ^5.2.0
permission_handler: ^11.3.1 permission_handler: ^11.3.1
pin_code_fields: ^8.0.1 pin_code_fields: ^8.0.1
share_plus: ^9.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: