Commit c8abf95b authored by DatHV's avatar DatHV
Browse files

update screen logic

parent fda33894
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'membership_info_response.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
MembershipInfoResponse _$MembershipInfoResponseFromJson(
Map<String, dynamic> json,
) => MembershipInfoResponse(
levels:
(json['levels'] as List<dynamic>?)
?.map((e) => MembershipLevelModel.fromJson(e as Map<String, dynamic>))
.toList(),
membershipRule: json['membership_rule'] as String?,
);
Map<String, dynamic> _$MembershipInfoResponseToJson(
MembershipInfoResponse instance,
) => <String, dynamic>{
'levels': instance.levels,
'membership_rule': instance.membershipRule,
};
import 'package:json_annotation/json_annotation.dart';
import 'package:mypoint_flutter_app/screen/home/models/image_model.dart';
import 'accumulated_counter_model.dart';
import 'membership_level_term_and_condition_model.dart';
part 'membership_level_model.g.dart';
@JsonSerializable()
class MembershipLevelModel {
final String? id;
@JsonKey(name: 'membership_level_rank')
final String? rank;
@JsonKey(name: 'membership_level_name')
final String? levelName;
@JsonKey(name: 'membership_level_description')
final String? description;
@JsonKey(name: 'membership_level_content')
final String? content;
@JsonKey(name: 'level_text_color')
final String? levelTextColor;
@JsonKey(name: 'logo')
final String? logo;
@JsonKey(name: 'level_start_at_date')
final String? levelStartAtDate;
@JsonKey(name: 'level_end_at_date')
final String? levelEndAtDate;
@JsonKey(name: 'refresh_level_after_months_from_start_date')
final String? refreshAfterMonths;
@JsonKey(name: 'upgrade_when_counter_is_greater_or_equal')
final String? upgradePointThreshold;
@JsonKey(name: 'upgrade_when_counter_gmv_is_greater_or_equal')
final String? upgradeGmvThreshold;
@JsonKey(name: 'downgrade_level_when_counter_is_less_than')
final String? downgradePointThreshold;
@JsonKey(name: 'downgrade_level_when_counter_gmv_is_less_than')
final String? downgradeGmvThreshold;
@JsonKey(name: 'upgrade_to_membership_level_id')
final String? upgradeToLevelId;
@JsonKey(name: 'downgrade_to_membership_level_id')
final String? downgradeToLevelId;
@JsonKey(name: 'membership_level_term_and_conditions')
final List<MembershipLevelTermAndConditionModel>? conditions;
@JsonKey(name: 'accumulated_counter')
final AccumulatedCounter? accumulatedCounter;
final List<ImageModel>? images;
MembershipLevelModel({
this.id,
this.rank,
this.levelName,
this.description,
this.content,
this.levelTextColor,
this.logo,
this.levelStartAtDate,
this.levelEndAtDate,
this.refreshAfterMonths,
this.upgradePointThreshold,
this.upgradeGmvThreshold,
this.downgradePointThreshold,
this.downgradeGmvThreshold,
this.upgradeToLevelId,
this.downgradeToLevelId,
this.conditions,
this.accumulatedCounter,
this.images,
});
factory MembershipLevelModel.fromJson(Map<String, dynamic> json) =>
_$MembershipLevelModelFromJson(json);
Map<String, dynamic> toJson() => _$MembershipLevelModelToJson(this);
}
\ No newline at end of file
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'membership_level_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
MembershipLevelModel _$MembershipLevelModelFromJson(
Map<String, dynamic> json,
) => MembershipLevelModel(
id: json['id'] as String?,
rank: json['membership_level_rank'] as String?,
levelName: json['membership_level_name'] as String?,
description: json['membership_level_description'] as String?,
content: json['membership_level_content'] as String?,
levelTextColor: json['level_text_color'] as String?,
logo: json['logo'] as String?,
levelStartAtDate: json['level_start_at_date'] as String?,
levelEndAtDate: json['level_end_at_date'] as String?,
refreshAfterMonths:
json['refresh_level_after_months_from_start_date'] as String?,
upgradePointThreshold:
json['upgrade_when_counter_is_greater_or_equal'] as String?,
upgradeGmvThreshold:
json['upgrade_when_counter_gmv_is_greater_or_equal'] as String?,
downgradePointThreshold:
json['downgrade_level_when_counter_is_less_than'] as String?,
downgradeGmvThreshold:
json['downgrade_level_when_counter_gmv_is_less_than'] as String?,
upgradeToLevelId: json['upgrade_to_membership_level_id'] as String?,
downgradeToLevelId: json['downgrade_to_membership_level_id'] as String?,
conditions:
(json['membership_level_term_and_conditions'] as List<dynamic>?)
?.map(
(e) => MembershipLevelTermAndConditionModel.fromJson(
e as Map<String, dynamic>,
),
)
.toList(),
accumulatedCounter:
json['accumulated_counter'] == null
? null
: AccumulatedCounter.fromJson(
json['accumulated_counter'] as Map<String, dynamic>,
),
images:
(json['images'] as List<dynamic>?)
?.map((e) => ImageModel.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$MembershipLevelModelToJson(
MembershipLevelModel instance,
) => <String, dynamic>{
'id': instance.id,
'membership_level_rank': instance.rank,
'membership_level_name': instance.levelName,
'membership_level_description': instance.description,
'membership_level_content': instance.content,
'level_text_color': instance.levelTextColor,
'logo': instance.logo,
'level_start_at_date': instance.levelStartAtDate,
'level_end_at_date': instance.levelEndAtDate,
'refresh_level_after_months_from_start_date': instance.refreshAfterMonths,
'upgrade_when_counter_is_greater_or_equal': instance.upgradePointThreshold,
'upgrade_when_counter_gmv_is_greater_or_equal': instance.upgradeGmvThreshold,
'downgrade_level_when_counter_is_less_than': instance.downgradePointThreshold,
'downgrade_level_when_counter_gmv_is_less_than':
instance.downgradeGmvThreshold,
'upgrade_to_membership_level_id': instance.upgradeToLevelId,
'downgrade_to_membership_level_id': instance.downgradeToLevelId,
'membership_level_term_and_conditions': instance.conditions,
'accumulated_counter': instance.accumulatedCounter,
'images': instance.images,
};
import 'package:json_annotation/json_annotation.dart';
part 'membership_level_term_and_condition_model.g.dart';
@JsonSerializable()
class MembershipLevelTermAndConditionModel {
final String? icon;
final String? title;
final String? content;
MembershipLevelTermAndConditionModel({
this.icon,
this.title,
this.content,
});
factory MembershipLevelTermAndConditionModel.fromJson(Map<String, dynamic> json) =>
_$MembershipLevelTermAndConditionModelFromJson(json);
Map<String, dynamic> toJson() => _$MembershipLevelTermAndConditionModelToJson(this);
}
\ No newline at end of file
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'membership_level_term_and_condition_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
MembershipLevelTermAndConditionModel
_$MembershipLevelTermAndConditionModelFromJson(Map<String, dynamic> json) =>
MembershipLevelTermAndConditionModel(
icon: json['icon'] as String?,
title: json['title'] as String?,
content: json['content'] as String?,
);
Map<String, dynamic> _$MembershipLevelTermAndConditionModelToJson(
MembershipLevelTermAndConditionModel instance,
) => <String, dynamic>{
'icon': instance.icon,
'title': instance.title,
'content': instance.content,
};
import 'package:flutter/material.dart';
import 'package:mypoint_flutter_app/widgets/custom_app_bar.dart';
import '../../widgets/custom_navigation_bar.dart';
class _OrderMenuItem {
final String title;
final IconData icon;
_OrderMenuItem({required this.title, required this.icon});
}
class OrderMenuScreen extends StatelessWidget {
OrderMenuScreen({super.key});
final List<_OrderMenuItem> items = [
_OrderMenuItem(title: 'Thẻ nạp của tôi', icon: Icons.credit_card),
_OrderMenuItem(title: 'Sổ sức khỏe điện tử', icon: Icons.medical_services_outlined),
_OrderMenuItem(title: 'Dịch vụ giao thông', icon: Icons.traffic_outlined),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomNavigationBar(title: "Đơn mua",),
body: Container(
color: Colors.white,
child: ListView.separated(
itemCount: items.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) {
final item = items[index];
return InkWell(
onTap: () {
print("Tapped on ${item.title}");
// TODO: handle tap
},
child: Container(
height: 48,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
Icon(item.icon, color: Colors.black54),
const SizedBox(width: 12),
Expanded(
child: Text(
item.title,
style: const TextStyle(fontSize: 16, color: Colors.black87),
),
),
const Icon(Icons.chevron_right, color: Colors.black54),
],
),
),
);
}
),
),
);
}
}
......@@ -176,7 +176,22 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba
}
} else if (mediaType == MediaTypeItemCampaign.text.key) {
if (item.contentText != null && item.contentText!.isNotEmpty) {
widgets.add(Padding(padding: const EdgeInsets.only(bottom: 16), child: HtmlWidget(item.contentText!)));
widgets.add(
Padding(padding: const EdgeInsets.only(bottom: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if ((item.contentCaption ?? "").isNotEmpty)
Text(
item.contentCaption!,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
HtmlWidget(item.contentText!),
],
)
)
);
}
} else if (mediaType == MediaTypeItemCampaign.pageLink.key) {
if (item.pages?.isNotEmpty == true) {
......
......@@ -21,6 +21,22 @@ class CampaignDetailViewModel extends RestfulApiViewModel {
fetchWebsitePage(type!);
return;
}
fetchFAQItems();
}
Future<void> fetchFAQItems() async {
showLoading();
isLoading(true);
client.websiteFolderGetPageList({"folder_uri": "ABOUT"}).then((value) {
hideLoading();
isLoading(false);
final pageId = (value.data?.items ?? []).first.pageId ?? "";
if (pageId.isEmpty) {
errorMessage.value = Constants.commonError;
} else {
fetchWebsitePageGetDetail(pageId);
}
});
}
void fetchWebsitePage(DetailPageRuleType type) {
......
import 'package:flutter/cupertino.dart';
import 'package:mypoint_flutter_app/extensions/datetime_extensions.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart';
import 'package:mypoint_flutter_app/screen/personal/personal_gender.dart';
import '../../networking/model_maker.dart';
import '../location_address/location_address_viewmodel.dart';
enum SectionPersonalEditType {
name,
nickname,
phone,
email,
identificationNumber,
birthday,
gender,
address,
province,
district,
}
class PersonalEditDataModel {
String? name;
String? nickname;
String? phone;
String? email;
String? identificationNumber;
DateTime? birthday;
PersonalGender? gender;
String? address;
AddressBaseModel? province;
AddressBaseModel? district;
String? avatar;
PersonalEditDataModel({
this.name,
this.nickname,
this.phone,
this.email,
this.identificationNumber,
this.birthday,
this.gender,
this.address,
this.province,
this.district,
this.avatar,
});
Json get body => <String, dynamic> {
"worker_site_id": DataPreference.instance.profile?.workerSite?.id,
"fullname": name,
"nickname": nickname,
"date_of_birth": birthday?.toFormattedString(format: "yyyy-MM-dd"),
"sex": gender?.value ?? "U",
"address_full": address,
"address_district_code": district?.code ?? "",
"address_province_code": province?.code ?? "",
"identification_number": identificationNumber,
"email": email,
"avatar": "",
"avatar_2": "",
};
}
class PersonalEditItemModel {
String? title;
String? value;
String? hintText;
bool? isRequired;
bool? showDropIcon;
String? warningText;
bool? isEditable = true;
TextInputType? keyboardType;
SectionPersonalEditType? sectionType;
PersonalEditItemModel({
this.title,
this.value,
this.hintText,
this.isRequired = false,
this.showDropIcon = false,
this.warningText,
this.isEditable = true,
this.keyboardType,
this.sectionType,
});
static List<PersonalEditItemModel> personalEditFields(PersonalEditDataModel data) {
return [
PersonalEditItemModel(
title: "Họ và tên",
value: data.name,
hintText: "Chưa cập nhật",
isRequired: true,
showDropIcon: false,
sectionType: SectionPersonalEditType.name,
),
PersonalEditItemModel(
title: "Biệt danh",
value: data.nickname,
hintText: "Chưa cập nhật",
isRequired: false,
showDropIcon: false,
sectionType: SectionPersonalEditType.nickname,
),
PersonalEditItemModel(
title: "Số điện thoại",
value: data.phone,
isRequired: false,
showDropIcon: false,
isEditable: false,
keyboardType: TextInputType.phone,
sectionType: SectionPersonalEditType.phone,
),
PersonalEditItemModel(
title: "Email",
value: data.email,
hintText: "Chưa cập nhật",
isRequired: false,
showDropIcon: false,
keyboardType: TextInputType.emailAddress,
sectionType: SectionPersonalEditType.email,
),
PersonalEditItemModel(
title: "Số CMND/CCCD",
value: data.identificationNumber,
hintText: "Chưa cập nhật",
isRequired: false,
showDropIcon: false,
keyboardType: TextInputType.number,
sectionType: SectionPersonalEditType.identificationNumber,
),
PersonalEditItemModel(
title: "Ngày sinh",
value: data.birthday?.toFormattedString() ?? "",
hintText: "Ngày/Tháng/Năm",
isRequired: true,
showDropIcon: true,
warningText: "Ngày sinh chỉ được cập nhật 1 lần duy nhất",
sectionType: SectionPersonalEditType.birthday,
),
PersonalEditItemModel(
title: "Giới tính",
value: data.gender?.display ?? "",
hintText: "Khác",
isRequired: true,
showDropIcon: true,
sectionType: SectionPersonalEditType.gender,
),
PersonalEditItemModel(
title: "Địa chỉ",
value: data.address,
hintText: "Số nhà, đường, phường/xã",
isRequired: false,
showDropIcon: false,
sectionType: SectionPersonalEditType.address,
),
PersonalEditItemModel(
title: "Tỉnh, Thành phố",
value: data.province?.name ?? "",
hintText: "Chọn tỉnh thành",
isRequired: false,
showDropIcon: true,
sectionType: SectionPersonalEditType.province,
),
PersonalEditItemModel(
title: "Quận, Huyện",
value: data.district?.name ?? "",
hintText: "Chọn quận/huyện",
isRequired: false,
showDropIcon: true,
sectionType: SectionPersonalEditType.district,
),
];
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart';
import 'package:mypoint_flutter_app/screen/personal/personal_edit_item_model.dart';
import 'package:mypoint_flutter_app/screen/personal/personal_edit_viewmodel.dart';
import 'package:mypoint_flutter_app/screen/personal/personal_gender.dart';
import 'package:mypoint_flutter_app/widgets/custom_app_bar.dart';
import '../../base/base_screen.dart';
import '../../base/basic_state.dart';
import '../../resouce/base_color.dart';
import '../../shared/router_gage.dart';
import '../../widgets/alert/data_alert_model.dart';
import '../../widgets/bottom_sheet_helper.dart';
import '../../widgets/time_picker_widget.dart';
class PersonalEditScreen extends BaseScreen {
const PersonalEditScreen({super.key});
@override
State<PersonalEditScreen> createState() => _PersonalEditScreenState();
}
class _PersonalEditScreenState extends BaseState<PersonalEditScreen> with BasicState {
final viewModel = Get.put(PersonalEditViewModel());
@override
initState() {
super.initState();
viewModel.onShowAlertError = (message) {
showAlertError(content: message, barrierDismissible: true, onConfirmed: null);
};
viewModel.updateProfileResponseSuccess = () {
DataAlertModel alertData = DataAlertModel(
localHeaderImage: "assets/images/ic_pipi_05.png",
title: "Thông báo",
description: "Cập nhật thông tin cá nhân thành công!",
buttons: [
AlertButton(
text: "Đã hiểu",
onPressed: () => Get.back(),
bgColor: BaseColor.primary500,
textColor: Colors.white,
),
],
);
showAlert(data: alertData);
};
}
@override
Widget createBody() {
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
behavior: HitTestBehavior.translucent,
child: Scaffold(
appBar: CustomAppBar.back(title: "Chỉnh sửa thông tin cá nhân"),
body: Obx(() {
List<PersonalEditItemModel> items;
final editDataModel = viewModel.editDataModel.value;
if (editDataModel == null) {
return const SizedBox.shrink();
}
items = PersonalEditItemModel.personalEditFields(editDataModel);
return CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12),
child: _buildAvatarItem(),
),
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
final item = items[index];
return _editPersonalItem(item);
}, childCount: items.length),
),
],
);
}),
bottomNavigationBar: _buildContinueButton(),
),
);
}
Widget _buildContinueButton() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
decoration: const BoxDecoration(
color: Colors.white,
boxShadow: [BoxShadow(color: Colors.black54, blurRadius: 8, offset: Offset(0, 4))],
),
child: SafeArea(
top: false,
child: Obx(() {
return SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton(
onPressed:
viewModel.isValidate.value
? () {
viewModel.updateProfile();
}
: null,
style: ElevatedButton.styleFrom(
backgroundColor: viewModel.isValidate.value ? BaseColor.primary500 : Colors.grey,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
child: const Text(
'Cập Nhật Ngay',
style: TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.bold),
),
),
);
}),
),
);
}
Widget _buildAvatarItem() {
return Center(
child: Stack(
alignment: Alignment.bottomRight,
children: [
ClipOval(child: Image.asset("assets/images/bg_default_11.png", width: 100, height: 100, fit: BoxFit.cover)),
Positioned(
bottom: 4,
right: 4,
child: GestureDetector(
onTap: () {
print("Change avatar tapped");
},
child: Container(
padding: const EdgeInsets.all(4),
decoration: const BoxDecoration(color: Colors.red, shape: BoxShape.circle),
child: const Icon(Icons.camera_alt, color: Colors.white, size: 18),
),
),
),
],
),
);
}
_onTapItemChangeValue(PersonalEditItemModel item) async {
if (item.sectionType == SectionPersonalEditType.province || item.sectionType == SectionPersonalEditType.district) {
viewModel.navigateToLocationScreen(item);
} else if (item.sectionType == SectionPersonalEditType.birthday) {
if ((DataPreference.instance.profile?.workerSite?.birthday ?? "").isNotEmpty) return;
final now = DateTime.now();
final picked = await showDatePicker(
context: context,
initialDate: viewModel.birthday ?? now,
firstDate: DateTime(1900),
lastDate: DateTime(2100),
);
if (picked != null) {
setState(() {
viewModel.birthday = picked;
viewModel.editDataModel.value?.birthday = picked;
viewModel.isValidate.value = viewModel.validate();
});
}
} else if (item.sectionType == SectionPersonalEditType.gender) {
showGenderPicker(
context: context,
selected: viewModel.gender ?? PersonalGender.unknown,
onSelected: (gender) {
setState(() {
viewModel.gender = gender;
viewModel.editDataModel.value?.gender = gender;
});
},
);
}
}
Widget _editPersonalItem(PersonalEditItemModel item) {
final isTapField =
item.sectionType == SectionPersonalEditType.province ||
item.sectionType == SectionPersonalEditType.district ||
item.sectionType == SectionPersonalEditType.gender;
final isDate = item.sectionType == SectionPersonalEditType.birthday;
final isTappableItem = isTapField || isDate;
return Padding(
padding: const EdgeInsets.only(top: 8, bottom: 8, left: 16, right: 16), // all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// const SizedBox(height: 8),
RichText(
text: TextSpan(
text: item.title,
style: const TextStyle(fontSize: 14, color: Colors.black, fontWeight: FontWeight.w600),
children: [
if (item.isRequired == true)
const TextSpan(text: ' (*)', style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold)),
],
),
),
const SizedBox(height: 6),
GestureDetector(
onTap: isTappableItem ? () => _onTapItemChangeValue(item) : null,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
decoration: BoxDecoration(
color: item.isEditable == true ? Colors.white : Colors.grey.shade100,
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(8),
// color: Colors.grey.shade50,
),
child: Row(
children: [
Expanded(
child: TextField(
keyboardType: item.keyboardType ?? TextInputType.text,
controller: TextEditingController(text: item.value ?? ""),
enabled: isTappableItem ? false : (item.isEditable ?? true),
decoration: InputDecoration.collapsed(
hintText: item.hintText ?? "",
hintStyle: TextStyle(color: Colors.grey, fontSize: 14),
),
style: TextStyle(color: item.isEditable ?? true ? Colors.black87 : Colors.grey, fontSize: 16),
onChanged: (value) {
viewModel.updateItemEditData(item, value);
viewModel.isValidate.value = viewModel.validate();
},
),
),
if (item.showDropIcon == true) const Icon(Icons.expand_more, size: 20, color: Colors.grey),
],
),
),
),
const SizedBox(height: 6),
if (item.warningText != null)
Row(
children: [
const Icon(Icons.warning_amber_rounded, color: Colors.orange, size: 14),
const SizedBox(width: 4),
Text(item.warningText ?? '', style: const TextStyle(fontSize: 12, color: Colors.orange)),
],
),
// const SizedBox(height: 12),
],
),
);
}
void showGenderPicker({
required BuildContext context,
required PersonalGender selected,
required Function(PersonalGender gender) onSelected,
}) {
showModalBottomSheet(
context: context,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))),
builder: (_) {
final genderList = PersonalGender.values.toList();
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Expanded(
child: Center(
child: const Text('Giới tính', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
),
),
GestureDetector(onTap: () => Navigator.of(context).pop(), child: const Icon(Icons.close, size: 20)),
],
),
),
const Divider(height: 1),
...genderList.map((gender) {
final isSelected = selected == gender;
return ListTile(
title: Text(
gender.display,
style: TextStyle(
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
color: isSelected ? Colors.blue : Colors.black,
),
),
trailing: isSelected ? const Icon(Icons.check, color: Colors.blue) : null,
onTap: () {
Navigator.of(context).pop();
onSelected(gender);
},
);
}).toList(),
const Divider(height: 100),
],
);
},
);
}
}
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import 'package:mypoint_flutter_app/screen/personal/personal_edit_item_model.dart';
import 'package:mypoint_flutter_app/screen/personal/personal_gender.dart';
import '../../base/restful_api_viewmodel.dart';
import '../../configs/constants.dart';
import '../../preference/data_preference.dart';
import '../../shared/router_gage.dart';
import '../location_address/location_address_viewmodel.dart';
class PersonalEditViewModel extends RestfulApiViewModel {
var editDataModel = Rxn<PersonalEditDataModel>();
var province = Rxn<AddressBaseModel>();
var district = Rxn<AddressBaseModel>();
DateTime? birthday;
PersonalGender? gender;
RxBool isValidate = false.obs;
void Function(String message)? onShowAlertError;
void Function()? updateProfileResponseSuccess;
@override
void onInit() {
super.onInit();
final profile = DataPreference.instance.profile;
if (profile == null) return;
province.value = AddressBaseModel(
code: profile?.workerSite?.locationProvinceCode,
name: profile?.workerSite?.locationProvinceName,
);
district.value = AddressBaseModel(
code: profile?.workerSite?.locationDistrictCode,
name: profile?.workerSite?.locationDistrictName,
);
birthday = profile?.workerSite?.birthday?.toDateFormat('yyyy-MM-dd');
gender = PersonalGender.from(profile.workerSite?.sex ?? "U");
editDataModel.value = PersonalEditDataModel(
name: DataPreference.instance.fullName,
nickname: profile?.workerSite?.nickname,
phone: profile?.workerSite?.phoneNumber,
email: profile?.workerSite?.email,
identificationNumber: profile?.workerSite?.identificationNumber,
birthday: birthday,
gender: gender,
address: profile?.workerSite?.addressFull,
province: province.value,
district: district.value,
);
isValidate.value = validate();
}
updateItemEditData(PersonalEditItemModel item, String value) {
if (editDataModel.value == null) return;
switch (item.sectionType ?? SectionPersonalEditType.nickname) {
case SectionPersonalEditType.name:
editDataModel.value?.name = value;
break;
case SectionPersonalEditType.nickname:
editDataModel.value?.nickname = value;
break;
case SectionPersonalEditType.phone:
editDataModel.value?.phone = value;
break;
case SectionPersonalEditType.email:
editDataModel.value?.email = value;
break;
case SectionPersonalEditType.identificationNumber:
editDataModel.value?.identificationNumber = value;
break;
case SectionPersonalEditType.address:
editDataModel.value?.address = value;
break;
default:
break;
}
}
Future<void> updateProfile() async {
showLoading();
try {
final body = editDataModel.value?.body ?? {};
final response = await client.updateWorkerSiteProfile(body);
hideLoading();
if (response.status?.toLowerCase() == "success") {
updateProfileResponseSuccess?.call();
_getUserProfile();
} else {
onShowAlertError?.call(response.errorMessage ?? Constants.commonError);
}
} catch (error) {
hideLoading();
onShowAlertError?.call(Constants.commonError);
}
}
void _getUserProfile() {
client.getUserProfile().then((value) async {
final userProfile = value.data;
if (value.isSuccess && userProfile != null) {
DataPreference.instance.saveUserProfile(userProfile);
}
});
}
navigateToLocationScreen(PersonalEditItemModel item) async {
if (item.sectionType == null) return;
if (item.sectionType == SectionPersonalEditType.province) {
final result = await Get.toNamed(
locationAddressScreen,
arguments: {"type": "province", "selectedCode": province.value?.code ?? ""},
);
if (result is AddressBaseModel && result.code != province.value?.code) {
province.value = result;
district.value = null;
editDataModel.value?.district = null;
editDataModel.value?.province = result;
editDataModel.refresh();
}
} else if (item.sectionType == SectionPersonalEditType.district && (province.value?.code ?? "").isNotEmpty) {
final result = await Get.toNamed(
locationAddressScreen,
arguments: {
"type": "district",
"selectedCode": district.value?.code ?? "",
"provinceCode": province.value?.code ?? "",
},
);
if (result is AddressBaseModel) {
district.value = result;
editDataModel.value?.district = result;
editDataModel.refresh();
}
}
isValidate.value = validate();
}
bool validate() {
final model = editDataModel.value;
if (model == null) return false;
if ((model.name ?? '').isEmpty) {
return false;
}
if (model.birthday == null) {
return false;
}
if (model.gender == null || model.gender == 'notAllowed') {
return false;
}
if ((model.email ?? '').isNotEmpty && !isValidEmail(model.email!)) {
return false;
}
return true;
}
bool isValidEmail(String email) {
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
return emailRegex.hasMatch(email);
}
}
enum PersonalGender {
female,
male,
unknown;
static PersonalGender from(String gender) {
switch (gender) {
case 'F':
return PersonalGender.female;
case 'M':
return PersonalGender.male;
default:
return PersonalGender.unknown;
}
}
String get value {
switch (this) {
case PersonalGender.female:
return 'F';
case PersonalGender.male:
return 'M';
case PersonalGender.unknown:
return 'U';
}
}
String get display {
switch (this) {
case PersonalGender.female:
return 'Nữ';
case PersonalGender.male:
return 'Nam';
case PersonalGender.unknown:
return 'Khác';
}
}
}
This diff is collapsed.
......@@ -8,6 +8,7 @@ import '../../base/basic_state.dart';
import '../../resouce/base_color.dart';
import '../../widgets/bottom_sheet_helper.dart';
import '../../widgets/custom_navigation_bar.dart';
import '../home/header_home_viewmodel.dart';
import 'affiliate_overview.dart';
import 'affiliate_tab_viewmodel.dart';
......@@ -18,30 +19,32 @@ class AffiliateTabScreen extends BaseScreen {
State<AffiliateTabScreen> createState() => _AffiliateTabScreenState();
}
class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicState {
class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicState {
final AffiliateTabViewModel viewModel = Get.put(AffiliateTabViewModel());
final _headerHomeVM = Get.find<HeaderHomeViewModel>();
@override
Widget createBody() {
return Scaffold(
backgroundColor: Colors.grey.shade50,
appBar: CustomNavigationBar(
title: "Mua sắm",
showBackButton: false,
rightButtons: [
IconButton(
icon: const Icon(Icons.info, color: Colors.white),
onPressed: () {
final descriptions = viewModel.overview.value?.descriptions ?? [];
if (descriptions.isNotEmpty) {
BottomSheetHelper.showBottomSheetPopup(
child: AffiliateOverviewPopup(descriptions: viewModel.overview.value?.descriptions ?? []),
);
}
},
),
],
),
title: "Mua sắm",
showBackButton: false,
backgroundImage: _headerHomeVM.headerData.background ?? "assets/images/bg_header_navi.png",
rightButtons: [
IconButton(
icon: const Icon(Icons.info, color: Colors.white),
onPressed: () {
final descriptions = viewModel.overview.value?.descriptions ?? [];
if (descriptions.isNotEmpty) {
BottomSheetHelper.showBottomSheetPopup(
child: AffiliateOverviewPopup(descriptions: viewModel.overview.value?.descriptions ?? []),
);
}
},
),
],
),
body: Obx(() {
if (viewModel.isLoading.value) {
return const Center(child: CircularProgressIndicator());
......@@ -56,10 +59,7 @@ class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with Basic
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.amber.shade100,
borderRadius: BorderRadius.circular(12),
),
decoration: BoxDecoration(color: Colors.amber.shade100, borderRadius: BorderRadius.circular(12)),
child: Row(
children: [
const Text("Điểm hoàn:", style: TextStyle(fontSize: 18, fontWeight: FontWeight.normal)),
......@@ -67,7 +67,9 @@ class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with Basic
Image.asset('assets/images/ic_point.png', width: 20, height: 20),
const SizedBox(width: 4),
const Text(
"0", style: TextStyle(fontSize: 20, color: BaseColor.primary400, fontWeight: FontWeight.bold)),
"0",
style: TextStyle(fontSize: 20, color: BaseColor.primary400, fontWeight: FontWeight.bold),
),
const Spacer(),
Icon(Icons.arrow_forward_ios, color: BaseColor.primary400, size: 16),
],
......@@ -89,9 +91,9 @@ class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with Basic
],
),
),
AffiliateBrand(brands: viewModel.affiliateBrands,),
AffiliateCategory(categories: viewModel.affiliateCategories,),
AffiliateProductTopSale(products: viewModel.affiliateProducts,),
AffiliateBrand(brands: viewModel.affiliateBrands),
AffiliateCategory(categories: viewModel.affiliateCategories),
AffiliateProductTopSale(products: viewModel.affiliateProducts),
],
),
),
......
......@@ -12,6 +12,8 @@ class AffiliateBrand extends StatelessWidget {
if (brands.isEmpty) {
return const SizedBox.shrink();
}
final space = 8.0;
final width = (MediaQuery.of(context).size.width - space * 2 - 32)/3;
return Column(
children: [
const SizedBox(height: 24),
......@@ -29,9 +31,9 @@ class AffiliateBrand extends StatelessWidget {
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: brands.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 3 / 3.2,
childAspectRatio: width/(width + 30),
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
......
......@@ -11,6 +11,7 @@ class AffiliateCategory extends StatelessWidget {
if (categories.isEmpty) {
return const SizedBox.shrink();
}
final width = (MediaQuery.of(context).size.width - 32)/4;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
......@@ -28,7 +29,7 @@ class AffiliateCategory extends StatelessWidget {
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 4,
childAspectRatio: 3 / 3.8,
childAspectRatio: width / (width + 30),
children: categories.map((category) => _buildAffiliateCategoryItem(category)).toList(),
),
),
......
......@@ -58,13 +58,7 @@ class AffiliateProductTopSale extends StatelessWidget {
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 4,
offset: const Offset(0, 2),
)
],
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 4, offset: const Offset(0, 2))],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
......@@ -76,27 +70,19 @@ class AffiliateProductTopSale extends StatelessWidget {
scale: 1,
width: double.infinity,
fit: BoxFit.cover,
errorBuilder: (_, __, ___) => Image.asset('assets/images/ic_logo.png',),
errorBuilder: (_, __, ___) => Image.asset('assets/images/ic_logo.png'),
),
),
const SizedBox(height: 6),
Text(
title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 13),
),
Text(title, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 13)),
const SizedBox(height: 4),
Text(
formattedPrice,
style: const TextStyle(
color: Colors.blueAccent,
fontSize: 14,
fontWeight: FontWeight.bold,
),
style: const TextStyle(color: Colors.blueAccent, fontSize: 14, fontWeight: FontWeight.bold),
),
const SizedBox(height: 2),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
RichText(
text: TextSpan(
......@@ -110,12 +96,18 @@ class AffiliateProductTopSale extends StatelessWidget {
],
),
),
const Spacer(),
Text("${sold.toNum()?.formatCompactNumber()} đã bán", style: TextStyle(fontSize: 12, color: Colors.grey)),
Flexible(
child: Text(
"${sold.toNum()?.formatCompactNumber()} đã bán",
style: TextStyle(fontSize: 12, color: Colors.grey),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
],
),
],
),
);
}
}
\ No newline at end of file
}
......@@ -117,10 +117,21 @@ class _TransactionDetailScreenState extends BaseState<TransactionDetailScreen> w
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(item.name ?? '', style: TextStyle(fontSize: 16, color: Colors.grey.shade700)),
Text(item.value ?? '', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
Expanded(
flex: 2,
child: Text(item.name ?? '', style: TextStyle(fontSize: 16, color: Colors.grey.shade700)),
),
Expanded(
flex: 3,
child: Text(
item.value ?? '',
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
textAlign: TextAlign.right,
softWrap: true,
),
),
],
),
);
......@@ -461,7 +472,8 @@ class _TransactionDetailScreenState extends BaseState<TransactionDetailScreen> w
ElevatedButton(
onPressed:
_viewModel.finalTotal > 0 && _viewModel.selectedPaymentMethodIndex < 0
? null : _viewModel.requestPaymentProduct,
? null
: _viewModel.requestPaymentProduct,
style: ElevatedButton.styleFrom(
backgroundColor: BaseColor.primary500,
foregroundColor: Colors.white,
......
......@@ -61,6 +61,13 @@ class ProductModel {
return content?.name;
}
int? get percentDiscount {
if (previewFlashSale?.isFlashSalePrice == true) {
return previewFlashSale?.percentTag;
}
return null;
}
ProductType get productType {
return ProductTypeExt.from(type) ?? ProductType.voucher;
}
......@@ -101,6 +108,34 @@ class ProductModel {
return media!.firstWhere((item) => item.type == MediaType.banner16_9, orElse: () => media!.first);
}
double get progress {
if (previewFlashSale?.fsQuantityTotal != null && previewFlashSale?.fsQuantitySold != null && previewFlashSale!.fsQuantityTotal! > 0) {
return previewFlashSale!.fsQuantitySold! / previewFlashSale!.fsQuantityTotal!;
}
return 0.0;
}
bool get isShowProsessSoldItem {
return previewFlashSale?.isFlashSale == true && (previewFlashSale?.fsQuantityTotal ?? 0) > 0;
}
double get extendSpaceFlashSaleItem {
double heightPrice = 24;
double heightName = 36;
double space = 4;
double extend = heightPrice + heightName + space * 5;
if (previewFlashSale?.rewardContent != null) {
extend += space;
extend += 30;
}
if (isShowProsessSoldItem) {
extend += space;
extend += 18;
}
print("extendSpaceFlashSaleItem $extend");
return extend;
}
factory ProductModel.fromJson(Map<String, dynamic> json) => _$ProductModelFromJson(json);
Map<String, dynamic> toJson() => _$ProductModelToJson(this);
......
import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:mypoint_flutter_app/networking/restful_api.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import '../../../base/restful_api_viewmodel.dart';
import '../../home/models/my_product_model.dart';
class MyProductListViewModel extends RestfulApiViewModel {
final RxInt selectedTabIndex = 0.obs;
var myProducts = <MyProductModel>[].obs;
@override
void onInit() {
super.onInit();
freshData(isRefresh: true);
}
void selectTab(int index) {
selectedTabIndex.value = index;
freshData(isRefresh: true);
}
void freshData({bool isRefresh = false}) {
final body = {
"index": isRefresh ? 0 : myProducts.length,
"size": 20,
"status": selectedTabIndex.value,
};
client.getCustomerProducts(body).then((response) {
final result = response.data ?? [];
if (isRefresh) {
myProducts.clear();
}
myProducts.addAll(result);
}).catchError((error) {
myProducts.clear();
print('Error fetching products: $error');
});
}
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment