Commit cc202df4 authored by DatHV's avatar DatHV
Browse files

update change id, popup common manager

parent 417358c5
...@@ -6,9 +6,9 @@ plugins { ...@@ -6,9 +6,9 @@ plugins {
} }
android { android {
namespace = "com.icom.vn.mypoint_flutter_app" namespace = "com.icom.mypoint"
compileSdk = flutter.compileSdkVersion compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion ndkVersion = "27.0.12077973"
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_11
...@@ -21,7 +21,7 @@ android { ...@@ -21,7 +21,7 @@ android {
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.icom.vn.mypoint_flutter_app" applicationId = "com.icom.mypoint"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config. // For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion minSdk = flutter.minSdkVersion
......
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<application <application
android:label="mypoint_flutter_app" android:label="mypoint_flutter_app"
android:name="${applicationName}" android:name="${applicationName}"
...@@ -41,6 +44,15 @@ ...@@ -41,6 +44,15 @@
<action android:name="android.intent.action.PROCESS_TEXT"/> <action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/> <data android:mimeType="text/plain"/>
</intent> </intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
</queries> </queries>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
</manifest> </manifest>
package com.icom.vn.mypoint_flutter_app package com.icom.mypoint
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
......
...@@ -101,4 +101,8 @@ class APIPaths { ...@@ -101,4 +101,8 @@ class APIPaths {
static const String categoryUnsubscribeList = "/categoryUnsubscribeList/1.0.0"; static const String categoryUnsubscribeList = "/categoryUnsubscribeList/1.0.0";
static const String customerEvnPaymentGatewayRequest = "/customerEvnPaymentGatewayRequest/1.0.0"; static const String customerEvnPaymentGatewayRequest = "/customerEvnPaymentGatewayRequest/1.0.0";
static const String getPopup = "/getPopup/1.0.0"; static const String getPopup = "/getPopup/1.0.0";
static const String getMyProductGetExpiredList = "/myProductGetExpiredList/2.0.0";
static const String getMyProductGetUsedList = "/myProductGetUsedList/2.0.0";
static const String getMyProductGetWaitingList = "/myProductGetWaitingList/2.0.0";
static const String orderPaymentMyAccounts = "order/api/v1.0/payment/my-accounts";
} }
\ No newline at end of file
...@@ -118,9 +118,9 @@ class DirectionalScreen { ...@@ -118,9 +118,9 @@ class DirectionalScreen {
if (uri == null) return true; if (uri == null) return true;
final requestId = const Uuid().v4(); // Cần package `uuid` final requestId = const Uuid().v4(); // Cần package `uuid`
final updatedUri = uri.replace(queryParameters: {...uri.queryParameters, 'aff_sub3': requestId}); final updatedUri = uri.replace(queryParameters: {...uri.queryParameters, 'aff_sub3': requestId});
LaunchMode mode = LaunchMode mode = type == DirectionalScreenName.viewDeepLink ? LaunchMode.externalApplication : LaunchMode.inAppWebView;
type == DirectionalScreenName.viewDeepLink ? LaunchMode.externalApplication : LaunchMode.platformDefault; // forceOpen(url: updatedUri, mode: mode);
forceOpen(url: updatedUri, mode: mode); safeOpenUrl(updatedUri, preferred: mode);
return true; return true;
case DirectionalScreenName.refundHistory: case DirectionalScreenName.refundHistory:
Get.toNamed(historyPointCashBackScreen); Get.toNamed(historyPointCashBackScreen);
...@@ -153,8 +153,14 @@ class DirectionalScreen { ...@@ -153,8 +153,14 @@ class DirectionalScreen {
Get.toNamed(personalEditScreen); Get.toNamed(personalEditScreen);
return true; return true;
case DirectionalScreenName.surveyCampaign: case DirectionalScreenName.surveyCampaign:
// if ((clickActionParam ?? '').isEmpty) return false; if ((clickActionParam ?? '').isEmpty) return false;
Get.toNamed(surveyQuestionScreen, arguments: {"quizId": "1"}); Get.toNamed(surveyQuestionScreen, arguments: {"quizId": clickActionParam ?? ''});
return true;
case DirectionalScreenName.myMobileCard:
Get.toNamed(myMobileCardListScreen);
return true;
case DirectionalScreenName.bankAccountManager:
Get.toNamed(bankAccountManagerScreen);
return true; 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");
...@@ -168,7 +174,7 @@ Future<bool> forceOpen({required Uri url, LaunchMode mode = LaunchMode.platformD ...@@ -168,7 +174,7 @@ Future<bool> forceOpen({required Uri url, LaunchMode mode = LaunchMode.platformD
if (await canLaunchUrl(url)) { if (await canLaunchUrl(url)) {
await launchUrl( await launchUrl(
url, url,
mode: LaunchMode.externalApplication, mode: LaunchMode.inAppBrowserView,
webViewConfiguration: const WebViewConfiguration(enableJavaScript: true, headers: <String, String>{}), webViewConfiguration: const WebViewConfiguration(enableJavaScript: true, headers: <String, String>{}),
); );
return true; return true;
...@@ -187,3 +193,53 @@ Future<void> openAppStore(String url) async { ...@@ -187,3 +193,53 @@ Future<void> openAppStore(String url) async {
debugPrint("⚠️ Không thể mở URL: $url"); debugPrint("⚠️ Không thể mở URL: $url");
} }
} }
Future<bool> safeOpenUrl(Uri url, {LaunchMode preferred = LaunchMode.platformDefault,}) async {
try {
// 1) Thử theo mode ưa thích
if (await canLaunchUrl(url)) {
final ok = await launchUrl(
url,
mode: preferred,
webViewConfiguration: const WebViewConfiguration(
enableJavaScript: true,
headers: <String, String>{},
),
);
if (ok) return true;
}
// 2) Fallback: mở bằng app ngoài (trình duyệt hệ thống)
if (await canLaunchUrl(url)) {
final ok = await launchUrl(
url,
mode: LaunchMode.externalApplication,
webViewConfiguration: const WebViewConfiguration(
enableJavaScript: true,
headers: <String, String>{},
),
);
if (ok) return true;
}
// 3) Fallback: mở trong webview của app (Custom Tabs / SFSafariViewController)
if (await canLaunchUrl(url)) {
final ok = await launchUrl(
url,
mode: LaunchMode.inAppBrowserView, // hoặc inAppWebView (tuỳ version url_launcher)
webViewConfiguration: const WebViewConfiguration(
enableJavaScript: true,
headers: <String, String>{},
),
);
if (ok) return true;
}
// 4) Fallback cuối
if (await canLaunchUrl(url)) {
final ok = await launchUrl(url, mode: LaunchMode.platformDefault);
if (ok) return true;
}
} catch (e) {
// ghi log lỗi nếu có
// debugPrint('safeOpenUrl error: $e');
}
return false;
}
...@@ -18,6 +18,7 @@ import '../screen/affiliate/model/affiliate_category_model.dart'; ...@@ -18,6 +18,7 @@ import '../screen/affiliate/model/affiliate_category_model.dart';
import '../screen/affiliate/model/affiliate_product_top_sale_model.dart'; import '../screen/affiliate/model/affiliate_product_top_sale_model.dart';
import '../screen/affiliate/model/cashback_overview_model.dart'; import '../screen/affiliate/model/cashback_overview_model.dart';
import '../screen/affiliate_brand_detail/models/affiliate_brand_detail_model.dart'; import '../screen/affiliate_brand_detail/models/affiliate_brand_detail_model.dart';
import '../screen/bank_account_manager/bank_account_info_model.dart';
import '../screen/campaign7day/models/campaign_7day_info_model.dart'; import '../screen/campaign7day/models/campaign_7day_info_model.dart';
import '../screen/campaign7day/models/campaign_7day_mission_model.dart'; import '../screen/campaign7day/models/campaign_7day_mission_model.dart';
import '../screen/campaign7day/models/campaign_7day_reward_model.dart'; import '../screen/campaign7day/models/campaign_7day_reward_model.dart';
...@@ -64,9 +65,10 @@ import '../screen/transaction/history/transaction_history_response_model.dart'; ...@@ -64,9 +65,10 @@ import '../screen/transaction/history/transaction_history_response_model.dart';
import '../screen/transaction/model/order_product_payment_response_model.dart'; import '../screen/transaction/model/order_product_payment_response_model.dart';
import '../screen/transaction/model/payment_bank_account_info_model.dart'; import '../screen/transaction/model/payment_bank_account_info_model.dart';
import '../screen/transaction/model/payment_method_model.dart'; import '../screen/transaction/model/payment_method_model.dart';
import '../screen/transaction/model/payment_method_type.dart';
import '../screen/transaction/model/preview_order_payment_model.dart'; import '../screen/transaction/model/preview_order_payment_model.dart';
import '../screen/voucher/models/like_product_reponse_model.dart'; import '../screen/voucher/models/like_product_reponse_model.dart';
import '../screen/voucher/models/my_mobile_card_response.dart';
import '../screen/voucher/models/my_product_status_type.dart';
import '../screen/voucher/models/product_brand_model.dart'; import '../screen/voucher/models/product_brand_model.dart';
import '../screen/voucher/models/product_store_model.dart'; import '../screen/voucher/models/product_store_model.dart';
import '../screen/voucher/models/product_type.dart'; import '../screen/voucher/models/product_type.dart';
...@@ -76,7 +78,7 @@ import 'model_maker.dart'; ...@@ -76,7 +78,7 @@ import 'model_maker.dart';
extension RestfullAPIClientAllApi on RestfulAPIClient { extension RestfullAPIClientAllApi on RestfulAPIClient {
Future<BaseResponseModel<UpdateResponseModel>> checkUpdateApp() async { Future<BaseResponseModel<UpdateResponseModel>> checkUpdateApp() async {
String version = Platform.version; String version = Platform.version;
final body = {"operating_system": "iOS", "software_model": "MyPoint", "version": "1.21.7", "build_number": "1"}; final body = {"operating_system": "iOS", "software_model": "MyPoint", "version": version, "build_number": "1"};
return requestNormal(APIPaths.checkUpdate, Method.POST, body, (data) => UpdateResponseModel.fromJson(data as Json)); return requestNormal(APIPaths.checkUpdate, Method.POST, body, (data) => UpdateResponseModel.fromJson(data as Json));
} }
...@@ -170,7 +172,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient { ...@@ -170,7 +172,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
} }
Future<BaseResponseModel<CreateOTPResponseModel>> otpCreateNew(String ownerId) async { Future<BaseResponseModel<CreateOTPResponseModel>> otpCreateNew(String ownerId) async {
var deviceKey = await DeviceInfo.getDeviceId(); // var deviceKey = await DeviceInfo.getDeviceId();
final body = {"owner_id": ownerId, "ttl": Constants.otpTtl, "resend_after_second": Constants.otpTtl}; final body = {"owner_id": ownerId, "ttl": Constants.otpTtl, "resend_after_second": Constants.otpTtl};
return requestNormal( return requestNormal(
APIPaths.otpCreateNew, APIPaths.otpCreateNew,
...@@ -241,7 +243,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient { ...@@ -241,7 +243,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
} }
Future<BaseResponseModel<EmptyCodable>> accountPasswordChange(String phone, String password) async { Future<BaseResponseModel<EmptyCodable>> accountPasswordChange(String phone, String password) async {
String? token = await DataPreference.instance.token ?? ""; String? token = DataPreference.instance.token ?? "";
final body = {"login_name": phone, "password": password.toSha256(), "access_token": token}; final body = {"login_name": phone, "password": password.toSha256(), "access_token": token};
return requestNormal( return requestNormal(
APIPaths.accountPasswordChange, APIPaths.accountPasswordChange,
...@@ -252,7 +254,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient { ...@@ -252,7 +254,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
} }
Future<BaseResponseModel<EmptyCodable>> accountLoginForPasswordChange(String phone, String password) async { Future<BaseResponseModel<EmptyCodable>> accountLoginForPasswordChange(String phone, String password) async {
String? token = await DataPreference.instance.token ?? ""; String? token = DataPreference.instance.token ?? "";
final body = {"login_name": phone, "password": password.toSha256(), "access_token": token}; final body = {"login_name": phone, "password": password.toSha256(), "access_token": token};
return requestNormal( return requestNormal(
APIPaths.accountLoginForPasswordChange, APIPaths.accountLoginForPasswordChange,
...@@ -893,10 +895,42 @@ extension RestfullAPIClientAllApi on RestfulAPIClient { ...@@ -893,10 +895,42 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
}); });
} }
Future<BaseResponseModel<List<PopupManagerModel>>> getPopup() async { Future<BaseResponseModel<List<PopupManagerModel>>> getPopupManagerCommonScreen() async {
return requestNormal(APIPaths.getPopup, Method.POST, {}, (data) { String? token = DataPreference.instance.token ?? "";
final body = {"access_token": token, "lang": "vi"};
return requestNormal(APIPaths.getPopup, Method.POST, body, (data) {
final list = data as List<dynamic>; final list = data as List<dynamic>;
return list.map((e) => PopupManagerModel.fromJson(e)).toList(); return list.map((e) => PopupManagerModel.fromJson(e)).toList();
}); });
} }
Future<BaseResponseModel<MyVoucherResponse>> getMyMobileCards(MyProductStatusType status, Json body) async {
String? token = DataPreference.instance.token ?? "";
body["access_token"] = token;
body["product_model_code"] = "PRODUCT_MODEL_MOBILE_CARD";
body["list_order"] = "DESC";
var path = '';
switch (status) {
case MyProductStatusType.waiting:
path = APIPaths.getMyProductGetWaitingList;
break;
case MyProductStatusType.used:
path = APIPaths.getMyProductGetUsedList;
break;
case MyProductStatusType.expired:
path = APIPaths.getMyProductGetExpiredList;
}
return requestNormal(path, Method.POST, body, (data) {
return MyVoucherResponse.fromJson(data as Json);
});
}
Future<BaseResponseModel<List<BankAccountInfoModel>>> getOrderPaymentMyAccounts() async {
// String? token = DataPreference.instance.token ?? "";
// final body = {"access_token": token, "lang": "vi"};
return requestNormal(APIPaths.orderPaymentMyAccounts, Method.GET, {}, (data) {
final list = data as List<dynamic>;
return list.map((e) => BankAccountInfoModel.fromJson(e)).toList();
});
}
} }
...@@ -2,6 +2,7 @@ import 'dart:convert'; ...@@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.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/popup_manager/popup_manager_viewmodel.dart';
class DataPreference { class DataPreference {
static final DataPreference _instance = DataPreference._internal(); static final DataPreference _instance = DataPreference._internal();
...@@ -55,6 +56,7 @@ class DataPreference { ...@@ -55,6 +56,7 @@ class DataPreference {
_loginToken = null; _loginToken = null;
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
await prefs.remove('login_token'); await prefs.remove('login_token');
await PopupManagerViewModel.instance.reset();
} }
Future<void> clearUserProfile() async { Future<void> clearUserProfile() async {
......
...@@ -3,13 +3,18 @@ import 'package:get/get.dart'; ...@@ -3,13 +3,18 @@ import 'package:get/get.dart';
import 'package:mypoint_flutter_app/screen/affiliate/sub_widget/build_affiliate_brand.dart'; import 'package:mypoint_flutter_app/screen/affiliate/sub_widget/build_affiliate_brand.dart';
import 'package:mypoint_flutter_app/screen/affiliate/sub_widget/build_affiliate_category.dart'; import 'package:mypoint_flutter_app/screen/affiliate/sub_widget/build_affiliate_category.dart';
import 'package:mypoint_flutter_app/screen/affiliate/sub_widget/build_affiliate_product_topsale.dart'; import 'package:mypoint_flutter_app/screen/affiliate/sub_widget/build_affiliate_product_topsale.dart';
import 'package:mypoint_flutter_app/widgets/back_button.dart';
import '../../base/base_screen.dart'; import '../../base/base_screen.dart';
import '../../base/basic_state.dart'; import '../../base/basic_state.dart';
import '../../directional/directional_action_type.dart';
import '../../resources/base_color.dart'; import '../../resources/base_color.dart';
import '../../shared/router_gage.dart'; import '../../shared/router_gage.dart';
import '../../widgets/bottom_sheet_helper.dart'; import '../../widgets/bottom_sheet_helper.dart';
import '../../widgets/custom_navigation_bar.dart'; import '../../widgets/custom_navigation_bar.dart';
import '../home/header_home_viewmodel.dart'; import '../home/header_home_viewmodel.dart';
import '../popup_manager/popup_manager_screen.dart';
import '../popup_manager/popup_manager_viewmodel.dart';
import '../popup_manager/popup_runner_helper.dart';
import 'affiliate_overview.dart'; import 'affiliate_overview.dart';
import 'affiliate_popup_brands.dart'; import 'affiliate_popup_brands.dart';
import 'affiliate_tab_viewmodel.dart'; import 'affiliate_tab_viewmodel.dart';
...@@ -22,7 +27,7 @@ class AffiliateTabScreen extends BaseScreen { ...@@ -22,7 +27,7 @@ class AffiliateTabScreen extends BaseScreen {
State<AffiliateTabScreen> createState() => _AffiliateTabScreenState(); State<AffiliateTabScreen> createState() => _AffiliateTabScreenState();
} }
class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicState { class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicState, PopupOnInit {
final AffiliateTabViewModel viewModel = Get.put(AffiliateTabViewModel()); final AffiliateTabViewModel viewModel = Get.put(AffiliateTabViewModel());
final _headerHomeVM = Get.find<HeaderHomeViewModel>(); final _headerHomeVM = Get.find<HeaderHomeViewModel>();
late var _canBackButton = false; late var _canBackButton = false;
...@@ -37,6 +42,7 @@ class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicS ...@@ -37,6 +42,7 @@ class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicS
viewModel.onShowAffiliateBrandPopup = (data) { viewModel.onShowAffiliateBrandPopup = (data) {
showAffiliateBrandPopup(context, data.$1, title: data.$2); showAffiliateBrandPopup(context, data.$1, title: data.$2);
}; };
runPopupCheck(DirectionalScreenName.pointBack);
} }
@override @override
...@@ -45,8 +51,8 @@ class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicS ...@@ -45,8 +51,8 @@ class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicS
backgroundColor: Colors.grey.shade50, backgroundColor: Colors.grey.shade50,
appBar: CustomNavigationBar( appBar: CustomNavigationBar(
title: "Mua sắm", title: "Mua sắm",
showBackButton: _canBackButton,
backgroundImage: _headerHomeVM.headerData.background ?? "assets/images/bg_header_navi.png", backgroundImage: _headerHomeVM.headerData.background ?? "assets/images/bg_header_navi.png",
leftButtons: _canBackButton ? [CustomBackButton()] : [],
rightButtons: [ rightButtons: [
IconButton( IconButton(
icon: const Icon(Icons.info, color: Colors.white), icon: const Icon(Icons.info, color: Colors.white),
......
import 'package:flutter/material.dart';
import '../../widgets/custom_navigation_bar.dart';
import 'bank_account_info_model.dart';
class BankAccountDetailScreen extends StatefulWidget {
final BankAccountInfoModel model;
const BankAccountDetailScreen({super.key, required this.model});
@override
State<BankAccountDetailScreen> createState() => _BankAccountDetailScreenState();
}
class _BankAccountDetailScreenState extends State<BankAccountDetailScreen> {
late bool _isDefault;
@override
void initState() {
super.initState();
_isDefault = widget.model.isDefault ?? false;
}
@override
Widget build(BuildContext context) {
final title = widget.model.cardName?.isNotEmpty == true
? widget.model.cardName!.toUpperCase()
: (widget.model.paymentMethod ?? 'THẺ/TÀI KHOẢN');
return Scaffold(
appBar: CustomNavigationBar(title: 'Thông tin thẻ'),
body: Column(
children: [
const SizedBox(height: 8),
_CardPreview(
title: title,
maskedNumber: widget.model.cardNumber ?? '',
),
const SizedBox(height: 12),
_defaultRow(context),
const Spacer(),
_deleteButton(context),
const SizedBox(height: 12),
SafeArea(top: false, child: const SizedBox(height: 0)),
],
),
backgroundColor: const Color(0xFFF7F7F7),
);
}
Widget _defaultRow(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.symmetric(horizontal: 16),
height: 56,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
const Expanded(
child: Text(
'Đặt làm mặc định',
style: TextStyle(fontSize: 16),
),
),
Switch(
value: _isDefault,
activeColor: Colors.white,
activeTrackColor: const Color(0xFF34C759),
onChanged: (val) async {
setState(() => _isDefault = val);
// if (widget.onMakeDefault != null) {
// await widget.onMakeDefault!(widget.model);
// }
},
),
],
),
);
}
Widget _deleteButton(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: ElevatedButton(
onPressed: () async {
final ok = await showDialog<bool>(
context: context,
builder: (_) => AlertDialog(
title: const Text('Xoá thẻ?'),
content: Text(
widget.model.formMessageDelete ??
'Bạn có chắc muốn xoá thẻ/tài khoản này?',
),
actions: [
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('Huỷ')),
TextButton(onPressed: () => Navigator.pop(context, true), child: const Text('Xoá')),
],
),
);
// if (ok == true && widget.onDelete != null) {
// await widget.onDelete!(widget.model);
// if (context.mounted) Navigator.pop(context);
// }
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFF2F2F2),
elevation: 0,
minimumSize: const Size.fromHeight(52),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
),
child: const Text(
'Xoá',
style: TextStyle(color: Colors.red, fontWeight: FontWeight.w600, fontSize: 16),
),
),
);
}
}
class _CardPreview extends StatelessWidget {
final String title;
final String maskedNumber;
const _CardPreview({
required this.title,
required this.maskedNumber,
});
@override
Widget build(BuildContext context) {
return Container(
height: 180,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFF3E3A6D),
Color(0xFF6E5DAA),
Color(0xFF8E77C7),
],
),
boxShadow: const [
BoxShadow(color: Colors.black26, blurRadius: 12, offset: Offset(0, 6)),
],
),
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title, // VISA / MASTER / BANK NAME
style: const TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.w700, letterSpacing: 1.2),
),
const Spacer(),
const Text('Số thẻ', style: TextStyle(color: Colors.white70, fontSize: 13)),
const SizedBox(height: 6),
Text(
maskedNumber,
style: const TextStyle(color: Colors.white, fontSize: 22, fontWeight: FontWeight.w700, letterSpacing: 2),
),
],
),
);
}
}
\ No newline at end of file
import 'package:json_annotation/json_annotation.dart';
part 'bank_account_info_model.g.dart';
@JsonSerializable()
class BankAccountInfoModel {
int? id;
String? paymentMethod;
String? cardName;
String? cardNumber;
String? cardDate;
String? bankLogo;
String? bankName;
String? bankShortName;
String? bankCode;
bool? isDefault;
bool isSelected;
String? formBankTitle;
String? formBankNumberTitle;
String? formMessageDelete;
String? formBankBackground;
String? formBankName;
String? formBankNumber;
BankAccountInfoModel({
this.id,
this.paymentMethod,
this.cardName,
this.cardNumber,
this.cardDate,
this.bankLogo,
this.bankName,
this.bankShortName,
this.bankCode,
this.isDefault,
this.isSelected = false,
this.formBankTitle,
this.formBankNumberTitle,
this.formMessageDelete,
this.formBankBackground,
this.formBankName,
this.formBankNumber,
});
factory BankAccountInfoModel.fromJson(Map<String, dynamic> json) =>
_$BankAccountInfoModelFromJson(json);
Map<String, dynamic> toJson() => _$BankAccountInfoModelToJson(this);
}
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'bank_account_info_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
BankAccountInfoModel _$BankAccountInfoModelFromJson(
Map<String, dynamic> json,
) => BankAccountInfoModel(
id: (json['id'] as num?)?.toInt(),
paymentMethod: json['paymentMethod'] as String?,
cardName: json['cardName'] as String?,
cardNumber: json['cardNumber'] as String?,
cardDate: json['cardDate'] as String?,
bankLogo: json['bankLogo'] as String?,
bankName: json['bankName'] as String?,
bankShortName: json['bankShortName'] as String?,
bankCode: json['bankCode'] as String?,
isDefault: json['isDefault'] as bool?,
isSelected: json['isSelected'] as bool? ?? false,
formBankTitle: json['formBankTitle'] as String?,
formBankNumberTitle: json['formBankNumberTitle'] as String?,
formMessageDelete: json['formMessageDelete'] as String?,
formBankBackground: json['formBankBackground'] as String?,
formBankName: json['formBankName'] as String?,
formBankNumber: json['formBankNumber'] as String?,
);
Map<String, dynamic> _$BankAccountInfoModelToJson(
BankAccountInfoModel instance,
) => <String, dynamic>{
'id': instance.id,
'paymentMethod': instance.paymentMethod,
'cardName': instance.cardName,
'cardNumber': instance.cardNumber,
'cardDate': instance.cardDate,
'bankLogo': instance.bankLogo,
'bankName': instance.bankName,
'bankShortName': instance.bankShortName,
'bankCode': instance.bankCode,
'isDefault': instance.isDefault,
'isSelected': instance.isSelected,
'formBankTitle': instance.formBankTitle,
'formBankNumberTitle': instance.formBankNumberTitle,
'formMessageDelete': instance.formMessageDelete,
'formBankBackground': instance.formBankBackground,
'formBankName': instance.formBankName,
'formBankNumber': instance.formBankNumber,
};
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/widgets/custom_empty_widget.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart';
import '../../widgets/custom_navigation_bar.dart';
import 'bank_account_detail_screen.dart';
import 'bank_account_info_model.dart';
import 'bank_account_manager_viewmodel.dart';
class BankAccountManagerScreen extends StatefulWidget {
const BankAccountManagerScreen({super.key});
@override
State<BankAccountManagerScreen> createState() => _BankAccountManagerScreenState();
}
class _BankAccountManagerScreenState extends State<BankAccountManagerScreen> {
final BankAccountManagerViewModel viewModel = Get.put(BankAccountManagerViewModel());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomNavigationBar(title: 'Quản lý tài khoản/ thẻ'),
body: Obx(() {
if (viewModel.bankAccounts.isEmpty) {
return EmptyWidget(content: "Bạn hiện chưa có tài khoản/ thẻ đã lưu");
}
return Container(
color: const Color(0xffff9fafb),
child: ListView.separated(
padding: const EdgeInsets.symmetric(vertical: 8),
itemCount: viewModel.bankAccounts.length,
separatorBuilder: (_, __) => const Divider(height: 1, thickness: 1),
itemBuilder: (item, index) => _BankAccountItem(model: viewModel.bankAccounts.value[index], onTap: () {
Get.to(() => BankAccountDetailScreen(
model: viewModel.bankAccounts.value[index],
));
}),
),
);
}),
);
}
}
class _BankAccountItem extends StatelessWidget {
final BankAccountInfoModel model;
final VoidCallback? onTap;
const _BankAccountItem({required this.model, this.onTap});
@override
Widget build(BuildContext context) {
final title = model.bankShortName?.isNotEmpty == true ? model.bankShortName! : (model.paymentMethod ?? 'Tài khoản/thẻ');
return Material(
color: Colors.white,
child: InkWell(
onTap: onTap,
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: loadNetworkImage(url: model.bankLogo, width: 32, height: 32),
),
const SizedBox(width: 12),
Expanded(
child: Row(
children: [
Text(
'$title ${model.cardNumber}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 15, color: Color(0xFF1D2129), fontWeight: FontWeight.w500),
),
if (model.isDefault == true) ...[const SizedBox(width: 8), const _DefaultBadge()],
],
),
),
const Icon(Icons.chevron_right, color: Color(0xFF8B8E95)),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Divider(
height: 1,
thickness: 1,
color: const Color(0xFFEBEDF0),
),
),
],
),
),
);
}
}
class _DefaultBadge extends StatelessWidget {
const _DefaultBadge();
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(color: const Color(0xFFFFE9EA), borderRadius: BorderRadius.circular(16)),
child: const Text(
' Mặc định ',
style: TextStyle(fontSize: 11, color: Color(0xFFEB4B54), fontWeight: FontWeight.w600),
),
);
}
}
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import '../../base/restful_api_viewmodel.dart';
import 'bank_account_info_model.dart';
class BankAccountManagerViewModel extends RestfulApiViewModel {
final RxList<BankAccountInfoModel> bankAccounts = <BankAccountInfoModel>[].obs;
@override
onInit() {
super.onInit();
getBankAccountList();
}
getBankAccountList() async {
showLoading();
try {
Future.delayed(const Duration(milliseconds: 500));
// final result = await client.getOrderPaymentMyAccounts();
// bankAccounts.value = result.data ?? [];
bankAccounts.value = [
BankAccountInfoModel(
id: 1,
paymentMethod: "VISA",
cardName: "Nguyen Van A",
cardNumber: "1234 5678 9012 3456",
cardDate: "12/25",
bankLogo: "https://example.com/logo.png",
bankName: "Bank A",
bankShortName: "BA",
bankCode: "BA123",
isDefault: true,
isSelected: false,
),
BankAccountInfoModel(
id: 2,
paymentMethod: "MasterCard",
cardName: "Nguyen Van B",
cardNumber: "2345 6789 0123 4567",
cardDate: "11/24",
bankLogo: "https://example.com/logo2.png",
bankName: "Bank B",
bankShortName: "BB",
bankCode: "BB456",
isDefault: false,
isSelected: false,
),
BankAccountInfoModel(
id: 3,
paymentMethod: "ATM",
cardName: "Nguyen Van C",
cardNumber: "3456 7890 1234 5678",
cardDate: "10/23",
bankLogo: "https://example.com/logo3.png",
bankName: "Bank C",
bankShortName: "BC",
bankCode: "BC789",
isDefault: false,
isSelected: false,
),
];
hideLoading();
} catch (error) {
hideLoading();
print("Error fetching bank accounts: $error");
}
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_contacts/flutter_contacts.dart';
import 'package:mypoint_flutter_app/resources/base_color.dart';
/// Mở bottom sheet hiển thị danh sách danh bạ để chọn
Future<Contact?> showContactPicker(BuildContext context) async {
final granted = await FlutterContacts.requestPermission();
if (!granted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Không có quyền truy cập danh bạ')),
);
return null;
}
// Lấy contacts (kèm số điện thoại)
final contacts = await FlutterContacts.getContacts(
withProperties: true,
withPhoto: false,
);
if (contacts.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Không có danh bạ nào')),
);
return null;
}
// Mở bottom sheet để user chọn
return showModalBottomSheet<Contact>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (_) => _ContactPickerSheet(contacts: contacts),
);
}
class _ContactPickerSheet extends StatefulWidget {
final List<Contact> contacts;
const _ContactPickerSheet({required this.contacts});
@override
State<_ContactPickerSheet> createState() => _ContactPickerSheetState();
}
class _ContactPickerSheetState extends State<_ContactPickerSheet> {
late List<Contact> _filtered;
final _searchCtrl = TextEditingController();
@override
void initState() {
super.initState();
_filtered = widget.contacts;
_searchCtrl.addListener(_onSearchChanged);
}
@override
void dispose() {
_searchCtrl.removeListener(_onSearchChanged);
_searchCtrl.dispose();
super.dispose();
}
void _onSearchChanged() {
final q = _searchCtrl.text.trim().toLowerCase();
if (q.isEmpty) {
setState(() => _filtered = widget.contacts);
} else {
setState(() {
_filtered = widget.contacts.where((c) {
final name = c.displayName.toLowerCase();
final phones = c.phones.map((e) => e.number).join(' ').toLowerCase();
return name.contains(q) || phones.contains(q);
}).toList();
});
}
}
@override
Widget build(BuildContext context) {
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
return SafeArea(
top: false,
child: Padding(
padding: EdgeInsets.only(bottom: bottomInset),
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.75,
child: Column(
children: [
const SizedBox(height: 8),
Container(width: 40, height: 4, decoration: BoxDecoration(color: Colors.black26, borderRadius: BorderRadius.circular(2))),
const SizedBox(height: 12),
const Text('Chọn liên hệ', style: TextStyle(fontWeight: FontWeight.w700, fontSize: 18)),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: TextField(
controller: _searchCtrl,
decoration: InputDecoration(
hintText: 'Tìm tên hoặc số',
prefixIcon: const Icon(Icons.search),
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade400, width: 1),),
),
),
),
const SizedBox(height: 8),
Expanded(
child: ListView.separated(
itemCount: _filtered.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) {
final c = _filtered[index];
final subtitle = c.phones.isNotEmpty ? c.phones.first.number : 'Không có số';
return ListTile(
leading: CircleAvatar(
backgroundColor: BaseColor.primary400,
child: Text(c.displayName.isNotEmpty ? c.displayName[0].toUpperCase() : '?'), // màu nền cho avatar
),
title: Text(c.displayName),
subtitle: Text(subtitle),
onTap: () => Navigator.of(context).pop<Contact>(c), // trả về contact
);
},
),
),
],
),
),
),
);
}
}
import 'package:contacts_service/contacts_service.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/extensions/num_extension.dart'; import 'package:mypoint_flutter_app/extensions/num_extension.dart';
...@@ -13,6 +12,7 @@ import '../../preference/point/point_manager.dart'; ...@@ -13,6 +12,7 @@ import '../../preference/point/point_manager.dart';
import '../../resources/base_color.dart'; import '../../resources/base_color.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 '../contacts/contacts_picker.dart';
import '../topup/brand_select_sheet_widget.dart'; import '../topup/brand_select_sheet_widget.dart';
import 'data_network_service_viewmodel.dart'; import 'data_network_service_viewmodel.dart';
...@@ -341,22 +341,19 @@ class _DataNetworkServiceScreenState extends BaseState<DataNetworkServiceScreen> ...@@ -341,22 +341,19 @@ class _DataNetworkServiceScreenState extends BaseState<DataNetworkServiceScreen>
Future<void> pickContact(BuildContext context) async { Future<void> pickContact(BuildContext context) async {
try { try {
// Gọi sẽ tự động hiện dialog yêu cầu quyền (nếu cần) final contact = await showContactPicker(context);
final Contact? contact = await ContactsService.openDeviceContactPicker(); if (contact == null) return;
if (contact != null && contact.phones != null && contact.phones!.isNotEmpty) { if (contact.phones.isEmpty) return;
String phone = contact.phones!.first.value ?? ''; String phone = contact.phones.first.number;
phone = phone.replaceAll(RegExp(r'[\s\-\(\)]'), ''); phone = phone.replaceAll(RegExp(r'[\s\-\(\)]'), '');
_phoneController.text = phone; _phoneController.text = phone;
_viewModel.phoneNumber.value = phone; _viewModel.phoneNumber.value = phone;
_viewModel.checkMobileNetwork(); _viewModel.checkMobileNetwork();
} else {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text("Không tìm thấy số điện thoại hợp lệ")));
}
} catch (e) { } catch (e) {
print("❌ Lỗi khi truy cập danh bạ: $e"); debugPrint('❌ pickContact error: $e');
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Không thể truy cập danh bạ"))); ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Không thể truy cập danh bạ')),
);
} }
} }
} }
...@@ -7,6 +7,7 @@ import '../../base/base_screen.dart'; ...@@ -7,6 +7,7 @@ import '../../base/base_screen.dart';
import '../../base/basic_state.dart'; import '../../base/basic_state.dart';
import '../../resources/base_color.dart'; import '../../resources/base_color.dart';
import '../../shared/router_gage.dart'; import '../../shared/router_gage.dart';
import '../../widgets/custom_navigation_bar.dart';
import 'faqs_viewmodel.dart'; import 'faqs_viewmodel.dart';
class FAQScreen extends BaseScreen { class FAQScreen extends BaseScreen {
...@@ -22,13 +23,7 @@ class _FAQScreenState extends BaseState<FAQScreen> with BasicState { ...@@ -22,13 +23,7 @@ class _FAQScreenState extends BaseState<FAQScreen> with BasicState {
@override @override
Widget createBody() { Widget createBody() {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: CustomNavigationBar(title: "Câu hỏi thường gặp"),
leading: CustomBackButton(),
title: const Text("Câu hỏi thường gặp", style: TextStyle(fontWeight: FontWeight.bold)),
backgroundColor: Colors.white,
foregroundColor: Colors.black,
elevation: 0,
),
body: Column( body: Column(
children: [ children: [
Obx(() { Obx(() {
......
...@@ -3,10 +3,15 @@ import 'package:get/get.dart'; ...@@ -3,10 +3,15 @@ import 'package:get/get.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart'; import 'package:mypoint_flutter_app/widgets/image_loader.dart';
import '../../base/base_screen.dart'; import '../../base/base_screen.dart';
import '../../base/basic_state.dart'; import '../../base/basic_state.dart';
import '../../directional/directional_action_type.dart';
import '../../shared/router_gage.dart'; import '../../shared/router_gage.dart';
import '../../widgets/back_button.dart';
import '../../widgets/custom_empty_widget.dart'; import '../../widgets/custom_empty_widget.dart';
import '../../widgets/custom_navigation_bar.dart'; import '../../widgets/custom_navigation_bar.dart';
import '../home/header_home_viewmodel.dart'; import '../home/header_home_viewmodel.dart';
import '../popup_manager/popup_manager_screen.dart';
import '../popup_manager/popup_manager_viewmodel.dart';
import '../popup_manager/popup_runner_helper.dart';
import 'game_tab_viewmodel.dart'; import 'game_tab_viewmodel.dart';
class GameTabScreen extends BaseScreen { class GameTabScreen extends BaseScreen {
...@@ -16,7 +21,7 @@ class GameTabScreen extends BaseScreen { ...@@ -16,7 +21,7 @@ class GameTabScreen extends BaseScreen {
State<GameTabScreen> createState() => _GameTabScreenState(); State<GameTabScreen> createState() => _GameTabScreenState();
} }
class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState { class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState, PopupOnInit {
final _headerHomeVM = Get.find<HeaderHomeViewModel>(); final _headerHomeVM = Get.find<HeaderHomeViewModel>();
final GameTabViewModel _viewModel = Get.put(GameTabViewModel()); final GameTabViewModel _viewModel = Get.put(GameTabViewModel());
final LayerLink _layerLink = LayerLink(); final LayerLink _layerLink = LayerLink();
...@@ -45,6 +50,7 @@ class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState { ...@@ -45,6 +50,7 @@ class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState {
Get.toNamed(gameCardScreen, arguments: data); Get.toNamed(gameCardScreen, arguments: data);
} }
}; };
runPopupCheck(DirectionalScreenName.productVoucher);
} }
@override @override
...@@ -52,7 +58,7 @@ class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState { ...@@ -52,7 +58,7 @@ class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState {
return Scaffold( return Scaffold(
appBar: CustomNavigationBar( appBar: CustomNavigationBar(
title: "Games", title: "Games",
showBackButton: _canBackButton, leftButtons: _canBackButton ? [CustomBackButton()] : [],
backgroundImage: _headerHomeVM.headerData.background ?? "assets/images/bg_header_navi.png", backgroundImage: _headerHomeVM.headerData.background ?? "assets/images/bg_header_navi.png",
rightButtons: [ rightButtons: [
CompositedTransformTarget( CompositedTransformTarget(
......
...@@ -6,9 +6,12 @@ import 'package:mypoint_flutter_app/screen/home/custom_widget/header_home.dart'; ...@@ -6,9 +6,12 @@ import 'package:mypoint_flutter_app/screen/home/custom_widget/header_home.dart';
import 'package:mypoint_flutter_app/screen/home/custom_widget/product_grid_widget.dart'; import 'package:mypoint_flutter_app/screen/home/custom_widget/product_grid_widget.dart';
import 'package:mypoint_flutter_app/screen/home/pipi_detail_screen.dart'; import 'package:mypoint_flutter_app/screen/home/pipi_detail_screen.dart';
import 'package:mypoint_flutter_app/shared/router_gage.dart'; import 'package:mypoint_flutter_app/shared/router_gage.dart';
import '../../directional/directional_action_type.dart';
import '../../preference/point/header_home_model.dart'; import '../../preference/point/header_home_model.dart';
import '../popup_manager/popup_manager_model.dart'; import '../popup_manager/popup_manager_model.dart';
import '../popup_manager/popup_manager_popup.dart'; import '../popup_manager/popup_manager_screen.dart';
import '../popup_manager/popup_manager_viewmodel.dart';
import '../popup_manager/popup_runner_helper.dart';
import '../voucher/sub_widget/voucher_section_title.dart'; import '../voucher/sub_widget/voucher_section_title.dart';
import 'custom_widget/achievement_carousel_widget.dart'; import 'custom_widget/achievement_carousel_widget.dart';
import 'custom_widget/affiliate_brand_grid_widget.dart'; import 'custom_widget/affiliate_brand_grid_widget.dart';
...@@ -30,13 +33,15 @@ class HomeScreen extends StatefulWidget { ...@@ -30,13 +33,15 @@ class HomeScreen extends StatefulWidget {
State<HomeScreen> createState() => _HomeScreenState(); State<HomeScreen> createState() => _HomeScreenState();
} }
class _HomeScreenState extends State<HomeScreen> { class _HomeScreenState extends State<HomeScreen> with PopupOnInit {
final HomeTabViewModel _viewModel = Get.put(HomeTabViewModel()); final HomeTabViewModel _viewModel = Get.put(HomeTabViewModel());
final _headerHomeVM = Get.find<HeaderHomeViewModel>(); final _headerHomeVM = Get.find<HeaderHomeViewModel>();
final RxBool _showHover = true.obs; final RxBool _showHover = true.obs;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
runPopupCheck(DirectionalScreenName.home);
} }
Widget _buildSliverHeader(double heightHeader) { Widget _buildSliverHeader(double heightHeader) {
...@@ -225,44 +230,15 @@ class _HomeScreenState extends State<HomeScreen> { ...@@ -225,44 +230,15 @@ class _HomeScreenState extends State<HomeScreen> {
} }
void _handleHoverViewTap() { void _handleHoverViewTap() {
showPopup( final result = _viewModel.hoverData.value?.direction?.begin();
context, if (result != true) {
modelPopup: PopupManagerModel( showModalBottomSheet(
timeCountDown: '10', context: context,
id: 'popup123', backgroundColor: Colors.transparent,
requestId: 'req_abc', isScrollControlled: true,
popupTitleTemplate: 'Khuyến mãi đặc biệt', builder: (_) => PipiDetailScreen(),
popupBodyTemplate: 'Giảm 50% cho đơn hàng hôm nay.', );
imageURL: 'https://picsum.photos/1200/800', }
clickActionType: 'VIEW_GIFT',
clickActionParam: 'gift_999',
),
onNavigate: ({
required String name,
required String identifier,
String? title,
String? body,
}) {
// Tự nối qua router của bạn
// ví dụ:
// context.pushNamed(name, extra: {'id': identifier, 'title': title, 'body': body});
debugPrint('Navigate -> $name, id=$identifier');
},
onDismissed: () {
// Thay cho NotificationCenter.dismissPopup
debugPrint('Popup dismissed');
},
);
// final result = _viewModel.hoverData.value?.direction?.begin();
// if (result != true) {
// showModalBottomSheet(
// context: context,
// backgroundColor: Colors.transparent,
// isScrollControlled: true,
// builder: (_) => PipiDetailScreen(),
// );
// }
} }
void _handleCloseHoverView() { void _handleCloseHoverView() {
......
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../widgets/custom_app_bar.dart'; import '../../widgets/custom_navigation_bar.dart';
import 'location_address_viewmodel.dart'; import 'location_address_viewmodel.dart';
enum LocationAddressType { enum LocationAddressType {
...@@ -54,7 +54,7 @@ class _LocationAddressScreenState extends State<LocationAddressScreen> { ...@@ -54,7 +54,7 @@ class _LocationAddressScreenState extends State<LocationAddressScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: CustomAppBar.back(title: "Location Address"), appBar: CustomNavigationBar(title: "Địa chỉ"),
// backgroundColor: Colors.transparent, // backgroundColor: Colors.transparent,
body: SafeArea( body: SafeArea(
child: Column( child: Column(
......
...@@ -53,7 +53,7 @@ class _MainTabScreenState extends State<MainTabScreen> { ...@@ -53,7 +53,7 @@ class _MainTabScreenState extends State<MainTabScreen> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
_buildTabItem(icon: Icons.home, label: 'Trang chủ', index: 0), _buildTabItem(icon: Icons.home, label: 'Trang chủ', index: 0),
_buildTabItem(icon: Icons.star, label: 'Ưu đãi', index: 1), _buildTabItem(icon: Icons.confirmation_number_sharp, label: 'Ưu đãi', index: 1),
_buildTabItem(icon: null, label: 'Game', index: 2, gift: 'assets/images/ic_tabbar_game.gif'), _buildTabItem(icon: null, label: 'Game', index: 2, gift: 'assets/images/ic_tabbar_game.gif'),
_buildTabItem(icon: Icons.shopping_cart, label: 'Mua sắm', index: 3), _buildTabItem(icon: Icons.shopping_cart, label: 'Mua sắm', index: 3),
_buildTabItem(icon: Icons.person, label: 'Cá nhân', index: 4), _buildTabItem(icon: Icons.person, label: 'Cá nhân', index: 4),
...@@ -95,7 +95,7 @@ class _MainTabScreenState extends State<MainTabScreen> { ...@@ -95,7 +95,7 @@ class _MainTabScreenState extends State<MainTabScreen> {
style: TextStyle( style: TextStyle(
color: isSelected ? Colors.white : Colors.white70, color: isSelected ? Colors.white : Colors.white70,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
fontSize: 12, fontSize: 14,
), ),
), ),
], ],
......
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