mirror of
https://github.com/SyncrowIOT/syncrow-app.git
synced 2025-07-16 01:56:19 +00:00
finished select period dialog design
This commit is contained in:
@ -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));
|
||||
}
|
||||
}
|
@ -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];
|
||||
}
|
@ -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];
|
||||
}
|
62
lib/features/scene/view/scene_auto_settings.dart
Normal file
62
lib/features/scene/view/scene_auto_settings.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -40,13 +40,15 @@ class SceneTasksView extends StatelessWidget {
|
||||
SizedBox(
|
||||
width: 40,
|
||||
child: GestureDetector(
|
||||
onTap: sceneSettings.sceneName.isEmpty
|
||||
? null
|
||||
: () {
|
||||
context.customBottomSheet(
|
||||
child: DeleteBottomSheetContent(
|
||||
sceneId: sceneSettings.sceneId,
|
||||
isAutomation: isAutomation),
|
||||
onTap: () {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
Routes.sceneAutoSettingsRoute,
|
||||
arguments: {
|
||||
"sceneId": sceneSettings.sceneId,
|
||||
"isAutomation": isAutomation,
|
||||
"sceneName": sceneSettings.sceneName,
|
||||
},
|
||||
);
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
@ -109,19 +111,16 @@ class DeleteBottomSheetContent extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DefaultContainer(
|
||||
height: context.height * 0.2,
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
BlocConsumer<CreateSceneBloc, CreateSceneState>(
|
||||
padding: EdgeInsets.zero,
|
||||
child: BlocConsumer<CreateSceneBloc, CreateSceneState>(
|
||||
listener: (context, state) {
|
||||
if (state is DeleteSceneSuccess) {
|
||||
if (state.success) {
|
||||
navigateToRoute(context, Routes.homeRoute);
|
||||
BlocProvider.of<SceneBloc>(context).add(
|
||||
LoadScenes(HomeCubit.getInstance().selectedSpace!.id!));
|
||||
BlocProvider.of<SceneBloc>(context).add(LoadAutomation(
|
||||
HomeCubit.getInstance().selectedSpace!.id!));
|
||||
BlocProvider.of<SceneBloc>(context).add(
|
||||
LoadAutomation(HomeCubit.getInstance().selectedSpace!.id!));
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -130,11 +129,11 @@ class DeleteBottomSheetContent extends StatelessWidget {
|
||||
onTap: () {
|
||||
context.read<CreateSceneBloc>().add(DeleteSceneEvent(
|
||||
sceneId: sceneId,
|
||||
unitUuid:
|
||||
HomeCubit.getInstance().selectedSpace!.id!,
|
||||
unitUuid: HomeCubit.getInstance().selectedSpace!.id!,
|
||||
));
|
||||
},
|
||||
child: SceneListTile(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
titleString: isAutomation
|
||||
? StringsManager.deleteAutomation
|
||||
: StringsManager.deleteScene,
|
||||
@ -149,8 +148,6 @@ class DeleteBottomSheetContent extends StatelessWidget {
|
||||
),
|
||||
));
|
||||
},
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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_event.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_rooms_tabbar.dart';
|
||||
import 'package:syncrow_app/features/scene/view/scene_view.dart';
|
||||
@ -95,6 +96,11 @@ class Router {
|
||||
builder: (_) => DeviceFunctionsView(),
|
||||
settings: settings,
|
||||
);
|
||||
case Routes.sceneAutoSettingsRoute:
|
||||
return MaterialPageRoute(
|
||||
builder: (_) => const SceneAutoSettings(),
|
||||
settings: settings,
|
||||
);
|
||||
default:
|
||||
return MaterialPageRoute(
|
||||
builder: (_) => Scaffold(
|
||||
|
@ -20,5 +20,6 @@ class Routes {
|
||||
static const String sceneTasksRoute = '/scene-tasks';
|
||||
static const String sceneControlDevicesRoute = '/scene-control-devices';
|
||||
static const String deviceFunctionsRoute = '/device-functions';
|
||||
|
||||
static const String sceneAutoSettingsRoute =
|
||||
'/scene-automation-settings';
|
||||
}
|
||||
|
Reference in New Issue
Block a user