Compare commits

..

17 Commits

Author SHA1 Message Date
6a7174b929 no need for selectedbookableSpaces list anymore instead i am using state list 2025-07-22 16:47:50 +03:00
b6d4084ca7 fix settings and back button to meet the UI 2025-07-22 10:58:30 +03:00
2b110b7c91 fix pagination UI 2025-07-22 10:50:00 +03:00
066fe4bc95 make variable final 2025-07-22 10:35:06 +03:00
85d65b2d96 Merge branch 'Implement-Spaces-Table-Empty-Filled-Failure-states-bookable-spaces' of https://github.com/SyncrowIOT/web into Implement-Spaces-Table-Empty-Filled-Failure-states-bookable-spaces 2025-07-22 10:12:14 +03:00
a64a9f1d12 fix unactive color 2025-07-22 10:12:02 +03:00
a7487f5434 Merge branch 'dev' into Implement-Spaces-Table-Empty-Filled-Failure-states-bookable-spaces 2025-07-22 09:58:14 +03:00
c0b74162e9 design fixes 2025-07-22 09:56:32 +03:00
0eb4652f26 Enhance garage door scheduling functionality and UI improvements (#358)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->



## Description

<!--- Describe your changes in detail -->
Enhance garage door scheduling functionality and UI improvements

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2025-07-21 16:25:53 +03:00
460639b681 Refactor booking system: update API endpoint format, add ResetEvents event, and enhance UI components for improved user experience (#361)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->



## Description

<!--- Describe your changes in detail -->
Refactor booking system: update API endpoint format, add ResetEvents
event, and enhance UI components for improved user experience

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2025-07-21 16:25:34 +03:00
2f89c3486c Release incomplete revamped space management (#360)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Jira User Story
[SP-1686](https://syncrow.atlassian.net/browse/SP-1686)

## Description
Released the new space management rewritten version, which for now
includes, CRUD operations for spaces, and community tree with the new
API.

Rewrote main files, since they had a lot of code duplication, i
extracted duplicated code into one component for all flavors to use.

Enhanced routing solution, to make sure there's only one instance of the
router, as the documentation of `go_router` guides us to do.

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [x]  New feature (non-breaking change which adds functionality)
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [x] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore 


[SP-1686]:
https://syncrow.atlassian.net/browse/SP-1686?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-07-21 15:33:06 +03:00
5589e5b587 Refactor booking system: update API endpoint format, add ResetEvents event, and enhance UI components for improved user experience 2025-07-21 15:22:17 +03:00
d3bd363b70 Fix category check in schedule dialog for water heater 2025-07-21 15:10:15 +03:00
3a4fce966c Merge branch 'dev' of https://github.com/SyncrowIOT/web into release_incomplete_revamped_space_management 2025-07-21 15:06:40 +03:00
6bdd28ec57 Refactor main entry points to utilize SyncrowApp, removing legacy MyApp implementation and associated dependencies, since there was too much duplicated code. 2025-07-21 14:50:29 +03:00
652163fdae Updated Space management route in app_routes.dart to use the new, incomplete, revamped space management. 2025-07-21 14:47:42 +03:00
076c80fe44 Enhance garage door scheduling functionality and UI improvements 2025-07-17 17:02:23 +03:00
48 changed files with 967 additions and 814 deletions

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.89852 11.08C-0.632759 8.54864 -0.632708 4.42983 1.89852 1.8985C4.42985 -0.632833 8.54861 -0.632833 11.0799 1.8985C13.2274 4.04599 13.5526 7.23986 12.0565 9.73392C12.0565 9.73392 11.949 9.91422 12.0942 10.0593C12.9222 10.8872 15.4065 13.3716 15.4065 13.3716C16.0658 14.0308 16.2227 14.9527 15.638 15.5374L15.5374 15.638C14.9527 16.2227 14.0308 16.0658 13.3716 15.4065C13.3716 15.4065 10.8926 12.9275 10.0662 12.1012C9.91414 11.9491 9.73389 12.0566 9.73389 12.0566C7.23988 13.5526 4.04601 13.2275 1.89852 11.08ZM9.88131 9.88133C11.7517 8.01094 11.7516 4.96763 9.88125 3.09724C8.01086 1.22689 4.96755 1.22684 3.09721 3.09724C1.22681 4.96758 1.22681 8.01094 3.09721 9.88133C4.9676 11.7516 8.01086 11.7516 9.88131 9.88133Z" fill="#999999" fill-opacity="0.7"/>
<path d="M9.46701 6.10386C9.55406 6.10386 9.64256 6.08674 9.72786 6.05072C10.0686 5.9065 10.228 5.51333 10.0838 5.17253C9.17738 3.03045 6.69729 2.0252 4.55526 2.93164C4.21451 3.07586 4.05509 3.46903 4.1993 3.80983C4.34357 4.15063 4.73664 4.30995 5.07755 4.16579C6.539 3.54737 8.23126 4.23326 8.84962 5.69471C8.95781 5.95031 9.20594 6.10386 9.46701 6.10386Z" fill="#999999" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,22 +1,9 @@
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/firebase_options.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
import 'package:syncrow_web/services/locator.dart';
import 'package:syncrow_web/utils/app_routes.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart';
import 'package:syncrow_web/utils/navigation_service.dart';
import 'package:syncrow_web/utils/theme/theme.dart';
import 'package:syncrow_web/syncrow_app.dart';
Future<void> main() async {
try {
@ -33,59 +20,5 @@ Future<void> main() async {
);
initialSetup();
} catch (_) {}
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({super.key});
final GoRouter _router = GoRouter(
initialLocation: RoutesConst.auth,
routes: AppRoutes.getRoutes(),
redirect: (context, state) async {
final checkToken = await AuthBloc.getTokenAndValidate();
final loggedIn = checkToken == 'Success';
final goingToLogin = state.uri.toString() == RoutesConst.auth;
if (!loggedIn && !goingToLogin) return RoutesConst.auth;
if (loggedIn && goingToLogin) return RoutesConst.home;
return null;
},
);
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<CreateRoutineBloc>(
create: (context) => CreateRoutineBloc(),
),
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(),
),
BlocProvider<RoutineBloc>(
create: (context) => RoutineBloc(),
),
BlocProvider<SpaceTreeBloc>(
create: (context) => SpaceTreeBloc(),
),
],
child: MaterialApp.router(
debugShowCheckedModeBanner: false,
scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.unknown,
},
),
key: NavigationService.navigatorKey,
// scaffoldMessengerKey: NavigationService.snackbarKey,
theme: myTheme,
routerConfig: _router,
));
}
runApp(const SyncrowApp());
}

View File

@ -1,22 +1,9 @@
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/firebase_options.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
import 'package:syncrow_web/services/locator.dart';
import 'package:syncrow_web/utils/app_routes.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart';
import 'package:syncrow_web/utils/navigation_service.dart';
import 'package:syncrow_web/utils/theme/theme.dart';
import 'package:syncrow_web/syncrow_app.dart';
Future<void> main() async {
try {
@ -33,59 +20,5 @@ Future<void> main() async {
);
initialSetup();
} catch (_) {}
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({super.key});
final GoRouter _router = GoRouter(
initialLocation: RoutesConst.auth,
routes: AppRoutes.getRoutes(),
redirect: (context, state) async {
final checkToken = await AuthBloc.getTokenAndValidate();
final loggedIn = checkToken == 'Success';
final goingToLogin = state.uri.toString() == RoutesConst.auth;
if (!loggedIn && !goingToLogin) return RoutesConst.auth;
if (loggedIn && goingToLogin) return RoutesConst.home;
return null;
},
);
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<CreateRoutineBloc>(
create: (context) => CreateRoutineBloc(),
),
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(),
),
BlocProvider<RoutineBloc>(
create: (context) => RoutineBloc(),
),
BlocProvider<SpaceTreeBloc>(
create: (context) => SpaceTreeBloc(),
),
],
child: MaterialApp.router(
debugShowCheckedModeBanner: false,
scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.unknown,
},
),
key: NavigationService.navigatorKey,
// scaffoldMessengerKey: NavigationService.snackbarKey,
theme: myTheme,
routerConfig: _router,
));
}
runApp(const SyncrowApp());
}

View File

@ -1,26 +1,16 @@
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/firebase_options.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
import 'package:syncrow_web/services/locator.dart';
import 'package:syncrow_web/utils/app_routes.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart';
import 'package:syncrow_web/utils/navigation_service.dart';
import 'package:syncrow_web/utils/theme/theme.dart';
import 'package:syncrow_web/syncrow_app.dart';
Future<void> main() async {
try {
const environment = String.fromEnvironment('FLAVOR', defaultValue: 'staging');
const environment = String.fromEnvironment(
'FLAVOR',
defaultValue: 'staging',
);
await dotenv.load(fileName: '.env.$environment');
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
@ -30,59 +20,5 @@ Future<void> main() async {
);
initialSetup();
} catch (_) {}
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({super.key});
final GoRouter _router = GoRouter(
initialLocation: RoutesConst.auth,
routes: AppRoutes.getRoutes(),
redirect: (context, state) async {
final checkToken = await AuthBloc.getTokenAndValidate();
final loggedIn = checkToken == 'Success';
final goingToLogin = state.uri.toString() == RoutesConst.auth;
if (!loggedIn && !goingToLogin) return RoutesConst.auth;
if (loggedIn && goingToLogin) return RoutesConst.home;
return null;
},
);
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<CreateRoutineBloc>(
create: (context) => CreateRoutineBloc(),
),
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(),
),
BlocProvider<RoutineBloc>(
create: (context) => RoutineBloc(),
),
BlocProvider<SpaceTreeBloc>(
create: (context) => SpaceTreeBloc(),
),
],
child: MaterialApp.router(
debugShowCheckedModeBanner: false,
scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.unknown,
},
),
key: NavigationService.navigatorKey,
// scaffoldMessengerKey: NavigationService.snackbarKey,
theme: myTheme,
routerConfig: _router,
));
}
runApp(const SyncrowApp());
}

View File

@ -10,7 +10,7 @@ part 'events_event.dart';
part 'events_state.dart';
class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
final EventController eventController = EventController();
EventController eventController = EventController();
final CalendarSystemService calendarService;
CalendarEventsBloc({
@ -20,7 +20,9 @@ class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
on<AddEvent>(_onAddEvent);
on<DisposeResources>(_onDisposeResources);
on<GoToWeek>(_onGoToWeek);
on<ResetEvents>(_onResetEvents);
}
Future<void> _onLoadEvents(
LoadEvents event,
Emitter<CalendarEventState> emit,
@ -126,4 +128,18 @@ class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
eventController.dispose();
return super.close();
}
void _onResetEvents(
ResetEvents event,
Emitter<CalendarEventState> emit,
) {
if (calendarService is MemoryCalendarServiceWithRemoteFallback) {
(calendarService as MemoryCalendarServiceWithRemoteFallback)
.memoryService
.clear();
}
eventController.dispose();
eventController = EventController();
emit(EventsInitial());
}
}

View File

@ -29,3 +29,10 @@ class CheckWeekHasEvents extends CalendarEventsEvent {
final DateTime weekStart;
const CheckWeekHasEvents(this.weekStart);
}
class ResetEvents extends CalendarEventsEvent {
const ResetEvents();
@override
List<Object?> get props => [];
}

View File

@ -1,3 +1,4 @@
// booking_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:calendar_view/calendar_view.dart';
@ -26,7 +27,33 @@ class BookingPage extends StatefulWidget {
}
class _BookingPageState extends State<BookingPage> {
late final EventController _eventController;
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (_) => SelectedBookableSpaceBloc()),
BlocProvider(create: (_) => DateSelectionBloc()),
BlocProvider(
create: (_) => CalendarEventsBloc(
calendarService: MemoryCalendarServiceWithRemoteFallback(
remoteService: RemoteCalendarService(HTTPService()),
memoryService: MemoryCalendarService(),
),
),
),
],
child: _BookingPageContent(),
);
}
}
class _BookingPageContent extends StatefulWidget {
@override
State<_BookingPageContent> createState() => _BookingPageContentState();
}
class _BookingPageContentState extends State<_BookingPageContent> {
late EventController _eventController;
@override
void initState() {
@ -40,7 +67,7 @@ class _BookingPageState extends State<BookingPage> {
super.dispose();
}
void _dispatchLoadEvents(BuildContext context) {
void _loadEvents(BuildContext context) {
final selectedRoom =
context.read<SelectedBookableSpaceBloc>().state.selectedBookableSpace;
final dateState = context.read<DateSelectionBloc>().state;
@ -60,186 +87,170 @@ class _BookingPageState extends State<BookingPage> {
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (_) => SelectedBookableSpaceBloc()),
BlocProvider(create: (_) => DateSelectionBloc()),
BlocProvider(
create: (_) => CalendarEventsBloc(
calendarService: MemoryCalendarServiceWithRemoteFallback(
remoteService: RemoteCalendarService(
HTTPService(),
),
memoryService: MemoryCalendarService(),
),
)),
],
child: Builder(
builder: (context) =>
BlocListener<CalendarEventsBloc, CalendarEventState>(
listenWhen: (prev, curr) => curr is EventsLoaded,
return BlocListener<SelectedBookableSpaceBloc, SelectedBookableSpaceState>(
listener: (context, state) {
if (state.selectedBookableSpace != null) {
// Reset events and clear cache when room changes
context.read<CalendarEventsBloc>().add(ResetEvents());
_loadEvents(context);
}
},
child: BlocListener<DateSelectionBloc, DateSelectionState>(
listener: (context, state) {
_loadEvents(context);
},
child: BlocListener<CalendarEventsBloc, CalendarEventState>(
listener: (context, state) {
if (state is EventsLoaded) {
_eventController.removeWhere((_) => true);
_eventController.addAll(state.events);
}
},
child: BlocListener<SelectedBookableSpaceBloc,
SelectedBookableSpaceState>(
listener: (context, state) => _dispatchLoadEvents(context),
child: BlocListener<DateSelectionBloc, DateSelectionState>(
listener: (context, state) => _dispatchLoadEvents(context),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
boxShadow: [
BoxShadow(
color: ColorsManager.blackColor.withOpacity(0.1),
offset: const Offset(3, 0),
blurRadius: 6,
spreadRadius: 0,
),
],
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
boxShadow: [
BoxShadow(
color: ColorsManager.blackColor.withOpacity(0.1),
offset: const Offset(3, 0),
blurRadius: 6,
spreadRadius: 0,
),
child: Column(
children: [
Expanded(
flex: 2,
child: BlocBuilder<SelectedBookableSpaceBloc,
SelectedBookableSpaceState>(
builder: (context, state) {
return BookingSidebar(
onRoomSelected: (selectedRoom) {
context
.read<SelectedBookableSpaceBloc>()
.add(SelectBookableSpace(selectedRoom));
},
);
},
),
),
Expanded(
child: BlocBuilder<DateSelectionBloc,
DateSelectionState>(
builder: (context, dateState) {
return CustomCalendarPage(
selectedDate: dateState.selectedDate,
onDateChanged: (day, month, year) {
final newDate = DateTime(year, month, day);
context
.read<DateSelectionBloc>()
.add(SelectDate(newDate));
context.read<DateSelectionBloc>().add(
SelectDateFromSidebarCalendar(newDate));
},
);
},
),
),
],
),
),
],
),
Expanded(
flex: 5,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
child: Column(
children: [
Expanded(
flex: 2,
child: BlocBuilder<SelectedBookableSpaceBloc,
SelectedBookableSpaceState>(
builder: (context, state) {
return BookingSidebar(
onRoomSelected: (selectedRoom) {
context
.read<SelectedBookableSpaceBloc>()
.add(SelectBookableSpace(selectedRoom));
},
);
},
),
),
Expanded(
child:
BlocBuilder<DateSelectionBloc, DateSelectionState>(
builder: (context, dateState) {
return CustomCalendarPage(
selectedDate: dateState.selectedDate,
onDateChanged: (day, month, year) {
final newDate = DateTime(year, month, day);
context
.read<DateSelectionBloc>()
.add(SelectDate(newDate));
context.read<DateSelectionBloc>().add(
SelectDateFromSidebarCalendar(newDate));
},
);
},
),
),
],
),
),
),
Expanded(
flex: 5,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
SvgTextButton(
svgAsset: Assets.homeIcon,
label: 'Manage Bookable Spaces',
onPressed: () {},
),
const SizedBox(width: 20),
SvgTextButton(
svgAsset: Assets.groupIcon,
label: 'Manage Users',
onPressed: () {},
),
],
SvgTextButton(
svgAsset: Assets.homeIcon,
label: 'Manage Bookable Spaces',
onPressed: () {},
),
BlocBuilder<DateSelectionBloc,
DateSelectionState>(
builder: (context, state) {
final weekStart = state.weekStart;
final weekEnd =
weekStart.add(const Duration(days: 6));
return WeekNavigation(
weekStart: weekStart,
weekEnd: weekEnd,
onPreviousWeek: () {
context
.read<DateSelectionBloc>()
.add(PreviousWeek());
},
onNextWeek: () {
context
.read<DateSelectionBloc>()
.add(NextWeek());
},
);
},
const SizedBox(width: 20),
SvgTextButton(
svgAsset: Assets.groupIcon,
label: 'Manage Users',
onPressed: () {},
),
],
),
Expanded(
flex: 5,
child: BlocBuilder<SelectedBookableSpaceBloc,
SelectedBookableSpaceState>(
builder: (context, roomState) {
final selectedRoom =
roomState.selectedBookableSpace;
return BlocBuilder<DateSelectionBloc,
DateSelectionState>(
builder: (context, dateState) {
return BlocListener<CalendarEventsBloc,
CalendarEventState>(
listenWhen: (prev, curr) =>
curr is EventsLoaded,
listener: (context, state) {
if (state is EventsLoaded) {
_eventController
.removeWhere((_) => true);
_eventController.addAll(state.events);
}
},
child: WeeklyCalendarPage(
startTime: selectedRoom
?.bookableConfig.startTime,
endTime: selectedRoom
?.bookableConfig.endTime,
weekStart: dateState.weekStart,
selectedDate: dateState.selectedDate,
eventController: _eventController,
selectedDateFromSideBarCalender: context
.watch<DateSelectionBloc>()
.state
.selectedDateFromSideBarCalender,
),
BlocBuilder<DateSelectionBloc, DateSelectionState>(
builder: (context, state) {
final weekStart = state.weekStart;
final weekEnd =
weekStart.add(const Duration(days: 6));
return WeekNavigation(
weekStart: weekStart,
weekEnd: weekEnd,
onPreviousWeek: () {
context
.read<DateSelectionBloc>()
.add(PreviousWeek());
},
onNextWeek: () {
context
.read<DateSelectionBloc>()
.add(NextWeek());
},
);
},
),
],
),
const SizedBox(height: 20),
Expanded(
flex: 5,
child: BlocBuilder<SelectedBookableSpaceBloc,
SelectedBookableSpaceState>(
builder: (context, roomState) {
final selectedRoom =
roomState.selectedBookableSpace;
return BlocBuilder<DateSelectionBloc,
DateSelectionState>(
builder: (context, dateState) {
return BlocBuilder<CalendarEventsBloc,
CalendarEventState>(
builder: (context, eventState) {
return WeeklyCalendarPage(
key: ValueKey(
selectedRoom?.uuid ?? 'no-room'),
startTime: selectedRoom
?.bookableConfig.startTime,
endTime:
selectedRoom?.bookableConfig.endTime,
weekStart: dateState.weekStart,
selectedDate: dateState.selectedDate,
eventController: _eventController,
selectedDateFromSideBarCalender: context
.watch<DateSelectionBloc>()
.state
.selectedDateFromSideBarCalender,
// isLoading: eventState is EventsLoading,
);
},
);
},
),
),
],
);
},
),
),
),
],
),
],
),
),
),
],
),
),
),

View File

@ -76,21 +76,29 @@ class __SidebarContentState extends State<_SidebarContent> {
builder: (context, state) {
return Column(
children: [
const _SidebarHeader(title: 'Spaces'),
Padding(
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
child: Container(
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
boxShadow: [
BoxShadow(
color: ColorsManager.blackColor.withOpacity(0.1),
offset: const Offset(0, 4),
blurRadius: 4,
spreadRadius: 0,
),
],
),
child: _SidebarHeader(title: 'Spaces')),
),
Container(
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(8.0),
boxShadow: [
BoxShadow(
color: ColorsManager.blackColor.withOpacity(0.1),
offset: const Offset(0, -2),
blurRadius: 4,
spreadRadius: 0,
),
BoxShadow(
color: ColorsManager.blackColor.withOpacity(0.1),
offset: const Offset(0, 2),
offset: const Offset(0, 4),
blurRadius: 4,
spreadRadius: 0,
),
@ -220,7 +228,7 @@ class _SidebarHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
padding: const EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [

View File

@ -66,18 +66,28 @@ class _CustomCalendarPageState extends State<CustomCalendarPage> {
weekdayLabels: const ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'],
);
return CalendarDatePicker2(
config: config,
value: [_selectedDate],
onValueChanged: (dates) {
final picked = dates.first;
if (picked != null) {
setState(() {
_selectedDate = picked;
});
widget.onDateChanged(picked.day, picked.month, picked.year);
}
},
return Container(
decoration: const BoxDecoration(
border: Border(
top: BorderSide(
color: ColorsManager.textGray,
width: 1.0,
),
),
),
child: CalendarDatePicker2(
config: config,
value: [_selectedDate],
onValueChanged: (dates) {
final picked = dates.first;
if (picked != null) {
setState(() {
_selectedDate = picked;
});
widget.onDateChanged(picked.day, picked.month, picked.year);
}
},
),
);
}
}

View File

@ -32,15 +32,17 @@ class EventTileWidget extends StatelessWidget {
return Expanded(
child: Container(
width: double.infinity,
padding: EdgeInsets.all(5),
padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
color: isEventEnded
? ColorsManager.grayColor.withOpacity(0.1)
: ColorsManager.blue1.withOpacity(0.1),
borderRadius: BorderRadius.circular(6),
border: const Border(
border: Border(
left: BorderSide(
color: ColorsManager.grayColor,
color: isEventEnded
? ColorsManager.grayColor
: ColorsManager.secondaryColor,
width: 4,
),
),

View File

@ -21,7 +21,7 @@ class RoomListItem extends StatelessWidget {
groupValue: isSelected ? room.uuid : null,
visualDensity: const VisualDensity(vertical: -4),
onChanged: (value) => onTap(),
activeColor: ColorsManager.primaryColor,
activeColor: ColorsManager.secondaryColor,
title: Text(
room.spaceName,
maxLines: 2,

View File

@ -14,26 +14,33 @@ class WeekDayHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
DateFormat('EEE').format(date).toUpperCase(),
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: 14,
color: isSelectedDay ? Colors.blue : Colors.black,
return ColoredBox(
color: isSelectedDay
? ColorsManager.secondaryColor.withOpacity(0.1)
: Colors.transparent,
child: Column(
children: [
const SizedBox(
height: 10,
),
),
Text(
DateFormat('d').format(date),
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 20,
color:
isSelectedDay ? ColorsManager.blue1 : ColorsManager.blackColor,
Text(
DateFormat('EEE').format(date).toUpperCase(),
style: const TextStyle(
fontWeight: FontWeight.w400,
fontSize: 14,
color: ColorsManager.blackColor,
),
),
),
],
Text(
DateFormat('d').format(date),
style: const TextStyle(
fontWeight: FontWeight.w700,
fontSize: 30,
color: ColorsManager.blackColor,
),
),
],
),
);
}
}

View File

@ -24,7 +24,7 @@ class WeeklyCalendarPage extends StatelessWidget {
});
static const double timeLineWidth = 65;
static const int totalDays = 7;
static const double dayColumnWidth = 220;
static const double dayColumnWidth = 220;
final double calendarContentWidth =
timeLineWidth + (totalDays * dayColumnWidth);
@ -57,13 +57,10 @@ class WeeklyCalendarPage extends StatelessWidget {
);
}
const double timeLineWidth = 65;
const double timeLineWidth = 90;
return LayoutBuilder(
builder: (context, constraints) {
bool isInRange(DateTime date, DateTime start, DateTime end) {
!date.isBefore(start) && !date.isAfter(end);
// remove this line and Check if the date is within the range
@ -100,7 +97,6 @@ class WeeklyCalendarPage extends StatelessWidget {
width: width,
decoration: BoxDecoration(
color: Colors.orange.withOpacity(0.13),
borderRadius: BorderRadius.circular(8),
),
);
} else if (isSelected) {
@ -110,7 +106,6 @@ class WeeklyCalendarPage extends StatelessWidget {
decoration: BoxDecoration(
color:
ColorsManager.spaceColor.withOpacity(0.07),
borderRadius: BorderRadius.circular(8),
),
);
}
@ -143,7 +138,7 @@ class WeeklyCalendarPage extends StatelessWidget {
heightPerMinute: 1.7,
showLiveTimeLineInAllDays: false,
showVerticalLines: true,
emulateVerticalOffsetBy: -80,
emulateVerticalOffsetBy: -95,
startDay: WeekDays.monday,
liveTimeIndicatorSettings:
const LiveTimeIndicatorSettings(
@ -161,7 +156,7 @@ class WeeklyCalendarPage extends StatelessWidget {
},
timeLineWidth: timeLineWidth,
weekPageHeaderBuilder: (start, end) => Container(),
weekTitleHeight: 60,
weekTitleHeight: 90,
weekNumberBuilder: (firstDayOfWeek) => Padding(
padding: const EdgeInsets.only(right: 15, bottom: 10),
child: Column(
@ -208,8 +203,6 @@ class WeeklyCalendarPage extends StatelessWidget {
},
);
}
}
bool isSameDay(DateTime d1, DateTime d2) {

View File

@ -1,6 +1,6 @@
class NonBookableSpacesParams {
final int currentPage;
String? searchedWords;
final String? searchedWords;
NonBookableSpacesParams({
required this.currentPage,
this.searchedWords,

View File

@ -10,51 +10,123 @@ part 'setup_bookable_spaces_state.dart';
class SetupBookableSpacesBloc
extends Bloc<SetupBookableSpacesEvent, SetupBookableSpacesState> {
NonBookableSpacesService nonBookableSpacesService;
List<BookableSpacemodel> selectedBookableSpaces = [];
SetupBookableSpacesBloc(this.nonBookableSpacesService)
: super(SetupBookableSpacesInitial()) {
: super(const SetupBookableSpacesInitial(bookableSpaces: [])) {
on<AddToBookableSpaceEvent>(_onAddToBookableSpaceEvent);
on<RemoveFromBookableSpaceEvent>(_onRemoveFromBookableSpaceEvent);
on<AddBookableDaysEvent>(_onAddBookableDays);
on<ChangeStartTimeEvent>(_onChangeStartTimeEvent);
on<ChangeEndTimeEvent>(_onChangeEndTimeEvent);
on<ChangeCostEvent>(_onChangeCostEvent);
on<CheckConfigurValidityEvent>(_onCheckConfigurValidityEvent);
on<EditModeSelected>(_onEditModeSelected);
}
TimeOfDay? get endTime =>
selectedBookableSpaces.first.spaceConfig!.bookingEndTime;
TimeOfDay? get startTime =>
selectedBookableSpaces.first.spaceConfig!.bookingStartTime;
List<BookableSpacemodel> get currentBookableSpaces {
return switch (state) {
AddNonBookableSpaceIntoBookableState(:final bookableSpaces) =>
bookableSpaces,
RemoveBookableSpaceIntoNonBookableState(:final bookableSpaces) =>
bookableSpaces,
SetupBookableSpacesInitial(:final bookableSpaces) => bookableSpaces,
InProgressState(:final bookableSpaces) => bookableSpaces,
ValidSaveButtonState(:final bookableSpaces) => bookableSpaces,
UnValidSaveButtonState(:final bookableSpaces) => bookableSpaces,
};
}
void _onAddToBookableSpaceEvent(
AddToBookableSpaceEvent event,
Emitter<SetupBookableSpacesState> emit,
) {
emit(InProgressState());
selectedBookableSpaces.add(event.nonBookableSpace);
emit(AddNonBookableSpaceIntoBookableState(
bookableSpaces: selectedBookableSpaces));
emit(InProgressState(bookableSpaces: state.bookableSpaces));
final updatedSpaces = List<BookableSpacemodel>.from(state.bookableSpaces);
updatedSpaces.add(event.nonBookableSpace);
emit(AddNonBookableSpaceIntoBookableState(bookableSpaces: updatedSpaces));
}
void _onRemoveFromBookableSpaceEvent(RemoveFromBookableSpaceEvent event,
Emitter<SetupBookableSpacesState> emit) {
emit(InProgressState());
selectedBookableSpaces.remove(event.bookableSpace);
emit(InProgressState(bookableSpaces: state.bookableSpaces));
state.bookableSpaces.remove(event.bookableSpace);
emit(RemoveBookableSpaceIntoNonBookableState(
bookableSpaces: selectedBookableSpaces));
bookableSpaces: state.bookableSpaces));
}
void _onAddBookableDays(
AddBookableDaysEvent event, Emitter<SetupBookableSpacesState> emit) {
final updatedSpaces = state.bookableSpaces.map((space) {
final updatedConfig = space.spaceConfig?.copyWith(
bookableDays: event.bookableDays,
);
return space.copyWith(spaceConfig: updatedConfig);
}).toList();
emit(SetupBookableSpacesInitial(bookableSpaces: updatedSpaces));
}
void _onChangeStartTimeEvent(
ChangeStartTimeEvent event, Emitter<SetupBookableSpacesState> emit) {
final updatedSpaces = state.bookableSpaces.map((space) {
final updatedConfig = space.spaceConfig?.copyWith(
bookingStartTime: event.startTime,
);
return space.copyWith(spaceConfig: updatedConfig);
}).toList();
emit(SetupBookableSpacesInitial(bookableSpaces: updatedSpaces));
}
void _onChangeEndTimeEvent(
ChangeEndTimeEvent event, Emitter<SetupBookableSpacesState> emit) {
final updatedSpaces = state.bookableSpaces.map((space) {
final updatedConfig = space.spaceConfig?.copyWith(
bookingEndTime: event.endTime,
);
return space.copyWith(spaceConfig: updatedConfig);
}).toList();
emit(SetupBookableSpacesInitial(bookableSpaces: updatedSpaces));
}
void _onChangeCostEvent(
ChangeCostEvent event, Emitter<SetupBookableSpacesState> emit) {
final updatedSpaces = state.bookableSpaces.map((space) {
final updatedConfig = space.spaceConfig?.copyWith(
cost: event.cost,
);
return space.copyWith(spaceConfig: updatedConfig);
}).toList();
emit(SetupBookableSpacesInitial(bookableSpaces: updatedSpaces));
}
void _onCheckConfigurValidityEvent(CheckConfigurValidityEvent event,
Emitter<SetupBookableSpacesState> emit) {
if (selectedBookableSpaces.first.spaceConfig!.isValid) {
emit(ValidSaveButtonState());
if (state.bookableSpaces.first.spaceConfig!.isValid) {
emit(ValidSaveButtonState(
bookableSpaces: state.bookableSpaces,
));
} else {
emit(UnValidSaveButtonState());
emit(UnValidSaveButtonState(
bookableSpaces: state.bookableSpaces,
));
}
}
void _onEditModeSelected(
EditModeSelected event, Emitter<SetupBookableSpacesState> emit) {
selectedBookableSpaces.clear();
selectedBookableSpaces.add(event.editingBookableSpace);
EditModeSelected event,
Emitter<SetupBookableSpacesState> emit,
) {
final updatedList = [event.editingBookableSpace];
emit(SetupBookableSpacesInitial(bookableSpaces: updatedList));
}
}

View File

@ -21,6 +21,34 @@ class RemoveFromBookableSpaceEvent extends SetupBookableSpacesEvent {
});
}
class AddBookableDaysEvent extends SetupBookableSpacesEvent {
final List<String> bookableDays;
const AddBookableDaysEvent({
required this.bookableDays,
});
}
class ChangeCostEvent extends SetupBookableSpacesEvent {
final int cost;
const ChangeCostEvent({
required this.cost,
});
}
class ChangeStartTimeEvent extends SetupBookableSpacesEvent {
final TimeOfDay startTime;
const ChangeStartTimeEvent({
required this.startTime,
});
}
class ChangeEndTimeEvent extends SetupBookableSpacesEvent {
final TimeOfDay endTime;
const ChangeEndTimeEvent({
required this.endTime,
});
}
class CheckConfigurValidityEvent extends SetupBookableSpacesEvent {}
class EditModeSelected extends SetupBookableSpacesEvent {

View File

@ -1,32 +1,37 @@
part of 'setup_bookable_spaces_bloc.dart';
sealed class SetupBookableSpacesState extends Equatable {
const SetupBookableSpacesState();
final List<BookableSpacemodel> bookableSpaces;
const SetupBookableSpacesState({required this.bookableSpaces});
TimeOfDay? get startTime =>
bookableSpaces.first.spaceConfig!.bookingStartTime;
TimeOfDay? get endTime => bookableSpaces.first.spaceConfig!.bookingEndTime;
@override
List<Object> get props => [];
}
final class SetupBookableSpacesInitial extends SetupBookableSpacesState {}
final class SetupBookableSpacesInitial extends SetupBookableSpacesState {
const SetupBookableSpacesInitial({required super.bookableSpaces});
}
class AddNonBookableSpaceIntoBookableState extends SetupBookableSpacesState {
final List<BookableSpacemodel> bookableSpaces;
const AddNonBookableSpaceIntoBookableState({
required this.bookableSpaces,
});
const AddNonBookableSpaceIntoBookableState({required super.bookableSpaces});
}
class InProgressState extends SetupBookableSpacesState {}
class InProgressState extends SetupBookableSpacesState {
const InProgressState({required super.bookableSpaces});
}
class RemoveBookableSpaceIntoNonBookableState extends SetupBookableSpacesState {
final List<BookableSpacemodel> bookableSpaces;
const RemoveBookableSpaceIntoNonBookableState({
required this.bookableSpaces,
required super.bookableSpaces,
});
}
class ValidSaveButtonState extends SetupBookableSpacesState {}
class UnValidSaveButtonState extends SetupBookableSpacesState {}
class ValidSaveButtonState extends SetupBookableSpacesState {
const ValidSaveButtonState({required super.bookableSpaces});
}
class UnValidSaveButtonState extends SetupBookableSpacesState {
const UnValidSaveButtonState({required super.bookableSpaces});
}

View File

@ -63,9 +63,10 @@ class _SetupBookableSpacesDialogState extends State<SetupBookableSpacesDialog> {
RemoteNonBookableSpaces(HTTPService()))
: (context) => SetupBookableSpacesBloc(
RemoteNonBookableSpaces(HTTPService()))
..add(EditModeSelected(
editingBookableSpace: widget.editingBookableSpace!,
)),
..add(
EditModeSelected(
editingBookableSpace: widget.editingBookableSpace!),
),
),
BlocProvider(
create: (context) => SendBookableSpacesBloc(
@ -115,16 +116,14 @@ class _SetupBookableSpacesDialogState extends State<SetupBookableSpacesDialog> {
),
Builder(builder: (context) {
final stepsState = context.watch<StepsCubit>().state;
final setupBookableSpacesBloc =
context.watch<SetupBookableSpacesBloc>();
final selectedSpaces =
setupBookableSpacesBloc.selectedBookableSpaces;
final bookableSpaces =
context.watch<SetupBookableSpacesBloc>().state.bookableSpaces;
return stepsState is StepOneState
? NextFirstStepButton(selectedSpaces: selectedSpaces)
? const NextFirstStepButton()
: SaveSecondStepButton(
selectedSpaces: selectedSpaces,
pointsController: pointsController,
isEditingMode: widget.editingBookableSpace != null,
bookableSpaces: bookableSpaces,
);
}),
],

View File

@ -47,7 +47,7 @@ class BookableSpaceSwitchActivationWidget extends StatelessWidget {
return ColorsManager.whiteColors;
}),
value: space.spaceConfig!.availability,
activeTrackColor: ColorsManager.blueColor,
activeTrackColor: ColorsManager.dialogBlueTitle,
inactiveTrackColor: ColorsManager.grayBorder,
thumbColor: WidgetStateProperty.resolveWith<Color>(
(Set<WidgetState> states) {

View File

@ -17,6 +17,8 @@ class BookingPeriodWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final state = context.watch<SetupBookableSpacesBloc>().state;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -32,122 +34,108 @@ class BookingPeriodWidget extends StatelessWidget {
const Text('Booking Period'),
],
),
const SizedBox(height: 5),
Container(
width: 300,
width: 230,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: ColorsManager.graysColor,
borderRadius: BorderRadius.circular(8),
color: ColorsManager.circleRolesBackground,
boxShadow: [
BoxShadow(
offset: Offset.zero,
blurRadius: 4,
spreadRadius: 0,
color: ColorsManager.timePickerColor.withValues(alpha: 0.15),
)
],
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TimePickerWidget(
title: editingBookableSpace == null
? 'Start Time'
: editingBookableSpace!.spaceConfig!.bookingStartTime!
.format(context),
onTimePicked: (timePicked) {
if (timePicked == null) {
title: editingBookableSpace?.spaceConfig?.bookingStartTime
?.format(context) ??
'Start Time',
onTimePicked: (pickedStartTime) {
if (pickedStartTime == null) return;
if (state.endTime != null &&
isEndTimeAfterStartTime(
pickedStartTime, state.endTime!)) {
_showInvalidSnackBar(
context, "You can't choose Start Time after End Time");
return;
}
final setupBookableSpacesBloc =
context.read<SetupBookableSpacesBloc>();
if (setupBookableSpacesBloc.endTime != null &&
isEndTimeAfterStartTime(
timePicked, setupBookableSpacesBloc.endTime!)) {
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content:
Text("You can't choose start Time Before End time"),
duration: Duration(seconds: 2),
backgroundColor: ColorsManager.red,
));
throw Exception();
} else {
for (int i = 0;
i <
setupBookableSpacesBloc
.selectedBookableSpaces.length;
i++) {
final space =
setupBookableSpacesBloc.selectedBookableSpaces[i];
final updatedConfig = space.spaceConfig
?.copyWith(bookingStartTime: timePicked);
final updatedSpace =
space.copyWith(spaceConfig: updatedConfig);
setupBookableSpacesBloc.selectedBookableSpaces[i] =
updatedSpace;
}
}
context.read<SetupBookableSpacesBloc>().add(
ChangeStartTimeEvent(startTime: pickedStartTime),
);
context.read<SetupBookableSpacesBloc>().add(
CheckConfigurValidityEvent(),
);
},
),
const SizedBox(width: 10),
const Icon(
Icons.arrow_right_alt,
color: ColorsManager.grayColor,
size: 13,
),
TimePickerWidget(
title: editingBookableSpace == null
? 'End Time'
: editingBookableSpace!.spaceConfig!.bookingEndTime!
.format(context),
onTimePicked: (timePicked) {
if (timePicked == null) {
title: editingBookableSpace?.spaceConfig?.bookingEndTime
?.format(context) ??
'End Time',
onTimePicked: (pickedEndTime) {
if (pickedEndTime == null) return;
if (state.startTime != null &&
isEndTimeAfterStartTime(
state.startTime!, pickedEndTime)) {
_showInvalidSnackBar(
context, "You can't choose End Time before Start Time");
return;
}
final setupBookableSpacesBloc =
context.read<SetupBookableSpacesBloc>();
if (setupBookableSpacesBloc.startTime != null &&
isEndTimeAfterStartTime(
setupBookableSpacesBloc.startTime!, timePicked)) {
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content:
Text("You can't choose End Time After Start time"),
duration: Duration(seconds: 2),
backgroundColor: ColorsManager.red,
));
throw Exception();
} else {
for (int i = 0;
i <
setupBookableSpacesBloc
.selectedBookableSpaces.length;
i++) {
final space =
setupBookableSpacesBloc.selectedBookableSpaces[i];
final updatedConfig = space.spaceConfig
?.copyWith(bookingEndTime: timePicked);
final updatedSpace =
space.copyWith(spaceConfig: updatedConfig);
setupBookableSpacesBloc.selectedBookableSpaces[i] =
updatedSpace;
}
}
context.read<SetupBookableSpacesBloc>().add(
ChangeEndTimeEvent(endTime: pickedEndTime),
);
context.read<SetupBookableSpacesBloc>().add(
CheckConfigurValidityEvent(),
);
},
),
const SizedBox(width: 15),
Container(
width: 50,
width: 30,
height: 32,
alignment: Alignment.center,
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
bottomLeft: Radius.circular(10),
),
),
alignment: Alignment.center,
child: SvgPicture.asset(
Assets.clockIcon,
height: 15,
height: 18,
color: ColorsManager.blackColor.withValues(alpha: 0.4),
),
)
),
],
),
),
],
);
}
void _showInvalidSnackBar(BuildContext context, String message) {
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
duration: const Duration(seconds: 2),
backgroundColor: ColorsManager.red,
),
);
}
}

View File

@ -2,16 +2,15 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/setup_bookable_spaces_bloc/setup_bookable_spaces_bloc.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/custom_checkbox_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CheckBoxSpaceWidget extends StatelessWidget {
final BookableSpacemodel nonBookableSpace;
final List<BookableSpacemodel> selectedSpaces;
const CheckBoxSpaceWidget({
super.key,
required this.nonBookableSpace,
required this.selectedSpaces,
});
@override
@ -32,22 +31,25 @@ class CheckBoxSpaceWidget extends StatelessWidget {
_ => false,
};
return Checkbox(
return CustomCheckboxWidget(
value: isChecked,
onChanged: (value) {
final bloc = context.read<SetupBookableSpacesBloc>();
if (value ?? false) {
bloc.add(AddToBookableSpaceEvent(
nonBookableSpace: nonBookableSpace));
nonBookableSpace: nonBookableSpace,
));
} else {
bloc.add(RemoveFromBookableSpaceEvent(
bookableSpace: nonBookableSpace));
bookableSpace: nonBookableSpace,
));
}
},
);
},
),
const SizedBox(width: 5),
const SizedBox(width: 15),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -55,15 +57,17 @@ class CheckBoxSpaceWidget extends StatelessWidget {
Text(
nonBookableSpace.spaceName,
style: const TextStyle(
fontWeight: FontWeight.bold,
color: ColorsManager.textGray,
fontWeight: FontWeight.w700,
fontSize: 12,
color: ColorsManager.titleGray,
),
),
Text(
nonBookableSpace.spaceVirtualAddress,
style: const TextStyle(
fontSize: 12,
color: ColorsManager.textGray,
fontWeight: FontWeight.w400,
fontSize: 10,
color: ColorsManager.titleGray,
),
),
],

View File

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CustomCheckboxWidget extends StatelessWidget {
final bool value;
final ValueChanged<bool?> onChanged;
final double? outHeight;
final double? outWidth;
final double? iconSize;
const CustomCheckboxWidget({
super.key,
required this.value,
required this.onChanged,
this.outWidth,
this.outHeight,
this.iconSize,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => onChanged(!value),
child: Container(
width: outWidth ?? 17,
height: outHeight ?? 17,
decoration: BoxDecoration(
color: value ? Colors.white : ColorsManager.checkBoxFillColor,
border: value
? Border.all(color: ColorsManager.secondaryColor, width: 1)
: Border.all(color: ColorsManager.checkBoxBorderGray, width: 1),
borderRadius: BorderRadius.circular(4),
),
child: value
? Center(
child: Container(
width: outWidth != null ? outWidth! - 4 : 13,
height: outHeight != null ? outHeight! - 4 : 13,
decoration: BoxDecoration(
color: ColorsManager.secondaryColor,
borderRadius: BorderRadius.circular(2),
),
child: const Icon(
Icons.check,
size: 12,
color: Colors.white,
),
),
)
: null,
),
);
}
}

View File

@ -20,38 +20,53 @@ class EditBookableSpaceButtonWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
onPressed: () {
final bookableBloc = context.read<BookableSpacesBloc>();
showDialog(
context: context,
builder: (context) => MultiBlocProvider(
providers: [
BlocProvider.value(
value: bookableBloc,
),
BlocProvider(
create: (context) => UpdateBookableSpacesBloc(
RemoteUpdateBookableSpaceService(HTTPService()),
),
),
],
child: SetupBookableSpacesDialog(
editingBookableSpace: space,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
offset: Offset.zero,
blurRadius: 3,
spreadRadius: 0,
color: ColorsManager.timePickerColor.withValues(
alpha: 0.3,
),
),
);
},
style: ElevatedButton.styleFrom(
padding: EdgeInsets.zero,
fixedSize: const Size(50, 30),
elevation: 1,
)
],
),
child: SvgPicture.asset(
Assets.settings,
height: 15,
color: ColorsManager.blue1,
child: ElevatedButton(
onPressed: () {
final bookableBloc = context.read<BookableSpacesBloc>();
showDialog(
context: context,
builder: (context) => MultiBlocProvider(
providers: [
BlocProvider.value(
value: bookableBloc,
),
BlocProvider(
create: (context) => UpdateBookableSpacesBloc(
RemoteUpdateBookableSpaceService(HTTPService()),
),
),
],
child: SetupBookableSpacesDialog(
editingBookableSpace: space,
),
),
);
},
style: ElevatedButton.styleFrom(
padding: EdgeInsets.zero,
minimumSize: const Size(45, 30),
elevation: 0,
),
child: SvgPicture.asset(
Assets.settings,
height: 13,
color: ColorsManager.blue1,
),
),
),
);

View File

@ -66,11 +66,13 @@ class PaginationButtonsWidget extends StatelessWidget {
height: 30,
alignment: Alignment.center,
decoration: BoxDecoration(
color: i == currentPage
? ColorsManager.dialogBlueTitle
: Colors.grey[300],
borderRadius: BorderRadius.circular(8),
),
color: i == currentPage
? ColorsManager.dialogBlueTitle
: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: ColorsManager.lightGrayBorderColor,
)),
child: Text(
'$i',
style: TextStyle(
@ -134,11 +136,15 @@ class PaginationButtonsWidget extends StatelessWidget {
child: GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
width: 30,
height: 30,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(8),
),
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: ColorsManager.lightGrayBorderColor,
)),
child: Text(
label,
style: const TextStyle(

View File

@ -24,20 +24,37 @@ class RowOfButtonsTitleWidget extends StatelessWidget {
children: [
Row(
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
offset: Offset.zero,
blurRadius: 3,
spreadRadius: 0,
color: ColorsManager.timePickerColor.withValues(
alpha: 0.3,
),
)
],
),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.zero,
minimumSize: const Size(50, 40),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
child: SvgPicture.asset(
Assets.backButtonIcon,
height: 15,
),
onPressed: () {
pageController.jumpToPage(1);
}),
child: SvgPicture.asset(
Assets.backButtonIcon,
height: 15,
),
onPressed: () {
pageController.jumpToPage(1);
}),
),
const SizedBox(
width: 10,
),

View File

@ -1,32 +1,42 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/setup_bookable_spaces_bloc/setup_bookable_spaces_bloc.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/steps_cubit/cubit/steps_cubit.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/buttons_divider_bottom_dialog_widget.dart';
class NextFirstStepButton extends StatelessWidget {
final List<BookableSpacemodel> selectedSpaces;
const NextFirstStepButton({
super.key,
required this.selectedSpaces,
});
@override
Widget build(BuildContext context) {
return ButtonsDividerBottomDialogWidget(
title: 'Next',
onNextPressed: selectedSpaces.isEmpty
? null
: () {
context.read<StepsCubit>().goToNextStep();
context
.read<SetupBookableSpacesBloc>()
.add(CheckConfigurValidityEvent());
},
onCancelPressed: () => context.pop(),
return BlocBuilder<SetupBookableSpacesBloc, SetupBookableSpacesState>(
builder: (context, state) {
return switch (state) {
SetupBookableSpacesInitial() => ButtonsDividerBottomDialogWidget(
title: 'Next',
onNextPressed: null,
onCancelPressed: () => context.pop(),
),
AddNonBookableSpaceIntoBookableState(:final bookableSpaces) ||
RemoveBookableSpaceIntoNonBookableState(:final bookableSpaces) =>
ButtonsDividerBottomDialogWidget(
title: 'Next',
onNextPressed: bookableSpaces.isEmpty
? null
: () {
context.read<StepsCubit>().goToNextStep();
context.read<SetupBookableSpacesBloc>().add(
CheckConfigurValidityEvent(),
);
},
onCancelPressed: () => context.pop(),
),
_ => const SizedBox(),
};
},
);
}
}

View File

@ -9,14 +9,14 @@ import 'package:syncrow_web/utils/color_manager.dart';
class PointsPartWidget extends StatefulWidget {
final BookableSpacemodel? editingBookableSpace;
final TextEditingController pointsController;
const PointsPartWidget({
super.key,
required this.pointsController,
this.editingBookableSpace,
});
final TextEditingController pointsController;
@override
State<PointsPartWidget> createState() => _PointsPartWidgetState();
}
@ -24,26 +24,28 @@ class PointsPartWidget extends StatefulWidget {
class _PointsPartWidgetState extends State<PointsPartWidget> {
@override
void initState() {
super.initState();
if (widget.editingBookableSpace != null) {
widget.pointsController.text =
widget.editingBookableSpace!.spaceConfig!.cost.toString();
}
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<TogglePointsSwitchCubit, TogglePointsSwitchState>(
builder: (context, state) {
builder: (context, switchState) {
final isSwitchOn = switchState is ActivatePointsSwitch;
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
if (state is ActivatePointsSwitch)
if (isSwitchOn)
Text(
'* ',
style: Theme.of(context)
@ -52,87 +54,62 @@ class _PointsPartWidgetState extends State<PointsPartWidget> {
.copyWith(color: Colors.red),
)
else
const SizedBox(
width: 11,
),
const SizedBox(width: 11),
const Text('Points/hrs'),
],
),
Transform.scale(
scale: 0.7,
child: Switch(
trackOutlineColor: WidgetStateProperty.resolveWith<Color>(
(Set<WidgetState> states) {
return ColorsManager.whiteColors;
}),
activeTrackColor: ColorsManager.blueColor,
inactiveTrackColor: ColorsManager.grayBorder,
thumbColor: WidgetStateProperty.resolveWith<Color>(
(Set<WidgetState> states) {
return ColorsManager.whiteColors;
}),
value: state is ActivatePointsSwitch,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
trackOutlineColor:
WidgetStateProperty.all(ColorsManager.whiteColors),
activeTrackColor: ColorsManager.dialogBlueTitle,
inactiveTrackColor: ColorsManager.lightGrayBorderColor,
thumbColor:
WidgetStateProperty.all(ColorsManager.whiteColors),
value: isSwitchOn,
onChanged: (value) {
final toggleCubit =
context.read<TogglePointsSwitchCubit>();
final bloc = context.read<SetupBookableSpacesBloc>();
final updatedCost = value ? -1 : 0;
final switchCubit =
context.read<TogglePointsSwitchCubit>();
if (value) {
switchCubit.activateSwitch();
toggleCubit.activateSwitch();
} else {
switchCubit.unActivateSwitch();
toggleCubit.unActivateSwitch();
widget.pointsController.clear();
}
for (int i = 0;
i < bloc.selectedBookableSpaces.length;
i++) {
final space = bloc.selectedBookableSpaces[i];
final updatedConfig =
space.spaceConfig?.copyWith(cost: updatedCost);
final updatedSpace =
space.copyWith(spaceConfig: updatedConfig);
bloc.selectedBookableSpaces[i] = updatedSpace;
}
bloc.add(ChangeCostEvent(cost: updatedCost));
bloc.add(CheckConfigurValidityEvent());
},
),
)
),
],
),
const SizedBox(
height: 5,
),
if (state is ActivatePointsSwitch)
const SizedBox(height: 5),
if (isSwitchOn)
SearchUnbookableSpacesWidget(
title: 'Ex: 0',
height: 40,
onChanged: (p0) {
topPadding: 0,
blur: 1,
raduis: 10,
height: 34,
controller: widget.pointsController,
suffix: const SizedBox(),
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onChanged: (_) {
final updatedCost =
int.tryParse(widget.pointsController.text) ?? 0;
final bloc = context.read<SetupBookableSpacesBloc>();
for (var i = 0; i < bloc.selectedBookableSpaces.length; i++) {
final space = bloc.selectedBookableSpaces[i];
final updatedConfig =
space.spaceConfig?.copyWith(cost: updatedCost);
final updatedSpace =
space.copyWith(spaceConfig: updatedConfig);
bloc.selectedBookableSpaces[i] = updatedSpace;
}
context
.read<SetupBookableSpacesBloc>()
.add(CheckConfigurValidityEvent());
.add(ChangeCostEvent(cost: updatedCost));
context.read<SetupBookableSpacesBloc>().add(
CheckConfigurValidityEvent(),
);
},
controller: widget.pointsController,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
suffix: const SizedBox(),
)
else
const SizedBox(),

View File

@ -10,68 +10,68 @@ import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/prese
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/buttons_divider_bottom_dialog_widget.dart';
class SaveSecondStepButton extends StatelessWidget {
final List<BookableSpacemodel> selectedSpaces;
final TextEditingController pointsController;
final bool isEditingMode;
final List<BookableSpacemodel> bookableSpaces;
const SaveSecondStepButton({
super.key,
required this.selectedSpaces,
required this.pointsController,
required this.isEditingMode,
required this.bookableSpaces,
});
@override
Widget build(BuildContext context) {
return BlocConsumer<SendBookableSpacesBloc, SendBookableSpacesState>(
return BlocListener<SendBookableSpacesBloc, SendBookableSpacesState>(
listener: (context, state) {
if (state is SendBookableSpacesSuccess) {
context.read<NonBookableSpacesBloc>().add(CallInitStateEvent());
} else if (state is SendBookableSpacesError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.error),
),
SnackBar(content: Text(state.error)),
);
}
},
builder: (context, state) {
return ButtonsDividerBottomDialogWidget(
title: 'Save',
onNextPressed: state is UnValidSaveButtonState
? null
: () {
if (selectedSpaces.any(
(element) => element.isValid,
)) {
isEditingMode
? callEditLogic(context)
: context.read<SendBookableSpacesBloc>().add(
child: BlocBuilder<SetupBookableSpacesBloc, SetupBookableSpacesState>(
builder: (context, state) {
return ButtonsDividerBottomDialogWidget(
title: 'Save',
onNextPressed: state is UnValidSaveButtonState
? null
: () {
if (bookableSpaces.any((e) => e.isValid)) {
if (isEditingMode) {
callEditLogic(context);
} else {
context.read<SendBookableSpacesBloc>().add(
SendBookableSpacesToApi(
selectedBookableSpaces: context
.read<SetupBookableSpacesBloc>()
.selectedBookableSpaces),
selectedBookableSpaces: bookableSpaces,
),
);
}
},
onCancelPressed: () => context.pop(),
);
},
}
}
},
onCancelPressed: () => context.pop(),
);
},
),
);
}
void callEditLogic(BuildContext context) {
context.read<UpdateBookableSpacesBloc>().add(
UpdateBookableSpace(
onSuccess: () =>
context.read<NonBookableSpacesBloc>().add(CallInitStateEvent()),
updatedParam: UpdateBookableSpaceParam.fromBookableModel(
context
.read<SetupBookableSpacesBloc>()
.selectedBookableSpaces
.first,
print(bookableSpaces.first.spaceConfig!.cost);
if (bookableSpaces.isNotEmpty) {
context.read<UpdateBookableSpacesBloc>().add(
UpdateBookableSpace(
onSuccess: () => context
.read<NonBookableSpacesBloc>()
.add(CallInitStateEvent()),
updatedParam: UpdateBookableSpaceParam.fromBookableModel(
bookableSpaces.first,
),
),
),
);
);
}
}
}

View File

@ -1,22 +1,30 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class SearchUnbookableSpacesWidget extends StatelessWidget {
final String title;
final Widget? suffix;
final double? height;
final double? width;
final double? blur;
final double? raduis;
final double? topPadding;
final TextEditingController? controller;
final List<TextInputFormatter>? inputFormatters;
final void Function(String)? onChanged;
const SearchUnbookableSpacesWidget({
required this.title,
this.controller,
this.blur,
this.onChanged,
this.suffix,
this.height,
this.width,
this.topPadding,
this.raduis,
this.inputFormatters,
super.key,
});
@ -25,16 +33,18 @@ class SearchUnbookableSpacesWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
width: width ?? 480,
height: height ?? 30,
height: height ?? 40,
padding: const EdgeInsets.only(top: 4),
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(12),
boxShadow: const [
borderRadius: BorderRadius.circular(raduis ?? 15),
boxShadow: [
BoxShadow(
color: ColorsManager.shadowOfSearchTextfield,
offset: Offset(0, 4),
blurRadius: 5,
color:
ColorsManager.shadowOfSearchTextfield.withValues(alpha: 0.15),
offset: Offset.zero,
blurRadius: blur ?? 5,
spreadRadius: 0,
),
],
),
@ -43,14 +53,21 @@ class SearchUnbookableSpacesWidget extends StatelessWidget {
inputFormatters: inputFormatters,
onChanged: onChanged,
decoration: InputDecoration(
contentPadding:
const EdgeInsets.symmetric(vertical: 5, horizontal: 15),
contentPadding: EdgeInsets.symmetric(
vertical: topPadding ?? 5,
horizontal: 15,
),
hintText: title,
hintStyle: const TextStyle(color: ColorsManager.hintTextGrey),
border: InputBorder.none,
suffixIcon: suffix ??
const Icon(Icons.search,
size: 20, color: ColorsManager.hintTextGrey),
suffixIcon: Padding(
padding: const EdgeInsets.all(10),
child: suffix ??
SvgPicture.asset(
Assets.searchIcon,
height: 12,
),
),
),
style: const TextStyle(
fontSize: 14,

View File

@ -26,7 +26,7 @@ class StepTwoDetailsWidget extends StatelessWidget {
editingBookableSpace: editingBookableSpace,
),
const SizedBox(
height: 20,
height: 30,
),
BookingPeriodWidget(
editingBookableSpace: editingBookableSpace,

View File

@ -26,7 +26,7 @@ class StepperPartWidget extends StatelessWidget {
Container(
padding: const EdgeInsets.only(left: 3),
alignment: Alignment.centerLeft,
height: 50,
height: 40,
child: const VerticalDivider(
width: 8,
)),
@ -59,7 +59,7 @@ class StepperPartWidget extends StatelessWidget {
Container(
padding: const EdgeInsets.only(left: 3),
alignment: Alignment.centerLeft,
height: 50,
height: 40,
child: const VerticalDivider(
width: 8,
)),

View File

@ -12,7 +12,7 @@ class TimePickerWidget extends StatefulWidget {
required this.onTimePicked,
required this.title,
});
late SetupBookableSpacesBloc setupBookableSpacesBloc;
late final SetupBookableSpacesBloc setupBookableSpacesBloc;
final void Function(TimeOfDay? timePicked) onTimePicked;
@override
State<TimePickerWidget> createState() => _TimePickerWidgetState();
@ -47,13 +47,17 @@ class _TimePickerWidgetState extends State<TimePickerWidget> {
);
},
);
if (tempTime == null) return;
widget.onTimePicked(tempTime);
timePicked = tempTime;
widget.setupBookableSpacesBloc.add(CheckConfigurValidityEvent());
setState(() {});
},
child: Container(
width: 100,
height: 32,
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(

View File

@ -36,9 +36,7 @@ class UnbookableListWidget extends StatelessWidget {
if (index < nonBookableSpaces.data.length) {
return CheckBoxSpaceWidget(
nonBookableSpace: nonBookableSpaces.data[index],
selectedSpaces: context
.read<SetupBookableSpacesBloc>()
.selectedBookableSpaces,
);
} else {
return const Padding(

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/setup_bookable_spaces_bloc/setup_bookable_spaces_bloc.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/custom_checkbox_widget.dart';
class WeekDaysCheckboxRow extends StatefulWidget {
final BookableSpacemodel? editingBookableSpace;
@ -24,6 +25,7 @@ class _WeekDaysCheckboxRowState extends State<WeekDaysCheckboxRow> {
'Sat': false,
'Sun': false,
};
@override
void initState() {
super.initState();
@ -40,52 +42,66 @@ class _WeekDaysCheckboxRowState extends State<WeekDaysCheckboxRow> {
@override
Widget build(BuildContext context) {
return Row(
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _daysChecked.entries.map((entry) {
return Expanded(
child: Row(
children: [
Expanded(
child: Checkbox(
value: entry.value,
onChanged: (newValue) {
setState(() {
_daysChecked[entry.key] = newValue ?? false;
});
children: [
Row(
children: [
Text(
'* ',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: Colors.red),
),
const Text('Days'),
],
),
const SizedBox(height: 20),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: _daysChecked.entries.map((entry) {
return Expanded(
child: Row(
children: [
CustomCheckboxWidget(
outHeight: 16,
outWidth: 16,
value: entry.value,
onChanged: (newValue) {
setState(() {
_daysChecked[entry.key] = newValue ?? false;
});
final selectedDays = _daysChecked.entries
.where((e) => e.value)
.map((e) => e.key)
.toList();
final selectedDays = _daysChecked.entries
.where((e) => e.value)
.map((e) => e.key)
.toList();
final bloc = context.read<SetupBookableSpacesBloc>();
for (int i = 0;
i < bloc.selectedBookableSpaces.length;
i++) {
final space = bloc.selectedBookableSpaces[i];
final updatedConfig = space.spaceConfig
?.copyWith(bookableDays: selectedDays);
final updatedSpace =
space.copyWith(spaceConfig: updatedConfig);
bloc.selectedBookableSpaces[i] = updatedSpace;
}
bloc.add(CheckConfigurValidityEvent());
},
),
context.read<SetupBookableSpacesBloc>().add(
AddBookableDaysEvent(bookableDays: selectedDays),
);
context.read<SetupBookableSpacesBloc>().add(
CheckConfigurValidityEvent(),
);
},
),
const SizedBox(width: 8),
Expanded(
child: Text(
entry.key,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w400,
),
),
),
],
),
Expanded(
child: Text(
entry.key,
style: const TextStyle(fontSize: 10),
)),
],
),
);
}).toList(),
);
}).toList(),
),
],
);
}
}

View File

@ -6,6 +6,7 @@ import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_
import 'package:syncrow_web/pages/device_managment/garage_door/helper/garage_door_helper.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/schedule_view/schedule_garage_view.dart';
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart';
import 'package:syncrow_web/pages/device_managment/shared/icon_name_status_container.dart';
import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
@ -94,11 +95,18 @@ class GarageDoorControlView extends StatelessWidget
FetchGarageDoorSchedulesEvent(
deviceId: deviceId, category: 'doorcontact_state'),
);
showDialog(
showDialog<void>(
context: context,
builder: (ctx) => BlocProvider.value(
value: BlocProvider.of<GarageDoorBloc>(context),
child: BuildGarageDoorScheduleView(status: status),
child: BuildScheduleView(
deviceUuid: deviceId,
category: 'Timer',
code: 'doorcontact_state',
countdownCode: 'Timer',
deviceType: 'GD',
),
));
},
name: 'Scheduling',

View File

@ -287,7 +287,8 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
try {
if (state is ScheduleLoaded) {
Status status = Status(code: '', value: '');
if (event.deviceType == 'CUR_2') {
if (event.deviceType == 'CUR_2' ||
event.deviceType == 'GD' ) {
status = status.copyWith(
code: 'control',
value: event.functionOn == true ? 'open' : 'close');

View File

@ -69,7 +69,7 @@ class CountdownModeButtons extends StatelessWidget {
countDownCode: countDownCode),
);
},
backgroundColor: ColorsManager.primaryColorWithOpacity,
backgroundColor: ColorsManager.secondaryColor,
child: const Text('Save'),
),
),

View File

@ -63,7 +63,7 @@ class InchingModeButtons extends StatelessWidget {
),
);
},
backgroundColor: ColorsManager.primaryColor,
backgroundColor: ColorsManager.secondaryColor,
child: const Text('Save'),
),
),

View File

@ -31,11 +31,12 @@ class BuildScheduleView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => ScheduleBloc(deviceId: deviceUuid,)
create: (_) => ScheduleBloc(
deviceId: deviceUuid,
)
..add(ScheduleGetEvent(category: category))
..add(ScheduleFetchStatusEvent(
deviceId: deviceUuid,
countdownCode: countdownCode ?? '')),
deviceId: deviceUuid, countdownCode: countdownCode ?? '')),
child: Dialog(
backgroundColor: Colors.white,
insetPadding: const EdgeInsets.all(20),
@ -56,7 +57,7 @@ class BuildScheduleView extends StatelessWidget {
children: [
const ScheduleHeader(),
const SizedBox(height: 20),
if (deviceType == 'CUR_2')
if (deviceType == 'CUR_2' || deviceType == 'GD')
const SizedBox()
else
ScheduleModeSelector(
@ -76,8 +77,7 @@ class BuildScheduleView extends StatelessWidget {
category: category,
time: '',
function: Status(
code: code.toString(),
value: true),
code: code.toString(), value: true),
days: [],
),
isEdit: false,
@ -96,7 +96,7 @@ class BuildScheduleView extends StatelessWidget {
}
},
),
if (deviceType != 'CUR_2')
if (deviceType != 'CUR_2'|| deviceType != 'GD')
if (state.scheduleMode == ScheduleModes.countdown ||
state.scheduleMode == ScheduleModes.inching)
CountdownInchingView(

View File

@ -24,12 +24,13 @@ class ScheduleManagementUI extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 170,
width: 177,
height: 40,
child: DefaultButton(
borderColor: ColorsManager.grayColor.withOpacity(0.5),
padding: 2,
backgroundColor: ColorsManager.graysColor,
borderWidth: 4,
borderColor: ColorsManager.neutralGray,
padding: 8,
backgroundColor: ColorsManager.textFieldGreyColor,
borderRadius: 15,
onPressed: onAddSchedule,
child: Row(

View File

@ -39,7 +39,7 @@ class ScheduleModeButtons extends StatelessWidget {
borderRadius: 8,
height: 40,
onPressed: onSave,
backgroundColor: ColorsManager.primaryColorWithOpacity,
backgroundColor: ColorsManager.secondaryColor,
child: const Text('Save'),
),
),

View File

@ -194,7 +194,7 @@ class _ScheduleTableView extends StatelessWidget {
child: Text(_getSelectedDays(
ScheduleModel.parseSelectedDays(schedule.days)))),
Center(child: Text(formatIsoStringToTime(schedule.time, context))),
if (deviceType == 'CUR_2')
if (deviceType == 'CUR_2' || deviceType == 'GD')
Center(
child: Text(schedule.function.value == true ? 'open' : 'close'))
else

View File

@ -23,7 +23,7 @@ class ScheduleDialogHelper {
required String deviceType,
}) {
bool temp;
if (deviceType == 'CUR_2') {
if (deviceType == 'CUR_2' || deviceType == 'GD') {
temp = schedule!.function.value == 'open' ? true : false;
} else {
temp = schedule!.function.value;
@ -116,7 +116,7 @@ class ScheduleDialogHelper {
ScheduleModeButtons(
onSave: () {
dynamic temp;
if (deviceType == 'CUR_2') {
if (deviceType == 'CUR_2' || deviceType == 'GD') {
temp = functionOn! ? 'open' : 'close';
} else {
temp = functionOn;
@ -202,18 +202,23 @@ class ScheduleDialogHelper {
),
const SizedBox(width: 10),
Radio<bool>(
activeColor: ColorsManager.secondaryColor,
focusColor: ColorsManager.secondaryColor,
value: true,
groupValue: isOn,
onChanged: (val) => onChanged(true),
),
Text(categor == 'CUR_2' ? 'open' : 'On'),
Text(categor == 'CUR_2' || categor == 'GD' ? 'open' : 'On'),
const SizedBox(width: 10),
Radio<bool>(
activeColor: ColorsManager.secondaryColor,
focusColor: ColorsManager.secondaryColor,
value: false,
groupValue: isOn,
onChanged: (val) => onChanged(false),
),
Text(categor == 'CUR_2' ? 'close' : 'Off'),
Text(categor == 'CUR_2' || categor == 'GD' ? 'close' : 'Off'),
],
);
}

View File

@ -4,9 +4,7 @@ import 'package:syncrow_web/services/api/http_interceptor.dart';
import 'package:syncrow_web/services/api/http_service.dart';
final GetIt serviceLocator = GetIt.instance;
//setupLocator() // to search for dependency injection in flutter
initialSetup() {
void initialSetup() {
serviceLocator.registerSingleton<HTTPInterceptor>(HTTPInterceptor());
//Base classes
serviceLocator.registerSingleton<Dio>(HTTPService.setupDioClient());
}

53
lib/syncrow_app.dart Normal file
View File

@ -0,0 +1,53 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
import 'package:syncrow_web/utils/app_routes.dart';
import 'package:syncrow_web/utils/navigation_service.dart';
import 'package:syncrow_web/utils/theme/theme.dart';
class SyncrowApp extends StatelessWidget {
const SyncrowApp({super.key});
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<CreateRoutineBloc>(
create: (context) => CreateRoutineBloc(),
),
BlocProvider(
create: (context) => HomeBloc()..add(const FetchUserInfo()),
),
BlocProvider<VisitorPasswordBloc>(
create: (context) => VisitorPasswordBloc(),
),
BlocProvider<RoutineBloc>(
create: (context) => RoutineBloc(),
),
BlocProvider<SpaceTreeBloc>(
create: (context) => SpaceTreeBloc(),
),
],
child: MaterialApp.router(
debugShowCheckedModeBanner: false,
scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.unknown,
},
),
key: NavigationService.navigatorKey,
theme: myTheme,
routerConfig: AppRoutes.router,
),
);
}
}

View File

@ -1,17 +1,31 @@
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/pages/access_management/view/access_management.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/views/analytics_page.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/pages/auth/view/login_page.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/view/device_managment_page.dart';
import 'package:syncrow_web/pages/home/view/home_page.dart';
import 'package:syncrow_web/pages/roles_and_permission/view/roles_and_permission_page.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/view/spaces_management_page.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/views/space_management_page.dart';
import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart';
class AppRoutes {
static List<GoRoute> getRoutes() {
return [
abstract final class AppRoutes {
const AppRoutes._();
static final GoRouter router = GoRouter(
initialLocation: RoutesConst.auth,
redirect: (context, state) async {
final checkToken = await AuthBloc.getTokenAndValidate();
final loggedIn = checkToken == 'Success';
final goingToLogin = state.uri.toString() == RoutesConst.auth;
if (!loggedIn && !goingToLogin) return RoutesConst.auth;
if (loggedIn && goingToLogin) return RoutesConst.home;
return null;
},
routes: [
GoRoute(
path: RoutesConst.auth,
builder: (context, state) => const LoginPage(),
@ -43,6 +57,6 @@ class AppRoutes {
name: 'analytics',
builder: (context, state) => const AnalyticsPage(),
),
];
}
],
);
}

View File

@ -38,6 +38,7 @@ abstract class ColorsManager {
static const Color lightGrayColor = Color(0xB2999999);
static const Color grayBorder = Color(0xFFCFCFCF);
static const Color textGray = Color(0xffD5D5D5);
static const Color titleGray = Color(0xB2999999);
static const Color btnColor = Color(0xFF00008B);
static const Color blueColor = Color(0xFF0036E6);
static const Color boxColor = Color(0xFFF5F6F7);
@ -90,4 +91,6 @@ abstract class ColorsManager {
static const Color shadowOfSearchTextfield = Color(0x26000000);
static const Color hintTextGrey = Colors.grey;
static const Color shadowOfDetailsContainer = Color(0x40000000);
static const Color checkBoxBorderGray = Color(0xffD0D0D0);
static const Color timePickerColor = Color(0xff000000);
}

View File

@ -140,6 +140,7 @@ abstract class ApiEndpoints {
static const String saveSchedule = '/schedule/{deviceUuid}';
////booking System
static const String bookableSpaces = '/bookable-spaces';
static const String getCalendarEvents = '/api';