mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-08-26 06:59:41 +00:00
Compare commits
49 Commits
enhancemen
...
0eb4652f26
Author | SHA1 | Date | |
---|---|---|---|
0eb4652f26 | |||
460639b681 | |||
2f89c3486c | |||
5589e5b587 | |||
d3bd363b70 | |||
3a4fce966c | |||
6bdd28ec57 | |||
652163fdae | |||
076c80fe44 | |||
d12b4c0c65 | |||
22c8c54fab | |||
95300071e9 | |||
06f00da02c | |||
7b5b40a03c | |||
8522c0bbc3 | |||
fc70669f1d | |||
f03c28f7fd | |||
6ec972a520 | |||
62f67f5a5f | |||
3e634dc7a2 | |||
8e303af0d7 | |||
4ef4858cee | |||
9a203b2fd9 | |||
39c5fd1bca | |||
308eb65d46 | |||
d65f9ceea9 | |||
f539b0ac8d | |||
5a3cf93748 | |||
e740652507 | |||
c60078c96a | |||
903c5dd29b | |||
df39fca050 | |||
f832c5d884 | |||
fa930571dc | |||
acefe7b355 | |||
b223194950 | |||
466f5b89c7 | |||
de5d8df01c | |||
5218641705 | |||
ab6a6851f2 | |||
beb33e37fa | |||
3bee17c574 | |||
f4b5c6fb52 | |||
086f3cedf8 | |||
035c03c6b2 | |||
cf1b34ee0a | |||
5663e2084e | |||
3cd0125310 | |||
e0980b324c |
5
assets/icons/x_delete.svg
Normal file
5
assets/icons/x_delete.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg width="35" height="35" viewBox="0 0 35 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M17.5 34.9999C27.1649 34.9999 34.9999 27.1649 34.9999 17.4999C34.9999 7.83499 27.1649 0 17.5 0C7.83499 0 0 7.83499 0 17.5C0 27.1651 7.83499 34.9999 17.5 34.9999Z" fill="#FF6465"/>
|
||||||
|
<path opacity="0.1" d="M4.70804 17.5C4.70804 8.63343 11.3024 1.30805 19.854 0.158115C19.0839 0.0545507 18.2984 0 17.5 0C7.835 0 0 7.835 0 17.5C0 27.1651 7.83499 35 17.4999 35C18.2983 35 19.0839 34.9455 19.8539 34.8419C11.3024 33.6919 4.70804 26.3665 4.70804 17.5Z" fill="black"/>
|
||||||
|
<path d="M21.4229 17.5003L26.0301 12.8931C26.365 12.5582 26.365 12.0152 26.0301 11.6804L23.3197 8.96992C22.9848 8.63503 22.4418 8.63503 22.107 8.96992L17.4997 13.5772L12.8924 8.96992C12.5576 8.63503 12.0146 8.63503 11.6798 8.96992L8.96931 11.6804C8.63442 12.0153 8.63442 12.5582 8.96931 12.8931L13.5766 17.5003L8.96931 22.1076C8.63442 22.4425 8.63442 22.9855 8.96931 23.3204L11.6798 26.0308C12.0146 26.3657 12.5576 26.3657 12.8924 26.0308L17.4997 21.4235L22.1071 26.0308C22.442 26.3657 22.9849 26.3657 23.3198 26.0308L26.0302 23.3204C26.3651 22.9855 26.3651 22.4425 26.0302 22.1076L21.4229 17.5003Z" fill="white"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -1,22 +1,9 @@
|
|||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:flutter_dotenv/flutter_dotenv.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/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/services/locator.dart';
|
||||||
import 'package:syncrow_web/utils/app_routes.dart';
|
import 'package:syncrow_web/syncrow_app.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';
|
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
try {
|
try {
|
||||||
@ -33,59 +20,5 @@ Future<void> main() async {
|
|||||||
);
|
);
|
||||||
initialSetup();
|
initialSetup();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
runApp(MyApp());
|
runApp(const SyncrowApp());
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,9 @@
|
|||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:flutter_dotenv/flutter_dotenv.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/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/services/locator.dart';
|
||||||
import 'package:syncrow_web/utils/app_routes.dart';
|
import 'package:syncrow_web/syncrow_app.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';
|
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
try {
|
try {
|
||||||
@ -33,59 +20,5 @@ Future<void> main() async {
|
|||||||
);
|
);
|
||||||
initialSetup();
|
initialSetup();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
runApp(MyApp());
|
runApp(const SyncrowApp());
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,16 @@
|
|||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:flutter_dotenv/flutter_dotenv.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/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/services/locator.dart';
|
||||||
import 'package:syncrow_web/utils/app_routes.dart';
|
import 'package:syncrow_web/syncrow_app.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';
|
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
try {
|
try {
|
||||||
const environment = String.fromEnvironment('FLAVOR', defaultValue: 'staging');
|
const environment = String.fromEnvironment(
|
||||||
|
'FLAVOR',
|
||||||
|
defaultValue: 'staging',
|
||||||
|
);
|
||||||
await dotenv.load(fileName: '.env.$environment');
|
await dotenv.load(fileName: '.env.$environment');
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Firebase.initializeApp(
|
await Firebase.initializeApp(
|
||||||
@ -30,59 +20,5 @@ Future<void> main() async {
|
|||||||
);
|
);
|
||||||
initialSetup();
|
initialSetup();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
runApp(MyApp());
|
runApp(const SyncrowApp());
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ part 'events_event.dart';
|
|||||||
part 'events_state.dart';
|
part 'events_state.dart';
|
||||||
|
|
||||||
class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
|
class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
|
||||||
final EventController eventController = EventController();
|
EventController eventController = EventController();
|
||||||
final CalendarSystemService calendarService;
|
final CalendarSystemService calendarService;
|
||||||
|
|
||||||
CalendarEventsBloc({
|
CalendarEventsBloc({
|
||||||
@ -20,7 +20,9 @@ class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
|
|||||||
on<AddEvent>(_onAddEvent);
|
on<AddEvent>(_onAddEvent);
|
||||||
on<DisposeResources>(_onDisposeResources);
|
on<DisposeResources>(_onDisposeResources);
|
||||||
on<GoToWeek>(_onGoToWeek);
|
on<GoToWeek>(_onGoToWeek);
|
||||||
|
on<ResetEvents>(_onResetEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onLoadEvents(
|
Future<void> _onLoadEvents(
|
||||||
LoadEvents event,
|
LoadEvents event,
|
||||||
Emitter<CalendarEventState> emit,
|
Emitter<CalendarEventState> emit,
|
||||||
@ -126,4 +128,18 @@ class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
|
|||||||
eventController.dispose();
|
eventController.dispose();
|
||||||
return super.close();
|
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;
|
final DateTime weekStart;
|
||||||
const CheckWeekHasEvents(this.weekStart);
|
const CheckWeekHasEvents(this.weekStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ResetEvents extends CalendarEventsEvent {
|
||||||
|
const ResetEvents();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
// booking_page.dart
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:calendar_view/calendar_view.dart';
|
import 'package:calendar_view/calendar_view.dart';
|
||||||
@ -26,7 +27,33 @@ class BookingPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _BookingPageState extends State<BookingPage> {
|
class _BookingPageState extends State<BookingPage> {
|
||||||
late final EventController _eventController;
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MultiBlocProvider(
|
||||||
|
providers: [
|
||||||
|
BlocProvider(create: (_) => SelectedBookableSpaceBloc()),
|
||||||
|
BlocProvider(create: (_) => DateSelectionBloc()),
|
||||||
|
BlocProvider(
|
||||||
|
create: (_) => CalendarEventsBloc(
|
||||||
|
calendarService: MemoryCalendarServiceWithRemoteFallback(
|
||||||
|
remoteService: RemoteCalendarService(HTTPService()),
|
||||||
|
memoryService: MemoryCalendarService(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: _BookingPageContent(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BookingPageContent extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
State<_BookingPageContent> createState() => _BookingPageContentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BookingPageContentState extends State<_BookingPageContent> {
|
||||||
|
late EventController _eventController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -40,7 +67,7 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _dispatchLoadEvents(BuildContext context) {
|
void _loadEvents(BuildContext context) {
|
||||||
final selectedRoom =
|
final selectedRoom =
|
||||||
context.read<SelectedBookableSpaceBloc>().state.selectedBookableSpace;
|
context.read<SelectedBookableSpaceBloc>().state.selectedBookableSpace;
|
||||||
final dateState = context.read<DateSelectionBloc>().state;
|
final dateState = context.read<DateSelectionBloc>().state;
|
||||||
@ -60,35 +87,25 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MultiBlocProvider(
|
return BlocListener<SelectedBookableSpaceBloc, SelectedBookableSpaceState>(
|
||||||
providers: [
|
listener: (context, state) {
|
||||||
BlocProvider(create: (_) => SelectedBookableSpaceBloc()),
|
if (state.selectedBookableSpace != null) {
|
||||||
BlocProvider(create: (_) => DateSelectionBloc()),
|
// Reset events and clear cache when room changes
|
||||||
BlocProvider(
|
context.read<CalendarEventsBloc>().add(ResetEvents());
|
||||||
create: (_) => CalendarEventsBloc(
|
_loadEvents(context);
|
||||||
calendarService: MemoryCalendarServiceWithRemoteFallback(
|
}
|
||||||
remoteService: RemoteCalendarService(
|
},
|
||||||
HTTPService(),
|
child: BlocListener<DateSelectionBloc, DateSelectionState>(
|
||||||
),
|
listener: (context, state) {
|
||||||
memoryService: MemoryCalendarService(),
|
_loadEvents(context);
|
||||||
),
|
},
|
||||||
)),
|
child: BlocListener<CalendarEventsBloc, CalendarEventState>(
|
||||||
],
|
|
||||||
child: Builder(
|
|
||||||
builder: (context) =>
|
|
||||||
BlocListener<CalendarEventsBloc, CalendarEventState>(
|
|
||||||
listenWhen: (prev, curr) => curr is EventsLoaded,
|
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state is EventsLoaded) {
|
if (state is EventsLoaded) {
|
||||||
_eventController.removeWhere((_) => true);
|
_eventController.removeWhere((_) => true);
|
||||||
_eventController.addAll(state.events);
|
_eventController.addAll(state.events);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: BlocListener<SelectedBookableSpaceBloc,
|
|
||||||
SelectedBookableSpaceState>(
|
|
||||||
listener: (context, state) => _dispatchLoadEvents(context),
|
|
||||||
child: BlocListener<DateSelectionBloc, DateSelectionState>(
|
|
||||||
listener: (context, state) => _dispatchLoadEvents(context),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -123,8 +140,8 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: BlocBuilder<DateSelectionBloc,
|
child:
|
||||||
DateSelectionState>(
|
BlocBuilder<DateSelectionBloc, DateSelectionState>(
|
||||||
builder: (context, dateState) {
|
builder: (context, dateState) {
|
||||||
return CustomCalendarPage(
|
return CustomCalendarPage(
|
||||||
selectedDate: dateState.selectedDate,
|
selectedDate: dateState.selectedDate,
|
||||||
@ -169,8 +186,7 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
BlocBuilder<DateSelectionBloc,
|
BlocBuilder<DateSelectionBloc, DateSelectionState>(
|
||||||
DateSelectionState>(
|
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final weekStart = state.weekStart;
|
final weekStart = state.weekStart;
|
||||||
final weekEnd =
|
final weekEnd =
|
||||||
@ -193,6 +209,7 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 5,
|
flex: 5,
|
||||||
child: BlocBuilder<SelectedBookableSpaceBloc,
|
child: BlocBuilder<SelectedBookableSpaceBloc,
|
||||||
@ -203,22 +220,16 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
return BlocBuilder<DateSelectionBloc,
|
return BlocBuilder<DateSelectionBloc,
|
||||||
DateSelectionState>(
|
DateSelectionState>(
|
||||||
builder: (context, dateState) {
|
builder: (context, dateState) {
|
||||||
return BlocListener<CalendarEventsBloc,
|
return BlocBuilder<CalendarEventsBloc,
|
||||||
CalendarEventState>(
|
CalendarEventState>(
|
||||||
listenWhen: (prev, curr) =>
|
builder: (context, eventState) {
|
||||||
curr is EventsLoaded,
|
return WeeklyCalendarPage(
|
||||||
listener: (context, state) {
|
key: ValueKey(
|
||||||
if (state is EventsLoaded) {
|
selectedRoom?.uuid ?? 'no-room'),
|
||||||
_eventController
|
|
||||||
.removeWhere((_) => true);
|
|
||||||
_eventController.addAll(state.events);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: WeeklyCalendarPage(
|
|
||||||
startTime: selectedRoom
|
startTime: selectedRoom
|
||||||
?.bookableConfig.startTime,
|
?.bookableConfig.startTime,
|
||||||
endTime: selectedRoom
|
endTime:
|
||||||
?.bookableConfig.endTime,
|
selectedRoom?.bookableConfig.endTime,
|
||||||
weekStart: dateState.weekStart,
|
weekStart: dateState.weekStart,
|
||||||
selectedDate: dateState.selectedDate,
|
selectedDate: dateState.selectedDate,
|
||||||
eventController: _eventController,
|
eventController: _eventController,
|
||||||
@ -226,7 +237,9 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
.watch<DateSelectionBloc>()
|
.watch<DateSelectionBloc>()
|
||||||
.state
|
.state
|
||||||
.selectedDateFromSideBarCalender,
|
.selectedDateFromSideBarCalender,
|
||||||
),
|
// isLoading: eventState is EventsLoading,
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -241,8 +254,6 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,21 +76,29 @@ class __SidebarContentState extends State<_SidebarContent> {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
const _SidebarHeader(title: 'Spaces'),
|
Padding(
|
||||||
Container(
|
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
|
||||||
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: ColorsManager.whiteColors,
|
color: ColorsManager.whiteColors,
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: ColorsManager.blackColor.withOpacity(0.1),
|
color: ColorsManager.blackColor.withOpacity(0.1),
|
||||||
offset: const Offset(0, -2),
|
offset: const Offset(0, 4),
|
||||||
blurRadius: 4,
|
blurRadius: 4,
|
||||||
spreadRadius: 0,
|
spreadRadius: 0,
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: _SidebarHeader(title: 'Spaces')),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: ColorsManager.blackColor.withOpacity(0.1),
|
color: ColorsManager.blackColor.withOpacity(0.1),
|
||||||
offset: const Offset(0, 2),
|
offset: const Offset(0, 4),
|
||||||
blurRadius: 4,
|
blurRadius: 4,
|
||||||
spreadRadius: 0,
|
spreadRadius: 0,
|
||||||
),
|
),
|
||||||
@ -220,7 +228,7 @@ class _SidebarHeader extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(10.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
|
@ -66,7 +66,16 @@ class _CustomCalendarPageState extends State<CustomCalendarPage> {
|
|||||||
weekdayLabels: const ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'],
|
weekdayLabels: const ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'],
|
||||||
);
|
);
|
||||||
|
|
||||||
return CalendarDatePicker2(
|
return Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(
|
||||||
|
color: ColorsManager.textGray,
|
||||||
|
width: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: CalendarDatePicker2(
|
||||||
config: config,
|
config: config,
|
||||||
value: [_selectedDate],
|
value: [_selectedDate],
|
||||||
onValueChanged: (dates) {
|
onValueChanged: (dates) {
|
||||||
@ -78,6 +87,7 @@ class _CustomCalendarPageState extends State<CustomCalendarPage> {
|
|||||||
widget.onDateChanged(picked.day, picked.month, picked.year);
|
widget.onDateChanged(picked.day, picked.month, picked.year);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,15 +32,17 @@ class EventTileWidget extends StatelessWidget {
|
|||||||
return Expanded(
|
return Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: EdgeInsets.all(5),
|
padding: const EdgeInsets.all(5),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isEventEnded
|
color: isEventEnded
|
||||||
? ColorsManager.grayColor.withOpacity(0.1)
|
? ColorsManager.grayColor.withOpacity(0.1)
|
||||||
: ColorsManager.blue1.withOpacity(0.1),
|
: ColorsManager.blue1.withOpacity(0.1),
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
border: const Border(
|
border: Border(
|
||||||
left: BorderSide(
|
left: BorderSide(
|
||||||
color: ColorsManager.grayColor,
|
color: isEventEnded
|
||||||
|
? ColorsManager.grayColor
|
||||||
|
: ColorsManager.secondaryColor,
|
||||||
width: 4,
|
width: 4,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -21,7 +21,7 @@ class RoomListItem extends StatelessWidget {
|
|||||||
groupValue: isSelected ? room.uuid : null,
|
groupValue: isSelected ? room.uuid : null,
|
||||||
visualDensity: const VisualDensity(vertical: -4),
|
visualDensity: const VisualDensity(vertical: -4),
|
||||||
onChanged: (value) => onTap(),
|
onChanged: (value) => onTap(),
|
||||||
activeColor: ColorsManager.primaryColor,
|
activeColor: ColorsManager.secondaryColor,
|
||||||
title: Text(
|
title: Text(
|
||||||
room.spaceName,
|
room.spaceName,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
|
@ -14,26 +14,33 @@ class WeekDayHeader extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return ColoredBox(
|
||||||
|
color: isSelectedDay
|
||||||
|
? ColorsManager.secondaryColor.withOpacity(0.1)
|
||||||
|
: Colors.transparent,
|
||||||
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
Text(
|
Text(
|
||||||
DateFormat('EEE').format(date).toUpperCase(),
|
DateFormat('EEE').format(date).toUpperCase(),
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: isSelectedDay ? Colors.blue : Colors.black,
|
color: ColorsManager.blackColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
DateFormat('d').format(date),
|
DateFormat('d').format(date),
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
fontSize: 20,
|
fontSize: 30,
|
||||||
color:
|
color: ColorsManager.blackColor,
|
||||||
isSelectedDay ? ColorsManager.blue1 : ColorsManager.blackColor,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,13 +57,10 @@ class WeeklyCalendarPage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const double timeLineWidth = 90;
|
||||||
|
|
||||||
const double timeLineWidth = 65;
|
|
||||||
|
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
|
|
||||||
bool isInRange(DateTime date, DateTime start, DateTime end) {
|
bool isInRange(DateTime date, DateTime start, DateTime end) {
|
||||||
!date.isBefore(start) && !date.isAfter(end);
|
!date.isBefore(start) && !date.isAfter(end);
|
||||||
// remove this line and Check if the date is within the range
|
// remove this line and Check if the date is within the range
|
||||||
@ -100,7 +97,6 @@ class WeeklyCalendarPage extends StatelessWidget {
|
|||||||
width: width,
|
width: width,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.orange.withOpacity(0.13),
|
color: Colors.orange.withOpacity(0.13),
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if (isSelected) {
|
} else if (isSelected) {
|
||||||
@ -110,7 +106,6 @@ class WeeklyCalendarPage extends StatelessWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color:
|
color:
|
||||||
ColorsManager.spaceColor.withOpacity(0.07),
|
ColorsManager.spaceColor.withOpacity(0.07),
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -143,7 +138,7 @@ class WeeklyCalendarPage extends StatelessWidget {
|
|||||||
heightPerMinute: 1.7,
|
heightPerMinute: 1.7,
|
||||||
showLiveTimeLineInAllDays: false,
|
showLiveTimeLineInAllDays: false,
|
||||||
showVerticalLines: true,
|
showVerticalLines: true,
|
||||||
emulateVerticalOffsetBy: -80,
|
emulateVerticalOffsetBy: -95,
|
||||||
startDay: WeekDays.monday,
|
startDay: WeekDays.monday,
|
||||||
liveTimeIndicatorSettings:
|
liveTimeIndicatorSettings:
|
||||||
const LiveTimeIndicatorSettings(
|
const LiveTimeIndicatorSettings(
|
||||||
@ -161,7 +156,7 @@ class WeeklyCalendarPage extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
timeLineWidth: timeLineWidth,
|
timeLineWidth: timeLineWidth,
|
||||||
weekPageHeaderBuilder: (start, end) => Container(),
|
weekPageHeaderBuilder: (start, end) => Container(),
|
||||||
weekTitleHeight: 60,
|
weekTitleHeight: 90,
|
||||||
weekNumberBuilder: (firstDayOfWeek) => Padding(
|
weekNumberBuilder: (firstDayOfWeek) => Padding(
|
||||||
padding: const EdgeInsets.only(right: 15, bottom: 10),
|
padding: const EdgeInsets.only(right: 15, bottom: 10),
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -208,8 +203,6 @@ class WeeklyCalendarPage extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSameDay(DateTime d1, DateTime d2) {
|
bool isSameDay(DateTime d1, DateTime d2) {
|
||||||
|
@ -53,8 +53,9 @@ class DeviceManagementBloc
|
|||||||
for (var community in spaceBloc.state.selectedCommunities) {
|
for (var community in spaceBloc.state.selectedCommunities) {
|
||||||
final spacesList =
|
final spacesList =
|
||||||
spaceBloc.state.selectedCommunityAndSpaces[community] ?? [];
|
spaceBloc.state.selectedCommunityAndSpaces[community] ?? [];
|
||||||
devices.addAll(await DevicesManagementApi()
|
devices.addAll(await DevicesManagementApi().fetchDevices(projectUuid,
|
||||||
.fetchDevices(projectUuid, spacesId: spacesList));
|
spacesId: spacesList,
|
||||||
|
communities: spaceBloc.state.selectedCommunities));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +159,8 @@ class DeviceManagementBloc
|
|||||||
add(FilterDevices(_getFilterFromIndex(_selectedIndex)));
|
add(FilterDevices(_getFilterFromIndex(_selectedIndex)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectDevice(SelectDevice event, Emitter<DeviceManagementState> emit) {
|
void _onSelectDevice(
|
||||||
|
SelectDevice event, Emitter<DeviceManagementState> emit) {
|
||||||
final selectedUuid = event.selectedDevice.uuid;
|
final selectedUuid = event.selectedDevice.uuid;
|
||||||
|
|
||||||
if (_selectedDevices.any((device) => device.uuid == selectedUuid)) {
|
if (_selectedDevices.any((device) => device.uuid == selectedUuid)) {
|
||||||
@ -254,7 +256,8 @@ class DeviceManagementBloc
|
|||||||
_onlineCount = _devices.where((device) => device.online == true).length;
|
_onlineCount = _devices.where((device) => device.online == true).length;
|
||||||
_offlineCount = _devices.where((device) => device.online == false).length;
|
_offlineCount = _devices.where((device) => device.online == false).length;
|
||||||
_lowBatteryCount = _devices
|
_lowBatteryCount = _devices
|
||||||
.where((device) => device.batteryLevel != null && device.batteryLevel! < 20)
|
.where((device) =>
|
||||||
|
device.batteryLevel != null && device.batteryLevel! < 20)
|
||||||
.length;
|
.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,7 +274,8 @@ class DeviceManagementBloc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSearchDevices(SearchDevices event, Emitter<DeviceManagementState> emit) {
|
void _onSearchDevices(
|
||||||
|
SearchDevices event, Emitter<DeviceManagementState> emit) {
|
||||||
if ((event.community == null || event.community!.isEmpty) &&
|
if ((event.community == null || event.community!.isEmpty) &&
|
||||||
(event.unitName == null || event.unitName!.isEmpty) &&
|
(event.unitName == null || event.unitName!.isEmpty) &&
|
||||||
(event.deviceNameOrProductName == null ||
|
(event.deviceNameOrProductName == null ||
|
||||||
@ -435,8 +439,8 @@ class DeviceManagementBloc
|
|||||||
final selectedDevices = loaded.selectedDevice?.map((device) {
|
final selectedDevices = loaded.selectedDevice?.map((device) {
|
||||||
if (device.uuid == event.deviceId) {
|
if (device.uuid == event.deviceId) {
|
||||||
return device.copyWith(
|
return device.copyWith(
|
||||||
subspace:
|
subspace: device.subspace
|
||||||
device.subspace?.copyWith(subspaceName: event.newSubSpaceName));
|
?.copyWith(subspaceName: event.newSubSpaceName));
|
||||||
}
|
}
|
||||||
return device;
|
return device;
|
||||||
}).toList();
|
}).toList();
|
||||||
|
@ -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/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/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/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/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/table/report_table.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||||
@ -94,11 +95,18 @@ class GarageDoorControlView extends StatelessWidget
|
|||||||
FetchGarageDoorSchedulesEvent(
|
FetchGarageDoorSchedulesEvent(
|
||||||
deviceId: deviceId, category: 'doorcontact_state'),
|
deviceId: deviceId, category: 'doorcontact_state'),
|
||||||
);
|
);
|
||||||
showDialog(
|
|
||||||
|
showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) => BlocProvider.value(
|
builder: (ctx) => BlocProvider.value(
|
||||||
value: BlocProvider.of<GarageDoorBloc>(context),
|
value: BlocProvider.of<GarageDoorBloc>(context),
|
||||||
child: BuildGarageDoorScheduleView(status: status),
|
child: BuildScheduleView(
|
||||||
|
deviceUuid: deviceId,
|
||||||
|
category: 'Timer',
|
||||||
|
code: 'doorcontact_state',
|
||||||
|
countdownCode: 'Timer',
|
||||||
|
deviceType: 'GD',
|
||||||
|
),
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
name: 'Scheduling',
|
name: 'Scheduling',
|
||||||
|
@ -287,7 +287,8 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
|||||||
try {
|
try {
|
||||||
if (state is ScheduleLoaded) {
|
if (state is ScheduleLoaded) {
|
||||||
Status status = Status(code: '', value: '');
|
Status status = Status(code: '', value: '');
|
||||||
if (event.deviceType == 'CUR_2') {
|
if (event.deviceType == 'CUR_2' ||
|
||||||
|
event.deviceType == 'GD' ) {
|
||||||
status = status.copyWith(
|
status = status.copyWith(
|
||||||
code: 'control',
|
code: 'control',
|
||||||
value: event.functionOn == true ? 'open' : 'close');
|
value: event.functionOn == true ? 'open' : 'close');
|
||||||
|
@ -69,7 +69,7 @@ class CountdownModeButtons extends StatelessWidget {
|
|||||||
countDownCode: countDownCode),
|
countDownCode: countDownCode),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
backgroundColor: ColorsManager.primaryColorWithOpacity,
|
backgroundColor: ColorsManager.secondaryColor,
|
||||||
child: const Text('Save'),
|
child: const Text('Save'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -63,7 +63,7 @@ class InchingModeButtons extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
backgroundColor: ColorsManager.primaryColor,
|
backgroundColor: ColorsManager.secondaryColor,
|
||||||
child: const Text('Save'),
|
child: const Text('Save'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -31,11 +31,12 @@ class BuildScheduleView extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (_) => ScheduleBloc(deviceId: deviceUuid,)
|
create: (_) => ScheduleBloc(
|
||||||
|
deviceId: deviceUuid,
|
||||||
|
)
|
||||||
..add(ScheduleGetEvent(category: category))
|
..add(ScheduleGetEvent(category: category))
|
||||||
..add(ScheduleFetchStatusEvent(
|
..add(ScheduleFetchStatusEvent(
|
||||||
deviceId: deviceUuid,
|
deviceId: deviceUuid, countdownCode: countdownCode ?? '')),
|
||||||
countdownCode: countdownCode ?? '')),
|
|
||||||
child: Dialog(
|
child: Dialog(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
insetPadding: const EdgeInsets.all(20),
|
insetPadding: const EdgeInsets.all(20),
|
||||||
@ -56,7 +57,7 @@ class BuildScheduleView extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
const ScheduleHeader(),
|
const ScheduleHeader(),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
if (deviceType == 'CUR_2')
|
if (deviceType == 'CUR_2' || deviceType == 'GD')
|
||||||
const SizedBox()
|
const SizedBox()
|
||||||
else
|
else
|
||||||
ScheduleModeSelector(
|
ScheduleModeSelector(
|
||||||
@ -76,8 +77,7 @@ class BuildScheduleView extends StatelessWidget {
|
|||||||
category: category,
|
category: category,
|
||||||
time: '',
|
time: '',
|
||||||
function: Status(
|
function: Status(
|
||||||
code: code.toString(),
|
code: code.toString(), value: true),
|
||||||
value: true),
|
|
||||||
days: [],
|
days: [],
|
||||||
),
|
),
|
||||||
isEdit: false,
|
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 ||
|
if (state.scheduleMode == ScheduleModes.countdown ||
|
||||||
state.scheduleMode == ScheduleModes.inching)
|
state.scheduleMode == ScheduleModes.inching)
|
||||||
CountdownInchingView(
|
CountdownInchingView(
|
||||||
|
@ -24,12 +24,13 @@ class ScheduleManagementUI extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 170,
|
width: 177,
|
||||||
height: 40,
|
height: 40,
|
||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
borderColor: ColorsManager.grayColor.withOpacity(0.5),
|
borderWidth: 4,
|
||||||
padding: 2,
|
borderColor: ColorsManager.neutralGray,
|
||||||
backgroundColor: ColorsManager.graysColor,
|
padding: 8,
|
||||||
|
backgroundColor: ColorsManager.textFieldGreyColor,
|
||||||
borderRadius: 15,
|
borderRadius: 15,
|
||||||
onPressed: onAddSchedule,
|
onPressed: onAddSchedule,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
@ -39,7 +39,7 @@ class ScheduleModeButtons extends StatelessWidget {
|
|||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
height: 40,
|
height: 40,
|
||||||
onPressed: onSave,
|
onPressed: onSave,
|
||||||
backgroundColor: ColorsManager.primaryColorWithOpacity,
|
backgroundColor: ColorsManager.secondaryColor,
|
||||||
child: const Text('Save'),
|
child: const Text('Save'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -194,7 +194,7 @@ class _ScheduleTableView extends StatelessWidget {
|
|||||||
child: Text(_getSelectedDays(
|
child: Text(_getSelectedDays(
|
||||||
ScheduleModel.parseSelectedDays(schedule.days)))),
|
ScheduleModel.parseSelectedDays(schedule.days)))),
|
||||||
Center(child: Text(formatIsoStringToTime(schedule.time, context))),
|
Center(child: Text(formatIsoStringToTime(schedule.time, context))),
|
||||||
if (deviceType == 'CUR_2')
|
if (deviceType == 'CUR_2' || deviceType == 'GD')
|
||||||
Center(
|
Center(
|
||||||
child: Text(schedule.function.value == true ? 'open' : 'close'))
|
child: Text(schedule.function.value == true ? 'open' : 'close'))
|
||||||
else
|
else
|
||||||
|
@ -23,7 +23,7 @@ class ScheduleDialogHelper {
|
|||||||
required String deviceType,
|
required String deviceType,
|
||||||
}) {
|
}) {
|
||||||
bool temp;
|
bool temp;
|
||||||
if (deviceType == 'CUR_2') {
|
if (deviceType == 'CUR_2' || deviceType == 'GD') {
|
||||||
temp = schedule!.function.value == 'open' ? true : false;
|
temp = schedule!.function.value == 'open' ? true : false;
|
||||||
} else {
|
} else {
|
||||||
temp = schedule!.function.value;
|
temp = schedule!.function.value;
|
||||||
@ -116,7 +116,7 @@ class ScheduleDialogHelper {
|
|||||||
ScheduleModeButtons(
|
ScheduleModeButtons(
|
||||||
onSave: () {
|
onSave: () {
|
||||||
dynamic temp;
|
dynamic temp;
|
||||||
if (deviceType == 'CUR_2') {
|
if (deviceType == 'CUR_2' || deviceType == 'GD') {
|
||||||
temp = functionOn! ? 'open' : 'close';
|
temp = functionOn! ? 'open' : 'close';
|
||||||
} else {
|
} else {
|
||||||
temp = functionOn;
|
temp = functionOn;
|
||||||
@ -202,18 +202,23 @@ class ScheduleDialogHelper {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Radio<bool>(
|
Radio<bool>(
|
||||||
|
activeColor: ColorsManager.secondaryColor,
|
||||||
|
focusColor: ColorsManager.secondaryColor,
|
||||||
value: true,
|
value: true,
|
||||||
groupValue: isOn,
|
groupValue: isOn,
|
||||||
onChanged: (val) => onChanged(true),
|
onChanged: (val) => onChanged(true),
|
||||||
),
|
),
|
||||||
Text(categor == 'CUR_2' ? 'open' : 'On'),
|
Text(categor == 'CUR_2' || categor == 'GD' ? 'open' : 'On'),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Radio<bool>(
|
Radio<bool>(
|
||||||
|
activeColor: ColorsManager.secondaryColor,
|
||||||
|
focusColor: ColorsManager.secondaryColor,
|
||||||
|
|
||||||
value: false,
|
value: false,
|
||||||
groupValue: isOn,
|
groupValue: isOn,
|
||||||
onChanged: (val) => onChanged(false),
|
onChanged: (val) => onChanged(false),
|
||||||
),
|
),
|
||||||
Text(categor == 'CUR_2' ? 'close' : 'Off'),
|
Text(categor == 'CUR_2' || categor == 'GD' ? 'close' : 'Off'),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -937,13 +937,17 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
List<String> spacesList =
|
List<String> spacesList =
|
||||||
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||||
|
|
||||||
devices.addAll(await DevicesManagementApi()
|
devices.addAll(await DevicesManagementApi().fetchDevices(
|
||||||
.fetchDevices(projectUuid, spacesId: spacesList));
|
projectUuid,
|
||||||
|
spacesId: spacesList,
|
||||||
|
communities: spaceBloc.state.selectedCommunities,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
devices.addAll(await DevicesManagementApi().fetchDevices(
|
devices.addAll(await DevicesManagementApi().fetchDevices(
|
||||||
projectUuid,
|
projectUuid,
|
||||||
spacesId: [createRoutineBloc.selectedSpaceId],
|
spacesId: [createRoutineBloc.selectedSpaceId],
|
||||||
|
communities: spaceBloc.state.selectedCommunities,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,71 @@
|
|||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||||
|
|
||||||
|
abstract final class SpacesRecursiveHelper {
|
||||||
|
const SpacesRecursiveHelper._();
|
||||||
|
|
||||||
|
static List<SpaceModel> recusrivelyUpdate(
|
||||||
|
List<SpaceModel> spaces,
|
||||||
|
SpaceDetailsModel updatedSpace,
|
||||||
|
) {
|
||||||
|
return spaces.map((space) {
|
||||||
|
final isUpdatedSpace = space.uuid == updatedSpace.uuid;
|
||||||
|
if (isUpdatedSpace) {
|
||||||
|
return space.copyWith(
|
||||||
|
spaceName: updatedSpace.spaceName,
|
||||||
|
icon: updatedSpace.icon,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final hasChildren = space.children.isNotEmpty;
|
||||||
|
if (hasChildren) {
|
||||||
|
return space.copyWith(
|
||||||
|
children: recusrivelyUpdate(space.children, updatedSpace),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return space;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<SpaceModel> recusrivelyDelete(
|
||||||
|
List<SpaceModel> spaces,
|
||||||
|
String spaceUuid,
|
||||||
|
) {
|
||||||
|
final updatedSpaces = spaces.map((space) {
|
||||||
|
if (space.uuid == spaceUuid) return null;
|
||||||
|
if (space.children.isNotEmpty) {
|
||||||
|
return space.copyWith(
|
||||||
|
children: recusrivelyDelete(space.children, spaceUuid),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return space;
|
||||||
|
}).toList();
|
||||||
|
final nonNullSpaces = updatedSpaces.whereType<SpaceModel>().toList();
|
||||||
|
return nonNullSpaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<SpaceModel> recursivelyInsert({
|
||||||
|
required List<SpaceModel> spaces,
|
||||||
|
required String parentUuid,
|
||||||
|
required SpaceModel newSpace,
|
||||||
|
}) {
|
||||||
|
return spaces.map((space) {
|
||||||
|
final isParentSpace = space.uuid == parentUuid;
|
||||||
|
if (isParentSpace) {
|
||||||
|
return space.copyWith(
|
||||||
|
children: [...space.children, newSpace],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final hasChildren = space.children.isNotEmpty;
|
||||||
|
if (hasChildren) {
|
||||||
|
return space.copyWith(
|
||||||
|
children: recursivelyInsert(
|
||||||
|
spaces: space.children,
|
||||||
|
parentUuid: parentUuid,
|
||||||
|
newSpace: newSpace,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return space;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
@ -5,13 +5,14 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
|||||||
class SpacesConnectionsArrowPainter extends CustomPainter {
|
class SpacesConnectionsArrowPainter extends CustomPainter {
|
||||||
final List<SpaceConnectionModel> connections;
|
final List<SpaceConnectionModel> connections;
|
||||||
final Map<String, Offset> positions;
|
final Map<String, Offset> positions;
|
||||||
final double cardWidth = 150.0;
|
final Map<String, double> cardWidths;
|
||||||
final double cardHeight = 90.0;
|
final double cardHeight = 90.0;
|
||||||
final Set<String> highlightedUuids;
|
final Set<String> highlightedUuids;
|
||||||
|
|
||||||
SpacesConnectionsArrowPainter({
|
SpacesConnectionsArrowPainter({
|
||||||
required this.connections,
|
required this.connections,
|
||||||
required this.positions,
|
required this.positions,
|
||||||
|
required this.cardWidths,
|
||||||
required this.highlightedUuids,
|
required this.highlightedUuids,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -29,19 +30,30 @@ class SpacesConnectionsArrowPainter extends CustomPainter {
|
|||||||
|
|
||||||
final from = positions[connection.from];
|
final from = positions[connection.from];
|
||||||
final to = positions[connection.to];
|
final to = positions[connection.to];
|
||||||
|
final fromWidth = cardWidths[connection.from] ?? 150.0;
|
||||||
|
final toWidth = cardWidths[connection.to] ?? 150.0;
|
||||||
|
|
||||||
if (from != null && to != null) {
|
if (from != null && to != null) {
|
||||||
final startPoint =
|
final startPoint =
|
||||||
Offset(from.dx + cardWidth / 2, from.dy + cardHeight - 10);
|
Offset(from.dx + fromWidth / 2, from.dy + cardHeight - 10);
|
||||||
final endPoint = Offset(to.dx + cardWidth / 2, to.dy);
|
final endPoint = Offset(to.dx + toWidth / 2, to.dy);
|
||||||
|
|
||||||
final path = Path()..moveTo(startPoint.dx, startPoint.dy);
|
final path = Path()..moveTo(startPoint.dx, startPoint.dy);
|
||||||
|
|
||||||
final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 20);
|
if ((startPoint.dx - endPoint.dx).abs() < 1.0) {
|
||||||
final controlPoint2 = Offset(endPoint.dx, endPoint.dy - 60);
|
path.lineTo(endPoint.dx, endPoint.dy);
|
||||||
|
} else {
|
||||||
path.cubicTo(controlPoint1.dx, controlPoint1.dy, controlPoint2.dx,
|
final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 100);
|
||||||
controlPoint2.dy, endPoint.dx, endPoint.dy);
|
final controlPoint2 = Offset(endPoint.dx, endPoint.dy - 100);
|
||||||
|
path.cubicTo(
|
||||||
|
controlPoint1.dx,
|
||||||
|
controlPoint1.dy,
|
||||||
|
controlPoint2.dx,
|
||||||
|
controlPoint2.dy,
|
||||||
|
endPoint.dx,
|
||||||
|
endPoint.dy,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
canvas.drawPath(path, paint);
|
canvas.drawPath(path, paint);
|
||||||
|
|
||||||
@ -51,7 +63,7 @@ class SpacesConnectionsArrowPainter extends CustomPainter {
|
|||||||
: ColorsManager.blackColor.withValues(alpha: 0.5)
|
: ColorsManager.blackColor.withValues(alpha: 0.5)
|
||||||
..style = PaintingStyle.fill
|
..style = PaintingStyle.fill
|
||||||
..blendMode = BlendMode.srcIn;
|
..blendMode = BlendMode.srcIn;
|
||||||
canvas.drawCircle(endPoint, 4, circlePaint);
|
canvas.drawCircle(endPoint, 6, circlePaint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/presen
|
|||||||
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/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/products/presentation/bloc/products_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/remote_space_details_service.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.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';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
|
||||||
import 'package:syncrow_web/services/api/http_service.dart';
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
||||||
@ -49,7 +49,7 @@ class _SpaceManagementPageState extends State<SpaceManagementPage> {
|
|||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => SpaceDetailsBloc(
|
create: (context) => SpaceDetailsBloc(
|
||||||
UniqueSubspacesDecorator(
|
UniqueSpaceDetailsSpacesDecoratorService(
|
||||||
RemoteSpaceDetailsService(httpService: HTTPService()),
|
RemoteSpaceDetailsService(httpService: HTTPService()),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/models/space_connection_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/main_module/models/space_connection_model.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/models/space_reorder_data_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/main_module/models/space_reorder_data_model.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart';
|
import 'package:syncrow_web/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart';
|
||||||
@ -30,10 +31,11 @@ class CommunityStructureCanvas extends StatefulWidget {
|
|||||||
class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
final Map<String, Offset> _positions = {};
|
final Map<String, Offset> _positions = {};
|
||||||
final double _cardWidth = 150.0;
|
final Map<String, double> _cardWidths = {};
|
||||||
final double _cardHeight = 90.0;
|
final double _cardHeight = 90.0;
|
||||||
final double _horizontalSpacing = 150.0;
|
final double _horizontalSpacing = 150.0;
|
||||||
final double _verticalSpacing = 120.0;
|
final double _verticalSpacing = 120.0;
|
||||||
|
static const double _minCardWidth = 150.0;
|
||||||
|
|
||||||
late final TransformationController _transformationController;
|
late final TransformationController _transformationController;
|
||||||
late final AnimationController _animationController;
|
late final AnimationController _animationController;
|
||||||
@ -52,6 +54,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant CommunityStructureCanvas oldWidget) {
|
void didUpdateWidget(covariant CommunityStructureCanvas oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (widget.selectedSpace == null) return;
|
||||||
if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) {
|
if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@ -68,6 +71,34 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double _calculateCardWidth(String text) {
|
||||||
|
final style = context.textTheme.bodyLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
);
|
||||||
|
final textPainter = TextPainter(
|
||||||
|
text: TextSpan(text: text, style: style),
|
||||||
|
maxLines: 1,
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
)..layout();
|
||||||
|
|
||||||
|
const iconWidth = 40.0;
|
||||||
|
const horizontalPadding = 10.0;
|
||||||
|
const contentPadding = 10.0;
|
||||||
|
final calculatedWidth =
|
||||||
|
iconWidth + horizontalPadding + textPainter.width + contentPadding;
|
||||||
|
|
||||||
|
return calculatedWidth.clamp(_minCardWidth, double.infinity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _calculateAllCardWidths(List<SpaceModel> spaces) {
|
||||||
|
for (final space in spaces) {
|
||||||
|
_cardWidths[space.uuid] = _calculateCardWidth(space.spaceName);
|
||||||
|
if (space.children.isNotEmpty) {
|
||||||
|
_calculateAllCardWidths(space.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Set<String> _getAllDescendantUuids(SpaceModel space) {
|
Set<String> _getAllDescendantUuids(SpaceModel space) {
|
||||||
final uuids = <String>{};
|
final uuids = <String>{};
|
||||||
for (final child in space.children) {
|
for (final child in space.children) {
|
||||||
@ -102,11 +133,12 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
final position = _positions[space.uuid];
|
final position = _positions[space.uuid];
|
||||||
if (position == null) return;
|
if (position == null) return;
|
||||||
|
|
||||||
|
final cardWidth = _cardWidths[space.uuid] ?? _minCardWidth;
|
||||||
const scale = 1;
|
const scale = 1;
|
||||||
final viewSize = context.size;
|
final viewSize = context.size;
|
||||||
if (viewSize == null) return;
|
if (viewSize == null) return;
|
||||||
|
|
||||||
final x = -position.dx * scale + (viewSize.width / 2) - (_cardWidth * scale / 2);
|
final x = -position.dx * scale + (viewSize.width / 2) - (cardWidth * scale / 2);
|
||||||
final y =
|
final y =
|
||||||
-position.dy * scale + (viewSize.height / 2) - (_cardHeight * scale / 2);
|
-position.dy * scale + (viewSize.height / 2) - (_cardHeight * scale / 2);
|
||||||
|
|
||||||
@ -155,13 +187,16 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
Map<int, double> levelXOffset,
|
Map<int, double> levelXOffset,
|
||||||
) {
|
) {
|
||||||
for (final space in spaces) {
|
for (final space in spaces) {
|
||||||
|
final cardWidth = _cardWidths[space.uuid] ?? _minCardWidth;
|
||||||
double childSubtreeWidth = 0;
|
double childSubtreeWidth = 0;
|
||||||
if (space.children.isNotEmpty) {
|
if (space.children.isNotEmpty) {
|
||||||
_calculateLayout(space.children, depth + 1, levelXOffset);
|
_calculateLayout(space.children, depth + 1, levelXOffset);
|
||||||
final firstChildPos = _positions[space.children.first.uuid];
|
final firstChildPos = _positions[space.children.first.uuid];
|
||||||
final lastChildPos = _positions[space.children.last.uuid];
|
final lastChildPos = _positions[space.children.last.uuid];
|
||||||
if (firstChildPos != null && lastChildPos != null) {
|
if (firstChildPos != null && lastChildPos != null) {
|
||||||
childSubtreeWidth = (lastChildPos.dx + _cardWidth) - firstChildPos.dx;
|
final lastChildWidth =
|
||||||
|
_cardWidths[space.children.last.uuid] ?? _minCardWidth;
|
||||||
|
childSubtreeWidth = (lastChildPos.dx + lastChildWidth) - firstChildPos.dx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +205,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
|
|
||||||
if (space.children.isNotEmpty) {
|
if (space.children.isNotEmpty) {
|
||||||
final firstChildPos = _positions[space.children.first.uuid]!;
|
final firstChildPos = _positions[space.children.first.uuid]!;
|
||||||
x = firstChildPos.dx + (childSubtreeWidth - _cardWidth) / 2;
|
x = firstChildPos.dx + (childSubtreeWidth - cardWidth) / 2;
|
||||||
} else {
|
} else {
|
||||||
x = currentX;
|
x = currentX;
|
||||||
}
|
}
|
||||||
@ -187,7 +222,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
|
|
||||||
final y = depth * (_verticalSpacing + _cardHeight);
|
final y = depth * (_verticalSpacing + _cardHeight);
|
||||||
_positions[space.uuid] = Offset(x, y);
|
_positions[space.uuid] = Offset(x, y);
|
||||||
levelXOffset[depth] = x + _cardWidth + _horizontalSpacing;
|
levelXOffset[depth] = x + cardWidth + _horizontalSpacing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,8 +237,11 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
|
|
||||||
List<Widget> _buildTreeWidgets() {
|
List<Widget> _buildTreeWidgets() {
|
||||||
_positions.clear();
|
_positions.clear();
|
||||||
|
_cardWidths.clear();
|
||||||
final community = widget.community;
|
final community = widget.community;
|
||||||
|
|
||||||
|
_calculateAllCardWidths(community.spaces);
|
||||||
|
|
||||||
final levelXOffset = <int, double>{};
|
final levelXOffset = <int, double>{};
|
||||||
_calculateLayout(community.spaces, 0, levelXOffset);
|
_calculateLayout(community.spaces, 0, levelXOffset);
|
||||||
|
|
||||||
@ -231,7 +269,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
Positioned(
|
Positioned(
|
||||||
left: createButtonX,
|
left: createButtonX,
|
||||||
top: createButtonY,
|
top: createButtonY,
|
||||||
child: CreateSpaceButton(communityUuid: widget.community.uuid),
|
child: CreateSpaceButton(community: widget.community),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -240,6 +278,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
painter: SpacesConnectionsArrowPainter(
|
painter: SpacesConnectionsArrowPainter(
|
||||||
connections: connections,
|
connections: connections,
|
||||||
positions: _positions,
|
positions: _positions,
|
||||||
|
cardWidths: _cardWidths,
|
||||||
highlightedUuids: highlightedUuids,
|
highlightedUuids: highlightedUuids,
|
||||||
),
|
),
|
||||||
child: Stack(alignment: AlignmentDirectional.center, children: widgets),
|
child: Stack(alignment: AlignmentDirectional.center, children: widgets),
|
||||||
@ -271,6 +310,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final cardWidth = _cardWidths[space.uuid] ?? _minCardWidth;
|
||||||
final isHighlighted = highlightedUuids.contains(space.uuid);
|
final isHighlighted = highlightedUuids.contains(space.uuid);
|
||||||
final hasNoSelectedSpace = widget.selectedSpace == null;
|
final hasNoSelectedSpace = widget.selectedSpace == null;
|
||||||
|
|
||||||
@ -278,20 +318,29 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
buildSpaceContainer: () {
|
buildSpaceContainer: () {
|
||||||
return Opacity(
|
return Opacity(
|
||||||
opacity: hasNoSelectedSpace || isHighlighted ? 1.0 : 0.5,
|
opacity: hasNoSelectedSpace || isHighlighted ? 1.0 : 0.5,
|
||||||
child: Tooltip(
|
|
||||||
message: space.spaceName,
|
|
||||||
preferBelow: false,
|
|
||||||
child: SpaceCell(
|
child: SpaceCell(
|
||||||
onTap: () => _onSpaceTapped(space),
|
onTap: () => _onSpaceTapped(space),
|
||||||
icon: space.icon,
|
icon: space.icon,
|
||||||
name: space.spaceName,
|
name: space.spaceName,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onTap: () => SpaceDetailsDialogHelper.showCreate(
|
onTap: () => SpaceDetailsDialogHelper.showCreate(
|
||||||
context,
|
context,
|
||||||
communityUuid: widget.community.uuid,
|
communityUuid: widget.community.uuid,
|
||||||
|
parentUuid: space.uuid,
|
||||||
|
onSuccess: (updatedSpaceModel) {
|
||||||
|
final updatedSpaces = SpacesRecursiveHelper.recursivelyInsert(
|
||||||
|
spaces: widget.community.spaces,
|
||||||
|
parentUuid: space.uuid,
|
||||||
|
newSpace: updatedSpaceModel,
|
||||||
|
);
|
||||||
|
context.read<CommunitiesBloc>().add(
|
||||||
|
CommunitiesUpdateCommunity(
|
||||||
|
widget.community.copyWith(spaces: updatedSpaces),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -305,7 +354,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
Positioned(
|
Positioned(
|
||||||
left: position.dx,
|
left: position.dx,
|
||||||
top: position.dy,
|
top: position.dy,
|
||||||
width: _cardWidth,
|
width: cardWidth,
|
||||||
height: _cardHeight,
|
height: _cardHeight,
|
||||||
child: Draggable<SpaceReorderDataModel>(
|
child: Draggable<SpaceReorderDataModel>(
|
||||||
data: reorderData,
|
data: reorderData,
|
||||||
@ -314,7 +363,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
child: Opacity(
|
child: Opacity(
|
||||||
opacity: 0.2,
|
opacity: 0.2,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: _cardWidth,
|
width: cardWidth,
|
||||||
height: _cardHeight,
|
height: _cardHeight,
|
||||||
child: spaceCard,
|
child: spaceCard,
|
||||||
),
|
),
|
||||||
@ -330,7 +379,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
);
|
);
|
||||||
|
|
||||||
final targetPos = Offset(
|
final targetPos = Offset(
|
||||||
position.dx + _cardWidth + (_horizontalSpacing / 4) - 20,
|
position.dx + cardWidth + (_horizontalSpacing / 4) - 20,
|
||||||
position.dy,
|
position.dy,
|
||||||
);
|
);
|
||||||
widgets.add(_buildDropTarget(parent, community, i + 1, targetPos));
|
widgets.add(_buildDropTarget(parent, community, i + 1, targetPos));
|
||||||
@ -418,7 +467,9 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final treeWidgets = _buildTreeWidgets();
|
final treeWidgets = _buildTreeWidgets();
|
||||||
return InteractiveViewer(
|
return GestureDetector(
|
||||||
|
onTap: _resetSelectionAndZoom,
|
||||||
|
child: InteractiveViewer(
|
||||||
transformationController: _transformationController,
|
transformationController: _transformationController,
|
||||||
boundaryMargin: EdgeInsets.symmetric(
|
boundaryMargin: EdgeInsets.symmetric(
|
||||||
horizontal: context.screenWidth * 0.3,
|
horizontal: context.screenWidth * 0.3,
|
||||||
@ -427,8 +478,6 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
|||||||
minScale: 0.5,
|
minScale: 0.5,
|
||||||
maxScale: 3.0,
|
maxScale: 3.0,
|
||||||
constrained: false,
|
constrained: false,
|
||||||
child: GestureDetector(
|
|
||||||
onTap: _resetSelectionAndZoom,
|
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: context.screenWidth * 5,
|
width: context.screenWidth * 5,
|
||||||
height: context.screenHeight * 5,
|
height: context.screenHeight * 5,
|
||||||
|
@ -2,41 +2,17 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart';
|
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.dart';
|
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons_composer.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/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/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
class CommunityStructureHeader extends StatelessWidget {
|
class CommunityStructureHeader extends StatelessWidget {
|
||||||
const CommunityStructureHeader({super.key});
|
const CommunityStructureHeader({super.key});
|
||||||
|
|
||||||
List<SpaceModel> _updateRecursive(
|
|
||||||
List<SpaceModel> spaces,
|
|
||||||
SpaceDetailsModel updatedSpace,
|
|
||||||
) {
|
|
||||||
return spaces.map((space) {
|
|
||||||
if (space.uuid == updatedSpace.uuid) {
|
|
||||||
return space.copyWith(
|
|
||||||
spaceName: updatedSpace.spaceName,
|
|
||||||
icon: updatedSpace.icon,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (space.children.isNotEmpty) {
|
|
||||||
return space.copyWith(
|
|
||||||
children: _updateRecursive(space.children, updatedSpace),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return space;
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
|
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
|
||||||
@ -44,9 +20,9 @@ class CommunityStructureHeader extends StatelessWidget {
|
|||||||
color: ColorsManager.whiteColors,
|
color: ColorsManager.whiteColors,
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: ColorsManager.shadowBlackColor,
|
color: ColorsManager.shadowBlackColor.withValues(alpha: 0.1),
|
||||||
blurRadius: 8,
|
blurRadius: 20,
|
||||||
offset: const Offset(0, 4),
|
offset: const Offset(0, 1),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -57,7 +33,7 @@ class CommunityStructureHeader extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildCommunityInfo(context, theme, screenWidth),
|
child: _buildCommunityInfo(context, screenWidth),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
],
|
],
|
||||||
@ -67,8 +43,7 @@ class CommunityStructureHeader extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCommunityInfo(
|
Widget _buildCommunityInfo(BuildContext context, double screenWidth) {
|
||||||
BuildContext context, ThemeData theme, double screenWidth) {
|
|
||||||
final selectedCommunity =
|
final selectedCommunity =
|
||||||
context.watch<CommunitiesTreeSelectionBloc>().state.selectedCommunity;
|
context.watch<CommunitiesTreeSelectionBloc>().state.selectedCommunity;
|
||||||
final selectedSpace =
|
final selectedSpace =
|
||||||
@ -78,7 +53,7 @@ class CommunityStructureHeader extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Community Structure',
|
'Community Structure',
|
||||||
style: theme.textTheme.headlineLarge?.copyWith(
|
style: context.textTheme.headlineLarge?.copyWith(
|
||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -91,7 +66,7 @@ class CommunityStructureHeader extends StatelessWidget {
|
|||||||
Flexible(
|
Flexible(
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
selectedCommunity.name,
|
selectedCommunity.name,
|
||||||
style: theme.textTheme.bodyLarge?.copyWith(
|
style: context.textTheme.bodyLarge?.copyWith(
|
||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
@ -115,27 +90,8 @@ class CommunityStructureHeader extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
CommunityStructureHeaderActionButtons(
|
CommunityStructureHeaderActionButtonsComposer(
|
||||||
onDelete: (space) {},
|
selectedCommunity: selectedCommunity,
|
||||||
onDuplicate: (space) {},
|
|
||||||
onEdit: (space) => SpaceDetailsDialogHelper.showEdit(
|
|
||||||
context,
|
|
||||||
spaceModel: selectedSpace!,
|
|
||||||
communityUuid: selectedCommunity.uuid,
|
|
||||||
onSuccess: (updatedSpaceDetails) {
|
|
||||||
final communitiesBloc = context.read<CommunitiesBloc>();
|
|
||||||
final updatedSpaces = _updateRecursive(
|
|
||||||
selectedCommunity.spaces,
|
|
||||||
updatedSpaceDetails,
|
|
||||||
);
|
|
||||||
|
|
||||||
final community = selectedCommunity.copyWith(
|
|
||||||
spaces: updatedSpaces,
|
|
||||||
);
|
|
||||||
|
|
||||||
communitiesBloc.add(CommunitiesUpdateCommunity(community));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
selectedSpace: selectedSpace,
|
selectedSpace: selectedSpace,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -19,11 +19,12 @@ class CommunityStructureHeaderActionButtons extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
if (selectedSpace == null) return const SizedBox.shrink();
|
||||||
|
|
||||||
return Wrap(
|
return Wrap(
|
||||||
alignment: WrapAlignment.end,
|
alignment: WrapAlignment.end,
|
||||||
spacing: 10,
|
spacing: 10,
|
||||||
children: [
|
children: [
|
||||||
if (selectedSpace != null) ...[
|
|
||||||
CommunityStructureHeaderButton(
|
CommunityStructureHeaderButton(
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
svgAsset: Assets.editSpace,
|
svgAsset: Assets.editSpace,
|
||||||
@ -40,7 +41,6 @@ class CommunityStructureHeaderActionButtons extends StatelessWidget {
|
|||||||
onPressed: () => onDelete(selectedSpace!),
|
onPressed: () => onDelete(selectedSpace!),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
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';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.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/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/space_details/presentation/helpers/space_details_dialog_helper.dart';
|
||||||
|
|
||||||
|
class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget {
|
||||||
|
const CommunityStructureHeaderActionButtonsComposer({
|
||||||
|
required this.selectedCommunity,
|
||||||
|
required this.selectedSpace,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
final CommunityModel selectedCommunity;
|
||||||
|
final SpaceModel? selectedSpace;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return CommunityStructureHeaderActionButtons(
|
||||||
|
onDelete: (space) => showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (_) => DeleteSpaceDialog(
|
||||||
|
space: space,
|
||||||
|
community: selectedCommunity,
|
||||||
|
onSuccess: () {
|
||||||
|
final updatedSpaces = SpacesRecursiveHelper.recusrivelyDelete(
|
||||||
|
selectedCommunity.spaces,
|
||||||
|
space.uuid,
|
||||||
|
);
|
||||||
|
final community = selectedCommunity.copyWith(
|
||||||
|
spaces: updatedSpaces,
|
||||||
|
);
|
||||||
|
context.read<CommunitiesBloc>().add(
|
||||||
|
CommunitiesUpdateCommunity(community),
|
||||||
|
);
|
||||||
|
context.read<CommunitiesTreeSelectionBloc>().add(
|
||||||
|
SelectCommunityEvent(community: selectedCommunity),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onDuplicate: (space) {},
|
||||||
|
onEdit: (space) => SpaceDetailsDialogHelper.showEdit(
|
||||||
|
context,
|
||||||
|
spaceModel: selectedSpace!,
|
||||||
|
communityUuid: selectedCommunity.uuid,
|
||||||
|
onSuccess: (updatedSpaceDetails) {
|
||||||
|
final communitiesBloc = context.read<CommunitiesBloc>();
|
||||||
|
final updatedSpaces = SpacesRecursiveHelper.recusrivelyUpdate(
|
||||||
|
selectedCommunity.spaces,
|
||||||
|
updatedSpaceDetails,
|
||||||
|
);
|
||||||
|
|
||||||
|
final community = selectedCommunity.copyWith(
|
||||||
|
spaces: updatedSpaces,
|
||||||
|
);
|
||||||
|
|
||||||
|
communitiesBloc.add(CommunitiesUpdateCommunity(community));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
selectedSpace: selectedSpace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,18 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_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/space_details/presentation/helpers/space_details_dialog_helper.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class CreateSpaceButton extends StatefulWidget {
|
class CreateSpaceButton extends StatefulWidget {
|
||||||
const CreateSpaceButton({
|
const CreateSpaceButton({
|
||||||
required this.communityUuid,
|
required this.community,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String communityUuid;
|
final CommunityModel community;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CreateSpaceButton> createState() => _CreateSpaceButtonState();
|
State<CreateSpaceButton> createState() => _CreateSpaceButtonState();
|
||||||
@ -25,7 +29,21 @@ class _CreateSpaceButtonState extends State<CreateSpaceButton> {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => SpaceDetailsDialogHelper.showCreate(
|
onTap: () => SpaceDetailsDialogHelper.showCreate(
|
||||||
context,
|
context,
|
||||||
communityUuid: widget.communityUuid,
|
communityUuid: widget.community.uuid,
|
||||||
|
onSuccess: (updatedSpaceModel) {
|
||||||
|
final newCommunity = widget.community.copyWith(
|
||||||
|
spaces: [...widget.community.spaces, updatedSpaceModel],
|
||||||
|
);
|
||||||
|
context.read<CommunitiesBloc>().add(
|
||||||
|
CommunitiesUpdateCommunity(newCommunity),
|
||||||
|
);
|
||||||
|
context.read<CommunitiesTreeSelectionBloc>().add(
|
||||||
|
SelectSpaceEvent(
|
||||||
|
space: updatedSpaceModel,
|
||||||
|
community: newCommunity,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
child: MouseRegion(
|
child: MouseRegion(
|
||||||
onEnter: (_) => setState(() => _isHovered = true),
|
onEnter: (_) => setState(() => _isHovered = true),
|
||||||
|
@ -2,32 +2,23 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class PlusButtonWidget extends StatelessWidget {
|
class PlusButtonWidget extends StatelessWidget {
|
||||||
final Offset offset;
|
final void Function() onTap;
|
||||||
final void Function() onButtonTap;
|
|
||||||
|
|
||||||
const PlusButtonWidget({
|
const PlusButtonWidget({
|
||||||
|
required this.onTap,
|
||||||
super.key,
|
super.key,
|
||||||
required this.offset,
|
|
||||||
required this.onButtonTap,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return IconButton.filled(
|
||||||
onTap: onButtonTap,
|
onPressed: onTap,
|
||||||
child: Container(
|
style: IconButton.styleFrom(backgroundColor: ColorsManager.spaceColor),
|
||||||
width: 30,
|
icon: const Icon(
|
||||||
height: 30,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: ColorsManager.spaceColor,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.add,
|
Icons.add,
|
||||||
color: ColorsManager.whiteColors,
|
color: ColorsManager.whiteColors,
|
||||||
size: 20,
|
size: 20,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,10 +29,9 @@ class _SpaceCardWidgetState extends State<SpaceCardWidget> {
|
|||||||
widget.buildSpaceContainer(),
|
widget.buildSpaceContainer(),
|
||||||
if (isHovered)
|
if (isHovered)
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0,
|
bottom: -5,
|
||||||
child: PlusButtonWidget(
|
child: PlusButtonWidget(
|
||||||
offset: Offset.zero,
|
onTap: widget.onTap,
|
||||||
onButtonTap: widget.onTap,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -20,22 +20,20 @@ class SpaceCell extends StatelessWidget {
|
|||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 150,
|
padding: const EdgeInsetsDirectional.only(end: 10),
|
||||||
height: 70,
|
height: 70,
|
||||||
decoration: _containerDecoration(),
|
decoration: _containerDecoration(),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
spacing: 10,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
_buildIconContainer(),
|
_buildIconContainer(),
|
||||||
const SizedBox(width: 10),
|
Text(
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
name,
|
name,
|
||||||
style: context.textTheme.bodyLarge?.copyWith(
|
style: context.textTheme.bodyLarge?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
),
|
),
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -3,6 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart';
|
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header.dart';
|
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/create_space_button.dart';
|
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/create_space_button.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.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/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/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||||
|
|
||||||
class SpaceManagementCommunityStructure extends StatelessWidget {
|
class SpaceManagementCommunityStructure extends StatelessWidget {
|
||||||
@ -10,31 +13,59 @@ class SpaceManagementCommunityStructure extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final selectionBloc = context.watch<CommunitiesTreeSelectionBloc>().state;
|
return BlocBuilder<CommunitiesTreeSelectionBloc, CommunitiesTreeSelectionState>(
|
||||||
final selectedCommunity = selectionBloc.selectedCommunity;
|
builder: (context, state) {
|
||||||
final selectedSpace = selectionBloc.selectedSpace;
|
final selectedCommunity = state.selectedCommunity;
|
||||||
const spacer = Spacer(flex: 6);
|
final selectedSpace = state.selectedSpace;
|
||||||
return Visibility(
|
|
||||||
visible: selectedCommunity!.spaces.isNotEmpty,
|
if (selectedCommunity == null) {
|
||||||
replacement: Row(
|
return const SizedBox.shrink();
|
||||||
children: [
|
}
|
||||||
spacer,
|
|
||||||
Expanded(
|
return Column(
|
||||||
child: CreateSpaceButton(communityUuid: selectedCommunity.uuid),
|
|
||||||
),
|
|
||||||
spacer
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const CommunityStructureHeader(),
|
const CommunityStructureHeader(),
|
||||||
Expanded(
|
BlocBuilder<CommunitiesBloc, CommunitiesState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final community = state.communities.firstWhere(
|
||||||
|
(element) => element.uuid == selectedCommunity.uuid,
|
||||||
|
orElse: () => selectedCommunity,
|
||||||
|
);
|
||||||
|
return Visibility(
|
||||||
|
visible: community.spaces.isNotEmpty,
|
||||||
|
replacement: _buildEmptyWidget(community),
|
||||||
|
child: _buildCanvas(community, selectedSpace),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCanvas(
|
||||||
|
CommunityModel selectedCommunity,
|
||||||
|
SpaceModel? selectedSpace,
|
||||||
|
) {
|
||||||
|
return Expanded(
|
||||||
child: CommunityStructureCanvas(
|
child: CommunityStructureCanvas(
|
||||||
community: selectedCommunity,
|
community: selectedCommunity,
|
||||||
selectedSpace: selectedSpace,
|
selectedSpace: selectedSpace,
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildEmptyWidget(CommunityModel selectedCommunity) {
|
||||||
|
const spacer = Spacer(flex: 6);
|
||||||
|
|
||||||
|
return Expanded(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
spacer,
|
||||||
|
Expanded(child: CreateSpaceButton(community: selectedCommunity)),
|
||||||
|
spacer,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
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/create_space/domain/params/create_space_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/create_space/domain/services/create_space_service.dart';
|
||||||
|
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
|
||||||
|
final class RemoteCreateSpaceService implements CreateSpaceService {
|
||||||
|
const RemoteCreateSpaceService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
static const _defaultErrorMessage = 'Failed to create space';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<SpaceModel> createSpace(CreateSpaceParam param) async {
|
||||||
|
try {
|
||||||
|
final path = await _makeUrl(param);
|
||||||
|
final response = await _httpService.post(
|
||||||
|
path: path,
|
||||||
|
body: param.toJson(),
|
||||||
|
expectedResponseModel: (data) {
|
||||||
|
final response = data as Map<String, dynamic>;
|
||||||
|
final isSuccess = response['success'] as bool;
|
||||||
|
if (!isSuccess) {
|
||||||
|
throw APIException(response['error'] as String);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SpaceModel.fromJson(response['data'] as Map<String, dynamic>);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
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? ?? '';
|
||||||
|
final formattedErrorMessage = [
|
||||||
|
_defaultErrorMessage,
|
||||||
|
errorMessage,
|
||||||
|
].join(': ');
|
||||||
|
throw APIException(formattedErrorMessage);
|
||||||
|
} catch (e) {
|
||||||
|
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
|
||||||
|
throw APIException(formattedErrorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> _makeUrl(CreateSpaceParam param) async {
|
||||||
|
final projectUuid = await ProjectManager.getProjectUUID();
|
||||||
|
if (projectUuid == null || projectUuid.isEmpty) {
|
||||||
|
throw APIException('Project UUID is not set');
|
||||||
|
}
|
||||||
|
|
||||||
|
final communityUuid = param.communityUuid;
|
||||||
|
if (communityUuid.isEmpty) {
|
||||||
|
throw APIException('Community UUID is not set');
|
||||||
|
}
|
||||||
|
|
||||||
|
return '/projects/$projectUuid/communities/$communityUuid/spaces';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||||
|
|
||||||
|
class CreateSpaceParam {
|
||||||
|
final String communityUuid;
|
||||||
|
final SpaceDetailsModel space;
|
||||||
|
final String? parentUuid;
|
||||||
|
|
||||||
|
const CreateSpaceParam({
|
||||||
|
required this.communityUuid,
|
||||||
|
required this.space,
|
||||||
|
required this.parentUuid,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'parentUuid': parentUuid,
|
||||||
|
...space.toJson(),
|
||||||
|
'x': 0,
|
||||||
|
'y': 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -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/create_space/domain/params/create_space_param.dart';
|
||||||
|
|
||||||
|
abstract interface class CreateSpaceService {
|
||||||
|
Future<SpaceModel> createSpace(CreateSpaceParam param);
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
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/create_space/domain/params/create_space_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/create_space/domain/services/create_space_service.dart';
|
||||||
|
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||||
|
|
||||||
|
part 'create_space_event.dart';
|
||||||
|
part 'create_space_state.dart';
|
||||||
|
|
||||||
|
class CreateSpaceBloc extends Bloc<CreateSpaceEvent, CreateSpaceState> {
|
||||||
|
CreateSpaceBloc(
|
||||||
|
this._createSpaceService,
|
||||||
|
) : super(const CreateSpaceInitial()) {
|
||||||
|
on<CreateSpace>(_onCreateSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
final CreateSpaceService _createSpaceService;
|
||||||
|
|
||||||
|
Future<void> _onCreateSpace(
|
||||||
|
CreateSpace event,
|
||||||
|
Emitter<CreateSpaceState> emit,
|
||||||
|
) async {
|
||||||
|
emit(const CreateSpaceLoading());
|
||||||
|
try {
|
||||||
|
final result = await _createSpaceService.createSpace(event.param);
|
||||||
|
emit(CreateSpaceSuccess(result));
|
||||||
|
} on APIException catch (e) {
|
||||||
|
emit(CreateSpaceFailure(e.message));
|
||||||
|
} catch (e) {
|
||||||
|
emit(CreateSpaceFailure(e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
part of 'create_space_bloc.dart';
|
||||||
|
|
||||||
|
sealed class CreateSpaceEvent extends Equatable {
|
||||||
|
const CreateSpaceEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class CreateSpace extends CreateSpaceEvent {
|
||||||
|
const CreateSpace(this.param);
|
||||||
|
|
||||||
|
final CreateSpaceParam param;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [param];
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
part of 'create_space_bloc.dart';
|
||||||
|
|
||||||
|
sealed class CreateSpaceState extends Equatable {
|
||||||
|
const CreateSpaceState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class CreateSpaceInitial extends CreateSpaceState {
|
||||||
|
const CreateSpaceInitial();
|
||||||
|
}
|
||||||
|
|
||||||
|
final class CreateSpaceLoading extends CreateSpaceState {
|
||||||
|
const CreateSpaceLoading();
|
||||||
|
}
|
||||||
|
|
||||||
|
final class CreateSpaceSuccess extends CreateSpaceState {
|
||||||
|
const CreateSpaceSuccess(this.space);
|
||||||
|
|
||||||
|
final SpaceModel space;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [space];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class CreateSpaceFailure extends CreateSpaceState {
|
||||||
|
const CreateSpaceFailure(this.errorMessage);
|
||||||
|
|
||||||
|
final String errorMessage;
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/services/delete_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 RemoteDeleteSpaceService implements DeleteSpaceService {
|
||||||
|
const RemoteDeleteSpaceService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> delete(DeleteSpaceParam param) async {
|
||||||
|
try {
|
||||||
|
await _httpService.delete(
|
||||||
|
path: await _makeUrl(param),
|
||||||
|
expectedResponseModel: (json) {
|
||||||
|
final response = json as Map<String, dynamic>;
|
||||||
|
final hasSuccessfullyDeletedSpace = response['success'] as bool? ?? false;
|
||||||
|
|
||||||
|
if (!hasSuccessfullyDeletedSpace) {
|
||||||
|
throw APIException('Failed to delete space');
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasSuccessfullyDeletedSpace;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} 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(DeleteSpaceParam 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.deleteSpace
|
||||||
|
.replaceAll('{projectId}', projectUuid)
|
||||||
|
.replaceAll('{communityId}', param.communityUuid)
|
||||||
|
.replaceAll('{spaceId}', param.spaceUuid);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
class DeleteSpaceParam {
|
||||||
|
const DeleteSpaceParam({
|
||||||
|
required this.spaceUuid,
|
||||||
|
required this.communityUuid,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String spaceUuid;
|
||||||
|
final String communityUuid;
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart';
|
||||||
|
|
||||||
|
abstract interface class DeleteSpaceService {
|
||||||
|
Future<void> delete(DeleteSpaceParam param);
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/services/delete_space_service.dart';
|
||||||
|
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||||
|
|
||||||
|
part 'delete_space_event.dart';
|
||||||
|
part 'delete_space_state.dart';
|
||||||
|
|
||||||
|
class DeleteSpaceBloc extends Bloc<DeleteSpaceEvent, DeleteSpaceState> {
|
||||||
|
DeleteSpaceBloc(this._deleteSpaceService) : super(DeleteSpaceInitial()) {
|
||||||
|
on<DeleteSpace>(_onDeleteSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DeleteSpaceService _deleteSpaceService;
|
||||||
|
|
||||||
|
Future<void> _onDeleteSpace(
|
||||||
|
DeleteSpace event,
|
||||||
|
Emitter<DeleteSpaceState> emit,
|
||||||
|
) async {
|
||||||
|
emit(DeleteSpaceLoading());
|
||||||
|
try {
|
||||||
|
await _deleteSpaceService.delete(event.param);
|
||||||
|
emit(const DeleteSpaceSuccess('Space deleted successfully'));
|
||||||
|
} on APIException catch (e) {
|
||||||
|
emit(DeleteSpaceFailure(e.message));
|
||||||
|
} catch (e) {
|
||||||
|
emit(DeleteSpaceFailure(e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
part of 'delete_space_bloc.dart';
|
||||||
|
|
||||||
|
sealed class DeleteSpaceEvent extends Equatable {
|
||||||
|
const DeleteSpaceEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class DeleteSpace extends DeleteSpaceEvent {
|
||||||
|
const DeleteSpace(this.param);
|
||||||
|
|
||||||
|
final DeleteSpaceParam param;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [param];
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
part of 'delete_space_bloc.dart';
|
||||||
|
|
||||||
|
sealed class DeleteSpaceState extends Equatable {
|
||||||
|
const DeleteSpaceState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class DeleteSpaceInitial extends DeleteSpaceState {}
|
||||||
|
|
||||||
|
final class DeleteSpaceLoading extends DeleteSpaceState {}
|
||||||
|
|
||||||
|
final class DeleteSpaceSuccess extends DeleteSpaceState {
|
||||||
|
const DeleteSpaceSuccess(this.successMessage);
|
||||||
|
|
||||||
|
final String successMessage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [successMessage];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class DeleteSpaceFailure extends DeleteSpaceState {
|
||||||
|
const DeleteSpaceFailure(this.errorMessage);
|
||||||
|
|
||||||
|
final String errorMessage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [errorMessage];
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.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/delete_space/data/remote_delete_space_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog_form.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_loading_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_status_widget.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class DeleteSpaceDialog extends StatelessWidget {
|
||||||
|
const DeleteSpaceDialog({
|
||||||
|
required this.space,
|
||||||
|
required this.community,
|
||||||
|
required this.onSuccess,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final SpaceModel space;
|
||||||
|
final CommunityModel community;
|
||||||
|
final void Function() onSuccess;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (context) => DeleteSpaceBloc(
|
||||||
|
RemoteDeleteSpaceService(HTTPService()),
|
||||||
|
),
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) => Dialog(
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsetsDirectional.all(32),
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: context.screenWidth * 0.2,
|
||||||
|
),
|
||||||
|
child: BlocConsumer<DeleteSpaceBloc, DeleteSpaceState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
if (state case DeleteSpaceSuccess()) onSuccess();
|
||||||
|
},
|
||||||
|
builder: (context, state) => switch (state) {
|
||||||
|
DeleteSpaceInitial() => DeleteSpaceDialogForm(
|
||||||
|
space: space,
|
||||||
|
communityUuid: community.uuid,
|
||||||
|
),
|
||||||
|
DeleteSpaceLoading() => const DeleteSpaceLoadingWidget(),
|
||||||
|
DeleteSpaceSuccess() => DeleteSpaceStatusWidget(
|
||||||
|
message: state.successMessage,
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.check_circle,
|
||||||
|
size: 92,
|
||||||
|
color: ColorsManager.goodGreen,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DeleteSpaceFailure() => DeleteSpaceStatusWidget(
|
||||||
|
message: state.errorMessage,
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.error,
|
||||||
|
size: 92,
|
||||||
|
color: ColorsManager.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_svg/svg.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/delete_space/domain/params/delete_space_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class DeleteSpaceDialogForm extends StatelessWidget {
|
||||||
|
const DeleteSpaceDialogForm({
|
||||||
|
required this.space,
|
||||||
|
required this.communityUuid,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final SpaceModel space;
|
||||||
|
final String communityUuid;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SvgPicture.asset(Assets.xDelete, width: 36, height: 36),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SelectableText(
|
||||||
|
'Delete Space',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: context.textTheme.titleLarge?.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
SelectableText(
|
||||||
|
'Are you sure you want to delete this space? This action is irreversible',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: context.textTheme.bodyLarge?.copyWith(
|
||||||
|
color: ColorsManager.lightGreyColor,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: FilledButton(
|
||||||
|
style: _buildButtonStyle(
|
||||||
|
context,
|
||||||
|
color: ColorsManager.grey25,
|
||||||
|
textColor: ColorsManager.blackColor,
|
||||||
|
),
|
||||||
|
onPressed: Navigator.of(context).pop,
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: FilledButton(
|
||||||
|
style: _buildButtonStyle(
|
||||||
|
context,
|
||||||
|
color: ColorsManager.semiTransparentRed,
|
||||||
|
textColor: ColorsManager.whiteColors,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
context.read<DeleteSpaceBloc>().add(
|
||||||
|
DeleteSpace(
|
||||||
|
DeleteSpaceParam(
|
||||||
|
spaceUuid: space.uuid,
|
||||||
|
communityUuid: communityUuid,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Text('Delete'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonStyle _buildButtonStyle(
|
||||||
|
BuildContext context, {
|
||||||
|
required Color color,
|
||||||
|
required Color textColor,
|
||||||
|
}) {
|
||||||
|
return FilledButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
backgroundColor: color,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
foregroundColor: textColor,
|
||||||
|
textStyle: context.textTheme.bodyLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class DeleteSpaceLoadingWidget extends StatelessWidget {
|
||||||
|
const DeleteSpaceLoadingWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const SizedBox.square(
|
||||||
|
dimension: 32,
|
||||||
|
child: Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class DeleteSpaceStatusWidget extends StatelessWidget {
|
||||||
|
const DeleteSpaceStatusWidget({
|
||||||
|
required this.message,
|
||||||
|
required this.icon,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String message;
|
||||||
|
final Widget icon;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
icon,
|
||||||
|
SelectableText(
|
||||||
|
message,
|
||||||
|
style: context.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontSize: 22,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: Navigator.of(context).pop,
|
||||||
|
child: const Text('Close'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,24 +1,29 @@
|
|||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/subspace.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/services/space_details_service.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/services/space_details_service.dart';
|
||||||
|
|
||||||
class UniqueSubspacesDecorator implements SpaceDetailsService {
|
class UniqueSpaceDetailsSpacesDecoratorService implements SpaceDetailsService {
|
||||||
final SpaceDetailsService _decoratee;
|
final SpaceDetailsService _decoratee;
|
||||||
|
|
||||||
const UniqueSubspacesDecorator(this._decoratee);
|
const UniqueSpaceDetailsSpacesDecoratorService(this._decoratee);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<SpaceDetailsModel> getSpaceDetails(LoadSpaceDetailsParam param) async {
|
Future<SpaceDetailsModel> getSpaceDetails(LoadSpaceDetailsParam param) async {
|
||||||
final response = await _decoratee.getSpaceDetails(param);
|
final response = await _decoratee.getSpaceDetails(param);
|
||||||
|
|
||||||
final uniqueSubspaces = <String, Subspace>{};
|
final uniqueSubspaces = <String, Subspace>{};
|
||||||
|
final duplicateNames = <String>{};
|
||||||
|
|
||||||
for (final subspace in response.subspaces) {
|
for (final subspace in response.subspaces) {
|
||||||
final normalizedName = subspace.name.trim().toLowerCase();
|
final normalizedName = subspace.name.trim().toLowerCase();
|
||||||
if (!uniqueSubspaces.containsKey(normalizedName)) {
|
if (uniqueSubspaces.containsKey(normalizedName)) {
|
||||||
|
duplicateNames.add(normalizedName);
|
||||||
|
} else {
|
||||||
uniqueSubspaces[normalizedName] = subspace;
|
uniqueSubspaces[normalizedName] = subspace;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
duplicateNames.forEach(uniqueSubspaces.remove);
|
||||||
|
|
||||||
return response.copyWith(
|
return response.copyWith(
|
||||||
subspaces: uniqueSubspaces.values.toList(),
|
subspaces: uniqueSubspaces.values.toList(),
|
@ -0,0 +1,47 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/products/domain/models/product.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
class ProductAllocation extends Equatable {
|
||||||
|
final String uuid;
|
||||||
|
final Product product;
|
||||||
|
final Tag tag;
|
||||||
|
|
||||||
|
const ProductAllocation({
|
||||||
|
required this.uuid,
|
||||||
|
required this.product,
|
||||||
|
required this.tag,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ProductAllocation.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ProductAllocation(
|
||||||
|
uuid: json['uuid'] as String? ?? const Uuid().v4(),
|
||||||
|
product: Product.fromJson(json['product'] as Map<String, dynamic>),
|
||||||
|
tag: Tag.fromJson(json['tag'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProductAllocation copyWith({
|
||||||
|
String? uuid,
|
||||||
|
Product? product,
|
||||||
|
Tag? tag,
|
||||||
|
}) {
|
||||||
|
return ProductAllocation(
|
||||||
|
uuid: uuid ?? this.uuid,
|
||||||
|
product: product ?? this.product,
|
||||||
|
tag: tag ?? this.tag,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final isNewTag = tag.uuid.isEmpty;
|
||||||
|
return <String, dynamic>{
|
||||||
|
if (isNewTag) 'tagName': tag.name else 'tagUuid': tag.uuid,
|
||||||
|
'productUuid': product.uuid,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [uuid, product, tag];
|
||||||
|
}
|
@ -1,8 +1,7 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/domain/models/product.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/product_allocation.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/subspace.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
|
||||||
|
|
||||||
class SpaceDetailsModel extends Equatable {
|
class SpaceDetailsModel extends Equatable {
|
||||||
final String uuid;
|
final String uuid;
|
||||||
@ -26,6 +25,7 @@ class SpaceDetailsModel extends Equatable {
|
|||||||
productAllocations: [],
|
productAllocations: [],
|
||||||
subspaces: [],
|
subspaces: [],
|
||||||
);
|
);
|
||||||
|
|
||||||
factory SpaceDetailsModel.fromJson(Map<String, dynamic> json) {
|
factory SpaceDetailsModel.fromJson(Map<String, dynamic> json) {
|
||||||
return SpaceDetailsModel(
|
return SpaceDetailsModel(
|
||||||
uuid: json['uuid'] as String,
|
uuid: json['uuid'] as String,
|
||||||
@ -56,78 +56,21 @@ class SpaceDetailsModel extends Equatable {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Map<String, dynamic> toJson() {
|
||||||
List<Object?> get props => [uuid, spaceName, icon, productAllocations, subspaces];
|
return {
|
||||||
}
|
'spaceName': spaceName,
|
||||||
|
'icon': icon,
|
||||||
class ProductAllocation extends Equatable {
|
'subspaces': subspaces.map((e) => e.toJson()).toList(),
|
||||||
final String uuid;
|
'productAllocations': productAllocations.map((e) => e.toJson()).toList(),
|
||||||
final Product product;
|
};
|
||||||
final Tag tag;
|
|
||||||
|
|
||||||
const ProductAllocation({
|
|
||||||
required this.uuid,
|
|
||||||
required this.product,
|
|
||||||
required this.tag,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory ProductAllocation.fromJson(Map<String, dynamic> json) {
|
|
||||||
return ProductAllocation(
|
|
||||||
uuid: json['uuid'] as String? ?? const Uuid().v4(),
|
|
||||||
product: Product.fromJson(json['product'] as Map<String, dynamic>),
|
|
||||||
tag: Tag.fromJson(json['tag'] as Map<String, dynamic>),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ProductAllocation copyWith({
|
|
||||||
String? uuid,
|
|
||||||
Product? product,
|
|
||||||
Tag? tag,
|
|
||||||
}) {
|
|
||||||
return ProductAllocation(
|
|
||||||
uuid: uuid ?? this.uuid,
|
|
||||||
product: product ?? this.product,
|
|
||||||
tag: tag ?? this.tag,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [uuid, product, tag];
|
List<Object?> get props => [
|
||||||
}
|
uuid,
|
||||||
|
spaceName,
|
||||||
class Subspace extends Equatable {
|
icon,
|
||||||
final String uuid;
|
productAllocations,
|
||||||
final String name;
|
subspaces,
|
||||||
final List<ProductAllocation> productAllocations;
|
];
|
||||||
|
|
||||||
const Subspace({
|
|
||||||
required this.uuid,
|
|
||||||
required this.name,
|
|
||||||
required this.productAllocations,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory Subspace.fromJson(Map<String, dynamic> json) {
|
|
||||||
return Subspace(
|
|
||||||
uuid: json['uuid'] as String,
|
|
||||||
name: json['subspaceName'] as String,
|
|
||||||
productAllocations: (json['productAllocations'] as List)
|
|
||||||
.map((e) => ProductAllocation.fromJson(e as Map<String, dynamic>))
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Subspace copyWith({
|
|
||||||
String? uuid,
|
|
||||||
String? name,
|
|
||||||
List<ProductAllocation>? productAllocations,
|
|
||||||
}) {
|
|
||||||
return Subspace(
|
|
||||||
uuid: uuid ?? this.uuid,
|
|
||||||
name: name ?? this.name,
|
|
||||||
productAllocations: productAllocations ?? this.productAllocations,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [uuid, name, productAllocations];
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/product_allocation.dart';
|
||||||
|
|
||||||
|
class Subspace extends Equatable {
|
||||||
|
final String uuid;
|
||||||
|
final String name;
|
||||||
|
final List<ProductAllocation> productAllocations;
|
||||||
|
|
||||||
|
const Subspace({
|
||||||
|
required this.uuid,
|
||||||
|
required this.name,
|
||||||
|
required this.productAllocations,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Subspace.fromJson(Map<String, dynamic> json) {
|
||||||
|
return Subspace(
|
||||||
|
uuid: json['uuid'] as String,
|
||||||
|
name: json['subspaceName'] as String,
|
||||||
|
productAllocations: (json['productAllocations'] as List)
|
||||||
|
.map((e) => ProductAllocation.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final isNewSubspace = uuid.endsWith('-NewTag');
|
||||||
|
return <String, dynamic>{
|
||||||
|
if (!isNewSubspace) 'uuid': uuid,
|
||||||
|
'subspaceName': name,
|
||||||
|
'productAllocations': productAllocations.map((e) => e.toJson()).toList(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Subspace copyWith({
|
||||||
|
String? uuid,
|
||||||
|
String? name,
|
||||||
|
List<ProductAllocation>? productAllocations,
|
||||||
|
}) {
|
||||||
|
return Subspace(
|
||||||
|
uuid: uuid ?? this.uuid,
|
||||||
|
name: name ?? this.name,
|
||||||
|
productAllocations: productAllocations ?? this.productAllocations,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [uuid, name, productAllocations];
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.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/communities/domain/models/space_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/create_space/data/services/remote_create_space_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/create_space/presentation/bloc/create_space_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/remote_space_details_service.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
|
||||||
@ -14,6 +17,8 @@ abstract final class SpaceDetailsDialogHelper {
|
|||||||
static void showCreate(
|
static void showCreate(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required String communityUuid,
|
required String communityUuid,
|
||||||
|
required void Function(SpaceModel updatedSpaceModel)? onSuccess,
|
||||||
|
String? parentUuid,
|
||||||
}) {
|
}) {
|
||||||
showDialog<void>(
|
showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
@ -24,14 +29,41 @@ abstract final class SpaceDetailsDialogHelper {
|
|||||||
RemoteSpaceDetailsService(httpService: HTTPService()),
|
RemoteSpaceDetailsService(httpService: HTTPService()),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => CreateSpaceBloc(
|
||||||
|
RemoteCreateSpaceService(HTTPService()),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) => SpaceDetailsDialog(
|
builder: (context) => BlocListener<CreateSpaceBloc, CreateSpaceState>(
|
||||||
|
listener: (context, state) => switch (state) {
|
||||||
|
CreateSpaceInitial() => null,
|
||||||
|
CreateSpaceLoading() => _onLoading(context),
|
||||||
|
CreateSpaceSuccess() => _onCreateSuccess(
|
||||||
|
context,
|
||||||
|
state.space,
|
||||||
|
onSuccess,
|
||||||
|
),
|
||||||
|
CreateSpaceFailure() => _onError(context, state.errorMessage),
|
||||||
|
},
|
||||||
|
child: SpaceDetailsDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: const SelectableText('Create Space'),
|
title: const SelectableText('Create Space'),
|
||||||
spaceModel: SpaceModel.empty(),
|
spaceModel: SpaceModel.empty(),
|
||||||
onSave: (space) {},
|
onSave: (space) {
|
||||||
|
context.read<CreateSpaceBloc>().add(
|
||||||
|
CreateSpace(
|
||||||
|
CreateSpaceParam(
|
||||||
communityUuid: communityUuid,
|
communityUuid: communityUuid,
|
||||||
|
space: space,
|
||||||
|
parentUuid: parentUuid,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
communityUuid: communityUuid,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -135,4 +167,14 @@ abstract final class SpaceDetailsDialogHelper {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _onCreateSuccess(
|
||||||
|
BuildContext context,
|
||||||
|
SpaceModel space,
|
||||||
|
void Function(SpaceModel updatedSpaceModel)? onSuccess,
|
||||||
|
) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
onSuccess?.call(space);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/common/edit_chip.dart';
|
import 'package:syncrow_web/common/edit_chip.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/subspace.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/button_content_widget.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/button_content_widget.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_dialog.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_dialog.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_name_display_widget.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_name_display_widget.dart';
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/subspace.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_action_buttons.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_action_buttons.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/sub_spaces_input.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/sub_spaces_input.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/subspace.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_chip.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_chip.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/subspace.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/subspace.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/update_space/presentation/bloc/space_details_model_bloc/space_details_model_bloc.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/update_space/presentation/bloc/space_details_model_bloc/space_details_model_bloc.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
@ -10,7 +10,12 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
|||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
class AddDeviceTypeWidget extends StatefulWidget {
|
class AddDeviceTypeWidget extends StatefulWidget {
|
||||||
const AddDeviceTypeWidget({super.key});
|
const AddDeviceTypeWidget({
|
||||||
|
super.key,
|
||||||
|
this.initialProducts = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<Product> initialProducts;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AddDeviceTypeWidget> createState() => _AddDeviceTypeWidgetState();
|
State<AddDeviceTypeWidget> createState() => _AddDeviceTypeWidgetState();
|
||||||
@ -18,6 +23,16 @@ class AddDeviceTypeWidget extends StatefulWidget {
|
|||||||
|
|
||||||
class _AddDeviceTypeWidgetState extends State<AddDeviceTypeWidget> {
|
class _AddDeviceTypeWidgetState extends State<AddDeviceTypeWidget> {
|
||||||
final Map<Product, int> _selectedProducts = {};
|
final Map<Product, int> _selectedProducts = {};
|
||||||
|
final Map<Product, int> _initialProductCounts = {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
for (final product in widget.initialProducts) {
|
||||||
|
_initialProductCounts[product] = (_initialProductCounts[product] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
_selectedProducts.addAll(_initialProductCounts);
|
||||||
|
}
|
||||||
|
|
||||||
void _onIncrement(Product product) {
|
void _onIncrement(Product product) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -27,8 +42,12 @@ class _AddDeviceTypeWidgetState extends State<AddDeviceTypeWidget> {
|
|||||||
|
|
||||||
void _onDecrement(Product product) {
|
void _onDecrement(Product product) {
|
||||||
setState(() {
|
setState(() {
|
||||||
if ((_selectedProducts[product] ?? 0) > 0) {
|
final initialCount = _initialProductCounts[product] ?? 0;
|
||||||
_selectedProducts[product] = _selectedProducts[product]! - 1;
|
final currentCount = _selectedProducts[product] ?? 0;
|
||||||
|
if (currentCount > initialCount) {
|
||||||
|
_selectedProducts[product] = currentCount - 1;
|
||||||
|
} else if (currentCount > 0 && initialCount == 0) {
|
||||||
|
_selectedProducts[product] = currentCount - 1;
|
||||||
if (_selectedProducts[product] == 0) {
|
if (_selectedProducts[product] == 0) {
|
||||||
_selectedProducts.remove(product);
|
_selectedProducts.remove(product);
|
||||||
}
|
}
|
||||||
@ -63,7 +82,22 @@ class _AddDeviceTypeWidgetState extends State<AddDeviceTypeWidget> {
|
|||||||
actions: [
|
actions: [
|
||||||
SpaceDetailsActionButtons(
|
SpaceDetailsActionButtons(
|
||||||
onSave: () {
|
onSave: () {
|
||||||
final result = _selectedProducts.entries
|
final resultMap = <Product, int>{};
|
||||||
|
resultMap.addAll(_selectedProducts);
|
||||||
|
|
||||||
|
for (final entry in _initialProductCounts.entries) {
|
||||||
|
final product = entry.key;
|
||||||
|
final initialCount = entry.value;
|
||||||
|
final currentCount = resultMap[product] ?? 0;
|
||||||
|
|
||||||
|
if (currentCount > initialCount) {
|
||||||
|
resultMap[product] = currentCount - initialCount;
|
||||||
|
} else {
|
||||||
|
resultMap.remove(product);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final result = resultMap.entries
|
||||||
.expand((entry) => List.generate(entry.value, (_) => entry.key))
|
.expand((entry) => List.generate(entry.value, (_) => entry.key))
|
||||||
.toList();
|
.toList();
|
||||||
Navigator.of(context).pop(result);
|
Navigator.of(context).pop(result);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/domain/models/product.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/products/domain/models/product.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/product_allocation.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_action_buttons.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_action_buttons.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart';
|
||||||
@ -205,7 +206,14 @@ class _AssignTagsDialogState extends State<AssignTagsDialog> {
|
|||||||
onCancel: () async {
|
onCancel: () async {
|
||||||
final newProducts = await showDialog<List<Product>>(
|
final newProducts = await showDialog<List<Product>>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => const AddDeviceTypeWidget(),
|
builder: (context) => AddDeviceTypeWidget(
|
||||||
|
initialProducts: [
|
||||||
|
..._space.productAllocations.map((e) => e.product),
|
||||||
|
..._space.subspaces
|
||||||
|
.expand((s) => s.productAllocations)
|
||||||
|
.map((e) => e.product),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (newProducts == null || newProducts.isEmpty) return;
|
if (newProducts == null || newProducts.isEmpty) return;
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/common/dialog_dropdown.dart';
|
import 'package:syncrow_web/common/dialog_dropdown.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/product_allocation.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/subspace.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/data/services/remote_tags_service.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/tags/data/services/remote_tags_service.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/presentation/bloc/tags_bloc.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/tags/presentation/bloc/tags_bloc.dart';
|
||||||
|
@ -34,7 +34,7 @@ class RemoteUpdateSpaceService implements UpdateSpaceService {
|
|||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
final message = e.response?.data as Map<String, dynamic>?;
|
final message = e.response?.data as Map<String, dynamic>?;
|
||||||
final error = message?['error'] as Map<String, dynamic>?;
|
final error = message?['error'] as Map<String, dynamic>?;
|
||||||
final errorMessage = error?['error'] as String? ?? '';
|
final errorMessage = error?['message'] as String? ?? '';
|
||||||
final formattedErrorMessage = [
|
final formattedErrorMessage = [
|
||||||
_defaultErrorMessage,
|
_defaultErrorMessage,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
|
@ -9,34 +9,5 @@ class UpdateSpaceParam {
|
|||||||
final SpaceDetailsModel space;
|
final SpaceDetailsModel space;
|
||||||
final String communityUuid;
|
final String communityUuid;
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() => space.toJson();
|
||||||
return {
|
|
||||||
'spaceName': space.spaceName,
|
|
||||||
'icon': space.icon,
|
|
||||||
'subspaces': space.subspaces.map((e) => e._toJson()).toList(),
|
|
||||||
'productAllocations':
|
|
||||||
space.productAllocations.map((e) => e._toJson()).toList(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension _ProductAllocationToJson on ProductAllocation {
|
|
||||||
Map<String, dynamic> _toJson() {
|
|
||||||
final isNewTag = tag.uuid.isEmpty;
|
|
||||||
return <String, dynamic>{
|
|
||||||
if (isNewTag) 'tagName': tag.name else 'tagUuid': tag.uuid,
|
|
||||||
'productUuid': product.uuid,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension _SubspaceToJson on Subspace {
|
|
||||||
Map<String, dynamic> _toJson() {
|
|
||||||
final isNewSubspace = uuid.endsWith('-NewTag');
|
|
||||||
return <String, dynamic>{
|
|
||||||
if (!isNewSubspace) 'uuid': uuid,
|
|
||||||
'subspaceName': name,
|
|
||||||
'productAllocations': productAllocations.map((e) => e._toJson()).toList(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/product_allocation.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/subspace.dart';
|
||||||
|
|
||||||
part 'space_details_model_event.dart';
|
part 'space_details_model_event.dart';
|
||||||
|
|
||||||
|
@ -13,11 +13,15 @@ import 'package:syncrow_web/utils/constants/api_const.dart';
|
|||||||
|
|
||||||
class DevicesManagementApi {
|
class DevicesManagementApi {
|
||||||
Future<List<AllDevicesModel>> fetchDevices(String projectId,
|
Future<List<AllDevicesModel>> fetchDevices(String projectId,
|
||||||
{List<String>? spacesId}) async {
|
{List<String>? spacesId, List<String>? communities}) async {
|
||||||
try {
|
try {
|
||||||
final response = await HTTPService().get(
|
final response = await HTTPService().get(
|
||||||
path: ApiEndpoints.getSpaceDevices.replaceAll('{projectId}', projectId),
|
path: ApiEndpoints.getSpaceDevices.replaceAll('{projectId}', projectId),
|
||||||
queryParameters: {if (spacesId != null) 'spaces': spacesId},
|
queryParameters: {
|
||||||
|
if (spacesId != null && spacesId.isNotEmpty) 'spaces': spacesId,
|
||||||
|
if (communities != null && communities.isNotEmpty)
|
||||||
|
'communities': communities,
|
||||||
|
},
|
||||||
showServerMessage: true,
|
showServerMessage: true,
|
||||||
expectedResponseModel: (json) {
|
expectedResponseModel: (json) {
|
||||||
final List<dynamic> jsonData = json['data'] as List<dynamic>;
|
final List<dynamic> jsonData = json['data'] as List<dynamic>;
|
||||||
|
@ -4,9 +4,7 @@ import 'package:syncrow_web/services/api/http_interceptor.dart';
|
|||||||
import 'package:syncrow_web/services/api/http_service.dart';
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
|
||||||
final GetIt serviceLocator = GetIt.instance;
|
final GetIt serviceLocator = GetIt.instance;
|
||||||
//setupLocator() // to search for dependency injection in flutter
|
void initialSetup() {
|
||||||
initialSetup() {
|
|
||||||
serviceLocator.registerSingleton<HTTPInterceptor>(HTTPInterceptor());
|
serviceLocator.registerSingleton<HTTPInterceptor>(HTTPInterceptor());
|
||||||
//Base classes
|
|
||||||
serviceLocator.registerSingleton<Dio>(HTTPService.setupDioClient());
|
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:go_router/go_router.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/view/access_management.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/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/auth/view/login_page.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/view/device_managment_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/home/view/home_page.dart';
|
||||||
import 'package:syncrow_web/pages/roles_and_permission/view/roles_and_permission_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/pages/visitor_password/view/visitor_password_dialog.dart';
|
||||||
import 'package:syncrow_web/utils/constants/routes_const.dart';
|
import 'package:syncrow_web/utils/constants/routes_const.dart';
|
||||||
|
|
||||||
class AppRoutes {
|
abstract final class AppRoutes {
|
||||||
static List<GoRoute> getRoutes() {
|
const AppRoutes._();
|
||||||
return [
|
|
||||||
|
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(
|
GoRoute(
|
||||||
path: RoutesConst.auth,
|
path: RoutesConst.auth,
|
||||||
builder: (context, state) => const LoginPage(),
|
builder: (context, state) => const LoginPage(),
|
||||||
@ -43,6 +57,6 @@ class AppRoutes {
|
|||||||
name: 'analytics',
|
name: 'analytics',
|
||||||
builder: (context, state) => const AnalyticsPage(),
|
builder: (context, state) => const AnalyticsPage(),
|
||||||
),
|
),
|
||||||
];
|
],
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
@ -140,6 +140,5 @@ abstract class ApiEndpoints {
|
|||||||
static const String saveSchedule = '/schedule/{deviceUuid}';
|
static const String saveSchedule = '/schedule/{deviceUuid}';
|
||||||
|
|
||||||
static const String getBookableSpaces = '/bookable-spaces';
|
static const String getBookableSpaces = '/bookable-spaces';
|
||||||
static const String getBookings =
|
static const String getBookings = '/bookings?month={mm}-{yyyy}&space={space}';
|
||||||
'/bookings?month={mm}%2F{yyyy}&space={space}';
|
|
||||||
}
|
}
|
||||||
|
@ -517,4 +517,5 @@ class Assets {
|
|||||||
static const String emptyRangeOfAqi = 'assets/icons/empty_range_of_aqi.svg';
|
static const String emptyRangeOfAqi = 'assets/icons/empty_range_of_aqi.svg';
|
||||||
static const String homeIcon = 'assets/icons/home_icon.svg';
|
static const String homeIcon = 'assets/icons/home_icon.svg';
|
||||||
static const String groupIcon = 'assets/icons/group_icon.svg';
|
static const String groupIcon = 'assets/icons/group_icon.svg';
|
||||||
|
static const String xDelete = 'assets/icons/x_delete.svg';
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user