mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 15:17:31 +00:00
Compare commits
66 Commits
fix-device
...
upgrade-pr
Author | SHA1 | Date | |
---|---|---|---|
7109f3712a | |||
88480142e1 | |||
e867c29086 | |||
a3b427c570 | |||
0b0e235f26 | |||
c250fb4469 | |||
59ac1bd74d | |||
bac1450c2b | |||
889461db7d | |||
27dbcb26f1 | |||
0c5db9dfeb | |||
1393a15eca | |||
3c6f88b245 | |||
0b92abff26 | |||
fc86042af7 | |||
cd6bf32aed | |||
e81b9a853e | |||
f415aa1676 | |||
08f8c3c79a | |||
329a4ef027 | |||
940b179686 | |||
5ddfb47977 | |||
2a5d602e94 | |||
5d6747056e | |||
8a274af7be | |||
316c3bd8a7 | |||
d66921c615 | |||
aa3b79bdaf | |||
0e31a3ea96 | |||
fd192894cd | |||
692c9e7792 | |||
08a9a5c71f | |||
7eb1d5b0b0 | |||
0d5734a236 | |||
a1b20078a3 | |||
ed06a760d2 | |||
e22bab00d9 | |||
d2a2d391e0 | |||
1d30c753f5 | |||
ca02de2093 | |||
8f7bfa984b | |||
8e9278c93c | |||
15d3a05553 | |||
662fe211eb | |||
a1d7457065 | |||
c99b32fb81 | |||
321df401fd | |||
ee244fa5ed | |||
1db069e9a5 | |||
cf9bafef4d | |||
2c73dd6c31 | |||
6ec20e2d72 | |||
4feae9ad87 | |||
52046909d5 | |||
fc81555be3 | |||
8967852ca8 | |||
a87e79878b | |||
056e7372e0 | |||
d69d867120 | |||
644fe56478 | |||
766a39f161 | |||
c97dd40b05 | |||
0b65c58947 | |||
e0951aa13d | |||
9e8ebf3768 | |||
b593e75c67 |
2
.github/workflows/pr-check.yml
vendored
2
.github/workflows/pr-check.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
||||
- name: Set up Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: '3.27.3'
|
||||
flutter-version: '3.32.1'
|
||||
|
||||
- name: Install dependencies
|
||||
run: flutter pub get
|
||||
|
@ -1,33 +1,27 @@
|
||||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
include: package:very_good_analysis/analysis_options.yaml
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
analyzer:
|
||||
errors:
|
||||
constant_identifier_names: ignore
|
||||
overridden_fields: ignore
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
strict_raw_type: warning
|
||||
argument_type_not_assignable: warning
|
||||
invalid_assignment: warning
|
||||
return_of_invalid_type: warning
|
||||
return_of_invalid_type_from_closure: warning
|
||||
list_element_type_not_assignable: warning
|
||||
for_in_of_invalid_type: warning
|
||||
cast_nullable_to_non_nullable: warning
|
||||
non_bool_condition: warning
|
||||
field_initializer_not_assignable: warning
|
||||
non_bool_negation_expression: warning
|
||||
non_bool_operand: warning
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at https://dart.dev/lints.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
prefer_const_constructors: true
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
prefer_single_quotes: true
|
||||
avoid_print: false
|
||||
public_member_api_docs: false
|
||||
sort_pub_dependencies: false
|
||||
one_member_abstracts: false
|
||||
prefer_int_literals: false
|
||||
sort_constructors_first: false
|
||||
avoid_redundant_argument_values: false
|
||||
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
@ -17,7 +17,8 @@ class TagDialogTextfieldDropdown extends StatefulWidget {
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_DialogTextfieldDropdownState createState() => _DialogTextfieldDropdownState();
|
||||
_DialogTextfieldDropdownState createState() =>
|
||||
_DialogTextfieldDropdownState();
|
||||
}
|
||||
|
||||
class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
|
||||
@ -36,6 +37,12 @@ class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
|
||||
|
||||
_focusNode.addListener(() {
|
||||
if (!_focusNode.hasFocus) {
|
||||
// Call onSelected when focus is lost
|
||||
final selectedTag = _filteredItems.firstWhere(
|
||||
(tag) => tag.tag == _controller.text,
|
||||
orElse: () => Tag(tag: _controller.text),
|
||||
);
|
||||
widget.onSelected(selectedTag);
|
||||
_closeDropdown();
|
||||
}
|
||||
});
|
||||
@ -43,7 +50,9 @@ class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
|
||||
|
||||
void _filterItems() {
|
||||
setState(() {
|
||||
_filteredItems = widget.items.where((tag) => tag.product?.uuid == widget.product).toList();
|
||||
_filteredItems = widget.items;
|
||||
// .where((tag) => tag.product?.uuid == widget.product)
|
||||
// .toList();
|
||||
});
|
||||
}
|
||||
|
||||
@ -112,7 +121,9 @@ class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(color: ColorsManager.textPrimaryColor)),
|
||||
?.copyWith(
|
||||
color: ColorsManager
|
||||
.textPrimaryColor)),
|
||||
onTap: () {
|
||||
_controller.text = tag.tag ?? '';
|
||||
widget.onSelected(tag);
|
||||
@ -156,13 +167,15 @@ class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
|
||||
controller: _controller,
|
||||
focusNode: _focusNode,
|
||||
onFieldSubmitted: (value) {
|
||||
final selectedTag = _filteredItems.firstWhere((tag) => tag.tag == value,
|
||||
final selectedTag = _filteredItems.firstWhere(
|
||||
(tag) => tag.tag == value,
|
||||
orElse: () => Tag(tag: value));
|
||||
widget.onSelected(selectedTag);
|
||||
_closeDropdown();
|
||||
},
|
||||
onTapOutside: (event) {
|
||||
widget.onSelected(_filteredItems.firstWhere((tag) => tag.tag == _controller.text,
|
||||
widget.onSelected(_filteredItems.firstWhere(
|
||||
(tag) => tag.tag == _controller.text,
|
||||
orElse: () => Tag(tag: _controller.text)));
|
||||
_closeDropdown();
|
||||
},
|
||||
|
@ -267,7 +267,8 @@ class AccessBloc extends Bloc<AccessEvent, AccessState> {
|
||||
selectedIndex = 0;
|
||||
effectiveTimeTimeStamp = null;
|
||||
expirationTimeTimeStamp = null;
|
||||
add(FetchTableData());
|
||||
filteredData = List.from(data);
|
||||
emit(TableLoaded(filteredData));
|
||||
}
|
||||
|
||||
String timestampToDate(dynamic timestamp) {
|
||||
|
@ -40,17 +40,18 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: null,
|
||||
),
|
||||
onPressed: () {
|
||||
BlocProvider.of<CreateRoutineBloc>(context)
|
||||
.add(const ResetSelectedEvent());
|
||||
onPressed: !state.routineTab
|
||||
? null
|
||||
: () {
|
||||
BlocProvider.of<CreateRoutineBloc>(context)
|
||||
.add(const ResetSelectedEvent());
|
||||
|
||||
context
|
||||
.read<RoutineBloc>()
|
||||
.add(const TriggerSwitchTabsEvent(isRoutineTab: false));
|
||||
context
|
||||
.read<DeviceManagementBloc>()
|
||||
.add(FetchDevices(context));
|
||||
},
|
||||
context.read<RoutineBloc>().add(
|
||||
const TriggerSwitchTabsEvent(isRoutineTab: false));
|
||||
context
|
||||
.read<DeviceManagementBloc>()
|
||||
.add(FetchDevices(context));
|
||||
},
|
||||
child: Text(
|
||||
'Devices',
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
@ -66,14 +67,15 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: null,
|
||||
),
|
||||
onPressed: () {
|
||||
BlocProvider.of<CreateRoutineBloc>(context)
|
||||
.add(const ResetSelectedEvent());
|
||||
onPressed: state.routineTab
|
||||
? null
|
||||
: () {
|
||||
BlocProvider.of<CreateRoutineBloc>(context)
|
||||
.add(const ResetSelectedEvent());
|
||||
|
||||
context
|
||||
.read<RoutineBloc>()
|
||||
.add(const TriggerSwitchTabsEvent(isRoutineTab: true));
|
||||
},
|
||||
context.read<RoutineBloc>().add(
|
||||
const TriggerSwitchTabsEvent(isRoutineTab: true));
|
||||
},
|
||||
child: Text(
|
||||
'Routines',
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
|
@ -34,7 +34,8 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
|
||||
runSpacing: 10,
|
||||
children: [
|
||||
_buildSearchField("Space Name", _unitNameController, 200),
|
||||
_buildSearchField("Device Name / Product Name", _productNameController, 300),
|
||||
_buildSearchField(
|
||||
"Device Name / Product Name", _productNameController, 300),
|
||||
_buildSearchResetButtons(),
|
||||
],
|
||||
);
|
||||
@ -74,9 +75,7 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
|
||||
onReset: () {
|
||||
_unitNameController.clear();
|
||||
_productNameController.clear();
|
||||
context.read<DeviceManagementBloc>()
|
||||
..add(ResetFilters())
|
||||
..add(FetchDevices(context));
|
||||
context.read<DeviceManagementBloc>().add(ResetFilters());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -379,7 +379,7 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
|
||||
}
|
||||
emit(GarageDoorLoadedState(status: deviceStatus));
|
||||
add(GarageDoorControlEvent(
|
||||
deviceId: event.deviceId,
|
||||
deviceId: deviceId,
|
||||
value: deviceStatus.delay.inSeconds,
|
||||
code: 'countdown_1'));
|
||||
} catch (e) {
|
||||
@ -396,7 +396,7 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
|
||||
_updateLocalValue(event.code, event.value);
|
||||
emit(GarageDoorLoadedState(status: deviceStatus));
|
||||
final success = await _runDeBouncer(
|
||||
deviceId: event.deviceId,
|
||||
deviceId: deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
|
@ -30,12 +30,13 @@ 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);
|
||||
|
||||
if (user != null && user!.project != null) {
|
||||
await ProjectManager.setProjectUUID(user!.project!.uuid);
|
||||
NavigationService.navigatorKey.currentContext!.read<SpaceTreeBloc>().add(InitialEvent());
|
||||
|
||||
}
|
||||
add(FetchTermEvent());
|
||||
add(FetchPolicyEvent());
|
||||
@ -67,10 +68,12 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future _confirmUserAgreement(ConfirmUserAgreementEvent event, Emitter<HomeState> emit) async {
|
||||
Future _confirmUserAgreement(
|
||||
ConfirmUserAgreementEvent event, Emitter<HomeState> emit) async {
|
||||
try {
|
||||
emit(LoadingHome());
|
||||
var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
|
||||
var uuid =
|
||||
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
|
||||
policy = await HomeApi().confirmUserAgreements(uuid);
|
||||
emit(PolicyAgreement());
|
||||
} catch (e) {
|
||||
@ -123,5 +126,41 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
||||
},
|
||||
color: const Color(0xFF023DFE),
|
||||
),
|
||||
|
||||
// HomeItemModel(
|
||||
// title: 'Move in',
|
||||
// icon: Assets.moveinIcon,
|
||||
// active: false,
|
||||
// onPress: (context) {},
|
||||
// color: ColorsManager.primaryColor,
|
||||
// ),
|
||||
// HomeItemModel(
|
||||
// title: 'Construction',
|
||||
// icon: Assets.constructionIcon,
|
||||
// active: false,
|
||||
// onPress: (context) {},
|
||||
// color: ColorsManager.primaryColor,
|
||||
// ),
|
||||
// HomeItemModel(
|
||||
// title: 'Energy',
|
||||
// icon: Assets.energyIcon,
|
||||
// active: false,
|
||||
// onPress: (context) {},
|
||||
// color: ColorsManager.slidingBlueColor.withOpacity(0.2),
|
||||
// ),
|
||||
// HomeItemModel(
|
||||
// title: 'Integrations',
|
||||
// icon: Assets.integrationsIcon,
|
||||
// active: false,
|
||||
// onPress: (context) {},
|
||||
// color: ColorsManager.slidingBlueColor.withOpacity(0.2),
|
||||
// ),
|
||||
// HomeItemModel(
|
||||
// title: 'Asset',
|
||||
// icon: Assets.assetIcon,
|
||||
// active: false,
|
||||
// onPress: (context) {},
|
||||
// color: ColorsManager.slidingBlueColor.withOpacity(0.2),
|
||||
// ),
|
||||
];
|
||||
}
|
||||
|
@ -20,12 +20,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
|
||||
// Flag to track whether the dialog is already shown.
|
||||
bool _dialogShown = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final homeBloc = BlocProvider.of<HomeBloc>(context);
|
||||
homeBloc.add(const FetchUserInfo());
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -38,8 +33,10 @@ class _HomeWebPageState extends State<HomeWebPage> {
|
||||
child: BlocConsumer<HomeBloc, HomeState>(
|
||||
listener: (BuildContext context, state) {
|
||||
if (state is HomeInitial) {
|
||||
if (homeBloc.user!.hasAcceptedWebAgreement == false && !_dialogShown) {
|
||||
_dialogShown = true; // Set the flag to true to indicate the dialog is showing.
|
||||
if (homeBloc.user!.hasAcceptedWebAgreement == false &&
|
||||
!_dialogShown) {
|
||||
_dialogShown =
|
||||
true; // Set the flag to true to indicate the dialog is showing.
|
||||
Future.delayed(const Duration(seconds: 1), () {
|
||||
showDialog(
|
||||
context: context,
|
||||
@ -54,7 +51,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
|
||||
_dialogShown = false;
|
||||
if (v != null) {
|
||||
homeBloc.add(ConfirmUserAgreementEvent());
|
||||
homeBloc.add(const FetchUserInfo());
|
||||
// homeBloc.add(const FetchUserInfo());
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -98,7 +95,8 @@ class _HomeWebPageState extends State<HomeWebPage> {
|
||||
width: size.width * 0.68,
|
||||
child: GridView.builder(
|
||||
itemCount: homeBloc.homeItems.length,
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3, // Adjust as needed.
|
||||
crossAxisSpacing: 20.0,
|
||||
mainAxisSpacing: 20.0,
|
||||
@ -111,7 +109,8 @@ class _HomeWebPageState extends State<HomeWebPage> {
|
||||
active: homeBloc.homeItems[index].active!,
|
||||
name: homeBloc.homeItems[index].title!,
|
||||
img: homeBloc.homeItems[index].icon!,
|
||||
onTap: () => homeBloc.homeItems[index].onPress(context),
|
||||
onTap: () =>
|
||||
homeBloc.homeItems[index].onPress(context),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -1,156 +1,181 @@
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/create_new_routines/dropdown_menu_content.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'space_tree_dropdown_bloc.dart';
|
||||
|
||||
class CommunityDropdown extends StatelessWidget {
|
||||
final String? selectedValue;
|
||||
final List<CommunityModel> communities;
|
||||
final Function(String?) onChanged;
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
class SpaceTreeDropdown extends StatefulWidget {
|
||||
final String? selectedSpaceId;
|
||||
final Function(String?)? onChanged;
|
||||
|
||||
CommunityDropdown({
|
||||
Key? key,
|
||||
required this.selectedValue,
|
||||
required this.onChanged,
|
||||
required this.communities,
|
||||
}) : super(key: key);
|
||||
const SpaceTreeDropdown({
|
||||
super.key,
|
||||
this.selectedSpaceId,
|
||||
this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SpaceTreeDropdown> createState() => _SpaceTreeDropdownState();
|
||||
}
|
||||
|
||||
class _SpaceTreeDropdownState extends State<SpaceTreeDropdown> {
|
||||
late SpaceTreeDropdownBloc _dropdownBloc;
|
||||
final LayerLink _layerLink = LayerLink();
|
||||
OverlayEntry? _overlayEntry;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_dropdownBloc = SpaceTreeDropdownBloc(widget.selectedSpaceId);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_dropdownBloc.close();
|
||||
_removeOverlay();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _removeOverlay() {
|
||||
_overlayEntry?.remove();
|
||||
_overlayEntry = null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Community",
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 13,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: DropdownButton2<String>(
|
||||
underline: const SizedBox(),
|
||||
value: selectedValue,
|
||||
items: communities.map((community) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: community.uuid,
|
||||
child: Text(
|
||||
' ${community.name}',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: onChanged,
|
||||
style: const TextStyle(color: Colors.black),
|
||||
hint: Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
" Please Select",
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
),
|
||||
),
|
||||
customButton: Container(
|
||||
height: 45,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: ColorsManager.textGray, width: 1.0),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: Text(
|
||||
selectedValue != null
|
||||
? " ${communities.firstWhere((element) => element.uuid == selectedValue).name}"
|
||||
: ' Please Select',
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||
color: selectedValue != null
|
||||
? Colors.black
|
||||
: ColorsManager.textGray,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[100],
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(10),
|
||||
bottomRight: Radius.circular(10),
|
||||
return BlocProvider.value(
|
||||
value: _dropdownBloc,
|
||||
child: BlocBuilder<SpaceTreeBloc, SpaceTreeState>(
|
||||
builder: (context, spaceTreeState) {
|
||||
final communities = spaceTreeState.searchQuery.isNotEmpty
|
||||
? spaceTreeState.filteredCommunity
|
||||
: spaceTreeState.communityList;
|
||||
|
||||
return BlocBuilder<SpaceTreeDropdownBloc, SpaceTreeDropdownState>(
|
||||
builder: (context, dropdownState) {
|
||||
final selectedCommunity = _findCommunity(
|
||||
communities,
|
||||
dropdownState.selectedSpaceId,
|
||||
);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Text(
|
||||
"Community",
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 13,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
height: 45,
|
||||
child: const Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
maxHeight: MediaQuery.of(context).size.height * 0.4,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
dropdownSearchData: DropdownSearchData(
|
||||
searchController: _searchController,
|
||||
searchInnerWidgetHeight: 50,
|
||||
searchInnerWidget: Container(
|
||||
height: 50,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: TextFormField(
|
||||
style: const TextStyle(color: Colors.black),
|
||||
controller: _searchController,
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 12,
|
||||
),
|
||||
hintText: 'Search for community...',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
CompositedTransformTarget(
|
||||
link: _layerLink,
|
||||
child: GestureDetector(
|
||||
onTap: () => _toggleDropdown(context, communities),
|
||||
child: Container(
|
||||
height: 46,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Text(
|
||||
selectedCommunity?.name ?? 'Please Select',
|
||||
style: TextStyle(
|
||||
color: selectedCommunity != null
|
||||
? ColorsManager.blackColor
|
||||
: ColorsManager.textGray,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[200],
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(10),
|
||||
bottomRight: Radius.circular(10),
|
||||
),
|
||||
),
|
||||
height: 45,
|
||||
width: 33,
|
||||
child: const Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
searchMatchFn: (item, searchValue) {
|
||||
final communityName =
|
||||
(item.child as Text).data?.toLowerCase() ?? '';
|
||||
return communityName
|
||||
.contains(searchValue.toLowerCase().trim());
|
||||
},
|
||||
),
|
||||
onMenuStateChange: (isOpen) {
|
||||
if (!isOpen) {
|
||||
_searchController.clear();
|
||||
}
|
||||
},
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
height: 40,
|
||||
),
|
||||
),
|
||||
))
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _toggleDropdown(BuildContext context, List<CommunityModel> communities) {
|
||||
if (_overlayEntry != null) {
|
||||
_removeOverlay();
|
||||
return;
|
||||
}
|
||||
|
||||
_overlayEntry = OverlayEntry(
|
||||
builder: (context) => Positioned(
|
||||
width: 300,
|
||||
child: CompositedTransformFollower(
|
||||
link: _layerLink,
|
||||
showWhenUnlinked: false,
|
||||
offset: const Offset(0, 48),
|
||||
child: Material(
|
||||
elevation: 8,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: DropdownMenuContent(
|
||||
selectedSpaceId: _dropdownBloc.state.selectedSpaceId,
|
||||
onChanged: (id) {
|
||||
if (id != null && mounted) {
|
||||
_dropdownBloc.add(SpaceTreeDropdownSelectEvent(id));
|
||||
widget.onChanged?.call(id);
|
||||
_removeOverlay();
|
||||
}
|
||||
},
|
||||
onClose: _removeOverlay,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Overlay.of(context).insert(_overlayEntry!);
|
||||
}
|
||||
|
||||
CommunityModel? _findCommunity(
|
||||
List<CommunityModel> communities, String? communityId) {
|
||||
if (communityId == null) return null;
|
||||
try {
|
||||
return communities.firstWhere((c) => c.uuid == communityId);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ class CreateNewRoutinesDialog extends StatefulWidget {
|
||||
class _CreateNewRoutinesDialogState extends State<CreateNewRoutinesDialog> {
|
||||
String? _selectedCommunity;
|
||||
String? _selectedSpace;
|
||||
String? _selectedId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -30,7 +31,7 @@ class _CreateNewRoutinesDialogState extends State<CreateNewRoutinesDialog> {
|
||||
final spaces = _bloc.spacesOnlyWithDevices;
|
||||
final isLoadingCommunities = state is CommunitiesLoadingState;
|
||||
final isLoadingSpaces = state is SpaceWithDeviceLoadingState;
|
||||
String spaceHint = 'Select a community first';
|
||||
String spaceHint = 'Please Select';
|
||||
if (_selectedCommunity != null) {
|
||||
if (isLoadingSpaces) {
|
||||
spaceHint = 'Loading spaces...';
|
||||
@ -40,7 +41,10 @@ class _CreateNewRoutinesDialogState extends State<CreateNewRoutinesDialog> {
|
||||
spaceHint = 'Select Space';
|
||||
}
|
||||
}
|
||||
|
||||
if (_selectedId != null && _selectedCommunity != _selectedId) {
|
||||
_selectedSpace = null;
|
||||
_selectedCommunity = _selectedId;
|
||||
}
|
||||
return AlertDialog(
|
||||
backgroundColor: Colors.white,
|
||||
insetPadding: EdgeInsets.zero,
|
||||
@ -51,7 +55,9 @@ class _CreateNewRoutinesDialogState extends State<CreateNewRoutinesDialog> {
|
||||
'Create New Routines',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: ColorsManager.primaryColor,
|
||||
color: ColorsManager.spaceColor,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
content: Stack(
|
||||
@ -60,40 +66,44 @@ class _CreateNewRoutinesDialogState extends State<CreateNewRoutinesDialog> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Divider(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 15, right: 15),
|
||||
child: CommunityDropdown(
|
||||
communities: _bloc.communities..sort(
|
||||
(a, b) => a.name.toLowerCase().compareTo(
|
||||
b.name.toLowerCase(),
|
||||
const SizedBox(height: 20),
|
||||
Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 13, right: 8),
|
||||
child: Column(
|
||||
children: [
|
||||
SpaceTreeDropdown(
|
||||
selectedSpaceId: _selectedId,
|
||||
onChanged: (String? newValue) {
|
||||
setState(() => _selectedId = newValue!);
|
||||
if (_selectedId != null) {
|
||||
_bloc.add(SpaceOnlyWithDevicesEvent(
|
||||
_selectedId!));
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
)),
|
||||
const SizedBox(height: 5),
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 15, right: 15),
|
||||
child: SpaceDropdown(
|
||||
hintMessage: spaceHint,
|
||||
spaces: spaces,
|
||||
selectedValue: _selectedSpace,
|
||||
onChanged: (String? newValue) {
|
||||
setState(() {
|
||||
_selectedSpace = newValue;
|
||||
});
|
||||
},
|
||||
),
|
||||
selectedValue: _selectedCommunity,
|
||||
onChanged: (String? newValue) {
|
||||
setState(() {
|
||||
_selectedCommunity = newValue;
|
||||
_selectedSpace = null;
|
||||
});
|
||||
if (newValue != null) {
|
||||
_bloc.add(SpaceOnlyWithDevicesEvent(newValue));
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 15, right: 15),
|
||||
child: SpaceDropdown(
|
||||
hintMessage: spaceHint,
|
||||
spaces: spaces,
|
||||
selectedValue: _selectedSpace,
|
||||
onChanged: (String? newValue) {
|
||||
setState(() {
|
||||
_selectedSpace = newValue;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Divider(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
|
@ -0,0 +1,148 @@
|
||||
|
||||
|
||||
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart';
|
||||
|
||||
class DropdownMenuContent extends StatefulWidget {
|
||||
final String? selectedSpaceId;
|
||||
final ValueChanged<String?> onChanged;
|
||||
final VoidCallback onClose;
|
||||
|
||||
const DropdownMenuContent({
|
||||
required this.selectedSpaceId,
|
||||
required this.onChanged,
|
||||
required this.onClose,
|
||||
});
|
||||
|
||||
@override
|
||||
State<DropdownMenuContent> createState() => _DropdownMenuContentState();
|
||||
}
|
||||
|
||||
class _DropdownMenuContentState extends State<DropdownMenuContent> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController.addListener(_onScroll);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_searchController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onScroll() {
|
||||
final bloc = context.read<SpaceTreeBloc>();
|
||||
final state = bloc.state;
|
||||
if (_scrollController.position.pixels >=
|
||||
_scrollController.position.maxScrollExtent - 30) {
|
||||
if (state is SpaceTreeState && !state.paginationIsLoading) {
|
||||
bloc.add(PaginationEvent(state.paginationModel, state.communityList));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 300),
|
||||
child: BlocBuilder<SpaceTreeBloc, SpaceTreeState>(
|
||||
builder: (context, state) {
|
||||
final communities = state.searchQuery.isNotEmpty
|
||||
? state.filteredCommunity
|
||||
: state.communityList;
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Search bar
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: TextFormField(
|
||||
controller: _searchController,
|
||||
onChanged: (query) {
|
||||
context.read<SpaceTreeBloc>().add(SearchQueryEvent(query));
|
||||
},
|
||||
style: const TextStyle(fontSize: 14, color: Colors.black),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search for space...',
|
||||
prefixIcon: const Icon(Icons.search, size: 20),
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
isDense: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
// Community list
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
controller: _scrollController,
|
||||
itemCount:
|
||||
communities.length + (state.paginationIsLoading ? 1 : 0),
|
||||
itemBuilder: (context, index) {
|
||||
if (index >= communities.length) {
|
||||
return state.paginationIsLoading
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child:
|
||||
CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final community = communities[index];
|
||||
final isSelected = community.uuid == widget.selectedSpaceId;
|
||||
|
||||
return ListTile(
|
||||
title: Text(
|
||||
community.name,
|
||||
style: TextStyle(
|
||||
color: isSelected ? Colors.blue : Colors.black,
|
||||
fontWeight:
|
||||
isSelected ? FontWeight.bold : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_searchController.clear();
|
||||
_searchController.text.isEmpty
|
||||
? context
|
||||
.read<SpaceTreeBloc>()
|
||||
.add(SearchQueryEvent(''))
|
||||
: context.read<SpaceTreeBloc>().add(
|
||||
SearchQueryEvent(_searchController.text));
|
||||
});
|
||||
// Future.delayed(const Duration(seconds: 1), () {
|
||||
widget.onChanged(community.uuid);
|
||||
widget.onClose();
|
||||
// });
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
|
||||
class SpaceDropdown extends StatelessWidget {
|
||||
final List<SpaceModel> spaces;
|
||||
final String? selectedValue;
|
||||
@ -21,7 +20,7 @@ class SpaceDropdown extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -33,7 +32,6 @@ class SpaceDropdown extends StatelessWidget {
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
@ -90,7 +88,7 @@ class SpaceDropdown extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 5,
|
||||
flex: 6,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
|
@ -0,0 +1,27 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
part 'space_tree_dropdown_event.dart';
|
||||
part 'space_tree_dropdown_state.dart';
|
||||
|
||||
class SpaceTreeDropdownBloc
|
||||
extends Bloc<SpaceTreeDropdownEvent, SpaceTreeDropdownState> {
|
||||
SpaceTreeDropdownBloc(String? initialId)
|
||||
: super(SpaceTreeDropdownState(selectedSpaceId: initialId)) {
|
||||
on<SpaceTreeDropdownSelectEvent>(_onSelect);
|
||||
on<SpaceTreeDropdownResetEvent>(_onReset);
|
||||
}
|
||||
|
||||
void _onSelect(
|
||||
SpaceTreeDropdownSelectEvent event,
|
||||
Emitter<SpaceTreeDropdownState> emit,
|
||||
) {
|
||||
emit(SpaceTreeDropdownState(selectedSpaceId: event.spaceId));
|
||||
}
|
||||
|
||||
void _onReset(
|
||||
SpaceTreeDropdownResetEvent event,
|
||||
Emitter<SpaceTreeDropdownState> emit,
|
||||
) {
|
||||
emit(SpaceTreeDropdownState(selectedSpaceId: event.initialId));
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
part of 'space_tree_dropdown_bloc.dart';
|
||||
|
||||
abstract class SpaceTreeDropdownEvent {}
|
||||
|
||||
class SpaceTreeDropdownSelectEvent extends SpaceTreeDropdownEvent {
|
||||
final String? spaceId;
|
||||
|
||||
SpaceTreeDropdownSelectEvent(this.spaceId);
|
||||
}
|
||||
|
||||
class SpaceTreeDropdownResetEvent extends SpaceTreeDropdownEvent {
|
||||
final String? initialId;
|
||||
|
||||
SpaceTreeDropdownResetEvent(this.initialId);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
part of 'space_tree_dropdown_bloc.dart';
|
||||
|
||||
class SpaceTreeDropdownState {
|
||||
final String? selectedSpaceId;
|
||||
|
||||
SpaceTreeDropdownState({this.selectedSpaceId});
|
||||
}
|
@ -21,7 +21,8 @@ import 'package:syncrow_web/services/space_mana_api.dart';
|
||||
import 'package:syncrow_web/services/space_model_mang_api.dart';
|
||||
import 'package:syncrow_web/utils/constants/action_enum.dart' as custom_action;
|
||||
|
||||
class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementState> {
|
||||
class SpaceManagementBloc
|
||||
extends Bloc<SpaceManagementEvent, SpaceManagementState> {
|
||||
final CommunitySpaceManagementApi _api;
|
||||
final ProductApi _productApi;
|
||||
final SpaceModelManagementApi _spaceModelApi;
|
||||
@ -62,7 +63,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
int page = 1;
|
||||
|
||||
while (hasNext) {
|
||||
final spaceModels = await _spaceModelApi.listSpaceModels(page: page, projectId: projectUuid);
|
||||
final spaceModels = await _spaceModelApi.listSpaceModels(
|
||||
page: page, projectId: projectUuid);
|
||||
if (spaceModels.isNotEmpty) {
|
||||
allSpaceModels.addAll(spaceModels);
|
||||
page++;
|
||||
@ -75,26 +77,29 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
await fetchTags();
|
||||
|
||||
emit(SpaceModelLoaded(
|
||||
communities:
|
||||
state is SpaceManagementLoaded ? (state as SpaceManagementLoaded).communities : [],
|
||||
communities: state is SpaceManagementLoaded
|
||||
? (state as SpaceManagementLoaded).communities
|
||||
: [],
|
||||
products: _cachedProducts ?? [],
|
||||
spaceModels: List.from(_cachedSpaceModels ?? []),
|
||||
allTags: _cachedTags ?? []));
|
||||
}
|
||||
|
||||
void _deleteSpaceModelFromCache(
|
||||
DeleteSpaceModelFromCache event, Emitter<SpaceManagementState> emit) async {
|
||||
void _deleteSpaceModelFromCache(DeleteSpaceModelFromCache event,
|
||||
Emitter<SpaceManagementState> emit) async {
|
||||
if (_cachedSpaceModels != null) {
|
||||
_cachedSpaceModels =
|
||||
_cachedSpaceModels!.where((model) => model.uuid != event.deletedUuid).toList();
|
||||
_cachedSpaceModels = _cachedSpaceModels!
|
||||
.where((model) => model.uuid != event.deletedUuid)
|
||||
.toList();
|
||||
} else {
|
||||
_cachedSpaceModels = await fetchSpaceModels();
|
||||
}
|
||||
await fetchTags();
|
||||
|
||||
emit(SpaceModelLoaded(
|
||||
communities:
|
||||
state is SpaceManagementLoaded ? (state as SpaceManagementLoaded).communities : [],
|
||||
communities: state is SpaceManagementLoaded
|
||||
? (state as SpaceManagementLoaded).communities
|
||||
: [],
|
||||
products: _cachedProducts ?? [],
|
||||
spaceModels: List.from(_cachedSpaceModels ?? []),
|
||||
allTags: _cachedTags ?? []));
|
||||
@ -122,8 +127,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
int page = 1;
|
||||
|
||||
while (hasNext) {
|
||||
final spaceModels =
|
||||
await _spaceModelApi.listSpaceModels(page: page, projectId: projectUuid);
|
||||
final spaceModels = await _spaceModelApi.listSpaceModels(
|
||||
page: page, projectId: projectUuid);
|
||||
if (spaceModels.isNotEmpty) {
|
||||
allSpaceModels.addAll(spaceModels);
|
||||
page++;
|
||||
@ -164,10 +169,12 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
await fetchTags();
|
||||
|
||||
emit(SpaceManagementLoading());
|
||||
final success = await _api.updateCommunity(event.communityUuid, event.name, projectUuid);
|
||||
final success = await _api.updateCommunity(
|
||||
event.communityUuid, event.name, projectUuid);
|
||||
if (success) {
|
||||
if (previousState is SpaceManagementLoaded) {
|
||||
final updatedCommunities = List<CommunityModel>.from(previousState.communities);
|
||||
final updatedCommunities =
|
||||
List<CommunityModel>.from(previousState.communities);
|
||||
for (var community in updatedCommunities) {
|
||||
if (community.uuid == event.communityUuid) {
|
||||
community.name = event.name;
|
||||
@ -212,7 +219,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<SpaceModel>> _fetchSpacesForCommunity(String communityUuid) async {
|
||||
Future<List<SpaceModel>> _fetchSpacesForCommunity(
|
||||
String communityUuid) async {
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
|
||||
return await _api.getSpaceHierarchy(communityUuid, projectUuid);
|
||||
@ -242,20 +250,23 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onBlankState(BlankStateEvent event, Emitter<SpaceManagementState> emit) async {
|
||||
Future<void> _onBlankState(
|
||||
BlankStateEvent event, Emitter<SpaceManagementState> emit) async {
|
||||
try {
|
||||
final previousState = state;
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
var spaceBloc = event.context.read<SpaceTreeBloc>();
|
||||
var spaceTreeState = event.context.read<SpaceTreeBloc>().state;
|
||||
|
||||
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc, spaceTreeState);
|
||||
List<CommunityModel> communities =
|
||||
await _waitForCommunityList(spaceBloc, spaceTreeState);
|
||||
await fetchSpaceModels();
|
||||
await fetchTags();
|
||||
// await fetchTags();
|
||||
|
||||
var prevSpaceModels = await fetchSpaceModels();
|
||||
|
||||
if (previousState is SpaceManagementLoaded || previousState is BlankState) {
|
||||
if (previousState is SpaceManagementLoaded ||
|
||||
previousState is BlankState) {
|
||||
final prevCommunities = (previousState as dynamic).communities ?? [];
|
||||
emit(BlankState(
|
||||
communities: List<CommunityModel>.from(prevCommunities),
|
||||
@ -286,7 +297,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
_onloadProducts();
|
||||
await fetchTags();
|
||||
// Wait until `communityList` is loaded
|
||||
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc, spaceTreeState);
|
||||
List<CommunityModel> communities =
|
||||
await _waitForCommunityList(spaceBloc, spaceTreeState);
|
||||
|
||||
// Fetch space models after communities are available
|
||||
final prevSpaceModels = await fetchSpaceModels();
|
||||
@ -310,8 +322,9 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
final completer = Completer<List<CommunityModel>>();
|
||||
final subscription = spaceBloc.stream.listen((state) {
|
||||
if (!completer.isCompleted && state.communityList.isNotEmpty) {
|
||||
completer
|
||||
.complete(state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList);
|
||||
completer.complete(state.searchQuery.isNotEmpty
|
||||
? state.filteredCommunity
|
||||
: state.communityList);
|
||||
}
|
||||
});
|
||||
try {
|
||||
@ -339,7 +352,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
emit(SpaceManagementLoading());
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
|
||||
final success = await _api.deleteCommunity(event.communityUuid, projectUuid);
|
||||
final success =
|
||||
await _api.deleteCommunity(event.communityUuid, projectUuid);
|
||||
if (success) {
|
||||
// add(LoadCommunityAndSpacesEvent());
|
||||
} else {
|
||||
@ -361,12 +375,13 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
try {
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
await fetchTags();
|
||||
CommunityModel? newCommunity =
|
||||
await _api.createCommunity(event.name, event.description, projectUuid);
|
||||
CommunityModel? newCommunity = await _api.createCommunity(
|
||||
event.name, event.description, projectUuid);
|
||||
var prevSpaceModels = await fetchSpaceModels();
|
||||
|
||||
if (newCommunity != null) {
|
||||
if (previousState is SpaceManagementLoaded || previousState is BlankState) {
|
||||
if (previousState is SpaceManagementLoaded ||
|
||||
previousState is BlankState) {
|
||||
final prevCommunities = List<CommunityModel>.from(
|
||||
(previousState as dynamic).communities,
|
||||
);
|
||||
@ -459,12 +474,15 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
try {
|
||||
final spaceTreeState = event.context.read<SpaceTreeBloc>().state;
|
||||
|
||||
final updatedSpaces =
|
||||
await saveSpacesHierarchically(event.context, event.spaces, event.communityUuid);
|
||||
final updatedSpaces = await saveSpacesHierarchically(
|
||||
event.context, event.spaces, event.communityUuid);
|
||||
final allSpaces = await _fetchSpacesForCommunity(event.communityUuid);
|
||||
|
||||
emit(SpaceCreationSuccess(spaces: updatedSpaces));
|
||||
|
||||
// emit(SpaceCreationSuccess(spaces: updatedSpaces));
|
||||
// updatedSpaces.forEach(
|
||||
// (element) => element.uuid,
|
||||
// );
|
||||
// final lastUpdatedSpaced = updatedSpaces..addAll(allSpaces);
|
||||
if (previousState is SpaceManagementLoaded) {
|
||||
await _updateLoadedState(
|
||||
spaceTreeState,
|
||||
@ -473,9 +491,15 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
event.communityUuid,
|
||||
emit,
|
||||
);
|
||||
event.spaces.removeWhere(
|
||||
(space) => space.status == SpaceStatus.deleted,
|
||||
);
|
||||
event.spaces.forEach(
|
||||
(space) => space.status = SpaceStatus.unchanged,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
emit(SpaceManagementError('Error saving spaces: $e'));
|
||||
// emit(SpaceManagementError('Error saving spaces: $e'));
|
||||
|
||||
if (previousState is SpaceManagementLoaded) {
|
||||
emit(previousState);
|
||||
@ -515,13 +539,15 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
return;
|
||||
}
|
||||
}
|
||||
emit(previousState);
|
||||
} catch (e, stackTrace) {
|
||||
rethrow;
|
||||
emit(previousState);
|
||||
// rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<SpaceModel>> saveSpacesHierarchically(
|
||||
BuildContext context, List<SpaceModel> spaces, String communityUuid) async {
|
||||
Future<List<SpaceModel>> saveSpacesHierarchically(BuildContext context,
|
||||
List<SpaceModel> spaces, String communityUuid) async {
|
||||
final orderedSpaces = flattenHierarchy(spaces);
|
||||
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
@ -534,6 +560,14 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
|
||||
selectedCommunity = filteredCommunities.firstWhere(
|
||||
(community) => community.uuid == communityUuid,
|
||||
orElse: () => CommunityModel(
|
||||
uuid: '',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
name: '',
|
||||
description: '',
|
||||
spaces: spaces,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
return [];
|
||||
@ -548,9 +582,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
if (parent.uuid != null) {
|
||||
await _api.deleteSpace(communityUuid, parent.uuid!, projectUuid);
|
||||
}
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
orderedSpaces.removeWhere((space) => parentsToDelete.contains(space));
|
||||
|
||||
@ -564,7 +596,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
|
||||
if (matchedSpaces.isEmpty) continue;
|
||||
|
||||
final prevSpace = matchedSpaces[0];
|
||||
final prevSpace = matchedSpaces.elementAtOrNull(0);
|
||||
|
||||
final List<UpdateSubspaceTemplateModel> subspaceUpdates = [];
|
||||
final List<SubspaceModel>? prevSubspaces = prevSpace?.subspaces;
|
||||
@ -575,17 +607,19 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
if (prevSubspaces != null || newSubspaces != null) {
|
||||
if (prevSubspaces != null && newSubspaces != null) {
|
||||
for (var prevSubspace in prevSubspaces) {
|
||||
final existsInNew =
|
||||
newSubspaces.any((subspace) => subspace.uuid == prevSubspace.uuid);
|
||||
final existsInNew = newSubspaces
|
||||
.any((subspace) => subspace.uuid == prevSubspace.uuid);
|
||||
if (!existsInNew) {
|
||||
subspaceUpdates.add(UpdateSubspaceTemplateModel(
|
||||
action: custom_action.Action.delete, uuid: prevSubspace.uuid));
|
||||
action: custom_action.Action.delete,
|
||||
uuid: prevSubspace.uuid));
|
||||
}
|
||||
}
|
||||
} else if (prevSubspaces != null && newSubspaces == null) {
|
||||
for (var prevSubspace in prevSubspaces) {
|
||||
subspaceUpdates.add(UpdateSubspaceTemplateModel(
|
||||
action: custom_action.Action.delete, uuid: prevSubspace.uuid));
|
||||
action: custom_action.Action.delete,
|
||||
uuid: prevSubspace.uuid));
|
||||
}
|
||||
}
|
||||
|
||||
@ -613,7 +647,9 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
}
|
||||
|
||||
if (prevSubspaces != null && newSubspaces != null) {
|
||||
final newSubspaceMap = {for (var subspace in newSubspaces) subspace.uuid: subspace};
|
||||
final newSubspaceMap = {
|
||||
for (var subspace in newSubspaces) subspace.uuid: subspace
|
||||
};
|
||||
|
||||
for (var prevSubspace in prevSubspaces) {
|
||||
final newSubspace = newSubspaceMap[prevSubspace.uuid];
|
||||
@ -639,9 +675,10 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
isPrivate: space.isPrivate,
|
||||
position: space.position,
|
||||
icon: space.icon,
|
||||
subspaces: subspaceUpdates,
|
||||
tags: tagUpdates,
|
||||
direction: space.incomingConnection?.direction,
|
||||
subspaces: space.subspaces,
|
||||
// subspaceUpdates,
|
||||
tags: space.tags,
|
||||
// tagUpdates,
|
||||
spaceModelUuid: space.spaceModel?.uuid,
|
||||
projectId: projectUuid);
|
||||
} else {
|
||||
@ -651,8 +688,10 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
: [];
|
||||
|
||||
var createSubspaceBodyModels = space.subspaces?.map((subspace) {
|
||||
final tagBodyModels =
|
||||
subspace.tags?.map((tag) => tag.toCreateTagBodyModel()).toList() ?? [];
|
||||
final tagBodyModels = subspace.tags
|
||||
?.map((tag) => tag.toCreateTagBodyModel())
|
||||
.toList() ??
|
||||
[];
|
||||
return CreateSubspaceModel()
|
||||
..subspaceName = subspace.subspaceName
|
||||
..tags = tagBodyModels;
|
||||
@ -671,7 +710,6 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
isPrivate: space.isPrivate,
|
||||
position: space.position,
|
||||
icon: space.icon,
|
||||
direction: space.incomingConnection?.direction,
|
||||
spaceModelUuid: space.spaceModel?.uuid,
|
||||
tags: tagBodyModels,
|
||||
subspaces: createSubspaceBodyModels,
|
||||
@ -679,7 +717,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
space.uuid = response?.uuid;
|
||||
}
|
||||
} catch (e) {
|
||||
rethrow; // Stop further execution on failure
|
||||
return [];
|
||||
// Stop further execution on failure
|
||||
}
|
||||
}
|
||||
return spaces;
|
||||
@ -710,7 +749,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
return result.toList(); // Convert back to a list
|
||||
}
|
||||
|
||||
void _onLoadSpaceModel(SpaceModelLoadEvent event, Emitter<SpaceManagementState> emit) async {
|
||||
void _onLoadSpaceModel(
|
||||
SpaceModelLoadEvent event, Emitter<SpaceManagementState> emit) async {
|
||||
emit(SpaceManagementLoading());
|
||||
|
||||
try {
|
||||
@ -757,14 +797,17 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
// Case 1: Tags deleted
|
||||
if (prevTags != null && newTags != null) {
|
||||
for (var prevTag in prevTags) {
|
||||
final existsInNew = newTags.any((newTag) => newTag.uuid == prevTag.uuid);
|
||||
final existsInNew =
|
||||
newTags.any((newTag) => newTag.uuid == prevTag.uuid);
|
||||
if (!existsInNew) {
|
||||
tagUpdates.add(TagModelUpdate(action: custom_action.Action.delete, uuid: prevTag.uuid));
|
||||
tagUpdates.add(TagModelUpdate(
|
||||
action: custom_action.Action.delete, uuid: prevTag.uuid));
|
||||
}
|
||||
}
|
||||
} else if (prevTags != null && newTags == null) {
|
||||
for (var prevTag in prevTags) {
|
||||
tagUpdates.add(TagModelUpdate(action: custom_action.Action.delete, uuid: prevTag.uuid));
|
||||
tagUpdates.add(TagModelUpdate(
|
||||
action: custom_action.Action.delete, uuid: prevTag.uuid));
|
||||
}
|
||||
}
|
||||
|
||||
@ -807,15 +850,16 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
||||
return tagUpdates;
|
||||
}
|
||||
|
||||
List<SpaceModel> findMatchingSpaces(List<SpaceModel> spaces, String targetUuid) {
|
||||
List<SpaceModel> findMatchingSpaces(
|
||||
List<SpaceModel> spaces, String targetUuid) {
|
||||
List<SpaceModel> matched = [];
|
||||
|
||||
for (var space in spaces) {
|
||||
if (space.uuid == targetUuid) {
|
||||
matched.add(space);
|
||||
}
|
||||
matched
|
||||
.addAll(findMatchingSpaces(space.children, targetUuid)); // Recursively search in children
|
||||
matched.addAll(findMatchingSpaces(
|
||||
space.children, targetUuid)); // Recursively search in children
|
||||
}
|
||||
|
||||
return matched;
|
||||
|
@ -3,23 +3,26 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model
|
||||
class Connection {
|
||||
final SpaceModel startSpace;
|
||||
final SpaceModel endSpace;
|
||||
final String direction;
|
||||
|
||||
Connection({required this.startSpace, required this.endSpace, required this.direction});
|
||||
Connection({
|
||||
required this.startSpace,
|
||||
required this.endSpace,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'startUuid': startSpace.uuid ?? 'unsaved-start-space-${startSpace.name}', // Fallback for unsaved spaces
|
||||
'endUuid': endSpace.uuid ?? 'unsaved-end-space-${endSpace.name}', // Fallback for unsaved spaces
|
||||
'direction': direction,
|
||||
'startUuid': startSpace.uuid ??
|
||||
'unsaved-start-space-${startSpace.name}', // Fallback for unsaved spaces
|
||||
'endUuid': endSpace.uuid ??
|
||||
'unsaved-end-space-${endSpace.name}', // Fallback for unsaved spaces
|
||||
};
|
||||
}
|
||||
|
||||
static Connection fromMap(Map<String, dynamic> map, Map<String, SpaceModel> spaces) {
|
||||
static Connection fromMap(
|
||||
Map<String, dynamic> map, Map<String, SpaceModel> spaces) {
|
||||
return Connection(
|
||||
startSpace: spaces[map['startUuid']]!,
|
||||
endSpace: spaces[map['endUuid']]!,
|
||||
direction: map['direction'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
import 'selected_product_model.dart';
|
||||
|
||||
class ProductModel {
|
||||
final String uuid;
|
||||
final String catName;
|
||||
@ -38,6 +40,15 @@ class ProductModel {
|
||||
};
|
||||
}
|
||||
|
||||
SelectedProduct toSelectedProduct(int count) {
|
||||
return SelectedProduct(
|
||||
productId: uuid,
|
||||
count: count,
|
||||
productName: name!,
|
||||
product: this,
|
||||
);
|
||||
}
|
||||
|
||||
static String _mapIconToProduct(String prodType) {
|
||||
const iconMapping = {
|
||||
'1G': Assets.Gang1SwitchIcon,
|
||||
|
@ -101,7 +101,7 @@ class SpaceModel {
|
||||
spaceModel: json['spaceModel'] != null
|
||||
? SpaceTemplateModel.fromJson(json['spaceModel'])
|
||||
: null,
|
||||
tags: (json['tags'] as List<dynamic>?)
|
||||
tags: (json['productAllocations'] as List<dynamic>?)
|
||||
?.where((item) => item is Map<String, dynamic>) // Validate type
|
||||
.map((item) => Tag.fromJson(item as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
@ -116,7 +116,6 @@ class SpaceModel {
|
||||
instance.incomingConnection = Connection(
|
||||
startSpace: instance.parent ?? instance, // Parent space
|
||||
endSpace: instance, // This space instance
|
||||
direction: conn['direction'],
|
||||
);
|
||||
}
|
||||
|
||||
@ -172,4 +171,13 @@ extension SpaceExtensions on SpaceModel {
|
||||
|
||||
return tagValues;
|
||||
}
|
||||
|
||||
bool isNoChangesSubmited(String name, icon, SpaceTemplateModel? spaceModel,
|
||||
List<SubspaceModel>? subspaces, List<Tag>? tags) {
|
||||
return (name == this.name &&
|
||||
icon == this.icon &&
|
||||
spaceModel == this.spaceModel &&
|
||||
subspaces == this.subspaces &&
|
||||
tags == this.tags);
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ class SubspaceModel {
|
||||
subspaceName: json['subspaceName'] ?? '',
|
||||
disabled: json['disabled'] ?? false,
|
||||
internalId: internalId,
|
||||
tags: (json['tags'] as List<dynamic>?)
|
||||
tags: (json['productAllocations'] as List<dynamic>?)
|
||||
?.map((item) => Tag.fromJson(item))
|
||||
.toList() ??
|
||||
[],
|
||||
@ -36,7 +36,7 @@ class SubspaceModel {
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'uuid': uuid,
|
||||
if (uuid != null) 'uuid': uuid,
|
||||
'subspaceName': subspaceName,
|
||||
'disabled': disabled,
|
||||
'tags': tags?.map((e) => e.toJson()).toList() ?? [],
|
||||
|
@ -23,10 +23,13 @@ class Tag extends BaseTag {
|
||||
final String internalId = json['internalId'] ?? const Uuid().v4();
|
||||
|
||||
return Tag(
|
||||
uuid: json['uuid'] ?? '',
|
||||
//TODO:insure UUId for tag or prodAlloc
|
||||
uuid: json['name'] != null ? json['uuid'] : json['tag']?['uuid'] ?? '',
|
||||
internalId: internalId,
|
||||
tag: json['name'] ?? '',
|
||||
product: json['product'] != null ? ProductModel.fromMap(json['product']) : null,
|
||||
tag: json['name'] ?? json['tag']?['name'] ?? '',
|
||||
product: json['product'] != null
|
||||
? ProductModel.fromMap(json['product'])
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
@ -49,9 +52,10 @@ class Tag extends BaseTag {
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'uuid': uuid,
|
||||
'tag': tag,
|
||||
'product': product?.toMap(),
|
||||
if (uuid != null) 'uuid': uuid,
|
||||
'name': tag,
|
||||
'productUuid': product?.uuid,
|
||||
// .toMap(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +67,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 ?? '',
|
||||
@ -96,13 +97,15 @@ 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();
|
||||
realignTree();
|
||||
});
|
||||
}
|
||||
|
||||
if (widget.selectedSpace != oldWidget.selectedSpace && widget.selectedSpace != null) {
|
||||
if (widget.selectedSpace != oldWidget.selectedSpace &&
|
||||
widget.selectedSpace != null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_moveToSpace(widget.selectedSpace!);
|
||||
});
|
||||
@ -185,7 +188,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
connection, widget.selectedSpace)
|
||||
? 1.0
|
||||
: 0.3, // Adjust opacity
|
||||
child: CustomPaint(painter: CurvedLinePainter([connection])),
|
||||
child: CustomPaint(
|
||||
painter: CurvedLinePainter([connection])),
|
||||
),
|
||||
for (var entry in spaces.asMap().entries)
|
||||
if (entry.value.status != SpaceStatus.deleted &&
|
||||
@ -195,11 +199,11 @@ 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) {
|
||||
_showCreateSpaceDialog(screenSize,
|
||||
position: spaces[index].position + newPosition,
|
||||
position:
|
||||
spaces[index].position + newPosition,
|
||||
parentIndex: index,
|
||||
direction: direction,
|
||||
projectTags: widget.projectTags);
|
||||
},
|
||||
position: entry.value.position,
|
||||
@ -210,8 +214,9 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
_updateNodePosition(entry.value, newPosition);
|
||||
},
|
||||
buildSpaceContainer: (int index) {
|
||||
final bool isHighlighted = SpaceHelper.isHighlightedSpace(
|
||||
spaces[index], widget.selectedSpace);
|
||||
final bool isHighlighted =
|
||||
SpaceHelper.isHighlightedSpace(
|
||||
spaces[index], widget.selectedSpace);
|
||||
|
||||
return Opacity(
|
||||
opacity: isHighlighted ? 1.0 : 0.3,
|
||||
@ -289,7 +294,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
void _showCreateSpaceDialog(Size screenSize,
|
||||
{Offset? position,
|
||||
int? parentIndex,
|
||||
String? direction,
|
||||
double? canvasWidth,
|
||||
double? canvasHeight,
|
||||
required List<Tag> projectTags}) {
|
||||
@ -299,19 +303,25 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
return CreateSpaceDialog(
|
||||
products: widget.products,
|
||||
spaceModels: widget.spaceModels,
|
||||
allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
|
||||
allTags:
|
||||
TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
|
||||
parentSpace: parentIndex != null ? spaces[parentIndex] : null,
|
||||
projectTags: projectTags,
|
||||
onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts,
|
||||
SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) {
|
||||
onCreateSpace: (String name,
|
||||
String icon,
|
||||
List<SelectedProduct> selectedProducts,
|
||||
SpaceTemplateModel? spaceModel,
|
||||
List<SubspaceModel>? subspaces,
|
||||
List<Tag>? tags) {
|
||||
setState(() {
|
||||
// Set the first space in the center or use passed position
|
||||
Offset newPosition;
|
||||
if (parentIndex != null) {
|
||||
newPosition =
|
||||
getBalancedChildPosition(spaces[parentIndex]); // Ensure balanced position
|
||||
newPosition = getBalancedChildPosition(
|
||||
spaces[parentIndex]); // Ensure balanced position
|
||||
} else {
|
||||
newPosition = position ?? ConnectionHelper.getCenterPosition(screenSize);
|
||||
newPosition =
|
||||
position ?? ConnectionHelper.getCenterPosition(screenSize);
|
||||
}
|
||||
|
||||
SpaceModel newSpace = SpaceModel(
|
||||
@ -325,14 +335,13 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
subspaces: subspaces,
|
||||
tags: tags);
|
||||
|
||||
if (parentIndex != null && direction != null) {
|
||||
if (parentIndex != null) {
|
||||
SpaceModel parentSpace = spaces[parentIndex];
|
||||
parentSpace.internalId = spaces[parentIndex].internalId;
|
||||
newSpace.parent = parentSpace;
|
||||
final newConnection = Connection(
|
||||
startSpace: parentSpace,
|
||||
endSpace: newSpace,
|
||||
direction: direction,
|
||||
);
|
||||
connections.add(newConnection);
|
||||
newSpace.incomingConnection = newConnection;
|
||||
@ -360,16 +369,30 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
name: widget.selectedSpace!.name,
|
||||
icon: widget.selectedSpace!.icon,
|
||||
projectTags: widget.projectTags,
|
||||
parentSpace:
|
||||
SpaceHelper.findSpaceByInternalId(widget.selectedSpace?.parent?.internalId, spaces),
|
||||
parentSpace: SpaceHelper.findSpaceByInternalId(
|
||||
widget.selectedSpace?.parent?.internalId, spaces),
|
||||
editSpace: widget.selectedSpace,
|
||||
currentSpaceModel: widget.selectedSpace?.spaceModel,
|
||||
tags: widget.selectedSpace?.tags,
|
||||
subspaces: widget.selectedSpace?.subspaces,
|
||||
isEdit: true,
|
||||
allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
|
||||
onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts,
|
||||
SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) {
|
||||
allTags: TagHelper.getAllTagValues(
|
||||
widget.communities, widget.spaceModels),
|
||||
onCreateSpace: (String name,
|
||||
String icon,
|
||||
List<SelectedProduct> selectedProducts,
|
||||
SpaceTemplateModel? spaceModel,
|
||||
List<SubspaceModel>? subspaces,
|
||||
List<Tag>? tags) {
|
||||
if (widget.selectedSpace!.isNoChangesSubmited(
|
||||
name,
|
||||
icon,
|
||||
spaceModel,
|
||||
subspaces,
|
||||
tags,
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
// Update the space's properties
|
||||
widget.selectedSpace!.name = name;
|
||||
@ -379,7 +402,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
widget.selectedSpace!.tags = tags;
|
||||
|
||||
if (widget.selectedSpace!.status != SpaceStatus.newSpace) {
|
||||
widget.selectedSpace!.status = SpaceStatus.modified; // Mark as modified
|
||||
widget.selectedSpace!.status =
|
||||
SpaceStatus.modified; // Mark as modified
|
||||
}
|
||||
|
||||
for (var space in spaces) {
|
||||
@ -410,7 +434,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
Map<String, SpaceModel> idToSpace = {};
|
||||
|
||||
void flatten(SpaceModel space) {
|
||||
if (space.status == SpaceStatus.deleted || space.status == SpaceStatus.parentDeleted) {
|
||||
if (space.status == SpaceStatus.deleted ||
|
||||
space.status == SpaceStatus.parentDeleted) {
|
||||
return;
|
||||
}
|
||||
result.add(space);
|
||||
@ -447,7 +472,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
Connection(
|
||||
startSpace: parent,
|
||||
endSpace: child,
|
||||
direction: "down",
|
||||
),
|
||||
);
|
||||
|
||||
@ -532,13 +556,16 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
|
||||
void _selectSpace(BuildContext context, SpaceModel space) {
|
||||
context.read<SpaceManagementBloc>().add(
|
||||
SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: space),
|
||||
SelectSpaceEvent(
|
||||
selectedCommunity: widget.selectedCommunity,
|
||||
selectedSpace: space),
|
||||
);
|
||||
}
|
||||
|
||||
void _deselectSpace(BuildContext context) {
|
||||
context.read<SpaceManagementBloc>().add(
|
||||
SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: null),
|
||||
SelectSpaceEvent(
|
||||
selectedCommunity: widget.selectedCommunity, selectedSpace: null),
|
||||
);
|
||||
}
|
||||
|
||||
@ -708,7 +735,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
|
||||
SpaceModel duplicated = _deepCloneSpaceTree(space, parent: parent);
|
||||
|
||||
duplicated.position = Offset(space.position.dx + 300, space.position.dy + 100);
|
||||
duplicated.position =
|
||||
Offset(space.position.dx + 300, space.position.dy + 100);
|
||||
List<SpaceModel> duplicatedSubtree = [];
|
||||
void collectSubtree(SpaceModel node) {
|
||||
duplicatedSubtree.add(node);
|
||||
@ -726,7 +754,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
final newConnection = Connection(
|
||||
startSpace: parent,
|
||||
endSpace: duplicated,
|
||||
direction: "down",
|
||||
);
|
||||
connections.add(newConnection);
|
||||
duplicated.incomingConnection = newConnection;
|
||||
@ -739,7 +766,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
}
|
||||
|
||||
SpaceModel _deepCloneSpaceTree(SpaceModel original, {SpaceModel? parent}) {
|
||||
final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces);
|
||||
final duplicatedName =
|
||||
SpaceHelper.generateUniqueSpaceName(original.name, spaces);
|
||||
|
||||
final newSpace = SpaceModel(
|
||||
name: duplicatedName,
|
||||
@ -761,7 +789,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
final newConnection = Connection(
|
||||
startSpace: newSpace,
|
||||
endSpace: duplicatedChild,
|
||||
direction: "down",
|
||||
);
|
||||
connections.add(newConnection);
|
||||
|
||||
|
@ -0,0 +1,101 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
import '../../../../../common/edit_chip.dart';
|
||||
import '../../../../../utils/color_manager.dart';
|
||||
import '../../../helper/tag_helper.dart';
|
||||
import '../../../space_model/widgets/button_content_widget.dart';
|
||||
import '../../model/subspace_model.dart';
|
||||
import '../../model/tag.dart';
|
||||
|
||||
class DevicesPartWidget extends StatelessWidget {
|
||||
const DevicesPartWidget({
|
||||
super.key,
|
||||
required this.tags,
|
||||
required this.subspaces,
|
||||
required this.screenWidth,
|
||||
required this.onEditChip,
|
||||
required this.onTextButtonPressed,
|
||||
required this.isTagsAndSubspaceModelDisabled,
|
||||
});
|
||||
final bool isTagsAndSubspaceModelDisabled;
|
||||
final void Function() onEditChip;
|
||||
final void Function() onTextButtonPressed;
|
||||
final double screenWidth;
|
||||
final List<Tag>? tags;
|
||||
final List<SubspaceModel>? subspaces;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
(tags?.isNotEmpty == true ||
|
||||
subspaces?.any(
|
||||
(subspace) => subspace.tags?.isNotEmpty == true) ==
|
||||
true)
|
||||
? SizedBox(
|
||||
width: screenWidth * 0.25,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
width: 3.0, // Border width
|
||||
),
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
// Combine tags from spaceModel and subspaces
|
||||
...TagHelper.groupTags([
|
||||
...?tags,
|
||||
...?subspaces?.expand((subspace) => subspace.tags ?? [])
|
||||
]).entries.map(
|
||||
(entry) => Chip(
|
||||
avatar: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: SvgPicture.asset(
|
||||
entry.key.icon ?? 'assets/icons/gateway.svg',
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
label: Text(
|
||||
'x${entry.value}', // Show count
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(color: ColorsManager.spaceColor),
|
||||
),
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: const BorderSide(
|
||||
color: ColorsManager.spaceColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
EditChip(onTap: onEditChip)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: TextButton(
|
||||
onPressed: onTextButtonPressed,
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
child: ButtonContentWidget(
|
||||
icon: Icons.add,
|
||||
label: 'Add Devices',
|
||||
disabled: isTagsAndSubspaceModelDisabled,
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
import '../../../../../utils/color_manager.dart';
|
||||
import '../../../../../utils/constants/assets.dart';
|
||||
|
||||
class IconChoosePartWidget extends StatelessWidget {
|
||||
const IconChoosePartWidget({
|
||||
super.key,
|
||||
required this.selectedIcon,
|
||||
required this.showIconSelection,
|
||||
required this.screenWidth,
|
||||
});
|
||||
final double screenWidth;
|
||||
final String selectedIcon;
|
||||
final void Function() showIconSelection;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
// crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 50),
|
||||
Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: screenWidth * 0.1,
|
||||
height: screenWidth * 0.1,
|
||||
decoration: const BoxDecoration(
|
||||
color: ColorsManager.boxColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
SvgPicture.asset(
|
||||
selectedIcon,
|
||||
width: screenWidth * 0.04,
|
||||
height: screenWidth * 0.04,
|
||||
),
|
||||
Positioned(
|
||||
top: 20,
|
||||
right: 20,
|
||||
child: InkWell(
|
||||
onTap: showIconSelection,
|
||||
child: Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
Assets.iconEdit,
|
||||
width: 16,
|
||||
height: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../../utils/color_manager.dart';
|
||||
import '../../../../../utils/constants/assets.dart';
|
||||
import '../../../space_model/models/space_template_model.dart';
|
||||
import '../../../space_model/widgets/button_content_widget.dart';
|
||||
|
||||
class SpaceModelLinkingWidget extends StatelessWidget {
|
||||
const SpaceModelLinkingWidget({
|
||||
super.key,
|
||||
required this.onDeleted,
|
||||
required this.onPressed,
|
||||
required this.screenWidth,
|
||||
required this.selectedSpaceModel,
|
||||
required this.isSpaceModelDisabled,
|
||||
});
|
||||
final bool isSpaceModelDisabled;
|
||||
final void Function()? onDeleted;
|
||||
final void Function()? onPressed;
|
||||
final double screenWidth;
|
||||
final SpaceTemplateModel? selectedSpaceModel;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
selectedSpaceModel == null
|
||||
? TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
onPressed: onPressed,
|
||||
child: ButtonContentWidget(
|
||||
svgAssets: Assets.link,
|
||||
label: 'Link a space model',
|
||||
disabled: isSpaceModelDisabled,
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
width: screenWidth * 0.25,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10.0, horizontal: 16.0),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.boxColor,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
Chip(
|
||||
label: Text(
|
||||
selectedSpaceModel?.modelName ?? '',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(color: ColorsManager.spaceColor),
|
||||
),
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: const BorderSide(
|
||||
color: ColorsManager.transparentColor,
|
||||
width: 0,
|
||||
),
|
||||
),
|
||||
deleteIcon: Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.close,
|
||||
size: 16,
|
||||
color: ColorsManager.lightGrayColor,
|
||||
),
|
||||
),
|
||||
onDeleted: onDeleted),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../../utils/color_manager.dart';
|
||||
|
||||
class SpaceNameTextfieldWidget extends StatelessWidget {
|
||||
SpaceNameTextfieldWidget({
|
||||
super.key,
|
||||
required this.isNameFieldExist,
|
||||
required this.isNameFieldInvalid,
|
||||
required this.onChange,
|
||||
required this.screenWidth,
|
||||
required this.nameController,
|
||||
});
|
||||
TextEditingController nameController;
|
||||
final void Function(String value) onChange;
|
||||
final double screenWidth;
|
||||
bool isNameFieldExist;
|
||||
bool isNameFieldInvalid;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: screenWidth * 0.25,
|
||||
child: TextField(
|
||||
controller: nameController,
|
||||
onChanged: onChange,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Please enter the name',
|
||||
hintStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(color: ColorsManager.lightGrayColor),
|
||||
filled: true,
|
||||
fillColor: ColorsManager.boxColor,
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: BorderSide(
|
||||
color: isNameFieldInvalid || isNameFieldExist
|
||||
? ColorsManager.red
|
||||
: ColorsManager.boxColor,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: const BorderSide(
|
||||
color: ColorsManager.boxColor,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isNameFieldInvalid)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
'*Space name should not be empty.',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(color: ColorsManager.red),
|
||||
),
|
||||
),
|
||||
if (isNameFieldExist)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
'*Name already exist',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(color: ColorsManager.red),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../../common/edit_chip.dart';
|
||||
import '../../../../../utils/color_manager.dart';
|
||||
import '../../../space_model/widgets/button_content_widget.dart';
|
||||
import '../../../space_model/widgets/subspace_name_label_widget.dart';
|
||||
import '../../model/subspace_model.dart';
|
||||
|
||||
class SubSpacePartWidget extends StatefulWidget {
|
||||
SubSpacePartWidget({
|
||||
super.key,
|
||||
required this.subspaces,
|
||||
required this.onPressed,
|
||||
required this.isTagsAndSubspaceModelDisabled,
|
||||
required this.screenWidth,
|
||||
required this.editChipOnTap,
|
||||
});
|
||||
double screenWidth;
|
||||
bool isTagsAndSubspaceModelDisabled;
|
||||
final void Function() editChipOnTap;
|
||||
List<SubspaceModel>? subspaces;
|
||||
final void Function() onPressed;
|
||||
|
||||
@override
|
||||
State<SubSpacePartWidget> createState() => _SubSpacePartWidgetState();
|
||||
}
|
||||
|
||||
class _SubSpacePartWidgetState extends State<SubSpacePartWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
widget.subspaces == null || widget.subspaces!.isEmpty
|
||||
? TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
overlayColor: ColorsManager.transparentColor,
|
||||
),
|
||||
onPressed: widget.onPressed,
|
||||
child: ButtonContentWidget(
|
||||
icon: Icons.add,
|
||||
label: 'Create Sub Spaces',
|
||||
disabled: widget.isTagsAndSubspaceModelDisabled,
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
width: widget.screenWidth * 0.25,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
width: 3.0, // Border width
|
||||
),
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
if (widget.subspaces != null)
|
||||
...widget.subspaces!.map((subspace) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SubspaceNameDisplayWidget(
|
||||
text: subspace.subspaceName,
|
||||
validateName: (updatedName) {
|
||||
bool nameExists = widget.subspaces!.any((s) {
|
||||
bool isSameId =
|
||||
s.internalId == subspace.internalId;
|
||||
bool isSameName =
|
||||
s.subspaceName.trim().toLowerCase() ==
|
||||
updatedName.trim().toLowerCase();
|
||||
|
||||
return !isSameId && isSameName;
|
||||
});
|
||||
|
||||
return !nameExists;
|
||||
},
|
||||
onNameChanged: (updatedName) {
|
||||
setState(() {
|
||||
subspace.subspaceName = updatedName;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
EditChip(
|
||||
onTap: widget.editChipOnTap,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -30,28 +30,13 @@ class CurvedLinePainter extends CustomPainter {
|
||||
Offset end = connection.endSpace.position +
|
||||
const Offset(75, 0); // Center top of end space
|
||||
|
||||
if (connection.direction == 'down') {
|
||||
// Curved line for down connections
|
||||
final controlPoint = Offset((start.dx + end.dx) / 2, start.dy + 50);
|
||||
final path = Path()
|
||||
..moveTo(start.dx, start.dy)
|
||||
..quadraticBezierTo(controlPoint.dx, controlPoint.dy, end.dx, end.dy);
|
||||
canvas.drawPath(path, paint);
|
||||
} else if (connection.direction == 'right') {
|
||||
start = connection.startSpace.position +
|
||||
const Offset(150, 30); // Right center
|
||||
end = connection.endSpace.position + const Offset(0, 30); // Left center
|
||||
// Curved line for down connections
|
||||
final controlPoint = Offset((start.dx + end.dx) / 2, start.dy + 50);
|
||||
final path = Path()
|
||||
..moveTo(start.dx, start.dy)
|
||||
..quadraticBezierTo(controlPoint.dx, controlPoint.dy, end.dx, end.dy);
|
||||
canvas.drawPath(path, paint);
|
||||
|
||||
canvas.drawLine(start, end, paint);
|
||||
} else if (connection.direction == 'left') {
|
||||
start =
|
||||
connection.startSpace.position + const Offset(0, 30); // Left center
|
||||
end = connection.endSpace.position +
|
||||
const Offset(150, 30); // Right center
|
||||
|
||||
canvas.drawLine(start, end, paint);
|
||||
}
|
||||
|
||||
final dotPaint = Paint()..color = ColorsManager.blackColor;
|
||||
canvas.drawCircle(start, 5, dotPaint); // Start dot
|
||||
canvas.drawCircle(end, 5, dotPaint); // End dot
|
||||
|
@ -1,6 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/common/edit_chip.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/add_device_type/views/add_device_type_widget.dart';
|
||||
@ -9,6 +7,11 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_pr
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/create_space_widgets/devices_part_widget.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/create_space_widgets/icon_choose_part_widget.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/create_space_widgets/space_model_linking_widget.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/create_space_widgets/space_name_textfield_widget.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/create_space_widgets/sub_space_part_widget.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart';
|
||||
@ -16,8 +19,6 @@ import 'package:syncrow_web/pages/spaces_management/helper/space_helper.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.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';
|
||||
@ -82,8 +83,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;
|
||||
if (widget.currentSpaceModel != null) {
|
||||
subspaces = [];
|
||||
tags = [];
|
||||
@ -96,13 +99,15 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool isSpaceModelDisabled =
|
||||
(tags != null && tags!.isNotEmpty || subspaces != null && subspaces!.isNotEmpty);
|
||||
bool isSpaceModelDisabled = (tags != null && tags!.isNotEmpty ||
|
||||
subspaces != null && subspaces!.isNotEmpty);
|
||||
bool isTagsAndSubspaceModelDisabled = (selectedSpaceModel != null);
|
||||
|
||||
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,
|
||||
@ -112,50 +117,10 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
// crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 50),
|
||||
Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: screenWidth * 0.1,
|
||||
height: screenWidth * 0.1,
|
||||
decoration: const BoxDecoration(
|
||||
color: ColorsManager.boxColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
SvgPicture.asset(
|
||||
selectedIcon,
|
||||
width: screenWidth * 0.04,
|
||||
height: screenWidth * 0.04,
|
||||
),
|
||||
Positioned(
|
||||
top: 20,
|
||||
right: 20,
|
||||
child: InkWell(
|
||||
onTap: _showIconSelectionDialog,
|
||||
child: Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
Assets.iconEdit,
|
||||
width: 16,
|
||||
height: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
child: IconChoosePartWidget(
|
||||
selectedIcon: selectedIcon,
|
||||
showIconSelection: _showIconSelectionDialog,
|
||||
screenWidth: screenWidth,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
@ -164,342 +129,146 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: screenWidth * 0.25,
|
||||
child: TextField(
|
||||
controller: nameController,
|
||||
onChanged: (value) {
|
||||
enteredName = value.trim();
|
||||
setState(() {
|
||||
isNameFieldExist = false;
|
||||
isOkButtonEnabled = false;
|
||||
isNameFieldInvalid = value.isEmpty;
|
||||
SpaceNameTextfieldWidget(
|
||||
isNameFieldExist: isNameFieldExist,
|
||||
isNameFieldInvalid: isNameFieldInvalid,
|
||||
nameController: nameController,
|
||||
screenWidth: screenWidth,
|
||||
onChange: (value) {
|
||||
enteredName = value.trim();
|
||||
setState(() {
|
||||
isNameFieldExist = false;
|
||||
isOkButtonEnabled = false;
|
||||
isNameFieldInvalid = value.isEmpty;
|
||||
|
||||
if (!isNameFieldInvalid) {
|
||||
if (SpaceHelper.isNameConflict(
|
||||
value, widget.parentSpace, widget.editSpace)) {
|
||||
isNameFieldExist = true;
|
||||
isOkButtonEnabled = false;
|
||||
} else {
|
||||
isNameFieldExist = false;
|
||||
isOkButtonEnabled = true;
|
||||
}
|
||||
if (!isNameFieldInvalid) {
|
||||
if (SpaceHelper.isNameConflict(
|
||||
value, widget.parentSpace, widget.editSpace)) {
|
||||
isNameFieldExist = true;
|
||||
isOkButtonEnabled = false;
|
||||
} else {
|
||||
isNameFieldExist = false;
|
||||
isOkButtonEnabled = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Please enter the name',
|
||||
hintStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(color: ColorsManager.lightGrayColor),
|
||||
filled: true,
|
||||
fillColor: ColorsManager.boxColor,
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: BorderSide(
|
||||
color: isNameFieldInvalid || isNameFieldExist
|
||||
? ColorsManager.red
|
||||
: ColorsManager.boxColor,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: const BorderSide(
|
||||
color: ColorsManager.boxColor,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
if (isNameFieldInvalid)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
'*Space name should not be empty.',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(color: ColorsManager.red),
|
||||
),
|
||||
),
|
||||
if (isNameFieldExist)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
'*Name already exist',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(color: ColorsManager.red),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
selectedSpaceModel == null
|
||||
? TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
onPressed: () {
|
||||
isSpaceModelDisabled ? null : _showLinkSpaceModelDialog(context);
|
||||
},
|
||||
child: ButtonContentWidget(
|
||||
svgAssets: Assets.link,
|
||||
label: 'Link a space model',
|
||||
disabled: isSpaceModelDisabled,
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
width: screenWidth * 0.25,
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.boxColor,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
Chip(
|
||||
label: Text(
|
||||
selectedSpaceModel?.modelName ?? '',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(color: ColorsManager.spaceColor),
|
||||
),
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: const BorderSide(
|
||||
color: ColorsManager.transparentColor,
|
||||
width: 0,
|
||||
),
|
||||
),
|
||||
deleteIcon: Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.close,
|
||||
size: 16,
|
||||
color: ColorsManager.lightGrayColor,
|
||||
),
|
||||
),
|
||||
onDeleted: () => setState(() {
|
||||
this.selectedSpaceModel = null;
|
||||
subspaces = widget.subspaces ?? [];
|
||||
tags = widget.tags ?? [];
|
||||
})),
|
||||
],
|
||||
),
|
||||
),
|
||||
// SpaceModelLinkingWidget(
|
||||
// isSpaceModelDisabled: true,
|
||||
// // isSpaceModelDisabled,
|
||||
// onPressed: () {
|
||||
// isSpaceModelDisabled
|
||||
// ? null
|
||||
// : _showLinkSpaceModelDialog(context);
|
||||
// },
|
||||
// onDeleted: () => setState(() {
|
||||
// selectedSpaceModel = null;
|
||||
// subspaces = widget.subspaces ?? [];
|
||||
// tags = widget.tags ?? [];
|
||||
// }),
|
||||
// screenWidth: screenWidth,
|
||||
// selectedSpaceModel: selectedSpaceModel,
|
||||
// ),
|
||||
const SizedBox(height: 25),
|
||||
Row(
|
||||
children: [
|
||||
const Expanded(
|
||||
child: Divider(
|
||||
color: ColorsManager.neutralGray,
|
||||
thickness: 1.0,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
||||
child: Text(
|
||||
'OR',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
const Expanded(
|
||||
child: Divider(
|
||||
color: ColorsManager.neutralGray,
|
||||
thickness: 1.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
// Row(
|
||||
// children: [
|
||||
// const Expanded(
|
||||
// child: Divider(
|
||||
// color: ColorsManager.neutralGray,
|
||||
// thickness: 1.0,
|
||||
// ),
|
||||
// ),
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
||||
// child: Text(
|
||||
// 'OR',
|
||||
// style: Theme.of(context)
|
||||
// .textTheme
|
||||
// .bodyMedium
|
||||
// ?.copyWith(fontWeight: FontWeight.bold),
|
||||
// ),
|
||||
// ),
|
||||
// const Expanded(
|
||||
// child: Divider(
|
||||
// color: ColorsManager.neutralGray,
|
||||
// thickness: 1.0,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
const SizedBox(height: 25),
|
||||
SubSpacePartWidget(
|
||||
subspaces: subspaces,
|
||||
onPressed: () {
|
||||
isTagsAndSubspaceModelDisabled
|
||||
? null
|
||||
: _showSubSpaceDialog(
|
||||
context,
|
||||
enteredName,
|
||||
[],
|
||||
false,
|
||||
widget.products,
|
||||
subspaces,
|
||||
);
|
||||
},
|
||||
isTagsAndSubspaceModelDisabled:
|
||||
isTagsAndSubspaceModelDisabled,
|
||||
screenWidth: screenWidth,
|
||||
editChipOnTap: () async {
|
||||
_showSubSpaceDialog(
|
||||
context,
|
||||
enteredName,
|
||||
[],
|
||||
true,
|
||||
widget.products,
|
||||
subspaces,
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 25),
|
||||
subspaces == null || subspaces!.isEmpty
|
||||
? TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
overlayColor: ColorsManager.transparentColor,
|
||||
),
|
||||
onPressed: () async {
|
||||
isTagsAndSubspaceModelDisabled
|
||||
? null
|
||||
: _showSubSpaceDialog(
|
||||
context, enteredName, [], false, widget.products, subspaces);
|
||||
},
|
||||
child: ButtonContentWidget(
|
||||
icon: Icons.add,
|
||||
label: 'Create Sub Space',
|
||||
disabled: isTagsAndSubspaceModelDisabled,
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
width: screenWidth * 0.25,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
width: 3.0, // Border width
|
||||
),
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
if (subspaces != null)
|
||||
...subspaces!.map((subspace) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SubspaceNameDisplayWidget(
|
||||
text: subspace.subspaceName,
|
||||
validateName: (updatedName) {
|
||||
bool nameExists = subspaces!.any((s) {
|
||||
bool isSameId = s.internalId == subspace.internalId;
|
||||
bool isSameName =
|
||||
s.subspaceName.trim().toLowerCase() ==
|
||||
updatedName.trim().toLowerCase();
|
||||
|
||||
return !isSameId && isSameName;
|
||||
});
|
||||
|
||||
return !nameExists;
|
||||
},
|
||||
onNameChanged: (updatedName) {
|
||||
setState(() {
|
||||
subspace.subspaceName = updatedName;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
EditChip(
|
||||
onTap: () async {
|
||||
_showSubSpaceDialog(context, enteredName, [], true,
|
||||
widget.products, subspaces);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
(tags?.isNotEmpty == true ||
|
||||
subspaces?.any((subspace) => subspace.tags?.isNotEmpty == true) == true)
|
||||
? SizedBox(
|
||||
width: screenWidth * 0.25,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
width: 3.0, // Border width
|
||||
),
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
// Combine tags from spaceModel and subspaces
|
||||
...TagHelper.groupTags([
|
||||
...?tags,
|
||||
...?subspaces?.expand((subspace) => subspace.tags ?? [])
|
||||
]).entries.map(
|
||||
(entry) => Chip(
|
||||
avatar: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: SvgPicture.asset(
|
||||
entry.key.icon ?? 'assets/icons/gateway.svg',
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
label: Text(
|
||||
'x${entry.value}', // Show count
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(color: ColorsManager.spaceColor),
|
||||
),
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: const BorderSide(
|
||||
color: ColorsManager.spaceColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
EditChip(onTap: () async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AssignTagDialog(
|
||||
products: widget.products,
|
||||
subspaces: subspaces,
|
||||
allTags: widget.allTags,
|
||||
addedProducts:
|
||||
TagHelper.createInitialSelectedProductsForTags(
|
||||
tags ?? [], subspaces),
|
||||
title: 'Edit Device',
|
||||
initialTags: TagHelper.generateInitialForTags(
|
||||
spaceTags: tags, subspaces: subspaces),
|
||||
spaceName: widget.name ?? '',
|
||||
projectTags: widget.projectTags,
|
||||
onSave: (updatedTags, updatedSubspaces) {
|
||||
setState(() {
|
||||
tags = updatedTags;
|
||||
subspaces = updatedSubspaces;
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: TextButton(
|
||||
onPressed: () {
|
||||
isTagsAndSubspaceModelDisabled
|
||||
? null
|
||||
: _showTagCreateDialog(
|
||||
context,
|
||||
enteredName,
|
||||
widget.isEdit,
|
||||
widget.products,
|
||||
);
|
||||
DevicesPartWidget(
|
||||
tags: tags,
|
||||
subspaces: subspaces,
|
||||
screenWidth: screenWidth,
|
||||
isTagsAndSubspaceModelDisabled:
|
||||
isTagsAndSubspaceModelDisabled,
|
||||
onEditChip: () async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AssignTagDialog(
|
||||
products: widget.products,
|
||||
subspaces: subspaces,
|
||||
allTags: widget.allTags,
|
||||
addedProducts:
|
||||
TagHelper.createInitialSelectedProductsForTags(
|
||||
tags ?? [], subspaces),
|
||||
title: 'Edit Device',
|
||||
initialTags: TagHelper.generateInitialForTags(
|
||||
spaceTags: tags, subspaces: subspaces),
|
||||
spaceName: widget.name ?? '',
|
||||
projectTags: widget.projectTags,
|
||||
onSave: (updatedTags, updatedSubspaces) {
|
||||
setState(() {
|
||||
tags = updatedTags;
|
||||
subspaces = updatedSubspaces;
|
||||
});
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
child: ButtonContentWidget(
|
||||
icon: Icons.add,
|
||||
label: 'Add Devices',
|
||||
disabled: isTagsAndSubspaceModelDisabled,
|
||||
))
|
||||
),
|
||||
);
|
||||
},
|
||||
onTextButtonPressed: () {
|
||||
isTagsAndSubspaceModelDisabled
|
||||
? null
|
||||
: _showTagCreateDialog(
|
||||
context,
|
||||
enteredName,
|
||||
widget.isEdit,
|
||||
widget.products,
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -529,17 +298,25 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
} else if (isNameFieldExist) {
|
||||
return;
|
||||
} else {
|
||||
String newName = enteredName.isNotEmpty ? enteredName : (widget.name ?? '');
|
||||
String newName = enteredName.isNotEmpty
|
||||
? enteredName
|
||||
: (widget.name ?? '');
|
||||
if (newName.isNotEmpty) {
|
||||
widget.onCreateSpace(newName, selectedIcon, selectedProducts,
|
||||
selectedSpaceModel, subspaces, tags);
|
||||
widget.onCreateSpace(
|
||||
newName,
|
||||
selectedIcon,
|
||||
selectedProducts,
|
||||
selectedSpaceModel,
|
||||
subspaces,
|
||||
tags);
|
||||
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'),
|
||||
),
|
||||
@ -550,6 +327,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
);
|
||||
}
|
||||
|
||||
//dialooogggs
|
||||
void _showIconSelectionDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
@ -586,26 +364,50 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
);
|
||||
}
|
||||
|
||||
void _showSubSpaceDialog(BuildContext context, String name, final List<Tag>? spaceTags,
|
||||
bool isEdit, List<ProductModel>? products, final List<SubspaceModel>? existingSubSpaces) {
|
||||
void _showSubSpaceDialog(
|
||||
BuildContext context,
|
||||
String name,
|
||||
final List<Tag>? spaceTags,
|
||||
bool isEdit,
|
||||
List<ProductModel>? products,
|
||||
final List<SubspaceModel>? existingSubSpaces,
|
||||
) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return CreateSubSpaceDialog(
|
||||
spaceName: name,
|
||||
dialogTitle: isEdit ? 'Edit Sub-space' : 'Create Sub-space',
|
||||
dialogTitle: isEdit ? 'Edit Sub-spaces' : 'Create Sub-spaces',
|
||||
products: products,
|
||||
existingSubSpaces: existingSubSpaces,
|
||||
onSave: (slectedSubspaces) {
|
||||
onSave: (slectedSubspaces, updatedSubSpaces) {
|
||||
final List<Tag> tagsToAppendToSpace = [];
|
||||
|
||||
if (slectedSubspaces != null) {
|
||||
final updatedIds = slectedSubspaces.map((s) => s.internalId).toSet();
|
||||
if (slectedSubspaces != null && slectedSubspaces.isNotEmpty) {
|
||||
final updatedIds =
|
||||
slectedSubspaces.map((s) => s.internalId).toSet();
|
||||
if (existingSubSpaces != null) {
|
||||
final deletedSubspaces =
|
||||
existingSubSpaces.where((s) => !updatedIds.contains(s.internalId)).toList();
|
||||
final deletedSubspaces = existingSubSpaces
|
||||
.where((s) => !updatedIds.contains(s.internalId))
|
||||
.toList();
|
||||
for (var s in deletedSubspaces) {
|
||||
if (s.tags != null) {
|
||||
s.tags!.forEach(
|
||||
(tag) => tag.location = null,
|
||||
);
|
||||
tagsToAppendToSpace.addAll(s.tags!);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (existingSubSpaces != null) {
|
||||
final deletedSubspaces = existingSubSpaces;
|
||||
|
||||
for (var s in deletedSubspaces) {
|
||||
if (s.tags != null) {
|
||||
s.tags!.forEach(
|
||||
(tag) => tag.location = null,
|
||||
);
|
||||
tagsToAppendToSpace.addAll(s.tags!);
|
||||
}
|
||||
}
|
||||
@ -623,15 +425,16 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
);
|
||||
}
|
||||
|
||||
void _showTagCreateDialog(
|
||||
BuildContext context, String name, bool isEdit, List<ProductModel>? products) {
|
||||
void _showTagCreateDialog(BuildContext context, String name, bool isEdit,
|
||||
List<ProductModel>? products) {
|
||||
isEdit
|
||||
? showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AssignTagDialog(
|
||||
title: 'Edit Device',
|
||||
addedProducts: TagHelper.createInitialSelectedProductsForTags(tags, subspaces),
|
||||
addedProducts: TagHelper.createInitialSelectedProductsForTags(
|
||||
tags, subspaces),
|
||||
spaceName: name,
|
||||
products: products,
|
||||
subspaces: subspaces,
|
||||
@ -646,7 +449,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
if (subspaces != null) {
|
||||
for (final subspace in subspaces!) {
|
||||
for (final selectedSubspace in selectedSubspaces) {
|
||||
if (subspace.subspaceName == selectedSubspace.subspaceName) {
|
||||
if (subspace.subspaceName ==
|
||||
selectedSubspace.subspaceName) {
|
||||
subspace.tags = selectedSubspace.tags;
|
||||
}
|
||||
}
|
||||
@ -670,7 +474,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
allTags: widget.allTags,
|
||||
projectTags: widget.projectTags,
|
||||
initialSelectedProducts:
|
||||
TagHelper.createInitialSelectedProductsForTags(tags, subspaces),
|
||||
TagHelper.createInitialSelectedProductsForTags(
|
||||
tags, subspaces),
|
||||
onSave: (selectedSpaceTags, selectedSubspaces) {
|
||||
setState(() {
|
||||
tags = selectedSpaceTags;
|
||||
@ -680,7 +485,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
if (subspaces != null) {
|
||||
for (final subspace in subspaces!) {
|
||||
for (final selectedSubspace in selectedSubspaces) {
|
||||
if (subspace.subspaceName == selectedSubspace.subspaceName) {
|
||||
if (subspace.subspaceName ==
|
||||
selectedSubspace.subspaceName) {
|
||||
subspace.tags = selectedSubspace.tags;
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ class PlusButtonWidget extends StatelessWidget {
|
||||
final int index;
|
||||
final String direction;
|
||||
final Offset offset;
|
||||
final Function(int index, Offset newPosition, String direction) onButtonTap;
|
||||
final Function(int index, Offset newPosition) onButtonTap;
|
||||
|
||||
const PlusButtonWidget({
|
||||
super.key,
|
||||
@ -17,35 +17,21 @@ class PlusButtonWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Positioned(
|
||||
left: offset.dx,
|
||||
top: offset.dy,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
Offset newPosition;
|
||||
switch (direction) {
|
||||
case 'left':
|
||||
newPosition = const Offset(-200, 0);
|
||||
break;
|
||||
case 'right':
|
||||
newPosition = const Offset(200, 0);
|
||||
break;
|
||||
case 'down':
|
||||
newPosition = const Offset(0, 150);
|
||||
break;
|
||||
default:
|
||||
newPosition = Offset.zero;
|
||||
}
|
||||
onButtonTap(index, newPosition, direction);
|
||||
},
|
||||
child: Container(
|
||||
width: 30,
|
||||
height: 30,
|
||||
decoration: const BoxDecoration(
|
||||
color: ColorsManager.spaceColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.add, color: ColorsManager.whiteColors, size: 20),
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
onButtonTap(index, const Offset(0, 150));
|
||||
},
|
||||
child: Container(
|
||||
width: 30,
|
||||
height: 30,
|
||||
decoration: const BoxDecoration(
|
||||
color: ColorsManager.spaceColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.add,
|
||||
color: ColorsManager.whiteColors,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -7,7 +7,7 @@ class SpaceCardWidget extends StatelessWidget {
|
||||
final Offset position;
|
||||
final bool isHovered;
|
||||
final Function(int index, bool isHovered) onHoverChanged;
|
||||
final Function(int index, Offset newPosition, String direction) onButtonTap;
|
||||
final Function(int index, Offset newPosition) onButtonTap;
|
||||
final Widget Function(int index) buildSpaceContainer;
|
||||
final ValueChanged<Offset> onPositionChanged;
|
||||
|
||||
@ -25,35 +25,34 @@ class SpaceCardWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onPanUpdate: (details) {
|
||||
// Call the provided callback to update the position
|
||||
final newPosition = position + details.delta;
|
||||
onPositionChanged(newPosition);
|
||||
},
|
||||
child: MouseRegion(
|
||||
onEnter: (_) {
|
||||
// Call the provided callback to handle hover state
|
||||
onHoverChanged(index, true);
|
||||
},
|
||||
onExit: (_) {
|
||||
// Call the provided callback to handle hover state
|
||||
onHoverChanged(index, false);
|
||||
},
|
||||
return MouseRegion(
|
||||
onEnter: (_) => onHoverChanged(index, true),
|
||||
onExit: (_) => onHoverChanged(index, false),
|
||||
child: SizedBox(
|
||||
width: 140, // Make sure this covers both card and plus button
|
||||
height: 90,
|
||||
child: Stack(
|
||||
clipBehavior: Clip
|
||||
.none, // Allow hovering elements to be displayed outside the boundary
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
buildSpaceContainer(index), // Build the space container
|
||||
if (isHovered) ...[
|
||||
PlusButtonWidget(
|
||||
index: index,
|
||||
direction: 'down',
|
||||
offset: const Offset(63, 50),
|
||||
onButtonTap: onButtonTap,
|
||||
// Main card
|
||||
Container(
|
||||
width: 140,
|
||||
height: 80,
|
||||
alignment: Alignment.center,
|
||||
color: Colors.transparent,
|
||||
child: buildSpaceContainer(index),
|
||||
),
|
||||
// Plus button (NO inner Positioned!)
|
||||
if (isHovered)
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: PlusButtonWidget(
|
||||
index: index,
|
||||
direction: 'down',
|
||||
offset: Offset.zero,
|
||||
onButtonTap: onButtonTap,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -13,7 +13,8 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
||||
final existingTagCounts = <String, int>{};
|
||||
for (var tag in initialTags) {
|
||||
if (tag.product != null) {
|
||||
existingTagCounts[tag.product!.uuid] = (existingTagCounts[tag.product!.uuid] ?? 0) + 1;
|
||||
existingTagCounts[tag.product!.uuid] =
|
||||
(existingTagCounts[tag.product!.uuid] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,14 +23,17 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
||||
for (var selectedProduct in event.addedProducts) {
|
||||
final existingCount = existingTagCounts[selectedProduct.productId] ?? 0;
|
||||
|
||||
if (selectedProduct.count == 0 || selectedProduct.count <= existingCount) {
|
||||
tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId));
|
||||
if (selectedProduct.count == 0 ||
|
||||
selectedProduct.count <= existingCount) {
|
||||
tags.addAll(initialTags
|
||||
.where((tag) => tag.product?.uuid == selectedProduct.productId));
|
||||
continue;
|
||||
}
|
||||
|
||||
final missingCount = selectedProduct.count - existingCount;
|
||||
|
||||
tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId));
|
||||
tags.addAll(initialTags
|
||||
.where((tag) => tag.product?.uuid == selectedProduct.productId));
|
||||
|
||||
if (missingCount > 0) {
|
||||
tags.addAll(List.generate(
|
||||
@ -85,7 +89,8 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
||||
final tags = List<Tag>.from(currentState.tags);
|
||||
|
||||
// Update the location
|
||||
tags[event.index] = tags[event.index].copyWith(location: event.location);
|
||||
tags[event.index] =
|
||||
tags[event.index].copyWith(location: event.location);
|
||||
|
||||
final updatedTags = _calculateAvailableTags(projectTags, tags);
|
||||
|
||||
@ -117,7 +122,8 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
||||
final currentState = state;
|
||||
|
||||
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
|
||||
final tags = List<Tag>.from(currentState.tags)..remove(event.tagToDelete);
|
||||
final tags = List<Tag>.from(currentState.tags)
|
||||
..remove(event.tagToDelete);
|
||||
|
||||
// Recalculate available tags
|
||||
final updatedTags = _calculateAvailableTags(projectTags, tags);
|
||||
@ -141,8 +147,10 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
||||
|
||||
// Get validation error for duplicate tags
|
||||
String? _getValidationError(List<Tag> tags) {
|
||||
final nonEmptyTags =
|
||||
tags.map((tag) => tag.tag?.trim() ?? '').where((tag) => tag.isNotEmpty).toList();
|
||||
final nonEmptyTags = tags
|
||||
.map((tag) => tag.tag?.trim() ?? '')
|
||||
.where((tag) => tag.isNotEmpty)
|
||||
.toList();
|
||||
|
||||
final duplicateTags = nonEmptyTags
|
||||
.fold<Map<String, int>>({}, (map, tag) {
|
||||
@ -168,9 +176,11 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
||||
.toSet();
|
||||
|
||||
final availableTags = allTags
|
||||
.where((tag) => tag.tag != null && !selectedTagSet.contains(tag.tag!.trim()))
|
||||
.where((tag) =>
|
||||
tag.tag != null && !selectedTagSet.contains(tag.tag!.trim()))
|
||||
.toList();
|
||||
|
||||
return availableTags;
|
||||
return projectTags;
|
||||
// availableTags;
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/common/dialog_dropdown.dart';
|
||||
import 'package:syncrow_web/common/tag_dialog_textfield_dropdown.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/add_device_type/views/add_device_type_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/model/subspace_model.dart';
|
||||
@ -12,9 +7,10 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/widgets/assign_tags_tables_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import 'widgets/save_add_device_row_widget.dart';
|
||||
|
||||
class AssignTagDialog extends StatelessWidget {
|
||||
final List<ProductModel>? products;
|
||||
@ -29,7 +25,7 @@ class AssignTagDialog extends StatelessWidget {
|
||||
final List<Tag> projectTags;
|
||||
|
||||
const AssignTagDialog(
|
||||
{Key? key,
|
||||
{super.key,
|
||||
required this.products,
|
||||
required this.subspaces,
|
||||
required this.addedProducts,
|
||||
@ -39,13 +35,14 @@ class AssignTagDialog extends StatelessWidget {
|
||||
required this.spaceName,
|
||||
required this.title,
|
||||
this.onSave,
|
||||
required this.projectTags})
|
||||
: super(key: key);
|
||||
required this.projectTags});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<String> locations =
|
||||
(subspaces ?? []).map((subspace) => subspace.subspaceName).toList()..add('Main Space');
|
||||
final List<String> locations = (subspaces ?? [])
|
||||
.map((subspace) => subspace.subspaceName)
|
||||
.toList()
|
||||
..add('Main Space');
|
||||
|
||||
return BlocProvider(
|
||||
create: (_) => AssignTagBloc(projectTags)
|
||||
@ -67,131 +64,31 @@ class AssignTagDialog extends StatelessWidget {
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: DataTable(
|
||||
headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey),
|
||||
key: ValueKey(state.tags.length),
|
||||
border: TableBorder.all(
|
||||
color: ColorsManager.dataHeaderGrey,
|
||||
width: 1,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
columns: [
|
||||
DataColumn(
|
||||
label: Text('#', style: Theme.of(context).textTheme.bodyMedium)),
|
||||
DataColumn(
|
||||
label: Text('Device', style: Theme.of(context).textTheme.bodyMedium)),
|
||||
DataColumn(
|
||||
numeric: false,
|
||||
label: Text('Tag', style: Theme.of(context).textTheme.bodyMedium)),
|
||||
DataColumn(
|
||||
label:
|
||||
Text('Location', style: Theme.of(context).textTheme.bodyMedium)),
|
||||
],
|
||||
rows: state.tags.isEmpty
|
||||
? [
|
||||
DataRow(cells: [
|
||||
DataCell(
|
||||
Center(
|
||||
child: Text('No Data Available',
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
)),
|
||||
),
|
||||
),
|
||||
const DataCell(SizedBox()),
|
||||
const DataCell(SizedBox()),
|
||||
const DataCell(SizedBox()),
|
||||
])
|
||||
]
|
||||
: List.generate(state.tags.length, (index) {
|
||||
final tag = state.tags[index];
|
||||
final controller = controllers[index];
|
||||
AssignTagsTable(
|
||||
controllers: controllers,
|
||||
locations: locations,
|
||||
tags: state.tags,
|
||||
updatedTags: state.updatedTags,
|
||||
onDeleteDevice: ({required index, required tag}) {
|
||||
context
|
||||
.read<AssignTagBloc>()
|
||||
.add(DeleteTag(tagToDelete: tag, tags: state.tags));
|
||||
|
||||
return DataRow(
|
||||
cells: [
|
||||
DataCell(Text((index + 1).toString())),
|
||||
DataCell(
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
tag.product?.name ?? 'Unknown',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)),
|
||||
const SizedBox(width: 10),
|
||||
Container(
|
||||
width: 20.0,
|
||||
height: 20.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
child: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: ColorsManager.lightGreyColor,
|
||||
size: 16,
|
||||
),
|
||||
onPressed: () {
|
||||
context.read<AssignTagBloc>().add(
|
||||
DeleteTag(tagToDelete: tag, tags: state.tags));
|
||||
|
||||
controllers.removeAt(index);
|
||||
},
|
||||
tooltip: 'Delete Tag',
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
Container(
|
||||
alignment:
|
||||
Alignment.centerLeft, // Align cell content to the left
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: TagDialogTextfieldDropdown(
|
||||
key: ValueKey('dropdown_${const Uuid().v4()}_$index'),
|
||||
items: state.updatedTags,
|
||||
product: tag.product?.uuid ?? 'Unknown',
|
||||
initialValue: tag,
|
||||
onSelected: (value) {
|
||||
controller.text = value.tag ?? '';
|
||||
context.read<AssignTagBloc>().add(UpdateTagEvent(
|
||||
index: index,
|
||||
tag: value,
|
||||
));
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: DialogDropdown(
|
||||
items: locations,
|
||||
selectedValue: tag.location ?? 'Main Space',
|
||||
onSelected: (value) {
|
||||
context.read<AssignTagBloc>().add(UpdateLocation(
|
||||
index: index,
|
||||
location: value,
|
||||
));
|
||||
},
|
||||
)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
controllers.removeAt(index);
|
||||
},
|
||||
onTagDropDownSelected: ({required index, required tag}) {
|
||||
context.read<AssignTagBloc>().add(UpdateTagEvent(
|
||||
index: index,
|
||||
tag: tag,
|
||||
));
|
||||
},
|
||||
onLocationDropDownSelected: (
|
||||
{required index, required location}) {
|
||||
context.read<AssignTagBloc>().add(UpdateLocation(
|
||||
index: index,
|
||||
location: location,
|
||||
));
|
||||
},
|
||||
),
|
||||
if (state.errorMessage != null)
|
||||
Text(state.errorMessage!,
|
||||
@ -203,69 +100,15 @@ class AssignTagDialog extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Builder(
|
||||
builder: (buttonContext) => CancelButton(
|
||||
label: 'Add New Device',
|
||||
onPressed: () async {
|
||||
final updatedTags = List<Tag>.from(state.tags);
|
||||
final result = TagHelper.processTags(updatedTags, subspaces);
|
||||
|
||||
final processedTags = result['updatedTags'] as List<Tag>;
|
||||
final processedSubspaces =
|
||||
List<SubspaceModel>.from(result['subspaces'] as List<dynamic>);
|
||||
|
||||
Navigator.of(context).pop();
|
||||
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AddDeviceTypeWidget(
|
||||
products: products,
|
||||
subspaces: processedSubspaces,
|
||||
projectTags: projectTags,
|
||||
initialSelectedProducts:
|
||||
TagHelper.createInitialSelectedProductsForTags(
|
||||
processedTags, processedSubspaces),
|
||||
spaceName: spaceName,
|
||||
spaceTags: processedTags,
|
||||
isCreate: false,
|
||||
onSave: onSave,
|
||||
allTags: allTags,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: DefaultButton(
|
||||
borderRadius: 10,
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
foregroundColor: state.isSaveEnabled
|
||||
? ColorsManager.whiteColors
|
||||
: ColorsManager.whiteColorsWithOpacity,
|
||||
onPressed: state.isSaveEnabled
|
||||
? () async {
|
||||
final updatedTags = List<Tag>.from(state.tags);
|
||||
final result = TagHelper.processTags(updatedTags, subspaces);
|
||||
|
||||
final processedTags = result['updatedTags'] as List<Tag>;
|
||||
final processedSubspaces =
|
||||
List<SubspaceModel>.from(result['subspaces'] as List<dynamic>);
|
||||
onSave?.call(processedTags, processedSubspaces);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
: null,
|
||||
child: const Text('Save'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
SaveAddDeviceRowWidget(
|
||||
subspaces: subspaces,
|
||||
products: products,
|
||||
projectTags: projectTags,
|
||||
spaceName: spaceName,
|
||||
onSave: onSave,
|
||||
allTags: allTags,
|
||||
tags: state.tags,
|
||||
isSaveEnabled: state.isSaveEnabled,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -0,0 +1,100 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.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/utils/color_manager.dart';
|
||||
|
||||
import '../../../add_device_type/views/add_device_type_widget.dart';
|
||||
import '../../../helper/tag_helper.dart';
|
||||
|
||||
class SaveAddDeviceRowWidget extends StatelessWidget {
|
||||
const SaveAddDeviceRowWidget({
|
||||
super.key,
|
||||
required this.subspaces,
|
||||
required this.products,
|
||||
required this.projectTags,
|
||||
required this.spaceName,
|
||||
required this.onSave,
|
||||
required this.allTags,
|
||||
required this.tags,
|
||||
required this.isSaveEnabled,
|
||||
});
|
||||
final List<Tag> tags;
|
||||
final List<SubspaceModel>? subspaces;
|
||||
final List<ProductModel>? products;
|
||||
final List<Tag> projectTags;
|
||||
final String spaceName;
|
||||
final Function(List<Tag> p1, List<SubspaceModel>? p2)? onSave;
|
||||
final List<String>? allTags;
|
||||
final bool isSaveEnabled;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Builder(
|
||||
builder: (buttonContext) => CancelButton(
|
||||
label: 'Add New Device',
|
||||
onPressed: () async {
|
||||
final updatedTags = List<Tag>.from(tags);
|
||||
final result = TagHelper.processTags(updatedTags, subspaces);
|
||||
|
||||
final processedTags = result['updatedTags'] as List<Tag>;
|
||||
final processedSubspaces = List<SubspaceModel>.from(
|
||||
result['subspaces'] as List<dynamic>);
|
||||
|
||||
Navigator.of(context).pop();
|
||||
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AddDeviceTypeWidget(
|
||||
products: products,
|
||||
subspaces: processedSubspaces,
|
||||
projectTags: projectTags,
|
||||
initialSelectedProducts:
|
||||
TagHelper.createInitialSelectedProductsForTags(
|
||||
processedTags, processedSubspaces),
|
||||
spaceName: spaceName,
|
||||
spaceTags: processedTags,
|
||||
isCreate: false,
|
||||
onSave: onSave,
|
||||
allTags: allTags,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: DefaultButton(
|
||||
borderRadius: 10,
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
foregroundColor: isSaveEnabled
|
||||
? ColorsManager.whiteColors
|
||||
: ColorsManager.whiteColorsWithOpacity,
|
||||
onPressed: isSaveEnabled
|
||||
? () async {
|
||||
final updatedTags = List<Tag>.from(tags);
|
||||
final result =
|
||||
TagHelper.processTags(updatedTags, subspaces);
|
||||
|
||||
final processedTags = result['updatedTags'] as List<Tag>;
|
||||
final processedSubspaces = List<SubspaceModel>.from(
|
||||
result['subspaces'] as List<dynamic>);
|
||||
onSave?.call(processedTags, processedSubspaces);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
: null,
|
||||
child: const Text('Save'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,9 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/common/dialog_dropdown.dart';
|
||||
import 'package:syncrow_web/common/tag_dialog_textfield_dropdown.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/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/tag.dart';
|
||||
@ -12,11 +8,10 @@ import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assig
|
||||
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import 'widgets/RowOfCancelSaveWidget.dart';
|
||||
import 'widgets/assign_tags_tables_widget.dart';
|
||||
|
||||
class AssignTagModelsDialog extends StatelessWidget {
|
||||
final List<ProductModel>? products;
|
||||
@ -53,8 +48,10 @@ class AssignTagModelsDialog extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<String> locations =
|
||||
(subspaces ?? []).map((subspace) => subspace.subspaceName).toList()..add('Main Space');
|
||||
final List<String> locations = (subspaces ?? [])
|
||||
.map((subspace) => subspace.subspaceName)
|
||||
.toList()
|
||||
..add('Main Space');
|
||||
|
||||
return BlocProvider(
|
||||
create: (_) => AssignTagModelBloc(projectTags)
|
||||
@ -78,137 +75,38 @@ class AssignTagModelsDialog extends StatelessWidget {
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: DataTable(
|
||||
headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey),
|
||||
key: ValueKey(state.tags.length),
|
||||
border: TableBorder.all(
|
||||
color: ColorsManager.dataHeaderGrey,
|
||||
width: 1,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
columns: [
|
||||
DataColumn(
|
||||
label: Text('#', style: Theme.of(context).textTheme.bodyMedium)),
|
||||
DataColumn(
|
||||
label: Text('Device',
|
||||
style: Theme.of(context).textTheme.bodyMedium)),
|
||||
DataColumn(
|
||||
numeric: false,
|
||||
label:
|
||||
Text('Tag', style: Theme.of(context).textTheme.bodyMedium)),
|
||||
DataColumn(
|
||||
label: Text('Location',
|
||||
style: Theme.of(context).textTheme.bodyMedium)),
|
||||
],
|
||||
rows: state.tags.isEmpty
|
||||
? [
|
||||
DataRow(cells: [
|
||||
DataCell(
|
||||
Center(
|
||||
child: Text('No Devices Available',
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
)),
|
||||
),
|
||||
),
|
||||
const DataCell(SizedBox()),
|
||||
const DataCell(SizedBox()),
|
||||
const DataCell(SizedBox()),
|
||||
])
|
||||
]
|
||||
: List.generate(state.tags.length, (index) {
|
||||
final tag = state.tags[index];
|
||||
final controller = controllers[index];
|
||||
|
||||
return DataRow(
|
||||
cells: [
|
||||
DataCell(Text((index + 1).toString())),
|
||||
DataCell(
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
tag.product?.name ?? 'Unknown',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)),
|
||||
const SizedBox(width: 10),
|
||||
Container(
|
||||
width: 20.0,
|
||||
height: 20.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
child: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: ColorsManager.lightGreyColor,
|
||||
size: 16,
|
||||
),
|
||||
onPressed: () {
|
||||
context.read<AssignTagModelBloc>().add(
|
||||
DeleteTagModel(
|
||||
tagToDelete: tag, tags: state.tags));
|
||||
controllers.removeAt(index);
|
||||
},
|
||||
tooltip: 'Delete Tag',
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
Container(
|
||||
alignment: Alignment
|
||||
.centerLeft, // Align cell content to the left
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: TagDialogTextfieldDropdown(
|
||||
key: ValueKey(
|
||||
'dropdown_${const Uuid().v4()}_$index'),
|
||||
product: tag.product?.uuid ?? 'Unknown',
|
||||
items: state.updatedTags,
|
||||
initialValue: tag,
|
||||
onSelected: (value) {
|
||||
controller.text = value.tag ?? '';
|
||||
context.read<AssignTagModelBloc>().add(UpdateTag(
|
||||
index: index,
|
||||
tag: value,
|
||||
));
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: DialogDropdown(
|
||||
items: locations,
|
||||
selectedValue: tag.location ?? 'Main Space',
|
||||
onSelected: (value) {
|
||||
context
|
||||
.read<AssignTagModelBloc>()
|
||||
.add(UpdateLocation(
|
||||
index: index,
|
||||
location: value,
|
||||
));
|
||||
},
|
||||
)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
AssignTagsTable(
|
||||
controllers: controllers,
|
||||
locations: locations,
|
||||
tags: state.tags,
|
||||
updatedTags: state.updatedTags,
|
||||
onDeleteDevice: ({required index, required tag}) {
|
||||
context
|
||||
.read<AssignTagModelBloc>()
|
||||
.add(DeleteTagModel(
|
||||
tagToDelete: tag,
|
||||
tags: state.tags,
|
||||
));
|
||||
controllers.removeAt(index);
|
||||
},
|
||||
onTagDropDownSelected: (
|
||||
{required index, required tag}) {
|
||||
context.read<AssignTagModelBloc>().add(
|
||||
UpdateTag(
|
||||
index: index,
|
||||
tag: tag,
|
||||
),
|
||||
);
|
||||
},
|
||||
onLocationDropDownSelected: (
|
||||
{required index, required location}) {
|
||||
context.read<AssignTagModelBloc>().add(
|
||||
UpdateLocation(
|
||||
index: index,
|
||||
location: location,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (state.errorMessage != null)
|
||||
Text(state.errorMessage!,
|
||||
@ -220,101 +118,16 @@ class AssignTagModelsDialog extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Builder(
|
||||
builder: (buttonContext) => CancelButton(
|
||||
label: 'Add New Device',
|
||||
onPressed: () async {
|
||||
final updatedTags = List<Tag>.from(state.tags);
|
||||
final result =
|
||||
TagHelper.updateSubspaceTagModels(updatedTags, subspaces);
|
||||
|
||||
final processedTags = result['updatedTags'] as List<Tag>;
|
||||
final processedSubspaces = List<SubspaceTemplateModel>.from(
|
||||
result['subspaces'] as List<dynamic>);
|
||||
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
|
||||
await showDialog<bool>(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (dialogContext) => AddDeviceTypeModelWidget(
|
||||
products: products,
|
||||
subspaces: processedSubspaces,
|
||||
isCreate: false,
|
||||
initialSelectedProducts:
|
||||
TagHelper.createInitialSelectedProducts(
|
||||
processedTags, processedSubspaces),
|
||||
allTags: allTags,
|
||||
spaceName: spaceName,
|
||||
otherSpaceModels: otherSpaceModels,
|
||||
spaceTagModels: processedTags,
|
||||
pageContext: pageContext,
|
||||
projectTags: projectTags,
|
||||
spaceModel: SpaceTemplateModel(
|
||||
modelName: spaceName,
|
||||
tags: updatedTags,
|
||||
uuid: spaceModel?.uuid,
|
||||
internalId: spaceModel?.internalId,
|
||||
subspaceModels: processedSubspaces)),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: DefaultButton(
|
||||
borderRadius: 10,
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
foregroundColor: state.isSaveEnabled
|
||||
? ColorsManager.whiteColors
|
||||
: ColorsManager.whiteColorsWithOpacity,
|
||||
onPressed: state.isSaveEnabled
|
||||
? () async {
|
||||
final updatedTags = List<Tag>.from(state.tags);
|
||||
|
||||
final result =
|
||||
TagHelper.updateSubspaceTagModels(updatedTags, subspaces);
|
||||
|
||||
final processedTags = result['updatedTags'] as List<Tag>;
|
||||
final processedSubspaces = List<SubspaceTemplateModel>.from(
|
||||
result['subspaces'] as List<dynamic>);
|
||||
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext dialogContext) {
|
||||
return CreateSpaceModelDialog(
|
||||
products: products,
|
||||
allSpaceModels: allSpaceModels,
|
||||
allTags: allTags,
|
||||
projectTags: projectTags,
|
||||
pageContext: pageContext,
|
||||
otherSpaceModels: otherSpaceModels,
|
||||
spaceModel: SpaceTemplateModel(
|
||||
modelName: spaceName,
|
||||
tags: processedTags,
|
||||
uuid: spaceModel?.uuid,
|
||||
internalId: spaceModel?.internalId,
|
||||
subspaceModels: processedSubspaces),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
: null,
|
||||
child: const Text('Save'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
RowOfSaveCancelWidget(
|
||||
subspaces: subspaces,
|
||||
products: products,
|
||||
allTags: allTags,
|
||||
spaceName: spaceName,
|
||||
otherSpaceModels: otherSpaceModels,
|
||||
pageContext: pageContext,
|
||||
projectTags: projectTags,
|
||||
spaceModel: spaceModel,
|
||||
allSpaceModels: allSpaceModels,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -0,0 +1,152 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../../../utils/color_manager.dart';
|
||||
import '../../../../common/buttons/cancel_button.dart';
|
||||
import '../../../../common/buttons/default_button.dart';
|
||||
import '../../../all_spaces/model/product_model.dart';
|
||||
import '../../../all_spaces/model/tag.dart';
|
||||
import '../../../helper/tag_helper.dart';
|
||||
import '../../../space_model/models/space_template_model.dart';
|
||||
import '../../../space_model/models/subspace_template_model.dart';
|
||||
import '../../../space_model/widgets/dialog/create_space_model_dialog.dart';
|
||||
import '../../../tag_model/views/add_device_type_model_widget.dart';
|
||||
import '../../bloc/assign_tag_model_bloc.dart';
|
||||
import '../../bloc/assign_tag_model_state.dart';
|
||||
|
||||
class RowOfSaveCancelWidget extends StatelessWidget {
|
||||
const RowOfSaveCancelWidget({
|
||||
super.key,
|
||||
required this.subspaces,
|
||||
required this.products,
|
||||
required this.allTags,
|
||||
required this.spaceName,
|
||||
required this.otherSpaceModels,
|
||||
required this.pageContext,
|
||||
required this.projectTags,
|
||||
required this.spaceModel,
|
||||
required this.allSpaceModels,
|
||||
});
|
||||
|
||||
final List<SubspaceTemplateModel>? subspaces;
|
||||
final List<ProductModel>? products;
|
||||
final List<String>? allTags;
|
||||
final String spaceName;
|
||||
final List<String>? otherSpaceModels;
|
||||
final BuildContext? pageContext;
|
||||
final List<Tag> projectTags;
|
||||
final SpaceTemplateModel? spaceModel;
|
||||
final List<SpaceTemplateModel>? allSpaceModels;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<AssignTagModelBloc, AssignTagModelState>(
|
||||
builder: (context, state) {
|
||||
if (state is AssignTagModelLoaded) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Builder(
|
||||
builder: (buttonContext) => CancelButton(
|
||||
label: 'Add New Device',
|
||||
onPressed: () async {
|
||||
final updatedTags = List<Tag>.from(state.tags);
|
||||
final result = TagHelper.updateSubspaceTagModels(
|
||||
updatedTags, subspaces);
|
||||
|
||||
final processedTags = result['updatedTags'] as List<Tag>;
|
||||
final processedSubspaces =
|
||||
List<SubspaceTemplateModel>.from(
|
||||
result['subspaces'] as List<dynamic>);
|
||||
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
|
||||
await showDialog<bool>(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (dialogContext) => AddDeviceTypeModelWidget(
|
||||
products: products,
|
||||
subspaces: processedSubspaces,
|
||||
isCreate: false,
|
||||
initialSelectedProducts:
|
||||
TagHelper.createInitialSelectedProducts(
|
||||
processedTags, processedSubspaces),
|
||||
allTags: allTags,
|
||||
spaceName: spaceName,
|
||||
otherSpaceModels: otherSpaceModels,
|
||||
spaceTagModels: processedTags,
|
||||
pageContext: pageContext,
|
||||
projectTags: projectTags,
|
||||
spaceModel: SpaceTemplateModel(
|
||||
modelName: spaceName,
|
||||
tags: updatedTags,
|
||||
uuid: spaceModel?.uuid,
|
||||
internalId: spaceModel?.internalId,
|
||||
subspaceModels: processedSubspaces)),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: DefaultButton(
|
||||
borderRadius: 10,
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
foregroundColor: state.isSaveEnabled
|
||||
? ColorsManager.whiteColors
|
||||
: ColorsManager.whiteColorsWithOpacity,
|
||||
onPressed: state.isSaveEnabled
|
||||
? () async {
|
||||
final updatedTags = List<Tag>.from(state.tags);
|
||||
|
||||
final result = TagHelper.updateSubspaceTagModels(
|
||||
updatedTags, subspaces);
|
||||
|
||||
final processedTags =
|
||||
result['updatedTags'] as List<Tag>;
|
||||
final processedSubspaces =
|
||||
List<SubspaceTemplateModel>.from(
|
||||
result['subspaces'] as List<dynamic>);
|
||||
|
||||
Navigator.of(context)
|
||||
.popUntil((route) => route.isFirst);
|
||||
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext dialogContext) {
|
||||
return CreateSpaceModelDialog(
|
||||
products: products,
|
||||
allSpaceModels: allSpaceModels,
|
||||
allTags: allTags,
|
||||
projectTags: projectTags,
|
||||
pageContext: pageContext,
|
||||
otherSpaceModels: otherSpaceModels,
|
||||
spaceModel: SpaceTemplateModel(
|
||||
modelName: spaceName,
|
||||
tags: processedTags,
|
||||
uuid: spaceModel?.uuid,
|
||||
internalId: spaceModel?.internalId,
|
||||
subspaceModels: processedSubspaces),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
: null,
|
||||
child: const Text('Save'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,155 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../../../../../common/dialog_dropdown.dart';
|
||||
import '../../../../../common/tag_dialog_textfield_dropdown.dart';
|
||||
import '../../../../../utils/color_manager.dart';
|
||||
|
||||
class AssignTagsTable extends StatelessWidget {
|
||||
const AssignTagsTable({
|
||||
super.key,
|
||||
required this.controllers,
|
||||
required this.locations,
|
||||
required this.tags,
|
||||
required this.updatedTags,
|
||||
required this.onDeleteDevice,
|
||||
required this.onLocationDropDownSelected,
|
||||
required this.onTagDropDownSelected,
|
||||
});
|
||||
final void Function({required Tag tag, required int index})
|
||||
onTagDropDownSelected;
|
||||
final void Function({required String location, required int index})
|
||||
onLocationDropDownSelected;
|
||||
final void Function({required Tag tag, required int index}) onDeleteDevice;
|
||||
final List<Tag> tags;
|
||||
final List<Tag> updatedTags;
|
||||
final List<TextEditingController> controllers;
|
||||
final List<String> locations;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: DataTable(
|
||||
headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey),
|
||||
key: ValueKey(tags.length),
|
||||
border: TableBorder.all(
|
||||
color: ColorsManager.dataHeaderGrey,
|
||||
width: 1,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
columns: [
|
||||
DataColumn(
|
||||
label: Text('#', style: Theme.of(context).textTheme.bodyMedium)),
|
||||
DataColumn(
|
||||
label: Text('Device',
|
||||
style: Theme.of(context).textTheme.bodyMedium)),
|
||||
DataColumn(
|
||||
numeric: false,
|
||||
label:
|
||||
Text('Tag', style: Theme.of(context).textTheme.bodyMedium)),
|
||||
DataColumn(
|
||||
label: Text('Location',
|
||||
style: Theme.of(context).textTheme.bodyMedium)),
|
||||
],
|
||||
rows: tags.isEmpty
|
||||
? [
|
||||
DataRow(cells: [
|
||||
DataCell(
|
||||
Center(
|
||||
child: Text('No Devices Available',
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
)),
|
||||
),
|
||||
),
|
||||
const DataCell(SizedBox()),
|
||||
const DataCell(SizedBox()),
|
||||
const DataCell(SizedBox()),
|
||||
])
|
||||
]
|
||||
: List.generate(tags.length, (index) {
|
||||
final tag = tags[index];
|
||||
final controller = controllers[index];
|
||||
|
||||
return DataRow(
|
||||
cells: [
|
||||
DataCell(Text((index + 1).toString())),
|
||||
DataCell(
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
tag.product?.name ?? 'Unknown',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)),
|
||||
const SizedBox(width: 10),
|
||||
Container(
|
||||
width: 20.0,
|
||||
height: 20.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
child: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: ColorsManager.lightGreyColor,
|
||||
size: 16,
|
||||
),
|
||||
onPressed: () {
|
||||
onDeleteDevice(tag: tag, index: index);
|
||||
},
|
||||
tooltip: 'Delete Tag',
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
Container(
|
||||
alignment: Alignment
|
||||
.centerLeft, // Align cell content to the left
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: TagDialogTextfieldDropdown(
|
||||
key: ValueKey(
|
||||
'dropdown_${const Uuid().v4()}_$index'),
|
||||
product: tag.product?.uuid ?? 'Unknown',
|
||||
items: updatedTags,
|
||||
initialValue: tag,
|
||||
onSelected: (value) {
|
||||
controller.text = value.tag ?? '';
|
||||
onTagDropDownSelected(tag: value, index: index);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: DialogDropdown(
|
||||
items: locations,
|
||||
selectedValue: tag.location ?? 'Main Space',
|
||||
onSelected: (value) {
|
||||
onLocationDropDownSelected(
|
||||
location: value, index: index);
|
||||
},
|
||||
)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -77,7 +77,7 @@ class SubSpaceBloc extends Bloc<SubSpaceEvent, SubSpaceState> {
|
||||
emit(SubSpaceState(
|
||||
updatedSubSpaces,
|
||||
updatedSubspaceModels,
|
||||
errorMessage,
|
||||
errorMessage ,
|
||||
updatedDuplicates,
|
||||
));
|
||||
});
|
||||
|
@ -1,23 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.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/all_spaces/model/product_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_event.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_state.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
import 'widgets/ok_cancel_sub_space_widget.dart';
|
||||
import 'widgets/textfield_sub_space_dialog_widget.dart';
|
||||
|
||||
class CreateSubSpaceDialog extends StatefulWidget {
|
||||
final String dialogTitle;
|
||||
final List<SubspaceModel>? existingSubSpaces;
|
||||
final String? spaceName;
|
||||
final List<ProductModel>? products;
|
||||
final void Function(List<SubspaceModel>?)? onSave;
|
||||
final void Function(
|
||||
List<SubspaceModel>?, List<UpdateSubspaceModel> updatedSubSpaces)? onSave;
|
||||
|
||||
const CreateSubSpaceDialog({
|
||||
required this.dialogTitle,
|
||||
@ -33,14 +33,7 @@ class CreateSubSpaceDialog extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _CreateSubSpaceDialogState extends State<CreateSubSpaceDialog> {
|
||||
late final TextEditingController _subspaceNameController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_subspaceNameController = TextEditingController();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
final TextEditingController _subspaceNameController = TextEditingController();
|
||||
@override
|
||||
void dispose() {
|
||||
_subspaceNameController.dispose();
|
||||
@ -79,83 +72,26 @@ class _CreateSubSpaceDialogState extends State<CreateSubSpaceDialog> {
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'press Enter to Save',
|
||||
style: context.textTheme.headlineLarge?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 10,
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 5,
|
||||
),
|
||||
const Icon(Icons.save_as_sharp, size: 10),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
width: context.screenWidth * 0.35,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10,
|
||||
horizontal: 16,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.boxColor,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
alignment: WrapAlignment.start,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
...state.subSpaces.asMap().entries.map(
|
||||
(entry) {
|
||||
final index = entry.key;
|
||||
final subSpace = entry.value;
|
||||
|
||||
final lowerName = subSpace.subspaceName.toLowerCase();
|
||||
|
||||
final duplicateIndices = state.subSpaces
|
||||
.asMap()
|
||||
.entries
|
||||
.where((e) =>
|
||||
e.value.subspaceName.toLowerCase() == lowerName)
|
||||
.map((e) => e.key)
|
||||
.toList();
|
||||
final isDuplicate = duplicateIndices.length > 1 &&
|
||||
duplicateIndices.indexOf(index) != 0;
|
||||
return SubspaceChip(
|
||||
subSpace: SubspaceTemplateModel(
|
||||
subspaceName: entry.value.subspaceName,
|
||||
disabled: entry.value.disabled,
|
||||
),
|
||||
isDuplicate: isDuplicate,
|
||||
onDeleted: () => context.read<SubSpaceBloc>().add(
|
||||
RemoveSubSpace(subSpace),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: TextField(
|
||||
controller: _subspaceNameController,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: state.subSpaces.isEmpty
|
||||
? 'Please enter the name'
|
||||
: null,
|
||||
hintStyle: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
),
|
||||
),
|
||||
onSubmitted: (value) {
|
||||
final trimmedValue = value.trim();
|
||||
if (trimmedValue.isNotEmpty) {
|
||||
context.read<SubSpaceBloc>().add(
|
||||
AddSubSpace(
|
||||
SubspaceModel(
|
||||
subspaceName: trimmedValue,
|
||||
disabled: false,
|
||||
),
|
||||
),
|
||||
);
|
||||
_subspaceNameController.clear();
|
||||
}
|
||||
},
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
TextFieldSubSpaceDialogWidget(
|
||||
subspaceNameController: _subspaceNameController,
|
||||
subSpaces: state.subSpaces,
|
||||
),
|
||||
if (state.errorMessage.isNotEmpty)
|
||||
Padding(
|
||||
@ -168,36 +104,10 @@ class _CreateSubSpaceDialogState extends State<CreateSubSpaceDialog> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CancelButton(
|
||||
label: 'Cancel',
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: DefaultButton(
|
||||
onPressed: state.errorMessage.isEmpty
|
||||
? () {
|
||||
final subSpacesBloc = context.read<SubSpaceBloc>();
|
||||
final subSpaces = subSpacesBloc.state.subSpaces;
|
||||
widget.onSave?.call(subSpaces);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
: null,
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
borderRadius: 10,
|
||||
foregroundColor: state.errorMessage.isNotEmpty
|
||||
? ColorsManager.whiteColorsWithOpacity
|
||||
: ColorsManager.whiteColors,
|
||||
child: const Text('OK'),
|
||||
),
|
||||
),
|
||||
],
|
||||
OkCancelSubSpaceWidget(
|
||||
subspaceNameController: _subspaceNameController,
|
||||
errorMessage: state.errorMessage,
|
||||
onSave: widget.onSave,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -0,0 +1,75 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.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';
|
||||
|
||||
import '../../bloc/subspace_bloc.dart';
|
||||
import '../../bloc/subspace_event.dart';
|
||||
|
||||
class OkCancelSubSpaceWidget extends StatelessWidget {
|
||||
const OkCancelSubSpaceWidget({
|
||||
super.key,
|
||||
required this.subspaceNameController,
|
||||
required this.onSave,
|
||||
required this.errorMessage,
|
||||
});
|
||||
|
||||
final TextEditingController subspaceNameController;
|
||||
final void Function(
|
||||
List<SubspaceModel>?, List<UpdateSubspaceModel> updatedSubSpaces)? onSave;
|
||||
final String errorMessage;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CancelButton(
|
||||
label: 'Cancel',
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: DefaultButton(
|
||||
onPressed: errorMessage.isEmpty
|
||||
? () async {
|
||||
final trimmedValue = subspaceNameController.text.trim();
|
||||
|
||||
final subSpacesBloc = context.read<SubSpaceBloc>();
|
||||
if (trimmedValue.isNotEmpty) {
|
||||
subSpacesBloc.add(
|
||||
AddSubSpace(
|
||||
SubspaceModel(
|
||||
subspaceName: trimmedValue,
|
||||
disabled: false,
|
||||
),
|
||||
),
|
||||
);
|
||||
subspaceNameController.clear();
|
||||
}
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 10));
|
||||
final subSpaces = subSpacesBloc.state.subSpaces;
|
||||
|
||||
onSave?.call(
|
||||
subSpaces, subSpacesBloc.state.updatedSubSpaceModels);
|
||||
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
: null,
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
borderRadius: 10,
|
||||
foregroundColor: errorMessage.isNotEmpty
|
||||
? ColorsManager.whiteColorsWithOpacity
|
||||
: ColorsManager.whiteColors,
|
||||
child: const Text('OK'),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
import '../../../../../utils/color_manager.dart';
|
||||
import '../../../all_spaces/model/subspace_model.dart';
|
||||
import '../../../create_subspace_model/widgets/subspace_chip.dart';
|
||||
import '../../../space_model/models/subspace_template_model.dart';
|
||||
import '../../bloc/subspace_bloc.dart';
|
||||
import '../../bloc/subspace_event.dart';
|
||||
|
||||
class TextFieldSubSpaceDialogWidget extends StatelessWidget {
|
||||
const TextFieldSubSpaceDialogWidget({
|
||||
super.key,
|
||||
required TextEditingController subspaceNameController,
|
||||
required this.subSpaces,
|
||||
}) : _subspaceNameController = subspaceNameController;
|
||||
|
||||
final TextEditingController _subspaceNameController;
|
||||
final List<SubspaceModel> subSpaces;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: context.screenWidth * 0.35,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10,
|
||||
horizontal: 16,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.boxColor,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
alignment: WrapAlignment.start,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
...subSpaces.asMap().entries.map(
|
||||
(entry) {
|
||||
final index = entry.key;
|
||||
final subSpace = entry.value;
|
||||
|
||||
final lowerName = subSpace.subspaceName.toLowerCase();
|
||||
|
||||
final duplicateIndices = subSpaces
|
||||
.asMap()
|
||||
.entries
|
||||
.where((e) => e.value.subspaceName.toLowerCase() == lowerName)
|
||||
.map((e) => e.key)
|
||||
.toList();
|
||||
final isDuplicate = duplicateIndices.length > 1 &&
|
||||
duplicateIndices.indexOf(index) != 0;
|
||||
return SubspaceChip(
|
||||
subSpace: SubspaceTemplateModel(
|
||||
subspaceName: entry.value.subspaceName,
|
||||
disabled: entry.value.disabled,
|
||||
),
|
||||
isDuplicate: isDuplicate,
|
||||
onDeleted: () => context.read<SubSpaceBloc>().add(
|
||||
RemoveSubSpace(subSpace),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: TextField(
|
||||
controller: _subspaceNameController,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: subSpaces.isEmpty ? 'Please enter the name' : null,
|
||||
hintStyle: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
),
|
||||
),
|
||||
onSubmitted: (value) {
|
||||
final trimmedValue = value.trim();
|
||||
if (trimmedValue.isNotEmpty) {
|
||||
context.read<SubSpaceBloc>().add(
|
||||
AddSubSpace(
|
||||
SubspaceModel(
|
||||
subspaceName: trimmedValue,
|
||||
disabled: false,
|
||||
),
|
||||
),
|
||||
);
|
||||
_subspaceNameController.clear();
|
||||
}
|
||||
},
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -14,11 +14,11 @@ class CenterBodyWidget extends StatelessWidget {
|
||||
if (state is InitialState) {
|
||||
context.read<CenterBodyBloc>().add(CommunityStructureSelectedEvent());
|
||||
}
|
||||
if (state is CommunityStructureState) {
|
||||
if (state is CommunityStructureState) {
|
||||
context.read<SpaceManagementBloc>().add(BlankStateEvent(context));
|
||||
}
|
||||
|
||||
if (state is SpaceModelState) {
|
||||
if (state is SpaceModelState) {
|
||||
context.read<SpaceManagementBloc>().add(SpaceModelLoadEvent(context));
|
||||
}
|
||||
|
||||
@ -30,16 +30,23 @@ class CenterBodyWidget extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.read<CenterBodyBloc>().add(CommunityStructureSelectedEvent());
|
||||
},
|
||||
onTap: state is CommunityStructureState ||
|
||||
state is CommunitySelectedState
|
||||
? null
|
||||
: () {
|
||||
context
|
||||
.read<CenterBodyBloc>()
|
||||
.add(CommunityStructureSelectedEvent());
|
||||
},
|
||||
child: Text(
|
||||
'Community Structure',
|
||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||
fontWeight: state is CommunityStructureState || state is CommunitySelectedState
|
||||
fontWeight: state is CommunityStructureState ||
|
||||
state is CommunitySelectedState
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
color: state is CommunityStructureState || state is CommunitySelectedState
|
||||
color: state is CommunityStructureState ||
|
||||
state is CommunitySelectedState
|
||||
? Theme.of(context).textTheme.bodyLarge!.color
|
||||
: Theme.of(context)
|
||||
.textTheme
|
||||
@ -50,26 +57,26 @@ class CenterBodyWidget extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.read<CenterBodyBloc>().add(SpaceModelSelectedEvent());
|
||||
},
|
||||
child: Text(
|
||||
'Space Model',
|
||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||
fontWeight: state is SpaceModelState
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
color: state is SpaceModelState
|
||||
? Theme.of(context).textTheme.bodyLarge!.color
|
||||
: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.color!
|
||||
.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
// GestureDetector(
|
||||
// onTap: () {
|
||||
// context.read<CenterBodyBloc>().add(SpaceModelSelectedEvent());
|
||||
// },
|
||||
// child: Text(
|
||||
// 'Space Model',
|
||||
// style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||
// fontWeight: state is SpaceModelState
|
||||
// ? FontWeight.bold
|
||||
// : FontWeight.normal,
|
||||
// color: state is SpaceModelState
|
||||
// ? Theme.of(context).textTheme.bodyLarge!.color
|
||||
// : Theme.of(context)
|
||||
// .textTheme
|
||||
// .bodyLarge!
|
||||
// .color!
|
||||
// .withOpacity(0.5),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
@ -24,37 +24,22 @@ class AddDeviceTypeModelBloc
|
||||
|
||||
if (currentState is AddDeviceModelLoaded) {
|
||||
final existingProduct = currentState.selectedProducts.firstWhere(
|
||||
(p) => p.productId == event.productId,
|
||||
orElse: () => SelectedProduct(
|
||||
productId: event.productId,
|
||||
count: 0,
|
||||
productName: event.productName,
|
||||
product: event.product,
|
||||
),
|
||||
(p) => p.productId == event.selectedProduct.productId,
|
||||
orElse: () => event.selectedProduct,
|
||||
);
|
||||
|
||||
List<SelectedProduct> updatedProducts;
|
||||
|
||||
if (event.count > 0) {
|
||||
if (event.selectedProduct.count > 0) {
|
||||
if (!currentState.selectedProducts.contains(existingProduct)) {
|
||||
updatedProducts = [
|
||||
...currentState.selectedProducts,
|
||||
SelectedProduct(
|
||||
productId: event.productId,
|
||||
count: event.count,
|
||||
productName: event.productName,
|
||||
product: event.product,
|
||||
),
|
||||
event.selectedProduct,
|
||||
];
|
||||
} else {
|
||||
updatedProducts = currentState.selectedProducts.map((p) {
|
||||
if (p.productId == event.productId) {
|
||||
return SelectedProduct(
|
||||
productId: p.productId,
|
||||
count: event.count,
|
||||
productName: p.productName,
|
||||
product: p.product,
|
||||
);
|
||||
if (p.productId == event.selectedProduct.productId) {
|
||||
return event.selectedProduct;
|
||||
}
|
||||
return p;
|
||||
}).toList();
|
||||
@ -62,7 +47,7 @@ class AddDeviceTypeModelBloc
|
||||
} else {
|
||||
// Remove the product if the count is 0
|
||||
updatedProducts = currentState.selectedProducts
|
||||
.where((p) => p.productId != event.productId)
|
||||
.where((p) => p.productId != event.selectedProduct.productId)
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
@ -10,20 +10,15 @@ abstract class AddDeviceTypeModelEvent extends Equatable {
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
|
||||
class UpdateProductCountEvent extends AddDeviceTypeModelEvent {
|
||||
final String productId;
|
||||
final int count;
|
||||
final String productName;
|
||||
final ProductModel product;
|
||||
final SelectedProduct selectedProduct;
|
||||
|
||||
UpdateProductCountEvent({required this.productId, required this.count, required this.productName, required this.product});
|
||||
UpdateProductCountEvent({required this.selectedProduct});
|
||||
|
||||
@override
|
||||
List<Object> get props => [productId, count];
|
||||
List<Object> get props => [selectedProduct];
|
||||
}
|
||||
|
||||
|
||||
class InitializeDeviceTypeModel extends AddDeviceTypeModelEvent {
|
||||
final List<Tag> initialTags;
|
||||
final List<SelectedProduct> addedProducts;
|
||||
|
@ -54,10 +54,10 @@ class DeviceTypeTileWidget extends StatelessWidget {
|
||||
onCountChanged: (newCount) {
|
||||
context.read<AddDeviceTypeModelBloc>().add(
|
||||
UpdateProductCountEvent(
|
||||
productId: product.uuid,
|
||||
count: newCount,
|
||||
productName: product.catName,
|
||||
product: product),
|
||||
selectedProduct: product.toSelectedProduct(
|
||||
newCount,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -22,6 +22,18 @@ class HTTPService {
|
||||
);
|
||||
|
||||
client.interceptors.add(serviceLocator.get<HTTPInterceptor>());
|
||||
// Add this interceptor for logging requests and responses
|
||||
// client.interceptors.add(
|
||||
// LogInterceptor(
|
||||
// request: true,
|
||||
// requestHeader: true,
|
||||
// requestBody: true,
|
||||
// responseHeader: false,
|
||||
// responseBody: true,
|
||||
// error: true,
|
||||
// logPrint: (object) => print(object),
|
||||
// ),
|
||||
// );
|
||||
return client;
|
||||
}
|
||||
|
||||
|
@ -5,12 +5,15 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_m
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_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/pages/spaces_management/all_spaces/model/tag.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
|
||||
import '../pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||
|
||||
class CommunitySpaceManagementApi {
|
||||
// Community Management APIs
|
||||
Future<List<CommunityModel>> fetchCommunities(String projectId,
|
||||
@ -216,7 +219,6 @@ class CommunitySpaceManagementApi {
|
||||
{required String communityId,
|
||||
required String name,
|
||||
String? parentId,
|
||||
String? direction,
|
||||
bool isPrivate = false,
|
||||
required Offset position,
|
||||
String? spaceModelUuid,
|
||||
@ -230,7 +232,6 @@ class CommunitySpaceManagementApi {
|
||||
'isPrivate': isPrivate,
|
||||
'x': position.dx,
|
||||
'y': position.dy,
|
||||
'direction': direction,
|
||||
'icon': icon,
|
||||
};
|
||||
if (parentId != null) {
|
||||
@ -265,11 +266,10 @@ class CommunitySpaceManagementApi {
|
||||
required String name,
|
||||
String? parentId,
|
||||
String? icon,
|
||||
String? direction,
|
||||
bool isPrivate = false,
|
||||
required Offset position,
|
||||
List<TagModelUpdate>? tags,
|
||||
List<UpdateSubspaceTemplateModel>? subspaces,
|
||||
List<Tag>? tags,
|
||||
List<SubspaceModel>? subspaces,
|
||||
String? spaceModelUuid,
|
||||
required String projectId}) async {
|
||||
try {
|
||||
@ -278,9 +278,8 @@ class CommunitySpaceManagementApi {
|
||||
'isPrivate': isPrivate,
|
||||
'x': position.dx,
|
||||
'y': position.dy,
|
||||
'direction': direction,
|
||||
'icon': icon,
|
||||
'subspace': subspaces,
|
||||
'subspaces': subspaces,
|
||||
'tags': tags,
|
||||
'spaceModelUuid': spaceModelUuid,
|
||||
};
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||
@ -7,17 +9,23 @@ import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
class SpaceModelManagementApi {
|
||||
Future<List<SpaceTemplateModel>> listSpaceModels(
|
||||
{required String projectId, int page = 1}) async {
|
||||
final response = await HTTPService().get(
|
||||
path: ApiEndpoints.listSpaceModels.replaceAll('{projectId}', projectId),
|
||||
queryParameters: {'page': page},
|
||||
expectedResponseModel: (json) {
|
||||
List<dynamic> jsonData = json['data'];
|
||||
return jsonData.map((jsonItem) {
|
||||
return SpaceTemplateModel.fromJson(jsonItem);
|
||||
}).toList();
|
||||
},
|
||||
);
|
||||
return response;
|
||||
try {
|
||||
// final response = await HTTPService().get(
|
||||
// path: ApiEndpoints.listSpaceModels.replaceAll('{projectId}', projectId),
|
||||
// queryParameters: {'page': page},
|
||||
// expectedResponseModel: (json) {
|
||||
// List<dynamic> jsonData = json['data'];
|
||||
// return jsonData.map((jsonItem) {
|
||||
// return SpaceTemplateModel.fromJson(jsonItem);
|
||||
// }).toList();
|
||||
// },
|
||||
// );
|
||||
return [];
|
||||
// response;
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<SpaceTemplateModel?> createSpaceModel(
|
||||
@ -33,8 +41,8 @@ class SpaceModelManagementApi {
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<String?> updateSpaceModel(
|
||||
CreateSpaceTemplateBodyModel spaceModel, String spaceModelUuid, String projectId) async {
|
||||
Future<String?> updateSpaceModel(CreateSpaceTemplateBodyModel spaceModel,
|
||||
String spaceModelUuid, String projectId) async {
|
||||
final response = await HTTPService().put(
|
||||
path: ApiEndpoints.updateSpaceModel
|
||||
.replaceAll('{projectId}', projectId)
|
||||
@ -47,7 +55,8 @@ class SpaceModelManagementApi {
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<SpaceTemplateModel?> getSpaceModel(String spaceModelUuid, String projectId) async {
|
||||
Future<SpaceTemplateModel?> getSpaceModel(
|
||||
String spaceModelUuid, String projectId) async {
|
||||
final response = await HTTPService().get(
|
||||
path: ApiEndpoints.getSpaceModel
|
||||
.replaceAll('{projectId}', projectId)
|
||||
|
@ -15,7 +15,7 @@ import shared_preferences_foundation
|
||||
import url_launcher_macos
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FLTFirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAnalyticsPlugin"))
|
||||
FirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FirebaseAnalyticsPlugin"))
|
||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||
FLTFirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCrashlyticsPlugin"))
|
||||
FLTFirebaseDatabasePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseDatabasePlugin"))
|
||||
|
@ -69,12 +69,7 @@ dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
# The "flutter_lints" package below contains a set of recommended lints to
|
||||
# encourage good coding practices. The lint set provided by the package is
|
||||
# activated in the `analysis_options.yaml` file located at the root of your
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^5.0.0
|
||||
very_good_analysis: ^9.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
Reference in New Issue
Block a user