working on functions view

This commit is contained in:
ashrafzarkanisala
2024-06-25 10:38:39 +03:00
parent d0ac4a650f
commit 6413d2b876
19 changed files with 405 additions and 196 deletions
@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
@@ -9,7 +11,7 @@ import 'package:syncrow_app/services/api/devices_api.dart';
import 'package:syncrow_app/services/api/home_management_api.dart';
class DeviceManagerBloc extends Bloc<DeviceManagerEvent, DeviceManagerState> {
DeviceManagerBloc() : super(DeviceManagerInitial()) {
DeviceManagerBloc() : super(DeviceManagerState.initial()) {
on<FetchAllDevices>(_onFetchAllDevices);
on<FetchDevicesByRoomId>(_onFetchDevicesByRoomId);
on<SelectCategory>(_onSelectCategory);
@@ -18,30 +20,30 @@ class DeviceManagerBloc extends Bloc<DeviceManagerEvent, DeviceManagerState> {
on<ChangeCategorySwitchValue>(_onChangeCategorySwitchValue);
on<TurnOnOffDevice>(_onTurnOnOffDevice);
on<ClearCategoriesSelection>(_onClearCategoriesSelection);
// on<DeviceControl>(_onDeviceControl);
on<DeviceFunctionsEvent>(_getDeviceFunctions);
}
static List<DevicesCategoryModel>? allCategories;
Future<void> _onFetchAllDevices(
FetchAllDevices event, Emitter<DeviceManagerState> emit) async {
emit(GetDevicesLoading());
emit(state.copyWith(loading: true));
try {
final allDevices = await HomeManagementAPI.fetchDevicesByUserId();
emit(GetDevicesSuccess(allDevices));
emit(state.copyWith(devices: allDevices, loading: false));
} catch (e) {
emit(GetDevicesError(e.toString()));
emit(state.copyWith(error: e.toString(), loading: false));
}
}
Future<void> _onFetchDevicesByRoomId(
FetchDevicesByRoomId event, Emitter<DeviceManagerState> emit) async {
emit(GetDevicesLoading());
emit(state.copyWith(loading: true));
try {
final devices = await DevicesAPI.getDevicesByRoomId(event.roomId);
emit(GetDevicesSuccess(devices));
emit(state.copyWith(devices: devices, loading: false));
} catch (e) {
emit(GetDevicesError(e.toString()));
emit(state.copyWith(error: e.toString(), loading: false));
}
}
@@ -50,7 +52,7 @@ class DeviceManagerBloc extends Bloc<DeviceManagerEvent, DeviceManagerState> {
for (var i = 0; i < allCategories!.length; i++) {
allCategories![i].isSelected = i == event.index;
}
emit(DevicesCategoryChanged());
emit(state.copyWith(categoryChanged: true));
}
void _onUnselectAllCategories(
@@ -58,7 +60,7 @@ class DeviceManagerBloc extends Bloc<DeviceManagerEvent, DeviceManagerState> {
for (var category in allCategories!) {
category.isSelected = false;
}
emit(DevicesCategoryChanged());
emit(state.copyWith(categoryChanged: true));
}
void _onSelectDevice(SelectDevice event, Emitter<DeviceManagerState> emit) {
@@ -67,14 +69,14 @@ class DeviceManagerBloc extends Bloc<DeviceManagerEvent, DeviceManagerState> {
for (var device in category.devices!) {
if (device.isSelected) {
category.isSelected = false;
emit(DeviceSelected());
emit(state.copyWith(deviceSelected: true));
return;
}
}
}
}
event.device.isSelected = !event.device.isSelected;
emit(DeviceSelected());
emit(state.copyWith(deviceSelected: true));
}
void _onChangeCategorySwitchValue(
@@ -119,28 +121,9 @@ class DeviceManagerBloc extends Bloc<DeviceManagerEvent, DeviceManagerState> {
}
}
Navigator.popUntil(event.context, (route) => route.isFirst);
emit(DevicesCategoryChanged());
emit(state.copyWith(categoryChanged: true)); // Set category changed state
}
// Future<void> _onDeviceControl(
// DeviceControl event, Emitter<DeviceManagerState> emit) async {
// emit(DeviceControlLoading(code: event.control.code));
// try {
// var response =
// await DevicesAPI.controlDevice(event.control, event.deviceId);
// if (response['success'] ?? false) {
// emit(DeviceControlSuccess(code: event.control.code));
// await Future.delayed(const Duration(milliseconds: 400), () {
// add(FetchDevicesByRoomId(event.deviceId));
// });
// } else {
// emit(DeviceControlError('Failed to control the device'));
// }
// } catch (e) {
// emit(DeviceControlError(e.toString()));
// }
// }
void _updateDevicesStatus(
DevicesCategoryModel category, Emitter<DeviceManagerState> emit) {
if (category.devices != null && category.devices!.isNotEmpty) {
@@ -152,7 +135,20 @@ class DeviceManagerBloc extends Bloc<DeviceManagerEvent, DeviceManagerState> {
}
category.devicesStatus = tempStatus;
}
emit(CategorySwitchChanged());
emit(state.copyWith(categoryChanged: true));
}
}
Future<void> _getDeviceFunctions(
DeviceFunctionsEvent event, Emitter<DeviceManagerState> emit) async {
emit(state.copyWith(functionsLoading: true));
try {
final deviceFunctions = await DevicesAPI.deviceFunctions(event.deviceId);
emit(state.copyWith(
functionsLoading: false, deviceFunctions: deviceFunctions));
} catch (e) {
emit(state.copyWith(functionsLoading: false, error: e.toString()));
}
}
}
@@ -78,3 +78,11 @@ class DeviceControl extends DeviceManagerEvent {
@override
List<Object> get props => [control, deviceId];
}
class DeviceFunctionsEvent extends DeviceManagerEvent {
final String deviceId;
const DeviceFunctionsEvent(this.deviceId);
@override
List<Object> get props => [deviceId];
}
@@ -1,112 +1,73 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_app/features/devices/model/device_category_model.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/devices/model/functions.dart';
abstract class DeviceManagerState extends Equatable {
const DeviceManagerState();
@override
List<Object> get props => [];
}
class DeviceManagerInitial extends DeviceManagerState {}
class DevicesCategoryChanged extends DeviceManagerState {}
class DeviceSelected extends DeviceManagerState {}
class DeviceControlLoading extends DeviceManagerState {
final String code;
const DeviceControlLoading({required this.code});
@override
List<Object> get props => [code];
}
class DeviceControlSuccess extends DeviceManagerState {
final String code;
const DeviceControlSuccess({required this.code});
@override
List<Object> get props => [code];
}
class DeviceControlError extends DeviceManagerState {
final String message;
const DeviceControlError(this.message);
@override
List<Object> get props => [message];
}
class DevicesCategoriesLoading extends DeviceManagerState {}
class DevicesCategoriesSuccess extends DeviceManagerState {}
class DevicesCategoriesError extends DeviceManagerState {
final String error;
const DevicesCategoriesError(this.error);
@override
List<Object> get props => [error];
}
class GetDevicesLoading extends DeviceManagerState {}
class GetDevicesSuccess extends DeviceManagerState {
class DeviceManagerState extends Equatable {
final bool loading;
final String? error;
final List<DevicesCategoryModel>? allCategories;
final List<DeviceModel>? devices;
final bool categoryChanged;
final bool deviceSelected;
final bool functionsLoading;
final FunctionsEntity? deviceFunctions;
const GetDevicesSuccess(this.devices);
const DeviceManagerState({
required this.loading,
this.error,
this.allCategories,
this.devices,
this.categoryChanged = false,
this.deviceSelected = false,
this.functionsLoading = false,
this.deviceFunctions,
});
factory DeviceManagerState.initial() {
return const DeviceManagerState(
loading: false,
error: null,
allCategories: null,
devices: null,
categoryChanged: false,
deviceSelected: false,
functionsLoading: false,
deviceFunctions: null,
);
}
DeviceManagerState copyWith({
bool? loading,
String? error,
List<DevicesCategoryModel>? allCategories,
List<DeviceModel>? devices,
bool? categoryChanged,
bool? deviceSelected,
bool? functionsLoading,
FunctionsEntity? deviceFunctions,
}) {
return DeviceManagerState(
loading: loading ?? this.loading,
error: error ?? this.error,
allCategories: allCategories ?? this.allCategories,
devices: devices ?? this.devices,
categoryChanged: categoryChanged ?? this.categoryChanged,
deviceSelected: deviceSelected ?? this.deviceSelected,
functionsLoading: functionsLoading ?? this.functionsLoading,
deviceFunctions: deviceFunctions ?? this.deviceFunctions,
);
}
@override
List<Object> get props => [devices ?? []];
List<Object?> get props => [
loading,
error,
allCategories,
devices,
categoryChanged,
deviceSelected,
functionsLoading,
deviceFunctions,
];
}
class GetDevicesError extends DeviceManagerState {
final String error;
const GetDevicesError(this.error);
@override
List<Object> get props => [error];
}
class GetDeviceStatusLoading extends DeviceManagerState {
final String? code;
const GetDeviceStatusLoading({this.code});
@override
List<Object> get props => [code ?? ''];
}
class GetDeviceStatusSuccess extends DeviceManagerState {
final String? code;
const GetDeviceStatusSuccess({this.code});
@override
List<Object> get props => [code ?? ''];
}
class GetDeviceStatusError extends DeviceManagerState {
final String error;
const GetDeviceStatusError(this.error);
@override
List<Object> get props => [error];
}
class CategorySwitchChanged extends DeviceManagerState {}
class DeviceSwitchChanged extends DeviceManagerState {}
class CurtainsIsOpening extends DeviceManagerState {}
class CurtainsIsClosing extends DeviceManagerState {}
class CurtainsStopped extends DeviceManagerState {}
@@ -331,18 +331,6 @@ class DevicesCubit extends Cubit<DevicesState> {
// }
}
void fetchAllDevices() async {
emitSafe(GetDevicesLoading());
try {
final allDevices = await HomeManagementAPI.fetchDevicesByUserId();
emitSafe(GetDevicesSuccess(allDevices));
} catch (e) {
emitSafe(GetDevicesError(e.toString()));
return;
}
}
fetchDevicesStatues(String deviceUuid, int roomIndex, {String? code}) async {
emitSafe(GetDeviceStatusLoading(code: code));
int deviceIndex = HomeCubit.getInstance()
+58
View File
@@ -0,0 +1,58 @@
class FunctionsEntity {
final String productUuid;
final String productType;
final List<DeviceFunction> functions;
FunctionsEntity({
required this.productUuid,
required this.productType,
required this.functions,
});
factory FunctionsEntity.fromJson(Map<String, dynamic> json) =>
FunctionsEntity(
productUuid: json["productUuid"],
productType: json["productType"],
functions: List<DeviceFunction>.from(
json["functions"].map((x) => DeviceFunction.fromJson(x)),
),
);
Map<String, dynamic> toJson() => {
"productUuid": productUuid,
"productType": productType,
"functions": List<dynamic>.from(functions.map((x) => x.toJson())),
};
}
class DeviceFunction {
final String code;
final String values;
final String dataType;
DeviceFunction({
required this.code,
required this.values,
required this.dataType,
});
factory DeviceFunction.fromJson(Map<String, dynamic> json) => DeviceFunction(
code: _formatCode(json["code"]),
values: json["values"],
dataType: json["dataType"],
);
Map<String, dynamic> toJson() => {
"code": code,
"values": values,
"dataType": dataType,
};
static String _formatCode(String originalCode) {
return originalCode
.replaceAll('_', ' ')
.split(' ')
.map((word) => word[0].toUpperCase() + word.substring(1))
.join(' ');
}
}
@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
@@ -6,8 +8,6 @@ part 'create_scene_state.dart';
class CreateSceneBloc extends Bloc<CreateSceneEvent, CreateSceneState> {
CreateSceneBloc() : super(CreateSceneInitial()) {
on<CreateSceneEvent>((event, emit) {
// TODO: implement event handler
});
on<CreateSceneEvent>((event, emit) => null as FutureOr<void>);
}
}
@@ -6,3 +6,5 @@ sealed class CreateSceneEvent extends Equatable {
@override
List<Object> get props => [];
}
@@ -8,3 +8,7 @@ sealed class CreateSceneState extends Equatable {
}
final class CreateSceneInitial extends CreateSceneState {}
class CreateSceneLoading extends CreateSceneState {}
@@ -0,0 +1,50 @@
import 'package:syncrow_app/features/devices/model/function_model.dart';
import 'package:syncrow_app/utils/resource_manager/constants.dart';
mixin SceneHelper {
get lightBulbFunctions => null;
get wallSensorFunctions => null;
get acFunctions => null;
get doorLockFunctions => null;
get curtainFunctions => null;
get threeGangFunctions => null;
get gatewayFunctions => null;
getFunctionsWithIcons(
{DeviceType? type, required List<FunctionModel> functions}) {
switch (type) {
case DeviceType.LightBulb:
return lightBulbFunctions;
case DeviceType.CeilingSensor:
return ceilingSensorFunctions(functions);
case DeviceType.WallSensor:
return wallSensorFunctions;
case DeviceType.AC:
return acFunctions;
case DeviceType.DoorLock:
return doorLockFunctions;
case DeviceType.Curtain:
return curtainFunctions;
case DeviceType.ThreeGang:
return threeGangFunctions;
case DeviceType.Gateway:
return gatewayFunctions;
default:
return lightBulbFunctions;
}
}
ceilingSensorFunctions(List<FunctionModel> functions) {
List<FunctionModel> temp = [];
for (var item in functions) {
}
return temp;
}
}
@@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
import 'package:syncrow_app/features/devices/model/device_model.dart';
import 'package:syncrow_app/features/scene/helper/scene_helper.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/default_scaffold.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/generated/assets.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/strings_manager.dart';
class DeviceFunctionsView extends StatelessWidget with SceneHelper {
const DeviceFunctionsView({super.key});
@override
Widget build(BuildContext context) {
final device = ModalRoute.of(context)?.settings.arguments as DeviceModel;
// var functions = [];
// if (device.functions.isNotEmpty) {
// functions = getFunctionsWithIcons(
// type: device.productType, functions: device.functions);
// }
return DefaultScaffold(
title: '${device.name} ${StringsManager.functions}',
actions: [
TextButton(
onPressed: () {},
child: BodyMedium(
text: 'Save',
fontWeight: FontWeight.normal,
fontColor: ColorsManager.secondaryColor.withOpacity(0.6),
),
),
],
leading: TextButton(
onPressed: () => Navigator.pop(context),
child: BodyMedium(
text: 'Cancel',
fontWeight: FontWeight.normal,
fontColor: ColorsManager.textPrimaryColor.withOpacity(0.6),
),
),
leadingWidth: 80,
padding: EdgeInsets.zero,
child: DefaultContainer(
margin: const EdgeInsets.only(top: 24),
child: ListView.builder(
shrinkWrap: true,
itemCount: device.functions.length,
itemBuilder: (context, index) {
final function = device.functions[index];
return SceneListTile(
assetPath: null,
minLeadingWidth: 40,
leadingWidget: Image.asset(
Assets.assetsIconsLogo,
width: 20,
),
titleWidget: BodyMedium(
text: function.code ?? '',
style: context.titleSmall.copyWith(
color: ColorsManager.secondaryTextColor,
fontWeight: FontWeight.w400,
fontSize: 15,
),
),
trailingWidget: const Icon(
Icons.arrow_forward_ios_rounded,
color: ColorsManager.greyColor,
size: 16,
),
onPressed: () {},
);
},
),
),
);
}
}
+23 -18
View File
@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart';
import 'package:syncrow_app/features/scene/widgets/if_then_containers/if_container.dart';
import 'package:syncrow_app/features/scene/widgets/if_then_containers/then_container.dart';
import 'package:syncrow_app/features/shared_widgets/default_button.dart';
@@ -34,24 +36,27 @@ class SceneAddTasksView extends StatelessWidget {
],
child: Stack(
children: [
const SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 24,
),
// IF
IFDefaultContainer(),
SizedBox(
height: 8,
),
// THEN
ThenDefaultContainer(),
SizedBox(
height: 100,
),
],
SingleChildScrollView(
child: BlocProvider(
create: (context) => CreateSceneBloc(),
child: const Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 24,
),
// IF
IFDefaultContainer(),
SizedBox(
height: 8,
),
// THEN
ThenDefaultContainer(),
SizedBox(
height: 100,
),
],
),
),
),
Positioned(
@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart';
import 'package:syncrow_app/features/scene/widgets/scene_list_tile.dart';
import 'package:syncrow_app/features/scene/widgets/bottom_sheet_widget.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart';
@@ -44,12 +46,16 @@ class ThenDefaultContainer extends StatelessWidget {
padding: EdgeInsets.zero,
),
const LightDivider(),
SceneListTile(
titleString: '+ Add Task',
textAlign: TextAlign.center,
onPressed: () => context.customBottomSheet(
child: const CustomBottomSheetWidget(),
),
BlocBuilder<CreateSceneBloc, CreateSceneState>(
builder: (context, state) {
return SceneListTile(
titleString: '+ Add Task',
textAlign: TextAlign.center,
onPressed: () => context.customBottomSheet(
child: const CustomBottomSheetWidget(),
),
);
},
)
],
),
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_app/features/devices/bloc/device_manager_bloc/device_manager_bloc.dart';
import 'package:syncrow_app/features/devices/bloc/device_manager_bloc/device_manager_state.dart';
import 'package:syncrow_app/features/devices/model/room_model.dart';
@@ -9,7 +10,7 @@ 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/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/navigation/routing_constants.dart';
import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
@@ -18,7 +19,7 @@ class SceneDevicesBody extends StatelessWidget {
super.key,
required TabController tabController,
required this.rooms,
}) : _tabController = tabController;
}) : _tabController = tabController;
final TabController _tabController;
final List<RoomModel>? rooms;
@@ -66,23 +67,17 @@ class SceneDevicesBody extends StatelessWidget {
Widget _buildRoomTab(RoomModel room, BuildContext context) {
return BlocBuilder<DeviceManagerBloc, DeviceManagerState>(
builder: (context, state) {
if (state is GetDevicesLoading) {
if (state.loading && state.devices == null) {
return const Center(child: CircularProgressIndicator());
} else if (state is GetDevicesSuccess) {
} else if (state.devices != null && state.devices!.isNotEmpty) {
return ListView.builder(
itemCount: state.devices?.length ?? 0,
itemCount: state.devices!.length,
itemBuilder: (context, index) {
final device = state.devices![index];
return DefaultContainer(
child: SceneListTile(
minLeadingWidth: 40,
leadingWidget: Image.network(
device.icon ?? '',
errorBuilder: (context, error, stackTrace) => Image.asset(
Assets.assetsIconsLogo,
width: 20,
),
),
leadingWidget: SvgPicture.asset(device.icon ?? ''),
titleWidget: BodyMedium(
text: device.name ?? '',
style: context.titleSmall.copyWith(
@@ -93,20 +88,25 @@ class SceneDevicesBody extends StatelessWidget {
),
trailingWidget: const Icon(
Icons.arrow_forward_ios_rounded,
color: ColorsManager.greyColor,
size: 16,
weight: 0.2,
),
onPressed: () {
Navigator.pushNamed(
context,
Routes.deviceFunctionsRoute,
arguments: device,
);
},
),
);
},
);
} else if (state is GetDevicesError) {
return Center(child: Text(state.error));
} else if (state.error != null) {
return Center(child: Text(state.error!));
} else {
return const SizedBox();
}
return const SizedBox();
},
);
}
@@ -15,6 +15,8 @@ class DefaultScaffold extends StatelessWidget {
this.appBar,
this.bottomNavBar,
this.padding,
this.leading,
this.leadingWidth,
});
final Widget child;
@@ -23,6 +25,8 @@ class DefaultScaffold extends StatelessWidget {
final PreferredSizeWidget? appBar;
final Widget? bottomNavBar;
final EdgeInsetsGeometry? padding;
final Widget? leading;
final double? leadingWidth;
@override
Widget build(BuildContext context) {
return AnnotatedRegion(
@@ -45,6 +49,8 @@ class DefaultScaffold extends StatelessWidget {
fontWeight: FontsManager.bold,
),
actions: actions,
leading: leading,
leadingWidth: leadingWidth,
),
body: Container(
width: MediaQuery.sizeOf(context).width,