Merged with Dev

This commit is contained in:
Abdullah Alassaf
2025-01-26 13:08:40 +03:00
60 changed files with 3824 additions and 644 deletions

View File

@ -1,4 +1,5 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
@ -55,7 +56,8 @@ class HomeCubit extends Cubit<HomeState> {
Future fetchUserInfo() async {
try {
var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
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) {
@ -123,7 +125,9 @@ class HomeCubit extends Cubit<HomeState> {
return;
}
var userUuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey) ?? '';
var userUuid =
await const FlutterSecureStorage().read(key: UserModel.userUuidKey) ??
'';
if (userUuid.isNotEmpty) {
await OneSignal.login(userUuid);
}
@ -219,7 +223,8 @@ class HomeCubit extends Cubit<HomeState> {
//////////////////////////////////////// API ////////////////////////////////////////
generateInvitation(SpaceModel unit) async {
try {
final invitationCode = await SpacesAPI.generateInvitationCode(unit.id, unit.community.uuid);
final invitationCode =
await SpacesAPI.generateInvitationCode(unit.id, unit.community.uuid);
if (invitationCode.isNotEmpty) {
Share.share('The invitation code is $invitationCode');
CustomSnackBar.displaySnackBar(
@ -235,7 +240,9 @@ class HomeCubit extends Cubit<HomeState> {
Future<bool> joinAUnit(String code) async {
try {
var userUuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey) ?? '';
var userUuid =
await const FlutterSecureStorage().read(key: UserModel.userUuidKey) ??
'';
Map<String, String> body = {'inviteCode': code};
final success = await SpacesAPI.joinUnit(userUuid, body);
@ -271,7 +278,8 @@ class HomeCubit extends Cubit<HomeState> {
fetchRoomsByUnitId(SpaceModel space) async {
emitSafe(GetSpaceRoomsLoading());
try {
space.subspaces = await SpacesAPI.getSubSpaceBySpaceId(space.community.uuid, space.id);
space.subspaces =
await SpacesAPI.getSubSpaceBySpaceId(space.community.uuid, space.id);
} catch (failure) {
emitSafe(GetSpaceRoomsError(failure.toString()));
return;
@ -283,6 +291,29 @@ class HomeCubit extends Cubit<HomeState> {
}
}
activationCode(activationCode) async {
try {
emitSafe(GetSpaceRoomsLoading());
var uuid =
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
var res = await SpacesAPI.activationCodeSpace(
activationCode: activationCode, userUuid: uuid);
if (res['success'] == true) {
fetchUserInfo();
fetchUnitsByUserId();
}
emitSafe(GetSpacesSuccess(spaces!));
return res['success'];
} on DioException catch (e) {
final errorMessage = e.response?.data['error']['message'];
emitSafe(ActivationError(errMessage: errorMessage));
return false;
} catch (e) {
emitSafe(ActivationError(errMessage: e.toString()));
return false;
}
}
/////////////////////////////////////// Nav ///////////////////////////////////////
static int pageIndex = 0;
@ -353,7 +384,8 @@ class HomeCubit extends Cubit<HomeState> {
size: 32,
),
style: ButtonStyle(
foregroundColor: WidgetStateProperty.all(ColorsManager.textPrimaryColor),
foregroundColor:
WidgetStateProperty.all(ColorsManager.textPrimaryColor),
),
onPressed: () {
Navigator.pushNamed(
@ -374,7 +406,8 @@ class HomeCubit extends Cubit<HomeState> {
NavigationService.navigatorKey.currentContext!
.read<SmartSceneSelectBloc>()
.add(const SmartSceneClearEvent());
BlocProvider.of<EffectPeriodBloc>(NavigationService.navigatorKey.currentState!.context)
BlocProvider.of<EffectPeriodBloc>(
NavigationService.navigatorKey.currentState!.context)
.add(ResetEffectivePeriod());
NavigationService.navigatorKey.currentContext!
.read<CreateSceneBloc>()
@ -447,7 +480,8 @@ class HomeCubit extends Cubit<HomeState> {
void updateDevice(String deviceId) async {
try {
final response = await DevicesAPI.firmwareDevice(deviceId: deviceId, firmwareVersion: '0');
final response = await DevicesAPI.firmwareDevice(
deviceId: deviceId, firmwareVersion: '0');
if (response['success'] ?? false) {
CustomSnackBar.displaySnackBar('No updates available');
}
@ -455,7 +489,8 @@ class HomeCubit extends Cubit<HomeState> {
}
}
BottomNavigationBarItem defaultBottomNavBarItem({required String icon, required String label}) {
BottomNavigationBarItem defaultBottomNavBarItem(
{required String icon, required String label}) {
return BottomNavigationBarItem(
icon: SvgPicture.asset(icon),
activeIcon: SvgPicture.asset(

View File

@ -32,6 +32,12 @@ class GetSpacesError extends HomeError {
//get rooms
class GetSpaceRoomsLoading extends HomeLoading {}
class ActivationError extends HomeLoading {
final String errMessage;
ActivationError({this.errMessage = ''});
}
class GetSpaceRoomsSuccess extends HomeSuccess {
final List<SubSpaceModel> rooms;
@ -58,6 +64,7 @@ class RoomSelected extends HomeState {
class RoomUnSelected extends HomeState {}
class NavChangePage extends HomeState {}
// Define new state classes
class HomeUserInfoLoaded extends HomeState {
final UserModel user;

View File

@ -1,7 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/app_layout/model/space_model.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/constants.dart';
class DefaultAppBar extends StatelessWidget implements PreferredSizeWidget {
@ -19,10 +22,21 @@ class DefaultAppBar extends StatelessWidget implements PreferredSizeWidget {
backgroundColor: Colors.transparent,
leadingWidth: 200,
toolbarHeight: Constants.appBarHeight,
leading: HomeCubit.getInstance().spaces!.isNotEmpty
? HomeCubit.appBarLeading[HomeCubit.bottomNavItems[HomeCubit.pageIndex].label]!
: null,
actions: HomeCubit.appBarActions[HomeCubit.bottomNavItems[HomeCubit.pageIndex].label],
leading: InkWell(
onTap: () {
final spaces = HomeCubit.getInstance().spaces!;
showSpaceBottomSheet(context, spaces);
},
child: HomeCubit.getInstance().spaces!.isNotEmpty
? AbsorbPointer(
absorbing: true,
child: HomeCubit.appBarLeading[HomeCubit
.bottomNavItems[HomeCubit.pageIndex].label]!,
)
: null,
),
actions: HomeCubit.appBarActions[
HomeCubit.bottomNavItems[HomeCubit.pageIndex].label],
));
},
);
@ -31,3 +45,83 @@ class DefaultAppBar extends StatelessWidget implements PreferredSizeWidget {
@override
Size get preferredSize => Size.fromHeight(Constants.appBarHeight);
}
void showSpaceBottomSheet(BuildContext context, List<SpaceModel> spaces) {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setModalState) {
String? selectedSpaceId = HomeCubit.getInstance().selectedSpace?.id;
final bool shouldLimitHeight = spaces.length > 5;
return Container(
constraints: shouldLimitHeight
? BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.5,
)
: const BoxConstraints(),
child: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 10),
Container(
decoration: const BoxDecoration(color: Colors.black12),
height: 5,
width: 50,
),
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: spaces.length,
itemBuilder: (BuildContext context, int index) {
final space = spaces[index];
return Padding(
padding: const EdgeInsets.only(left: 30, right: 30),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Text(space.name),
],
),
Radio<String>(
value: space.id,
groupValue: selectedSpaceId,
onChanged: (String? newValue) {
if (newValue != null) {
setModalState(() {
selectedSpaceId = newValue;
});
HomeCubit.getInstance().changeSelectedSpace(
spaces.firstWhere((s) => s.id == newValue),
);
Navigator.of(context).pop();
}
},
),
],
),
);
},
separatorBuilder: (BuildContext context, int index) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 50),
child: const Divider(
color: Colors.grey,
thickness: 0.5,
),
);
},
),
],
),
),
);
},
);
},
);
}

View File

@ -204,6 +204,7 @@ class AuthCubit extends Cubit<AuthState> {
const FlutterSecureStorage().write(
key: UserModel.userUuidKey,
value: Token.decodeToken(token.accessToken)['uuid'].toString());
user = UserModel.fromToken(token);
emailController.clear();
passwordController.clear();

View File

@ -1,5 +1,4 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:syncrow_app/features/auth/model/token.dart';
@ -14,6 +13,7 @@ class UserModel {
final bool? isEmailVerified;
final String? regionName;
final String? timeZone;
final String? regionUuid;
final bool? isAgreementAccepted;
UserModel({
@ -24,10 +24,11 @@ class UserModel {
required this.profilePicture,
required this.phoneNumber,
required this.isEmailVerified,
required this.regionUuid,
required this.isAgreementAccepted,
required this.regionName, // Add this line
required this.timeZone, // Add this line
required this.regionName,
required this.timeZone,
// required this.role,
});
factory UserModel.fromJson(Map<String, dynamic> json) {
@ -40,23 +41,24 @@ class UserModel {
phoneNumber: json['phoneNumber'],
isEmailVerified: json['isEmailVerified'],
isAgreementAccepted: json['isAgreementAccepted'],
regionName: json['region']?['regionName'], // Extract regionName
timeZone: json['timeZone']?['timeZoneOffset'], // Extract regionName
regionName: json['region']?['regionName'],
timeZone: json['timeZone']?['timeZoneOffset'],
regionUuid: json['region']?['uuid'],
);
}
//uuid to json
//from token
factory UserModel.fromToken(Token token) {
factory UserModel.fromToken(Token token) {
Map<String, dynamic> tempJson = Token.decodeToken(token.accessToken);
return UserModel(
uuid: tempJson['uuid'].toString(),
email: tempJson['email'],
lastName: tempJson['lastName'],
firstName:tempJson['firstName'] ,
firstName: tempJson['firstName'],
profilePicture: UserModel.decodeBase64Image(tempJson['profilePicture']),
phoneNumber: null,
isEmailVerified: null,
isAgreementAccepted: null,
regionUuid: null,
regionName: tempJson['region']?['regionName'],
timeZone: tempJson['timezone']?['timeZoneOffset'],
);
@ -71,14 +73,18 @@ class UserModel {
Map<String, dynamic> toJson() {
return {
'id': uuid,
'uuid': uuid,
'email': email,
'lastName': lastName,
'firstName': firstName,
'photoUrl': profilePicture,
'lastName': lastName,
'profilePicture':
profilePicture != null ? base64.encode(profilePicture!) : null,
'phoneNumber': phoneNumber,
'isEmailVerified': isEmailVerified,
'regionUuid': regionUuid,
'isAgreementAccepted': isAgreementAccepted,
'regionName': regionName,
'timeZone': timeZone,
};
}
}

View File

@ -3,12 +3,15 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart';
import 'package:syncrow_app/features/menu/bloc/privacy_policy.dart';
import 'package:syncrow_app/features/menu/bloc/user_agreement.dart';
import 'package:syncrow_app/features/shared_widgets/default_button.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/generated/assets.dart';
import 'package:syncrow_app/navigation/routing_constants.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/constants.dart';
import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
import 'package:syncrow_app/utils/resource_manager/styles_manager.dart';
@ -202,6 +205,93 @@ class SignUpView extends StatelessWidget {
hint: "At least 8 characters"),
),
const SizedBox(height: 40),
Center(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0),
child: Text.rich(
TextSpan(
text:
'By signing up you agree to our ',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
fontSize: 16,
color: ColorsManager
.onPrimaryColor,
),
children: [
WidgetSpan(
child: GestureDetector(
onTap: () {
Navigator.of(context)
.push(MaterialPageRoute(
builder: (context) =>
const UserAgreement(),
));
},
child: BodyMedium(
text: 'Terms & Conditions',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
decoration:
TextDecoration
.underline,
decorationColor:
ColorsManager
.onPrimaryColor,
color: ColorsManager
.onPrimaryColor,
fontSize: 16),
),
),
),
TextSpan(
text: ' and ',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
fontSize: 16,
color: ColorsManager
.onPrimaryColor,
)),
WidgetSpan(
child: GestureDetector(
onTap: () {
Navigator.of(context)
.push(MaterialPageRoute(
builder: (context) =>
const PrivacyPolicy(),
));
},
child: BodyMedium(
text: 'Privacy Policy',
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(
decoration:
TextDecoration
.underline,
decorationColor:
Colors.white,
color: ColorsManager
.onPrimaryColor,
fontSize: 16),
),
),
),
],
),
textAlign: TextAlign.center,
),
),
),
const SizedBox(height: 40),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [

View File

@ -14,6 +14,7 @@ import 'package:syncrow_app/utils/resource_manager/constants.dart';
class ACsBloc extends Bloc<AcsEvent, AcsState> {
final String acId;
AcStatusModel deviceStatus = AcStatusModel(
countdown1: 0,
uuid: '',
acSwitch: true,
modeString: 'hot',
@ -41,6 +42,12 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
on<IncreaseAllTemp>(_increaseAllTemp);
on<DecreaseAllTemp>(_decreaseAllTemp);
on<AcUpdated>(_onAcUpdated);
on<OnClose>(_onClose);
on<GetCounterEvent>(_getCounterValue);
on<SetCounterValue>(_setCounterValue);
on<TickTimer>(_onTickTimer);
// on<SetTimeOutValue>(_setTimeOutAlarm);
}
void _fetchAcsStatus(AcsInitial event, Emitter<AcsState> emit) async {
@ -61,7 +68,8 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
for (var status in response['status']) {
statusModelList.add(StatusModel.fromJson(status));
}
deviceStatus = AcStatusModel.fromJson(response['productUuid'], statusModelList);
deviceStatus =
AcStatusModel.fromJson(response['productUuid'], statusModelList);
emit(GetAcStatusState(acStatusModel: deviceStatus));
Future.delayed(const Duration(milliseconds: 500));
// _listenToChanges();
@ -74,18 +82,22 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
_listenToChanges() {
try {
DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$acId');
DatabaseReference ref =
FirebaseDatabase.instance.ref('device-status/$acId');
Stream<DatabaseEvent> stream = ref.onValue;
stream.listen((DatabaseEvent event) {
Map<dynamic, dynamic> usersMap = event.snapshot.value as Map<dynamic, dynamic>;
Map<dynamic, dynamic> usersMap =
event.snapshot.value as Map<dynamic, dynamic>;
List<StatusModel> statusList = [];
usersMap['status'].forEach((element) {
statusList.add(StatusModel(code: element['code'], value: element['value']));
statusList
.add(StatusModel(code: element['code'], value: element['value']));
});
deviceStatus = AcStatusModel.fromJson(usersMap['productUuid'], statusList);
deviceStatus =
AcStatusModel.fromJson(usersMap['productUuid'], statusList);
add(AcUpdated());
});
} catch (_) {}
@ -102,12 +114,14 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
HomeCubit.getInstance().selectedSpace?.id ?? '', 'AC');
for (int i = 0; i < devicesList.length; i++) {
var response = await DevicesAPI.getDeviceStatus(devicesList[i].uuid ?? '');
var response =
await DevicesAPI.getDeviceStatus(devicesList[i].uuid ?? '');
List<StatusModel> statusModelList = [];
for (var status in response['status']) {
statusModelList.add(StatusModel.fromJson(status));
}
deviceStatusList.add(AcStatusModel.fromJson(response['productUuid'], statusModelList));
deviceStatusList.add(
AcStatusModel.fromJson(response['productUuid'], statusModelList));
}
_setAllAcsTempsAndSwitches();
}
@ -129,7 +143,8 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
emit(AcModifyingState(acStatusModel: deviceStatus));
}
await _runDeBouncerForOneDevice(deviceId: event.deviceId, code: 'switch', value: acSwitchValue);
await _runDeBouncerForOneDevice(
deviceId: event.deviceId, code: 'switch', value: acSwitchValue);
}
void _changeAllAcSwitch(ChangeAllSwitch event, Emitter<AcsState> emit) async {
@ -190,7 +205,8 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
deviceStatus.childLock = lockValue;
emit(AcModifyingState(acStatusModel: deviceStatus));
await _runDeBouncerForOneDevice(deviceId: acId, code: 'child_lock', value: lockValue);
await _runDeBouncerForOneDevice(
deviceId: acId, code: 'child_lock', value: lockValue);
}
void _increaseCoolTo(IncreaseCoolToTemp event, Emitter<AcsState> emit) async {
@ -218,7 +234,8 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
emit(AcModifyingState(acStatusModel: deviceStatus));
}
await _runDeBouncerForOneDevice(deviceId: event.deviceId, code: 'temp_set', value: value);
await _runDeBouncerForOneDevice(
deviceId: event.deviceId, code: 'temp_set', value: value);
}
void _decreaseCoolTo(DecreaseCoolToTemp event, Emitter<AcsState> emit) async {
@ -246,7 +263,8 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
emit(AcModifyingState(acStatusModel: deviceStatus));
}
await _runDeBouncerForOneDevice(deviceId: event.deviceId, code: 'temp_set', value: value);
await _runDeBouncerForOneDevice(
deviceId: event.deviceId, code: 'temp_set', value: value);
}
void _changeAcMode(ChangeAcMode event, Emitter<AcsState> emit) async {
@ -268,7 +286,9 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
}
await _runDeBouncerForOneDevice(
deviceId: event.deviceId, code: 'mode', value: getACModeString(tempMode));
deviceId: event.deviceId,
code: 'mode',
value: getACModeString(tempMode));
}
void _changeFanSpeed(ChangeFanSpeed event, Emitter<AcsState> emit) async {
@ -281,19 +301,23 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
for (AcStatusModel ac in deviceStatusList) {
if (ac.uuid == event.productId) {
ac.fanSpeedsString = getNextFanSpeedKey(fanSpeed);
ac.acFanSpeed = AcStatusModel.getFanSpeed(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));
deviceStatus.acFanSpeed =
AcStatusModel.getFanSpeed(getNextFanSpeedKey(fanSpeed));
emit(AcModifyingState(acStatusModel: deviceStatus));
}
await _runDeBouncerForOneDevice(
deviceId: event.deviceId, code: 'level', value: getNextFanSpeedKey(fanSpeed));
deviceId: event.deviceId,
code: 'level',
value: getNextFanSpeedKey(fanSpeed));
}
String getACModeString(TempModes value) {
@ -338,7 +362,8 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
for (int i = 0; i < deviceStatusList.length; i++) {
try {
await DevicesAPI.controlDevice(
DeviceControlModel(deviceId: devicesList[i].uuid, code: code, value: value),
DeviceControlModel(
deviceId: devicesList[i].uuid, code: code, value: value),
devicesList[i].uuid ?? '');
} catch (_) {
await Future.delayed(const Duration(milliseconds: 500));
@ -360,7 +385,10 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
_timer = Timer(const Duration(seconds: 1), () async {
try {
final response = await DevicesAPI.controlDevice(
DeviceControlModel(deviceId: allAcsPage ? deviceId : acId, code: code, value: value),
DeviceControlModel(
deviceId: allAcsPage ? deviceId : acId,
code: code,
value: value),
allAcsPage ? deviceId : acId);
if (!response['success']) {
@ -377,7 +405,8 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
if (value >= 20 && value <= 30) {
return true;
} else {
emit(const AcsFailedState(errorMessage: 'The temperature must be between 20 and 30'));
emit(const AcsFailedState(
errorMessage: 'The temperature must be between 20 and 30'));
emit(GetAllAcsStatusState(
allAcsStatues: deviceStatusList,
allAcs: devicesList,
@ -396,4 +425,74 @@ class ACsBloc extends Bloc<AcsEvent, AcsState> {
allTempSame: allTempSame,
temp: globalTemp));
}
void _setCounterValue(SetCounterValue event, Emitter<AcsState> emit) async {
emit(AcsLoadingState());
int seconds = 0;
try {
seconds = event.seconds;
final response = await DevicesAPI.controlDevice(
DeviceControlModel(
deviceId: acId, code: 'countdown_time', value: event.duration),
acId);
if (response['success'] ?? false) {
deviceStatus.countdown1 = seconds;
} else {
emit(const AcsFailedState(errorMessage: 'Something went wrong'));
return;
}
} catch (e) {
emit(AcsFailedState(errorMessage: e.toString()));
return;
}
if (event.duration > 0) {
_onStartTimer(seconds);
} else {
_timer?.cancel();
emit(TimerRunComplete());
}
}
void _getCounterValue(GetCounterEvent event, Emitter<AcsState> emit) async {
try {
emit(AcsLoadingState());
var response = await DevicesAPI.getDeviceStatus(acId);
List<StatusModel> statusModelList = [];
for (var status in response['status']) {
statusModelList.add(StatusModel.fromJson(status));
}
deviceStatus =
AcStatusModel.fromJson(response['productUuid'], statusModelList);
if (event.deviceCode == 'countdown_time') {
deviceStatus.countdown1 > 0
? _onStartTimer(deviceStatus.countdown1)
: emit(UpdateTimerState(seconds: deviceStatus.countdown1));
}
} catch (e) {
emit(AcsFailedState(errorMessage: e.toString()));
return;
}
}
void _onStartTimer(int seconds) {
_timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
add(TickTimer(remainingTime: seconds - timer.tick));
});
}
void _onTickTimer(TickTimer event, Emitter<AcsState> emit) {
if (event.remainingTime > 0) {
emit(TimerRunInProgress(event.remainingTime));
} else {
_timer?.cancel();
emit(TimerRunComplete());
}
}
void _onClose(OnClose event, Emitter<AcsState> emit) {
_timer?.cancel(); // Cancel the timer
}
}

View File

@ -14,7 +14,8 @@ class AcSwitch extends AcsEvent {
final bool acSwitch;
final String deviceId;
final String productId;
const AcSwitch({required this.acSwitch, this.deviceId = '', this.productId = ''});
const AcSwitch(
{required this.acSwitch, this.deviceId = '', this.productId = ''});
@override
List<Object> get props => [acSwitch, deviceId, productId];
@ -35,7 +36,8 @@ class IncreaseCoolToTemp extends AcsEvent {
final double value;
final String deviceId;
final String productId;
const IncreaseCoolToTemp({required this.value, this.deviceId = '', this.productId = ''});
const IncreaseCoolToTemp(
{required this.value, this.deviceId = '', this.productId = ''});
@override
List<Object> get props => [value, deviceId];
@ -46,7 +48,8 @@ class DecreaseCoolToTemp extends AcsEvent {
final String deviceId;
final String productId;
const DecreaseCoolToTemp({required this.value, this.deviceId = '', this.productId = ''});
const DecreaseCoolToTemp(
{required this.value, this.deviceId = '', this.productId = ''});
@override
List<Object> get props => [value, deviceId];
@ -56,7 +59,8 @@ class ChangeAcMode extends AcsEvent {
final TempModes tempModes;
final String deviceId;
final String productId;
const ChangeAcMode({required this.tempModes, this.deviceId = '', this.productId = ''});
const ChangeAcMode(
{required this.tempModes, this.deviceId = '', this.productId = ''});
@override
List<Object> get props => [tempModes, deviceId, productId];
@ -67,7 +71,8 @@ class ChangeFanSpeed extends AcsEvent {
final String deviceId;
final String productId;
const ChangeFanSpeed({required this.fanSpeeds, this.deviceId = '', this.productId = ''});
const ChangeFanSpeed(
{required this.fanSpeeds, this.deviceId = '', this.productId = ''});
@override
List<Object> get props => [fanSpeeds, deviceId, productId];
@ -104,3 +109,52 @@ class DecreaseAllTemp extends AcsEvent {
@override
List<Object> get props => [value];
}
class SetCounterValue extends AcsEvent {
final int duration;
final String deviceCode;
final int seconds;
const SetCounterValue(
{required this.duration,
required this.deviceCode,
required this.seconds});
@override
List<Object> get props => [duration, deviceCode, seconds];
}
class SetTimeOutValue extends AcsEvent {
final Duration duration;
final String deviceCode;
const SetTimeOutValue({required this.duration, required this.deviceCode});
@override
List<Object> get props => [duration, deviceCode];
}
class StartTimer extends AcsEvent {
final int duration;
const StartTimer(this.duration);
@override
List<Object> get props => [duration];
}
class TickTimer extends AcsEvent {
final int remainingTime;
const TickTimer({required this.remainingTime});
@override
List<Object> get props => [remainingTime];
}
class StopTimer extends AcsEvent {}
class GetCounterEvent extends AcsEvent {
final String deviceCode;
const GetCounterEvent({required this.deviceCode});
@override
List<Object> get props => [deviceCode];
}
class OnClose extends AcsEvent {}

View File

@ -63,3 +63,22 @@ class AcsFailedState extends AcsState {
@override
List<Object> get props => [errorMessage];
}
class UpdateTimerState extends AcsState {
final int seconds;
const UpdateTimerState({required this.seconds});
@override
List<Object> get props => [seconds];
}
class TimerRunInProgress extends AcsState {
final int remainingTime;
const TimerRunInProgress(this.remainingTime);
@override
List<Object> get props => [remainingTime];
}
class TimerRunComplete extends AcsState {}

View File

@ -1,3 +1,4 @@
import 'package:dio/dio.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/ceiling_bloc/ceiling_sensor_event.dart';
@ -5,6 +6,7 @@ import 'package:syncrow_app/features/devices/bloc/ceiling_bloc/ceiling_sensor_st
import 'package:syncrow_app/features/devices/model/ceiling_sensor_model.dart';
import 'package:syncrow_app/features/devices/model/device_control_model.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/model/device_report_model.dart';
import 'package:syncrow_app/features/devices/model/status_model.dart';
import 'package:syncrow_app/services/api/devices_api.dart';
@ -17,9 +19,11 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
on<InitialEvent>(_fetchCeilingSensorStatus);
on<ChangeValueEvent>(_changeValue);
on<CeilingSensorUpdated>(_onCeilingSensorUpdated);
on<ReportLogsInitial>(fetchLogsForLastMonth);
}
void _fetchCeilingSensorStatus(InitialEvent event, Emitter<CeilingSensorState> emit) async {
void _fetchCeilingSensorStatus(
InitialEvent event, Emitter<CeilingSensorState> emit) async {
emit(LoadingInitialState());
try {
var response = await DevicesAPI.getDeviceStatus(deviceId);
@ -38,15 +42,18 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
_listenToChanges() {
try {
DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
DatabaseReference ref =
FirebaseDatabase.instance.ref('device-status/$deviceId');
Stream<DatabaseEvent> stream = ref.onValue;
stream.listen((DatabaseEvent event) {
Map<dynamic, dynamic> usersMap = event.snapshot.value as Map<dynamic, dynamic>;
Map<dynamic, dynamic> usersMap =
event.snapshot.value as Map<dynamic, dynamic>;
List<StatusModel> statusList = [];
usersMap['status'].forEach((element) {
statusList.add(StatusModel(code: element['code'], value: element['value']));
statusList
.add(StatusModel(code: element['code'], value: element['value']));
});
deviceStatus = CeilingSensorModel.fromJson(statusList);
@ -55,20 +62,62 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
} catch (_) {}
}
_onCeilingSensorUpdated(CeilingSensorUpdated event, Emitter<CeilingSensorState> emit) {
_onCeilingSensorUpdated(
CeilingSensorUpdated event, Emitter<CeilingSensorState> emit) {
emit(UpdateState(ceilingSensorModel: deviceStatus));
}
void _changeValue(ChangeValueEvent event, Emitter<CeilingSensorState> emit) async {
void _changeValue(
ChangeValueEvent event, Emitter<CeilingSensorState> emit) async {
emit(LoadingNewSate(ceilingSensorModel: deviceStatus));
try {
final response = await DevicesAPI.controlDevice(
DeviceControlModel(deviceId: deviceId, code: event.code, value: event.value), deviceId);
DeviceControlModel(
deviceId: deviceId, code: event.code, value: event.value),
deviceId);
if (response['success'] ?? false) {
deviceStatus.sensitivity = event.value;
}
if (event.type == 'Sensitivity') {
deviceStatus.sensitivity = event.value;
} else if (event.type == 'Maximum Distance') {
deviceStatus.movingMaxDis = event.value;
} else if (event.type == 'Nobody Time') {
deviceStatus.nobodyTime = event.value;
} else if (event.type == 'Space Type') {
deviceStatus.spaceType = SpaceTypes.values.firstWhere(
(e) => e.name == event.value,
orElse: () => SpaceTypes.none,
);
}
} //scene
emit(UpdateState(ceilingSensorModel: deviceStatus));
} catch (_) {}
emit(UpdateState(ceilingSensorModel: deviceStatus));
}
DeviceReport recordGroups =
DeviceReport(startTime: '0', endTime: '0', data: []);
Future<void> fetchLogsForLastMonth(
ReportLogsInitial event, Emitter<CeilingSensorState> emit) async {
DateTime now = DateTime.now();
DateTime lastMonth = DateTime(now.year, now.month - 1, now.day);
int startTime = lastMonth.millisecondsSinceEpoch;
int endTime = now.millisecondsSinceEpoch;
try {
emit(LoadingInitialState());
var response = await DevicesAPI.getReportLogs(
startTime: startTime.toString(),
endTime: endTime.toString(),
deviceUuid: deviceId,
code: 'presence_state',
);
recordGroups = response;
// print('---${recordGroups.data!.first.eventTime}');
emit(FitchData());
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
emit(FailedState(error: errorMessage));
}
}
}

View File

@ -14,10 +14,16 @@ class InitialEvent extends CeilingSensorEvent {}
class CeilingSensorUpdated extends CeilingSensorEvent {}
class ChangeValueEvent extends CeilingSensorEvent {
final int value;
final dynamic value;
final String code;
const ChangeValueEvent({required this.value, required this.code});
final String type;
const ChangeValueEvent({required this.value, required this.code,required this.type});
@override
List<Object> get props => [value, code];
List<Object> get props => [value, code,type];
}
class ReportLogsInitial extends CeilingSensorEvent {
const ReportLogsInitial();
}

View File

@ -10,6 +10,8 @@ class CeilingSensorState extends Equatable {
class InitialState extends CeilingSensorState {}
class FitchData extends CeilingSensorState {}
class LoadingInitialState extends CeilingSensorState {}
class UpdateState extends CeilingSensorState {

View File

@ -65,7 +65,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
batteryPercentage: 0,
);
void _fetchStatus(GarageDoorInitial event, Emitter<GarageDoorSensorState> emit) async {
void _fetchStatus(
GarageDoorInitial event, Emitter<GarageDoorSensorState> emit) async {
emit(GarageDoorLoadingState());
try {
var response = await DevicesAPI.getDeviceStatus(GDId);
@ -113,8 +114,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
}
}
void _toggleClosingReminder(
ToggleClosingReminderEvent event, Emitter<GarageDoorSensorState> emit) async {
void _toggleClosingReminder(ToggleClosingReminderEvent event,
Emitter<GarageDoorSensorState> emit) async {
emit(LoadingNewSate(doorSensor: deviceStatus));
try {
closingReminder = event.isClosingReminderEnabled;
@ -132,7 +133,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
}
}
void _toggleDoorAlarm(ToggleDoorAlarmEvent event, Emitter<GarageDoorSensorState> emit) async {
void _toggleDoorAlarm(
ToggleDoorAlarmEvent event, Emitter<GarageDoorSensorState> emit) async {
emit(LoadingNewSate(doorSensor: deviceStatus));
try {
doorAlarm = event.isDoorAlarmEnabled;
@ -150,7 +152,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
}
}
DeviceReport recordGroups = DeviceReport(startTime: '0', endTime: '0', data: []);
DeviceReport recordGroups =
DeviceReport(startTime: '0', endTime: '0', data: []);
Future<void> fetchLogsForLastMonth(
ReportLogsInitial event, Emitter<GarageDoorSensorState> emit) async {
@ -179,14 +182,16 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
_listenToChanges() {
try {
DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$GDId');
DatabaseReference ref =
FirebaseDatabase.instance.ref('device-status/$GDId');
Stream<DatabaseEvent> stream = ref.onValue;
stream.listen((DatabaseEvent event) async {
if (_timer != null) {
await Future.delayed(const Duration(seconds: 2));
}
Map<dynamic, dynamic> usersMap = event.snapshot.value as Map<dynamic, dynamic>;
Map<dynamic, dynamic> usersMap =
event.snapshot.value as Map<dynamic, dynamic>;
List<StatusModel> statusList = [];
usersMap['status'].forEach((element) {
statusList.add(StatusModel(code: element['code'], value: true));
@ -261,7 +266,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
deviceId: GDId,
);
List<dynamic> jsonData = response;
listSchedule = jsonData.map((item) => ScheduleModel.fromJson(item)).toList();
listSchedule =
jsonData.map((item) => ScheduleModel.fromJson(item)).toList();
emit(UpdateState(garageSensor: deviceStatus));
} on DioException catch (e) {
final errorData = e.response!.data;
@ -272,12 +278,13 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
int? getTimeStampWithoutSeconds(DateTime? dateTime) {
if (dateTime == null) return null;
DateTime dateTimeWithoutSeconds =
DateTime(dateTime.year, dateTime.month, dateTime.day, dateTime.hour, dateTime.minute);
DateTime dateTimeWithoutSeconds = DateTime(dateTime.year, dateTime.month,
dateTime.day, dateTime.hour, dateTime.minute);
return dateTimeWithoutSeconds.millisecondsSinceEpoch ~/ 1000;
}
Future toggleChange(ToggleScheduleEvent event, Emitter<GarageDoorSensorState> emit) async {
Future toggleChange(
ToggleScheduleEvent event, Emitter<GarageDoorSensorState> emit) async {
try {
emit(GarageDoorLoadingState());
final response = await DevicesAPI.changeSchedule(
@ -295,7 +302,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
}
}
Future deleteSchedule(DeleteScheduleEvent event, Emitter<GarageDoorSensorState> emit) async {
Future deleteSchedule(
DeleteScheduleEvent event, Emitter<GarageDoorSensorState> emit) async {
try {
emit(GarageDoorLoadingState());
final response = await DevicesAPI.deleteSchedule(
@ -314,13 +322,15 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
}
}
void toggleSelectedIndex(ToggleSelectedEvent event, Emitter<GarageDoorSensorState> emit) {
void toggleSelectedIndex(
ToggleSelectedEvent event, Emitter<GarageDoorSensorState> emit) {
emit(GarageDoorLoadingState());
selectedTabIndex = event.index;
emit(ChangeSlidingSegmentState(value: selectedTabIndex));
}
void toggleCreateSchedule(ToggleCreateScheduleEvent event, Emitter<GarageDoorSensorState> emit) {
void toggleCreateSchedule(
ToggleCreateScheduleEvent event, Emitter<GarageDoorSensorState> emit) {
emit(GarageDoorLoadingState());
createSchedule = !createSchedule;
selectedDays.clear();
@ -337,13 +347,16 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
int secondSelected = 0;
bool toggleDoor = false;
Future<void> selectSeconds(SelectSecondsEvent event, Emitter<GarageDoorSensorState> emit) async {
Future<void> selectSeconds(
SelectSecondsEvent event, Emitter<GarageDoorSensorState> emit) async {
try {
emit(GarageDoorLoadingState());
secondSelected = event.seconds;
await DevicesAPI.controlDevice(
DeviceControlModel(deviceId: GDId, code: 'tr_timecon', value: secondSelected), GDId);
DeviceControlModel(
deviceId: GDId, code: 'tr_timecon', value: secondSelected),
GDId);
emit(UpdateState(garageSensor: deviceStatus));
} on DioException catch (e) {
final errorData = e.response!.data;
@ -352,12 +365,15 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
}
}
openCloseGarageDoor(ToggleDoorEvent event, Emitter<GarageDoorSensorState> emit) async {
openCloseGarageDoor(
ToggleDoorEvent event, Emitter<GarageDoorSensorState> emit) async {
emit(GarageDoorLoadingState());
try {
toggleDoor = !event.toggle;
await DevicesAPI.controlDevice(
DeviceControlModel(deviceId: GDId, code: 'switch_1', value: toggleDoor), GDId);
DeviceControlModel(
deviceId: GDId, code: 'switch_1', value: toggleDoor),
GDId);
add(const GarageDoorInitial());
emit(UpdateState(garageSensor: deviceStatus));
} on DioException catch (e) {
@ -367,13 +383,16 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
}
}
void _setCounterValue(SetCounterValue event, Emitter<GarageDoorSensorState> emit) async {
void _setCounterValue(
SetCounterValue event, Emitter<GarageDoorSensorState> emit) async {
emit(LoadingNewSate(doorSensor: deviceStatus));
int seconds = 0;
try {
seconds = event.duration.inSeconds;
final response = await DevicesAPI.controlDevice(
DeviceControlModel(deviceId: GDId, code: 'countdown_1', value: seconds), GDId);
DeviceControlModel(
deviceId: GDId, code: 'countdown_1', value: seconds),
GDId);
if (response['success'] ?? false) {
deviceStatus.countdown1 = seconds;
@ -393,7 +412,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
}
}
void _getCounterValue(GetCounterEvent event, Emitter<GarageDoorSensorState> emit) async {
void _getCounterValue(
GetCounterEvent event, Emitter<GarageDoorSensorState> emit) async {
emit(LoadingInitialState());
try {
var response = await DevicesAPI.getDeviceStatus(GDId);
@ -434,7 +454,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
List<GroupGarageModel> groupList = [];
bool allSwitchesOn = true;
void _fetchWizardStatus(InitialWizardEvent event, Emitter<GarageDoorSensorState> emit) async {
void _fetchWizardStatus(
InitialWizardEvent event, Emitter<GarageDoorSensorState> emit) async {
emit(LoadingInitialState());
try {
devicesList = [];
@ -444,7 +465,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
HomeCubit.getInstance().selectedSpace?.id ?? '', 'GD');
for (int i = 0; i < devicesList.length; i++) {
var response = await DevicesAPI.getDeviceStatus(devicesList[i].uuid ?? '');
var response =
await DevicesAPI.getDeviceStatus(devicesList[i].uuid ?? '');
List<StatusModel> statusModelList = [];
for (var status in response['status']) {
statusModelList.add(StatusModel.fromJson(status));
@ -473,7 +495,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
}
}
void _groupAllOn(GroupAllOnEvent event, Emitter<GarageDoorSensorState> emit) async {
void _groupAllOn(
GroupAllOnEvent event, Emitter<GarageDoorSensorState> emit) async {
emit(LoadingNewSate(doorSensor: deviceStatus));
try {
// Set all switches (firstSwitch and secondSwitch) based on the event value (on/off)
@ -485,7 +508,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
emit(UpdateGroupState(garageList: groupList, allSwitches: true));
// Get a list of all device IDs
List<String> allDeviceIds = groupList.map((device) => device.deviceId).toList();
List<String> allDeviceIds =
groupList.map((device) => device.deviceId).toList();
// First call for switch_1
final response = await DevicesAPI.deviceBatchController(
@ -505,7 +529,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
}
}
void _groupAllOff(GroupAllOffEvent event, Emitter<GarageDoorSensorState> emit) async {
void _groupAllOff(
GroupAllOffEvent event, Emitter<GarageDoorSensorState> emit) async {
emit(LoadingNewSate(doorSensor: deviceStatus));
try {
// Set all switches (firstSwitch and secondSwitch) based on the event value (on/off)
@ -517,7 +542,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
emit(UpdateGroupState(garageList: groupList, allSwitches: false));
// Get a list of all device IDs
List<String> allDeviceIds = groupList.map((device) => device.deviceId).toList();
List<String> allDeviceIds =
groupList.map((device) => device.deviceId).toList();
// First call for switch_1
final response = await DevicesAPI.deviceBatchController(
@ -538,8 +564,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
}
}
void _changeFirstWizardSwitch(
ChangeFirstWizardSwitchStatusEvent event, Emitter<GarageDoorSensorState> emit) async {
void _changeFirstWizardSwitch(ChangeFirstWizardSwitchStatusEvent event,
Emitter<GarageDoorSensorState> emit) async {
emit(LoadingNewSate(doorSensor: deviceStatus));
try {
bool allSwitchesValue = true;
@ -552,7 +578,8 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
}
});
emit(UpdateGroupState(garageList: groupList, allSwitches: allSwitchesValue));
emit(UpdateGroupState(
garageList: groupList, allSwitches: allSwitchesValue));
final response = await DevicesAPI.deviceBatchController(
code: 'switch_1',
@ -568,13 +595,16 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorSensorState> {
}
}
void _setTimeOutAlarm(SetTimeOutValue event, Emitter<GarageDoorSensorState> emit) async {
void _setTimeOutAlarm(
SetTimeOutValue event, Emitter<GarageDoorSensorState> emit) async {
emit(LoadingNewSate(doorSensor: deviceStatus));
int seconds = 0;
try {
seconds = event.duration.inSeconds;
final response = await DevicesAPI.controlDevice(
DeviceControlModel(deviceId: GDId, code: 'countdown_alarm', value: seconds), GDId);
DeviceControlModel(
deviceId: GDId, code: 'countdown_alarm', value: seconds),
GDId);
if (response['success'] ?? false) {
deviceStatus.countdownAlarm = seconds;

View File

@ -11,12 +11,14 @@ class AcStatusModel {
bool childLock;
late TempModes acMode;
late FanSpeeds acFanSpeed;
int countdown1;
AcStatusModel(
{required this.uuid,
required this.acSwitch,
required this.modeString,
required this.tempSet,
required this.countdown1,
required this.currentTemp,
required this.fanSpeedsString,
required this.childLock}) {
@ -30,6 +32,7 @@ class AcStatusModel {
late int _tempSet;
late int _currentTemp;
late String _fanSpeeds;
late int _countdown1;
late bool _childLock;
for (int i = 0; i < jsonList.length; i++) {
if (jsonList[i].code == 'switch') {
@ -44,6 +47,8 @@ class AcStatusModel {
_fanSpeeds = jsonList[i].value ?? 210;
} else if (jsonList[i].code == 'child_lock') {
_childLock = jsonList[i].value ?? false;
} else if (jsonList[i].code == 'countdown_time') {
_countdown1 = jsonList[i].value ?? 0;
}
}
return AcStatusModel(
@ -53,6 +58,7 @@ class AcStatusModel {
tempSet: _tempSet,
currentTemp: _currentTemp,
fanSpeedsString: _fanSpeeds,
countdown1: _countdown1,
childLock: _childLock);
}

View File

@ -7,13 +7,19 @@ class CeilingSensorModel {
int presenceRange;
int sportsPara;
String bodyMovement;
String nobodyTime;
int movingMaxDis;
SpaceTypes spaceType;
CeilingSensorModel(
{required this.presenceState,
required this.sensitivity,
required this.nobodyTime,
required this.spaceType,
required this.checkingResult,
required this.presenceRange,
required this.sportsPara,
required this.movingMaxDis,
required this.bodyMovement});
factory CeilingSensorModel.fromJson(List<StatusModel> jsonList) {
@ -22,8 +28,10 @@ class CeilingSensorModel {
late String _checkingResult;
int _presenceRange = 1;
int _sportsPara = 1;
int _moving_max_dis = 0;
String _bodyMovement = 'none';
String _nobody_time = 'none';
SpaceTypes _spaceType = SpaceTypes.none;
for (int i = 0; i < jsonList.length; i++) {
if (jsonList[i].code == 'presence_state') {
_presenceState = jsonList[i].value ?? 'none';
@ -37,14 +45,46 @@ class CeilingSensorModel {
_sportsPara = jsonList[i].value ?? 0;
} else if (jsonList[i].code == 'body_movement') {
_bodyMovement = jsonList[i].value ?? '';
}
} else if (jsonList[i].code == 'nobody_time') {
_nobody_time = jsonList[i].value ?? 'none';
} else if (jsonList[i].code == 'moving_max_dis') {
_moving_max_dis = jsonList[i].value ?? 0;
} else if (jsonList[i].code == 'scene')
_spaceType = getSpaceType(jsonList[i].value ?? 'none');
}
return CeilingSensorModel(
spaceType: _spaceType,
movingMaxDis: _moving_max_dis,
presenceState: _presenceState,
sensitivity: _sensitivity,
checkingResult: _checkingResult,
presenceRange: _presenceRange,
sportsPara: _sportsPara,
nobodyTime: _nobody_time,
bodyMovement: _bodyMovement);
}
}
enum SpaceTypes {
none,
parlour,
area,
toilet,
bedroom,
}
SpaceTypes getSpaceType(String value) {
switch (value) {
case 'parlour':
return SpaceTypes.parlour;
case 'area':
return SpaceTypes.area;
case 'toilet':
return SpaceTypes.toilet;
case 'bedroom':
return SpaceTypes.bedroom;
case 'none':
default:
return SpaceTypes.none;
}
}

View File

@ -28,6 +28,7 @@ class AcInterface extends StatelessWidget {
},
builder: (context, state) {
AcStatusModel statusModel = AcStatusModel(
countdown1: 0,
uuid: ac.uuid ?? '',
acSwitch: true,
modeString: 'hot',

View File

@ -7,11 +7,13 @@ import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_state.dart';
import 'package:syncrow_app/features/devices/model/ac_model.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/ACs/ac_mode_control_unit.dart';
import 'package:syncrow_app/features/devices/view/widgets/ACs/ac_timer_page.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/generated/assets.dart';
class AcInterfaceControls extends StatelessWidget {
const AcInterfaceControls({super.key, required this.deviceModel, required this.deviceStatus});
const AcInterfaceControls(
{super.key, required this.deviceModel, required this.deviceStatus});
final DeviceModel deviceModel;
final AcStatusModel deviceStatus;
@ -20,8 +22,9 @@ class AcInterfaceControls extends StatelessWidget {
Widget build(BuildContext context) {
return BlocBuilder<ACsBloc, AcsState>(
builder: (context, state) {
String lockIconName =
deviceStatus.childLock ? Assets.assetsIconsLock : Assets.assetsIconsUnLock;
String lockIconName = deviceStatus.childLock
? Assets.assetsIconsLock
: Assets.assetsIconsUnLock;
return Column(
children: [
@ -34,11 +37,22 @@ class AcInterfaceControls extends StatelessWidget {
children: [
Flexible(
child: GestureDetector(
onTap: () {},
onTap: () {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) =>
AcTimerPage(
device: deviceModel,
deviceCode: deviceModel.type!,
switchCode: '',
)));
},
child: DefaultContainer(
height: 55,
child: Center(
child: SvgPicture.asset(Assets.assetsIconsAutomatedClock),
child:
SvgPicture.asset(Assets.assetsIconsAutomatedClock),
),
),
),

View File

@ -0,0 +1,168 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_app/features/devices/bloc/6_scene_switch_bloc/6_scene_state.dart';
import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_event.dart';
import 'package:syncrow_app/features/devices/bloc/acs_bloc/acs_state.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/ACs/custom_halfhour_timer_picker.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
class AcTimerPage extends StatelessWidget {
final DeviceModel device;
final String deviceCode;
final String switchCode;
const AcTimerPage(
{required this.device,
required this.deviceCode,
required this.switchCode,
super.key});
@override
Widget build(BuildContext context) {
return AnnotatedRegion(
value: SystemUiOverlayStyle(
statusBarColor: ColorsManager.primaryColor.withOpacity(0.5),
statusBarIconBrightness: Brightness.light,
),
child: BlocProvider(
create: (context) => ACsBloc(acId: device.uuid ?? ''),
child: BlocBuilder<ACsBloc, AcsState>(
builder: (context, state) {
final oneGangBloc = BlocProvider.of<ACsBloc>(context);
Duration duration = Duration.zero;
int selectedValue = 0;
int countNum = 0;
if (state is UpdateTimerState) {
countNum = state.seconds;
} else if (state is TimerRunInProgress) {
countNum = state.remainingTime;
} else if (state is TimerRunComplete) {
countNum = 0;
}
// else if (state is LoadingNewSate) {
// countNum = 0;
// }
return PopScope(
canPop: false,
onPopInvoked: (didPop) {
if (!didPop) {
oneGangBloc.add(OnClose());
Navigator.pop(context);
}
},
child: DefaultTabController(
length: 2,
child: DefaultScaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
centerTitle: true,
title: const BodyLarge(
text: 'Countdown',
fontColor: ColorsManager.primaryColor,
fontWeight: FontsManager.bold,
),
),
child: state is AcsLoadingState
? const Center(child: CircularProgressIndicator())
: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: MediaQuery.of(context).size.width,
decoration: const ShapeDecoration(
color: ColorsManager.onPrimaryColor,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(30)),
),
),
),
Center(
child: Container(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
countNum > 0
? BodyLarge(
text: formatDuration(countNum),
fontColor: ColorsManager
.slidingBlueColor,
fontSize: 40,
)
: Container(
child: CustomHalfHourPicker(
onValueChanged: (value) {
selectedValue =
(value * 10).toInt();
if (selectedValue == 5) {
duration = const Duration(
minutes: 30);
countNum =
duration.inSeconds;
} else {
duration = Duration(
minutes:
selectedValue *
6);
countNum =
duration.inSeconds;
}
print(
"Selected Value: $selectedValue, Duration: $duration");
},
),
),
GestureDetector(
onTap: () {
if (state is LoadingNewSate) {
return;
}
if (countNum > 0) {
oneGangBloc.add(SetCounterValue(
seconds: countNum,
deviceCode:'countdown_time',
duration: selectedValue));
} else if (duration != Duration.zero) {
oneGangBloc.add(SetCounterValue(
seconds: 0,
deviceCode:
'countdown_time',
duration: selectedValue));
}
},
child: SvgPicture.asset(countNum > 0
? Assets.pauseIcon
: Assets.playIcon)),
],
),
),
),
],
),
)));
},
),
),
);
}
String formatDuration(int seconds) {
final hours = (seconds ~/ 3600).toString().padLeft(2, '0');
final minutes = ((seconds % 3600) ~/ 60).toString().padLeft(2, '0');
final secs = (seconds % 60).toString().padLeft(2, '0');
return '$hours:$minutes:$secs';
}
}

View File

@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
class CustomHalfHourPicker extends StatefulWidget {
final Function(double) onValueChanged;
const CustomHalfHourPicker({super.key, required this.onValueChanged});
@override
_CustomHalfHourPickerState createState() => _CustomHalfHourPickerState();
}
class _CustomHalfHourPickerState extends State<CustomHalfHourPicker> {
double selectedValue = 0.0;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 200,
child: ListWheelScrollView.useDelegate(
itemExtent: 50,
perspective: 0.005,
physics: const FixedExtentScrollPhysics(),
overAndUnderCenterOpacity: 0.3,
onSelectedItemChanged: (index) {
setState(() {
selectedValue =
index * 0.5; // Convert index to half-hour increments
widget.onValueChanged(selectedValue);
});
},
childDelegate: ListWheelChildBuilderDelegate(
builder: (context, index) {
if (index < 0 || index > 48)
return null; // Limit to 24.0 (48 * 0.5)
return Center(
child: Text(
(index * 0.5)
.toStringAsFixed(1), // Display value as 0.0, 0.5, etc.
style: const TextStyle(
fontSize: 25,
color: Colors.black,
fontWeight: FontWeight.w800),
),
);
},
childCount: 49, // 0.0 to 24.0 (inclusive)
),
),
);
}
}

View File

@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
class CeilingHelpDescription extends StatelessWidget {
const CeilingHelpDescription({super.key});
@override
Widget build(BuildContext context) {
return DefaultScaffold(
title: 'Help Description',
child: Center(
child: ListView(
shrinkWrap: true,
padding: const EdgeInsets.all(16.0),
children: [
Text(
'1. Nobody, the report to someone\n'
'Instruction: The propagation and processing of electromagnetic waves are complicated. '
'There will be false alarms in the actual use of radar, and there will be some influencing '
'factors in the environment, including:\n\n'
'A. Physical disturbance: including air conditioning, fan, motor and other facilities vibration, '
'cats, dogs, mice, birds and other animals passing by, which may cause radar misjudgement '
'of the environment.\n\n'
'B. Space electromagnetic wave disturbance: This includes the possible existence of high-power '
'electrical equipment around the radar, electromagnetic-wave-intensive places, and the '
'simultaneous coexistence of multiple radars and other environmental factors. These '
'interferences are relatively few in home and office scenarios but may be more common '
'in factories and industrial environments.\n\n'
'C. Power supply disturbance: This is mainly caused by power supply radar crosstalk due to '
'associated facilities and equipment in the mains environment, resulting in an unstable '
'power supply for the radar and potential misjudgement.\n\n'
'2. In the case of human misreporting no one:\n'
'A. The presence of a human body may be beyond the radar test range.\n\n'
'B. The human body is covered by metal or by extremely thick office desks and chairs.\n\n'
'C. When sleeping, the body might not exhibit noticeable breathing micro-movements on the side, '
'leading to a short-term misjudgement as nobody.',
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
);
}
}

View File

@ -7,6 +7,11 @@ import 'package:syncrow_app/features/devices/bloc/ceiling_bloc/ceiling_sensor_ev
import 'package:syncrow_app/features/devices/bloc/ceiling_bloc/ceiling_sensor_state.dart';
import 'package:syncrow_app/features/devices/model/ceiling_sensor_model.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/view/widgets/ceiling_sensor/ceiling_help_description.dart';
import 'package:syncrow_app/features/devices/view/widgets/ceiling_sensor/double_parameter_control.dart';
import 'package:syncrow_app/features/devices/view/widgets/ceiling_sensor/max_distance_control.dart';
import 'package:syncrow_app/features/devices/view/widgets/ceiling_sensor/presence_record.dart';
import 'package:syncrow_app/features/devices/view/widgets/ceiling_sensor/presence_space_type.dart';
import 'package:syncrow_app/features/devices/view/widgets/device_appbar.dart';
import 'package:syncrow_app/features/devices/view/widgets/wall_sensor/wall_sensor_interface.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
@ -22,382 +27,375 @@ import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
class CeilingSensorInterface extends StatelessWidget {
const CeilingSensorInterface({super.key, required this.ceilingSensor});
final DeviceModel ceilingSensor;
@override
Widget build(BuildContext context) {
// String state = ceilingSensor.status
// .firstWhere((element) => element.code == "presence_state")
// .value
// .toString();
return BlocProvider(
create: (context) =>
CeilingSensorBloc(deviceId: ceilingSensor.uuid ?? '')..add(InitialEvent()),
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(builder: (context, state) {
CeilingSensorModel ceilingSensorModel = CeilingSensorModel(
presenceState: 'none',
sensitivity: 1,
checkingResult: '',
presenceRange: 1,
sportsPara: 1,
bodyMovement: 'none');
if (state is UpdateState) {
ceilingSensorModel = state.ceilingSensorModel;
} else if (state is LoadingNewSate) {
ceilingSensorModel = state.ceilingSensorModel;
}
return AnnotatedRegion(
value: SystemUiOverlayStyle(
statusBarColor: ColorsManager.primaryColor.withOpacity(0.5),
statusBarIconBrightness: Brightness.light,
),
child: Scaffold(
backgroundColor: ColorsManager.backgroundColor,
extendBodyBehindAppBar: true,
extendBody: true,
appBar: DeviceAppbar(
deviceName: ceilingSensor.name!,
deviceUuid: ceilingSensor.uuid!,
create: (context) => CeilingSensorBloc(deviceId: ceilingSensor.uuid ?? '')
..add(InitialEvent()),
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
builder: (context, state) {
final _bloc = BlocProvider.of<CeilingSensorBloc>(context);
CeilingSensorModel ceilingSensorModel = _initializeSensorModel(state);
return AnnotatedRegion(
value: SystemUiOverlayStyle(
statusBarColor: ColorsManager.primaryColor.withOpacity(0.5),
statusBarIconBrightness: Brightness.light,
),
body: Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
padding: const EdgeInsets.all(Constants.defaultPadding),
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(
Assets.assetsImagesBackground,
),
fit: BoxFit.cover,
opacity: 0.4,
),
),
child: state is LoadingInitialState
? const Center(
child: RefreshProgressIndicator(),
)
: SafeArea(
child: RefreshIndicator(
onRefresh: () async {
BlocProvider.of<CeilingSensorBloc>(context).add(InitialEvent());
},
child: Container(
child: ListView(
shrinkWrap: true,
children: [
Container(
height: MediaQuery.of(context).size.height,
child: Column(
children: [
Expanded(
child: Column(
children: [
// InkWell(
// onTap: () {
// if ((ceilingSensor.isOnline ?? false) == false) {
// ScaffoldMessenger.of(context).showSnackBar(
// const SnackBar(
// content: Text(
// 'Device is offline',
// ),
// backgroundColor: Colors.red,
// ),
// );
// return;
// }
// String controlCode = 'sensitivity';
// showDialog(
// context: context,
// builder: (context) => ParameterControlDialog(
// title: 'Sensitivity',
// sensor: ceilingSensor,
// controlCode: controlCode,
// value: ceilingSensor.status
// .firstWhere((element) => element.code == controlCode)
// .value as int,
// min: ceilingSensor.functions
// .firstWhere((element) => element.code == controlCode)
// .values
// ?.min ??
// 0,
// max: ceilingSensor.functions
// .firstWhere((element) => element.code == controlCode)
// .values
// ?.max ??
// 0,
// ),
// );
// },
// child:
// ),
SvgPicture.asset(
ceilingSensorModel.presenceState.toLowerCase() ==
'motion'
? Assets
.assetsIconsPresenceSensorAssetsPresenceSensorMotion
: Assets.assetsIconsPresenceSensorAssetsPresence,
width: 100,
height: 100,
// colorFilter: ColorFilter.mode(
// (ceilingSensor.isOnline ?? false)
// ? ColorsManager.primaryColor
// : Colors.grey.withOpacity(0.9),
// BlendMode.srcIn,
// ),
),
const SizedBox(
height: 10,
),
BodyMedium(
text: StringHelpers.toTitleCase(
ceilingSensorModel.presenceState),
// (ceilingSensor.isOnline ?? false)
// ? StringHelpers.toTitleCase(ceilingSensor.status
// .firstWhere((element) =>
// element.code == 'presence_state')
// .value
// .toString())
// : "Offline",
style: context.bodyMedium.copyWith(
fontWeight: FontsManager.bold,
),
),
],
),
),
Expanded(
flex: 3,
child: Column(
children: [
DefaultContainer(
padding: const EdgeInsets.symmetric(
vertical: 20,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const BodySmall(text: 'Sports Para'),
BodyLarge(
text: ceilingSensorModel.sportsPara
.toString(),
style: context.bodyLarge.copyWith(
fontWeight: FontsManager.bold,
),
),
],
),
),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 10),
child: Container(
width: 1,
height: 45,
color: ColorsManager.greyColor,
),
),
Flexible(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const BodySmall(
text: 'Detection Range',
textOverflow: TextOverflow.ellipsis,
),
BodyLarge(
text:
'${ceilingSensorModel.presenceRange}M',
textOverflow: TextOverflow.ellipsis,
style: context.bodyLarge.copyWith(
fontWeight: FontsManager.bold,
),
),
],
),
),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 10),
child: Container(
width: 1,
height: 45,
color: ColorsManager.greyColor,
),
),
Flexible(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const BodySmall(
text: 'Movement',
textOverflow: TextOverflow.ellipsis,
),
BodyLarge(
text: ceilingSensorModel.bodyMovement,
textOverflow: TextOverflow.ellipsis,
style: context.bodyLarge.copyWith(
fontWeight: FontsManager.bold,
),
),
],
),
),
],
)),
const SizedBox(
height: 15,
),
for (int index = 0;
index < ceilingSensorButtons().length;
index++)
DefaultContainer(
margin: const EdgeInsets.only(bottom: 5),
padding: const EdgeInsets.symmetric(
vertical: 12, horizontal: 20),
onTap: () async {
if (ceilingSensorButtons()[index]['title'] ==
'Sensitivity') {
final result = await showDialog(
context: context,
builder: (context) {
return ParameterControlDialog(
title: ceilingSensorButtons()[index]
['title']
.toString(),
sensor: ceilingSensor,
value: ceilingSensorModel.sensitivity,
min: 0,
max: 10,
);
},
);
if (result != null) {
BlocProvider.of<CeilingSensorBloc>(context).add(
ChangeValueEvent(
value: result, code: 'sensitivity'));
}
}
// if (ceilingSensorButtons[index]['page'] != null) {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) =>
// ceilingSensorButtons[index]['page'] as Widget,
// ),
// );
// }
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
SvgPicture.asset(
ceilingSensorButtons()[index]['icon']
as String,
// width: 30,
// height: 50,
),
const SizedBox(
width: 25,
),
BodyMedium(
text: ceilingSensorButtons()[index]['title']
as String,
style: context.bodyMedium.copyWith(
fontWeight: FontsManager.bold,
),
),
],
),
if (ceilingSensorButtons()[index]['withArrow'] ==
true)
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(ceilingSensorButtons(
sensitivity_val: ceilingSensorModel
.sensitivity
.toString())[index]['val']
.toString()),
const Icon(
Icons.arrow_forward_ios,
color: ColorsManager.greyColor,
size: 15,
),
],
),
],
),
),
],
),
),
],
),
),
],
),
),
))),
),
);
}),
child: Scaffold(
backgroundColor: ColorsManager.backgroundColor,
extendBodyBehindAppBar: true,
extendBody: true,
appBar: DeviceAppbar(
deviceName: ceilingSensor.name!,
deviceUuid: ceilingSensor.uuid!,
),
body: _buildBody(context, state, _bloc, ceilingSensorModel),
),
);
},
),
);
}
dynamic ceilingSensorButtons({
sensitivity_val = 0,
CeilingSensorModel _initializeSensorModel(CeilingSensorState state) {
CeilingSensorModel ceilingSensorModel = CeilingSensorModel(
spaceType: SpaceTypes.none,
nobodyTime: '',
movingMaxDis: 0,
presenceState: 'none',
sensitivity: 1,
checkingResult: '',
presenceRange: 1,
sportsPara: 1,
bodyMovement: 'none',
);
if (state is UpdateState) {
ceilingSensorModel = state.ceilingSensorModel;
} else if (state is LoadingNewSate) {
ceilingSensorModel = state.ceilingSensorModel;
}
return ceilingSensorModel;
}
Widget _buildBody(BuildContext context, CeilingSensorState state,
CeilingSensorBloc bloc, CeilingSensorModel model) {
return Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
padding: const EdgeInsets.all(Constants.defaultPadding),
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(Assets.assetsImagesBackground),
fit: BoxFit.cover,
opacity: 0.4,
),
),
child: state is LoadingInitialState
? const Center(child: RefreshProgressIndicator())
: SafeArea(
child: RefreshIndicator(
onRefresh: () async => bloc.add(InitialEvent()),
child: ListView(
shrinkWrap: true,
children: [
_buildMainContent(context, bloc, model),
],
),
),
),
);
}
Widget _buildMainContent(
BuildContext context, CeilingSensorBloc bloc, CeilingSensorModel model) {
return SizedBox(
height: MediaQuery.of(context).size.height,
child: Column(
children: [
_buildPresenceDisplay(context, model),
Expanded(flex: 3, child: _buildControlButtons(context, bloc, model)),
],
),
);
}
Widget _buildPresenceDisplay(BuildContext context, CeilingSensorModel model) {
return Expanded(
child: Column(
children: [
SvgPicture.asset(
model.presenceState.toLowerCase() == 'motion'
? Assets.assetsIconsPresenceSensorAssetsPresenceSensorMotion
: Assets.assetsIconsPresenceSensorAssetsPresence,
width: 100,
height: 100,
),
const SizedBox(height: 10),
BodyMedium(
text: StringHelpers.toTitleCase(model.presenceState),
style: context.bodyMedium.copyWith(fontWeight: FontsManager.bold),
),
],
),
);
}
Widget _buildControlButtons(
BuildContext context, CeilingSensorBloc bloc, CeilingSensorModel model) {
return Column(
children: [
DefaultContainer(
padding: const EdgeInsets.symmetric(vertical: 20),
child: _buildSensorAttributes(context, model),
),
const SizedBox(height: 15),
..._buildActionButtons(context, bloc, model),
],
);
}
Widget _buildSensorAttributes(
BuildContext context, CeilingSensorModel model) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildAttribute(context, 'Sports Para', model.sportsPara.toString()),
_divider(),
_buildAttribute(context, 'Detection Range', '${model.presenceRange}M'),
_divider(),
_buildAttribute(context, 'Movement', model.bodyMovement),
],
);
}
Widget _buildAttribute(BuildContext context, String label, String value) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
BodySmall(
text: label,
),
BodyLarge(
text: value,
style: context.bodyLarge.copyWith(
fontWeight: FontsManager.bold,
),
),
],
);
}
Widget _divider() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Container(width: 1, height: 45, color: ColorsManager.greyColor),
);
}
List<Widget> _buildActionButtons(
BuildContext context, CeilingSensorBloc bloc, CeilingSensorModel model) {
return ceilingSensorButtons().map((button) {
return DefaultContainer(
margin: const EdgeInsets.only(bottom: 5),
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 20),
onTap: () => _handleButtonTap(context, bloc, button, model),
child: _buildButtonContent(context, button, model),
);
}).toList();
}
Widget _buildButtonContent(BuildContext context, Map<String, dynamic> button,
CeilingSensorModel model) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
SvgPicture.asset(button['icon'] as String),
const SizedBox(width: 25),
BodyMedium(
text: button['title'] as String,
style: context.bodyMedium.copyWith(fontWeight: FontsManager.bold),
),
],
),
if (button['withArrow'] == true)
Row(
children: [
if (button['title'] == 'Sensitivity') ...[
Text(
model.sensitivity.toString(),
style: const TextStyle(color: Colors.black),
),
] else if (button['title'] == 'Maximum Distance') ...[
Text(
model.movingMaxDis.toString(),
style: const TextStyle(color: Colors.black),
),
const Text(
"m",
style: TextStyle(color: ColorsManager.greyColor),
),
] else if (button['title'] == 'Nobody Time') ...[
Text(
model.nobodyTime.toString(),
style: const TextStyle(color: Colors.black),
),
] else if (button['title'] == 'Space Type') ...[
Text(
model.spaceType.name.toString(),
style: const TextStyle(color: Colors.black),
),
] else ...[
Text(
button['val'].toString(),
style: const TextStyle(color: Colors.black),
),
],
const Icon(
Icons.arrow_forward_ios,
color: ColorsManager.greyColor,
size: 15,
),
],
),
],
);
}
Future<void> _handleButtonTap(BuildContext context, CeilingSensorBloc bloc,
Map<String, dynamic> button, CeilingSensorModel model) async {
final title = button['title'];
if (title == 'Space Type') {
final result = await _showDialog(
context,
PresenceSpaceTypeDialog(
description: 'Space Type',
onSpaceTypeSelected: (spaceType) {
bloc.add(ChangeValueEvent(
type: title.toString(), value: spaceType.name, code: 'scene'));
},
selectedSpaceType: model.spaceType,
),
);
} else if (title == 'Nobody Time') {
final result = await _showDialog(
context,
MaxDistanceControl(
title: title.toString(),
sensor: ceilingSensor,
value: model.nobodyTime,
min: 0.0,
max: 5.0,
measurement: '',
),
);
if (result != null) {
bloc.add(ChangeValueEvent(
type: title.toString(), value: result, code: 'nobody_time'));
}
} else if (title == 'Maximum Distance') {
final result = await _showDialog(
context,
DoubleParameterControl(
title: title.toString(),
sensor: ceilingSensor,
value: model.movingMaxDis,
min: 0,
max: 500,
measurement: 'm',
),
);
if (result != null) {
bloc.add(ChangeValueEvent(
type: title.toString(), value: result, code: 'moving_max_dis'));
}
} else if (title == 'Sensitivity') {
final result = await _showDialog(
context,
ParameterControlDialog(
title: title.toString(),
sensor: ceilingSensor,
value: model.sensitivity,
min: 0,
max: 10,
));
if (result != null) {
bloc.add(ChangeValueEvent(
type: title.toString(), value: result, code: 'sensitivity'));
}
} else if (title == 'Help Description') {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const CeilingHelpDescription()),
);
} else if (title == 'Induction History') {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PresenceRecord(uuid: ceilingSensor.uuid!)),
);
}
}
Future<dynamic> _showDialog(BuildContext context, Widget dialog) {
return showDialog(context: context, builder: (context) => dialog);
}
List<Map<String, dynamic>> ceilingSensorButtons({
sensitivityVal = 0,
spaceType = 'Office',
maximumDistanceVal = '3.9m',
nobodyTimeVal = '1hr',
}) =>
[
{
'title': 'Space Type',
'icon': Assets.spaceTypeIcon,
'page': null,
'withArrow': true,
'val': spaceType
},
{
'title': 'Sensitivity',
'icon': Assets.sensitivityIcon,
'page': null,
'withArrow': true,
'val': sensitivity_val
},
{
'title': 'Maximum Distance',
'icon': Assets.maximumDistanceIcon,
'page': null,
'withArrow': true,
'val': maximumDistanceVal,
},
{
'title': 'Nobody Time',
'icon': Assets.assetsIconsPresenceSensorAssetsEmpty,
'page': null,
'withArrow': true,
'val': nobodyTimeVal,
},
{
'title': 'Induction History',
'icon': Assets.assetsIconsPresenceSensorAssetsInductionRecording,
'page': null,
'withArrow': false,
},
{
'title': 'Help Description',
'icon': Assets.assetsIconsPresenceSensorAssetsHelpDescription,
'page': null,
'withArrow': false,
},
];
}) {
return [
{
'title': 'Space Type',
'icon': Assets.spaceTypeIcon,
'page': null,
'withArrow': true,
'val': spaceType,
},
{
'title': 'Sensitivity',
'icon': Assets.sensitivityIcon,
'page': null,
'withArrow': true,
'val': sensitivityVal,
},
{
'title': 'Maximum Distance',
'icon': Assets.maximumDistanceIcon,
'page': null,
'withArrow': true,
'val': maximumDistanceVal,
},
{
'title': 'Nobody Time',
'icon': Assets.assetsIconsPresenceSensorAssetsEmpty,
'page': null,
'withArrow': true,
'val': nobodyTimeVal,
},
{
'title': 'Induction History',
'icon': Assets.assetsIconsPresenceSensorAssetsInductionRecording,
'page': null,
'withArrow': false,
},
{
'title': 'Help Description',
'icon': Assets.assetsIconsPresenceSensorAssetsHelpDescription,
'page': null,
'withArrow': false,
},
];
}
}

View File

@ -0,0 +1,201 @@
import 'package:flutter/material.dart';
import 'package:syncrow_app/features/devices/model/device_model.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/helpers/snack_bar.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
class DoubleParameterControl extends StatefulWidget {
final String title;
final String measurement;
final DeviceModel sensor;
final int value;
final int min;
final int max;
const DoubleParameterControl({
super.key,
required this.title,
required this.measurement,
required this.sensor,
required this.value,
required this.min,
required this.max,
});
@override
DoubleParameterControlState createState() => DoubleParameterControlState();
}
class DoubleParameterControlState extends State<DoubleParameterControl> {
late int _value;
@override
void initState() {
super.initState();
_value = widget.value.clamp(widget.min, widget.max);
}
int? _getDivisions(int min, int max, int step) {
final range = max - min;
final divisions = (range / step).floor();
return divisions > 0 ? divisions : null;
}
@override
Widget build(BuildContext context) {
return Dialog(
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.only(top: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
BodyMedium(
text: widget.title,
style: context.bodyMedium.copyWith(
color: ColorsManager.primaryColorWithOpacity,
fontWeight: FontsManager.extraBold,
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 15,
horizontal: 50,
),
child: Container(
height: 1,
width: double.infinity,
color: ColorsManager.greyColor,
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 10,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
TitleMedium(
text: _value.toStringAsFixed(0),
style: context.titleMedium.copyWith(
color: Colors.black,
fontWeight: FontsManager.bold,
),
),
TitleMedium(
text: widget.measurement.toString(),
style: context.titleMedium.copyWith(
color: Colors.black,
fontWeight: FontsManager.bold,
),
),
],
),
),
SizedBox(
width: MediaQuery.sizeOf(context).width,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
onPressed: () {
setState(() {
_value = (_value - 50).clamp(widget.min, widget.max);
});
},
icon: const Icon(
Icons.remove,
color: Colors.grey,
),
),
Flexible(
child: Slider(
value: _value.toDouble(),
min: widget.min.toDouble(),
max: widget.max.toDouble(),
divisions: _getDivisions(
widget.min, widget.max, 50),
onChanged: (value) {
setState(() {
_value =
value.round();
});
},
label: _value.toStringAsFixed(0),
inactiveColor: ColorsManager.greyColor,
),
),
IconButton(
onPressed: () {
setState(() {
_value = (_value + 50).clamp(widget.min, widget.max);
});
},
icon: const Icon(
Icons.add,
color: Colors.grey,
),
),
],
),
),
Container(
height: 1,
width: double.infinity,
color: ColorsManager.greyColor,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
InkWell(
onTap: () {
Navigator.pop(context);
},
child: Center(
child: BodyMedium(
text: 'Cancel',
style: context.bodyMedium
.copyWith(color: ColorsManager.greyColor),
),
),
),
Container(
height: 50,
width: 1,
color: ColorsManager.greyColor,
),
InkWell(
onTap: () {
Navigator.pop(context, _value);
if (widget.sensor.isOnline == null) {
CustomSnackBar.displaySnackBar('The device is offline');
return;
}
if (!widget.sensor.isOnline!) {
CustomSnackBar.displaySnackBar('The device is offline');
return;
}
},
child: Center(
child: BodyMedium(
text: 'Confirm',
style: context.bodyMedium.copyWith(
color: ColorsManager.primaryColorWithOpacity),
),
),
),
],
)
],
),
),
);
}
}

View File

@ -0,0 +1,248 @@
import 'package:flutter/material.dart';
import 'package:syncrow_app/features/devices/model/device_model.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/helpers/snack_bar.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
class MaxDistanceControl extends StatefulWidget {
final String title;
final String measurement;
final DeviceModel sensor;
final String value;
final double min;
final double max;
const MaxDistanceControl({
super.key,
required this.title,
required this.measurement,
required this.sensor,
required this.value,
required this.min,
required this.max,
});
@override
MaxDistanceControlState createState() => MaxDistanceControlState();
}
int _parseValue(String value) {
if (value.endsWith('sec')) {
return int.parse(value.replaceAll('sec', '').trim());
} else if (value.endsWith('min')) {
return int.parse(value.replaceAll('min', '').trim()) * 60;
} else if (value.endsWith('hr')) {
return int.parse(value.replaceAll('hr', '').trim()) * 3600;
}
return 0; // Default to 0 if the format is unrecognized
}
class MaxDistanceControlState extends State<MaxDistanceControl> {
final List<double> _stepValues = [
0,
10,
30,
60,
120,
300,
600,
1800,
3600,
];
late int _currentIndex;
String _formatLabel(double seconds) {
if (seconds == 0) return 'None';
if (seconds < 60) return '${seconds.toInt()}sec';
if (seconds < 3600) {
final minutes = (seconds / 60).round();
return '${minutes}min';
}
final hours = (seconds / 3600).round();
return '${hours}hr';
}
int _nearestStepIndex(double initialValue) {
double minDiff = double.infinity;
int nearestIndex = 0;
for (int i = 0; i < _stepValues.length; i++) {
final diff = (initialValue - _stepValues[i]).abs();
if (diff < minDiff) {
minDiff = diff;
nearestIndex = i;
}
}
return nearestIndex;
}
@override
void initState() {
super.initState();
final int parsedSeconds = _parseValue(widget.value);
_currentIndex = _nearestStepIndex(parsedSeconds.toDouble());
}
@override
Widget build(BuildContext context) {
final double currentSeconds = _stepValues[_currentIndex];
return Dialog(
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
padding: const EdgeInsets.only(top: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
BodyMedium(
text: widget.title,
style: context.bodyMedium.copyWith(
color: ColorsManager.primaryColorWithOpacity,
fontWeight: FontsManager.extraBold,
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 15,
horizontal: 50,
),
child: Container(
height: 1,
width: double.infinity,
color: ColorsManager.greyColor,
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 10,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
TitleMedium(
text: _formatLabel(currentSeconds),
style: context.titleMedium.copyWith(
color: Colors.black,
fontWeight: FontsManager.bold,
),
),
const SizedBox(width: 5),
TitleMedium(
text: widget.measurement,
style: context.titleMedium.copyWith(
color: Colors.black,
fontWeight: FontsManager.bold,
),
),
],
),
),
SizedBox(
width: MediaQuery.sizeOf(context).width,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
onPressed: () {
setState(() {
_currentIndex =
(_currentIndex > 0) ? _currentIndex - 1 : 0;
});
},
icon: const Icon(
Icons.remove,
color: Colors.grey,
),
),
Flexible(
child: Slider(
min: 0,
max: (_stepValues.length - 1).toDouble(),
divisions: _stepValues.length - 1,
value: _currentIndex.toDouble(),
onChanged: (double newIndex) {
setState(() {
_currentIndex = newIndex.round();
});
},
label: _formatLabel(_stepValues[_currentIndex]),
inactiveColor: ColorsManager.greyColor,
),
),
IconButton(
onPressed: () {
setState(() {
_currentIndex = (_currentIndex < _stepValues.length - 1)
? _currentIndex + 1
: _currentIndex;
});
},
icon: const Icon(
Icons.add,
color: Colors.grey,
),
),
],
),
),
Container(
height: 1,
width: double.infinity,
color: ColorsManager.greyColor,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
InkWell(
onTap: () => Navigator.pop(context),
child: Center(
child: BodyMedium(
text: 'Cancel',
style: context.bodyMedium.copyWith(
color: ColorsManager.greyColor,
),
),
),
),
Container(
height: 50,
width: 1,
color: ColorsManager.greyColor,
),
InkWell(
onTap: () {
Future.delayed(const Duration(seconds: 1), () {
Navigator.pop(context, _formatLabel(currentSeconds));
if (widget.sensor.isOnline == null ||
widget.sensor.isOnline == false) {
CustomSnackBar.displaySnackBar('The device is offline');
return;
}
});
},
child: Center(
child: BodyMedium(
text: 'Confirm',
style: context.bodyMedium.copyWith(
color: ColorsManager.primaryColorWithOpacity,
),
),
),
),
],
),
],
),
),
);
}
}

View File

@ -0,0 +1,122 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:syncrow_app/features/devices/bloc/ceiling_bloc/ceiling_sensor_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/ceiling_bloc/ceiling_sensor_event.dart';
import 'package:syncrow_app/features/devices/bloc/ceiling_bloc/ceiling_sensor_state.dart';
import 'package:syncrow_app/features/devices/model/device_report_model.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/utils/resource_manager/color_manager.dart';
class PresenceRecord extends StatelessWidget {
final String uuid;
const PresenceRecord({super.key, required this.uuid});
@override
Widget build(BuildContext context) {
return DefaultScaffold(
title: 'Presence Record',
child: BlocProvider(
create: (context) =>
CeilingSensorBloc(deviceId: uuid)..add(const ReportLogsInitial()),
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
builder: (context, state) {
final garageDoorBloc = BlocProvider.of<CeilingSensorBloc>(context);
final Map<String, List<DeviceEvent>> groupedRecords = {};
if (state is LoadingInitialState) {
return const Center(
child: DefaultContainer(
width: 50, height: 50, child: CircularProgressIndicator()),
);
} else if (state is FitchData) {
for (var record in garageDoorBloc.recordGroups.data!) {
final DateTime eventDateTime =
DateTime.fromMillisecondsSinceEpoch(record.eventTime!);
final String formattedDate =
DateFormat('EEEE, dd/MM/yyyy').format(eventDateTime);
// Group by formatted date
if (groupedRecords.containsKey(formattedDate)) {
groupedRecords[formattedDate]!.add(record);
} else {
groupedRecords[formattedDate] = [record];
}
}
}
return ListView.builder(
itemCount: groupedRecords.length,
itemBuilder: (context, index) {
final String date = groupedRecords.keys.elementAt(index);
final List<DeviceEvent> recordsForDate = groupedRecords[date]!;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 5, top: 10),
child: Text(
date,
style: const TextStyle(
color: ColorsManager.grayColor,
fontSize: 13,
fontWeight: FontWeight.w700,
),
),
),
DefaultContainer(
child: Column(
children: [
...recordsForDate.asMap().entries.map((entry) {
final int idx = entry.key;
final DeviceEvent record = entry.value;
final DateTime eventDateTime =
DateTime.fromMillisecondsSinceEpoch(
record.eventTime!);
final String formattedTime =
DateFormat('HH:mm:ss').format(eventDateTime);
return Column(
children: [
Container(
child: ListTile(
leading: Icon(
record.value == 'true'
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
color: record.value == 'true'
? Colors.blue
: Colors.grey,
),
title: Text(
record.value == 'true'
? "Opened"
: "Closed",
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
subtitle: Text('$formattedTime'),
),
),
if (idx != recordsForDate.length - 1)
const Divider(
color: ColorsManager.graysColor,
),
],
);
}).toList(),
],
),
),
],
);
},
);
}),
),
);
}
}

View File

@ -0,0 +1,181 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/devices/model/ceiling_sensor_model.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
class PresenceSpaceTypeDialog extends StatefulWidget {
final String description;
final SpaceTypes selectedSpaceType;
final void Function(SpaceTypes spaceType) onSpaceTypeSelected;
const PresenceSpaceTypeDialog({
super.key,
required this.description,
required this.selectedSpaceType,
required this.onSpaceTypeSelected,
});
@override
_PresenceSpaceTypeDialogState createState() =>
_PresenceSpaceTypeDialogState();
}
class _PresenceSpaceTypeDialogState extends State<PresenceSpaceTypeDialog> {
late SpaceTypes _selectedSpaceType;
@override
void initState() {
super.initState();
_selectedSpaceType = widget.selectedSpaceType;
}
@override
Widget build(BuildContext context) {
final Map<SpaceTypes, String> spaceTypeIcons = {
SpaceTypes.none: Assets.office,
SpaceTypes.parlour: Assets.parlour,
SpaceTypes.area: Assets.dyi,
SpaceTypes.toilet: Assets.bathroom,
SpaceTypes.bedroom: Assets.bedroom,
};
final Map<SpaceTypes, String> spaceTypeTitles = {
SpaceTypes.none: 'None',
SpaceTypes.parlour: 'Parlour',
SpaceTypes.area: 'Area',
SpaceTypes.toilet: 'Toilet',
SpaceTypes.bedroom: 'Bedroom',
};
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: Container(
// padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
widget.description,
style: context.bodyMedium.copyWith(
color: ColorsManager.primaryColorWithOpacity,
fontWeight: FontsManager.extraBold,
fontSize: 16),
),
),
Container(
height: 1,
width: double.infinity,
color: ColorsManager.greyColor,
),
const SizedBox(height: 16),
Wrap(
runSpacing: 12,
spacing: 16,
children: spaceTypeIcons.entries.map((entry) {
final spaceType = entry.key;
final icon = entry.value;
final title = spaceTypeTitles[spaceType]!;
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
color: _selectedSpaceType == spaceType
? ColorsManager.primaryColorWithOpacity
: ColorsManager.textGray,
),
child: GestureDetector(
onTap: () {
setState(() {
_selectedSpaceType = spaceType;
});
},
child: Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(10),
child: SvgPicture.asset(
icon,
),
),
const SizedBox(height: 4),
Text(
title,
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(
color: _selectedSpaceType == spaceType
? ColorsManager.onPrimaryColor
: ColorsManager.blackColor,
fontWeight: FontWeight.w400,
fontSize: 15),
),
],
),
),
),
);
}).toList(),
),
const SizedBox(height: 20),
Container(
height: 1,
width: double.infinity,
color: ColorsManager.greyColor,
),
/// Cancel / Confirm
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
InkWell(
onTap: () => Navigator.pop(context),
child: Center(
child: BodyMedium(
text: 'Cancel',
style: context.bodyMedium.copyWith(
color: ColorsManager.greyColor,
),
),
),
),
Container(
height: 50,
width: 1,
color: ColorsManager.greyColor,
),
InkWell(
onTap: () {
widget.onSpaceTypeSelected(_selectedSpaceType);
Navigator.pop(context, _selectedSpaceType);
},
child: Center(
child: BodyMedium(
text: 'Confirm',
style: context.bodyMedium.copyWith(
color: ColorsManager.primaryColorWithOpacity,
),
),
),
),
],
),
],
),
),
);
}
}

View File

@ -1,14 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart';
import 'package:syncrow_app/features/scene/bloc/smart_scene/smart_scene_select_dart_bloc.dart';
import 'package:syncrow_app/features/scene/enum/create_scene_enum.dart';
import 'package:syncrow_app/features/scene/model/scene_settings_route_arguments.dart';
import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_bloc.dart';
import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_event.dart';
import 'package:syncrow_app/features/scene/model/scenes_model.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/navigation/routing_constants.dart';
class SceneListview extends StatelessWidget {
final List<ScenesModel> scenes;
@ -31,23 +28,28 @@ class SceneListview extends StatelessWidget {
padding: const EdgeInsets.only(right: 10),
child: DefaultContainer(
onTap: () {
Navigator.pushNamed(
context,
Routes.sceneTasksRoute,
arguments: SceneSettingsRouteArguments(
sceneType: CreateSceneEnum.tabToRun.name,
sceneId: scene.id,
sceneName: scene.name,
),
);
context.read<SmartSceneSelectBloc>().add(const SmartSceneClearEvent());
context
.read<SceneBloc>()
.add(SceneTrigger(scene.id, scene.name));
// Navigator.pushNamed(
// context,
// Routes.sceneTasksRoute,
// arguments: SceneSettingsRouteArguments(
// sceneType: CreateSceneEnum.tabToRun.name,
// sceneId: scene.id,
// sceneName: scene.name,
// ),
// );
// context.read<SmartSceneSelectBloc>()
// .add(const SmartSceneClearEvent());
BlocProvider.of<CreateSceneBloc>(context)
.add(FetchSceneTasksEvent(sceneId: scene.id, isAutomation: false));
// BlocProvider.of<CreateSceneBloc>(context).add(
// FetchSceneTasksEvent(
// sceneId: scene.id, isAutomation: false));
/// the state to set the scene type must be after the fetch
BlocProvider.of<CreateSceneBloc>(context)
.add(const SceneTypeEvent(CreateSceneEnum.tabToRun));
// /// the state to set the scene type must be after the fetch
// BlocProvider.of<CreateSceneBloc>(context)
// .add(const SceneTypeEvent(CreateSceneEnum.tabToRun));
},
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.4,
@ -62,7 +64,8 @@ class SceneListview extends StatelessWidget {
height: 32,
width: 32,
fit: BoxFit.fill,
errorBuilder: (context, error, stackTrace) => Image.asset(
errorBuilder: (context, error, stackTrace) =>
Image.asset(
Assets.assetsIconsLogo,
height: 32,
width: 32,

View File

@ -1,12 +1,35 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/services/api/profile_api.dart';
part 'menu_state.dart';
class MenuCubit extends Cubit<MenuState> {
MenuCubit() : super(MenuInitial());
static MenuCubit of(context) => BlocProvider.of<MenuCubit>(context);
String name = '';
String userAgreementHtml = "";
String privacyPolicyHtml = "";
Future<void> fetchAgreement() async {
try {
emit(MenuLoading());
final response = await ProfileApi().fetchUserAgreement();
userAgreementHtml = response;
emit(MenuLoaded(userAgreementHtml));
} catch (error) {
emit(MenuError(error.toString()));
}
}
Future<void> fetchPrivacyPolicy() async {
try {
emit(MenuLoading());
final response = await ProfileApi().fetchPrivacyPolicy();
privacyPolicyHtml = response;
emit(MenuLoaded(privacyPolicyHtml));
} catch (error) {
emit(MenuError(error.toString()));
}
}
}

View File

@ -3,3 +3,22 @@ part of 'menu_cubit.dart';
abstract class MenuState {}
class MenuInitial extends MenuState {}
class MenuLoading extends MenuState {}
class MenuLoaded extends MenuState {
final String userAgreementHtml;
MenuLoaded(this.userAgreementHtml);
}
class MenuError extends MenuState {
final String message;
MenuError(this.message);
}
class MenuNameUpdated extends MenuState {
final String name;
MenuNameUpdated(this.name);
}

View File

@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:syncrow_app/features/menu/bloc/menu_cubit.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:url_launcher/url_launcher.dart';
class PrivacyPolicy extends StatefulWidget {
const PrivacyPolicy({super.key});
@override
_PrivacyPolicyState createState() => _PrivacyPolicyState();
}
class _PrivacyPolicyState extends State<PrivacyPolicy> {
@override
void initState() {
super.initState();
MenuCubit.of(context).fetchPrivacyPolicy();
}
@override
Widget build(BuildContext context) {
return DefaultScaffold(
title: 'Privacy Policy',
child: BlocBuilder<MenuCubit, MenuState>(
builder: (context, state) {
if (state is MenuLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is MenuLoaded) {
return ListView(
children: [
Html(
data: state.userAgreementHtml.isNotEmpty
? state.userAgreementHtml
: '',
onLinkTap: (url, attributes, element) async {
final uri = Uri.parse(url!);
await launchUrl(uri, mode: LaunchMode.externalApplication);
},
),
],
);
} else if (state is MenuError) {
return Center(
child: Text(
'Error: ${state.message}',
style: const TextStyle(color: Colors.red),
),
);
}
return const Center(child: Text('Loading User Agreement...'));
},
),
);
}
}

View File

@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:syncrow_app/features/menu/bloc/menu_cubit.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:url_launcher/url_launcher.dart';
class UserAgreement extends StatefulWidget {
const UserAgreement({super.key});
@override
_UserAgreementState createState() => _UserAgreementState();
}
class _UserAgreementState extends State<UserAgreement> {
@override
void initState() {
super.initState();
MenuCubit.of(context).fetchAgreement();
}
@override
Widget build(BuildContext context) {
return DefaultScaffold(
title: 'User Agreement',
child: BlocBuilder<MenuCubit, MenuState>(
builder: (context, state) {
if (state is MenuLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is MenuLoaded) {
return ListView(
children: [
Html(
data: state.userAgreementHtml.isNotEmpty
? state.userAgreementHtml
: '',
onLinkTap: (url, attributes, element) async {
final uri = Uri.parse(url!);
await launchUrl(uri, mode: LaunchMode.externalApplication);
},
),
],
);
} else if (state is MenuError) {
return Center(
child: Text(
'Error: ${state.message}',
style: const TextStyle(color: Colors.red),
),
);
}
return const Center(child: Text('Loading User Agreement...'));
},
),
);
}
}

View File

@ -57,9 +57,11 @@ class JoinHomeView extends StatelessWidget {
'Please enter the invitation code');
return;
}
if (await HomeCubit.getInstance()
.joinAUnit(textEditingController.text)) {
if (await HomeCubit.getInstance().activationCode(textEditingController.text)) {
await HomeCubit.getInstance().fetchUnitsByUserId();
CustomSnackBar.displaySnackBar('Done successfully');
Navigator.of(context).pop();
} else {
CustomSnackBar.displaySnackBar('Wrong code!');

View File

@ -0,0 +1,199 @@
import 'dart:async';
import 'package:dio/dio.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/securty/bloc/security_event.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_state.dart';
import 'package:syncrow_app/services/api/authentication_api.dart';
class SecurityBloc extends Bloc<SecurityEvent, SecurityState> {
bool _isPasswordVisible = false;
String otpCode = '';
String validate = '';
SecurityBloc() : super(PasswordVisibilityState(false)) {
on<SetPassword>(_onSetPassword);
on<TogglePasswordVisibility>(_onTogglePasswordVisibility);
on<StartTimerEvent>(_onStartTimer);
on<StopTimerEvent>(_onStopTimer);
on<UpdateTimerEvent>(_onUpdateTimer);
on<VerifyPassCodeEvent>(verifyCode);
on<ChangePasswordEvent>(changePassword);
}
void _onSetPassword(SetPassword event, Emitter<SecurityState> emit) {
if (event.password.length < 6) {
emit(PasswordErrorState('Password must be at least 6 characters long.'));
} else {
emit(PasswordSetState('Password successfully set.'));
}
}
void _onTogglePasswordVisibility(
TogglePasswordVisibility event, Emitter<SecurityState> emit) {
_isPasswordVisible = !_isPasswordVisible;
emit(PasswordVisibilityState(_isPasswordVisible));
}
String? passwordValidator(String? value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
List<String> validationErrors = [];
if (!RegExp(r'^(?=.*[a-z])').hasMatch(value)) {
validationErrors.add(' - one lowercase letter');
}
if (!RegExp(r'^(?=.*[A-Z])').hasMatch(value)) {
validationErrors.add(' - one uppercase letter');
}
if (!RegExp(r'^(?=.*\d)').hasMatch(value)) {
validationErrors.add(' - one number');
}
if (!RegExp(r'^(?=.*[@$!%*?&])').hasMatch(value)) {
validationErrors.add(' - one special character');
}
if (value.length < 8) {
validationErrors.add(' - minimum 8 characters');
}
if (validationErrors.isNotEmpty) {
return 'Password must contain at least:\n${validationErrors.join('\n')}';
}
return null;
}
TextEditingController newPassword = TextEditingController();
Timer? _timer;
int _remainingTime = 0;
Future _onStartTimer(
StartTimerEvent event, Emitter<SecurityState> emit) async {
if (_timer != null && _timer!.isActive) {
return;
}
_remainingTime = 1;
add(UpdateTimerEvent(
remainingTime: _remainingTime, isButtonEnabled: false));
try {
_remainingTime = 30;
var res = (await AuthenticationAPI.sendOtp(body: {
'email': HomeCubit.user!.email,
'type': 'PASSWORD',
if (HomeCubit.user!.regionUuid != null)
'regionUuid': HomeCubit.user!.regionUuid
}));
_remainingTime = res['cooldown'];
} on DioException catch (e) {
if (e.response!.statusCode == 400) {
final errorData = e.response!.data;
String errorMessage = errorData['message'];
if (errorMessage == 'User not found') {
validate = 'Invalid Credential';
emit(AuthInitialState());
return 1;
} else {
validate = '';
_remainingTime = errorData['data']['cooldown'] ?? 1;
emit(AuthInitialState());
}
} else {
emit(AuthInitialState());
return 1;
}
emit(AuthInitialState());
} catch (e) {
emit(AuthInitialState());
return 1;
}
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
_remainingTime--;
if (_remainingTime <= 0) {
_timer?.cancel();
add(UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true));
} else {
add(UpdateTimerEvent(
remainingTime: _remainingTime, isButtonEnabled: false));
}
});
}
void _onStopTimer(StopTimerEvent event, Emitter<SecurityState> emit) {
_timer?.cancel();
emit(TimerState(isButtonEnabled: true, remainingTime: 0));
}
void _onUpdateTimer(UpdateTimerEvent event, Emitter<SecurityState> emit) {
emit(TimerState(
isButtonEnabled: event.isButtonEnabled,
remainingTime: event.remainingTime));
}
String formattedTime(int time) {
final int days = (time / 86400).floor(); // 86400 seconds in a day
final int hours = ((time % 86400) / 3600).floor();
final int minutes = (((time % 86400) % 3600) / 60).floor();
final int seconds = (((time % 86400) % 3600) % 60).floor();
final String formattedTime = [
if (days > 0) '${days}d', // Append 'd' for days
if (days > 0 || hours > 0)
hours
.toString()
.padLeft(2, '0'), // Show hours if there are days or hours
minutes.toString().padLeft(2, '0'),
seconds.toString().padLeft(2, '0'),
].join(':');
return formattedTime;
}
Future<void> verifyCode(
VerifyPassCodeEvent event, Emitter<SecurityState> emit) async {
emit(LoadingForgetState());
try {
final response = await AuthenticationAPI.verifyPassCode(body: {
'email': HomeCubit.user!.email!,
'type': 'PASSWORD',
'otpCode': otpCode
});
if (response['statusCode'] == 200) {
_timer?.cancel();
emit(SuccessForgetState());
}
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage =
errorData['error']['message'] ?? 'something went wrong';
validate = errorMessage;
emit(AuthInitialState());
}
}
Future<void> changePassword(
ChangePasswordEvent event, Emitter<SecurityState> emit) async {
emit(LoadingForgetState());
try {
final response = await AuthenticationAPI.forgetPassword(
email: HomeCubit.user!.email!,
otpCode: event.otpCode,
password: newPassword.text,
);
emit(ChangedPassState());
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage =
errorData['error']['message'] ?? 'something went wrong';
validate = errorMessage;
emit(AuthInitialState());
}
}
}

View File

@ -0,0 +1,31 @@
abstract class SecurityEvent {}
class SetPassword extends SecurityEvent {
final String password;
SetPassword(this.password);
}
class TogglePasswordVisibility extends SecurityEvent {}
class SubmitEvent extends SecurityEvent {}
class StartTimerEvent extends SecurityEvent {}
class StopTimerEvent extends SecurityEvent {}
class UpdateTimerEvent extends SecurityEvent {
final int remainingTime;
final bool isButtonEnabled;
UpdateTimerEvent(
{required this.remainingTime, required this.isButtonEnabled});
}
class ChangePasswordEvent extends SecurityEvent {
final String otpCode;
ChangePasswordEvent({
required this.otpCode,
});
}
class VerifyPassCodeEvent extends SecurityEvent {}

View File

@ -0,0 +1,44 @@
abstract class SecurityState {}
class InitialState extends SecurityState {}
class PasswordVisibilityState extends SecurityState {
final bool isVisible;
PasswordVisibilityState(this.isVisible);
}
class PasswordSetState extends SecurityState {
final String message;
PasswordSetState(this.message);
}
class PasswordErrorState extends SecurityState {
final String error;
PasswordErrorState(this.error);
}
class AuthTokenLoading extends SecurityState {}
class AuthLoading extends SecurityState {}
class AuthInitialState extends SecurityState {}
class TimerState extends SecurityState {
final bool isButtonEnabled;
final int remainingTime;
TimerState({required this.isButtonEnabled, required this.remainingTime});
@override
List<Object> get props => [isButtonEnabled, remainingTime];
}
class InitialForgetState extends SecurityState {}
class LoadingForgetState extends SecurityState {}
class SuccessForgetState extends SecurityState {}
class ChangedPassState extends SecurityState {}

View File

@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_bloc.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/view/verification_code_page.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
class ChangePasswordPage extends StatelessWidget {
const ChangePasswordPage({super.key});
@override
Widget build(BuildContext context) {
return DefaultScaffold(
title: 'Change Password',
bottomNavBar: SizedBox(
height: 150,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
onTap: () {
// In your parent widget or navigator
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BlocProvider(
create: (_) => SecurityBloc(), // Provide the Bloc
child: const VerificationCodePage(),
),
),
);
},
child: Container(
height: 50,
margin: const EdgeInsets.only(right: 20, left: 20),
decoration: const BoxDecoration(
color: ColorsManager.blueColor,
borderRadius: BorderRadius.all(Radius.circular(20))),
child: const Center(
child: Text(
'Get Verification Code',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: ColorsManager.onPrimaryColor),
)),
),
),
],
),
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 80, bottom: 30),
child: SvgPicture.asset(Assets.verificationIcon),
),
const Padding(
padding: EdgeInsets.all(8.0),
child: BodyMedium(
text: 'Account Verification',
fontWeight: FontWeight.w700,
fontSize: 18,
),
),
const BodyMedium(
text: 'Click here to send a verification ',
fontWeight: FontWeight.w400,
fontSize: 16,
),
const SizedBox(
height: 4,
),
const BodyMedium(
text: 'code to your email: test@test.com',
fontWeight: FontWeight.w400,
fontSize: 16,
),
],
),
);
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/view/change_password_page.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
@ -23,7 +24,11 @@ class SecurtyView extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.start,
children: [
InkWell(
onTap: () {},
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const ChangePasswordPage(),
));
},
child: const Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,

View File

@ -0,0 +1,247 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_bloc.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_event.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_state.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
class SetPasswordPage extends StatelessWidget {
String? otpCode;
SetPasswordPage({super.key, this.otpCode});
@override
Widget build(BuildContext context) {
final _formKey = GlobalKey<FormState>();
return BlocProvider<SecurityBloc>(
create: (context) => SecurityBloc(),
child: Form(
key: _formKey,
child: BlocConsumer<SecurityBloc, SecurityState>(
listener: (context, state) {
if (state is SuccessForgetState) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Change Password Successfully '),
),
);
Navigator.of(context).pop();
}
if (state is ChangedPassState) {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
contentPadding: EdgeInsets.zero,
content: SizedBox(
height: MediaQuery.of(context).size.height * 0.2,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const SizedBox(
height: 10,
),
BodyLarge(
text: 'Password Changed',
fontWeight: FontWeight.w700,
fontColor:
ColorsManager.switchButton.withOpacity(0.6),
fontSize: 16,
),
const Padding(
padding: EdgeInsets.only(left: 15, right: 15),
child: Divider(
color: ColorsManager.textGray,
),
),
const Padding(
padding: EdgeInsets.only(
left: 15, right: 20, top: 15, bottom: 20),
child: Column(
children: [
Center(
child: Text(
'Your password has been',
textAlign: TextAlign.center,
)),
Center(
child: Text(
'successfully updated.',
textAlign: TextAlign.center,
)),
],
),
),
Expanded(
child: Container(
decoration: const BoxDecoration(
border: Border(
top: BorderSide(
color: ColorsManager.textGray,
width: 1.0,
),
)),
child: InkWell(
onTap: () {
AuthCubit.get(context).logout();
},
child: Padding(
padding: const EdgeInsets.only(
top: 5, bottom: 5),
child: Center(
child: Text(
'Done',
style: TextStyle(
color: ColorsManager.switchButton
.withOpacity(0.6),
fontSize: 14,
fontWeight: FontWeight.w400),
),
),
)),
),
)
],
),
),
);
},
);
}
}, builder: (context, state) {
final _bloc = BlocProvider.of<SecurityBloc>(context);
return DefaultScaffold(
title: 'Change Password',
child: ListView(
shrinkWrap: true,
children: [
const SizedBox(height: 55),
const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: BodyMedium(
text: 'Set Password',
fontWeight: FontWeight.w700,
fontSize: 18,
),
),
),
const Center(
child: BodyMedium(
text: 'Secure your account with a',
fontWeight: FontWeight.w400,
fontSize: 16,
),
),
const SizedBox(height: 4),
const Center(
child: BodyMedium(
text: 'strong password',
fontWeight: FontWeight.w400,
fontSize: 14,
),
),
const SizedBox(height: 40),
BlocBuilder<SecurityBloc, SecurityState>(
builder: (context, state) {
if (state is PasswordErrorState) {
return Center(
child: Text(
state.error,
style: const TextStyle(color: Colors.red),
),
);
} else if (state is PasswordSetState) {
return Center(
child: Text(
state.message,
style: const TextStyle(color: Colors.green),
),
);
}
return const SizedBox.shrink();
},
),
PasswordInputField(
controller: _bloc.newPassword,
validatorl: _bloc.passwordValidator,
),
const SizedBox(height: 55),
InkWell(
onTap: () {
if (_formKey.currentState!.validate()) {
_bloc.add(
ChangePasswordEvent(otpCode: otpCode.toString()));
}
},
child: Container(
padding: const EdgeInsets.only(
right: 20, left: 20, top: 15, bottom: 15),
decoration: const BoxDecoration(
color: ColorsManager.blueColor,
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: const Center(
child: Text(
"Done",
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.w700,
),
),
),
),
)
],
),
);
})));
}
}
class PasswordInputField extends StatelessWidget {
PasswordInputField({this.controller, this.validatorl});
TextEditingController? controller = TextEditingController();
String? Function(String?)? validatorl;
@override
Widget build(BuildContext context) {
return BlocBuilder<SecurityBloc, SecurityState>(
builder: (context, state) {
bool isPasswordVisible = false;
if (state is PasswordVisibilityState) {
isPasswordVisible = state.isVisible;
}
return TextFormField(
controller: controller,
obscureText: !isPasswordVisible,
validator: validatorl,
decoration: InputDecoration(
hintText: 'Password',
suffixIcon: IconButton(
icon: SvgPicture.asset(isPasswordVisible
? Assets.passwordVisibility
: Assets.passwordUnvisibility),
onPressed: () {
context.read<SecurityBloc>().add(TogglePasswordVisibility());
},
),
),
);
},
);
}
void dispose() {
controller!.dispose();
}
}

View File

@ -0,0 +1,181 @@
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/securty/bloc/security_bloc.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_event.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_state.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/view/set_password_page.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';
import 'package:pin_code_fields/pin_code_fields.dart';
class VerificationCodePage extends StatelessWidget {
const VerificationCodePage({super.key});
@override
Widget build(BuildContext context) {
String otp = '';
return BlocProvider<SecurityBloc>(
create: (context) => SecurityBloc()..add(StartTimerEvent()),
child: BlocConsumer<SecurityBloc, SecurityState>(
listener: (context, state) {
if (state is SuccessForgetState) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => SetPasswordPage(
otpCode: otp,
),
));
}
},
builder: (context, state) {
final _bloc = BlocProvider.of<SecurityBloc>(context);
return DefaultScaffold(
title: 'Change Password',
child: Column(
children: [
const SizedBox(height: 55),
const Padding(
padding: EdgeInsets.all(8.0),
child: BodyMedium(
text: 'Verification Code',
fontWeight: FontWeight.w700,
fontSize: 18,
),
),
const BodyMedium(
text: 'We have sent the verification code',
fontWeight: FontWeight.w400,
fontSize: 16,
),
const SizedBox(height: 4),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const BodyMedium(
text: 'to ',
fontWeight: FontWeight.w400,
fontSize: 14,
),
BodyMedium(
text: HomeCubit.user!.email!,
fontWeight: FontWeight.w700,
fontSize: 14,
),
],
),
const SizedBox(height: 40),
BlocBuilder<SecurityBloc, SecurityState>(
builder: (context, state) {
return Center(
child: PinCodeTextField(
hintCharacter: '0',
appContext: context,
length: 6,
mainAxisAlignment: MainAxisAlignment.center,
backgroundColor: Colors.transparent,
enableActiveFill: true,
pinTheme: PinTheme(
shape: PinCodeFieldShape.box,
activeColor: Colors.white,
selectedColor: Colors.white,
inactiveColor: Colors.white,
inactiveFillColor: Colors.white70,
selectedFillColor: Colors.white70,
activeFillColor: Colors.white,
errorBorderColor: Colors.white,
fieldHeight: 55.0,
fieldWidth: 55.0,
fieldOuterPadding: const EdgeInsets.only(right: 8),
borderRadius: BorderRadius.circular(17),
borderWidth: 1,
),
cursorWidth: 1,
keyboardType: TextInputType.number,
onChanged: (value) {
// Update OTP code in the bloc when user enters a pin
_bloc.otpCode = value;
otp = value;
},
),
);
},
),
const SizedBox(height: 55),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: InkWell(
onTap: state is TimerState &&
!state.isButtonEnabled &&
state.remainingTime != 1
? null
: () {
_bloc.add(StartTimerEvent());
},
child: Container(
padding: const EdgeInsets.only(
right: 20, left: 20, top: 15, bottom: 15),
decoration: BoxDecoration(
color: state is TimerState && !state.isButtonEnabled
? ColorsManager.blueButton
: ColorsManager.blueColor,
borderRadius:
BorderRadius.all(Radius.circular(20))),
child: Center(
child: Center(
child: Text(
'${state is TimerState && !state.isButtonEnabled && state.remainingTime != 1 ? "${_bloc.formattedTime(state.remainingTime)} " : "Resend"}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: state is TimerState &&
!state.isButtonEnabled
? Colors.white
: ColorsManager.onPrimaryColor,
),
),
),
),
),
)),
const SizedBox(width: 20),
Expanded(
child: InkWell(
onTap: () {
context
.read<SecurityBloc>()
.add(VerifyPassCodeEvent());
},
child: Container(
padding: const EdgeInsets.only(
right: 20, left: 20, top: 15, bottom: 15),
decoration: const BoxDecoration(
color: ColorsManager.blueColor,
borderRadius:
BorderRadius.all(Radius.circular(20))),
child: const Center(
child: Text(
"Verify",
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.w700),
),
),
),
),
),
],
),
],
),
);
},
),
);
}
}

View File

@ -1,15 +1,141 @@
import 'package:flutter/material.dart';
// import 'package:flutter_svg/svg.dart';
// import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart';
// import 'package:syncrow_app/generated/assets.dart';
// import 'package:syncrow_app/navigation/routing_constants.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
class CreateUnitWidget extends StatelessWidget {
const CreateUnitWidget({super.key});
@override
Widget build(BuildContext context) {
return Container();
TextEditingController textEditingController = TextEditingController();
return BlocConsumer<HomeCubit, HomeState>(
listener: (context, state) {
if (state is ActivationError) {}
},
builder: (context, state) {
return SingleChildScrollView(
child: Column(
children: [
const Text(
'Join a Space',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: ColorsManager.secondaryColor),
),
SizedBox(
height: MediaQuery.of(context).size.height * 0.6,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
Assets.assetsIconsMenuIconsHomeManagementIconsJoinAHome,
width: 70,
height: 70,
color: ColorsManager.grayButtonColors,
),
const Padding(
padding: EdgeInsets.symmetric(
vertical: 30,
),
child: BodyMedium(
fontWeight: FontWeight.w400,
textAlign: TextAlign.center,
text: 'Please enter your invitation code'),
),
Column(
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: state is ActivationError
? ColorsManager.red // Red border for error
: ColorsManager
.grayBox, // Default border color
width: 1.5, // Border width
),
borderRadius:
BorderRadius.circular(20.0), // Border radius
),
padding: const EdgeInsets.symmetric(
vertical: 5,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: TextFormField(
validator: (value) {},
controller: textEditingController,
decoration: InputDecoration(
hintText: 'Invitation code',
hintStyle: context.bodyMedium.copyWith(
color: Colors.grey,
),
border: InputBorder.none,
),
),
),
IconButton(
onPressed: () async {
if (textEditingController.text.isEmpty) {
CustomSnackBar.displaySnackBar(
'Please enter the invitation code');
return;
}
if (await HomeCubit.getInstance()
.activationCode(
textEditingController.text)) {
CustomSnackBar.displaySnackBar(
'Done successfully');
Navigator.of(context).pop();
} else {
CustomSnackBar.displaySnackBar(
'Wrong code!');
}
},
icon: const Icon(
Icons.arrow_right_alt,
),
),
],
),
),
state is ActivationError
? Text(
state.errMessage,
style: const TextStyle(
color: ColorsManager.red,
fontWeight: FontWeight.w400),
)
: const SizedBox()
],
),
],
),
),
),
],
),
);
// return state is! GetSpacesLoading
// ? state is! GetSpaceRoomsLoading
// ? HomeCubit.getInstance().pages[HomeCubit.pageIndex]
// : const Center(child: CircularProgressIndicator())
// : const Center(child: CircularProgressIndicator());
},
);
}
}
// return SizedBox(
// width: MediaQuery.sizeOf(context).width,
// height: MediaQuery.sizeOf(context).height,
@ -47,5 +173,5 @@ class CreateUnitWidget extends StatelessWidget {
// ],
// ),
// );
}
}
// }
// }

View File

@ -1125,4 +1125,18 @@ class Assets {
"assets/icons/edit_device_name_icon.svg";
static const String sosHomeIcon = "assets/icons/sos_home_icon.svg";
static const String editNameSetting = "assets/icons/edit_name_setting.svg";
static const String verificationIcon = "assets/icons/verification_icon.svg";
static const String passwordUnvisibility =
"assets/icons/password_unvisibility.svg";
static const String passwordVisibility =
"assets/icons/password_visibility.svg";
static const String bathroom = 'assets/icons/bathroom.svg';
static const String bedroom = 'assets/icons/bedroom.svg';
static const String dyi = 'assets/icons/dyi.svg';
static const String office = 'assets/icons/office.svg';
static const String parlour = 'assets/icons/parlour.svg';
static const String grid = 'assets/images/grid.svg';
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart';
import 'package:syncrow_app/features/menu/bloc/menu_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/effective_period/effect_period_bloc.dart';
@ -34,6 +35,7 @@ class MyApp extends StatelessWidget {
BlocProvider(create: (context) => CreateSceneBloc()),
BlocProvider(create: (context) => SceneBloc()),
BlocProvider(create: (context) => ProfileBloc()),
BlocProvider(create: (context) => MenuCubit()),
],
child: MaterialApp(
navigatorKey: NavigationService.navigatorKey,

View File

@ -53,7 +53,8 @@ abstract class ApiEndpoints {
//POST
static const String addUnit = '/unit';
static const String addUnitToUser = '/unit/user';
static const String verifyInvitationCode = '/user/{userUuid}/spaces/verify-code';
static const String verifyInvitationCode =
'/user/{userUuid}/spaces/verify-code';
//GET
static const String unitByUuid = '/unit/';
@ -63,6 +64,7 @@ abstract class ApiEndpoints {
static const String unitUser = '/unit/user/';
static const String invitationCode =
'/projects/{projectUuid}/communities/{communityUuid}/spaces/{unitUuid}/invitation-code';
static const String activationCode = '/invite-user/activation';
//PUT
static const String renameUnit = '/unit/{unitUuid}';
@ -95,7 +97,8 @@ abstract class ApiEndpoints {
static const String controlGroup = '/group/control';
//GET
static const String groupBySpace = '/group/{unitUuid}';
static const String devicesByGroupName = '/group/{unitUuid}/devices/{groupName}';
static const String devicesByGroupName =
'/group/{unitUuid}/devices/{groupName}';
static const String groupByUuid = '/group/{groupUuid}';
//DELETE
@ -107,7 +110,8 @@ abstract class ApiEndpoints {
static const String addDeviceToRoom = '/device/room';
static const String addDeviceToGroup = '/device/group';
static const String controlDevice = '/device/{deviceUuid}/control';
static const String firmwareDevice = '/device/{deviceUuid}/firmware/{firmwareVersion}';
static const String firmwareDevice =
'/device/{deviceUuid}/firmware/{firmwareVersion}';
static const String getDevicesByUserId = '/device/user/{userId}';
static const String getDevicesByUnitId = '/device/unit/{unitUuid}';
static const String openDoorLock = '/door-lock/open/{doorLockUuid}';
@ -118,8 +122,10 @@ abstract class ApiEndpoints {
static const String deviceByUuid = '/device/{deviceUuid}';
static const String deviceFunctions = '/device/{deviceUuid}/functions';
static const String gatewayApi = '/device/gateway/{gatewayUuid}/devices';
static const String deviceFunctionsStatus = '/device/{deviceUuid}/functions/status';
static const String powerClamp = '/device/{powerClampUuid}/power-clamp/status';
static const String deviceFunctionsStatus =
'/device/{deviceUuid}/functions/status';
static const String powerClamp =
'/device/{powerClampUuid}/power-clamp/status';
///Device Permission Module
//POST
@ -147,14 +153,16 @@ abstract class ApiEndpoints {
static const String getUnitAutomation = '/automation/{unitUuid}';
static const String getAutomationDetails = '/automation/details/{automationId}';
static const String getAutomationDetails =
'/automation/details/{automationId}';
/// PUT
static const String updateScene = '/scene/tap-to-run/{sceneId}';
static const String updateAutomation = '/automation/{automationId}';
static const String updateAutomationStatus = '/automation/status/{automationId}';
static const String updateAutomationStatus =
'/automation/status/{automationId}';
/// DELETE
static const String deleteScene = '/scene/tap-to-run/{sceneId}';
@ -163,8 +171,10 @@ abstract class ApiEndpoints {
//////////////////////Door Lock //////////////////////
//online
static const String addTemporaryPassword = '/door-lock/temporary-password/online/{doorLockUuid}';
static const String getTemporaryPassword = '/door-lock/temporary-password/online/{doorLockUuid}';
static const String addTemporaryPassword =
'/door-lock/temporary-password/online/{doorLockUuid}';
static const String getTemporaryPassword =
'/door-lock/temporary-password/online/{doorLockUuid}';
//one-time offline
static const String addOneTimeTemporaryPassword =
@ -196,7 +206,8 @@ abstract class ApiEndpoints {
'/door-lock/temporary-password/online/{doorLockUuid}/{passwordId}';
static const String saveSchedule = '/schedule/{deviceUuid}';
static const String getSchedule = '/schedule/{deviceUuid}?category={category}';
static const String getSchedule =
'/schedule/{deviceUuid}?category={category}';
static const String changeSchedule = '/schedule/enable/{deviceUuid}';
static const String deleteSchedule = '/schedule/{deviceUuid}/{scheduleId}';
static const String reportLogs =
@ -205,9 +216,13 @@ abstract class ApiEndpoints {
static const String statusBatch = '/device/status/batch';
static const String deviceScene = '/device/{deviceUuid}/scenes';
static const String fourSceneByName = '/device/{deviceUuid}/scenes?switchName={switchName}';
static const String fourSceneByName =
'/device/{deviceUuid}/scenes?switchName={switchName}';
static const String resetDevice = '/factory/reset/{deviceUuid}';
static const String unAssignScenesDevice = '/device/{deviceUuid}/scenes?switchName={switchName}';
static const String unAssignScenesDevice =
'/device/{deviceUuid}/scenes?switchName={switchName}';
static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}';
static const String terms = '/terms';
static const String policy = '/policy';
}

View File

@ -41,7 +41,6 @@ class AuthenticationAPI {
body: body,
showServerMessage: false,
expectedResponseModel: (json) {
print(json['data']);
return json['data'];
});
return response;

View File

@ -448,14 +448,14 @@ class DevicesAPI {
required String startTime,
required String endTime,
}) async {
final requestUrl = ApiEndpoints.reportLogs
.replaceAll('{deviceUuid}', deviceUuid)
.replaceAll('{code}', code)
.replaceAll('{startTime}', startTime)
.replaceAll('{endTime}', endTime);
final response = await _httpService.get(
path: ApiEndpoints.reportLogs
.replaceAll('{deviceUuid}', deviceUuid)
.replaceAll('{code}', code)
.replaceAll('{startTime}', startTime)
.replaceAll('{endTime}', endTime),
path: requestUrl,
expectedResponseModel: (json) {
log('json=====$json');
return DeviceReport.fromJson(json);
},
);

View File

@ -1,5 +1,4 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart';

View File

@ -31,8 +31,10 @@ class ServerFailure extends Failure {
{
// var document = parser.parse(dioError.response!.data.toString());
// var message = document.body!.text;
return ServerFailure.fromResponse(dioError.response!.statusCode!,
dioError.response?.data['error']['message'] ?? "Something went wrong");
return ServerFailure.fromResponse(
dioError.response!.statusCode!,
dioError.response?.data['error']['message'] ??
"Something went wrong");
}
case DioExceptionType.cancel:
return ServerFailure("The request to ApiServer was canceled");
@ -54,15 +56,7 @@ class ServerFailure extends Failure {
case 403:
return ServerFailure(responseMessage);
case 400:
List<String> errors = [];
if (responseMessage is List) {
for (var error in responseMessage) {
errors.add(error);
}
} else {
return ServerFailure(responseMessage);
}
return ServerFailure(errors.join('\n'));
return ServerFailure(responseMessage);
case 404:
return ServerFailure("");
case 500:

View File

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:developer';
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/model/region_model.dart';
@ -9,14 +10,15 @@ 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 {
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
},
path: ApiEndpoints.saveName
.replaceAll('{userUuid}', HomeCubit.user!.uuid!),
body: {"firstName": firstName, "lastName": lastName},
expectedResponseModel: (json) {
return json;
},
@ -27,10 +29,13 @@ class ProfileApi {
}
}
static Future saveRegion({String? regionUuid,}) async {
static Future saveRegion({
String? regionUuid,
}) async {
try {
final response = await _httpService.put(
path: ApiEndpoints.saveRegion.replaceAll('{userUuid}', HomeCubit.user!.uuid!),
path: ApiEndpoints.saveRegion
.replaceAll('{userUuid}', HomeCubit.user!.uuid!),
body: {
"regionUuid": regionUuid,
},
@ -44,10 +49,13 @@ class ProfileApi {
}
}
static Future saveTimeZone({String? regionUuid,}) async {
static Future saveTimeZone({
String? regionUuid,
}) async {
try {
final response = await _httpService.put(
path: ApiEndpoints.saveTimeZone.replaceAll('{userUuid}', HomeCubit.user!.uuid!),
path: ApiEndpoints.saveTimeZone
.replaceAll('{userUuid}', HomeCubit.user!.uuid!),
body: {
"timezoneUuid": regionUuid,
},
@ -64,10 +72,9 @@ class ProfileApi {
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'
},
path: ApiEndpoints.sendPicture
.replaceAll('{userUuid}', HomeCubit.user!.uuid!),
body: {"profilePicture": 'data:image/png;base64,$image'},
expectedResponseModel: (json) {
return json;
},
@ -78,14 +85,13 @@ class ProfileApi {
}
}
Future fetchUserInfo(userId) async {
Future fetchUserInfo(userId) async {
final response = await _httpService.get(
path: ApiEndpoints.getUser.replaceAll('{userUuid}', userId!),
showServerMessage: true,
expectedResponseModel: (json) {
return UserModel.fromJson(json);
}
);
path: ApiEndpoints.getUser.replaceAll('{userUuid}', userId!),
showServerMessage: true,
expectedResponseModel: (json) {
return UserModel.fromJson(json);
});
return response;
}
@ -94,9 +100,10 @@ class ProfileApi {
path: ApiEndpoints.getRegion,
showServerMessage: true,
expectedResponseModel: (json) {
return (json as List).map((zone) => RegionModel.fromJson(zone)).toList();
}
);
return (json as List)
.map((zone) => RegionModel.fromJson(zone))
.toList();
});
return response as List<RegionModel>;
}
@ -106,9 +113,27 @@ class ProfileApi {
showServerMessage: true,
expectedResponseModel: (json) {
return (json as List).map((zone) => TimeZone.fromJson(zone)).toList();
}
);
});
return response as List<TimeZone>;
}
Future fetchUserAgreement() async {
final response = await _httpService.get(
path: ApiEndpoints.terms,
showServerMessage: true,
expectedResponseModel: (json) {
return json['data'];
});
return response;
}
Future fetchPrivacyPolicy() async {
final response = await _httpService.get(
path: ApiEndpoints.policy,
showServerMessage: true,
expectedResponseModel: (json) {
return json['data'];
});
return response;
}
}

View File

@ -1,3 +1,4 @@
import 'package:dio/dio.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:syncrow_app/features/app_layout/model/space_model.dart';
import 'package:syncrow_app/features/auth/model/user_model.dart';
@ -66,7 +67,7 @@ class SpacesAPI {
static Future<String> generateInvitationCode(
String unitId, String communityId) async {
final response = await _httpService.get(
final response = await _httpService.post(
path: ApiEndpoints.invitationCode
.replaceAll('{unitUuid}', unitId)
.replaceAll('{communityUuid}', communityId)
@ -95,4 +96,20 @@ class SpacesAPI {
);
return response;
}
static Future activationCodeSpace({
String? activationCode,
String? userUuid,
}) async {
Map body = {"activationCode": activationCode, "userUuid": userUuid};
final response = await _httpService.post(
path: ApiEndpoints.activationCode,
showServerMessage: true,
body: body,
expectedResponseModel: (json) {
return json;
},
);
return response;
}
}

View File

@ -34,5 +34,8 @@ abstract class ColorsManager {
static const Color blueColor = Color(0xff5481F3);
static const Color blueColor1 = Color(0xff0A7AFF);
static const Color grayButtonColors = Color(0xffCCCCCC);
static const Color blueButton = Color(0xff85BDFF);
}
//background: #F5F5F5;023DFE
//background: #F5F5F5;background: #85BDFF;

View File

@ -1,11 +1,12 @@
//ignore_for_file: constant_identifier_names
import 'dart:ui';
import 'package:syncrow_app/features/devices/model/function_model.dart';
import 'package:syncrow_app/features/menu/bloc/privacy_policy.dart';
import 'package:syncrow_app/features/menu/bloc/user_agreement.dart';
// import 'package:syncrow_app/features/menu/view/widgets/create_home/create_home_view.dart';
import 'package:syncrow_app/features/menu/view/widgets/join_home/join_home_view.dart';
import 'package:syncrow_app/features/menu/view/widgets/manage_home/manage_home_view.dart';
import 'package:syncrow_app/features/menu/view/widgets/privacy/privacy_view.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/securty_view.dart';
import 'package:syncrow_app/features/menu/view/widgets/securty/view/securty_view.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
@ -99,7 +100,9 @@ Map<String, DeviceType> devicesTypesMap = {
Map<DeviceType, List<FunctionModel>> devicesFunctionsMap = {
DeviceType.AC: [
FunctionModel(
code: 'switch', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),
code: 'switch',
type: functionTypesMap['Boolean'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'mode',
type: functionTypesMap['Enum'],
@ -122,7 +125,9 @@ Map<DeviceType, List<FunctionModel>> devicesFunctionsMap = {
// "range": ["low", "middle", "high", "auto"]
})),
FunctionModel(
code: 'child_lock', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),
code: 'child_lock',
type: functionTypesMap['Boolean'],
values: ValueModel.fromJson({})),
],
DeviceType.Gateway: [
FunctionModel(
@ -136,7 +141,9 @@ Map<DeviceType, List<FunctionModel>> devicesFunctionsMap = {
"range": ["normal", "alarm"]
})),
FunctionModel(
code: 'factory_reset', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),
code: 'factory_reset',
type: functionTypesMap['Boolean'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'alarm_active',
type: functionTypesMap['String'],
@ -146,7 +153,8 @@ Map<DeviceType, List<FunctionModel>> devicesFunctionsMap = {
FunctionModel(
code: 'sensitivity',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "", "min": 1, "max": 10, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "", "min": 1, "max": 10, "scale": 0, "step": 1})),
],
DeviceType.DoorLock: [
FunctionModel(
@ -154,7 +162,9 @@ Map<DeviceType, List<FunctionModel>> devicesFunctionsMap = {
type: functionTypesMap['Raw'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'remote_no_dp_key', type: functionTypesMap['Raw'], values: ValueModel.fromJson({})),
code: 'remote_no_dp_key',
type: functionTypesMap['Raw'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'normal_open_switch',
type: functionTypesMap['Boolean'],
@ -164,64 +174,87 @@ Map<DeviceType, List<FunctionModel>> devicesFunctionsMap = {
FunctionModel(
code: 'far_detection',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "cm", "min": 75, "max": 600, "scale": 0, "step": 75})),
values: ValueModel.fromJson(
{"unit": "cm", "min": 75, "max": 600, "scale": 0, "step": 75})),
FunctionModel(
code: 'presence_time',
type: functionTypesMap['Integer'],
values:
ValueModel.fromJson({"unit": "Min", "min": 0, "max": 65535, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "Min", "min": 0, "max": 65535, "scale": 0, "step": 1})),
FunctionModel(
code: 'motion_sensitivity_value',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "", "min": 1, "max": 5, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "", "min": 1, "max": 5, "scale": 0, "step": 1})),
FunctionModel(
code: 'motionless_sensitivity',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "", "min": 1, "max": 5, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "", "min": 1, "max": 5, "scale": 0, "step": 1})),
FunctionModel(
code: 'indicator', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),
code: 'indicator',
type: functionTypesMap['Boolean'],
values: ValueModel.fromJson({})),
],
DeviceType.OneGang: [
FunctionModel(
code: 'switch_1', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),
code: 'switch_1',
type: functionTypesMap['Boolean'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'countdown_1',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
],
DeviceType.TwoGang: [
FunctionModel(
code: 'switch_1', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),
code: 'switch_1',
type: functionTypesMap['Boolean'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'switch_2', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),
code: 'switch_2',
type: functionTypesMap['Boolean'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'countdown_1',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
FunctionModel(
code: 'countdown_2',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
],
DeviceType.ThreeGang: [
FunctionModel(
code: 'switch_1', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),
code: 'switch_1',
type: functionTypesMap['Boolean'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'switch_2', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),
code: 'switch_2',
type: functionTypesMap['Boolean'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'switch_3', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),
code: 'switch_3',
type: functionTypesMap['Boolean'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'countdown_1',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
FunctionModel(
code: 'countdown_2',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
FunctionModel(
code: 'countdown_3',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
],
DeviceType.Curtain: [
FunctionModel(
@ -233,15 +266,19 @@ Map<DeviceType, List<FunctionModel>> devicesFunctionsMap = {
FunctionModel(
code: 'percent_control',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "%", "min": 0, "max": 100, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "%", "min": 0, "max": 100, "scale": 0, "step": 1})),
],
DeviceType.WH: [
FunctionModel(
code: 'switch_1', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),
code: 'switch_1',
type: functionTypesMap['Boolean'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'countdown_1',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
FunctionModel(
code: 'relay_status',
type: functionTypesMap['Enum'],
@ -265,7 +302,9 @@ Map<DeviceType, List<FunctionModel>> devicesFunctionsMap = {
],
DeviceType.DS: [
FunctionModel(
code: 'doorcontact_state', type: functionTypesMap['Raw'], values: ValueModel.fromJson({})),
code: 'doorcontact_state',
type: functionTypesMap['Raw'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'battery_percentage',
type: functionTypesMap['Integer'],
@ -273,11 +312,14 @@ Map<DeviceType, List<FunctionModel>> devicesFunctionsMap = {
],
DeviceType.OneTouch: [
FunctionModel(
code: 'switch_1', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),
code: 'switch_1',
type: functionTypesMap['Boolean'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'countdown_1',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
FunctionModel(
code: 'relay_status',
type: functionTypesMap['Enum'],
@ -299,17 +341,23 @@ Map<DeviceType, List<FunctionModel>> devicesFunctionsMap = {
],
DeviceType.TowTouch: [
FunctionModel(
code: 'switch_1', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),
code: 'switch_1',
type: functionTypesMap['Boolean'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'switch_2', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),
code: 'switch_2',
type: functionTypesMap['Boolean'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'countdown_1',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
FunctionModel(
code: 'countdown_2',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
FunctionModel(
code: 'relay_status',
type: functionTypesMap['Enum'],
@ -337,23 +385,32 @@ Map<DeviceType, List<FunctionModel>> devicesFunctionsMap = {
],
DeviceType.ThreeTouch: [
FunctionModel(
code: 'switch_1', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),
code: 'switch_1',
type: functionTypesMap['Boolean'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'switch_2', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),
code: 'switch_2',
type: functionTypesMap['Boolean'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'switch_3', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),
code: 'switch_3',
type: functionTypesMap['Boolean'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'countdown_1',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
FunctionModel(
code: 'countdown_2',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
FunctionModel(
code: 'countdown_3',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "s", "min": 0, "max": 43200, "scale": 0, "step": 1})),
FunctionModel(
code: 'relay_status',
type: functionTypesMap['Enum'],
@ -387,19 +444,24 @@ Map<DeviceType, List<FunctionModel>> devicesFunctionsMap = {
],
DeviceType.GarageDoor: [
FunctionModel(
code: 'switch_1', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),
code: 'switch_1',
type: functionTypesMap['Boolean'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'countdown_1',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 86400, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "s", "min": 0, "max": 86400, "scale": 0, "step": 1})),
FunctionModel(
code: 'tr_timecon',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 120, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "s", "min": 0, "max": 120, "scale": 0, "step": 1})),
FunctionModel(
code: 'countdown_alarm',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 86400, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "s", "min": 0, "max": 86400, "scale": 0, "step": 1})),
FunctionModel(
code: 'door_control_1',
type: functionTypesMap['Enum'],
@ -420,19 +482,24 @@ Map<DeviceType, List<FunctionModel>> devicesFunctionsMap = {
DeviceType.WaterLeak: [],
DeviceType.PC: [
FunctionModel(
code: 'switch_1', type: functionTypesMap['Boolean'], values: ValueModel.fromJson({})),
code: 'switch_1',
type: functionTypesMap['Boolean'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'countdown_1',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 86400, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "s", "min": 0, "max": 86400, "scale": 0, "step": 1})),
FunctionModel(
code: 'tr_timecon',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 120, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "s", "min": 0, "max": 120, "scale": 0, "step": 1})),
FunctionModel(
code: 'countdown_alarm',
type: functionTypesMap['Integer'],
values: ValueModel.fromJson({"unit": "s", "min": 0, "max": 86400, "scale": 0, "step": 1})),
values: ValueModel.fromJson(
{"unit": "s", "min": 0, "max": 86400, "scale": 0, "step": 1})),
FunctionModel(
code: 'door_control_1',
type: functionTypesMap['Enum'],
@ -476,9 +543,13 @@ Map<DeviceType, List<FunctionModel>> devicesFunctionsMap = {
"range": ["scene"]
})),
FunctionModel(
code: 'scene_id_group_id', type: functionTypesMap['Raw'], values: ValueModel.fromJson({})),
code: 'scene_id_group_id',
type: functionTypesMap['Raw'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'switch_backlight', type: functionTypesMap['Enum'], values: ValueModel.fromJson({})),
code: 'switch_backlight',
type: functionTypesMap['Enum'],
values: ValueModel.fromJson({})),
],
DeviceType.SixScene: [
FunctionModel(
@ -518,13 +589,19 @@ Map<DeviceType, List<FunctionModel>> devicesFunctionsMap = {
"range": ["scene"]
})),
FunctionModel(
code: 'scene_id_group_id', type: functionTypesMap['Raw'], values: ValueModel.fromJson({})),
code: 'scene_id_group_id',
type: functionTypesMap['Raw'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'switch_backlight', type: functionTypesMap['Enum'], values: ValueModel.fromJson({})),
code: 'switch_backlight',
type: functionTypesMap['Enum'],
values: ValueModel.fromJson({})),
],
DeviceType.SOS: [
FunctionModel(
code: 'contact_state', type: functionTypesMap['Raw'], values: ValueModel.fromJson({})),
code: 'contact_state',
type: functionTypesMap['Raw'],
values: ValueModel.fromJson({})),
FunctionModel(
code: 'battery_percentage',
type: functionTypesMap['Integer'],
@ -709,16 +786,20 @@ List<Map<String, Object>> menuSections = [
'title': 'Legal Information',
'color': const Color(0xFF001B72),
'buttons': [
{'title': 'About', 'Icon': Assets.assetsIconsMenuIconsLeagalInfoIconsAbout, 'page': null},
// {
// 'title': 'Privacy Policy',
// 'Icon': Assets.assetsIconsMenuIconsLeagalInfoIconsPrivacyPolicy,
// 'page': null
// },
{
'title': 'About',
'Icon': Assets.assetsIconsMenuIconsLeagalInfoIconsAbout,
'page': null
},
{
'title': 'Privacy Policy',
'Icon': Assets.assetsIconsMenuIconsLeagalInfoIconsPrivacyPolicy,
'page': const PrivacyPolicy()
},
{
'title': 'User Agreement',
'Icon': Assets.assetsIconsMenuIconsLeagalInfoIconsUserAgreement,
'page': null
'page': const UserAgreement()
},
],
},