mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 15:17:31 +00:00
Compare commits
16 Commits
upgrade-fl
...
SP-1732-FE
Author | SHA1 | Date | |
---|---|---|---|
1da0cdad4b | |||
5b3152e833 | |||
c1d3296b59 | |||
b3069ab749 | |||
8d408867bb | |||
57508fe17e | |||
13360fe6f3 | |||
3e5b501167 | |||
4d9f08af31 | |||
28aa3bc406 | |||
51ad74b2be | |||
994e9f4e57 | |||
c642ba2644 | |||
218f43bacb | |||
04250ebc98 | |||
29959f567e |
@ -25,3 +25,8 @@ linter:
|
||||
prefer_int_literals: false
|
||||
sort_constructors_first: false
|
||||
avoid_redundant_argument_values: false
|
||||
always_put_required_named_parameters_first: false
|
||||
unnecessary_breaks: false
|
||||
avoid_catches_without_on_clauses: false
|
||||
cascade_invocations: false
|
||||
overridden_fields: false
|
||||
|
@ -34,8 +34,8 @@ class OccupancyHeatMapGradient extends StatelessWidget {
|
||||
width: 1,
|
||||
),
|
||||
gradient: LinearGradient(
|
||||
begin: AlignmentDirectional.centerEnd,
|
||||
end: AlignmentDirectional.centerStart,
|
||||
begin: AlignmentDirectional.centerStart,
|
||||
end: AlignmentDirectional.centerEnd,
|
||||
colors: _heatMapColors(),
|
||||
),
|
||||
),
|
||||
|
@ -28,11 +28,11 @@ class OccupancyPainter extends CustomPainter {
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final Paint fillPaint = Paint();
|
||||
final Paint borderPaint = Paint()
|
||||
final fillPaint = Paint();
|
||||
final borderPaint = Paint()
|
||||
..color = ColorsManager.grayBorder.withValues(alpha: 0.4)
|
||||
..style = PaintingStyle.stroke;
|
||||
final Paint hoveredBorderPaint = Paint()
|
||||
final hoveredBorderPaint = Paint()
|
||||
..color = Colors.black
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.5;
|
||||
@ -48,7 +48,6 @@ class OccupancyPainter extends CustomPainter {
|
||||
final rect = Rect.fromLTWH(x, y, cellSize, cellSize);
|
||||
canvas.drawRect(rect, fillPaint);
|
||||
|
||||
// Highlight the hovered item
|
||||
if (hoveredItem != null && hoveredItem!.index == item.index) {
|
||||
canvas.drawRect(rect, hoveredBorderPaint);
|
||||
} else {
|
||||
@ -73,16 +72,16 @@ class OccupancyPainter extends CustomPainter {
|
||||
}
|
||||
|
||||
void _drawDashedLine(Canvas canvas, Offset start, Offset end, Paint paint) {
|
||||
const double dashWidth = 2.0;
|
||||
const double dashSpace = 4.0;
|
||||
final double totalLength = (end - start).distance;
|
||||
final Offset direction = (end - start) / (end - start).distance;
|
||||
const dashWidth = 2.0;
|
||||
const dashSpace = 4.0;
|
||||
final totalLength = (end - start).distance;
|
||||
final direction = (end - start) / (end - start).distance;
|
||||
|
||||
double currentLength = 0.0;
|
||||
var currentLength = 0.0;
|
||||
while (currentLength < totalLength) {
|
||||
final Offset dashStart = start + direction * currentLength;
|
||||
final double nextLength = currentLength + dashWidth;
|
||||
final Offset dashEnd =
|
||||
final dashStart = start + direction * currentLength;
|
||||
final nextLength = currentLength + dashWidth;
|
||||
final dashEnd =
|
||||
start + direction * (nextLength < totalLength ? nextLength : totalLength);
|
||||
canvas.drawLine(dashStart, dashEnd, paint);
|
||||
currentLength = nextLength + dashSpace;
|
||||
@ -91,8 +90,9 @@ class OccupancyPainter extends CustomPainter {
|
||||
|
||||
Color _getColor(int value) {
|
||||
if (maxValue == 0) return ColorsManager.vividBlue.withValues(alpha: 0);
|
||||
final opacity = value.clamp(0, maxValue) / maxValue;
|
||||
return ColorsManager.vividBlue.withValues(alpha: opacity);
|
||||
final clampedValue = 0.075 + (1 * value.clamp(0, maxValue) / maxValue);
|
||||
final opacity = value == 0 ? 0 : clampedValue;
|
||||
return ColorsManager.vividBlue.withValues(alpha: opacity.toDouble());
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -78,7 +78,7 @@ class CustomWebTextField extends StatelessWidget {
|
||||
controller: controller,
|
||||
style: const TextStyle(color: Colors.black),
|
||||
decoration: textBoxDecoration()!.copyWith(
|
||||
errorStyle: const TextStyle(height: 0),
|
||||
errorStyle: const TextStyle(height: 0.01),
|
||||
hintStyle: context.textTheme.titleSmall!
|
||||
.copyWith(color: Colors.grey, fontSize: 12),
|
||||
hintText: hintText ?? 'Please enter'),
|
||||
|
@ -40,7 +40,7 @@ class HomeCard extends StatelessWidget {
|
||||
child: Text(
|
||||
name,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontSize: 30,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
|
@ -97,7 +97,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
|
||||
itemCount: homeBloc.homeItems.length,
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3, // Adjust as needed.
|
||||
crossAxisCount: 4, // Adjust as needed.
|
||||
crossAxisSpacing: 20.0,
|
||||
mainAxisSpacing: 20.0,
|
||||
childAspectRatio: 1.5,
|
||||
|
@ -11,7 +11,6 @@ class CreateRoutineBloc extends Bloc<CreateRoutineEvent, CreateRoutineState> {
|
||||
on<SpaceOnlyWithDevicesEvent>(_fetchSpaceOnlyWithDevices);
|
||||
on<SaveCommunityIdAndSpaceIdEvent>(saveSpaceIdCommunityId);
|
||||
on<ResetSelectedEvent>(resetSelected);
|
||||
on<FetchCommunityEvent>(_fetchCommunity);
|
||||
}
|
||||
|
||||
String selectedSpaceId = '';
|
||||
@ -50,18 +49,4 @@ class CreateRoutineBloc extends Bloc<CreateRoutineEvent, CreateRoutineState> {
|
||||
selectedCommunityId = '';
|
||||
emit(const ResetSelectedState());
|
||||
}
|
||||
|
||||
Future<void> _fetchCommunity(
|
||||
FetchCommunityEvent event, Emitter<CreateRoutineState> emit) async {
|
||||
emit(const CommunitiesLoadingState());
|
||||
|
||||
try {
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
communities =
|
||||
await CommunitySpaceManagementApi().fetchCommunities(projectUuid);
|
||||
emit(const CommunityLoadedState());
|
||||
} catch (e) {
|
||||
emit(SpaceTreeErrorState('Error loading communities $e'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,9 +43,3 @@ class ResetSelectedEvent extends CreateRoutineEvent {
|
||||
}
|
||||
|
||||
|
||||
class FetchCommunityEvent extends CreateRoutineEvent {
|
||||
const FetchCommunityEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
@ -1,13 +1,11 @@
|
||||
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 SpaceTreeDropdown extends StatefulWidget {
|
||||
class SpaceTreeDropdown extends StatelessWidget {
|
||||
final String? selectedSpaceId;
|
||||
final Function(String?)? onChanged;
|
||||
|
||||
@ -18,23 +16,33 @@ class SpaceTreeDropdown extends StatefulWidget {
|
||||
});
|
||||
|
||||
@override
|
||||
State<SpaceTreeDropdown> createState() => _SpaceTreeDropdownState();
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) {
|
||||
final bloc = SpaceTreeDropdownBloc(selectedSpaceId);
|
||||
bloc.add(FetchSpacesEvent());
|
||||
return bloc;
|
||||
},
|
||||
child: _DropdownContent(onChanged: onChanged),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SpaceTreeDropdownState extends State<SpaceTreeDropdown> {
|
||||
late SpaceTreeDropdownBloc _dropdownBloc;
|
||||
class _DropdownContent extends StatefulWidget {
|
||||
final Function(String?)? onChanged;
|
||||
|
||||
const _DropdownContent({this.onChanged});
|
||||
|
||||
@override
|
||||
State<_DropdownContent> createState() => _DropdownContentState();
|
||||
}
|
||||
|
||||
class _DropdownContentState extends State<_DropdownContent> {
|
||||
final LayerLink _layerLink = LayerLink();
|
||||
OverlayEntry? _overlayEntry;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_dropdownBloc = SpaceTreeDropdownBloc(widget.selectedSpaceId);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_dropdownBloc.close();
|
||||
_removeOverlay();
|
||||
super.dispose();
|
||||
}
|
||||
@ -46,100 +54,120 @@ class _SpaceTreeDropdownState extends State<SpaceTreeDropdown> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _dropdownBloc,
|
||||
child: BlocBuilder<SpaceTreeBloc, SpaceTreeState>(
|
||||
builder: (context, spaceTreeState) {
|
||||
final communities = spaceTreeState.searchQuery.isNotEmpty
|
||||
? spaceTreeState.filteredCommunity
|
||||
: spaceTreeState.communityList;
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
CompositedTransformTarget(
|
||||
link: _layerLink,
|
||||
child: GestureDetector(
|
||||
onTap: () => _toggleDropdown(context),
|
||||
child: BlocBuilder<SpaceTreeDropdownBloc, SpaceTreeDropdownState>(
|
||||
builder: (context, state) {
|
||||
return _buildDropdownTrigger(state);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return BlocBuilder<SpaceTreeDropdownBloc, SpaceTreeDropdownState>(
|
||||
builder: (context, dropdownState) {
|
||||
final selectedCommunity = _findCommunity(
|
||||
communities,
|
||||
dropdownState.selectedSpaceId,
|
||||
);
|
||||
Widget _buildDropdownTrigger(SpaceTreeDropdownState state) {
|
||||
if (state.status == SpaceTreeDropdownStatus.loading) {
|
||||
return Container(
|
||||
height: 46,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: const Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
if (state.status == SpaceTreeDropdownStatus.failure) {
|
||||
return Container(
|
||||
height: 46,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Error: ${state.errorMessage}',
|
||||
style: const TextStyle(color: Colors.red),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final selectedCommunity = _findCommunity(state, state.selectedSpaceId);
|
||||
|
||||
return 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _toggleDropdown(BuildContext context, List<CommunityModel> communities) {
|
||||
void _toggleDropdown(BuildContext context) {
|
||||
if (_overlayEntry != null) {
|
||||
_removeOverlay();
|
||||
return;
|
||||
}
|
||||
|
||||
final bloc = context.read<SpaceTreeDropdownBloc>();
|
||||
|
||||
_overlayEntry = OverlayEntry(
|
||||
builder: (context) => Positioned(
|
||||
width: 300,
|
||||
@ -148,18 +176,22 @@ class _SpaceTreeDropdownState extends State<SpaceTreeDropdown> {
|
||||
showWhenUnlinked: false,
|
||||
offset: const Offset(0, 48),
|
||||
child: Material(
|
||||
color: ColorsManager.whiteColors,
|
||||
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,
|
||||
child: BlocProvider.value(
|
||||
value: bloc,
|
||||
child: DropdownMenuContent(
|
||||
selectedSpaceId: bloc.state.selectedSpaceId,
|
||||
onChanged: (id) {
|
||||
if (id != null && mounted) {
|
||||
bloc.add(SpaceTreeDropdownSelectEvent(id));
|
||||
widget.onChanged?.call(id);
|
||||
_removeOverlay();
|
||||
}
|
||||
},
|
||||
onClose: _removeOverlay,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -170,10 +202,13 @@ class _SpaceTreeDropdownState extends State<SpaceTreeDropdown> {
|
||||
}
|
||||
|
||||
CommunityModel? _findCommunity(
|
||||
List<CommunityModel> communities, String? communityId) {
|
||||
SpaceTreeDropdownState state, String? communityId) {
|
||||
if (communityId == null) return null;
|
||||
try {
|
||||
return communities.firstWhere((c) => c.uuid == communityId);
|
||||
return state.filteredCommunities.firstWhere((c) => c.uuid == communityId);
|
||||
} catch (_) {}
|
||||
try {
|
||||
return state.communities.firstWhere((c) => c.uuid == communityId);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
@ -23,8 +23,7 @@ class _CreateNewRoutinesDialogState extends State<CreateNewRoutinesDialog> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (BuildContext context) =>
|
||||
CreateRoutineBloc()..add(const FetchCommunityEvent()),
|
||||
create: (BuildContext context) => CreateRoutineBloc(),
|
||||
child: BlocBuilder<CreateRoutineBloc, CreateRoutineState>(
|
||||
builder: (context, state) {
|
||||
final _bloc = BlocProvider.of<CreateRoutineBloc>(context);
|
||||
|
@ -1,12 +1,7 @@
|
||||
|
||||
|
||||
|
||||
|
||||
import 'dart:async';
|
||||
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';
|
||||
import 'space_tree_dropdown_bloc.dart';
|
||||
|
||||
class DropdownMenuContent extends StatefulWidget {
|
||||
final String? selectedSpaceId;
|
||||
@ -14,6 +9,7 @@ class DropdownMenuContent extends StatefulWidget {
|
||||
final VoidCallback onClose;
|
||||
|
||||
const DropdownMenuContent({
|
||||
super.key,
|
||||
required this.selectedSpaceId,
|
||||
required this.onChanged,
|
||||
required this.onClose,
|
||||
@ -26,6 +22,7 @@ class DropdownMenuContent extends StatefulWidget {
|
||||
class _DropdownMenuContentState extends State<DropdownMenuContent> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
Timer? _debounceTimer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -35,43 +32,49 @@ class _DropdownMenuContentState extends State<DropdownMenuContent> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_debounceTimer?.cancel();
|
||||
_scrollController.dispose();
|
||||
_searchController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onScroll() {
|
||||
final bloc = context.read<SpaceTreeBloc>();
|
||||
final bloc = context.read<SpaceTreeDropdownBloc>();
|
||||
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));
|
||||
if (state.paginationModel?.hasNext == true &&
|
||||
!state.paginationIsLoading) {
|
||||
bloc.add(PaginationEvent());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _handleSearch(String query) {
|
||||
_debounceTimer?.cancel();
|
||||
_debounceTimer = Timer(const Duration(milliseconds: 500), () {
|
||||
context.read<SpaceTreeDropdownBloc>().add(SearchQueryEvent(query));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 300),
|
||||
child: BlocBuilder<SpaceTreeBloc, SpaceTreeState>(
|
||||
child: BlocBuilder<SpaceTreeDropdownBloc, SpaceTreeDropdownState>(
|
||||
builder: (context, state) {
|
||||
final communities = state.searchQuery.isNotEmpty
|
||||
? state.filteredCommunity
|
||||
: state.communityList;
|
||||
? state.filteredCommunities
|
||||
: state.communities;
|
||||
|
||||
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));
|
||||
},
|
||||
onChanged: _handleSearch,
|
||||
style: const TextStyle(fontSize: 14, color: Colors.black),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search for space...',
|
||||
@ -85,7 +88,6 @@ class _DropdownMenuContentState extends State<DropdownMenuContent> {
|
||||
),
|
||||
),
|
||||
),
|
||||
// Community list
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
controller: _scrollController,
|
||||
@ -121,19 +123,12 @@ class _DropdownMenuContentState extends State<DropdownMenuContent> {
|
||||
),
|
||||
),
|
||||
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), () {
|
||||
context
|
||||
.read<SpaceTreeDropdownBloc>()
|
||||
.add(SearchQueryEvent(''));
|
||||
|
||||
widget.onChanged(community.uuid);
|
||||
widget.onClose();
|
||||
// });
|
||||
},
|
||||
);
|
||||
},
|
||||
|
@ -1,5 +1,9 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/model/pagination_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
||||
import 'package:syncrow_web/services/space_mana_api.dart';
|
||||
part 'space_tree_dropdown_event.dart';
|
||||
part 'space_tree_dropdown_state.dart';
|
||||
|
||||
@ -9,19 +13,158 @@ class SpaceTreeDropdownBloc
|
||||
: super(SpaceTreeDropdownState(selectedSpaceId: initialId)) {
|
||||
on<SpaceTreeDropdownSelectEvent>(_onSelect);
|
||||
on<SpaceTreeDropdownResetEvent>(_onReset);
|
||||
on<FetchSpacesEvent>(_fetchSpaces);
|
||||
on<SearchQueryEvent>(_onSearch);
|
||||
on<PaginationEvent>(_onPagination);
|
||||
on<DebouncedSearchEvent>(_onDebouncedSearch);
|
||||
}
|
||||
Timer? _debounceTimer;
|
||||
|
||||
void _onSelect(
|
||||
SpaceTreeDropdownSelectEvent event,
|
||||
Emitter<SpaceTreeDropdownState> emit,
|
||||
) {
|
||||
emit(SpaceTreeDropdownState(selectedSpaceId: event.spaceId));
|
||||
final exists = state.communities.any((c) => c.uuid == event.spaceId);
|
||||
|
||||
if (!exists) {
|
||||
final community = state.filteredCommunities.firstWhere(
|
||||
(c) => c.uuid == event.spaceId,
|
||||
orElse: () => CommunityModel(
|
||||
uuid: event.spaceId!,
|
||||
name: 'Loading...',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
spaces: [],
|
||||
description: ''),
|
||||
);
|
||||
|
||||
emit(state.copyWith(
|
||||
selectedSpaceId: event.spaceId,
|
||||
communities: [...state.communities, community],
|
||||
));
|
||||
} else {
|
||||
emit(state.copyWith(selectedSpaceId: event.spaceId));
|
||||
}
|
||||
}
|
||||
|
||||
void _onReset(
|
||||
SpaceTreeDropdownResetEvent event,
|
||||
Emitter<SpaceTreeDropdownState> emit,
|
||||
) {
|
||||
emit(SpaceTreeDropdownState(selectedSpaceId: event.initialId));
|
||||
emit(state.copyWith(selectedSpaceId: event.initialId));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _fetchSpaces(
|
||||
FetchSpacesEvent event,
|
||||
Emitter<SpaceTreeDropdownState> emit,
|
||||
) async {
|
||||
if (state.status != SpaceTreeDropdownStatus.initial) return;
|
||||
emit(state.copyWith(status: SpaceTreeDropdownStatus.loading));
|
||||
try {
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
final paginationModel = await CommunitySpaceManagementApi()
|
||||
.fetchCommunitiesAndSpaces(projectId: projectUuid, page: 1);
|
||||
|
||||
emit(state.copyWith(
|
||||
status: SpaceTreeDropdownStatus.success,
|
||||
communities: paginationModel.communities,
|
||||
filteredCommunities: paginationModel.communities,
|
||||
paginationModel: paginationModel,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
status: SpaceTreeDropdownStatus.failure,
|
||||
errorMessage: 'Error loading communities: $e',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void _onSearch(
|
||||
SearchQueryEvent event,
|
||||
Emitter<SpaceTreeDropdownState> emit,
|
||||
) {
|
||||
_debounceTimer?.cancel();
|
||||
_debounceTimer = Timer(const Duration(seconds: 1), () {
|
||||
add(DebouncedSearchEvent(event.searchQuery));
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _onDebouncedSearch(
|
||||
DebouncedSearchEvent event,
|
||||
Emitter<SpaceTreeDropdownState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(isSearching: true));
|
||||
|
||||
try {
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
final paginationModel =
|
||||
await CommunitySpaceManagementApi().fetchCommunitiesAndSpaces(
|
||||
projectId: projectUuid,
|
||||
page: 1,
|
||||
search: event.searchQuery,
|
||||
);
|
||||
|
||||
emit(state.copyWith(
|
||||
filteredCommunities: paginationModel.communities,
|
||||
isSearching: false,
|
||||
searchQuery: event.searchQuery,
|
||||
paginationModel: paginationModel,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
isSearching: false,
|
||||
errorMessage: 'Error searching communities: $e',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_debounceTimer?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
Future<void> _onPagination(
|
||||
PaginationEvent event,
|
||||
Emitter<SpaceTreeDropdownState> emit,
|
||||
) async {
|
||||
if (state.paginationIsLoading || state.paginationModel?.hasNext != true) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit(state.copyWith(paginationIsLoading: true));
|
||||
|
||||
try {
|
||||
final nextPage = state.paginationModel!.pageNum;
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
final newPagination = await CommunitySpaceManagementApi()
|
||||
.fetchCommunitiesAndSpaces(projectId: projectUuid, page: nextPage);
|
||||
|
||||
final combinedCommunities = [
|
||||
...state.communities,
|
||||
...newPagination.communities
|
||||
];
|
||||
List<CommunityModel> filteredCommunities;
|
||||
if (state.searchQuery.isNotEmpty) {
|
||||
final query = state.searchQuery.toLowerCase();
|
||||
filteredCommunities = combinedCommunities.where((community) {
|
||||
return community.name.toLowerCase().contains(query);
|
||||
}).toList();
|
||||
} else {
|
||||
filteredCommunities = combinedCommunities;
|
||||
}
|
||||
|
||||
emit(state.copyWith(
|
||||
communities: combinedCommunities,
|
||||
filteredCommunities: filteredCommunities,
|
||||
paginationModel: newPagination,
|
||||
paginationIsLoading: false,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
paginationIsLoading: false,
|
||||
errorMessage: 'Error loading more communities: $e',
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,4 +12,20 @@ class SpaceTreeDropdownResetEvent extends SpaceTreeDropdownEvent {
|
||||
final String? initialId;
|
||||
|
||||
SpaceTreeDropdownResetEvent(this.initialId);
|
||||
}
|
||||
}
|
||||
|
||||
class FetchSpacesEvent extends SpaceTreeDropdownEvent {}
|
||||
|
||||
class SearchQueryEvent extends SpaceTreeDropdownEvent {
|
||||
final String searchQuery;
|
||||
|
||||
SearchQueryEvent(this.searchQuery);
|
||||
}
|
||||
|
||||
class DebouncedSearchEvent extends SpaceTreeDropdownEvent {
|
||||
final String searchQuery;
|
||||
|
||||
DebouncedSearchEvent(this.searchQuery);
|
||||
}
|
||||
|
||||
class PaginationEvent extends SpaceTreeDropdownEvent {}
|
@ -1,7 +1,51 @@
|
||||
part of 'space_tree_dropdown_bloc.dart';
|
||||
|
||||
enum SpaceTreeDropdownStatus { initial, loading, success, failure }
|
||||
|
||||
class SpaceTreeDropdownState {
|
||||
final String? selectedSpaceId;
|
||||
final List<CommunityModel> communities;
|
||||
final List<CommunityModel> filteredCommunities;
|
||||
final SpaceTreeDropdownStatus status;
|
||||
final String? errorMessage;
|
||||
final String searchQuery;
|
||||
final bool paginationIsLoading;
|
||||
final PaginationModel? paginationModel;
|
||||
final bool isSearching;
|
||||
|
||||
SpaceTreeDropdownState({this.selectedSpaceId});
|
||||
SpaceTreeDropdownState({
|
||||
this.selectedSpaceId,
|
||||
this.communities = const [],
|
||||
this.filteredCommunities = const [],
|
||||
this.status = SpaceTreeDropdownStatus.initial,
|
||||
this.errorMessage,
|
||||
this.searchQuery = '',
|
||||
this.paginationIsLoading = false,
|
||||
this.paginationModel,
|
||||
this.isSearching = false,
|
||||
});
|
||||
|
||||
SpaceTreeDropdownState copyWith({
|
||||
String? selectedSpaceId,
|
||||
List<CommunityModel>? communities,
|
||||
List<CommunityModel>? filteredCommunities,
|
||||
SpaceTreeDropdownStatus? status,
|
||||
String? errorMessage,
|
||||
String? searchQuery,
|
||||
bool? paginationIsLoading,
|
||||
PaginationModel? paginationModel,
|
||||
bool? isSearching,
|
||||
}) {
|
||||
return SpaceTreeDropdownState(
|
||||
selectedSpaceId: selectedSpaceId ?? this.selectedSpaceId,
|
||||
communities: communities ?? this.communities,
|
||||
filteredCommunities: filteredCommunities ?? this.filteredCommunities,
|
||||
status: status ?? this.status,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
searchQuery: searchQuery ?? this.searchQuery,
|
||||
paginationIsLoading: paginationIsLoading ?? this.paginationIsLoading,
|
||||
paginationModel: paginationModel ?? this.paginationModel,
|
||||
isSearching: isSearching ?? this.isSearching,
|
||||
);
|
||||
}
|
||||
}
|
@ -118,6 +118,7 @@ class DeviceDialogHelper {
|
||||
uniqueCustomId: data['uniqueCustomId'],
|
||||
deviceSelectedFunctions: deviceSelectedFunctions,
|
||||
device: data['device'],
|
||||
dialogType: dialogType,
|
||||
);
|
||||
case 'NCPS':
|
||||
return FlushPresenceSensor.showFlushFunctionsDialog(
|
||||
|
@ -65,7 +65,9 @@ class ACHelper {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DialogHeader('AC Functions'),
|
||||
DialogHeader(dialogType == 'THEN'
|
||||
? 'AC Functions'
|
||||
: 'AC Conditions'),
|
||||
Expanded(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
|
@ -96,7 +96,9 @@ class _WallPresenceSensorState extends State<FlushPresenceSensor> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DialogHeader('Presence Sensor Condition'),
|
||||
DialogHeader(widget.dialogType == 'THEN'
|
||||
? 'Presence Sensor Functions'
|
||||
: 'Presence Sensor Condition'),
|
||||
Expanded(child: _buildMainContent(context, state)),
|
||||
_buildDialogFooter(context, state),
|
||||
],
|
||||
|
@ -16,9 +16,10 @@ class GatewayDialog extends StatefulWidget {
|
||||
required this.functions,
|
||||
required this.deviceSelectedFunctions,
|
||||
required this.device,
|
||||
required this.dialogType,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String dialogType;
|
||||
final String? uniqueCustomId;
|
||||
final List<DeviceFunction> functions;
|
||||
final List<DeviceFunctionData> deviceSelectedFunctions;
|
||||
@ -55,7 +56,9 @@ class _GatewayDialogState extends State<GatewayDialog> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DialogHeader('Gateway Conditions'),
|
||||
DialogHeader(widget.dialogType == 'THEN'
|
||||
? 'Gateway Functions'
|
||||
: 'Gateway Conditions'),
|
||||
Expanded(child: _buildMainContent(context, state)),
|
||||
_buildDialogFooter(context, state),
|
||||
],
|
||||
|
@ -14,6 +14,7 @@ abstract final class GatewayHelper {
|
||||
required String? uniqueCustomId,
|
||||
required List<DeviceFunctionData> deviceSelectedFunctions,
|
||||
required AllDevicesModel? device,
|
||||
required String dialogType,
|
||||
}) async {
|
||||
return showDialog(
|
||||
context: context,
|
||||
@ -27,6 +28,7 @@ abstract final class GatewayHelper {
|
||||
functions: functions,
|
||||
deviceSelectedFunctions: deviceSelectedFunctions,
|
||||
device: device,
|
||||
dialogType:dialogType,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -59,7 +59,9 @@ class OneGangSwitchHelper {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DialogHeader('1 Gang Light Switch Condition'),
|
||||
DialogHeader(dialogType == 'THEN'
|
||||
? '1 Gang Light Switch Functions'
|
||||
: '1 Gang Light Switch Condition'),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
@ -246,9 +248,9 @@ class OneGangSwitchHelper {
|
||||
withSpecialChar: false,
|
||||
currentCondition: selectedFunctionData?.condition,
|
||||
dialogType: dialogType,
|
||||
sliderRange: (0, 43200),
|
||||
sliderRange: (0, 43200),
|
||||
displayedValue: (initialValue ?? 0).toString(),
|
||||
initialValue: (initialValue ?? 0).toString(),
|
||||
initialValue: (initialValue ?? 0).toString(),
|
||||
onConditionChanged: (condition) {
|
||||
context.read<FunctionBloc>().add(
|
||||
AddFunction(
|
||||
|
@ -98,7 +98,9 @@ class _EnergyClampDialogState extends State<EnergyClampDialog> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DialogHeader('Energy Clamp Conditions'),
|
||||
DialogHeader(widget.dialogType == 'THEN'
|
||||
? 'Energy Clamp Functions'
|
||||
: 'Energy Clamp Conditions'),
|
||||
Expanded(
|
||||
child: Visibility(
|
||||
visible: _functions.isNotEmpty,
|
||||
|
@ -58,7 +58,9 @@ class ThreeGangSwitchHelper {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DialogHeader('3 Gangs Light Switch Condition'),
|
||||
DialogHeader(dialogType == 'THEN'
|
||||
? '3 Gangs Light Switch Functions'
|
||||
: '3 Gangs Light Switch Condition'),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
|
@ -59,7 +59,9 @@ class TwoGangSwitchHelper {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DialogHeader('2 Gangs Light Switch Condition'),
|
||||
DialogHeader(dialogType == 'THEN'
|
||||
? '2 Gangs Light Switch Functions'
|
||||
: '2 Gangs Light Switch Condition'),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
|
@ -63,7 +63,8 @@ class _WallPresenceSensorState extends State<WallPresenceSensor> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_wpsFunctions = widget.functions.whereType<WpsFunctions>().where((function) {
|
||||
_wpsFunctions =
|
||||
widget.functions.whereType<WpsFunctions>().where((function) {
|
||||
if (widget.dialogType == 'THEN') {
|
||||
return function.type == 'THEN' || function.type == 'BOTH';
|
||||
}
|
||||
@ -97,7 +98,9 @@ class _WallPresenceSensorState extends State<WallPresenceSensor> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DialogHeader('Presence Sensor Condition'),
|
||||
DialogHeader(widget.dialogType == 'THEN'
|
||||
? 'Presence Sensor Functions'
|
||||
: 'Presence Sensor Condition'),
|
||||
Expanded(child: _buildMainContent(context, state)),
|
||||
_buildDialogFooter(context, state),
|
||||
],
|
||||
|
@ -93,7 +93,9 @@ class _WaterHeaterDialogRoutinesState extends State<WaterHeaterDialogRoutines> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DialogHeader('Water Heater Condition'),
|
||||
DialogHeader(widget.dialogType == 'THEN'
|
||||
? 'Water Heater Funtions'
|
||||
: 'Water Heater Condition'),
|
||||
Expanded(child: _buildMainContent(context, state)),
|
||||
_buildDialogFooter(context, state),
|
||||
],
|
||||
|
15
linux/flutter/generated_plugin_registrant.h
Normal file
15
linux/flutter/generated_plugin_registrant.h
Normal file
@ -0,0 +1,15 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||
#define GENERATED_PLUGIN_REGISTRANT_
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
|
||||
// Registers Flutter plugins.
|
||||
void fl_register_plugins(FlPluginRegistry* registry);
|
||||
|
||||
#endif // GENERATED_PLUGIN_REGISTRANT_
|
Reference in New Issue
Block a user