Commit efb4662c authored by DatHV's avatar DatHV
Browse files

update campaign 7day

parent 4c376d38
import 'package:json_annotation/json_annotation.dart';
import 'campaign_7day_config_model.dart';
import 'campaign_7day_mission_model.dart';
import 'package:intl/intl.dart' as intl;
part 'campaign_7day_info_model.g.dart';
@JsonSerializable()
class Campaign7DayInfoModel {
final int id;
final String? description;
final String? icon;
@JsonKey(name: 'start_time')
final String? startTime;
@JsonKey(name: 'end_time')
final String? endTime;
final List<Campaign7DayMissionModel>? missions;
final String? name;
final Campaign7dayConfigModel? config;
Campaign7DayInfoModel({
required this.id,
this.description,
this.icon,
this.startTime,
this.endTime,
this.missions,
this.name,
this.config,
});
double? get countDownTime {
print("End time: $endTime");
if (endTime == null) return null;
try {
final date = intl.DateFormat("yyyy-MM-dd HH:mm:ss").parseUtc(endTime!);
print("Parsed date: $date");
final now = DateTime.now().toUtc();
final diff = date.difference(now).inSeconds;
print("Difference in seconds: $diff");
return (diff > 0 ? diff : 0).toDouble();
} catch (_) {
return null;
}
}
factory Campaign7DayInfoModel.fromJson(Map<String, dynamic> json) =>
_$Campaign7DayInfoModelFromJson(json);
Map<String, dynamic> toJson() => _$Campaign7DayInfoModelToJson(this);
}
\ No newline at end of file
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'campaign_7day_info_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Campaign7DayInfoModel _$Campaign7DayInfoModelFromJson(
Map<String, dynamic> json,
) => Campaign7DayInfoModel(
id: (json['id'] as num).toInt(),
description: json['description'] as String?,
icon: json['icon'] as String?,
startTime: json['start_time'] as String?,
endTime: json['end_time'] as String?,
missions:
(json['missions'] as List<dynamic>?)
?.map(
(e) => Campaign7DayMissionModel.fromJson(e as Map<String, dynamic>),
)
.toList(),
name: json['name'] as String?,
config:
json['config'] == null
? null
: Campaign7dayConfigModel.fromJson(
json['config'] as Map<String, dynamic>,
),
);
Map<String, dynamic> _$Campaign7DayInfoModelToJson(
Campaign7DayInfoModel instance,
) => <String, dynamic>{
'id': instance.id,
'description': instance.description,
'icon': instance.icon,
'start_time': instance.startTime,
'end_time': instance.endTime,
'missions': instance.missions,
'name': instance.name,
'config': instance.config,
};
import 'package:json_annotation/json_annotation.dart';
import '../../../directional/directional_screen.dart';
import '../../../widgets/alert/popup_data_model.dart';
import 'campaign_7day_config_model.dart';
import 'campaign_7day_reward_model.dart';
part 'campaign_7day_mission_model.g.dart';
@JsonSerializable()
class Campaign7DayMissionModel {
final int id;
final String? title;
final String? description;
final String? info;
final String? avatar;
@JsonKey(name: 'title_button_action')
final String? titleButtonAction;
@JsonKey(name: 'type')
final String? type;
@JsonKey(name: 'status')
final String? status;
final Campaign7DayRewardModel? reward;
@JsonKey(name: 'click_action_param')
final String? clickActionParam;
@JsonKey(name: 'click_action_type')
final String? clickActionType;
final PopupDataModel? popup;
Campaign7DayMissionModel({
required this.id,
this.title,
this.description,
this.info,
this.avatar,
this.titleButtonAction,
this.type,
this.status,
this.reward,
this.clickActionParam,
this.clickActionType,
this.popup,
});
bool get isSpecial => type == "special";
MissionStatus get mStatus =>
MissionStatus.values.firstWhere((e) => e.name.toUpperCase() == status?.toUpperCase(), orElse: () => MissionStatus.pending);
bool get isReady => mStatus == MissionStatus.ready;
DirectionalScreen? get directionScreen =>
(clickActionType != null && clickActionParam != null)
? DirectionalScreen.build(clickActionType: clickActionType!, clickActionParam: clickActionParam!)
: null;
factory Campaign7DayMissionModel.fromJson(Map<String, dynamic> json) =>
_$Campaign7DayMissionModelFromJson(json);
Map<String, dynamic> toJson() => _$Campaign7DayMissionModelToJson(this);
}
\ No newline at end of file
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'campaign_7day_mission_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Campaign7DayMissionModel _$Campaign7DayMissionModelFromJson(
Map<String, dynamic> json,
) => Campaign7DayMissionModel(
id: (json['id'] as num).toInt(),
title: json['title'] as String?,
description: json['description'] as String?,
info: json['info'] as String?,
avatar: json['avatar'] as String?,
titleButtonAction: json['title_button_action'] as String?,
type: json['type'] as String?,
status: json['status'] as String?,
reward:
json['reward'] == null
? null
: Campaign7DayRewardModel.fromJson(
json['reward'] as Map<String, dynamic>,
),
clickActionParam: json['click_action_param'] as String?,
clickActionType: json['click_action_type'] as String?,
popup:
json['popup'] == null
? null
: PopupDataModel.fromJson(json['popup'] as Map<String, dynamic>),
);
Map<String, dynamic> _$Campaign7DayMissionModelToJson(
Campaign7DayMissionModel instance,
) => <String, dynamic>{
'id': instance.id,
'title': instance.title,
'description': instance.description,
'info': instance.info,
'avatar': instance.avatar,
'title_button_action': instance.titleButtonAction,
'type': instance.type,
'status': instance.status,
'reward': instance.reward,
'click_action_param': instance.clickActionParam,
'click_action_type': instance.clickActionType,
'popup': instance.popup,
};
import 'package:json_annotation/json_annotation.dart';
import '../../../directional/directional_screen.dart';
import 'campaign_7day_config_model.dart';
part 'campaign_7day_reward_model.g.dart';
@JsonSerializable()
class Campaign7DayRewardModel {
@JsonKey(name: 'type')
final String? type;
final String? value;
@JsonKey(name: 'text_display')
final String? textDisplay;
@JsonKey(name: 'created_at')
final String? createdAt;
@JsonKey(name: 'click_action_type')
final String? clickActionType;
@JsonKey(name: 'click_action_params')
final String? clickActionParams;
@JsonKey(name: 'button_text')
final String? buttonText;
Campaign7DayRewardModel({
this.type,
this.value,
this.textDisplay,
this.createdAt,
this.clickActionType,
this.clickActionParams,
this.buttonText,
});
TypeReward get typeReward =>
TypeReward.values.firstWhere((e) => e.name == type?.toLowerCase(), orElse: () => TypeReward.point);
String get imageAsset =>
(typeReward == TypeReward.point) ? 'assets/images/ic_point.png' : 'assets/images/ic_campaign_voucher_gift.png';
DirectionalScreen? get direction =>
(clickActionType != null && clickActionParams != null)
? DirectionalScreen.build(clickActionType: clickActionType, clickActionParam: clickActionParams)
: null;
factory Campaign7DayRewardModel.fromJson(Map<String, dynamic> json) =>
_$Campaign7DayRewardModelFromJson(json);
Map<String, dynamic> toJson() => _$Campaign7DayRewardModelToJson(this);
}
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'campaign_7day_reward_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Campaign7DayRewardModel _$Campaign7DayRewardModelFromJson(
Map<String, dynamic> json,
) => Campaign7DayRewardModel(
type: json['type'] as String?,
value: json['value'] as String?,
textDisplay: json['text_display'] as String?,
createdAt: json['created_at'] as String?,
clickActionType: json['click_action_type'] as String?,
clickActionParams: json['click_action_params'] as String?,
buttonText: json['button_text'] as String?,
);
Map<String, dynamic> _$Campaign7DayRewardModelToJson(
Campaign7DayRewardModel instance,
) => <String, dynamic>{
'type': instance.type,
'value': instance.value,
'text_display': instance.textDisplay,
'created_at': instance.createdAt,
'click_action_type': instance.clickActionType,
'click_action_params': instance.clickActionParams,
'button_text': instance.buttonText,
};
import '../mobile_card/models/mobile_service_redeem_data.dart';
class CheckInDataModel {
final String? campaignCode;
final List<Counter>? counters;
......@@ -67,3 +69,33 @@ class CounterValue {
);
}
}
class SubmitCheckInData {
final String? reward;
final String? message;
final CustomerBalance? customerBalance;
SubmitCheckInData({
this.reward,
this.message,
this.customerBalance,
});
factory SubmitCheckInData.fromJson(Map<String, dynamic> json) {
return SubmitCheckInData(
reward: json['reward'] as String?,
message: json['message'] as String?,
customerBalance: json['customer_balance'] != null
? CustomerBalance.fromJson(json['customer_balance'])
: null,
);
}
Map<String, dynamic> toJson() {
return {
'reward': reward,
'message': message,
'customer_balance': customerBalance?.toJson(),
};
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/screen/daily_checkin/daily_checkin_models.dart';
import '../../base/base_screen.dart';
import '../../base/basic_state.dart';
import '../../preference/point/point_manager.dart';
import '../../resouce/base_color.dart';
import '../../widgets/custom_navigation_bar.dart';
import 'daily_checkin_viewmodel.dart';
class DailyCheckInScreen extends StatefulWidget {
class DailyCheckInScreen extends BaseScreen {
const DailyCheckInScreen({super.key});
@override
State<DailyCheckInScreen> createState() => _DailyCheckInScreenState();
}
class _DailyCheckInScreenState extends State<DailyCheckInScreen> {
class _DailyCheckInScreenState extends BaseState<DailyCheckInScreen> with BasicState {
final DailyCheckInViewModel _viewModel = Get.put(DailyCheckInViewModel());
@override
Widget build(BuildContext context) {
void initState() {
super.initState();
_viewModel.onShowAlertError = (message) {
if (message.isNotEmpty) {
showAlertError(content: message);
}
};
_viewModel.submitDataResponse = (SubmitCheckInData? data) {
if (data == null) return;
showAlertError(content: data.message ?? "Check-in thành công", headerImage: 'assets/images/ic_pipi_05.png');
};
}
@override
Widget createBody() {
final double screenWidth = MediaQuery.of(context).size.width;
return Scaffold(
appBar: CustomNavigationBar(title: "Check-in nhận quà"),
body: Obx(() {
print("_viewModel_viewModel${_viewModel.submitData.value?.customerBalance?.amountActive}");
print(UserPointManager().point.toString());
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
......@@ -31,28 +49,26 @@ class _DailyCheckInScreenState extends State<DailyCheckInScreen> {
children: [
const Text('Điểm Check-in của tôi:', style: TextStyle(color: Colors.black54, fontSize: 16)),
const SizedBox(width: 4),
Image.asset(
'assets/images/ic_point.png',
width: 24,
fit: BoxFit.cover,
),
Image.asset('assets/images/ic_point.png', width: 24, fit: BoxFit.cover),
const SizedBox(width: 4),
Text(
'${UserPointManager().point}',
_viewModel.submitData.value?.customerBalance?.amountActive ?? UserPointManager().point.toString(),
style: TextStyle(color: Colors.orange, fontSize: 16, fontWeight: FontWeight.w600),
),
],
),
const SizedBox(height: 32),
const SizedBox(height: 16),
Image.asset(
'assets/images/ic_pipi_checkin.png', // ảnh background
((_viewModel.checkInData.value?.campaignCode ?? '') == "SMOKING_TO_DIE")
? 'assets/images/bg_smoking_campaign.png'
: 'assets/images/ic_pipi_checkin.png',
width: screenWidth - 64,
height: (screenWidth - 64) * 9 / 16,
fit: BoxFit.cover,
height: (screenWidth - 64),
fit: BoxFit.fitWidth,
),
const SizedBox(height: 32),
const SizedBox(height: 16),
_buildCheckInList(),
const SizedBox(height: 32),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: SizedBox(
......@@ -60,13 +76,13 @@ class _DailyCheckInScreenState extends State<DailyCheckInScreen> {
height: 48,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: BaseColor.primary500,
backgroundColor: _viewModel.todayIsChecked ? Colors.grey : BaseColor.primary500,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
onPressed: () {},
child: const Text(
'Check-in để nhận điểm hôm nay',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white),
onPressed: _viewModel.todayIsChecked ? null : _viewModel.submitCheckIn,
child: Text(
_viewModel.todayIsChecked ? 'Quay lại vào ngày mai để nhận điểm' : 'Check-in để nhận điểm hôm nay',
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white),
),
),
),
......
import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import '../../base/restful_api_viewmodel.dart';
import '../../preference/point/point_manager.dart';
import 'daily_checkin_models.dart';
class DailyCheckInViewModel extends RestfulApiViewModel {
var checkInData = Rxn<CheckInDataModel>();
var submitData = Rxn<SubmitCheckInData>();
void Function(String message)? onShowAlertError;
void Function(SubmitCheckInData? data)? submitDataResponse;
bool get todayIsChecked {
final counter = checkInData.value?.counters?.first;
final items = counter?.values ?? [];
return (items.firstOrNull?.counterValue ?? '') == '1';
}
@override
onInit() {
super.onInit();
// UserPointManager().fetchUserPoint();
_rewardOpportunityGetList();
}
Future<void> _rewardOpportunityGetList() async {
showLoading();
try {
final response = await client.rewardOpportunityGetList();
hideLoading();
checkInData.value = response.data;
} catch (error) {
hideLoading();
onShowAlertError?.call("Error fetching product detail: $error");
}
}
Future<void> submitCheckIn() async {
showLoading();
try {
final response = await client.submitCheckIn();
hideLoading();
submitData.value = response.data;
submitDataResponse?.call(response.data);
} catch (error) {
hideLoading();
onShowAlertError?.call("Error fetching product detail: $error");
}
}
......
class CustomerContractModel {
String? location;
String? maKH;
String? nameKH;
int? amount;
String? ky;
String? idHoaHon;
CustomerContractModel({
this.location,
this.maKH,
this.nameKH,
this.amount,
this.ky,
this.idHoaHon,
});
factory CustomerContractModel.fromJson(Map<String, dynamic> json) {
return CustomerContractModel(
location: json['location'],
maKH: json['ma_khang'],
nameKH: json['name_khang'],
amount: json['amount'],
ky: json['ky'],
idHoaHon: json['id_hoadon'],
);
}
Map<String, dynamic> toJson() {
return {
'location': location,
'ma_khang': maKH,
'name_khang': nameKH,
'amount': amount,
'ky': ky,
'id_hoadon': idHoaHon,
};
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/extensions/num_extension.dart';
import 'package:mypoint_flutter_app/widgets/custom_empty_widget.dart';
import '../../resouce/base_color.dart';
import '../../shared/router_gage.dart';
import '../../widgets/custom_navigation_bar.dart';
import 'customer_contract_object_model.dart';
import 'electric_payment_viewmodel.dart';
class ElectricPaymentHistoryScreen extends StatefulWidget {
const ElectricPaymentHistoryScreen({super.key});
@override
State<ElectricPaymentHistoryScreen> createState() => _ElectricPaymentHistoryScreenState();
}
class _ElectricPaymentHistoryScreenState extends State<ElectricPaymentHistoryScreen> {
final ElectricPaymentViewModel _viewModel = Get.put(ElectricPaymentViewModel());
bool isEditMode = false;
bool selectAll = false;
List<String> selectedCodes = [];
void toggleEditMode() {
if (_viewModel.billContracts.value.isEmpty) return;
setState(() {
isEditMode = !isEditMode;
selectedCodes.clear();
selectAll = false;
});
}
void toggleSelectAll(bool? value) {
setState(() {
selectAll = value ?? false;
if (selectAll) {
final codes = _viewModel.billContracts.value.map((e) => e.maKH ?? '').where((e) => e.isNotEmpty).toList();
selectedCodes = codes.cast<String>();
} else {
selectedCodes.clear();
}
});
}
void toggleItem(CustomerContractModel data, bool? value) {
setState(() {
if (value ?? false) {
selectedCodes.add(data.maKH ?? '');
} else {
selectedCodes.remove(data.maKH ?? '');
selectAll = false;
}
});
}
void deleteSelected() {
if (selectedCodes.isEmpty) return;
setState(() {
_viewModel.customerContractDelete(selectedCodes.join(','));
isEditMode = false;
selectedCodes.clear();
selectAll = false;
});
}
@override
void initState() {
super.initState();
_viewModel.customerContractSearchHistoryGetList();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomNavigationBar(title: 'Hoá đơn điện'),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(20),
child: SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: BaseColor.primary500,
minimumSize: const Size(double.infinity, 48),
),
icon: const Icon(Icons.add, color: Colors.white),
label: const Text(
'Thêm hóa đơn mới',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
onPressed: () async {
await Get.toNamed(electricPaymentScreen);
_viewModel.customerContractSearchHistoryGetList();
},
),
),
),
Container(height: 12, color: BaseColor.second100),
const SizedBox(height: 24),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
const Expanded(child: Text('Lịch sử', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold))),
GestureDetector(
onTap: toggleEditMode,
child: Text(isEditMode ? 'Hủy' : 'Sửa', style: const TextStyle(color: Colors.blue)),
),
],
),
),
const SizedBox(height: 8),
Obx(() {
return (_viewModel.billContracts.value.isEmpty)
? Expanded(child: Center(child: EmptyWidget()))
: Expanded(
child: ListView.builder(
itemCount: _viewModel.billContracts.value.length,
itemBuilder: (context, index) {
final bill = _viewModel.billContracts.value[index];
final isSelected = selectedCodes.contains(bill.maKH ?? '');
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Container(
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(8),
),
child: ListTile(
leading:
isEditMode
? Checkbox(value: isSelected, onChanged: (val) => toggleItem(bill, val))
: null,
title: Text(bill.location ?? ''),
subtitle: Text(bill.maKH ?? ''),
trailing:
((bill.amount ?? 0) == 0)
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.check_circle, color: Colors.green),
Text(
'Bạn đã hết nợ cước',
style: const TextStyle(fontSize: 12, color: Colors.green),
),
],
)
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error, color: Colors.red),
Text(
(bill.amount ?? 0).money(CurrencyUnit.vnd),
style: const TextStyle(fontSize: 12, color: Colors.red),
),
],
),
),
),
);
},
),
);
}),
],
),
bottomNavigationBar: isEditMode ? _buildBottomButtonEditMode() : null,
);
}
Widget _buildBottomButtonEditMode() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
decoration: const BoxDecoration(
color: Colors.white,
boxShadow: [BoxShadow(color: Colors.black54, blurRadius: 8, offset: Offset(0, 4))],
),
child: SafeArea(
top: false,
child: Container(
padding: EdgeInsets.only(left: 8, right: 8),
child: Row(
children: [
Checkbox(value: selectAll, onChanged: toggleSelectAll),
const Text('Tất cả'),
const Spacer(),
ElevatedButton(
onPressed: selectedCodes.isNotEmpty ? deleteSelected : null,
style: ElevatedButton.styleFrom(
backgroundColor: BaseColor.primary500,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: const Text('Xoá', style: TextStyle(fontSize: 14, color: Colors.white)),
),
],
),
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../base/base_screen.dart';
import '../../base/basic_state.dart';
import '../../resouce/base_color.dart';
import '../../widgets/custom_navigation_bar.dart';
import 'electric_payment_viewmodel.dart';
class ElectricPaymentScreen extends BaseScreen {
const ElectricPaymentScreen({super.key});
@override
State<ElectricPaymentScreen> createState() => _ElectricPaymentScreenState();
}
class _ElectricPaymentScreenState extends BaseState<ElectricPaymentScreen> with BasicState {
final ElectricPaymentViewModel _viewModel = Get.put(ElectricPaymentViewModel());
final TextEditingController _controller = TextEditingController();
bool _canContinue = false;
@override
void initState() {
super.initState();
_controller.addListener(() {
setState(() {
_canContinue = _controller.text.isNotEmpty;
});
});
_viewModel.onShowAlertError = (message) {
if (message.isNotEmpty) {
showAlertError(content: message);
}
};
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget createBody() {
return Scaffold(
appBar: CustomNavigationBar(title: 'Thêm hoá đơn điện'),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
const SizedBox(height: 32),
Image.asset('assets/images/ic_evn_logo.png', height: 80),
const SizedBox(height: 8),
const Text(
'Điện lực Việt Nam - EVN',
style: TextStyle(fontSize: 18, color: Colors.blueAccent, fontWeight: FontWeight.bold),
),
const SizedBox(height: 32),
TextField(
controller: _controller,
decoration: InputDecoration(
labelText: 'Mã khách hàng',
border: const UnderlineInputBorder(),
suffixIcon: _controller.text.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear_rounded, color: Colors.grey),
onPressed: () {
_controller.clear();
setState(() {
_canContinue = false;
});
},
)
: null,
),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _canContinue ? () {
final code = _controller.text.trim();
if (code.isEmpty) return;
_viewModel.customerContractRequestSearch(code);
} : null,
style: ElevatedButton.styleFrom(
backgroundColor: BaseColor.primary500,
minimumSize: const Size(double.infinity, 48),
),
child: const Text(
'Tiếp tục',
style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white, fontSize: 16),
),
),
const SizedBox(height: 12),
Divider(color: Colors.grey.shade300, thickness: 1, height: 32),
const SizedBox(height: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Hướng dẫn lấy mã khách hàng', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Text('Bạn có thể tìm thấy mã khách hàng tại Hóa đơn tiền điện:'),
],
),
const SizedBox(height: 16),
Image.asset('assets/images/ic_electric_tutorial.png', fit: BoxFit.cover),
],
),
),
);
}
}
import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import '../../base/restful_api_viewmodel.dart';
import '../../configs/constants.dart';
import 'customer_contract_object_model.dart';
class ElectricPaymentViewModel extends RestfulApiViewModel {
var responseData = Rxn<CustomerContractModel>();
void Function(String message)? onShowAlertError;
final RxList<CustomerContractModel> billContracts = <CustomerContractModel>[].obs;
void customerContractRequestSearch(String maKH) {
showLoading();
client.customerContractRequestSearch(maKH).then((value) {
hideLoading();
final result = value.data;
if (!value.isSuccess) {
onShowAlertError?.call(
value.errorMessage ?? "Không tìm thấy thông tin mã khách hàng, vui lòng kiểm tra và thử lại.",
);
} else if (result != null) {
if ((result.amount ?? 0) == 0) {
onShowAlertError?.call("Bạn đã thanh toán hết hóa đơn.");
} else {
// TODO
responseData.value = result;
}
} else {
onShowAlertError?.call("Không tìm thấy thông tin mã khách hàng, vui lòng kiểm tra và thử lại.");
}
});
}
void customerContractSearchHistoryGetList() {
showLoading();
client.customerContractSearchHistoryGetList().then((value) {
hideLoading();
final result = value.data;
if (!value.isSuccess && result == null) {
onShowAlertError?.call(value.errorMessage ?? Constants.commonError);
} else {
billContracts.value = result ?? [];
}
});
}
void customerContractDelete(String code) {
showLoading();
client.customerContractDelete(code).then((value) {
hideLoading();
if (!value.isSuccess) {
onShowAlertError?.call(value.errorMessage ?? Constants.commonError,);
}
customerContractSearchHistoryGetList();
});
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart';
import '../../base/base_screen.dart';
import '../../base/basic_state.dart';
import '../../shared/router_gage.dart';
......@@ -93,16 +94,13 @@ class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState {
_viewModel.getGameDetail(item.id ?? "");
},
child: AspectRatio(
aspectRatio: 343/132,
aspectRatio: 343 / 132,
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Image.network(
item.icon ?? '',
fit: BoxFit.cover,
errorBuilder: (_, __, ___) => const ColoredBox(
color: Colors.grey,
child: Center(child: Icon(Icons.broken_image)),
),
child: loadNetworkImage(
url: item.icon ?? '',
fit: BoxFit.fitWidth,
placeholderAsset: 'assets/images/bg_default_169.png',
),
),
),
......@@ -132,7 +130,8 @@ class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState {
final widthSize = MediaQuery.of(context).size.width * 0.85;
_popupEntry = OverlayEntry(
builder: (context) => Stack(
builder:
(context) => Stack(
children: [
// 👉 Tap ngoài popup để close
Positioned.fill(
......@@ -144,17 +143,14 @@ class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState {
),
Positioned(
top: offset.dy + size.height + 8,
left: widthSize*0.15/0.85/2, // offset.dx - widthSize,
left: widthSize * 0.15 / 0.85 / 2, // offset.dx - widthSize,
child: Material(
borderRadius: BorderRadius.circular(16),
elevation: 4,
child: Container(
width: widthSize,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)),
child: const Text(
'1/ Thể lể trò chơi rất đơn giản, khách hàng sử dụng dịch vụ gói cước viễn thông của chúng tôi sẽ có cơ hội tham gia chơi game, khách hàng nào chơi cũng có thưởng do MyPoint (PayTech) tài trợ 100% phần quà.'
'\n\n2/ Ngoài ra các giải thưởng và luật lệ trong trò chơi không phải do Apple quản lý và tài trợ, điều này đã được thể hiện trong thể lệ và văn bản công bố trò chơi với khách hàng.',
......
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart';
class HoverView extends StatefulWidget {
final String imagePath;
......@@ -32,10 +34,10 @@ class _HoverViewState extends State<HoverView> {
bool _showCloseButton = false;
Size _screenSize = Size.zero;
bool _isInitialized = false;
late int _remainingSeconds;
final _remainingSeconds = 0.obs;
Timer? _timer;
double get _expandBottom {
if (_remainingSeconds > 0) {
if (_remainingSeconds.value > 0) {
return 30.0;
}
return 8.0;
......@@ -44,21 +46,11 @@ class _HoverViewState extends State<HoverView> {
@override
void initState() {
super.initState();
_remainingSeconds = widget.countDownTime.toInt();
if (_remainingSeconds > 0) {
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (!mounted) return;
setState(() {
_remainingSeconds--;
if (_remainingSeconds <= 0) {
_timer?.cancel();
}
});
});
}
WidgetsBinding.instance.addPostFrameCallback((_) {
_setInitialPosition();
});
_remainingSeconds.value = widget.countDownTime.toInt();
_startTimer();
}
@override
......@@ -67,6 +59,20 @@ class _HoverViewState extends State<HoverView> {
super.dispose();
}
void _startTimer() {
_timer?.cancel();
if (_remainingSeconds.value > 0) {
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (!mounted) return;
setState(() {
_remainingSeconds.value--;
print("Remaining seconds: ${_remainingSeconds.value}");
if (_remainingSeconds.value <= 0) _timer?.cancel();
});
});
}
}
void _setInitialPosition() {
if (!mounted) return;
final paddingBottom = MediaQuery.of(context).padding.bottom + _expandBottom;
......@@ -184,7 +190,7 @@ class _HoverViewState extends State<HoverView> {
left: _position.dx,
top: _position.dy,
width: widget.size,
height: widget.size + (_remainingSeconds > 0 ? 24 : 0),
height: widget.size + (_remainingSeconds.value > 0 ? 26 : 0),
child: GestureDetector(
onPanStart: _onPanStart,
onPanUpdate: _onPanUpdate,
......@@ -209,21 +215,24 @@ class _HoverViewState extends State<HoverView> {
height: widget.size,
child:
widget.imagePath.startsWith('http')
? Image.network(widget.imagePath, fit: BoxFit.cover)
? loadNetworkImage(url: widget.imagePath, placeholderAsset: 'assets/images/ic_pipi_05.png')
: Image.asset(widget.imagePath, fit: BoxFit.cover),
),
),
if (_remainingSeconds > 0)
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
Obx(()
=> (_remainingSeconds.value > 0)
? Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.6),
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(18),
),
child: Text(
_formatTime(_remainingSeconds),
style: const TextStyle(fontSize: 12, color: Colors.white, fontWeight: FontWeight.w500),
_formatTime(_remainingSeconds.value),
style: const TextStyle(fontSize: 12, color: Colors.white, fontWeight: FontWeight.w600),
),
)
: SizedBox.shrink(),
),
],
),
......
......@@ -31,9 +31,7 @@ class HomeScreen extends StatefulWidget {
class _HomeScreenState extends State<HomeScreen> {
final HomeTabViewModel _viewModel = Get.put(HomeTabViewModel());
final _headerHomeVM = Get.find<HeaderHomeViewModel>();
bool _showHover = true;
final RxBool _showHover = true.obs;
@override
void initState() {
super.initState();
......@@ -76,7 +74,9 @@ class _HomeScreenState extends State<HomeScreen> {
services: _viewModel.services,
sectionConfig: _viewModel.getMainSectionConfigModel(HeaderSectionType.topButton),
onTap: (item) {
print("item serviceName serviceName ${item.serviceName} ${item.clickActionType} ${item.clickActionParam}");
print(
"item serviceName serviceName ${item.serviceName} ${item.clickActionType} ${item.clickActionParam}",
);
item.directionalScreen?.begin();
},
),
......@@ -204,25 +204,27 @@ class _HomeScreenState extends State<HomeScreen> {
}),
),
),
if (_showHover)
Positioned.fill(
child: Obx(() {
return HoverView(
Obx(() {
if (!_showHover.value) return SizedBox.shrink();
return Positioned.fill(
child: HoverView(
imagePath: _viewModel.hoverData.value?.icon ?? '',
onTap: _handleHoverViewTap,
onClose: _handleCloseHoverView,
backgroundColor: Colors.transparent,
size: 80,
countDownTime: _viewModel.hoverData.value?.countDownTime ?? 0.0,
),
);
}),
),
],
),
);
}
void _handleHoverViewTap() {
final result = _viewModel.hoverData.value?.direction?.begin();
if (result != true) {
showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
......@@ -230,10 +232,11 @@ class _HomeScreenState extends State<HomeScreen> {
builder: (_) => PipiDetailScreen(),
);
}
}
void _handleCloseHoverView() {
setState(() {
_showHover = false;
_showHover.value = false;
});
}
......
......@@ -62,6 +62,7 @@ class HomeTabViewModel extends RestfulApiViewModel {
try {
final result = await client.getDataPiPiHome();
hoverData.value = result.data;
hoverData.refresh();
} catch (error) {
print("Error fetching loadDataPiPiHome: $error");
}
......
......@@ -24,7 +24,7 @@ class HoverDataModel {
double? get countDownTime {
if (countdown == null) return null;
try {
final date = DateFormat("yyyy-MM-dd'T'HH:mm:ss").parseUtc(countdown!);
final date = DateFormat("yyyy-MM-dd HH:mm:ss").parseUtc(countdown!);
final now = DateTime.now().toUtc();
final diff = date.difference(now).inSeconds;
return (diff > 0 ? diff : 0).toDouble();
......
......@@ -35,7 +35,9 @@ class _MainTabScreenState extends State<MainTabScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
return WillPopScope(
onWillPop: () async => false,
child: Scaffold(
backgroundColor: Colors.transparent, // cho sáng nền tổng thể
extendBody: true,
body: _pages[_currentIndex],
......@@ -60,6 +62,7 @@ class _MainTabScreenState extends State<MainTabScreen> {
),
),
),
),
);
}
......
import 'package:flutter/material.dart';
import 'package:mypoint_flutter_app/widgets/custom_app_bar.dart';
import '../../directional/directional_screen.dart';
import '../../widgets/custom_navigation_bar.dart';
class _OrderMenuItem {
final String title;
final IconData icon;
_OrderMenuItem({required this.title, required this.icon});
final String type;
_OrderMenuItem({required this.title, required this.icon, required this.type});
}
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),
_OrderMenuItem(title: 'Thẻ nạp của tôi', icon: Icons.credit_card, type: ''),
_OrderMenuItem(title: 'Sổ sức khỏe điện tử', icon: Icons.medical_services_outlined, type: ''),
_OrderMenuItem(title: 'Dịch vụ giao thông', icon: Icons.traffic_outlined, type: 'APP_SCREEN_MY_VNTRA_PACKAGE'),
];
@override
......@@ -31,8 +33,7 @@ class OrderMenuScreen extends StatelessWidget {
final item = items[index];
return InkWell(
onTap: () {
print("Tapped on ${item.title}");
// TODO: handle tap
DirectionalScreen.build(clickActionType: item.type)?.begin();
},
child: Container(
height: 48,
......
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