Commit fc2caf86 authored by DatHV's avatar DatHV
Browse files

update ui, logic.

parent 0b973e61
...@@ -88,6 +88,8 @@ class APIPaths {//sandbox ...@@ -88,6 +88,8 @@ class APIPaths {//sandbox
static const String customerContractDelete = "/customerContractDelete/1.0.0"; static const String customerContractDelete = "/customerContractDelete/1.0.0";
static const String getProductVnTraSold = "/product/api/v2.0/vntra_sold"; static const String getProductVnTraSold = "/product/api/v2.0/vntra_sold";
static const String detailMyPackageVnTra = "/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 getCampaignLiveTransactions = "/campaign/api/v3.0/%@/live-transactions";
static const String getCampaignMissions = "/campaign/api/v3.0/%@/missions"; static const String getCampaignMissions = "/campaign/api/v3.0/%@/missions";
static const String getCampaignReward = "/campaign/api/v3.0/%@/reward"; static const String getCampaignReward = "/campaign/api/v3.0/%@/reward";
......
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.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/preference/data_preference.dart';
import 'package:mypoint_flutter_app/widgets/alert/popup_data_model.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:uuid/uuid.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 '../screen/webview/web_view_screen.dart';
import '../shared/router_gage.dart'; import '../shared/router_gage.dart';
import 'directional_action_type.dart'; import 'directional_action_type.dart';
...@@ -16,12 +21,14 @@ class Defines { ...@@ -16,12 +21,14 @@ class Defines {
class DirectionalScreen { class DirectionalScreen {
final String? clickActionType; final String? clickActionType;
final String? clickActionParam; 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._( factory DirectionalScreen.fromJson(Map<String, dynamic> json) => DirectionalScreen._(
clickActionType: json['click_action_type'] as String?, clickActionType: json['click_action_type'] as String?,
clickActionParam: json['click_action_param'] 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() => { Map<String, dynamic> toJson() => {
...@@ -156,6 +163,9 @@ class DirectionalScreen { ...@@ -156,6 +163,9 @@ class DirectionalScreen {
case DirectionalScreenName.myVnTraPackage: case DirectionalScreenName.myVnTraPackage:
Get.toNamed(trafficServiceScreen); Get.toNamed(trafficServiceScreen);
return true; return true;
case DirectionalScreenName.familyHealthBook:
Get.toNamed(healthBookScreen);
return true;
case DirectionalScreenName.campaignSevenDayScreen: case DirectionalScreenName.campaignSevenDayScreen:
Get.toNamed(campaignSevenDayScreen); Get.toNamed(campaignSevenDayScreen);
return true; return true;
...@@ -178,6 +188,25 @@ class DirectionalScreen { ...@@ -178,6 +188,25 @@ class DirectionalScreen {
case DirectionalScreenName.qrCode: case DirectionalScreenName.qrCode:
Get.toNamed(qrCodeScreen); Get.toNamed(qrCodeScreen);
return true; 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: default:
print("Không nhận diện được action type: $clickActionType"); print("Không nhận diện được action type: $clickActionType");
return false; return false;
......
...@@ -14,6 +14,8 @@ extension NullableString on String? { ...@@ -14,6 +14,8 @@ extension NullableString on String? {
final s = this?.trim(); final s = this?.trim();
return (s == null || s.isEmpty) ? fallback : s; return (s == null || s.isEmpty) ? fallback : s;
} }
bool get hasText => (this?.trim().isNotEmpty ?? false);
} }
extension StringUrlExtension on String { extension StringUrlExtension on String {
......
...@@ -9,6 +9,7 @@ import '../resources/base_color.dart'; ...@@ -9,6 +9,7 @@ import '../resources/base_color.dart';
import '../shared/router_gage.dart'; import '../shared/router_gage.dart';
import '../widgets/alert/custom_alert_dialog.dart'; import '../widgets/alert/custom_alert_dialog.dart';
import '../widgets/alert/data_alert_model.dart'; import '../widgets/alert/data_alert_model.dart';
import '../widgets/alert/popup_data_model.dart';
class AppNavigator { class AppNavigator {
static final GlobalKey<NavigatorState> key = GlobalKey<NavigatorState>(); static final GlobalKey<NavigatorState> key = GlobalKey<NavigatorState>();
...@@ -92,7 +93,7 @@ class AppNavigator { ...@@ -92,7 +93,7 @@ class AppNavigator {
); );
} }
static showAlertError({ static void showAlertError({
required String content, required String content,
bool? barrierDismissible, bool? barrierDismissible,
String headerImage = "assets/images/ic_pipi_03.png", String headerImage = "assets/images/ic_pipi_03.png",
...@@ -127,4 +128,16 @@ class AppNavigator { ...@@ -127,4 +128,16 @@ class AppNavigator {
barrierDismissible: barrierDismissible ?? false, 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'; ...@@ -14,8 +14,10 @@ import '../directional/directional_screen.dart';
import '../model/auth/biometric_register_response_model.dart'; import '../model/auth/biometric_register_response_model.dart';
import '../model/auth/login_token_response_model.dart'; import '../model/auth/login_token_response_model.dart';
import '../model/auth/profile_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/history_point_models.dart';
import '../screen/history_point/models/transaction_summary_by_date_model.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 '../screen/splash/models/update_response_model.dart';
import '../preference/point/header_home_model.dart'; import '../preference/point/header_home_model.dart';
import '../screen/affiliate/model/affiliate_brand_model.dart'; import '../screen/affiliate/model/affiliate_brand_model.dart';
...@@ -791,6 +793,19 @@ extension RestfulAPIClientAllRequest on RestfulAPIClient { ...@@ -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 { Future<BaseResponseModel<TrafficServiceResponseModel>> getProductVnTraSold(Json body) async {
return requestNormal(APIPaths.getProductVnTraSold, Method.GET, body, (data) { return requestNormal(APIPaths.getProductVnTraSold, Method.GET, body, (data) {
return TrafficServiceResponseModel.fromJson(data as Json); return TrafficServiceResponseModel.fromJson(data as Json);
...@@ -1018,4 +1033,25 @@ extension RestfulAPIClientAllRequest on RestfulAPIClient { ...@@ -1018,4 +1033,25 @@ extension RestfulAPIClientAllRequest on RestfulAPIClient {
return EmptyCodable.fromJson(data as Json); 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 { ...@@ -40,9 +40,9 @@ class Campaign7DayViewModel extends RestfulApiViewModel {
}); });
} }
void getCampaign7DayInfo() { void getCampaign7DayInfo({bool silent = false}) {
client.getCampaignMissions(campaignId).then((value) { client.getCampaignMissions(campaignId).then((value) {
if (!value.isSuccess) { if (!value.isSuccess && !silent) {
onShowAlertError?.call(value.errorMessage ?? Constants.commonError); onShowAlertError?.call(value.errorMessage ?? Constants.commonError);
} }
campaign7DayInfo.value = value.data; campaign7DayInfo.value = value.data;
...@@ -55,7 +55,7 @@ class Campaign7DayViewModel extends RestfulApiViewModel { ...@@ -55,7 +55,7 @@ class Campaign7DayViewModel extends RestfulApiViewModel {
client.submitPerformMission(mission, campaignId).then((value) { client.submitPerformMission(mission, campaignId).then((value) {
hideLoading(); hideLoading();
if (value.isSuccess) { if (value.isSuccess) {
// getCampaign7DayInfo(); getCampaign7DayInfo(silent: true);
if (mission.popup != null) { if (mission.popup != null) {
submitPerformMissionResponse?.call(mission); submitPerformMissionResponse?.call(mission);
} else { } 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 { ...@@ -106,19 +106,20 @@ class HomeGreetingHeader extends StatelessWidget {
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Row( Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
_buildStatItem( _buildStatItem(
icon: "assets/images/ic_point_gray.png", icon: "assets/images/ic_point_gray.png",
value: (dataHeader.totalPointActive ?? 0).money(CurrencyUnit.none),// .toString(), value: (dataHeader.totalPointActive ?? 0).money(CurrencyUnit.none),// .toString(),
onTap: _onPointTap, onTap: _onPointTap,
), ),
SizedBox(width: 12), const SizedBox(width: 8),
_buildStatItem( _buildStatItem(
icon: "assets/images/ic_voucher_gray.png", icon: "assets/images/ic_voucher_gray.png",
value: dataHeader.totalVoucher.toString(), value: dataHeader.totalVoucher.toString(),
onTap: _onMyVoucherTap, onTap: _onMyVoucherTap,
), ),
SizedBox(width: 12), const SizedBox(width: 8),
_buildStatItem(icon: "assets/images/ic_rank_gray.png", value: level, onTap: _onRankTap), _buildStatItem(icon: "assets/images/ic_rank_gray.png", value: level, onTap: _onRankTap),
], ],
), ),
......
...@@ -32,7 +32,7 @@ class _InviteFriendCampaignScreenState extends BaseState<InviteFriendCampaignScr ...@@ -32,7 +32,7 @@ class _InviteFriendCampaignScreenState extends BaseState<InviteFriendCampaignScr
fetchContacts(); fetchContacts();
viewModel.onShowAlertError = (message, onBack) { viewModel.onShowAlertError = (message, onBack) {
if (message.isNotEmpty) { 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) { viewModel.phoneInviteFriendResponse = (sms, phone) {
...@@ -41,7 +41,7 @@ class _InviteFriendCampaignScreenState extends BaseState<InviteFriendCampaignScr ...@@ -41,7 +41,7 @@ class _InviteFriendCampaignScreenState extends BaseState<InviteFriendCampaignScr
viewModel.loadData(); 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}); final uri = Uri(scheme: 'sms', path: phone, queryParameters: <String, String>{'body': sms});
if (await canLaunchUrl(uri)) { if (await canLaunchUrl(uri)) {
......
...@@ -52,7 +52,7 @@ class _NotificationScreenState extends BaseState<NotificationScreen> with BasicS ...@@ -52,7 +52,7 @@ class _NotificationScreenState extends BaseState<NotificationScreen> with BasicS
link: _layerLink, link: _layerLink,
child: IconButton( child: IconButton(
key: _infoKey, key: _infoKey,
icon: const Icon(Icons.settings, color: Colors.black54), icon: const Icon(Icons.settings),
onPressed: _toggleSetting, onPressed: _toggleSetting,
), ),
), ),
......
...@@ -16,7 +16,7 @@ class OrderMenuScreen extends StatelessWidget { ...@@ -16,7 +16,7 @@ class OrderMenuScreen extends StatelessWidget {
final List<_OrderMenuItem> items = [ final List<_OrderMenuItem> items = [
_OrderMenuItem(title: 'Thẻ nạp của tôi', icon: Icons.credit_card, type: 'VIEW_MY_MOBILE_CARD'), _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'), _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'; ...@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/widgets/custom_empty_widget.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/base_screen.dart';
import '../../base/basic_state.dart'; import '../../base/basic_state.dart';
import '../../extensions/string_extension.dart'; import '../../extensions/string_extension.dart';
...@@ -61,6 +62,7 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba ...@@ -61,6 +62,7 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba
final List<CampaignDetailItemModel> items = pageDetail.items ?? []; final List<CampaignDetailItemModel> items = pageDetail.items ?? [];
final buttonOn = pageDetail.buttonOn ?? "0"; final buttonOn = pageDetail.buttonOn ?? "0";
final heightContainerBottomButton = MediaQuery.of(context).padding.bottom + 16 + 48; final heightContainerBottomButton = MediaQuery.of(context).padding.bottom + 16 + 48;
final showShareButton = (pageDetail.shareContent ?? '').isNotEmpty;
return Stack( return Stack(
children: [ children: [
SingleChildScrollView( SingleChildScrollView(
...@@ -105,6 +107,29 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba ...@@ -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()), Positioned(top: MediaQuery.of(context).padding.top + 8, left: 8, child: CustomBackButton()),
if (buttonOn == "1") _bottomButton(pageDetail), if (buttonOn == "1") _bottomButton(pageDetail),
], ],
...@@ -131,7 +156,6 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba ...@@ -131,7 +156,6 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba
SizedBox(height: 12), SizedBox(height: 12),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
print("pageDetail?.directionalScreen");
print(pageDetail?.directionalScreen); print(pageDetail?.directionalScreen);
print(pageDetail?.buttonClickActionType); print(pageDetail?.buttonClickActionType);
print(pageDetail?.buttonClickActionParam); print(pageDetail?.buttonClickActionParam);
...@@ -178,20 +202,18 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba ...@@ -178,20 +202,18 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba
} else if (mediaType == MediaTypeItemCampaign.text.key) { } else if (mediaType == MediaTypeItemCampaign.text.key) {
if (item.contentText != null && item.contentText!.isNotEmpty) { if (item.contentText != null && item.contentText!.isNotEmpty) {
widgets.add( widgets.add(
Padding(padding: const EdgeInsets.only(bottom: 16), Padding(
child: Column( padding: const EdgeInsets.only(bottom: 16),
crossAxisAlignment: CrossAxisAlignment.start, child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
if ((item.contentCaption ?? "").isNotEmpty) children: [
Text( if ((item.contentCaption ?? "").isNotEmpty)
item.contentCaption!, Text(item.contentCaption!, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), const SizedBox(height: 4),
), HtmlWidget(item.contentText!),
const SizedBox(height: 4), ],
HtmlWidget(item.contentText!), ),
], ),
)
)
); );
} }
} else if (mediaType == MediaTypeItemCampaign.pageLink.key) { } else if (mediaType == MediaTypeItemCampaign.pageLink.key) {
...@@ -201,11 +223,7 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba ...@@ -201,11 +223,7 @@ class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with Ba
CampaignItemPageWidget( CampaignItemPageWidget(
itemPageCampaign: item, itemPageCampaign: item,
onTap: () async { onTap: () async {
Get.offNamed( Get.offNamed(campaignDetailScreen, arguments: {"id": item.pageId}, preventDuplicates: false);
campaignDetailScreen,
arguments: {"id": item.pageId},
preventDuplicates: false,
);
}, },
), ),
); );
......
...@@ -64,4 +64,10 @@ class CampaignDetailViewModel extends RestfulApiViewModel { ...@@ -64,4 +64,10 @@ class CampaignDetailViewModel extends RestfulApiViewModel {
isLoading(false); 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 { ...@@ -22,6 +22,10 @@ class CampaignDetailModel {
final String? buttonClickActionType; final String? buttonClickActionType;
@JsonKey(name: "button_click_action_param") @JsonKey(name: "button_click_action_param")
final String? buttonClickActionParam; final String? buttonClickActionParam;
@JsonKey(name: "share_content")
final String? shareContent;
@JsonKey(name: "click_button_share")
final String? clickButtonShare;
final List<CampaignDetailItemModel>? items; final List<CampaignDetailItemModel>? items;
CampaignDetailModel({ CampaignDetailModel({
...@@ -34,6 +38,8 @@ class CampaignDetailModel { ...@@ -34,6 +38,8 @@ class CampaignDetailModel {
this.buttonColor, this.buttonColor,
this.buttonName, this.buttonName,
this.buttonTextColor, this.buttonTextColor,
this.shareContent,
this.clickButtonShare,
this.items, this.items,
}); });
......
...@@ -17,6 +17,8 @@ CampaignDetailModel _$CampaignDetailModelFromJson(Map<String, dynamic> json) => ...@@ -17,6 +17,8 @@ CampaignDetailModel _$CampaignDetailModelFromJson(Map<String, dynamic> json) =>
buttonColor: json['button_color'] as String?, buttonColor: json['button_color'] as String?,
buttonName: json['button_name'] as String?, buttonName: json['button_name'] as String?,
buttonTextColor: json['button_text_color'] as String?, buttonTextColor: json['button_text_color'] as String?,
shareContent: json['share_content'] as String?,
clickButtonShare: json['click_button_share'] as String?,
items: items:
(json['items'] as List<dynamic>?) (json['items'] as List<dynamic>?)
?.map( ?.map(
...@@ -38,6 +40,8 @@ Map<String, dynamic> _$CampaignDetailModelToJson( ...@@ -38,6 +40,8 @@ Map<String, dynamic> _$CampaignDetailModelToJson(
'button_text_color': instance.buttonTextColor, 'button_text_color': instance.buttonTextColor,
'button_click_action_type': instance.buttonClickActionType, 'button_click_action_type': instance.buttonClickActionType,
'button_click_action_param': instance.buttonClickActionParam, 'button_click_action_param': instance.buttonClickActionParam,
'share_content': instance.shareContent,
'click_button_share': instance.clickButtonShare,
'items': instance.items, 'items': instance.items,
}; };
......
...@@ -223,7 +223,7 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState, Po ...@@ -223,7 +223,7 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState, Po
'type': 'APP_SCREEN_ORDER_MENU', 'type': 'APP_SCREEN_ORDER_MENU',
}, },
{'icon': Icons.info_outline, 'title': 'Giới thiệu MyPoint', 'sectionDivider': true, 'type': 'VIEW_WEBSITE_PAGE'}, {'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.settings_outlined, 'title': 'Cài đặt', 'type': 'APP_SCREEN_SETTING'},
{'icon': Icons.logout, 'title': 'Đăng xuất', 'color': Colors.red[400], 'type': 'LOGOUT'}, {'icon': Icons.logout, 'title': 'Đăng xuất', 'color': Colors.red[400], 'type': 'LOGOUT'},
]; ];
......
...@@ -71,7 +71,7 @@ class _InputFormCellState extends State<InputFormCell> { ...@@ -71,7 +71,7 @@ class _InputFormCellState extends State<InputFormCell> {
border: InputBorder.none, border: InputBorder.none,
counterText: '', counterText: '',
), ),
style: const TextStyle(fontSize: 14), style: const TextStyle(fontSize: 14, color: Colors.black),
onChanged: (value) { onChanged: (value) {
widget.model.content = value; // ✅ update model widget.model.content = value; // ✅ update model
widget.onChanged?.call(); // ✅ callback để validate bên ngoài 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