mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-08-25 15:59:40 +00:00
Compare commits
73 Commits
b1b72fa8aa
...
SP-1722-FE
Author | SHA1 | Date | |
---|---|---|---|
ae3eb6fca8 | |||
f341dcd482 | |||
e98b091253 | |||
77d6d822cb | |||
f1aab13263 | |||
a56e422bf5 | |||
97530dd351 | |||
a57b6e0853 | |||
8f71fcb96a | |||
845397e819 | |||
2077ef053f | |||
d21850edc8 | |||
85f53ed1f2 | |||
5fde74fc7d | |||
994efc302b | |||
c403048da7 | |||
04b7a506be | |||
19ddf443a9 | |||
3779176978 | |||
7c5bca35fc | |||
aed3004a31 | |||
8ae4e561c2 | |||
4241d11cb6 | |||
ef8c9efff0 | |||
c59d2b7fd6 | |||
71f0da9299 | |||
e6d9000ee2 | |||
7dc103f904 | |||
e4c41bab90 | |||
7f3dfebf15 | |||
0de882d43b | |||
6a737e5d43 | |||
c323d88790 | |||
68153e41ed | |||
6a7174b929 | |||
59058cf2d2 | |||
b6d4084ca7 | |||
2b110b7c91 | |||
066fe4bc95 | |||
85d65b2d96 | |||
a64a9f1d12 | |||
94f9c1beea | |||
a7487f5434 | |||
c0b74162e9 | |||
dfd8c5fa31 | |||
60b8ee8b50 | |||
9d60f913eb | |||
40251b846b | |||
1323bceca1 | |||
0eb4652f26 | |||
460639b681 | |||
35c8a73156 | |||
ce65b068ff | |||
96f107f972 | |||
a3a7937021 | |||
9bf715501b | |||
c65f4a7fab | |||
7af8887d4f | |||
2f89c3486c | |||
5589e5b587 | |||
d3bd363b70 | |||
3a4fce966c | |||
6bdd28ec57 | |||
652163fdae | |||
b738596b50 | |||
0ad562b6ce | |||
995ce480cb | |||
076c80fe44 | |||
b41733ee40 | |||
fe090175e3 | |||
85544c69f8 | |||
9f71bbff63 | |||
deb227034a |
4
assets/icons/search_icon.svg
Normal file
4
assets/icons/search_icon.svg
Normal 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 |
9
assets/images/4_sceen_switch.svg
Normal file
9
assets/images/4_sceen_switch.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M38.0142 39.2553L35.3682 40H20.9308H19.9999H19.0691H1.24111C0.555643 40 0 39.4444 0 38.7589V1.24111C0 0.555643 0.555643 0 1.24111 0H19.0682H20.1226H20.9543H35.4255L38.2625 1.24111C38.9479 1.24111 39.5036 1.79675 39.5036 2.48221L39.2553 38.0142C39.2553 38.6997 38.6997 39.2553 38.0142 39.2553Z" fill="#E9E9E9"/>
|
||||
<path d="M38.7585 0H35.0352C35.7206 0 36.2763 0.555643 36.2763 1.24111V38.7589C36.2763 39.4444 35.7206 40 35.0352 40H38.7585C39.4439 40 39.9996 39.4444 39.9996 38.7589V1.24111C39.9996 0.555643 39.4439 0 38.7585 0Z" fill="#D1D1D1"/>
|
||||
<path opacity="0.6" d="M12.0283 31.8319V33.3212C12.0283 34.0067 11.6086 34.5623 11.0908 34.5623H6.96582C6.44804 34.5623 6.02832 34.0067 6.02832 33.3212V31.8319C6.02832 31.1465 6.44804 30.5908 6.96582 30.5908H11.0908C11.6086 30.5908 12.0283 31.1465 12.0283 31.8319Z" fill="#023DFE" fill-opacity="0.5"/>
|
||||
<path opacity="0.6" d="M12.0283 7.24109V8.73042C12.0283 9.41588 11.6086 9.97153 11.0908 9.97153H6.96582C6.44804 9.97153 6.02832 9.41588 6.02832 8.73042V7.24109C6.02832 6.55563 6.44804 5.99998 6.96582 5.99998H11.0908C11.6086 5.99998 12.0283 6.55563 12.0283 7.24109Z" fill="#023DFE" fill-opacity="0.5"/>
|
||||
<path opacity="0.6" d="M26.0283 31.8319V33.3212C26.0283 34.0067 26.448 34.5623 26.9658 34.5623H31.0908C31.6086 34.5623 32.0283 34.0067 32.0283 33.3212V31.8319C32.0283 31.1465 31.6086 30.5908 31.0908 30.5908H26.9658C26.448 30.5908 26.0283 31.1465 26.0283 31.8319Z" fill="#023DFE" fill-opacity="0.5"/>
|
||||
<path opacity="0.6" d="M26.0283 7.24109V8.73042C26.0283 9.41588 26.448 9.97153 26.9658 9.97153H31.0908C31.6086 9.97153 32.0283 9.41588 32.0283 8.73042V7.24109C32.0283 6.55563 31.6086 5.99998 31.0908 5.99998H26.9658C26.448 5.99998 26.0283 6.55563 26.0283 7.24109Z" fill="#023DFE" fill-opacity="0.5"/>
|
||||
<path d="M19.0693 0H20.931V40H19.0693V0Z" fill="#D1D1D1"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
12
assets/images/6_sceen_switch.svg
Normal file
12
assets/images/6_sceen_switch.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M38.0142 39.2553L35.3682 40H20.9308H19.9999H19.0691H1.24111C0.555643 40 0 39.4444 0 38.7589V1.24111C0 0.555651 0.555643 7.62939e-06 1.24111 7.62939e-06H19.0682H20.1226H20.9543H35.4255L38.2625 1.24111C38.9479 1.24111 39.5036 1.79676 39.5036 2.48222L39.2553 38.0142C39.2553 38.6997 38.6997 39.2553 38.0142 39.2553Z" fill="#E9E9E9"/>
|
||||
<path d="M38.7585 0H35.0352C35.7206 0 36.2763 0.555643 36.2763 1.24111V38.7589C36.2763 39.4444 35.7206 40 35.0352 40H38.7585C39.4439 40 39.9996 39.4444 39.9996 38.7589V1.24111C39.9996 0.555643 39.4439 0 38.7585 0Z" fill="#D1D1D1"/>
|
||||
<path opacity="0.6" d="M8.64062 31.8319V33.3212C8.64062 34.0067 8.22091 34.5623 7.70312 34.5623H3.57813C3.06034 34.5623 2.64062 34.0067 2.64062 33.3212V31.8319C2.64062 31.1464 3.06034 30.5908 3.57813 30.5908H7.70312C8.22091 30.5908 8.64062 31.1464 8.64062 31.8319Z" fill="#023DFE" fill-opacity="0.5"/>
|
||||
<path opacity="0.6" d="M8.64062 7.24109V8.73042C8.64062 9.41588 8.22091 9.97152 7.70312 9.97152H3.57813C3.06034 9.97152 2.64062 9.41588 2.64062 8.73042V7.24109C2.64062 6.55563 3.06034 5.99998 3.57813 5.99998H7.70312C8.22091 5.99998 8.64062 6.55563 8.64062 7.24109Z" fill="#023DFE" fill-opacity="0.5"/>
|
||||
<path opacity="0.6" d="M27.6406 31.8319V33.3212C27.6406 34.0067 28.0603 34.5623 28.5781 34.5623H32.7031C33.2209 34.5623 33.6406 34.0067 33.6406 33.3212V31.8319C33.6406 31.1464 33.2209 30.5908 32.7031 30.5908H28.5781C28.0603 30.5908 27.6406 31.1464 27.6406 31.8319Z" fill="#023DFE" fill-opacity="0.5"/>
|
||||
<path opacity="0.6" d="M27.6406 7.24109V8.73042C27.6406 9.41588 28.0603 9.97152 28.5781 9.97152H32.7031C33.2209 9.97152 33.6406 9.41588 33.6406 8.73042V7.24109C33.6406 6.55563 33.2209 5.99998 32.7031 5.99998H28.5781C28.0603 5.99998 27.6406 6.55563 27.6406 7.24109Z" fill="#023DFE" fill-opacity="0.5"/>
|
||||
<path opacity="0.6" d="M15.0625 31.8319V33.3212C15.0625 34.0067 15.4822 34.5623 16 34.5623H20.125C20.6428 34.5623 21.0625 34.0067 21.0625 33.3212V31.8319C21.0625 31.1464 20.6428 30.5908 20.125 30.5908H16C15.4822 30.5908 15.0625 31.1464 15.0625 31.8319Z" fill="#023DFE" fill-opacity="0.5"/>
|
||||
<path opacity="0.6" d="M15.0625 7.24109V8.73042C15.0625 9.41588 15.4822 9.97152 16 9.97152H20.125C20.6428 9.97152 21.0625 9.41588 21.0625 8.73042V7.24109C21.0625 6.55563 20.6428 5.99998 20.125 5.99998H16C15.4822 5.99998 15.0625 6.55563 15.0625 7.24109Z" fill="#023DFE" fill-opacity="0.5"/>
|
||||
<path d="M23.125 0H24.9867V40H23.125V0Z" fill="#D1D1D1"/>
|
||||
<path d="M11.1719 0H13.0335V40H11.1719V0Z" fill="#D1D1D1"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
@ -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());
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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 => [];
|
||||
}
|
@ -19,14 +19,44 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class BookingPage extends StatefulWidget {
|
||||
const BookingPage({super.key});
|
||||
final PageController? pageController;
|
||||
const BookingPage({super.key, this.pageController});
|
||||
|
||||
@override
|
||||
State<BookingPage> createState() => _BookingPageState();
|
||||
}
|
||||
|
||||
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(widget.pageController),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _BookingPageContent extends StatefulWidget {
|
||||
final PageController? pageController;
|
||||
const _BookingPageContent(this.pageController);
|
||||
|
||||
@override
|
||||
State<_BookingPageContent> createState() => _BookingPageContentState();
|
||||
}
|
||||
|
||||
class _BookingPageContentState extends State<_BookingPageContent> {
|
||||
late EventController _eventController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -40,7 +70,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 +90,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) {
|
||||
context.read<CalendarEventsBloc>().add(const 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: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
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());
|
||||
},
|
||||
);
|
||||
SvgTextButton(
|
||||
svgAsset: Assets.homeIcon,
|
||||
label: 'Manage Bookable Spaces',
|
||||
onPressed: () {
|
||||
widget.pageController!.jumpToPage(2);
|
||||
},
|
||||
),
|
||||
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,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -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: [
|
||||
|
@ -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);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ class WeekNavigation extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 250,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.circleRolesBackground,
|
||||
@ -32,6 +33,8 @@ class WeekNavigation extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
iconSize: 15,
|
||||
@ -40,12 +43,16 @@ class WeekNavigation extends StatelessWidget {
|
||||
onPressed: onPreviousWeek,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
_getMonthYearText(weekStart, weekEnd),
|
||||
style: const TextStyle(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
SizedBox(
|
||||
width: 120,
|
||||
child: Text(
|
||||
_getMonthYearText(weekStart, weekEnd),
|
||||
style: const TextStyle(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
|
@ -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) {
|
||||
|
@ -1,57 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/icon_text_button.dart';
|
||||
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class BookingPage extends StatelessWidget {
|
||||
final PageController pageController;
|
||||
const BookingPage({
|
||||
super.key,
|
||||
required this.pageController,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
color: Colors.blueGrey[100],
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'Side bar',
|
||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
)),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: SizedBox(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
SvgTextButton(
|
||||
svgAsset: Assets.homeIcon,
|
||||
label: 'Manage Bookable Spaces',
|
||||
onPressed: () {
|
||||
pageController.jumpToPage(2);
|
||||
}),
|
||||
const SizedBox(width: 20),
|
||||
SvgTextButton(
|
||||
svgAsset: Assets.groupIcon,
|
||||
label: 'Manage Users',
|
||||
onPressed: () {})
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
))
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
class NonBookableSpacesParams {
|
||||
final int currentPage;
|
||||
String? searchedWords;
|
||||
final String? searchedWords;
|
||||
NonBookableSpacesParams({
|
||||
required this.currentPage,
|
||||
this.searchedWords,
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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});
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
}),
|
||||
],
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
),
|
||||
|
@ -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(),
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -26,7 +26,7 @@ class StepTwoDetailsWidget extends StatelessWidget {
|
||||
editingBookableSpace: editingBookableSpace,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
height: 30,
|
||||
),
|
||||
BookingPeriodWidget(
|
||||
editingBookableSpace: editingBookableSpace,
|
||||
|
@ -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,
|
||||
)),
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/view/booking_page.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/screens/manage_bookable_spaces_screen.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/booking_page.dart' hide BookingPage;
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/booking_page.dart';
|
||||
import 'package:syncrow_web/pages/access_management/view/access_overview_content.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
@ -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',
|
||||
|
@ -100,6 +100,7 @@ class _DeviceItem extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return DeviceControlsContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
@ -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');
|
||||
|
@ -69,7 +69,7 @@ class CountdownModeButtons extends StatelessWidget {
|
||||
countDownCode: countDownCode),
|
||||
);
|
||||
},
|
||||
backgroundColor: ColorsManager.primaryColorWithOpacity,
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
child: const Text('Save'),
|
||||
),
|
||||
),
|
||||
|
@ -63,7 +63,7 @@ class InchingModeButtons extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
},
|
||||
backgroundColor: ColorsManager.primaryColor,
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
child: const Text('Save'),
|
||||
),
|
||||
),
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -39,7 +39,7 @@ class ScheduleModeButtons extends StatelessWidget {
|
||||
borderRadius: 8,
|
||||
height: 40,
|
||||
onPressed: onSave,
|
||||
backgroundColor: ColorsManager.primaryColorWithOpacity,
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
child: const Text('Save'),
|
||||
),
|
||||
),
|
||||
|
@ -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
|
||||
|
@ -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'),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'space_tree_dropdown_bloc.dart';
|
||||
|
||||
class DropdownMenuContent extends StatefulWidget {
|
||||
@ -75,7 +76,8 @@ class _DropdownMenuContentState extends State<DropdownMenuContent> {
|
||||
child: TextFormField(
|
||||
controller: _searchController,
|
||||
onChanged: _handleSearch,
|
||||
style: const TextStyle(fontSize: 14, color: Colors.black),
|
||||
style: const TextStyle(
|
||||
fontSize: 14, color: ColorsManager.blackColor),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search for space...',
|
||||
prefixIcon: const Icon(Icons.search, size: 20),
|
||||
@ -84,6 +86,12 @@ class _DropdownMenuContentState extends State<DropdownMenuContent> {
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: const BorderSide(
|
||||
color: ColorsManager.dropDownSelectBlue,
|
||||
),
|
||||
),
|
||||
isDense: true,
|
||||
),
|
||||
),
|
||||
@ -117,7 +125,9 @@ class _DropdownMenuContentState extends State<DropdownMenuContent> {
|
||||
title: Text(
|
||||
community.name,
|
||||
style: TextStyle(
|
||||
color: isSelected ? Colors.blue : Colors.black,
|
||||
color: isSelected
|
||||
? ColorsManager.dropDownSelectBlue
|
||||
: ColorsManager.blackColor,
|
||||
fontWeight:
|
||||
isSelected ? FontWeight.bold : FontWeight.normal,
|
||||
),
|
||||
|
@ -52,7 +52,7 @@ class SpaceDropdown extends StatelessWidget {
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: selectedValue == space.uuid
|
||||
? ColorsManager.dialogBlueTitle
|
||||
? ColorsManager.dropDownSelectBlue
|
||||
: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
@ -61,7 +61,7 @@ class SpaceDropdown extends StatelessWidget {
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 12,
|
||||
color: selectedValue == space.uuid
|
||||
? ColorsManager.dialogBlueTitle
|
||||
? ColorsManager.dropDownSelectBlue
|
||||
: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
|
@ -9,6 +9,8 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/presen
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/data/services/remote_products_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/presentation/bloc/products_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/data/services/remote_reorder_spaces_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/presentation/bloc/reorder_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
|
||||
@ -25,15 +27,16 @@ class SpaceManagementPage extends StatefulWidget {
|
||||
|
||||
class _SpaceManagementPageState extends State<SpaceManagementPage> {
|
||||
late final CommunitiesBloc communitiesBloc;
|
||||
late final HTTPService _httpService;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_httpService = HTTPService();
|
||||
communitiesBloc = CommunitiesBloc(
|
||||
communitiesService: DebouncedCommunitiesService(
|
||||
RemoteCommunitiesService(HTTPService()),
|
||||
RemoteCommunitiesService(_httpService),
|
||||
),
|
||||
)..add(const LoadCommunities(LoadCommunitiesParam()));
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@ -50,13 +53,18 @@ class _SpaceManagementPageState extends State<SpaceManagementPage> {
|
||||
BlocProvider(
|
||||
create: (context) => SpaceDetailsBloc(
|
||||
UniqueSpaceDetailsSpacesDecoratorService(
|
||||
RemoteSpaceDetailsService(httpService: HTTPService()),
|
||||
RemoteSpaceDetailsService(httpService: _httpService),
|
||||
),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => ProductsBloc(
|
||||
RemoteProductsService(HTTPService()),
|
||||
RemoteProductsService(_httpService),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => ReorderSpacesBloc(
|
||||
RemoteReorderSpacesService(_httpService),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart';
|
||||
@ -11,6 +13,8 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/params/reorder_spaces_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/presentation/bloc/reorder_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
@ -49,13 +53,23 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
duration: const Duration(milliseconds: 150),
|
||||
);
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
_centerOnTree();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant CommunityStructureCanvas oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.selectedSpace == null) return;
|
||||
if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) {
|
||||
if (oldWidget.community.uuid != widget.community.uuid) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
_centerOnTree(animate: true);
|
||||
}
|
||||
});
|
||||
} else if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
_animateToSpace(widget.selectedSpace);
|
||||
@ -149,6 +163,60 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
_runAnimation(matrix);
|
||||
}
|
||||
|
||||
void _centerOnTree({bool animate = false}) {
|
||||
if (_positions.isEmpty) {
|
||||
if (animate) {
|
||||
_runAnimation(Matrix4.identity());
|
||||
} else {
|
||||
_transformationController.value = Matrix4.identity();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var minX = double.infinity;
|
||||
var maxX = double.negativeInfinity;
|
||||
var minY = double.infinity;
|
||||
var maxY = double.negativeInfinity;
|
||||
|
||||
_positions.forEach((uuid, offset) {
|
||||
final cardWidth = _cardWidths[uuid] ?? _minCardWidth;
|
||||
minX = min(minX, offset.dx);
|
||||
maxX = max(maxX, offset.dx + cardWidth);
|
||||
minY = min(minY, offset.dy);
|
||||
maxY = max(maxY, offset.dy + _cardHeight);
|
||||
});
|
||||
|
||||
if (!minX.isFinite || !maxX.isFinite || !minY.isFinite || !maxY.isFinite) {
|
||||
return;
|
||||
}
|
||||
|
||||
final treeWidth = maxX - minX;
|
||||
final treeHeight = maxY - minY;
|
||||
|
||||
final viewSize = context.size;
|
||||
if (viewSize == null) return;
|
||||
|
||||
final scaleX = viewSize.width / treeWidth;
|
||||
final scaleY = viewSize.height / treeHeight;
|
||||
final scale = min(scaleX, scaleY).clamp(0.5, 1.0) * 0.9;
|
||||
|
||||
final treeCenterX = minX + treeWidth / 2;
|
||||
final treeCenterY = minY + treeHeight / 2;
|
||||
|
||||
final x = -treeCenterX * scale + viewSize.width / 2;
|
||||
final y = -treeCenterY * scale + viewSize.height / 2;
|
||||
|
||||
final matrix = Matrix4.identity()
|
||||
..translate(x, y)
|
||||
..scale(scale);
|
||||
|
||||
if (animate) {
|
||||
_runAnimation(matrix);
|
||||
} else {
|
||||
_transformationController.value = matrix;
|
||||
}
|
||||
}
|
||||
|
||||
void _onReorder(SpaceReorderDataModel data, int newIndex) {
|
||||
final newCommunity = widget.community.copyWith();
|
||||
final children = data.parent?.children ?? newCommunity.spaces;
|
||||
@ -164,6 +232,16 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
context.read<CommunitiesBloc>().add(
|
||||
CommunitiesUpdateCommunity(newCommunity),
|
||||
);
|
||||
|
||||
context.read<ReorderSpacesBloc>().add(
|
||||
ReorderSpacesEvent(
|
||||
ReorderSpacesParam(
|
||||
communityUuid: widget.community.uuid,
|
||||
parentSpaceUuid: data.parent?.uuid ?? '',
|
||||
spaces: children,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onSpaceTapped(SpaceModel? space) {
|
||||
@ -245,6 +323,13 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
final levelXOffset = <int, double>{};
|
||||
_calculateLayout(community.spaces, 0, levelXOffset);
|
||||
|
||||
const horizontalCanvasPadding = 100.0;
|
||||
final originalPositions = Map.of(_positions);
|
||||
_positions.clear();
|
||||
for (final entry in originalPositions.entries) {
|
||||
_positions[entry.key] = entry.value.translate(horizontalCanvasPadding, 0);
|
||||
}
|
||||
|
||||
final selectedSpace = widget.selectedSpace;
|
||||
final highlightedUuids = <String>{};
|
||||
if (selectedSpace != null) {
|
||||
@ -262,7 +347,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
community: widget.community,
|
||||
);
|
||||
|
||||
final createButtonX = levelXOffset[0] ?? 0.0;
|
||||
final createButtonX = (levelXOffset[0] ?? 0.0) + horizontalCanvasPadding;
|
||||
const createButtonY = 0.0;
|
||||
|
||||
widgets.add(
|
||||
@ -294,10 +379,12 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
CommunityModel? community,
|
||||
SpaceModel? parent,
|
||||
}) {
|
||||
const targetWidth = 40.0;
|
||||
final padding = (_horizontalSpacing - targetWidth) / 2;
|
||||
if (spaces.isNotEmpty) {
|
||||
final firstChildPos = _positions[spaces.first.uuid]!;
|
||||
final targetPos = Offset(
|
||||
firstChildPos.dx - (_horizontalSpacing / 4),
|
||||
firstChildPos.dx - padding - targetWidth,
|
||||
firstChildPos.dy,
|
||||
);
|
||||
widgets.add(_buildDropTarget(parent, community, 0, targetPos));
|
||||
@ -379,7 +466,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
);
|
||||
|
||||
final targetPos = Offset(
|
||||
position.dx + cardWidth + (_horizontalSpacing / 4) - 20,
|
||||
position.dx + cardWidth + padding,
|
||||
position.dy,
|
||||
);
|
||||
widgets.add(_buildDropTarget(parent, community, i + 1, targetPos));
|
||||
@ -414,24 +501,33 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
child: DragTarget<SpaceReorderDataModel>(
|
||||
builder: (context, candidateData, rejectedData) {
|
||||
if (_draggedData == null) {
|
||||
return const SizedBox();
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final isTargetForDragged = (_draggedData?.parent?.uuid == parent?.uuid &&
|
||||
_draggedData?.community == null) ||
|
||||
(_draggedData?.community?.uuid == community?.uuid &&
|
||||
_draggedData?.parent == null);
|
||||
final children = parent?.children ?? community?.spaces ?? [];
|
||||
final isSameParent = (_draggedData!.parent?.uuid == parent?.uuid &&
|
||||
_draggedData!.community == null) ||
|
||||
(_draggedData!.community?.uuid == community?.uuid &&
|
||||
_draggedData!.parent == null);
|
||||
|
||||
if (!isTargetForDragged) {
|
||||
return const SizedBox();
|
||||
if (!isSameParent) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Container(
|
||||
final oldIndex =
|
||||
children.indexWhere((s) => s.uuid == _draggedData!.space.uuid);
|
||||
if (oldIndex != -1 && (oldIndex == index || oldIndex == index - 1)) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
width: 40,
|
||||
alignment: Alignment.center,
|
||||
height: _cardHeight,
|
||||
decoration: BoxDecoration(
|
||||
color: context.theme.colorScheme.primary.withValues(
|
||||
alpha: candidateData.isNotEmpty ? 0.7 : 0.3,
|
||||
alpha: candidateData.isNotEmpty ? 0.9 : 0.3,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
@ -454,6 +550,9 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
|
||||
final oldIndex =
|
||||
children.indexWhere((s) => s.uuid == data.data.space.uuid);
|
||||
if (oldIndex == -1) {
|
||||
return true;
|
||||
}
|
||||
if (oldIndex == index || oldIndex == index - 1) {
|
||||
return false;
|
||||
}
|
||||
@ -481,7 +580,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
child: SizedBox(
|
||||
width: context.screenWidth * 5,
|
||||
height: context.screenHeight * 5,
|
||||
child: Stack(children: treeWidgets),
|
||||
child: Stack(clipBehavior: Clip.none, children: treeWidgets),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -7,6 +7,7 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/views/duplicate_space_dialog.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
|
||||
|
||||
class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget {
|
||||
@ -44,7 +45,22 @@ class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
),
|
||||
onDuplicate: (space) {},
|
||||
onDuplicate: (space) => showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => DuplicateSpaceDialog(
|
||||
initialName: space.spaceName,
|
||||
selectedSpaceUuid: space.uuid,
|
||||
selectedCommunityUuid: selectedCommunity.uuid,
|
||||
onSuccess: (spaces) {
|
||||
final updatedCommunity = selectedCommunity.copyWith(
|
||||
spaces: spaces,
|
||||
);
|
||||
context.read<CommunitiesBloc>().add(
|
||||
CommunitiesUpdateCommunity(updatedCommunity),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
onEdit: (space) => SpaceDetailsDialogHelper.showEdit(
|
||||
context,
|
||||
spaceModel: selectedSpace!,
|
||||
|
@ -10,21 +10,26 @@ class SpaceManagementBody extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
return Stack(
|
||||
children: [
|
||||
const SpaceManagementCommunitiesTree(),
|
||||
Expanded(
|
||||
child: BlocBuilder<CommunitiesTreeSelectionBloc,
|
||||
CommunitiesTreeSelectionState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.selectedCommunity != current.selectedCommunity,
|
||||
builder: (context, state) => Visibility(
|
||||
visible: state.selectedCommunity == null,
|
||||
replacement: const SpaceManagementCommunityStructure(),
|
||||
child: const SpaceManagementTemplatesView(),
|
||||
Row(
|
||||
children: [
|
||||
const SizedBox(width: 320),
|
||||
Expanded(
|
||||
child: BlocBuilder<CommunitiesTreeSelectionBloc,
|
||||
CommunitiesTreeSelectionState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.selectedCommunity != current.selectedCommunity,
|
||||
builder: (context, state) => Visibility(
|
||||
visible: state.selectedCommunity == null,
|
||||
replacement: const SpaceManagementCommunityStructure(),
|
||||
child: const SpaceManagementTemplatesView(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SpaceManagementCommunitiesTree(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/presen
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_communities_tree_community_tile.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_sidebar_communities_list.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/widgets/space_management_sidebar_header.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class SpaceManagementCommunitiesTree extends StatefulWidget {
|
||||
@ -44,7 +45,15 @@ class _SpaceManagementCommunitiesTreeState
|
||||
return BlocBuilder<CommunitiesBloc, CommunitiesState>(
|
||||
builder: (context, state) => Container(
|
||||
width: 320,
|
||||
decoration: subSectionContainerDecoration,
|
||||
decoration: subSectionContainerDecoration.copyWith(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: ColorsManager.shadowBlackColor.withValues(alpha: 0.1),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(10, 0),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const SpaceManagementSidebarHeader(),
|
||||
|
@ -0,0 +1,60 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/services/duplicate_space_service.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
|
||||
final class RemoteDuplicateSpaceService implements DuplicateSpaceService {
|
||||
RemoteDuplicateSpaceService(this._httpService);
|
||||
|
||||
final HTTPService _httpService;
|
||||
|
||||
@override
|
||||
Future<List<SpaceModel>> duplicateSpace(DuplicateSpaceParam param) async {
|
||||
try {
|
||||
final response = await _httpService.post(
|
||||
path: await _makeUrl(param),
|
||||
body: param.toJson(),
|
||||
expectedResponseModel: (json) {
|
||||
final response = json as Map<String, dynamic>;
|
||||
final data = response['data'] as List<dynamic>;
|
||||
return data
|
||||
.map((e) => SpaceModel.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
},
|
||||
);
|
||||
|
||||
return response;
|
||||
} on DioException catch (e) {
|
||||
final message = e.response?.data as Map<String, dynamic>?;
|
||||
final error = message?['error'] as Map<String, dynamic>?;
|
||||
final errorMessage = error?['error'] as String? ?? '';
|
||||
throw APIException(errorMessage);
|
||||
} catch (e) {
|
||||
throw APIException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> _makeUrl(DuplicateSpaceParam param) async {
|
||||
final projectUuid = await ProjectManager.getProjectUUID();
|
||||
if (projectUuid == null) {
|
||||
throw APIException('Project UUID is not set');
|
||||
}
|
||||
|
||||
if (param.communityUuid.isEmpty) {
|
||||
throw APIException('Community UUID is not set');
|
||||
}
|
||||
|
||||
if (param.spaceUuid.isEmpty) {
|
||||
throw APIException('Space UUID is not set');
|
||||
}
|
||||
|
||||
return ApiEndpoints.duplicateSpace
|
||||
.replaceAll('{projectUuid}', projectUuid)
|
||||
.replaceAll('{communityUuid}', param.communityUuid)
|
||||
.replaceAll('{spaceUuid}', param.spaceUuid);
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
class DuplicateSpaceParam {
|
||||
final String communityUuid;
|
||||
final String spaceUuid;
|
||||
final String newSpaceName;
|
||||
|
||||
DuplicateSpaceParam({
|
||||
required this.communityUuid,
|
||||
required this.spaceUuid,
|
||||
required this.newSpaceName,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'spaceName': newSpaceName,
|
||||
};
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart';
|
||||
|
||||
abstract interface class DuplicateSpaceService {
|
||||
Future<List<SpaceModel>> duplicateSpace(DuplicateSpaceParam param);
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/services/duplicate_space_service.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
|
||||
part 'duplicate_space_event.dart';
|
||||
part 'duplicate_space_state.dart';
|
||||
|
||||
class DuplicateSpaceBloc extends Bloc<DuplicateSpaceEvent, DuplicateSpaceState> {
|
||||
DuplicateSpaceBloc(
|
||||
this._duplicateSpaceService,
|
||||
) : super(const DuplicateSpaceInitial()) {
|
||||
on<DuplicateSpaceEvent>(_onDuplicateSpaceEvent);
|
||||
}
|
||||
|
||||
final DuplicateSpaceService _duplicateSpaceService;
|
||||
|
||||
Future<void> _onDuplicateSpaceEvent(
|
||||
DuplicateSpaceEvent event,
|
||||
Emitter<DuplicateSpaceState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(const DuplicateSpaceLoading());
|
||||
final result = await _duplicateSpaceService.duplicateSpace(event.param);
|
||||
emit(DuplicateSpaceSuccess(result));
|
||||
} on APIException catch (e) {
|
||||
emit(DuplicateSpaceFailure(e.message));
|
||||
} catch (e) {
|
||||
emit(DuplicateSpaceFailure(e.toString()));
|
||||
} finally {
|
||||
emit(const DuplicateSpaceInitial());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
part of 'duplicate_space_bloc.dart';
|
||||
|
||||
final class DuplicateSpaceEvent extends Equatable {
|
||||
const DuplicateSpaceEvent({required this.param});
|
||||
|
||||
final DuplicateSpaceParam param;
|
||||
|
||||
@override
|
||||
List<Object> get props => [param];
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
part of 'duplicate_space_bloc.dart';
|
||||
|
||||
sealed class DuplicateSpaceState extends Equatable {
|
||||
const DuplicateSpaceState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class DuplicateSpaceInitial extends DuplicateSpaceState {
|
||||
const DuplicateSpaceInitial();
|
||||
}
|
||||
|
||||
final class DuplicateSpaceLoading extends DuplicateSpaceState {
|
||||
const DuplicateSpaceLoading();
|
||||
}
|
||||
|
||||
final class DuplicateSpaceSuccess extends DuplicateSpaceState {
|
||||
const DuplicateSpaceSuccess(this.spaces);
|
||||
|
||||
final List<SpaceModel> spaces;
|
||||
|
||||
@override
|
||||
List<Object> get props => [spaces];
|
||||
}
|
||||
|
||||
final class DuplicateSpaceFailure extends DuplicateSpaceState {
|
||||
const DuplicateSpaceFailure(this.errorMessage);
|
||||
|
||||
final String errorMessage;
|
||||
|
||||
@override
|
||||
List<Object> get props => [errorMessage];
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/common/widgets/app_loading_indicator.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/data/services/remote_duplicate_space_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_dialog_form.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/extension/app_snack_bar.dart';
|
||||
|
||||
class DuplicateSpaceDialog extends StatelessWidget {
|
||||
const DuplicateSpaceDialog({
|
||||
required this.initialName,
|
||||
required this.onSuccess,
|
||||
required this.selectedSpaceUuid,
|
||||
required this.selectedCommunityUuid,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String initialName;
|
||||
final void Function(List<SpaceModel> spaces) onSuccess;
|
||||
final String selectedSpaceUuid;
|
||||
final String selectedCommunityUuid;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => DuplicateSpaceBloc(
|
||||
RemoteDuplicateSpaceService(HTTPService()),
|
||||
),
|
||||
child: BlocListener<DuplicateSpaceBloc, DuplicateSpaceState>(
|
||||
listener: _listener,
|
||||
child: DuplicateSpaceDialogForm(
|
||||
initialName: initialName,
|
||||
selectedSpaceUuid: selectedSpaceUuid,
|
||||
selectedCommunityUuid: selectedCommunityUuid,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _listener(BuildContext context, DuplicateSpaceState state) {
|
||||
switch (state) {
|
||||
case DuplicateSpaceLoading():
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => const AppLoadingIndicator(),
|
||||
);
|
||||
break;
|
||||
|
||||
case DuplicateSpaceFailure(:final errorMessage):
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
context.showFailureSnackbar(errorMessage);
|
||||
break;
|
||||
|
||||
case DuplicateSpaceSuccess(:final spaces):
|
||||
onSuccess.call(spaces);
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
context.showSuccessSnackbar('Space duplicated successfully');
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/domain/params/duplicate_space_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/widgets/duplicate_space_text_field.dart';
|
||||
|
||||
class DuplicateSpaceDialogForm extends StatefulWidget {
|
||||
const DuplicateSpaceDialogForm({
|
||||
required this.initialName,
|
||||
required this.selectedSpaceUuid,
|
||||
required this.selectedCommunityUuid,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String initialName;
|
||||
final String selectedSpaceUuid;
|
||||
final String selectedCommunityUuid;
|
||||
|
||||
@override
|
||||
State<DuplicateSpaceDialogForm> createState() => _DuplicateSpaceDialogFormState();
|
||||
}
|
||||
|
||||
class _DuplicateSpaceDialogFormState extends State<DuplicateSpaceDialogForm> {
|
||||
late final TextEditingController _nameController;
|
||||
bool _isNameValid = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_nameController = TextEditingController(text: '${widget.initialName}(1)');
|
||||
_nameController.addListener(_validateName);
|
||||
}
|
||||
|
||||
void _validateName() => setState(
|
||||
() => _isNameValid = _nameController.text.trim() != widget.initialName,
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const SelectableText('Duplicate Space'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 16,
|
||||
children: [
|
||||
const SelectableText('Enter a new name for the duplicated space:'),
|
||||
DuplicateSpaceTextField(
|
||||
nameController: _nameController,
|
||||
isNameValid: _isNameValid,
|
||||
initialName: widget.initialName,
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Navigator.of(context).pop,
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _isNameValid ? () => _submit(context) : null,
|
||||
child: const Text('Duplicate'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _submit(BuildContext context) {
|
||||
context.read<DuplicateSpaceBloc>().add(
|
||||
DuplicateSpaceEvent(
|
||||
param: DuplicateSpaceParam(
|
||||
newSpaceName: _nameController.text,
|
||||
spaceUuid: widget.selectedSpaceUuid,
|
||||
communityUuid: widget.selectedCommunityUuid,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DuplicateSpaceFailureDialog extends StatelessWidget {
|
||||
const DuplicateSpaceFailureDialog(this.errorMessage, {super.key});
|
||||
|
||||
final String errorMessage;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Failed to duplicate space'),
|
||||
content: Text(errorMessage),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Navigator.of(context).pop,
|
||||
child: const Text('Close'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class DuplicateSpaceTextField extends StatelessWidget {
|
||||
const DuplicateSpaceTextField({
|
||||
required this.nameController,
|
||||
required this.isNameValid,
|
||||
required this.initialName,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final TextEditingController nameController;
|
||||
final bool isNameValid;
|
||||
final String initialName;
|
||||
|
||||
String get _errorText => 'Name must be different from "$initialName"';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextField(
|
||||
controller: nameController,
|
||||
style: context.textTheme.bodyMedium!.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
label: const Text('Space Name'),
|
||||
border: _border(),
|
||||
enabledBorder: _border(),
|
||||
focusedBorder: _border(ColorsManager.primaryColor),
|
||||
errorBorder: _border(context.theme.colorScheme.error),
|
||||
focusedErrorBorder: _border(context.theme.colorScheme.error),
|
||||
errorStyle: context.textTheme.bodyMedium!.copyWith(
|
||||
color: context.theme.colorScheme.error,
|
||||
fontSize: 8,
|
||||
),
|
||||
errorText: isNameValid ? null : _errorText,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
OutlineInputBorder _border([Color? color]) {
|
||||
return OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderSide: BorderSide(
|
||||
color: color ?? ColorsManager.blackColor,
|
||||
width: 0.5,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/params/reorder_spaces_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/services/reorder_spaces_service.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
|
||||
final class RemoteReorderSpacesService implements ReorderSpacesService {
|
||||
RemoteReorderSpacesService(this._httpClient);
|
||||
|
||||
final HTTPService _httpClient;
|
||||
|
||||
@override
|
||||
Future<void> reorderSpaces(ReorderSpacesParam param) async {
|
||||
try {
|
||||
await _httpClient.post(
|
||||
path: await _makeUrl(param),
|
||||
body: param.toJson(),
|
||||
expectedResponseModel: (json) => json,
|
||||
);
|
||||
} on DioException catch (e) {
|
||||
final message = e.response?.data as Map<String, dynamic>?;
|
||||
throw APIException(_getErrorMessageFromBody(message));
|
||||
} catch (e) {
|
||||
throw APIException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
String _getErrorMessageFromBody(Map<String, dynamic>? body) {
|
||||
if (body == null) return 'Failed to delete space';
|
||||
final error = body['error'] as Map<String, dynamic>?;
|
||||
final errorMessage = error?['message'] as String? ?? '';
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
Future<String> _makeUrl(ReorderSpacesParam param) async {
|
||||
final projectUuid = await ProjectManager.getProjectUUID();
|
||||
final communityUuid = param.communityUuid;
|
||||
|
||||
if (projectUuid == null || projectUuid.isEmpty) {
|
||||
throw APIException('Project UUID is not set');
|
||||
}
|
||||
|
||||
if (communityUuid.isEmpty) {
|
||||
throw APIException('Community UUID is not set');
|
||||
}
|
||||
|
||||
if (param.parentSpaceUuid.isEmpty) {
|
||||
throw APIException('Parent Space UUID is not set');
|
||||
}
|
||||
|
||||
return ApiEndpoints.reorderSpaces
|
||||
.replaceAll('{projectUuid}', projectUuid)
|
||||
.replaceAll('{communityUuid}', communityUuid)
|
||||
.replaceAll('{parentSpaceUuid}', param.parentSpaceUuid);
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
|
||||
class ReorderSpacesParam extends Equatable {
|
||||
const ReorderSpacesParam({
|
||||
required this.communityUuid,
|
||||
required this.parentSpaceUuid,
|
||||
required this.spaces,
|
||||
});
|
||||
|
||||
final String communityUuid;
|
||||
final String parentSpaceUuid;
|
||||
final List<SpaceModel> spaces;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [spaces, communityUuid, parentSpaceUuid];
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'spacesUuids': spaces.map((space) => space.uuid).toList(),
|
||||
};
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/params/reorder_spaces_param.dart';
|
||||
|
||||
abstract interface class ReorderSpacesService {
|
||||
Future<void> reorderSpaces(ReorderSpacesParam param);
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/params/reorder_spaces_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/services/reorder_spaces_service.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
|
||||
part 'reorder_spaces_event.dart';
|
||||
part 'reorder_spaces_state.dart';
|
||||
|
||||
class ReorderSpacesBloc extends Bloc<ReorderSpacesEvent, ReorderSpacesState> {
|
||||
ReorderSpacesBloc(
|
||||
this._reorderSpacesService,
|
||||
) : super(const ReorderSpacesInitial()) {
|
||||
on<ReorderSpacesEvent>(_onReorderSpacesEvent);
|
||||
}
|
||||
|
||||
final ReorderSpacesService _reorderSpacesService;
|
||||
|
||||
Future<void> _onReorderSpacesEvent(
|
||||
ReorderSpacesEvent event,
|
||||
Emitter<ReorderSpacesState> emit,
|
||||
) async {
|
||||
emit(const ReorderSpacesLoading());
|
||||
try {
|
||||
await _reorderSpacesService.reorderSpaces(event.param);
|
||||
emit(const ReorderSpacesSuccess());
|
||||
} on APIException catch (e) {
|
||||
emit(ReorderSpacesFailure(e.message));
|
||||
} catch (e) {
|
||||
emit(ReorderSpacesFailure(e.toString()));
|
||||
} finally {
|
||||
emit(const ReorderSpacesInitial());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
part of 'reorder_spaces_bloc.dart';
|
||||
|
||||
final class ReorderSpacesEvent extends Equatable {
|
||||
const ReorderSpacesEvent(this.param);
|
||||
|
||||
final ReorderSpacesParam param;
|
||||
|
||||
@override
|
||||
List<Object> get props => [param];
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
part of 'reorder_spaces_bloc.dart';
|
||||
|
||||
sealed class ReorderSpacesState extends Equatable {
|
||||
const ReorderSpacesState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class ReorderSpacesInitial extends ReorderSpacesState {
|
||||
const ReorderSpacesInitial();
|
||||
}
|
||||
|
||||
final class ReorderSpacesLoading extends ReorderSpacesState {
|
||||
const ReorderSpacesLoading();
|
||||
}
|
||||
|
||||
final class ReorderSpacesSuccess extends ReorderSpacesState {
|
||||
const ReorderSpacesSuccess();
|
||||
}
|
||||
|
||||
final class ReorderSpacesFailure extends ReorderSpacesState {
|
||||
const ReorderSpacesFailure(this.errorMessage);
|
||||
|
||||
final String errorMessage;
|
||||
|
||||
@override
|
||||
List<Object> get props => [errorMessage];
|
||||
}
|
@ -103,7 +103,9 @@ class SpaceDetailsDevicesBox extends StatelessWidget {
|
||||
).then((resultSpace) {
|
||||
if (resultSpace != null) {
|
||||
if (context.mounted) {
|
||||
context.read<SpaceDetailsModelBloc>().add(UpdateSpaceDetails(resultSpace));
|
||||
context
|
||||
.read<SpaceDetailsModelBloc>()
|
||||
.add(UpdateSpaceDetails(resultSpace));
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -133,6 +135,9 @@ class SpaceDetailsDevicesBox extends StatelessWidget {
|
||||
DeviceType.ThreeTouch => Assets.gangSwitch,
|
||||
DeviceType.NCPS => Assets.sensors,
|
||||
DeviceType.PC => Assets.powerClamp,
|
||||
DeviceType.fourSceen => Assets.fourSceenSwitch,
|
||||
DeviceType.sixSceen => Assets.sixSceenSwitch,
|
||||
DeviceType.SOS => Assets.sos,
|
||||
DeviceType.Other => Assets.blackLogo,
|
||||
null => Assets.blackLogo,
|
||||
};
|
||||
|
@ -19,6 +19,7 @@ class SpaceSubSpacesDialog extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
|
||||
late final TextEditingController _subspaceNameController;
|
||||
late List<Subspace> _subspaces;
|
||||
|
||||
bool get _hasDuplicateNames =>
|
||||
@ -29,6 +30,13 @@ class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_subspaces = List.from(widget.subspaces);
|
||||
_subspaceNameController = TextEditingController();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_subspaceNameController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _handleSubspaceAdded(String name) {
|
||||
@ -49,6 +57,10 @@ class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
|
||||
);
|
||||
|
||||
void _handleSave() {
|
||||
final name = _subspaceNameController.text.trim();
|
||||
if (name.isNotEmpty) {
|
||||
_handleSubspaceAdded(name);
|
||||
}
|
||||
widget.onSave(_subspaces);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
@ -65,6 +77,7 @@ class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
|
||||
subSpaces: _subspaces,
|
||||
onSubspaceAdded: _handleSubspaceAdded,
|
||||
onSubspaceDeleted: _handleSubspaceDeleted,
|
||||
controller: _subspaceNameController,
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
|
@ -10,29 +10,28 @@ class SubSpacesInput extends StatefulWidget {
|
||||
required this.subSpaces,
|
||||
required this.onSubspaceAdded,
|
||||
required this.onSubspaceDeleted,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
final List<Subspace> subSpaces;
|
||||
final void Function(String name) onSubspaceAdded;
|
||||
final void Function(String uuid) onSubspaceDeleted;
|
||||
final TextEditingController controller;
|
||||
|
||||
@override
|
||||
State<SubSpacesInput> createState() => _SubSpacesInputState();
|
||||
}
|
||||
|
||||
class _SubSpacesInputState extends State<SubSpacesInput> {
|
||||
late final TextEditingController _subspaceNameController;
|
||||
late final FocusNode _focusNode;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_subspaceNameController = TextEditingController();
|
||||
_focusNode = FocusNode();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_subspaceNameController.dispose();
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
@ -81,7 +80,7 @@ class _SubSpacesInputState extends State<SubSpacesInput> {
|
||||
width: 200,
|
||||
child: TextField(
|
||||
focusNode: _focusNode,
|
||||
controller: _subspaceNameController,
|
||||
controller: widget.controller,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: widget.subSpaces.isEmpty ? 'Please enter the name' : null,
|
||||
@ -93,7 +92,7 @@ class _SubSpacesInputState extends State<SubSpacesInput> {
|
||||
final trimmedValue = value.trim();
|
||||
if (trimmedValue.isNotEmpty) {
|
||||
widget.onSubspaceAdded(trimmedValue);
|
||||
_subspaceNameController.clear();
|
||||
widget.controller.clear();
|
||||
_focusNode.requestFocus();
|
||||
}
|
||||
},
|
||||
|
@ -31,173 +31,178 @@ class AssignTagsTable extends StatelessWidget {
|
||||
|
||||
DataColumn _buildDataColumn(BuildContext context, String label) {
|
||||
return DataColumn(
|
||||
label: SelectableText(label, style: context.textTheme.bodyMedium),
|
||||
label: Expanded(
|
||||
child: FittedBox(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
fit: BoxFit.scaleDown,
|
||||
child: SelectableText(label, style: context.textTheme.bodyMedium),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<TagsBloc>(
|
||||
create: (BuildContext context) => TagsBloc(
|
||||
create: (context) => TagsBloc(
|
||||
RemoteTagsService(HTTPService()),
|
||||
)..add(const LoadTags()),
|
||||
child: BlocBuilder<TagsBloc, TagsState>(
|
||||
builder: (context, state) {
|
||||
return switch (state) {
|
||||
TagsLoading() || TagsInitial() => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
TagsFailure(:final message) => Center(
|
||||
child: Text(message),
|
||||
),
|
||||
TagsLoaded(:final tags) => ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: DataTable(
|
||||
headingRowColor: WidgetStateProperty.all(
|
||||
ColorsManager.dataHeaderGrey,
|
||||
),
|
||||
key: ValueKey(productAllocations.length),
|
||||
border: TableBorder.all(
|
||||
color: ColorsManager.dataHeaderGrey,
|
||||
width: 1,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
columns: [
|
||||
_buildDataColumn(context, '#'),
|
||||
_buildDataColumn(context, 'Device'),
|
||||
_buildDataColumn(context, 'Tag'),
|
||||
_buildDataColumn(context, 'Location'),
|
||||
],
|
||||
rows: productAllocations.isEmpty
|
||||
? [
|
||||
DataRow(
|
||||
cells: [
|
||||
DataCell(
|
||||
Center(
|
||||
child: SelectableText(
|
||||
'No Devices Available',
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell.empty,
|
||||
DataCell.empty,
|
||||
DataCell.empty,
|
||||
],
|
||||
),
|
||||
]
|
||||
: List.generate(productAllocations.length, (index) {
|
||||
final productAllocation = productAllocations[index];
|
||||
final allocationUuid = productAllocation.uuid;
|
||||
|
||||
final availableTags = tags
|
||||
.where(
|
||||
(tag) =>
|
||||
!productAllocations
|
||||
.where((p) =>
|
||||
p.product.productType ==
|
||||
productAllocation.product.productType)
|
||||
.map((p) => p.tag.name.toLowerCase())
|
||||
.contains(tag.name.toLowerCase()) ||
|
||||
tag.uuid == productAllocation.tag.uuid,
|
||||
)
|
||||
.toList();
|
||||
|
||||
final currentLocationUuid =
|
||||
productLocations[allocationUuid];
|
||||
final currentLocationName = currentLocationUuid == null
|
||||
? 'Main Space'
|
||||
: subspaces
|
||||
.firstWhere((s) => s.uuid == currentLocationUuid)
|
||||
.name;
|
||||
|
||||
return DataRow(
|
||||
key: ValueKey(allocationUuid),
|
||||
cells: [
|
||||
DataCell(Text((index + 1).toString())),
|
||||
DataCell(
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
productAllocation.product.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)),
|
||||
const SizedBox(width: 10),
|
||||
Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: ColorsManager.lightGreyColor,
|
||||
size: 16,
|
||||
),
|
||||
onPressed: () {
|
||||
onProductDeleted(allocationUuid);
|
||||
},
|
||||
tooltip: 'Delete Tag',
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
width: double.infinity,
|
||||
child: ProductTagField(
|
||||
key: ValueKey('dropdown_$allocationUuid'),
|
||||
productName: productAllocation.product.uuid,
|
||||
initialValue: productAllocation.tag,
|
||||
onSelected: (newTag) {
|
||||
onTagSelected(allocationUuid, newTag);
|
||||
},
|
||||
items: availableTags,
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: DialogDropdown(
|
||||
items: [
|
||||
'Main Space',
|
||||
...subspaces.map((s) => s.name)
|
||||
],
|
||||
selectedValue: currentLocationName,
|
||||
onSelected: (newLocationName) {
|
||||
final newSubspaceUuid = newLocationName ==
|
||||
'Main Space'
|
||||
? null
|
||||
: subspaces
|
||||
.firstWhere(
|
||||
(s) => s.name == newLocationName)
|
||||
.uuid;
|
||||
onLocationSelected(
|
||||
allocationUuid, newSubspaceUuid);
|
||||
},
|
||||
)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
builder: (context, state) => switch (state) {
|
||||
TagsLoading() || TagsInitial() => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
TagsFailure(:final message) => Center(
|
||||
child: Text(message),
|
||||
),
|
||||
TagsLoaded(:final tags) => ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: DataTable(
|
||||
headingRowColor: WidgetStateProperty.all(
|
||||
ColorsManager.dataHeaderGrey,
|
||||
),
|
||||
key: ValueKey(productAllocations.length),
|
||||
border: TableBorder.all(
|
||||
color: ColorsManager.dataHeaderGrey,
|
||||
width: 1,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
columns: [
|
||||
_buildDataColumn(context, '#'),
|
||||
_buildDataColumn(context, 'Device'),
|
||||
_buildDataColumn(context, 'Tag'),
|
||||
_buildDataColumn(context, 'Location'),
|
||||
],
|
||||
rows: productAllocations.isEmpty
|
||||
? [
|
||||
DataRow(
|
||||
cells: [
|
||||
DataCell(
|
||||
FittedBox(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
fit: BoxFit.scaleDown,
|
||||
child: SelectableText(
|
||||
'No Devices Available',
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell.empty,
|
||||
DataCell.empty,
|
||||
DataCell.empty,
|
||||
],
|
||||
),
|
||||
]
|
||||
: List.generate(productAllocations.length, (index) {
|
||||
final productAllocation = productAllocations[index];
|
||||
final allocationUuid = productAllocation.uuid;
|
||||
|
||||
final availableTags = tags
|
||||
.where(
|
||||
(tag) =>
|
||||
!productAllocations
|
||||
.where((p) =>
|
||||
p.product.productType ==
|
||||
productAllocation.product.productType)
|
||||
.map((p) => p.tag.name.toLowerCase())
|
||||
.contains(tag.name.toLowerCase()) ||
|
||||
tag.uuid == productAllocation.tag.uuid,
|
||||
)
|
||||
.toList();
|
||||
|
||||
final currentLocationUuid = productLocations[allocationUuid];
|
||||
final currentLocationName = currentLocationUuid == null
|
||||
? 'Main Space'
|
||||
: subspaces
|
||||
.firstWhere((s) => s.uuid == currentLocationUuid)
|
||||
.name;
|
||||
|
||||
return DataRow(
|
||||
key: ValueKey(allocationUuid),
|
||||
cells: [
|
||||
DataCell(Text((index + 1).toString())),
|
||||
DataCell(
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
productAllocation.product.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)),
|
||||
const SizedBox(width: 10),
|
||||
Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: ColorsManager.lightGreyColor,
|
||||
size: 16,
|
||||
),
|
||||
onPressed: () {
|
||||
onProductDeleted(allocationUuid);
|
||||
},
|
||||
tooltip: 'Delete Tag',
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
width: double.infinity,
|
||||
child: ProductTagField(
|
||||
key: ValueKey('dropdown_$allocationUuid'),
|
||||
productName: productAllocation.product.uuid,
|
||||
initialValue: productAllocation.tag,
|
||||
onSelected: (newTag) {
|
||||
onTagSelected(allocationUuid, newTag);
|
||||
},
|
||||
items: availableTags,
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: DialogDropdown(
|
||||
items: [
|
||||
'Main Space',
|
||||
...subspaces.map((s) => s.name)
|
||||
],
|
||||
selectedValue: currentLocationName,
|
||||
onSelected: (newLocationName) {
|
||||
final newSubspaceUuid = newLocationName ==
|
||||
'Main Space'
|
||||
? null
|
||||
: subspaces
|
||||
.firstWhere(
|
||||
(s) => s.name == newLocationName)
|
||||
.uuid;
|
||||
onLocationSelected(
|
||||
allocationUuid, newSubspaceUuid);
|
||||
},
|
||||
)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
_ => const SizedBox.shrink(),
|
||||
};
|
||||
),
|
||||
_ => const SizedBox.shrink(),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -84,6 +84,14 @@ class DeviceModel {
|
||||
tempIcon = Assets.curtainIcon;
|
||||
} else if (type == DeviceType.Curtain) {
|
||||
tempIcon = Assets.curtainIcon;
|
||||
} else if (type == DeviceType.fourSceen) {
|
||||
tempIcon = Assets.fourSceenSwitch;
|
||||
} else if (type == DeviceType.sixSceen) {
|
||||
tempIcon = Assets.sixSceenSwitch;
|
||||
} else if (type == DeviceType.SOS) {
|
||||
tempIcon = Assets.sos;
|
||||
} else if (type == DeviceType.NCPS) {
|
||||
tempIcon = Assets.presenceSensor;
|
||||
} else {
|
||||
tempIcon = Assets.blackLogo;
|
||||
}
|
||||
|
@ -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
53
lib/syncrow_app.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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(),
|
||||
),
|
||||
];
|
||||
}
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
@ -84,10 +85,15 @@ abstract class ColorsManager {
|
||||
static const Color minBlue = Color(0xFF93AAFD);
|
||||
static const Color minBlueDot = Color(0xFF023DFE);
|
||||
static const Color grey25 = Color(0xFFF9F9F9);
|
||||
static const Color dropDownSelectBlue = Color(0xFF2196F3);
|
||||
static const Color drpoDownSelectBlue = Color(0xFF2196F3);
|
||||
static const Color grey50 = Color(0xFF718096);
|
||||
static const Color red100 = Color(0xFFFE0202);
|
||||
static const Color grey800 = Color(0xffF8F8F8);
|
||||
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);
|
||||
|
||||
}
|
||||
|
@ -41,6 +41,8 @@ abstract class ApiEndpoints {
|
||||
'/projects/{projectId}/communities/{communityId}/spaces/{spaceId}';
|
||||
static const String getSpaceHierarchy =
|
||||
'/projects/{projectId}/communities/{communityId}/spaces';
|
||||
static const String reorderSpaces =
|
||||
'/projects/{projectUuid}/communities/{communityUuid}/spaces/{parentSpaceUuid}/spaces/order';
|
||||
|
||||
// Community Module
|
||||
static const String createCommunity = '/projects/{projectId}/communities';
|
||||
@ -139,6 +141,7 @@ abstract class ApiEndpoints {
|
||||
'/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/subspaces/{subSpaceUuid}/devices/{deviceUuid}';
|
||||
static const String saveSchedule = '/schedule/{deviceUuid}';
|
||||
|
||||
static const String duplicateSpace = '/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/duplicate';
|
||||
|
||||
////booking System
|
||||
static const String bookableSpaces = '/bookable-spaces';
|
||||
|
@ -4,6 +4,8 @@ class Assets {
|
||||
static const String webBackground = 'assets/images/web_Background.svg';
|
||||
static const String webBackgroundPng = 'assets/images/web_Background.png';
|
||||
static const String blackLogo = 'assets/images/black-logo.png';
|
||||
static const String fourSceenSwitch = 'assets/images/4_sceen_switch.svg';
|
||||
static const String sixSceenSwitch = 'assets/images/6_sceen_switch.svg';
|
||||
static const String logo = 'assets/images/Logo.svg';
|
||||
static const String logoHorizontal = 'assets/images/logo_horizontal.png';
|
||||
static const String vector = 'assets/images/Vector.png';
|
||||
|
@ -21,6 +21,9 @@ enum DeviceType {
|
||||
NCPS,
|
||||
DoorSensor,
|
||||
PC,
|
||||
fourSceen,
|
||||
sixSceen,
|
||||
SOS,
|
||||
Other,
|
||||
}
|
||||
/*
|
||||
@ -63,4 +66,7 @@ Map<String, DeviceType> devicesTypesMap = {
|
||||
'WL': DeviceType.WaterLeak,
|
||||
'NCPS': DeviceType.NCPS,
|
||||
'PC': DeviceType.PC,
|
||||
'4S': DeviceType.fourSceen,
|
||||
'6S': DeviceType.sixSceen,
|
||||
'SOS': DeviceType.SOS,
|
||||
};
|
||||
|
56
lib/utils/extension/app_snack_bar.dart
Normal file
56
lib/utils/extension/app_snack_bar.dart
Normal file
@ -0,0 +1,56 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
extension AppSnackBarsBuildContextExtension on BuildContext {
|
||||
void showSuccessSnackbar(String message) {
|
||||
ScaffoldMessenger.of(this).showSnackBar(
|
||||
_makeSnackbar(
|
||||
message: message,
|
||||
icon: Icons.check_circle,
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void showFailureSnackbar(String message) {
|
||||
ScaffoldMessenger.of(this).showSnackBar(
|
||||
_makeSnackbar(
|
||||
message: message,
|
||||
icon: Icons.error,
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
SnackBar _makeSnackbar({
|
||||
required String message,
|
||||
required Color backgroundColor,
|
||||
required IconData icon,
|
||||
}) {
|
||||
return SnackBar(
|
||||
content: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Icon(icon, color: Colors.white),
|
||||
Text(
|
||||
message,
|
||||
style: textTheme.bodyMedium?.copyWith(
|
||||
color: ColorsManager.whiteColors,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: backgroundColor,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
margin: const EdgeInsetsDirectional.symmetric(
|
||||
horizontal: 92,
|
||||
vertical: 32,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user