Compare commits

...

45 Commits

Author SHA1 Message Date
4ac6077011 change all routine words into automation 2025-07-21 13:22:29 +03:00
d5321a9ca0 fix inncorrect parent in automation screen by removing Expanded 2025-07-21 13:21:02 +03:00
09dc8cc330 use uppercase 2025-07-20 14:34:19 +03:00
f7245e5de9 change th e word Routine to automation 2025-07-15 11:06:49 +03:00
fe472f0ca0 Merge pull request #112 from SyncrowIOT/SP-368-clarification-on-default-value-for-start-date-in-door-lock-online-tile-limited-password-repeat-section
Clarification on Default Value for Start Date in Door Lock Online Tile Limited Password repeat section
2025-07-02 09:12:48 +03:00
d9b68a11e5 fix the initial value on start date and fix logic that prevent user from select old date (befor now) 2025-07-01 14:09:14 +03:00
6c91af6f90 Merge pull request #111 from SyncrowIOT/SP-1781-fe-fix-bugs-related-to-wi-fi-door-lock-on-mobile-app
[FE] Fix bugs related to Wi-Fi door lock on Mobile App
2025-06-24 12:55:50 +03:00
65b8ecf672 PR improvments 2025-06-24 08:39:30 +03:00
ea0bd32eb7 Merge pull request #110 from SyncrowIOT/SP-1439-FE-On-Manage-your-home-screen-the-container-height-that-has-the-spaces-is-longer-than-needed-it-should-extend-based-on-the-data-in-the-list
[FE] On Manage your home screen the container height that has the spaces is longer than needed it should extend based on the data in the list
2025-06-23 14:06:56 +03:00
069a8e16db fix the issue in mobile to have indicator depend on time of unlock and unlock on pressing with lock animation 2025-06-23 13:55:52 +03:00
5989cdcfa0 using Align to keep the container stick with elemnts number (tested for many items) 2025-06-23 10:00:13 +03:00
4c81d22605 Merge pull request #109 from SyncrowIOT/build_booking_system
Build booking system
2025-06-17 16:25:55 +03:00
dc123e6231 PR fixes 2025-06-17 14:14:16 +03:00
a705384717 the Book Page is Ready now but not linked with any logic 2025-06-16 14:11:41 +03:00
d644c9c949 make the UI and link it with its blocs 2025-06-16 13:48:34 +03:00
9d7113cee8 make the domain and data layers 2025-06-16 13:48:15 +03:00
3893740080 start with upcoming widget 2025-06-16 09:40:49 +03:00
c7a9cd7ea7 add current balance widget 2025-06-16 09:40:28 +03:00
24a12af0f6 add booking appbar widget 2025-06-16 09:40:08 +03:00
5d327e29c7 add phone Height and Width into helpers 2025-06-16 09:39:47 +03:00
2b58af4560 add points to user model 2025-06-16 09:39:27 +03:00
19f1bb8ec3 add booking to setting list 2025-06-16 08:51:56 +03:00
a4f56effbb Merge pull request #108 from SyncrowIOT/bump-version-to-1.0.30+18
bump-v to `1.0.30+18`.
2025-06-03 16:46:22 +03:00
b35447384c bump-v to 1.0.30+18. 2025-06-03 12:39:07 +03:00
dbf031287b Merge pull request #107 from SyncrowIOT/SP-1562-FE-On-verify-OTP-screen-when-the-user-verifies-the-OTP-both-buttons-Verify-Resend-get-intoo-loading-state
fix loading issue seperate loading states between verify and resend O…
2025-05-22 04:55:52 -05:00
293d6a1d98 fix loading issue seperate loading states between verify and resend Otp&&perevent user to send api if otp length !=6 2025-05-22 02:45:24 -05:00
eaecd4996e Update flush sensor logic to trigger initial events when value is null or true, 2025-05-20 15:59:17 +03:00
bfb0cb1dc1 Merge pull request #106 from SyncrowIOT/Fix-Flush-Sensor-Bugs
Refactor device info model to handle optional fields more efficiently
2025-05-20 15:00:39 +03:00
c2bf32af1c Refactor device info model to handle optional fields more efficiently 2025-05-20 14:53:28 +03:00
b7a42af223 Merge pull request #105 from SyncrowIOT/fix-issue-SceneDetails
Refactor Action class constructor to handle optional fields more effi…
2025-05-19 13:26:02 +03:00
e8e5ddd102 Refactor Action class constructor to handle optional fields more efficiently 2025-05-19 13:24:49 +03:00
fca69cef73 Merge pull request #104 from SyncrowIOT/fix-back-button-status
Refactor screen titles to use bloc device info
2025-05-19 11:06:57 +03:00
7a19291088 Refactor screen titles to use bloc device info 2025-05-19 11:04:59 +03:00
c76995fbb8 Merge pull request #103 from SyncrowIOT/SP-1586-FE-On-Device-settings-screen-after-editing
add PopScope to setting page
2025-05-15 14:23:50 +03:00
5d1b8e39b0 add PopScope to setting page 2025-05-15 12:35:47 +03:00
a04beb32f2 Merge pull request #102 from SyncrowIOT/Fix-Flush-Presence-Records
Refactor flush presence records widget to display the correct status
2025-05-14 11:16:45 +03:00
23c307338a Refactor flush presence records widget to display the correct status 2025-05-14 11:14:27 +03:00
4db16fd567 Merge pull request #101 from SyncrowIOT/change-New-PS-Functions-UI
Refactor operation dialog types and add counter steps
2025-05-13 15:58:28 +03:00
2c5ca67b10 Refactor operation dialog types and add counter steps 2025-05-13 14:46:27 +03:00
79c1205932 Merge pull request #100 from SyncrowIOT/fix-flush-bugs
fix flush bugs
2025-05-13 10:51:06 +03:00
3589e349b3 fix comments 2025-05-13 10:50:09 +03:00
86bd3fdd5b fix flush bugs 2025-05-13 10:23:35 +03:00
2e2db90c19 Merge pull request #99 from SyncrowIOT/SP-1459-FE-Display-Build-Number-and-Environment-Label-DEV-in-App-UI
Refactor menu view and add build number and environment label
2025-05-12 11:11:43 +03:00
21cad0d9e8 Refactor menu view and add build number and environment label 2025-05-12 10:58:16 +03:00
9c6ab888a7 Merge pull request #98 from SyncrowIOT/SP-1523-FE-Implement-Task-Dialogs-for-Editable-Sensor-Parameters-Sensitivity-Distances-Timings-Levels
Implement Flush Mounted Presence Sensor Routine Control and change th…
2025-05-12 10:51:11 +03:00
64 changed files with 2272 additions and 1082 deletions

12
assets/icons/Booking.svg Normal file
View File

@ -0,0 +1,12 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_9795_16810)">
<path d="M3.68007 14.1344H4.98041C5.73422 14.1344 5.73533 12.96 4.98041 12.96H3.68007C2.92629 12.96 2.92518 14.1344 3.68007 14.1344ZM3.68007 13.3994H4.98041C5.17143 13.3994 5.17143 13.695 4.98041 13.695H3.68007C3.48908 13.695 3.48908 13.3994 3.68007 13.3994Z" fill="white"/>
<path d="M5.29431 7.55226L3.86289 8.9837L3.36716 8.48794C3.16621 8.28714 2.85552 8.59777 3.05641 8.79869L3.70753 9.44981C3.81112 9.53559 3.91472 9.53559 4.01828 9.44981L5.60509 7.86301C5.80595 7.66209 5.49525 7.35145 5.29431 7.55226Z" fill="white"/>
<path d="M14.8368 9.72059C14.7481 9.55125 14.6838 9.36574 14.6401 9.17956C14.4259 8.28325 14.1003 7.41489 13.6727 6.59856L12.9576 5.23374C12.5529 4.46106 11.3583 4.58953 11.1328 5.437C10.5978 5.24455 9.99396 5.56087 9.84768 6.11024C9.40544 5.9511 8.89477 6.13855 8.6603 6.54527V1.7204V0.858486C8.66028 0.385137 8.27517 0 7.80176 0H2.68301C2.39892 0 2.39892 0.439453 2.68301 0.439453H7.80176C8.03282 0.439453 8.22082 0.627422 8.22082 0.858486V1.50067H0.439659V0.858486C0.439659 0.627422 0.627628 0.439453 0.858692 0.439453H1.45257C1.73666 0.439453 1.73666 0 1.45257 0H0.858692C0.385343 0 0.000205994 0.385137 0.000205994 0.858486V1.7204V12.3108V14.1415C0.000205994 14.6149 0.385343 15 0.858692 15H7.80176C8.27514 15 8.66027 14.6149 8.66027 14.1415V12.3108V11.4307L9.44546 11.819C9.63949 11.915 9.79896 12.0748 9.89452 12.269C10.2433 12.9779 11.1374 13.2717 11.8396 12.9038L13.1487 12.218C13.4004 12.0861 13.1965 11.6969 12.9448 11.8287L11.6357 12.5145C11.1494 12.7694 10.5312 12.5676 10.2888 12.075C10.1508 11.7945 9.92051 11.5637 9.64028 11.4251L6.30826 9.77698C5.68245 9.47303 6.13503 8.49126 6.7706 8.78769L7.98044 9.42457C8.16709 9.52283 8.3753 9.31506 8.27742 9.1282L6.17569 5.11652C5.83058 4.46771 6.75481 3.86883 7.10733 4.54184L8.59292 7.37742L8.98096 8.1181C9.10904 8.36271 9.50171 8.16518 9.37023 7.91417L8.99244 7.19309C8.83945 6.68329 9.46884 6.27226 9.87393 6.62766L10.2943 7.43004C10.4224 7.67464 10.8151 7.47712 10.6836 7.2261L10.2561 6.41024C10.2161 5.94152 10.7901 5.63988 11.153 5.94905L11.5761 6.75671C11.7042 7.00131 12.0968 6.80379 11.9653 6.55277L11.5421 5.745C11.4846 5.16976 12.2942 4.91437 12.5683 5.43765L13.2834 6.80247C13.6942 7.5866 14.0068 8.42074 14.2127 9.28163C14.2627 9.50496 14.3413 9.7217 14.4475 9.92446C14.7007 10.4077 14.5135 11.0068 14.0303 11.26C13.7786 11.3919 13.9826 11.7811 14.2342 11.6492C14.9321 11.2836 15.2024 10.4184 14.8368 9.72059ZM8.22082 14.1415C8.22082 14.3726 8.03282 14.5605 7.80176 14.5605H0.858692C0.627628 14.5605 0.439659 14.3726 0.439659 14.1415V12.5305H8.22082V14.1415ZM5.64987 8.85926C5.50072 9.17279 5.5297 9.54832 5.72405 9.83587C5.36068 10.2161 4.86217 10.4316 4.33022 10.4316C3.26581 10.4316 2.39985 9.56563 2.39985 8.50122C2.39985 7.43681 3.26581 6.57085 4.33022 6.57085C5.34018 6.57085 6.17092 7.35062 6.2533 8.33965C5.99306 8.41904 5.76686 8.61328 5.64987 8.85926ZM7.24526 4.0302C7.14946 3.95241 7.03893 3.89259 6.91758 3.85468C6.21375 3.63475 5.50491 4.28016 5.65983 5.00092H1.48843C1.44803 5.00092 1.41519 4.96808 1.41519 4.92768V3.35742C1.41519 3.31702 1.44803 3.28418 1.48843 3.28418H7.17202C7.21242 3.28418 7.24526 3.31702 7.24526 3.35742V4.0302ZM8.22082 5.72027L7.68472 4.69702V3.35742C7.68472 3.07474 7.45471 2.84473 7.17202 2.84473H1.48843C1.20574 2.84473 0.975733 3.07474 0.975733 3.35742V4.92771C0.975733 5.21039 1.20574 5.4404 1.48843 5.4404H5.84921L7.56065 8.70703L6.97102 8.39663C6.96841 8.39525 6.96574 8.39391 6.96308 8.39265C6.87577 8.3511 6.78445 8.3231 6.69155 8.30851C6.59306 7.09166 5.572 6.1314 4.33025 6.1314C3.02353 6.1314 1.96043 7.19449 1.96043 8.50122C1.96043 9.80795 3.02353 10.871 4.33025 10.871C4.98563 10.871 5.60016 10.6053 6.04658 10.1354C6.74367 10.5371 7.49956 10.8565 8.22082 11.2133V12.091H0.439659V1.94013H8.22082V5.72027Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_9795_16810">
<rect width="15" height="15" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -26,6 +26,7 @@
buildConfiguration = "Debug-prod"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
@ -54,6 +55,7 @@
buildConfiguration = "Debug-prod"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"

View File

@ -479,7 +479,7 @@ class HomeCubit extends Cubit<HomeState> {
// onPressed: () {},
// ),
],
'Routine': [
'Automation': [
// IconButton(
// icon: Image.asset(
// Assets.assetsIconsFilter,
@ -553,7 +553,7 @@ class HomeCubit extends Cubit<HomeState> {
static Map<String, Widget?> appBarLeading = {
// 'Dashboard': const AppBarHomeDropdown(),
'Devices': const AppBarHomeDropdown(),
'Routine': const AppBarHomeDropdown(),
'Automation': const AppBarHomeDropdown(),
'Menu': Padding(
padding: const EdgeInsets.only(left: 15),
child: Image.asset(
@ -569,7 +569,8 @@ class HomeCubit extends Cubit<HomeState> {
// defaultBottomNavBarItem(icon: Assets.assetsIconsDashboard, label: 'Dashboard'),
// defaultBottomNavBarItem(icon: Assets.assetsIconslayout, label: 'Layout'),
defaultBottomNavBarItem(icon: Assets.assetsIconsDevices, label: 'Devices'),
defaultBottomNavBarItem(icon: Assets.assetsIconsRoutines, label: 'Routine'),
defaultBottomNavBarItem(
icon: Assets.assetsIconsRoutines, label: 'Automation'),
defaultBottomNavBarItem(icon: Assets.assetsIconsMenu, label: 'Menu'),
];
@ -616,7 +617,6 @@ BottomNavigationBarItem defaultBottomNavBarItem(
);
}
// class PermissionUtils {
// // Check if the "VIEW" permission exists in "MANAGE_SUBSPACE"
// static bool hasViewPermission(List<dynamic> permissions) {

View File

@ -270,7 +270,7 @@ class AuthCubit extends Cubit<AuthState> {
Future<bool> reSendOtp({bool? forget}) async {
try {
emit(AuthLoading());
emit(ResendOtpLoading());
await AuthenticationAPI.sendOtp(body: {
'email': email,
'type': forget == true ? 'PASSWORD' : 'VERIFICATION'
@ -286,7 +286,10 @@ class AuthCubit extends Cubit<AuthState> {
}
verifyOtp(bool isForgotPass) async {
emit(AuthLoginLoading());
if (otpCode.length != 6) {
return;
}
emit(AuthOtpLoading());
try {
final response = await AuthenticationAPI.verifyPassCode(body: {
'email': email,

View File

@ -20,9 +20,13 @@ class AuthLoginLoading extends AuthLoading {}
class AuthLoginSuccess extends AuthSuccess {}
class AuthSignUpSuccess extends AuthSuccess {}
class AuthOtpSuccess extends AuthSuccess {}
class AuthSignUpSuccess extends AuthSuccess {}
class AuthOtpLoading extends AuthLoading {}
class ResendOtpLoading extends AuthLoading {}
class ResendOtpSuccess extends AuthSuccess {}

View File

@ -22,7 +22,7 @@ class UserModel {
final DateTime? appAgreementAcceptedAt;
final Role? role;
final Project? project;
final int? points;
UserModel({
required this.uuid,
required this.email,
@ -41,6 +41,7 @@ class UserModel {
required this.appAgreementAcceptedAt,
required this.role,
required this.project,
this.points,
});
factory UserModel.fromJson(Map<String, dynamic> json) {
@ -67,33 +68,36 @@ class UserModel {
role: json['role'] != null ? Role.fromJson(json['role']) : null,
project:
json['project'] != null ? Project.fromJson(json['project']) : null,
points: json['points'],
);
}
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'],
profilePicture: UserModel.decodeBase64Image(tempJson['profilePicture']),
phoneNumber: null,
isEmailVerified: null,
isAgreementAccepted: null,
regionUuid: null,
regionName: tempJson['region']?['regionName'],
timeZone: tempJson['timezone']?['timeZoneOffset'],
hasAcceptedWebAgreement: tempJson['hasAcceptedWebAgreement'],
webAgreementAcceptedAt: tempJson['webAgreementAcceptedAt'] != null
? DateTime.parse(tempJson['webAgreementAcceptedAt'])
: null,
hasAcceptedAppAgreement: tempJson['hasAcceptedAppAgreement'],
appAgreementAcceptedAt: tempJson['appAgreementAcceptedAt'] != null
? DateTime.parse(tempJson['appAgreementAcceptedAt'])
: null,
role: tempJson['role'] != null ? Role.fromJson(tempJson['role']) : null,
project: null);
uuid: tempJson['uuid'].toString(),
email: tempJson['email'],
lastName: tempJson['lastName'],
firstName: tempJson['firstName'],
profilePicture: UserModel.decodeBase64Image(tempJson['profilePicture']),
phoneNumber: null,
isEmailVerified: null,
isAgreementAccepted: null,
regionUuid: null,
regionName: tempJson['region']?['regionName'],
timeZone: tempJson['timezone']?['timeZoneOffset'],
hasAcceptedWebAgreement: tempJson['hasAcceptedWebAgreement'],
webAgreementAcceptedAt: tempJson['webAgreementAcceptedAt'] != null
? DateTime.parse(tempJson['webAgreementAcceptedAt'])
: null,
hasAcceptedAppAgreement: tempJson['hasAcceptedAppAgreement'],
appAgreementAcceptedAt: tempJson['appAgreementAcceptedAt'] != null
? DateTime.parse(tempJson['appAgreementAcceptedAt'])
: null,
role: tempJson['role'] != null ? Role.fromJson(tempJson['role']) : null,
project: null,
points: tempJson['points'],
);
}
static Uint8List? decodeBase64Image(String? base64String) {

View File

@ -321,7 +321,7 @@ class _OtpViewState extends State<OtpView> {
Expanded(
child: DefaultButton(
isDone: state is AuthLoginSuccess,
isLoading: state is AuthLoading,
isLoading: state is AuthOtpLoading,
customButtonStyle: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(
@ -349,8 +349,8 @@ class _OtpViewState extends State<OtpView> {
),
Expanded(
child: DefaultButton(
isDone: state is AuthLoginSuccess,
isLoading: state is AuthLoading,
isDone: state is ResendOtpSuccess,
isLoading: state is ResendOtpLoading,
customButtonStyle: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(
@ -383,7 +383,8 @@ class _OtpViewState extends State<OtpView> {
if (success) {
showDialog(
context: context,
builder: (_) =>const SuccessDialog(
builder: (_) =>
const SuccessDialog(
key: ValueKey(
'SuccessDialog'),
message: 'New OTP sent!',

View File

@ -0,0 +1,32 @@
import 'package:syncrow_app/features/booking_system/domain/booking_model.dart';
import 'package:syncrow_app/features/booking_system/domain/booking_service.dart';
class BookingDummySource implements BookingService {
@override
Future<List<BookingModel>> get() async {
await Future.delayed(Duration(seconds: 2));
return [
BookingModel(
uuid: 'uuid1',
roomName: 'roomName1',
date: 'wed 28th May 2025',
timeSlot: '10:30 AM - 11:30 AM',
cost: 4,
),
BookingModel(
uuid: 'uuid2',
roomName: 'roomName2',
date: 'wed 28th May 2025',
timeSlot: '10:30 AM - 11:30 AM',
cost: 6,
),
BookingModel(
uuid: 'uuid3',
roomName: 'roomName3',
date: 'thur 2th june 2025',
timeSlot: '10:30 AM - 1:30 PM',
cost: 10,
)
];
}
}

View File

@ -0,0 +1,35 @@
import 'package:dio/dio.dart';
import 'package:syncrow_app/features/booking_system/domain/booking_model.dart';
import 'package:syncrow_app/features/booking_system/domain/booking_service.dart';
import 'package:syncrow_app/services/api/api_links_endpoints.dart';
import 'package:syncrow_app/services/api/http_service.dart';
class BookingRemoteSource implements BookingService {
final HTTPService _httpService;
BookingRemoteSource(this._httpService);
@override
Future<List<BookingModel>> get() async {
try {
return _httpService.get(
path: ApiEndpoints.upcomingBookings,
expectedResponseModel: (json) {
return BookingModel.fromJsonList(json['data']);
},
);
} on DioException catch (e) {
return [];
// final message = e.response?.data as Map<String, dynamic>?;
// final error = message?['error'] as Map<String, dynamic>?;
// final errorMessage = error?['error'] as String? ?? '';
// final formattedErrorMessage =
// [_defaultErrorMessage, errorMessage].join(': ');
// throw APIException(formattedErrorMessage);
// } catch (e) {
// final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
// throw APIException(formattedErrorMessage);
// }
//
}
}
}

View File

@ -0,0 +1,39 @@
class BookingModel {
final String uuid, roomName, date, timeSlot;
final int cost;
BookingModel({
required this.uuid,
required this.roomName,
required this.date,
required this.timeSlot,
required this.cost,
});
factory BookingModel.zero() => BookingModel(
uuid: '',
roomName: '',
date: '',
timeSlot: '',
cost: -1,
);
factory BookingModel.fromJson(Map<String, dynamic> json) => BookingModel(
uuid: json['uuid'] as String,
roomName: json['roomName'] as String,
date: json['date'] as String,
timeSlot: json['timeSlot'] as String,
cost: json['cost'] as int,
);
static List<BookingModel> fromJsonList(List<dynamic> jsonList) => jsonList
.map(
(bookModel) => BookingModel.fromJson(bookModel),
)
.toList();
Map<String, dynamic> toJson() => {
'uuid': uuid,
'roomName': roomName,
'date': date,
'timeSlot': timeSlot,
'cost': cost,
};
}

View File

@ -0,0 +1,5 @@
import 'package:syncrow_app/features/booking_system/domain/booking_model.dart';
abstract interface class BookingService {
Future<List<BookingModel>> get();
}

View File

@ -0,0 +1,32 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import '../../../domain/booking_model.dart';
import '../../../domain/booking_service.dart';
part 'past_bookings_event.dart';
part 'past_bookings_state.dart';
class PastBookingsBloc extends Bloc<PastBookingsEvent, PastBookingsState> {
final BookingService _bookingService;
PastBookingsBloc(this._bookingService) : super(PastBookingsInitial()) {
on<GetPastBookingsEvent>(_onGetPastBookingsEvent);
}
Future<void> _onGetPastBookingsEvent(
GetPastBookingsEvent event,
Emitter<PastBookingsState> emit,
) async {
emit(PastBookingLoadingState());
try {
final pastBookings = await _bookingService.get();
emit(
PastBookingLoadedState(pastBookings: pastBookings),
);
} catch (e) {
emit(
PastBookingErrorState(e.toString()),
);
}
}
}

View File

@ -0,0 +1,10 @@
part of 'past_bookings_bloc.dart';
sealed class PastBookingsEvent extends Equatable {
const PastBookingsEvent();
@override
List<Object> get props => [];
}
class GetPastBookingsEvent extends PastBookingsEvent {}

View File

@ -0,0 +1,24 @@
part of 'past_bookings_bloc.dart';
sealed class PastBookingsState extends Equatable {
const PastBookingsState();
@override
List<Object> get props => [];
}
final class PastBookingsInitial extends PastBookingsState {}
final class PastBookingLoadingState extends PastBookingsState {}
final class PastBookingLoadedState extends PastBookingsState {
final List<BookingModel> pastBookings;
const PastBookingLoadedState({
required this.pastBookings,
});
}
final class PastBookingErrorState extends PastBookingsState {
final String errorMsg;
const PastBookingErrorState(this.errorMsg);
}

View File

@ -0,0 +1,32 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/booking_system/domain/booking_model.dart';
import 'package:syncrow_app/features/booking_system/domain/booking_service.dart';
part 'upcoming_bookings_event.dart';
part 'upcoming_bookings_state.dart';
class UpcomingBookingsBloc
extends Bloc<UpcomingBookingsEvent, UpcomingBookingsState> {
final BookingService _bookingService;
UpcomingBookingsBloc(this._bookingService)
: super(UpcomingBookingsInitial()) {
on<GetUpcomingBookingsEvent>(_onGetUpcomingBookingsEvent);
}
Future<void> _onGetUpcomingBookingsEvent(
GetUpcomingBookingsEvent event,
Emitter<UpcomingBookingsState> emit,
) async {
emit(UpcomingBookingLoadingState());
try {
final upcomingBookings = await _bookingService.get();
emit(
UpcomingBookingLoadedState(upcomingBookings: upcomingBookings),
);
} catch (e) {
emit(
UpcomingBookingErrorState(e.toString()),
);
}
}
}

View File

@ -0,0 +1,10 @@
part of 'upcoming_bookings_bloc.dart';
sealed class UpcomingBookingsEvent extends Equatable {
const UpcomingBookingsEvent();
@override
List<Object> get props => [];
}
class GetUpcomingBookingsEvent extends UpcomingBookingsEvent {}

View File

@ -0,0 +1,24 @@
part of 'upcoming_bookings_bloc.dart';
sealed class UpcomingBookingsState extends Equatable {
const UpcomingBookingsState();
@override
List<Object> get props => [];
}
final class UpcomingBookingsInitial extends UpcomingBookingsState {}
final class UpcomingBookingLoadingState extends UpcomingBookingsState {}
final class UpcomingBookingLoadedState extends UpcomingBookingsState {
final List<BookingModel> upcomingBookings;
const UpcomingBookingLoadedState({
required this.upcomingBookings,
});
}
final class UpcomingBookingErrorState extends UpcomingBookingsState {
final String errorMsg;
const UpcomingBookingErrorState(this.errorMsg);
}

View File

@ -0,0 +1,89 @@
import 'package:flutter/material.dart';
import 'package:syncrow_app/features/booking_system/presentation/widgets/booking_appbar_widget.dart';
import 'package:syncrow_app/features/shared_widgets/default_button.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/utils/helpers/app_size.dart';
import '../../../../utils/resource_manager/color_manager.dart';
import '../widgets/row_of_title_arrow_widget.dart';
class BookPage extends StatelessWidget {
const BookPage({super.key});
@override
Widget build(BuildContext context) {
return DefaultScaffold(
padding: EdgeInsets.zero,
appBar: BookingAppBar(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 20,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Text(
'Booking Details',
style: TextStyle(
fontSize: 15,
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
),
SizedBox(
height: 5,
),
Container(
padding: EdgeInsets.all(10),
height: deviceHeight(context) * 0.25,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: ColorsManager.onPrimaryColor,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RowOfTitleAndArrowWidget(
title: 'Space',
onTap: () {},
),
Divider(
color: ColorsManager.grayButtonColors,
),
RowOfTitleAndArrowWidget(
title: 'Booking Date',
onTap: () {},
),
Divider(
color: ColorsManager.grayButtonColors,
),
RowOfTitleAndArrowWidget(
title: 'Time Slot',
onTap: () {},
)
],
),
),
SizedBox(
height: 30,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: DefaultButton(
backgroundColor: ColorsManager.blueColor1,
child: Text(
'Book Now',
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
);
}
}

View File

@ -0,0 +1,67 @@
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/booking_system/data/booking_dummy_source.dart';
import 'package:syncrow_app/features/booking_system/presentation/blocs/upcoming_bookings_bloc/upcoming_bookings_bloc.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import '../blocs/past_bookings_bloc/past_bookings_bloc.dart';
import '../widgets/booking_appbar_widget.dart';
import '../widgets/current_balance_widget.dart';
import '../widgets/past_booking_widget.dart';
import '../widgets/upcoming_bookings_widget.dart';
class BookingSystemPage extends StatelessWidget {
const BookingSystemPage({super.key});
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<UpcomingBookingsBloc>(
create: (_) => UpcomingBookingsBloc(BookingDummySource())
..add(GetUpcomingBookingsEvent()),
),
BlocProvider<PastBookingsBloc>(
create: (_) => PastBookingsBloc(BookingDummySource())
..add(GetPastBookingsEvent()),
)
],
child: DefaultScaffold(
appBar: BookingAppBar(),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 20,
),
Expanded(
flex: 2,
child: CurrentBalanceWidget(
userBalance: HomeCubit.user!.points == null
? '0'
: HomeCubit.user!.points.toString(),
),
),
SizedBox(
height: 20,
),
Expanded(
flex: 8,
child: Column(
children: [
UpcomingBookingsWidget(),
SizedBox(
height: 10,
),
PastBookingsWidget(),
],
),
)
],
),
),
));
}
}

View File

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import '../../../../utils/resource_manager/color_manager.dart';
class BookingAppBar extends StatelessWidget implements PreferredSizeWidget {
const BookingAppBar({super.key});
@override
Widget build(BuildContext context) {
return AppBar(
backgroundColor: Colors.transparent,
centerTitle: true,
leading: IconButton(
onPressed: () => Navigator.pop(context),
icon: Icon(Icons.arrow_back_ios_new)),
title: Text(
'Booking',
style: TextStyle(
color: ColorsManager.blueColor1,
fontSize: 15,
fontWeight: FontWeight.bold,
),
),
);
}
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
}

View File

@ -0,0 +1,105 @@
import 'package:flutter/material.dart';
import '../../../../utils/resource_manager/color_manager.dart';
import '../../domain/booking_model.dart';
class BookingCardWidget extends StatelessWidget {
const BookingCardWidget({
super.key,
required this.bookingModel,
});
final BookingModel bookingModel;
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(15),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: ColorsManager.onPrimaryColor,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
bookingModel.roomName,
style: TextStyle(
color: ColorsManager.blackColor,
fontSize: 17,
fontWeight: FontWeight.bold,
),
),
SizedBox(
height: 15,
),
Text(
'Booking Date',
style: TextStyle(
color: ColorsManager.grayColor,
fontSize: 15,
),
),
Text(
bookingModel.date,
style: TextStyle(
color: ColorsManager.blackColor,
fontSize: 15,
fontWeight: FontWeight.w500,
),
),
SizedBox(
height: 15,
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Time slot',
style: TextStyle(
color: ColorsManager.grayColor,
fontSize: 15,
),
),
Text(
bookingModel.timeSlot,
style: TextStyle(
color: ColorsManager.blackColor,
fontSize: 15,
fontWeight: FontWeight.w500,
),
),
],
),
SizedBox(
width: 80,
),
Column(
children: [
Text(
'cost',
style: TextStyle(
color: ColorsManager.grayColor,
fontSize: 15,
),
),
Text(
bookingModel.cost.toString(),
style: TextStyle(
color: ColorsManager.blackColor,
fontSize: 15,
fontWeight: FontWeight.w500,
),
),
],
),
],
)
],
),
);
}
}

View File

@ -0,0 +1,97 @@
import 'package:flutter/material.dart';
import '../../../../utils/resource_manager/color_manager.dart';
import '../screens/book_page.dart';
class CurrentBalanceWidget extends StatelessWidget {
final String userBalance;
const CurrentBalanceWidget({
super.key,
required this.userBalance,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: ColorsManager.onPrimaryColor,
borderRadius: BorderRadius.circular(20)),
child: Row(
children: [
Expanded(
flex: 75,
child: Padding(
padding: EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
'Current Balance',
style: TextStyle(
color: ColorsManager.blackColor,
fontWeight: FontWeight.bold,
fontSize: 17,
),
)),
SizedBox(
height: 10,
),
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
userBalance,
style: TextStyle(
color: ColorsManager.blueColor1,
fontWeight: FontWeight.bold,
fontSize: 50),
),
SizedBox(
width: 5,
),
Text(
'Points',
style: TextStyle(
color: ColorsManager.blueColor1,
fontWeight: FontWeight.bold,
fontSize: 17),
),
],
)),
],
),
)),
Expanded(
flex: 25,
child: InkWell(
//TODO:should use custom Navigator
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => BookPage(),
)),
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.only(left: 15),
height: double.infinity,
decoration: BoxDecoration(
color: ColorsManager.blueColor,
borderRadius:
BorderRadius.horizontal(right: Radius.circular(20)),
),
child: Text(
'Book Now',
style: TextStyle(
color: ColorsManager.onPrimaryColor,
fontWeight: FontWeight.bold,
fontSize: 20),
),
),
),
)
],
),
);
}
}

View File

@ -0,0 +1,64 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../utils/helpers/app_size.dart';
import '../../../../utils/resource_manager/color_manager.dart';
import '../../../shared_widgets/default_button.dart';
import '../blocs/past_bookings_bloc/past_bookings_bloc.dart';
import 'booking_card_widget.dart';
class PastBookingsWidget extends StatelessWidget {
const PastBookingsWidget({super.key});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Past Bookings',
style: TextStyle(
fontSize: 15,
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
SizedBox(
height: 10,
),
BlocBuilder<PastBookingsBloc, PastBookingsState>(
builder: (context, state) {
if (state is PastBookingLoadingState) {
return CircularProgressIndicator();
} else if (state is PastBookingErrorState) {
return DefaultButton(
onPressed: () => context
.read<PastBookingsBloc>()
.add(GetPastBookingsEvent()),
child: Text('Try again'),
);
} else if (state is PastBookingLoadedState) {
return SizedBox(
height: deviceHeight(context) * 0.3,
child: state.pastBookings.isEmpty
? Text('You Dont Have past Bookings')
: ListView.separated(
separatorBuilder: (context, index) => SizedBox(
height: 10,
),
itemCount: state.pastBookings.length,
itemBuilder: (context, index) {
final pastBooking = state.pastBookings[index];
return BookingCardWidget(bookingModel: pastBooking);
},
),
);
} else {
return SizedBox();
}
},
)
],
);
}
}

View File

@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import '../../../../utils/resource_manager/color_manager.dart';
class RowOfTitleAndArrowWidget extends StatelessWidget {
final String title;
final void Function() onTap;
const RowOfTitleAndArrowWidget({
super.key,
required this.title,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: EdgeInsets.all(10),
child: Text(
title,
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.bold,
),
),
),
IconButton(
onPressed: onTap,
icon: Icon(
Icons.arrow_forward_ios,
color: ColorsManager.grayColor,
size: 15,
),
)
],
);
}
}

View File

@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../utils/helpers/app_size.dart';
import '../../../../utils/resource_manager/color_manager.dart';
import '../../../shared_widgets/default_button.dart';
import '../blocs/upcoming_bookings_bloc/upcoming_bookings_bloc.dart';
import 'booking_card_widget.dart';
class UpcomingBookingsWidget extends StatelessWidget {
const UpcomingBookingsWidget({super.key});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Upcoming Bookings',
style: TextStyle(
fontSize: 15,
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
SizedBox(
height: 10,
),
BlocBuilder<UpcomingBookingsBloc, UpcomingBookingsState>(
builder: (context, state) {
if (state is UpcomingBookingLoadingState) {
//TODO:should use CustomLoadingWidget
return CircularProgressIndicator();
} else if (state is UpcomingBookingErrorState) {
//TODO:shold use CustomErrorWidget
return DefaultButton(
onPressed: () => context
.read<UpcomingBookingsBloc>()
.add(GetUpcomingBookingsEvent()),
child: Text('Try again'),
);
} else if (state is UpcomingBookingLoadedState) {
return SizedBox(
height: deviceHeight(context) * 0.3,
child: state.upcomingBookings.isEmpty
? Text('You Dont Have Upcoming Bookings')
: ListView.separated(
separatorBuilder: (context, index) => SizedBox(
height: 10,
),
itemCount: state.upcomingBookings.length,
itemBuilder: (context, index) {
final upcomingBooking = state.upcomingBookings[index];
return BookingCardWidget(
bookingModel: upcomingBooking);
},
),
);
} else {
return SizedBox();
}
},
)
],
);
}
}

View File

@ -22,7 +22,7 @@ class FlushSensorBloc extends Bloc<FlushSensorEvent, FlushSensorState> {
on<FlushSensorChangeValueEvent>(_changeValue);
on<FlushSensorUpdatedEvent>(_flushSensorUpdated);
on<FlushSensorGetDeviceReportsEvent>(_getDeviceReports);
on<FlushSensorInitialDeviseInfo>(fetchDeviceInfo);
on<FlushSensorInitialDeviceInfo>(fetchDeviceInfo);
}
void _fetchFlushSensorStatus(
@ -163,7 +163,7 @@ class FlushSensorBloc extends Bloc<FlushSensorEvent, FlushSensorState> {
);
static String deviceName = '';
void fetchDeviceInfo(FlushSensorInitialDeviseInfo event,
void fetchDeviceInfo(FlushSensorInitialDeviceInfo event,
Emitter<FlushSensorState> emit) async {
try {
emit(FlushSensorLoadingInitialState());
@ -171,6 +171,7 @@ class FlushSensorBloc extends Bloc<FlushSensorEvent, FlushSensorState> {
deviceInfo = DeviceInfoModel.fromJson(response);
deviceName = deviceInfo.name;
emit(FlushSensorLoadingDeviceInfo(deviceInfo: deviceInfo));
emit(FlushSensorUpdateState(flushSensorModel: deviceStatus));
} catch (e) {
emit(FlushSensorFailedState(error: e.toString()));
}

View File

@ -11,7 +11,7 @@ class FlushSensorLoadingEvent extends FlushSensorEvent {}
class FlushSensorInitialEvent extends FlushSensorEvent {}
class FlushSensorInitialDeviseInfo extends FlushSensorEvent {}
class FlushSensorInitialDeviceInfo extends FlushSensorEvent {}
class FlushSensorUpdatedEvent extends FlushSensorEvent {}

View File

@ -149,7 +149,6 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
_streamSubscription = null;
return super.close();
}
_doorLockUpdated(DoorLockUpdated event, Emitter<SmartDoorState> emit) {
unlockRequest = deviceStatus.unlockRequest;
@ -254,18 +253,42 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
}
void _updateLock(UpdateLockEvent event, Emitter<SmartDoorState> emit) async {
emit(LoadingNewSate(smartDoorModel: deviceStatus));
final oldValue = deviceStatus.normalOpenSwitch;
deviceStatus = deviceStatus.copyWith(normalOpenSwitch: !oldValue);
emit(UpdateState(smartDoorModel: deviceStatus));
try {
// final response = await DevicesAPI.controlDevice(
// DeviceControlModel(deviceId: deviceId, code: 'normal_open_switch', value: !event.value),
// deviceId);
final response = await DevicesAPI.openDoorLock(deviceId);
if (response) {
deviceStatus.normalOpenSwitch = !event.value;
if (!response) {
_revertValueAndEmit(deviceId, 'normal_open_switch', oldValue, emit);
}
} catch (_) {}
} catch (_) {
_revertValueAndEmit(deviceId, 'normal_open_switch', oldValue, emit);
}
}
void _revertValueAndEmit(String deviceId, String code, dynamic oldValue,
Emitter<SmartDoorState> emit) {
_updateLocalValue(code, oldValue);
emit(UpdateState(smartDoorModel: deviceStatus));
emit(const FailedState(errorMessage: 'Failed to control the device.'));
}
void _updateLocalValue(String code, dynamic value) {
switch (code) {
case 'normal_open_switch':
if (value is bool) {
deviceStatus = deviceStatus.copyWith(normalOpenSwitch: value);
}
break;
case 'reverse_lock':
if (value is bool) {
deviceStatus = deviceStatus.copyWith(reverseLock: value);
}
break;
default:
break;
}
emit(UpdateState(smartDoorModel: deviceStatus));
}
@ -331,6 +354,7 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
Future<void> selectTimeOnlinePassword(
SelectTimeOnlinePasswordEvent event, Emitter<SmartDoorState> emit) async {
effectiveTimeTimeStamp ??= DateTime.now().millisecondsSinceEpoch ~/ 1000;
emit(ChangeTimeState());
final DateTime? picked = await showDatePicker(
context: event.context,
@ -375,7 +399,13 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
selectedDateTime.minute,
).millisecondsSinceEpoch ~/
1000; // Divide by 1000 to remove milliseconds
final currentTimestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
if (event.isEffective) {
if (selectedTimestamp < currentTimestamp) {
CustomSnackBar.displaySnackBar(
'Effective Time cannot be later than Expiration Time.');
return;
}
if (expirationTimeTimeStamp != null &&
selectedTimestamp > expirationTimeTimeStamp!) {
CustomSnackBar.displaySnackBar(

View File

@ -55,31 +55,33 @@ class DeviceInfoModel {
factory DeviceInfoModel.fromJson(Map<String, dynamic> json) {
return DeviceInfoModel(
activeTime: json['activeTime'],
category: json['category'],
categoryName: json['categoryName'],
createTime: json['createTime'],
gatewayId: json['gatewayId'],
icon: json['icon'],
activeTime: json['activeTime'] ?? '',
category: json['category'] ?? '',
categoryName: json['categoryName'] ?? '',
createTime: json['createTime'] ?? '',
gatewayId: json['gatewayId'] ?? '',
icon: json['icon'] ?? '',
ip: json['ip'] ?? "",
lat: json['lat'],
localKey: json['localKey'],
lon: json['lon'],
model: json['model'],
name: json['name'],
nodeId: json['nodeId'],
online: json['online'],
ownerId: json['ownerId'],
productName: json['productName'],
sub: json['sub'],
timeZone: json['timeZone'],
updateTime: json['updateTime'],
uuid: json['uuid'],
productUuid: json['productUuid'],
productType: json['productType'],
lat: json['lat'] ?? '',
localKey: json['localKey'] ?? '',
lon: json['lon'] ?? '',
model: json['model'] ?? '',
name: json['name'] ?? '',
nodeId: json['nodeId'] ?? '',
online: json['online'] ?? '',
ownerId: json['ownerId'] ?? '',
productName: json['productName'] ?? '',
sub: json['sub'] ?? '',
timeZone: json['timeZone'] ?? '',
updateTime: json['updateTime'] ?? '',
uuid: json['uuid'] ?? '',
productUuid: json['productUuid'] ?? '',
productType: json['productType'] ?? '',
permissionType: json['permissionType'] ?? '',
macAddress: json['macAddress'],
subspace: Subspace.fromJson(json['subspace']),
macAddress: json['macAddress'] ?? '',
subspace: json['subspace'] != null
? Subspace.fromJson(json['subspace'])
: throw ArgumentError('subspace cannot be null'),
);
}
@ -129,10 +131,10 @@ class Subspace {
factory Subspace.fromJson(Map<String, dynamic> json) {
return Subspace(
uuid: json['uuid'],
createdAt: json['createdAt'],
updatedAt: json['updatedAt'],
subspaceName: json['subspaceName'],
uuid: json['uuid'] ?? '',
createdAt: json['createdAt'] ?? '',
updatedAt: json['updatedAt'] ?? '',
subspaceName: json['subspaceName'] ?? '',
);
}

View File

@ -113,4 +113,44 @@ class SmartDoorModel {
remoteNoDpKey: _remoteNoDpKey,
normalOpenSwitch: _normalOpenSwitch);
}
SmartDoorModel copyWith({
String? uuid,
int? unlockFingerprint,
int? unlockPassword,
int? unlockTemporary,
int? unlockCard,
String? alarmLock,
int? unlockRequest,
int? residualElectricity,
bool? reverseLock,
int? unlockApp,
bool? hijack,
bool? doorbell,
String? unlockOfflinePd,
String? unlockOfflineClear,
String? unlockDoubleKit,
String? remoteNoPdSetkey,
String? remoteNoDpKey,
bool? normalOpenSwitch,
}) {
return SmartDoorModel(
unlockAlarm: alarmLock ?? unlockAlarm,
unlockFingerprint: unlockFingerprint ?? this.unlockFingerprint,
unlockPassword: unlockPassword ?? this.unlockPassword,
unlockTemporary: unlockTemporary ?? this.unlockTemporary,
unlockCard: unlockCard ?? this.unlockCard,
unlockRequest: unlockRequest ?? this.unlockRequest,
residualElectricity: residualElectricity ?? this.residualElectricity,
reverseLock: reverseLock ?? this.reverseLock,
unlockApp: unlockApp ?? this.unlockApp,
hijack: hijack ?? this.hijack,
doorbell: doorbell ?? this.doorbell,
unlockOfflinePd: unlockOfflinePd ?? this.unlockOfflinePd,
unlockOfflineClear: unlockOfflineClear ?? this.unlockOfflineClear,
unlockDoubleKit: unlockDoubleKit ?? this.unlockDoubleKit,
remoteNoPdSetkey: remoteNoPdSetkey ?? this.remoteNoPdSetkey,
remoteNoDpKey: remoteNoDpKey ?? this.remoteNoDpKey,
normalOpenSwitch: normalOpenSwitch ?? this.normalOpenSwitch,
);
}
}

View File

@ -22,141 +22,155 @@ class SettingProfilePage extends StatelessWidget {
Widget build(BuildContext context) {
var spaces = HomeCubit.getInstance().spaces;
return DefaultScaffold(
title: 'Device Settings',
leading: IconButton(
onPressed: () {
Navigator.of(context).pop(true);
},
icon: const Icon(Icons.arrow_back_ios)),
child: BlocProvider(
create: (context) => DeviceSettingBloc(deviceId: device?.uuid ?? '')
..add(const DeviceSettingInitial())
..add(const DeviceSettingInitialInfo()),
child: BlocBuilder<DeviceSettingBloc, DeviceSettingState>(
builder: (context, state) {
final _bloc = BlocProvider.of<DeviceSettingBloc>(context);
return state is DeviceSettingLoadingState
? const Center(
child: DefaultContainer(
width: 50,
height: 50,
child: CircularProgressIndicator()),
)
: RefreshIndicator(
onRefresh: () async {
_bloc.add(const DeviceSettingInitial());
},
child: ListView(
children: [
buildDeviceAvatar(context, device!),
const SizedBox(
height: 10,
),
SizedBox(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IntrinsicWidth(
child: ConstrainedBox(
constraints:
const BoxConstraints(maxWidth: 200),
child: TextFormField(
maxLength: 30,
style: const TextStyle(
color: Colors.black,
),
textAlign: TextAlign.center,
focusNode: _bloc.focusNode,
controller: _bloc.nameController,
enabled: _bloc.editName,
onEditingComplete: () {
_bloc.add(const SaveNameEvent());
},
decoration: const InputDecoration(
hintText: "Your Name",
border: InputBorder.none,
fillColor: Colors.white10,
counterText: '',
),
),
),
),
const SizedBox(width: 5),
InkWell(
onTap: () {
_bloc.add(const ChangeNameEvent(value: true));
},
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: SvgPicture.asset(
Assets.sosEditProfile,
color: Colors.grey,
fit: BoxFit.contain,
height: MediaQuery.of(context).size.height *
0.02,
),
),
),
],
return PopScope(
canPop: false,
onPopInvoked: (didPop) {
if (didPop) {
return;
}
Navigator.of(context).pop(true);
},
child: DefaultScaffold(
title: 'Device Settings',
leading: IconButton(
onPressed: () {
Navigator.of(context).pop(true);
},
icon: const Icon(Icons.arrow_back_ios)),
child: BlocProvider(
create: (context) => DeviceSettingBloc(deviceId: device?.uuid ?? '')
..add(const DeviceSettingInitial())
..add(const DeviceSettingInitialInfo()),
child: BlocBuilder<DeviceSettingBloc, DeviceSettingState>(
builder: (context, state) {
final _bloc = BlocProvider.of<DeviceSettingBloc>(context);
return state is DeviceSettingLoadingState
? const Center(
child: DefaultContainer(
width: 50,
height: 50,
child: CircularProgressIndicator()),
)
: RefreshIndicator(
onRefresh: () async {
_bloc.add(const DeviceSettingInitial());
},
child: ListView(
children: [
buildDeviceAvatar(context, device!),
const SizedBox(
height: 10,
),
),
const SizedBox(height: 20),
const BodyMedium(
text: 'Smart Device Information',
fontWeight: FontWeight.w700,
fontSize: 12,
fontColor: ColorsManager.grayColor,
),
const SizedBox(height: 7),
DefaultContainer(
padding: const EdgeInsets.all(20),
child: InkWell(
onTap: () async {
if (HomeCubit.visitorPasswordManagement) {
bool? val = await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LocationSettingPage(
space: spaces!.first,
deviceId: device?.uuid ?? '',
)),
);
if (val != null && val == true) {
_bloc.add(const DeviceSettingInitialInfo());
}
}
},
SizedBox(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(
child: Text('Location'),
),
Row(
children: [
SizedBox(
child: BodyMedium(
text: _bloc
.deviceInfo.subspace.subspaceName,
fontColor: ColorsManager.textGray,
IntrinsicWidth(
child: ConstrainedBox(
constraints:
const BoxConstraints(maxWidth: 200),
child: TextFormField(
maxLength: 30,
style: const TextStyle(
color: Colors.black,
),
textAlign: TextAlign.center,
focusNode: _bloc.focusNode,
controller: _bloc.nameController,
enabled: _bloc.editName,
onEditingComplete: () {
_bloc.add(const SaveNameEvent());
},
decoration: const InputDecoration(
hintText: "Your Name",
border: InputBorder.none,
fillColor: Colors.white10,
counterText: '',
),
),
const Icon(
Icons.arrow_forward_ios,
size: 15,
color: ColorsManager.textGray,
),
),
const SizedBox(width: 5),
InkWell(
onTap: () {
_bloc.add(
const ChangeNameEvent(value: true));
},
child: Padding(
padding:
EdgeInsets.symmetric(horizontal: 10),
child: SvgPicture.asset(
Assets.sosEditProfile,
color: Colors.grey,
fit: BoxFit.contain,
height:
MediaQuery.of(context).size.height *
0.02,
),
],
)
),
),
],
),
),
)
],
),
);
},
const SizedBox(height: 20),
const BodyMedium(
text: 'Smart Device Information',
fontWeight: FontWeight.w700,
fontSize: 12,
fontColor: ColorsManager.grayColor,
),
const SizedBox(height: 7),
DefaultContainer(
padding: const EdgeInsets.all(20),
child: InkWell(
onTap: () async {
if (HomeCubit.visitorPasswordManagement) {
bool? val = await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
LocationSettingPage(
space: spaces!.first,
deviceId: device?.uuid ?? '',
)),
);
if (val != null && val == true) {
_bloc.add(const DeviceSettingInitialInfo());
}
}
},
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
const SizedBox(
child: Text('Location'),
),
Row(
children: [
SizedBox(
child: BodyMedium(
text: _bloc
.deviceInfo.subspace.subspaceName,
fontColor: ColorsManager.textGray,
),
),
const Icon(
Icons.arrow_forward_ios,
size: 15,
color: ColorsManager.textGray,
),
],
)
],
),
),
)
],
),
);
},
),
),
),
);

View File

@ -28,406 +28,449 @@ class SettingsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DefaultScaffold(
title: 'Device Settings',
child: BlocProvider(
create: (context) => DeviceSettingBloc(deviceId: device?.uuid ?? '')
..add(const DeviceSettingInitial())
..add(const DeviceSettingInitialInfo()),
child: BlocBuilder<DeviceSettingBloc, DeviceSettingState>(
builder: (context, state) {
final _bloc = BlocProvider.of<DeviceSettingBloc>(context);
return PopScope(
canPop: false,
onPopInvoked: (didPop) {
if (didPop) {
return;
}
Navigator.of(context).pop(true);
},
child: DefaultScaffold(
title: 'Device Settings',
child: BlocProvider(
create: (context) => DeviceSettingBloc(deviceId: device?.uuid ?? '')
..add(const DeviceSettingInitial())
..add(const DeviceSettingInitialInfo()),
child: BlocBuilder<DeviceSettingBloc, DeviceSettingState>(
builder: (context, state) {
final _bloc = BlocProvider.of<DeviceSettingBloc>(context);
return state is DeviceSettingLoadingState
? const Center(
child:
DefaultContainer(width: 50, height: 50, child: CircularProgressIndicator()),
)
: ListView(
children: [
Padding(
padding: const EdgeInsets.symmetric(
vertical: 10,
),
child: InkWell(
onTap: () async {
bool val = await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SettingProfilePage(
device: device,
),
),
);
if (val == true) {
_bloc.add(const DeviceSettingInitialInfo());
}
},
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 20),
DefaultContainer(
borderRadius: const BorderRadius.all(Radius.circular(30)),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Padding(
padding: const EdgeInsets.only(left: 90),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
child: Text(
_bloc.deviceInfo.name,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: ColorsManager.grayColor,
),
overflow: TextOverflow
.ellipsis, // Adds ellipsis (...) when the text overflows.
maxLines:
1, // Restricts the text to a single line.
)),
const SizedBox(
height: 5,
),
BodySmall(
text: _bloc.deviceInfo.subspace.subspaceName),
],
),
),
SvgPicture.asset(
Assets.editNameSetting,
fit: BoxFit.contain,
height: 30,
),
],
),
),
),
),
],
),
Positioned(
top: 0,
left: 20,
child: CircleAvatar(
radius: 43,
backgroundColor: Colors.white,
child: CircleAvatar(
radius: 40,
backgroundColor: Colors.white,
child: CircleAvatar(
radius: 40,
backgroundColor: ColorsManager.backgroundColor,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
device!.type == 'NCPS'
? SizedBox(
height: 80,
width: 50,
child: SvgPicture.asset(
Assets.flushIcon,
fit: BoxFit.contain,
),
):
device!.type == "SOS"
? ClipOval(
child: SvgPicture.asset(
Assets.sosHomeIcon,
fit: BoxFit.contain,
height: 70,
),
)
: Padding(
padding: device!.type == "4S"
? const EdgeInsets.only(top: 5)
: const EdgeInsets.only(top: 0),
child: SizedBox(
height: 70,
child: SvgPicture.asset(
device!.type == "4S"
? Assets.fourSceneIcon
: Assets.sixSceneIcon,
fit: BoxFit.contain,
),
),
),
],
),
),
)),
),
],
return state is DeviceSettingLoadingState
? const Center(
child: DefaultContainer(
width: 50,
height: 50,
child: CircularProgressIndicator()),
)
: ListView(
children: [
Padding(
padding: const EdgeInsets.symmetric(
vertical: 10,
),
),
),
const SizedBox(height: 20),
const BodyMedium(
text: 'Device Management',
fontWeight: FontWeight.w700,
fontSize: 12,
fontColor: ColorsManager.grayColor,
),
DefaultContainer(
child: Column(
children: [
SettingWidget(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SettingInfoPage(
device: device!,
)),
);
},
text: 'Device Information',
icon: Assets.infoIcon,
),
// const Divider(
// color: ColorsManager.dividerColor,
// ),
// SettingWidget(
// onTap: () {
// Navigator.of(context).push(
// MaterialPageRoute(
// builder: (context) =>
// ShareFourScenePage(
// device: device!)),
// );
// },
// text: 'Tap-to Run and Automation',
// icon: Assets.tapRunIcon,
// ),
],
),
),
const SizedBox(height: 20),
// const BodyMedium(
// text: 'Device Offline Notification',
// fontWeight: FontWeight.w700,
// fontSize: 12,
// fontColor: ColorsManager.grayColor,
// ),
// DefaultContainer(
// child: Column(
// children: [
// SettingWidget(
// value: _bloc.enableAlarm,
// onChanged: (p0) {
// context
// .read<DeviceSettingBloc>()
// .add(ToggleEnableAlarmEvent(p0));
// },
// isNotification: true,
// onTap: () {},
// text: 'Offline Notification',
// icon: Assets.notificationIcon,
// ),
// ],
// ),
// ),
// const SizedBox(height: 20),
// const BodyMedium(
// text: 'Others',
// fontWeight: FontWeight.w700,
// fontSize: 12,
// fontColor: ColorsManager.grayColor,
// ),
// const SizedBox(height: 5),
// DefaultContainer(
// child: Column(
// children: [
// SettingWidget(
// onTap: () {
// Navigator.of(context).push(
// MaterialPageRoute(
// builder: (context) =>
// ShareDevicePage(device: device!)),
// );
// },
// text: 'Share Device',
// icon: Assets.shareIcon,
// ),
// // const Divider(
// // color: ColorsManager.dividerColor,
// // ),
// // SettingWidget(
// // onTap: () {
// // Navigator.of(context).push(
// // MaterialPageRoute(
// // builder: (context) =>
// // FourSceneCreateGroup(
// // device: device!)),
// // );
// // },
// // text: 'Create Group',
// // icon: Assets.createGroupIcon,
// // ),
// const Divider(
// color: ColorsManager.dividerColor,
// ),
// SettingWidget(
// onTap: () {
// Navigator.of(context).push(
// MaterialPageRoute(
// builder: (context) =>
// FaqSettingPage(device: device!)),
// );
// },
// text: 'Device FAQ',
// icon: Assets.faqIcon,
// ),
// const Divider(
// color: ColorsManager.dividerColor,
// ),
// SettingWidget(
// onTapUpdate: () {
// showDialog(
// context: context,
// builder: (context) {
// return UpdateInfoDialog(
// cancelTab: () {
// Navigator.of(context).pop();
// },
// confirmTab: () {
// Navigator.of(context).pop();
// },
// );
// },
// );
// },
// isUpdate: true,
// onTap: () {
// Navigator.of(context).push(
// MaterialPageRoute(
// builder: (context) =>
// const UpdatePageSetting()),
// );
// },
// text: 'Device Update',
// icon: Assets.updateIcon,
// ),
// ],
// ),
// ),
const SizedBox(height: 20),
InkWell(
onTap: () {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Container(
height: 200,
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
child: InkWell(
onTap: () async {
bool val = await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SettingProfilePage(
device: device,
),
),
);
if (val == true) {
_bloc.add(const DeviceSettingInitialInfo());
}
},
child: Stack(
children: [
Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
const BodyMedium(
text: 'Remove Device',
fontWeight: FontWeight.w700,
fontSize: 16,
fontColor: ColorsManager.red,
),
const SizedBox(height: 10),
const SizedBox(
width: 250,
child: Divider(
color: ColorsManager.dividerColor,
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
InkWell(
onTap: () {
showDialog(
context: context,
builder: (context) {
return DisconnectDeviceDialog(
cancelTab: () {
Navigator.of(context).pop();
},
confirmTab: () {
Navigator.of(context).pop();
},
);
},
);
},
child: const BodyMedium(
text: 'Disconnect Device',
fontWeight: FontWeight.w400,
fontSize: 15,
fontColor: ColorsManager.textPrimaryColor,
),
),
const Icon(
Icons.keyboard_arrow_right,
color: ColorsManager.textGray,
)
],
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
InkWell(
onTap: () {
showDialog(
context: context,
builder: (context) {
return DisconnectWipeData(
cancelTab: () {
Navigator.of(context).pop();
},
confirmTab: () {
_bloc.add(DeleteDeviceEvent());
},
);
},
);
},
child: const BodyMedium(
text: 'Disconnect Device and Wipe Data',
fontWeight: FontWeight.w400,
fontSize: 15,
fontColor: ColorsManager.textPrimaryColor,
DefaultContainer(
borderRadius: const BorderRadius.all(
Radius.circular(30)),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Padding(
padding:
const EdgeInsets.only(left: 90),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
SizedBox(
child: Text(
_bloc.deviceInfo.name,
style: const TextStyle(
fontSize: 16,
fontWeight:
FontWeight.w700,
color: ColorsManager
.grayColor,
),
overflow: TextOverflow
.ellipsis, // Adds ellipsis (...) when the text overflows.
maxLines:
1, // Restricts the text to a single line.
)),
const SizedBox(
height: 5,
),
BodySmall(
text: _bloc
.deviceInfo
.subspace
.subspaceName),
],
),
),
SvgPicture.asset(
Assets.editNameSetting,
fit: BoxFit.contain,
height: 30,
),
],
),
),
const Icon(
Icons.keyboard_arrow_right,
color: ColorsManager.textGray,
)
],
),
),
],
),
);
},
);
},
child: const Center(
child: BodyMedium(
text: 'Remove Device',
fontWeight: FontWeight.w400,
fontSize: 15,
fontColor: ColorsManager.red,
Positioned(
top: 0,
left: 20,
child: CircleAvatar(
radius: 43,
backgroundColor: Colors.white,
child: CircleAvatar(
radius: 40,
backgroundColor: Colors.white,
child: CircleAvatar(
radius: 40,
backgroundColor:
ColorsManager.backgroundColor,
child: Column(
crossAxisAlignment:
CrossAxisAlignment.center,
mainAxisAlignment:
MainAxisAlignment.center,
children: [
device!.type == 'NCPS'
? SizedBox(
height: 80,
width: 50,
child: SvgPicture.asset(
Assets.flushIcon,
fit: BoxFit.contain,
),
)
: device!.type == "SOS"
? ClipOval(
child:
SvgPicture.asset(
Assets.sosHomeIcon,
fit: BoxFit.contain,
height: 70,
),
)
: Padding(
padding: device!
.type ==
"4S"
? const EdgeInsets
.only(top: 5)
: const EdgeInsets
.only(top: 0),
child: SizedBox(
height: 70,
child: SvgPicture
.asset(
device!.type ==
"4S"
? Assets
.fourSceneIcon
: Assets
.sixSceneIcon,
fit: BoxFit
.contain,
),
),
),
],
),
),
)),
),
],
),
),
),
),
],
);
},
const SizedBox(height: 20),
const BodyMedium(
text: 'Device Management',
fontWeight: FontWeight.w700,
fontSize: 12,
fontColor: ColorsManager.grayColor,
),
DefaultContainer(
child: Column(
children: [
SettingWidget(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SettingInfoPage(
device: device!,
)),
);
},
text: 'Device Information',
icon: Assets.infoIcon,
),
// const Divider(
// color: ColorsManager.dividerColor,
// ),
// SettingWidget(
// onTap: () {
// Navigator.of(context).push(
// MaterialPageRoute(
// builder: (context) =>
// ShareFourScenePage(
// device: device!)),
// );
// },
// text: 'Tap-to Run and Automation',
// icon: Assets.tapRunIcon,
// ),
],
),
),
const SizedBox(height: 20),
// const BodyMedium(
// text: 'Device Offline Notification',
// fontWeight: FontWeight.w700,
// fontSize: 12,
// fontColor: ColorsManager.grayColor,
// ),
// DefaultContainer(
// child: Column(
// children: [
// SettingWidget(
// value: _bloc.enableAlarm,
// onChanged: (p0) {
// context
// .read<DeviceSettingBloc>()
// .add(ToggleEnableAlarmEvent(p0));
// },
// isNotification: true,
// onTap: () {},
// text: 'Offline Notification',
// icon: Assets.notificationIcon,
// ),
// ],
// ),
// ),
// const SizedBox(height: 20),
// const BodyMedium(
// text: 'Others',
// fontWeight: FontWeight.w700,
// fontSize: 12,
// fontColor: ColorsManager.grayColor,
// ),
// const SizedBox(height: 5),
// DefaultContainer(
// child: Column(
// children: [
// SettingWidget(
// onTap: () {
// Navigator.of(context).push(
// MaterialPageRoute(
// builder: (context) =>
// ShareDevicePage(device: device!)),
// );
// },
// text: 'Share Device',
// icon: Assets.shareIcon,
// ),
// // const Divider(
// // color: ColorsManager.dividerColor,
// // ),
// // SettingWidget(
// // onTap: () {
// // Navigator.of(context).push(
// // MaterialPageRoute(
// // builder: (context) =>
// // FourSceneCreateGroup(
// // device: device!)),
// // );
// // },
// // text: 'Create Group',
// // icon: Assets.createGroupIcon,
// // ),
// const Divider(
// color: ColorsManager.dividerColor,
// ),
// SettingWidget(
// onTap: () {
// Navigator.of(context).push(
// MaterialPageRoute(
// builder: (context) =>
// FaqSettingPage(device: device!)),
// );
// },
// text: 'Device FAQ',
// icon: Assets.faqIcon,
// ),
// const Divider(
// color: ColorsManager.dividerColor,
// ),
// SettingWidget(
// onTapUpdate: () {
// showDialog(
// context: context,
// builder: (context) {
// return UpdateInfoDialog(
// cancelTab: () {
// Navigator.of(context).pop();
// },
// confirmTab: () {
// Navigator.of(context).pop();
// },
// );
// },
// );
// },
// isUpdate: true,
// onTap: () {
// Navigator.of(context).push(
// MaterialPageRoute(
// builder: (context) =>
// const UpdatePageSetting()),
// );
// },
// text: 'Device Update',
// icon: Assets.updateIcon,
// ),
// ],
// ),
// ),
const SizedBox(height: 20),
InkWell(
onTap: () {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Container(
height: 200,
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const BodyMedium(
text: 'Remove Device',
fontWeight: FontWeight.w700,
fontSize: 16,
fontColor: ColorsManager.red,
),
const SizedBox(height: 10),
const SizedBox(
width: 250,
child: Divider(
color: ColorsManager.dividerColor,
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
InkWell(
onTap: () {
showDialog(
context: context,
builder: (context) {
return DisconnectDeviceDialog(
cancelTab: () {
Navigator.of(context)
.pop();
},
confirmTab: () {
Navigator.of(context)
.pop();
},
);
},
);
},
child: const BodyMedium(
text: 'Disconnect Device',
fontWeight: FontWeight.w400,
fontSize: 15,
fontColor: ColorsManager
.textPrimaryColor,
),
),
const Icon(
Icons.keyboard_arrow_right,
color: ColorsManager.textGray,
)
],
),
const SizedBox(height: 20),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
InkWell(
onTap: () {
showDialog(
context: context,
builder: (context) {
return DisconnectWipeData(
cancelTab: () {
Navigator.of(context)
.pop();
},
confirmTab: () {
_bloc.add(
DeleteDeviceEvent());
},
);
},
);
},
child: const BodyMedium(
text:
'Disconnect Device and Wipe Data',
fontWeight: FontWeight.w400,
fontSize: 15,
fontColor: ColorsManager
.textPrimaryColor,
),
),
const Icon(
Icons.keyboard_arrow_right,
color: ColorsManager.textGray,
)
],
),
],
),
);
},
);
},
child: const Center(
child: BodyMedium(
text: 'Remove Device',
fontWeight: FontWeight.w400,
fontSize: 15,
fontColor: ColorsManager.red,
),
),
),
],
);
},
),
),
),
);

View File

@ -46,7 +46,7 @@ class SixSceneScreen extends StatelessWidget {
model = state.device;
}
return DefaultScaffold(
title: device?.name ?? '6 Scene Switch',
title: bloc.deviceInfo.name ,
actions: [
InkWell(
onTap: () async {
@ -55,7 +55,7 @@ class SixSceneScreen extends StatelessWidget {
builder: (context) => SettingsPage(device: device!),
),
);
if (val == null) {
if (val == null || val == true) {
bloc.add(const SixSceneInitialInfo());
bloc.add(const SixSceneInitial());
bloc.add(const SexSceneSwitchInitial());
@ -195,8 +195,8 @@ class SixSceneScreen extends StatelessWidget {
);
if (value == true) {
Future.delayed(
const Duration(milliseconds: 200),
() {
const Duration(
milliseconds: 200), () {
bloc.add(
const SexSceneSwitchInitial());
});

View File

@ -95,17 +95,15 @@ class FlushPresenceRecords extends StatelessWidget {
SizedBox(
child: ListTile(
leading: Icon(
record.value == 'true'
record.value == "presence"
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
color: record.value == 'true'
color: record.value == "presence"
? Colors.blue
: Colors.grey,
),
title: Text(
record.value == 'true'
? "Opened"
: "Closed",
record.value.toString(),
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,

View File

@ -9,7 +9,6 @@ import 'package:syncrow_app/features/devices/bloc/flush_sensor_bloc/flush_sensor
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/model/flush_sensor_model.dart';
import 'package:syncrow_app/features/devices/view/device_settings/settings_page.dart';
import 'package:syncrow_app/features/devices/view/widgets/device_appbar.dart';
import 'package:syncrow_app/features/devices/view/widgets/flush_sensor/flush_persence_records.dart';
import 'package:syncrow_app/features/devices/view/widgets/flush_sensor/flush_sensor_option.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
@ -20,7 +19,6 @@ import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/helpers/misc_string_helpers.dart';
import 'package:syncrow_app/utils/helpers/snack_bar.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/constants.dart';
import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
part "presence_indicator.dart";
part "flush_sensor_options_list.dart";
@ -33,7 +31,8 @@ class FlushMountedInterface extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => FlushSensorBloc(deviceId: deviceModel.uuid ?? '')
..add(FlushSensorInitialEvent()),
..add(FlushSensorInitialEvent())
..add(FlushSensorInitialDeviceInfo()),
child: BlocBuilder<FlushSensorBloc, FlushSensorState>(
builder: (context, state) {
final bloc = BlocProvider.of<FlushSensorBloc>(context);
@ -71,8 +70,8 @@ class FlushMountedInterface extends StatelessWidget {
SettingsPage(device: deviceModel),
),
);
if (val == null) {
bloc.add(FlushSensorInitialDeviseInfo());
if (val == null || val == true) {
bloc.add(FlushSensorInitialDeviceInfo());
bloc.add(FlushSensorInitialEvent());
}
},

View File

@ -47,7 +47,7 @@ class FourSceneScreen extends StatelessWidget {
}
return DefaultScaffold(
title: device?.name ?? '4 Scene',
title: _bloc.deviceInfo.name,
actions: [
InkWell(
onTap: () async {
@ -56,7 +56,7 @@ class FourSceneScreen extends StatelessWidget {
builder: (context) => SettingsPage(device: device!),
),
);
if (val == null) {
if (val == null || val == true) {
_bloc.add(const FourSceneInitial());
_bloc.add(const FourSceneInitialInfo());
_bloc.add(const FourSceneSwitchInitial());
@ -164,8 +164,7 @@ class FourSceneScreen extends StatelessWidget {
cancelTab: () {
Navigator.of(context).pop();
},
confirmTab:
(switchSelected) {
confirmTab: (switchSelected) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>

View File

@ -11,6 +11,9 @@ class NameTimeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
DateTime now = DateTime.now();
DateTime cleaned =
DateTime(now.year, now.month, now.day, now.hour, now.minute);
return DefaultContainer(
padding: const EdgeInsets.all(20),
child: Column(
@ -59,19 +62,22 @@ class NameTimeWidget extends StatelessWidget {
width: MediaQuery.of(context).size.width / 3.5,
child: InkWell(
onTap: () {
BlocProvider.of<SmartDoorBloc>(context).add(SelectTimeOnlinePasswordEvent(context: context, isEffective: true));
BlocProvider.of<SmartDoorBloc>(context).add(
SelectTimeOnlinePasswordEvent(
context: context, isEffective: true));
},
child: Text(
BlocProvider.of<SmartDoorBloc>(context).effectiveTime,
style: TextStyle(fontSize: 14,
color: BlocProvider.of<SmartDoorBloc>(context).effectiveTime ==
BlocProvider.of<SmartDoorBloc>(context)
.effectiveTime ==
'Select Time'
? ColorsManager.textGray
: null),
? cleaned.toString()
: BlocProvider.of<SmartDoorBloc>(context)
.effectiveTime,
style: TextStyle(fontSize: 14),
),
)),
],),
],
),
),
const Divider(
color: ColorsManager.graysColor,
@ -96,10 +102,13 @@ class NameTimeWidget extends StatelessWidget {
context: context, isEffective: false));
},
child: Text(
BlocProvider.of<SmartDoorBloc>(context).expirationTime,
BlocProvider.of<SmartDoorBloc>(context)
.expirationTime,
style: TextStyle(
fontSize: 14,
color: BlocProvider.of<SmartDoorBloc>(context).expirationTime == 'Select Time'
color: BlocProvider.of<SmartDoorBloc>(context)
.expirationTime ==
'Select Time'
? ColorsManager.textGray
: null),
),

View File

@ -6,10 +6,9 @@ import 'package:syncrow_app/features/devices/bloc/smart_door_bloc/smart_door_eve
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/model/smart_door_model.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';
class DoorLockButton extends StatefulWidget {
class DoorLockButton extends StatelessWidget {
const DoorLockButton({
super.key,
required this.doorLock,
@ -18,157 +17,79 @@ class DoorLockButton extends StatefulWidget {
final DeviceModel doorLock;
final SmartDoorModel smartDoorModel;
@override
State<DoorLockButton> createState() => _DoorLockButtonState(smartDoorModel: smartDoorModel);
}
class _DoorLockButtonState extends State<DoorLockButton> with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _animation;
SmartDoorModel smartDoorModel;
_DoorLockButtonState({required this.smartDoorModel});
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
value: context.read<SmartDoorBloc>().unlockRequest > 0 ? 1 : 0,
duration: Duration(seconds: context.read<SmartDoorBloc>().unlockRequest),
);
if (context.read<SmartDoorBloc>().unlockRequest > 0) {
_animationController.reverse();
}
_animation = Tween<double>(begin: 0, end: 1).animate(_animationController)
..addListener(() {
setState(() {});
});
}
@override
void didUpdateWidget(DoorLockButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (_animationController.status == AnimationStatus.dismissed) {
if (context.read<SmartDoorBloc>().unlockRequest > 0) {
_animationController.value = 1;
_animationController.duration =
Duration(seconds: context.read<SmartDoorBloc>().unlockRequest);
_animationController.reverse();
}
}
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
double _calculateProgress() {
final value = smartDoorModel.unlockRequest;
if (value <= 0 || value > 30) return 0;
return value / 30.0;
}
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(
right: context.width * 0.25 / 2,
left: context.width * 0.25 / 2,
bottom: context.width * 0.2 / 2,
),
final progress = _calculateProgress();
final isEnabled = smartDoorModel.unlockRequest > 0;
return SizedBox(
width: 255,
height: 255,
child: InkWell(
overlayColor:
WidgetStateProperty.all(ColorsManager.primaryColorWithOpacity.withOpacity(0.1)),
borderRadius: BorderRadius.circular(999),
onTapDown: (details) {
// if (_animationController.status == AnimationStatus.dismissed) {
// _animationController.forward();
// } else if (_animationController.status == AnimationStatus.completed) {
// _animationController.reverse();
// } else if (_animationController.status == AnimationStatus.forward) {
// _animationController.reverse();
// } else if (_animationController.status == AnimationStatus.reverse) {
// _animationController.forward();
// }
if (context.read<SmartDoorBloc>().unlockRequest > 0) {
BlocProvider.of<SmartDoorBloc>(context)
.add(UpdateLockEvent(value: smartDoorModel.normalOpenSwitch));
}
},
onTapUp: (details) {
// if (_animationController.status == AnimationStatus.forward) {
// _animationController.reverse();
// } else if (_animationController.status == AnimationStatus.reverse) {
// _animationController.forward();
// }
},
onTap: isEnabled
? () {
BlocProvider.of<SmartDoorBloc>(context).add(
UpdateLockEvent(value: !smartDoorModel.normalOpenSwitch),
);
}
: null,
child: Container(
width: context.width * 06,
height: context.width * 0.6,
margin: const EdgeInsets.all(10),
decoration: const BoxDecoration(
width: 255,
height: 255,
decoration: BoxDecoration(
color: const Color(0xFFEBECED),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.grey,
color: Colors.grey.withOpacity(0.5),
blurRadius: 18,
// offset: Offset(6, 7),
blurStyle: BlurStyle.outer,
),
],
color: Color(0xFFEBECED),
borderRadius: BorderRadius.all(Radius.circular(999)),
),
child: Padding(
padding: const EdgeInsets.all(25),
child: Stack(
alignment: Alignment.center,
children: [
Container(
margin: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(999),
boxShadow: [
BoxShadow(
color: Colors.white.withOpacity(0.5),
blurRadius: 30,
offset: const Offset(-5, -5),
blurStyle: BlurStyle.outer,
),
BoxShadow(
color: Colors.black.withOpacity(0.14),
blurRadius: 25,
offset: const Offset(5, 5),
blurStyle: BlurStyle.outer,
),
BoxShadow(
color: Colors.black.withOpacity(0.14),
blurRadius: 30,
offset: const Offset(5, 5),
blurStyle: BlurStyle.inner,
),
],
),
child: Center(
child: SvgPicture.asset(
smartDoorModel.normalOpenSwitch
? Assets.doorUnlockIcon
: Assets.assetsIconsDoorlockAssetsLockIcon,
),
child: Stack(
alignment: Alignment.center,
children: [
Container(
margin: const EdgeInsets.all(30),
decoration: const BoxDecoration(
color: Color(0xFFF9F9F9),
shape: BoxShape.circle,
),
child: Center(
child: SvgPicture.asset(
smartDoorModel.normalOpenSwitch
? Assets.doorUnlockIcon
: Assets.assetsIconsDoorlockAssetsLockIcon,
width: 60,
height: 60,
),
),
SizedBox.expand(
),
if (progress > 0)
Container(
decoration: BoxDecoration(shape: BoxShape.circle),
height: 250,
width: 250,
child: CircularProgressIndicator(
value: _animation.value,
strokeWidth: 15,
value: progress,
strokeWidth: 8,
backgroundColor: Colors.transparent,
valueColor: const AlwaysStoppedAnimation<Color>(ColorsManager.primaryColor),
valueColor: const AlwaysStoppedAnimation<Color>(
ColorsManager.primaryColor),
),
)
],
),
),
],
),
),
),
);
}
}
}

View File

@ -18,7 +18,8 @@ import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
class CreateOfflineTimeLimitPasswordPage extends StatelessWidget {
final String? deviceId;
final String? type;
const CreateOfflineTimeLimitPasswordPage({super.key, this.deviceId, this.type});
const CreateOfflineTimeLimitPasswordPage(
{super.key, this.deviceId, this.type});
@override
Widget build(BuildContext context) {
bool isRepeat = false;
@ -28,9 +29,7 @@ class CreateOfflineTimeLimitPasswordPage extends StatelessWidget {
child: BlocConsumer<SmartDoorBloc, SmartDoorState>(
listener: (context, state) {
if (state is FailedState) {
CustomSnackBar.displaySnackBar(
state.errorMessage
);
CustomSnackBar.displaySnackBar(state.errorMessage);
}
if (state is IsRepeatState) {
isRepeat = state.repeat;
@ -39,6 +38,10 @@ class CreateOfflineTimeLimitPasswordPage extends StatelessWidget {
generated = state.generated;
}
}, builder: (context, state) {
DateTime now = DateTime.now();
DateTime cleaned =
DateTime(now.year, now.month, now.day, now.hour, now.minute);
final smartDoorBloc = BlocProvider.of<SmartDoorBloc>(context);
return DefaultScaffold(
appBar: AppBar(
@ -85,46 +88,56 @@ class CreateOfflineTimeLimitPasswordPage extends StatelessWidget {
Flexible(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: smartDoorBloc.passwordController.text.isEmpty ?
List.generate(10, (index) {
return const Padding(
padding: EdgeInsets.symmetric(
horizontal: 4.0,
vertical: 15),
child: Icon(
Icons.circle,
size: 20.0,
color: Colors.black,
),
);
}) : [
Expanded(
child: Row(
children: [
children: smartDoorBloc
.passwordController.text.isEmpty
? List.generate(10, (index) {
return const Padding(
padding: EdgeInsets.symmetric(
horizontal: 4.0,
vertical: 15),
child: Icon(
Icons.circle,
size: 20.0,
color: Colors.black,
),
);
})
: [
Expanded(
child: BodyLarge(
style: const TextStyle(
color: ColorsManager.primaryColor,
fontWeight: FontWeight.bold,
letterSpacing: 8.0,
fontSize: 25,
wordSpacing: 2),
textAlign: TextAlign.center,
text: smartDoorBloc.passwordController.text,
fontSize: 25,
child: Row(
children: [
Expanded(
child: BodyLarge(
style: const TextStyle(
color: ColorsManager
.primaryColor,
fontWeight:
FontWeight.bold,
letterSpacing: 8.0,
fontSize: 25,
wordSpacing: 2),
textAlign:
TextAlign.center,
text: smartDoorBloc
.passwordController
.text,
fontSize: 25,
),
),
IconButton(
onPressed: () async {
await Clipboard.setData(
ClipboardData(
text: smartDoorBloc
.passwordController
.text));
},
icon: const Icon(
Icons.copy)),
],
),
),
IconButton(
onPressed: () async {
await Clipboard.setData(
ClipboardData(text: smartDoorBloc.passwordController.text)
);
},
icon: const Icon(Icons.copy)),
],
),
),
],
)),
const SizedBox(
width: 10,
@ -135,91 +148,142 @@ class CreateOfflineTimeLimitPasswordPage extends StatelessWidget {
padding: const EdgeInsets.all(20),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Container(
padding: const EdgeInsets.all(10.0),
child: const BodyMedium(
text: 'Password Name',
fontWeight: FontWeight.normal,
),
),
),
SizedBox(
width: MediaQuery.of(context).size.width / 2.6,
child: TextFormField(
controller: BlocProvider.of<SmartDoorBloc>(context).passwordNameController,
decoration:
const InputDecoration(
hintText: 'Enter The Name',
hintStyle: TextStyle(
fontSize: 14,
color: ColorsManager.textGray)
),
)),
],
),
Column(
children: [
const Divider(color: ColorsManager.graysColor,),
Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Expanded(
child: BodyMedium(
text: 'Effective Time',
fontWeight: FontWeight.normal,
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Container(
padding:
const EdgeInsets.all(10.0),
child: const BodyMedium(
text: 'Password Name',
fontWeight: FontWeight.normal,
),
),
),
),
SizedBox(
width: MediaQuery.of(context).size.width / 3.5,
child: InkWell(
onTap: () {
BlocProvider.of<SmartDoorBloc>(context).add(SelectTimeEvent(context: context, isEffective: true));
},
child: Text(
BlocProvider.of<SmartDoorBloc>(context).effectiveTime,
style: TextStyle(
fontSize: 14,
color: BlocProvider.of<SmartDoorBloc>(context).effectiveTime ==
'Select Time' ? ColorsManager.textGray : null),
),
)),],
),
),
const Divider(
color: ColorsManager.graysColor,
),
Padding(
padding: const EdgeInsets.all(10.0),
child: Row(mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
const Expanded(
child: BodyMedium(
text: 'Expiration Time',
fontWeight: FontWeight.normal,
),
),
SizedBox(
width: MediaQuery.of(context).size.width / 3.5,
child: InkWell(
onTap: () {
BlocProvider.of<SmartDoorBloc>(context).add(SelectTimeEvent(
context: context,
isEffective: false));
},
child: Text(
BlocProvider.of<SmartDoorBloc>(context).expirationTime,
style: TextStyle(
fontSize: 14,
color: BlocProvider.of<SmartDoorBloc>(context)
.expirationTime == 'Select Time' ? ColorsManager
.textGray : null),
SizedBox(
width: MediaQuery.of(context)
.size
.width /
2.6,
child: TextFormField(
controller: BlocProvider.of<
SmartDoorBloc>(context)
.passwordNameController,
decoration:
const InputDecoration(
hintText:
'Enter The Name',
hintStyle: TextStyle(
fontSize: 14,
color: ColorsManager
.textGray)),
)),
],
),
Column(
children: [
const Divider(
color: ColorsManager.graysColor,
),
Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
const Expanded(
child: BodyMedium(
text: 'Effective Time',
fontWeight:
FontWeight.normal,
),
),
SizedBox(
width:
MediaQuery.of(context)
.size
.width /
3.5,
child: InkWell(
onTap: () {
BlocProvider.of<
SmartDoorBloc>(
context)
.add(
SelectTimeEvent(
context:
context,
isEffective:
true));
},
child: Text(
BlocProvider.of<SmartDoorBloc>(
context)
.effectiveTime ==
'Select Time'
? cleaned.toString()
: BlocProvider.of<
SmartDoorBloc>(
context)
.effectiveTime,
style: TextStyle(
fontSize: 14,
),
),
)),
],
),
),
const Divider(
color: ColorsManager.graysColor,
),
Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
const Expanded(
child: BodyMedium(
text: 'Expiration Time',
fontWeight:
FontWeight.normal,
),
),
SizedBox(
width: MediaQuery.of(context)
.size
.width /
3.5,
child: InkWell(
onTap: () {
BlocProvider.of<
SmartDoorBloc>(
context)
.add(SelectTimeEvent(
context: context,
isEffective:
false));
},
child: Text(
BlocProvider.of<
SmartDoorBloc>(
context)
.expirationTime,
style: TextStyle(
fontSize: 14,
color: BlocProvider.of<
SmartDoorBloc>(
context)
.expirationTime ==
'Select Time'
? ColorsManager
.textGray
: null),
),
),
),
@ -238,7 +302,8 @@ class CreateOfflineTimeLimitPasswordPage extends StatelessWidget {
),
const BodyMedium(
textAlign: TextAlign.center,
text: 'Use the time-limited password at least once within 24 hours after the password takes effect. Otherwise, the password becomes invalid.',
text:
'Use the time-limited password at least once within 24 hours after the password takes effect. Otherwise, the password becomes invalid.',
fontWeight: FontWeight.normal,
fontColor: ColorsManager.grayColor,
),
@ -256,10 +321,13 @@ class CreateOfflineTimeLimitPasswordPage extends StatelessWidget {
backgroundColor: ColorsManager.primaryColor,
onPressed: () async {
if (generated == false) {
smartDoorBloc.add(GenerateAndSavePasswordTimeLimitEvent(context: context));
smartDoorBloc.add(
GenerateAndSavePasswordTimeLimitEvent(
context: context));
} else {
if(smartDoorBloc.passwordNameController.text.isNotEmpty){
smartDoorBloc.add(RenamePasswordEvent());
if (smartDoorBloc
.passwordNameController.text.isNotEmpty) {
smartDoorBloc.add(RenamePasswordEvent());
}
Navigator.of(context).pop(true);
}

View File

@ -29,7 +29,7 @@ class SosScreen extends StatelessWidget {
builder: (context, state) {
final sensor = BlocProvider.of<SosBloc>(context);
return DefaultScaffold(
title: device?.name,
title: sensor.deviceInfo.name,
actions: [
InkWell(
onTap: () async {
@ -38,7 +38,7 @@ class SosScreen extends StatelessWidget {
builder: (context) => SettingsPage(device: device!),
),
);
if (val == null) {
if (val == null || val == true) {
sensor.add(SosInitialDeviseInfo());
sensor.add(const SosInitial());
}
@ -72,33 +72,38 @@ class SosScreen extends StatelessWidget {
Expanded(
flex: 4,
child: InkWell(
overlayColor:
WidgetStateProperty.all(Colors.transparent),
overlayColor: WidgetStateProperty.all(
Colors.transparent),
onTap: () {
// Add functionality for the main SOS button here
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(890),
borderRadius:
BorderRadius.circular(890),
boxShadow: [
BoxShadow(
color: Colors.white.withOpacity(0.1),
color:
Colors.white.withOpacity(0.1),
blurRadius: 24,
offset: const Offset(-5, -5),
blurStyle: BlurStyle.outer,
),
BoxShadow(
color: Colors.black.withOpacity(0.11),
color: Colors.black
.withOpacity(0.11),
blurRadius: 25,
offset: const Offset(5, 5),
blurStyle: BlurStyle.outer,
),
BoxShadow(
color: Colors.black.withOpacity(0.13),
color: Colors.black
.withOpacity(0.13),
blurRadius: 30,
offset: const Offset(5, 5),
blurStyle: BlurStyle.inner,
@ -125,8 +130,9 @@ class SosScreen extends StatelessWidget {
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SosRecordsScreen(
sosId: device!.uuid!),
builder: (context) =>
SosRecordsScreen(
sosId: device!.uuid!),
),
);
},
@ -181,8 +187,8 @@ class SosScreen extends StatelessWidget {
maxHeight: 46,
maxWidth: 50,
),
child: SvgPicture.asset(
Assets.doorNotificationSetting),
child: SvgPicture.asset(Assets
.doorNotificationSetting),
),
const SizedBox(height: 15),
const Flexible(

View File

@ -2,6 +2,7 @@ import 'dart:ui';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/booking_system/presentation/screens/booking_system_page.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/join_home/join_home_view.dart';
@ -44,6 +45,18 @@ class MenuCubit extends Cubit<MenuState> {
}
List<Map<String, Object>> menuSections = [
//Booking System
{
'title': 'Booking System',
'color': const Color(0xFF8AB9FF),
'buttons': [
{
'title': 'Booking',
'Icon': Assets.assetsIconsMenuBookingSystem,
'page': BookingSystemPage()
},
],
},
//Home Management
{
'title': 'Home Management',

View File

@ -4,6 +4,7 @@ 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/view/widgets/menu_list.dart';
import 'package:syncrow_app/features/menu/view/widgets/profile/profile_tab.dart';
import 'package:syncrow_app/features/shared_widgets/build_number_environment.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/utils/context_extension.dart';
@ -51,6 +52,7 @@ class MenuView extends StatelessWidget {
],
),
),
buildNumberAndEnvironmentLabel()
],
),
);

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/app_layout/model/community_model.dart';
import 'package:syncrow_app/features/menu/view/widgets/manage_home/home_settings.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
@ -15,29 +16,37 @@ class ManageHomeView extends StatelessWidget {
Widget build(BuildContext context) {
var spaces = HomeCubit.getInstance().spaces;
return DefaultScaffold(
title: 'Manage Your Home',
height: MediaQuery.sizeOf(context).height,
title: 'Manage Your Home',
child: Align(
alignment: Alignment.topCenter,
child: spaces.isEmpty
? const Center(
child: BodyMedium(text: 'No spaces found'),
)
? const Center(child: BodyMedium(text: 'No spaces found'))
: DefaultContainer(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 25),
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 25),
child: ListView.builder(
itemCount: spaces.length,
itemBuilder: (context, index) {
if (index == spaces.length - 1) {
return InkWell(
onTap: () {
Navigator.of(context).push(CustomPageRoute(
builder: (context) => HomeSettingsView(
space: spaces[index],
)));
},
child: Row(
shrinkWrap: true,
itemCount: spaces.length,
itemBuilder: (context, index) {
final space = spaces[index];
return InkWell(
onTap: () {
Navigator.of(context).push(
CustomPageRoute(
builder: (context) =>
HomeSettingsView(space: space),
),
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BodyMedium(text: StringHelpers.toTitleCase(spaces[index].name)),
BodyMedium(
text: StringHelpers.toTitleCase(space.name)),
const Icon(
Icons.arrow_forward_ios,
color: ColorsManager.greyColor,
@ -45,45 +54,19 @@ class ManageHomeView extends StatelessWidget {
)
],
),
);
}
return InkWell(
onTap: () {
//TODO refactor the routing to use named routes
// Navigator.of(context).pushNamed(
// '/home_settings',
// arguments: spaces[index],
// );
Navigator.of(context).push(CustomPageRoute(
builder: (context) => HomeSettingsView(
space: spaces[index],
)));
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BodyMedium(text: HomeCubit.getInstance().spaces[index].name),
const Icon(
Icons.arrow_forward_ios,
color: ColorsManager.greyColor,
size: 15,
)
],
),
if (index != spaces.length - 1)
Container(
margin: const EdgeInsets.symmetric(vertical: 15),
height: 1,
color: ColorsManager.greyColor,
),
],
),
);
}),
));
],
),
);
},
),
),
),
);
}
}

View File

@ -4,6 +4,7 @@ enum OperationDialogType {
temperature,
onOff,
integerSteps,
counterSteps,
listOfOptions,
none,
}

View File

@ -116,7 +116,7 @@ class FlushFunctionsHelper {
operationName: 'Min Detection Distance',
code: 'near_detection',
functionValue: functionValue,
operationDialogType: OperationDialogType.integerSteps,
operationDialogType: OperationDialogType.counterSteps,
operationalValues: [
SceneOperationalValue(
icon: '',
@ -136,10 +136,10 @@ class FlushFunctionsHelper {
operationName: 'Max Detection Distance',
code: 'far_detection',
functionValue: functionValue,
operationDialogType: OperationDialogType.integerSteps,
operationDialogType: OperationDialogType.counterSteps,
operationalValues: [
SceneOperationalValue(
icon: '',
icon: Assets.assetsCelsiusDegrees,
value: 0.0,
description: "m",
minValue: 0.0,
@ -156,23 +156,17 @@ class FlushFunctionsHelper {
operationName: 'Trigger Level',
code: 'sensi_reduce',
functionValue: functionValue,
operationDialogType: OperationDialogType.listOfOptions,
operationDialogType: OperationDialogType.counterSteps,
operationalValues: [
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
icon: Assets.assetsCelsiusDegrees,
value: 1,
description: 1.toString(),
),
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
value: 2,
description: 2.toString(),
),
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
value: 3,
description: 3.toString(),
description: "",
minValue: 1,
maxValue: 3,
stepValue: 1,
),
],
),
SceneStaticFunction(
@ -183,23 +177,17 @@ class FlushFunctionsHelper {
operationName: 'Indent Level',
code: 'occur_dist_reduce',
functionValue: functionValue,
operationDialogType: OperationDialogType.listOfOptions,
operationDialogType: OperationDialogType.counterSteps,
operationalValues: [
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
icon: Assets.assetsCelsiusDegrees,
value: 1,
description: 1.toString(),
),
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
value: 2,
description: 2.toString(),
),
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
value: 3,
description: 3.toString(),
description: "",
minValue: 1,
maxValue: 3,
stepValue: 1,
),
],
),
SceneStaticFunction(
@ -210,7 +198,7 @@ class FlushFunctionsHelper {
operationName: 'Target Confirm Time',
code: 'presence_delay',
functionValue: functionValue,
operationDialogType: OperationDialogType.integerSteps,
operationDialogType: OperationDialogType.counterSteps,
operationalValues: [
SceneOperationalValue(
icon: '',
@ -235,7 +223,7 @@ class FlushFunctionsHelper {
SceneOperationalValue(
icon: '',
value: 20.0,
description: "",
description: "sec",
minValue: 20.0,
maxValue: 300.0,
stepValue: 1.0,

View File

@ -8,6 +8,7 @@ import 'package:syncrow_app/features/scene/model/create_automation_model.dart';
import 'package:syncrow_app/features/scene/model/create_scene_model.dart';
import 'package:syncrow_app/features/scene/model/scene_static_function.dart';
import 'package:syncrow_app/features/scene/widgets/alert_dialogs/alert_dialog_countdown.dart';
import 'package:syncrow_app/features/scene/widgets/alert_dialogs/alert_dialog_counter.dart';
import 'package:syncrow_app/features/scene/widgets/alert_dialogs/alert_dialog_functions_body.dart';
import 'package:syncrow_app/features/scene/widgets/alert_dialogs/alert_dialog_slider_steps.dart';
import 'package:syncrow_app/features/scene/widgets/alert_dialogs/alert_dialog_temperature_body.dart';
@ -132,7 +133,7 @@ mixin SceneLogicHelper {
'presence_delay' ||
action.executorProperty!.functionCode == 'none_delay') {
action.executorProperty!.functionValue =
action.executorProperty!.functionValue * 10;
(action.executorProperty!.functionValue * 10).round();
}
}
}
@ -193,12 +194,12 @@ mixin SceneLogicHelper {
if (action.executorProperty!.functionCode == 'near_detection' ||
action.executorProperty!.functionCode == 'far_detection') {
action.executorProperty!.functionValue =
action.executorProperty!.functionValue * 100;
(action.executorProperty!.functionValue * 100).round();
} else if (action.executorProperty!.functionCode ==
'presence_delay' ||
action.executorProperty!.functionCode == 'none_delay') {
action.executorProperty!.functionValue =
action.executorProperty!.functionValue * 10;
(action.executorProperty!.functionValue * 10).round();
}
}
}
@ -234,6 +235,13 @@ mixin SceneLogicHelper {
functionValue: functionValue ?? taskItem.functionValue,
isAutomation: isAutomation,
);
} else if (taskItem.operationDialogType ==
OperationDialogType.counterSteps) {
return AlertDialogCounterSteps(
taskItem: taskItem,
functionValue: functionValue ?? taskItem.functionValue,
isAutomation: isAutomation,
);
}
return AlertDialogFunctionsOperationsBody(

View File

@ -1748,7 +1748,7 @@ mixin SceneOperationsDataHelper {
action.deviceName,
Assets.flushIcon,
'Min Detection distance',
OperationDialogType.integerSteps,
OperationDialogType.counterSteps,
_createMinDetection(),
isAutomation,
comparator,
@ -1763,7 +1763,7 @@ mixin SceneOperationsDataHelper {
action.deviceName,
Assets.flushIcon,
'Max Detection distance',
OperationDialogType.integerSteps,
OperationDialogType.counterSteps,
_createFarDetection(),
isAutomation,
comparator,
@ -1778,7 +1778,7 @@ mixin SceneOperationsDataHelper {
action.deviceName,
Assets.flushIcon,
"Trigger Level",
OperationDialogType.listOfOptions,
OperationDialogType.counterSteps,
_createTriggerLevelFunction(),
isAutomation,
comparator,
@ -1789,19 +1789,12 @@ mixin SceneOperationsDataHelper {
List<SceneOperationalValue> _createTriggerLevelFunction() {
return [
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
description: "1",
value: 1,
),
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
description: "2",
value: 2,
),
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
description: "3",
value: 3,
icon: Assets.assetsCelsiusDegrees,
value: 0,
description: '',
minValue: 0,
maxValue: 3,
stepValue: 1,
),
];
}
@ -1813,7 +1806,7 @@ mixin SceneOperationsDataHelper {
action.deviceName,
Assets.flushIcon,
'Indent Level',
OperationDialogType.listOfOptions,
OperationDialogType.counterSteps,
_createIndentLevelFunction(),
isAutomation,
comparator,
@ -1824,19 +1817,12 @@ mixin SceneOperationsDataHelper {
List<SceneOperationalValue> _createIndentLevelFunction() {
return [
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
description: "1",
value: 1,
),
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
description: "2",
value: 2,
),
SceneOperationalValue(
icon: Assets.assetsSensitivityOperationIcon,
description: "3",
value: 3,
icon: Assets.assetsCelsiusDegrees,
value: 0,
description: '',
minValue: 0,
maxValue: 3,
stepValue: 1,
),
];
}
@ -1848,7 +1834,7 @@ mixin SceneOperationsDataHelper {
action.deviceName,
Assets.flushIcon,
'Target Confirm Time',
OperationDialogType.integerSteps,
OperationDialogType.counterSteps,
_targetConfirmTimeFun(),
isAutomation,
comparator,
@ -1876,7 +1862,7 @@ mixin SceneOperationsDataHelper {
SceneOperationalValue(
icon: Assets.flushIcon,
value: 0.0,
description: '',
description: 'sec',
minValue: 20,
maxValue: 300,
stepValue: 1,
@ -1892,7 +1878,7 @@ mixin SceneOperationsDataHelper {
value: 0.0,
minValue: 0.0,
maxValue: 9.5,
stepValue: 1,
stepValue: 0.10,
),
];
}
@ -1905,7 +1891,7 @@ mixin SceneOperationsDataHelper {
value: 0.0,
minValue: 0.0,
maxValue: 9.5,
stepValue: 1,
stepValue: 0.10,
),
];
}
@ -1915,7 +1901,7 @@ mixin SceneOperationsDataHelper {
SceneOperationalValue(
icon: Assets.assetsCelsiusDegrees,
value: 0.0,
description: '',
description: 'sec',
minValue: 0.0,
maxValue: 0.5,
stepValue: 0.1,

View File

@ -90,44 +90,25 @@ class Action {
String toRawJson() => json.encode(toJson());
static Action? fromJson(Map<String, dynamic> json) {
// Safely extract required fields with null checks
final String? actionExecutor = json["actionExecutor"] as String?;
final String? entityId = json["entityId"] as String?;
final String? productType = json['productType'] as String?;
final String? deviceName = json['deviceName'] as String?;
// Skip invalid actions with missing required fields
if (actionExecutor == null ||
entityId == null ||
productType == null ||
deviceName == null) {
if (actionExecutor == null || entityId == null) {
return null;
}
// Handle actions with 'name' and 'type'
if (json['name'] != null && json['type'] != null) {
return Action(
actionExecutor: actionExecutor,
entityId: entityId,
name: json['name'] as String?,
type: json['type'] as String?,
productType: productType,
deviceName: deviceName,
);
}
// Handle actions with 'executorProperty'
if (json["executorProperty"] != null) {
return Action(
actionExecutor: actionExecutor,
entityId: entityId,
executorProperty: ExecutorProperty.fromJson(json["executorProperty"]),
productType: productType,
deviceName: deviceName,
);
}
return null; // Skip invalid actions
return Action(
actionExecutor: actionExecutor,
entityId: entityId,
executorProperty: json["executorProperty"] != null
? ExecutorProperty.fromJson(json["executorProperty"])
: null,
name: json['name'] as String?,
type: json['type'] as String?,
productType: json['productType'] as String? ?? 'unknown',
deviceName: json['deviceName'] as String? ?? 'Delay Action',
);
}
Map<String, dynamic> toJson() => {

View File

@ -27,12 +27,15 @@ class RoutinesView extends StatelessWidget {
builder: (context, state) {
final selectedSpace = HomeCubit.getInstance().selectedSpace;
if (state is DeleteSceneSuccess) {
if (state.success) _loadScenesAndAutomations(context, selectedSpace);
if (state.success)
_loadScenesAndAutomations(context, selectedSpace);
}
if (state is CreateSceneWithTasks) {
if (state.success) {
_loadScenesAndAutomations(context, selectedSpace);
context.read<SmartSceneSelectBloc>().add(const SmartSceneClearEvent());
context
.read<SmartSceneSelectBloc>()
.add(const SmartSceneClearEvent());
}
}
return BlocListener<SceneBloc, SceneState>(
@ -72,30 +75,28 @@ class RoutinesView extends StatelessWidget {
data: Theme.of(context).copyWith(
dividerColor: Colors.transparent,
),
child: Expanded(
child: ListView(
children: [
RoutinesExpansionTile(
title: 'Tap to run routines',
emptyRoutinesMessage:
'No scenes have been added yet',
routines: state.scenes,
loadingStates: state.loadingStates,
loadingSceneId: state.loadingSceneId,
disablePlayButton: false,
),
RoutinesExpansionTile(
title: 'Automation',
emptyRoutinesMessage:
'No automations have been added yet',
routines: state.automationList,
loadingStates: state.loadingStates,
loadingSceneId: state.loadingSceneId,
disablePlayButton: true,
),
const SizedBox(height: 15),
],
),
child: ListView(
children: [
RoutinesExpansionTile(
title: 'Tap to run automations',
emptyRoutinesMessage:
'No scenes have been added yet',
routines: state.scenes,
loadingStates: state.loadingStates,
loadingSceneId: state.loadingSceneId,
disablePlayButton: false,
),
RoutinesExpansionTile(
title: 'Automation',
emptyRoutinesMessage:
'No automations have been added yet',
routines: state.automationList,
loadingStates: state.loadingStates,
loadingSceneId: state.loadingSceneId,
disablePlayButton: true,
),
const SizedBox(height: 15),
],
),
);
}
@ -111,7 +112,8 @@ class RoutinesView extends StatelessWidget {
);
}
void _loadScenesAndAutomations(BuildContext context, SpaceModel? selectedSpace) {
void _loadScenesAndAutomations(
BuildContext context, SpaceModel? selectedSpace) {
context.read<SceneBloc>()
..add(LoadScenes(selectedSpace!.id, selectedSpace, showInDevice: false))
..add(LoadAutomation(selectedSpace.id, selectedSpace.community.uuid));

View File

@ -100,7 +100,7 @@ class SceneView extends StatelessWidget {
initiallyExpanded: true,
iconColor: ColorsManager.grayColor,
title: const BodyMedium(
text: 'Tap to run routines',
text: 'Tap to run automations',
),
children: [
if (scenes.isNotEmpty)

View File

@ -0,0 +1,243 @@
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/model/scene_static_function.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart';
import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
class AlertDialogCounterSteps extends StatefulWidget {
const AlertDialogCounterSteps({
super.key,
this.functionValue,
required this.taskItem,
required this.isAutomation,
});
final dynamic functionValue;
final SceneStaticFunction taskItem;
final bool isAutomation;
@override
State<AlertDialogCounterSteps> createState() =>
_AlertDialogCounterStepsState();
}
class _AlertDialogCounterStepsState extends State<AlertDialogCounterSteps> {
double? groupValue;
int selectedToggleIndex = 1;
@override
void didChangeDependencies() {
super.didChangeDependencies();
final createSceneBloc = context.read<CreateSceneBloc>();
if (widget.taskItem.comparator != null) {
selectedToggleIndex = _comparatorToIndex(widget.taskItem.comparator);
}
if (widget.isAutomation) {
final automationTempTaskList = createSceneBloc.automationTempTasksList;
final automationComparatorValues =
createSceneBloc.automationComparatorValues;
for (var element in automationTempTaskList) {
if (element.code == widget.taskItem.code) {
groupValue = element.functionValue;
selectedToggleIndex =
_comparatorToIndex(automationComparatorValues[element.code]);
}
}
}
if (widget.taskItem.code == 'temp_current') {
groupValue = widget.functionValue != null
? _normalizeValue(
double.tryParse(widget.functionValue.toString()) ??
widget.taskItem.operationalValues[0].minValue,
)
: widget.taskItem.operationalValues[0].minValue;
} else {
groupValue = widget.functionValue != null
? _normalizeValue(widget.functionValue)
: _normalizeValue(widget.taskItem.operationalValues[0].minValue);
}
setState(() {});
context.read<CreateSceneBloc>().add(
SelectedValueEvent(
value: _deNormalizeValue(groupValue),
code: widget.taskItem.code,
isAutomation: widget.isAutomation,
comparator: _indexToComparator(selectedToggleIndex),
),
);
}
int _comparatorToIndex(String? comparator) {
switch (comparator) {
case "<":
return 0;
case "==":
return 1;
case ">":
return 2;
default:
return 1;
}
}
String _indexToComparator(int index) {
switch (index) {
case 0:
return "<";
case 1:
return "==";
case 2:
return ">";
default:
return "==";
}
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.isAutomation)
ToggleButtons(
isSelected: [
selectedToggleIndex == 0,
selectedToggleIndex == 1,
selectedToggleIndex == 2,
],
onPressed: (index) {
setState(() {
selectedToggleIndex = index;
});
context.read<CreateSceneBloc>().add(
SelectedValueEvent(
value: _deNormalizeValue(groupValue),
code: widget.taskItem.code,
isAutomation: widget.isAutomation,
comparator: _indexToComparator(selectedToggleIndex),
),
);
},
borderRadius: BorderRadius.circular(30),
selectedColor: Colors.white,
color: ColorsManager.blackColor,
fillColor: ColorsManager.primaryColorWithOpacity,
borderColor: ColorsManager.greyColor,
constraints: BoxConstraints.tight(Size(70, 30)),
children: const [
SizedBox(width: 70, height: 30, child: Center(child: Text("<"))),
SizedBox(width: 70, height: 30, child: Center(child: Text("="))),
SizedBox(width: 70, height: 30, child: Center(child: Text(">"))),
],
),
const SizedBox(height: 12),
...widget.taskItem.operationalValues.map(
(operation) => BlocBuilder<CreateSceneBloc, CreateSceneState>(
builder: (context, state) {
final step = operation.stepValue?.toDouble() ?? 1.0;
final min = operation.minValue?.toDouble() ?? 0.0;
final max = operation.maxValue?.toDouble() ?? 100.0;
return Column(
children: [
const SizedBox(height: 12),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: const Icon(Icons.remove),
onPressed: () {
setState(() {
groupValue =
((groupValue ?? min) - step).clamp(min, max);
});
context.read<CreateSceneBloc>().add(
SelectedValueEvent(
value: _deNormalizeValue(groupValue),
code: widget.taskItem.code,
isAutomation: widget.isAutomation,
comparator:
_indexToComparator(selectedToggleIndex),
),
);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TitleMedium(
text: groupValue != null
? (groupValue! % 1 == 0
? groupValue!.toStringAsFixed(0)
: groupValue!.toStringAsFixed(1))
: "0",
style: context.titleMedium.copyWith(
color: ColorsManager.primaryColorWithOpacity,
fontSize: 30,
),
),
const SizedBox(width: 8),
TitleMedium(
text: operation.description.toString(),
style: context.titleMedium.copyWith(
color: ColorsManager.primaryColorWithOpacity,
fontSize: 30,
),
),
],
),
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
setState(() {
groupValue =
((groupValue ?? 0) + step).clamp(min, max);
});
context.read<CreateSceneBloc>().add(
SelectedValueEvent(
value: _deNormalizeValue(groupValue),
code: widget.taskItem.code,
isAutomation: widget.isAutomation,
comparator:
_indexToComparator(selectedToggleIndex),
),
);
},
),
],
),
const SizedBox(height: 12),
],
);
},
),
),
],
);
}
double _normalizeValue(dynamic value) {
if ((widget.taskItem.code == "temp_set" && value > 199) ||
(widget.taskItem.code == "temp_current" && value >= -99.0)) {
return (value) / 10;
}
return value.toDouble();
}
double _deNormalizeValue(double? value) {
if (widget.taskItem.code == "temp_set" ||
widget.taskItem.code == "temp_current") {
return (value ?? 0) * 10;
}
return value ?? 0;
}
}

View File

@ -23,7 +23,7 @@ class DeleteRoutineDialog extends StatelessWidget {
height: 10,
),
const BodyLarge(
text: 'Delete Routine',
text: 'Delete Automation',
fontWeight: FontWeight.w700,
fontColor: ColorsManager.red,
fontSize: 16,
@ -39,7 +39,7 @@ class DeleteRoutineDialog extends StatelessWidget {
child: Column(
children: [
Center(child: const Text('Are you sure you want to ')),
Center(child: const Text('delete the routine?'))
Center(child: const Text('delete the automation?'))
],
),
),

View File

@ -52,7 +52,7 @@ class DeleteRoutineButton extends StatelessWidget {
},
child: const Center(
child: Text(
'Remove Routine',
'Remove Automation',
style: TextStyle(color: ColorsManager.red),
))
// : SceneListTile(

View File

@ -9,7 +9,7 @@ class EmptyDevicesWidget extends StatelessWidget {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 48),
child: Text(
"No routines.\nEnable 'Show on Home Screen' to add routines",
"No automations.\nEnable 'Show on Home Screen' to add automations",
textAlign: TextAlign.center,
style: TextStyle(
color: ColorsManager.grayColor,

View File

@ -23,7 +23,7 @@ class EmptyRoutinesWidget extends StatelessWidget {
),
),
BodyMedium(
text: 'No Routines yet',
text: 'No automations yet',
fontColor: ColorsManager.textGray,
),
],

View File

@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:syncrow_app/main.dart';
Widget buildNumberAndEnvironmentLabel() {
String envNAME = dotenv.env['ENV_NAME'] ?? '';
return SizedBox(
child: Center(
child: Text(
'\n$envNAME \nBuild Number $buildNumber',
textAlign: TextAlign.center,
),
),
);
}

View File

@ -495,6 +495,7 @@ class Assets {
/// assets/icons/MenuIcons/HomeManagementIcons/CreateHome.svg
static const String assetsIconsMenuIconsHomeManagementIconsCreateHome =
"assets/icons/MenuIcons/HomeManagementIcons/CreateHome.svg";
static const String assetsIconsMenuBookingSystem = 'assets/icons/Booking.svg';
/// Assets for assetsIconsMenuIconsHomeManagementIconsJoinAHome
/// assets/icons/MenuIcons/HomeManagementIcons/joinAHome.svg

View File

@ -9,6 +9,9 @@ import 'package:syncrow_app/utils/bloc_observer.dart';
import 'package:syncrow_app/utils/helpers/localization_helpers.dart';
import 'my_app.dart';
const String buildNumber = '1.0.30+18';
void main() {
//to observe the state of the blocs in the output console
Bloc.observer = MyBlocObserver();

View File

@ -234,4 +234,7 @@ abstract class ApiEndpoints {
'/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/devices';
static const String getReportLogs = '/devices/{uuid}/report-logs?code={code}';
//booking System APIs
static const String upcomingBookings = '/bookings/{uuid}/';
}

View File

@ -579,7 +579,7 @@ class DevicesAPI {
.replaceAll('{code}', code),
showServerMessage: false,
expectedResponseModel: (json) {
return DeviceReport.fromJson(json);
return DeviceReport.fromJson(json['data'] as Map<String, dynamic>);
},
);
return response;

View File

@ -0,0 +1,4 @@
import 'package:flutter/material.dart';
double deviceHeight(BuildContext context) => MediaQuery.sizeOf(context).height;
double deviceWidth(BuildContext context) => MediaQuery.sizeOf(context).width;

View File

@ -4,8 +4,8 @@ class StringsManager {
static const String dashboard = 'Dashboard';
static const String devices = 'Devices';
static const String routine = 'Routines';
static const String tapToRunRoutine = 'Tap to run routine';
static const String routine = 'Automation';
static const String tapToRunRoutine = 'Tap to run automation';
static const String wizard = 'Wizard';
static const String active = 'Active';
static const String current = 'Current';
@ -39,6 +39,6 @@ class StringsManager {
'Example: when an unusual activity is detected.';
static const String functions = "Functions";
static const String firstLaunch = "firstLaunch";
static const String deleteScene = 'Remove Routine';
static const String deleteScene = 'Remove Automation';
static const String deleteAutomation = 'Delete Automation';
}

View File

@ -207,7 +207,7 @@ abstract class ThemeManager {
),
///card theme
cardTheme: const CardTheme(
cardTheme: const CardThemeData(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),
@ -372,7 +372,7 @@ abstract class ThemeManager {
///card theme
//TODO edit card theme
cardTheme: const CardTheme(
cardTheme: const CardThemeData(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),

View File

@ -5,7 +5,7 @@ description: This is the mobile application project, developed with Flutter for
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: "none" # Remove this line if you wish to publish to pub.dev
version: 1.0.30+16
version: 1.0.30+18
environment:
sdk: ">=3.0.6 <4.0.0"