Commit 682ab1de authored by DatHV's avatar DatHV
Browse files

fix bug.

parent 1edd930e
......@@ -166,7 +166,7 @@ class _OnboardingScreenState extends BaseState<OnboardingScreen> with BasicState
const TextSpan(text: " và "),
WidgetSpan(
child: GestureDetector(
onTap: () => Get.to(FAQScreen()),// Get.to(CampaignDetailScreen(type: DetailPageRuleType.privacyPolicy)),
onTap: () => Get.toNamed(campaignDetailScreen, arguments: {"type": DetailPageRuleType.privacyPolicy}),
child: const Text(
"Chính sách bảo mật",
style: TextStyle(
......
......@@ -6,6 +6,7 @@ import '../../core/network/restful_api_viewmodel.dart';
import '../../app/config/constants.dart';
import '../../shared/preferences/data_preference.dart';
import '../../shared/widgets/custom_toast_message.dart';
import 'model/otp_verify_response_model.dart';
import 'otp_viewmodel.dart';
class DeleteAccountOtpRepository extends RestfulApiViewModel implements IOtpRepository {
......@@ -20,10 +21,10 @@ class DeleteAccountOtpRepository extends RestfulApiViewModel implements IOtpRepo
Future<void> sendOtp() async {}
@override
Future<BaseResponseModel<EmptyCodable>> verifyOtp(String otpCode) async {
Future<BaseResponseModel<OTPVerifyResponseModel>> verifyOtp(String otpCode) {
showLoading();
try {
final value = await client.verifyDeleteAccount(otpCode);
return client.verifyDeleteAccount(otpCode).then((value) async {
hideLoading();
if (value.isSuccess) {
await DataPreference.instance.clearBioToken(phoneNumber);
await DataPreference.instance.clearData();
......@@ -31,9 +32,7 @@ class DeleteAccountOtpRepository extends RestfulApiViewModel implements IOtpRepo
showToastMessage("Xóa tài khoản thành công");
}
return value;
} finally {
hideLoading();
}
});
}
@override
......@@ -46,7 +45,7 @@ class DeleteAccountOtpRepository extends RestfulApiViewModel implements IOtpRepo
return otpTtl;
}
final mgs = value.errorMessage ?? Constants.commonError;
Get.snackbar("Thông báo", mgs);
showToastMessage(mgs);
return null;
} finally {
hideLoading();
......
......@@ -17,7 +17,11 @@ class ForgotPassOTPRepository extends RestfulApiViewModel implements IOtpReposit
@override
Future<int?> resendOtp() {
throw UnimplementedError();
showLoading();
return client.otpCreateNew(phoneNumber).then((value) {
hideLoading();
return value.data?.resendAfterSecond;
});
}
@override
......
......@@ -46,16 +46,16 @@ class PersonalEditDataModel {
});
Json get body => <String, dynamic> {
"worker_site_id": DataPreference.instance.profile?.workerSite?.id,
"fullname": name,
"nickname": nickname,
"worker_site_id": DataPreference.instance.profile?.workerSite?.id ?? "",
"fullname": name ?? "",
"nickname": nickname ?? "",
"date_of_birth": birthday?.toFormattedString(format: "yyyy-MM-dd"),
"sex": gender?.value ?? "U",
"address_full": address,
"address_full": address ?? "",
"address_district_code": district?.code ?? "",
"address_province_code": province?.code ?? "",
"identification_number": identificationNumber,
"email": email,
"identification_number": identificationNumber ?? "",
"email": email ?? "",
"avatar": "",
"avatar_2": "",
};
......
......@@ -28,20 +28,10 @@ class _PersonalEditScreenState extends BaseState<PersonalEditScreen> with BasicS
showAlertError(content: message, barrierDismissible: true, onConfirmed: null);
};
viewModel.updateProfileResponseSuccess = () {
DataAlertModel alertData = DataAlertModel(
localHeaderImage: "assets/images/ic_pipi_05.png",
title: "Thông báo",
description: "Cập nhật thông tin cá nhân thành công!",
buttons: [
AlertButton(
text: "Đã hiểu",
onPressed: () => Get.back(),
bgColor: BaseColor.primary500,
textColor: Colors.white,
),
],
);
showAlert(data: alertData);
showAlertError(
content: "Cập nhật thông tin cá nhân thành công!",
headerImage: "assets/images/ic_pipi_05.png",
onConfirmed: () => Get.back());
};
}
......@@ -191,6 +181,7 @@ class _PersonalEditScreenState extends BaseState<PersonalEditScreen> with BasicS
item.sectionType == SectionPersonalEditType.gender;
final isDate = item.sectionType == SectionPersonalEditType.birthday;
final isTappableItem = isTapField || isDate;
final value = item.value ?? "";
return Padding(
padding: const EdgeInsets.only(top: 8, bottom: 8, left: 16, right: 16), // all(16.0),
child: Column(
......@@ -223,7 +214,7 @@ class _PersonalEditScreenState extends BaseState<PersonalEditScreen> with BasicS
Expanded(
child: TextField(
keyboardType: item.keyboardType ?? TextInputType.text,
controller: TextEditingController(text: item.value ?? ""),
controller: TextEditingController(text: value),
enabled: isTappableItem ? false : (item.isEditable ?? true),
decoration: InputDecoration.collapsed(
hintText: item.hintText ?? "",
......@@ -242,6 +233,14 @@ class _PersonalEditScreenState extends BaseState<PersonalEditScreen> with BasicS
),
),
const SizedBox(height: 6),
if (item.sectionType == SectionPersonalEditType.email && value.isNotEmpty && viewModel.isValidEmail(value) == false)
Row(
children: [
const Icon(Icons.error, color: Colors.red, size: 14),
const SizedBox(width: 4),
Text('Email không đúng định dạng', style: const TextStyle(fontSize: 12, color: Colors.red)),
],
),
if (item.warningText != null)
Row(
children: [
......
......@@ -157,7 +157,7 @@ class PersonalEditViewModel extends RestfulApiViewModel {
}
if (model.birthday == null) {
return false;
}
}
if (model.gender == null || model.gender == PersonalGender.unknown) {
return false;
}
......
......@@ -74,19 +74,18 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState, Po
}
Widget _buildHeaderPersonal(HeaderHomeModel data) {
final width = MediaQuery.of(context).size.width;
final topPadding = MediaQuery.of(context).padding.top;
final name = DataPreference.instance.displayName;
final level = DataPreference.instance.rankName ?? "Hạng Đồng";
final email = DataPreference.instance.profile?.workerSite?.email ?? "";
final topWebPadding = Constants.extendTopPaddingNavigation;
final avatar = WebData.getAvatar();
return Container(
height: width * 163 / 375 + topWebPadding,
decoration: BoxDecoration(image: DecorationImage(image: NetworkImage(data.background ?? ""), fit: BoxFit.cover)),
child: Padding(
padding: EdgeInsets.only(top: 12 + topWebPadding, bottom: 12, left: 8, right: 8),// symmetric(horizontal: 12, vertical: 8),
child: SafeArea(
bottom: false,
minimum: EdgeInsets.only(top: 12 + topWebPadding, bottom: 12, left: 12, right: 12),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
......@@ -94,47 +93,51 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState, Po
await Get.toNamed(personalEditScreen);
setState(() {});
},
child: Container(
margin: EdgeInsets.only(top: topPadding),
child: Row(
children: [
Container(
width: 64,
height: 64,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2),
),
child: ClipOval(
child: loadNetworkImage(
url: avatar,
fit: BoxFit.cover,
placeholderAsset: "assets/images/ic_logo.png"
)
),
child: Row(
children: [
Container(
width: 64,
height: 64,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2),
),
child: ClipOval(
child: loadNetworkImage(
url: avatar,
fit: BoxFit.cover,
placeholderAsset: "assets/images/ic_logo.png"
)
),
const SizedBox(width: 8),
Column(
),
const SizedBox(width: 8),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.white),
),
if (email.isNotEmpty)
Text(
email,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 16, color: Colors.white70, fontWeight: FontWeight.w600),
),
],
),
const Spacer(),
const Icon(Icons.chevron_right, color: Colors.white, size: 22),
],
),
),
const SizedBox(width: 8),
const Icon(Icons.chevron_right, color: Colors.white, size: 22),
],
),
),
const Spacer(),
const SizedBox(height: 16),
Row(
children: [
GestureDetector(
......
......@@ -85,7 +85,7 @@ class SplashScreenViewModel extends RestfulApiViewModel {
// Get token from SDK
final token = await webGetToken().timeout(_sdkTimeout);
if (token != null && token.isNotEmpty) {
debugPrint('✅ SplashScreen - Token retrieved from x-app-sdk: ${token.substring(0, 8)}...');
debugPrint('✅ SplashScreen - Token retrieved from x-app-sdk: $token...');
return token;
} else {
final error = webGetLastError();
......
......@@ -89,7 +89,7 @@ class _PhoneTopUpScreenState extends BaseState<PhoneTopUpScreen> with BasicState
),
keyboardType: TextInputType.phone,
onChanged: (value) {
_viewModel.phoneNumber.value = value;
_viewModel.phoneNumber.value = value.trim();
_deb.run(() => _viewModel.checkMobileNetwork());
},
),
......@@ -110,9 +110,11 @@ class _PhoneTopUpScreenState extends BaseState<PhoneTopUpScreen> with BasicState
selectedBrand: _viewModel.selectedBrand.value,
onSelected: (brand) {
Navigator.pop(context);
print("BrandSelectShe 2222 2 et ${brand.name}");
if (brand.id != _viewModel.selectedBrand.value?.id) return;
_viewModel.selectedProduct.value = null;
_viewModel.selectedBrand.value = brand;
print("BrandSelectSheet ${brand.name}");
_viewModel.getTelcoDetail();
},
),
......@@ -163,7 +165,7 @@ class _PhoneTopUpScreenState extends BaseState<PhoneTopUpScreen> with BasicState
return GestureDetector(
onTap: () {
setState(() {
_viewModel.phoneNumber.value = phone;
_viewModel.phoneNumber.value = phone.trim();
_phoneController.text = phone;
_viewModel.checkMobileNetwork();
});
......@@ -365,7 +367,7 @@ class _PhoneTopUpScreenState extends BaseState<PhoneTopUpScreen> with BasicState
String phone = contact.phones.first.number;
phone = phone.replaceAll(RegExp(r'[\s\-\(\)]'), '');
_phoneController.text = phone;
_viewModel.phoneNumber.value = phone;
_viewModel.phoneNumber.value = phone.trim();
_viewModel.checkMobileNetwork();
} catch (e) {
debugPrint('❌ pickContact error: $e');
......
......@@ -66,13 +66,15 @@ class TopUpViewModel extends RestfulApiViewModel {
}
Future<void> checkMobileNetwork() async {
final phone = phoneNumber.value.trim();
if (phone.isEmpty) return;
await callApi<BrandNameCheckResponse>(
request: () => _callProductApi((api) => api.checkMobileNetwork(phoneNumber.value)),
request: () => _callProductApi((api) => api.checkMobileNetwork(phone)),
onSuccess: (data, _) {
final brandCode = data.brand ?? '';
final brandCode = (data.brand ?? '').toUpperCase();
var brand = topUpBrands.isNotEmpty
? topUpBrands.firstWhere(
(brand) => brand.code == brandCode,
(brand) => (brand.code ?? "").toUpperCase() == brandCode,
orElse: () => topUpBrands.first,
) : topUpBrands.firstOrNull;
selectedBrand.value = brand;
......
......@@ -25,6 +25,9 @@ class TransactionHistoryDetailScreen extends BaseScreen {
class _TransactionHistoryDetailScreenState extends BaseState<TransactionHistoryDetailScreen> with BasicState {
late final TransactionHistoryDetailViewModel _viewModel;
late var canBack = true;
void _goHome() {
Get.offAllNamed(mainScreen, arguments: {"tabIndex": 0});
}
@override
void initState() {
......@@ -252,7 +255,8 @@ class _TransactionHistoryDetailScreenState extends BaseState<TransactionHistoryD
onPressed: () {
final finish = transaction.directionScreenRedButton?.begin();
if (finish != true) {
Get.until((route) => Get.currentRoute == mainScreen);
print("finish directionScreenRedButton");
_goHome();
}
},
style: ElevatedButton.styleFrom(
......@@ -270,7 +274,7 @@ class _TransactionHistoryDetailScreenState extends BaseState<TransactionHistoryD
if (transaction.titleClearButton != null)
TextButton(
onPressed: () {
Get.until((route) => Get.currentRoute == mainScreen);
_goHome();
},
style: TextButton.styleFrom(
minimumSize: const Size(double.infinity, 50),
......
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart';
import 'package:mypoint_flutter_app/core/utils/extensions/num_extension.dart';
......@@ -35,6 +36,7 @@ class VoucherDetailScreen extends BaseScreen {
class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with BasicState {
late final VoucherDetailViewModel _viewModel;
late final String _viewModelTag;
double _infoHeight = 0;
@override
......@@ -59,13 +61,23 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
});
return;
}
_viewModel = Get.put(VoucherDetailViewModel(productId: productId, customerProductId: customerProductId));
_viewModelTag = 'voucher_detail_${DateTime.now().microsecondsSinceEpoch}';
_viewModel = Get.put(
VoucherDetailViewModel(productId: productId, customerProductId: customerProductId),
tag: _viewModelTag,
);
_viewModel.onShowAlertError = (message) {
if (message.isEmpty) return;
showAlertError(content: message);
};
}
@override
void dispose() {
Get.delete<VoucherDetailViewModel>(tag: _viewModelTag);
super.dispose();
}
@override
Widget createBody() {
return Scaffold(
......@@ -108,7 +120,12 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [CustomBackButton(), _buildFavoriteButton()],
children: [
CustomBackButton(),
_buildFavoriteButton(),
if (kIsWeb)
Expanded(child: SizedBox.shrink())
],
),
),
),
......
......@@ -11,7 +11,7 @@ class VoucherActionMenu extends StatelessWidget {
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
children: const [
_ActionItem(icon: "assets/images/ic_topup.png", label: 'Nạp tiền\ndiện thoại', type: DirectionalScreenName.topup,),
_ActionItem(icon: "assets/images/ic_topup.png", label: 'Nạp tiền\nđiện thoại', type: DirectionalScreenName.topup,),
_ActionItem(icon: "assets/images/ic_card_code.png", label: 'Đổi mã\nthẻ nạp', type: DirectionalScreenName.productMobileCard,),
_ActionItem(icon: "assets/images/ic_sim_service.png", label: 'Gói cước\nnhà mạng', type: DirectionalScreenName.simService,),
_ActionItem(icon: "assets/images/ic_topup_data.png", label: 'Ưu đãi\nData', type: DirectionalScreenName.mobileTopupData,),
......
......@@ -52,8 +52,9 @@ class VoucherListViewModel extends RestfulApiViewModel {
}
void onSearchChanged(String value) {
if (_searchQuery == value) return;
_searchQuery = value;
final value_ = value.trim();
if (_searchQuery == value_) return;
_searchQuery = value_;
_debounce?.cancel();
_debounce = Timer(const Duration(seconds: 1), () {
loadData(reset: true);
......
......@@ -33,30 +33,33 @@ class CustomAlertDialog extends StatelessWidget {
children: [
_buildHeaderImage(),
Padding(
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(8),
child: Column(
children: [
if ((alertData.title ?? "").isNotEmpty)
if ((alertData.title ?? "").isNotEmpty)... [
Text(
alertData.title!,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
if (alertData.description != null)
const SizedBox(height: 4)
],
if (alertData.description != null)... [
HtmlWidget('''
<div style="text-align: center;">
${alertData.description!}
</div>
'''),
const SizedBox(height: 4),
if ((alertData.content ?? "").isNotEmpty)
const SizedBox(height: 4),
],
if ((alertData.content ?? "").isNotEmpty)... [
Text(
alertData.content!,
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: BaseColor.primary500),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
const SizedBox(height: 4),
],
_buildButtons(),
],
),
......
......@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.21.11+2025102401
version: 1.21.13+2025123101
environment:
sdk: ^3.7.0
......
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