Compare commits

..

64 Commits

Author SHA1 Message Date
4896b6c593 changed endpoint for communtiies and spaces, hardcoded p[roject value for now 2024-12-10 09:50:42 +04:00
246098b83a Read icon from fetch scene api 2024-12-05 12:42:53 +03:00
26f50d59dd Bug fixes 2024-12-05 11:19:22 +03:00
610cdf83a0 Merge pull request #59 from SyncrowIOT/bugfix/SP-852
Bugfix/sp 852
2024-12-04 12:10:02 +04:00
d423a3eb59 added focused border 2024-12-04 11:14:02 +04:00
9bddd151bb added bloc for create community 2024-12-04 11:05:46 +04:00
0b628c85a5 updated create community dialog to mange edit/create 2024-12-04 10:36:41 +04:00
768996ca45 Merge pull request #58 from SyncrowIOT/dev
Dev
2024-12-03 16:02:31 +03:00
7ee335ab1a Bug fixes 2024-12-03 12:01:17 +03:00
d5ad06335b Merge pull request #57 from SyncrowIOT/routines_issues
Routines issues
2024-12-03 01:04:12 +03:00
d9ef5b4574 push fixes and merge 2024-12-03 01:02:13 +03:00
c58f2eb24e fixed autoamtion update 2024-12-03 00:29:59 +03:00
4e94d2df89 Bug fixes 2024-12-03 00:02:52 +03:00
2f5c5d7da1 rule_disable selected & CreateAutomation actionType 2024-12-02 16:49:44 +03:00
ff4ce8ed01 Bug fixes 2024-12-02 03:58:31 +03:00
1aa6b78fb4 Bug fixes 2024-12-01 12:05:19 +03:00
e003d9b8b3 Merge pull request #56 from SyncrowIOT/routines_fixes
Routines fixes
2024-12-01 10:50:54 +03:00
76e44c8910 push changes 2024-12-01 01:07:26 +03:00
02c788d03a merge and push 2024-12-01 00:43:30 +03:00
938f8c6b51 update scene debug 2024-12-01 00:42:00 +03:00
3ce9a313f7 Bug fixes 2024-12-01 00:21:15 +03:00
b732b0b957 Pulled latest changes 2024-11-30 21:37:55 +03:00
fb6b922df4 Added delete scene function 2024-11-30 20:45:25 +03:00
ce6e7700bc push changes 2024-11-30 20:43:21 +03:00
8d388ac482 Merge pull request #55 from SyncrowIOT/bugfix/community-flow
Bugfix/community flow
2024-11-29 18:36:03 +03:00
42c7577b35 validity for space name 2024-11-29 19:03:27 +04:00
ae2b4b9b06 border 2024-11-29 17:31:37 +04:00
98faef2bde typo 2024-11-29 17:30:03 +04:00
edf297c569 border 2024-11-29 17:29:18 +04:00
2cd850b66f border color change to red 2024-11-29 17:26:57 +04:00
3c543b2e0f added validation for community name 2024-11-29 17:22:00 +04:00
a3c6421b0d updated flow for create community 2024-11-29 16:50:49 +04:00
875787919e Merge pull request #54 from SyncrowIOT/bugfix/add-new-community
Bugfix/add new community
2024-11-29 14:41:52 +03:00
51402720dd space load on space save 2024-11-28 22:50:05 +04:00
123291fd89 parse incoming connection 2024-11-28 20:31:19 +04:00
69e3f50269 reduced width of text controller 2024-11-28 20:11:16 +04:00
bd78c8c1cc format 2024-11-28 20:09:28 +04:00
4047664c59 create community should move back to blank page 2024-11-28 20:07:18 +04:00
084f4fbda8 updated highlighted space 2024-11-28 20:02:41 +04:00
817671dbeb updated deselect on structure 2024-11-28 20:01:14 +04:00
660b5aaf6c remove unused code 2024-11-28 19:56:40 +04:00
0f500a4efa remove unused state functions 2024-11-28 19:55:45 +04:00
0b2d44fc2b updated LoadedSpaceView 2024-11-28 19:54:04 +04:00
b75f8a3440 side bar widget uses context 2024-11-28 19:52:20 +04:00
d02d680520 add events for select space and community 2024-11-28 19:45:24 +04:00
68ae992963 added select space and select community event 2024-11-28 19:40:44 +04:00
ea20afd34e fixed issue of not recursively calling communtiy list 2024-11-28 15:16:07 +04:00
b0f0a91cda Merge pull request #49 from SyncrowIOT/feature/space-management
Feature/space management
2024-11-28 12:15:06 +03:00
5da42e975a Merge pull request #46 from SyncrowIOT/dev
Dev
2024-10-30 16:38:18 +03:00
bc02664a09 Merge pull request #40 from SyncrowIOT/dev
Dev
2024-10-09 21:36:15 +03:00
057ea8515a Merge pull request #37 from SyncrowIOT/dev
Dev
2024-10-09 11:50:32 +03:00
9cc9fd7f24 Merge pull request #24 from SyncrowIOT/dev
Dev
2024-09-12 16:32:36 +03:00
f5958c1599 Merge pull request #21 from SyncrowIOT/dev
fix changes
2024-09-09 15:03:21 +03:00
1d0ef20015 Merge pull request #20 from SyncrowIOT/dev
fix changes
2024-09-09 14:30:05 +03:00
2b7a70e0a1 Merge pull request #19 from SyncrowIOT/dev
Dev
2024-09-09 13:13:36 +03:00
321a9b5fa2 Merge pull request #17 from SyncrowIOT/dev
Dev
2024-09-09 12:17:55 +03:00
9058f29787 Merged with dev 2024-09-08 23:24:04 +03:00
fd497d5797 Updated the pipeline 2024-09-04 17:05:13 +03:00
5f4aa93e01 remove last workflows 2024-09-04 17:01:28 +03:00
96bf262466 setup new staging 2024-09-04 16:55:06 +03:00
2969e936d0 ci: add Azure Static Web Apps workflow file
on-behalf-of: @Azure opensource@microsoft.com
2024-09-04 16:51:55 +03:00
e6d0e95ddc Updated github workflowf file 2024-09-04 01:07:31 +03:00
c7b1ed5b8e Added staging base url 2024-09-04 00:50:16 +03:00
d0e7d12279 test deploy 2024-09-03 17:06:28 +03:00
70 changed files with 2666 additions and 1464 deletions

View File

@ -0,0 +1,60 @@
name: Azure Static Web Apps CI/CD
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, closed]
branches:
- main
jobs:
build_and_deploy_job:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
runs-on: ubuntu-latest
name: Build and Deploy Job
steps:
- name: Checkout Code
uses: actions/checkout@v3
with:
submodules: true
lfs: false
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.22.2' # Specify the Flutter version you want to use
- name: Install dependencies
run: flutter pub get
- name: Build Flutter Web App
run: flutter build web --release --dart-define=FLAVOR=production
- name: Build And Deploy
id: builddeploy
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_MANGO_BUSH_01E607F10 }}
repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
action: "upload"
###### Repository/Build Configurations - These values can be configured to match your app requirements. ######
# For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
app_location: "/build/web" # App source code path
api_location: "" # Api source code path - optional
output_location: "/build/web" # Built app content directory - optional
###### End of Repository/Build Configurations ######
close_pull_request_job:
if: github.event_name == 'pull_request' && github.event.action == 'closed'
runs-on: ubuntu-latest
name: Close Pull Request Job
steps:
- name: Close Pull Request
id: closepullrequest
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_MANGO_BUSH_01E607F10 }}
action: "close"

View File

@ -1,28 +0,0 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
part 'switch_tabs_event.dart';
part 'switch_tabs_state.dart';
class SwitchTabsBloc extends Bloc<SwitchTabsEvent, SwitchTabsState> {
SwitchTabsBloc() : super(SwitchTabsInitial()) {
on<TriggerSwitchTabsEvent>(_switchTab);
on<CreateNewRoutineViewEvent>(_newRoutineView);
}
FutureOr<void> _switchTab(
TriggerSwitchTabsEvent event,
Emitter<SwitchTabsState> emit,
) {
emit(SelectedTabState(event.isRoutineView));
}
FutureOr<void> _newRoutineView(
CreateNewRoutineViewEvent event,
Emitter<SwitchTabsState> emit,
) {
emit(ShowCreateRoutineState(event.showCreateNewRoutineView));
}
}

View File

@ -1,21 +0,0 @@
part of 'switch_tabs_bloc.dart';
sealed class SwitchTabsEvent extends Equatable {
const SwitchTabsEvent();
}
class TriggerSwitchTabsEvent extends SwitchTabsEvent {
final bool isRoutineView;
const TriggerSwitchTabsEvent(this.isRoutineView);
@override
List<Object?> get props => [isRoutineView];
}
class CreateNewRoutineViewEvent extends SwitchTabsEvent {
final bool showCreateNewRoutineView;
const CreateNewRoutineViewEvent(this.showCreateNewRoutineView);
@override
List<Object?> get props => [showCreateNewRoutineView];
}

View File

@ -1,26 +0,0 @@
part of 'switch_tabs_bloc.dart';
sealed class SwitchTabsState extends Equatable {
const SwitchTabsState();
}
final class SwitchTabsInitial extends SwitchTabsState {
@override
List<Object> get props => [];
}
class SelectedTabState extends SwitchTabsState {
final bool selectedTab;
const SelectedTabState(this.selectedTab);
@override
List<Object?> get props => [selectedTab];
}
class ShowCreateRoutineState extends SwitchTabsState {
final bool showCreateRoutine;
const ShowCreateRoutineState(this.showCreateRoutine);
@override
List<Object?> get props => [showCreateRoutine];
}

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart';
import 'package:syncrow_web/pages/routiens/view/routines_view.dart';
import 'package:syncrow_web/utils/color_manager.dart';
@ -18,10 +18,6 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) =>
SwitchTabsBloc()..add(const TriggerSwitchTabsEvent(false)),
),
BlocProvider(
create: (context) => DeviceManagementBloc()..add(FetchDevices()),
),
@ -33,8 +29,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
style: Theme.of(context).textTheme.headlineLarge,
),
),
centerBody: BlocBuilder<SwitchTabsBloc, SwitchTabsState>(
builder: (context, state) {
centerBody: BlocBuilder<RoutineBloc, RoutineState>(builder: (context, state) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
@ -44,20 +39,14 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
),
onPressed: () {
context
.read<SwitchTabsBloc>()
.add(const TriggerSwitchTabsEvent(false));
.read<RoutineBloc>()
.add(const TriggerSwitchTabsEvent(isRoutineTab: false));
},
child: Text(
'Devices',
style: context.textTheme.titleMedium?.copyWith(
color:
state is SelectedTabState && state.selectedTab == false
? ColorsManager.whiteColors
: ColorsManager.grayColor,
fontWeight: (state is SelectedTabState) &&
state.selectedTab == false
? FontWeight.w700
: FontWeight.w400,
color: !state.routineTab ? ColorsManager.whiteColors : ColorsManager.grayColor,
fontWeight: !state.routineTab ? FontWeight.w700 : FontWeight.w400,
),
),
),
@ -66,21 +55,13 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
backgroundColor: null,
),
onPressed: () {
context
.read<SwitchTabsBloc>()
.add(const TriggerSwitchTabsEvent(true));
context.read<RoutineBloc>().add(const TriggerSwitchTabsEvent(isRoutineTab: true));
},
child: Text(
'Routines',
style: context.textTheme.titleMedium?.copyWith(
color:
(state is SelectedTabState) && state.selectedTab == true
? ColorsManager.whiteColors
: ColorsManager.grayColor,
fontWeight:
(state is SelectedTabState) && state.selectedTab == true
? FontWeight.w700
: FontWeight.w400,
color: state.routineTab ? ColorsManager.whiteColors : ColorsManager.grayColor,
fontWeight: state.routineTab ? FontWeight.w700 : FontWeight.w400,
),
),
),
@ -88,13 +69,12 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
);
}),
rightBody: const NavigateHomeGridView(),
scaffoldBody: BlocBuilder<SwitchTabsBloc, SwitchTabsState>(
builder: (context, state) {
if (state is SelectedTabState && state.selectedTab) {
scaffoldBody: BlocBuilder<RoutineBloc, RoutineState>(builder: (context, state) {
if (state.routineTab) {
return const RoutinesView();
}
if (state is ShowCreateRoutineState && state.showCreateRoutine) {
return const CreateNewRoutineView();
if (state.createRoutineView) {
return CreateNewRoutineView();
}
return BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
@ -104,8 +84,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
} else if (deviceState is DeviceManagementLoaded) {
return DeviceManagementBody(devices: deviceState.devices);
} else if (deviceState is DeviceManagementFiltered) {
return DeviceManagementBody(
devices: deviceState.filteredDevices);
return DeviceManagementBody(devices: deviceState.filteredDevices);
} else {
return const Center(child: Text('Error fetching Devices'));
}

View File

@ -6,6 +6,7 @@ import 'package:syncrow_web/pages/auth/model/user_model.dart';
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/home/bloc/home_state.dart';
import 'package:syncrow_web/pages/home/home_model/home_item_model.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/services/home_api.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
@ -41,8 +42,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
Future _fetchUserInfo(FetchUserInfo event, Emitter<HomeState> emit) async {
try {
var uuid =
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
user = await HomeApi().fetchUserInfo(uuid);
emit(HomeInitial());
} catch (e) {
@ -84,6 +84,8 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
icon: Assets.devicesIcon,
active: true,
onPress: (context) {
BlocProvider.of<RoutineBloc>(context)
.add(const TriggerSwitchTabsEvent(isRoutineTab: false));
context.go(RoutesConst.deviceManagementPage);
},
color: ColorsManager.primaryColor,

View File

@ -16,6 +16,7 @@ class EffectPeriodBloc extends Bloc<EffectPeriodEvent, EffectPeriodState> {
};
EffectPeriodBloc() : super(EffectPeriodState.initial()) {
on<InitialEffectPeriodEvent>(_initialEvent);
on<SetPeriod>(_onSetPeriod);
on<ToggleDay>(_onToggleDay);
on<SetCustomTime>(_onSetCustomTime);
@ -24,6 +25,15 @@ class EffectPeriodBloc extends Bloc<EffectPeriodEvent, EffectPeriodState> {
on<SetDays>(_setAllDays);
}
void _initialEvent(InitialEffectPeriodEvent event, Emitter<EffectPeriodState> emit) {
add(SetCustomTime(event.effectiveTime.start, event.effectiveTime.end));
emit(state.copyWith(
selectedDaysBinary: event.effectiveTime.loops,
customStartTime: event.effectiveTime.start,
customEndTime: event.effectiveTime.end,
));
}
void _onSetPeriod(SetPeriod event, Emitter<EffectPeriodState> emit) {
String startTime = '';
String endTime = '';

View File

@ -1,4 +1,5 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart';
import 'package:syncrow_web/utils/constants/app_enum.dart';
abstract class EffectPeriodEvent extends Equatable {
@ -8,6 +9,15 @@ abstract class EffectPeriodEvent extends Equatable {
List<Object> get props => [];
}
class InitialEffectPeriodEvent extends EffectPeriodEvent {
final EffectiveTime effectiveTime;
const InitialEffectPeriodEvent(this.effectiveTime);
@override
List<Object> get props => [effectiveTime];
}
class SetPeriod extends EffectPeriodEvent {
final EnumEffectivePeriodOptions period;

File diff suppressed because it is too large Load Diff

View File

@ -84,8 +84,7 @@ class RemoveDragCard extends RoutineEvent {
final int index;
final bool isFromThen;
final String key;
const RemoveDragCard(
{required this.index, required this.isFromThen, required this.key});
const RemoveDragCard({required this.index, required this.isFromThen, required this.key});
@override
List<Object> get props => [index, isFromThen, key];
}
@ -105,12 +104,10 @@ class EffectiveTimePeriodEvent extends RoutineEvent {
}
class CreateAutomationEvent extends RoutineEvent {
// final CreateAutomationModel createAutomationModel;
final String? automationId;
final bool updateAutomation;
const CreateAutomationEvent({
//required this.createAutomationModel,
this.automationId,
this.updateAutomation = false,
});
@ -159,21 +156,55 @@ class InitializeRoutineState extends RoutineEvent {
}
class DeleteScene extends RoutineEvent {
final String sceneId;
final String unitUuid;
const DeleteScene({required this.sceneId, required this.unitUuid});
const DeleteScene();
@override
List<Object> get props => [sceneId];
List<Object> get props => [];
}
class DeleteAutomation extends RoutineEvent {
final String automationId;
final String unitUuid;
const DeleteAutomation({required this.automationId, required this.unitUuid});
// class DeleteAutomation extends RoutineEvent {
// final String automationId;
// const DeleteAutomation({required this.automationId});
// @override
// List<Object> get props => [automationId];
// }
class UpdateScene extends RoutineEvent {
const UpdateScene();
@override
List<Object> get props => [automationId];
List<Object> get props => [];
}
class UpdateAutomation extends RoutineEvent {
const UpdateAutomation();
@override
List<Object> get props => [];
}
class SetAutomationActionExecutor extends RoutineEvent {
final String automationActionExecutor;
const SetAutomationActionExecutor({required this.automationActionExecutor});
@override
List<Object> get props => [automationActionExecutor];
}
class TriggerSwitchTabsEvent extends RoutineEvent {
final bool isRoutineTab;
const TriggerSwitchTabsEvent({required this.isRoutineTab});
@override
List<Object> get props => [isRoutineTab];
}
class CreateNewRoutineViewEvent extends RoutineEvent {
final bool createRoutineView;
const CreateNewRoutineViewEvent({required this.createRoutineView});
@override
List<Object> get props => [createRoutineView];
}
class FetchDevicesInRoutine extends RoutineEvent {}
class ResetRoutineState extends RoutineEvent {}
class ClearFunctions extends RoutineEvent {}
class ResetErrorMessage extends RoutineEvent {}

View File

@ -21,9 +21,13 @@ class RoutineState extends Equatable {
final String? sceneId;
final String? automationId;
final bool? isUpdate;
final List<AllDevicesModel> devices;
// final String? automationActionExecutor;
final bool routineTab;
final bool createRoutineView;
const RoutineState({
this.ifItems = const [],
const RoutineState(
{this.ifItems = const [],
this.thenItems = const [],
this.availableCards = const [],
this.scenes = const [],
@ -43,7 +47,10 @@ class RoutineState extends Equatable {
this.sceneId,
this.automationId,
this.isUpdate,
});
this.devices = const [],
// this.automationActionExecutor,
this.routineTab = false,
this.createRoutineView = false});
RoutineState copyWith({
List<Map<String, dynamic>>? ifItems,
@ -65,6 +72,11 @@ class RoutineState extends Equatable {
String? sceneId,
String? automationId,
bool? isUpdate,
List<AllDevicesModel>? devices,
// String? automationActionExecutor,
TextEditingController? nameController,
bool? routineTab,
bool? createRoutineView,
}) {
return RoutineState(
ifItems: ifItems ?? this.ifItems,
@ -89,7 +101,10 @@ class RoutineState extends Equatable {
sceneId: sceneId ?? this.sceneId,
automationId: automationId ?? this.automationId,
isUpdate: isUpdate ?? this.isUpdate,
);
devices: devices ?? this.devices,
// automationActionExecutor: automationActionExecutor ?? this.automationActionExecutor,
routineTab: routineTab ?? this.routineTab,
createRoutineView: createRoutineView ?? this.createRoutineView);
}
@override
@ -112,6 +127,10 @@ class RoutineState extends Equatable {
effectiveTime,
sceneId,
automationId,
isUpdate
isUpdate,
devices,
// automationActionExecutor,
routineTab,
createRoutineView
];
}

View File

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
@ -6,10 +8,11 @@ import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.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 SaveRoutineHelper {
static Future<bool?> showSaveRoutineDialog(BuildContext context) async {
return showDialog<bool?>(
static Future<void> showSaveRoutineDialog(BuildContext context) async {
return showDialog<void>(
context: context,
builder: (BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(
@ -98,18 +101,29 @@ class SaveRoutineHelper {
final functions =
state.selectedFunctions[item['uniqueCustomId']] ?? [];
return ListTile(
leading: SvgPicture.asset(
leading: item['type'] == 'tap_to_run' || item['type'] == 'scene'
? Image.memory(
base64Decode(item['icon']),
width: 22,
height: 22,
)
: SvgPicture.asset(
item['imagePath'],
width: 22,
height: 22,
),
title:
Text(item['title'], style: const TextStyle(fontSize: 14)),
title: Text(
item['title'],
style: context.textTheme.bodySmall?.copyWith(
fontSize: 14,
color: ColorsManager.grayColor,
),
),
subtitle: Wrap(
children: functions
.map((f) => Text(
'${f.operationName}: ${f.value}, ',
style: const TextStyle(
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.grayColor, fontSize: 8),
overflow: TextOverflow.ellipsis,
maxLines: 3,
@ -124,24 +138,33 @@ class SaveRoutineHelper {
],
),
),
if (state.errorMessage != null)
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
state.errorMessage!,
style: const TextStyle(color: Colors.red),
),
),
// if (state.errorMessage != null || state.errorMessage!.isNotEmpty)
// Padding(
// padding: const EdgeInsets.all(8.0),
// child: Text(
// state.errorMessage!,
// style: const TextStyle(color: Colors.red),
// ),
// ),
DialogFooter(
onCancel: () => Navigator.pop(context, false),
onConfirm: () {
onCancel: () => Navigator.pop(context),
onConfirm: () async {
if (state.isAutomation) {
if (state.isUpdate ?? false) {
context.read<RoutineBloc>().add(const UpdateAutomation());
} else {
context.read<RoutineBloc>().add(const CreateAutomationEvent());
}
} else {
if (state.isUpdate ?? false) {
context.read<RoutineBloc>().add(const UpdateScene());
} else {
context.read<RoutineBloc>().add(const CreateSceneEvent());
}
Navigator.pop(context, true);
}
// if (state.errorMessage == null || state.errorMessage!.isEmpty) {
Navigator.pop(context);
// }
},
isConfirmEnabled: true,
),

View File

@ -17,7 +17,7 @@ class CreateAutomationModel {
required this.actions,
});
Map<String, dynamic> toMap() {
Map<String, dynamic> toMap([String? automationId]) {
return {
'spaceUuid': spaceUuid,
'automationName': automationName,
@ -41,7 +41,7 @@ class CreateAutomationModel {
);
}
String toJson() => json.encode(toMap());
String toJson(String? automationId) => json.encode(toMap(automationId));
factory CreateAutomationModel.fromJson(String source) =>
CreateAutomationModel.fromMap(json.decode(source));
@ -92,7 +92,7 @@ class Condition {
return {
'code': code,
'entityId': entityId,
'entityType': entityType,
'entityType': 'device_report',
'expr': expr.toMap(),
};
}
@ -137,11 +137,13 @@ class ConditionExpr {
class AutomationAction {
String entityId;
String? actionType;
String actionExecutor;
ExecutorProperty? executorProperty;
AutomationAction({
required this.entityId,
this.actionType,
required this.actionExecutor,
this.executorProperty,
});
@ -150,12 +152,15 @@ class AutomationAction {
return {
'entityId': entityId,
'actionExecutor': actionExecutor,
if (executorProperty != null)
'executorProperty': executorProperty?.toMap(),
'actionType': actionType
};
}
factory AutomationAction.fromMap(Map<String, dynamic> map) {
return AutomationAction(
actionType: map['actionType'],
entityId: map['entityId'] ?? '',
actionExecutor: map['actionExecutor'] ?? '',
executorProperty: map['executorProperty'] != null

View File

@ -95,10 +95,12 @@ class CreateSceneModel {
class CreateSceneAction {
String entityId;
String? actionType;
String actionExecutor;
CreateSceneExecutorProperty? executorProperty;
CreateSceneAction({
this.actionType,
required this.entityId,
required this.actionExecutor,
required this.executorProperty,
@ -110,6 +112,7 @@ class CreateSceneAction {
CreateSceneExecutorProperty? executorProperty,
}) {
return CreateSceneAction(
actionType: actionType ?? this.actionType,
entityId: entityId ?? this.entityId,
actionExecutor: actionExecutor ?? this.actionExecutor,
executorProperty: executorProperty ?? this.executorProperty,
@ -125,6 +128,7 @@ class CreateSceneAction {
};
} else {
return {
"actionType": actionType,
'entityId': entityId,
'actionExecutor': actionExecutor,
};
@ -133,6 +137,7 @@ class CreateSceneAction {
factory CreateSceneAction.fromMap(Map<String, dynamic> map) {
return CreateSceneAction(
actionType: map['actionType'],
entityId: map['entityId'] ?? '',
actionExecutor: map['actionExecutor'] ?? '',
executorProperty:

View File

@ -13,6 +13,8 @@ class RoutineDetailsModel {
final EffectiveTime? effectiveTime;
final List<RoutineCondition>? conditions;
final String? type;
final String? sceneId;
final String? automationId;
RoutineDetailsModel({
required this.spaceUuid,
@ -24,6 +26,8 @@ class RoutineDetailsModel {
this.effectiveTime,
this.conditions,
this.type,
this.sceneId,
this.automationId,
});
// Convert to CreateSceneModel
@ -44,8 +48,7 @@ class RoutineDetailsModel {
spaceUuid: spaceUuid,
automationName: name,
decisionExpr: decisionExpr,
effectiveTime:
effectiveTime ?? EffectiveTime(start: '', end: '', loops: ''),
effectiveTime: effectiveTime ?? EffectiveTime(start: '', end: '', loops: ''),
conditions: conditions?.map((c) => c.toCondition()).toList() ?? [],
actions: actions.map((a) => a.toAutomationAction()).toList(),
);
@ -57,12 +60,13 @@ class RoutineDetailsModel {
'name': name,
'decisionExpr': decisionExpr,
'actions': actions.map((x) => x.toMap()).toList(),
if (iconId != null) 'iconId': iconId,
if (iconId != null) 'iconUuid': iconId,
if (showInDevice != null) 'showInDevice': showInDevice,
if (effectiveTime != null) 'effectiveTime': effectiveTime!.toMap(),
if (conditions != null)
'conditions': conditions!.map((x) => x.toMap()).toList(),
if (conditions != null) 'conditions': conditions!.map((x) => x.toMap()).toList(),
if (type != null) 'type': type,
if (sceneId != null) 'sceneId': sceneId,
if (automationId != null) 'automationId': automationId,
};
}
@ -74,16 +78,16 @@ class RoutineDetailsModel {
actions: List<RoutineAction>.from(
map['actions']?.map((x) => RoutineAction.fromMap(x)) ?? [],
),
iconId: map['iconId'],
iconId: map['iconUuid'],
showInDevice: map['showInDevice'],
effectiveTime: map['effectiveTime'] != null
? EffectiveTime.fromMap(map['effectiveTime'])
: null,
effectiveTime:
map['effectiveTime'] != null ? EffectiveTime.fromMap(map['effectiveTime']) : null,
conditions: map['conditions'] != null
? List<RoutineCondition>.from(
map['conditions'].map((x) => RoutineCondition.fromMap(x)))
? List<RoutineCondition>.from(map['conditions'].map((x) => RoutineCondition.fromMap(x)))
: null,
type: map['type'],
sceneId: map['sceneId'],
automationId: map['automationId'],
);
}
@ -96,13 +100,20 @@ class RoutineDetailsModel {
class RoutineAction {
final String entityId;
final String actionExecutor;
final String? name;
final RoutineExecutorProperty? executorProperty;
final String productType;
final String? type;
final String? icon;
RoutineAction({
required this.entityId,
RoutineAction(
{required this.entityId,
required this.actionExecutor,
required this.productType,
this.executorProperty,
});
this.name,
this.type,
this.icon});
CreateSceneAction toCreateSceneAction() {
return CreateSceneAction(
@ -124,8 +135,9 @@ class RoutineAction {
return {
'entityId': entityId,
'actionExecutor': actionExecutor,
if (executorProperty != null)
'executorProperty': executorProperty!.toMap(),
if (type != null) 'type': type,
if (name != null) 'name': name,
if (executorProperty != null) 'executorProperty': executorProperty!.toMap(),
};
}
@ -133,10 +145,13 @@ class RoutineAction {
return RoutineAction(
entityId: map['entityId'] ?? '',
actionExecutor: map['actionExecutor'] ?? '',
productType: map['productType'] ?? '',
name: map['name'] ?? '',
type: map['type'] ?? '',
executorProperty: map['executorProperty'] != null
? RoutineExecutorProperty.fromMap(map['executorProperty'])
: null,
);
icon: map['icon']);
}
}
@ -189,12 +204,14 @@ class RoutineCondition {
final String entityId;
final String entityType;
final RoutineConditionExpr expr;
final String productType;
RoutineCondition({
required this.code,
required this.entityId,
required this.entityType,
required this.expr,
required this.productType,
});
Condition toCondition() {
@ -221,6 +238,7 @@ class RoutineCondition {
entityId: map['entityId'] ?? '',
entityType: map['entityType'] ?? '',
expr: RoutineConditionExpr.fromMap(map['expr']),
productType: map['productType'] ?? '',
);
}
}

View File

@ -16,6 +16,7 @@ class CreateNewRoutineView extends StatelessWidget {
this.routineId,
this.isScene = true,
});
@override
Widget build(BuildContext context) {
return Container(

View File

@ -1,20 +1,30 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart';
import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart';
import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/routine_view_card.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class RoutinesView extends StatelessWidget {
class RoutinesView extends StatefulWidget {
const RoutinesView({super.key});
@override
State<RoutinesView> createState() => _RoutinesViewState();
}
class _RoutinesViewState extends State<RoutinesView> {
@override
void initState() {
super.initState();
context.read<RoutineBloc>().add(FetchDevicesInRoutine());
}
@override
Widget build(BuildContext context) {
return BlocBuilder<SwitchTabsBloc, SwitchTabsState>(
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) {
if (state is ShowCreateRoutineState && state.showCreateRoutine) {
if (state.createRoutineView) {
return const CreateNewRoutineView();
}
return Padding(
@ -36,12 +46,12 @@ class RoutinesView extends StatelessWidget {
),
RoutineViewCard(
onTap: () {
BlocProvider.of<SwitchTabsBloc>(context).add(
const CreateNewRoutineViewEvent(true),
);
context.read<RoutineBloc>().add(
(ResetRoutineState()),
);
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(createRoutineView: true),
);
},
icon: Icons.add,
textString: '',

View File

@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/custom_dialog.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class DeleteSceneWidget extends StatelessWidget {
const DeleteSceneWidget({super.key});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
const SizedBox(
height: 10,
),
GestureDetector(
onTap: () async {
await showCustomDialog(
context: context,
message: 'Are you sure you want to delete this scene?',
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
InkWell(
onTap: () {
Navigator.of(context).pop();
},
child: Container(
alignment: AlignmentDirectional.center,
child: Text(
'Cancel',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.textGray,
),
),
),
),
Container(width: 1, height: 50, color: ColorsManager.greyColor),
InkWell(
onTap: () {
context.read<RoutineBloc>().add(const DeleteScene());
Navigator.of(context).pop();
Navigator.of(context).pop();
},
child: Container(
alignment: AlignmentDirectional.center,
child: Text(
'Confirm',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
),
),
),
),
],
),
]);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.delete,
color: ColorsManager.red,
),
const SizedBox(
width: 2,
),
Text(
'Delete',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.red,
),
),
],
),
),
const SizedBox(
height: 10,
),
],
);
}
}

View File

@ -32,15 +32,27 @@ class DraggableCard extends StatelessWidget {
Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) {
final deviceFunctions =
state.selectedFunctions[deviceData['uniqueCustomId']] ?? [];
final deviceFunctions = state.selectedFunctions[deviceData['uniqueCustomId']] ?? [];
int index = state.thenItems
.indexWhere((item) => item['uniqueCustomId'] == deviceData['uniqueCustomId']);
if (index != -1) {
return _buildCardContent(context, deviceFunctions, padding: padding);
}
int ifIndex = state.ifItems
.indexWhere((item) => item['uniqueCustomId'] == deviceData['uniqueCustomId']);
if (ifIndex != -1) {
return _buildCardContent(context, deviceFunctions, padding: padding);
}
return Draggable<Map<String, dynamic>>(
data: deviceData,
feedback: Transform.rotate(
angle: -0.1,
child:
_buildCardContent(context, deviceFunctions, padding: padding),
child: _buildCardContent(context, deviceFunctions, padding: padding),
),
childWhenDragging: _buildGreyContainer(),
child: _buildCardContent(context, deviceFunctions, padding: padding),
@ -49,8 +61,7 @@ class DraggableCard extends StatelessWidget {
);
}
Widget _buildCardContent(
BuildContext context, List<DeviceFunctionData> deviceFunctions,
Widget _buildCardContent(BuildContext context, List<DeviceFunctionData> deviceFunctions,
{EdgeInsetsGeometry? padding}) {
return Stack(
children: [
@ -79,19 +90,19 @@ class DraggableCard extends StatelessWidget {
),
),
padding: const EdgeInsets.all(8),
child: imagePath.contains('.svg')
? SvgPicture.asset(
imagePath,
child: deviceData['type'] == 'tap_to_run' || deviceData['type'] == 'scene'
? Image.memory(
base64Decode(deviceData['icon']),
)
: Image.memory(
base64Decode(imagePath),
: SvgPicture.asset(
imagePath,
),
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 3),
child: Text(
title,
deviceData['title'] ?? deviceData['name'] ?? title,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 2,
@ -104,7 +115,6 @@ class DraggableCard extends StatelessWidget {
],
),
if (deviceFunctions.isNotEmpty)
// const Divider(height: 1),
...deviceFunctions.map((function) => Row(
mainAxisSize: MainAxisSize.min,
children: [
@ -149,8 +159,7 @@ class DraggableCard extends StatelessWidget {
}
String _formatFunctionValue(DeviceFunctionData function) {
if (function.functionCode == 'temp_set' ||
function.functionCode == 'temp_current') {
if (function.functionCode == 'temp_set' || function.functionCode == 'temp_current') {
return '${(function.value / 10).toStringAsFixed(0)}°C';
} else if (function.functionCode.contains('countdown')) {
final seconds = function.value.toInt();

View File

@ -11,8 +11,7 @@ class FetchRoutineScenesAutomation extends StatefulWidget {
const FetchRoutineScenesAutomation({super.key});
@override
State<FetchRoutineScenesAutomation> createState() =>
_FetchRoutineScenesState();
State<FetchRoutineScenesAutomation> createState() => _FetchRoutineScenesState();
}
class _FetchRoutineScenesState extends State<FetchRoutineScenesAutomation>
@ -67,49 +66,23 @@ class _FetchRoutineScenesState extends State<FetchRoutineScenesAutomation>
padding: EdgeInsets.only(
right: isSmallScreenSize(context) ? 4.0 : 8.0,
),
child: Stack(
children: [
RoutineViewCard(
onTap: () {},
textString: state.scenes[index].name,
icon: state.scenes[index].icon ??
Assets.logoHorizontal,
isFromScenes: true,
iconInBytes:
state.scenes[index].iconInBytes,
),
Positioned(
top: 0,
right: 0,
child: InkWell(
onTap: () => context
.read<RoutineBloc>()
.add(
DeleteScene(
child: RoutineViewCard(
onTap: () {
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(createRoutineView: true),
);
context.read<RoutineBloc>().add(
GetSceneDetails(
sceneId: state.scenes[index].id,
unitUuid: spaceId,
isTabToRun: true,
isUpdate: true,
),
),
child: Container(
height: 20,
width: 20,
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager.grayColor,
width: 2.0,
),
),
child: const Center(
child: Icon(Icons.delete,
size: 15,
color: ColorsManager.grayColor),
),
),
),
),
],
);
},
textString: state.scenes[index].name,
icon: state.scenes[index].icon ?? Assets.logoHorizontal,
isFromScenes: true,
iconInBytes: state.scenes[index].iconInBytes,
),
),
),
@ -142,46 +115,20 @@ class _FetchRoutineScenesState extends State<FetchRoutineScenesAutomation>
padding: EdgeInsets.only(
right: isSmallScreenSize(context) ? 4.0 : 8.0,
),
child: Stack(
children: [
RoutineViewCard(
onTap: () {},
textString: state.automations[index].name,
icon: state.automations[index].icon ??
Assets.automation,
),
Positioned(
top: 0,
right: 0,
child: InkWell(
onTap: () =>
child: RoutineViewCard(
onTap: () {
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(createRoutineView: true),
);
context.read<RoutineBloc>().add(
DeleteAutomation(
automationId: state
.automations[index].id,
unitUuid: spaceId,
),
),
child: Container(
height: 20,
width: 20,
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager.grayColor,
width: 2.0,
),
),
child: const Center(
child: Icon(Icons.delete,
size: 15,
color: ColorsManager.grayColor),
),
),
),
),
],
GetAutomationDetails(
automationId: state.automations[index].id,
isAutomation: true,
isUpdate: true),
);
},
textString: state.automations[index].name,
icon: state.automations[index].icon ?? Assets.automation,
),
),
),

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart';
@ -10,11 +9,18 @@ class RoutineDevices extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => DeviceManagementBloc()..add(FetchDevices()),
child: BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) {
if (state is DeviceManagementLoaded) {
if (state.isLoading) {
return const Center(child: CircularProgressIndicator());
}
Future.delayed(const Duration(seconds: 1), () {
if (state.devices.isEmpty) {
return const Center(child: Text('No devices found'));
}
});
List<AllDevicesModel> deviceList = state.devices
.where((device) =>
device.productType == 'AC' ||
@ -23,18 +29,15 @@ class RoutineDevices extends StatelessWidget {
device.productType == '3G')
.toList();
// Provide the RoutineBloc to the child widgets
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, routineState) {
return Wrap(
spacing: 10,
runSpacing: 10,
children: deviceList.asMap().entries.map((entry) {
final device = entry.value;
if (routineState.searchText != null && routineState.searchText!.isNotEmpty) {
if (state.searchText != null && state.searchText!.isNotEmpty) {
return device.name!
.toLowerCase()
.contains(routineState.searchText!.toLowerCase())
.contains(state.searchText!.toLowerCase())
? DraggableCard(
imagePath: device.getDefaultIcon(device.productType),
title: device.name ?? '',
@ -69,9 +72,4 @@ class RoutineDevices extends StatelessWidget {
},
);
}
return const Center(child: CircularProgressIndicator());
},
),
);
}
}

View File

@ -11,20 +11,36 @@ class AutomationDialog extends StatefulWidget {
final String automationName;
final String automationId;
final String uniqueCustomId;
final String? passedAutomationActionExecutor;
const AutomationDialog({
super.key,
required this.automationName,
required this.automationId,
required this.uniqueCustomId,
this.passedAutomationActionExecutor,
});
@override
_AutomationDialogState createState() => _AutomationDialogState();
State<AutomationDialog> createState() => _AutomationDialogState();
}
class _AutomationDialogState extends State<AutomationDialog> {
bool _isEnabled = true;
String? selectedAutomationActionExecutor;
@override
void initState() {
super.initState();
List<DeviceFunctionData>? functions = context
.read<RoutineBloc>()
.state
.selectedFunctions[widget.uniqueCustomId];
for (DeviceFunctionData data in functions ?? []) {
if (data.entityId == widget.automationId) {
selectedAutomationActionExecutor = data.value;
}
}
}
@override
Widget build(BuildContext context) {
@ -41,26 +57,25 @@ class _AutomationDialogState extends State<AutomationDialog> {
ListTile(
leading: SvgPicture.asset(Assets.acPower, width: 24, height: 24),
title: const Text('Enable'),
trailing: Radio<bool>(
value: true,
groupValue: _isEnabled,
onChanged: (bool? value) {
trailing: Radio<String?>(
value: 'rule_enable',
groupValue: selectedAutomationActionExecutor,
onChanged: (String? value) {
setState(() {
_isEnabled = value!;
selectedAutomationActionExecutor = 'rule_enable';
});
},
),
}),
),
ListTile(
leading:
SvgPicture.asset(Assets.acPowerOff, width: 24, height: 24),
title: const Text('Disable'),
trailing: Radio<bool>(
value: false,
groupValue: _isEnabled,
onChanged: (bool? value) {
trailing: Radio<String?>(
value: 'rule_disable',
groupValue: selectedAutomationActionExecutor,
onChanged: (String? value) {
setState(() {
_isEnabled = value!;
selectedAutomationActionExecutor = 'rule_disable';
});
},
),
@ -68,23 +83,25 @@ class _AutomationDialogState extends State<AutomationDialog> {
const SizedBox(height: 16),
DialogFooter(
onConfirm: () {
if (selectedAutomationActionExecutor != null) {
context.read<RoutineBloc>().add(
AddFunctionToRoutine(
[
DeviceFunctionData(
entityId: widget.automationId,
functionCode: 'automation',
value: _isEnabled ? 'rule_enable' : 'rule_disable',
value: selectedAutomationActionExecutor,
operationName: 'Automation',
),
],
widget.uniqueCustomId,
),
);
}
Navigator.of(context).pop(true);
},
onCancel: () => Navigator.of(context).pop(false),
isConfirmEnabled: true,
onCancel: () => Navigator.of(context).pop(),
isConfirmEnabled: selectedAutomationActionExecutor != null,
dialogWidth: 400,
),
],

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -49,11 +48,11 @@ class DiscardDialog {
onConfirm: () {
context.read<RoutineBloc>().add(ResetRoutineState());
Navigator.pop(context);
BlocProvider.of<SwitchTabsBloc>(context).add(
const CreateNewRoutineViewEvent(false),
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(createRoutineView: false),
);
BlocProvider.of<SwitchTabsBloc>(context).add(
const TriggerSwitchTabsEvent(true),
BlocProvider.of<RoutineBloc>(context).add(
const TriggerSwitchTabsEvent(isRoutineTab: true),
);
});
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart';
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_bloc.dart';
@ -9,6 +10,7 @@ import 'package:syncrow_web/pages/routiens/bloc/setting_bloc/setting_state.dart'
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart';
import 'package:syncrow_web/pages/routiens/models/icon_model.dart';
import 'package:syncrow_web/pages/routiens/view/effective_period_view.dart';
import 'package:syncrow_web/pages/routiens/widgets/delete_scene.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:flutter/cupertino.dart';
@ -22,8 +24,15 @@ class SettingHelper {
context: context,
builder: (BuildContext context) {
final isAutomation = context.read<RoutineBloc>().state.isAutomation;
final effectiveTime = context.read<RoutineBloc>().state.effectiveTime;
return MultiBlocProvider(
providers: [
if (effectiveTime != null)
BlocProvider(
create: (_) => EffectPeriodBloc()..add(InitialEffectPeriodEvent(effectiveTime)),
),
if (effectiveTime == null)
BlocProvider(
create: (_) => EffectPeriodBloc(),
),
@ -44,7 +53,7 @@ class SettingHelper {
}
return Container(
width: context.read<SettingBloc>().isExpanded ? 800 : 400,
height: context.read<SettingBloc>().isExpanded && isAutomation ? 500 : 300,
height: context.read<SettingBloc>().isExpanded && isAutomation ? 500 : 350,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
@ -167,6 +176,9 @@ class SettingHelper {
fontSize: 14)),
],
),
if (context.read<RoutineBloc>().state.isUpdate ??
false)
const DeleteSceneWidget()
],
)),
],
@ -284,6 +296,9 @@ class SettingHelper {
fontSize: 14)),
],
),
if (context.read<RoutineBloc>().state.isUpdate ??
false)
const DeleteSceneWidget()
],
)),
],

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/helper/save_routine_helper.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/discard_dialog.dart';
@ -10,20 +9,47 @@ import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class RoutineSearchAndButtons extends StatelessWidget {
class RoutineSearchAndButtons extends StatefulWidget {
const RoutineSearchAndButtons({
super.key,
});
@override
State<RoutineSearchAndButtons> createState() => _RoutineSearchAndButtonsState();
}
class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
late TextEditingController _nameController;
@override
void initState() {
super.initState();
_nameController = TextEditingController();
}
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) {
_nameController.text = state.routineName ?? '';
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Wrap(
runSpacing: 16,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
state.errorMessage ?? '',
style: const TextStyle(color: Colors.red),
),
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
@ -37,24 +63,6 @@ class RoutineSearchAndButtons extends StatelessWidget {
constraints: BoxConstraints(
maxWidth:
constraints.maxWidth > 700 ? 450 : constraints.maxWidth - 32),
// child: StatefulTextField(
// title: 'Routine Name',
// initialValue: state.routineName ?? '',
// height: 40,
// // controller: TextEditingController(),
// hintText: 'Please enter the name',
// boxDecoration: containerWhiteDecoration,
// elevation: 0,
// borderRadius: 15,
// isRequired: true,
// width: 450,
// onSubmitted: (value) {
// // context.read<RoutineBloc>().add(SetRoutineName(value));
// },
// onChanged: (value) {
// context.read<RoutineBloc>().add(SetRoutineName(value));
// },
// ),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
@ -81,7 +89,7 @@ class RoutineSearchAndButtons extends StatelessWidget {
child: TextFormField(
style: context.textTheme.bodyMedium!
.copyWith(color: ColorsManager.blackColor),
initialValue: state.routineName,
controller: _nameController,
decoration: InputDecoration(
hintText: 'Please enter the name',
hintStyle: context.textTheme.bodyMedium!
@ -90,8 +98,10 @@ class RoutineSearchAndButtons extends StatelessWidget {
const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
border: InputBorder.none,
),
onChanged: (value) {
context.read<RoutineBloc>().add(SetRoutineName(value));
onTapOutside: (_) {
context
.read<RoutineBloc>()
.add(SetRoutineName(_nameController.text));
},
validator: (value) {
if (value == null || value.isEmpty) {
@ -115,6 +125,7 @@ class RoutineSearchAndButtons extends StatelessWidget {
? () async {
final result = await SettingHelper.showSettingDialog(
context: context,
iconId: state.selectedIcon ?? '',
);
if (result != null) {
context
@ -208,16 +219,18 @@ class RoutineSearchAndButtons extends StatelessWidget {
);
return;
}
final result =
await SaveRoutineHelper.showSaveRoutineDialog(context);
if (result != null && result) {
BlocProvider.of<SwitchTabsBloc>(context).add(
const CreateNewRoutineViewEvent(false),
);
BlocProvider.of<SwitchTabsBloc>(context).add(
const TriggerSwitchTabsEvent(true),
);
}
// final result =
// await
BlocProvider.of<RoutineBloc>(context).add(ResetErrorMessage());
SaveRoutineHelper.showSaveRoutineDialog(context);
// if (result != null && result) {
// BlocProvider.of<RoutineBloc>(context).add(
// const CreateNewRoutineViewEvent(createRoutineView: false),
// );
// BlocProvider.of<RoutineBloc>(context).add(
// const TriggerSwitchTabsEvent(isRoutineTab: true),
// );
// }
},
borderRadius: 15,
elevation: 0,
@ -249,8 +262,7 @@ class RoutineSearchAndButtons extends StatelessWidget {
onPressed: state.isAutomation || state.isTabToRun
? () async {
final result = await SettingHelper.showSettingDialog(
context: context,
);
context: context, iconId: state.selectedIcon ?? '');
if (result != null) {
context.read<RoutineBloc>().add(AddSelectedIcon(result));
}
@ -301,7 +313,7 @@ class RoutineSearchAndButtons extends StatelessWidget {
width: 200,
child: Center(
child: DefaultButton(
onPressed: () {
onPressed: () async {
if (state.routineName == null || state.routineName!.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
@ -335,7 +347,18 @@ class RoutineSearchAndButtons extends StatelessWidget {
);
return;
}
// final result =
// await
BlocProvider.of<RoutineBloc>(context).add(ResetErrorMessage());
SaveRoutineHelper.showSaveRoutineDialog(context);
// if (result != null && result) {
// BlocProvider.of<RoutineBloc>(context).add(
// const CreateNewRoutineViewEvent(createRoutineView: false),
// );
// BlocProvider.of<RoutineBloc>(context).add(
// const TriggerSwitchTabsEvent(isRoutineTab: true),
// );
// }
},
borderRadius: 15,
elevation: 0,

View File

@ -26,27 +26,25 @@ class ThenContainer extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('THEN',
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
const Text('THEN', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
Wrap(
state.isLoading && state.isUpdate == true
? const Center(
child: CircularProgressIndicator(),
)
: Wrap(
spacing: 8,
runSpacing: 8,
children: List.generate(
state.thenItems.length,
(index) => GestureDetector(
onTap: () async {
if (state.thenItems[index]['deviceId'] ==
'delay') {
final result = await DelayHelper
.showDelayPickerDialog(
if (state.thenItems[index]['deviceId'] == 'delay') {
final result = await DelayHelper.showDelayPickerDialog(
context, state.thenItems[index]);
if (result != null) {
context
.read<RoutineBloc>()
.add(AddToThenContainer({
context.read<RoutineBloc>().add(AddToThenContainer({
...state.thenItems[index],
'imagePath': Assets.delay,
'title': 'Delay',
@ -55,41 +53,58 @@ class ThenContainer extends StatelessWidget {
return;
}
final result = await DeviceDialogHelper
.showDeviceDialog(
if (state.thenItems[index]['type'] == 'automation') {
final result = await showDialog<bool>(
context: context,
builder: (BuildContext context) => AutomationDialog(
automationName:
state.thenItems[index]['name'] ?? 'Automation',
automationId:
state.thenItems[index]['deviceId'] ?? '',
uniqueCustomId: state.thenItems[index]
['uniqueCustomId'],
),
);
if (result != null) {
context.read<RoutineBloc>().add(AddToThenContainer({
...state.thenItems[index],
'imagePath': Assets.automation,
'title': state.thenItems[index]['name'] ??
state.thenItems[index]['title'],
}));
}
return;
}
final result = await DeviceDialogHelper.showDeviceDialog(
context, state.thenItems[index],
removeComparetors: true);
if (result != null) {
context.read<RoutineBloc>().add(
AddToThenContainer(
state.thenItems[index]));
context
.read<RoutineBloc>()
.add(AddToThenContainer(state.thenItems[index]));
} else if (!['AC', '1G', '2G', '3G']
.contains(state.thenItems[index]
['productType'])) {
context.read<RoutineBloc>().add(
AddToThenContainer(
state.thenItems[index]));
.contains(state.thenItems[index]['productType'])) {
context
.read<RoutineBloc>()
.add(AddToThenContainer(state.thenItems[index]));
}
},
child: DraggableCard(
imagePath: state.thenItems[index]
['imagePath'] ??
'',
title:
state.thenItems[index]['title'] ?? '',
imagePath: state.thenItems[index]['imagePath'] ?? '',
title: state.thenItems[index]['title'] ?? '',
deviceData: state.thenItems[index],
padding: const EdgeInsets.symmetric(
horizontal: 4, vertical: 8),
padding:
const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
isFromThen: true,
isFromIf: false,
onRemove: () {
context.read<RoutineBloc>().add(
RemoveDragCard(
context.read<RoutineBloc>().add(RemoveDragCard(
index: index,
isFromThen: true,
key: state.thenItems[index]
['uniqueCustomId']));
key: state.thenItems[index]['uniqueCustomId']));
},
),
))),
@ -108,7 +123,18 @@ class ThenContainer extends StatelessWidget {
return;
}
if (state.automationId == mutableData['deviceId'] ||
state.sceneId == mutableData['deviceId']) {
return;
}
if (mutableData['type'] == 'automation') {
int index =
state.thenItems.indexWhere((item) => item['deviceId'] == mutableData['deviceId']);
if (index != -1) {
return;
}
final result = await showDialog<bool>(
context: context,
builder: (BuildContext context) => AutomationDialog(
@ -129,9 +155,14 @@ class ThenContainer extends StatelessWidget {
}
if (mutableData['type'] == 'tap_to_run' && state.isAutomation) {
int index =
state.thenItems.indexWhere((item) => item['deviceId'] == mutableData['deviceId']);
if (index != -1) {
return;
}
context.read<RoutineBloc>().add(AddToThenContainer({
...mutableData,
'imagePath': Assets.logo,
'imagePath': mutableData['imagePath'] ?? Assets.logo,
'title': mutableData['name'],
}));
@ -143,8 +174,7 @@ class ThenContainer extends StatelessWidget {
}
if (mutableData['deviceId'] == 'delay') {
final result =
await DelayHelper.showDelayPickerDialog(context, mutableData);
final result = await DelayHelper.showDelayPickerDialog(context, mutableData);
if (result != null) {
context.read<RoutineBloc>().add(AddToThenContainer({
@ -156,13 +186,11 @@ class ThenContainer extends StatelessWidget {
return;
}
final result = await DeviceDialogHelper.showDeviceDialog(
context, mutableData,
final result = await DeviceDialogHelper.showDeviceDialog(context, mutableData,
removeComparetors: true);
if (result != null) {
context.read<RoutineBloc>().add(AddToThenContainer(mutableData));
} else if (!['AC', '1G', '2G', '3G']
.contains(mutableData['productType'])) {
} else if (!['AC', '1G', '2G', '3G'].contains(mutableData['productType'])) {
context.read<RoutineBloc>().add(AddToThenContainer(mutableData));
}
},

View File

@ -1,26 +1,31 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_state.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_state.dart';
import 'package:syncrow_web/services/product_api.dart';
import 'package:syncrow_web/services/space_mana_api.dart';
class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementState> {
class SpaceManagementBloc
extends Bloc<SpaceManagementEvent, SpaceManagementState> {
final CommunitySpaceManagementApi _api;
final ProductApi _productApi;
List<ProductModel>? _cachedProducts;
SpaceManagementBloc(this._api, this._productApi) : super(SpaceManagementInitial()) {
SpaceManagementBloc(this._api, this._productApi)
: super(SpaceManagementInitial()) {
on<LoadCommunityAndSpacesEvent>(_onLoadCommunityAndSpaces);
on<UpdateSpacePositionEvent>(_onUpdateSpacePosition);
on<CreateCommunityEvent>(_onCreateCommunity);
on<SaveSpacesEvent>(_onSaveSpaces);
on<FetchProductsEvent>(_onFetchProducts);
on<SelectCommunityEvent>(_onSelectCommunity);
on<DeleteCommunityEvent>(_onCommunityDelete);
on<UpdateCommunityEvent>(_onUpdateCommunity);
on<SaveSpacesEvent>(_onSaveSpaces);
on<FetchProductsEvent>(_onFetchProducts);
on<SelectSpaceEvent>(_onSelectSpace);
on<NewCommunityEvent>(_onNewCommunity);
}
void _onUpdateCommunity(
@ -30,12 +35,14 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
final previousState = state;
try {
emit(SpaceManagementLoading());
final success = await _api.updateCommunity(event.communityUuid, event.name);
final success =
await _api.updateCommunity(event.communityUuid, event.name);
if (success) {
if (previousState is SpaceManagementLoaded) {
final updatedCommunities = List<CommunityModel>.from(previousState.communities);
for(var community in updatedCommunities){
if(community.uuid == event.communityUuid){
final updatedCommunities =
List<CommunityModel>.from(previousState.communities);
for (var community in updatedCommunities) {
if (community.uuid == event.communityUuid) {
community.name = event.name;
break;
}
@ -45,7 +52,6 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
products: previousState.products,
selectedCommunity: previousState.selectedCommunity,
));
}
} else {
emit(const SpaceManagementError('Failed to update the community.'));
@ -55,20 +61,45 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
}
}
void _onloadProducts() async {
if (_cachedProducts == null) {
final products = await _productApi.fetchProducts();
_cachedProducts = products;
}
}
void _onFetchProducts(
FetchProductsEvent event,
Emitter<SpaceManagementState> emit,
) async {
if (_cachedProducts != null) {
// Products are already cached, no need to fetch again
try {
_onloadProducts();
} catch (e) {
emit(SpaceManagementError('Error fetching products: $e'));
}
}
Future<List<SpaceModel>> _fetchSpacesForCommunity(
String communityUuid) async {
return await _api.getSpaceHierarchy(communityUuid);
}
void _onNewCommunity(
NewCommunityEvent event,
Emitter<SpaceManagementState> emit,
) {
try {
if (event.communities.isEmpty) {
emit(const SpaceManagementError('No communities provided.'));
return;
}
try {
final products = await _productApi.fetchProducts();
_cachedProducts = products; // Cache the products locally
} catch (e) {
emit(SpaceManagementError('Error fetching products: $e'));
emit(BlankState(
communities: event.communities,
products: _cachedProducts ?? [],
));
} catch (error) {
emit(SpaceManagementError('Error loading communities: $error'));
}
}
@ -78,17 +109,13 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
) async {
emit(SpaceManagementLoading());
try {
if (_cachedProducts == null) {
final products = await _productApi.fetchProducts();
_cachedProducts = products;
}
// Fetch all communities
_onloadProducts();
List<CommunityModel> communities = await _api.fetchCommunities();
List<CommunityModel> updatedCommunities = await Future.wait(
communities.map((community) async {
List<SpaceModel> spaces = await _api.getSpaceHierarchy(community.uuid);
List<SpaceModel> spaces =
await _fetchSpacesForCommunity(community.uuid);
return CommunityModel(
uuid: community.uuid,
createdAt: community.createdAt,
@ -101,7 +128,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
}).toList(),
);
emit(SpaceManagementLoaded(communities: updatedCommunities, products: _cachedProducts ?? []));
emit(SpaceManagementLoaded(
communities: updatedCommunities, products: _cachedProducts ?? []));
} catch (e) {
emit(SpaceManagementError('Error loading communities and spaces: $e'));
}
@ -139,16 +167,21 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
emit(SpaceManagementLoading());
try {
CommunityModel? newCommunity = await _api.createCommunity(event.name, event.description);
CommunityModel? newCommunity =
await _api.createCommunity(event.name, event.description);
if (newCommunity != null) {
if (previousState is SpaceManagementLoaded) {
final updatedCommunities = List<CommunityModel>.from(previousState.communities)
..add(newCommunity);
if (previousState is SpaceManagementLoaded ||
previousState is BlankState) {
final prevCommunities = List<CommunityModel>.from(
(previousState as dynamic).communities,
);
final updatedCommunities = prevCommunities..add(newCommunity);
emit(SpaceManagementLoaded(
communities: updatedCommunities,
products: _cachedProducts ?? [],
selectedCommunity: newCommunity));
selectedCommunity: newCommunity,
selectedSpace: null));
}
} else {
emit(const SpaceManagementError('Error creating community'));
@ -158,6 +191,54 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
}
}
void _onSelectCommunity(
SelectCommunityEvent event,
Emitter<SpaceManagementState> emit,
) async {
_handleCommunitySpaceStateUpdate(
emit: emit,
selectedCommunity: event.selectedCommunity,
selectedSpace: null,
);
}
void _onSelectSpace(
SelectSpaceEvent event,
Emitter<SpaceManagementState> emit,
) {
_handleCommunitySpaceStateUpdate(
emit: emit,
selectedCommunity: event.selectedCommunity,
selectedSpace: event.selectedSpace,
);
}
void _handleCommunitySpaceStateUpdate({
required Emitter<SpaceManagementState> emit,
CommunityModel? selectedCommunity,
SpaceModel? selectedSpace,
}) {
final previousState = state;
emit(SpaceManagementLoading());
try {
if (previousState is SpaceManagementLoaded ||
previousState is BlankState) {
final communities = List<CommunityModel>.from(
(previousState as dynamic).communities,
);
emit(SpaceManagementLoaded(
communities: communities,
products: _cachedProducts ?? [],
selectedCommunity: selectedCommunity,
selectedSpace: selectedSpace,
));
}
} catch (e) {
emit(SpaceManagementError('Error updating state: $e'));
}
}
void _onSaveSpaces(
SaveSpacesEvent event,
Emitter<SpaceManagementState> emit,
@ -166,17 +247,54 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
emit(SpaceManagementLoading());
try {
final updatedSpaces = await saveSpacesHierarchically(event.spaces, event.communityUuid);
final updatedSpaces =
await saveSpacesHierarchically(event.spaces, event.communityUuid);
final allSpaces = await _fetchSpacesForCommunity(event.communityUuid);
emit(SpaceCreationSuccess(spaces: updatedSpaces));
if (previousState is SpaceManagementLoaded) {
_updateLoadedState(
previousState,
allSpaces,
event.communityUuid,
emit,
);
} else {
add(LoadCommunityAndSpacesEvent());
}
} catch (e) {
emit(SpaceManagementError('Error saving spaces: $e'));
if (previousState is SpaceManagementLoaded) {
emit(previousState);
}
}
}
void _updateLoadedState(
SpaceManagementLoaded previousState,
List<SpaceModel> allSpaces,
String communityUuid,
Emitter<SpaceManagementState> emit,
) {
final communities = List<CommunityModel>.from(previousState.communities);
for (var community in communities) {
if (community.uuid == communityUuid) {
community.spaces = allSpaces;
emit(SpaceManagementLoaded(
communities: communities,
products: _cachedProducts ?? [],
selectedCommunity: community,
selectedSpace: null,
));
return;
}
}
}
Future<List<SpaceModel>> saveSpacesHierarchically(
List<SpaceModel> spaces, String communityUuid) async {
final orderedSpaces = flattenHierarchy(spaces);
@ -187,7 +305,6 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
for (var parent in parentsToDelete) {
try {
// Ensure parent.uuid is not null before calling the API
if (parent.uuid != null) {
await _api.deleteSpace(communityUuid, parent.uuid!);
}

View File

@ -1,6 +1,7 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; // Import for Offset
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; // Import for Offset
abstract class SpaceManagementEvent extends Equatable {
const SpaceManagementEvent();
@ -83,17 +84,6 @@ class CreateCommunityEvent extends SpaceManagementEvent {
List<Object> get props => [name, description];
}
class FetchProductsEvent extends SpaceManagementEvent {}
class LoadSpaceHierarchyEvent extends SpaceManagementEvent {
final String communityId;
const LoadSpaceHierarchyEvent({required this.communityId});
@override
List<Object> get props => [communityId];
}
class UpdateCommunityEvent extends SpaceManagementEvent {
final String communityUuid;
final String name;
@ -106,3 +96,47 @@ class UpdateCommunityEvent extends SpaceManagementEvent {
@override
List<Object> get props => [communityUuid, name];
}
class SelectCommunityEvent extends SpaceManagementEvent {
final CommunityModel? selectedCommunity;
const SelectCommunityEvent({
required this.selectedCommunity,
});
@override
List<Object> get props => [];
}
class NewCommunityEvent extends SpaceManagementEvent {
final List<CommunityModel> communities;
const NewCommunityEvent({required this.communities});
@override
List<Object> get props => [communities];
}
class SelectSpaceEvent extends SpaceManagementEvent {
final CommunityModel? selectedCommunity;
final SpaceModel? selectedSpace;
const SelectSpaceEvent({
required this.selectedCommunity,
required this.selectedSpace,
});
@override
List<Object> get props => [];
}
class FetchProductsEvent extends SpaceManagementEvent {}
class LoadSpaceHierarchyEvent extends SpaceManagementEvent {
final String communityId;
const LoadSpaceHierarchyEvent({required this.communityId});
@override
List<Object> get props => [communityId];
}

View File

@ -1,7 +1,7 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
abstract class SpaceManagementState extends Equatable {
const SpaceManagementState();
@ -17,10 +17,24 @@ class SpaceManagementLoading extends SpaceManagementState {}
class SpaceManagementLoaded extends SpaceManagementState {
final List<CommunityModel> communities;
final List<ProductModel> products;
CommunityModel? selectedCommunity; // Include products in the state
CommunityModel? selectedCommunity;
SpaceModel? selectedSpace;
SpaceManagementLoaded(
{required this.communities, required this.products, this.selectedCommunity});
{required this.communities,
required this.products,
this.selectedCommunity,
this.selectedSpace});
}
class BlankState extends SpaceManagementState {
final List<CommunityModel> communities;
final List<ProductModel> products;
BlankState({
required this.communities,
required this.products,
});
}
class SpaceCreationSuccess extends SpaceManagementState {

View File

@ -1,5 +1,5 @@
import 'package:syncrow_web/pages/auth/model/region_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
class CommunityModel {
final String uuid;

View File

@ -1,4 +1,4 @@
import 'package:syncrow_web/pages/spaces_management/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
class Connection {
final SpaceModel startSpace;

View File

@ -1,7 +1,7 @@
import 'dart:ui';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/connection_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:uuid/uuid.dart';
@ -44,7 +44,8 @@ class SpaceModel {
this.selectedProducts = const [],
}) : internalId = internalId ?? const Uuid().v4();
factory SpaceModel.fromJson(Map<String, dynamic> json, {String? parentInternalId}) {
factory SpaceModel.fromJson(Map<String, dynamic> json,
{String? parentInternalId}) {
final String internalId = json['internalId'] ?? const Uuid().v4();
final List<SpaceModel> children = json['children'] != null
@ -56,7 +57,7 @@ class SpaceModel {
}).toList()
: [];
return SpaceModel(
final instance = SpaceModel(
internalId: internalId,
uuid: json['uuid'] ?? '',
spaceTuyaUuid: json['spaceTuyaUuid'],
@ -72,11 +73,14 @@ class SpaceModel {
isPrivate: json['parent']?['isPrivate'] ?? false,
invitationCode: json['parent']?['invitationCode'],
children: [],
position: Offset(json['parent']?['x'] ?? 0, json['parent']?['y'] ?? 0),
position:
Offset(json['parent']?['x'] ?? 0, json['parent']?['y'] ?? 0),
icon: json['parent']?['icon'] ?? Assets.location,
)
: null,
community: json['community'] != null ? CommunityModel.fromJson(json['community']) : null,
community: json['community'] != null
? CommunityModel.fromJson(json['community'])
: null,
children: children,
icon: json['icon'] ?? Assets.location,
position: Offset(json['x'] ?? 0, json['y'] ?? 0),
@ -90,6 +94,20 @@ class SpaceModel {
}).toList()
: [],
);
if (json['incomingConnections'] != null &&
json['incomingConnections'] is List &&
(json['incomingConnections'] as List).isNotEmpty &&
instance.parent != null) {
final conn = json['incomingConnections'][0];
instance.incomingConnection = Connection(
startSpace: instance.parent ?? instance, // Parent space
endSpace: instance, // This space instance
direction: conn['direction'],
);
}
return instance;
}
Map<String, dynamic> toMap() {

View File

@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_state.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart';
import 'package:syncrow_web/services/product_api.dart';
import 'package:syncrow_web/services/space_mana_api.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
class SpaceManagementPage extends StatefulWidget {
const SpaceManagementPage({super.key});
@override
SpaceManagementPageState createState() => SpaceManagementPageState();
}
class SpaceManagementPageState extends State<SpaceManagementPage> {
final CommunitySpaceManagementApi _api = CommunitySpaceManagementApi();
final ProductApi _productApi = ProductApi();
Map<String, List<SpaceModel>> communitySpaces = {};
List<ProductModel> products = [];
bool isProductDataLoaded = false;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => SpaceManagementBloc(_api, _productApi)
..add(LoadCommunityAndSpacesEvent()),
child: WebScaffold(
appBarTitle: Text('Space Management',
style: Theme.of(context).textTheme.headlineLarge),
enableMenuSidebar: false,
rightBody: const NavigateHomeGridView(),
scaffoldBody: BlocBuilder<SpaceManagementBloc, SpaceManagementState>(
builder: (context, state) {
if (state is SpaceManagementLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is BlankState) {
return LoadedSpaceView(
communities: state.communities,
selectedCommunity: null,
selectedSpace: null,
products: state.products,
);
} else if (state is SpaceManagementLoaded) {
return LoadedSpaceView(
communities: state.communities,
selectedCommunity: state.selectedCommunity,
selectedSpace: state.selectedSpace,
products: state.products,
);
} else if (state is SpaceManagementError) {
return Center(child: Text('Error: ${state.errorMessage}'));
}
return Container();
}),
),
);
}
}

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/counter_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/counter_widget.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';

View File

@ -1,32 +1,42 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/dialogs/create_community_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/create_community/view/create_community_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class BlankCommunityWidget extends StatelessWidget {
const BlankCommunityWidget({Key? key}) : super(key: key);
class BlankCommunityWidget extends StatefulWidget {
final List<CommunityModel> communities;
BlankCommunityWidget({required this.communities});
@override
_BlankCommunityWidgetState createState() => _BlankCommunityWidgetState();
}
class _BlankCommunityWidgetState extends State<BlankCommunityWidget> {
@override
Widget build(BuildContext context) {
return Expanded(
child: Container(
color: ColorsManager.whiteColors, // Parent container with white background
color:
ColorsManager.whiteColors, // Parent container with white background
child: GridView.builder(
padding: const EdgeInsets.only(left: 40.0, top: 20.0),
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 400, // Each item's width will be 400 or less
mainAxisSpacing: 10, // Spacing between items
crossAxisSpacing: 10, // Spacing between items
childAspectRatio: 2.0, // Aspect ratio for width:height (e.g., 300:150 = 2.0)
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 400,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 2.0,
),
itemCount: 1, // Only one item
itemBuilder: (context, index) {
return GestureDetector(
onTap: () => _showCreateCommunityDialog(context),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center, // Center align the content
crossAxisAlignment:
CrossAxisAlignment.center, // Center align the content
children: [
Expanded(
child: AspectRatio(
@ -34,7 +44,7 @@ class BlankCommunityWidget extends StatelessWidget {
child: Container(
decoration: ShapeDecoration(
shape: RoundedRectangleBorder(
side: BorderSide(
side: const BorderSide(
width: 4,
strokeAlign: BorderSide.strokeAlignOutside,
color: ColorsManager.borderColor,
@ -45,9 +55,8 @@ class BlankCommunityWidget extends StatelessWidget {
),
),
),
const SizedBox(height: 9), // Space between item and text
Text('Blank', // Text below the item
const SizedBox(height: 9),
Text('Blank',
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: ColorsManager.blackColor,
)),
@ -63,6 +72,8 @@ class BlankCommunityWidget extends StatelessWidget {
showDialog(
context: parentContext,
builder: (context) => CreateCommunityDialog(
isEditMode: false,
existingCommunityNames: widget.communities.map((community) => community.name).toList(),
onCreateCommunity: (String communityName, String description) {
parentContext.read<SpaceManagementBloc>().add(
CreateCommunityEvent(
@ -74,4 +85,5 @@ class BlankCommunityWidget extends StatelessWidget {
),
);
}
}

View File

@ -1,10 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/create_community/view/create_community_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class CommunityStructureHeader extends StatelessWidget {
class CommunityStructureHeader extends StatefulWidget {
final String? communityName;
final bool isEditingName;
final bool isSave;
@ -13,9 +15,11 @@ class CommunityStructureHeader extends StatelessWidget {
final VoidCallback onDelete;
final VoidCallback onEditName;
final ValueChanged<String> onNameSubmitted;
final List<CommunityModel> communities;
final CommunityModel? community;
const CommunityStructureHeader({
Key? key,
const CommunityStructureHeader(
{super.key,
required this.communityName,
required this.isSave,
required this.isEditingName,
@ -24,8 +28,15 @@ class CommunityStructureHeader extends StatelessWidget {
required this.onDelete,
required this.onEditName,
required this.onNameSubmitted,
}) : super(key: key);
this.community,
required this.communities});
@override
State<CommunityStructureHeader> createState() =>
_CommunityStructureHeaderState();
}
class _CommunityStructureHeaderState extends State<CommunityStructureHeader> {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
@ -60,47 +71,63 @@ class CommunityStructureHeader extends StatelessWidget {
);
}
void _showCreateCommunityDialog(BuildContext parentContext) {
showDialog(
context: parentContext,
builder: (context) => CreateCommunityDialog(
isEditMode: true,
existingCommunityNames:
widget.communities.map((community) => community.name).toList(),
initialName: widget.community?.name ?? '',
onCreateCommunity: (String communityName, String description) {
widget.onNameSubmitted(communityName);
},
),
);
}
Widget _buildCommunityInfo(ThemeData theme, double screenWidth) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Community Structure',
style: theme.textTheme.headlineLarge?.copyWith(color: ColorsManager.blackColor),
style: theme.textTheme.headlineLarge
?.copyWith(color: ColorsManager.blackColor),
),
if (communityName != null)
if (widget.communityName != null)
Row(
children: [
Expanded(
child: Row(
children: [
if (!isEditingName)
if (!widget.isEditingName)
Flexible(
child: Text(
communityName!,
style:
theme.textTheme.bodyLarge?.copyWith(color: ColorsManager.blackColor),
widget.communityName!,
style: theme.textTheme.bodyLarge
?.copyWith(color: ColorsManager.blackColor),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
if (isEditingName)
if (widget.isEditingName)
SizedBox(
width: screenWidth * 0.3,
width: screenWidth * 0.1,
child: TextField(
controller: nameController,
controller: widget.nameController,
decoration: const InputDecoration(
border: InputBorder.none,
isDense: true,
),
style:
theme.textTheme.bodyLarge?.copyWith(color: ColorsManager.blackColor),
onSubmitted: onNameSubmitted,
style: theme.textTheme.bodyLarge
?.copyWith(color: ColorsManager.blackColor),
onSubmitted: widget.onNameSubmitted,
),
),
const SizedBox(width: 2),
GestureDetector(
onTap: onEditName,
onTap: () => _showCreateCommunityDialog(context),
child: SvgPicture.asset(
Assets.iconEdit,
width: 16,
@ -110,7 +137,7 @@ class CommunityStructureHeader extends StatelessWidget {
],
),
),
if (isSave) ...[
if (widget.isSave) ...[
const SizedBox(width: 8),
_buildActionButtons(theme),
],
@ -127,8 +154,9 @@ class CommunityStructureHeader extends StatelessWidget {
children: [
_buildButton(
label: "Save",
icon: const Icon(Icons.save, size: 18, color: ColorsManager.spaceColor),
onPressed: onSave,
icon: const Icon(Icons.save,
size: 18, color: ColorsManager.spaceColor),
onPressed: widget.onSave,
theme: theme),
],
);
@ -159,7 +187,8 @@ class CommunityStructureHeader extends StatelessWidget {
Flexible(
child: Text(
label,
style: theme.textTheme.bodySmall?.copyWith(color: ColorsManager.blackColor),
style: theme.textTheme.bodySmall
?.copyWith(color: ColorsManager.blackColor),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),

View File

@ -4,19 +4,19 @@ import 'package:flutter_bloc/flutter_bloc.dart';
// Syncrow project imports
import 'package:syncrow_web/pages/common/buttons/add_space_button.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/connection_model.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/blank_community_widget.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/community_structure_header_widget.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/dialogs/create_space_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/curved_line_painter.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/space_card_widget.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/space_container_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/blank_community_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/curved_line_painter.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_card_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_container_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CommunityStructureArea extends StatefulWidget {
@ -24,12 +24,13 @@ class CommunityStructureArea extends StatefulWidget {
SpaceModel? selectedSpace;
final List<ProductModel>? products;
final ValueChanged<SpaceModel?>? onSpaceSelected;
final List<CommunityModel> communities;
final List<SpaceModel> spaces;
CommunityStructureArea({
this.selectedCommunity,
this.selectedSpace,
required this.communities,
this.products,
required this.spaces,
this.onSpaceSelected,
@ -52,7 +53,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void initState() {
super.initState();
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
connections =
widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
_adjustCanvasSizeForSpaces();
_nameController = TextEditingController(
text: widget.selectedCommunity?.name ?? '',
@ -79,12 +81,14 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
if (oldWidget.spaces != widget.spaces) {
setState(() {
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
connections =
widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
_adjustCanvasSizeForSpaces();
});
}
if (widget.selectedSpace != oldWidget.selectedSpace && widget.selectedSpace != null) {
if (widget.selectedSpace != oldWidget.selectedSpace &&
widget.selectedSpace != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_moveToSpace(widget.selectedSpace!);
});
@ -94,14 +98,16 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
@override
Widget build(BuildContext context) {
if (widget.selectedCommunity == null) {
return BlankCommunityWidget();
return BlankCommunityWidget(
communities: widget.communities,
);
}
Size screenSize = MediaQuery.of(context).size;
return Expanded(
child: GestureDetector(
onTap: () {
_deselectSpace();
_deselectSpace(context);
},
child: Container(
decoration: const BoxDecoration(
@ -113,7 +119,9 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CommunityStructureHeader(
communities: widget.communities,
communityName: widget.selectedCommunity?.name,
community: widget.selectedCommunity,
isSave: isSave(spaces),
isEditingName: isEditingName,
nameController: _nameController,
@ -156,9 +164,11 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
children: [
for (var connection in connections)
Opacity(
opacity:
_isHighlightedConnection(connection) ? 1.0 : 0.3, // Adjust opacity
child: CustomPaint(painter: CurvedLinePainter([connection])),
opacity: _isHighlightedConnection(connection)
? 1.0
: 0.3, // Adjust opacity
child: CustomPaint(
painter: CurvedLinePainter([connection])),
),
for (var entry in spaces.asMap().entries)
if (entry.value.status != SpaceStatus.deleted)
@ -167,10 +177,12 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
top: entry.value.position.dy,
child: SpaceCardWidget(
index: entry.key,
onButtonTap: (int index, Offset newPosition, String direction) {
onButtonTap: (int index, Offset newPosition,
String direction) {
_showCreateSpaceDialog(
screenSize,
position: spaces[index].position + newPosition,
position:
spaces[index].position + newPosition,
parentIndex: index,
direction: direction,
);
@ -183,7 +195,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
_updateNodePosition(entry.value, newPosition);
},
buildSpaceContainer: (int index) {
final bool isHighlighted = _isHighlightedSpace(spaces[index]);
final bool isHighlighted =
_isHighlightedSpace(spaces[index]);
return Opacity(
opacity: isHighlighted ? 1.0 : 0.3,
@ -193,7 +206,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
_showEditSpaceDialog(spaces[index]);
},
onTap: () {
_selectSpace(spaces[index]);
_selectSpace(context, spaces[index]);
},
icon: spaces[index].icon ?? '',
name: spaces[index].name,
@ -210,7 +223,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
child: AddSpaceButton(
onTap: () {
_showCreateSpaceDialog(screenSize,
canvasHeight: canvasHeight, canvasWidth: canvasWidth);
canvasHeight: canvasHeight,
canvasWidth: canvasWidth);
},
),
),
@ -270,12 +284,14 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
builder: (BuildContext context) {
return CreateSpaceDialog(
products: widget.products,
parentSpace: parentIndex != null? spaces[parentIndex] : null,
onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts) {
parentSpace: parentIndex != null ? spaces[parentIndex] : null,
onCreateSpace: (String name, String icon,
List<SelectedProduct> selectedProducts) {
setState(() {
// Set the first space in the center or use passed position
Offset centerPosition = position ?? _getCenterPosition(screenSize);
Offset centerPosition =
position ?? _getCenterPosition(screenSize);
SpaceModel newSpace = SpaceModel(
name: name,
icon: icon,
@ -317,9 +333,11 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
products: widget.products,
name: space.name,
icon: space.icon,
editSpace: space,
isEdit: true,
selectedProducts: space.selectedProducts,
onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts) {
onCreateSpace: (String name, String icon,
List<SelectedProduct> selectedProducts) {
setState(() {
// Update the space's properties
space.name = name;
@ -331,8 +349,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
}
});
},
// Pre-fill the dialog with current space data
key: Key(space.name), // Add a unique key to ensure dialog refresh
key: Key(space.name),
);
},
);
@ -465,40 +482,30 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
..scale(1.2);
}
void _selectSpace(SpaceModel space) {
setState(() {
widget.selectedSpace = space;
});
if (widget.onSpaceSelected != null) {
widget.onSpaceSelected!(space);
}
void _selectSpace(BuildContext context, SpaceModel space) {
context.read<SpaceManagementBloc>().add(
SelectSpaceEvent(
selectedCommunity: widget.selectedCommunity,
selectedSpace: space),
);
}
bool _isHighlightedSpace(SpaceModel space) {
if (widget.selectedSpace == null) return true;
if (space == widget.selectedSpace) return true;
if (widget.selectedSpace?.parent?.internalId == space.internalId) return true;
if (widget.selectedSpace?.children != null) {
for (var child in widget.selectedSpace!.children) {
if (child.internalId == space.internalId) {
return true;
}
}
}
return false;
final selectedSpace = widget.selectedSpace;
if (selectedSpace == null) return true;
return space == selectedSpace ||
selectedSpace.parent?.internalId == space.internalId ||
selectedSpace.children
?.any((child) => child.internalId == space.internalId) ==
true;
}
void _deselectSpace() {
if (widget.selectedSpace != null) {
setState(() {
widget.selectedSpace = null;
});
if (widget.onSpaceSelected != null) {
widget.onSpaceSelected!(null); // Notify parent that no space is selected
}
}
void _deselectSpace(BuildContext context) {
context.read<SpaceManagementBloc>().add(
SelectSpaceEvent(
selectedCommunity: widget.selectedCommunity, selectedSpace: null),
);
}
bool _isHighlightedConnection(Connection connection) {

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/model/connection_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CurvedLinePainter extends CustomPainter {

View File

@ -2,24 +2,26 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/add_device_type_widget.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/dialogs/icon_selection_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/hoverable_button.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/hoverable_button.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/constants/space_icon_const.dart';
class CreateSpaceDialog extends StatefulWidget {
final Function(String, String, List<SelectedProduct> selectedProducts) onCreateSpace;
final Function(String, String, List<SelectedProduct> selectedProducts)
onCreateSpace;
final List<ProductModel>? products;
final String? name;
final String? icon;
final bool isEdit;
final List<SelectedProduct> selectedProducts;
final SpaceModel? parentSpace;
final SpaceModel? editSpace;
const CreateSpaceDialog(
{super.key,
@ -29,6 +31,7 @@ class CreateSpaceDialog extends StatefulWidget {
this.name,
this.icon,
this.isEdit = false,
this.editSpace,
this.selectedProducts = const []});
@override
@ -49,8 +52,10 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
super.initState();
selectedIcon = widget.icon ?? Assets.location;
nameController = TextEditingController(text: widget.name ?? '');
selectedProducts = widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
isOkButtonEnabled = enteredName.isNotEmpty || nameController.text.isNotEmpty;
selectedProducts =
widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
isOkButtonEnabled =
enteredName.isNotEmpty || nameController.text.isNotEmpty;
}
@override
@ -59,7 +64,9 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
final screenWidth = MediaQuery.of(context).size.width;
return AlertDialog(
title: widget.isEdit ? const Text('Edit Space') : const Text('Create New Space'),
title: widget.isEdit
? const Text('Edit Space')
: const Text('Create New Space'),
backgroundColor: ColorsManager.whiteColors,
content: SizedBox(
width: screenWidth * 0.5, // Limit dialog width
@ -126,11 +133,17 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
isNameFieldInvalid = value.isEmpty;
if (!isNameFieldInvalid) {
if (widget.parentSpace?.children
.any((child) => child.name == value) ??
false) {
if ((widget.parentSpace?.children.any(
(child) => child.name == value) ??
false) ||
(widget.parentSpace?.name == value) ||
(widget.editSpace?.children.any(
(child) => child.name == value) ??
false)) {
isNameFieldExist = true;
isOkButtonEnabled = false;
} else {
isNameFieldExist = false;
isOkButtonEnabled = true;
}
}
@ -218,7 +231,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
padding: const EdgeInsets.only(left: 6.0),
child: SvgPicture.asset(
Assets.addIcon,
width: screenWidth * 0.015, // Adjust icon size
width: screenWidth *
0.015, // Adjust icon size
height: screenWidth * 0.015,
),
),
@ -226,8 +240,11 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
Flexible(
child: Text(
'Add devices / Assign a space model',
overflow: TextOverflow.ellipsis, // Prevent overflow
style: Theme.of(context).textTheme.bodyMedium,
overflow: TextOverflow
.ellipsis, // Prevent overflow
style: Theme.of(context)
.textTheme
.bodyMedium,
),
),
],
@ -262,16 +279,20 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
});
return;
} else {
String newName = enteredName.isNotEmpty ? enteredName : (widget.name ?? '');
String newName = enteredName.isNotEmpty
? enteredName
: (widget.name ?? '');
if (newName.isNotEmpty) {
widget.onCreateSpace(newName, selectedIcon, selectedProducts);
widget.onCreateSpace(
newName, selectedIcon, selectedProducts);
Navigator.of(context).pop();
}
}
},
borderRadius: 10,
backgroundColor:
isOkButtonEnabled ? ColorsManager.secondaryColor : ColorsManager.grayColor,
backgroundColor: isOkButtonEnabled
? ColorsManager.secondaryColor
: ColorsManager.grayColor,
foregroundColor: ColorsManager.whiteColors,
child: const Text('OK'),
),
@ -318,7 +339,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
children: [
for (var i = 0; i < selectedProducts.length; i++) ...[
HoverableButton(
iconPath: _mapIconToProduct(selectedProducts[i].productId, products),
iconPath:
_mapIconToProduct(selectedProducts[i].productId, products),
text: 'x${selectedProducts[i].count}',
onTap: () {
setState(() {
@ -328,7 +350,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
},
),
if (i < selectedProducts.length - 1)
const SizedBox(width: 2), // Add space except after the last button
const SizedBox(
width: 2), // Add space except after the last button
],
const SizedBox(width: 2),
GestureDetector(
@ -364,6 +387,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
);
}
String _mapIconToProduct(String uuid, List<ProductModel> products) {
// Find the product with the matching UUID
final product = products.firstWhere(

View File

@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/gradient_canvas_border_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart';
class LoadedSpaceView extends StatefulWidget {
final List<CommunityModel> communities;
final CommunityModel? selectedCommunity;
final SpaceModel? selectedSpace;
final List<ProductModel>? products;
const LoadedSpaceView({
super.key,
required this.communities,
this.selectedCommunity,
this.selectedSpace,
this.products,
});
@override
_LoadedStateViewState createState() => _LoadedStateViewState();
}
class _LoadedStateViewState extends State<LoadedSpaceView> {
@override
Widget build(BuildContext context) {
return Stack(
clipBehavior: Clip.none,
children: [
Row(
children: [
SidebarWidget(
communities: widget.communities,
selectedSpaceUuid: widget.selectedSpace?.uuid ??
widget.selectedCommunity?.uuid ??
'',
),
CommunityStructureArea(
selectedCommunity: widget.selectedCommunity,
selectedSpace: widget.selectedSpace,
spaces: widget.selectedCommunity?.spaces ?? [],
products: widget.products,
communities: widget.communities,
),
],
),
const GradientCanvasBorderWidget(),
],
);
}
SpaceModel? findSpaceByUuid(String? uuid, List<CommunityModel> communities) {
for (var community in communities) {
for (var space in community.spaces) {
if (space.uuid == uuid) return space;
}
}
return null;
}
}

View File

@ -2,30 +2,22 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/common/search_bar.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/community_tile.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/dialogs/create_community_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/space_tile_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_tile.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart';
class SidebarWidget extends StatefulWidget {
final Function(CommunityModel)? onCommunitySelected;
final Function(SpaceModel?)? onSpaceSelected;
final List<CommunityModel> communities;
final Function(String?)? onSelectedSpaceChanged; // New callback
final String? selectedSpaceUuid;
const SidebarWidget({
super.key,
this.onCommunitySelected,
this.onSpaceSelected,
this.onSelectedSpaceChanged,
required this.communities,
this.selectedSpaceUuid,
});
@ -42,7 +34,8 @@ class _SidebarWidgetState extends State<SidebarWidget> {
@override
void initState() {
super.initState();
_selectedId = widget.selectedSpaceUuid; // Initialize with the passed selected space UUID
_selectedId = widget
.selectedSpaceUuid; // Initialize with the passed selected space UUID
}
@override
@ -55,22 +48,6 @@ class _SidebarWidgetState extends State<SidebarWidget> {
}
}
void _showCreateCommunityDialog(BuildContext parentContext) {
showDialog(
context: parentContext,
builder: (context) => CreateCommunityDialog(
onCreateCommunity: (String communityName, String description) {
parentContext.read<SpaceManagementBloc>().add(
CreateCommunityEvent(
name: communityName,
description: description,
),
);
},
),
);
}
// Function to filter communities based on the search query
List<CommunityModel> _filterCommunities() {
if (_searchQuery.isEmpty) {
@ -83,8 +60,8 @@ class _SidebarWidgetState extends State<SidebarWidget> {
return widget.communities.where((community) {
final containsQueryInCommunity =
community.name.toLowerCase().contains(_searchQuery.toLowerCase());
final containsQueryInSpaces =
community.spaces.any((space) => _containsQuery(space, _searchQuery.toLowerCase()));
final containsQueryInSpaces = community.spaces
.any((space) => _containsQuery(space, _searchQuery.toLowerCase()));
return containsQueryInCommunity || containsQueryInSpaces;
}).toList();
@ -93,8 +70,8 @@ class _SidebarWidgetState extends State<SidebarWidget> {
// Helper function to determine if any space or its children match the search query
bool _containsQuery(SpaceModel space, String query) {
final matchesSpace = space.name.toLowerCase().contains(query);
final matchesChildren =
space.children.any((child) => _containsQuery(child, query)); // Recursive check for children
final matchesChildren = space.children.any((child) =>
_containsQuery(child, query)); // Recursive check for children
// If the space or any of its children match the query, expand this space
if (matchesSpace || matchesChildren) {
@ -128,7 +105,8 @@ class _SidebarWidgetState extends State<SidebarWidget> {
width: 300,
decoration: subSectionContainerDecoration,
child: Column(
mainAxisSize: MainAxisSize.min, // Ensures the Column only takes necessary height
mainAxisSize:
MainAxisSize.min, // Ensures the Column only takes necessary height
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Communities title with the add button
@ -143,7 +121,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
color: Colors.black,
)),
GestureDetector(
onTap: () => _showCreateCommunityDialog(context),
onTap: () => _navigateToBlank(context),
child: Container(
width: 30,
height: 30,
@ -176,7 +154,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
Expanded(
child: ListView(
children: filteredCommunities.map((community) {
return _buildCommunityTile(community);
return _buildCommunityTile(context, community);
}).toList(),
),
),
@ -185,7 +163,16 @@ class _SidebarWidgetState extends State<SidebarWidget> {
);
}
Widget _buildCommunityTile(CommunityModel community) {
void _navigateToBlank(BuildContext context) {
setState(() {
_selectedId = '';
});
context.read<SpaceManagementBloc>().add(
NewCommunityEvent(communities: widget.communities),
);
}
Widget _buildCommunityTile(BuildContext context, CommunityModel community) {
bool hasChildren = community.spaces.isNotEmpty;
return CommunityTile(
@ -199,23 +186,23 @@ class _SidebarWidgetState extends State<SidebarWidget> {
_selectedSpaceUuid = null; // Update the selected community
});
if (widget.onCommunitySelected != null) {
widget.onCommunitySelected!(community);
widget.onSpaceSelected!(null); // Pass the entire community
}
context.read<SpaceManagementBloc>().add(
SelectCommunityEvent(selectedCommunity: community),
);
},
onExpansionChanged: (String title, bool expanded) {
_handleExpansionChange(community.uuid, expanded);
},
children: hasChildren
? community.spaces.map((space) => _buildSpaceTile(space, community)).toList()
? community.spaces
.map((space) => _buildSpaceTile(space, community))
.toList()
: null, // Render spaces within the community
);
}
Widget _buildSpaceTile(SpaceModel space, CommunityModel community) {
bool isExpandedSpace = _isSpaceOrChildSelected(space);
// Check if space should be expanded
return SpaceTile(
title: space.name,
key: ValueKey(space.uuid),
@ -230,17 +217,15 @@ class _SidebarWidgetState extends State<SidebarWidget> {
_selectedSpaceUuid = space.uuid;
});
if (widget.onSpaceSelected != null) {
widget.onCommunitySelected!(community);
widget.onSpaceSelected!(space);
}
if (widget.onSelectedSpaceChanged != null) {
widget.onSelectedSpaceChanged!(space.uuid);
}
context.read<SpaceManagementBloc>().add(
SelectSpaceEvent(
selectedCommunity: community, selectedSpace: space),
);
},
children: space.children.isNotEmpty
? space.children.map((childSpace) => _buildSpaceTile(childSpace, community)).toList()
? space.children
.map((childSpace) => _buildSpaceTile(childSpace, community))
.toList()
: [], // Recursively render child spaces if available
);
}

View File

@ -0,0 +1,21 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'community_dialog_event.dart';
import 'community_dialog_state.dart';
class CommunityDialogBloc extends Bloc<CommunityDialogEvent, CommunityDialogState> {
final List<String> existingCommunityNames;
CommunityDialogBloc(this.existingCommunityNames)
: super(CommunityDialogInitial()) {
on<ValidateCommunityNameEvent>((event, emit) {
final trimmedName = event.name.trim();
final isNameValid = !existingCommunityNames.contains(trimmedName);
final isNameEmpty = trimmedName.isEmpty;
emit(CommunityNameValidationState(
isNameValid: isNameValid,
isNameEmpty: isNameEmpty,
));
});
}
}

View File

@ -0,0 +1,15 @@
import 'package:equatable/equatable.dart';
abstract class CommunityDialogEvent extends Equatable {
@override
List<Object?> get props => [];
}
class ValidateCommunityNameEvent extends CommunityDialogEvent {
final String name;
ValidateCommunityNameEvent(this.name);
@override
List<Object?> get props => [name];
}

View File

@ -0,0 +1,21 @@
import 'package:equatable/equatable.dart';
abstract class CommunityDialogState extends Equatable {
@override
List<Object?> get props => [];
}
class CommunityDialogInitial extends CommunityDialogState {}
class CommunityNameValidationState extends CommunityDialogState {
final bool isNameValid;
final bool isNameEmpty;
CommunityNameValidationState({
required this.isNameValid,
required this.isNameEmpty,
});
@override
List<Object?> get props => [isNameValid, isNameEmpty];
}

View File

@ -0,0 +1,170 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/create_community/bloc/community_dialog_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/create_community/bloc/community_dialog_event.dart';
import 'package:syncrow_web/pages/spaces_management/create_community/bloc/community_dialog_state.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
class CreateCommunityDialog extends StatelessWidget {
final Function(String name, String description) onCreateCommunity;
final List<String> existingCommunityNames;
final bool isEditMode;
final String? initialName;
const CreateCommunityDialog({
super.key,
required this.onCreateCommunity,
required this.existingCommunityNames,
required this.isEditMode,
this.initialName,
});
@override
Widget build(BuildContext context) {
final nameController =
TextEditingController(text: isEditMode ? initialName : '');
return BlocProvider(
create: (_) => CommunityDialogBloc(existingCommunityNames),
child: Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
backgroundColor: ColorsManager.transparentColor,
child: Stack(
children: [
Container(
width: MediaQuery.of(context).size.width * 0.3,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: ColorsManager.blackColor.withOpacity(0.25),
blurRadius: 20,
spreadRadius: 5,
offset: const Offset(0, 5),
),
],
),
child: SingleChildScrollView(
child: BlocBuilder<CommunityDialogBloc, CommunityDialogState>(
builder: (context, state) {
bool isNameValid = true;
bool isNameEmpty = false;
if (state is CommunityNameValidationState) {
isNameValid = state.isNameValid;
isNameEmpty = state.isNameEmpty;
}
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
isEditMode ? 'Edit Community Name' : 'Community Name',
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 18),
TextField(
controller: nameController,
onChanged: (value) {
context
.read<CommunityDialogBloc>()
.add(ValidateCommunityNameEvent(value));
},
style: const TextStyle(
color: ColorsManager.blackColor,
),
decoration: InputDecoration(
hintText: 'Please enter the community name',
filled: true,
fillColor: ColorsManager.boxColor,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: isNameValid && !isNameEmpty
? ColorsManager.boxColor
: ColorsManager.red,
width: 1,
),
borderRadius: BorderRadius.circular(10),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(
color: ColorsManager.boxColor,
width: 1.5,
),
),
),
),
if (!isNameValid)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
'*Name already exists.',
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.red),
),
),
if (isNameEmpty)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
'*Name should not be empty.',
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.red),
),
),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: CancelButton(
label: 'Cancel',
onPressed: () => Navigator.of(context).pop(),
),
),
const SizedBox(width: 16),
Expanded(
child: DefaultButton(
onPressed: () {
if (isNameValid && !isNameEmpty) {
onCreateCommunity(
nameController.text.trim(),
"",
);
Navigator.of(context).pop();
}
},
backgroundColor: isNameValid && !isNameEmpty
? ColorsManager.secondaryColor
: ColorsManager.lightGrayColor,
borderRadius: 10,
foregroundColor: ColorsManager.whiteColors,
child: const Text('OK'),
),
),
],
),
],
);
},
),
),
),
],
),
),
);
}
}

View File

@ -1,85 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_state.dart';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/loaded_space_widget.dart';
import 'package:syncrow_web/services/product_api.dart';
import 'package:syncrow_web/services/space_mana_api.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
class SpaceManagementPage extends StatefulWidget {
const SpaceManagementPage({super.key});
@override
SpaceManagementPageState createState() => SpaceManagementPageState();
}
class SpaceManagementPageState extends State<SpaceManagementPage> {
CommunityModel? selectedCommunity;
SpaceModel? selectedSpace;
final CommunitySpaceManagementApi _api = CommunitySpaceManagementApi();
final ProductApi _productApi = ProductApi();
Map<String, List<SpaceModel>> communitySpaces = {};
List<ProductModel> products = [];
bool isProductDataLoaded = false;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) =>
SpaceManagementBloc(_api, _productApi)..add(LoadCommunityAndSpacesEvent()),
child: WebScaffold(
appBarTitle: Text('Space Management', style: Theme.of(context).textTheme.headlineLarge),
enableMenuSidebar: false,
rightBody: const NavigateHomeGridView(),
scaffoldBody:
BlocBuilder<SpaceManagementBloc, SpaceManagementState>(builder: (context, state) {
if (state is SpaceManagementLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is SpaceManagementLoaded) {
int selectedIndex = state.communities.indexWhere(
(community) => community.uuid == selectedCommunity?.uuid,
);
if (selectedIndex != -1) {
selectedCommunity = state.communities[selectedIndex];
} else if (state.selectedCommunity != null) {
selectedCommunity = state.selectedCommunity;
} else {
selectedCommunity = null;
selectedSpace = null;
}
return LoadedSpaceView(
communities: state.communities,
selectedCommunity: selectedCommunity,
selectedSpace: selectedSpace,
products: state.products,
onCommunitySelected: (community) {
setState(() {
selectedCommunity = community;
});
},
onSpaceSelected: (space) {
setState(() {
selectedSpace = space;
});
},
);
} else if (state is SpaceManagementError) {
return Center(child: Text('Error: ${state.errorMessage}'));
}
return Container();
}),
),
);
}
}

View File

@ -1,137 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CreateCommunityDialog extends StatefulWidget {
final Function(String name, String description) onCreateCommunity;
const CreateCommunityDialog({super.key, required this.onCreateCommunity});
@override
CreateCommunityDialogState createState() => CreateCommunityDialogState();
}
class CreateCommunityDialogState extends State<CreateCommunityDialog> {
String enteredName = ''; // Store the entered community name
@override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
backgroundColor: Colors.transparent, // Transparent for shadow effect
child: Stack(
children: [
// Background container with shadow and rounded corners
Container(
width: 490,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.25),
blurRadius: 20,
spreadRadius: 5,
offset: const Offset(0, 5),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Community Name',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
// Input field for the community name
TextField(
onChanged: (value) {
enteredName = value; // Capture entered name
},
style: const TextStyle(
color: Colors.black,
),
decoration: InputDecoration(
hintText: 'Please enter the community name',
filled: true,
fillColor: ColorsManager.boxColor,
hintStyle: const TextStyle(
fontSize: 14,
color: ColorsManager.grayBorder,
fontWeight: FontWeight.w400,
),
border: OutlineInputBorder(
borderSide: const BorderSide(color: ColorsManager.boxColor, width: 1),
borderRadius: BorderRadius.circular(10),
),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(color: ColorsManager.boxColor, width: 1),
borderRadius: BorderRadius.circular(10),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(color: ColorsManager.boxColor, width: 1),
),
),
),
const SizedBox(height: 24),
// Action buttons
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: TextButton(
onPressed: () => Navigator.of(context).pop(),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: ColorsManager.boxColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text(
'Cancel',
style: TextStyle(color: Colors.black),
),
),
),
const SizedBox(width: 16),
Expanded(
child: ElevatedButton(
onPressed: () {
if (enteredName.isNotEmpty) {
widget.onCreateCommunity(
enteredName,
"",
);
Navigator.of(context).pop();
}
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: ColorsManager.secondaryColor,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text('OK'),
),
),
],
),
],
),
),
],
),
);
}
}

View File

@ -1,78 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/community_structure_widget.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/gradient_canvas_border_widget.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/sidebar_widget.dart';
class LoadedSpaceView extends StatefulWidget {
final List<CommunityModel> communities;
final CommunityModel? selectedCommunity;
final SpaceModel? selectedSpace;
final ValueChanged<CommunityModel>? onCommunitySelected;
final ValueChanged<SpaceModel?>? onSpaceSelected;
final List<ProductModel>? products;
const LoadedSpaceView({
super.key,
required this.communities,
this.selectedCommunity,
this.selectedSpace,
required this.onCommunitySelected,
required this.onSpaceSelected,
this.products,
});
@override
_LoadedStateViewState createState() => _LoadedStateViewState();
}
class _LoadedStateViewState extends State<LoadedSpaceView> {
@override
Widget build(BuildContext context) {
return Stack(
clipBehavior: Clip.none,
children: [
Row(
children: [
SidebarWidget(
communities: widget.communities,
onCommunitySelected: widget.onCommunitySelected,
onSpaceSelected: widget.onSpaceSelected,
selectedSpaceUuid: widget.selectedSpace?.uuid,
onSelectedSpaceChanged: (String? spaceUuid) {
setState(() {
final selectedSpace = findSpaceByUuid(spaceUuid, widget.communities);
if (selectedSpace != null) {
widget.onSpaceSelected!(selectedSpace);
}
});
}),
CommunityStructureArea(
selectedCommunity: widget.selectedCommunity,
selectedSpace: widget.selectedSpace,
spaces: widget.selectedCommunity?.spaces ?? [],
products: widget.products,
onSpaceSelected: (SpaceModel? space) {
setState(() {
widget.onSpaceSelected!(space);
});
},
),
],
),
const GradientCanvasBorderWidget(),
],
);
}
SpaceModel? findSpaceByUuid(String? uuid, List<CommunityModel> communities) {
for (var community in communities) {
for (var space in community.spaces) {
if (space.uuid == uuid) return space;
}
}
return null;
}
}

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart';

View File

@ -6,6 +6,7 @@ import 'package:syncrow_web/pages/routiens/models/routine_details_model.dart';
import 'package:syncrow_web/pages/routiens/models/routine_model.dart';
import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
class SceneApi {
static final HTTPService _httpService = HTTPService();
@ -36,7 +37,6 @@ class SceneApi {
static Future<Map<String, dynamic>> createAutomation(
CreateAutomationModel createAutomationModel) async {
try {
debugPrint("automation body ${createAutomationModel.toMap()}");
final response = await _httpService.post(
path: ApiEndpoints.createAutomation,
body: createAutomationModel.toMap(),
@ -77,7 +77,8 @@ class SceneApi {
final response = await _httpService.get(
path: ApiEndpoints.getUnitScenes
.replaceAll('{spaceUuid}', unitId)
.replaceAll('{communityUuid}', communityId),
.replaceAll('{communityUuid}', communityId)
.replaceAll('{projectId}', TempConst.projectId),
queryParameters: {'showInHomePage': showInDevice},
showServerMessage: false,
expectedResponseModel: (json) {
@ -138,29 +139,49 @@ class SceneApi {
path: ApiEndpoints.getAutomationDetails
.replaceAll('{automationId}', automationId),
showServerMessage: false,
expectedResponseModel: (json) => RoutineDetailsModel.fromJson(json),
expectedResponseModel: (json) => RoutineDetailsModel.fromMap(json),
);
return response;
} catch (e) {
rethrow;
}
}
//update Scene
static updateScene(CreateSceneModel createSceneModel, String sceneId) async {
try {
final response = await _httpService.put(
path: ApiEndpoints.updateScene.replaceAll('{sceneId}', sceneId),
body: createSceneModel
.toJson(sceneId.isNotEmpty == true ? sceneId : null),
expectedResponseModel: (json) {
return json;
},
);
return response;
} catch (e) {
rethrow;
}
}
//update automation
static updateAutomation(
CreateAutomationModel createAutomationModel, String automationId) async {
try {
final response = await _httpService.put(
path: ApiEndpoints.updateAutomation
.replaceAll('{automationId}', automationId),
body: createAutomationModel
.toJson(automationId.isNotEmpty == true ? automationId : null),
expectedResponseModel: (json) {
return json;
},
);
return response;
} catch (e) {
rethrow;
}
}
//
// //updateAutomationStatus
// static Future<bool> updateAutomationStatus(String automationId,
// AutomationStatusUpdate createAutomationEnable) async {
// try {
// final response = await _httpService.put(
// path: ApiEndpoints.updateAutomationStatus
// .replaceAll('{automationId}', automationId),
// body: createAutomationEnable.toMap(),
// expectedResponseModel: (json) => json['success'],
// );
// return response;
// } catch (e) {
// rethrow;
// }
// }
//getScene
static Future<RoutineDetailsModel> getSceneDetails(String sceneId) async {
@ -168,51 +189,14 @@ class SceneApi {
final response = await _httpService.get(
path: ApiEndpoints.getScene.replaceAll('{sceneId}', sceneId),
showServerMessage: false,
expectedResponseModel: (json) => RoutineDetailsModel.fromJson(json),
expectedResponseModel: (json) =>
RoutineDetailsModel.fromMap(json['data']),
);
return response;
} catch (e) {
rethrow;
}
}
//
// //update Scene
// static updateScene(CreateSceneModel createSceneModel, String sceneId) async {
// try {
// final response = await _httpService.put(
// path: ApiEndpoints.updateScene.replaceAll('{sceneId}', sceneId),
// body: createSceneModel
// .toJson(sceneId.isNotEmpty == true ? sceneId : null),
// expectedResponseModel: (json) {
// return json;
// },
// );
// return response;
// } catch (e) {
// rethrow;
// }
// }
//
// //update automation
// static updateAutomation(
// CreateAutomationModel createAutomationModel, String automationId) async {
// try {
// final response = await _httpService.put(
// path: ApiEndpoints.updateAutomation
// .replaceAll('{automationId}', automationId),
// body: createAutomationModel
// .toJson(automationId.isNotEmpty == true ? automationId : null),
// expectedResponseModel: (json) {
// return json;
// },
// );
// return response;
// } catch (e) {
// rethrow;
// }
// }
//
//delete Scene
static Future<bool> deleteScene(

View File

@ -1,29 +1,40 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/space_response_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_model.dart';
import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
class CommunitySpaceManagementApi {
// Community Management APIs
Future<List<CommunityModel>> fetchCommunities() async {
Future<List<CommunityModel>> fetchCommunities({int page = 1}) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.getCommunityList,
expectedResponseModel: (json) {
// Access the 'data' key from the response
List<dynamic> jsonData = json['data'];
List<CommunityModel> allCommunities = [];
bool hasNext = true;
// Check if jsonData is actually a List
while (hasNext) {
await HTTPService().get(
path: ApiEndpoints.getCommunityList
.replaceAll('{projectId}', TempConst.projectId),
queryParameters: {'page': page},
expectedResponseModel: (json) {
List<dynamic> jsonData = json['data'];
hasNext = json['hasNext'] ?? false;
int currentPage = json['page'] ?? 1;
List<CommunityModel> communityList = jsonData.map((jsonItem) {
return CommunityModel.fromJson(jsonItem);
}).toList();
allCommunities.addAll(communityList);
page = currentPage + 1;
return communityList;
},
);
return response;
}
return allCommunities;
} catch (e) {
debugPrint('Error fetching communities: $e');
return [];
@ -33,7 +44,8 @@ class CommunitySpaceManagementApi {
Future<CommunityModel?> getCommunityById(String communityId) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.getCommunityById.replaceAll('{communityId}', communityId),
path: ApiEndpoints.getCommunityById
.replaceAll('{communityId}', communityId),
expectedResponseModel: (json) {
return CommunityModel.fromJson(json['data']);
},
@ -45,10 +57,12 @@ class CommunitySpaceManagementApi {
}
}
Future<CommunityModel?> createCommunity(String name, String description) async {
Future<CommunityModel?> createCommunity(
String name, String description) async {
try {
final response = await HTTPService().post(
path: ApiEndpoints.createCommunity,
path: ApiEndpoints.createCommunity
.replaceAll('{projectId}', TempConst.projectId),
body: {
'name': name,
'description': description,
@ -67,7 +81,9 @@ class CommunitySpaceManagementApi {
Future<bool> updateCommunity(String communityId, String name) async {
try {
final response = await HTTPService().put(
path: ApiEndpoints.updateCommunity.replaceAll('{communityId}', communityId),
path: ApiEndpoints.updateCommunity
.replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', TempConst.projectId),
body: {
'name': name,
},
@ -85,7 +101,9 @@ class CommunitySpaceManagementApi {
Future<bool> deleteCommunity(String communityId) async {
try {
final response = await HTTPService().delete(
path: ApiEndpoints.deleteCommunity.replaceAll('{communityId}', communityId),
path: ApiEndpoints.deleteCommunity
.replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', TempConst.projectId),
expectedResponseModel: (json) {
return json['success'] ?? false;
},
@ -100,7 +118,9 @@ class CommunitySpaceManagementApi {
Future<SpacesResponse> fetchSpaces(String communityId) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.listSpaces.replaceAll('{communityId}', communityId),
path: ApiEndpoints.listSpaces
.replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', TempConst.projectId),
expectedResponseModel: (json) {
return SpacesResponse.fromJson(json);
},
@ -126,7 +146,8 @@ class CommunitySpaceManagementApi {
final response = await HTTPService().get(
path: ApiEndpoints.getSpace
.replaceAll('{communityId}', communityId)
.replaceAll('{spaceId}', spaceId),
.replaceAll('{spaceId}', spaceId)
.replaceAll('{projectId}', TempConst.projectId),
expectedResponseModel: (json) {
return SpaceModel.fromJson(json);
},
@ -162,7 +183,9 @@ class CommunitySpaceManagementApi {
body['parentUuid'] = parentId;
}
final response = await HTTPService().post(
path: ApiEndpoints.createSpace.replaceAll('{communityId}', communityId),
path: ApiEndpoints.createSpace
.replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', TempConst.projectId),
body: body,
expectedResponseModel: (json) {
return SpaceModel.fromJson(json['data']);
@ -203,7 +226,8 @@ class CommunitySpaceManagementApi {
final response = await HTTPService().put(
path: ApiEndpoints.updateSpace
.replaceAll('{communityId}', communityId)
.replaceAll('{spaceId}', spaceId),
.replaceAll('{spaceId}', spaceId)
.replaceAll('{projectId}', TempConst.projectId),
body: body,
expectedResponseModel: (json) {
return SpaceModel.fromJson(json['data']);
@ -221,7 +245,8 @@ class CommunitySpaceManagementApi {
final response = await HTTPService().delete(
path: ApiEndpoints.deleteSpace
.replaceAll('{communityId}', communityId)
.replaceAll('{spaceId}', spaceId),
.replaceAll('{spaceId}', spaceId)
.replaceAll('{projectId}', TempConst.projectId),
expectedResponseModel: (json) {
return json['success'] ?? false;
},
@ -236,10 +261,13 @@ class CommunitySpaceManagementApi {
Future<List<SpaceModel>> getSpaceHierarchy(String communityId) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.getSpaceHierarchy.replaceAll('{communityId}', communityId),
path: ApiEndpoints.getSpaceHierarchy
.replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', TempConst.projectId),
expectedResponseModel: (json) {
final spaceModels =
(json['data'] as List).map((spaceJson) => SpaceModel.fromJson(spaceJson)).toList();
final spaceModels = (json['data'] as List)
.map((spaceJson) => SpaceModel.fromJson(spaceJson))
.toList();
return spaceModels;
},

View File

@ -3,7 +3,7 @@ import 'package:syncrow_web/pages/access_management/view/access_management.dart'
import 'package:syncrow_web/pages/auth/view/login_page.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/view/device_managment_page.dart';
import 'package:syncrow_web/pages/home/view/home_page.dart';
import 'package:syncrow_web/pages/spaces_management/view/spaces_management_page.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/view/spaces_management_page.dart';
import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart';
@ -32,7 +32,7 @@ class AppRoutes {
),
GoRoute(
path: RoutesConst.spacesManagementPage,
builder: (context, state) => SpaceManagementPage()),
builder: (context, state) => const SpaceManagementPage()),
];
}
}

View File

@ -36,21 +36,21 @@ abstract class ApiEndpoints {
static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}';
// Space Module
static const String createSpace = '/communities/{communityId}/spaces';
static const String listSpaces = '/communities/{communityId}/spaces';
static const String deleteSpace = '/communities/{communityId}/spaces/{spaceId}';
static const String updateSpace = '/communities/{communityId}/spaces/{spaceId}';
static const String getSpace = '/communities/{communityId}/spaces/{spaceId}';
static const String getSpaceHierarchy = '/communities/{communityId}/spaces';
static const String createSpace = '/projects/{projectId}/communities/{communityId}/spaces';
static const String listSpaces = '/projects/{projectId}/communities/{communityId}/spaces';
static const String deleteSpace = '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}';
static const String updateSpace = '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}';
static const String getSpace = '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}';
static const String getSpaceHierarchy = '/projects/{projectId}/communities/{communityId}/spaces';
// Community Module
static const String createCommunity = '/communities';
static const String getCommunityList = '/communities';
static const String getCommunityById = '/communities/{communityId}';
static const String updateCommunity = '/communities/{communityId}';
static const String deleteCommunity = '/communities/{communityId}';
static const String getUserCommunities = '/communities/user/{userUuid}';
static const String createUserCommunity = '/communities/user';
static const String createCommunity = '/projects/{projectId}/communities';
static const String getCommunityList = '/projects/{projectId}/communities';
static const String getCommunityById = '/projects/{projectId}/communities/{communityId}';
static const String updateCommunity = '/projects/{projectId}/communities/{communityId}';
static const String deleteCommunity = '/projects/{projectId}communities/{communityId}';
static const String getUserCommunities = '/projects/{projectId}/communities/user/{userUuid}';
static const String createUserCommunity = '/projects/{projectId}/communities/user';
static const String getDeviceLogsByDate =
'/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}';
@ -68,10 +68,13 @@ abstract class ApiEndpoints {
static const String getIconScene = '/scene/icon';
static const String createScene = '/scene/tap-to-run';
static const String createAutomation = '/automation';
static const String getUnitScenes = '/communities/{communityUuid}/spaces/{spaceUuid}/scenes';
static const String getUnitScenes = '/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/scenes';
static const String getAutomationDetails = '/automation/details/{automationId}';
static const String getScene = '/scene/tap-to-run/{sceneId}';
static const String deleteScene = '/scene/tap-to-run/{sceneId}';
static const String deleteAutomation = '/automation/{automationId}';
static const String updateScene = '/scene/tap-to-run/{sceneId}';
static const String updateAutomation = '/automation/{automationId}';
}

View File

@ -0,0 +1,3 @@
class TempConst {
static const projectId = '0e62577c-06fa-41b9-8a92-99a21fbaf51c';
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/navigation_service.dart';
class CustomSnackBar {
@ -11,6 +12,35 @@ class CustomSnackBar {
}
}
static redSnackBar(String message) {
final key = NavigationService.snackbarKey;
BuildContext? currentContext = key?.currentContext;
if (key != null && currentContext != null) {
final snackBar = SnackBar(
padding: const EdgeInsets.all(16),
backgroundColor: ColorsManager.red,
content: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
const Icon(
Icons.check_circle,
color: ColorsManager.whiteColors,
size: 32,
),
const SizedBox(
width: 8,
),
Text(
message,
style: Theme.of(currentContext)
.textTheme
.bodySmall!
.copyWith(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.green),
)
]),
);
key.currentState?.showSnackBar(snackBar);
}
}
static greenSnackBar(String message) {
final key = NavigationService.snackbarKey;
BuildContext? currentContext = key?.currentContext;
@ -29,8 +59,10 @@ class CustomSnackBar {
),
Text(
message,
style: Theme.of(currentContext).textTheme.bodySmall!.copyWith(
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.green),
style: Theme.of(currentContext)
.textTheme
.bodySmall!
.copyWith(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.green),
)
]),
);