Commit 5413611e authored by DatHV's avatar DatHV
Browse files

update screen support - logic

parent e7dd4bc3
{
"data": {
"items": [
{
"icon": "ic_mail",
"title": "cskh.mypoint@paytech.vn",
"value": "cskh.mypoint@paytech.vn",
"type": "mail"
},
{
"icon": "ic_facebook",
"title": "Facebook",
"value": "https://www.facebook.com/tichdiemMyPoint",
"type": "facebook"
},
{
"icon": "ic_phone",
"title": "1900599863",
"value": "1900599863",
"type": "phone"
},
{
"icon": "ic_question",
"title": "Câu hỏi thường gặp",
"value": "",
"type": "question"
},
{
"icon": "ic_terms_of_use",
"title": "Điều khoản sử dụng",
"value": "",
"type": "termsOfUse"
},
{
"icon": "ic_privacyPolicy",
"title": "Chính sách bảo mật",
"value": "",
"type": "privacyPolicy"
}
]
}
}
class APIPaths {
static const String baseUrl = "https://api.mypoint.com.vn/8854/gup2start/rest";
static const String baseUrl = "https://api.sandbox.mypoint.com.vn/8854/gup2start/rest";
static const String checkUpdate = "/version-management-service/api/v1.0/check-customer-software-update";
static const String getOnboardingInfo = "/resource/api/v2.0/intro-screen";
static const String checkPhoneNumber = "/user/api/v2.0/account/users/checkPhoneNumber";
......@@ -9,4 +9,5 @@ class APIPaths {
static const String signup = "/user/api/v2.0/signup";
static const String otpCreateNew = "/otpCreateNew/1.0.0";
static const String websitePageGetDetail = "/websitePageGetDetail/1.0.0";
static const String websitePage = "/user/api/v2.0/websitePage";
}
enum ClickActionType {
campaignDetail,
}
extension ClickActionTypeExtension on ClickActionType {
String get key {
switch (this) {
case ClickActionType.campaignDetail:
return "campaignDetail";
}
}
static ClickActionType? fromString(String value) {
switch (value) {
case "campaignDetail":
return ClickActionType.campaignDetail;
default:
return null;
}
}
}
\ No newline at end of file
import 'package:get/get.dart';
import 'package:json_annotation/json_annotation.dart';
import '../screen/pageDetail/campaign_detail_screen.dart';
import 'directional_action_type.dart';
part 'directional_screen.g.dart';
@JsonSerializable()
class DirectionalScreen {
@JsonKey(name: "click_action_type")
final String clickActionType;
@JsonKey(name: "click_action_param")
final String? clickActionParam;
DirectionalScreen({
required this.clickActionType,
this.clickActionParam,
});
factory DirectionalScreen.fromJson(Map<String, dynamic> json) => _$DirectionalScreenFromJson(json);
Map<String, dynamic> toJson() => _$DirectionalScreenToJson(this);
void begin() {
final type = ClickActionTypeExtension.fromString(clickActionType);
if (type == null) {
print("Không nhận diện được action type: $clickActionType");
return;
}
switch (type) {
case ClickActionType.campaignDetail:
Get.to(() => const CampaignDetailScreen(), arguments: clickActionParam);
break;
}
}
}
\ No newline at end of file
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'directional_screen.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
DirectionalScreen _$DirectionalScreenFromJson(Map<String, dynamic> json) =>
DirectionalScreen(
clickActionType: json['click_action_type'] as String,
clickActionParam: json['click_action_param'] as String?,
);
Map<String, dynamic> _$DirectionalScreenToJson(DirectionalScreen instance) =>
<String, dynamic>{
'click_action_type': instance.clickActionType,
'click_action_param': instance.clickActionParam,
};
......@@ -10,6 +10,7 @@ import '../screen/onboarding/model/check_phone_response_model.dart';
import '../screen/onboarding/model/onboarding_info_model.dart';
import '../screen/otp/model/otp_verify_response_model.dart';
import '../screen/pageDetail/model/campaign_detail_model.dart';
import '../screen/pageDetail/model/detail_page_rule_type.dart';
import '../screen/splash/splash_screen_viewmodel.dart';
import 'model_maker.dart';
......@@ -77,9 +78,9 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
);
}
Future<BaseResponseModel<EmptyCodable>> otpCreateNew(String ownerId, String password) async {
Future<BaseResponseModel<EmptyCodable>> otpCreateNew(String ownerId,) async {
var deviceKey = await DeviceInfo.getDeviceId();
final body = {"owner_id": ownerId, "ttl": Constants.otpTtl, "resend_after_second": 30};
final body = {"owner_id": ownerId, "ttl": Constants.otpTtl, "resend_after_second": Constants.otpTtl};
return requestNormal(
APIPaths.otpCreateNew,
Method.POST,
......@@ -97,4 +98,14 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
(data) => CampaignDetailResponseModel.fromJson(data as Json),
);
}
Future<BaseResponseModel<CampaignDetailResponseModel>> websitePage(DetailPageRuleType rule) async {
final body = {"code": rule.key,};
return requestNormal(
APIPaths.websitePage,
Method.GET,
body,
(data) => CampaignDetailResponseModel.fromJson(data as Json),
);
}
}
......@@ -27,7 +27,6 @@ class BiometricManager {
}
}
/// Kiểm tra nhanh thiết bị có thể dùng sinh trắc học hay không
Future<bool> canCheckBiometrics() async {
try {
final canCheck = await _localAuth.canCheckBiometrics;
......
......@@ -39,7 +39,6 @@ class CreatePasswordViewModel extends GetxController {
if (!isButtonEnabled.value) return;
try {
final response = await repository.signup(newPassword.value);
// errorMessage.value = success
if (response.isSuccess) {
errorMessage.value = "";
// TODO: Điều hướng sang màn hình tiếp theo
......
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import 'package:mypoint_flutter_app/screen/login/login_screen.dart';
import '../../base/base_response_model.dart';
import '../../base/restful_api_viewmodel.dart';
import '../splash/splash_screen_viewmodel.dart';
......@@ -22,6 +25,10 @@ class SignUpCreatePasswordRepository extends RestfulApiViewModel implements ICre
showLoading();
return client.signup(phoneNumber, password).then((value) {
hideLoading();
if (value.status == "success" || value.code == 200) {
print("signup success");
Get.off(() => LoginScreen(phoneNumber: phoneNumber));
}
return value;
});
}
......
......@@ -161,7 +161,9 @@ class _LoginScreenState extends BaseState<LoginScreen> with BasicState {
child: const Text("Đổi số điện thoại", style: TextStyle(fontSize: 14, color: Color(0xFF3662FE))),
),
TextButton(
onPressed: vm.onForgotPassPressed,
onPressed: () {
vm.onForgotPassPressed(widget.phoneNumber);
},
child: const Text("Quên mật khẩu?", style: TextStyle(fontSize: 14, color: Color(0xFF3662FE))),
),
],
......
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import 'package:mypoint_flutter_app/screen/onboarding/onboarding_screen.dart';
import '../../base/restful_api_viewmodel.dart';
import '../../permission/biometric_manager.dart';
......@@ -65,7 +66,12 @@ class LoginViewModel extends RestfulApiViewModel {
Get.back();
}
void onForgotPassPressed() {
void onForgotPassPressed(String phoneNumber) {
showLoading();
client.otpCreateNew(phoneNumber).then((value) {
hideLoading();
print(value);
});
}
/// Xác thực đăng nhập bằng sinh trắc
......
......@@ -7,13 +7,11 @@ import '../../base/base_screen.dart';
import '../../base/basic_state.dart';
import '../../configs/constants.dart';
import '../../resouce/base_color.dart';
import '../biometric/biometric_screen.dart';
import '../create_pass/create_pass_screen.dart';
import '../create_pass/signup_create_password_repository.dart';
import '../login/login_screen.dart';
import '../otp/otp_screen.dart';
import '../otp/verify_otp_repository.dart';
import '../pageDetail/campaign_detail_screen.dart';
import '../pageDetail/model/detail_page_rule_type.dart';
import 'model/check_phone_response_model.dart';
import 'onboarding_viewmodel.dart';
......@@ -35,11 +33,10 @@ class _OnboardingScreenState extends BaseState<OnboardingScreen> with BasicState
_viewModel.checkPhoneRes.listen((response) {
WidgetsBinding.instance.addPostFrameCallback((_) {
hideKeyboard();
// Get.to(() => const LoginScreen());
_handleResponseCheckPhoneNumber(response.data);
_handleResponseError(response);
});
//
// _handleResponseError(response);
});
}
......@@ -58,13 +55,9 @@ class _OnboardingScreenState extends BaseState<OnboardingScreen> with BasicState
}
void _handleResponseCheckPhoneNumber(CheckPhoneResponseModel? response) {
Get.to(CampaignDetailScreen());
return;
if (response == null) return;
if (response.requireRecaptcha == true) {
// show Captcha
return;
}
if (response.requireRecaptcha == true) return;
if ((response.mfaToken ?? "").isNotEmpty || (response.nextAction == "signup")) {
// show OTP
Get.to(
......@@ -195,7 +188,7 @@ class _OnboardingScreenState extends BaseState<OnboardingScreen> with BasicState
const TextSpan(text: "Bằng việc tiếp tục, bạn đã đọc và đồng ý với "),
WidgetSpan(
child: GestureDetector(
onTap: () => debugPrint("Điều khoản sử dụng"),
onTap: () => Get.to(CampaignDetailScreen(type: DetailPageRuleType.termsOfUse)),
child: const Text(
"Điều khoản sử dụng",
style: TextStyle(
......@@ -210,7 +203,7 @@ class _OnboardingScreenState extends BaseState<OnboardingScreen> with BasicState
const TextSpan(text: " và "),
WidgetSpan(
child: GestureDetector(
onTap: () => debugPrint("Chính sách bảo mật"),
onTap: () => Get.to(CampaignDetailScreen(type: DetailPageRuleType.privacyPolicy)),
child: const Text(
"Chính sách bảo mật",
style: TextStyle(
......
......@@ -33,8 +33,8 @@ class OnboardingViewModel extends RestfulApiViewModel {
Future<void> checkPhoneNumber() async {
showLoading();
client.checkPhoneNumber(phoneNumber.value).then((value) {
checkPhoneRes.value = value;
hideLoading();
checkPhoneRes.value = value;
});
}
......
......@@ -19,6 +19,19 @@ class OtpScreen extends BaseScreen {
class _OtpScreenState extends BaseState<OtpScreen> with BasicState {
final TextEditingController _pinController = TextEditingController();
@override
void initState() {
super.initState();
final OtpViewModel otpVM = Get.put(OtpViewModel(widget.repository));
ever(otpVM.errorMessage, (value) {
if (value != null && value.toString().isNotEmpty) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showAlertError(value);
});
}
});
}
@override
Widget createBody() {
final otpVM = Get.put(OtpViewModel(widget.repository));
......
......@@ -7,7 +7,6 @@ import 'package:mypoint_flutter_app/screen/login/login_screen.dart';
import '../create_pass/signup_create_password_repository.dart';
import 'model/otp_verify_response_model.dart';
// i_otp_repository.dart
abstract class IOtpRepository {
Future<void> sendOtp();
Future<BaseResponseModel<OTPVerifyResponseModel>> verifyOtp(String otpCode);
......
import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:get/get.dart';
import '../../base/base_screen.dart';
import '../../base/basic_state.dart';
import '../../extensions/string_extension.dart'; // tuỳ dự án
import '../../resouce/base_color.dart';
import '../../widgets/back_button.dart';
import '../../widgets/network_image_with_aspect_ratio.dart'; // widget custom
import 'campaign_detail_viewmodel.dart';
import 'model/campaign_detail_item_model.dart';
import 'model/campaign_detail_model.dart';
import 'model/detail_page_rule_type.dart';
import 'model/media_type_item_campaign.dart';
class CampaignDetailScreen extends StatefulWidget {
const CampaignDetailScreen({super.key});
class CampaignDetailScreen extends BaseScreen {
final DetailPageRuleType? type;
final String? pageId;
const CampaignDetailScreen({super.key, this.type, this.pageId});
@override
State<CampaignDetailScreen> createState() => _CampaignDetailScreenState();
}
class _CampaignDetailScreenState extends State<CampaignDetailScreen> {
class _CampaignDetailScreenState extends BaseState<CampaignDetailScreen> with BasicState {
final CampaignDetailViewModel _viewModel = Get.put(CampaignDetailViewModel());
@override
void initState() {
super.initState();
_viewModel.fetchCampaignDetail();
ever(_viewModel.errorMessage, (value) {
if (value != null && value.toString().isNotEmpty) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showAlertError(value);
});
}
});
_viewModel.fetchData(widget.type, widget.pageId);
}
@override
Widget build(BuildContext context) {
Widget createBody() {
return Scaffold(
backgroundColor: BaseColor.second200,
// Không dùng AppBar mặc định
body: Obx(() {
CampaignDetailModel? pageDetail = _viewModel.campaignDetail.value.data?.pageDetail;
if (pageDetail == null) {
return const Center(child: CircularProgressIndicator());
}
// Lấy các giá trị
final thumbnail = pageDetail.thumbnail ?? "";
final publishDate = pageDetail.publishDate ?? "";
final title = pageDetail.title ?? "";
final List<CampaignDetailItemModel> items = pageDetail.items ?? [];
final buttonOn = pageDetail.buttonOn ?? "0";
final buttonColor = pageDetail.buttonColor ?? "#d9d9d9";
final buttonName = pageDetail.buttonName ?? "";
final buttonTextColor = pageDetail.buttonTextColor ?? "#FFFFFF";
final heightContainerBottomButton = MediaQuery.of(context).padding.bottom + 16 + 48;
return Stack(
children: [
SingleChildScrollView(
padding: EdgeInsets.only(bottom: heightContainerBottomButton),
child: Column(
children: [
if (thumbnail.isNotEmpty)
......@@ -57,10 +68,10 @@ class _CampaignDetailScreenState extends State<CampaignDetailScreen> {
color: Colors.grey.shade200,
child: const Center(child: CircularProgressIndicator()),
),
errorWidget: Image.asset("assets/bg_header_campain_default.png", fit: BoxFit.cover),
errorWidget: Image.asset("assets/images/bg_header_campaign_default.png", fit: BoxFit.cover),
)
else
Image.asset("assets/bg_header_campain_default.png", fit: BoxFit.cover),
Image.asset("assets/images/bg_header_campaign_default.png", fit: BoxFit.cover),
Transform.translate(
offset: const Offset(0, -32),
child: Container(
......@@ -79,22 +90,7 @@ class _CampaignDetailScreenState extends State<CampaignDetailScreen> {
Text(title, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
_buildItems(items),
const SizedBox(height: 24),
// 3) Nút, nếu có
if (buttonOn == "1")
ElevatedButton(
onPressed: () {
// Xử lý khi bấm nút
},
style: ElevatedButton.styleFrom(
backgroundColor: parseHexColor(buttonColor),
minimumSize: const Size.fromHeight(48),
),
child: Text(
buttonName,
style: TextStyle(color: parseHexColor(buttonTextColor), fontWeight: FontWeight.bold),
),
),
const SizedBox(height: 8),
],
),
),
......@@ -105,10 +101,7 @@ class _CampaignDetailScreenState extends State<CampaignDetailScreen> {
Positioned(
top: MediaQuery.of(context).padding.top + 8,
left: 8,
child: IconButton(
icon: const Icon(Icons.arrow_back_ios, color: Colors.white),
onPressed: () => Get.back(),
),
child: CustomBackButton(),
),
if (buttonOn == "1") _bottomButton(pageDetail),
],
......@@ -124,8 +117,16 @@ class _CampaignDetailScreenState extends State<CampaignDetailScreen> {
return Positioned(
left: 16,
right: 16,
bottom: MediaQuery.of(context).padding.bottom + 16,
child: ElevatedButton(
bottom: 0,
child: Container(
color: BaseColor.second200,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).padding.bottom + 16 + 48,
child: Center(
child: Column(
children: [
SizedBox(height: 12,),
ElevatedButton(
onPressed: () {
// Xử lý khi bấm nút
},
......@@ -135,6 +136,11 @@ class _CampaignDetailScreenState extends State<CampaignDetailScreen> {
),
child: Text(buttonName, style: TextStyle(color: parseHexColor(buttonTextColor), fontWeight: FontWeight.bold)),
),
SizedBox(height: MediaQuery.of(context).padding.bottom)
],
),
),
),
);
}
......
......@@ -5,16 +5,41 @@ import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import '../../base/base_response_model.dart';
import '../../base/restful_api_viewmodel.dart';
import 'model/campaign_detail_model.dart';
import 'model/detail_page_rule_type.dart';
class CampaignDetailViewModel extends RestfulApiViewModel {
var campaignDetail = BaseResponseModel<CampaignDetailResponseModel>().obs;
var isLoading = false.obs;
var errorMessage = "".obs;
void fetchCampaignDetail() {
void fetchData(DetailPageRuleType? type, String? pageId) {
if ((pageId ?? "").isNotEmpty) {
fetchWebsitePageGetDetail(pageId!);
return;
}
if (type != null) {
fetchWebsitePage(type!);
return;
}
}
void fetchWebsitePage(DetailPageRuleType type) {
showLoading();
isLoading(true);
client.websitePage(type).then((value) {
campaignDetail.value = value;
if (!value.isSuccess) {
errorMessage.value = value.errorMessage ?? Constants.commonError;
}
hideLoading();
isLoading(false);
});
}
void fetchWebsitePageGetDetail(String pageId) {
showLoading();
isLoading(true);
client.websitePageGetDetail("").then((value) {
client.websitePageGetDetail(pageId).then((value) {
campaignDetail.value = value;
if (!value.isSuccess) {
errorMessage.value = value.errorMessage ?? Constants.commonError;
......
enum DetailPageRuleType {
privacyPolicy,
termsOfUse,
decree,
policyDeleteAccount,
}
extension DetailPageRuleTypeExtension on DetailPageRuleType {
String get key {
switch (this) {
case DetailPageRuleType.privacyPolicy:
return "APP_SECURITY_POLICY";
case DetailPageRuleType.termsOfUse:
return "APP_TERM_OF_USE";
case DetailPageRuleType.decree:
return "DECREE_13";
case DetailPageRuleType.policyDeleteAccount:
return "APP_TERM_OF_DELETE_ACCOUNT";
}
}
static DetailPageRuleType? fromString(String value) {
switch (value) {
case "APP_SECURITY_POLICY":
return DetailPageRuleType.privacyPolicy;
case "APP_TERM_OF_USE":
return DetailPageRuleType.termsOfUse;
case "DECREE_13":
return DetailPageRuleType.decree;
case "APP_TERM_OF_DELETE_ACCOUNT":
return DetailPageRuleType.policyDeleteAccount;
}
}
}
\ No newline at end of file
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