Commit fc2caf86 authored by DatHV's avatar DatHV
Browse files

update ui, logic.

parent 0b973e61
......@@ -88,6 +88,8 @@ class APIPaths {//sandbox
static const String customerContractDelete = "/customerContractDelete/1.0.0";
static const String getProductVnTraSold = "/product/api/v2.0/vntra_sold";
static const String detailMyPackageVnTra = "/product/api/v2.0/vntra_sold/%@";
static const String getHealthBookCards = "/product/api/v2.0/cards_sold";
static const String detailHealthBookCardDetail = "/product/api/v2.0/cards_sold/%@";
static const String getCampaignLiveTransactions = "/campaign/api/v3.0/%@/live-transactions";
static const String getCampaignMissions = "/campaign/api/v3.0/%@/missions";
static const String getCampaignReward = "/campaign/api/v3.0/%@/reward";
......
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart';
import 'package:mypoint_flutter_app/widgets/alert/popup_data_model.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:uuid/uuid.dart';
import '../base/app_loading.dart';
import '../networking/app_navigator.dart';
import '../networking/restful_api_viewmodel.dart';
import '../screen/webview/web_view_screen.dart';
import '../shared/router_gage.dart';
import 'directional_action_type.dart';
......@@ -16,12 +21,14 @@ class Defines {
class DirectionalScreen {
final String? clickActionType;
final String? clickActionParam;
final PopupDataModel? popup;
const DirectionalScreen._({this.clickActionType, this.clickActionParam});
const DirectionalScreen._({this.clickActionType, this.clickActionParam, this.popup});
factory DirectionalScreen.fromJson(Map<String, dynamic> json) => DirectionalScreen._(
clickActionType: json['click_action_type'] as String?,
clickActionParam: json['click_action_param'] as String?,
popup: json['popup'] != null ? PopupDataModel.fromJson(json['popup'] as Map<String, dynamic>) : null,
);
Map<String, dynamic> toJson() => {
......@@ -156,6 +163,9 @@ class DirectionalScreen {
case DirectionalScreenName.myVnTraPackage:
Get.toNamed(trafficServiceScreen);
return true;
case DirectionalScreenName.familyHealthBook:
Get.toNamed(healthBookScreen);
return true;
case DirectionalScreenName.campaignSevenDayScreen:
Get.toNamed(campaignSevenDayScreen);
return true;
......@@ -178,6 +188,25 @@ class DirectionalScreen {
case DirectionalScreenName.qrCode:
Get.toNamed(qrCodeScreen);
return true;
case DirectionalScreenName.makeDirectionScreen:
if ((clickActionParam ?? '').isEmpty) return false;
() async {
final vm = RestfulApiViewModel();
await vm.callApi<DirectionalScreen>(
request: () => vm.client.getDirectionScreen(clickActionParam!),
onSuccess: (screen, res) async {
final popup = screen.popup;
if (popup != null) {
AppNavigator.showPopup(data: popup);
} else {
screen.begin();
}
},
withLoading: true,
showAppNavigatorDialog: false,
);
}();
return true;
default:
print("Không nhận diện được action type: $clickActionType");
return false;
......
......@@ -14,6 +14,8 @@ extension NullableString on String? {
final s = this?.trim();
return (s == null || s.isEmpty) ? fallback : s;
}
bool get hasText => (this?.trim().isNotEmpty ?? false);
}
extension StringUrlExtension on String {
......
......@@ -9,6 +9,7 @@ import '../resources/base_color.dart';
import '../shared/router_gage.dart';
import '../widgets/alert/custom_alert_dialog.dart';
import '../widgets/alert/data_alert_model.dart';
import '../widgets/alert/popup_data_model.dart';
class AppNavigator {
static final GlobalKey<NavigatorState> key = GlobalKey<NavigatorState>();
......@@ -92,7 +93,7 @@ class AppNavigator {
);
}
static showAlertError({
static void showAlertError({
required String content,
bool? barrierDismissible,
String headerImage = "assets/images/ic_pipi_03.png",
......@@ -127,4 +128,16 @@ class AppNavigator {
barrierDismissible: barrierDismissible ?? false,
);
}
static void showPopup({
required PopupDataModel data,
bool? barrierDismissibl,
bool showCloseButton = false,
ButtonsDirection direction = ButtonsDirection.column,
}) {
Get.dialog(
CustomAlertDialog(alertData: data.dataAlertModel, showCloseButton: showCloseButton, direction: direction),
barrierDismissible: barrierDismissibl ?? true,
);
}
}
......@@ -14,8 +14,10 @@ import '../directional/directional_screen.dart';
import '../model/auth/biometric_register_response_model.dart';
import '../model/auth/login_token_response_model.dart';
import '../model/auth/profile_response_model.dart';
import '../screen/health_book/health_book_model.dart';
import '../screen/history_point/models/history_point_models.dart';
import '../screen/history_point/models/transaction_summary_by_date_model.dart';
import '../screen/register_campaign/model/verify_register_model.dart';
import '../screen/splash/models/update_response_model.dart';
import '../preference/point/header_home_model.dart';
import '../screen/affiliate/model/affiliate_brand_model.dart';
......@@ -791,6 +793,19 @@ extension RestfulAPIClientAllRequest on RestfulAPIClient {
});
}
Future<BaseResponseModel<HealthBookResponseModel>> getHealthBookCards(Json body) async {
return requestNormal(APIPaths.getHealthBookCards, Method.GET, body, (data) {
return HealthBookResponseModel.fromJson(data as Json);
});
}
Future<BaseResponseModel<HealthBookCardItemModel>> getDetailHealthBookCard(String id) async {
final path = APIPaths.detailHealthBookCardDetail.replaceAll("%@", id);
return requestNormal(path, Method.GET, {}, (data) {
return HealthBookCardItemModel.fromJson(data as Json);
});
}
Future<BaseResponseModel<TrafficServiceResponseModel>> getProductVnTraSold(Json body) async {
return requestNormal(APIPaths.getProductVnTraSold, Method.GET, body, (data) {
return TrafficServiceResponseModel.fromJson(data as Json);
......@@ -1018,4 +1033,25 @@ extension RestfulAPIClientAllRequest on RestfulAPIClient {
return EmptyCodable.fromJson(data as Json);
});
}
Future<BaseResponseModel<DirectionalScreen>> getDirectionScreen(String path) async {
var path_ = path.startsWith('/') ? path : '/$path';
return requestNormal(path_, Method.GET, {}, (data) {
return DirectionalScreen.fromJson(data as Json);
});
}
Future<BaseResponseModel<EmptyCodable>> submitShareContent(String path) async {
var path_ = path.startsWith('/') ? path : '/$path';
return requestNormal(path_, Method.GET, {}, (data) {
return EmptyCodable.fromJson(data as Json);
});
}
Future<BaseResponseModel<VerifyRegisterCampaignModel>> verifyRegisterForm(String path) async {
var path_ = path.startsWith('/') ? path : '/$path';
return requestNormal(path_, Method.POST, {}, (data) {
return VerifyRegisterCampaignModel.fromJson(data as Json);
});
}
}
\ No newline at end of file
......@@ -40,9 +40,9 @@ class Campaign7DayViewModel extends RestfulApiViewModel {
});
}
void getCampaign7DayInfo() {
void getCampaign7DayInfo({bool silent = false}) {
client.getCampaignMissions(campaignId).then((value) {
if (!value.isSuccess) {
if (!value.isSuccess && !silent) {
onShowAlertError?.call(value.errorMessage ?? Constants.commonError);
}
campaign7DayInfo.value = value.data;
......@@ -55,7 +55,7 @@ class Campaign7DayViewModel extends RestfulApiViewModel {
client.submitPerformMission(mission, campaignId).then((value) {
hideLoading();
if (value.isSuccess) {
// getCampaign7DayInfo();
getCampaign7DayInfo(silent: true);
if (mission.popup != null) {
submitPerformMissionResponse?.call(mission);
} else {
......
import 'package:json_annotation/json_annotation.dart';
import '../traffic_service/traffic_service_model.dart';
part 'health_book_model.g.dart';
@JsonSerializable(explicitToJson: true)
class HealthBookCardItemModel {
@JsonKey(name: 'item_id')
final int? itemId;
@JsonKey(name: 'card_name')
final String? cardName;
@JsonKey(name: 'full_name')
final String? fullName;
@JsonKey(name: 'card_code')
final String? cardCode;
@JsonKey(name: 'expire_date')
final String? expireDate;
@JsonKey(name: 'phone_number')
final String? phoneNumber;
@JsonKey(name: 'updated_at')
final String? updatedAt;
@JsonKey(name: 'count_checkup_unused')
final int? countCheckupUnused;
@JsonKey(name: 'bottom_button')
final ButtonConfigModel? bottomButton;
final List<ProductMediaItem>? media;
@JsonKey(name: 'buy_more_note')
final ButtonConfigModel? buyMoreNote;
final ActiveTextConfig? active;
const HealthBookCardItemModel({
this.itemId,
this.cardName,
this.fullName,
this.cardCode,
this.expireDate,
this.phoneNumber,
this.updatedAt,
this.countCheckupUnused,
this.bottomButton,
this.media,
this.buyMoreNote,
this.active,
});
factory HealthBookCardItemModel.fromJson(Map<String, dynamic> json)
=> _$HealthBookCardItemModelFromJson(json);
Map<String, dynamic> toJson() => _$HealthBookCardItemModelToJson(this);
}
class HealthBookResponseModel {
final int? total;
final List<HealthBookCardItemModel>? products;
HealthBookResponseModel({
this.total,
this.products,
});
factory HealthBookResponseModel.fromJson(Map<String, dynamic> json) {
return HealthBookResponseModel(
total: json['total'],
products: (json['products'] as List<dynamic>?)
?.map((e) => HealthBookCardItemModel.fromJson(e))
.toList(),
);
}
Map<String, dynamic> toJson() => {
'total': total,
'products': products?.map((e) => e.toJson()).toList(),
};
}
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'health_book_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
HealthBookCardItemModel _$HealthBookCardItemModelFromJson(
Map<String, dynamic> json,
) => HealthBookCardItemModel(
itemId: (json['item_id'] as num?)?.toInt(),
cardName: json['card_name'] as String?,
fullName: json['full_name'] as String?,
cardCode: json['card_code'] as String?,
expireDate: json['expire_date'] as String?,
phoneNumber: json['phone_number'] as String?,
updatedAt: json['updated_at'] as String?,
countCheckupUnused: (json['count_checkup_unused'] as num?)?.toInt(),
bottomButton:
json['bottom_button'] == null
? null
: ButtonConfigModel.fromJson(
json['bottom_button'] as Map<String, dynamic>,
),
media:
(json['media'] as List<dynamic>?)
?.map((e) => ProductMediaItem.fromJson(e as Map<String, dynamic>))
.toList(),
buyMoreNote:
json['buy_more_note'] == null
? null
: ButtonConfigModel.fromJson(
json['buy_more_note'] as Map<String, dynamic>,
),
active:
json['active'] == null
? null
: ActiveTextConfig.fromJson(json['active'] as Map<String, dynamic>),
);
Map<String, dynamic> _$HealthBookCardItemModelToJson(
HealthBookCardItemModel instance,
) => <String, dynamic>{
'item_id': instance.itemId,
'card_name': instance.cardName,
'full_name': instance.fullName,
'card_code': instance.cardCode,
'expire_date': instance.expireDate,
'phone_number': instance.phoneNumber,
'updated_at': instance.updatedAt,
'count_checkup_unused': instance.countCheckupUnused,
'bottom_button': instance.bottomButton?.toJson(),
'media': instance.media?.map((e) => e.toJson()).toList(),
'buy_more_note': instance.buyMoreNote?.toJson(),
'active': instance.active?.toJson(),
};
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/extensions/datetime_extensions.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.dart';
import 'package:mypoint_flutter_app/widgets/custom_empty_widget.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart';
import '../../extensions/date_format.dart';
import '../../resources/base_color.dart';
import '../../shared/router_gage.dart';
import '../../widgets/custom_navigation_bar.dart';
import 'health_book_viewmodel.dart';
class HealthBookScreen extends StatefulWidget {
const HealthBookScreen({super.key});
@override
State<HealthBookScreen> createState() => _HealthBookScreenState();
}
class _HealthBookScreenState extends State<HealthBookScreen> {
final HealthBookViewModel _viewModel = Get.put(HealthBookViewModel());
@override
void initState() {
super.initState();
_viewModel.getHealthBookCards();
}
@override
Widget build(BuildContext context) {
final tags = _viewModel.headerFilterOrder;
return Scaffold(
appBar: CustomNavigationBar(title: "Sổ sức khoẻ điện tử"),
body: Column(
children: [
const SizedBox(height: 8),
Obx(()
=> SizedBox(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Row(
children: List.generate(tags.length, (index) {
final isSelected = index == _viewModel.selectedIndex.value;
return GestureDetector(
onTap: () {
if (_viewModel.selectedIndex.value == index) return;
setState(() {
_viewModel.selectedIndex.value = index;
_viewModel.getHealthBookCards();
});
},
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
decoration: BoxDecoration(
border: Border.all(
color: isSelected ? BaseColor.primary500 : Colors.grey.shade300,
),
borderRadius: BorderRadius.circular(8),
),
child: Text(
tags[index].title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
softWrap: false,
style: TextStyle(
color: isSelected ? BaseColor.primary500 : Colors.black87,
),
),
),
);
}),
),
),
)
),
const Divider(height: 14, color: Colors.black12),
const SizedBox(height: 8),
Expanded(
child: Obx(() {
final products = _viewModel.healthBookData.value?.products ?? [];
return products.isEmpty
? Center(child: EmptyWidget())
: ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final item = products[index];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(12),
),
child: ListTile(
onTap: () {
print('TODO Tapped on item: ${item.phoneNumber}');
// Get.toNamed(trafficServiceDetailScreen, arguments: {'serviceId': item.itemId});
},
leading: SizedBox(
width: 60, // <= giới hạn rõ
height: 60,
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: loadNetworkImage(
url: item.media?.firstOrNull?.url ?? '',
fit: BoxFit.cover,
placeholderAsset: 'assets/images/bg_default_11.png',
),
),
),
title: Text(item.fullName ?? '', style: TextStyle(fontWeight: FontWeight.bold)),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 4),
Text(
item.cardName ?? '',
style: const TextStyle(
fontSize: 14,
color: Colors.black87,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
Text(
(item.expireDate ?? '').toDate()?.toFormattedString() ?? '',
style: const TextStyle(
fontSize: 14,
color: Colors.black87,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
Text(
'Cập nhật lúc ${(item.updatedAt ?? '').toDate()?.toFormattedString(format: DateFormat.ddMMyyyyhhmm) ?? ''}',
style: const TextStyle(fontSize: 11, color: Colors.black54),
),
],
),
trailing: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.green.shade50,
borderRadius: BorderRadius.circular(4),
),
child: Text(
item.active?.text ?? '',
style: const TextStyle(fontSize: 12, color: Colors.green, fontWeight: FontWeight.w500),
),
),
),
),
);
},
);
}),
),
],
),
);
}
}
import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:mypoint_flutter_app/configs/constants.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart';
import 'package:mypoint_flutter_app/screen/traffic_service/traffic_service_model.dart';
import '../../networking/restful_api_viewmodel.dart';
import 'health_book_model.dart';
class HealthBookViewModel extends RestfulApiViewModel {
var healthBookData = Rxn<HealthBookResponseModel>();
var healthBookDataDetail = Rxn<HealthBookCardItemModel>();
void Function(String message)? onShowAlertError;
RxInt selectedIndex = 0.obs;
List<HeaderFilterOrderModel> get headerFilterOrder {
return [
HeaderFilterOrderModel(
title: 'Tất cả',
suffixChecking: 'tatca',
selected: true,
),
HeaderFilterOrderModel(
title: 'Hiệu lực',
suffixChecking: 'hieuluc',
),
HeaderFilterOrderModel(
title: 'Không hiệu lực',
expired: "true",
suffixChecking: 'khonghieuluc',
),
HeaderFilterOrderModel(
title: 'Lượt khám',
expired: "true",
sort: SortFilter.asc,
suffixChecking: 'luotkham',
),
];
}
Future<void> getHealthBookCards() async {
var body = headerFilterOrder[selectedIndex.value].params;
body['page'] = 1;
body['size'] = 10000;
showLoading();
try {
final response = await client.getHealthBookCards(body);
hideLoading();
if (response.isSuccess) {
healthBookData.value = response.data;
} else {
onShowAlertError?.call(response.errorMessage ?? Constants.commonError);
}
} catch (error) {
hideLoading();
onShowAlertError?.call("Error fetching product detail: $error");
}
}
Future<void> getDetailHealthBookCard(String id) async {
showLoading();
try {
final response = await client.getDetailHealthBookCard(id);
hideLoading();
if (response.isSuccess) {
healthBookDataDetail.value = response.data;
} else {
onShowAlertError?.call(response.errorMessage ?? Constants.commonError);
}
} catch (error) {
hideLoading();
onShowAlertError?.call("Error fetching product detail: $error");
}
}
}
\ No newline at end of file
......@@ -106,19 +106,20 @@ class HomeGreetingHeader extends StatelessWidget {
),
const SizedBox(height: 2),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
_buildStatItem(
icon: "assets/images/ic_point_gray.png",
value: (dataHeader.totalPointActive ?? 0).money(CurrencyUnit.none),// .toString(),
onTap: _onPointTap,
),
SizedBox(width: 12),
const SizedBox(width: 8),
_buildStatItem(
icon: "assets/images/ic_voucher_gray.png",
value: dataHeader.totalVoucher.toString(),
onTap: _onMyVoucherTap,
),
SizedBox(width: 12),
const SizedBox(width: 8),
_buildStatItem(icon: "assets/images/ic_rank_gray.png", value: level, onTap: _onRankTap),
],
),
......
......@@ -32,7 +32,7 @@ class _InviteFriendCampaignScreenState extends BaseState<InviteFriendCampaignScr
fetchContacts();
viewModel.onShowAlertError = (message, onBack) {
if (message.isNotEmpty) {
showAlertError(content: message, onConfirmed: onBack ? () => Get.back() : null);
showAlertError(content: message, onConfirmed: onBack ? () => Get.back() : null, showCloseButton: onBack == null);
}
};
viewModel.phoneInviteFriendResponse = (sms, phone) {
......@@ -41,7 +41,7 @@ class _InviteFriendCampaignScreenState extends BaseState<InviteFriendCampaignScr
viewModel.loadData();
}
_openSMS(String sms, String phone) async {
Future<void> _openSMS(String sms, String phone) async {
final uri = Uri(scheme: 'sms', path: phone, queryParameters: <String, String>{'body': sms});
if (await canLaunchUrl(uri)) {
......
......@@ -52,7 +52,7 @@ class _NotificationScreenState extends BaseState<NotificationScreen> with BasicS
link: _layerLink,
child: IconButton(
key: _infoKey,
icon: const Icon(Icons.settings, color: Colors.black54),
icon: const Icon(Icons.settings),
onPressed: _toggleSetting,
),
),
......
......@@ -16,7 +16,7 @@ class OrderMenuScreen extends StatelessWidget {
final List<_OrderMenuItem> items = [
_OrderMenuItem(title: 'Thẻ nạp của tôi', icon: Icons.credit_card, type: 'VIEW_MY_MOBILE_CARD'),
_OrderMenuItem(title: 'Sổ sức khỏe điện tử', icon: Icons.medical_services_outlined, type: ''),
_OrderMenuItem(title: 'Sổ sức khỏe điện tử', icon: Icons.medical_services_outlined, type: 'FAMILY_HEALTH_BOOK'),
_OrderMenuItem(title: 'Dịch vụ giao thông', icon: Icons.traffic_outlined, type: 'APP_SCREEN_MY_VNTRA_PACKAGE'),
];
......
......@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/widgets/custom_empty_widget.dart';
import 'package:share_plus/share_plus.dart';
import '../../base/base_screen.dart';
import '../../base/basic_state.dart';
import '../../extensions/string_extension.dart';
......@@ -61,6 +62,7 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba
final List<CampaignDetailItemModel> items = pageDetail.items ?? [];
final buttonOn = pageDetail.buttonOn ?? "0";
final heightContainerBottomButton = MediaQuery.of(context).padding.bottom + 16 + 48;
final showShareButton = (pageDetail.shareContent ?? '').isNotEmpty;
return Stack(
children: [
SingleChildScrollView(
......@@ -105,6 +107,29 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba
],
),
),
if (showShareButton)
Positioned(
top: MediaQuery.of(context).padding.top + 8,
right: 12,
child: SizedBox(
width: 32,
height: 32,
child: Material(
color: Colors.white,
shape: const CircleBorder(),
child: IconButton(
onPressed: () {
final content = pageDetail.shareContent ?? "";
SharePlus.instance.share(
ShareParams(text: content, title: "Chia sẻ từ MyPoint"),
);
_viewModel.submitShareContent();
},
icon: const Icon(Icons.share, size: 16),
),
),
),
),
Positioned(top: MediaQuery.of(context).padding.top + 8, left: 8, child: CustomBackButton()),
if (buttonOn == "1") _bottomButton(pageDetail),
],
......@@ -131,7 +156,6 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba
SizedBox(height: 12),
ElevatedButton(
onPressed: () {
print("pageDetail?.directionalScreen");
print(pageDetail?.directionalScreen);
print(pageDetail?.buttonClickActionType);
print(pageDetail?.buttonClickActionParam);
......@@ -178,20 +202,18 @@ 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: 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!),
],
)
)
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) {
......@@ -201,11 +223,7 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba
CampaignItemPageWidget(
itemPageCampaign: item,
onTap: () async {
Get.offNamed(
campaignDetailScreen,
arguments: {"id": item.pageId},
preventDuplicates: false,
);
Get.offNamed(campaignDetailScreen, arguments: {"id": item.pageId}, preventDuplicates: false);
},
),
);
......
......@@ -64,4 +64,10 @@ class CampaignDetailViewModel extends RestfulApiViewModel {
isLoading(false);
});
}
Future<void> submitShareContent() async {
final path = campaignDetail.value.data?.pageDetail?.clickButtonShare ?? "";
if (path.isEmpty) return;
await client.submitShareContent(path);
}
}
......@@ -22,6 +22,10 @@ class CampaignDetailModel {
final String? buttonClickActionType;
@JsonKey(name: "button_click_action_param")
final String? buttonClickActionParam;
@JsonKey(name: "share_content")
final String? shareContent;
@JsonKey(name: "click_button_share")
final String? clickButtonShare;
final List<CampaignDetailItemModel>? items;
CampaignDetailModel({
......@@ -34,6 +38,8 @@ class CampaignDetailModel {
this.buttonColor,
this.buttonName,
this.buttonTextColor,
this.shareContent,
this.clickButtonShare,
this.items,
});
......
......@@ -17,6 +17,8 @@ CampaignDetailModel _$CampaignDetailModelFromJson(Map<String, dynamic> json) =>
buttonColor: json['button_color'] as String?,
buttonName: json['button_name'] as String?,
buttonTextColor: json['button_text_color'] as String?,
shareContent: json['share_content'] as String?,
clickButtonShare: json['click_button_share'] as String?,
items:
(json['items'] as List<dynamic>?)
?.map(
......@@ -38,6 +40,8 @@ Map<String, dynamic> _$CampaignDetailModelToJson(
'button_text_color': instance.buttonTextColor,
'button_click_action_type': instance.buttonClickActionType,
'button_click_action_param': instance.buttonClickActionParam,
'share_content': instance.shareContent,
'click_button_share': instance.clickButtonShare,
'items': instance.items,
};
......
......@@ -223,7 +223,7 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState, Po
'type': 'APP_SCREEN_ORDER_MENU',
},
{'icon': Icons.info_outline, 'title': 'Giới thiệu MyPoint', 'sectionDivider': true, 'type': 'VIEW_WEBSITE_PAGE'},
{'icon': Icons.headset_mic_outlined, 'title': 'Hỗ trợ', 'type': 'APP_SCREEN_CUSTOMER_FEEDBACK'},
{'icon': Icons.headset_rounded, 'title': 'Hỗ trợ', 'type': 'APP_SCREEN_CUSTOMER_FEEDBACK'},
{'icon': Icons.settings_outlined, 'title': 'Cài đặt', 'type': 'APP_SCREEN_SETTING'},
{'icon': Icons.logout, 'title': 'Đăng xuất', 'color': Colors.red[400], 'type': 'LOGOUT'},
];
......
......@@ -71,7 +71,7 @@ class _InputFormCellState extends State<InputFormCell> {
border: InputBorder.none,
counterText: '',
),
style: const TextStyle(fontSize: 14),
style: const TextStyle(fontSize: 14, color: Colors.black),
onChanged: (value) {
widget.model.content = value; // ✅ update model
widget.onChanged?.call(); // ✅ callback để validate bên ngoài
......
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