Merge pull request #148 from SyncrowIOT/SP-1435-FE-On-routines-page-when-the-screen-height-is-decreased-or-is-small-the-scroll-area-for-routine-cards-is-not-fitting-the-whole-screen-width

Sp 1435 fe on routines page when the screen height is decreased or is small the scroll area for routine cards is not fitting the whole screen width
This commit is contained in:
Faris Armoush
2025-04-21 09:49:08 +03:00
committed by GitHub
4 changed files with 261 additions and 284 deletions

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_event.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_event.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/create_new_routines/create_new_routines.dart'; import 'package:syncrow_web/pages/routines/create_new_routines/create_new_routines.dart';
import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart'; import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart';
@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/routines/widgets/main_routine_view/fetch_routi
import 'package:syncrow_web/pages/routines/widgets/main_routine_view/routine_view_card.dart'; import 'package:syncrow_web/pages/routines/widgets/main_routine_view/routine_view_card.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class RoutinesView extends StatefulWidget { class RoutinesView extends StatefulWidget {
const RoutinesView({super.key}); const RoutinesView({super.key});
@ -27,9 +28,10 @@ class _RoutinesViewState extends State<RoutinesView> {
if (result == null) return; if (result == null) return;
final communityId = result['community']; final communityId = result['community'];
final spaceId = result['space']; final spaceId = result['space'];
final _bloc = BlocProvider.of<CreateRoutineBloc>(context); final bloc = BlocProvider.of<CreateRoutineBloc>(context);
final routineBloc = context.read<RoutineBloc>(); final routineBloc = context.read<RoutineBloc>();
_bloc.add(SaveCommunityIdAndSpaceIdEvent(communityID: communityId, spaceID: spaceId)); bloc.add(
SaveCommunityIdAndSpaceIdEvent(communityID: communityId, spaceID: spaceId));
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
routineBloc.add(const CreateNewRoutineViewEvent(createRoutineView: true)); routineBloc.add(const CreateNewRoutineViewEvent(createRoutineView: true));
} }
@ -54,43 +56,41 @@ class _RoutinesViewState extends State<RoutinesView> {
), ),
Expanded( Expanded(
flex: 4, flex: 4,
child: ListView( child: SizedBox(
children: [ height: context.screenHeight,
Container( width: context.screenWidth,
padding: const EdgeInsets.all(16), child: SingleChildScrollView(
height: MediaQuery.sizeOf(context).height, padding: const EdgeInsetsDirectional.all(16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ mainAxisAlignment: MainAxisAlignment.start,
Text( spacing: 16,
"Create New Routines", children: [
style: Theme.of(context).textTheme.titleLarge?.copyWith( Text(
color: ColorsManager.grayColor, "Create New Routines",
fontWeight: FontWeight.bold, style: Theme.of(context).textTheme.titleLarge?.copyWith(
), color: ColorsManager.grayColor,
), fontWeight: FontWeight.bold,
const SizedBox(height: 10), ),
RoutineViewCard( ),
isLoading: false, RoutineViewCard(
onChanged: (v) {}, isLoading: false,
status: '', onChanged: (v) {},
spaceId: '', status: '',
automationId: '', spaceId: '',
communityId: '', automationId: '',
sceneId: '', communityId: '',
cardType: '', sceneId: '',
spaceName: '', cardType: '',
onTap: () => _handleRoutineCreation(context), spaceName: '',
icon: Icons.add, onTap: () => _handleRoutineCreation(context),
textString: '', icon: Icons.add,
), textString: '',
const SizedBox(height: 15), ),
const Expanded(child: FetchRoutineScenesAutomation()), const FetchRoutineScenesAutomation(),
], ],
),
), ),
const SizedBox(height: 50), ),
],
), ),
) )
], ],

View File

@ -8,211 +8,182 @@ import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class FetchRoutineScenesAutomation extends StatefulWidget { class FetchRoutineScenesAutomation extends StatelessWidget
const FetchRoutineScenesAutomation({super.key});
@override
State<FetchRoutineScenesAutomation> createState() =>
_FetchRoutineScenesState();
}
class _FetchRoutineScenesState extends State<FetchRoutineScenesAutomation>
with HelperResponsiveLayout { with HelperResponsiveLayout {
@override const FetchRoutineScenesAutomation({super.key});
void initState() {
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>( return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) { builder: (context, state) {
return state.isLoading if (state.isLoading) return const Center(child: CircularProgressIndicator());
? const Center(
child: CircularProgressIndicator(),
)
: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Scenes (Tab to Run)",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
if (state.scenes.isEmpty)
Text(
"No scenes found",
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.grayColor,
),
),
if (state.scenes.isNotEmpty)
SizedBox(
height: 200,
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: state.scenes.length,
itemBuilder: (context, index) {
final scene = state.scenes[index];
final isLoading =
state.loadingSceneId == scene.id;
return Padding( return SingleChildScrollView(
padding: EdgeInsets.only( child: Padding(
right: padding: const EdgeInsets.symmetric(vertical: 16.0),
isSmallScreenSize(context) ? 4.0 : 8.0, child: Column(
), crossAxisAlignment: CrossAxisAlignment.start,
child: Column( mainAxisSize: MainAxisSize.min,
children: [ children: [
RoutineViewCard( _buildListTitle(context, "Scenes (Tab to Run)"),
isLoading: isLoading, const SizedBox(height: 10),
sceneOnTap: () { Visibility(
context.read<RoutineBloc>().add( visible: state.scenes.isNotEmpty,
SceneTrigger( replacement: _buildEmptyState(context, "No scenes found"),
sceneId: scene.id, child: SizedBox(
name: scene.name)); height: 200,
}, child: _buildScenes(state),
status: state.scenes[index].status,
communityId:
state.scenes[index].communityId ??
'',
spaceId: state.scenes[index].spaceId,
sceneId:
state.scenes[index].sceneTuyaId!,
automationId: state.scenes[index].id,
cardType: 'scenes',
spaceName:
state.scenes[index].spaceName,
onTap: () {
BlocProvider.of<RoutineBloc>(context)
.add(
const CreateNewRoutineViewEvent(
createRoutineView: true),
);
context.read<RoutineBloc>().add(
GetSceneDetails(
sceneId:
state.scenes[index].id,
isTabToRun: true,
isUpdate: true,
),
);
},
textString: state.scenes[index].name,
icon: state.scenes[index].icon ??
Assets.logoHorizontal,
isFromScenes: true,
iconInBytes:
state.scenes[index].iconInBytes,
),
],
),
);
}),
),
const SizedBox(height: 10),
Text(
"Automations",
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 3),
if (state.automations.isEmpty)
Text(
"No automations found",
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.grayColor,
),
),
if (state.automations.isNotEmpty)
SizedBox(
height: 200,
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: state.automations.length,
itemBuilder: (context, index) {
final isLoading = state.automations!
.contains(state.automations[index].id);
return Column(
children: [
Padding(
padding: EdgeInsets.only(
right: isSmallScreenSize(context)
? 4.0
: 8.0,
),
child: RoutineViewCard(
isLoading: isLoading,
onChanged: (v) {
context.read<RoutineBloc>().add(
UpdateAutomationStatus(
automationId: state
.automations[index].id,
automationStatusUpdate:
AutomationStatusUpdate(
spaceUuid: state
.automations[
index]
.spaceId,
isEnable: v),
communityId: state
.automations[index]
.communityId,
),
);
},
status: state.automations[index].status,
communityId: '',
spaceId:
state.automations[index].spaceId,
sceneId: '',
automationId:
state.automations[index].id,
cardType: 'automations',
spaceName:
state.automations[index].spaceName,
onTap: () {
BlocProvider.of<RoutineBloc>(context)
.add(
const CreateNewRoutineViewEvent(
createRoutineView: true),
);
context.read<RoutineBloc>().add(
GetAutomationDetails(
automationId: state
.automations[index].id,
isAutomation: true,
isUpdate: true),
);
},
textString:
state.automations[index].name,
icon: state.automations[index].icon ??
Assets.automation,
),
),
],
);
}),
),
],
), ),
), ),
); const SizedBox(height: 10),
_buildListTitle(context, "Automations"),
const SizedBox(height: 3),
Visibility(
visible: state.automations.isNotEmpty,
replacement: _buildEmptyState(context, "No automations found"),
child: SizedBox(
height: 200,
child: _buildAutomations(state),
),
)
],
),
),
);
}, },
); );
} }
Widget _buildAutomations(RoutineState state) {
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: state.automations.length,
itemBuilder: (context, index) {
final isLoading = state.automations.contains(state.automations[index].id);
return Column(
children: [
Padding(
padding: EdgeInsets.only(
right: isSmallScreenSize(context) ? 4.0 : 8.0,
),
child: RoutineViewCard(
isLoading: isLoading,
onChanged: (v) {
context.read<RoutineBloc>().add(
UpdateAutomationStatus(
automationId: state.automations[index].id,
automationStatusUpdate: AutomationStatusUpdate(
spaceUuid: state.automations[index].spaceId,
isEnable: v,
),
communityId: state.automations[index].communityId,
),
);
},
status: state.automations[index].status,
communityId: '',
spaceId: state.automations[index].spaceId,
sceneId: '',
automationId: state.automations[index].id,
cardType: 'automations',
spaceName: state.automations[index].spaceName,
onTap: () {
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(
createRoutineView: true,
),
);
context.read<RoutineBloc>().add(
GetAutomationDetails(
automationId: state.automations[index].id,
isAutomation: true,
isUpdate: true,
),
);
},
textString: state.automations[index].name,
icon: state.automations[index].icon ?? Assets.automation,
),
),
],
);
},
);
}
Widget _buildScenes(RoutineState state) {
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: state.scenes.length,
itemBuilder: (context, index) {
final scene = state.scenes[index];
final isLoading = state.loadingSceneId == scene.id;
return Padding(
padding: EdgeInsets.only(
right: isSmallScreenSize(context) ? 4.0 : 8.0,
),
child: Column(
children: [
RoutineViewCard(
isLoading: isLoading,
sceneOnTap: () {
context.read<RoutineBloc>().add(
SceneTrigger(
sceneId: scene.id,
name: scene.name,
),
);
},
status: state.scenes[index].status,
communityId: state.scenes[index].communityId,
spaceId: state.scenes[index].spaceId,
sceneId: state.scenes[index].sceneTuyaId!,
automationId: state.scenes[index].id,
cardType: 'scenes',
spaceName: state.scenes[index].spaceName,
onTap: () {
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(
createRoutineView: true,
),
);
context.read<RoutineBloc>().add(
GetSceneDetails(
sceneId: state.scenes[index].id,
isTabToRun: true,
isUpdate: true,
),
);
},
textString: state.scenes[index].name,
icon: state.scenes[index].icon ?? Assets.logoHorizontal,
isFromScenes: true,
iconInBytes: state.scenes[index].iconInBytes,
),
],
),
);
});
}
Widget _buildListTitle(BuildContext context, String title) {
return Text(
title,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor,
fontWeight: FontWeight.bold,
),
);
}
Widget _buildEmptyState(BuildContext context, String title) {
return Text(
title,
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.grayColor,
),
);
}
} }

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -66,7 +67,6 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Use widget.<mixinMethod> instead of just <mixinMethod>
final double cardWidth = widget.isSmallScreenSize(context) final double cardWidth = widget.isSmallScreenSize(context)
? 120 ? 120
: widget.isMediumScreenSize(context) : widget.isMediumScreenSize(context)
@ -127,22 +127,23 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
) )
else else
CupertinoSwitch( CupertinoSwitch(
activeColor: ColorsManager.primaryColor, activeTrackColor: ColorsManager.primaryColor,
value: widget.status == 'enable', value: widget.status == 'enable',
onChanged: widget.onChanged, onChanged: widget.onChanged,
) )
], ],
) )
: const SizedBox(), : const SizedBox(),
InkWell( Column(
onTap: widget.onTap, children: [
child: Column( Center(
children: [ child: InkWell(
Center( customBorder: const CircleBorder(),
onTap: widget.onTap,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: ColorsManager.graysColor, color: ColorsManager.graysColor,
borderRadius: BorderRadius.circular(120), shape: BoxShape.circle,
border: Border.all( border: Border.all(
color: ColorsManager.greyColor, color: ColorsManager.greyColor,
width: 2.0, width: 2.0,
@ -158,7 +159,8 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
height: iconSize, height: iconSize,
width: iconSize, width: iconSize,
fit: BoxFit.contain, fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) => Image.asset( errorBuilder: (context, error, stackTrace) =>
Image.asset(
Assets.logo, Assets.logo,
height: iconSize, height: iconSize,
width: iconSize, width: iconSize,
@ -171,7 +173,8 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
width: iconSize, width: iconSize,
fit: BoxFit.contain, fit: BoxFit.contain,
) )
: (widget.icon is String && widget.icon.endsWith('.svg')) : (widget.icon is String &&
widget.icon.endsWith('.svg'))
? SvgPicture.asset( ? SvgPicture.asset(
height: iconSize, height: iconSize,
width: iconSize, width: iconSize,
@ -181,51 +184,54 @@ class _RoutineViewCardState extends State<RoutineViewCard> {
: Icon( : Icon(
widget.icon, widget.icon,
color: ColorsManager.dialogBlueTitle, color: ColorsManager.dialogBlueTitle,
size: widget.isSmallScreenSize(context) ? 30 : 40, size: widget.isSmallScreenSize(context)
? 30
: 40,
), ),
), ),
), ),
const SizedBox(height: 8), ),
Padding( const SizedBox(height: 8),
padding: const EdgeInsets.symmetric(horizontal: 3), Padding(
child: Column( padding: const EdgeInsets.symmetric(horizontal: 3),
children: [ child: Column(
Text( children: [
widget.textString, Text(
textAlign: TextAlign.center, widget.textString,
overflow: TextOverflow.ellipsis, textAlign: TextAlign.center,
maxLines: 1, overflow: TextOverflow.ellipsis,
style: context.textTheme.bodySmall?.copyWith( maxLines: 1,
color: ColorsManager.blackColor, style: context.textTheme.bodySmall?.copyWith(
fontSize: widget.isSmallScreenSize(context) ? 10 : 12, color: ColorsManager.blackColor,
), fontSize: widget.isSmallScreenSize(context) ? 10 : 12,
), ),
if (widget.spaceName != '') ),
Row( if (widget.spaceName != '')
crossAxisAlignment: CrossAxisAlignment.center, Row(
mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ mainAxisAlignment: MainAxisAlignment.center,
SvgPicture.asset( children: [
Assets.spaceLocationIcon, SvgPicture.asset(
fit: BoxFit.contain, Assets.spaceLocationIcon,
fit: BoxFit.contain,
),
Text(
widget.spaceName,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor,
fontSize:
widget.isSmallScreenSize(context) ? 10 : 12,
), ),
Text( ),
widget.spaceName, ],
textAlign: TextAlign.center, ),
overflow: TextOverflow.ellipsis, ],
maxLines: 1,
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor,
fontSize: widget.isSmallScreenSize(context) ? 10 : 12,
),
),
],
),
],
),
), ),
], ),
), ],
), ),
], ],
), ),

View File

@ -31,7 +31,7 @@ class CustomExpansionTileSpaceTree extends StatelessWidget {
children: [ children: [
Checkbox( Checkbox(
value: isSoldCheck ? null : isSelected, value: isSoldCheck ? null : isSelected,
onChanged: (value) => onItemSelected ?? () {}, onChanged: (value) => onItemSelected?.call(),
tristate: true, tristate: true,
side: WidgetStateBorderSide.resolveWith( side: WidgetStateBorderSide.resolveWith(
(states) => const BorderSide(color: ColorsManager.grayBorder), (states) => const BorderSide(color: ColorsManager.grayBorder),