Merged with SP-203

This commit is contained in:
Abdullah Alassaf
2024-07-25 14:15:06 +03:00
46 changed files with 1973 additions and 811 deletions

View File

2
.env.development Normal file
View File

@ -0,0 +1,2 @@
ENV_NAME=development
BASE_URL=https://syncrow-dev.azurewebsites.net

View File

2
.env.production Normal file
View File

@ -0,0 +1,2 @@
ENV_NAME=production
BASE_URL=https://syncrow-staging.azurewebsites.net

View File

@ -0,0 +1,2 @@
ENV_NAME=staging
BASE_URL=https://syncrow-staging.azurewebsites.net

2
.gitignore vendored
View File

@ -20,7 +20,7 @@ migrate_working_dir/
# VS Code which you may wish to be included in version control, so this line # VS Code which you may wish to be included in version control, so this line
# is commented out by default. # is commented out by default.
#.vscode/ #.vscode/
*.env
# Flutter/Dart/Pub related # Flutter/Dart/Pub related
**/doc/api/ **/doc/api/
**/ios/Flutter/.last_build_id **/ios/Flutter/.last_build_id

View File

@ -2,12 +2,17 @@
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.GALLERY"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<application <application
android:label="syncrow_app" android:label="syncrow_app"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:allowBackup="false" android:allowBackup="false">
>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"

View File

@ -40,6 +40,13 @@ end
post_install do |installer| post_install do |installer|
installer.pods_project.targets.each do |target| installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target) flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
## dart: PermissionGroup.photos
'PERMISSION_PHOTOS=1',
]
end
end end
end end

View File

@ -1,4 +1,6 @@
PODS: PODS:
- device_info_plus (0.0.1):
- Flutter
- Firebase/Analytics (10.20.0): - Firebase/Analytics (10.20.0):
- Firebase/Core - Firebase/Core
- Firebase/Core (10.20.0): - Firebase/Core (10.20.0):
@ -125,6 +127,8 @@ PODS:
- GoogleUtilities/UserDefaults (7.13.3): - GoogleUtilities/UserDefaults (7.13.3):
- GoogleUtilities/Logger - GoogleUtilities/Logger
- GoogleUtilities/Privacy - GoogleUtilities/Privacy
- image_picker_ios (0.0.1):
- Flutter
- nanopb (2.30909.1): - nanopb (2.30909.1):
- nanopb/decode (= 2.30909.1) - nanopb/decode (= 2.30909.1)
- nanopb/encode (= 2.30909.1) - nanopb/encode (= 2.30909.1)
@ -199,12 +203,14 @@ PODS:
- Flutter - Flutter
DEPENDENCIES: DEPENDENCIES:
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`) - firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_localization (from `.symlinks/plugins/flutter_localization/ios`) - flutter_localization (from `.symlinks/plugins/flutter_localization/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- onesignal_flutter (from `.symlinks/plugins/onesignal_flutter/ios`) - onesignal_flutter (from `.symlinks/plugins/onesignal_flutter/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
@ -232,6 +238,8 @@ SPEC REPOS:
- PromisesSwift - PromisesSwift
EXTERNAL SOURCES: EXTERNAL SOURCES:
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
firebase_analytics: firebase_analytics:
:path: ".symlinks/plugins/firebase_analytics/ios" :path: ".symlinks/plugins/firebase_analytics/ios"
firebase_core: firebase_core:
@ -244,6 +252,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_localization/ios" :path: ".symlinks/plugins/flutter_localization/ios"
flutter_secure_storage: flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios" :path: ".symlinks/plugins/flutter_secure_storage/ios"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
onesignal_flutter: onesignal_flutter:
:path: ".symlinks/plugins/onesignal_flutter/ios" :path: ".symlinks/plugins/onesignal_flutter/ios"
path_provider_foundation: path_provider_foundation:
@ -260,6 +270,7 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/url_launcher_ios/ios" :path: ".symlinks/plugins/url_launcher_ios/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
Firebase: 10c8cb12fb7ad2ae0c09ffc86cd9c1ab392a0031 Firebase: 10c8cb12fb7ad2ae0c09ffc86cd9c1ab392a0031
firebase_analytics: 2c1c3057d5da3bd3aab819f7e6ee153a4e46c59e firebase_analytics: 2c1c3057d5da3bd3aab819f7e6ee153a4e46c59e
firebase_core: c8628c7ce80f79439149549052bff22f6784fbf5 firebase_core: c8628c7ce80f79439149549052bff22f6784fbf5
@ -277,6 +288,7 @@ SPEC CHECKSUMS:
GoogleAppMeasurement: bb3c564c3efb933136af0e94899e0a46167466a8 GoogleAppMeasurement: bb3c564c3efb933136af0e94899e0a46167466a8
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5 nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5
onesignal_flutter: 5ce68a29861960168e81101cb1bd685d264361de onesignal_flutter: 5ce68a29861960168e81101cb1bd685d264361de
OneSignalXCFramework: bdf74fdc06888f9466dc21e826fe1549ed143095 OneSignalXCFramework: bdf74fdc06888f9466dc21e826fe1549ed143095
@ -289,6 +301,6 @@ SPEC CHECKSUMS:
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
PODFILE CHECKSUM: cf86fcba3fb3dbd505936bc190bb0b8fe3dd2498 PODFILE CHECKSUM: 4243bd7f9184f79552dd731a7c9d5cad03bd2706
COCOAPODS: 1.14.3 COCOAPODS: 1.15.2

View File

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need access to your photo library to allow you to select and upload photos.</string>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>

View File

@ -27,6 +27,7 @@ import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/navigation/navigation_service.dart'; import 'package:syncrow_app/navigation/navigation_service.dart';
import 'package:syncrow_app/navigation/routing_constants.dart'; import 'package:syncrow_app/navigation/routing_constants.dart';
import 'package:syncrow_app/services/api/devices_api.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/services/api/spaces_api.dart';
import 'package:syncrow_app/utils/helpers/custom_page_route.dart'; import 'package:syncrow_app/utils/helpers/custom_page_route.dart';
import 'package:syncrow_app/utils/helpers/snack_bar.dart'; import 'package:syncrow_app/utils/helpers/snack_bar.dart';
@ -38,6 +39,7 @@ part 'home_state.dart';
class HomeCubit extends Cubit<HomeState> { class HomeCubit extends Cubit<HomeState> {
HomeCubit._() : super(HomeInitial()) { HomeCubit._() : super(HomeInitial()) {
checkIfNotificationPermissionGranted(); checkIfNotificationPermissionGranted();
fetchUserInfo();
if (selectedSpace == null) { if (selectedSpace == null) {
fetchUnitsByUserId(); fetchUnitsByUserId();
// .then((value) { // .then((value) {
@ -47,7 +49,7 @@ class HomeCubit extends Cubit<HomeState> {
// }); // });
} }
} }
static UserModel? user;
static HomeCubit? _instance; static HomeCubit? _instance;
static HomeCubit getInstance() { static HomeCubit getInstance() {
// If an instance already exists, return it // If an instance already exists, return it
@ -55,6 +57,18 @@ class HomeCubit extends Cubit<HomeState> {
return _instance!; 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) { void emitSafe(HomeState newState) {
final cubit = this; final cubit = this;
if (!cubit.isClosed) { if (!cubit.isClosed) {

View File

@ -58,3 +58,9 @@ class RoomSelected extends HomeState {
class RoomUnSelected extends HomeState {} class RoomUnSelected extends HomeState {}
class NavChangePage extends HomeState {} class NavChangePage extends HomeState {}
// Define new state classes
class HomeUserInfoLoaded extends HomeState {
final UserModel user;
HomeUserInfoLoaded(this.user);
}

View File

@ -1,3 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
@ -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/navigation_service.dart';
import 'package:syncrow_app/navigation/routing_constants.dart'; import 'package:syncrow_app/navigation/routing_constants.dart';
import 'package:syncrow_app/services/api/authentication_api.dart'; import 'package:syncrow_app/services/api/authentication_api.dart';
import 'package:syncrow_app/services/api/profile_api.dart';
import 'package:syncrow_app/utils/helpers/shared_preferences_helper.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/helpers/snack_bar.dart';
import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; import 'package:syncrow_app/utils/resource_manager/strings_manager.dart';
@ -62,7 +64,8 @@ class AuthCubit extends Cubit<AuthState> {
return 'Please enter your password'; return 'Please enter your password';
} }
if (value.isNotEmpty) { if (value.isNotEmpty) {
if (!RegExp(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$') if (!RegExp(
r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!"#$%&()*+,-./:;<=>?@[\]^_`{|}~])[A-Za-z\d!"#$%&()*+,-./:;<=>?@[\]^_`{|}~]{8,}$')
.hasMatch(value)) { .hasMatch(value)) {
return 'Password must contain at least:\n - one uppercase letter.\n - one lowercase letter.\n - one number. \n - special character'; 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<AuthState> {
if (token.accessTokenIsNotEmpty) { if (token.accessTokenIsNotEmpty) {
debugPrint('token: ${token.accessToken}'); debugPrint('token: ${token.accessToken}');
FlutterSecureStorage storage = const FlutterSecureStorage(); 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( const FlutterSecureStorage().write(
key: UserModel.userUuidKey, key: UserModel.userUuidKey,
value: Token.decodeToken(token.accessToken)['uuid'].toString()); value: Token.decodeToken(token.accessToken)['uuid'].toString()
);
user = UserModel.fromToken(token); user = UserModel.fromToken(token);
emailController.clear(); emailController.clear();
passwordController.clear(); passwordController.clear();
@ -277,8 +282,7 @@ class AuthCubit extends Cubit<AuthState> {
try { try {
emit(AuthTokenLoading()); emit(AuthTokenLoading());
const storage = FlutterSecureStorage(); const storage = FlutterSecureStorage();
final firstLaunch = final firstLaunch = await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true;
await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true;
if (firstLaunch) { if (firstLaunch) {
storage.deleteAll(); storage.deleteAll();
@ -311,6 +315,7 @@ class AuthCubit extends Cubit<AuthState> {
} }
} }
sendToForgetPassword({required String password}) async { sendToForgetPassword({required String password}) async {
try { try {
emit(AuthForgetPassLoading()); emit(AuthForgetPassLoading());
@ -320,4 +325,8 @@ class AuthCubit extends Cubit<AuthState> {
emit(AuthForgetPassError(message: 'Something went wrong')); emit(AuthForgetPassError(message: 'Something went wrong'));
} }
} }
} }

View File

@ -1,63 +1,81 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:syncrow_app/features/auth/model/token.dart'; import 'package:syncrow_app/features/auth/model/token.dart';
class UserModel { class UserModel {
static String userUuidKey = 'userUuid'; static String userUuidKey = 'userUuid';
final String? uuid; final String? uuid;
final String? email; final String? email;
final String? name; final String? firstName;
final String? photoUrl; final String? lastName;
final Uint8List? profilePicture;
final String? phoneNumber; final String? phoneNumber;
final bool? isEmailVerified; final bool? isEmailVerified;
final String? regionName;
final String? timeZone;
final bool? isAgreementAccepted; final bool? isAgreementAccepted;
UserModel({ UserModel({
required this.uuid, required this.uuid,
required this.email, required this.email,
required this.name, required this.firstName,
required this.photoUrl, required this.lastName,
required this.profilePicture,
required this.phoneNumber, required this.phoneNumber,
required this.isEmailVerified, required this.isEmailVerified,
required this.isAgreementAccepted, required this.isAgreementAccepted,
required this.regionName, // Add this line
required this.timeZone, // Add this line
}); });
factory UserModel.fromJson(Map<String, dynamic> json) { factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel( return UserModel(
uuid: json['id'], uuid: json['uuid'],
email: json['email'], email: json['email'],
name: json['name'], firstName: json['firstName'],
photoUrl: json['photoUrl'], lastName: json['lastName'],
profilePicture: UserModel.decodeBase64Image(json['profilePicture']),
phoneNumber: json['phoneNumber'], phoneNumber: json['phoneNumber'],
isEmailVerified: json['isEmailVerified'], isEmailVerified: json['isEmailVerified'],
isAgreementAccepted: json['isAgreementAccepted'], isAgreementAccepted: json['isAgreementAccepted'],
regionName: json['region']?['regionName'], // Extract regionName
timeZone: json['timeZone']?['timeZoneOffset'], // Extract regionName
); );
} }
//uuid to json //uuid to json
//from token //from token
factory UserModel.fromToken(Token token) { factory UserModel.fromToken(Token token) {
Map<String, dynamic> tempJson = Token.decodeToken(token.accessToken); Map<String, dynamic> tempJson = Token.decodeToken(token.accessToken);
return UserModel( return UserModel(
uuid: tempJson['uuid'].toString(), uuid: tempJson['uuid'].toString(),
email: tempJson['email'], email: tempJson['email'],
name: null, lastName: tempJson['lastName'],
photoUrl: null, firstName:tempJson['firstName'] ,
profilePicture: UserModel.decodeBase64Image(tempJson['profilePicture']),
phoneNumber: null, phoneNumber: null,
isEmailVerified: null, isEmailVerified: null,
isAgreementAccepted: 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<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
'id': uuid, 'id': uuid,
'email': email, 'email': email,
'name': name, 'lastName': lastName,
'photoUrl': photoUrl, 'firstName': firstName,
'photoUrl': profilePicture,
'phoneNumber': phoneNumber, 'phoneNumber': phoneNumber,
'isEmailVerified': isEmailVerified, 'isEmailVerified': isEmailVerified,
'isAgreementAccepted': isAgreementAccepted, 'isAgreementAccepted': isAgreementAccepted,

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.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<AcsEvent, AcsState> { class ACsBloc extends Bloc<AcsEvent, AcsState> {
final String acId; final String acId;
AcStatusModel deviceStatus = AcStatusModel( AcStatusModel deviceStatus = AcStatusModel(
uuid: '',
acSwitch: true, acSwitch: true,
modeString: 'hot', modeString: 'hot',
tempSet: 300, tempSet: 300,
@ -24,6 +27,7 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
bool allAcsOn = true; bool allAcsOn = true;
bool allTempSame = true; bool allTempSame = true;
int globalTemp = 25; int globalTemp = 25;
Timer? _timer;
ACsBloc({required this.acId}) : super(AcsInitialState()) { ACsBloc({required this.acId}) : super(AcsInitialState()) {
on<AcsInitial>(_fetchAcsStatus); on<AcsInitial>(_fetchAcsStatus);
@ -56,11 +60,11 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
for (var status in response['status']) { for (var status in response['status']) {
statusModelList.add(StatusModel.fromJson(status)); statusModelList.add(StatusModel.fromJson(status));
} }
deviceStatus = AcStatusModel.fromJson(statusModelList); deviceStatus = AcStatusModel.fromJson(response['productUuid'], statusModelList);
emit(GetAcStatusState(acStatusModel: deviceStatus)); emit(GetAcStatusState(acStatusModel: deviceStatus));
} }
} catch (e) { } catch (e) {
emit(AcsFailedState(error: e.toString())); emit(AcsFailedState(errorMessage: e.toString()));
return; return;
} }
} }
@ -68,8 +72,6 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
_getAllAcs() async { _getAllAcs() async {
deviceStatusList = []; deviceStatusList = [];
devicesList = []; devicesList = [];
allAcsOn = true;
allTempSame = true;
devicesList = await DevicesAPI.getDeviceByGroupName( devicesList = await DevicesAPI.getDeviceByGroupName(
HomeCubit.getInstance().selectedSpace?.id ?? '', 'AC'); HomeCubit.getInstance().selectedSpace?.id ?? '', 'AC');
@ -79,8 +81,210 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
for (var status in response['status']) { for (var status in response['status']) {
statusModelList.add(StatusModel.fromJson(status)); statusModelList.add(StatusModel.fromJson(status));
} }
deviceStatusList.add(AcStatusModel.fromJson(statusModelList)); deviceStatusList.add(AcStatusModel.fromJson(response['productUuid'], statusModelList));
} }
_setAllAcsTempsAndSwitches();
}
void _changeAcSwitch(AcSwitch event, Emitter<AcsState> 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<AcsState> 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<AcsState> 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<AcsState> 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<AcsState> 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<AcsState> 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<AcsState> 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<AcsState> 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<AcsState> 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) { if (deviceStatusList.isNotEmpty) {
int temp = deviceStatusList[0].tempSet; int temp = deviceStatusList[0].tempSet;
deviceStatusList.firstWhere((element) { deviceStatusList.firstWhere((element) {
@ -99,195 +303,71 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
} }
} }
void _changeAcSwitch(AcSwitch event, Emitter<AcsState> emit) async { _runDeBouncerForAllAcs({required String code, required dynamic value}) {
emit(AcChangeLoading(acStatusModel: deviceStatus)); if (_timer != null) {
_timer!.cancel();
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));
} }
} _timer = Timer(const Duration(seconds: 1), () async {
void _changeAllAcSwitch(ChangeAllSwitch event, Emitter<AcsState> emit) async {
emit(AcsLoadingState());
try {
if (deviceStatusList.length == devicesList.length) { if (deviceStatusList.length == devicesList.length) {
for (int i = 0; i < deviceStatusList.length; i++) { for (int i = 0; i < deviceStatusList.length; i++) {
await DevicesAPI.controlDevice( try {
DeviceControlModel(deviceId: devicesList[i].uuid, code: 'switch', value: event.value), await DevicesAPI.controlDevice(
devicesList[i].uuid ?? ''); 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<AcsState> emit) async { _runDeBouncerForOneDevice({
emit(AcsLoadingState()); required String deviceId,
try { required String code,
double tempValue = event.value + 0.5; required dynamic value,
int value = (tempValue * 10).toInt(); }) {
if (deviceStatusList.length == devicesList.length) { if (_timer != null) {
for (int i = 0; i < deviceStatusList.length; i++) { _timer!.cancel();
await DevicesAPI.controlDevice( }
DeviceControlModel(deviceId: devicesList[i].uuid, code: 'temp_set', value: value), _timer = Timer(const Duration(seconds: 1), () async {
devicesList[i].uuid ?? ''); 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<AcsState> emit) async { bool _checkTemperatureValue(double value, Emitter<AcsState> emit) {
emit(AcsLoadingState()); if (value >= 20 && value <= 30) {
try { return true;
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<AcsState> 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<AcsState> 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 { } 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<AcsState> emit) async { _emitAcsStatus(Emitter<AcsState> emit) {
emit(AcChangeLoading(acStatusModel: deviceStatus)); emit(GetAllAcsStatusState(
allAcsStatues: deviceStatusList,
double tempValue = event.value - 0.5; allAcs: devicesList,
int value = (tempValue * 10).toInt(); allOn: allAcsOn,
try { allTempSame: allTempSame,
final response = await DevicesAPI.controlDevice( temp: globalTemp));
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<AcsState> 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<AcsState> 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';
}
} }
} }

View File

@ -13,10 +13,11 @@ class AcsLoading extends AcsEvent {}
class AcSwitch extends AcsEvent { class AcSwitch extends AcsEvent {
final bool acSwitch; final bool acSwitch;
final String deviceId; final String deviceId;
const AcSwitch({required this.acSwitch, this.deviceId = ''}); final String productId;
const AcSwitch({required this.acSwitch, this.deviceId = '', this.productId = ''});
@override @override
List<Object> get props => [acSwitch, deviceId]; List<Object> get props => [acSwitch, deviceId, productId];
} }
class AcsInitial extends AcsEvent { class AcsInitial extends AcsEvent {
@ -31,7 +32,8 @@ class ACsChangeStatus extends AcsEvent {}
class IncreaseCoolToTemp extends AcsEvent { class IncreaseCoolToTemp extends AcsEvent {
final double value; final double value;
final String deviceId; final String deviceId;
const IncreaseCoolToTemp({required this.value, this.deviceId = ''}); final String productId;
const IncreaseCoolToTemp({required this.value, this.deviceId = '', this.productId = ''});
@override @override
List<Object> get props => [value, deviceId]; List<Object> get props => [value, deviceId];
@ -40,7 +42,9 @@ class IncreaseCoolToTemp extends AcsEvent {
class DecreaseCoolToTemp extends AcsEvent { class DecreaseCoolToTemp extends AcsEvent {
final double value; final double value;
final String deviceId; final String deviceId;
const DecreaseCoolToTemp({required this.value, this.deviceId = ''}); final String productId;
const DecreaseCoolToTemp({required this.value, this.deviceId = '', this.productId = ''});
@override @override
List<Object> get props => [value, deviceId]; List<Object> get props => [value, deviceId];
@ -49,19 +53,22 @@ class DecreaseCoolToTemp extends AcsEvent {
class ChangeAcMode extends AcsEvent { class ChangeAcMode extends AcsEvent {
final TempModes tempModes; final TempModes tempModes;
final String deviceId; final String deviceId;
const ChangeAcMode({required this.tempModes, this.deviceId = ''}); final String productId;
const ChangeAcMode({required this.tempModes, this.deviceId = '', this.productId = ''});
@override @override
List<Object> get props => [tempModes, deviceId]; List<Object> get props => [tempModes, deviceId, productId];
} }
class ChangeFanSpeed extends AcsEvent { class ChangeFanSpeed extends AcsEvent {
final FanSpeeds fanSpeeds; final FanSpeeds fanSpeeds;
final String deviceId; final String deviceId;
const ChangeFanSpeed({required this.fanSpeeds, this.deviceId = ''}); final String productId;
const ChangeFanSpeed({required this.fanSpeeds, this.deviceId = '', this.productId = ''});
@override @override
List<Object> get props => [fanSpeeds, deviceId]; List<Object> get props => [fanSpeeds, deviceId, productId];
} }
class ChangeLock extends AcsEvent { class ChangeLock extends AcsEvent {

View File

@ -56,10 +56,10 @@ class GetAllAcsStatusState extends AcsState {
} }
class AcsFailedState extends AcsState { class AcsFailedState extends AcsState {
final String error; final String errorMessage;
const AcsFailedState({required this.error}); const AcsFailedState({required this.errorMessage});
@override @override
List<Object> get props => [error]; List<Object> get props => [errorMessage];
} }

View File

@ -95,6 +95,22 @@ class ThreeGangBloc extends Bloc<ThreeGangEvent, ThreeGangState> {
void _changeFirstSwitch(ChangeFirstSwitchStatusEvent event, Emitter<ThreeGangState> emit) async { void _changeFirstSwitch(ChangeFirstSwitchStatusEvent event, Emitter<ThreeGangState> emit) async {
emit(LoadingNewSate(threeGangModel: deviceStatus)); emit(LoadingNewSate(threeGangModel: deviceStatus));
try { 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( final response = await DevicesAPI.controlDevice(
DeviceControlModel( DeviceControlModel(
deviceId: threeGangGroup ? event.deviceId : threeGangId, deviceId: threeGangGroup ? event.deviceId : threeGangId,
@ -102,15 +118,11 @@ class ThreeGangBloc extends Bloc<ThreeGangEvent, ThreeGangState> {
value: !event.value), value: !event.value),
threeGangGroup ? event.deviceId : threeGangId); threeGangGroup ? event.deviceId : threeGangId);
if (response['success'] ?? false) { if (!response['success']) {
deviceStatus.firstSwitch = !event.value; add(InitialEvent(groupScreen: threeGangGroup));
} }
} catch (_) {} } catch (_) {
if (threeGangGroup) { add(InitialEvent(groupScreen: threeGangGroup));
await Future.delayed(const Duration(seconds: 1));
add(const InitialEvent(groupScreen: true));
} else {
emit(UpdateState(threeGangModel: deviceStatus));
} }
} }
@ -118,6 +130,22 @@ class ThreeGangBloc extends Bloc<ThreeGangEvent, ThreeGangState> {
ChangeSecondSwitchStatusEvent event, Emitter<ThreeGangState> emit) async { ChangeSecondSwitchStatusEvent event, Emitter<ThreeGangState> emit) async {
emit(LoadingNewSate(threeGangModel: deviceStatus)); emit(LoadingNewSate(threeGangModel: deviceStatus));
try { 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( final response = await DevicesAPI.controlDevice(
DeviceControlModel( DeviceControlModel(
deviceId: threeGangGroup ? event.deviceId : threeGangId, deviceId: threeGangGroup ? event.deviceId : threeGangId,
@ -125,21 +153,33 @@ class ThreeGangBloc extends Bloc<ThreeGangEvent, ThreeGangState> {
value: !event.value), value: !event.value),
threeGangGroup ? event.deviceId : threeGangId); threeGangGroup ? event.deviceId : threeGangId);
if (response['success'] ?? false) { if (!response['success']) {
deviceStatus.secondSwitch = !event.value; add(InitialEvent(groupScreen: threeGangGroup));
} }
} catch (_) {} } catch (_) {
if (threeGangGroup) { add(InitialEvent(groupScreen: threeGangGroup));
await Future.delayed(const Duration(seconds: 1));
add(const InitialEvent(groupScreen: true));
} else {
emit(UpdateState(threeGangModel: deviceStatus));
} }
} }
void _changeThirdSwitch(ChangeThirdSwitchStatusEvent event, Emitter<ThreeGangState> emit) async { void _changeThirdSwitch(ChangeThirdSwitchStatusEvent event, Emitter<ThreeGangState> emit) async {
emit(LoadingNewSate(threeGangModel: deviceStatus)); emit(LoadingNewSate(threeGangModel: deviceStatus));
try { 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( final response = await DevicesAPI.controlDevice(
DeviceControlModel( DeviceControlModel(
deviceId: threeGangGroup ? event.deviceId : threeGangId, deviceId: threeGangGroup ? event.deviceId : threeGangId,
@ -147,15 +187,11 @@ class ThreeGangBloc extends Bloc<ThreeGangEvent, ThreeGangState> {
value: !event.value), value: !event.value),
threeGangGroup ? event.deviceId : threeGangId); threeGangGroup ? event.deviceId : threeGangId);
if (response['success'] ?? false) { if (!response['success']) {
deviceStatus.thirdSwitch = !event.value; add(InitialEvent(groupScreen: threeGangGroup));
} }
} catch (_) {} } catch (_) {
if (threeGangGroup) { add(InitialEvent(groupScreen: threeGangGroup));
await Future.delayed(const Duration(seconds: 1));
add(const InitialEvent(groupScreen: true));
} else {
emit(UpdateState(threeGangModel: deviceStatus));
} }
} }
@ -163,52 +199,82 @@ class ThreeGangBloc extends Bloc<ThreeGangEvent, ThreeGangState> {
emit(LoadingNewSate(threeGangModel: deviceStatus)); emit(LoadingNewSate(threeGangModel: deviceStatus));
try { try {
deviceStatus.firstSwitch = false;
deviceStatus.secondSwitch = false;
deviceStatus.thirdSwitch = false;
emit(UpdateState(threeGangModel: deviceStatus));
final response = await Future.wait([ final response = await Future.wait([
DevicesAPI.controlDevice( DevicesAPI.controlDevice(
DeviceControlModel(deviceId: threeGangId, code: 'switch_1', value: false), threeGangId), DeviceControlModel(
deviceId: threeGangId, code: 'switch_1', value: deviceStatus.firstSwitch),
threeGangId),
DevicesAPI.controlDevice( DevicesAPI.controlDevice(
DeviceControlModel(deviceId: threeGangId, code: 'switch_2', value: false), threeGangId), DeviceControlModel(
deviceId: threeGangId, code: 'switch_2', value: deviceStatus.secondSwitch),
threeGangId),
DevicesAPI.controlDevice( 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)) { if (response.every((element) => !element['success'])) {
deviceStatus.firstSwitch = false; await Future.delayed(const Duration(milliseconds: 500));
deviceStatus.secondSwitch = false; add(const InitialEvent(groupScreen: false));
deviceStatus.thirdSwitch = false;
} }
} catch (_) {} } catch (_) {
emit(UpdateState(threeGangModel: deviceStatus)); await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: false));
}
} }
void _allOn(AllOnEvent event, Emitter<ThreeGangState> emit) async { void _allOn(AllOnEvent event, Emitter<ThreeGangState> emit) async {
emit(LoadingNewSate(threeGangModel: deviceStatus)); emit(LoadingNewSate(threeGangModel: deviceStatus));
try { try {
deviceStatus.firstSwitch = true;
deviceStatus.secondSwitch = true;
deviceStatus.thirdSwitch = true;
emit(UpdateState(threeGangModel: deviceStatus));
final response = await Future.wait([ final response = await Future.wait([
DevicesAPI.controlDevice( DevicesAPI.controlDevice(
DeviceControlModel(deviceId: threeGangId, code: 'switch_1', value: true), threeGangId), DeviceControlModel(
deviceId: threeGangId, code: 'switch_1', value: deviceStatus.firstSwitch),
threeGangId),
DevicesAPI.controlDevice( DevicesAPI.controlDevice(
DeviceControlModel(deviceId: threeGangId, code: 'switch_2', value: true), threeGangId), DeviceControlModel(
deviceId: threeGangId, code: 'switch_2', value: deviceStatus.secondSwitch),
threeGangId),
DevicesAPI.controlDevice( 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)) { if (response.every((element) => !element['success'])) {
deviceStatus.firstSwitch = true; await Future.delayed(const Duration(milliseconds: 500));
deviceStatus.secondSwitch = true; add(const InitialEvent(groupScreen: false));
deviceStatus.thirdSwitch = true;
} }
} catch (_) {} } catch (_) {
emit(UpdateState(threeGangModel: deviceStatus)); await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: false));
}
} }
void _groupAllOn(GroupAllOnEvent event, Emitter<ThreeGangState> emit) async { void _groupAllOn(GroupAllOnEvent event, Emitter<ThreeGangState> emit) async {
emit(LoadingNewSate(threeGangModel: deviceStatus)); emit(LoadingNewSate(threeGangModel: deviceStatus));
try { try {
for (int i = 0; i < groupThreeGangList.length; i++) { 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( DevicesAPI.controlDevice(
DeviceControlModel( DeviceControlModel(
deviceId: groupThreeGangList[i].deviceId, code: 'switch_1', value: true), deviceId: groupThreeGangList[i].deviceId, code: 'switch_1', value: true),
@ -222,18 +288,31 @@ class ThreeGangBloc extends Bloc<ThreeGangEvent, ThreeGangState> {
deviceId: groupThreeGangList[i].deviceId, code: 'switch_3', value: true), deviceId: groupThreeGangList[i].deviceId, code: 'switch_3', value: true),
groupThreeGangList[i].deviceId), groupThreeGangList[i].deviceId),
]); ]);
if (response.every((element) => !element['success'])) {
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: true));
break;
}
} }
} catch (_) {} } catch (_) {
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: true)); add(const InitialEvent(groupScreen: true));
}
} }
void _groupAllOff(GroupAllOffEvent event, Emitter<ThreeGangState> emit) async { void _groupAllOff(GroupAllOffEvent event, Emitter<ThreeGangState> emit) async {
emit(LoadingNewSate(threeGangModel: deviceStatus)); emit(LoadingNewSate(threeGangModel: deviceStatus));
try { try {
for (int i = 0; i < groupThreeGangList.length; i++) { 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( DevicesAPI.controlDevice(
DeviceControlModel( DeviceControlModel(
deviceId: groupThreeGangList[i].deviceId, code: 'switch_1', value: false), deviceId: groupThreeGangList[i].deviceId, code: 'switch_1', value: false),
@ -247,10 +326,17 @@ class ThreeGangBloc extends Bloc<ThreeGangEvent, ThreeGangState> {
deviceId: groupThreeGangList[i].deviceId, code: 'switch_3', value: false), deviceId: groupThreeGangList[i].deviceId, code: 'switch_3', value: false),
groupThreeGangList[i].deviceId), groupThreeGangList[i].deviceId),
]); ]);
if (response.every((element) => !element['success'])) {
await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: true));
break;
}
} }
} catch (_) {} } catch (_) {
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(milliseconds: 500));
add(const InitialEvent(groupScreen: true)); add(const InitialEvent(groupScreen: true));
}
} }
void _changeSliding(ChangeSlidingSegment event, Emitter<ThreeGangState> emit) async { void _changeSliding(ChangeSlidingSegment event, Emitter<ThreeGangState> emit) async {

View File

@ -2,6 +2,7 @@ import 'package:syncrow_app/features/devices/model/status_model.dart';
import 'package:syncrow_app/utils/resource_manager/constants.dart'; import 'package:syncrow_app/utils/resource_manager/constants.dart';
class AcStatusModel { class AcStatusModel {
String uuid;
bool acSwitch; bool acSwitch;
String modeString; String modeString;
int tempSet; int tempSet;
@ -12,7 +13,8 @@ class AcStatusModel {
late FanSpeeds acFanSpeed; late FanSpeeds acFanSpeed;
AcStatusModel( AcStatusModel(
{required this.acSwitch, {required this.uuid,
required this.acSwitch,
required this.modeString, required this.modeString,
required this.tempSet, required this.tempSet,
required this.currentTemp, required this.currentTemp,
@ -22,7 +24,7 @@ class AcStatusModel {
acFanSpeed = getFanSpeed(fanSpeedsString); acFanSpeed = getFanSpeed(fanSpeedsString);
} }
factory AcStatusModel.fromJson(List<StatusModel> jsonList) { factory AcStatusModel.fromJson(String id, List<StatusModel> jsonList) {
late bool _acSwitch; late bool _acSwitch;
late String _mode; late String _mode;
late int _tempSet; late int _tempSet;
@ -45,6 +47,7 @@ class AcStatusModel {
} }
} }
return AcStatusModel( return AcStatusModel(
uuid: id,
acSwitch: _acSwitch, acSwitch: _acSwitch,
modeString: _mode, modeString: _mode,
tempSet: _tempSet, tempSet: _tempSet,

View File

@ -17,12 +17,13 @@ class DeviceModel {
String? timeZone; String? timeZone;
int? updateTime; int? updateTime;
String? uuid; String? uuid;
String? productUuid;
DeviceType? productType; DeviceType? productType;
bool isSelected = false; bool isSelected = false;
late List<FunctionModel> functions; late List<FunctionModel> functions;
DeviceModel( DeviceModel(
{this.activeTime, {this.activeTime,
// this.id, this.productUuid,
this.localKey, this.localKey,
this.model, this.model,
this.name, this.name,
@ -61,27 +62,26 @@ class DeviceModel {
tempIcon = Assets.assetsIconsLogo; tempIcon = Assets.assetsIconsLogo;
} }
return DeviceModel( return DeviceModel(
icon: tempIcon, icon: tempIcon,
activeTime: json['activeTime'], activeTime: json['activeTime'],
// id: json['id'], // id: json['id'],
localKey: json['localKey'], localKey: json['localKey'],
model: json['model'], model: json['model'],
name: json['name'], name: json['name'],
isOnline: json['online'], isOnline: json['online'],
productName: json['productName'], productName: json['productName'],
timeZone: json['timeZone'], timeZone: json['timeZone'],
updateTime: json['updateTime'], updateTime: json['updateTime'],
uuid: json['uuid'], uuid: json['uuid'],
productType: type, productType: type,
type: json['productType'], type: json['productType'],
status: [], status: [],
); productUuid: json['productUuid']);
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
'activeTime': activeTime, 'activeTime': activeTime,
// 'id': id,
'localKey': localKey, 'localKey': localKey,
'model': model, 'model': model,
'name': name, 'name': name,

View File

@ -1,9 +1,9 @@
class GroupThreeGangModel { class GroupThreeGangModel {
final String deviceId; final String deviceId;
final String deviceName; final String deviceName;
final bool firstSwitch; bool firstSwitch;
final bool secondSwitch; bool secondSwitch;
final bool thirdSwitch; bool thirdSwitch;
GroupThreeGangModel({ GroupThreeGangModel({
required this.deviceId, required this.deviceId,

View File

@ -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/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_large.dart';
import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
class AcInterface extends StatelessWidget { class AcInterface extends StatelessWidget {
const AcInterface({super.key, required this.ac}); const AcInterface({super.key, required this.ac});
@ -22,15 +23,12 @@ class AcInterface extends StatelessWidget {
return BlocConsumer<ACsBloc, AcsState>( return BlocConsumer<ACsBloc, AcsState>(
listener: (context, state) { listener: (context, state) {
if (state is AcsFailedState) { if (state is AcsFailedState) {
ScaffoldMessenger.of(context).showSnackBar( CustomSnackBar.displaySnackBar(state.errorMessage);
SnackBar(
content: Text(state.error),
),
);
} }
}, },
builder: (context, state) { builder: (context, state) {
AcStatusModel statusModel = AcStatusModel( AcStatusModel statusModel = AcStatusModel(
uuid: ac.uuid ?? '',
acSwitch: true, acSwitch: true,
modeString: 'hot', modeString: 'hot',
tempSet: 300, tempSet: 300,

View File

@ -9,14 +9,12 @@ import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/utils/resource_manager/constants.dart'; import 'package:syncrow_app/utils/resource_manager/constants.dart';
class ACModeControlUnit extends StatelessWidget { class ACModeControlUnit extends StatelessWidget {
const ACModeControlUnit({ const ACModeControlUnit(
super.key, {super.key, required this.acStatus, required this.deviceId, this.productId = ''});
required this.acStatus,
required this.deviceId,
});
final AcStatusModel acStatus; final AcStatusModel acStatus;
final String deviceId; final String deviceId;
final String? productId;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -28,8 +26,10 @@ class ACModeControlUnit extends StatelessWidget {
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
if (state is! AcChangeLoading && state is! AcsLoadingState) { if (state is! AcChangeLoading && state is! AcsLoadingState) {
BlocProvider.of<ACsBloc>(context) BlocProvider.of<ACsBloc>(context).add(ChangeFanSpeed(
.add(ChangeFanSpeed(fanSpeeds: acStatus.acFanSpeed, deviceId: deviceId)); fanSpeeds: acStatus.acFanSpeed,
deviceId: deviceId,
productId: productId ?? ''));
} }
// else if (state is AcModifyingState) { // else if (state is AcModifyingState) {
// BlocProvider.of<ACsBloc>(context) // BlocProvider.of<ACsBloc>(context)
@ -54,8 +54,10 @@ class ACModeControlUnit extends StatelessWidget {
// .add(ChangeAcMode(tempModes: state.acStatusModel.acMode)); // .add(ChangeAcMode(tempModes: state.acStatusModel.acMode));
// } // }
if (state is! AcChangeLoading && state is! AcsLoadingState) { if (state is! AcChangeLoading && state is! AcsLoadingState) {
BlocProvider.of<ACsBloc>(context) BlocProvider.of<ACsBloc>(context).add(ChangeAcMode(
.add(ChangeAcMode(tempModes: acStatus.acMode, deviceId: deviceId)); tempModes: acStatus.acMode,
deviceId: deviceId,
productId: productId ?? ''));
} }
}, },
child: DefaultContainer( child: DefaultContainer(

View File

@ -35,10 +35,10 @@ class ACTempWidget extends StatelessWidget {
child: InkWell( child: InkWell(
onTap: () { onTap: () {
double tempC = temp / 10; double tempC = temp / 10;
if (tempC > 20) { BlocProvider.of<ACsBloc>(context).add(DecreaseCoolToTemp(
BlocProvider.of<ACsBloc>(context) value: tempC,
.add(DecreaseCoolToTemp(value: tempC, deviceId: deviceModel.uuid ?? '')); deviceId: deviceModel.uuid ?? '',
} productId: deviceModel.productUuid ?? ''));
}, },
child: SvgPicture.asset( child: SvgPicture.asset(
Assets.assetsIconsMinus, Assets.assetsIconsMinus,
@ -57,10 +57,10 @@ class ACTempWidget extends StatelessWidget {
child: InkWell( child: InkWell(
onTap: () { onTap: () {
double tempC = temp / 10; double tempC = temp / 10;
if (tempC < 30) { BlocProvider.of<ACsBloc>(context).add(IncreaseCoolToTemp(
BlocProvider.of<ACsBloc>(context) value: tempC,
.add(IncreaseCoolToTemp(value: tempC, deviceId: deviceModel.uuid ?? '')); deviceId: deviceModel.uuid ?? '',
} productId: deviceModel.productUuid ?? ''));
}, },
child: SvgPicture.asset( child: SvgPicture.asset(
Assets.assetsIconsPlus, Assets.assetsIconsPlus,

View File

@ -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/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/ACs/universal_ac_temp.dart';
import 'package:syncrow_app/features/devices/view/widgets/universal_switch.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/devices_default_switch.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
class ACsList extends StatelessWidget { class ACsList extends StatelessWidget {
const ACsList({ const ACsList({
@ -20,7 +20,12 @@ class ACsList extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<ACsBloc, AcsState>( return BlocConsumer<ACsBloc, AcsState>(
listener: (context, state) {
if (state is AcsFailedState) {
CustomSnackBar.displaySnackBar(state.errorMessage);
}
},
builder: (context, state) { builder: (context, state) {
List<AcStatusModel> devicesStatuesList = []; List<AcStatusModel> devicesStatuesList = [];
List<DeviceModel> devicesList = []; List<DeviceModel> devicesList = [];
@ -35,74 +40,78 @@ class ACsList extends StatelessWidget {
temperature = state.temp; temperature = state.temp;
} }
return SingleChildScrollView( return SingleChildScrollView(
child: state is AcChangeLoading || state is AcsLoadingState child:
? const Center( // state is AcChangeLoading || state is AcsLoadingState
child: // ? const Center(
DefaultContainer(width: 50, height: 50, child: CircularProgressIndicator()), // child:
) // DefaultContainer(width: 50, height: 50, child: CircularProgressIndicator()),
: Column( // )
crossAxisAlignment: CrossAxisAlignment.stretch, // :
children: [ Column(
// universal AC controller crossAxisAlignment: CrossAxisAlignment.stretch,
const SizedBox(height: 10), children: [
const BodySmall(text: "All ACs"), // universal AC controller
const SizedBox(height: 5), const SizedBox(height: 10),
UniversalSwitch( const BodySmall(text: "All ACs"),
allOn: allOn, const SizedBox(height: 5),
), UniversalSwitch(
const SizedBox(height: 10), allOn: allOn,
UniversalACTemp( ),
allTempSame: allTempSame, const SizedBox(height: 10),
temp: temperature, UniversalACTemp(
), allTempSame: allTempSame,
const SizedBox(height: 10), temp: temperature,
),
const SizedBox(height: 10),
// other ACs controls // other ACs controls
ListView.builder( ListView.builder(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.all(0), padding: const EdgeInsets.all(0),
itemCount: devicesList.length, itemCount: devicesList.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SizedBox( const SizedBox(
height: 10, height: 10,
), ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
BodySmall(text: devicesList[index].name ?? ''), BodySmall(text: devicesList[index].name ?? ''),
], ],
), ),
const SizedBox(height: 5), const SizedBox(height: 5),
DevicesDefaultSwitch( DevicesDefaultSwitch(
switchValue: devicesStatuesList[index].acSwitch, switchValue: devicesStatuesList[index].acSwitch,
action: () { action: () {
BlocProvider.of<ACsBloc>(context).add(AcSwitch( BlocProvider.of<ACsBloc>(context).add(AcSwitch(
acSwitch: devicesStatuesList[index].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],
deviceId: devicesList[index].uuid ?? '', deviceId: devicesList[index].uuid ?? '',
), productId: devicesList[index].productUuid ?? ''));
const SizedBox(height: 10), },
], ),
); 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),
],
);
},
),
],
),
); );
}, },
); );

View File

@ -29,9 +29,7 @@ class UniversalACTemp extends StatelessWidget {
child: InkWell( child: InkWell(
onTap: () { onTap: () {
double temperature = temp / 10; double temperature = temp / 10;
if (temperature < 30) { BlocProvider.of<ACsBloc>(context).add(DecreaseAllTemp(value: temperature));
BlocProvider.of<ACsBloc>(context).add(DecreaseAllTemp(value: temperature));
}
}, },
child: SvgPicture.asset( child: SvgPicture.asset(
Assets.assetsIconsMinus, Assets.assetsIconsMinus,
@ -50,9 +48,7 @@ class UniversalACTemp extends StatelessWidget {
child: InkWell( child: InkWell(
onTap: () { onTap: () {
double temperature = temp / 10; double temperature = temp / 10;
if (temperature > 20) { BlocProvider.of<ACsBloc>(context).add(IncreaseAllTemp(value: temperature));
BlocProvider.of<ACsBloc>(context).add(IncreaseAllTemp(value: temperature));
}
}, },
child: SvgPicture.asset( child: SvgPicture.asset(
Assets.assetsIconsPlus, Assets.assetsIconsPlus,

View File

@ -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_event.dart';
import 'package:syncrow_app/features/devices/bloc/three_gang_bloc/three_gang_state.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/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/devices_default_switch.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
@ -18,78 +17,71 @@ class ThreeGangList extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<ThreeGangBloc, ThreeGangState>( return BlocBuilder<ThreeGangBloc, ThreeGangState>(
builder: (context, state) { builder: (context, state) {
return state is LoadingNewSate return SingleChildScrollView(
? const Center( child: Column(
child: DefaultContainer(width: 50, height: 50, child: CircularProgressIndicator()), crossAxisAlignment: CrossAxisAlignment.stretch,
) children: [
: SingleChildScrollView( const SizedBox(height: 10),
child: Column( const BodySmall(text: 'All Lights'),
crossAxisAlignment: CrossAxisAlignment.stretch, const SizedBox(height: 5),
children: [ DevicesDefaultSwitch(
const SizedBox(height: 10), switchValue: allSwitches,
const BodySmall(text: 'All Lights'), action: () {
const SizedBox(height: 5), BlocProvider.of<ThreeGangBloc>(context).add(GroupAllOnEvent());
DevicesDefaultSwitch( },
switchValue: allSwitches, secondAction: () {
action: () { BlocProvider.of<ThreeGangBloc>(context).add(GroupAllOffEvent());
BlocProvider.of<ThreeGangBloc>(context).add(GroupAllOnEvent()); },
}, ),
secondAction: () { ListView.builder(
BlocProvider.of<ThreeGangBloc>(context).add(GroupAllOffEvent()); shrinkWrap: true,
}, physics: const NeverScrollableScrollPhysics(),
), padding: const EdgeInsets.all(0),
ListView.builder( itemCount: threeGangList.length,
shrinkWrap: true, itemBuilder: (context, index) {
physics: const NeverScrollableScrollPhysics(), return Column(
padding: const EdgeInsets.all(0), crossAxisAlignment: CrossAxisAlignment.start,
itemCount: threeGangList.length, children: [
itemBuilder: (context, index) { const SizedBox(height: 10),
return Column( BodySmall(text: '${threeGangList[index].deviceName} beside light'),
crossAxisAlignment: CrossAxisAlignment.start, const SizedBox(height: 5),
children: [ DevicesDefaultSwitch(
const SizedBox(height: 10), switchValue: threeGangList[index].firstSwitch,
BodySmall(text: '${threeGangList[index].deviceName} beside light'), action: () {
const SizedBox(height: 5), BlocProvider.of<ThreeGangBloc>(context).add(ChangeFirstSwitchStatusEvent(
DevicesDefaultSwitch( value: threeGangList[index].firstSwitch,
switchValue: threeGangList[index].firstSwitch, deviceId: threeGangList[index].deviceId));
action: () { },
BlocProvider.of<ThreeGangBloc>(context).add( ),
ChangeFirstSwitchStatusEvent( const SizedBox(height: 10),
value: threeGangList[index].firstSwitch, BodySmall(text: '${threeGangList[index].deviceName} ceiling light'),
deviceId: threeGangList[index].deviceId)); const SizedBox(height: 5),
}, DevicesDefaultSwitch(
), switchValue: threeGangList[index].secondSwitch,
const SizedBox(height: 10), action: () {
BodySmall(text: '${threeGangList[index].deviceName} ceiling light'), BlocProvider.of<ThreeGangBloc>(context).add(ChangeSecondSwitchStatusEvent(
const SizedBox(height: 5), value: threeGangList[index].secondSwitch,
DevicesDefaultSwitch( deviceId: threeGangList[index].deviceId));
switchValue: threeGangList[index].secondSwitch, },
action: () { ),
BlocProvider.of<ThreeGangBloc>(context).add( const SizedBox(height: 10),
ChangeSecondSwitchStatusEvent( BodySmall(text: '${threeGangList[index].deviceName} spotlight'),
value: threeGangList[index].secondSwitch, const SizedBox(height: 5),
deviceId: threeGangList[index].deviceId)); DevicesDefaultSwitch(
}, switchValue: threeGangList[index].thirdSwitch,
), action: () {
const SizedBox(height: 10), BlocProvider.of<ThreeGangBloc>(context).add(ChangeThirdSwitchStatusEvent(
BodySmall(text: '${threeGangList[index].deviceName} spotlight'), value: threeGangList[index].thirdSwitch,
const SizedBox(height: 5), deviceId: threeGangList[index].deviceId));
DevicesDefaultSwitch( },
switchValue: threeGangList[index].thirdSwitch, ),
action: () { ],
BlocProvider.of<ThreeGangBloc>(context).add( );
ChangeThirdSwitchStatusEvent( },
value: threeGangList[index].thirdSwitch, ),
deviceId: threeGangList[index].deviceId)); ],
}, ),
), );
],
);
},
),
],
),
);
}, },
); );
} }

View File

@ -7,85 +7,6 @@ class MenuCubit extends Cubit<MenuState> {
static MenuCubit of(context) => BlocProvider.of<MenuCubit>(context); static MenuCubit of(context) => BlocProvider.of<MenuCubit>(context);
// List<MenuListModel> menuLists = [ String name = '';
// 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',
// ),
// ],
// ),
// ];
} }

View File

@ -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<ProfileEvent, ProfileState> {
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<TimeZone>? timeZoneList;
List<RegionModel>? regionList;
ProfileBloc() : super(InitialState()) {
on<InitialProfileEvent>(_fetchUserInfo);
on<TimeZoneInitialEvent>(_fetchTimeZone);
on<RegionInitialEvent>(_fetchRegion);
on<SaveNameEvent>(saveName);
on<SelectImageEvent>(_selectImage);
on<ChangeNameEvent>(_changeName);
on<SelectTimeZoneEvent>(selectTimeZone);
on<SearchRegionEvent>(searchRegion);
on<SearchTimeZoneEvent>(searchTimeZone);
on<SelectRegionEvent>(selectRegion);
}
Future<void> saveName(SaveNameEvent event, Emitter<ProfileState> 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<HomeCubit>();
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<ProfileState> 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<ProfileState> 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<ProfileState> 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<ProfileState> 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<ProfileState> 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<ProfileState> 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<ProfileState> 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<ProfileState> emit) async {
try {
emit(LoadingInitialState());
regionList = await ProfileApi.fetchRegion();
emit(RegionsLoadedState(regions: regionList!));
} catch (e) {
emit(FailedState(errorMessage: e.toString()));
}
}
Future<void> _selectImage(SelectImageEvent event, Emitter<ProfileState> 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<void> _saveImage() async {
emit(LoadingInitialState());
List<int> 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<bool> _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;
}
}
}
}

View File

@ -0,0 +1,76 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';
abstract class ProfileEvent extends Equatable {
const ProfileEvent();
@override
List<Object> 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<Object> get props => [context];
}
class SelectImageEvent extends ProfileEvent {
final BuildContext context;
final bool isSelected;
const SelectImageEvent({required this.context,required this.isSelected});
@override
List<Object> 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<Object> get props => [val];
}
class SelectRegionEvent extends ProfileEvent {
final String val;
final BuildContext context;
const SelectRegionEvent({required this.val,required this.context});
@override
List<Object> get props => [val,context];
}
class SearchRegionEvent extends ProfileEvent {
final String query;
const SearchRegionEvent({required this.query});
@override
List<Object> get props => [query];
}
class SearchTimeZoneEvent extends ProfileEvent {
final String query;
const SearchTimeZoneEvent({required this.query});
@override
List<Object> get props => [query];
}

View File

@ -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<Object> get props => [];
}
class InitialState extends ProfileState {}
class LoadingInitialState extends ProfileState {}
class UpdateState extends ProfileState {
final List<TimeZone> 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<Object> 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<RegionModel> regions;
const RegionsLoadedState({required this.regions});
}
class TimeZoneLoadedState extends ProfileState {
final List<TimeZone> regions;
const TimeZoneLoadedState({required this.regions});
}

View File

@ -0,0 +1,25 @@
class RegionModel {
final String name;
final String id;
RegionModel({
required this.name,
required this.id,
});
factory RegionModel.fromJson(Map<String, dynamic> json) {
return RegionModel(
name: json['regionName'],
id: json['uuid'].toString(), // Ensure id is a String
);
}
Map<String, dynamic> toJson() {
return {
'regionName': name,
'uuid': id,
};
}
}

View File

@ -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<String, dynamic> json) {
return TimeZone(
name: json['cityName'],
offset: json['timeZoneOffset'],
id: json['uuid'].toString(), // Ensure id is a String
);
}
Map<String, dynamic> toJson() {
return {
'name': name,
'offset': offset,
'id': id,
};
}
}

View File

@ -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/menu/view/widgets/profile/profile_tab.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart'; import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/utils/context_extension.dart'; import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/constants.dart'; import 'package:syncrow_app/utils/resource_manager/constants.dart';
@ -20,11 +21,12 @@ class MenuView extends StatelessWidget {
builder: (context, state) { builder: (context, state) {
return BlocBuilder<AuthCubit, AuthState>( return BlocBuilder<AuthCubit, AuthState>(
builder: (context, state) { builder: (context, state) {
final profileBloc = BlocProvider.of<MenuCubit>(context);
return SingleChildScrollView( return SingleChildScrollView(
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
child: Column( child: Column(
children: [ children: [
const ProfileTab(), ProfileTab(),
for (var section in menuSections) for (var section in menuSections)
MenuList( MenuList(
section: section, section: section,
@ -32,6 +34,11 @@ class MenuView extends StatelessWidget {
const SizedBox( const SizedBox(
height: 15, height: 15,
), ),
const BodyMedium(
text: String.fromEnvironment('FLAVOR', defaultValue: 'production')),
const SizedBox(
height: 15,
),
InkWell( InkWell(
onTap: () { onTap: () {
AuthCubit.get(context).logout(); AuthCubit.get(context).logout();

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_bloc.dart'; import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_bloc.dart';
import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_event.dart'; import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_event.dart';
import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_state.dart'; import 'package:syncrow_app/features/menu/bloc/create_unit_bloc/create_unit_state.dart';
@ -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_medium.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
import 'package:syncrow_app/generated/assets.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/context_extension.dart';
import 'package:syncrow_app/utils/helpers/snack_bar.dart'; import 'package:syncrow_app/utils/helpers/snack_bar.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart';

View File

@ -1,42 +1,60 @@
import 'package:flutter/material.dart'; 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/menu/view/widgets/profile/profile_view.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart'; import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/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_medium.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
class ProfileTab extends StatelessWidget { class ProfileTab extends StatelessWidget {
const ProfileTab({ const ProfileTab({super.key,});
super.key,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<HomeCubit, HomeState>(
builder: (context, state) {
return _buildProfileContent(context );
},
);
}
Widget _buildProfileContent(BuildContext context) {
final homeCubit = context.read<HomeCubit>();
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 10), padding: const EdgeInsets.symmetric(vertical: 10),
child: InkWell( child: InkWell(
onTap: () { onTap: () {
Navigator.of(context).push( Navigator.of(context)
.push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const ProfileView(), builder: (context) => const ProfileView(),
), ),
); ).then((result) {
context.read<HomeCubit>().fetchUserInfo();
});
}, },
child: const Stack( child: Stack(
children: [ children: [
Column( Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
SizedBox(height: 20), const SizedBox(height: 20),
DefaultContainer( DefaultContainer(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
BodyMedium( Row(
text: "Karim", children: [
fontWeight: FontWeight.bold, 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( child: CircleAvatar(
radius: 37, radius: 37,
backgroundColor: Colors.grey, 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
),
), ),
), ),
), ),

View File

@ -1,9 +1,16 @@
import 'package:flutter/material.dart'; 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_container.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/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'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
class ProfileView extends StatelessWidget { class ProfileView extends StatelessWidget {
@ -11,118 +18,171 @@ class ProfileView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DefaultScaffold(
title: 'Profile Page', return BlocProvider(
child: Column( create: (BuildContext context) => ProfileBloc()..add(InitialProfileEvent()),
children: [ child: BlocConsumer<ProfileBloc, ProfileState>(
//profile pic listener: (context, state) {},
const SizedBox.square( builder: (context, state) {
dimension: 120, final profileBloc = BlocProvider.of<ProfileBloc>(context);
child: CircleAvatar( return DefaultScaffold(
backgroundColor: Colors.white, title: 'Syncrow Account',
child: SizedBox.square( child:
dimension: 115, state is LoadingInitialState
child: CircleAvatar( ? const Center(child: CircularProgressIndicator()):
backgroundColor: Colors.grey, Column(
child: FlutterLogo(), 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,
),
),
),
],
),
],
)),
],
), ),
); );
} }

View File

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

View File

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

View File

@ -29,24 +29,23 @@ class SceneView extends StatelessWidget {
if (state.success) { if (state.success) {
BlocProvider.of<SceneBloc>(context) BlocProvider.of<SceneBloc>(context)
.add(LoadScenes(HomeCubit.getInstance().selectedSpace!.id!)); .add(LoadScenes(HomeCubit.getInstance().selectedSpace!.id!));
BlocProvider.of<SceneBloc>(context).add( BlocProvider.of<SceneBloc>(context)
LoadAutomation(HomeCubit.getInstance().selectedSpace!.id!)); .add(LoadAutomation(HomeCubit.getInstance().selectedSpace!.id!));
} }
} }
if (state is CreateSceneWithTasks) { if (state is CreateSceneWithTasks) {
if (state.success == true) { if (state.success == true) {
BlocProvider.of<SceneBloc>(context) BlocProvider.of<SceneBloc>(context)
.add(LoadScenes(HomeCubit.getInstance().selectedSpace!.id!)); .add(LoadScenes(HomeCubit.getInstance().selectedSpace!.id!));
BlocProvider.of<SceneBloc>(context).add( BlocProvider.of<SceneBloc>(context)
LoadAutomation(HomeCubit.getInstance().selectedSpace!.id!)); .add(LoadAutomation(HomeCubit.getInstance().selectedSpace!.id!));
} }
} }
return BlocListener<SceneBloc, SceneState>( return BlocListener<SceneBloc, SceneState>(
listener: (context, state) { listener: (context, state) {
if (state is SceneTriggerSuccess) { if (state is SceneTriggerSuccess) {
context.showCustomSnackbar( context.showCustomSnackbar(
message: message: 'Scene ${state.sceneName} triggered successfully!');
'Scene ${state.sceneName} triggered successfully!');
} }
}, },
child: HomeCubit.getInstance().spaces?.isEmpty ?? true child: HomeCubit.getInstance().spaces?.isEmpty ?? true
@ -76,32 +75,27 @@ class SceneView extends StatelessWidget {
return pageType return pageType
? Expanded( ? Expanded(
child: SceneListview( child: SceneListview(
scenes: state.scenes, scenes: scenes,
loadingSceneId: state.loadingSceneId, loadingSceneId: state.loadingSceneId,
)) ))
: Expanded( : Expanded(
child: ListView( child: ListView(
children: [ children: [
ExpansionTile( ExpansionTile(
tilePadding: tilePadding: const EdgeInsets.symmetric(horizontal: 6),
const EdgeInsets.symmetric(
horizontal: 6),
initiallyExpanded: true, initiallyExpanded: true,
iconColor: ColorsManager.grayColor, iconColor: ColorsManager.grayColor,
title: const BodySmall( title: const BodySmall(text: 'Tap to run routines'),
text: 'Tap to run routines'),
children: [ children: [
scenes.isNotEmpty scenes.isNotEmpty
? SceneGrid( ? SceneGrid(
scenes: scenes, scenes: scenes,
loadingSceneId: loadingSceneId: state.loadingSceneId,
state.loadingSceneId,
disablePLayButton: false, disablePLayButton: false,
) )
: const Center( : const Center(
child: BodyMedium( child: BodyMedium(
text: text: 'No scenes have been added yet',
'No scenes have been added yet',
), ),
), ),
const SizedBox( const SizedBox(
@ -112,23 +106,18 @@ class SceneView extends StatelessWidget {
ExpansionTile( ExpansionTile(
initiallyExpanded: true, initiallyExpanded: true,
iconColor: ColorsManager.grayColor, iconColor: ColorsManager.grayColor,
tilePadding: tilePadding: const EdgeInsets.symmetric(horizontal: 6),
const EdgeInsets.symmetric( title: const BodySmall(text: 'Automation'),
horizontal: 6),
title: const BodySmall(
text: 'Automation'),
children: [ children: [
automationList.isNotEmpty automationList.isNotEmpty
? SceneGrid( ? SceneGrid(
scenes: automationList, scenes: automationList,
loadingSceneId: loadingSceneId: state.loadingSceneId,
state.loadingSceneId,
disablePLayButton: true, disablePLayButton: true,
) )
: const Center( : const Center(
child: BodyMedium( child: BodyMedium(
text: text: 'No automations have been added yet',
'No automations have been added yet',
), ),
), ),
const SizedBox( const SizedBox(

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:syncrow_app/firebase_options.dart'; import 'package:syncrow_app/firebase_options.dart';
import 'package:syncrow_app/services/locator.dart'; import 'package:syncrow_app/services/locator.dart';
import 'package:syncrow_app/utils/bloc_observer.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 //to catch all the errors in the app and send them to firebase
runZonedGuarded(() async { runZonedGuarded(() async {
//to load the environment variables //to load the environment variables
// const environment = const environment = String.fromEnvironment('FLAVOR', defaultValue: 'production');
// String.fromEnvironment('FLAVOR', defaultValue: 'production'); await dotenv.load(fileName: '.env.$environment');
// await dotenv.load(fileName: '.env.$environment');
// //this is to make the app work with the self-signed certificate // //this is to make the app work with the self-signed certificate
// HttpOverrides.global = MyHttpOverrides(); // HttpOverrides.global = MyHttpOverrides();

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/auth/bloc/auth_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/scene/bloc/create_scene/create_scene_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/navigation/navigation_service.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.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) => AuthCubit()),
BlocProvider(create: (context) => CreateSceneBloc()), BlocProvider(create: (context) => CreateSceneBloc()),
BlocProvider(create: (context) => SceneBloc()), BlocProvider(create: (context) => SceneBloc()),
BlocProvider(create: (context) => ProfileBloc()),
], ],
child: MaterialApp( child: MaterialApp(

View File

@ -1,184 +1,172 @@
import 'package:flutter/foundation.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart';
abstract class ApiEndpoints { abstract class ApiEndpoints {
static const String baseUrl = kReleaseMode static String baseUrl = dotenv.env['BASE_URL'] ?? '';
? 'https://syncrow-staging.azurewebsites.net'
: 'https://syncrow-dev.azurewebsites.net';
// static const String baseUrl = 'http://100.107.182.63:4001'; //Localhost
////////////////////////////////////// Authentication /////////////////////////////// ////////////////////////////////////// Authentication ///////////////////////////////
static const String signUp = '$baseUrl/authentication/user/signup'; static const String signUp = '/authentication/user/signup';
static const String login = '$baseUrl/authentication/user/login'; static const String login = '/authentication/user/login';
static const String deleteUser = '$baseUrl/authentication/user/delete/{id}'; static const String deleteUser = '/authentication/user/delete/{id}';
static const String sendOtp = '$baseUrl/authentication/user/send-otp'; static const String sendOtp = '/authentication/user/send-otp';
static const String verifyOtp = '$baseUrl/authentication/user/verify-otp'; static const String verifyOtp = '/authentication/user/verify-otp';
static const String forgetPassword = static const String forgetPassword = '/authentication/user/forget-password';
'$baseUrl/authentication/user/forget-password';
////////////////////////////////////// Spaces /////////////////////////////////////// ////////////////////////////////////// Spaces ///////////////////////////////////////
///Community Module ///Community Module
//POST //POST
static const String addCommunity = '$baseUrl/community'; static const String addCommunity = '/community';
static const String addCommunityToUser = '$baseUrl/community/user'; static const String addCommunityToUser = '/community/user';
//GET //GET
static const String communityByUuid = '$baseUrl/community/{communityUuid}'; static const String communityByUuid = '/community/{communityUuid}';
static const String communityChild = static const String communityChild = '/community/child/{communityUuid}';
'$baseUrl/community/child/{communityUuid}'; static const String communityUser = '/community/user/{userUuid}';
static const String communityUser = '$baseUrl/community/user/{userUuid}';
//PUT //PUT
static const String renameCommunity = static const String renameCommunity = '/community/rename/{communityUuid}';
'$baseUrl/community/rename/{communityUuid}';
///Building Module ///Building Module
//POST //POST
static const String addBuilding = '$baseUrl/building'; static const String addBuilding = '/building';
static const String addBuildingToUser = '$baseUrl/building/user'; static const String addBuildingToUser = '/building/user';
//GET //GET
static const String buildingByUuid = '$baseUrl/building/{buildingUuid}'; static const String buildingByUuid = '/building/{buildingUuid}';
static const String buildingChild = '$baseUrl/building/child/{buildingUuid}'; static const String buildingChild = '/building/child/{buildingUuid}';
static const String buildingParent = static const String buildingParent = '/building/parent/{buildingUuid}';
'$baseUrl/building/parent/{buildingUuid}'; static const String buildingUser = '/building/user/{userUuid}';
static const String buildingUser = '$baseUrl/building/user/{userUuid}';
//PUT //PUT
static const String renameBuilding = static const String renameBuilding = '/building/rename/{buildingUuid}';
'$baseUrl/building/rename/{buildingUuid}';
///Floor Module ///Floor Module
//POST //POST
static const String addFloor = '$baseUrl/floor'; static const String addFloor = '/floor';
static const String addFloorToUser = '$baseUrl/floor/user'; static const String addFloorToUser = '/floor/user';
//GET //GET
static const String floorByUuid = '$baseUrl/floor/{floorUuid}'; static const String floorByUuid = '/floor/{floorUuid}';
static const String floorChild = '$baseUrl/floor/child/{floorUuid}'; static const String floorChild = '/floor/child/{floorUuid}';
static const String floorParent = '$baseUrl/floor/parent/{floorUuid}'; static const String floorParent = '/floor/parent/{floorUuid}';
static const String floorUser = '$baseUrl/floor/user/{userUuid}'; static const String floorUser = '/floor/user/{userUuid}';
//PUT //PUT
static const String renameFloor = '$baseUrl/floor/rename/{floorUuid}'; static const String renameFloor = '/floor/rename/{floorUuid}';
///Unit Module ///Unit Module
//POST //POST
static const String addUnit = '$baseUrl/unit'; static const String addUnit = '/unit';
static const String addUnitToUser = '$baseUrl/unit/user'; static const String addUnitToUser = '/unit/user';
//GET //GET
static const String unitByUuid = '$baseUrl/unit/'; static const String unitByUuid = '/unit/';
static const String unitChild = '$baseUrl/unit/child/'; static const String unitChild = '/unit/child/';
static const String unitParent = '$baseUrl/unit/parent/{unitUuid}'; static const String unitParent = '/unit/parent/{unitUuid}';
static const String unitUser = '$baseUrl/unit/user/'; static const String unitUser = '/unit/user/';
static const String invitationCode = static const String invitationCode = '/unit/{unitUuid}/invitation-code';
'$baseUrl/unit/{unitUuid}/invitation-code'; static const String verifyInvitationCode = '/unit/user/verify-code';
static const String verifyInvitationCode = '$baseUrl/unit/user/verify-code';
//PUT //PUT
static const String renameUnit = '$baseUrl/unit/rename/{unitUuid}'; static const String renameUnit = '/unit/rename/{unitUuid}';
///Room Module ///Room Module
//POST //POST
static const String addRoom = '$baseUrl/room'; static const String addRoom = '/room';
static const String addRoomToUser = '$baseUrl/room/user'; static const String addRoomToUser = '/room/user';
//GET //GET
static const String roomByUuid = '$baseUrl/room/{roomUuid}'; static const String roomByUuid = '/room/{roomUuid}';
static const String roomParent = '$baseUrl/room/parent/{roomUuid}'; static const String roomParent = '/room/parent/{roomUuid}';
static const String roomUser = '$baseUrl/room/user/{userUuid}'; static const String roomUser = '/room/user/{userUuid}';
//PUT //PUT
static const String renameRoom = '$baseUrl/room/rename/{roomUuid}'; static const String renameRoom = '/room/rename/{roomUuid}';
///Group Module ///Group Module
//POST //POST
static const String addGroup = '$baseUrl/group'; static const String addGroup = '/group';
static const String controlGroup = '$baseUrl/group/control'; static const String controlGroup = '/group/control';
//GET //GET
static const String groupBySpace = '$baseUrl/group/{unitUuid}'; static const String groupBySpace = '/group/{unitUuid}';
static const String devicesByGroupName = static const String devicesByGroupName = '/group/{unitUuid}/devices/{groupName}';
'$baseUrl/group/{unitUuid}/devices/{groupName}';
static const String groupByUuid = '$baseUrl/group/{groupUuid}'; static const String groupByUuid = '/group/{groupUuid}';
//DELETE //DELETE
static const String deleteGroup = '$baseUrl/group/{groupUuid}'; static const String deleteGroup = '/group/{groupUuid}';
////////////////////////////////////// Devices /////////////////////////////////////// ////////////////////////////////////// Devices ///////////////////////////////////////
///Device Module ///Device Module
//POST //POST
static const String addDeviceToRoom = '$baseUrl/device/room'; static const String addDeviceToRoom = '/device/room';
static const String addDeviceToGroup = '$baseUrl/device/group'; static const String addDeviceToGroup = '/device/group';
static const String controlDevice = '$baseUrl/device/{deviceUuid}/control'; static const String controlDevice = '/device/{deviceUuid}/control';
static const String firmwareDevice = static const String firmwareDevice = '/device/{deviceUuid}/firmware/{firmwareVersion}';
'$baseUrl/device/{deviceUuid}/firmware/{firmwareVersion}'; static const String getDevicesByUserId = '/device/user/{userId}';
static const String getDevicesByUserId = '$baseUrl/device/user/{userId}'; static const String getDevicesByUnitId = '/device/unit/{unitUuid}';
static const String getDevicesByUnitId = '$baseUrl/device/unit/{unitUuid}';
//GET //GET
static const String deviceByRoom = '$baseUrl/device/room'; static const String deviceByRoom = '/device/room';
static const String deviceByUuid = '$baseUrl/device/{deviceUuid}'; static const String deviceByUuid = '/device/{deviceUuid}';
static const String deviceFunctions = static const String deviceFunctions = '/device/{deviceUuid}/functions';
'$baseUrl/device/{deviceUuid}/functions'; static const String gatewayApi = '/device/gateway/{gatewayUuid}/devices';
static const String gatewayApi = static const String deviceFunctionsStatus = '/device/{deviceUuid}/functions/status';
'$baseUrl/device/gateway/{gatewayUuid}/devices';
static const String deviceFunctionsStatus =
'$baseUrl/device/{deviceUuid}/functions/status';
///Device Permission Module ///Device Permission Module
//POST //POST
static const String addDevicePermission = '$baseUrl/device-permission/add'; static const String addDevicePermission = '/device-permission/add';
//GET //GET
static const String devicePermissionList = '$baseUrl/device-permission/list'; static const String devicePermissionList = '/device-permission/list';
//PUT //PUT
static const String editDevicePermission = static const String editDevicePermission = '/device-permission/edit/{userId}';
'$baseUrl/device-permission/edit/{userId}';
static const String assignDeviceToRoom = '$baseUrl/device/room'; static const String assignDeviceToRoom = '/device/room';
/// Scene & Automation API //////////////////// /// Scene & Automation API ////////////////////
/// POST /// POST
static const String createScene = '$baseUrl/scene/tap-to-run'; static const String createScene = '/scene/tap-to-run';
static const String triggerScene = static const String triggerScene = '/scene/tap-to-run/trigger/{sceneId}';
'$baseUrl/scene/tap-to-run/trigger/{sceneId}'; static const String createAutomation = '/automation';
static const String createAutomation = '$baseUrl/automation';
/// GET /// 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 = static const String getAutomationDetails = '/automation/details/{automationId}';
'$baseUrl/automation/details/{automationId}';
/// PUT /// 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 /// DELETE
static const String deleteScene = static const String deleteScene = '/scene/tap-to-run/{unitUuid}/{sceneId}';
'$baseUrl/scene/tap-to-run/{unitUuid}/{sceneId}';
static const String deleteAutomation = static const String deleteAutomation = '/automation/{unitUuid}/{automationId}';
'$baseUrl/automation/{unitUuid}/{automationId}';
//////////////////////Door Lock ////////////////////// //////////////////////Door Lock //////////////////////
//online //online
static const String addTemporaryPassword = static const String addTemporaryPassword = '/door-lock/temporary-password/online/{doorLockUuid}';
'$baseUrl/door-lock/temporary-password/online/{doorLockUuid}'; static const String getTemporaryPassword = '/door-lock/temporary-password/online/{doorLockUuid}';
static const String getTemporaryPassword =
'$baseUrl/door-lock/temporary-password/online/{doorLockUuid}';
//one-time offline //one-time offline
static const String addOneTimeTemporaryPassword = 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 = static const String getOneTimeTemporaryPassword =
'$baseUrl/door-lock/temporary-password/offline/one-time/{doorLockUuid}'; '/door-lock/temporary-password/offline/one-time/{doorLockUuid}';
//multiple-time offline //multiple-time offline
static const String addMultipleTimeTemporaryPassword = 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 = static const String getMultipleTimeTemporaryPassword =
'$baseUrl/door-lock/temporary-password/offline/multiple-time/{doorLockUuid}'; '/door-lock/temporary-password/offline/multiple-time/{doorLockUuid}';
//multiple-time offline //multiple-time offline
static const String deleteTemporaryPassword = 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';
} }

View File

@ -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<Map<String, dynamic>> 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<Map<String, dynamic>> 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<List<RegionModel>> 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<RegionModel>;
}
static Future<List<TimeZone>> 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<TimeZone>;
}
}

View File

@ -129,6 +129,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" 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: dio:
dependency: "direct main" dependency: "direct main"
description: description:
@ -169,6 +185,38 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.0" 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: firebase_analytics:
dependency: "direct main" dependency: "direct main"
description: description:
@ -307,6 +355,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" 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: flutter_secure_storage:
dependency: "direct main" dependency: "direct main"
description: description:
@ -405,6 +461,70 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" 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: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@ -986,6 +1106,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.2.0" 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: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@ -1003,5 +1131,5 @@ packages:
source: hosted source: hosted
version: "6.5.0" version: "6.5.0"
sdks: sdks:
dart: ">=3.3.0 <4.0.0" dart: ">=3.4.0 <4.0.0"
flutter: ">=3.19.0" flutter: ">=3.22.0"

View File

@ -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. # pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: "none" # Remove this line if you wish to publish to pub.dev publish_to: "none" # Remove this line if you wish to publish to pub.dev
version: 1.0.1+11 version: 1.0.1+12
environment: environment:
sdk: ">=3.0.6 <4.0.0" sdk: ">=3.0.6 <4.0.0"
@ -43,6 +43,8 @@ dependencies:
smooth_page_indicator: ^1.1.0 smooth_page_indicator: ^1.1.0
uuid: ^4.4.0 uuid: ^4.4.0
time_picker_spinner: ^1.0.0 time_picker_spinner: ^1.0.0
image_picker: ^1.1.2
device_info_plus: ^10.1.0
dev_dependencies: dev_dependencies:
flutter_lints: ^3.0.1 flutter_lints: ^3.0.1
@ -75,6 +77,11 @@ flutter:
- assets/icons/curtainsIcon/ - assets/icons/curtainsIcon/
- assets/icons/functions_icons/ - assets/icons/functions_icons/
- assets/icons/functions_icons/automation_functions/ - assets/icons/functions_icons/automation_functions/
- .env.development
- .env.staging
- .env.production
fonts: fonts:
- family: Aftika - family: Aftika
fonts: fonts: