17 Commits

Author SHA1 Message Date
f842940bef some changes 2026-01-18 14:23:55 +03:30
f25b8514cd refactor: update text form field model to use dynamic value type, enhance condition evaluator for step index extraction, and improve SDK path formatting 2026-01-07 15:57:01 +03:30
17f8a2d79b refactor: update getSDUIForm method to require token parameter, enhance error handling in SDUIFormWidget, and clean up code formatting across various files 2026-01-06 15:59:42 +03:30
0f0fa8da09 chore: update Android SDK version to 1.3.42+38, enhance SDUIFormWidget with improved button layout and padding, and remove unused JSON configuration file 2026-01-05 09:24:43 +03:30
6a65aa2883 chore: update version number to 1.3.42+38 in pubspec.yaml 2026-01-04 12:03:11 +03:30
a6cf440840 feat: introduce _SpacingWidget for conditional spacing in SDUIFormWidget, enhance visibility handling with reactive updates, and improve error handling for child widgets 2026-01-04 11:57:32 +03:30
4dcc574e19 refactor: update SDUIFormWidget error handling, enhance StepperSDUI with state management and scrolling behavior, and clean up SDUIWidgetModel documentation 2026-01-04 10:03:28 +03:30
26f94345f6 feat: enhance SDUI widget model with dynamic visibility conditions, refactor NewPageLogic for improved form handling, and update UI components to support new visibility features 2025-12-31 15:28:17 +03:30
fb0b817cf9 feat: refactor SDUI widget model to use sealed classes for type-safe handling, enhance form widget with stepper support, and improve error handling in SDUIFormWidget 2025-12-31 13:36:13 +03:30
8c25e2c65c chore: remove unused dependencies from pubspec.lock files and update Flutter SDK version constraints 2025-12-30 09:54:30 +03:30
dcfe9f6dcf feat: add stepper and page view components to SDUI, enhance form handling with dynamic visibility conditions and improved error handling 2025-12-29 10:09:57 +03:30
fc0161e261 feat: enhance NewPage UI with dynamic bottom sheet for form input and restructure SDUI JSON schema for improved data handling 2025-12-28 16:05:37 +03:30
71952bef5a feat: refactor NewPageLogic to utilize SDUIWidgetModel, enhance form handling with dynamic controllers, and implement SDUIFormWidget for rendering UI components 2025-12-28 13:50:48 +03:30
0b49302434 chore: remove unused assets and data source files related to the kill house module, and update import paths for better organization 2025-12-27 16:35:37 +03:30
60c58ef17e chore: clean up unused imports, update routing for authentication pages, and enhance UI components in the profile and captcha widgets 2025-12-24 11:51:41 +03:30
3eccf492ff feat: implement authentication feature with login logic, UI components, and routing for user access 2025-12-24 11:51:30 +03:30
12c6b51aac chore: add missing import for auth_repository_imp in auth_flow_integration_test.dart 2025-12-23 12:03:18 +03:30
139 changed files with 11748 additions and 2769 deletions

View File

@@ -2,6 +2,7 @@ allprojects {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
maven { url = uri("https://archive.ito.gov.ir/gradle/maven_central/") }
} }
} }

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
sdk.dir=C:\\Users\\Housh11\\AppData\\Local\\Android\\sdk sdk.dir=C:\\Users\\Housh11\\AppData\\Local\\Android\\sdk
flutter.sdk=C:\\src\\flutter flutter.sdk=C:\\src\\flutter
flutter.buildMode=debug flutter.buildMode=debug
flutter.versionName=1.3.41 flutter.versionName=1.3.42
flutter.versionCode=37 flutter.versionCode=38

View File

@@ -13,6 +13,7 @@ pluginManagement {
google() google()
mavenCentral() mavenCentral()
gradlePluginPortal() gradlePluginPortal()
maven { url = uri("https://archive.ito.gov.ir/gradle/maven_central/") }
} }
} }

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

View File

@@ -1,5 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:rasadyar_app/data/model/app_info_model.dart'; import 'package:rasadyar_app/data/model/app_info_model.dart';
@@ -35,9 +36,15 @@ class SplashLogic extends GetxController with GetTickerProviderStateMixin {
duration: const Duration(milliseconds: 8000), duration: const Duration(milliseconds: 8000),
); );
scaleAnimation.value = Tween<double>(begin: 0.8, end: 1.2).animate(scaleController); scaleAnimation.value = Tween<double>(
begin: 0.8,
end: 1.2,
).animate(scaleController);
rotationAnimation.value = Tween<double>(begin: 0.0, end: 1).animate(rotateController); rotationAnimation.value = Tween<double>(
begin: 0.0,
end: 1,
).animate(rotateController);
rotateController.forward(); rotateController.forward();
rotateController.addStatusListener((status) { rotateController.addStatusListener((status) {
@@ -152,8 +159,10 @@ class SplashLogic extends GetxController with GetTickerProviderStateMixin {
Future.delayed(const Duration(milliseconds: 250), () async { Future.delayed(const Duration(milliseconds: 250), () async {
try { try {
if (!kDebugMode) {
final isUpdateNeeded = await checkVersion(); final isUpdateNeeded = await checkVersion();
if (isUpdateNeeded) return; if (isUpdateNeeded) return;
}
final module = gService.getSelectedModule(); final module = gService.getSelectedModule();
final target = gService.getTargetPage(module); final target = gService.getTargetPage(module);
@@ -164,8 +173,6 @@ class SplashLogic extends GetxController with GetTickerProviderStateMixin {
if (target != null) { if (target != null) {
var mFuns = getFunctionsList(target.functions); var mFuns = getFunctionsList(target.functions);
await Future.wait(mFuns ?? []); await Future.wait(mFuns ?? []);
iLog("target.route ===>${target.route!}");
Get.offAndToNamed(target.route!); Get.offAndToNamed(target.route!);
} }
} catch (e, st) { } catch (e, st) {
@@ -185,7 +192,9 @@ class SplashLogic extends GetxController with GetTickerProviderStateMixin {
try { try {
final info = await PackageInfo.fromPlatform(); final info = await PackageInfo.fromPlatform();
int version = info.version.versionNumber; int version = info.version.versionNumber;
var res = await _dio.get("https://rsibackend.rasadyar.com/app/rasadyar-app-info/"); var res = await _dio.get(
"https://rsibackend.rasadyar.com/app/rasadyar-app-info/",
);
appInfoModel = AppInfoModel.fromJson(res.data); appInfoModel = AppInfoModel.fromJson(res.data);
@@ -244,7 +253,9 @@ class SplashLogic extends GetxController with GetTickerProviderStateMixin {
Future<void> installApk() async { Future<void> installApk() async {
try { try {
eLog(_updateFilePath.value); eLog(_updateFilePath.value);
await platform.invokeMethod('apk_installer', {'appPath': _updateFilePath.value}); await platform.invokeMethod('apk_installer', {
'appPath': _updateFilePath.value,
});
} catch (e) { } catch (e) {
eLog(e); eLog(e);
} }

View File

@@ -1 +0,0 @@
{"format-version":[1,0,0],"native-assets":{}}

View File

@@ -1 +0,0 @@
{"packages/cupertino_icons/assets/CupertinoIcons.ttf":["packages/cupertino_icons/assets/CupertinoIcons.ttf"],"packages/flutter_map/lib/assets/flutter_map_logo.png":["packages/flutter_map/lib/assets/flutter_map_logo.png"]}

View File

@@ -1 +0,0 @@
[{"family":"packages/cupertino_icons/CupertinoIcons","fonts":[{"asset":"packages/cupertino_icons/assets/CupertinoIcons.ttf"}]}]

View File

@@ -1 +0,0 @@
{"format-version":[1,0,0],"native-assets":{}}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -1,10 +1,6 @@
import 'package:rasadyar_chicken/data/common/dio_error_handler.dart'; import 'package:rasadyar_chicken/data/common/dio_error_handler.dart';
import 'package:rasadyar_chicken/features/common/presentation/routes/routes.dart'; import 'package:rasadyar_chicken/features/common/presentation/routes/routes.dart';
import 'package:rasadyar_chicken/features/common/data/di/common_di.dart'; import 'package:rasadyar_chicken/features/common/data/di/common_di.dart';
import 'package:rasadyar_chicken/data/data_source/remote/kill_house/kill_house_remote.dart';
import 'package:rasadyar_chicken/data/data_source/remote/kill_house/kill_house_remote_impl.dart';
import 'package:rasadyar_chicken/data/repositories/kill_house/kill_house_repository.dart';
import 'package:rasadyar_chicken/data/repositories/kill_house/kill_house_repository_impl.dart';
import 'package:rasadyar_chicken/features/poultry_science/data/di/poultry_science_di.dart'; import 'package:rasadyar_chicken/features/poultry_science/data/di/poultry_science_di.dart';
import 'package:rasadyar_chicken/features/steward/data/di/steward_di.dart'; import 'package:rasadyar_chicken/features/steward/data/di/steward_di.dart';
import 'package:rasadyar_chicken/features/province_operator/data/di/province_operator_di.dart'; import 'package:rasadyar_chicken/features/province_operator/data/di/province_operator_di.dart';
@@ -86,14 +82,7 @@ Future<void> setupChickenDI() async {
// Setup jahad feature DI // Setup jahad feature DI
await setupJahadDI(diChicken, dioRemote); await setupJahadDI(diChicken, dioRemote);
//region kill house module DI
diChicken.registerLazySingleton<KillHouseRemoteDataSource>(
() => KillHouseRemoteDataSourceImpl(diChicken.get<DioRemote>()),
);
diChicken.registerLazySingleton<KillHouseRepository>(
() => KillHouseRepositoryImpl(diChicken.get<KillHouseRemoteDataSource>()),
);
//endregion
} }
Future<void> newSetupAuthDI(String newUrl) async { Future<void> newSetupAuthDI(String newUrl) async {

View File

@@ -1,244 +0,0 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/common/dio_error_handler.dart';
import 'package:rasadyar_chicken/data/di/chicken_di.dart';
import 'package:rasadyar_chicken/features/common/data/model/response/user_info/user_info_model.dart';
import 'package:rasadyar_chicken/features/common/data/model/response/user_profile_model/user_profile_model.dart';
import 'package:rasadyar_chicken/features/common/data/repositories/auth/auth_repository.dart';
import 'package:rasadyar_chicken/features/common/presentation/routes/routes.dart';
import 'package:rasadyar_chicken/features/steward/presentation/routes/routes.dart';
import 'package:rasadyar_chicken/presentation/widget/captcha/logic.dart';
import 'package:rasadyar_core/core.dart';
enum AuthType { useAndPass, otp }
enum AuthStatus { init }
enum OtpStatus { init, sent, verified, reSend }
class AuthLogic extends GetxController with GetTickerProviderStateMixin {
GlobalKey<FormState> formKey = GlobalKey<FormState>();
late AnimationController _textAnimationController;
late Animation<double> textAnimation;
RxBool showCard = false.obs;
RxBool rememberMe = false.obs;
Rx<GlobalKey<FormState>> formKeyOtp = GlobalKey<FormState>().obs;
Rx<GlobalKey<FormState>> formKeySentOtp = GlobalKey<FormState>().obs;
Rx<TextEditingController> usernameController = TextEditingController().obs;
Rx<TextEditingController> passwordController = TextEditingController().obs;
Rx<TextEditingController> phoneOtpNumberController =
TextEditingController().obs;
Rx<TextEditingController> otpCodeController = TextEditingController().obs;
var captchaController = Get.find<CaptchaWidgetLogic>();
RxnString phoneNumber = RxnString(null);
RxBool isLoading = false.obs;
RxBool isDisabled = true.obs;
GService gService = Get.find<GService>();
TokenStorageService tokenStorageService = Get.find<TokenStorageService>();
Rx<AuthType> authType = AuthType.useAndPass.obs;
Rx<AuthStatus> authStatus = AuthStatus.init.obs;
Rx<OtpStatus> otpStatus = OtpStatus.init.obs;
RxnString deviceName = RxnString(null);
RxInt secondsRemaining = 120.obs;
Timer? _timer;
AuthRepository authRepository = diChicken.get<AuthRepository>();
final Module _module = Get.arguments;
@override
void onInit() {
super.onInit();
_textAnimationController =
AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1200),
)
..repeat(reverse: true, count: 2).whenComplete(() {
showCard.value = true;
});
textAnimation = CurvedAnimation(
parent: _textAnimationController,
curve: Curves.easeInOut,
);
initUserPassData();
getDeviceModel();
}
@override
void onClose() {
_textAnimationController.dispose();
_timer?.cancel();
super.onClose();
}
void startTimer() {
_timer?.cancel();
secondsRemaining.value = 120;
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (secondsRemaining.value > 0) {
secondsRemaining.value--;
} else {
timer.cancel();
}
});
}
void stopTimer() {
_timer?.cancel();
}
String get timeFormatted {
final minutes = secondsRemaining.value ~/ 60;
final seconds = secondsRemaining.value % 60;
return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
}
bool _isFormValid() {
final isCaptchaValid =
captchaController.formKey.currentState?.validate() ?? false;
final isFormValid = formKey.currentState?.validate() ?? false;
return isCaptchaValid && isFormValid;
}
Future<void> submitLoginForm() async {
if (!_isFormValid()) return;
AuthRepository authTmp = diChicken.get<AuthRepository>();
isLoading.value = true;
await safeCall<UserProfileModel?>(
call: () => authTmp.login(
authRequest: {
"username": usernameController.value.text,
"password": passwordController.value.text,
},
),
onSuccess: (result) async {
await gService.saveSelectedModule(_module);
await tokenStorageService.saveModule(_module);
await tokenStorageService.saveAccessToken(
_module,
result?.accessToken ?? '',
);
await tokenStorageService.saveRefreshToken(
_module,
result?.accessToken ?? '',
);
var tmpRoles = result?.role?.where((element) {
final allowedRoles = {
'poultryscience',
'steward',
'killhouse',
'provinceinspector',
'cityjahad',
'jahad',
'vetfarm',
'provincesupervisor',
'superadmin',
};
final lowerElement = element.toString().toLowerCase().trim();
return allowedRoles.contains(lowerElement);
}).toList();
if (tmpRoles?.length==1) {
await tokenStorageService.saveRoles(_module, tmpRoles ?? []);
}
await tokenStorageService.saveRoles(_module, tmpRoles ?? []);
if (rememberMe.value) {
await tokenStorageService.saveUserPass(
_module,
usernameController.value.text,
passwordController.value.text,
);
}
authTmp.stewardAppLogin(
token: result?.accessToken ?? '',
queryParameters: {
"mobile": usernameController.value.text,
"device_name": deviceName.value,
},
);
Get.offAndToNamed(CommonRoutes.role);
/* if (tmpRoles!.length > 1) {
Get.offAndToNamed(CommonRoutes.role);
} else {
Get.offAllNamed(StewardRoutes.initSteward);
} */
},
onError: (error, stackTrace) {
if (error is DioException) {
diChicken.get<DioErrorHandler>().handle(error);
if ((error.type == DioExceptionType.unknown) ||
(error.type == DioExceptionType.connectionError)) {
getUserInfo(usernameController.value.text);
}
}
captchaController.getCaptcha();
},
);
isLoading.value = false;
}
Future<void> getUserInfo(String value) async {
isLoading.value = true;
await safeCall<UserInfoModel?>(
call: () async => await authRepository.getUserInfo(value),
onSuccess: (result) async {
if (result != null) {
await newSetupAuthDI(result.backend ?? '');
await diChicken.allReady();
}
},
onError: (error, stackTrace) {
if (error is DioException) {
diChicken.get<DioErrorHandler>().handle(error);
}
captchaController.getCaptcha();
},
);
isLoading.value = false;
}
void initUserPassData() {
UserLocalModel? userLocalModel = tokenStorageService.getUserLocal(
Module.chicken,
);
if (userLocalModel?.username != null && userLocalModel?.password != null) {
usernameController.value.text = userLocalModel?.username ?? '';
passwordController.value.text = userLocalModel?.password ?? '';
rememberMe.value = true;
}
}
Future<void> getDeviceModel() async {
final deviceInfo = DeviceInfoPlugin();
if (Platform.isAndroid) {
final info = await deviceInfo.androidInfo;
deviceName.value =
'Device:${info.manufacturer} Model:${info.model} version ${info.version.release}';
} else if (Platform.isIOS) {
final info = await deviceInfo.iosInfo;
deviceName.value =
'Device:${info.utsname.machine} Model:${info.model} version ${info.systemVersion}';
} else {}
}
}

View File

@@ -1,257 +0,0 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/di/chicken_di.dart';
import 'package:rasadyar_chicken/features/common/data/model/request/change_password/change_password_request_model.dart';
import 'package:rasadyar_chicken/features/common/data/model/response/iran_province_city/iran_province_city_model.dart';
import 'package:rasadyar_chicken/features/common/data/model/response/user_profile/user_profile.dart';
import 'package:rasadyar_chicken/features/common/data/repositories/common/common_repository.dart';
import 'package:rasadyar_core/core.dart';
class ProfileLogic extends GetxController {
CommonRepository commonRepository = diChicken.get<CommonRepository>();
GService gService = Get.find<GService>();
TokenStorageService tokenService = Get.find<TokenStorageService>();
RxInt selectedInformationType = 0.obs;
Rxn<Jalali> birthDate = Rxn<Jalali>();
Rx<Resource<UserProfile>> userProfile = Rx<Resource<UserProfile>>(
Resource.loading(),
);
Rx<Resource<UserLocalModel>> userLocal = Rx<Resource<UserLocalModel>>(
Resource.loading(),
);
TextEditingController nameController = TextEditingController();
TextEditingController lastNameController = TextEditingController();
TextEditingController nationalCodeController = TextEditingController();
TextEditingController nationalIdController = TextEditingController();
TextEditingController birthdayController = TextEditingController();
TextEditingController oldPasswordController = TextEditingController();
TextEditingController newPasswordController = TextEditingController();
TextEditingController retryNewPasswordController = TextEditingController();
RxList<IranProvinceCityModel> cites = <IranProvinceCityModel>[].obs;
Rxn<IranProvinceCityModel> selectedProvince = Rxn();
Rxn<IranProvinceCityModel> selectedCity = Rxn();
GlobalKey<FormState> formKey = GlobalKey();
ImagePicker imagePicker = ImagePicker();
Rxn<XFile> selectedImage = Rxn<XFile>();
final RxnString _base64Image = RxnString();
RxBool isOnLoading = false.obs;
RxBool isUserInformationOpen = true.obs;
RxBool isUnitInformationOpen = false.obs;
ScrollController scrollController = ScrollController();
@override
void onInit() {
super.onInit();
ever(selectedImage, (data) async {
if (data?.path != null) {
_base64Image.value = await convertImageToBase64(data!.path);
}
});
}
@override
void onReady() {
super.onReady();
getUserProfile();
getUserRole();
selectedProvince.listen((p0) => getCites());
userProfile.listen((data) {
nameController.text = data.data?.firstName ?? '';
lastNameController.text = data.data?.lastName ?? '';
nationalCodeController.text = data.data?.nationalCode ?? '';
nationalIdController.text = data.data?.nationalId ?? '';
birthdayController.text =
data.data?.birthday?.toJalali.formatCompactDate() ?? '';
birthDate.value = data.data?.birthday?.toJalali;
selectedProvince.value = IranProvinceCityModel(
name: data.data?.province ?? '',
id: data.data?.provinceNumber ?? 0,
);
selectedCity.value = IranProvinceCityModel(
name: data.data?.city ?? '',
id: data.data?.cityNumber ?? 0,
);
});
}
Future<void> getUserProfile() async {
userProfile.value = Resource.loading();
await safeCall<UserProfile?>(
call: () async => await commonRepository.getUserProfile(
token: tokenService.accessToken.value!,
),
onSuccess: (result) {
if (result != null) {
userProfile.value = Resource.success(result);
}
},
onError: (error, stackTrace) {},
);
}
Future<void> getCites() async {
await safeCall(
call: () => commonRepository.getCity(
provinceName: selectedProvince.value?.name ?? '',
),
onSuccess: (result) {
if (result != null && result.isNotEmpty) {
cites.value = result;
}
},
);
}
Future<void> updateUserProfile() async {
UserProfile userProfile = UserProfile(
firstName: nameController.text,
lastName: lastNameController.text,
nationalCode: nationalCodeController.text,
nationalId: nationalIdController.text,
birthday: birthDate.value
?.toDateTime()
.formattedDashedGregorian
.toString(),
image: _base64Image.value,
personType: 'self',
type: 'self_profile',
);
isOnLoading.value = true;
await safeCall(
call: () async => await commonRepository.updateUserProfile(
token: tokenService.accessToken.value!,
userProfile: userProfile,
),
onSuccess: (result) {
isOnLoading.value = false;
},
onError: (error, stackTrace) {
isOnLoading.value = false;
},
);
}
Future<void> updatePassword() async {
if (formKey.currentState?.validate() ?? false) {
ChangePasswordRequestModel model = ChangePasswordRequestModel(
username: userProfile.value.data?.mobile,
password: newPasswordController.text,
);
await safeCall(
call: () async => await commonRepository.updatePassword(
token: tokenService.accessToken.value!,
model: model,
),
);
}
}
Future<void> getUserRole() async {
userLocal.value = Resource.loading();
await safeCall<UserLocalModel?>(
call: () async => tokenService.getUserLocal(Module.chicken),
onSuccess: (result) {
if (result != null) {
userLocal.value = Resource.success(result);
}
},
onError: (error, stackTrace) {},
);
}
void clearPasswordForm() {
oldPasswordController.clear();
newPasswordController.clear();
retryNewPasswordController.clear();
}
Future<void> changeUserRole(String newRole) async {
dLog(newRole);
await gService.saveRoute(Module.chicken, newRole);
Get.offAllNamed(newRole);
}
void scrollToSelectedItem(
int index, {
double chipWidth = 100,
double spacing = 8,
GlobalKey? itemKey,
}) {
if (!scrollController.hasClients) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_performScroll(index, chipWidth, spacing, itemKey);
});
} else {
_performScroll(index, chipWidth, spacing, itemKey);
}
}
void _performScroll(
int index,
double chipWidth,
double spacing,
GlobalKey? itemKey,
) {
if (!scrollController.hasClients) return;
double targetOffset;
// If we have a GlobalKey, use it for precise positioning
if (itemKey?.currentContext != null) {
final RenderBox? renderBox =
itemKey!.currentContext?.findRenderObject() as RenderBox?;
if (renderBox != null) {
final position = renderBox.localToGlobal(Offset.zero);
final scrollPosition = scrollController.position;
final viewportWidth = scrollPosition.viewportDimension;
final chipWidth = renderBox.size.width;
// Get the scroll position of the item
final itemScrollPosition = position.dx - scrollPosition.pixels;
// Center the item
targetOffset =
scrollPosition.pixels +
itemScrollPosition -
(viewportWidth / 2) +
(chipWidth / 2);
} else {
// Fallback to estimated position
targetOffset = _calculateEstimatedPosition(index, chipWidth, spacing);
}
} else {
// Use estimated position
targetOffset = _calculateEstimatedPosition(index, chipWidth, spacing);
}
scrollController.animateTo(
targetOffset.clamp(0.0, scrollController.position.maxScrollExtent),
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
}
double _calculateEstimatedPosition(
int index,
double chipWidth,
double spacing,
) {
final double itemPosition = (chipWidth + spacing) * index;
final double viewportWidth = scrollController.position.viewportDimension;
return itemPosition - (viewportWidth / 2) + (chipWidth / 2);
}
@override
void onClose() {
scrollController.dispose();
super.onClose();
}
}

View File

@@ -1,902 +0,0 @@
import 'dart:io';
import 'package:flutter/cupertino.dart' hide Image;
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/common/fa_user_role.dart';
import 'package:rasadyar_chicken/data/di/chicken_di.dart';
import 'package:rasadyar_chicken/features/common/data/model/response/user_profile/user_profile.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class ProfilePage extends GetView<ProfileLogic> {
const ProfilePage({super.key});
@override
Widget build(BuildContext context) {
return Column(
spacing: 30,
children: [
Expanded(
child: Container(
color: AppColor.blueNormal,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(),
ObxValue((data) {
final status = data.value.status;
if (status == ResourceStatus.loading) {
return Container(
width: 128.w,
height: 128.h,
child: Center(child: CupertinoActivityIndicator(color: AppColor.greenNormal)),
);
}
if (status == ResourceStatus.error) {
return Container(
width: 128.w,
height: 128.h,
child: Center(child: Text('خطا در دریافت اطلاعات')),
);
}
// Default UI
return Container(
width: 128.w,
height: 128.h,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColor.blueLightActive,
),
child: Center(
child: data.value.data?.image != null
? CircleAvatar(
radius: 64.w,
backgroundImage: NetworkImage(data.value.data!.image!),
)
: Icon(Icons.person, size: 64.w),
),
);
}, controller.userProfile),
],
),
),
),
Expanded(
flex: 3,
child: SingleChildScrollView(
physics: BouncingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
rolesWidget(),
SizedBox(height: 12.h),
ObxValue((data) {
if (data.value.status == ResourceStatus.loading) {
return LoadingWidget();
} else if (data.value.status == ResourceStatus.error) {
return ErrorWidget('خطا در دریافت اطلاعات کاربر');
} else if (data.value.status == ResourceStatus.success) {
return Column(
spacing: 6,
children: [
ObxValue((isOpen) {
return GestureDetector(
onTap: () => isOpen.toggle(),
child: AnimatedContainer(
height: isOpen.value ? 320.h : 47.h,
duration: Duration(milliseconds: 500),
curve: Curves.linear,
child: userProfileInformation(data.value),
),
);
}, controller.isUserInformationOpen),
Visibility(
visible:
data.value.data?.unitName != null ||
data.value.data?.unitAddress != null ||
data.value.data?.unitPostalCode != null ||
data.value.data?.unitRegistrationNumber != null ||
data.value.data?.unitEconomicalNumber != null ||
data.value.data?.unitCity != null ||
data.value.data?.unitProvince != null ||
data.value.data?.unitNationalId != null,
child: ObxValue((isOpen) {
return GestureDetector(
onTap: () => isOpen.toggle(),
child: AnimatedContainer(
height: isOpen.value ? 320.h : 47.h,
duration: Duration(milliseconds: 500),
curve: Curves.linear,
child: unitInformation(data.value),
),
);
}, controller.isUnitInformationOpen),
),
],
);
} else {
return SizedBox.shrink();
}
}, controller.userProfile),
GestureDetector(
onTap: () {
Get.bottomSheet(changePasswordBottomSheet(), isScrollControlled: true);
},
child: Container(
height: 47.h,
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 8.h),
padding: EdgeInsets.symmetric(horizontal: 11.h, vertical: 8.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 1, color: const Color(0xFFD6D6D6)),
),
child: Row(
spacing: 6,
children: [
Assets.vec.lockSvg.svg(
width: 24.w,
height: 24.h,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
Text(
'تغییر رمز عبور',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.blueNormal),
),
],
),
),
),
GestureDetector(
onTap: () {
Get.bottomSheet(exitBottomSheet(), isScrollControlled: true);
},
child: Container(
height: 47.h,
margin: EdgeInsets.symmetric(horizontal: 8),
padding: EdgeInsets.symmetric(horizontal: 11.h, vertical: 8.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 1, color: const Color(0xFFD6D6D6)),
),
child: Row(
spacing: 6,
children: [
Assets.vec.logoutSvg.svg(
width: 24.w,
height: 24.h,
colorFilter: ColorFilter.mode(AppColor.redNormal, BlendMode.srcIn),
),
Text(
'خروج',
textAlign: TextAlign.center,
style: AppFonts.yekan14.copyWith(color: AppColor.redNormal),
),
],
),
),
),
SizedBox(height: 100),
],
),
),
),
],
);
}
Container invoiceIssuanceInformation() => Container();
Widget bankInformationWidget() => Column(
spacing: 16,
children: [
itemList(title: 'نام بانک', content: 'سامان'),
itemList(title: 'نام صاحب حساب', content: 'رضا رضایی'),
itemList(title: 'شماره کارت ', content: '54154545415'),
itemList(title: 'شماره حساب', content: '62565263263652'),
itemList(title: 'شماره شبا', content: '62565263263652'),
],
);
Widget userProfileInformation(Resource<UserProfile> value) {
UserProfile item = value.data!;
return Stack(
clipBehavior: Clip.none,
children: [
Positioned.fill(
child: ObxValue(
(val) => Container(
height: val.value ? 320.h : 47.h,
margin: EdgeInsets.symmetric(horizontal: 8, vertical: val.value ? 8 : 0),
padding: EdgeInsets.symmetric(horizontal: 11.h, vertical: 8.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 0.5, color: AppColor.darkGreyLight),
),
child: val.value
? Column(
spacing: 6,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
GestureDetector(
onTap: () {
Get.bottomSheet(
userInformationBottomSheet(),
isScrollControlled: true,
ignoreSafeArea: false,
);
},
child: Assets.vec.editSvg.svg(
width: 24.w,
height: 24.h,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
),
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
child: Column(
children: [
itemList(
title: 'نام و نام خانوادگی',
content: item.fullname ?? 'نامشخص',
icon: Assets.vec.userSvg.path,
hasColoredBox: true,
),
itemList(
title: 'کدملی',
content: item.nationalId ?? 'نامشخص',
icon: Assets.vec.tagUserSvg.path,
),
itemList(
title: 'موبایل',
content: item.mobile ?? 'نامشخص',
icon: Assets.vec.callSvg.path,
),
itemList(
title: 'شماره شناسنامه',
content: item.nationalCode ?? 'نامشخص',
icon: Assets.vec.userSquareSvg.path,
),
itemList(
title: 'تاریخ تولد',
content: item.birthday?.toJalali.formatCompactDate() ?? 'نامشخص',
icon: Assets.vec.calendarSvg.path,
),
//todo
itemList(
title: 'استان',
content: item.province ?? 'نامشخص',
icon: Assets.vec.pictureFrameSvg.path,
),
itemList(
title: 'شهر',
content: item.city ?? 'نامشخص',
icon: Assets.vec.mapSvg.path,
),
],
),
),
],
)
: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [Icon(CupertinoIcons.chevron_down, color: AppColor.iconColor)],
),
),
controller.isUserInformationOpen,
),
),
ObxValue(
(isOpen) => AnimatedPositioned(
right: 16,
top: isOpen.value ? -7 : 11,
duration: Duration(milliseconds: 500),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: isOpen.value
? BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 0.5, color: Color(0xFFA9A9A9)),
)
: null,
child: Text(
'اطلاعات هویتی',
style: AppFonts.yekan16.copyWith(color: AppColor.iconColor),
),
),
),
controller.isUserInformationOpen,
),
],
);
}
Widget unitInformation(Resource<UserProfile> value) {
UserProfile item = value.data!;
return Stack(
clipBehavior: Clip.none,
children: [
Positioned.fill(
child: ObxValue(
(val) => Container(
height: val.value ? 320.h : 47.h,
margin: EdgeInsets.symmetric(horizontal: 8, vertical: val.value ? 12 : 0),
padding: EdgeInsets.symmetric(horizontal: 11.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 0.5, color: AppColor.darkGreyLight),
),
child: val.value
? Column(
children: [
SizedBox(height: 5.h),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
GestureDetector(
onTap: () {
Get.bottomSheet(
userInformationBottomSheet(),
isScrollControlled: true,
ignoreSafeArea: false,
);
},
child: Assets.vec.editSvg.svg(
width: 24.w,
height: 24.h,
colorFilter: ColorFilter.mode(AppColor.blueNormal, BlendMode.srcIn),
),
),
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
child: Column(
spacing: 2,
children: [
itemList(
title: 'نام صنفی',
content: item.unitName ?? 'نامشخص',
hasColoredBox: true,
visible: item.unitName != null,
),
itemList(
title: 'شناسنامه ملی',
content: item.unitNationalId ?? 'نامشخص',
visible: item.unitName != null,
),
itemList(
title: 'شماره ثبت',
content: item.unitRegistrationNumber ?? 'نامشخص',
visible: item.unitName != null,
),
itemList(
title: 'کد اقتصادی',
content: item.unitEconomicalNumber ?? 'نامشخص',
visible: item.unitName != null,
),
itemList(
title: 'کد پستی',
content: item.unitPostalCode ?? 'نامشخص',
visible: item.unitName != null,
),
itemList(
title: 'استان',
content: item.province ?? 'نامشخص',
visible: item.unitName != null,
),
itemList(
title: 'شهر',
content: item.city ?? 'نامشخص',
visible: item.unitName != null,
),
itemList(title: 'آدرس', content: item.unitAddress ?? 'نامشخص'),
],
),
),
],
)
: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [Icon(CupertinoIcons.chevron_down, color: AppColor.iconColor)],
),
),
controller.isUnitInformationOpen,
),
),
ObxValue(
(isOpen) => AnimatedPositioned(
right: 16,
top: isOpen.value ? -2 : 11,
duration: Duration(milliseconds: 500),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: isOpen.value
? BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 0.5, color: Color(0xFFA9A9A9)),
)
: null,
child: Text(
'اطلاعات صنفی',
style: AppFonts.yekan16.copyWith(color: AppColor.iconColor),
),
),
),
controller.isUnitInformationOpen,
),
],
);
}
Widget itemList({
required String title,
required String content,
String? icon,
bool hasColoredBox = false,
bool? visible,
}) => Visibility(
visible: visible ?? true,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12.h, vertical: 6.h),
decoration: BoxDecoration(
color: hasColoredBox ? AppColor.greenLight : Colors.transparent,
borderRadius: BorderRadius.circular(8),
border: hasColoredBox
? Border.all(width: 0.25, color: AppColor.bgDark)
: Border.all(width: 0, color: Colors.transparent),
),
child: Row(
spacing: 4,
children: [
if (icon != null)
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: SvgGenImage.vec(icon).svg(
width: 20.w,
height: 20.h,
colorFilter: ColorFilter.mode(AppColor.textColor, BlendMode.srcIn),
),
),
Text(title, style: AppFonts.yekan14.copyWith(color: AppColor.textColor)),
Spacer(),
Text(content, style: AppFonts.yekan14.copyWith(color: AppColor.textColor)),
],
),
),
);
Widget cardActionWidget({
required String title,
required VoidCallback onPressed,
required String icon,
bool selected = false,
ColorFilter? color,
Color? cardColor,
Color? cardIconColor,
Color? textColor,
}) {
return GestureDetector(
onTap: onPressed,
child: Column(
spacing: 4,
children: [
Container(
width: 52.w,
height: 52.h,
padding: EdgeInsets.all(6),
decoration: ShapeDecoration(
color: cardColor ?? AppColor.blueLight,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: Container(
padding: EdgeInsets.all(4),
decoration: BoxDecoration(
color: cardIconColor,
borderRadius: BorderRadius.circular(8),
),
child: SvgGenImage.vec(icon).svg(
width: 40.w,
height: 40.h,
colorFilter: color ?? ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
),
),
SizedBox(height: 2),
Text(
title,
style: AppFonts.yekan10.copyWith(color: AppColor.textColor),
textAlign: TextAlign.center,
),
],
),
);
}
Widget userInformationBottomSheet() {
return BaseBottomSheet(
height: 750.h,
child: SingleChildScrollView(
child: Column(
spacing: 8,
children: [
Text(
'ویرایش اطلاعات هویتی',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.darkGreyDarkHover),
),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
spacing: 12,
children: [
RTextField(
controller: controller.nameController,
label: 'نام',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
),
RTextField(
controller: controller.lastNameController,
label: 'نام خانوادگی',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
),
RTextField(
controller: controller.nationalCodeController,
label: 'شماره شناسنامه',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
),
RTextField(
controller: controller.nationalIdController,
label: 'کد ملی',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
),
ObxValue((data) {
return RTextField(
controller: controller.birthdayController,
label: 'تاریخ تولد',
initText: data.value?.formatCompactDate() ?? '',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
onTap: () {},
);
}, controller.birthDate),
SizedBox(),
],
),
),
SizedBox(),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.darkGreyLight, width: 1),
),
child: Column(
spacing: 8,
children: [
Text(
'عکس پروفایل',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.blueNormal),
),
ObxValue((data) {
return Container(
width: Get.width,
height: 270,
decoration: BoxDecoration(
color: AppColor.lightGreyNormal,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 1, color: AppColor.blackLight),
),
child: Center(
child: data.value == null
? Padding(
padding: const EdgeInsets.fromLTRB(30, 10, 10, 30),
child: Image.network(
controller.userProfile.value.data?.image ?? '',
),
)
: Image.file(File(data.value!.path), fit: BoxFit.cover),
),
);
}, controller.selectedImage),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
RElevated(
text: 'گالری',
width: 150.w,
height: 40.h,
textStyle: AppFonts.yekan20.copyWith(color: Colors.white),
onPressed: () async {
controller.selectedImage.value = await controller.imagePicker.pickImage(
source: ImageSource.gallery,
imageQuality: 60,
maxWidth: 1080,
maxHeight: 720,
);
},
),
SizedBox(width: 16),
ROutlinedElevated(
text: 'دوربین',
width: 150.w,
height: 40.h,
textStyle: AppFonts.yekan20.copyWith(color: AppColor.blueNormal),
onPressed: () async {
controller.selectedImage.value = await controller.imagePicker.pickImage(
source: ImageSource.camera,
imageQuality: 60,
maxWidth: 1080,
maxHeight: 720,
);
},
),
],
),
],
),
),
Row(
spacing: 16,
mainAxisAlignment: MainAxisAlignment.center,
children: [
ObxValue((data) {
return RElevated(
height: 40.h,
text: 'ویرایش',
isLoading: data.value,
onPressed: () async {
await controller.updateUserProfile();
controller.getUserProfile();
Get.back();
},
);
}, controller.isOnLoading),
ROutlinedElevated(
height: 40.h,
text: 'انصراف',
borderColor: AppColor.blueNormal,
onPressed: () {
Get.back();
},
),
],
),
],
),
),
);
}
Widget changePasswordBottomSheet() {
return BaseBottomSheet(
height: 400.h,
child: SingleChildScrollView(
child: Form(
key: controller.formKey,
child: Column(
spacing: 8,
children: [
Text(
'تغییر رمز عبور',
style: AppFonts.yekan16Bold.copyWith(color: AppColor.darkGreyDarkHover),
),
SizedBox(),
RTextField(
controller: controller.oldPasswordController,
hintText: 'رمز عبور قبلی',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'رمز عبور را وارد کنید';
} else if (controller.userProfile.value.data?.password != value) {
return 'رمز عبور صحیح نیست';
}
return null;
},
),
RTextField(
controller: controller.newPasswordController,
hintText: 'رمز عبور جدید',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'رمز عبور را وارد کنید';
} else if (value.length < 6) {
return 'رمز عبور باید بیش از 6 کارکتر باشد.';
}
return null;
},
),
RTextField(
controller: controller.retryNewPasswordController,
hintText: 'تکرار رمز عبور جدید',
borderColor: AppColor.darkGreyLight,
filledColor: AppColor.bgLight,
filled: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'رمز عبور را وارد کنید';
} else if (value.length < 6) {
return 'رمز عبور باید بیش از 6 کارکتر باشد.';
} else if (controller.newPasswordController.text != value) {
return 'رمز عبور جدید یکسان نیست';
}
return null;
},
),
SizedBox(),
Row(
spacing: 16,
mainAxisAlignment: MainAxisAlignment.center,
children: [
RElevated(
height: 40.h,
text: 'ویرایش',
onPressed: () async {
if (controller.formKey.currentState?.validate() != true) {
return;
}
await controller.updatePassword();
controller.getUserProfile();
controller.clearPasswordForm();
Get.back();
},
),
ROutlinedElevated(
height: 40.h,
text: 'انصراف',
borderColor: AppColor.blueNormal,
onPressed: () {
Get.back();
},
),
],
),
],
),
),
),
);
}
Widget exitBottomSheet() {
return BaseBottomSheet(
height: 220.h,
child: SingleChildScrollView(
child: Form(
key: controller.formKey,
child: Column(
spacing: 8,
children: [
Text('خروج', style: AppFonts.yekan16Bold.copyWith(color: AppColor.error)),
SizedBox(),
Text(
'آیا مطمئن هستید که می‌خواهید از حساب کاربری خود خارج شوید؟',
textAlign: TextAlign.center,
style: AppFonts.yekan16Bold.copyWith(color: AppColor.textColor),
),
SizedBox(),
Row(
spacing: 16,
mainAxisAlignment: MainAxisAlignment.center,
children: [
RElevated(
height: 40.h,
text: 'خروج',
backgroundColor: AppColor.error,
onPressed: () async {
await Future.wait([
controller.tokenService.deleteModuleTokens(Module.chicken),
controller.gService.clearSelectedModule(),
]).then((value) async {
await removeChickenDI();
Get.offAllNamed("/moduleList");
});
},
),
ROutlinedElevated(
height: 40.h,
text: 'انصراف',
borderColor: AppColor.blueNormal,
onPressed: () {
Get.back();
},
),
],
),
],
),
),
),
);
}
Widget rolesWidget() {
return ObxValue((data) {
if (data.value.status == ResourceStatus.loading) {
return CupertinoActivityIndicator();
} else if (data.value.status == ResourceStatus.error) {
return ErrorWidget('خطا در دریافت اطلاعات کاربر');
} else if (data.value.status == ResourceStatus.success) {
List<String>? item = data.value.data?.roles;
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(horizontal: 8.w),
physics: BouncingScrollPhysics(),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 8.w,
children: List.generate(item?.length ?? 0, (index) {
Map tmpRole = getFaUserRoleWithOnTap(item?[index]);
return CustomChip(
isSelected: controller.gService.getRoute(Module.chicken) == tmpRole.values.first,
title: tmpRole.keys.first,
index: index,
onTap: (int p1) {
controller.changeUserRole(tmpRole.values.first);
},
);
}),
),
);
} else {
return SizedBox.shrink();
}
}, controller.userLocal);
}
}

View File

@@ -1,18 +0,0 @@
import 'package:rasadyar_core/core.dart';
class RoleLogic extends GetxController {
TokenStorageService tokenService = Get.find<TokenStorageService>();
GService gService = Get.find<GService>();
RxList<String> roles = <String>[].obs;
@override
void onInit() {
super.onInit();
List<String> items = tokenService.getUserLocal(Module.chicken)!.roles ?? [];
if (items.isNotEmpty) {
roles.assignAll(items);
}
}
}

View File

@@ -1,79 +0,0 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/common/fa_user_role.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class RolePage extends GetView<RoleLogic> {
const RolePage({super.key});
@override
Widget build(BuildContext context) {
return ChickenBasePage(
isBase: true,
child: Column(
children: [
Assets.images.selectRole.image(height: 212.h, width: Get.width.w, fit: BoxFit.cover),
ObxValue((data) {
return Expanded(
child: GridView.builder(
physics: BouncingScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 12.h,
crossAxisSpacing: 12.w,
childAspectRatio: 2,
),
itemCount: data.length,
hitTestBehavior: HitTestBehavior.opaque,
itemBuilder: (BuildContext context, int index) {
Map role = getFaUserRoleWithOnTap(data[index]);
return roleCard(
title: role.keys.first,
onTap: () async {
try {
String route = role.values.first;
await controller.gService.saveRoute(Module.chicken, route);
await controller.gService.saveRole(Module.chicken, data[index]);
Get.offAllNamed(route);
} catch (e) {
eLog(
"احتمالا در\n ``getFaUserRoleWithOnTap`` \nروت اش را تعریف نکردی 👻👻 ==>$e ",
);
}
},
);
},
),
);
}, controller.roles),
],
),
);
}
}
Widget roleCard({required String title, Function()? onTap, int? width, int? height}) {
return Container(
width: width?.w ?? 128.w,
height: height?.h ?? 48.h,
margin: EdgeInsets.all(8.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8.r),
border: Border.all(color: AppColor.blueNormal, width: 1.w),
),
child: InkWell(
onTap: onTap,
child: Center(
child: Text(
title,
style: AppFonts.yekan12Bold.copyWith(color: AppColor.blueNormal),
textAlign: TextAlign.center,
),
),
),
);
}

View File

@@ -1,9 +1,9 @@
import 'package:rasadyar_chicken/data/models/kill_house_module/register_request/response/chicken_commission_prices/chicken_commission_prices.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/register_request/response/chicken_commission_prices/chicken_commission_prices.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/register_request/response/kill_house/kill_house_response.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/register_request/response/kill_house/kill_house_response.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/register_request/response/kill_request_list/kill_request_list.dart' import 'package:rasadyar_chicken/features/kill_house/data/model/register_request/response/kill_request_list/kill_request_list.dart'
as listModel; as listModel;
import 'package:rasadyar_chicken/data/models/kill_house_module/warehouse_and_distribution/response/kill_house_bars/kill_house_bars_response.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/warehouse_and_distribution/response/kill_house_bars/kill_house_bars_response.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/warehouse_and_distribution/response/kill_house_sales_info_dashboard/kill_house_sales_info_dashboard.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/warehouse_and_distribution/response/kill_house_sales_info_dashboard/kill_house_sales_info_dashboard.dart';
import 'package:rasadyar_core/core.dart'; import 'package:rasadyar_core/core.dart';
abstract class KillHouseRemoteDataSource { abstract class KillHouseRemoteDataSource {

View File

@@ -1,10 +1,10 @@
import 'package:rasadyar_chicken/data/data_source/remote/kill_house/kill_house_remote.dart'; import 'package:rasadyar_chicken/features/kill_house/data/data_source/remote/kill_house/kill_house_remote.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/register_request/response/chicken_commission_prices/chicken_commission_prices.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/register_request/response/chicken_commission_prices/chicken_commission_prices.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/register_request/response/kill_house/kill_house_response.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/register_request/response/kill_house/kill_house_response.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/register_request/response/kill_request_list/kill_request_list.dart' import 'package:rasadyar_chicken/features/kill_house/data/model/register_request/response/kill_request_list/kill_request_list.dart'
as listModel; as listModel;
import 'package:rasadyar_chicken/data/models/kill_house_module/warehouse_and_distribution/response/kill_house_bars/kill_house_bars_response.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/warehouse_and_distribution/response/kill_house_bars/kill_house_bars_response.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/warehouse_and_distribution/response/kill_house_sales_info_dashboard/kill_house_sales_info_dashboard.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/warehouse_and_distribution/response/kill_house_sales_info_dashboard/kill_house_sales_info_dashboard.dart';
import 'package:rasadyar_core/core.dart'; import 'package:rasadyar_core/core.dart';
class KillHouseRemoteDataSourceImpl extends KillHouseRemoteDataSource { class KillHouseRemoteDataSourceImpl extends KillHouseRemoteDataSource {

View File

@@ -1,11 +1,11 @@
import 'package:rasadyar_chicken/data/models/kill_house_module/register_request/request/kill_request_response.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/register_request/request/kill_request_response.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/register_request/response/chicken_commission_prices/chicken_commission_prices.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/register_request/response/chicken_commission_prices/chicken_commission_prices.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/register_request/response/kill_house/kill_house_response.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/register_request/response/kill_house/kill_house_response.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/register_request/response/kill_request_list/kill_request_list.dart' import 'package:rasadyar_chicken/features/kill_house/data/model/register_request/response/kill_request_list/kill_request_list.dart'
as listModel as listModel
show KillRequestList; show KillRequestList;
import 'package:rasadyar_chicken/data/models/kill_house_module/warehouse_and_distribution/response/kill_house_bars/kill_house_bars_response.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/warehouse_and_distribution/response/kill_house_bars/kill_house_bars_response.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/warehouse_and_distribution/response/kill_house_sales_info_dashboard/kill_house_sales_info_dashboard.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/warehouse_and_distribution/response/kill_house_sales_info_dashboard/kill_house_sales_info_dashboard.dart';
import 'package:rasadyar_core/data/model/pagination_model/pagination_model.dart'; import 'package:rasadyar_core/data/model/pagination_model/pagination_model.dart';

View File

@@ -1,11 +1,11 @@
import 'package:rasadyar_chicken/data/data_source/remote/kill_house/kill_house_remote.dart'; import 'package:rasadyar_chicken/features/kill_house/data/data_source/remote/kill_house/kill_house_remote.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/register_request/request/kill_request_response.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/register_request/request/kill_request_response.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/register_request/response/chicken_commission_prices/chicken_commission_prices.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/register_request/response/chicken_commission_prices/chicken_commission_prices.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/register_request/response/kill_house/kill_house_response.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/register_request/response/kill_house/kill_house_response.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/register_request/response/kill_request_list/kill_request_list.dart' import 'package:rasadyar_chicken/features/kill_house/data/model/register_request/response/kill_request_list/kill_request_list.dart'
as ListModel; as ListModel;
import 'package:rasadyar_chicken/data/models/kill_house_module/warehouse_and_distribution/response/kill_house_bars/kill_house_bars_response.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/warehouse_and_distribution/response/kill_house_bars/kill_house_bars_response.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/warehouse_and_distribution/response/kill_house_sales_info_dashboard/kill_house_sales_info_dashboard.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/warehouse_and_distribution/response/kill_house_sales_info_dashboard/kill_house_sales_info_dashboard.dart';
import 'package:rasadyar_core/core.dart'; import 'package:rasadyar_core/core.dart';
import 'kill_house_repository.dart'; import 'kill_house_repository.dart';

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/di/chicken_di.dart'; import 'package:rasadyar_chicken/data/di/chicken_di.dart';
import 'package:rasadyar_chicken/data/repositories/kill_house/kill_house_repository.dart'; import 'package:rasadyar_chicken/features/kill_house/data/repository/kill_house_repository.dart';
import 'package:rasadyar_chicken/features/common/presentation/page/profile/view.dart'; import 'package:rasadyar_chicken/features/common/presentation/page/profile/view.dart';
import 'package:rasadyar_chicken/presentation/routes/pages.dart'; import 'package:rasadyar_chicken/presentation/routes/pages.dart';
import 'package:rasadyar_chicken/presentation/routes/routes.dart'; import 'package:rasadyar_chicken/presentation/routes/routes.dart';

View File

@@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/register_request/request/kill_request_response.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/register_request/request/kill_request_response.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/register_request/response/chicken_commission_prices/chicken_commission_prices.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/register_request/response/chicken_commission_prices/chicken_commission_prices.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/register_request/response/kill_house/kill_house_response.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/register_request/response/kill_house/kill_house_response.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/register_request/response/kill_request_list/kill_request_list.dart' import 'package:rasadyar_chicken/features/kill_house/data/model/register_request/response/kill_request_list/kill_request_list.dart'
as listModel; as listModel;
import 'package:rasadyar_chicken/features/kill_house/root/logic.dart'; import 'package:rasadyar_chicken/features/kill_house/root/logic.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/logic.dart'; import 'package:rasadyar_chicken/presentation/widget/base_page/logic.dart';

View File

@@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/register_request/response/kill_request_list/kill_request_list.dart' import 'package:rasadyar_chicken/features/kill_house/data/model/register_request/response/kill_request_list/kill_request_list.dart'
as listModel as listModel
show KillRequestList; show KillRequestList;
import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart'; import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart';

View File

@@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/warehouse_and_distribution/response/kill_house_bars/kill_house_bars_response.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/warehouse_and_distribution/response/kill_house_bars/kill_house_bars_response.dart';
import 'package:rasadyar_chicken/features/kill_house/warehouse_and_distribution/root/logic.dart'; import 'package:rasadyar_chicken/features/kill_house/warehouse_and_distribution/root/logic.dart';
import 'package:rasadyar_core/core.dart'; import 'package:rasadyar_core/core.dart';

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/warehouse_and_distribution/response/kill_house_bars/kill_house_bars_response.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/warehouse_and_distribution/response/kill_house_bars/kill_house_bars_response.dart';
import 'package:rasadyar_core/core.dart'; import 'package:rasadyar_core/core.dart';

View File

@@ -1,7 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/warehouse_and_distribution/response/kill_house_bars/kill_house_bars_response.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/warehouse_and_distribution/response/kill_house_bars/kill_house_bars_response.dart';
import 'package:rasadyar_chicken/features/kill_house/warehouse_and_distribution/root/logic.dart'; import 'package:rasadyar_chicken/features/kill_house/warehouse_and_distribution/root/logic.dart';
import 'package:rasadyar_chicken/presentation/utils/utils.dart'; import 'package:rasadyar_chicken/presentation/utils/utils.dart';
import 'package:rasadyar_core/core.dart'; import 'package:rasadyar_core/core.dart';

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/warehouse_and_distribution/response/kill_house_bars/kill_house_bars_response.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/warehouse_and_distribution/response/kill_house_bars/kill_house_bars_response.dart';
import 'package:rasadyar_core/core.dart'; import 'package:rasadyar_core/core.dart';
import 'logic.dart'; import 'logic.dart';

View File

@@ -3,9 +3,9 @@ import 'dart:async';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:rasadyar_chicken/data/di/chicken_di.dart'; import 'package:rasadyar_chicken/data/di/chicken_di.dart';
import 'package:rasadyar_chicken/data/models/kill_house_module/warehouse_and_distribution/response/kill_house_sales_info_dashboard/kill_house_sales_info_dashboard.dart'; import 'package:rasadyar_chicken/features/kill_house/data/model/warehouse_and_distribution/response/kill_house_sales_info_dashboard/kill_house_sales_info_dashboard.dart';
import 'package:rasadyar_chicken/data/repositories/kill_house/kill_house_repository.dart'; import 'package:rasadyar_chicken/features/kill_house/data/repository/kill_house_repository.dart';
import 'package:rasadyar_chicken/features/common/profile/view.dart'; import 'package:rasadyar_chicken/features/common/presentation/page/profile/view.dart';
import 'package:rasadyar_chicken/features/kill_house/warehouse_and_distribution/buy/view.dart'; import 'package:rasadyar_chicken/features/kill_house/warehouse_and_distribution/buy/view.dart';
import 'package:rasadyar_chicken/features/kill_house/warehouse_and_distribution/home/view.dart'; import 'package:rasadyar_chicken/features/kill_house/warehouse_and_distribution/home/view.dart';
import 'package:rasadyar_chicken/features/kill_house/warehouse_and_distribution/sale/view.dart'; import 'package:rasadyar_chicken/features/kill_house/warehouse_and_distribution/sale/view.dart';

View File

@@ -4,7 +4,7 @@ import 'package:rasadyar_chicken/features/poultry_science/presentation/widgets/s
import 'package:rasadyar_core/core.dart'; import 'package:rasadyar_core/core.dart';
Widget farmInfoWidget({ Widget farmInfoWidget({
required CreateInspectionBottomSheetLogic controller, CreateInspectionBottomSheetLogic? controller,
required String title, required String title,
required Widget child, required Widget child,
EdgeInsets? padding, EdgeInsets? padding,

View File

@@ -1,4 +1,3 @@
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';
import 'package:rasadyar_chicken/features/poultry_science/presentation/widgets/submit_inspection_bottom_sheet/card_info.dart'; import 'package:rasadyar_chicken/features/poultry_science/presentation/widgets/submit_inspection_bottom_sheet/card_info.dart';

View File

@@ -619,6 +619,7 @@ GestureDetector containerChips({
onTap: () => onTap(index), onTap: () => onTap(index),
child: Container( child: Container(
height: 24.h, height: 24.h,
padding: EdgeInsets.symmetric(horizontal: 12.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: index == selectedIndex color: index == selectedIndex
? AppColor.green1Normal ? AppColor.green1Normal

View File

@@ -12,4 +12,8 @@ abstract class VetFarmRemoteDataSource {
required String token, required String token,
required SubmitInspectionResponse request, required SubmitInspectionResponse request,
}); });
Future<DioResponse<Map<String, dynamic>>> getSDUIForm({
required String token,
});
} }

View File

@@ -1,5 +1,5 @@
import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart';
import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart'; import 'package:rasadyar_chicken/features/poultry_science/data/model/request/submit_inspection/submit_inspection_response.dart';
import 'package:rasadyar_chicken/features/poultry_science/data/model/response/poultry_science_report/poultry_science_report.dart';
import 'package:rasadyar_chicken/features/vet_farm/data/datasources/remote/vet_farm_remote_data_source.dart'; import 'package:rasadyar_chicken/features/vet_farm/data/datasources/remote/vet_farm_remote_data_source.dart';
import 'package:rasadyar_core/core.dart'; import 'package:rasadyar_core/core.dart';
@@ -36,4 +36,16 @@ class VetFarmRemoteDataSourceImpl implements VetFarmRemoteDataSource {
data: request.toJson(), data: request.toJson(),
); );
} }
@override
Future<DioResponse<Map<String, dynamic>>> getSDUIForm({required String token}) async {
var res = await _httpClient.get(
'/form-information/?hatching=232&form=b1',
headers: {'Authorization': 'Bearer $token'},
fromJson: (json) {
return json;
},
);
return res;
}
} }

View File

@@ -12,4 +12,9 @@ abstract class VetFarmRepository {
required String token, required String token,
required SubmitInspectionResponse request, required SubmitInspectionResponse request,
}); });
Future<DioResponse<Map<String, dynamic>>> getSDUIForm({
required String token,
});
} }

View File

@@ -27,4 +27,8 @@ class VetFarmRepositoryImpl implements VetFarmRepository {
}) async { }) async {
return await _remote.submitInspection(token: token, request: request); return await _remote.submitInspection(token: token, request: request);
} }
@override
Future<DioResponse<Map<String, dynamic>>> getSDUIForm({required String token,}) async {
return await _remote.getSDUIForm(token: token);
}
} }

View File

@@ -21,5 +21,10 @@ class VetFarmHomeLogic extends GetxController {
route: VetFarmRoutes.newInspectionVetFarm, route: VetFarmRoutes.newInspectionVetFarm,
icon: Assets.vec.activeFramSvg.path, icon: Assets.vec.activeFramSvg.path,
), ),
VetFarmHomeItem(
title: "صفحه جدید",
route: VetFarmRoutes.newPageVetFarm,
icon: Assets.vec.activeFramSvg.path,
),
].obs; ].obs;
} }

View File

@@ -0,0 +1,213 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/features/vet_farm/presentation/pages/root/logic.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/model/sdui_widget.dart';
import 'package:rasadyar_core/core.dart';
class NewPageLogic extends GetxController {
VetFarmRootLogic rootLogic = Get.find<VetFarmRootLogic>();
Rxn<SDUIWidgetModel> sduiModel = Rxn<SDUIWidgetModel>();
Map<String, TextEditingController> controllers = {};
RxMap<String, dynamic> formState = <String, dynamic>{}.obs;
Map<String, RxList<XFile>> images = {};
@override
void onInit() {
super.onInit();
getSDUIForm();
}
@override
void onClose() {
for (var controller in controllers.values) {
controller.dispose();
}
controllers.clear();
super.onClose();
}
Future<void> getSDUIForm() async {
await safeCall(
call: () async => await rootLogic.vetFarmRepository.getSDUIForm(
token: rootLogic.tokenService.accessToken.value ?? '',
),
onSuccess: (result) {
if (result.data != null) {
try {
Map<String, dynamic>? sduiData;
if (result.data!['info'] != null && result.data!['info'] is Map) {
final infoMap = result.data!['info'] as Map<String, dynamic>;
// Check if info has type field (meaning it's the SDUI structure itself)
if (infoMap.containsKey('type')) {
sduiData = infoMap;
iLog('SDUI data extracted from info (info contains type field)');
} else if (infoMap['data'] != null) {
// Fallback: if info.data exists, use it
sduiData = infoMap['data'] as Map<String, dynamic>;
iLog('SDUI data extracted from info.data');
} else {
iLog('info exists but has no type or data field');
}
} else {
// Fallback: try direct data structure
sduiData = result.data;
iLog('Using direct data structure (no info field)');
}
if (sduiData != null) {
iLog('SDUI data to parse: $sduiData');
sduiModel.value = SDUIWidgetModel.fromJson(sduiData);
// Log model info using pattern matching
final modelType =
sduiModel.value?.maybeWhen(
textFormField: (data, visible, visibleCondition) => 'text_form_field',
cardLabelItem: (data, child, children, visible, visibleCondition) =>
'card_label_item',
chipSelection: (data, visible, visibleCondition) => 'chip_selection',
dropdown: (data, visible, visibleCondition) => 'dropdown',
imagePicker: (data, visible, visibleCondition) => 'image_picker',
column:
(
children,
spacing,
mainAxisSize,
crossAxisAlignment,
visible,
paddingHorizontal,
paddingVertical,
visibleCondition,
) => 'column',
row: (children, spacing, mainAxisAlignment, visible, visibleCondition) => 'row',
sizedBox: (width, height, visible, visibleCondition) => 'sized_box',
stepper: (data, children, visible, visibleCondition) => 'stepper',
pageView: (data, children, visible, visibleCondition) => 'page_view',
orElse: () => 'unknown',
) ??
'null';
final childrenCount =
sduiModel.value?.maybeWhen(
column:
(
children,
spacing,
mainAxisSize,
crossAxisAlignment,
visible,
paddingHorizontal,
paddingVertical,
visibleCondition,
) => children.length,
row: (children, spacing, mainAxisAlignment, visible, visibleCondition) =>
children.length,
cardLabelItem: (data, child, children, visible, visibleCondition) =>
(child != null ? 1 : 0) + (children?.length ?? 0),
stepper: (data, children, visible, visibleCondition) => children?.length ?? 0,
pageView: (data, children, visible, visibleCondition) => children.length,
orElse: () => 0,
) ??
0;
iLog(
'SDUI Model parsed successfully. Type: $modelType, Visible: ${sduiModel.value?.visible}, Children count: $childrenCount',
);
_initializeControllers(sduiModel.value!);
} else {
iLog('SDUI data is null after extraction');
}
} catch (e, stackTrace) {
eLog('Error parsing SDUI model: $e');
eLog('Stack trace: $stackTrace');
eLog('JSON data: ${result.data}');
}
} else {
iLog('SDUI result.data is null');
}
},
onError: (error, stackTrace) {
eLog('Error fetching SDUI form: $error');
eLog('Stack trace: $stackTrace');
},
);
}
void _initializeControllers(SDUIWidgetModel model) {
// Extract all text form field keys from the model and create controllers
_extractTextFields(model);
}
void _extractTextFields(SDUIWidgetModel model) {
// Extract text form field using pattern matching
model.maybeWhen(
textFormField: (data, visible, visibleCondition) {
final key = data.key;
final value = data.value;
if (key != null && !controllers.containsKey(key)) {
controllers[key] = TextEditingController(text: value.toString() ?? '');
}
},
orElse: () {},
);
// Recursively extract from children using pattern matching
model.maybeWhen(
column:
(
children,
spacing,
mainAxisSize,
crossAxisAlignment,
visible,
paddingHorizontal,
paddingVertical,
visibleCondition,
) {
for (var child in children) {
_extractTextFields(child);
}
},
row: (children, spacing, mainAxisAlignment, visible, visibleCondition) {
for (var child in children) {
_extractTextFields(child);
}
},
cardLabelItem: (data, child, children, visible, visibleCondition) {
if (child != null) {
_extractTextFields(child);
}
if (children != null) {
for (var child in children) {
_extractTextFields(child);
}
}
},
stepper: (data, children, visible, visibleCondition) {
if (children != null) {
for (var child in children) {
_extractTextFields(child);
}
}
},
pageView: (data, children, visible, visibleCondition) {
for (var child in children) {
_extractTextFields(child);
}
},
orElse: () {},
);
}
void onButtonPressed() {
// Example: Get all form values
controllers.forEach((key, controller) {
iLog('Field $key: ${controller.text}');
});
// Example: Get all chip selection values
formState.forEach((key, value) {
iLog('State $key: $value');
});
}
}

View File

@@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/presentation/utils/nested_keys_utils.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/view.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/form/sdui_form_widget.dart';
import 'package:rasadyar_core/core.dart';
import 'logic.dart';
class NewPage extends GetView<NewPageLogic> {
const NewPage({super.key});
@override
Widget build(BuildContext context) {
return ChickenBasePage(
hasBack: true,
backId: vetFarmActionKey,
child: contentWidget(),
);
}
Widget contentWidget() {
return SingleChildScrollView(
physics: BouncingScrollPhysics(),
padding: EdgeInsets.all(16.w),
child: Column(
children: [
SizedBox(height: 24.h),
Row(children: []),
SizedBox(height: 24.h),
RElevated(
text: 'دکمه نمونه',
onPressed: () async {
await controller.getSDUIForm();
Get.bottomSheet(
isScrollControlled: true,
enableDrag: true,
BaseBottomSheet(
height: Get.height * 0.8,
rootChild: ObxValue((data) {
if (data.value == null) {
return Center(child: CircularProgressIndicator());
}
return Obx(
() => SDUIFormWidget(
model: data.value!,
controllers: controller.controllers,
state: controller.formState,
onStateChanged: (key, value) {
controller.formState[key] = value;
},
images: controller.images,
onImagesChanged: (key, imageList) {
controller.images[key] = imageList;
},
),
);
}, controller.sduiModel),
),
);
},
),
SizedBox(height: 24.h),
RElevated(
text: '222222222222دکمه نمونه',
onPressed: () {
Get.bottomSheet(
isScrollControlled: true,
enableDrag: true,
BaseBottomSheet(
height: Get.height * 0.8,
rootChild: Column(
children: [
Container(height: 100, width: 100, color: Colors.red),
Expanded(child: Container(color: Colors.blue)),
Container(height: 100, width: 100, color: Colors.green),
],
),
),
);
},
),
],
),
);
}
}

View File

@@ -7,6 +7,8 @@ import 'package:rasadyar_chicken/features/vet_farm/presentation/pages/active_hat
import 'package:rasadyar_chicken/features/vet_farm/presentation/pages/active_hatching/view.dart'; import 'package:rasadyar_chicken/features/vet_farm/presentation/pages/active_hatching/view.dart';
import 'package:rasadyar_chicken/features/vet_farm/presentation/pages/new_inspection/logic.dart'; import 'package:rasadyar_chicken/features/vet_farm/presentation/pages/new_inspection/logic.dart';
import 'package:rasadyar_chicken/features/vet_farm/presentation/pages/new_inspection/view.dart'; import 'package:rasadyar_chicken/features/vet_farm/presentation/pages/new_inspection/view.dart';
import 'package:rasadyar_chicken/features/vet_farm/presentation/pages/new_page/logic.dart';
import 'package:rasadyar_chicken/features/vet_farm/presentation/pages/new_page/view.dart';
import 'package:rasadyar_chicken/features/vet_farm/presentation/routes/routes.dart'; import 'package:rasadyar_chicken/features/vet_farm/presentation/routes/routes.dart';
import 'package:rasadyar_chicken/presentation/routes/global_binding.dart'; import 'package:rasadyar_chicken/presentation/routes/global_binding.dart';
import 'package:rasadyar_chicken/presentation/widget/base_page/logic.dart'; import 'package:rasadyar_chicken/presentation/widget/base_page/logic.dart';
@@ -72,5 +74,18 @@ class VetFarmPages {
}), }),
], ],
), ),
GetPage(
name: VetFarmRoutes.newPageVetFarm,
page: () => NewPage(),
middlewares: [AuthMiddleware()],
bindings: [
GlobalBinding(),
BindingsBuilder(() {
Get.lazyPut(() => NewPageLogic());
}),
],
),
]; ];
} }

View File

@@ -7,4 +7,5 @@ sealed class VetFarmRoutes {
static const actionVetFarm = '$_base/action'; static const actionVetFarm = '$_base/action';
static const activeHatchingVetFarm = '$_base/activeHatching'; static const activeHatchingVetFarm = '$_base/activeHatching';
static const newInspectionVetFarm = '$_base/newInspection'; static const newInspectionVetFarm = '$_base/newInspection';
static const newPageVetFarm = '$_base/newPage';
} }

View File

@@ -0,0 +1,61 @@
import 'package:flutter/foundation.dart';
import 'package:rasadyar_core/core.dart';
class ConditionEvaluator {
static bool check(String? condition, RxMap<String, dynamic>? state) {
if (condition == null || condition.isEmpty) return true;
if (state == null) {
return condition.contains('activeStepperIndex == 0');
}
try {
if (condition.contains(' == ')) {
final parts = condition.split(' == ');
if (parts.length != 2) return true;
final key = parts[0].trim();
var expectedValue = parts[1].trim();
// Remove quotes
if ((expectedValue.startsWith("'") && expectedValue.endsWith("'")) ||
(expectedValue.startsWith('"') && expectedValue.endsWith('"'))) {
expectedValue = expectedValue.substring(1, expectedValue.length - 1);
}
final actualValue = state[key];
if (actualValue == null) {
if (key == 'activeStepperIndex' && expectedValue == '0') return true;
return false;
}
// Handle numeric comparison if possible
final expectedInt = int.tryParse(expectedValue);
if (expectedInt != null) {
if (actualValue is int) return actualValue == expectedInt;
if (actualValue is String) return int.tryParse(actualValue) == expectedInt;
}
return actualValue.toString() == expectedValue;
}
return true;
} catch (e) {
debugPrint('Error parsing condition: $e');
return false;
}
}
/// Extracts step index without using expensive RegExp in a loop if possible,
/// or at least centralizes the logic.
static int? extractStepIndex(String? condition) {
if (condition == null || !condition.contains('activeStepperIndex')) return null;
try {
final parts = condition.split(' == ');
if (parts.length == 2 && parts[0].trim() == 'activeStepperIndex') {
return int.tryParse(parts[1].trim());
}
} catch (_) {}
return null;
}
}

View File

@@ -0,0 +1,11 @@
import 'package:rasadyar_chicken/presentation/widget/sdui/model/sdui_widget.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/stepper/model/stepper_sdui_model.dart';
extension SDUIModelExtensions on SDUIWidgetModel {
// چک میکند آیا این ویجت استپر است؟
bool get isStepper => this.maybeWhen(stepper: (_, _, _, _) => true, orElse: () => false);
// دیتای استپر را برمی‌گرداند (اگر استپر باشد)
StepperSDUIModel? get stepperData =>
maybeWhen(stepper: (data, _, _, _) => data, orElse: () => null);
}

View File

@@ -0,0 +1,27 @@
{
"type": "chip_selection",
"visible": true,
"data": {
"key": "grain_quality",
"label": "کیفیت دانه",
"selectedIndex": -1,
"options": [
{
"index": 0,
"label": "خوب",
"value": "خوب"
},
{
"index": 1,
"label": "متوسط",
"value": "متوسط"
},
{
"index": 2,
"label": "ضعیف",
"value": "ضعیف"
}
]
}
}

View File

@@ -0,0 +1,15 @@
{
"type": "dropdown",
"visible": true,
"data": {
"key": "training_status",
"label": "آموزش‌دیده در حوزه بهداشت و امنیت زیستی",
"placeholder": "آموزش‌دیده در حوزه بهداشت و امنیت زیستی",
"items": [
"بله",
"خیر"
],
"selectedValue": null,
"enabled": true
}
}

View File

@@ -0,0 +1,157 @@
{
"type": "column",
"visible": true,
"data": {
"spacing": 10.0,
"crossAxisAlignment": "start"
},
"children": [
{
"type": "card_label_item",
"visible": true,
"data": {
"title": "اطلاعات پایه واحد",
"padding_horizontal": 12.0,
"padding_vertical": 11.0
},
"child": {
"type": "column",
"visible": true,
"data": {
"spacing": 10.0
},
"children": [
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "unit_name",
"label": "نام واحد مرغداری",
"keyboardType": "text",
"enabled": true,
"readonly": true
}
},
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "breeding_unique_id",
"label": "کد یکتا / شناسه واحد",
"keyboardType": "text",
"enabled": true,
"readonly": true
}
},
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "health_license",
"label": "پروانه بهداشتی",
"keyboardType": "text",
"enabled": true,
"readonly": false
}
},
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "total_capacity",
"label": "ظرفیت اسمی سالن‌ها",
"keyboardType": "number",
"enabled": true,
"readonly": false,
"commaSperator": true
}
}
]
}
},
{
"type": "sized_box",
"visible": true,
"data": {
"height": 30.0
}
},
{
"type": "card_label_item",
"visible": true,
"data": {
"title": "اطلاعات جوجه ریزی",
"padding_horizontal": 12.0,
"padding_vertical": 11.0
},
"child": {
"type": "column",
"visible": true,
"data": {
"spacing": 10.0
},
"children": [
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "hatching_date",
"label": "تاریخ جوجه ریزی",
"keyboardType": "text",
"enabled": true,
"readonly": true
}
},
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "visit_date",
"label": "تاریخ بازدید",
"keyboardType": "text",
"enabled": true,
"readonly": true
}
},
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "hatching_count",
"label": "تعداد جوجه‌ریزی اولیه",
"keyboardType": "number",
"enabled": true,
"readonly": true,
"commaSperator": true
}
},
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "hatching_breed",
"label": "نوع نژاد",
"keyboardType": "text",
"enabled": true,
"readonly": false
}
},
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "hatching_average_weight",
"label": "میانگین وزن جوجه",
"keyboardType": "number",
"enabled": true,
"readonly": false,
"commaSperator": true,
"decimal": true
}
}
]
}
}
]
}

View File

@@ -0,0 +1,11 @@
{
"type": "image_picker",
"visible": true,
"data": {
"key": "hall_images",
"label": "ثبت عکس سالن (حداقل ۳ زاویه)",
"required": true,
"maxImages": null
}
}

View File

@@ -0,0 +1,9 @@
{
"type": "stepper",
"visible": true,
"data": {
"key": "activeStepperIndex",
"totalSteps": 5,
"activeStep": 0
}
}

View File

@@ -0,0 +1,518 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/presentation/utils/condition_evaluator.dart';
import 'package:rasadyar_chicken/presentation/utils/sdui_extension.dart';
// ایمپورت‌های پروژه شما (این‌ها را طبق پروژه خودتان تنظیم کنید)
import 'package:rasadyar_chicken/presentation/widget/sdui/form/sdui_form_widget_controller.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/model/sdui_widget.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/card_label_item/card_label_item_sdui.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/card_label_item/model/card_label_item_sdui_model.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/chip_selection/chip_selection_sdui.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/chip_selection/model/chip_selection_sdui_model.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/dropdown/dropdown_sdui.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/dropdown/model/dropdown_sdui_model.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/page_view/model/page_view_sdui_model.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/page_view/page_view_sdui.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/stepper/stepper_sdui.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/text_form_filed/model/text_form_field_sdui_model.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/text_form_filed/text_form_filed_sdui.dart';
import 'package:rasadyar_core/core.dart';
import '../widgets/image_picker/image_picker_sdui.dart' show ImagePickerSDUI;
import '../widgets/image_picker/model/image_picker_sdui_model.dart' show ImagePickerSDUIModel;
import '../widgets/stepper/model/stepper_sdui_model.dart';
class SDUIFormWidget extends StatelessWidget {
final SDUIWidgetModel model;
final Map<String, TextEditingController>? controllers;
final RxMap<String, dynamic>? state;
final Function(String key, dynamic value)? onStateChanged;
final Map<String, RxList<XFile>>? images;
final Function(String key, RxList<XFile> images)? onImagesChanged;
final Map<String, PageController>? pageControllers;
const SDUIFormWidget({
super.key,
required this.model,
this.controllers,
this.state,
this.onStateChanged,
this.images,
this.onImagesChanged,
this.pageControllers,
});
@override
Widget build(BuildContext context) {
// اگر ویجت اصلی مخفی است، هیچ چیز نساز
final isVisible = model.maybeWhen(
textFormField: (_, v, __) => v,
cardLabelItem: (_, __, ___, v, ____) => v,
chipSelection: (_, v, __) => v,
dropdown: (_, v, __) => v,
imagePicker: (_, v, __) => v,
column: (_, __, ___, ____, _____, ______, v, _______) => v,
row: (_, __, ___, v, ____) => v,
stepper: (_, __, v, ___) => v,
pageView: (_, __, v, ___) => v,
sizedBox: (_, __, v, ___) => v,
orElse: () => false,
);
if (!isVisible) return const SizedBox.shrink();
// ۱. جستجو برای استپر (فقط در فرزندان مستقیم ستون اصلی)
StepperSDUIModel? foundStepperData;
model.maybeWhen(
column: (children, _, _, _, _, _, _, _) {
final stepperWidget = children.firstWhereOrNull((c) => c.isStepper);
foundStepperData = stepperWidget?.stepperData;
},
orElse: () {},
);
// ۲. اگر استپر پیدا شد، لایوت مخصوص استپر را بساز
if (foundStepperData != null) {
return _buildStepperLayout(model, foundStepperData!);
}
// ۳. در غیر این صورت، رندر استاندارد و بازگشتی
return _buildStandardWidget(model);
}
// ===========================================================================
// LAYOUT BUILDERS (STEPPER & STANDARD)
// ===========================================================================
Widget _buildStepperLayout(SDUIWidgetModel rootModel, StepperSDUIModel stepperData) {
final stepperKey = stepperData.key ?? 'activeStepperIndex';
final controllerTag = 'stepper_$stepperKey';
// مدیریت کنترلر GetX برای دکمه‌های بعدی/قبلی
SDUIFormWidgetController formController;
try {
formController = Get.find<SDUIFormWidgetController>(tag: controllerTag);
} catch (_) {
formController = Get.put(SDUIFormWidgetController(), tag: controllerTag);
}
formController.initializeStepper(stepperData.totalSteps ?? 10, stepperKey);
// مقداردهی اولیه استیت اگر وجود نداشته باشد
if (state != null && !state!.containsKey(stepperKey)) {
state![stepperKey] = 0;
}
final scrollController = ScrollController();
// استخراج فرزندان Column اصلی برای جدا کردن محتوا از خودِ نوار استپر
final List<SDUIWidgetModel> rawChildren = rootModel.maybeWhen(
column: (children, _, __, ___, ____, _____, ______, _______) => children,
orElse: () => [],
);
return Obx(() {
// فقط برای تریگر شدن بیلد هنگام تغییر استپ
// ignore: unused_local_variable
final currentStep = formController.currentStep.value;
// اسکرول به بالا هنگام تغییر صفحه
if (scrollController.hasClients) {
scrollController.animateTo(
0,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
}
return Column(
children: [
// A. نوار استپر (Header)
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
child: StepperSDUI(model: stepperData, state: state),
),
// B. بدنه فرم (Scrollable Content)
Expanded(
child: SingleChildScrollView(
controller: scrollController,
padding: EdgeInsets.all(16.w),
physics: const BouncingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
for (final child in rawChildren)
// خود ویجت استپر را دوباره در بدنه رندر نکن
if (!child.isStepper) _buildConditionAwareChild(child),
],
),
),
),
// C. دکمه‌های نویگیشن (Footer)
_buildNavigationButtons(formController, stepperKey),
],
);
});
}
/// این متد قبل از رندر هر ویجت، شرط `visibleCondition` را چک می‌کند
Widget _buildConditionAwareChild(SDUIWidgetModel widgetModel) {
// استخراج visibleCondition از مدل با استفاده از pattern matching
final String? condition = widgetModel.maybeWhen(
textFormField: (_, __, c) => c,
cardLabelItem: (_, __, ___, ____, c) => c,
chipSelection: (_, __, c) => c,
dropdown: (_, __, c) => c,
imagePicker: (_, __, c) => c,
column: (_, _, _, _, _, _, _, c) => c,
row: (_, __, ___, ____, c) => c,
stepper: (_, __, ___, c) => c,
pageView: (_, __, ___, c) => c,
sizedBox: (_, __, ___, c) => c,
orElse: () => null, // Should not happen given the union is exhaustive
);
// بررسی شرط
final isVisible = ConditionEvaluator.check(condition, state);
if (!isVisible) return const SizedBox.shrink();
return Padding(
padding: EdgeInsets.only(bottom: 16.h), // فاصله پیش‌فرض بین آیتم‌ها
child: _buildStandardWidget(widgetModel),
);
}
// ===========================================================================
// WIDGET FACTORY (RECURSIVE MAPPER)
// ===========================================================================
Widget _buildStandardWidget(SDUIWidgetModel widget) {
// ۱. استخراج داده‌ها (بدون نیاز به Obx)
final String? condition = widget.maybeWhen(
textFormField: (_, __, c) => c,
cardLabelItem: (_, __, ___, ____, c) => c,
chipSelection: (_, __, c) => c,
dropdown: (_, __, c) => c,
imagePicker: (_, __, c) => c,
column: (_, _, _, _, _, _, _, c) => c,
row: (_, __, ___, ____, c) => c,
stepper: (_, __, ___, c) => c,
pageView: (_, __, ___, c) => c,
sizedBox: (_, __, ___, c) => c,
orElse: () => null,
);
final bool isVisibleProp = widget.maybeWhen(
textFormField: (_, v, __) => v,
cardLabelItem: (_, __, ___, v, ____) => v,
chipSelection: (_, v, __) => v,
dropdown: (_, v, __) => v,
imagePicker: (_, v, __) => v,
column: (_, __, ___, ____, _____, ______, v, _______) => v,
row: (_, __, ___, v, ____) => v,
stepper: (_, __, v, ___) => v,
pageView: (_, __, v, ___) => v,
sizedBox: (_, __, v, ___) => v,
orElse: () => true,
);
// ۲. اگر ویجت کلاً غیرفعال است، همان اول برگرد (پرفورمنس)
if (!isVisibleProp) {
return const SizedBox.shrink();
}
// ۳. تابع سازنده ویجت (برای جلوگیری از تکرار کد)
Widget buildTheWidget() {
return widget.map(
textFormField: (m) => _buildTextField(m.data),
chipSelection: (m) => _buildChipSelection(m.data),
dropdown: (m) => _buildDropdown(m.data),
imagePicker: (m) => _buildImagePicker(m.data),
cardLabelItem: (m) => _buildCard(m.data, m.child, m.children),
column: (m) => _buildColumn(m.children, m.spacing, m.mainAxisSize, m.crossAxisAlignment),
row: (m) => _buildRow(m.children, m.spacing, m.mainAxisAlignment),
sizedBox: (m) => SizedBox(width: m.width?.w, height: m.height?.h),
stepper: (m) => const SizedBox.shrink(),
pageView: (m) => _buildPageView(m.data, m.children),
);
}
// ۴. تصمیم‌گیری هوشمند برای استفاده از Obx
// اگر شرطی وجود ندارد یا state نال است، نیازی به Obx نیست!
// (چون چیزی برای تغییر کردن وجود ندارد)
if (condition == null || condition.isEmpty || state == null) {
return buildTheWidget();
}
// ۵. فقط اگر شرط وجود داشت، Obx را فعال کن
return Obx(() {
// اینجا مطمئن هستیم که ConditionEvaluator وضعیت state را می‌خواند
if (!ConditionEvaluator.check(condition, state)) {
return const SizedBox.shrink();
}
return buildTheWidget();
});
}
// ===========================================================================
// INDIVIDUAL WIDGET BUILDERS
// ===========================================================================
Widget _buildTextField(TextFormFieldSDUIModel data) {
final key = data.key ?? 'unknown_${UniqueKey()}';
TextEditingController controller;
if (controllers != null) {
controller = controllers!.putIfAbsent(key, () => TextEditingController(text: data.value));
} else {
controller = TextEditingController(text: data.value);
}
return textFormFiledSDUI(
model: data,
controller: controller,
onChanged: (val) => onStateChanged?.call(key, val),
);
}
Widget _buildDropdown(DropdownSDUIModel data) {
return DropdownSDUI(
model: data,
state: state,
onChanged: (key, value) => onStateChanged?.call(key, value),
);
}
Widget _buildChipSelection(ChipSelectionSDUIModel data) {
return ChipSelectionSDUI(
model: data,
state: state,
onChanged: (key, index, value) => onStateChanged?.call(key, index),
);
}
Widget _buildImagePicker(ImagePickerSDUIModel data) {
return ImagePickerSDUI(
model: data,
images: images,
onImagesChanged: (key, imageList) => onImagesChanged?.call(key, imageList),
);
}
Widget _buildCard(
CardLabelItemData data,
SDUIWidgetModel? child,
List<SDUIWidgetModel>? children,
) {
Widget? content;
if (child != null) {
content = _buildStandardWidget(child);
} else if (children != null && children.isNotEmpty) {
content = Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: children
.map(
(c) => Padding(
padding: EdgeInsets.only(bottom: 10.h),
child: _buildStandardWidget(c),
),
)
.toList(),
);
}
// ساختن مدل داخلی کارت (اگر ویجت شما مدل داخلی متفاوتی می‌گیرد)
final cardWidgetModel = CardLabelItemSDUI(
type: 'card_label_item',
visible: true,
data: data,
child: null,
);
return cardLabelItemSDUI(model: cardWidgetModel, child: content);
}
Widget _buildColumn(
List<SDUIWidgetModel> children, // لیست خام (همه فرزندان)
double spacing,
String mainAxisSizeStr,
String crossAlignStr,
) {
// ۲. فیلتر کردن لیست دقیقاً همینجا (داخل Obx) انجام می‌شود
// چون ConditionEvaluator.check مقدار state را می‌خواند، Obx خوشحال می‌شود!
final visibleChildren = children.where((child) {
// لاجیک ساده برای استخراج شرط (یا استفاده از getter اکستنشن)
final condition = child.maybeWhen(
textFormField: (_, __, c) => c,
cardLabelItem: (_, __, ___, ____, c) => c,
chipSelection: (_, __, c) => c,
dropdown: (_, __, c) => c,
imagePicker: (_, __, c) => c,
column: (_, _, _, _, _, _, _, c) => c,
row: (_, __, ___, ____, c) => c,
stepper: (_, __, ___, c) => c,
pageView: (_, __, ___, c) => c,
sizedBox: (_, __, ___, c) => c,
orElse: () => null,
);
final isVisibleProp = child.maybeWhen(
textFormField: (_, v, __) => v,
cardLabelItem: (_, __, ___, v, ____) => v,
chipSelection: (_, v, __) => v,
dropdown: (_, v, __) => v,
imagePicker: (_, v, __) => v,
column: (_, __, ___, ____, _____, ______, v, _______) => v,
row: (_, __, ___, v, ____) => v,
stepper: (_, __, v, ___) => v,
pageView: (_, __, v, ___) => v,
sizedBox: (_, __, v, ___) => v,
orElse: () => true,
);
return isVisibleProp && ConditionEvaluator.check(condition, state);
}).toList();
// ۳. اگر لیست خالی شد، هیچی نشون نده
if (visibleChildren.isEmpty) return const SizedBox.shrink();
// ۴. ساختن ستون با لیست فیلتر شده
return Column(
mainAxisSize: mainAxisSizeStr == 'min' ? MainAxisSize.min : MainAxisSize.max,
crossAxisAlignment: _parseCrossAxis(crossAlignStr),
children: [
for (int i = 0; i < visibleChildren.length; i++) ...[
_buildStandardWidget(visibleChildren[i]),
if (i < visibleChildren.length - 1 && spacing > 0) SizedBox(height: spacing.h),
],
],
);
}
Widget _buildRow(List<SDUIWidgetModel> children, double spacing, String mainAlignStr) {
final visibleChildren = children.where((child) {
// (همان لاجیک استخراج شرط که در Column بود)
// برای جلوگیری از تکرار کد، می‌توانید getter visibleCondition را به extension اضافه کنید
return true; // اینجا فرض را بر سادگی می‌گذاریم، لاجیک مشابه Column است
}).toList();
if (visibleChildren.isEmpty) return const SizedBox.shrink();
return Obx(() {
return Row(
mainAxisAlignment: _parseMainAxis(mainAlignStr),
children: [
for (int i = 0; i < visibleChildren.length; i++) ...[
Expanded(child: _buildStandardWidget(visibleChildren[i])),
if (i < visibleChildren.length - 1 && spacing > 0) SizedBox(width: spacing.w),
],
],
);
});
}
Widget _buildPageView(PageViewSDUIModel data, List<SDUIWidgetModel> children) {
PageController? controller;
if (data.key != null && pageControllers != null) {
controller = pageControllers![data.key];
}
return SizedBox(
height: 200.h, // ارتفاع باید مدیریت شود
child: PageViewSDUI(
model: data,
controller: controller,
state: state,
children: children.map(_buildStandardWidget).toList(),
),
);
}
Widget _buildNavigationButtons(SDUIFormWidgetController formController, String stepperKey) {
return Obx(() {
final isLastStep = formController.isLastStep;
final canGoPrevious = formController.canGoPrevious;
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(color: Colors.white),
child: Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: canGoPrevious
? () {
formController.previousStep(state);
onStateChanged?.call(stepperKey, formController.currentStep.value);
}
: null,
style: OutlinedButton.styleFrom(
side: const BorderSide(color: Colors.blue),
minimumSize: Size(0, 40.h),
),
child: const Text('قبلی'),
),
),
SizedBox(width: 10.w),
Expanded(
flex: 2,
child: ElevatedButton(
onPressed: () {
if (isLastStep) {
debugPrint('Form Submitted');
} else {
formController.nextStep(state);
onStateChanged?.call(stepperKey, formController.currentStep.value);
}
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
minimumSize: Size(0, 40.h),
),
child: Text(isLastStep ? 'ثبت' : 'بعدی'),
),
),
],
),
);
});
}
// ===========================================================================
// HELPERS
// ===========================================================================
CrossAxisAlignment _parseCrossAxis(String? val) {
switch (val) {
case 'start':
return CrossAxisAlignment.start;
case 'end':
return CrossAxisAlignment.end;
case 'stretch':
return CrossAxisAlignment.stretch;
default:
return CrossAxisAlignment.center;
}
}
MainAxisAlignment _parseMainAxis(String? val) {
switch (val) {
case 'start':
return MainAxisAlignment.start;
case 'end':
return MainAxisAlignment.end;
case 'spaceBetween':
return MainAxisAlignment.spaceBetween;
case 'spaceAround':
return MainAxisAlignment.spaceAround;
default:
return MainAxisAlignment.center;
}
}
}

View File

@@ -0,0 +1,38 @@
import 'package:rasadyar_core/core.dart';
class SDUIFormWidgetController extends GetxController {
RxInt currentStep = 0.obs;
int totalSteps = 0;
String? stepperKey;
void initializeStepper(int totalSteps, String? key) {
this.totalSteps = totalSteps;
this.stepperKey = key;
currentStep.value = 0;
}
void nextStep(RxMap<String, dynamic>? state) {
if (currentStep.value < totalSteps - 1) {
currentStep.value++;
if (state != null && stepperKey != null) {
state[stepperKey!] = currentStep.value;
}
}
}
void previousStep(RxMap<String, dynamic>? state) {
if (currentStep.value > 0) {
currentStep.value--;
if (state != null && stepperKey != null) {
state[stepperKey!] = currentStep.value;
}
}
}
bool get canGoNext => currentStep.value < totalSteps - 1;
bool get canGoPrevious => currentStep.value > 0;
bool get isLastStep => currentStep.value == totalSteps - 1;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,102 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/text_form_filed/model/text_form_field_sdui_model.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/card_label_item/model/card_label_item_sdui_model.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/chip_selection/model/chip_selection_sdui_model.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/dropdown/model/dropdown_sdui_model.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/image_picker/model/image_picker_sdui_model.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/stepper/model/stepper_sdui_model.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/page_view/model/page_view_sdui_model.dart';
part 'sdui_widget.freezed.dart';
part 'sdui_widget.g.dart';
// استفاده از snake_case برای تبدیل خودکار نام‌ها (مثلاً text_form_field به textFormField)
@Freezed(unionKey: 'type', unionValueCase: FreezedUnionCase.snake)
sealed class SDUIWidgetModel with _$SDUIWidgetModel {
// ۱. فیلد متنی
const factory SDUIWidgetModel.textFormField({
required TextFormFieldSDUIModel data,
@Default(true) bool visible,
String? visibleCondition,
}) = _TextFormField;
// ۲. کارت لیبل‌دار
const factory SDUIWidgetModel.cardLabelItem({
required CardLabelItemData data,
SDUIWidgetModel? child,
List<SDUIWidgetModel>? children,
@Default(true) bool visible,
String? visibleCondition,
}) = _CardLabelItem;
// ۳. انتخاب گزینه‌ای
const factory SDUIWidgetModel.chipSelection({
required ChipSelectionSDUIModel data,
@Default(true) bool visible,
String? visibleCondition,
}) = _ChipSelection;
// ۴. لیست کشویی
const factory SDUIWidgetModel.dropdown({
required DropdownSDUIModel data,
@Default(true) bool visible,
String? visibleCondition,
}) = _Dropdown;
// ۵. انتخاب عکس
const factory SDUIWidgetModel.imagePicker({
required ImagePickerSDUIModel data,
@Default(true) bool visible,
String? visibleCondition,
}) = _ImagePicker;
// ۶. ستون (Container ویجت - فاقد شیء data در JSON جدید)
const factory SDUIWidgetModel.column({
required List<SDUIWidgetModel> children,
@Default(0.0) double spacing,
@Default('max') String mainAxisSize,
@Default(0.0) double paddingHorizontal,
@Default(0.0) double paddingVertical,
@Default('center') String crossAxisAlignment,
@Default(true) bool visible,
String? visibleCondition,
}) = _Column;
// ۷. ردیف (Container ویجت - فاقد شیء data در JSON جدید)
const factory SDUIWidgetModel.row({
required List<SDUIWidgetModel> children,
@Default(0.0) double spacing,
@Default('start') String mainAxisAlignment,
@Default(true) bool visible,
String? visibleCondition,
}) = _Row;
// ۸. استپر
const factory SDUIWidgetModel.stepper({
required StepperSDUIModel data,
List<SDUIWidgetModel>? children,
@Default(true) bool visible,
String? visibleCondition,
}) = _Stepper;
// ۹. پیج ویو
const factory SDUIWidgetModel.pageView({
required PageViewSDUIModel data,
required List<SDUIWidgetModel> children,
@Default(true) bool visible,
String? visibleCondition,
}) = _PageView;
// ۱۰. جعبه با اندازه ثابت
const factory SDUIWidgetModel.sizedBox({
double? width,
double? height,
@Default(true) bool visible,
String? visibleCondition,
}) = _SizedBox;
factory SDUIWidgetModel.fromJson(Map<String, dynamic> json) =>
_$SDUIWidgetModelFromJson(json);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,194 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'sdui_widget.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_TextFormField _$TextFormFieldFromJson(Map<String, dynamic> json) =>
_TextFormField(
data: TextFormFieldSDUIModel.fromJson(
json['data'] as Map<String, dynamic>,
),
visible: json['visible'] as bool? ?? true,
visibleCondition: json['visible_condition'] as String?,
$type: json['type'] as String?,
);
Map<String, dynamic> _$TextFormFieldToJson(_TextFormField instance) =>
<String, dynamic>{
'data': instance.data,
'visible': instance.visible,
'visible_condition': instance.visibleCondition,
'type': instance.$type,
};
_CardLabelItem _$CardLabelItemFromJson(Map<String, dynamic> json) =>
_CardLabelItem(
data: CardLabelItemData.fromJson(json['data'] as Map<String, dynamic>),
child: json['child'] == null
? null
: SDUIWidgetModel.fromJson(json['child'] as Map<String, dynamic>),
children: (json['children'] as List<dynamic>?)
?.map((e) => SDUIWidgetModel.fromJson(e as Map<String, dynamic>))
.toList(),
visible: json['visible'] as bool? ?? true,
visibleCondition: json['visible_condition'] as String?,
$type: json['type'] as String?,
);
Map<String, dynamic> _$CardLabelItemToJson(_CardLabelItem instance) =>
<String, dynamic>{
'data': instance.data,
'child': instance.child,
'children': instance.children,
'visible': instance.visible,
'visible_condition': instance.visibleCondition,
'type': instance.$type,
};
_ChipSelection _$ChipSelectionFromJson(Map<String, dynamic> json) =>
_ChipSelection(
data: ChipSelectionSDUIModel.fromJson(
json['data'] as Map<String, dynamic>,
),
visible: json['visible'] as bool? ?? true,
visibleCondition: json['visible_condition'] as String?,
$type: json['type'] as String?,
);
Map<String, dynamic> _$ChipSelectionToJson(_ChipSelection instance) =>
<String, dynamic>{
'data': instance.data,
'visible': instance.visible,
'visible_condition': instance.visibleCondition,
'type': instance.$type,
};
_Dropdown _$DropdownFromJson(Map<String, dynamic> json) => _Dropdown(
data: DropdownSDUIModel.fromJson(json['data'] as Map<String, dynamic>),
visible: json['visible'] as bool? ?? true,
visibleCondition: json['visible_condition'] as String?,
$type: json['type'] as String?,
);
Map<String, dynamic> _$DropdownToJson(_Dropdown instance) => <String, dynamic>{
'data': instance.data,
'visible': instance.visible,
'visible_condition': instance.visibleCondition,
'type': instance.$type,
};
_ImagePicker _$ImagePickerFromJson(Map<String, dynamic> json) => _ImagePicker(
data: ImagePickerSDUIModel.fromJson(json['data'] as Map<String, dynamic>),
visible: json['visible'] as bool? ?? true,
visibleCondition: json['visible_condition'] as String?,
$type: json['type'] as String?,
);
Map<String, dynamic> _$ImagePickerToJson(_ImagePicker instance) =>
<String, dynamic>{
'data': instance.data,
'visible': instance.visible,
'visible_condition': instance.visibleCondition,
'type': instance.$type,
};
_Column _$ColumnFromJson(Map<String, dynamic> json) => _Column(
children: (json['children'] as List<dynamic>)
.map((e) => SDUIWidgetModel.fromJson(e as Map<String, dynamic>))
.toList(),
spacing: (json['spacing'] as num?)?.toDouble() ?? 0.0,
mainAxisSize: json['main_axis_size'] as String? ?? 'max',
paddingHorizontal: (json['padding_horizontal'] as num?)?.toDouble() ?? 0.0,
paddingVertical: (json['padding_vertical'] as num?)?.toDouble() ?? 0.0,
crossAxisAlignment: json['cross_axis_alignment'] as String? ?? 'center',
visible: json['visible'] as bool? ?? true,
visibleCondition: json['visible_condition'] as String?,
$type: json['type'] as String?,
);
Map<String, dynamic> _$ColumnToJson(_Column instance) => <String, dynamic>{
'children': instance.children,
'spacing': instance.spacing,
'main_axis_size': instance.mainAxisSize,
'padding_horizontal': instance.paddingHorizontal,
'padding_vertical': instance.paddingVertical,
'cross_axis_alignment': instance.crossAxisAlignment,
'visible': instance.visible,
'visible_condition': instance.visibleCondition,
'type': instance.$type,
};
_Row _$RowFromJson(Map<String, dynamic> json) => _Row(
children: (json['children'] as List<dynamic>)
.map((e) => SDUIWidgetModel.fromJson(e as Map<String, dynamic>))
.toList(),
spacing: (json['spacing'] as num?)?.toDouble() ?? 0.0,
mainAxisAlignment: json['main_axis_alignment'] as String? ?? 'start',
visible: json['visible'] as bool? ?? true,
visibleCondition: json['visible_condition'] as String?,
$type: json['type'] as String?,
);
Map<String, dynamic> _$RowToJson(_Row instance) => <String, dynamic>{
'children': instance.children,
'spacing': instance.spacing,
'main_axis_alignment': instance.mainAxisAlignment,
'visible': instance.visible,
'visible_condition': instance.visibleCondition,
'type': instance.$type,
};
_Stepper _$StepperFromJson(Map<String, dynamic> json) => _Stepper(
data: StepperSDUIModel.fromJson(json['data'] as Map<String, dynamic>),
children: (json['children'] as List<dynamic>?)
?.map((e) => SDUIWidgetModel.fromJson(e as Map<String, dynamic>))
.toList(),
visible: json['visible'] as bool? ?? true,
visibleCondition: json['visible_condition'] as String?,
$type: json['type'] as String?,
);
Map<String, dynamic> _$StepperToJson(_Stepper instance) => <String, dynamic>{
'data': instance.data,
'children': instance.children,
'visible': instance.visible,
'visible_condition': instance.visibleCondition,
'type': instance.$type,
};
_PageView _$PageViewFromJson(Map<String, dynamic> json) => _PageView(
data: PageViewSDUIModel.fromJson(json['data'] as Map<String, dynamic>),
children: (json['children'] as List<dynamic>)
.map((e) => SDUIWidgetModel.fromJson(e as Map<String, dynamic>))
.toList(),
visible: json['visible'] as bool? ?? true,
visibleCondition: json['visible_condition'] as String?,
$type: json['type'] as String?,
);
Map<String, dynamic> _$PageViewToJson(_PageView instance) => <String, dynamic>{
'data': instance.data,
'children': instance.children,
'visible': instance.visible,
'visible_condition': instance.visibleCondition,
'type': instance.$type,
};
_SizedBox _$SizedBoxFromJson(Map<String, dynamic> json) => _SizedBox(
width: (json['width'] as num?)?.toDouble(),
height: (json['height'] as num?)?.toDouble(),
visible: json['visible'] as bool? ?? true,
visibleCondition: json['visible_condition'] as String?,
$type: json['type'] as String?,
);
Map<String, dynamic> _$SizedBoxToJson(_SizedBox instance) => <String, dynamic>{
'width': instance.width,
'height': instance.height,
'visible': instance.visible,
'visible_condition': instance.visibleCondition,
'type': instance.$type,
};

View File

@@ -0,0 +1,7 @@
// SDUI Export file
export 'form/sdui_form_widget.dart';
export 'model/sdui_widget.dart'; // Sealed class برای type-safe widget handling
export 'widgets/card_label_item/card_label_item_sdui.dart';
export 'widgets/card_label_item/model/card_label_item_sdui_model.dart';
export 'widgets/text_form_filed/text_form_filed_sdui.dart';
export 'widgets/text_form_filed/model/text_form_field_sdui_model.dart';

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,79 @@
{
"info": {
"type": "column",
"visible": true,
"data": {
"spacing": 10.0,
"crossAxisAlignment": "start"
},
"children": [
{
"type": "card_label_item",
"visible": true,
"data": {
"title": "اطلاعات پایه واحد",
"padding_horizontal": 12.0,
"padding_vertical": 11.0
},
"child": {
"type": "column",
"visible": true,
"data": {
"spacing": 10.0
},
"children": [
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "unit_name",
"label": "نام واحد مرغداری",
"keyboard_type": "text"
}
},
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "breeding_unique_id",
"label": "(عدد معمولی)کد یکتا / شناسه واحد",
"keyboard_type": "number"
}
},
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "health_a",
"label": "(سه تایی جداشه)پروانه بهداشتی",
"keyboard_type": "number",
"comma_sperator": true
}
},
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "health_license",
"label": "عدد اعشاری ",
"keyboard_type": "number",
"decimal": true
}
},
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "health_q",
"label": "عدد اعشاری با 2 رقم اعشار",
"keyboard_type": "number",
"decimal": true,
"decimal_places": 2
}
}
]
}
}
]
}
}

View File

@@ -0,0 +1,77 @@
{
"id": 1,
"key": "simple-test-form",
"create_date": "2025-01-15T10:00:00.000000",
"modify_date": "2025-01-15T10:00:00.000000",
"trash": false,
"info": {
"data": {
"type": "column",
"visible": true,
"data": {
"spacing": 20.0,
"crossAxisAlignment": "start"
},
"children": [
{
"type": "card_label_item",
"visible": true,
"data": {
"title": "فرم تست",
"padding_horizontal": 12.0,
"padding_vertical": 11.0
},
"child": {
"type": "column",
"visible": true,
"data": {
"spacing": 10.0
},
"children": [
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "name",
"label": "نام",
"keyboardType": "text",
"enabled": true,
"readonly": false,
"value": ""
}
},
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "phone",
"label": "شماره تماس",
"keyboardType": "text",
"enabled": true,
"readonly": false,
"value": ""
}
},
{
"type": "text_form_field",
"visible": true,
"data": {
"key": "age",
"label": "سن",
"keyboardType": "number",
"enabled": true,
"readonly": false,
"commaSperator": true,
"value": ""
}
}
]
}
}
]
}
},
"created_by": null,
"modified_by": null
}

View File

@@ -0,0 +1,54 @@
import 'package:rasadyar_core/core.dart';
import 'model/card_label_item_sdui_model.dart';
import 'package:flutter/material.dart';
Widget cardLabelItemSDUI({required CardLabelItemSDUI model, Widget? child}) {
return farmInfoWidget(
title: model.data?.title ?? '',
child: child ?? Container(),
);
}
Widget farmInfoWidget({
required String title,
required Widget child,
EdgeInsets? padding,
}) {
return Stack(
clipBehavior: Clip.none,
children: [
Container(
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 0.50, color: AppColor.mediumGrey),
),
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 11.h),
child: Column(
children: [
SizedBox(height: 8.h),
child,
],
),
),
Positioned(
top: -17,
right: 7,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 5.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 0.50, color: AppColor.mediumGrey),
),
child: Text(
title,
style: AppFonts.yekan14.copyWith(color: AppColor.iconColor),
),
),
),
],
);
}

View File

@@ -0,0 +1,29 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'card_label_item_sdui_model.freezed.dart';
part 'card_label_item_sdui_model.g.dart';
@freezed
abstract class CardLabelItemSDUI with _$CardLabelItemSDUI {
const factory CardLabelItemSDUI({
String? type,
bool? visible,
CardLabelItemData? data,
Map<String, dynamic>? child,
}) = _CardLabelItemSDUI;
factory CardLabelItemSDUI.fromJson(Map<String, dynamic> json) =>
_$CardLabelItemSDUIFromJson(json);
}
@freezed
abstract class CardLabelItemData with _$CardLabelItemData {
const factory CardLabelItemData({
String? title,
double? paddingHorizontal,
double? paddingVertical,
}) = _CardLabelItemData;
factory CardLabelItemData.fromJson(Map<String, dynamic> json) =>
_$CardLabelItemDataFromJson(json);
}

View File

@@ -0,0 +1,587 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'card_label_item_sdui_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$CardLabelItemSDUI {
String? get type; bool? get visible; CardLabelItemData? get data; Map<String, dynamic>? get child;
/// Create a copy of CardLabelItemSDUI
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$CardLabelItemSDUICopyWith<CardLabelItemSDUI> get copyWith => _$CardLabelItemSDUICopyWithImpl<CardLabelItemSDUI>(this as CardLabelItemSDUI, _$identity);
/// Serializes this CardLabelItemSDUI to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is CardLabelItemSDUI&&(identical(other.type, type) || other.type == type)&&(identical(other.visible, visible) || other.visible == visible)&&(identical(other.data, data) || other.data == data)&&const DeepCollectionEquality().equals(other.child, child));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,type,visible,data,const DeepCollectionEquality().hash(child));
@override
String toString() {
return 'CardLabelItemSDUI(type: $type, visible: $visible, data: $data, child: $child)';
}
}
/// @nodoc
abstract mixin class $CardLabelItemSDUICopyWith<$Res> {
factory $CardLabelItemSDUICopyWith(CardLabelItemSDUI value, $Res Function(CardLabelItemSDUI) _then) = _$CardLabelItemSDUICopyWithImpl;
@useResult
$Res call({
String? type, bool? visible, CardLabelItemData? data, Map<String, dynamic>? child
});
$CardLabelItemDataCopyWith<$Res>? get data;
}
/// @nodoc
class _$CardLabelItemSDUICopyWithImpl<$Res>
implements $CardLabelItemSDUICopyWith<$Res> {
_$CardLabelItemSDUICopyWithImpl(this._self, this._then);
final CardLabelItemSDUI _self;
final $Res Function(CardLabelItemSDUI) _then;
/// Create a copy of CardLabelItemSDUI
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? type = freezed,Object? visible = freezed,Object? data = freezed,Object? child = freezed,}) {
return _then(_self.copyWith(
type: freezed == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as String?,visible: freezed == visible ? _self.visible : visible // ignore: cast_nullable_to_non_nullable
as bool?,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
as CardLabelItemData?,child: freezed == child ? _self.child : child // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,
));
}
/// Create a copy of CardLabelItemSDUI
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$CardLabelItemDataCopyWith<$Res>? get data {
if (_self.data == null) {
return null;
}
return $CardLabelItemDataCopyWith<$Res>(_self.data!, (value) {
return _then(_self.copyWith(data: value));
});
}
}
/// Adds pattern-matching-related methods to [CardLabelItemSDUI].
extension CardLabelItemSDUIPatterns on CardLabelItemSDUI {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _CardLabelItemSDUI value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _CardLabelItemSDUI() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _CardLabelItemSDUI value) $default,){
final _that = this;
switch (_that) {
case _CardLabelItemSDUI():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _CardLabelItemSDUI value)? $default,){
final _that = this;
switch (_that) {
case _CardLabelItemSDUI() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? type, bool? visible, CardLabelItemData? data, Map<String, dynamic>? child)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _CardLabelItemSDUI() when $default != null:
return $default(_that.type,_that.visible,_that.data,_that.child);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? type, bool? visible, CardLabelItemData? data, Map<String, dynamic>? child) $default,) {final _that = this;
switch (_that) {
case _CardLabelItemSDUI():
return $default(_that.type,_that.visible,_that.data,_that.child);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? type, bool? visible, CardLabelItemData? data, Map<String, dynamic>? child)? $default,) {final _that = this;
switch (_that) {
case _CardLabelItemSDUI() when $default != null:
return $default(_that.type,_that.visible,_that.data,_that.child);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _CardLabelItemSDUI implements CardLabelItemSDUI {
const _CardLabelItemSDUI({this.type, this.visible, this.data, final Map<String, dynamic>? child}): _child = child;
factory _CardLabelItemSDUI.fromJson(Map<String, dynamic> json) => _$CardLabelItemSDUIFromJson(json);
@override final String? type;
@override final bool? visible;
@override final CardLabelItemData? data;
final Map<String, dynamic>? _child;
@override Map<String, dynamic>? get child {
final value = _child;
if (value == null) return null;
if (_child is EqualUnmodifiableMapView) return _child;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(value);
}
/// Create a copy of CardLabelItemSDUI
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$CardLabelItemSDUICopyWith<_CardLabelItemSDUI> get copyWith => __$CardLabelItemSDUICopyWithImpl<_CardLabelItemSDUI>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$CardLabelItemSDUIToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CardLabelItemSDUI&&(identical(other.type, type) || other.type == type)&&(identical(other.visible, visible) || other.visible == visible)&&(identical(other.data, data) || other.data == data)&&const DeepCollectionEquality().equals(other._child, _child));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,type,visible,data,const DeepCollectionEquality().hash(_child));
@override
String toString() {
return 'CardLabelItemSDUI(type: $type, visible: $visible, data: $data, child: $child)';
}
}
/// @nodoc
abstract mixin class _$CardLabelItemSDUICopyWith<$Res> implements $CardLabelItemSDUICopyWith<$Res> {
factory _$CardLabelItemSDUICopyWith(_CardLabelItemSDUI value, $Res Function(_CardLabelItemSDUI) _then) = __$CardLabelItemSDUICopyWithImpl;
@override @useResult
$Res call({
String? type, bool? visible, CardLabelItemData? data, Map<String, dynamic>? child
});
@override $CardLabelItemDataCopyWith<$Res>? get data;
}
/// @nodoc
class __$CardLabelItemSDUICopyWithImpl<$Res>
implements _$CardLabelItemSDUICopyWith<$Res> {
__$CardLabelItemSDUICopyWithImpl(this._self, this._then);
final _CardLabelItemSDUI _self;
final $Res Function(_CardLabelItemSDUI) _then;
/// Create a copy of CardLabelItemSDUI
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? type = freezed,Object? visible = freezed,Object? data = freezed,Object? child = freezed,}) {
return _then(_CardLabelItemSDUI(
type: freezed == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as String?,visible: freezed == visible ? _self.visible : visible // ignore: cast_nullable_to_non_nullable
as bool?,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
as CardLabelItemData?,child: freezed == child ? _self._child : child // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>?,
));
}
/// Create a copy of CardLabelItemSDUI
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$CardLabelItemDataCopyWith<$Res>? get data {
if (_self.data == null) {
return null;
}
return $CardLabelItemDataCopyWith<$Res>(_self.data!, (value) {
return _then(_self.copyWith(data: value));
});
}
}
/// @nodoc
mixin _$CardLabelItemData {
String? get title; double? get paddingHorizontal; double? get paddingVertical;
/// Create a copy of CardLabelItemData
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$CardLabelItemDataCopyWith<CardLabelItemData> get copyWith => _$CardLabelItemDataCopyWithImpl<CardLabelItemData>(this as CardLabelItemData, _$identity);
/// Serializes this CardLabelItemData to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is CardLabelItemData&&(identical(other.title, title) || other.title == title)&&(identical(other.paddingHorizontal, paddingHorizontal) || other.paddingHorizontal == paddingHorizontal)&&(identical(other.paddingVertical, paddingVertical) || other.paddingVertical == paddingVertical));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,title,paddingHorizontal,paddingVertical);
@override
String toString() {
return 'CardLabelItemData(title: $title, paddingHorizontal: $paddingHorizontal, paddingVertical: $paddingVertical)';
}
}
/// @nodoc
abstract mixin class $CardLabelItemDataCopyWith<$Res> {
factory $CardLabelItemDataCopyWith(CardLabelItemData value, $Res Function(CardLabelItemData) _then) = _$CardLabelItemDataCopyWithImpl;
@useResult
$Res call({
String? title, double? paddingHorizontal, double? paddingVertical
});
}
/// @nodoc
class _$CardLabelItemDataCopyWithImpl<$Res>
implements $CardLabelItemDataCopyWith<$Res> {
_$CardLabelItemDataCopyWithImpl(this._self, this._then);
final CardLabelItemData _self;
final $Res Function(CardLabelItemData) _then;
/// Create a copy of CardLabelItemData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? title = freezed,Object? paddingHorizontal = freezed,Object? paddingVertical = freezed,}) {
return _then(_self.copyWith(
title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String?,paddingHorizontal: freezed == paddingHorizontal ? _self.paddingHorizontal : paddingHorizontal // ignore: cast_nullable_to_non_nullable
as double?,paddingVertical: freezed == paddingVertical ? _self.paddingVertical : paddingVertical // ignore: cast_nullable_to_non_nullable
as double?,
));
}
}
/// Adds pattern-matching-related methods to [CardLabelItemData].
extension CardLabelItemDataPatterns on CardLabelItemData {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _CardLabelItemData value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _CardLabelItemData() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _CardLabelItemData value) $default,){
final _that = this;
switch (_that) {
case _CardLabelItemData():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _CardLabelItemData value)? $default,){
final _that = this;
switch (_that) {
case _CardLabelItemData() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? title, double? paddingHorizontal, double? paddingVertical)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _CardLabelItemData() when $default != null:
return $default(_that.title,_that.paddingHorizontal,_that.paddingVertical);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? title, double? paddingHorizontal, double? paddingVertical) $default,) {final _that = this;
switch (_that) {
case _CardLabelItemData():
return $default(_that.title,_that.paddingHorizontal,_that.paddingVertical);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? title, double? paddingHorizontal, double? paddingVertical)? $default,) {final _that = this;
switch (_that) {
case _CardLabelItemData() when $default != null:
return $default(_that.title,_that.paddingHorizontal,_that.paddingVertical);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _CardLabelItemData implements CardLabelItemData {
const _CardLabelItemData({this.title, this.paddingHorizontal, this.paddingVertical});
factory _CardLabelItemData.fromJson(Map<String, dynamic> json) => _$CardLabelItemDataFromJson(json);
@override final String? title;
@override final double? paddingHorizontal;
@override final double? paddingVertical;
/// Create a copy of CardLabelItemData
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$CardLabelItemDataCopyWith<_CardLabelItemData> get copyWith => __$CardLabelItemDataCopyWithImpl<_CardLabelItemData>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$CardLabelItemDataToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CardLabelItemData&&(identical(other.title, title) || other.title == title)&&(identical(other.paddingHorizontal, paddingHorizontal) || other.paddingHorizontal == paddingHorizontal)&&(identical(other.paddingVertical, paddingVertical) || other.paddingVertical == paddingVertical));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,title,paddingHorizontal,paddingVertical);
@override
String toString() {
return 'CardLabelItemData(title: $title, paddingHorizontal: $paddingHorizontal, paddingVertical: $paddingVertical)';
}
}
/// @nodoc
abstract mixin class _$CardLabelItemDataCopyWith<$Res> implements $CardLabelItemDataCopyWith<$Res> {
factory _$CardLabelItemDataCopyWith(_CardLabelItemData value, $Res Function(_CardLabelItemData) _then) = __$CardLabelItemDataCopyWithImpl;
@override @useResult
$Res call({
String? title, double? paddingHorizontal, double? paddingVertical
});
}
/// @nodoc
class __$CardLabelItemDataCopyWithImpl<$Res>
implements _$CardLabelItemDataCopyWith<$Res> {
__$CardLabelItemDataCopyWithImpl(this._self, this._then);
final _CardLabelItemData _self;
final $Res Function(_CardLabelItemData) _then;
/// Create a copy of CardLabelItemData
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? title = freezed,Object? paddingHorizontal = freezed,Object? paddingVertical = freezed,}) {
return _then(_CardLabelItemData(
title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String?,paddingHorizontal: freezed == paddingHorizontal ? _self.paddingHorizontal : paddingHorizontal // ignore: cast_nullable_to_non_nullable
as double?,paddingVertical: freezed == paddingVertical ? _self.paddingVertical : paddingVertical // ignore: cast_nullable_to_non_nullable
as double?,
));
}
}
// dart format on

View File

@@ -0,0 +1,39 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'card_label_item_sdui_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_CardLabelItemSDUI _$CardLabelItemSDUIFromJson(Map<String, dynamic> json) =>
_CardLabelItemSDUI(
type: json['type'] as String?,
visible: json['visible'] as bool?,
data: json['data'] == null
? null
: CardLabelItemData.fromJson(json['data'] as Map<String, dynamic>),
child: json['child'] as Map<String, dynamic>?,
);
Map<String, dynamic> _$CardLabelItemSDUIToJson(_CardLabelItemSDUI instance) =>
<String, dynamic>{
'type': instance.type,
'visible': instance.visible,
'data': instance.data,
'child': instance.child,
};
_CardLabelItemData _$CardLabelItemDataFromJson(Map<String, dynamic> json) =>
_CardLabelItemData(
title: json['title'] as String?,
paddingHorizontal: (json['padding_horizontal'] as num?)?.toDouble(),
paddingVertical: (json['padding_vertical'] as num?)?.toDouble(),
);
Map<String, dynamic> _$CardLabelItemDataToJson(_CardLabelItemData instance) =>
<String, dynamic>{
'title': instance.title,
'padding_horizontal': instance.paddingHorizontal,
'padding_vertical': instance.paddingVertical,
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,109 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/chip_selection/model/chip_selection_sdui_model.dart';
import 'package:rasadyar_core/core.dart';
class ChipSelectionSDUI extends StatelessWidget {
final ChipSelectionSDUIModel model;
final RxMap<String, dynamic>? state;
final Function(String key, int index, String? value)? onChanged;
const ChipSelectionSDUI({
super.key,
required this.model,
this.state,
this.onChanged,
});
@override
Widget build(BuildContext context) {
final options = model.options ?? [];
final spacing = 10.0;
if (options.isEmpty) {
return const SizedBox.shrink();
}
return Obx(() {
final selectedIndex =
state?[model.key] as int? ?? model.selectedIndex ?? -1;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (model.label != null && model.label!.isNotEmpty) ...[
Row(
children: [
Text(
model.label!,
style: AppFonts.yekan14Bold.copyWith(
color: AppColor.textColor2,
),
),
],
),
SizedBox(height: 9.h),
],
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
spacing: spacing,
children: options.asMap().entries.map((entry) {
final option = entry.value;
final optionIndex = option.index ?? entry.key;
return containerChips(
selectedIndex: selectedIndex,
index: optionIndex,
label: option.label ?? '',
onTap: (index) {
if (onChanged != null && model.key != null) {
onChanged!(model.key!, index, option.value);
}
},
);
}).toList(),
),
),
],
);
});
}
}
GestureDetector containerChips({
required int selectedIndex,
required int index,
required String label,
required Function(int) onTap,
}) {
return GestureDetector(
onTap: () => onTap(index),
child: Container(
height: 24.h,
padding: EdgeInsets.symmetric(horizontal: 20.w),
decoration: BoxDecoration(
color: index == selectedIndex
? AppColor.green1Normal
: AppColor.whiteGreyNormal,
borderRadius: BorderRadius.circular(8),
border: index == selectedIndex
? Border.fromBorderSide(BorderSide.none)
: Border.all(width: 1, color: AppColor.blackLightHover),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 5,
children: [
if (index == selectedIndex)
Icon(Icons.check, color: Colors.white, size: 16),
Text(
label,
style: index == selectedIndex
? AppFonts.yekan14Bold.copyWith(color: Colors.white)
: AppFonts.yekan14.copyWith(color: AppColor.textColor),
),
],
),
),
);
}

View File

@@ -0,0 +1,30 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'chip_selection_sdui_model.freezed.dart';
part 'chip_selection_sdui_model.g.dart';
@freezed
abstract class ChipSelectionSDUIModel with _$ChipSelectionSDUIModel {
const factory ChipSelectionSDUIModel({
String? key,
String? label,
List<ChipOption>? options,
int? selectedIndex,
}) = _ChipSelectionSDUIModel;
factory ChipSelectionSDUIModel.fromJson(Map<String, dynamic> json) =>
_$ChipSelectionSDUIModelFromJson(json);
}
@freezed
abstract class ChipOption with _$ChipOption {
const factory ChipOption({
int? index,
String? label,
String? value,
}) = _ChipOption;
factory ChipOption.fromJson(Map<String, dynamic> json) =>
_$ChipOptionFromJson(json);
}

View File

@@ -0,0 +1,563 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'chip_selection_sdui_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$ChipSelectionSDUIModel {
String? get key; String? get label; List<ChipOption>? get options; int? get selectedIndex;
/// Create a copy of ChipSelectionSDUIModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ChipSelectionSDUIModelCopyWith<ChipSelectionSDUIModel> get copyWith => _$ChipSelectionSDUIModelCopyWithImpl<ChipSelectionSDUIModel>(this as ChipSelectionSDUIModel, _$identity);
/// Serializes this ChipSelectionSDUIModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ChipSelectionSDUIModel&&(identical(other.key, key) || other.key == key)&&(identical(other.label, label) || other.label == label)&&const DeepCollectionEquality().equals(other.options, options)&&(identical(other.selectedIndex, selectedIndex) || other.selectedIndex == selectedIndex));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,key,label,const DeepCollectionEquality().hash(options),selectedIndex);
@override
String toString() {
return 'ChipSelectionSDUIModel(key: $key, label: $label, options: $options, selectedIndex: $selectedIndex)';
}
}
/// @nodoc
abstract mixin class $ChipSelectionSDUIModelCopyWith<$Res> {
factory $ChipSelectionSDUIModelCopyWith(ChipSelectionSDUIModel value, $Res Function(ChipSelectionSDUIModel) _then) = _$ChipSelectionSDUIModelCopyWithImpl;
@useResult
$Res call({
String? key, String? label, List<ChipOption>? options, int? selectedIndex
});
}
/// @nodoc
class _$ChipSelectionSDUIModelCopyWithImpl<$Res>
implements $ChipSelectionSDUIModelCopyWith<$Res> {
_$ChipSelectionSDUIModelCopyWithImpl(this._self, this._then);
final ChipSelectionSDUIModel _self;
final $Res Function(ChipSelectionSDUIModel) _then;
/// Create a copy of ChipSelectionSDUIModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? key = freezed,Object? label = freezed,Object? options = freezed,Object? selectedIndex = freezed,}) {
return _then(_self.copyWith(
key: freezed == key ? _self.key : key // ignore: cast_nullable_to_non_nullable
as String?,label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
as String?,options: freezed == options ? _self.options : options // ignore: cast_nullable_to_non_nullable
as List<ChipOption>?,selectedIndex: freezed == selectedIndex ? _self.selectedIndex : selectedIndex // ignore: cast_nullable_to_non_nullable
as int?,
));
}
}
/// Adds pattern-matching-related methods to [ChipSelectionSDUIModel].
extension ChipSelectionSDUIModelPatterns on ChipSelectionSDUIModel {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ChipSelectionSDUIModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _ChipSelectionSDUIModel() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ChipSelectionSDUIModel value) $default,){
final _that = this;
switch (_that) {
case _ChipSelectionSDUIModel():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ChipSelectionSDUIModel value)? $default,){
final _that = this;
switch (_that) {
case _ChipSelectionSDUIModel() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? key, String? label, List<ChipOption>? options, int? selectedIndex)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ChipSelectionSDUIModel() when $default != null:
return $default(_that.key,_that.label,_that.options,_that.selectedIndex);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? key, String? label, List<ChipOption>? options, int? selectedIndex) $default,) {final _that = this;
switch (_that) {
case _ChipSelectionSDUIModel():
return $default(_that.key,_that.label,_that.options,_that.selectedIndex);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? key, String? label, List<ChipOption>? options, int? selectedIndex)? $default,) {final _that = this;
switch (_that) {
case _ChipSelectionSDUIModel() when $default != null:
return $default(_that.key,_that.label,_that.options,_that.selectedIndex);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _ChipSelectionSDUIModel implements ChipSelectionSDUIModel {
const _ChipSelectionSDUIModel({this.key, this.label, final List<ChipOption>? options, this.selectedIndex}): _options = options;
factory _ChipSelectionSDUIModel.fromJson(Map<String, dynamic> json) => _$ChipSelectionSDUIModelFromJson(json);
@override final String? key;
@override final String? label;
final List<ChipOption>? _options;
@override List<ChipOption>? get options {
final value = _options;
if (value == null) return null;
if (_options is EqualUnmodifiableListView) return _options;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override final int? selectedIndex;
/// Create a copy of ChipSelectionSDUIModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ChipSelectionSDUIModelCopyWith<_ChipSelectionSDUIModel> get copyWith => __$ChipSelectionSDUIModelCopyWithImpl<_ChipSelectionSDUIModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$ChipSelectionSDUIModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ChipSelectionSDUIModel&&(identical(other.key, key) || other.key == key)&&(identical(other.label, label) || other.label == label)&&const DeepCollectionEquality().equals(other._options, _options)&&(identical(other.selectedIndex, selectedIndex) || other.selectedIndex == selectedIndex));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,key,label,const DeepCollectionEquality().hash(_options),selectedIndex);
@override
String toString() {
return 'ChipSelectionSDUIModel(key: $key, label: $label, options: $options, selectedIndex: $selectedIndex)';
}
}
/// @nodoc
abstract mixin class _$ChipSelectionSDUIModelCopyWith<$Res> implements $ChipSelectionSDUIModelCopyWith<$Res> {
factory _$ChipSelectionSDUIModelCopyWith(_ChipSelectionSDUIModel value, $Res Function(_ChipSelectionSDUIModel) _then) = __$ChipSelectionSDUIModelCopyWithImpl;
@override @useResult
$Res call({
String? key, String? label, List<ChipOption>? options, int? selectedIndex
});
}
/// @nodoc
class __$ChipSelectionSDUIModelCopyWithImpl<$Res>
implements _$ChipSelectionSDUIModelCopyWith<$Res> {
__$ChipSelectionSDUIModelCopyWithImpl(this._self, this._then);
final _ChipSelectionSDUIModel _self;
final $Res Function(_ChipSelectionSDUIModel) _then;
/// Create a copy of ChipSelectionSDUIModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? key = freezed,Object? label = freezed,Object? options = freezed,Object? selectedIndex = freezed,}) {
return _then(_ChipSelectionSDUIModel(
key: freezed == key ? _self.key : key // ignore: cast_nullable_to_non_nullable
as String?,label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
as String?,options: freezed == options ? _self._options : options // ignore: cast_nullable_to_non_nullable
as List<ChipOption>?,selectedIndex: freezed == selectedIndex ? _self.selectedIndex : selectedIndex // ignore: cast_nullable_to_non_nullable
as int?,
));
}
}
/// @nodoc
mixin _$ChipOption {
int? get index; String? get label; String? get value;
/// Create a copy of ChipOption
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$ChipOptionCopyWith<ChipOption> get copyWith => _$ChipOptionCopyWithImpl<ChipOption>(this as ChipOption, _$identity);
/// Serializes this ChipOption to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ChipOption&&(identical(other.index, index) || other.index == index)&&(identical(other.label, label) || other.label == label)&&(identical(other.value, value) || other.value == value));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,index,label,value);
@override
String toString() {
return 'ChipOption(index: $index, label: $label, value: $value)';
}
}
/// @nodoc
abstract mixin class $ChipOptionCopyWith<$Res> {
factory $ChipOptionCopyWith(ChipOption value, $Res Function(ChipOption) _then) = _$ChipOptionCopyWithImpl;
@useResult
$Res call({
int? index, String? label, String? value
});
}
/// @nodoc
class _$ChipOptionCopyWithImpl<$Res>
implements $ChipOptionCopyWith<$Res> {
_$ChipOptionCopyWithImpl(this._self, this._then);
final ChipOption _self;
final $Res Function(ChipOption) _then;
/// Create a copy of ChipOption
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? index = freezed,Object? label = freezed,Object? value = freezed,}) {
return _then(_self.copyWith(
index: freezed == index ? _self.index : index // ignore: cast_nullable_to_non_nullable
as int?,label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
as String?,value: freezed == value ? _self.value : value // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// Adds pattern-matching-related methods to [ChipOption].
extension ChipOptionPatterns on ChipOption {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ChipOption value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _ChipOption() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ChipOption value) $default,){
final _that = this;
switch (_that) {
case _ChipOption():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ChipOption value)? $default,){
final _that = this;
switch (_that) {
case _ChipOption() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int? index, String? label, String? value)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ChipOption() when $default != null:
return $default(_that.index,_that.label,_that.value);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int? index, String? label, String? value) $default,) {final _that = this;
switch (_that) {
case _ChipOption():
return $default(_that.index,_that.label,_that.value);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int? index, String? label, String? value)? $default,) {final _that = this;
switch (_that) {
case _ChipOption() when $default != null:
return $default(_that.index,_that.label,_that.value);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _ChipOption implements ChipOption {
const _ChipOption({this.index, this.label, this.value});
factory _ChipOption.fromJson(Map<String, dynamic> json) => _$ChipOptionFromJson(json);
@override final int? index;
@override final String? label;
@override final String? value;
/// Create a copy of ChipOption
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$ChipOptionCopyWith<_ChipOption> get copyWith => __$ChipOptionCopyWithImpl<_ChipOption>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$ChipOptionToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ChipOption&&(identical(other.index, index) || other.index == index)&&(identical(other.label, label) || other.label == label)&&(identical(other.value, value) || other.value == value));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,index,label,value);
@override
String toString() {
return 'ChipOption(index: $index, label: $label, value: $value)';
}
}
/// @nodoc
abstract mixin class _$ChipOptionCopyWith<$Res> implements $ChipOptionCopyWith<$Res> {
factory _$ChipOptionCopyWith(_ChipOption value, $Res Function(_ChipOption) _then) = __$ChipOptionCopyWithImpl;
@override @useResult
$Res call({
int? index, String? label, String? value
});
}
/// @nodoc
class __$ChipOptionCopyWithImpl<$Res>
implements _$ChipOptionCopyWith<$Res> {
__$ChipOptionCopyWithImpl(this._self, this._then);
final _ChipOption _self;
final $Res Function(_ChipOption) _then;
/// Create a copy of ChipOption
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? index = freezed,Object? label = freezed,Object? value = freezed,}) {
return _then(_ChipOption(
index: freezed == index ? _self.index : index // ignore: cast_nullable_to_non_nullable
as int?,label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
as String?,value: freezed == value ? _self.value : value // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
// dart format on

View File

@@ -0,0 +1,40 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'chip_selection_sdui_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_ChipSelectionSDUIModel _$ChipSelectionSDUIModelFromJson(
Map<String, dynamic> json,
) => _ChipSelectionSDUIModel(
key: json['key'] as String?,
label: json['label'] as String?,
options: (json['options'] as List<dynamic>?)
?.map((e) => ChipOption.fromJson(e as Map<String, dynamic>))
.toList(),
selectedIndex: (json['selected_index'] as num?)?.toInt(),
);
Map<String, dynamic> _$ChipSelectionSDUIModelToJson(
_ChipSelectionSDUIModel instance,
) => <String, dynamic>{
'key': instance.key,
'label': instance.label,
'options': instance.options,
'selected_index': instance.selectedIndex,
};
_ChipOption _$ChipOptionFromJson(Map<String, dynamic> json) => _ChipOption(
index: (json['index'] as num?)?.toInt(),
label: json['label'] as String?,
value: json['value'] as String?,
);
Map<String, dynamic> _$ChipOptionToJson(_ChipOption instance) =>
<String, dynamic>{
'index': instance.index,
'label': instance.label,
'value': instance.value,
};

View File

@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/dropdown/model/dropdown_sdui_model.dart';
import 'package:rasadyar_core/core.dart';
class DropdownSDUI extends StatelessWidget {
final DropdownSDUIModel model;
final RxMap<String, dynamic>? state;
final Function(String key, String value)? onChanged;
const DropdownSDUI({
super.key,
required this.model,
this.state,
this.onChanged,
});
@override
Widget build(BuildContext context) {
final items = model.items ?? [];
if (items.isEmpty) {
return const SizedBox.shrink();
}
return Obx(() {
final selectedValue = state?[model.key] as String? ?? model.selectedValue;
return ResourceOverlayDropdown<String>(
items: Resource.success(items),
selectedItem: selectedValue,
onChanged: (item) {
if (onChanged != null && model.key != null) {
onChanged!(model.key!, item);
}
},
itemBuilder: (item) => Text(item),
labelBuilder: (selected) =>
Text(selected ?? (model.placeholder ?? model.label ?? '')),
isDisabled: model.enabled == false,
);
});
}
}

View File

@@ -0,0 +1,20 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'dropdown_sdui_model.freezed.dart';
part 'dropdown_sdui_model.g.dart';
@freezed
abstract class DropdownSDUIModel with _$DropdownSDUIModel {
const factory DropdownSDUIModel({
String? key,
String? label,
String? placeholder,
List<String>? items,
String? selectedValue,
bool? enabled,
}) = _DropdownSDUIModel;
factory DropdownSDUIModel.fromJson(Map<String, dynamic> json) =>
_$DropdownSDUIModelFromJson(json);
}

View File

@@ -0,0 +1,300 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'dropdown_sdui_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$DropdownSDUIModel {
String? get key; String? get label; String? get placeholder; List<String>? get items; String? get selectedValue; bool? get enabled;
/// Create a copy of DropdownSDUIModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$DropdownSDUIModelCopyWith<DropdownSDUIModel> get copyWith => _$DropdownSDUIModelCopyWithImpl<DropdownSDUIModel>(this as DropdownSDUIModel, _$identity);
/// Serializes this DropdownSDUIModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is DropdownSDUIModel&&(identical(other.key, key) || other.key == key)&&(identical(other.label, label) || other.label == label)&&(identical(other.placeholder, placeholder) || other.placeholder == placeholder)&&const DeepCollectionEquality().equals(other.items, items)&&(identical(other.selectedValue, selectedValue) || other.selectedValue == selectedValue)&&(identical(other.enabled, enabled) || other.enabled == enabled));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,key,label,placeholder,const DeepCollectionEquality().hash(items),selectedValue,enabled);
@override
String toString() {
return 'DropdownSDUIModel(key: $key, label: $label, placeholder: $placeholder, items: $items, selectedValue: $selectedValue, enabled: $enabled)';
}
}
/// @nodoc
abstract mixin class $DropdownSDUIModelCopyWith<$Res> {
factory $DropdownSDUIModelCopyWith(DropdownSDUIModel value, $Res Function(DropdownSDUIModel) _then) = _$DropdownSDUIModelCopyWithImpl;
@useResult
$Res call({
String? key, String? label, String? placeholder, List<String>? items, String? selectedValue, bool? enabled
});
}
/// @nodoc
class _$DropdownSDUIModelCopyWithImpl<$Res>
implements $DropdownSDUIModelCopyWith<$Res> {
_$DropdownSDUIModelCopyWithImpl(this._self, this._then);
final DropdownSDUIModel _self;
final $Res Function(DropdownSDUIModel) _then;
/// Create a copy of DropdownSDUIModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? key = freezed,Object? label = freezed,Object? placeholder = freezed,Object? items = freezed,Object? selectedValue = freezed,Object? enabled = freezed,}) {
return _then(_self.copyWith(
key: freezed == key ? _self.key : key // ignore: cast_nullable_to_non_nullable
as String?,label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
as String?,placeholder: freezed == placeholder ? _self.placeholder : placeholder // ignore: cast_nullable_to_non_nullable
as String?,items: freezed == items ? _self.items : items // ignore: cast_nullable_to_non_nullable
as List<String>?,selectedValue: freezed == selectedValue ? _self.selectedValue : selectedValue // ignore: cast_nullable_to_non_nullable
as String?,enabled: freezed == enabled ? _self.enabled : enabled // ignore: cast_nullable_to_non_nullable
as bool?,
));
}
}
/// Adds pattern-matching-related methods to [DropdownSDUIModel].
extension DropdownSDUIModelPatterns on DropdownSDUIModel {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _DropdownSDUIModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _DropdownSDUIModel() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _DropdownSDUIModel value) $default,){
final _that = this;
switch (_that) {
case _DropdownSDUIModel():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _DropdownSDUIModel value)? $default,){
final _that = this;
switch (_that) {
case _DropdownSDUIModel() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? key, String? label, String? placeholder, List<String>? items, String? selectedValue, bool? enabled)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _DropdownSDUIModel() when $default != null:
return $default(_that.key,_that.label,_that.placeholder,_that.items,_that.selectedValue,_that.enabled);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? key, String? label, String? placeholder, List<String>? items, String? selectedValue, bool? enabled) $default,) {final _that = this;
switch (_that) {
case _DropdownSDUIModel():
return $default(_that.key,_that.label,_that.placeholder,_that.items,_that.selectedValue,_that.enabled);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? key, String? label, String? placeholder, List<String>? items, String? selectedValue, bool? enabled)? $default,) {final _that = this;
switch (_that) {
case _DropdownSDUIModel() when $default != null:
return $default(_that.key,_that.label,_that.placeholder,_that.items,_that.selectedValue,_that.enabled);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _DropdownSDUIModel implements DropdownSDUIModel {
const _DropdownSDUIModel({this.key, this.label, this.placeholder, final List<String>? items, this.selectedValue, this.enabled}): _items = items;
factory _DropdownSDUIModel.fromJson(Map<String, dynamic> json) => _$DropdownSDUIModelFromJson(json);
@override final String? key;
@override final String? label;
@override final String? placeholder;
final List<String>? _items;
@override List<String>? get items {
final value = _items;
if (value == null) return null;
if (_items is EqualUnmodifiableListView) return _items;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override final String? selectedValue;
@override final bool? enabled;
/// Create a copy of DropdownSDUIModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$DropdownSDUIModelCopyWith<_DropdownSDUIModel> get copyWith => __$DropdownSDUIModelCopyWithImpl<_DropdownSDUIModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$DropdownSDUIModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DropdownSDUIModel&&(identical(other.key, key) || other.key == key)&&(identical(other.label, label) || other.label == label)&&(identical(other.placeholder, placeholder) || other.placeholder == placeholder)&&const DeepCollectionEquality().equals(other._items, _items)&&(identical(other.selectedValue, selectedValue) || other.selectedValue == selectedValue)&&(identical(other.enabled, enabled) || other.enabled == enabled));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,key,label,placeholder,const DeepCollectionEquality().hash(_items),selectedValue,enabled);
@override
String toString() {
return 'DropdownSDUIModel(key: $key, label: $label, placeholder: $placeholder, items: $items, selectedValue: $selectedValue, enabled: $enabled)';
}
}
/// @nodoc
abstract mixin class _$DropdownSDUIModelCopyWith<$Res> implements $DropdownSDUIModelCopyWith<$Res> {
factory _$DropdownSDUIModelCopyWith(_DropdownSDUIModel value, $Res Function(_DropdownSDUIModel) _then) = __$DropdownSDUIModelCopyWithImpl;
@override @useResult
$Res call({
String? key, String? label, String? placeholder, List<String>? items, String? selectedValue, bool? enabled
});
}
/// @nodoc
class __$DropdownSDUIModelCopyWithImpl<$Res>
implements _$DropdownSDUIModelCopyWith<$Res> {
__$DropdownSDUIModelCopyWithImpl(this._self, this._then);
final _DropdownSDUIModel _self;
final $Res Function(_DropdownSDUIModel) _then;
/// Create a copy of DropdownSDUIModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? key = freezed,Object? label = freezed,Object? placeholder = freezed,Object? items = freezed,Object? selectedValue = freezed,Object? enabled = freezed,}) {
return _then(_DropdownSDUIModel(
key: freezed == key ? _self.key : key // ignore: cast_nullable_to_non_nullable
as String?,label: freezed == label ? _self.label : label // ignore: cast_nullable_to_non_nullable
as String?,placeholder: freezed == placeholder ? _self.placeholder : placeholder // ignore: cast_nullable_to_non_nullable
as String?,items: freezed == items ? _self._items : items // ignore: cast_nullable_to_non_nullable
as List<String>?,selectedValue: freezed == selectedValue ? _self.selectedValue : selectedValue // ignore: cast_nullable_to_non_nullable
as String?,enabled: freezed == enabled ? _self.enabled : enabled // ignore: cast_nullable_to_non_nullable
as bool?,
));
}
}
// dart format on

View File

@@ -0,0 +1,29 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'dropdown_sdui_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_DropdownSDUIModel _$DropdownSDUIModelFromJson(Map<String, dynamic> json) =>
_DropdownSDUIModel(
key: json['key'] as String?,
label: json['label'] as String?,
placeholder: json['placeholder'] as String?,
items: (json['items'] as List<dynamic>?)
?.map((e) => e as String)
.toList(),
selectedValue: json['selected_value'] as String?,
enabled: json['enabled'] as bool?,
);
Map<String, dynamic> _$DropdownSDUIModelToJson(_DropdownSDUIModel instance) =>
<String, dynamic>{
'key': instance.key,
'label': instance.label,
'placeholder': instance.placeholder,
'items': instance.items,
'selected_value': instance.selectedValue,
'enabled': instance.enabled,
};

View File

@@ -0,0 +1,251 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:rasadyar_chicken/presentation/widget/sdui/widgets/image_picker/model/image_picker_sdui_model.dart';
import 'package:rasadyar_core/core.dart';
class ImagePickerSDUI extends StatefulWidget {
final ImagePickerSDUIModel model;
final Map<String, RxList<XFile>>? images;
final Function(String key, RxList<XFile> images)? onImagesChanged;
const ImagePickerSDUI({
super.key,
required this.model,
this.images,
this.onImagesChanged,
});
@override
State<ImagePickerSDUI> createState() => _ImagePickerSDUIState();
}
class _ImagePickerSDUIState extends State<ImagePickerSDUI> {
late RImagePickerController imagePickerController;
RxList<XFile>? imageList;
Set<String> _addedImagePaths = {}; // برای track کردن عکس‌های اضافه شده
@override
void initState() {
super.initState();
imagePickerController = RImagePickerController();
final key = widget.model.key ?? '';
if (key.isNotEmpty) {
// Get or create the images list for this key
if (widget.images != null && widget.images!.containsKey(key)) {
imageList = widget.images![key]!;
// اضافه کردن path های عکس‌های موجود به set
_addedImagePaths = imageList!.map((img) => img.path).toSet();
} else {
imageList = RxList<XFile>();
if (widget.images != null) {
widget.images![key] = imageList!;
}
_addedImagePaths = {};
}
imagePickerController.capturedImages.clear();
// Listen to controller changes
imagePickerController.addListener(() {
if (imagePickerController.capturedImages.isNotEmpty &&
imageList != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
final maxImages = widget.model.maxImages;
final allCapturedImages = imagePickerController.capturedImages;
// فقط عکس‌های جدید را پیدا می‌کنیم (عکس‌هایی که قبلاً اضافه نشده‌اند)
final newImages = allCapturedImages
.where((img) => !_addedImagePaths.contains(img.path))
.toList();
if (newImages.isNotEmpty) {
if (maxImages != null) {
// اگر محدودیت وجود دارد، فقط تا maxImages عکس اضافه می‌کنیم
final currentCount = imageList!.length;
final remainingSlots = maxImages - currentCount;
if (remainingSlots > 0) {
final imagesToAdd = newImages.take(remainingSlots).toList();
imageList!.addAll(imagesToAdd);
// اضافه کردن path های عکس‌های جدید به set
_addedImagePaths.addAll(imagesToAdd.map((img) => img.path));
}
} else {
// اگر محدودیت وجود ندارد، همه عکس‌های جدید را اضافه می‌کنیم
imageList!.addAll(newImages);
// اضافه کردن path های عکس‌های جدید به set
_addedImagePaths.addAll(newImages.map((img) => img.path));
}
}
if (widget.onImagesChanged != null) {
widget.onImagesChanged!(key, imageList!);
}
});
}
});
}
}
@override
void dispose() {
imagePickerController.disposeCameraController();
super.dispose();
}
@override
Widget build(BuildContext context) {
final key = widget.model.key ?? '';
if (key.isEmpty || imageList == null) {
return const SizedBox.shrink();
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 10.h),
Row(
children: [
Expanded(
child: Obx(() {
final maxImages = widget.model.maxImages;
final currentImageCount = imageList!.length;
final canAddMore =
maxImages == null || currentImageCount < maxImages;
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
physics: BouncingScrollPhysics(),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
spacing: 8,
children: [
...imageList!.map(
(entry) => Stack(
children: [
Container(
height: 80.h,
width: 80.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(
width: 1,
color: AppColor.blackLightHover,
),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.file(
File(entry.path),
fit: BoxFit.cover,
),
),
),
// Delete button
Positioned(
top: 4,
left: 4,
child: GestureDetector(
onTap: () {
imageList!.removeWhere(
(element) => element.path == entry.path,
);
// حذف path از set
_addedImagePaths.remove(entry.path);
if (widget.onImagesChanged != null) {
widget.onImagesChanged!(key, imageList!);
}
},
child: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: Icon(
Icons.close,
color: Colors.white,
size: 16,
),
),
),
),
],
),
),
// Add image button - only show if we can add more
if (canAddMore)
GestureDetector(
onTap: () {
imagePickerController.capturedImages.clear();
final existingPaths = imageList!
.map((img) => img.path)
.toSet();
_addedImagePaths = existingPaths;
Get.to(
() => RImagePicker(
controller: imagePickerController,
maxImages: maxImages != null
? maxImages - currentImageCount
: null,
),
fullscreenDialog: true,
transition: Transition.fade,
duration: Duration(milliseconds: 300),
);
},
child: Container(
height: 80.h,
width: 80.w,
padding: EdgeInsets.all(22),
decoration: BoxDecoration(
color: Color(0xFFE9E9E9),
border: Border.all(
width: 1,
color: AppColor.blackLightHover,
),
borderRadius: BorderRadius.circular(8),
),
child: Assets.vec.galleryAddSvg.svg(
width: 36,
height: 36,
),
),
),
],
),
);
}),
),
],
),
if (widget.model.label != null && widget.model.label!.isNotEmpty) ...[
SizedBox(height: 9.h),
RichText(
text: TextSpan(
children: [
TextSpan(
text: widget.model.label!,
style: AppFonts.yekan14.copyWith(
color: AppColor.textColorLight,
),
),
if (widget.model.required == true)
TextSpan(
text: ' *',
style: AppFonts.yekan14.copyWith(color: Colors.red),
),
],
),
),
],
],
);
}
}

View File

@@ -0,0 +1,18 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'image_picker_sdui_model.freezed.dart';
part 'image_picker_sdui_model.g.dart';
@freezed
abstract class ImagePickerSDUIModel with _$ImagePickerSDUIModel {
const factory ImagePickerSDUIModel({
String? key,
String? label,
bool? required,
int? maxImages,
}) = _ImagePickerSDUIModel;
factory ImagePickerSDUIModel.fromJson(Map<String, dynamic> json) =>
_$ImagePickerSDUIModelFromJson(json);
}

Some files were not shown because too many files have changed in this diff Show More