Compare commits

...

40 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
60 changed files with 2245 additions and 1075 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,6 +68,7 @@ class UserModel {
role: json['role'] != null ? Role.fromJson(json['role']) : null,
project:
json['project'] != null ? Project.fromJson(json['project']) : null,
points: json['points'],
);
}
@ -93,7 +95,9 @@ class UserModel {
? DateTime.parse(tempJson['appAgreementAcceptedAt'])
: null,
role: tempJson['role'] != null ? Role.fromJson(tempJson['role']) : null,
project: 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

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

@ -150,7 +150,6 @@ class SmartDoorBloc extends Bloc<SmartDoorEvent, SmartDoorState> {
return super.close();
}
_doorLockUpdated(DoorLockUpdated event, Emitter<SmartDoorState> emit) {
unlockRequest = deviceStatus.unlockRequest;
emit(UpdateState(smartDoorModel: deviceStatus));
@ -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 (_) {
_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;
}
} catch (_) {}
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,7 +22,15 @@ class SettingProfilePage extends StatelessWidget {
Widget build(BuildContext context) {
var spaces = HomeCubit.getInstance().spaces;
return DefaultScaffold(
return PopScope(
canPop: false,
onPopInvoked: (didPop) {
if (didPop) {
return;
}
Navigator.of(context).pop(true);
},
child: DefaultScaffold(
title: 'Device Settings',
leading: IconButton(
onPressed: () {
@ -86,15 +94,18 @@ class SettingProfilePage extends StatelessWidget {
const SizedBox(width: 5),
InkWell(
onTap: () {
_bloc.add(const ChangeNameEvent(value: true));
_bloc.add(
const ChangeNameEvent(value: true));
},
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
padding:
EdgeInsets.symmetric(horizontal: 10),
child: SvgPicture.asset(
Assets.sosEditProfile,
color: Colors.grey,
fit: BoxFit.contain,
height: MediaQuery.of(context).size.height *
height:
MediaQuery.of(context).size.height *
0.02,
),
),
@ -117,7 +128,8 @@ class SettingProfilePage extends StatelessWidget {
if (HomeCubit.visitorPasswordManagement) {
bool? val = await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LocationSettingPage(
builder: (context) =>
LocationSettingPage(
space: spaces!.first,
deviceId: device?.uuid ?? '',
)),
@ -128,7 +140,8 @@ class SettingProfilePage extends StatelessWidget {
}
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
const SizedBox(
child: Text('Location'),
@ -159,6 +172,7 @@ class SettingProfilePage extends StatelessWidget {
},
),
),
),
);
}

View File

@ -28,7 +28,15 @@ class SettingsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DefaultScaffold(
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 ?? '')
@ -40,8 +48,10 @@ class SettingsPage extends StatelessWidget {
return state is DeviceSettingLoadingState
? const Center(
child:
DefaultContainer(width: 50, height: 50, child: CircularProgressIndicator()),
child: DefaultContainer(
width: 50,
height: 50,
child: CircularProgressIndicator()),
)
: ListView(
children: [
@ -65,29 +75,36 @@ class SettingsPage extends StatelessWidget {
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 20),
DefaultContainer(
borderRadius: const BorderRadius.all(Radius.circular(30)),
borderRadius: const BorderRadius.all(
Radius.circular(30)),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Padding(
padding: const EdgeInsets.only(left: 90),
padding:
const EdgeInsets.only(left: 90),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
SizedBox(
child: Text(
_bloc.deviceInfo.name,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: ColorsManager.grayColor,
fontWeight:
FontWeight.w700,
color: ColorsManager
.grayColor,
),
overflow: TextOverflow
.ellipsis, // Adds ellipsis (...) when the text overflows.
@ -98,7 +115,10 @@ class SettingsPage extends StatelessWidget {
height: 5,
),
BodySmall(
text: _bloc.deviceInfo.subspace.subspaceName),
text: _bloc
.deviceInfo
.subspace
.subspaceName),
],
),
),
@ -125,10 +145,13 @@ class SettingsPage extends StatelessWidget {
backgroundColor: Colors.white,
child: CircleAvatar(
radius: 40,
backgroundColor: ColorsManager.backgroundColor,
backgroundColor:
ColorsManager.backgroundColor,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.center,
mainAxisAlignment:
MainAxisAlignment.center,
children: [
device!.type == 'NCPS'
? SizedBox(
@ -138,26 +161,36 @@ class SettingsPage extends StatelessWidget {
Assets.flushIcon,
fit: BoxFit.contain,
),
):
device!.type == "SOS"
)
: device!.type == "SOS"
? ClipOval(
child: SvgPicture.asset(
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),
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,
child: SvgPicture
.asset(
device!.type ==
"4S"
? Assets
.fourSceneIcon
: Assets
.sixSceneIcon,
fit: BoxFit
.contain,
),
),
),
@ -346,7 +379,8 @@ class SettingsPage extends StatelessWidget {
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
InkWell(
onTap: () {
@ -355,10 +389,12 @@ class SettingsPage extends StatelessWidget {
builder: (context) {
return DisconnectDeviceDialog(
cancelTab: () {
Navigator.of(context).pop();
Navigator.of(context)
.pop();
},
confirmTab: () {
Navigator.of(context).pop();
Navigator.of(context)
.pop();
},
);
},
@ -368,7 +404,8 @@ class SettingsPage extends StatelessWidget {
text: 'Disconnect Device',
fontWeight: FontWeight.w400,
fontSize: 15,
fontColor: ColorsManager.textPrimaryColor,
fontColor: ColorsManager
.textPrimaryColor,
),
),
const Icon(
@ -379,7 +416,8 @@ class SettingsPage extends StatelessWidget {
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
InkWell(
onTap: () {
@ -388,20 +426,24 @@ class SettingsPage extends StatelessWidget {
builder: (context) {
return DisconnectWipeData(
cancelTab: () {
Navigator.of(context).pop();
Navigator.of(context)
.pop();
},
confirmTab: () {
_bloc.add(DeleteDeviceEvent());
_bloc.add(
DeleteDeviceEvent());
},
);
},
);
},
child: const BodyMedium(
text: 'Disconnect Device and Wipe Data',
text:
'Disconnect Device and Wipe Data',
fontWeight: FontWeight.w400,
fontSize: 15,
fontColor: ColorsManager.textPrimaryColor,
fontColor: ColorsManager
.textPrimaryColor,
),
),
const Icon(
@ -430,6 +472,7 @@ class SettingsPage extends StatelessWidget {
},
),
),
),
);
}
}

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

@ -70,7 +70,7 @@ class FlushMountedInterface extends StatelessWidget {
SettingsPage(device: deviceModel),
),
);
if (val == null) {
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));
onTap: isEnabled
? () {
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();
// }
},
: 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,
),
],
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,8 +88,9 @@ class CreateOfflineTimeLimitPasswordPage extends StatelessWidget {
Flexible(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: smartDoorBloc.passwordController.text.isEmpty ?
List.generate(10, (index) {
children: smartDoorBloc
.passwordController.text.isEmpty
? List.generate(10, (index) {
return const Padding(
padding: EdgeInsets.symmetric(
horizontal: 4.0,
@ -97,30 +101,39 @@ class CreateOfflineTimeLimitPasswordPage extends StatelessWidget {
color: Colors.black,
),
);
}) : [
})
: [
Expanded(
child: Row(
children: [
Expanded(
child: BodyLarge(
style: const TextStyle(
color: ColorsManager.primaryColor,
fontWeight: FontWeight.bold,
color: ColorsManager
.primaryColor,
fontWeight:
FontWeight.bold,
letterSpacing: 8.0,
fontSize: 25,
wordSpacing: 2),
textAlign: TextAlign.center,
text: smartDoorBloc.passwordController.text,
textAlign:
TextAlign.center,
text: smartDoorBloc
.passwordController
.text,
fontSize: 25,
),
),
IconButton(
onPressed: () async {
await Clipboard.setData(
ClipboardData(text: smartDoorBloc.passwordController.text)
);
ClipboardData(
text: smartDoorBloc
.passwordController
.text));
},
icon: const Icon(Icons.copy)),
icon: const Icon(
Icons.copy)),
],
),
),
@ -136,11 +149,13 @@ class CreateOfflineTimeLimitPasswordPage extends StatelessWidget {
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Container(
padding: const EdgeInsets.all(10.0),
padding:
const EdgeInsets.all(10.0),
child: const BodyMedium(
text: 'Password Name',
fontWeight: FontWeight.normal,
@ -148,47 +163,78 @@ class CreateOfflineTimeLimitPasswordPage extends StatelessWidget {
),
),
SizedBox(
width: MediaQuery.of(context).size.width / 2.6,
width: MediaQuery.of(context)
.size
.width /
2.6,
child: TextFormField(
controller: BlocProvider.of<SmartDoorBloc>(context).passwordNameController,
controller: BlocProvider.of<
SmartDoorBloc>(context)
.passwordNameController,
decoration:
const InputDecoration(
hintText: 'Enter The Name',
hintText:
'Enter The Name',
hintStyle: TextStyle(
fontSize: 14,
color: ColorsManager.textGray)
),
color: ColorsManager
.textGray)),
)),
],
),
Column(
children: [
const Divider(color: ColorsManager.graysColor,),
const Divider(
color: ColorsManager.graysColor,
),
Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
const Expanded(
child: BodyMedium(
text: 'Effective Time',
fontWeight: FontWeight.normal,
fontWeight:
FontWeight.normal,
),
),
SizedBox(
width: MediaQuery.of(context).size.width / 3.5,
width:
MediaQuery.of(context)
.size
.width /
3.5,
child: InkWell(
onTap: () {
BlocProvider.of<SmartDoorBloc>(context).add(SelectTimeEvent(context: context, isEffective: true));
BlocProvider.of<
SmartDoorBloc>(
context)
.add(
SelectTimeEvent(
context:
context,
isEffective:
true));
},
child: Text(
BlocProvider.of<SmartDoorBloc>(context).effectiveTime,
BlocProvider.of<SmartDoorBloc>(
context)
.effectiveTime ==
'Select Time'
? cleaned.toString()
: BlocProvider.of<
SmartDoorBloc>(
context)
.effectiveTime,
style: TextStyle(
fontSize: 14,
color: BlocProvider.of<SmartDoorBloc>(context).effectiveTime ==
'Select Time' ? ColorsManager.textGray : null),
),
)),],
),
)),
],
),
),
const Divider(
@ -196,30 +242,48 @@ class CreateOfflineTimeLimitPasswordPage extends StatelessWidget {
),
Padding(
padding: const EdgeInsets.all(10.0),
child: Row(mainAxisAlignment:
MainAxisAlignment.spaceBetween,
child: Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
const Expanded(
child: BodyMedium(
text: 'Expiration Time',
fontWeight: FontWeight.normal,
fontWeight:
FontWeight.normal,
),
),
SizedBox(
width: MediaQuery.of(context).size.width / 3.5,
width: MediaQuery.of(context)
.size
.width /
3.5,
child: InkWell(
onTap: () {
BlocProvider.of<SmartDoorBloc>(context).add(SelectTimeEvent(
BlocProvider.of<
SmartDoorBloc>(
context)
.add(SelectTimeEvent(
context: context,
isEffective: false));
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' ? ColorsManager
.textGray : null),
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,9 +321,12 @@ 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){
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,7 +130,8 @@ class SosScreen extends StatelessWidget {
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SosRecordsScreen(
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

@ -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';
@ -16,49 +17,26 @@ class ManageHomeView extends StatelessWidget {
var spaces = HomeCubit.getInstance().spaces;
return DefaultScaffold(
title: 'Manage Your Home',
height: MediaQuery.sizeOf(context).height,
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(
shrinkWrap: true,
itemCount: spaces.length,
itemBuilder: (context, index) {
if (index == spaces.length - 1) {
final space = spaces[index];
return InkWell(
onTap: () {
Navigator.of(context).push(CustomPageRoute(
builder: (context) => HomeSettingsView(
space: spaces[index],
)));
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BodyMedium(text: StringHelpers.toTitleCase(spaces[index].name)),
const Icon(
Icons.arrow_forward_ios,
color: ColorsManager.greyColor,
size: 15,
)
],
Navigator.of(context).push(
CustomPageRoute(
builder: (context) =>
HomeSettingsView(space: space),
),
);
}
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,
@ -67,7 +45,8 @@ class ManageHomeView extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
BodyMedium(text: HomeCubit.getInstance().spaces[index].name),
BodyMedium(
text: StringHelpers.toTitleCase(space.name)),
const Icon(
Icons.arrow_forward_ios,
color: ColorsManager.greyColor,
@ -75,6 +54,7 @@ class ManageHomeView extends StatelessWidget {
)
],
),
if (index != spaces.length - 1)
Container(
margin: const EdgeInsets.symmetric(vertical: 15),
height: 1,
@ -83,7 +63,10 @@ class ManageHomeView extends StatelessWidget {
],
),
);
}),
));
},
),
),
),
);
}
}

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,46 +90,27 @@ 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,
executorProperty: json["executorProperty"] != null
? ExecutorProperty.fromJson(json["executorProperty"])
: null,
name: json['name'] as String?,
type: json['type'] as String?,
productType: productType,
deviceName: deviceName,
productType: json['productType'] as String? ?? 'unknown',
deviceName: json['deviceName'] as String? ?? 'Delay Action',
);
}
// 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
}
Map<String, dynamic> toJson() => {
"actionExecutor": actionExecutor,
"entityId": entityId,

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,11 +75,10 @@ class RoutinesView extends StatelessWidget {
data: Theme.of(context).copyWith(
dividerColor: Colors.transparent,
),
child: Expanded(
child: ListView(
children: [
RoutinesExpansionTile(
title: 'Tap to run routines',
title: 'Tap to run automations',
emptyRoutinesMessage:
'No scenes have been added yet',
routines: state.scenes,
@ -96,7 +98,6 @@ class RoutinesView extends StatelessWidget {
const SizedBox(height: 15),
],
),
),
);
}
return const SizedBox.shrink();
@ -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

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

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

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

@ -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"