Compare commits

..

26 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
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
47 changed files with 618 additions and 544 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

@ -54,8 +54,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
TriggerSwitchTabsEvent event, TriggerSwitchTabsEvent event,
Emitter<RoutineState> emit, Emitter<RoutineState> emit,
) { ) {
emit(state.copyWith( emit(state.copyWith(routineTab: event.isRoutineTab, createRoutineView: false));
routineTab: event.isRoutineTab, createRoutineView: false));
add(ResetRoutineState()); add(ResetRoutineState());
if (event.isRoutineTab) { if (event.isRoutineTab) {
add(const LoadScenes(spaceId, communityId)); add(const LoadScenes(spaceId, communityId));
@ -81,8 +80,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final updatedIfItems = List<Map<String, dynamic>>.from(state.ifItems); final updatedIfItems = List<Map<String, dynamic>>.from(state.ifItems);
// Find the index of the item in teh current itemsList // Find the index of the item in teh current itemsList
int index = updatedIfItems.indexWhere( int index =
(map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); updatedIfItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
// Replace the map if the index is valid // Replace the map if the index is valid
if (index != -1) { if (index != -1) {
updatedIfItems[index] = event.item; updatedIfItems[index] = event.item;
@ -91,21 +90,18 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
if (event.isTabToRun) { if (event.isTabToRun) {
emit(state.copyWith( emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
} else { } else {
emit(state.copyWith( emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
} }
} }
void _onAddToThenContainer( void _onAddToThenContainer(AddToThenContainer event, Emitter<RoutineState> emit) {
AddToThenContainer event, Emitter<RoutineState> emit) {
final currentItems = List<Map<String, dynamic>>.from(state.thenItems); final currentItems = List<Map<String, dynamic>>.from(state.thenItems);
// Find the index of the item in teh current itemsList // Find the index of the item in teh current itemsList
int index = currentItems.indexWhere( int index =
(map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
// Replace the map if the index is valid // Replace the map if the index is valid
if (index != -1) { if (index != -1) {
currentItems[index] = event.item; currentItems[index] = event.item;
@ -116,26 +112,22 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
emit(state.copyWith(thenItems: currentItems)); emit(state.copyWith(thenItems: currentItems));
} }
void _onAddFunctionsToRoutine( void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter<RoutineState> emit) {
AddFunctionToRoutine event, Emitter<RoutineState> emit) {
try { try {
if (event.functions.isEmpty) return; if (event.functions.isEmpty) return;
List<DeviceFunctionData> selectedFunction = List<DeviceFunctionData> selectedFunction = List<DeviceFunctionData>.from(event.functions);
List<DeviceFunctionData>.from(event.functions);
Map<String, List<DeviceFunctionData>> currentSelectedFunctions = Map<String, List<DeviceFunctionData>> currentSelectedFunctions =
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions); Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) { if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) {
List<DeviceFunctionData> currentFunctions = List<DeviceFunctionData> currentFunctions =
List<DeviceFunctionData>.from( List<DeviceFunctionData>.from(currentSelectedFunctions[event.uniqueCustomId] ?? []);
currentSelectedFunctions[event.uniqueCustomId] ?? []);
List<String> functionCode = []; List<String> functionCode = [];
for (int i = 0; i < selectedFunction.length; i++) { for (int i = 0; i < selectedFunction.length; i++) {
for (int j = 0; j < currentFunctions.length; j++) { for (int j = 0; j < currentFunctions.length; j++) {
if (selectedFunction[i].functionCode == if (selectedFunction[i].functionCode == currentFunctions[j].functionCode) {
currentFunctions[j].functionCode) {
currentFunctions[j] = selectedFunction[i]; currentFunctions[j] = selectedFunction[i];
if (!functionCode.contains(currentFunctions[j].functionCode)) { if (!functionCode.contains(currentFunctions[j].functionCode)) {
functionCode.add(currentFunctions[j].functionCode); functionCode.add(currentFunctions[j].functionCode);
@ -145,15 +137,13 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
for (int i = 0; i < functionCode.length; i++) { for (int i = 0; i < functionCode.length; i++) {
selectedFunction selectedFunction.removeWhere((code) => code.functionCode == functionCode[i]);
.removeWhere((code) => code.functionCode == functionCode[i]);
} }
currentSelectedFunctions[event.uniqueCustomId] = currentSelectedFunctions[event.uniqueCustomId] = List.from(currentFunctions)
List.from(currentFunctions)..addAll(selectedFunction); ..addAll(selectedFunction);
} else { } else {
currentSelectedFunctions[event.uniqueCustomId] = currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions);
List.from(event.functions);
} }
emit(state.copyWith(selectedFunctions: currentSelectedFunctions)); emit(state.copyWith(selectedFunctions: currentSelectedFunctions));
@ -162,13 +152,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
Future<void> _onLoadScenes( Future<void> _onLoadScenes(LoadScenes event, Emitter<RoutineState> emit) async {
LoadScenes event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null)); emit(state.copyWith(isLoading: true, errorMessage: null));
try { try {
final scenes = final scenes = await SceneApi.getScenesByUnitId(event.unitId, event.communityId);
await SceneApi.getScenesByUnitId(event.unitId, event.communityId);
emit(state.copyWith( emit(state.copyWith(
scenes: scenes, scenes: scenes,
isLoading: false, isLoading: false,
@ -183,8 +171,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
Future<void> _onLoadAutomation( Future<void> _onLoadAutomation(LoadAutomation event, Emitter<RoutineState> emit) async {
LoadAutomation event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null)); emit(state.copyWith(isLoading: true, errorMessage: null));
try { try {
@ -212,16 +199,14 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
FutureOr<void> _onSearchRoutines( FutureOr<void> _onSearchRoutines(SearchRoutines event, Emitter<RoutineState> emit) async {
SearchRoutines event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null)); emit(state.copyWith(isLoading: true, errorMessage: null));
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
emit(state.copyWith(isLoading: false, errorMessage: null)); emit(state.copyWith(isLoading: false, errorMessage: null));
emit(state.copyWith(searchText: event.query)); emit(state.copyWith(searchText: event.query));
} }
FutureOr<void> _onAddSelectedIcon( FutureOr<void> _onAddSelectedIcon(AddSelectedIcon event, Emitter<RoutineState> emit) {
AddSelectedIcon event, Emitter<RoutineState> emit) {
emit(state.copyWith(selectedIcon: event.icon)); emit(state.copyWith(selectedIcon: event.icon));
} }
@ -235,8 +220,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
return actions.last['deviceId'] == 'delay'; return actions.last['deviceId'] == 'delay';
} }
Future<void> _onCreateScene( Future<void> _onCreateScene(CreateSceneEvent event, Emitter<RoutineState> emit) async {
CreateSceneEvent event, Emitter<RoutineState> emit) async {
try { try {
// Check if first action is delay // Check if first action is delay
// if (_isFirstActionDelay(state.thenItems)) { // if (_isFirstActionDelay(state.thenItems)) {
@ -249,8 +233,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (_isLastActionDelay(state.thenItems)) { if (_isLastActionDelay(state.thenItems)) {
emit(state.copyWith( emit(state.copyWith(
errorMessage: errorMessage: 'A delay condition cannot be the only or the last action',
'A delay condition cannot be the only or the last action',
isLoading: false, isLoading: false,
)); ));
return; return;
@ -323,8 +306,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
Future<void> _onCreateAutomation( Future<void> _onCreateAutomation(CreateAutomationEvent event, Emitter<RoutineState> emit) async {
CreateAutomationEvent event, Emitter<RoutineState> emit) async {
try { try {
if (state.routineName == null || state.routineName!.isEmpty) { if (state.routineName == null || state.routineName!.isEmpty) {
emit(state.copyWith( emit(state.copyWith(
@ -345,23 +327,19 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (_isLastActionDelay(state.thenItems)) { if (_isLastActionDelay(state.thenItems)) {
emit(state.copyWith( emit(state.copyWith(
errorMessage: errorMessage: 'A delay condition cannot be the only or the last action',
'A delay condition cannot be the only or the last action',
isLoading: false, isLoading: false,
)); ));
CustomSnackBar.redSnackBar('Cannot have delay as the last action'); CustomSnackBar.redSnackBar('Cannot have delay as the last action');
return; return;
} }
emit(state.copyWith(isLoading: true, errorMessage: null)); emit(state.copyWith(isLoading: true, errorMessage: null));
int i = 0;
final conditions = state.ifItems.expand((item) { final conditions = state.ifItems.expand((item) {
final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; final functions = state.selectedFunctions[item['uniqueCustomId']] ?? [];
return functions.map((function) { return functions.map((function) {
return Condition( return Condition(
code: state.selectedFunctions[item['uniqueCustomId']]!.indexOf( code: i++,
function,
) +
1,
entityId: function.entityId, entityId: function.entityId,
entityType: 'device_report', entityType: 'device_report',
expr: ConditionExpr( expr: ConditionExpr(
@ -459,21 +437,17 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
FutureOr<void> _onRemoveDragCard( FutureOr<void> _onRemoveDragCard(RemoveDragCard event, Emitter<RoutineState> emit) {
RemoveDragCard event, Emitter<RoutineState> emit) {
if (event.isFromThen) { if (event.isFromThen) {
final thenItems = List<Map<String, dynamic>>.from(state.thenItems); final thenItems = List<Map<String, dynamic>>.from(state.thenItems);
final selectedFunctions = final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
thenItems.removeAt(event.index); thenItems.removeAt(event.index);
selectedFunctions.remove(event.key); selectedFunctions.remove(event.key);
emit(state.copyWith( emit(state.copyWith(thenItems: thenItems, selectedFunctions: selectedFunctions));
thenItems: thenItems, selectedFunctions: selectedFunctions));
} else { } else {
final ifItems = List<Map<String, dynamic>>.from(state.ifItems); final ifItems = List<Map<String, dynamic>>.from(state.ifItems);
final selectedFunctions = final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
ifItems.removeAt(event.index); ifItems.removeAt(event.index);
selectedFunctions.remove(event.key); selectedFunctions.remove(event.key);
@ -484,8 +458,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
isAutomation: false, isAutomation: false,
isTabToRun: false)); isTabToRun: false));
} else { } else {
emit(state.copyWith( emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions));
ifItems: ifItems, selectedFunctions: selectedFunctions));
} }
} }
} }
@ -497,23 +470,18 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
)); ));
} }
FutureOr<void> _onEffectiveTimeEvent( FutureOr<void> _onEffectiveTimeEvent(EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
emit(state.copyWith(effectiveTime: event.effectiveTime)); emit(state.copyWith(effectiveTime: event.effectiveTime));
} }
FutureOr<void> _onSetRoutineName( FutureOr<void> _onSetRoutineName(SetRoutineName event, Emitter<RoutineState> emit) {
SetRoutineName event, Emitter<RoutineState> emit) {
emit(state.copyWith( emit(state.copyWith(
routineName: event.name, routineName: event.name,
)); ));
} }
( (List<Map<String, dynamic>>, List<Map<String, dynamic>>, Map<String, List<DeviceFunctionData>>)
List<Map<String, dynamic>>, _createCardData(
List<Map<String, dynamic>>,
Map<String, List<DeviceFunctionData>>
) _createCardData(
List<RoutineAction> actions, List<RoutineAction> actions,
List<RoutineCondition>? conditions, List<RoutineCondition>? conditions,
Map<String, List<DeviceFunctionData>> currentFunctions, Map<String, List<DeviceFunctionData>> currentFunctions,
@ -546,8 +514,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
'deviceId': condition.entityId, 'deviceId': condition.entityId,
'title': matchingDevice.name ?? condition.entityId, 'title': matchingDevice.name ?? condition.entityId,
'productType': condition.entityType, 'productType': condition.entityType,
'imagePath': 'imagePath': matchingDevice.getDefaultIcon(condition.entityType),
matchingDevice.getDefaultIcon(condition.entityType),
}; };
final functions = matchingDevice.functions; final functions = matchingDevice.functions;
@ -583,11 +550,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final cardData = { final cardData = {
'entityId': action.entityId, 'entityId': action.entityId,
'uniqueCustomId': const Uuid().v4(), 'uniqueCustomId': const Uuid().v4(),
'deviceId': 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
action.actionExecutor == 'delay' ? 'delay' : action.entityId, 'title': action.actionExecutor == 'delay' ? 'Delay' : (matchingDevice.name ?? 'Device'),
'title': action.actionExecutor == 'delay'
? 'Delay'
: (matchingDevice.name ?? 'Device'),
'productType': action.productType, 'productType': action.productType,
'imagePath': matchingDevice.getDefaultIcon(action.productType), 'imagePath': matchingDevice.getDefaultIcon(action.productType),
}; };
@ -630,8 +594,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
return (thenItems, ifItems, currentFunctions); return (thenItems, ifItems, currentFunctions);
} }
Future<void> _onGetSceneDetails( Future<void> _onGetSceneDetails(GetSceneDetails event, Emitter<RoutineState> emit) async {
GetSceneDetails event, Emitter<RoutineState> emit) async {
try { try {
emit(state.copyWith( emit(state.copyWith(
isLoading: true, isLoading: true,
@ -679,12 +642,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (!deviceCards.containsKey(deviceId)) { if (!deviceCards.containsKey(deviceId)) {
deviceCards[deviceId] = { deviceCards[deviceId] = {
'entityId': action.entityId, 'entityId': action.entityId,
'deviceId': 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
action.actionExecutor == 'delay' ? 'delay' : action.entityId, 'uniqueCustomId': action.type == 'automation' || action.actionExecutor == 'delay'
'uniqueCustomId': ? const Uuid().v4()
action.type == 'automation' || action.actionExecutor == 'delay' : action.entityId,
? const Uuid().v4()
: action.entityId,
'title': action.actionExecutor == 'delay' 'title': action.actionExecutor == 'delay'
? 'Delay' ? 'Delay'
: action.type == 'automation' : action.type == 'automation'
@ -719,8 +680,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
), ),
); );
// emit(state.copyWith(automationActionExecutor: action.actionExecutor)); // emit(state.copyWith(automationActionExecutor: action.actionExecutor));
} else if (action.executorProperty != null && } else if (action.executorProperty != null && action.actionExecutor != 'delay') {
action.actionExecutor != 'delay') {
if (!updatedFunctions.containsKey(uniqueCustomId)) { if (!updatedFunctions.containsKey(uniqueCustomId)) {
updatedFunctions[uniqueCustomId] = []; updatedFunctions[uniqueCustomId] = [];
} }
@ -792,20 +752,21 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
FutureOr<void> _onResetRoutineState( FutureOr<void> _onResetRoutineState(ResetRoutineState event, Emitter<RoutineState> emit) {
ResetRoutineState event, Emitter<RoutineState> emit) {
emit(state.copyWith( emit(state.copyWith(
ifItems: [], ifItems: [],
thenItems: [], thenItems: [],
selectedFunctions: {}, selectedFunctions: {},
scenes: [], scenes: [],
sceneId: '',
automationId: '',
automations: [], automations: [],
isLoading: false, isLoading: false,
errorMessage: '', errorMessage: '',
loadScenesErrorMessage: null, loadScenesErrorMessage: null,
loadAutomationErrorMessage: null, loadAutomationErrorMessage: null,
searchText: '', searchText: '',
selectedIcon: null, selectedIcon: '',
isTabToRun: false, isTabToRun: false,
isAutomation: false, isAutomation: false,
selectedAutomationOperator: 'or', selectedAutomationOperator: 'or',
@ -821,8 +782,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (state.isTabToRun) { if (state.isTabToRun) {
SceneApi.deleteScene(unitUuid: spaceId, sceneId: state.sceneId ?? ''); SceneApi.deleteScene(unitUuid: spaceId, sceneId: state.sceneId ?? '');
} else { } else {
SceneApi.deleteAutomation( SceneApi.deleteAutomation(unitUuid: spaceId, automationId: state.automationId ?? '');
unitUuid: spaceId, automationId: state.automationId ?? '');
} }
add(const LoadScenes(spaceId, communityId)); add(const LoadScenes(spaceId, communityId));
@ -851,8 +811,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
// } // }
// } // }
FutureOr<void> _fetchDevices( FutureOr<void> _fetchDevices(FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true)); emit(state.copyWith(isLoading: true));
try { try {
final devices = await DevicesManagementApi().fetchDevices(); final devices = await DevicesManagementApi().fetchDevices();
@ -863,8 +822,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
FutureOr<void> _onUpdateScene( FutureOr<void> _onUpdateScene(UpdateScene event, Emitter<RoutineState> emit) async {
UpdateScene event, Emitter<RoutineState> emit) async {
try { try {
// Check if first action is delay // Check if first action is delay
// if (_isFirstActionDelay(state.thenItems)) { // if (_isFirstActionDelay(state.thenItems)) {
@ -878,8 +836,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (_isLastActionDelay(state.thenItems)) { if (_isLastActionDelay(state.thenItems)) {
emit(state.copyWith( emit(state.copyWith(
errorMessage: errorMessage: 'A delay condition cannot be the only or the last action',
'A delay condition cannot be the only or the last action',
isLoading: false, isLoading: false,
)); ));
return; return;
@ -932,8 +889,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
actions: actions, actions: actions,
); );
final result = final result = await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
if (result['success']) { if (result['success']) {
add(ResetRoutineState()); add(ResetRoutineState());
add(const LoadScenes(spaceId, communityId)); add(const LoadScenes(spaceId, communityId));
@ -952,8 +908,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
FutureOr<void> _onUpdateAutomation( FutureOr<void> _onUpdateAutomation(UpdateAutomation event, Emitter<RoutineState> emit) async {
UpdateAutomation event, Emitter<RoutineState> emit) async {
try { try {
if (state.routineName == null || state.routineName!.isEmpty) { if (state.routineName == null || state.routineName!.isEmpty) {
emit(state.copyWith( emit(state.copyWith(
@ -977,15 +932,12 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
return; return;
} }
emit(state.copyWith(isLoading: true, errorMessage: null)); emit(state.copyWith(isLoading: true, errorMessage: null));
int i = 0;
final conditions = state.ifItems.expand((item) { final conditions = state.ifItems.expand((item) {
final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; final functions = state.selectedFunctions[item['uniqueCustomId']] ?? [];
return functions.map((function) { return functions.map((function) {
return Condition( return Condition(
code: state.selectedFunctions[item['uniqueCustomId']]!.indexOf( code: i++,
function,
) +
1,
entityId: function.entityId, entityId: function.entityId,
entityType: 'device_report', entityType: 'device_report',
expr: ConditionExpr( expr: ConditionExpr(
@ -1064,8 +1016,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
actions: actions, actions: actions,
); );
final result = await SceneApi.updateAutomation( final result =
createAutomationModel, state.automationId ?? ''); await SceneApi.updateAutomation(createAutomationModel, state.automationId ?? '');
if (result['success']) { if (result['success']) {
add(ResetRoutineState()); add(ResetRoutineState());
@ -1098,10 +1050,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
thenItems: [], thenItems: [],
)); ));
final automationDetails = final automationDetails = await SceneApi.getAutomationDetails(event.automationId);
await SceneApi.getAutomationDetails(event.automationId);
final Map<String, Map<String, dynamic>> deviceIfCards = {};
final Map<String, Map<String, dynamic>> deviceThenCards = {};
final Map<String, Map<String, dynamic>> deviceCards = {};
final Map<String, List<DeviceFunctionData>> updatedFunctions = final Map<String, List<DeviceFunctionData>> updatedFunctions =
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions); Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
@ -1117,8 +1070,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final deviceId = condition.entityId; final deviceId = condition.entityId;
if (!deviceCards.containsKey(deviceId)) { if (!deviceIfCards.containsKey(deviceId)) {
deviceCards[deviceId] = { deviceIfCards[deviceId] = {
'entityId': condition.entityId, 'entityId': condition.entityId,
'deviceId': condition.entityId, 'deviceId': condition.entityId,
'uniqueCustomId': const Uuid().v4(), 'uniqueCustomId': const Uuid().v4(),
@ -1131,7 +1084,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}; };
} }
final cardData = deviceCards[deviceId]!; final cardData = deviceIfCards[deviceId]!;
final uniqueCustomId = cardData['uniqueCustomId'].toString(); final uniqueCustomId = cardData['uniqueCustomId'].toString();
if (!updatedFunctions.containsKey(uniqueCustomId)) { if (!updatedFunctions.containsKey(uniqueCustomId)) {
@ -1165,15 +1118,13 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
), ),
); );
final deviceId = action.actionExecutor == 'delay' final deviceId =
? '${action.entityId}_delay' action.actionExecutor == 'delay' ? '${action.entityId}_delay' : action.entityId;
: action.entityId;
if (!deviceCards.containsKey(deviceId)) { if (!deviceThenCards.containsKey(deviceId)) {
deviceCards[deviceId] = { deviceThenCards[deviceId] = {
'entityId': action.entityId, 'entityId': action.entityId,
'deviceId': 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'uniqueCustomId': const Uuid().v4(), 'uniqueCustomId': const Uuid().v4(),
'title': action.actionExecutor == 'delay' 'title': action.actionExecutor == 'delay'
? 'Delay' ? 'Delay'
@ -1193,18 +1144,18 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
: action.type == 'automation' : action.type == 'automation'
? 'automation' ? 'automation'
: 'action', : 'action',
'icon': action.icon ?? ''
}; };
} }
final cardData = deviceCards[deviceId]!; final cardData = deviceThenCards[deviceId]!;
final uniqueCustomId = cardData['uniqueCustomId'].toString(); final uniqueCustomId = cardData['uniqueCustomId'].toString();
if (!updatedFunctions.containsKey(uniqueCustomId)) { if (!updatedFunctions.containsKey(uniqueCustomId)) {
updatedFunctions[uniqueCustomId] = []; updatedFunctions[uniqueCustomId] = [];
} }
if (action.executorProperty != null && if (action.executorProperty != null && action.actionExecutor != 'delay') {
action.actionExecutor != 'delay') {
final functions = matchingDevice.functions; final functions = matchingDevice.functions;
final functionCode = action.executorProperty!.functionCode; final functionCode = action.executorProperty!.functionCode;
for (var function in functions) { for (var function in functions) {
@ -1246,14 +1197,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
final ifItems = deviceCards.values final ifItems = deviceIfCards.values.where((card) => card['type'] == 'condition').toList();
.where((card) => card['type'] == 'condition') final thenItems = deviceThenCards.values
.toList();
final thenItems = deviceCards.values
.where((card) => .where((card) =>
card['type'] == 'action' || card['type'] == 'action' || card['type'] == 'automation' || card['type'] == 'scene')
card['type'] == 'automation' ||
card['type'] == 'scene')
.toList(); .toList();
emit(state.copyWith( emit(state.copyWith(

View File

@ -101,7 +101,7 @@ class SaveRoutineHelper {
final functions = final functions =
state.selectedFunctions[item['uniqueCustomId']] ?? []; state.selectedFunctions[item['uniqueCustomId']] ?? [];
return ListTile( return ListTile(
leading: item['type'] == 'tap_to_run' leading: item['type'] == 'tap_to_run' || item['type'] == 'scene'
? Image.memory( ? Image.memory(
base64Decode(item['icon']), base64Decode(item['icon']),
width: 22, width: 22,
@ -150,13 +150,13 @@ class SaveRoutineHelper {
onCancel: () => Navigator.pop(context), onCancel: () => Navigator.pop(context),
onConfirm: () async { onConfirm: () async {
if (state.isAutomation) { if (state.isAutomation) {
if (state.automationId != null) { if (state.isUpdate ?? false) {
context.read<RoutineBloc>().add(const UpdateAutomation()); context.read<RoutineBloc>().add(const UpdateAutomation());
} else { } else {
context.read<RoutineBloc>().add(const CreateAutomationEvent()); context.read<RoutineBloc>().add(const CreateAutomationEvent());
} }
} else { } else {
if (state.sceneId != null) { if (state.isUpdate ?? false) {
context.read<RoutineBloc>().add(const UpdateScene()); context.read<RoutineBloc>().add(const UpdateScene());
} else { } else {
context.read<RoutineBloc>().add(const CreateSceneEvent()); context.read<RoutineBloc>().add(const CreateSceneEvent());

View File

@ -48,8 +48,7 @@ class RoutineDetailsModel {
spaceUuid: spaceUuid, spaceUuid: spaceUuid,
automationName: name, automationName: name,
decisionExpr: decisionExpr, decisionExpr: decisionExpr,
effectiveTime: effectiveTime: effectiveTime ?? EffectiveTime(start: '', end: '', loops: ''),
effectiveTime ?? EffectiveTime(start: '', end: '', loops: ''),
conditions: conditions?.map((c) => c.toCondition()).toList() ?? [], conditions: conditions?.map((c) => c.toCondition()).toList() ?? [],
actions: actions.map((a) => a.toAutomationAction()).toList(), actions: actions.map((a) => a.toAutomationAction()).toList(),
); );
@ -64,8 +63,7 @@ class RoutineDetailsModel {
if (iconId != null) 'iconUuid': iconId, if (iconId != null) 'iconUuid': iconId,
if (showInDevice != null) 'showInDevice': showInDevice, if (showInDevice != null) 'showInDevice': showInDevice,
if (effectiveTime != null) 'effectiveTime': effectiveTime!.toMap(), if (effectiveTime != null) 'effectiveTime': effectiveTime!.toMap(),
if (conditions != null) if (conditions != null) 'conditions': conditions!.map((x) => x.toMap()).toList(),
'conditions': conditions!.map((x) => x.toMap()).toList(),
if (type != null) 'type': type, if (type != null) 'type': type,
if (sceneId != null) 'sceneId': sceneId, if (sceneId != null) 'sceneId': sceneId,
if (automationId != null) 'automationId': automationId, if (automationId != null) 'automationId': automationId,
@ -82,12 +80,10 @@ class RoutineDetailsModel {
), ),
iconId: map['iconUuid'], iconId: map['iconUuid'],
showInDevice: map['showInDevice'], showInDevice: map['showInDevice'],
effectiveTime: map['effectiveTime'] != null effectiveTime:
? EffectiveTime.fromMap(map['effectiveTime']) map['effectiveTime'] != null ? EffectiveTime.fromMap(map['effectiveTime']) : null,
: null,
conditions: map['conditions'] != null conditions: map['conditions'] != null
? List<RoutineCondition>.from( ? List<RoutineCondition>.from(map['conditions'].map((x) => RoutineCondition.fromMap(x)))
map['conditions'].map((x) => RoutineCondition.fromMap(x)))
: null, : null,
type: map['type'], type: map['type'],
sceneId: map['sceneId'], sceneId: map['sceneId'],
@ -108,15 +104,16 @@ class RoutineAction {
final RoutineExecutorProperty? executorProperty; final RoutineExecutorProperty? executorProperty;
final String productType; final String productType;
final String? type; final String? type;
final String? icon;
RoutineAction({ RoutineAction(
required this.entityId, {required this.entityId,
required this.actionExecutor, required this.actionExecutor,
required this.productType, required this.productType,
this.executorProperty, this.executorProperty,
this.name, this.name,
this.type, this.type,
}); this.icon});
CreateSceneAction toCreateSceneAction() { CreateSceneAction toCreateSceneAction() {
return CreateSceneAction( return CreateSceneAction(
@ -140,22 +137,21 @@ class RoutineAction {
'actionExecutor': actionExecutor, 'actionExecutor': actionExecutor,
if (type != null) 'type': type, if (type != null) 'type': type,
if (name != null) 'name': name, if (name != null) 'name': name,
if (executorProperty != null) if (executorProperty != null) 'executorProperty': executorProperty!.toMap(),
'executorProperty': executorProperty!.toMap(),
}; };
} }
factory RoutineAction.fromMap(Map<String, dynamic> map) { factory RoutineAction.fromMap(Map<String, dynamic> map) {
return RoutineAction( return RoutineAction(
entityId: map['entityId'] ?? '', entityId: map['entityId'] ?? '',
actionExecutor: map['actionExecutor'] ?? '', actionExecutor: map['actionExecutor'] ?? '',
productType: map['productType'] ?? '', productType: map['productType'] ?? '',
name: map['name'] ?? '', name: map['name'] ?? '',
type: map['type'] ?? '', type: map['type'] ?? '',
executorProperty: map['executorProperty'] != null executorProperty: map['executorProperty'] != null
? RoutineExecutorProperty.fromMap(map['executorProperty']) ? RoutineExecutorProperty.fromMap(map['executorProperty'])
: null, : null,
); icon: map['icon']);
} }
} }

View File

@ -32,18 +32,17 @@ class DraggableCard extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>( return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) { builder: (context, state) {
final deviceFunctions = final deviceFunctions = state.selectedFunctions[deviceData['uniqueCustomId']] ?? [];
state.selectedFunctions[deviceData['uniqueCustomId']] ?? [];
int index = state.thenItems.indexWhere( int index = state.thenItems
(item) => item['uniqueCustomId'] == deviceData['uniqueCustomId']); .indexWhere((item) => item['uniqueCustomId'] == deviceData['uniqueCustomId']);
if (index != -1) { if (index != -1) {
return _buildCardContent(context, deviceFunctions, padding: padding); return _buildCardContent(context, deviceFunctions, padding: padding);
} }
int ifIndex = state.ifItems.indexWhere( int ifIndex = state.ifItems
(item) => item['uniqueCustomId'] == deviceData['uniqueCustomId']); .indexWhere((item) => item['uniqueCustomId'] == deviceData['uniqueCustomId']);
if (ifIndex != -1) { if (ifIndex != -1) {
return _buildCardContent(context, deviceFunctions, padding: padding); return _buildCardContent(context, deviceFunctions, padding: padding);
@ -53,8 +52,7 @@ class DraggableCard extends StatelessWidget {
data: deviceData, data: deviceData,
feedback: Transform.rotate( feedback: Transform.rotate(
angle: -0.1, angle: -0.1,
child: child: _buildCardContent(context, deviceFunctions, padding: padding),
_buildCardContent(context, deviceFunctions, padding: padding),
), ),
childWhenDragging: _buildGreyContainer(), childWhenDragging: _buildGreyContainer(),
child: _buildCardContent(context, deviceFunctions, padding: padding), child: _buildCardContent(context, deviceFunctions, padding: padding),
@ -63,8 +61,7 @@ class DraggableCard extends StatelessWidget {
); );
} }
Widget _buildCardContent( Widget _buildCardContent(BuildContext context, List<DeviceFunctionData> deviceFunctions,
BuildContext context, List<DeviceFunctionData> deviceFunctions,
{EdgeInsetsGeometry? padding}) { {EdgeInsetsGeometry? padding}) {
return Stack( return Stack(
children: [ children: [
@ -93,7 +90,7 @@ class DraggableCard extends StatelessWidget {
), ),
), ),
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: deviceData['type'] == 'tap_to_run' child: deviceData['type'] == 'tap_to_run' || deviceData['type'] == 'scene'
? Image.memory( ? Image.memory(
base64Decode(deviceData['icon']), base64Decode(deviceData['icon']),
) )
@ -162,8 +159,7 @@ class DraggableCard extends StatelessWidget {
} }
String _formatFunctionValue(DeviceFunctionData function) { String _formatFunctionValue(DeviceFunctionData function) {
if (function.functionCode == 'temp_set' || if (function.functionCode == 'temp_set' || function.functionCode == 'temp_current') {
function.functionCode == 'temp_current') {
return '${(function.value / 10).toStringAsFixed(0)}°C'; return '${(function.value / 10).toStringAsFixed(0)}°C';
} else if (function.functionCode.contains('countdown')) { } else if (function.functionCode.contains('countdown')) {
final seconds = function.value.toInt(); final seconds = function.value.toInt();

View File

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

View File

@ -1,9 +1,9 @@
import 'package:flutter_bloc/flutter_bloc.dart'; 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/all_spaces/model/community_model.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/pages/spaces_management/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/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/bloc/space_management_state.dart';
import 'package:syncrow_web/services/product_api.dart'; import 'package:syncrow_web/services/product_api.dart';
import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/services/space_mana_api.dart';

View File

@ -1,7 +1,7 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.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/space_model.dart'; // Import for Offset
abstract class SpaceManagementEvent extends Equatable { abstract class SpaceManagementEvent extends Equatable {
const SpaceManagementEvent(); const SpaceManagementEvent();

View File

@ -1,7 +1,7 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.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/pages/spaces_management/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
abstract class SpaceManagementState extends Equatable { abstract class SpaceManagementState extends Equatable {
const SpaceManagementState(); const SpaceManagementState();

View File

@ -1,5 +1,5 @@
import 'package:syncrow_web/pages/auth/model/region_model.dart'; 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 { class CommunityModel {
final String uuid; 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 { class Connection {
final SpaceModel startSpace; final SpaceModel startSpace;

View File

@ -1,7 +1,7 @@
import 'dart:ui'; import 'dart:ui';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.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/pages/spaces_management/model/selected_product_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:syncrow_web/utils/constants/assets.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';

View File

@ -1,13 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.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/all_spaces/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/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/bloc/space_management_state.dart';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.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/pages/spaces_management/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/loaded_space_widget.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/product_api.dart';
import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/services/space_mana_api.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart';

View File

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

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/dialogs/create_community_dialog.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/color_manager.dart';
class BlankCommunityWidget extends StatefulWidget { class BlankCommunityWidget extends StatefulWidget {
@ -72,7 +72,8 @@ class _BlankCommunityWidgetState extends State<BlankCommunityWidget> {
showDialog( showDialog(
context: parentContext, context: parentContext,
builder: (context) => CreateCommunityDialog( builder: (context) => CreateCommunityDialog(
communities: widget.communities, isEditMode: false,
existingCommunityNames: widget.communities.map((community) => community.name).toList(),
onCreateCommunity: (String communityName, String description) { onCreateCommunity: (String communityName, String description) {
parentContext.read<SpaceManagementBloc>().add( parentContext.read<SpaceManagementBloc>().add(
CreateCommunityEvent( CreateCommunityEvent(
@ -84,4 +85,5 @@ class _BlankCommunityWidgetState extends State<BlankCommunityWidget> {
), ),
); );
} }
} }

View File

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

View File

@ -4,19 +4,19 @@ import 'package:flutter_bloc/flutter_bloc.dart';
// Syncrow project imports // Syncrow project imports
import 'package:syncrow_web/pages/common/buttons/add_space_button.dart'; 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/all_spaces/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.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/pages/spaces_management/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_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';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.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/pages/spaces_management/widgets/blank_community_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/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/all_spaces/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/all_spaces/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/all_spaces/widgets/curved_line_painter.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/space_card_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/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/widgets/space_container_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class CommunityStructureArea extends StatefulWidget { class CommunityStructureArea extends StatefulWidget {
@ -119,7 +119,9 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
CommunityStructureHeader( CommunityStructureHeader(
communities: widget.communities,
communityName: widget.selectedCommunity?.name, communityName: widget.selectedCommunity?.name,
community: widget.selectedCommunity,
isSave: isSave(spaces), isSave: isSave(spaces),
isEditingName: isEditingName, isEditingName: isEditingName,
nameController: _nameController, nameController: _nameController,

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; 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'; import 'package:syncrow_web/utils/color_manager.dart';
class CurvedLinePainter extends CustomPainter { class CurvedLinePainter extends CustomPainter {

View File

@ -2,12 +2,12 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.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/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_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/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_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';
import 'package:syncrow_web/pages/spaces_management/widgets/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/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/all_spaces/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/widgets/hoverable_button.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/constants/space_icon_const.dart'; import 'package:syncrow_web/utils/constants/space_icon_const.dart';

View File

@ -1,10 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.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/pages/spaces_management/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/community_structure_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/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/all_spaces/widgets/gradient_canvas_border_widget.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/sidebar_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart';
class LoadedSpaceView extends StatefulWidget { class LoadedSpaceView extends StatefulWidget {
final List<CommunityModel> communities; final List<CommunityModel> communities;

View File

@ -2,12 +2,12 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/common/search_bar.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/all_spaces/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_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';
import 'package:syncrow_web/pages/spaces_management/widgets/community_tile.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_tile.dart';
import 'package:syncrow_web/pages/spaces_management/widgets/space_tile_widget.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/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart'; import 'package:syncrow_web/utils/style.dart';

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,177 +0,0 @@
import 'package:flutter/material.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/community_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CreateCommunityDialog extends StatefulWidget {
final Function(String name, String description) onCreateCommunity;
final List<CommunityModel> communities;
const CreateCommunityDialog(
{super.key, required this.onCreateCommunity, required this.communities});
@override
CreateCommunityDialogState createState() => CreateCommunityDialogState();
}
class CreateCommunityDialogState extends State<CreateCommunityDialog> {
String enteredName = '';
bool isNameFieldExist = false;
bool isNameEmpty = false;
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
backgroundColor:
ColorsManager.transparentColor, // Transparent for shadow effect
child: Stack(
children: [
// Background container with shadow and rounded corners
Container(
width: screenWidth * 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: 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) {
setState(() {
enteredName = value.trim();
isNameFieldExist = widget.communities.any(
(community) => community.name == enteredName,
);
if (value.isEmpty) {
isNameEmpty = true;
} else {
isNameEmpty = false;
}
});
},
style: const TextStyle(
color: ColorsManager.blackColor,
),
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: BorderSide(
color: isNameFieldExist || isNameEmpty
? ColorsManager.red
: ColorsManager.boxColor,
width: 1),
borderRadius: BorderRadius.circular(10),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: isNameFieldExist || isNameEmpty
? ColorsManager.red
: ColorsManager.boxColor, width: 1),
borderRadius: BorderRadius.circular(10),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(
color: isNameFieldExist || isNameEmpty
? ColorsManager.red
: ColorsManager.boxColor,
width: 1),
),
),
),
if (isNameFieldExist)
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),
// Action buttons
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: CancelButton(
label: 'Cancel',
onPressed: () => Navigator.of(context).pop(),
),
),
const SizedBox(width: 16),
Expanded(
child: DefaultButton(
onPressed: () {
if (enteredName.isNotEmpty && !isNameFieldExist) {
widget.onCreateCommunity(
enteredName,
"",
);
Navigator.of(context).pop();
}
},
backgroundColor: isNameFieldExist || isNameEmpty
? ColorsManager.lightGrayColor
: ColorsManager.secondaryColor,
borderRadius: 10,
foregroundColor: ColorsManager.whiteColors,
child: const Text('OK'),
),
),
],
),
],
),
),
],
),
);
}
}

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; 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/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.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/pages/routiens/models/routine_model.dart';
import 'package:syncrow_web/services/api/http_service.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/api_const.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
class SceneApi { class SceneApi {
static final HTTPService _httpService = HTTPService(); static final HTTPService _httpService = HTTPService();
@ -76,7 +77,8 @@ class SceneApi {
final response = await _httpService.get( final response = await _httpService.get(
path: ApiEndpoints.getUnitScenes path: ApiEndpoints.getUnitScenes
.replaceAll('{spaceUuid}', unitId) .replaceAll('{spaceUuid}', unitId)
.replaceAll('{communityUuid}', communityId), .replaceAll('{communityUuid}', communityId)
.replaceAll('{projectId}', TempConst.projectId),
queryParameters: {'showInHomePage': showInDevice}, queryParameters: {'showInHomePage': showInDevice},
showServerMessage: false, showServerMessage: false,
expectedResponseModel: (json) { expectedResponseModel: (json) {

View File

@ -1,10 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_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';
import 'package:syncrow_web/pages/spaces_management/model/space_response_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/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart'; import 'package:syncrow_web/utils/constants/api_const.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
class CommunitySpaceManagementApi { class CommunitySpaceManagementApi {
// Community Management APIs // Community Management APIs
@ -15,7 +16,8 @@ class CommunitySpaceManagementApi {
while (hasNext) { while (hasNext) {
await HTTPService().get( await HTTPService().get(
path: ApiEndpoints.getCommunityList, path: ApiEndpoints.getCommunityList
.replaceAll('{projectId}', TempConst.projectId),
queryParameters: {'page': page}, queryParameters: {'page': page},
expectedResponseModel: (json) { expectedResponseModel: (json) {
List<dynamic> jsonData = json['data']; List<dynamic> jsonData = json['data'];
@ -59,7 +61,8 @@ class CommunitySpaceManagementApi {
String name, String description) async { String name, String description) async {
try { try {
final response = await HTTPService().post( final response = await HTTPService().post(
path: ApiEndpoints.createCommunity, path: ApiEndpoints.createCommunity
.replaceAll('{projectId}', TempConst.projectId),
body: { body: {
'name': name, 'name': name,
'description': description, 'description': description,
@ -79,7 +82,8 @@ class CommunitySpaceManagementApi {
try { try {
final response = await HTTPService().put( final response = await HTTPService().put(
path: ApiEndpoints.updateCommunity path: ApiEndpoints.updateCommunity
.replaceAll('{communityId}', communityId), .replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', TempConst.projectId),
body: { body: {
'name': name, 'name': name,
}, },
@ -98,7 +102,8 @@ class CommunitySpaceManagementApi {
try { try {
final response = await HTTPService().delete( final response = await HTTPService().delete(
path: ApiEndpoints.deleteCommunity path: ApiEndpoints.deleteCommunity
.replaceAll('{communityId}', communityId), .replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', TempConst.projectId),
expectedResponseModel: (json) { expectedResponseModel: (json) {
return json['success'] ?? false; return json['success'] ?? false;
}, },
@ -113,7 +118,9 @@ class CommunitySpaceManagementApi {
Future<SpacesResponse> fetchSpaces(String communityId) async { Future<SpacesResponse> fetchSpaces(String communityId) async {
try { try {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.listSpaces.replaceAll('{communityId}', communityId), path: ApiEndpoints.listSpaces
.replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', TempConst.projectId),
expectedResponseModel: (json) { expectedResponseModel: (json) {
return SpacesResponse.fromJson(json); return SpacesResponse.fromJson(json);
}, },
@ -139,7 +146,8 @@ class CommunitySpaceManagementApi {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.getSpace path: ApiEndpoints.getSpace
.replaceAll('{communityId}', communityId) .replaceAll('{communityId}', communityId)
.replaceAll('{spaceId}', spaceId), .replaceAll('{spaceId}', spaceId)
.replaceAll('{projectId}', TempConst.projectId),
expectedResponseModel: (json) { expectedResponseModel: (json) {
return SpaceModel.fromJson(json); return SpaceModel.fromJson(json);
}, },
@ -175,7 +183,9 @@ class CommunitySpaceManagementApi {
body['parentUuid'] = parentId; body['parentUuid'] = parentId;
} }
final response = await HTTPService().post( final response = await HTTPService().post(
path: ApiEndpoints.createSpace.replaceAll('{communityId}', communityId), path: ApiEndpoints.createSpace
.replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', TempConst.projectId),
body: body, body: body,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return SpaceModel.fromJson(json['data']); return SpaceModel.fromJson(json['data']);
@ -216,7 +226,8 @@ class CommunitySpaceManagementApi {
final response = await HTTPService().put( final response = await HTTPService().put(
path: ApiEndpoints.updateSpace path: ApiEndpoints.updateSpace
.replaceAll('{communityId}', communityId) .replaceAll('{communityId}', communityId)
.replaceAll('{spaceId}', spaceId), .replaceAll('{spaceId}', spaceId)
.replaceAll('{projectId}', TempConst.projectId),
body: body, body: body,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return SpaceModel.fromJson(json['data']); return SpaceModel.fromJson(json['data']);
@ -234,7 +245,8 @@ class CommunitySpaceManagementApi {
final response = await HTTPService().delete( final response = await HTTPService().delete(
path: ApiEndpoints.deleteSpace path: ApiEndpoints.deleteSpace
.replaceAll('{communityId}', communityId) .replaceAll('{communityId}', communityId)
.replaceAll('{spaceId}', spaceId), .replaceAll('{spaceId}', spaceId)
.replaceAll('{projectId}', TempConst.projectId),
expectedResponseModel: (json) { expectedResponseModel: (json) {
return json['success'] ?? false; return json['success'] ?? false;
}, },
@ -250,7 +262,8 @@ class CommunitySpaceManagementApi {
try { try {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.getSpaceHierarchy path: ApiEndpoints.getSpaceHierarchy
.replaceAll('{communityId}', communityId), .replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', TempConst.projectId),
expectedResponseModel: (json) { expectedResponseModel: (json) {
final spaceModels = (json['data'] as List) final spaceModels = (json['data'] as List)
.map((spaceJson) => SpaceModel.fromJson(spaceJson)) .map((spaceJson) => SpaceModel.fromJson(spaceJson))

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/auth/view/login_page.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/view/device_managment_page.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/view/device_managment_page.dart';
import 'package:syncrow_web/pages/home/view/home_page.dart'; import 'package:syncrow_web/pages/home/view/home_page.dart';
import 'package:syncrow_web/pages/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/pages/visitor_password/view/visitor_password_dialog.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart'; import 'package:syncrow_web/utils/constants/routes_const.dart';
@ -32,7 +32,7 @@ class AppRoutes {
), ),
GoRoute( GoRoute(
path: RoutesConst.spacesManagementPage, 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}'; static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}';
// Space Module // Space Module
static const String createSpace = '/communities/{communityId}/spaces'; static const String createSpace = '/projects/{projectId}/communities/{communityId}/spaces';
static const String listSpaces = '/communities/{communityId}/spaces'; static const String listSpaces = '/projects/{projectId}/communities/{communityId}/spaces';
static const String deleteSpace = '/communities/{communityId}/spaces/{spaceId}'; static const String deleteSpace = '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}';
static const String updateSpace = '/communities/{communityId}/spaces/{spaceId}'; static const String updateSpace = '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}';
static const String getSpace = '/communities/{communityId}/spaces/{spaceId}'; static const String getSpace = '/projects/{projectId}/communities/{communityId}/spaces/{spaceId}';
static const String getSpaceHierarchy = '/communities/{communityId}/spaces'; static const String getSpaceHierarchy = '/projects/{projectId}/communities/{communityId}/spaces';
// Community Module // Community Module
static const String createCommunity = '/communities'; static const String createCommunity = '/projects/{projectId}/communities';
static const String getCommunityList = '/communities'; static const String getCommunityList = '/projects/{projectId}/communities';
static const String getCommunityById = '/communities/{communityId}'; static const String getCommunityById = '/projects/{projectId}/communities/{communityId}';
static const String updateCommunity = '/communities/{communityId}'; static const String updateCommunity = '/projects/{projectId}/communities/{communityId}';
static const String deleteCommunity = '/communities/{communityId}'; static const String deleteCommunity = '/projects/{projectId}communities/{communityId}';
static const String getUserCommunities = '/communities/user/{userUuid}'; static const String getUserCommunities = '/projects/{projectId}/communities/user/{userUuid}';
static const String createUserCommunity = '/communities/user'; static const String createUserCommunity = '/projects/{projectId}/communities/user';
static const String getDeviceLogsByDate = static const String getDeviceLogsByDate =
'/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}'; '/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}';
@ -68,7 +68,7 @@ abstract class ApiEndpoints {
static const String getIconScene = '/scene/icon'; static const String getIconScene = '/scene/icon';
static const String createScene = '/scene/tap-to-run'; static const String createScene = '/scene/tap-to-run';
static const String createAutomation = '/automation'; 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 getAutomationDetails = '/automation/details/{automationId}';
static const String getScene = '/scene/tap-to-run/{sceneId}'; static const String getScene = '/scene/tap-to-run/{sceneId}';
static const String deleteScene = '/scene/tap-to-run/{sceneId}'; static const String deleteScene = '/scene/tap-to-run/{sceneId}';

View File

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