Commit fa01087d authored by DatHV's avatar DatHV
Browse files

update brand, detail..

parent c8abf95b
...@@ -63,4 +63,15 @@ class APIPaths { ...@@ -63,4 +63,15 @@ class APIPaths {
static const String locationDistrictGetList = "/locationDistrictGetList/1.0.0"; static const String locationDistrictGetList = "/locationDistrictGetList/1.0.0";
static const String locationProvinceGetList = "/locationProvinceGetList/1.0.0"; static const String locationProvinceGetList = "/locationProvinceGetList/1.0.0";
static const String getMembershipLevelInfo = "/primaryMembershipLevelGetList/2.0.0"; static const String getMembershipLevelInfo = "/primaryMembershipLevelGetList/2.0.0";
static const String getTopUpBrands = "/product/api/v2.0/topup-brands";
static const String checkMobileNetwork = "/order/api/v1.0/check-mobile-network/";
static const String productMobileCardGetList = "/productMobileCardGetList/1.0.0";
static const String redeemMobileCard = "/productMobileCardRedeemRequest/1.0.0";
static const String getMobileCardCode = "/myProductItemGetDetail/1.0.0";
static const String productTopUpsBrands = "/product/api/v2.0/topups/brands";
static const String getNetworkProducts = "/product/api/v2.0/topups";
static const String redeemProductTopUps = "/product/api/v2.0/topups/redeem";
static const String historyCashBackPoint = "/cashback/orders";
static const String historyCashBackPointDetail = "/cashback/order/details";
static const String affiliateBrandGetDetail = "/affiliateBrandGetDetail/1.0.0";
} }
\ No newline at end of file
...@@ -43,7 +43,7 @@ enum DirectionalScreenName { ...@@ -43,7 +43,7 @@ enum DirectionalScreenName {
memberShip, memberShip,
ranking, ranking,
mobileTopup, mobileTopup,
mobileTopupData, carrierPackage,
topup, topup,
register, register,
gifts, gifts,
...@@ -208,7 +208,7 @@ extension DirectionalScreenNameExtension on DirectionalScreenName { ...@@ -208,7 +208,7 @@ extension DirectionalScreenNameExtension on DirectionalScreenName {
return "APP_SCREEN_RANKING_PROGRAM"; return "APP_SCREEN_RANKING_PROGRAM";
case DirectionalScreenName.mobileTopup: case DirectionalScreenName.mobileTopup:
return "APP_SCREEN_PRODUCT_MOBILE_TOPUP"; return "APP_SCREEN_PRODUCT_MOBILE_TOPUP";
case DirectionalScreenName.mobileTopupData: case DirectionalScreenName.carrierPackage:
return "APP_SCREEN_MOBILE_TOPUP_DATA"; return "APP_SCREEN_MOBILE_TOPUP_DATA";
case DirectionalScreenName.topup: case DirectionalScreenName.topup:
return "APP_SCREEN_TOPUP"; return "APP_SCREEN_TOPUP";
...@@ -319,7 +319,7 @@ extension DirectionalScreenNameExtension on DirectionalScreenName { ...@@ -319,7 +319,7 @@ extension DirectionalScreenNameExtension on DirectionalScreenName {
static DirectionalScreenName fromRawValue(String rawValue) { static DirectionalScreenName fromRawValue(String rawValue) {
return DirectionalScreenName.values.firstWhere( return DirectionalScreenName.values.firstWhere(
(e) => e.rawValue == rawValue, (e) => e.rawValue == rawValue.trim(),
orElse: () => DirectionalScreenName.unknown, orElse: () => DirectionalScreenName.unknown,
); );
} }
......
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:uuid/uuid.dart';
import '../screen/webview/web_view_screen.dart'; import '../screen/webview/web_view_screen.dart';
import '../shared/router_gage.dart'; import '../shared/router_gage.dart';
import 'directional_action_type.dart'; import 'directional_action_type.dart';
...@@ -18,6 +22,13 @@ class DirectionalScreen { ...@@ -18,6 +22,13 @@ class DirectionalScreen {
return DirectionalScreen._(clickActionType: clickActionType, clickActionParam: clickActionParam); return DirectionalScreen._(clickActionType: clickActionType, clickActionParam: clickActionParam);
} }
static DirectionalScreen? buildByName({required DirectionalScreenName name, String? clickActionParam}) {
if (name == DirectionalScreenName.appScreen) {
return DirectionalScreen._(clickActionType: clickActionParam, clickActionParam: null);
}
return DirectionalScreen._(clickActionType: name.rawValue, clickActionParam: clickActionParam);
}
factory DirectionalScreen.fromJson(Map<String, dynamic> json) => DirectionalScreen._( factory DirectionalScreen.fromJson(Map<String, dynamic> json) => DirectionalScreen._(
clickActionType: json['click_action_type'] as String?, clickActionType: json['click_action_type'] as String?,
clickActionParam: json['click_action_param'] as String?, clickActionParam: json['click_action_param'] as String?,
...@@ -59,7 +70,7 @@ class DirectionalScreen { ...@@ -59,7 +70,7 @@ class DirectionalScreen {
case DirectionalScreenName.gifts: case DirectionalScreenName.gifts:
Get.toNamed(achievementListScreen); Get.toNamed(achievementListScreen);
return true; return true;
case DirectionalScreenName.pointHunting: case DirectionalScreenName.pointHunting || DirectionalScreenName.achievement:
Get.toNamed(achievementListScreen, arguments: true); Get.toNamed(achievementListScreen, arguments: true);
return true; return true;
case DirectionalScreenName.myPurchaseItems || DirectionalScreenName.myPurchaseItemsWaitingList: case DirectionalScreenName.myPurchaseItems || DirectionalScreenName.myPurchaseItemsWaitingList:
...@@ -68,9 +79,86 @@ class DirectionalScreen { ...@@ -68,9 +79,86 @@ class DirectionalScreen {
case DirectionalScreenName.orderMenu: case DirectionalScreenName.orderMenu:
Get.toNamed(orderMenuScreen); Get.toNamed(orderMenuScreen);
return true; return true;
case DirectionalScreenName.topup || DirectionalScreenName.mobileTopup:
Get.toNamed(phoneTopUpScreen);
return true;
case DirectionalScreenName.carrierPackage:
BaseWebViewInput input = BaseWebViewInput(url: clickActionParam ?? "");
Get.toNamed(baseWebViewScreen, arguments: input);
return true;
case DirectionalScreenName.productMobileCard:
Get.toNamed(productMobileCardScreen);
return true;
case DirectionalScreenName.simService:
Get.toNamed(dataNetworkServiceScreen);
return true;
case DirectionalScreenName.pointBack:
Get.toNamed(affiliateTabScreen, arguments: {"can_back_button": true});
return true;
case DirectionalScreenName.gamesBundle:
Get.toNamed(gameTabScreen, arguments: {"can_back_button": true});
return true;
case DirectionalScreenName.vnTraPackage || DirectionalScreenName.familyMedon:
Get.toNamed(voucherDetailScreen, arguments: {"productId": int.parse(clickActionParam ?? "")});
return true;
case DirectionalScreenName.viewDeepLink || DirectionalScreenName.viewDeepLinkInApp:
if ((clickActionParam ?? "").isEmpty) return true;
const replaceId1 = '{{customerId}}';
const replaceId2 = '{customerId}';
final customerId = DataPreference.instance.profile?.workerSite?.customerId ?? "";
String urlString = clickActionParam!.urlDecoded;
if (urlString.contains(replaceId1)) {
urlString = urlString.replaceAll(replaceId1, customerId);
}
if (urlString.contains(replaceId2)) {
urlString = urlString.replaceAll(replaceId2, customerId);
}
// Parse thành Uri và thêm query aff_sub3
Uri? uri = Uri.tryParse(urlString);
if (uri == null) return true;
final requestId = const Uuid().v4(); // Cần package `uuid`
final updatedUri = uri.replace(queryParameters: {
...uri.queryParameters,
'aff_sub3': requestId,
});
LaunchMode mode = type == DirectionalScreenName.viewDeepLink ? LaunchMode.externalApplication : LaunchMode.platformDefault;
forceOpen(url: updatedUri, mode: mode);
return true;
case DirectionalScreenName.refundHistory:
Get.toNamed(historyPointCashBackScreen);
return true;
default: default:
print("Không nhận diện được action type: $clickActionType"); print("Không nhận diện được action type: $clickActionType");
return false; return false;
} }
} }
} }
Future<bool> forceOpen({required Uri url, LaunchMode mode = LaunchMode.platformDefault}) async {
print("force open ${url.toString()}");
if (await canLaunchUrl(url)) {
await launchUrl(
url,
mode: LaunchMode.externalApplication,
webViewConfiguration: const WebViewConfiguration(
enableJavaScript: true,
headers: <String, String>{},
),
);
return true;
}
return false;
}
Future<void> openAppStore(String url) async {
final uri = Uri.parse(url);
if (await canLaunchUrl(uri)) {
await launchUrl(
uri,
mode: LaunchMode.externalApplication, // ⚠️ Phải dùng mode này
);
} else {
debugPrint("⚠️ Không thể mở URL: $url");
}
}
\ No newline at end of file
...@@ -33,8 +33,8 @@ enum CurrencyUnit { ...@@ -33,8 +33,8 @@ enum CurrencyUnit {
eur(' EUR'), eur(' EUR'),
none(' '), none(' '),
noneSpace(''), noneSpace(''),
VND(' VNĐ'),
point(' điểm'); point(' điểm');
final String symbol; final String symbol;
const CurrencyUnit(this.symbol); const CurrencyUnit(this.symbol);
} }
\ No newline at end of file
...@@ -9,6 +9,10 @@ extension PhoneValidator on String { ...@@ -9,6 +9,10 @@ extension PhoneValidator on String {
} }
} }
extension StringUrlExtension on String {
String get urlDecoded => Uri.decodeFull(this);
}
extension StringConvert on String { extension StringConvert on String {
String toSha256() { String toSha256() {
var bytes1 = utf8.encode(this); var bytes1 = utf8.encode(this);
......
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/preference/data_preference.dart'; import 'package:mypoint_flutter_app/preference/data_preference.dart';
import 'package:mypoint_flutter_app/preference/point/point_manager.dart';
import 'package:mypoint_flutter_app/shared/router_gage.dart'; import 'package:mypoint_flutter_app/shared/router_gage.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await DataPreference.instance.init(); await DataPreference.instance.init();
await UserPointManager().fetchUserPoint();
runApp(const MyApp()); runApp(const MyApp());
} }
......
...@@ -13,8 +13,15 @@ import '../model/auth/login_token_response_model.dart'; ...@@ -13,8 +13,15 @@ import '../model/auth/login_token_response_model.dart';
import '../model/auth/profile_response_model.dart'; import '../model/auth/profile_response_model.dart';
import '../model/update_response_model.dart'; import '../model/update_response_model.dart';
import '../preference/point/header_home_model.dart'; import '../preference/point/header_home_model.dart';
import '../screen/affiliate/model/affiliate_brand_model.dart';
import '../screen/affiliate/model/affiliate_category_model.dart';
import '../screen/affiliate/model/affiliate_product_top_sale_model.dart';
import '../screen/affiliate/model/cashback_overview_model.dart';
import '../screen/affiliate_brand_detail/models/affiliate_brand_detail_model.dart';
import '../screen/data_network_service/product_network_data_model.dart';
import '../screen/faqs/faqs_model.dart'; import '../screen/faqs/faqs_model.dart';
import '../screen/game/models/game_bundle_item_model.dart'; import '../screen/game/models/game_bundle_item_model.dart';
import '../screen/history_point_cashback/models/history_point_cashback_model.dart';
import '../screen/home/models/achievement_model.dart'; import '../screen/home/models/achievement_model.dart';
import '../screen/home/models/hover_data_model.dart'; import '../screen/home/models/hover_data_model.dart';
import '../screen/home/models/main_section_config_model.dart'; import '../screen/home/models/main_section_config_model.dart';
...@@ -24,6 +31,9 @@ import '../screen/home/models/pipi_detail_model.dart'; ...@@ -24,6 +31,9 @@ import '../screen/home/models/pipi_detail_model.dart';
import '../screen/location_address/models/district_address_model.dart'; import '../screen/location_address/models/district_address_model.dart';
import '../screen/location_address/models/province_address_model.dart'; import '../screen/location_address/models/province_address_model.dart';
import '../screen/membership/models/membership_info_response.dart'; import '../screen/membership/models/membership_info_response.dart';
import '../screen/mobile_card/models/mobile_service_redeem_data.dart';
import '../screen/mobile_card/models/product_mobile_card_model.dart';
import '../screen/mobile_card/models/usable_voucher_model.dart';
import '../screen/notification/models/category_notify_item_model.dart'; import '../screen/notification/models/category_notify_item_model.dart';
import '../screen/notification/models/notification_detail_model.dart'; import '../screen/notification/models/notification_detail_model.dart';
import '../screen/notification/models/notification_list_data_model.dart'; import '../screen/notification/models/notification_list_data_model.dart';
...@@ -34,11 +44,8 @@ import '../screen/otp/model/otp_verify_response_model.dart'; ...@@ -34,11 +44,8 @@ import '../screen/otp/model/otp_verify_response_model.dart';
import '../screen/pageDetail/model/campaign_detail_model.dart'; import '../screen/pageDetail/model/campaign_detail_model.dart';
import '../screen/pageDetail/model/detail_page_rule_type.dart'; import '../screen/pageDetail/model/detail_page_rule_type.dart';
import '../screen/register_campaign/model/registration_form_package_model.dart'; import '../screen/register_campaign/model/registration_form_package_model.dart';
import '../screen/shopping/model/affiliate_brand_model.dart';
import '../screen/shopping/model/affiliate_category_model.dart';
import '../screen/shopping/model/affiliate_product_top_sale_model.dart';
import '../screen/shopping/model/cashback_overview_model.dart';
import '../screen/splash/splash_screen_viewmodel.dart'; import '../screen/splash/splash_screen_viewmodel.dart';
import '../screen/topup/models/brand_network_model.dart';
import '../screen/transaction/history/transaction_history_model.dart'; import '../screen/transaction/history/transaction_history_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';
...@@ -46,7 +53,9 @@ import '../screen/transaction/model/payment_method_model.dart'; ...@@ -46,7 +53,9 @@ import '../screen/transaction/model/payment_method_model.dart';
import '../screen/transaction/model/payment_method_type.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/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/search_product_response_model.dart'; import '../screen/voucher/models/search_product_response_model.dart';
import 'model_maker.dart'; import 'model_maker.dart';
...@@ -347,9 +356,12 @@ extension RestfullAPIClientAllApi on RestfulAPIClient { ...@@ -347,9 +356,12 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
}); });
} }
Future<BaseResponseModel<List<AffiliateBrandModel>>> affiliateBrandGetList() async { Future<BaseResponseModel<List<AffiliateBrandModel>>> affiliateBrandGetList({String? categoryCode}) async {
String? token = DataPreference.instance.token ?? ""; String? token = DataPreference.instance.token ?? "";
final body = {"access_token": token}; final body = {"access_token": token};
if ((categoryCode ?? '').isNotEmpty) {
body['category_code'] = categoryCode!;
}
return requestNormal(APIPaths.affiliateBrandGetList, Method.POST, body, (data) { return requestNormal(APIPaths.affiliateBrandGetList, Method.POST, body, (data) {
final list = data as List<dynamic>; final list = data as List<dynamic>;
return list.map((e) => AffiliateBrandModel.fromJson(e)).toList(); return list.map((e) => AffiliateBrandModel.fromJson(e)).toList();
...@@ -458,10 +470,11 @@ extension RestfullAPIClientAllApi on RestfulAPIClient { ...@@ -458,10 +470,11 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
int? paymentTokenId, int? paymentTokenId,
bool? saveToken, bool? saveToken,
String? metadata, String? metadata,
String? targetPhoneNumber,
}) async { }) async {
final items = final items =
products.map((product) { products.map((product) {
return {'product_id': product.id, 'product_type': product.type ?? '', 'quantity': quantity}; return {'product_id': product.id, 'product_type': product.type ?? '', 'quantity': quantity, 'target_phone_number': targetPhoneNumber ?? ''};
}).toList(); }).toList();
final Map<String, dynamic> params = {'request_id': requestId, 'items': items, 'flow': '21'}; final Map<String, dynamic> params = {'request_id': requestId, 'items': items, 'flow': '21'};
// flash_sale // flash_sale
...@@ -572,4 +585,97 @@ extension RestfullAPIClientAllApi on RestfulAPIClient { ...@@ -572,4 +585,97 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
return MembershipInfoResponse.fromJson(data as Json); return MembershipInfoResponse.fromJson(data as Json);
}); });
} }
Future<BaseResponseModel<List<ProductBrandModel>>> getTopUpBrands(ProductType type) async {
final body = {"type": type.value};
return requestNormal(APIPaths.getTopUpBrands, Method.GET, body, (data) {
final list = data as List<dynamic>;
return list.map((e) => ProductBrandModel.fromJson(e)).toList();
});
}
Future<BaseResponseModel<List<ProductBrandModel>>> productTopUpBrands() async {
final body = {"topup_type": "PRODUCT_MODEL_MOBILE_SERVICE", "page_size": "999", "page_index": 0};
return requestNormal(APIPaths.getTopUpBrands, Method.GET, body, (data) {
final list = data as List<dynamic>;
return list.map((e) => ProductBrandModel.fromJson(e)).toList();
});
}
// productTopUpsBrands
Future<BaseResponseModel<BrandNameCheckResponse>> checkMobileNetwork(String phone) async {
final body = {"phone": phone};
return requestNormal(APIPaths.checkMobileNetwork, Method.GET, body, (data) {
return BrandNameCheckResponse.fromJson(data as Json);
});
}
Future<BaseResponseModel<ProductMobileCardResponse>> productMobileCardGetList() async {
String? token = DataPreference.instance.token ?? "";
final body = {"start": 0, "limit": 200, "access_token": token};
return requestNormal(APIPaths.productMobileCardGetList, Method.POST, body, (data) {
return ProductMobileCardResponse.fromJson(data as Json);
});
}
Future<BaseResponseModel<MobileServiceRedeemData>> redeemMobileCard(String productId) async {
String? token = DataPreference.instance.token ?? "";
final body = {"product_id": productId, "get_customer_balance": "1", "access_token": token};
return requestNormal(APIPaths.redeemMobileCard, Method.POST, body, (data) {
return MobileServiceRedeemData.fromJson(data as Json);
});
}
Future<BaseResponseModel<RedeemProductResponseModel>> getMobileCardCode(String itemId) async {
String? token = DataPreference.instance.token ?? "";
final body = {"product_item_id": itemId, "access_token": token};
return requestNormal(APIPaths.redeemMobileCard, Method.POST, body, (data) {
return RedeemProductResponseModel.fromJson(data as Json);
});
}
Future<BaseResponseModel<List<TopUpNetworkDataModel>>> getNetworkProducts(String brandId) async {
final body = {
"brand_id": brandId,
"topup_type": "PRODUCT_MODEL_MOBILE_SERVICE",
"page_size": "999",
"page_index": 0,
};
return requestNormal(APIPaths.getNetworkProducts, Method.GET, body, (data) {
final list = data as List<dynamic>;
return list.map((e) => TopUpNetworkDataModel.fromJson(e)).toList();
});
}
Future<BaseResponseModel<EmptyCodable>> redeemProductTopUps(String productId, String phoneNumber) async {
String? token = DataPreference.instance.token ?? "";
final body = {
"access_token": token,
"product_id": productId,
"quantity": 1,
"phone_number": phoneNumber,
};
return requestNormal(APIPaths.redeemProductTopUps, Method.POST, body, (data) {
return EmptyCodable.fromJson(data as Json);
});
}
Future<BaseResponseModel<HistoryPointCashBackResponse>> historyPointCashBackRequest(Json body) async {
String? token = DataPreference.instance.token ?? "";
body["access_token"] = token;
return requestNormal(APIPaths.historyCashBackPoint, Method.GET, body, (data) {
return HistoryPointCashBackResponse.fromJson(data as Json);
});
}
Future<BaseResponseModel<AffiliateBrandDetailModel>> getAffiliateBrandDetail(String brandId) async {
String? token = DataPreference.instance.token ?? "";
final body = {
"access_token": token,
"brand_id": brandId,
};
return requestNormal(APIPaths.affiliateBrandGetDetail, Method.POST, body, (data) {
return AffiliateBrandDetailModel.fromJson(data as Json);
});
}
} }
\ No newline at end of file
import 'package:shared_preferences/shared_preferences.dart';
class ContactStorageService {
static const _keyUsedContacts = 'used_contacts';
/// Lưu 1 số điện thoại vào danh sách đã dùng
Future<void> saveUsedContact(String phone) async {
final prefs = await SharedPreferences.getInstance();
final list = prefs.getStringList(_keyUsedContacts) ?? [];
/// Nếu chưa có thì thêm vào đầu
if (!list.contains(phone)) {
list.add(phone);
await prefs.setStringList(_keyUsedContacts, list);
}
}
/// Lấy danh sách số đã dùng
Future<List<String>> getUsedContacts() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getStringList(_keyUsedContacts) ?? [];
}
/// Xóa toàn bộ danh sách đã dùng (nếu cần)
Future<void> clearUsedContacts() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_keyUsedContacts);
}
}
...@@ -23,7 +23,13 @@ class DataPreference { ...@@ -23,7 +23,13 @@ class DataPreference {
_profile = ProfileResponseModel.fromJson(jsonDecode(profileJson)); _profile = ProfileResponseModel.fromJson(jsonDecode(profileJson));
} }
} }
String get fullName => _profile?.workerSite?.fullname ?? "Quý Khách"; String get displayName {
var name = _profile?.workerSite?.fullname ?? "";
if (name.isEmpty) {
name = "Quý Khách";
}
return name;
}
String? get rankName => _profile?.workingSite?.primaryMembership?.membershipLevel?.levelName ?? ""; String? get rankName => _profile?.workingSite?.primaryMembership?.membershipLevel?.levelName ?? "";
String? get token => _loginToken?.accessToken; String? get token => _loginToken?.accessToken;
String? get phone => _profile?.workerSite?.phoneNumber; String? get phone => _profile?.workerSite?.phoneNumber;
......
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/resouce/base_color.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart';
import '../../shared/router_gage.dart';
import 'model/affiliate_brand_model.dart';
void showAffiliateBrandPopup(BuildContext context, List<AffiliateBrandModel> brands, {String? title}) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))),
builder: (_) {
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 8),
Row(
children: [
const SizedBox(width: 16),
Expanded(
child: Center(
child: Text(
title ?? "Thương hiệu",
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black),
),
),
),
IconButton(icon: const Icon(Icons.close), onPressed: () => Navigator.of(context).pop()),
// const SizedBox(width: 8),
],
),
const Divider(height: 1),
Flexible(
child: ListView.separated(
shrinkWrap: true,
padding: const EdgeInsets.symmetric(vertical: 8),
itemCount: brands.length,
separatorBuilder: (_, __) => const SizedBox(height: 4),
itemBuilder: (context, index) {
final brand = brands[index];
return GestureDetector(
onTap: () {
Navigator.of(context).pop();
Get.toNamed(affiliateBrandDetailScreen, arguments: {"brandId": brand.brandId});
},
child: Padding(
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 8),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(40),
child: loadNetworkImage(
url: brand.logo,
width: 60,
height: 60,
placeholderAsset: "assets/images/ic_logo.png",
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
brand.brandName ?? '',
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
const SizedBox(height: 4),
RichText(
text: TextSpan(
text: 'Hoàn đến: ',
style: const TextStyle(color: Colors.black54, fontSize: 13),
children: [
TextSpan(
text: brand.showCommision ?? '',
style: const TextStyle(color: Colors.deepOrange, fontWeight: FontWeight.bold, fontSize: 14),
),
],
),
),
],
),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: BaseColor.primary500,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
onPressed: () {
Navigator.of(context).pop();
brand.directionalScreen?.begin();
},
child: const Text(
"Mua ngay",
style: TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w600),
),
),
],
),
),
),
);
},
),
),
],
),
);
},
);
}
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/screen/shopping/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/shopping/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/shopping/sub_widget/build_affiliate_product_topsale.dart'; import 'package:mypoint_flutter_app/screen/affiliate/sub_widget/build_affiliate_product_topsale.dart';
import '../../base/base_screen.dart'; import '../../base/base_screen.dart';
import '../../base/basic_state.dart'; import '../../base/basic_state.dart';
import '../../resouce/base_color.dart'; import '../../resouce/base_color.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 'affiliate_overview.dart'; import 'affiliate_overview.dart';
import 'affiliate_popup_brands.dart';
import 'affiliate_tab_viewmodel.dart'; import 'affiliate_tab_viewmodel.dart';
import 'model/affiliate_category_type.dart';
class AffiliateTabScreen extends BaseScreen { class AffiliateTabScreen extends BaseScreen {
const AffiliateTabScreen({super.key}); const AffiliateTabScreen({super.key});
...@@ -22,6 +25,19 @@ class AffiliateTabScreen extends BaseScreen { ...@@ -22,6 +25,19 @@ class AffiliateTabScreen extends BaseScreen {
class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicState { class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicState {
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;
@override
void initState() {
super.initState();
final args = Get.arguments;
if (args is Map) {
_canBackButton = args['can_back_button'] as bool;
}
viewModel.onShowAffiliateBrandPopup = (data) {
showAffiliateBrandPopup(context, data.$1, title: data.$2);
};
}
@override @override
Widget createBody() { Widget createBody() {
...@@ -29,7 +45,7 @@ class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicS ...@@ -29,7 +45,7 @@ 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: false, showBackButton: _canBackButton,
backgroundImage: _headerHomeVM.headerData.background ?? "assets/images/bg_header_navi.png", backgroundImage: _headerHomeVM.headerData.background ?? "assets/images/bg_header_navi.png",
rightButtons: [ rightButtons: [
IconButton( IconButton(
...@@ -57,7 +73,11 @@ class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicS ...@@ -57,7 +73,11 @@ class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicS
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Container( GestureDetector(
onTap: () {
Get.toNamed(historyPointCashBackScreen);
},
child: Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: Colors.amber.shade100, borderRadius: BorderRadius.circular(12)), decoration: BoxDecoration(color: Colors.amber.shade100, borderRadius: BorderRadius.circular(12)),
child: Row( child: Row(
...@@ -75,6 +95,7 @@ class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicS ...@@ -75,6 +95,7 @@ class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicS
], ],
), ),
), ),
),
const SizedBox(height: 16), const SizedBox(height: 16),
SizedBox( SizedBox(
height: 130, height: 130,
...@@ -92,7 +113,13 @@ class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicS ...@@ -92,7 +113,13 @@ class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicS
), ),
), ),
AffiliateBrand(brands: viewModel.affiliateBrands), AffiliateBrand(brands: viewModel.affiliateBrands),
AffiliateCategory(categories: viewModel.affiliateCategories), AffiliateCategory(categories: viewModel.affiliateCategories, onTap: (category) {
if (category.code == AffiliateCategoryType.other) {
Get.toNamed(affiliateCategoryGridScreen, arguments: {"categories": viewModel.allAffiliateCategories});
return;
}
viewModel.affiliateBrandGetListBuyCategory(category);
},),
AffiliateProductTopSale(products: viewModel.affiliateProducts), AffiliateProductTopSale(products: viewModel.affiliateProducts),
], ],
), ),
......
import 'package:get/get_rx/src/rx_types/rx_types.dart'; import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart'; import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import '../../base/restful_api_viewmodel.dart'; import '../../base/restful_api_viewmodel.dart';
import 'affiliate_popup_brands.dart';
import 'model/affiliate_brand_model.dart'; import 'model/affiliate_brand_model.dart';
import 'model/affiliate_category_model.dart'; import 'model/affiliate_category_model.dart';
import 'model/affiliate_category_type.dart'; import 'model/affiliate_category_type.dart';
...@@ -10,9 +11,11 @@ import 'model/cashback_overview_model.dart'; ...@@ -10,9 +11,11 @@ import 'model/cashback_overview_model.dart';
class AffiliateTabViewModel extends RestfulApiViewModel { class AffiliateTabViewModel extends RestfulApiViewModel {
final RxList<AffiliateBrandModel> affiliateBrands = <AffiliateBrandModel>[].obs; final RxList<AffiliateBrandModel> affiliateBrands = <AffiliateBrandModel>[].obs;
final RxList<AffiliateCategoryModel> affiliateCategories = <AffiliateCategoryModel>[].obs; final RxList<AffiliateCategoryModel> affiliateCategories = <AffiliateCategoryModel>[].obs;
final RxList<AffiliateCategoryModel> allAffiliateCategories = <AffiliateCategoryModel>[].obs;
final RxList<AffiliateProductTopSaleModel> affiliateProducts = <AffiliateProductTopSaleModel>[].obs; final RxList<AffiliateProductTopSaleModel> affiliateProducts = <AffiliateProductTopSaleModel>[].obs;
final RxBool isLoading = false.obs; final RxBool isLoading = false.obs;
var overview = Rxn<CashbackOverviewModel>(); var overview = Rxn<CashbackOverviewModel>();
void Function((List<AffiliateBrandModel>, String) data)? onShowAffiliateBrandPopup;
@override @override
void onInit() { void onInit() {
...@@ -34,7 +37,7 @@ class AffiliateTabViewModel extends RestfulApiViewModel { ...@@ -34,7 +37,7 @@ class AffiliateTabViewModel extends RestfulApiViewModel {
Future<void> _getAffiliateBrandGetList() async { Future<void> _getAffiliateBrandGetList() async {
try { try {
final result = await client.affiliateBrandGetList(); final result = await client.affiliateBrandGetList();
affiliateBrands.value = (result.data ?? []).take(6).toList(); affiliateBrands.value = result.data ?? [];
} catch (error) { } catch (error) {
print("Error fetching affiliate brands: $error"); print("Error fetching affiliate brands: $error");
} }
...@@ -47,7 +50,10 @@ class AffiliateTabViewModel extends RestfulApiViewModel { ...@@ -47,7 +50,10 @@ class AffiliateTabViewModel extends RestfulApiViewModel {
code: AffiliateCategoryType.other, code: AffiliateCategoryType.other,
name: "Khác", name: "Khác",
); );
final data = (result.data ?? []).take(7).toList(); final results = (result.data ?? []);
allAffiliateCategories.value = results;
final data = results.take(7).toList();
data.add(category); data.add(category);
affiliateCategories.value = data; affiliateCategories.value = data;
} catch (error) { } catch (error) {
...@@ -72,4 +78,19 @@ class AffiliateTabViewModel extends RestfulApiViewModel { ...@@ -72,4 +78,19 @@ class AffiliateTabViewModel extends RestfulApiViewModel {
print("Error fetching affiliate brands: $error"); print("Error fetching affiliate brands: $error");
} }
} }
affiliateBrandGetListBuyCategory(AffiliateCategoryModel category) async {
showLoading();
try {
final result = await client.affiliateBrandGetList(categoryCode: AffiliateCategoryModel.codeToJson(category.code));
hideLoading();
final data = result.data ?? [];
if (result.isSuccess && data.isNotEmpty) {
onShowAffiliateBrandPopup?.call((data, category.name ?? ''));
}
} catch (error) {
hideLoading();
print("Error fetching affiliate brands: $error");
}
}
} }
\ No newline at end of file
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import '../../../configs/constants.dart';
import '../../../directional/directional_action_type.dart';
import '../../../directional/directional_screen.dart';
part 'affiliate_brand_model.g.dart'; part 'affiliate_brand_model.g.dart';
@JsonSerializable() @JsonSerializable()
...@@ -25,6 +28,18 @@ class AffiliateBrandModel { ...@@ -25,6 +28,18 @@ class AffiliateBrandModel {
this.direction, this.direction,
}); });
DirectionalScreen? get directionalScreen {
if (brandUrl == null || brandUrl!.isEmpty) return null;
if (direction == Constants.directionInApp) {
return DirectionalScreen.buildByName(name: DirectionalScreenName.viewDeepLinkInApp, clickActionParam: brandUrl);
}
return DirectionalScreen.buildByName(name: DirectionalScreenName.viewDeepLink, clickActionParam: brandUrl);
}
String get showCommision {
return '${double.tryParse(commision ?? '0')?.toStringAsFixed(1) ?? '0.0'}%';
}
factory AffiliateBrandModel.fromJson(Map<String, dynamic> json) => _$AffiliateBrandModelFromJson(json); factory AffiliateBrandModel.fromJson(Map<String, dynamic> json) => _$AffiliateBrandModelFromJson(json);
Map<String, dynamic> toJson() => _$AffiliateBrandModelToJson(this); Map<String, dynamic> toJson() => _$AffiliateBrandModelToJson(this);
} }
...@@ -2,10 +2,9 @@ import 'package:json_annotation/json_annotation.dart'; ...@@ -2,10 +2,9 @@ import 'package:json_annotation/json_annotation.dart';
import 'affiliate_category_type.dart'; import 'affiliate_category_type.dart';
part 'affiliate_category_model.g.dart'; part 'affiliate_category_model.g.dart';
@JsonSerializable() @JsonSerializable()
class AffiliateCategoryModel { class AffiliateCategoryModel {
@JsonKey(name: "category_code", fromJson: _codeFromJson, toJson: _codeToJson) @JsonKey(name: "category_code", fromJson: _codeFromJson, toJson: codeToJson)
final AffiliateCategoryType code; final AffiliateCategoryType code;
@JsonKey(name: "category_name") @JsonKey(name: "category_name")
...@@ -19,19 +18,16 @@ class AffiliateCategoryModel { ...@@ -19,19 +18,16 @@ class AffiliateCategoryModel {
static AffiliateCategoryType _codeFromJson(String code) => static AffiliateCategoryType _codeFromJson(String code) =>
affiliateCategoryTypeValues[code] ?? AffiliateCategoryType.other; affiliateCategoryTypeValues[code] ?? AffiliateCategoryType.other;
static String _codeToJson(AffiliateCategoryType type) => static String codeToJson(AffiliateCategoryType type) =>
affiliateCategoryTypeValues.entries affiliateCategoryTypeValues.entries
.firstWhere((e) => e.value == type, .firstWhere((e) => e.value == type,
orElse: () => const MapEntry("other", AffiliateCategoryType.other)) orElse: () => const MapEntry("other", AffiliateCategoryType.other))
.key; .key;
String get icon => "ic_cashback_${_codeToJson(code)}"; String get icon => "ic_cashback_${codeToJson(code)}";
bool get isOther => code == AffiliateCategoryType.other; bool get isOther => code == AffiliateCategoryType.other;
static AffiliateCategoryModel get other =>
AffiliateCategoryModel(code: AffiliateCategoryType.other, name: "Khác");
factory AffiliateCategoryModel.fromJson(Map<String, dynamic> json) => factory AffiliateCategoryModel.fromJson(Map<String, dynamic> json) =>
_$AffiliateCategoryModelFromJson(json); _$AffiliateCategoryModelFromJson(json);
......
...@@ -16,6 +16,6 @@ AffiliateCategoryModel _$AffiliateCategoryModelFromJson( ...@@ -16,6 +16,6 @@ AffiliateCategoryModel _$AffiliateCategoryModelFromJson(
Map<String, dynamic> _$AffiliateCategoryModelToJson( Map<String, dynamic> _$AffiliateCategoryModelToJson(
AffiliateCategoryModel instance, AffiliateCategoryModel instance,
) => <String, dynamic>{ ) => <String, dynamic>{
'category_code': AffiliateCategoryModel._codeToJson(instance.code), 'category_code': AffiliateCategoryModel.codeToJson(instance.code),
'category_name': instance.name, 'category_name': instance.name,
}; };
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:mypoint_flutter_app/directional/directional_screen.dart';
import '../../../directional/directional_action_type.dart';
part 'affiliate_product_top_sale_model.g.dart'; part 'affiliate_product_top_sale_model.g.dart';
@JsonSerializable() @JsonSerializable()
...@@ -17,6 +20,20 @@ class AffiliateProductTopSaleModel { ...@@ -17,6 +20,20 @@ class AffiliateProductTopSaleModel {
final String? quantitySold; final String? quantitySold;
final String? direction; final String? direction;
DirectionalScreen? get direcionalScreen {
if ((productLink ?? "").isEmpty) return null;
if (direction == "IN-APP") {
return DirectionalScreen.buildByName(
name: DirectionalScreenName.viewDeepLinkInApp,
clickActionParam: productLink,
);
}
return DirectionalScreen.buildByName(
name: DirectionalScreenName.viewDeepLink,
clickActionParam: productLink,
);
}
AffiliateProductTopSaleModel({ AffiliateProductTopSaleModel({
this.productName, this.productName,
this.productPrice, this.productPrice,
......
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