diff --git a/.env.dev b/.env.dev deleted file mode 100644 index e69de29..0000000 diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..e77609d --- /dev/null +++ b/.env.development @@ -0,0 +1,2 @@ +ENV_NAME=development +BASE_URL=https://syncrow-dev.azurewebsites.net \ No newline at end of file diff --git a/.env.prod b/.env.prod deleted file mode 100644 index e69de29..0000000 diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..4e9dcb8 --- /dev/null +++ b/.env.production @@ -0,0 +1,2 @@ +ENV_NAME=production +BASE_URL=https://syncrow-staging.azurewebsites.net \ No newline at end of file diff --git a/.env.staging b/.env.staging index e69de29..9565b42 100644 --- a/.env.staging +++ b/.env.staging @@ -0,0 +1,2 @@ +ENV_NAME=staging +BASE_URL=https://syncrow-staging.azurewebsites.net \ No newline at end of file diff --git a/.gitignore b/.gitignore index add5842..c3e0679 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,7 @@ migrate_working_dir/ # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ - +*.env # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 9287665..ad16c69 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,12 +2,17 @@ + + + + + + android:allowBackup="false"> + NSPhotoLibraryUsageDescription + We need access to your photo library to allow you to select and upload photos. CADisableMinimumFrameDurationOnPhone CFBundleDevelopmentRegion diff --git a/lib/features/app_layout/bloc/home_cubit.dart b/lib/features/app_layout/bloc/home_cubit.dart index faa4f7b..a313f02 100644 --- a/lib/features/app_layout/bloc/home_cubit.dart +++ b/lib/features/app_layout/bloc/home_cubit.dart @@ -27,6 +27,7 @@ import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/navigation/navigation_service.dart'; import 'package:syncrow_app/navigation/routing_constants.dart'; import 'package:syncrow_app/services/api/devices_api.dart'; +import 'package:syncrow_app/services/api/profile_api.dart'; import 'package:syncrow_app/services/api/spaces_api.dart'; import 'package:syncrow_app/utils/helpers/custom_page_route.dart'; import 'package:syncrow_app/utils/helpers/snack_bar.dart'; @@ -38,6 +39,7 @@ part 'home_state.dart'; class HomeCubit extends Cubit { HomeCubit._() : super(HomeInitial()) { checkIfNotificationPermissionGranted(); + fetchUserInfo(); if (selectedSpace == null) { fetchUnitsByUserId(); // .then((value) { @@ -47,7 +49,7 @@ class HomeCubit extends Cubit { // }); } } - + static UserModel? user; static HomeCubit? _instance; static HomeCubit getInstance() { // If an instance already exists, return it @@ -55,6 +57,18 @@ class HomeCubit extends Cubit { return _instance!; } + + Future fetchUserInfo() async { + try { + var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey); + user = await ProfileApi().fetchUserInfo(uuid); + emit(HomeUserInfoLoaded(user!)); // Emit state after fetching user info + + } catch (e) { + return; + } + } + void emitSafe(HomeState newState) { final cubit = this; if (!cubit.isClosed) { diff --git a/lib/features/app_layout/bloc/home_state.dart b/lib/features/app_layout/bloc/home_state.dart index 9c20d4e..1125e01 100644 --- a/lib/features/app_layout/bloc/home_state.dart +++ b/lib/features/app_layout/bloc/home_state.dart @@ -58,3 +58,9 @@ class RoomSelected extends HomeState { class RoomUnSelected extends HomeState {} class NavChangePage extends HomeState {} +// Define new state classes +class HomeUserInfoLoaded extends HomeState { + final UserModel user; + + HomeUserInfoLoaded(this.user); +} diff --git a/lib/features/auth/bloc/auth_cubit.dart b/lib/features/auth/bloc/auth_cubit.dart index 2b9f14b..5f05563 100644 --- a/lib/features/auth/bloc/auth_cubit.dart +++ b/lib/features/auth/bloc/auth_cubit.dart @@ -1,3 +1,4 @@ + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; @@ -8,6 +9,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/services/api/profile_api.dart'; import 'package:syncrow_app/utils/helpers/shared_preferences_helper.dart'; import 'package:syncrow_app/utils/helpers/snack_bar.dart'; import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; @@ -62,7 +64,8 @@ class AuthCubit extends Cubit { return 'Please enter your password'; } 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)) { return 'Password must contain at least:\n - one uppercase letter.\n - one lowercase letter.\n - one number. \n - special character'; } @@ -178,12 +181,14 @@ class AuthCubit extends Cubit { if (token.accessTokenIsNotEmpty) { debugPrint('token: ${token.accessToken}'); FlutterSecureStorage storage = const FlutterSecureStorage(); - await storage.write(key: Token.loginAccessTokenKey, value: token.accessToken); - + await storage.write( + key: Token.loginAccessTokenKey, + value: token.accessToken + ); const FlutterSecureStorage().write( key: UserModel.userUuidKey, - value: Token.decodeToken(token.accessToken)['uuid'].toString()); - + value: Token.decodeToken(token.accessToken)['uuid'].toString() + ); user = UserModel.fromToken(token); emailController.clear(); passwordController.clear(); @@ -277,8 +282,7 @@ class AuthCubit extends Cubit { try { emit(AuthTokenLoading()); const storage = FlutterSecureStorage(); - final firstLaunch = - await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true; + final firstLaunch = await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true; if (firstLaunch) { storage.deleteAll(); @@ -311,6 +315,7 @@ class AuthCubit extends Cubit { } } + sendToForgetPassword({required String password}) async { try { emit(AuthForgetPassLoading()); @@ -320,4 +325,8 @@ class AuthCubit extends Cubit { emit(AuthForgetPassError(message: 'Something went wrong')); } } + + + + } diff --git a/lib/features/auth/model/user_model.dart b/lib/features/auth/model/user_model.dart index 5491d73..fd68fb4 100644 --- a/lib/features/auth/model/user_model.dart +++ b/lib/features/auth/model/user_model.dart @@ -1,63 +1,81 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; import 'package:syncrow_app/features/auth/model/token.dart'; class UserModel { static String userUuidKey = 'userUuid'; final String? uuid; final String? email; - final String? name; - final String? photoUrl; - + final String? firstName; + final String? lastName; + final Uint8List? profilePicture; final String? phoneNumber; - final bool? isEmailVerified; - + final String? regionName; + final String? timeZone; final bool? isAgreementAccepted; UserModel({ required this.uuid, required this.email, - required this.name, - required this.photoUrl, + required this.firstName, + required this.lastName, + required this.profilePicture, required this.phoneNumber, required this.isEmailVerified, required this.isAgreementAccepted, + required this.regionName, // Add this line + required this.timeZone, // Add this line + }); factory UserModel.fromJson(Map json) { return UserModel( - uuid: json['id'], + uuid: json['uuid'], email: json['email'], - name: json['name'], - photoUrl: json['photoUrl'], + firstName: json['firstName'], + lastName: json['lastName'], + profilePicture: UserModel.decodeBase64Image(json['profilePicture']), phoneNumber: json['phoneNumber'], isEmailVerified: json['isEmailVerified'], isAgreementAccepted: json['isAgreementAccepted'], + regionName: json['region']?['regionName'], // Extract regionName + timeZone: json['timeZone']?['timeZoneOffset'], // Extract regionName ); } - //uuid to json - //from token - factory UserModel.fromToken(Token token) { + factory UserModel.fromToken(Token token) { Map tempJson = Token.decodeToken(token.accessToken); - return UserModel( uuid: tempJson['uuid'].toString(), email: tempJson['email'], - name: null, - photoUrl: null, + lastName: tempJson['lastName'], + firstName:tempJson['firstName'] , + profilePicture: UserModel.decodeBase64Image(tempJson['profilePicture']), phoneNumber: null, isEmailVerified: null, isAgreementAccepted: null, + regionName: tempJson['region']?['regionName'], + timeZone: tempJson['timezone']?['timeZoneOffset'], ); } + static Uint8List? decodeBase64Image(String? base64String) { + if (base64String != null) { + return base64.decode(base64String); + } + return null; + } + Map toJson() { return { 'id': uuid, 'email': email, - 'name': name, - 'photoUrl': photoUrl, + 'lastName': lastName, + 'firstName': firstName, + 'photoUrl': profilePicture, 'phoneNumber': phoneNumber, 'isEmailVerified': isEmailVerified, 'isAgreementAccepted': isAgreementAccepted, diff --git a/lib/features/devices/bloc/acs_bloc/acs_bloc.dart b/lib/features/devices/bloc/acs_bloc/acs_bloc.dart index b8dad95..8603f98 100644 --- a/lib/features/devices/bloc/acs_bloc/acs_bloc.dart +++ b/lib/features/devices/bloc/acs_bloc/acs_bloc.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart'; @@ -12,6 +14,7 @@ import 'package:syncrow_app/utils/resource_manager/constants.dart'; class ACsBloc extends Bloc { final String acId; AcStatusModel deviceStatus = AcStatusModel( + uuid: '', acSwitch: true, modeString: 'hot', tempSet: 300, @@ -24,6 +27,7 @@ class ACsBloc extends Bloc { bool allAcsOn = true; bool allTempSame = true; int globalTemp = 25; + Timer? _timer; ACsBloc({required this.acId}) : super(AcsInitialState()) { on(_fetchAcsStatus); @@ -56,11 +60,11 @@ class ACsBloc extends Bloc { for (var status in response['status']) { statusModelList.add(StatusModel.fromJson(status)); } - deviceStatus = AcStatusModel.fromJson(statusModelList); + deviceStatus = AcStatusModel.fromJson(response['productUuid'], statusModelList); emit(GetAcStatusState(acStatusModel: deviceStatus)); } } catch (e) { - emit(AcsFailedState(error: e.toString())); + emit(AcsFailedState(errorMessage: e.toString())); return; } } @@ -68,8 +72,6 @@ class ACsBloc extends Bloc { _getAllAcs() async { deviceStatusList = []; devicesList = []; - allAcsOn = true; - allTempSame = true; devicesList = await DevicesAPI.getDeviceByGroupName( HomeCubit.getInstance().selectedSpace?.id ?? '', 'AC'); @@ -79,8 +81,210 @@ class ACsBloc extends Bloc { for (var status in response['status']) { statusModelList.add(StatusModel.fromJson(status)); } - deviceStatusList.add(AcStatusModel.fromJson(statusModelList)); + deviceStatusList.add(AcStatusModel.fromJson(response['productUuid'], statusModelList)); } + _setAllAcsTempsAndSwitches(); + } + + void _changeAcSwitch(AcSwitch event, Emitter emit) async { + final acSwitchValue = !event.acSwitch; + if (allAcsPage) { + emit(AcsLoadingState()); + for (AcStatusModel ac in deviceStatusList) { + if (ac.uuid == event.productId) { + ac.acSwitch = acSwitchValue; + } + } + _setAllAcsTempsAndSwitches(); + _emitAcsStatus(emit); + } else { + emit(AcChangeLoading(acStatusModel: deviceStatus)); + deviceStatus.acSwitch = acSwitchValue; + emit(AcModifyingState(acStatusModel: deviceStatus)); + } + + await _runDeBouncerForOneDevice(deviceId: event.deviceId, code: 'switch', value: acSwitchValue); + } + + void _changeAllAcSwitch(ChangeAllSwitch event, Emitter emit) async { + emit(AcsLoadingState()); + if (deviceStatusList.length == devicesList.length) { + for (int i = 0; i < deviceStatusList.length; i++) { + deviceStatusList[i].acSwitch = event.value; + } + } + _setAllAcsTempsAndSwitches(); + _emitAcsStatus(emit); + _runDeBouncerForAllAcs(code: 'switch', value: event.value); + } + + void _increaseAllTemp(IncreaseAllTemp event, Emitter emit) async { + emit(AcsLoadingState()); + double tempValue = event.value + 0.5; + int value = (tempValue * 10).toInt(); + + if (!_checkTemperatureValue(tempValue, emit)) { + return; + } + + if (deviceStatusList.length == devicesList.length) { + for (int i = 0; i < deviceStatusList.length; i++) { + deviceStatusList[i].tempSet = value; + } + } + _setAllAcsTempsAndSwitches(); + _emitAcsStatus(emit); + _runDeBouncerForAllAcs(code: 'temp_set', value: value); + } + + void _decreaseAllTemp(DecreaseAllTemp event, Emitter emit) async { + emit(AcsLoadingState()); + + double tempValue = event.value - 0.5; + int value = (tempValue * 10).toInt(); + + if (!_checkTemperatureValue(tempValue, emit)) { + return; + } + + if (deviceStatusList.length == devicesList.length) { + for (int i = 0; i < deviceStatusList.length; i++) { + deviceStatusList[i].tempSet = value; + } + } + _setAllAcsTempsAndSwitches(); + _emitAcsStatus(emit); + _runDeBouncerForAllAcs(code: 'temp_set', value: value); + } + + void _changeLockValue(ChangeLock event, Emitter emit) async { + emit(AcChangeLoading(acStatusModel: deviceStatus)); + + final lockValue = !event.lockBool; + deviceStatus.childLock = lockValue; + emit(AcModifyingState(acStatusModel: deviceStatus)); + + _runDeBouncerForOneDevice(deviceId: acId, code: 'child_lock', value: lockValue); + } + + void _increaseCoolTo(IncreaseCoolToTemp event, Emitter emit) async { + emit(AcChangeLoading(acStatusModel: deviceStatus)); + + double tempValue = event.value + 0.5; + int value = (tempValue * 10).toInt(); + + if (!_checkTemperatureValue(tempValue, emit)) { + return; + } + + if (allAcsPage) { + emit(AcsLoadingState()); + for (AcStatusModel ac in deviceStatusList) { + if (ac.uuid == event.productId) { + ac.tempSet = value; + } + } + _setAllAcsTempsAndSwitches(); + _emitAcsStatus(emit); + } else { + emit(AcChangeLoading(acStatusModel: deviceStatus)); + deviceStatus.tempSet = value; + emit(AcModifyingState(acStatusModel: deviceStatus)); + } + + await _runDeBouncerForOneDevice(deviceId: event.deviceId, code: 'temp_set', value: value); + } + + void _decreaseCoolTo(DecreaseCoolToTemp event, Emitter emit) async { + emit(AcChangeLoading(acStatusModel: deviceStatus)); + + double tempValue = event.value - 0.5; + int value = (tempValue * 10).toInt(); + + if (!_checkTemperatureValue(tempValue, emit)) { + return; + } + + if (allAcsPage) { + emit(AcsLoadingState()); + for (AcStatusModel ac in deviceStatusList) { + if (ac.uuid == event.productId) { + ac.tempSet = value; + } + } + _setAllAcsTempsAndSwitches(); + _emitAcsStatus(emit); + } else { + emit(AcChangeLoading(acStatusModel: deviceStatus)); + deviceStatus.tempSet = value; + emit(AcModifyingState(acStatusModel: deviceStatus)); + } + + await _runDeBouncerForOneDevice(deviceId: event.deviceId, code: 'temp_set', value: value); + } + + void _changeAcMode(ChangeAcMode event, Emitter emit) async { + final tempMode = tempModesMap[getNextItem(tempModesMap, event.tempModes)]!; + if (allAcsPage) { + emit(AcsLoadingState()); + for (AcStatusModel ac in deviceStatusList) { + if (ac.uuid == event.productId) { + ac.modeString = getACModeString(tempMode); + ac.acMode = AcStatusModel.getACMode(getACModeString(tempMode)); + } + } + _emitAcsStatus(emit); + } else { + emit(AcChangeLoading(acStatusModel: deviceStatus)); + deviceStatus.modeString = getACModeString(tempMode); + deviceStatus.acMode = AcStatusModel.getACMode(getACModeString(tempMode)); + emit(AcModifyingState(acStatusModel: deviceStatus)); + } + + await _runDeBouncerForOneDevice( + deviceId: event.deviceId, code: 'mode', value: getACModeString(tempMode)); + } + + void _changeFanSpeed(ChangeFanSpeed event, Emitter emit) async { + emit(AcChangeLoading(acStatusModel: deviceStatus)); + + final fanSpeed = event.fanSpeeds; + + if (allAcsPage) { + emit(AcsLoadingState()); + for (AcStatusModel ac in deviceStatusList) { + if (ac.uuid == event.productId) { + ac.fanSpeedsString = getNextFanSpeedKey(fanSpeed); + ac.acFanSpeed = AcStatusModel.getFanSpeed(getNextFanSpeedKey(fanSpeed)); + } + } + _emitAcsStatus(emit); + } else { + emit(AcChangeLoading(acStatusModel: deviceStatus)); + deviceStatus.fanSpeedsString = getNextFanSpeedKey(fanSpeed); + deviceStatus.acFanSpeed = AcStatusModel.getFanSpeed(getNextFanSpeedKey(fanSpeed)); + emit(AcModifyingState(acStatusModel: deviceStatus)); + } + + await _runDeBouncerForOneDevice( + deviceId: event.deviceId, code: 'level', value: getNextFanSpeedKey(fanSpeed)); + } + + String getACModeString(TempModes value) { + if (value == TempModes.cold) { + return 'cold'; + } else if (value == TempModes.hot) { + return 'hot'; + } else if (value == TempModes.wind) { + return 'wind'; + } else { + return 'cold'; + } + } + + void _setAllAcsTempsAndSwitches() { + allAcsOn = true; + allTempSame = true; if (deviceStatusList.isNotEmpty) { int temp = deviceStatusList[0].tempSet; deviceStatusList.firstWhere((element) { @@ -99,195 +303,71 @@ class ACsBloc extends Bloc { } } - void _changeAcSwitch(AcSwitch event, Emitter emit) async { - emit(AcChangeLoading(acStatusModel: deviceStatus)); - - final acSwitchValue = !event.acSwitch; - try { - final response = await DevicesAPI.controlDevice( - DeviceControlModel( - deviceId: allAcsPage ? event.deviceId : acId, code: 'switch', value: acSwitchValue), - allAcsPage ? event.deviceId : acId); - - if (response['success'] ?? false) { - deviceStatus.acSwitch = acSwitchValue; - } - } catch (_) {} - if (allAcsPage) { - await Future.delayed(const Duration(seconds: 1)); - add(const AcsInitial(allAcs: true)); - } else { - emit(AcModifyingState(acStatusModel: deviceStatus)); + _runDeBouncerForAllAcs({required String code, required dynamic value}) { + if (_timer != null) { + _timer!.cancel(); } - } - - void _changeAllAcSwitch(ChangeAllSwitch event, Emitter emit) async { - emit(AcsLoadingState()); - try { + _timer = Timer(const Duration(seconds: 1), () async { if (deviceStatusList.length == devicesList.length) { for (int i = 0; i < deviceStatusList.length; i++) { - await DevicesAPI.controlDevice( - DeviceControlModel(deviceId: devicesList[i].uuid, code: 'switch', value: event.value), - devicesList[i].uuid ?? ''); + try { + await DevicesAPI.controlDevice( + DeviceControlModel(deviceId: devicesList[i].uuid, code: code, value: value), + devicesList[i].uuid ?? ''); + } catch (_) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const AcsInitial(allAcs: true)); + } } } - } catch (_) {} - await Future.delayed(const Duration(seconds: 1)); - add(const AcsInitial(allAcs: true)); + }); } - void _increaseAllTemp(IncreaseAllTemp event, Emitter emit) async { - emit(AcsLoadingState()); - try { - double tempValue = event.value + 0.5; - int value = (tempValue * 10).toInt(); - if (deviceStatusList.length == devicesList.length) { - for (int i = 0; i < deviceStatusList.length; i++) { - await DevicesAPI.controlDevice( - DeviceControlModel(deviceId: devicesList[i].uuid, code: 'temp_set', value: value), - devicesList[i].uuid ?? ''); + _runDeBouncerForOneDevice({ + required String deviceId, + required String code, + required dynamic value, + }) { + if (_timer != null) { + _timer!.cancel(); + } + _timer = Timer(const Duration(seconds: 1), () async { + try { + final response = await DevicesAPI.controlDevice( + DeviceControlModel(deviceId: allAcsPage ? deviceId : acId, code: code, value: value), + allAcsPage ? deviceId : acId); + + if (!response['success']) { + add(AcsInitial(allAcs: allAcsPage)); } + } catch (_) { + await Future.delayed(const Duration(milliseconds: 500)); + add(AcsInitial(allAcs: allAcsPage)); } - } catch (_) {} - await Future.delayed(const Duration(seconds: 1)); - add(const AcsInitial(allAcs: true)); + }); } - void _decreaseAllTemp(DecreaseAllTemp event, Emitter emit) async { - emit(AcsLoadingState()); - try { - double tempValue = event.value - 0.5; - int value = (tempValue * 10).toInt(); - if (deviceStatusList.length == devicesList.length) { - for (int i = 0; i < deviceStatusList.length; i++) { - await DevicesAPI.controlDevice( - DeviceControlModel(deviceId: devicesList[i].uuid, code: 'temp_set', value: value), - devicesList[i].uuid ?? ''); - } - } - } catch (_) {} - await Future.delayed(const Duration(seconds: 1)); - add(const AcsInitial(allAcs: true)); - } - - void _changeLockValue(ChangeLock event, Emitter emit) async { - emit(AcChangeLoading(acStatusModel: deviceStatus)); - - final lockValue = !event.lockBool; - try { - final response = await DevicesAPI.controlDevice( - DeviceControlModel(deviceId: acId, code: 'child_lock', value: lockValue), acId); - - if (response['success'] ?? false) { - deviceStatus.childLock = lockValue; - } - } catch (_) {} - emit(AcModifyingState(acStatusModel: deviceStatus)); - } - - void _increaseCoolTo(IncreaseCoolToTemp event, Emitter emit) async { - emit(AcChangeLoading(acStatusModel: deviceStatus)); - double tempValue = event.value + 0.5; - int value = (tempValue * 10).toInt(); - try { - final response = await DevicesAPI.controlDevice( - DeviceControlModel( - deviceId: allAcsPage ? event.deviceId : acId, code: 'temp_set', value: value), - allAcsPage ? event.deviceId : acId); - - if (response['success'] ?? false) { - deviceStatus.tempSet = value; - } - } catch (_) {} - if (allAcsPage) { - await Future.delayed(const Duration(seconds: 1)); - add(const AcsInitial(allAcs: true)); + bool _checkTemperatureValue(double value, Emitter emit) { + if (value >= 20 && value <= 30) { + return true; } else { - emit(AcModifyingState(acStatusModel: deviceStatus)); + emit(const AcsFailedState(errorMessage: 'The temperature must be between 20 and 30')); + emit(GetAllAcsStatusState( + allAcsStatues: deviceStatusList, + allAcs: devicesList, + allOn: allAcsOn, + allTempSame: allTempSame, + temp: globalTemp)); + return false; } } - void _decreaseCoolTo(DecreaseCoolToTemp event, Emitter emit) async { - emit(AcChangeLoading(acStatusModel: deviceStatus)); - - double tempValue = event.value - 0.5; - int value = (tempValue * 10).toInt(); - try { - final response = await DevicesAPI.controlDevice( - DeviceControlModel( - deviceId: allAcsPage ? event.deviceId : acId, code: 'temp_set', value: value), - allAcsPage ? event.deviceId : acId); - - if (response['success'] ?? false) { - deviceStatus.tempSet = value; - } - } catch (_) {} - if (allAcsPage) { - await Future.delayed(const Duration(seconds: 1)); - add(const AcsInitial(allAcs: true)); - } else { - emit(AcModifyingState(acStatusModel: deviceStatus)); - } - } - - void _changeAcMode(ChangeAcMode event, Emitter emit) async { - emit(AcChangeLoading(acStatusModel: deviceStatus)); - final tempMode = tempModesMap[getNextItem(tempModesMap, event.tempModes)]!; - try { - final response = await DevicesAPI.controlDevice( - DeviceControlModel( - deviceId: allAcsPage ? event.deviceId : acId, - code: 'mode', - value: getACModeString(tempMode)), - allAcsPage ? event.deviceId : acId); - - if (response['success'] ?? false) { - deviceStatus.modeString = getACModeString(tempMode); - deviceStatus.acMode = AcStatusModel.getACMode(getACModeString(tempMode)); - } - } catch (_) {} - if (allAcsPage) { - await Future.delayed(const Duration(seconds: 1)); - add(const AcsInitial(allAcs: true)); - } else { - emit(AcModifyingState(acStatusModel: deviceStatus)); - } - } - - void _changeFanSpeed(ChangeFanSpeed event, Emitter emit) async { - emit(AcChangeLoading(acStatusModel: deviceStatus)); - - final fanSpeed = event.fanSpeeds; - final response = await DevicesAPI.controlDevice( - DeviceControlModel( - deviceId: allAcsPage ? event.deviceId : acId, - code: 'level', - value: getNextFanSpeedKey(fanSpeed)), - allAcsPage ? event.deviceId : acId); - - try { - if (response['success'] ?? false) { - deviceStatus.fanSpeedsString = getNextFanSpeedKey(fanSpeed); - deviceStatus.acFanSpeed = AcStatusModel.getFanSpeed(getNextFanSpeedKey(fanSpeed)); - } - } catch (_) {} - if (allAcsPage) { - await Future.delayed(const Duration(seconds: 1)); - add(const AcsInitial(allAcs: true)); - } else { - emit(AcModifyingState(acStatusModel: deviceStatus)); - } - } - - String getACModeString(TempModes value) { - if (value == TempModes.cold) { - return 'cold'; - } else if (value == TempModes.hot) { - return 'hot'; - } else if (value == TempModes.wind) { - return 'wind'; - } else { - return 'cold'; - } + _emitAcsStatus(Emitter emit) { + emit(GetAllAcsStatusState( + allAcsStatues: deviceStatusList, + allAcs: devicesList, + allOn: allAcsOn, + allTempSame: allTempSame, + temp: globalTemp)); } } diff --git a/lib/features/devices/bloc/acs_bloc/acs_event.dart b/lib/features/devices/bloc/acs_bloc/acs_event.dart index 806e878..966f69b 100644 --- a/lib/features/devices/bloc/acs_bloc/acs_event.dart +++ b/lib/features/devices/bloc/acs_bloc/acs_event.dart @@ -13,10 +13,11 @@ class AcsLoading extends AcsEvent {} class AcSwitch extends AcsEvent { final bool acSwitch; final String deviceId; - const AcSwitch({required this.acSwitch, this.deviceId = ''}); + final String productId; + const AcSwitch({required this.acSwitch, this.deviceId = '', this.productId = ''}); @override - List get props => [acSwitch, deviceId]; + List get props => [acSwitch, deviceId, productId]; } class AcsInitial extends AcsEvent { @@ -31,7 +32,8 @@ class ACsChangeStatus extends AcsEvent {} class IncreaseCoolToTemp extends AcsEvent { final double value; final String deviceId; - const IncreaseCoolToTemp({required this.value, this.deviceId = ''}); + final String productId; + const IncreaseCoolToTemp({required this.value, this.deviceId = '', this.productId = ''}); @override List get props => [value, deviceId]; @@ -40,7 +42,9 @@ class IncreaseCoolToTemp extends AcsEvent { class DecreaseCoolToTemp extends AcsEvent { final double value; final String deviceId; - const DecreaseCoolToTemp({required this.value, this.deviceId = ''}); + final String productId; + + const DecreaseCoolToTemp({required this.value, this.deviceId = '', this.productId = ''}); @override List get props => [value, deviceId]; @@ -49,19 +53,22 @@ class DecreaseCoolToTemp extends AcsEvent { class ChangeAcMode extends AcsEvent { final TempModes tempModes; final String deviceId; - const ChangeAcMode({required this.tempModes, this.deviceId = ''}); + final String productId; + const ChangeAcMode({required this.tempModes, this.deviceId = '', this.productId = ''}); @override - List get props => [tempModes, deviceId]; + List get props => [tempModes, deviceId, productId]; } class ChangeFanSpeed extends AcsEvent { final FanSpeeds fanSpeeds; final String deviceId; - const ChangeFanSpeed({required this.fanSpeeds, this.deviceId = ''}); + final String productId; + + const ChangeFanSpeed({required this.fanSpeeds, this.deviceId = '', this.productId = ''}); @override - List get props => [fanSpeeds, deviceId]; + List get props => [fanSpeeds, deviceId, productId]; } class ChangeLock extends AcsEvent { diff --git a/lib/features/devices/bloc/acs_bloc/acs_state.dart b/lib/features/devices/bloc/acs_bloc/acs_state.dart index fe3d8e5..231da9a 100644 --- a/lib/features/devices/bloc/acs_bloc/acs_state.dart +++ b/lib/features/devices/bloc/acs_bloc/acs_state.dart @@ -56,10 +56,10 @@ class GetAllAcsStatusState extends AcsState { } class AcsFailedState extends AcsState { - final String error; + final String errorMessage; - const AcsFailedState({required this.error}); + const AcsFailedState({required this.errorMessage}); @override - List get props => [error]; + List get props => [errorMessage]; } diff --git a/lib/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart b/lib/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart index b6a566c..39a9444 100644 --- a/lib/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart +++ b/lib/features/devices/bloc/three_gang_bloc/three_gang_bloc.dart @@ -95,6 +95,22 @@ class ThreeGangBloc extends Bloc { void _changeFirstSwitch(ChangeFirstSwitchStatusEvent event, Emitter emit) async { emit(LoadingNewSate(threeGangModel: deviceStatus)); try { + if (threeGangGroup) { + bool allSwitchesValue = true; + groupThreeGangList.forEach((element) { + if (element.deviceId == event.deviceId) { + element.firstSwitch = !event.value; + } + if (!element.firstSwitch || !element.secondSwitch || !element.thirdSwitch) { + allSwitchesValue = false; + } + }); + emit(UpdateGroupState(threeGangList: groupThreeGangList, allSwitches: allSwitchesValue)); + } else { + deviceStatus.firstSwitch = !event.value; + emit(UpdateState(threeGangModel: deviceStatus)); + } + final response = await DevicesAPI.controlDevice( DeviceControlModel( deviceId: threeGangGroup ? event.deviceId : threeGangId, @@ -102,15 +118,11 @@ class ThreeGangBloc extends Bloc { value: !event.value), threeGangGroup ? event.deviceId : threeGangId); - if (response['success'] ?? false) { - deviceStatus.firstSwitch = !event.value; + if (!response['success']) { + add(InitialEvent(groupScreen: threeGangGroup)); } - } catch (_) {} - if (threeGangGroup) { - await Future.delayed(const Duration(seconds: 1)); - add(const InitialEvent(groupScreen: true)); - } else { - emit(UpdateState(threeGangModel: deviceStatus)); + } catch (_) { + add(InitialEvent(groupScreen: threeGangGroup)); } } @@ -118,6 +130,22 @@ class ThreeGangBloc extends Bloc { ChangeSecondSwitchStatusEvent event, Emitter emit) async { emit(LoadingNewSate(threeGangModel: deviceStatus)); try { + if (threeGangGroup) { + bool allSwitchesValue = true; + groupThreeGangList.forEach((element) { + if (element.deviceId == event.deviceId) { + element.secondSwitch = !event.value; + } + if (!element.firstSwitch || !element.secondSwitch || !element.thirdSwitch) { + allSwitchesValue = false; + } + }); + emit(UpdateGroupState(threeGangList: groupThreeGangList, allSwitches: allSwitchesValue)); + } else { + deviceStatus.secondSwitch = !event.value; + emit(UpdateState(threeGangModel: deviceStatus)); + } + final response = await DevicesAPI.controlDevice( DeviceControlModel( deviceId: threeGangGroup ? event.deviceId : threeGangId, @@ -125,21 +153,33 @@ class ThreeGangBloc extends Bloc { value: !event.value), threeGangGroup ? event.deviceId : threeGangId); - if (response['success'] ?? false) { - deviceStatus.secondSwitch = !event.value; + if (!response['success']) { + add(InitialEvent(groupScreen: threeGangGroup)); } - } catch (_) {} - if (threeGangGroup) { - await Future.delayed(const Duration(seconds: 1)); - add(const InitialEvent(groupScreen: true)); - } else { - emit(UpdateState(threeGangModel: deviceStatus)); + } catch (_) { + add(InitialEvent(groupScreen: threeGangGroup)); } } void _changeThirdSwitch(ChangeThirdSwitchStatusEvent event, Emitter emit) async { emit(LoadingNewSate(threeGangModel: deviceStatus)); try { + if (threeGangGroup) { + bool allSwitchesValue = true; + groupThreeGangList.forEach((element) { + if (element.deviceId == event.deviceId) { + element.thirdSwitch = !event.value; + } + if (!element.firstSwitch || !element.secondSwitch || !element.thirdSwitch) { + allSwitchesValue = false; + } + }); + emit(UpdateGroupState(threeGangList: groupThreeGangList, allSwitches: allSwitchesValue)); + } else { + deviceStatus.thirdSwitch = !event.value; + emit(UpdateState(threeGangModel: deviceStatus)); + } + final response = await DevicesAPI.controlDevice( DeviceControlModel( deviceId: threeGangGroup ? event.deviceId : threeGangId, @@ -147,15 +187,11 @@ class ThreeGangBloc extends Bloc { value: !event.value), threeGangGroup ? event.deviceId : threeGangId); - if (response['success'] ?? false) { - deviceStatus.thirdSwitch = !event.value; + if (!response['success']) { + add(InitialEvent(groupScreen: threeGangGroup)); } - } catch (_) {} - if (threeGangGroup) { - await Future.delayed(const Duration(seconds: 1)); - add(const InitialEvent(groupScreen: true)); - } else { - emit(UpdateState(threeGangModel: deviceStatus)); + } catch (_) { + add(InitialEvent(groupScreen: threeGangGroup)); } } @@ -163,52 +199,82 @@ class ThreeGangBloc extends Bloc { emit(LoadingNewSate(threeGangModel: deviceStatus)); try { + deviceStatus.firstSwitch = false; + deviceStatus.secondSwitch = false; + deviceStatus.thirdSwitch = false; + emit(UpdateState(threeGangModel: deviceStatus)); + final response = await Future.wait([ DevicesAPI.controlDevice( - DeviceControlModel(deviceId: threeGangId, code: 'switch_1', value: false), threeGangId), + DeviceControlModel( + deviceId: threeGangId, code: 'switch_1', value: deviceStatus.firstSwitch), + threeGangId), DevicesAPI.controlDevice( - DeviceControlModel(deviceId: threeGangId, code: 'switch_2', value: false), threeGangId), + DeviceControlModel( + deviceId: threeGangId, code: 'switch_2', value: deviceStatus.secondSwitch), + threeGangId), DevicesAPI.controlDevice( - DeviceControlModel(deviceId: threeGangId, code: 'switch_3', value: false), threeGangId), + DeviceControlModel( + deviceId: threeGangId, code: 'switch_3', value: deviceStatus.thirdSwitch), + threeGangId), ]); - if (response.every((element) => element['success'] ?? false)) { - deviceStatus.firstSwitch = false; - deviceStatus.secondSwitch = false; - deviceStatus.thirdSwitch = false; + if (response.every((element) => !element['success'])) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: false)); } - } catch (_) {} - emit(UpdateState(threeGangModel: deviceStatus)); + } catch (_) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: false)); + } } void _allOn(AllOnEvent event, Emitter emit) async { emit(LoadingNewSate(threeGangModel: deviceStatus)); try { + deviceStatus.firstSwitch = true; + deviceStatus.secondSwitch = true; + deviceStatus.thirdSwitch = true; + emit(UpdateState(threeGangModel: deviceStatus)); + final response = await Future.wait([ DevicesAPI.controlDevice( - DeviceControlModel(deviceId: threeGangId, code: 'switch_1', value: true), threeGangId), + DeviceControlModel( + deviceId: threeGangId, code: 'switch_1', value: deviceStatus.firstSwitch), + threeGangId), DevicesAPI.controlDevice( - DeviceControlModel(deviceId: threeGangId, code: 'switch_2', value: true), threeGangId), + DeviceControlModel( + deviceId: threeGangId, code: 'switch_2', value: deviceStatus.secondSwitch), + threeGangId), DevicesAPI.controlDevice( - DeviceControlModel(deviceId: threeGangId, code: 'switch_3', value: true), threeGangId), + DeviceControlModel( + deviceId: threeGangId, code: 'switch_3', value: deviceStatus.thirdSwitch), + threeGangId), ]); - if (response.every((element) => element['success'] ?? false)) { - deviceStatus.firstSwitch = true; - deviceStatus.secondSwitch = true; - deviceStatus.thirdSwitch = true; + if (response.every((element) => !element['success'])) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: false)); } - } catch (_) {} - emit(UpdateState(threeGangModel: deviceStatus)); + } catch (_) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: false)); + } } void _groupAllOn(GroupAllOnEvent event, Emitter emit) async { emit(LoadingNewSate(threeGangModel: deviceStatus)); - try { for (int i = 0; i < groupThreeGangList.length; i++) { - await Future.wait([ + groupThreeGangList[i].firstSwitch = true; + groupThreeGangList[i].secondSwitch = true; + groupThreeGangList[i].thirdSwitch = true; + } + emit(UpdateGroupState(threeGangList: groupThreeGangList, allSwitches: true)); + + for (int i = 0; i < groupThreeGangList.length; i++) { + final response = await Future.wait([ DevicesAPI.controlDevice( DeviceControlModel( deviceId: groupThreeGangList[i].deviceId, code: 'switch_1', value: true), @@ -222,18 +288,31 @@ class ThreeGangBloc extends Bloc { deviceId: groupThreeGangList[i].deviceId, code: 'switch_3', value: true), groupThreeGangList[i].deviceId), ]); + + if (response.every((element) => !element['success'])) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: true)); + break; + } } - } catch (_) {} - await Future.delayed(const Duration(seconds: 1)); - add(const InitialEvent(groupScreen: true)); + } catch (_) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: true)); + } } void _groupAllOff(GroupAllOffEvent event, Emitter emit) async { emit(LoadingNewSate(threeGangModel: deviceStatus)); - try { for (int i = 0; i < groupThreeGangList.length; i++) { - await Future.wait([ + groupThreeGangList[i].firstSwitch = false; + groupThreeGangList[i].secondSwitch = false; + groupThreeGangList[i].thirdSwitch = false; + } + emit(UpdateGroupState(threeGangList: groupThreeGangList, allSwitches: false)); + + for (int i = 0; i < groupThreeGangList.length; i++) { + final response = await Future.wait([ DevicesAPI.controlDevice( DeviceControlModel( deviceId: groupThreeGangList[i].deviceId, code: 'switch_1', value: false), @@ -247,10 +326,17 @@ class ThreeGangBloc extends Bloc { deviceId: groupThreeGangList[i].deviceId, code: 'switch_3', value: false), groupThreeGangList[i].deviceId), ]); + + if (response.every((element) => !element['success'])) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: true)); + break; + } } - } catch (_) {} - await Future.delayed(const Duration(seconds: 1)); - add(const InitialEvent(groupScreen: true)); + } catch (_) { + await Future.delayed(const Duration(milliseconds: 500)); + add(const InitialEvent(groupScreen: true)); + } } void _changeSliding(ChangeSlidingSegment event, Emitter emit) async { diff --git a/lib/features/devices/model/ac_model.dart b/lib/features/devices/model/ac_model.dart index 0106ad1..9f9e06f 100644 --- a/lib/features/devices/model/ac_model.dart +++ b/lib/features/devices/model/ac_model.dart @@ -2,6 +2,7 @@ import 'package:syncrow_app/features/devices/model/status_model.dart'; import 'package:syncrow_app/utils/resource_manager/constants.dart'; class AcStatusModel { + String uuid; bool acSwitch; String modeString; int tempSet; @@ -12,7 +13,8 @@ class AcStatusModel { late FanSpeeds acFanSpeed; AcStatusModel( - {required this.acSwitch, + {required this.uuid, + required this.acSwitch, required this.modeString, required this.tempSet, required this.currentTemp, @@ -22,7 +24,7 @@ class AcStatusModel { acFanSpeed = getFanSpeed(fanSpeedsString); } - factory AcStatusModel.fromJson(List jsonList) { + factory AcStatusModel.fromJson(String id, List jsonList) { late bool _acSwitch; late String _mode; late int _tempSet; @@ -45,6 +47,7 @@ class AcStatusModel { } } return AcStatusModel( + uuid: id, acSwitch: _acSwitch, modeString: _mode, tempSet: _tempSet, diff --git a/lib/features/devices/model/device_model.dart b/lib/features/devices/model/device_model.dart index 4109397..82fac11 100644 --- a/lib/features/devices/model/device_model.dart +++ b/lib/features/devices/model/device_model.dart @@ -17,12 +17,13 @@ class DeviceModel { String? timeZone; int? updateTime; String? uuid; + String? productUuid; DeviceType? productType; bool isSelected = false; late List functions; DeviceModel( {this.activeTime, - // this.id, + this.productUuid, this.localKey, this.model, this.name, @@ -61,27 +62,26 @@ class DeviceModel { tempIcon = Assets.assetsIconsLogo; } return DeviceModel( - icon: tempIcon, - activeTime: json['activeTime'], - // id: json['id'], - localKey: json['localKey'], - model: json['model'], - name: json['name'], - isOnline: json['online'], - productName: json['productName'], - timeZone: json['timeZone'], - updateTime: json['updateTime'], - uuid: json['uuid'], - productType: type, - type: json['productType'], - status: [], - ); + icon: tempIcon, + activeTime: json['activeTime'], + // id: json['id'], + localKey: json['localKey'], + model: json['model'], + name: json['name'], + isOnline: json['online'], + productName: json['productName'], + timeZone: json['timeZone'], + updateTime: json['updateTime'], + uuid: json['uuid'], + productType: type, + type: json['productType'], + status: [], + productUuid: json['productUuid']); } Map toJson() { return { 'activeTime': activeTime, - // 'id': id, 'localKey': localKey, 'model': model, 'name': name, diff --git a/lib/features/devices/model/group_three_gang_model.dart b/lib/features/devices/model/group_three_gang_model.dart index 76ca7e9..8c77e0f 100644 --- a/lib/features/devices/model/group_three_gang_model.dart +++ b/lib/features/devices/model/group_three_gang_model.dart @@ -1,9 +1,9 @@ class GroupThreeGangModel { final String deviceId; final String deviceName; - final bool firstSwitch; - final bool secondSwitch; - final bool thirdSwitch; + bool firstSwitch; + bool secondSwitch; + bool thirdSwitch; GroupThreeGangModel({ required this.deviceId, diff --git a/lib/features/devices/view/widgets/ACs/ac_interface.dart b/lib/features/devices/view/widgets/ACs/ac_interface.dart index 852c3e6..02dee8d 100644 --- a/lib/features/devices/view/widgets/ACs/ac_interface.dart +++ b/lib/features/devices/view/widgets/ACs/ac_interface.dart @@ -11,6 +11,7 @@ import 'package:syncrow_app/features/devices/view/widgets/ACs/ac_interface_temp_ import 'package:syncrow_app/features/shared_widgets/default_container.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; class AcInterface extends StatelessWidget { const AcInterface({super.key, required this.ac}); @@ -22,15 +23,12 @@ class AcInterface extends StatelessWidget { return BlocConsumer( listener: (context, state) { if (state is AcsFailedState) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(state.error), - ), - ); + CustomSnackBar.displaySnackBar(state.errorMessage); } }, builder: (context, state) { AcStatusModel statusModel = AcStatusModel( + uuid: ac.uuid ?? '', acSwitch: true, modeString: 'hot', tempSet: 300, diff --git a/lib/features/devices/view/widgets/ACs/ac_mode_control_unit.dart b/lib/features/devices/view/widgets/ACs/ac_mode_control_unit.dart index 96698bc..24f1737 100644 --- a/lib/features/devices/view/widgets/ACs/ac_mode_control_unit.dart +++ b/lib/features/devices/view/widgets/ACs/ac_mode_control_unit.dart @@ -9,14 +9,12 @@ import 'package:syncrow_app/features/shared_widgets/default_container.dart'; import 'package:syncrow_app/utils/resource_manager/constants.dart'; class ACModeControlUnit extends StatelessWidget { - const ACModeControlUnit({ - super.key, - required this.acStatus, - required this.deviceId, - }); + const ACModeControlUnit( + {super.key, required this.acStatus, required this.deviceId, this.productId = ''}); final AcStatusModel acStatus; final String deviceId; + final String? productId; @override Widget build(BuildContext context) { @@ -28,8 +26,10 @@ class ACModeControlUnit extends StatelessWidget { child: GestureDetector( onTap: () { if (state is! AcChangeLoading && state is! AcsLoadingState) { - BlocProvider.of(context) - .add(ChangeFanSpeed(fanSpeeds: acStatus.acFanSpeed, deviceId: deviceId)); + BlocProvider.of(context).add(ChangeFanSpeed( + fanSpeeds: acStatus.acFanSpeed, + deviceId: deviceId, + productId: productId ?? '')); } // else if (state is AcModifyingState) { // BlocProvider.of(context) @@ -54,8 +54,10 @@ class ACModeControlUnit extends StatelessWidget { // .add(ChangeAcMode(tempModes: state.acStatusModel.acMode)); // } if (state is! AcChangeLoading && state is! AcsLoadingState) { - BlocProvider.of(context) - .add(ChangeAcMode(tempModes: acStatus.acMode, deviceId: deviceId)); + BlocProvider.of(context).add(ChangeAcMode( + tempModes: acStatus.acMode, + deviceId: deviceId, + productId: productId ?? '')); } }, child: DefaultContainer( diff --git a/lib/features/devices/view/widgets/ACs/ac_temp_widget.dart b/lib/features/devices/view/widgets/ACs/ac_temp_widget.dart index 3368898..d56ba22 100644 --- a/lib/features/devices/view/widgets/ACs/ac_temp_widget.dart +++ b/lib/features/devices/view/widgets/ACs/ac_temp_widget.dart @@ -35,10 +35,10 @@ class ACTempWidget extends StatelessWidget { child: InkWell( onTap: () { double tempC = temp / 10; - if (tempC > 20) { - BlocProvider.of(context) - .add(DecreaseCoolToTemp(value: tempC, deviceId: deviceModel.uuid ?? '')); - } + BlocProvider.of(context).add(DecreaseCoolToTemp( + value: tempC, + deviceId: deviceModel.uuid ?? '', + productId: deviceModel.productUuid ?? '')); }, child: SvgPicture.asset( Assets.assetsIconsMinus, @@ -57,10 +57,10 @@ class ACTempWidget extends StatelessWidget { child: InkWell( onTap: () { double tempC = temp / 10; - if (tempC < 30) { - BlocProvider.of(context) - .add(IncreaseCoolToTemp(value: tempC, deviceId: deviceModel.uuid ?? '')); - } + BlocProvider.of(context).add(IncreaseCoolToTemp( + value: tempC, + deviceId: deviceModel.uuid ?? '', + productId: deviceModel.productUuid ?? '')); }, child: SvgPicture.asset( Assets.assetsIconsPlus, diff --git a/lib/features/devices/view/widgets/ACs/acs_list.dart b/lib/features/devices/view/widgets/ACs/acs_list.dart index fd35cb4..e58f679 100644 --- a/lib/features/devices/view/widgets/ACs/acs_list.dart +++ b/lib/features/devices/view/widgets/ACs/acs_list.dart @@ -9,9 +9,9 @@ import 'package:syncrow_app/features/devices/view/widgets/ACs/ac_mode_control_un import 'package:syncrow_app/features/devices/view/widgets/ACs/ac_temp_widget.dart'; import 'package:syncrow_app/features/devices/view/widgets/ACs/universal_ac_temp.dart'; import 'package:syncrow_app/features/devices/view/widgets/universal_switch.dart'; -import 'package:syncrow_app/features/shared_widgets/default_container.dart'; import 'package:syncrow_app/features/shared_widgets/devices_default_switch.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; class ACsList extends StatelessWidget { const ACsList({ @@ -20,7 +20,12 @@ class ACsList extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocConsumer( + listener: (context, state) { + if (state is AcsFailedState) { + CustomSnackBar.displaySnackBar(state.errorMessage); + } + }, builder: (context, state) { List devicesStatuesList = []; List devicesList = []; @@ -35,74 +40,78 @@ class ACsList extends StatelessWidget { temperature = state.temp; } return SingleChildScrollView( - child: state is AcChangeLoading || state is AcsLoadingState - ? const Center( - child: - DefaultContainer(width: 50, height: 50, child: CircularProgressIndicator()), - ) - : Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // universal AC controller - const SizedBox(height: 10), - const BodySmall(text: "All ACs"), - const SizedBox(height: 5), - UniversalSwitch( - allOn: allOn, - ), - const SizedBox(height: 10), - UniversalACTemp( - allTempSame: allTempSame, - temp: temperature, - ), - const SizedBox(height: 10), + child: + // state is AcChangeLoading || state is AcsLoadingState + // ? const Center( + // child: + // DefaultContainer(width: 50, height: 50, child: CircularProgressIndicator()), + // ) + // : + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // universal AC controller + const SizedBox(height: 10), + const BodySmall(text: "All ACs"), + const SizedBox(height: 5), + UniversalSwitch( + allOn: allOn, + ), + const SizedBox(height: 10), + UniversalACTemp( + allTempSame: allTempSame, + temp: temperature, + ), + const SizedBox(height: 10), - // other ACs controls - ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - padding: const EdgeInsets.all(0), - itemCount: devicesList.length, - itemBuilder: (context, index) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox( - height: 10, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - BodySmall(text: devicesList[index].name ?? ''), - ], - ), - const SizedBox(height: 5), - DevicesDefaultSwitch( - switchValue: devicesStatuesList[index].acSwitch, - action: () { - BlocProvider.of(context).add(AcSwitch( - acSwitch: devicesStatuesList[index].acSwitch, - deviceId: devicesList[index].uuid ?? '')); - }, - ), - const SizedBox(height: 10), - ACTempWidget( - deviceModel: devicesList[index], - temp: devicesStatuesList[index].tempSet, - ), - const SizedBox(height: 10), - ACModeControlUnit( - acStatus: devicesStatuesList[index], + // other ACs controls + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.all(0), + itemCount: devicesList.length, + itemBuilder: (context, index) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 10, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + BodySmall(text: devicesList[index].name ?? ''), + ], + ), + const SizedBox(height: 5), + DevicesDefaultSwitch( + switchValue: devicesStatuesList[index].acSwitch, + action: () { + BlocProvider.of(context).add(AcSwitch( + acSwitch: devicesStatuesList[index].acSwitch, deviceId: devicesList[index].uuid ?? '', - ), - const SizedBox(height: 10), - ], - ); - }, - ), - ], - ), + productId: devicesList[index].productUuid ?? '')); + }, + ), + const SizedBox(height: 10), + ACTempWidget( + deviceModel: devicesList[index], + temp: devicesStatuesList[index].tempSet, + ), + const SizedBox(height: 10), + ACModeControlUnit( + acStatus: devicesStatuesList[index], + deviceId: devicesList[index].uuid ?? '', + productId: devicesList[index].productUuid ?? '', + ), + const SizedBox(height: 10), + ], + ); + }, + ), + ], + ), ); }, ); diff --git a/lib/features/devices/view/widgets/ACs/universal_ac_temp.dart b/lib/features/devices/view/widgets/ACs/universal_ac_temp.dart index 5a6e735..37779b6 100644 --- a/lib/features/devices/view/widgets/ACs/universal_ac_temp.dart +++ b/lib/features/devices/view/widgets/ACs/universal_ac_temp.dart @@ -29,9 +29,7 @@ class UniversalACTemp extends StatelessWidget { child: InkWell( onTap: () { double temperature = temp / 10; - if (temperature < 30) { - BlocProvider.of(context).add(DecreaseAllTemp(value: temperature)); - } + BlocProvider.of(context).add(DecreaseAllTemp(value: temperature)); }, child: SvgPicture.asset( Assets.assetsIconsMinus, @@ -50,9 +48,7 @@ class UniversalACTemp extends StatelessWidget { child: InkWell( onTap: () { double temperature = temp / 10; - if (temperature > 20) { - BlocProvider.of(context).add(IncreaseAllTemp(value: temperature)); - } + BlocProvider.of(context).add(IncreaseAllTemp(value: temperature)); }, child: SvgPicture.asset( Assets.assetsIconsPlus, diff --git a/lib/features/devices/view/widgets/three_gang/three_gang_list.dart b/lib/features/devices/view/widgets/three_gang/three_gang_list.dart index 7bbf700..0d9b143 100644 --- a/lib/features/devices/view/widgets/three_gang/three_gang_list.dart +++ b/lib/features/devices/view/widgets/three_gang/three_gang_list.dart @@ -4,7 +4,6 @@ import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_blo import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_event.dart'; import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_state.dart'; import 'package:syncrow_app/features/devices/model/group_three_gang_model.dart'; -import 'package:syncrow_app/features/shared_widgets/default_container.dart'; import 'package:syncrow_app/features/shared_widgets/devices_default_switch.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; @@ -18,78 +17,71 @@ class ThreeGangList extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - return state is LoadingNewSate - ? const Center( - child: DefaultContainer(width: 50, height: 50, child: CircularProgressIndicator()), - ) - : SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const SizedBox(height: 10), - const BodySmall(text: 'All Lights'), - const SizedBox(height: 5), - DevicesDefaultSwitch( - switchValue: allSwitches, - action: () { - BlocProvider.of(context).add(GroupAllOnEvent()); - }, - secondAction: () { - BlocProvider.of(context).add(GroupAllOffEvent()); - }, - ), - ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - padding: const EdgeInsets.all(0), - itemCount: threeGangList.length, - itemBuilder: (context, index) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 10), - BodySmall(text: '${threeGangList[index].deviceName} beside light'), - const SizedBox(height: 5), - DevicesDefaultSwitch( - switchValue: threeGangList[index].firstSwitch, - action: () { - BlocProvider.of(context).add( - ChangeFirstSwitchStatusEvent( - value: threeGangList[index].firstSwitch, - deviceId: threeGangList[index].deviceId)); - }, - ), - const SizedBox(height: 10), - BodySmall(text: '${threeGangList[index].deviceName} ceiling light'), - const SizedBox(height: 5), - DevicesDefaultSwitch( - switchValue: threeGangList[index].secondSwitch, - action: () { - BlocProvider.of(context).add( - ChangeSecondSwitchStatusEvent( - value: threeGangList[index].secondSwitch, - deviceId: threeGangList[index].deviceId)); - }, - ), - const SizedBox(height: 10), - BodySmall(text: '${threeGangList[index].deviceName} spotlight'), - const SizedBox(height: 5), - DevicesDefaultSwitch( - switchValue: threeGangList[index].thirdSwitch, - action: () { - BlocProvider.of(context).add( - ChangeThirdSwitchStatusEvent( - value: threeGangList[index].thirdSwitch, - deviceId: threeGangList[index].deviceId)); - }, - ), - ], - ); - }, - ), - ], - ), - ); + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 10), + const BodySmall(text: 'All Lights'), + const SizedBox(height: 5), + DevicesDefaultSwitch( + switchValue: allSwitches, + action: () { + BlocProvider.of(context).add(GroupAllOnEvent()); + }, + secondAction: () { + BlocProvider.of(context).add(GroupAllOffEvent()); + }, + ), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.all(0), + itemCount: threeGangList.length, + itemBuilder: (context, index) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + BodySmall(text: '${threeGangList[index].deviceName} beside light'), + const SizedBox(height: 5), + DevicesDefaultSwitch( + switchValue: threeGangList[index].firstSwitch, + action: () { + BlocProvider.of(context).add(ChangeFirstSwitchStatusEvent( + value: threeGangList[index].firstSwitch, + deviceId: threeGangList[index].deviceId)); + }, + ), + const SizedBox(height: 10), + BodySmall(text: '${threeGangList[index].deviceName} ceiling light'), + const SizedBox(height: 5), + DevicesDefaultSwitch( + switchValue: threeGangList[index].secondSwitch, + action: () { + BlocProvider.of(context).add(ChangeSecondSwitchStatusEvent( + value: threeGangList[index].secondSwitch, + deviceId: threeGangList[index].deviceId)); + }, + ), + const SizedBox(height: 10), + BodySmall(text: '${threeGangList[index].deviceName} spotlight'), + const SizedBox(height: 5), + DevicesDefaultSwitch( + switchValue: threeGangList[index].thirdSwitch, + action: () { + BlocProvider.of(context).add(ChangeThirdSwitchStatusEvent( + value: threeGangList[index].thirdSwitch, + deviceId: threeGangList[index].deviceId)); + }, + ), + ], + ); + }, + ), + ], + ), + ); }, ); } diff --git a/lib/features/menu/bloc/menu_cubit.dart b/lib/features/menu/bloc/menu_cubit.dart index a51a41e..068d947 100644 --- a/lib/features/menu/bloc/menu_cubit.dart +++ b/lib/features/menu/bloc/menu_cubit.dart @@ -7,85 +7,6 @@ class MenuCubit extends Cubit { static MenuCubit of(context) => BlocProvider.of(context); - // List menuLists = [ - // MenuListModel( - // label: 'Home Management', - // listItems: [ - // ListItemModel( - // label: 'Create a Home', - // ), - // ListItemModel( - // label: 'Join a Home', - // ), - // ListItemModel( - // label: 'Manage Your Home', - // ), - // ], - // ), - // MenuListModel( - // label: 'General Settings', - // listItems: [ - // ListItemModel( - // label: 'Voice Assistant', - // ), - // ListItemModel( - // label: 'Temperature unit', - // ), - // ListItemModel( - // label: 'Touch tone on panel', - // ), - // ListItemModel( - // label: 'Language', - // ), - // ListItemModel( - // label: 'Network diagnosis', - // ), - // ListItemModel( - // label: 'Clear cache', - // ), - // ], - // ), - // MenuListModel( - // label: 'Messages Center', - // listItems: [ - // ListItemModel( - // label: 'Alerts', - // ), - // ListItemModel( - // label: 'Messages', - // ), - // ListItemModel( - // label: 'FAQs', - // ), - // ListItemModel( - // label: 'Help & Feedback', - // ), - // ], - // ), - // MenuListModel( - // label: 'Security and Privacy', - // listItems: [ - // ListItemModel( - // label: 'Security', - // ), - // ListItemModel( - // label: 'privacy', - // ), - // ], - // ), - // MenuListModel( - // label: 'Legal Information', - // listItems: [ - // ListItemModel( - // label: 'About', - // ), - // ListItemModel( - // label: 'Privacy Policy', - // ), - // ListItemModel( - // label: 'User Agreement', - // ), - // ], - // ), - // ]; + String name = ''; + } diff --git a/lib/features/menu/bloc/profile_bloc/profile_bloc.dart b/lib/features/menu/bloc/profile_bloc/profile_bloc.dart new file mode 100644 index 0000000..3e7e76e --- /dev/null +++ b/lib/features/menu/bloc/profile_bloc/profile_bloc.dart @@ -0,0 +1,281 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:device_info_plus/device_info_plus.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_event.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_state.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/region_model.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/time_zone_model.dart'; +import 'package:syncrow_app/services/api/profile_api.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class ProfileBloc extends Bloc { + + bool isSaving = false; + bool editName = false; + final FocusNode focusNode = FocusNode(); + File? image; + final ImagePicker _picker = ImagePicker(); + String timeZoneSelected = ''; + String regionSelected = ''; + final TextEditingController searchController = TextEditingController(); + final TextEditingController nameController = TextEditingController(text: '${HomeCubit.user!.firstName} ${HomeCubit.user!.lastName}'); + List? timeZoneList; + List? regionList; + + + ProfileBloc() : super(InitialState()) { + on(_fetchUserInfo); + on(_fetchTimeZone); + on(_fetchRegion); + on(saveName); + on(_selectImage); + on(_changeName); + on(selectTimeZone); + on(searchRegion); + on(searchTimeZone); + on(selectRegion); + } + + Future saveName(SaveNameEvent event, Emitter emit) async { + if (_validateInputs()) return; + try { + add(const ChangeNameEvent(value: false)); + isSaving = true; + emit(LoadingInitialState()); + final fullName = nameController.text; + final nameParts = fullName.split(' '); + final firstName = nameParts[0]; + final lastName = nameParts.length > 1 ? nameParts[1] : ''; + var response = await ProfileApi.saveName(firstName: firstName, lastName: lastName); + add(InitialProfileEvent()); + final homeCubit = event.context.read(); + await homeCubit.fetchUserInfo(); + Navigator.of(event.context).pop(true); + CustomSnackBar.displaySnackBar('Save Successfully'); + emit(SaveState()); + } catch (_) { + // Handle the error + } finally { + isSaving = false; + } + } + + void _changeName(ChangeNameEvent event, Emitter emit) { + emit(LoadingInitialState()); + editName = event.value!; + if (editName) { + Future.delayed(const Duration(milliseconds: 500), () { + focusNode.requestFocus(); + }); + }else { + focusNode.unfocus(); + } + emit(NameEditingState(editName: editName)); + } + + void _fetchUserInfo(InitialProfileEvent event, Emitter emit) async { + try { + emit(LoadingInitialState()); + HomeCubit.user = await ProfileApi().fetchUserInfo(HomeCubit.user!.uuid); + emit(SaveState()); + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + return; + } + } + + Future _fetchTimeZone(TimeZoneInitialEvent event, Emitter emit) async { + emit(LoadingInitialState()); + try { + timeZoneList = await ProfileApi.fetchTimeZone(); + emit(UpdateState(timeZoneList: timeZoneList!)); + return timeZoneList; + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + return; + } + } + + + Future selectTimeZone(SelectTimeZoneEvent event, Emitter emit) async { + try { + emit(LoadingInitialState()); + timeZoneSelected = event.val; + await ProfileApi.saveTimeZone(regionUuid: event.val); + CustomSnackBar.displaySnackBar('Save Successfully'); + emit(SaveState()); + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + } + } + + Future selectRegion(SelectRegionEvent event, Emitter emit) async { + try { + emit(LoadingInitialState()); + await ProfileApi.saveRegion(regionUuid:event.val ); + CustomSnackBar.displaySnackBar('Save Successfully'); + emit(SaveState()); + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + return; + } + } + + Future searchRegion(SearchRegionEvent event, Emitter emit) async { + emit(LoadingInitialState()); + final query = event.query.toLowerCase(); + if(event.query.isNotEmpty){ + final filteredRegions = regionList?.where((region) { + return region.name.toLowerCase().contains(query); + }).toList() ?? []; + regionList = filteredRegions;// Assume this fetches the regions + emit(RegionsLoadedState(regions: filteredRegions)); + }else{ + regionList = await ProfileApi.fetchRegion(); + emit(RegionsLoadedState(regions: regionList!)); + } + } + + Future searchTimeZone(SearchTimeZoneEvent event, Emitter emit) async { + emit(LoadingInitialState()); + final query = event.query.toLowerCase(); + if(event.query.isNotEmpty){ + final filtered = timeZoneList?.where((region) { + return region.name.toLowerCase().contains(query); + }).toList() ?? []; + timeZoneList = filtered; + emit(TimeZoneLoadedState(regions: filtered)); + }else{ + timeZoneList = await ProfileApi.fetchTimeZone(); + emit(UpdateState(timeZoneList: timeZoneList!)); + } + } + + + void _fetchRegion(RegionInitialEvent event, Emitter emit) async { + try { + emit(LoadingInitialState()); + regionList = await ProfileApi.fetchRegion(); + emit(RegionsLoadedState(regions: regionList!)); + } catch (e) { + emit(FailedState(errorMessage: e.toString())); + } + } + + Future _selectImage(SelectImageEvent event, Emitter emit) async { + if (await _requestPermission()) { + emit(ChangeImageState()); + final pickedFile = await _picker.pickImage(source: ImageSource.gallery); + if (pickedFile != null) { + image = File(pickedFile.path); + final bytes = image!.readAsBytesSync().lengthInBytes; + final kb = bytes / 1024; + final mb = kb / 1024; + if(mb>1){ + image=null; + CustomSnackBar.displaySnackBar('Image size must be 1 MB or less'); + }else{ + await _saveImage(); + } + } else { + print('No image selected.'); + } + emit(ImageSelectedState()); + } else { + _showPermissionDeniedDialog(event.context); + } + } + + Future _saveImage() async { + emit(LoadingInitialState()); + List imageBytes = image!.readAsBytesSync(); + String base64Image = base64Encode(imageBytes); + print(base64Image); + var response = await ProfileApi.saveImage(base64Image); + emit(ImageSelectedState()); + } + + void _showPermissionDeniedDialog(BuildContext context) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Permission Denied'), + content: const Text( + 'Photo access is required to select an image. Please allow photo access in the app settings.'), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + openAppSettings(); + }, + child: const Text('Settings'), + ), + ], + ); + }, + ); + } + + + + bool _validateInputs() { + if (nameController.text.length < 2) { + CustomSnackBar.displaySnackBar('Name Must More than 2 '); + return true; + } + return false; + } + + Future _requestPermission() async { + DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); + if (Platform.isAndroid ) { + AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; + PermissionStatus status = await Permission.photos.status; + if(androidInfo.version.sdkInt<= 33){ + if (status.isDenied) { + PermissionStatus status = await Permission.storage.request(); + if (status.isGranted) { + return true; + } else { + return false; + } + } + }else{ + if (status.isGranted) { + return true; + } else if (status.isDenied) { + PermissionStatus status = await Permission.photos.request(); + if (status.isGranted) { + return true; + } else { + return false; + } + } + } + return false; + } else { + SharedPreferences sharedPreferences = await SharedPreferences.getInstance(); + bool firstClick = sharedPreferences.getBool('firstPermission') ?? true; + await sharedPreferences.setBool('firstPermission', false); + if (firstClick == false) { + var status = await Permission.photos.status; + return status.isGranted; + } else { + return true; + } + } + } + +} diff --git a/lib/features/menu/bloc/profile_bloc/profile_event.dart b/lib/features/menu/bloc/profile_bloc/profile_event.dart new file mode 100644 index 0000000..15d9336 --- /dev/null +++ b/lib/features/menu/bloc/profile_bloc/profile_event.dart @@ -0,0 +1,76 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.dart'; + +abstract class ProfileEvent extends Equatable { + const ProfileEvent(); + + @override + List get props => []; +} + +class InitialProfileEvent extends ProfileEvent {} + +class TimeZoneInitialEvent extends ProfileEvent {} + +class ChangeNameEvent extends ProfileEvent { + final bool? value; + const ChangeNameEvent({ this.value}); +} + +class RegionInitialEvent extends ProfileEvent {} + + +class SaveNameEvent extends ProfileEvent { + final BuildContext context; + const SaveNameEvent({required this.context}); + @override + List get props => [context]; +} + + +class SelectImageEvent extends ProfileEvent { + final BuildContext context; + final bool isSelected; + const SelectImageEvent({required this.context,required this.isSelected}); + @override + List get props => [context,isSelected]; +} + + +class ToggleRepeatEvent extends ProfileEvent {} + +class SelectTimeZoneEvent extends ProfileEvent { + final String val; + final BuildContext context; + const SelectTimeZoneEvent({required this.val,required this.context}); + @override + List get props => [val]; +} + +class SelectRegionEvent extends ProfileEvent { + final String val; + final BuildContext context; + const SelectRegionEvent({required this.val,required this.context}); + @override + List get props => [val,context]; +} + + +class SearchRegionEvent extends ProfileEvent { + final String query; + + const SearchRegionEvent({required this.query}); + @override + List get props => [query]; +} + +class SearchTimeZoneEvent extends ProfileEvent { + final String query; + + const SearchTimeZoneEvent({required this.query}); + @override + List get props => [query]; +} + + + diff --git a/lib/features/menu/bloc/profile_bloc/profile_state.dart b/lib/features/menu/bloc/profile_bloc/profile_state.dart new file mode 100644 index 0000000..19b1f5c --- /dev/null +++ b/lib/features/menu/bloc/profile_bloc/profile_state.dart @@ -0,0 +1,57 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/region_model.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/time_zone_model.dart'; + +class ProfileState extends Equatable { + const ProfileState(); + + @override + List get props => []; +} + +class InitialState extends ProfileState {} + +class LoadingInitialState extends ProfileState {} + +class UpdateState extends ProfileState { + final List timeZoneList; + + UpdateState({required this.timeZoneList}); +} + +class NameEditingState extends ProfileState { + final bool editName; + + NameEditingState({required this.editName}); +} + + + +class FailedState extends ProfileState { + final String errorMessage; + + const FailedState({required this.errorMessage}); + + @override + List get props => [errorMessage]; +} + + +class ImageSelectedState extends ProfileState {} + +class ChangeImageState extends ProfileState {} + +class SaveState extends ProfileState {} + +class LoadingSaveState extends ProfileState {} +class RegionsLoadedState extends ProfileState { + final List regions; + + const RegionsLoadedState({required this.regions}); +} + +class TimeZoneLoadedState extends ProfileState { + final List regions; + + const TimeZoneLoadedState({required this.regions}); +} \ No newline at end of file diff --git a/lib/features/menu/bloc/profile_bloc/region_model.dart b/lib/features/menu/bloc/profile_bloc/region_model.dart new file mode 100644 index 0000000..fd6306a --- /dev/null +++ b/lib/features/menu/bloc/profile_bloc/region_model.dart @@ -0,0 +1,25 @@ + + +class RegionModel { + final String name; + final String id; + + RegionModel({ + required this.name, + required this.id, + }); + + factory RegionModel.fromJson(Map json) { + return RegionModel( + name: json['regionName'], + id: json['uuid'].toString(), // Ensure id is a String + ); + } + + Map toJson() { + return { + 'regionName': name, + 'uuid': id, + }; + } +} diff --git a/lib/features/menu/bloc/profile_bloc/time_zone_model.dart b/lib/features/menu/bloc/profile_bloc/time_zone_model.dart new file mode 100644 index 0000000..b37049b --- /dev/null +++ b/lib/features/menu/bloc/profile_bloc/time_zone_model.dart @@ -0,0 +1,27 @@ +class TimeZone { + final String name; + final String offset; + final String id; + + TimeZone({ + required this.name, + required this.offset, + required this.id, + }); + + factory TimeZone.fromJson(Map json) { + return TimeZone( + name: json['cityName'], + offset: json['timeZoneOffset'], + id: json['uuid'].toString(), // Ensure id is a String + ); + } + + Map toJson() { + return { + 'name': name, + 'offset': offset, + 'id': id, + }; + } +} diff --git a/lib/features/menu/view/menu_view.dart b/lib/features/menu/view/menu_view.dart index 0d3dc83..9b2cf56 100644 --- a/lib/features/menu/view/menu_view.dart +++ b/lib/features/menu/view/menu_view.dart @@ -6,6 +6,7 @@ import 'package:syncrow_app/features/menu/view/widgets/menu_list.dart'; import 'package:syncrow_app/features/menu/view/widgets/profile/profile_tab.dart'; import 'package:syncrow_app/features/shared_widgets/default_container.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; import 'package:syncrow_app/utils/context_extension.dart'; import 'package:syncrow_app/utils/resource_manager/constants.dart'; @@ -20,11 +21,12 @@ class MenuView extends StatelessWidget { builder: (context, state) { return BlocBuilder( builder: (context, state) { + final profileBloc = BlocProvider.of(context); return SingleChildScrollView( physics: const BouncingScrollPhysics(), child: Column( children: [ - const ProfileTab(), + ProfileTab(), for (var section in menuSections) MenuList( section: section, @@ -32,6 +34,11 @@ class MenuView extends StatelessWidget { const SizedBox( height: 15, ), + const BodyMedium( + text: String.fromEnvironment('FLAVOR', defaultValue: 'production')), + const SizedBox( + height: 15, + ), InkWell( onTap: () { AuthCubit.get(context).logout(); diff --git a/lib/features/menu/view/widgets/create_home/create_home_view.dart b/lib/features/menu/view/widgets/create_home/create_home_view.dart index 201f80b..e11bede 100644 --- a/lib/features/menu/view/widgets/create_home/create_home_view.dart +++ b/lib/features/menu/view/widgets/create_home/create_home_view.dart @@ -1,7 +1,6 @@ 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'; @@ -11,8 +10,6 @@ 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/generated/assets.dart'; -import 'package:syncrow_app/navigation/navigation_service.dart'; -import 'package:syncrow_app/navigation/routing_constants.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'; diff --git a/lib/features/menu/view/widgets/profile/profile_tab.dart b/lib/features/menu/view/widgets/profile/profile_tab.dart index 2d86229..19c091a 100644 --- a/lib/features/menu/view/widgets/profile/profile_tab.dart +++ b/lib/features/menu/view/widgets/profile/profile_tab.dart @@ -1,42 +1,60 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; import 'package:syncrow_app/features/menu/view/widgets/profile/profile_view.dart'; import 'package:syncrow_app/features/shared_widgets/default_container.dart'; -import 'package:syncrow_app/features/shared_widgets/syncrow_logo.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'; class ProfileTab extends StatelessWidget { - const ProfileTab({ - super.key, - }); - + const ProfileTab({super.key,}); @override Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return _buildProfileContent(context ); + }, + ); + } + + Widget _buildProfileContent(BuildContext context) { + final homeCubit = context.read(); return Padding( padding: const EdgeInsets.symmetric(vertical: 10), child: InkWell( onTap: () { - Navigator.of(context).push( + Navigator.of(context) + .push( MaterialPageRoute( builder: (context) => const ProfileView(), ), - ); + ).then((result) { + context.read().fetchUserInfo(); + }); }, - child: const Stack( + child: Stack( children: [ Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - SizedBox(height: 20), + const SizedBox(height: 20), DefaultContainer( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - BodyMedium( - text: "Karim", - fontWeight: FontWeight.bold, + Row( + children: [ + BodyMedium( + text: '${HomeCubit.user!.firstName ?? ''} ', + fontWeight: FontWeight.bold, + ), + BodyMedium( + text: HomeCubit.user!.lastName ?? '', + fontWeight: FontWeight.bold, + ), + ], ), - BodySmall(text: "Syncrow Account") + const BodySmall(text: "Syncrow Account"), ], ), ), @@ -51,7 +69,16 @@ class ProfileTab extends StatelessWidget { child: CircleAvatar( radius: 37, backgroundColor: Colors.grey, - child: SyncrowLogo(), + child: ClipOval( + child: HomeCubit.user?.profilePicture != null + ? Image.memory( + HomeCubit.user!.profilePicture!, + fit: BoxFit.cover, + width: 110, + height: 110, + ) + : Icon(Icons.person, size: 70), // Fallback if no image + ), ), ), ), diff --git a/lib/features/menu/view/widgets/profile/profile_view.dart b/lib/features/menu/view/widgets/profile/profile_view.dart index 91a346e..760bbce 100644 --- a/lib/features/menu/view/widgets/profile/profile_view.dart +++ b/lib/features/menu/view/widgets/profile/profile_view.dart @@ -1,9 +1,16 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_bloc.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_event.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_state.dart'; +import 'package:syncrow_app/features/menu/view/widgets/profile/region_page.dart'; +import 'package:syncrow_app/features/menu/view/widgets/profile/time_zone_screen_page.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/features/shared_widgets/text_widgets/title_medium.dart'; -import 'package:syncrow_app/utils/context_extension.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; class ProfileView extends StatelessWidget { @@ -11,118 +18,171 @@ class ProfileView extends StatelessWidget { @override Widget build(BuildContext context) { - return DefaultScaffold( - title: 'Profile Page', - child: Column( - children: [ - //profile pic - const SizedBox.square( - dimension: 120, - child: CircleAvatar( - backgroundColor: Colors.white, - child: SizedBox.square( - dimension: 115, - child: CircleAvatar( - backgroundColor: Colors.grey, - child: FlutterLogo(), + + return BlocProvider( + create: (BuildContext context) => ProfileBloc()..add(InitialProfileEvent()), + child: BlocConsumer( + listener: (context, state) {}, + builder: (context, state) { + final profileBloc = BlocProvider.of(context); + return DefaultScaffold( + title: 'Syncrow Account', + child: + state is LoadingInitialState + ? const Center(child: CircularProgressIndicator()): + Column( + children: [ + SizedBox( + height: MediaQuery.of(context).size.height * 0.05, ), - ), + InkWell( + onTap: () { + profileBloc.add( + SelectImageEvent(context: context, isSelected: false)); + }, + child: SizedBox.square( + dimension: 125, + child: CircleAvatar( + backgroundColor: Colors.white, + child: SizedBox.square( + dimension: 120, + child: CircleAvatar( + backgroundColor: Colors.grey, + backgroundImage: profileBloc.image == null + ? null + : FileImage(profileBloc.image!), + child: profileBloc.image != null + ? null + :HomeCubit.user!.profilePicture != null + ? ClipOval( + child: Image.memory( + HomeCubit.user!.profilePicture!, + fit: BoxFit.cover, + width: 110, + height: 110, + ), + ) + : null, + ), + ), + ), + ), + ), + const SizedBox(height: 20), + SizedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IntrinsicWidth( + child: TextFormField( + maxLength: 30, + style: const TextStyle( + color: Colors.black, + ), + textAlign: TextAlign.center, + focusNode: profileBloc.focusNode, + controller: profileBloc.nameController, + enabled: profileBloc.editName, + onEditingComplete: () { + profileBloc.add(SaveNameEvent(context: context)); + }, + decoration: const InputDecoration( + hintText: "Your Name", + border: InputBorder.none, + fillColor: Colors.white10, + counterText: '', // Hides the character count + ), + ), + ), + const SizedBox(width: 5), + InkWell( + onTap: () { + profileBloc.add(const ChangeNameEvent(value: true)); + }, + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 10), + child: Icon( + Icons.edit_outlined, + size: 20, + color: ColorsManager.textPrimaryColor, + ), + ), + ), + ], + ), + ), + const SizedBox(height: 10), + // Info + DefaultContainer( + padding: const EdgeInsets.symmetric( + horizontal: 25, + vertical: 5, + ), + child: + + Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const RegionPage(), + ), + ).then((result) { + profileBloc.add(InitialProfileEvent()); + }); + }, + child: Padding( + padding: const EdgeInsets.only(top: 20, bottom: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const BodyMedium(text: 'Region '), + Flexible(child: BodyMedium(text: HomeCubit.user!.regionName ?? 'No Region')), + ], + ), + ), + ), + Container( + height: 1, + color: ColorsManager.greyColor, + ), + InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const TimeZoneScreenPage(), + ), + ).then((result) { + profileBloc.add(InitialProfileEvent()); + }); + }, + child: Padding( + padding: const EdgeInsets.only(top: 15, bottom: 15), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const BodyMedium(text: 'Time Zone '), + Flexible( + child: BodyMedium(text: HomeCubit.user!.timeZone ?? "No Time Zone"), + ), + ], + ), + ), + ), + ], + ) + + ), + ], ), - ), - const SizedBox(height: 20), - //name - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const TitleMedium(text: 'Karim'), - const SizedBox( - width: 5, - ), - InkWell( - onTap: () { - //TODO: Implement edit name - }, - child: const Icon( - Icons.edit_outlined, - size: 20, - color: ColorsManager.textPrimaryColor, - ), - ), - ], - ), - const SizedBox(height: 10), - //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: 'Email '), - Flexible( - child: TextField( - textAlign: TextAlign.end, - decoration: InputDecoration( - hintText: ' Test@test.com', - hintStyle: - context.bodyMedium.copyWith(color: Colors.grey), - border: InputBorder.none, - ), - ), - ), - ], - ), - Container( - height: 1, - color: ColorsManager.greyColor, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const BodyMedium(text: 'Region '), - Flexible( - child: TextField( - textAlign: TextAlign.end, - decoration: InputDecoration( - hintText: 'United Arab Emirates', - hintStyle: - context.bodyMedium.copyWith(color: Colors.grey), - border: InputBorder.none, - ), - ), - ), - ], - ), - Container( - height: 1, - color: ColorsManager.greyColor, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const BodyMedium(text: 'Time Zone '), - Flexible( - child: TextField( - textAlign: TextAlign.end, - decoration: InputDecoration( - hintText: 'GMT +4', - hintStyle: - context.bodyMedium.copyWith(color: Colors.grey), - border: InputBorder.none, - ), - ), - ), - ], - ), - ], - )), - ], + ); + }, ), ); } diff --git a/lib/features/menu/view/widgets/profile/region_page.dart b/lib/features/menu/view/widgets/profile/region_page.dart new file mode 100644 index 0000000..16fef45 --- /dev/null +++ b/lib/features/menu/view/widgets/profile/region_page.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_bloc.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_event.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_state.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/resource_manager/color_manager.dart'; + +class RegionPage extends StatelessWidget { + const RegionPage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => ProfileBloc()..add(RegionInitialEvent()), + child: BlocConsumer(listener: (context, state) { + if (state is FailedState) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.errorMessage), + backgroundColor: Colors.red, + ), + ); + } + }, + builder: (context, state) { + final profileBloc = BlocProvider.of(context); + final regionList = profileBloc.regionList ?? []; // Safeguard against null + return DefaultScaffold( + padding: const EdgeInsets.all(0), + title: 'Region', + child: state is LoadingInitialState + ? const Center(child: CircularProgressIndicator()) + : Column( + children: [ + TextFormField( + controller:profileBloc.searchController , + onChanged: (value) { + profileBloc.add(SearchRegionEvent(query: value)); + }, + decoration: const InputDecoration( + prefixIcon: Icon(Icons.search), + hintText: 'Search', + fillColor: ColorsManager.textGray, + ), + ), + const SizedBox(height: 10), + Expanded( + child: Container( + decoration: const BoxDecoration( + color: ColorsManager.onPrimaryColor, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + child: ListView.builder( + itemCount: regionList.length, + itemBuilder: (context, index) { + return InkWell( + onTap: () { + profileBloc.add(SelectRegionEvent( + val: regionList[index].id, context: context)); + }, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: SizedBox( + height: 45, + child: ListTile( + contentPadding: EdgeInsets.zero, + // trailing: BodyMedium( + // text: regionList[index].offset, + // fontSize: 13, + // fontColor: ColorsManager.textGray,), + leading: BodyMedium( + fontSize: 15, + text: regionList[index].name,),), + ), + ), + const Divider(color: ColorsManager.textGray), // Divider between items + ], + ), + ), + ); + }, + ), + ), + ), + ], + ), + ); + })); + } +} diff --git a/lib/features/menu/view/widgets/profile/time_zone_screen_page.dart b/lib/features/menu/view/widgets/profile/time_zone_screen_page.dart new file mode 100644 index 0000000..fe80b0f --- /dev/null +++ b/lib/features/menu/view/widgets/profile/time_zone_screen_page.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_bloc.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_event.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_state.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/resource_manager/color_manager.dart'; + +class TimeZoneScreenPage extends StatelessWidget { + const TimeZoneScreenPage({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => ProfileBloc()..add(TimeZoneInitialEvent()), + child: + BlocConsumer(listener: (context, state) { + if (state is FailedState) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.errorMessage), + backgroundColor: Colors.red, + ), + ); + } + }, + builder: (context, state) { + final profileBloc = BlocProvider.of(context); + final timeZoneList = profileBloc.timeZoneList ?? []; // Safeguard against null + return DefaultScaffold( + padding: const EdgeInsets.all(0), + title: 'Time Zone', + child: state is LoadingInitialState + ? const Center(child: CircularProgressIndicator()) + : Column( + children: [ + TextFormField( + controller:profileBloc.searchController , + onChanged: (value) { + profileBloc.add(SearchTimeZoneEvent(query: value)); + }, + decoration: const InputDecoration( + prefixIcon: Icon(Icons.search), + hintText: 'Search', + fillColor: ColorsManager.textGray, + ), + ), + const SizedBox(height: 10), + Expanded( + child: Container( + decoration: const BoxDecoration( + color: ColorsManager.onPrimaryColor, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + child: ListView.builder( + itemCount: timeZoneList.length, + itemBuilder: (context, index) { + return InkWell( + onTap: () { + profileBloc.add(SelectTimeZoneEvent( + val: timeZoneList[index].id, + context: context)); + }, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: SizedBox( + height: 45, + child: ListTile( + contentPadding: EdgeInsets.zero, + trailing: BodyMedium( + text: timeZoneList[index].offset, + fontSize: 13, + fontColor: ColorsManager.textGray,), + leading: BodyMedium( + fontSize: 15, + text: timeZoneList[index].name,),), + ), + ), + const Divider(color: ColorsManager.textGray), // Divider between items + ], + ), + ), + ); + }, + ), + ), + ), + ], + ), + ); + }) + ); + } +} diff --git a/lib/features/scene/view/scene_view.dart b/lib/features/scene/view/scene_view.dart index 96e5cdf..c924cbc 100644 --- a/lib/features/scene/view/scene_view.dart +++ b/lib/features/scene/view/scene_view.dart @@ -29,24 +29,23 @@ class SceneView extends StatelessWidget { if (state.success) { BlocProvider.of(context) .add(LoadScenes(HomeCubit.getInstance().selectedSpace!.id!)); - BlocProvider.of(context).add( - LoadAutomation(HomeCubit.getInstance().selectedSpace!.id!)); + BlocProvider.of(context) + .add(LoadAutomation(HomeCubit.getInstance().selectedSpace!.id!)); } } if (state is CreateSceneWithTasks) { if (state.success == true) { BlocProvider.of(context) .add(LoadScenes(HomeCubit.getInstance().selectedSpace!.id!)); - BlocProvider.of(context).add( - LoadAutomation(HomeCubit.getInstance().selectedSpace!.id!)); + BlocProvider.of(context) + .add(LoadAutomation(HomeCubit.getInstance().selectedSpace!.id!)); } } return BlocListener( listener: (context, state) { if (state is SceneTriggerSuccess) { context.showCustomSnackbar( - message: - 'Scene ${state.sceneName} triggered successfully!'); + message: 'Scene ${state.sceneName} triggered successfully!'); } }, child: HomeCubit.getInstance().spaces?.isEmpty ?? true @@ -76,32 +75,27 @@ class SceneView extends StatelessWidget { return pageType ? Expanded( child: SceneListview( - scenes: state.scenes, + scenes: scenes, loadingSceneId: state.loadingSceneId, )) : Expanded( child: ListView( children: [ ExpansionTile( - tilePadding: - const EdgeInsets.symmetric( - horizontal: 6), + tilePadding: const EdgeInsets.symmetric(horizontal: 6), initiallyExpanded: true, iconColor: ColorsManager.grayColor, - title: const BodySmall( - text: 'Tap to run routines'), + title: const BodySmall(text: 'Tap to run routines'), children: [ scenes.isNotEmpty ? SceneGrid( scenes: scenes, - loadingSceneId: - state.loadingSceneId, + loadingSceneId: state.loadingSceneId, disablePLayButton: false, ) : const Center( child: BodyMedium( - text: - 'No scenes have been added yet', + text: 'No scenes have been added yet', ), ), const SizedBox( @@ -112,23 +106,18 @@ class SceneView extends StatelessWidget { ExpansionTile( initiallyExpanded: true, iconColor: ColorsManager.grayColor, - tilePadding: - const EdgeInsets.symmetric( - horizontal: 6), - title: const BodySmall( - text: 'Automation'), + tilePadding: const EdgeInsets.symmetric(horizontal: 6), + title: const BodySmall(text: 'Automation'), children: [ automationList.isNotEmpty ? SceneGrid( scenes: automationList, - loadingSceneId: - state.loadingSceneId, + loadingSceneId: state.loadingSceneId, disablePLayButton: true, ) : const Center( child: BodyMedium( - text: - 'No automations have been added yet', + text: 'No automations have been added yet', ), ), const SizedBox( diff --git a/lib/main.dart b/lib/main.dart index 786eeda..ec2a3ea 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:syncrow_app/firebase_options.dart'; import 'package:syncrow_app/services/locator.dart'; import 'package:syncrow_app/utils/bloc_observer.dart'; @@ -15,9 +16,8 @@ void main() { //to catch all the errors in the app and send them to firebase runZonedGuarded(() async { //to load the environment variables - // const environment = - // String.fromEnvironment('FLAVOR', defaultValue: 'production'); - // await dotenv.load(fileName: '.env.$environment'); + const environment = String.fromEnvironment('FLAVOR', defaultValue: 'production'); + await dotenv.load(fileName: '.env.$environment'); // //this is to make the app work with the self-signed certificate // HttpOverrides.global = MyHttpOverrides(); diff --git a/lib/my_app.dart b/lib/my_app.dart index e8e4f4c..a49fec7 100644 --- a/lib/my_app.dart +++ b/lib/my_app.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/profile_bloc.dart'; import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; import 'package:syncrow_app/navigation/navigation_service.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; @@ -26,6 +27,7 @@ class MyApp extends StatelessWidget { BlocProvider(create: (context) => AuthCubit()), BlocProvider(create: (context) => CreateSceneBloc()), BlocProvider(create: (context) => SceneBloc()), + BlocProvider(create: (context) => ProfileBloc()), ], child: MaterialApp( diff --git a/lib/services/api/api_links_endpoints.dart b/lib/services/api/api_links_endpoints.dart index 513a9e6..8e22e5d 100644 --- a/lib/services/api/api_links_endpoints.dart +++ b/lib/services/api/api_links_endpoints.dart @@ -1,184 +1,172 @@ -import 'package:flutter/foundation.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; abstract class ApiEndpoints { - static const String baseUrl = kReleaseMode - ? 'https://syncrow-staging.azurewebsites.net' - : 'https://syncrow-dev.azurewebsites.net'; - // static const String baseUrl = 'http://100.107.182.63:4001'; //Localhost + static String baseUrl = dotenv.env['BASE_URL'] ?? ''; ////////////////////////////////////// Authentication /////////////////////////////// - static const String signUp = '$baseUrl/authentication/user/signup'; - static const String login = '$baseUrl/authentication/user/login'; - static const String deleteUser = '$baseUrl/authentication/user/delete/{id}'; - static const String sendOtp = '$baseUrl/authentication/user/send-otp'; - static const String verifyOtp = '$baseUrl/authentication/user/verify-otp'; - static const String forgetPassword = - '$baseUrl/authentication/user/forget-password'; + static const String signUp = '/authentication/user/signup'; + static const String login = '/authentication/user/login'; + static const String deleteUser = '/authentication/user/delete/{id}'; + static const String sendOtp = '/authentication/user/send-otp'; + static const String verifyOtp = '/authentication/user/verify-otp'; + static const String forgetPassword = '/authentication/user/forget-password'; ////////////////////////////////////// Spaces /////////////////////////////////////// ///Community Module //POST - static const String addCommunity = '$baseUrl/community'; - static const String addCommunityToUser = '$baseUrl/community/user'; + static const String addCommunity = '/community'; + static const String addCommunityToUser = '/community/user'; //GET - static const String communityByUuid = '$baseUrl/community/{communityUuid}'; - static const String communityChild = - '$baseUrl/community/child/{communityUuid}'; - static const String communityUser = '$baseUrl/community/user/{userUuid}'; + static const String communityByUuid = '/community/{communityUuid}'; + static const String communityChild = '/community/child/{communityUuid}'; + static const String communityUser = '/community/user/{userUuid}'; //PUT - static const String renameCommunity = - '$baseUrl/community/rename/{communityUuid}'; + static const String renameCommunity = '/community/rename/{communityUuid}'; ///Building Module //POST - static const String addBuilding = '$baseUrl/building'; - static const String addBuildingToUser = '$baseUrl/building/user'; + static const String addBuilding = '/building'; + static const String addBuildingToUser = '/building/user'; //GET - static const String buildingByUuid = '$baseUrl/building/{buildingUuid}'; - static const String buildingChild = '$baseUrl/building/child/{buildingUuid}'; - static const String buildingParent = - '$baseUrl/building/parent/{buildingUuid}'; - static const String buildingUser = '$baseUrl/building/user/{userUuid}'; + static const String buildingByUuid = '/building/{buildingUuid}'; + static const String buildingChild = '/building/child/{buildingUuid}'; + static const String buildingParent = '/building/parent/{buildingUuid}'; + static const String buildingUser = '/building/user/{userUuid}'; //PUT - static const String renameBuilding = - '$baseUrl/building/rename/{buildingUuid}'; + static const String renameBuilding = '/building/rename/{buildingUuid}'; ///Floor Module //POST - static const String addFloor = '$baseUrl/floor'; - static const String addFloorToUser = '$baseUrl/floor/user'; + static const String addFloor = '/floor'; + static const String addFloorToUser = '/floor/user'; //GET - static const String floorByUuid = '$baseUrl/floor/{floorUuid}'; - static const String floorChild = '$baseUrl/floor/child/{floorUuid}'; - static const String floorParent = '$baseUrl/floor/parent/{floorUuid}'; - static const String floorUser = '$baseUrl/floor/user/{userUuid}'; + static const String floorByUuid = '/floor/{floorUuid}'; + static const String floorChild = '/floor/child/{floorUuid}'; + static const String floorParent = '/floor/parent/{floorUuid}'; + static const String floorUser = '/floor/user/{userUuid}'; //PUT - static const String renameFloor = '$baseUrl/floor/rename/{floorUuid}'; + static const String renameFloor = '/floor/rename/{floorUuid}'; ///Unit Module //POST - static const String addUnit = '$baseUrl/unit'; - static const String addUnitToUser = '$baseUrl/unit/user'; + static const String addUnit = '/unit'; + static const String addUnitToUser = '/unit/user'; //GET - static const String unitByUuid = '$baseUrl/unit/'; - 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'; + static const String unitByUuid = '/unit/'; + static const String unitChild = '/unit/child/'; + static const String unitParent = '/unit/parent/{unitUuid}'; + static const String unitUser = '/unit/user/'; + static const String invitationCode = '/unit/{unitUuid}/invitation-code'; + static const String verifyInvitationCode = '/unit/user/verify-code'; //PUT - static const String renameUnit = '$baseUrl/unit/rename/{unitUuid}'; + static const String renameUnit = '/unit/rename/{unitUuid}'; ///Room Module //POST - static const String addRoom = '$baseUrl/room'; - static const String addRoomToUser = '$baseUrl/room/user'; + static const String addRoom = '/room'; + static const String addRoomToUser = '/room/user'; //GET - static const String roomByUuid = '$baseUrl/room/{roomUuid}'; - static const String roomParent = '$baseUrl/room/parent/{roomUuid}'; - static const String roomUser = '$baseUrl/room/user/{userUuid}'; + static const String roomByUuid = '/room/{roomUuid}'; + static const String roomParent = '/room/parent/{roomUuid}'; + static const String roomUser = '/room/user/{userUuid}'; //PUT - static const String renameRoom = '$baseUrl/room/rename/{roomUuid}'; + static const String renameRoom = '/room/rename/{roomUuid}'; ///Group Module //POST - static const String addGroup = '$baseUrl/group'; - static const String controlGroup = '$baseUrl/group/control'; + static const String addGroup = '/group'; + static const String controlGroup = '/group/control'; //GET - static const String groupBySpace = '$baseUrl/group/{unitUuid}'; - static const String devicesByGroupName = - '$baseUrl/group/{unitUuid}/devices/{groupName}'; + static const String groupBySpace = '/group/{unitUuid}'; + static const String devicesByGroupName = '/group/{unitUuid}/devices/{groupName}'; - static const String groupByUuid = '$baseUrl/group/{groupUuid}'; + static const String groupByUuid = '/group/{groupUuid}'; //DELETE - static const String deleteGroup = '$baseUrl/group/{groupUuid}'; + static const String deleteGroup = '/group/{groupUuid}'; ////////////////////////////////////// Devices /////////////////////////////////////// ///Device Module //POST - 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 firmwareDevice = - '$baseUrl/device/{deviceUuid}/firmware/{firmwareVersion}'; - static const String getDevicesByUserId = '$baseUrl/device/user/{userId}'; - static const String getDevicesByUnitId = '$baseUrl/device/unit/{unitUuid}'; + static const String addDeviceToRoom = '/device/room'; + static const String addDeviceToGroup = '/device/group'; + static const String controlDevice = '/device/{deviceUuid}/control'; + static const String firmwareDevice = '/device/{deviceUuid}/firmware/{firmwareVersion}'; + static const String getDevicesByUserId = '/device/user/{userId}'; + static const String getDevicesByUnitId = '/device/unit/{unitUuid}'; //GET - static const String deviceByRoom = '$baseUrl/device/room'; - static const String deviceByUuid = '$baseUrl/device/{deviceUuid}'; - static const String deviceFunctions = - '$baseUrl/device/{deviceUuid}/functions'; - static const String gatewayApi = - '$baseUrl/device/gateway/{gatewayUuid}/devices'; - static const String deviceFunctionsStatus = - '$baseUrl/device/{deviceUuid}/functions/status'; + static const String deviceByRoom = '/device/room'; + static const String deviceByUuid = '/device/{deviceUuid}'; + static const String deviceFunctions = '/device/{deviceUuid}/functions'; + static const String gatewayApi = '/device/gateway/{gatewayUuid}/devices'; + static const String deviceFunctionsStatus = '/device/{deviceUuid}/functions/status'; ///Device Permission Module //POST - static const String addDevicePermission = '$baseUrl/device-permission/add'; + static const String addDevicePermission = '/device-permission/add'; //GET - static const String devicePermissionList = '$baseUrl/device-permission/list'; + static const String devicePermissionList = '/device-permission/list'; //PUT - static const String editDevicePermission = - '$baseUrl/device-permission/edit/{userId}'; + static const String editDevicePermission = '/device-permission/edit/{userId}'; - static const String assignDeviceToRoom = '$baseUrl/device/room'; + static const String assignDeviceToRoom = '/device/room'; /// Scene & Automation API //////////////////// /// POST - static const String createScene = '$baseUrl/scene/tap-to-run'; - static const String triggerScene = - '$baseUrl/scene/tap-to-run/trigger/{sceneId}'; - - static const String createAutomation = '$baseUrl/automation'; + static const String createScene = '/scene/tap-to-run'; + static const String triggerScene = '/scene/tap-to-run/trigger/{sceneId}'; + static const String createAutomation = '/automation'; /// GET - static const String getUnitScenes = '$baseUrl/scene/tap-to-run/{unitUuid}'; + static const String getUnitScenes = '/scene/tap-to-run/{unitUuid}'; - static const String getScene = '$baseUrl/scene/tap-to-run/details/{sceneId}'; + static const String getScene = '/scene/tap-to-run/details/{sceneId}'; - static const String getUnitAutomation = '$baseUrl/automation/{unitUuid}'; + static const String getUnitAutomation = '/automation/{unitUuid}'; - static const String getAutomationDetails = - '$baseUrl/automation/details/{automationId}'; + static const String getAutomationDetails = '/automation/details/{automationId}'; /// PUT - static const String updateScene = '$baseUrl/scene/tap-to-run/{sceneId}'; + static const String updateScene = '/scene/tap-to-run/{sceneId}'; - static const String updateAutomation = '$baseUrl/automation/{automationId}'; + static const String updateAutomation = '/automation/{automationId}'; /// DELETE - static const String deleteScene = - '$baseUrl/scene/tap-to-run/{unitUuid}/{sceneId}'; + static const String deleteScene = '/scene/tap-to-run/{unitUuid}/{sceneId}'; - static const String deleteAutomation = - '$baseUrl/automation/{unitUuid}/{automationId}'; + static const String deleteAutomation = '/automation/{unitUuid}/{automationId}'; //////////////////////Door Lock ////////////////////// //online - static const String addTemporaryPassword = - '$baseUrl/door-lock/temporary-password/online/{doorLockUuid}'; - static const String getTemporaryPassword = - '$baseUrl/door-lock/temporary-password/online/{doorLockUuid}'; + static const String addTemporaryPassword = '/door-lock/temporary-password/online/{doorLockUuid}'; + static const String getTemporaryPassword = '/door-lock/temporary-password/online/{doorLockUuid}'; //one-time offline static const String addOneTimeTemporaryPassword = - '$baseUrl/door-lock/temporary-password/offline/one-time/{doorLockUuid}'; + '/door-lock/temporary-password/offline/one-time/{doorLockUuid}'; static const String getOneTimeTemporaryPassword = - '$baseUrl/door-lock/temporary-password/offline/one-time/{doorLockUuid}'; + '/door-lock/temporary-password/offline/one-time/{doorLockUuid}'; //multiple-time offline static const String addMultipleTimeTemporaryPassword = - '$baseUrl/door-lock/temporary-password/offline/multiple-time/{doorLockUuid}'; + '/door-lock/temporary-password/offline/multiple-time/{doorLockUuid}'; static const String getMultipleTimeTemporaryPassword = - '$baseUrl/door-lock/temporary-password/offline/multiple-time/{doorLockUuid}'; + '/door-lock/temporary-password/offline/multiple-time/{doorLockUuid}'; //multiple-time offline static const String deleteTemporaryPassword = - '$baseUrl/door-lock/temporary-password/{doorLockUuid}/{passwordId}'; + '/door-lock/temporary-password/{doorLockUuid}/{passwordId}'; + + //user + + static const String getUser = '/user/{userUuid}'; + static const String saveRegion = '/user/region/{userUuid}'; + static const String saveTimeZone = '/user/timezone/{userUuid}'; + static const String saveName = '/user/name/{userUuid}'; + static const String sendPicture = '/user/profile-picture/{userUuid}'; + static const String getRegion = '/region'; + static const String getTimezone = '/timezone'; } diff --git a/lib/services/api/profile_api.dart b/lib/services/api/profile_api.dart new file mode 100644 index 0000000..5666923 --- /dev/null +++ b/lib/services/api/profile_api.dart @@ -0,0 +1,115 @@ +import 'dart:async'; +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/profile_bloc/region_model.dart'; +import 'package:syncrow_app/features/menu/bloc/profile_bloc/time_zone_model.dart'; +import 'package:syncrow_app/services/api/api_links_endpoints.dart'; +import 'package:syncrow_app/services/api/http_service.dart'; + +class ProfileApi { + static final HTTPService _httpService = HTTPService(); + + static Future> saveName({String? firstName, String? lastName,}) async { + try { + final response = await _httpService.put( + path: ApiEndpoints.saveName.replaceAll('{userUuid}', HomeCubit.user!.uuid!), + body: { + "firstName": firstName, + "lastName": lastName + }, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future saveRegion({String? regionUuid,}) async { + try { + final response = await _httpService.put( + path: ApiEndpoints.saveRegion.replaceAll('{userUuid}', HomeCubit.user!.uuid!), + body: { + "regionUuid": regionUuid, + }, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + static Future saveTimeZone({String? regionUuid,}) async { + try { + final response = await _httpService.put( + path: ApiEndpoints.saveTimeZone.replaceAll('{userUuid}', HomeCubit.user!.uuid!), + body: { + "timezoneUuid": regionUuid, + }, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> saveImage(String image) async { + try { + final response = await _httpService.put( + path: ApiEndpoints.sendPicture.replaceAll('{userUuid}', HomeCubit.user!.uuid!), + body: { + "profilePicture": 'data:image/png;base64,$image' + }, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + Future fetchUserInfo(userId) async { + final response = await _httpService.get( + path: ApiEndpoints.getUser.replaceAll('{userUuid}', userId!), + showServerMessage: true, + expectedResponseModel: (json) { + return UserModel.fromJson(json); + } + ); + return response; + } + + static Future> fetchRegion() async { + final response = await _httpService.get( + path: ApiEndpoints.getRegion, + showServerMessage: true, + expectedResponseModel: (json) { + return (json as List).map((zone) => RegionModel.fromJson(zone)).toList(); + } + ); + return response as List; + } + + + static Future> fetchTimeZone() async { + final response = await _httpService.get( + path: ApiEndpoints.getTimezone, + showServerMessage: true, + expectedResponseModel: (json) { + return (json as List).map((zone) => TimeZone.fromJson(zone)).toList(); + } + ); + return response as List; + } + + +} diff --git a/pubspec.lock b/pubspec.lock index ec31228..6530155 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -129,6 +129,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + device_info_plus: + dependency: "direct main" + description: + name: device_info_plus + sha256: eead12d1a1ed83d8283ab4c2f3fca23ac4082f29f25f29dff0f758f57d06ec91 + url: "https://pub.dev" + source: hosted + version: "10.1.0" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 + url: "https://pub.dev" + source: hosted + version: "7.0.0" dio: dependency: "direct main" description: @@ -169,6 +185,38 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + url: "https://pub.dev" + source: hosted + version: "0.9.2+1" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 + url: "https://pub.dev" + source: hosted + version: "0.9.3+1" firebase_analytics: dependency: "direct main" description: @@ -307,6 +355,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: c6b0b4c05c458e1c01ad9bcc14041dd7b1f6783d487be4386f793f47a8a4d03e + url: "https://pub.dev" + source: hosted + version: "2.0.20" flutter_secure_storage: dependency: "direct main" description: @@ -405,6 +461,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "4161e1f843d8480d2e9025ee22411778c3c9eb7e40076dcf2da23d8242b7b51c" + url: "https://pub.dev" + source: hosted + version: "0.8.12+3" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "5d6eb13048cd47b60dbf1a5495424dea226c5faf3950e20bf8120a58efb5b5f3" + url: "https://pub.dev" + source: hosted + version: "3.0.4" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" + url: "https://pub.dev" + source: hosted + version: "0.8.12" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" + url: "https://pub.dev" + source: hosted + version: "2.10.0" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" intl: dependency: "direct main" description: @@ -986,6 +1106,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.2.0" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" xdg_directories: dependency: transitive description: @@ -1003,5 +1131,5 @@ packages: source: hosted version: "6.5.0" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.19.0" + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index aaead8f..5bfe40d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ description: This is the mobile application project, developed with Flutter for # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: "none" # Remove this line if you wish to publish to pub.dev -version: 1.0.1+11 +version: 1.0.1+12 environment: sdk: ">=3.0.6 <4.0.0" @@ -43,6 +43,8 @@ dependencies: smooth_page_indicator: ^1.1.0 uuid: ^4.4.0 time_picker_spinner: ^1.0.0 + image_picker: ^1.1.2 + device_info_plus: ^10.1.0 dev_dependencies: flutter_lints: ^3.0.1 @@ -75,6 +77,11 @@ flutter: - assets/icons/curtainsIcon/ - assets/icons/functions_icons/ - assets/icons/functions_icons/automation_functions/ + - .env.development + - .env.staging + - .env.production + + fonts: - family: Aftika fonts: