finished select period dialog design

This commit is contained in:
ashrafzarkanisala
2024-07-27 22:35:53 +03:00
parent 0cad30557d
commit 0783f8dacd
8 changed files with 541 additions and 53 deletions

View File

@ -0,0 +1,30 @@
import 'package:bloc/bloc.dart';
import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_event.dart';
import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_state.dart';
class EffectPeriodBloc extends Bloc<EffectPeriodEvent, EffectPeriodState> {
EffectPeriodBloc() : super(EffectPeriodState.initial()) {
on<SetPeriod>(_onSetPeriod);
on<ToggleDay>(_onToggleDay);
on<SetCustomTime>(_onSetCustomTime);
}
void _onSetPeriod(SetPeriod event, Emitter<EffectPeriodState> emit) {
emit(state.copyWith(selectedPeriod: event.period));
}
void _onToggleDay(ToggleDay event, Emitter<EffectPeriodState> emit) {
final days = List.of(state.selectedDays);
if (days.contains(event.day)) {
days.remove(event.day);
} else {
days.add(event.day);
}
emit(state.copyWith(selectedDays: days));
}
void _onSetCustomTime(SetCustomTime event, Emitter<EffectPeriodState> emit) {
emit(state.copyWith(
customStartTime: event.startTime, customEndTime: event.endTime));
}
}

View File

@ -0,0 +1,36 @@
import 'package:equatable/equatable.dart';
abstract class EffectPeriodEvent extends Equatable {
const EffectPeriodEvent();
@override
List<Object> get props => [];
}
class SetPeriod extends EffectPeriodEvent {
final String period;
const SetPeriod(this.period);
@override
List<Object> get props => [period];
}
class ToggleDay extends EffectPeriodEvent {
final String day;
const ToggleDay(this.day);
@override
List<Object> get props => [day];
}
class SetCustomTime extends EffectPeriodEvent {
final String startTime;
final String endTime;
const SetCustomTime(this.startTime, this.endTime);
@override
List<Object> get props => [startTime, endTime];
}

View File

@ -0,0 +1,42 @@
import 'package:equatable/equatable.dart';
class EffectPeriodState extends Equatable {
final String selectedPeriod;
final List<String> selectedDays;
final String? customStartTime;
final String? customEndTime;
const EffectPeriodState({
required this.selectedPeriod,
required this.selectedDays,
this.customStartTime,
this.customEndTime,
});
factory EffectPeriodState.initial() {
return const EffectPeriodState(
selectedPeriod: 'All Day',
selectedDays: [],
customStartTime: null,
customEndTime: null,
);
}
EffectPeriodState copyWith({
String? selectedPeriod,
List<String>? selectedDays,
String? customStartTime,
String? customEndTime,
}) {
return EffectPeriodState(
selectedPeriod: selectedPeriod ?? this.selectedPeriod,
selectedDays: selectedDays ?? this.selectedDays,
customStartTime: customStartTime ?? this.customStartTime,
customEndTime: customEndTime ?? this.customEndTime,
);
}
@override
List<Object?> get props =>
[selectedPeriod, selectedDays, customStartTime, customEndTime];
}

View File

@ -0,0 +1,62 @@
import 'package:flutter/material.dart';
import 'package:syncrow_app/features/scene/view/scene_tasks_view.dart';
import 'package:syncrow_app/features/scene/widgets/bottom_sheet/effective_period_bottom_sheet.dart';
import 'package:syncrow_app/features/scene/widgets/scene_list_tile.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart';
import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
class SceneAutoSettings extends StatelessWidget {
const SceneAutoSettings({super.key});
@override
Widget build(BuildContext context) {
final sceneSettings =
ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>? ??
{};
final sceneId = sceneSettings['sceneId'] as String? ?? '';
final isAutomation = sceneSettings['isAutomation'] as bool? ?? false;
final sceneName = sceneSettings['sceneName'] as String? ?? '';
return Scaffold(
backgroundColor: ColorsManager.backgroundColor,
appBar: AppBar(
centerTitle: true,
backgroundColor: ColorsManager.backgroundColor,
title: const BodyLarge(
text: "Settings",
fontColor: ColorsManager.secondaryColor,
fontWeight: FontsManager.bold,
),
),
body: DefaultContainer(
padding: EdgeInsets.zero,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SceneListTile(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
titleString: "Effective Period",
trailingWidget: const Icon(Icons.arrow_forward_ios_rounded),
onPressed: () {
context.customBottomSheet(
child: const EffectPeriodBottomSheetContent(),
);
},
),
Visibility(
visible: sceneName.isNotEmpty,
child: DeleteBottomSheetContent(
isAutomation: isAutomation,
sceneId: sceneId,
),
),
],
),
),
);
}
}

View File

@ -40,13 +40,15 @@ class SceneTasksView extends StatelessWidget {
SizedBox( SizedBox(
width: 40, width: 40,
child: GestureDetector( child: GestureDetector(
onTap: sceneSettings.sceneName.isEmpty onTap: () {
? null Navigator.pushNamed(
: () { context,
context.customBottomSheet( Routes.sceneAutoSettingsRoute,
child: DeleteBottomSheetContent( arguments: {
sceneId: sceneSettings.sceneId, "sceneId": sceneSettings.sceneId,
isAutomation: isAutomation), "isAutomation": isAutomation,
"sceneName": sceneSettings.sceneName,
},
); );
}, },
child: SvgPicture.asset( child: SvgPicture.asset(
@ -109,19 +111,16 @@ class DeleteBottomSheetContent extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DefaultContainer( return DefaultContainer(
height: context.height * 0.2, padding: EdgeInsets.zero,
child: ListView( child: BlocConsumer<CreateSceneBloc, CreateSceneState>(
shrinkWrap: true,
children: [
BlocConsumer<CreateSceneBloc, CreateSceneState>(
listener: (context, state) { listener: (context, state) {
if (state is DeleteSceneSuccess) { if (state is DeleteSceneSuccess) {
if (state.success) { if (state.success) {
navigateToRoute(context, Routes.homeRoute); navigateToRoute(context, Routes.homeRoute);
BlocProvider.of<SceneBloc>(context).add( BlocProvider.of<SceneBloc>(context).add(
LoadScenes(HomeCubit.getInstance().selectedSpace!.id!)); LoadScenes(HomeCubit.getInstance().selectedSpace!.id!));
BlocProvider.of<SceneBloc>(context).add(LoadAutomation( BlocProvider.of<SceneBloc>(context).add(
HomeCubit.getInstance().selectedSpace!.id!)); LoadAutomation(HomeCubit.getInstance().selectedSpace!.id!));
} }
} }
}, },
@ -130,11 +129,11 @@ class DeleteBottomSheetContent extends StatelessWidget {
onTap: () { onTap: () {
context.read<CreateSceneBloc>().add(DeleteSceneEvent( context.read<CreateSceneBloc>().add(DeleteSceneEvent(
sceneId: sceneId, sceneId: sceneId,
unitUuid: unitUuid: HomeCubit.getInstance().selectedSpace!.id!,
HomeCubit.getInstance().selectedSpace!.id!,
)); ));
}, },
child: SceneListTile( child: SceneListTile(
padding: const EdgeInsets.symmetric(horizontal: 8),
titleString: isAutomation titleString: isAutomation
? StringsManager.deleteAutomation ? StringsManager.deleteAutomation
: StringsManager.deleteScene, : StringsManager.deleteScene,
@ -149,8 +148,6 @@ class DeleteBottomSheetContent extends StatelessWidget {
), ),
)); ));
}, },
),
],
)); ));
} }
} }

View File

@ -0,0 +1,314 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_event.dart';
import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_state.dart';
import 'package:syncrow_app/features/scene/bloc/effective_period/effect_period_bloc.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/font_manager.dart';
import 'package:time_picker_spinner/time_picker_spinner.dart';
class EffectPeriodBottomSheetContent extends StatefulWidget {
const EffectPeriodBottomSheetContent({super.key});
@override
State<EffectPeriodBottomSheetContent> createState() =>
_EffectPeriodBottomSheetContentState();
}
class _EffectPeriodBottomSheetContentState
extends State<EffectPeriodBottomSheetContent> {
Future<List<String>?> showCustomTimePicker(BuildContext context) async {
String selectedStartTime = "00:00";
String selectedEndTime = "23:59";
PageController pageController = PageController(initialPage: 0);
DateTime startDateTime = DateTime(2022, 1, 1, 0, 0); // Fixed 00:00
DateTime endDateTime = DateTime(2022, 1, 1, 23, 59); // Fixed 23:59
context.customAlertDialog(
alertBody: SizedBox(
height: 250,
child: PageView(
controller: pageController,
physics: const NeverScrollableScrollPhysics(),
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
onPressed: () {},
child: BodyMedium(
text: "Start",
style: context.bodyMedium.copyWith(
color: ColorsManager.primaryColor,
),
),
),
TextButton(
onPressed: () {
pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeIn,
);
},
child: const BodyMedium(
text: "End",
),
),
],
),
),
TimePickerSpinner(
is24HourMode: false,
normalTextStyle: const TextStyle(
fontSize: 24,
color: Colors.grey,
),
highlightedTextStyle: const TextStyle(
fontSize: 24,
color: ColorsManager.primaryColor,
),
spacing: 20,
itemHeight: 50,
isForce2Digits: true,
time: startDateTime,
onTimeChange: (time) {
selectedStartTime =
"${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}";
},
),
const SizedBox(height: 16),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
onPressed: () {
pageController.previousPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeIn,
);
},
child: const BodyMedium(text: "Start"),
),
TextButton(
onPressed: () {},
child: BodyMedium(
text: "End",
style: context.bodyMedium.copyWith(
color: ColorsManager.primaryColor,
),
),
),
],
),
),
TimePickerSpinner(
is24HourMode: false,
normalTextStyle: const TextStyle(
fontSize: 24,
color: Colors.grey,
),
highlightedTextStyle: const TextStyle(
fontSize: 24,
color: ColorsManager.primaryColor,
),
spacing: 20,
itemHeight: 50,
isForce2Digits: true,
time: endDateTime,
onTimeChange: (time) {
selectedEndTime =
"${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}";
},
),
const SizedBox(height: 16),
],
),
],
),
),
title: "Custom",
onConfirm: () {
context.read<EffectPeriodBloc>().add(
SetCustomTime(selectedStartTime, selectedEndTime),
);
context.read<EffectPeriodBloc>().add(
const SetPeriod('Custom'),
);
Navigator.of(context).pop();
},
);
return null;
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => EffectPeriodBloc(),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
BodyMedium(
text: 'Effective Period',
fontColor: ColorsManager.primaryColorWithOpacity,
fontWeight: FontsManager.bold,
),
const Divider(
color: ColorsManager.backgroundColor,
),
_buildPeriodOptions(context),
const SizedBox(height: 16),
_buildRepeatDays(context),
const SizedBox(height: 24),
],
),
),
);
}
Widget _buildPeriodOptions(BuildContext context) {
return BlocBuilder<EffectPeriodBloc, EffectPeriodState>(
builder: (context, state) {
return Column(
children: [
_buildRadioOption(context, 'All Day', '24 Hours'),
_buildRadioOption(context, 'Daytime', 'Sunrise to Sunset'),
_buildRadioOption(context, 'Night', 'Sunset to Sunrise'),
ListTile(
contentPadding: EdgeInsets.zero,
onTap: () => showCustomTimePicker(context),
title: BodyMedium(
text: 'Custom',
fontColor: ColorsManager.primaryColorWithOpacity,
),
subtitle:
state.customStartTime != null && state.customEndTime != null
? BodySmall(
text:
'${"${state.customStartTime} AM"} - ${"${state.customEndTime} PM"}',
style: context.bodySmall.copyWith(fontSize: 10),
)
: BodySmall(
text: '00:00 AM - 11:59 PM',
style: context.bodySmall.copyWith(fontSize: 10),
),
trailing: Radio<String>(
value: 'Custom',
groupValue: state.selectedPeriod,
onChanged: (value) async {},
),
),
],
);
},
);
}
Widget _buildRadioOption(
BuildContext context, String value, String subtitle) {
return BlocBuilder<EffectPeriodBloc, EffectPeriodState>(
builder: (context, state) {
return ListTile(
contentPadding: EdgeInsets.zero,
onTap: () {
context.read<EffectPeriodBloc>().add(SetPeriod(value));
},
title: BodyMedium(text: value),
subtitle: BodySmall(
text: subtitle,
style: context.bodySmall.copyWith(fontSize: 10),
),
trailing: Radio<String>(
value: value,
groupValue: state.selectedPeriod,
onChanged: (value) {
if (value != null) {
context.read<EffectPeriodBloc>().add(SetPeriod(value));
}
},
),
);
},
);
}
Widget _buildRepeatDays(BuildContext context) {
final daysMap = {
'Mon': 'M',
'Tue': 'T',
'Wed': 'W',
'Thu': 'T',
'Fri': 'F',
'Sat': 'S',
'Sun': 'S',
};
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const BodyMedium(text: 'Repeat'),
const SizedBox(width: 8),
BlocBuilder<EffectPeriodBloc, EffectPeriodState>(
builder: (context, state) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: daysMap.entries.map((entry) {
final day = entry.key;
final abbreviation = entry.value;
final isSelected = state.selectedDays.contains(day);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 3.0),
child: GestureDetector(
onTap: () {
context.read<EffectPeriodBloc>().add(ToggleDay(day));
},
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color:
isSelected ? Colors.grey : Colors.grey.shade300,
width: 1,
),
),
child: CircleAvatar(
radius: 15,
backgroundColor: Colors.white,
child: Text(
abbreviation,
style: TextStyle(
fontSize: 20,
color:
isSelected ? Colors.grey : Colors.grey.shade300,
),
),
),
),
),
);
}).toList(),
);
},
),
],
);
}
}

View File

@ -14,6 +14,7 @@ import 'package:syncrow_app/features/menu/view/widgets/profile/profile_view.dart
import 'package:syncrow_app/features/scene/bloc/tab_change/tab_change_bloc.dart'; import 'package:syncrow_app/features/scene/bloc/tab_change/tab_change_bloc.dart';
import 'package:syncrow_app/features/scene/bloc/tab_change/tab_change_event.dart'; import 'package:syncrow_app/features/scene/bloc/tab_change/tab_change_event.dart';
import 'package:syncrow_app/features/scene/view/device_functions_view.dart'; import 'package:syncrow_app/features/scene/view/device_functions_view.dart';
import 'package:syncrow_app/features/scene/view/scene_auto_settings.dart';
import 'package:syncrow_app/features/scene/view/scene_tasks_view.dart'; import 'package:syncrow_app/features/scene/view/scene_tasks_view.dart';
import 'package:syncrow_app/features/scene/view/scene_rooms_tabbar.dart'; import 'package:syncrow_app/features/scene/view/scene_rooms_tabbar.dart';
import 'package:syncrow_app/features/scene/view/scene_view.dart'; import 'package:syncrow_app/features/scene/view/scene_view.dart';
@ -95,6 +96,11 @@ class Router {
builder: (_) => DeviceFunctionsView(), builder: (_) => DeviceFunctionsView(),
settings: settings, settings: settings,
); );
case Routes.sceneAutoSettingsRoute:
return MaterialPageRoute(
builder: (_) => const SceneAutoSettings(),
settings: settings,
);
default: default:
return MaterialPageRoute( return MaterialPageRoute(
builder: (_) => Scaffold( builder: (_) => Scaffold(

View File

@ -19,6 +19,7 @@ class Routes {
static const String createUnit = '/create-unit'; static const String createUnit = '/create-unit';
static const String sceneTasksRoute = '/scene-tasks'; static const String sceneTasksRoute = '/scene-tasks';
static const String sceneControlDevicesRoute = '/scene-control-devices'; static const String sceneControlDevicesRoute = '/scene-control-devices';
static const String deviceFunctionsRoute = '/device-functions'; static const String deviceFunctionsRoute = '/device-functions';
static const String sceneAutoSettingsRoute =
'/scene-automation-settings';
} }