Commit f714cdcc authored by DatHV's avatar DatHV
Browse files

update logic, flutter refactor

parent cc202df4
......@@ -3,6 +3,7 @@ class Constants {
static get commonError => "Hệ thống không thể xử lý yêu cầu hiện tại. Vui lòng thử lại sau hoặc liên hệ hotline 1900599863 để được trợ giúp.";
static var otpTtl = 180;
static var directionInApp = "IN-APP";
static var phoneNumberCount = 10;
}
class ErrorCodes {
......
import 'dart:async';
class Debouncer {
Debouncer({this.ms = 300});
final int ms;
Timer? _t;
void run(void Function() action) {
_t?.cancel();
_t = Timer(Duration(milliseconds: ms), action);
}
void dispose() => _t?.cancel();
}
\ No newline at end of file
......@@ -5,10 +5,12 @@ import 'package:mypoint_flutter_app/networking/app_navigator.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/resources/base_color.dart';
import 'package:mypoint_flutter_app/screen/home/header_home_viewmodel.dart';
import 'package:mypoint_flutter_app/shared/router_gage.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
Get.put(HeaderThemeController(), permanent: true);
await DataPreference.instance.init();
await UserPointManager().fetchUserPoint();
runApp(const MyApp());
......
......@@ -47,6 +47,7 @@ class RestfulAPIClient {
final isGet = method == Method.GET;
Json query = isGet ? params : {};
Json body = !isGet ? params : {};
// body["lang"] = 'vi';
final option = Options(method: method.name)
.compose(
_dio.options,
......
......@@ -66,7 +66,7 @@ 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_method_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_response_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';
......@@ -338,10 +338,10 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
});
}
Future<BaseResponseModel<LikeProductReponseModel>> likeProduct(int id) async {
Future<BaseResponseModel<LikeProductResponseModel>> likeProduct(int id) async {
final body = {"product_id": id};
return requestNormal(APIPaths.productCustomerLikes, Method.POST, body, (data) {
return LikeProductReponseModel.fromJson(data as Json);
return LikeProductResponseModel.fromJson(data as Json);
});
}
......@@ -608,7 +608,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
Future<BaseResponseModel<MembershipInfoResponse>> getMembershipLevelInfo() async {
String? token = DataPreference.instance.token ?? "";
final body = {"access_token": token};
final body = {"access_token": token, "lang": "vi"};
return requestNormal(APIPaths.getMembershipLevelInfo, Method.POST, body, (data) {
return MembershipInfoResponse.fromJson(data as Json);
});
......
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import '../../base/restful_api_viewmodel.dart';
import '../data_preference.dart';
import 'header_home_model.dart';
class UserPointManager extends RestfulApiViewModel {
......@@ -13,7 +14,8 @@ class UserPointManager extends RestfulApiViewModel {
get point => _userPoint.value;
Future<int> fetchUserPoint() async {
Future fetchUserPoint() async {
if (!DataPreference.instance.logged) return;
try {
final response = await client.getHomeHeaderData();
if (response.isSuccess && response.data != null) {
......@@ -25,6 +27,5 @@ class UserPointManager extends RestfulApiViewModel {
} catch (e) {
_userPoint.value = 0;
}
return _userPoint.value;
}
}
......@@ -51,7 +51,6 @@ class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicS
backgroundColor: Colors.grey.shade50,
appBar: CustomNavigationBar(
title: "Mua sắm",
backgroundImage: _headerHomeVM.headerData.background ?? "assets/images/bg_header_navi.png",
leftButtons: _canBackButton ? [CustomBackButton()] : [],
rightButtons: [
IconButton(
......
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/extensions/num_extension.dart';
......@@ -7,6 +8,7 @@ import 'package:mypoint_flutter_app/widgets/custom_navigation_bar.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart';
import '../../base/base_screen.dart';
import '../../base/basic_state.dart';
import '../../extensions/debouncer.dart';
import '../../preference/data_preference.dart';
import '../../preference/point/point_manager.dart';
import '../../resources/base_color.dart';
......@@ -26,6 +28,7 @@ class DataNetworkServiceScreen extends BaseScreen {
class _DataNetworkServiceScreenState extends BaseState<DataNetworkServiceScreen> with BasicState {
final DataNetworkServiceViewModel _viewModel = Get.put(DataNetworkServiceViewModel());
late final TextEditingController _phoneController;
final _deb = Debouncer(ms: 500);
@override
void initState() {
......@@ -76,8 +79,7 @@ class _DataNetworkServiceScreenState extends BaseState<DataNetworkServiceScreen>
Widget _buildButton() {
return Obx(() {
final isValidInput =
(_viewModel.phoneNumber.value.trim().length >= 10) && (_viewModel.selectedProduct.value != null);
final isValidInput = _viewModel.validatePhoneNumber() && (_viewModel.selectedProduct.value != null);
return ElevatedButton(
onPressed: isValidInput ? _redeemProductMobileCard : null,
style: ElevatedButton.styleFrom(
......@@ -234,7 +236,7 @@ class _DataNetworkServiceScreenState extends BaseState<DataNetworkServiceScreen>
keyboardType: TextInputType.phone,
onChanged: (value) {
_viewModel.phoneNumber.value = value;
_viewModel.checkMobileNetwork();
_deb.run(() => _viewModel.checkMobileNetwork());
},
),
),
......@@ -356,4 +358,4 @@ class _DataNetworkServiceScreenState extends BaseState<DataNetworkServiceScreen>
);
}
}
}
}
\ No newline at end of file
......@@ -30,6 +30,12 @@ class DataNetworkServiceViewModel extends RestfulApiViewModel {
return UserPointManager().point >= payPoint;
}
bool validatePhoneNumber() {
final phone = phoneNumber.value.replaceAll(RegExp(r'\s+'), '');
final regex = RegExp(r'^(0|\+84)(3[2-9]|5[6|8|9]|7[0|6-9]|8[1-5]|9[0-4|6-9])[0-9]{7}$');
return regex.hasMatch(phone);
}
@override
void onInit() {
super.onInit();
......@@ -49,37 +55,39 @@ class DataNetworkServiceViewModel extends RestfulApiViewModel {
}
firstLoadNetworkData() async {
showLoading();
await getNetworkBrands();
print("topUpBrands ${topUpBrands.length}");
await checkMobileNetwork();
hideLoading();
_getNetworkBrands();
}
getNetworkBrands() {
_getNetworkBrands() {
showLoading();
client.productTopUpBrands().then((response) {
topUpBrands.value = response.data ?? [];
hideLoading();
checkMobileNetwork();
}).catchError((error) {
hideLoading();
print('Error fetching brands topup: $error');
});
}
checkMobileNetwork() {
showLoading();
client.checkMobileNetwork(phoneNumber.value).then((response) {
final brandCode = response.data?.brand ?? '';
final brand = topUpBrands.isNotEmpty
? topUpBrands.firstWhere(
(brand) => brand.code == brandCode,
orElse: () => topUpBrands.first,
)
: null;
) : null;
selectedBrand.value = brand;
hideLoading();
getTelcoDetail();
}).catchError((error) {
final first = topUpBrands.value.firstOrNull;
if (first != null) {
selectedBrand.value = first;
}
hideLoading();
getTelcoDetail();
print('Error checking mobile network: $error');
});
......
......@@ -59,7 +59,6 @@ class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState, Popu
appBar: CustomNavigationBar(
title: "Games",
leftButtons: _canBackButton ? [CustomBackButton()] : [],
backgroundImage: _headerHomeVM.headerData.background ?? "assets/images/bg_header_navi.png",
rightButtons: [
CompositedTransformTarget(
link: _layerLink,
......
import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import '../../base/restful_api_viewmodel.dart';
import '../../preference/point/header_home_model.dart';
import 'models/notification_unread_model.dart';
class HeaderThemeController extends GetxController {
final background = RxnString();
void setBackground(String? url) => background.value = url;
}
class HeaderHomeViewModel extends RestfulApiViewModel {
final Rx<HeaderHomeModel?> _headerHomeData = Rx<HeaderHomeModel?>(null);
var notificationUnreadData = Rxn<NotificationUnreadData>();
......@@ -30,6 +35,7 @@ class HeaderHomeViewModel extends RestfulApiViewModel {
try {
final result = await client.getDynamicHeaderHome();
_headerHomeData.value = result.data;
Get.find<HeaderThemeController>().setBackground(_headerHomeData.value?.background);
} catch (error) {
print("Error fetching getDynamicHeaderHome: $error");
}
......
......@@ -92,10 +92,11 @@ class _MainTabScreenState extends State<MainTabScreen> {
const SizedBox(height: 2),
Text(
label,
maxLines: 1,
style: TextStyle(
color: isSelected ? Colors.white : Colors.white70,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
fontSize: 14,
fontSize: 12,
),
),
],
......
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:intl/intl.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.dart';
......@@ -10,6 +12,7 @@ import 'models/membership_level_model.dart';
class MemberLevelHeaderWidget extends StatelessWidget {
final MembershipLevelModel? level;
const MemberLevelHeaderWidget({super.key, this.level});
@override
......@@ -38,13 +41,13 @@ class MemberLevelHeaderWidget extends StatelessWidget {
children: [
Row(
children: [
Container(
width: 72,
height: 72,
decoration: BoxDecoration(shape: BoxShape.circle, border: Border.all(color: Colors.white, width: 2)),
child: ClipOval(child: Image.asset("assets/images/bg_default_11.png")),
loadNetworkImage(
url: "level?.logo",
width: 116,
height: 116,
placeholderAsset: "assets/images/ic_logo_rank_member.png",
),
const SizedBox(width: 12),
const SizedBox(width: 6),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
......@@ -53,19 +56,20 @@ class MemberLevelHeaderWidget extends StatelessWidget {
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.white),
),
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20)),
child: Text(
(level?.levelName ?? "").capitalizeWords(),
style: TextStyle(color: BaseColor.primary800, fontSize: 14, fontWeight: FontWeight.bold),
if ((level?.levelName ?? "").isNotEmpty)
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20)),
child: Text(
(level?.levelName ?? "").capitalizeWords(),
style: TextStyle(color: BaseColor.primary800, fontSize: 14, fontWeight: FontWeight.bold),
),
),
),
],
),
],
),
const SizedBox(height: 16),
const SizedBox(height: 6),
// Progress bar
_buildCardInfo(),
],
......@@ -79,13 +83,19 @@ class MemberLevelHeaderWidget extends StatelessWidget {
final int spendingMax = double.tryParse(level?.upgradeGmvThreshold ?? "0")?.toInt() ?? 1;
return Container(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(color: Colors.black.withOpacity(0.3), blurRadius: 16, spreadRadius: 0, offset: const Offset(0, 6)),
],
),
child: Row(
children: [
Expanded(
child: GestureDetector(
onTap: () {
print("GestureDetector");
Get.toNamed('/pointHistoryScreen');
},
behavior: HitTestBehavior.opaque,
child: Column(
......@@ -118,7 +128,7 @@ class MemberLevelHeaderWidget extends StatelessWidget {
Expanded(
child: GestureDetector(
onTap: () {
print("GestureDetector");
Get.toNamed('/historyPointCashBackScreen');
},
behavior: HitTestBehavior.opaque,
child: Column(
......
......@@ -45,7 +45,7 @@ class _MembershipScreenState extends BaseState<MembershipScreen> with BasicState
Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Text(
"Hạng thành viên sẽ được cập nhật sau ${_viewModel.selectedLevel?.levelEndAtDate}",
"Hạng thành viên sẽ được cập nhật sau ${_viewModel.selectedLevel?.levelEndAtDate ?? ''}",
style: TextStyle(color: Colors.black54, fontSize: 13),
),
),
......@@ -82,7 +82,7 @@ class _MembershipScreenState extends BaseState<MembershipScreen> with BasicState
],
),
const SizedBox(height: 8),
HtmlWidget("item.content ?? "),
HtmlWidget(item.content ?? ''),
],
),
);
......
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/extensions/collection_extension.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
......@@ -22,14 +20,13 @@ class MembershipViewModel extends RestfulApiViewModel {
if (levels == null || levels!.isEmpty) {
return null;
}
return levels?.safe(selectedTab.value)?.conditions?.whereType<MembershipLevelTermAndConditionModel>().toList();
return levels?.safe(selectedTab.value)?.conditions;
}
@override
onInit() {
super.onInit();
getMembershipLevelInfo();
// loadMembershipInfoFromAssets();
}
_makeSelectedLevel() {
......@@ -40,22 +37,10 @@ class MembershipViewModel extends RestfulApiViewModel {
selectedLevel = levels!.firstWhere((e) => e.levelStartAtDate?.isNotEmpty == true, orElse: () => levels!.first);
}
loadMembershipInfoFromAssets() async {
final jsonStr = await rootBundle.loadString('assets/data/membership_info.json');
final jsonMap = jsonDecode(jsonStr);
final result = MembershipInfoResponse.fromJson(jsonMap['data']);
membershipInfo.value = result;
_makeSelectedLevel();
}
getMembershipLevelInfo() async {
showLoading();
try {
final response = await client.getMembershipLevelInfo();
print("getMembershipLevelInfo");
print(response.data?.membershipRule);
print(response.data?.levels?.first?.condition);
print(response.data?.levels?.first?.conditions);
membershipInfo.value = response.data;
_makeSelectedLevel();
hideLoading();
......
......@@ -59,7 +59,7 @@ class MembershipLevelModel {
final String? downgradeToLevelId;
@JsonKey(name: 'membership_level_term_and_conditions')
final List<MembershipLevelTermAndConditionModel?>? conditions;
final List<MembershipLevelTermAndConditionModel>? conditions;
@JsonKey(name: 'accumulated_counter')
final AccumulatedCounter? accumulatedCounter;
......
......@@ -32,12 +32,9 @@ MembershipLevelModel _$MembershipLevelModelFromJson(
conditions:
(json['membership_level_term_and_conditions'] as List<dynamic>?)
?.map(
(e) =>
e == null
? null
: MembershipLevelTermAndConditionModel.fromJson(
e as Map<String, dynamic>,
),
(e) => MembershipLevelTermAndConditionModel.fromJson(
e as Map<String, dynamic>,
),
)
.toList(),
accumulatedCounter:
......
......@@ -124,13 +124,15 @@ class _ProductMobileCardScreenState extends BaseState<ProductMobileCardScreen> w
}
Widget _buildProductItem() {
const double kItemHeight = 80;
final widthItem = (MediaQuery.of(context).size.width - 12*3)/2;
return Expanded(
child: GridView.count(
crossAxisCount: 2,
padding: const EdgeInsets.symmetric(horizontal: 16),
crossAxisSpacing: 12,
mainAxisSpacing: 12,
childAspectRatio: 2.4,
childAspectRatio: widthItem/kItemHeight,
children:
_viewModel.products.map((product) {
final isSelected = _viewModel.selectedProduct?.id == product.id;
......@@ -153,7 +155,7 @@ class _ProductMobileCardScreenState extends BaseState<ProductMobileCardScreen> w
borderRadius: BorderRadius.circular(12),
color: isSelected ? Colors.orange.withOpacity(0.1) : Colors.white,
),
padding: const EdgeInsets.all(12),
padding: const EdgeInsets.all(10),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
......
......@@ -7,6 +7,7 @@ import '../../resources/base_color.dart';
import '../../widgets/alert/data_alert_model.dart';
import '../../widgets/back_button.dart';
import '../../widgets/custom_empty_widget.dart';
import '../../widgets/custom_navigation_bar.dart';
import '../../widgets/image_loader.dart';
import 'models/notification_item_model.dart';
import 'notification_viewmodel.dart';
......@@ -44,22 +45,14 @@ class _NotificationScreenState extends BaseState<NotificationScreen> with BasicS
@override
Widget createBody() {
return Scaffold(
appBar: AppBar(
scrolledUnderElevation: 0,
backgroundColor: Colors.white,
elevation: 0,
centerTitle: true,
title: const Text(
'Thông báo',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87),
),
leading: CustomBackButton(),
actions: [
appBar: CustomNavigationBar(
title: "Thông báo",
rightButtons: [
CompositedTransformTarget(
link: _layerLink,
child: IconButton(
key: _infoKey,
icon: const Icon(Icons.settings, color: Colors.black),
icon: const Icon(Icons.settings, color: Colors.black54),
onPressed: _toggleSetting,
),
),
......
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