Commit 56e8b038 authored by DatHV's avatar DatHV
Browse files

update game, shopping

parent c285d072
......@@ -10,13 +10,13 @@ import '../resouce/define_image.dart';
import '../resouce/text_style.dart';
import '../widgets/alert/custom_alert_dialog.dart';
import '../widgets/alert/data_alert_model.dart';
import '../widgets/alert/popup_data_model.dart';
abstract class BaseScreen extends StatefulWidget {
const BaseScreen({super.key});
}
abstract class BaseState<Screen extends BaseScreen> extends State<Screen> {
var isShowLoading = false;
@override
void initState() {
......@@ -26,87 +26,26 @@ abstract class BaseState<Screen extends BaseScreen> extends State<Screen> {
}
}
AppBar headerView(
{required String title,
Color backgroundColor = Colors.white,
Color iconColor = Colors.white,
Color textColor = BaseColor.second900,
List<Widget>? actions,
Widget? leading,
TabBar? tabBar,
double elevation = 0.5,
bool isShowBack = true}) {
return AppBar(
iconTheme: IconThemeData(
color: iconColor, //change your color here
),
elevation: elevation,
bottom: tabBar,
backgroundColor: backgroundColor,
centerTitle: true,
automaticallyImplyLeading: isShowBack,
actions: actions,
leading: leading,
title: Text(
title.tr,
textAlign: TextAlign.center,
style: textSemiBold.copyWith(fontSize: 18, color: textColor),
),
);
}
double heightBottomSafa() {
return MediaQuery.of(context).padding.bottom;
}
FToast fToast = FToast();
showMessage(BuildContext context, String message) {
fToast.init(context);
Widget toast = Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25.0),
color: BaseColor.second400,
),
child: Row(mainAxisSize: MainAxisSize.min, children: [
SvgPicture.asset(icLogo, width: 16, height: 16),
const SizedBox(width: 12),
Flexible(
child: Text(message,
style:
textNormal.copyWith(fontSize: 16, color: Colors.white)))
]));
fToast.showToast(
child: toast,
gravity: ToastGravity.BOTTOM,
toastDuration: const Duration(seconds: 2),
showPopup({
required PopupDataModel data,
bool? barrierDismissibl,
bool showCloseButton = false,
ButtonsDirection direction = ButtonsDirection.column,
}) {
Get.dialog(
CustomAlertDialog(alertData: data.dataAlertModel, showCloseButton: showCloseButton, direction: direction),
barrierDismissible: barrierDismissibl ?? true,
);
}
showAlertDialog(BuildContext context, String message,
{Callback<bool>? callback}) {
context.showAlertDialog(message, callback: callback);
}
showConfirmAlertDialog(BuildContext context, String message,
{String cancel = "Huỷ",
String confirm = "Xác nhận",
Callback<bool>? callback}) {
context.showConfirmAlertDialog(message, cancel: cancel, confirm: confirm, callback: callback);
}
showAlert({required DataAlertModel data,
showAlert({
required DataAlertModel data,
bool? barrierDismissibl,
bool showCloseButton = true,
ButtonsDirection direction = ButtonsDirection.column}) {
ButtonsDirection direction = ButtonsDirection.column,
}) {
Get.dialog(
CustomAlertDialog(
alertData: data,
showCloseButton: showCloseButton,
direction: direction,
),
CustomAlertDialog(alertData: data, showCloseButton: showCloseButton, direction: direction),
barrierDismissible: barrierDismissibl ?? false,
);
}
......@@ -115,9 +54,9 @@ abstract class BaseState<Screen extends BaseScreen> extends State<Screen> {
Get.dialog(
CustomAlertDialog(
alertData: DataAlertModel(
background: "assets/images/ic_pipi_03.png",
localHeaderImage: "assets/images/ic_pipi_03.png",
title: "",
content: content,
description: content,
buttons: [
AlertButton(
text: "Đã Hiểu",
......@@ -129,7 +68,6 @@ abstract class BaseState<Screen extends BaseScreen> extends State<Screen> {
},
bgColor: BaseColor.primary500,
textColor: Colors.white,
isPrimary: true,
),
],
),
......
......@@ -32,4 +32,6 @@ class APIPaths {
static const String productCustomerLikes = "/product/api/v2.0/customer/likes";
static const String productCustomerUnlikes = "/product/api/v2.0/customer/likes/%@";
static const String getGames = "/campaign/api/v3.0/games";
static const String verifyOrderProduct = "/order/api/v1.0/verify-product";
static const String getGameDetail = "/campaign/api/v3.0/games/%@/play";
}
\ No newline at end of file
......@@ -2,7 +2,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;
// device key
static var directionInApp = "IN-APP";
}
class ErrorCodes {
......
import 'dart:ui';
extension ColorExtension on Color {
Color get invert => Color.fromARGB(
alpha,
255 - red,
255 - green,
255 - blue,
);
}
\ No newline at end of file
......@@ -53,3 +53,19 @@ extension StringDateExtension on String {
}
}
}
extension HexColorExtension on String {
Color? toColor() {
try {
final hex = replaceAll('#', '').toUpperCase();
if (hex.length == 6) {
return Color(int.parse('FF$hex', radix: 16)); // thêm alpha mặc định
} else if (hex.length == 8) {
return Color(int.parse(hex, radix: 16));
}
} catch (_) {
// Ignored – return null bên dưới
}
return null;
}
}
\ No newline at end of file
......@@ -14,6 +14,7 @@ import '../model/auth/profile_response_model.dart';
import '../model/update_response_model.dart';
import '../preference/point/header_home_model.dart';
import '../screen/faqs/faqs_model.dart';
import '../screen/game/models/game_bundle_item_model.dart';
import '../screen/onboarding/model/check_phone_response_model.dart';
import '../screen/onboarding/model/onboarding_info_model.dart';
import '../screen/otp/model/create_otp_response_model.dart';
......@@ -30,12 +31,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
Future<BaseResponseModel<UpdateResponseModel>> checkUpdateApp() async {
String version = Platform.version;
final body = {"operating_system": "iOS", "software_model": "MyPoint", "version": "1.21.7", "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));
}
Future<BaseResponseModel<OnboardingInfoModel>> getOnboardingInfo() async {
......@@ -84,17 +80,14 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
APIPaths.otpDeleteAccountRequest,
Method.POST,
{},
(data) => CreateOTPResponseModel.fromJson(data as Json),
(data) => CreateOTPResponseModel.fromJson(data as Json),
);
}
Future<BaseResponseModel<CreateOTPResponseModel>> verifyDeleteAccount(String otp) async {
return requestNormal(
APIPaths.verifyDeleteAccount,
Method.POST,
{"otp": otp},
(data) => CreateOTPResponseModel.fromJson(data as Json),
);
return requestNormal(APIPaths.verifyDeleteAccount, Method.POST, {
"otp": otp,
}, (data) => CreateOTPResponseModel.fromJson(data as Json));
}
Future<BaseResponseModel<EmptyCodable>> signup(String phone, String password) async {
......@@ -109,19 +102,21 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
"username": phone,
"password": password.toSha256(),
"device_key": deviceKey,
"workspace_code": "8854"};
"workspace_code": "8854",
};
return requestNormal(APIPaths.login, Method.POST, body, (data) => LoginTokenResponseModel.fromJson(data as Json));
}
Future<BaseResponseModel<LoginTokenResponseModel>> loginWithBiometric(String phone) async {
var deviceKey = await DeviceInfo.getDeviceId();
var bioToken = await DataPreference.instance.getBioToken(phone) ?? "";
final body = {
"username": phone,
"bioToken": bioToken,
"deviceKey": deviceKey,
"workspaceCode": "8854"};
return requestNormal(APIPaths.loginWithBiometric, Method.POST, body, (data) => LoginTokenResponseModel.fromJson(data as Json));
final body = {"username": phone, "bioToken": bioToken, "deviceKey": deviceKey, "workspaceCode": "8854"};
return requestNormal(
APIPaths.loginWithBiometric,
Method.POST,
body,
(data) => LoginTokenResponseModel.fromJson(data as Json),
);
}
Future<BaseResponseModel<ProfileResponseModel>> getUserProfile() async {
......@@ -196,7 +191,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
APIPaths.accountPasswordReset,
Method.POST,
body,
(data) => EmptyCodable.fromJson(data as Json),
(data) => EmptyCodable.fromJson(data as Json),
);
}
......@@ -207,7 +202,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
APIPaths.accountPasswordChange,
Method.POST,
body,
(data) => EmptyCodable.fromJson(data as Json),
(data) => EmptyCodable.fromJson(data as Json),
);
}
......@@ -218,7 +213,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
APIPaths.accountLoginForPasswordChange,
Method.POST,
body,
(data) => EmptyCodable.fromJson(data as Json),
(data) => EmptyCodable.fromJson(data as Json),
);
}
......@@ -229,7 +224,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
APIPaths.bioCredential,
Method.POST,
body,
(data) => BiometricRegisterResponseModel.fromJson(data as Json),
(data) => BiometricRegisterResponseModel.fromJson(data as Json),
);
}
......@@ -240,7 +235,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
APIPaths.registerBiometric,
Method.POST,
body,
(data) => BiometricRegisterResponseModel.fromJson(data as Json),
(data) => BiometricRegisterResponseModel.fromJson(data as Json),
);
}
......@@ -248,21 +243,11 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
var deviceKey = await DeviceInfo.getDeviceId();
final path = "${APIPaths.unRegisterBiometric}/$deviceKey";
final body = {"deviceKey": deviceKey};
return requestNormal(
path,
Method.POST,
body,
(data) => EmptyCodable.fromJson(data as Json),
);
return requestNormal(path, Method.POST, body, (data) => EmptyCodable.fromJson(data as Json));
}
Future<BaseResponseModel<HeaderHomeModel>> getHomeHeaderData() async {
return requestNormal(
APIPaths.headerHome,
Method.GET,
{},
(data) => HeaderHomeModel.fromJson(data as Json),
);
return requestNormal(APIPaths.headerHome, Method.GET, {}, (data) => HeaderHomeModel.fromJson(data as Json));
}
Future<BaseResponseModel<List<ProductModel>>> getProducts(Json body) async {
......@@ -277,18 +262,13 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
APIPaths.getSearchProducts,
Method.POST,
body,
(data) =>SearchProductResponseModel.fromJson(data as Json),
(data) => SearchProductResponseModel.fromJson(data as Json),
);
}
Future<BaseResponseModel<ProductModel>> getProduct(int id) async {
final path = APIPaths.getProductDetail.replaceAll("%@", id.toString());
return requestNormal(
path,
Method.GET,
{},
(data) =>ProductModel.fromJson(data as Json),
);
return requestNormal(path, Method.GET, {}, (data) => ProductModel.fromJson(data as Json));
}
Future<BaseResponseModel<List<ProductStoreModel>>> getProductStores(int id) async {
......@@ -308,12 +288,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
Future<BaseResponseModel<EmptyCodable>> unlikeProduct(int id) async {
final path = APIPaths.productCustomerUnlikes.replaceAll("%@", id.toString());
return requestNormal(
path,
Method.DELETE,
{},
(data) => EmptyCodable.fromJson(data as Json),
);
return requestNormal(path, Method.DELETE, {}, (data) => EmptyCodable.fromJson(data as Json));
}
Future<BaseResponseModel<GameBundleResponse>> getGames() async {
......@@ -321,4 +296,18 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
return GameBundleResponse.fromJson(data as Json);
});
}
}
\ No newline at end of file
Future<BaseResponseModel<List<EmptyCodable>>> verifyOrderProduct(Json body) async {
return requestNormal(APIPaths.verifyOrderProduct, Method.POST, body, (data) {
final list = data as List<dynamic>;
return list.map((e) => EmptyCodable.fromJson(e)).toList();
});
}
Future<BaseResponseModel<GameBundleItemModel>> getGameDetail(String id) async {
final path = APIPaths.getGameDetail.replaceAll("%@", id);
return requestNormal(path, Method.POST, {}, (data) {
return GameBundleItemModel.fromJson(data as Json);
});
}
}
......@@ -71,23 +71,21 @@ class BiometricManager {
final result = await Get.dialog<bool>(
CustomAlertDialog(
alertData: DataAlertModel(
background: "assets/images/bg_alert_header.png",
localHeaderImage: "assets/images/bg_alert_header.png",
title: title,
content: content,
description: content,
buttons: [
AlertButton(
text: "Huỷ",
onPressed: () => Get.back(result: false),
bgColor: BaseColor.primary500,
textColor: Colors.white,
isPrimary: true,
),
AlertButton(
text: "Đồng ý",
onPressed: () => Get.back(result: true),
bgColor: BaseColor.primary500,
textColor: Colors.white,
isPrimary: true,
),
],
),
......
......@@ -40,16 +40,15 @@ class _BiometricAuthScreenState extends BaseState<BiometricAuthScreen> with Basi
? "Từ bây giờ bạn có thể sử dụng $type để đăng nhập thay vì nhập mật khẩu."
: "Đã có lỗi xảy ra khi xác thực thông tin sinh trắc học. Vui lòng thử lại sau.";
DataAlertModel alertData = DataAlertModel(
background: result? "assets/images/ic_pipi_05.png" : "assets/images/ic_pipi_03.png",
localHeaderImage: result? "assets/images/ic_pipi_05.png" : "assets/images/ic_pipi_03.png",
title: title,
content: message,
description: message,
buttons: [
AlertButton(
text: "Đã hiểu",
onPressed: () => Get.offAll(MainTabScreen()),
bgColor: BaseColor.primary500,
textColor: Colors.white,
isPrimary: true,
),
],
);
......
......@@ -163,8 +163,8 @@ class _ChangePassScreenState extends BaseState<ChangePassScreen> with BasicState
onPressed: () {
final dataAlert = DataAlertModel(
title: "Quên mật khẩu",
content: "Bạn cần đăng xuất khỏi tài khoản này để đặt lại mật khẩu. Bạn chắc chứ?.",
background: "assets/images/ic_pipi_03.png",
description: "Bạn cần đăng xuất khỏi tài khoản này để đặt lại mật khẩu. Bạn chắc chứ?.",
localHeaderImage: "assets/images/ic_pipi_03.png",
buttons: [AlertButton(
text: "Đồng ý",
onPressed: () {
......@@ -173,14 +173,12 @@ class _ChangePassScreenState extends BaseState<ChangePassScreen> with BasicState
},
bgColor: BaseColor.primary500,
textColor: Colors.white,
isPrimary: true,
),
AlertButton(
text: "Huỷ",
onPressed: () => Get.back(),
bgColor: Colors.white,
textColor: BaseColor.second500,
isPrimary: false,
),],
);
showAlert(data: dataAlert);
......
......@@ -25,6 +25,18 @@ class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState {
void initState() {
super.initState();
_viewModel.getGames();
_viewModel.onShowAlertError = (message) {
if (message.isNotEmpty) {
showAlertError(content: message);
}
};
_viewModel.gotoGameDetail = (data) {
if (data.popup != null) {
showPopup(data: data.popup!);
} else {
Get.toNamed(gameCardScreen, arguments: data);
}
};
}
@override
......@@ -70,7 +82,7 @@ class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState {
final item = _viewModel.games[index];
return GestureDetector(
onTap: () {
Get.toNamed(gameCardScreen, arguments: item);
_viewModel.getGameDetail(item.id ?? "");
},
child: AspectRatio(
aspectRatio: 343/132,
......
......@@ -9,13 +9,14 @@ class GameTabViewModel extends RestfulApiViewModel {
final RxList<GameBundleItemModel> games = <GameBundleItemModel>[].obs;
var turnsNumberText = "".obs;
var isLoading = false.obs;
var errorMessage = "".obs;
void Function(String message)? onShowAlertError;
void Function(GameBundleItemModel data)? gotoGameDetail;
void getGames() {
isLoading(true);
client.getGames().then((value) {
if (!value.isSuccess) {
errorMessage.value = value.errorMessage ?? Constants.commonError;
onShowAlertError?.call(value.errorMessage ?? Constants.commonError);
} else {
games.value = value.data?.games ?? [];
turnsNumberText.value = value.data?.turnsNumberText ?? "";
......@@ -23,4 +24,18 @@ class GameTabViewModel extends RestfulApiViewModel {
isLoading(false);
});
}
void getGameDetail(String gameId) {
isLoading(true);
client.getGameDetail(gameId).then((value) {
if (!value.isSuccess) {
onShowAlertError?.call(value.errorMessage ?? Constants.commonError);
} else if (value.data != null) {
gotoGameDetail?.call(value.data!);
} else {
onShowAlertError?.call(Constants.commonError);
}
isLoading(false);
});
}
}
\ No newline at end of file
import 'package:json_annotation/json_annotation.dart';
import '../../../widgets/alert/popup_data_model.dart';
import 'game_card_item_model.dart';
part 'game_bundle_item_model.g.dart';
......@@ -11,6 +12,7 @@ class GameBundleItemModel {
final String? background;
final String? description;
final List<GameCardItemModel>? options;
final PopupDataModel? popup;
GameBundleItemModel({
this.id,
......@@ -19,6 +21,7 @@ class GameBundleItemModel {
this.background,
this.description,
this.options,
this.popup
});
factory GameBundleItemModel.fromJson(Map<String, dynamic> json) => _$GameBundleItemModelFromJson(json);
......
......@@ -19,6 +19,10 @@ GameBundleItemModel _$GameBundleItemModelFromJson(Map<String, dynamic> json) =>
(e) => GameCardItemModel.fromJson(e as Map<String, dynamic>),
)
.toList(),
popup:
json['popup'] == null
? null
: PopupDataModel.fromJson(json['popup'] as Map<String, dynamic>),
);
Map<String, dynamic> _$GameBundleItemModelToJson(
......@@ -30,4 +34,5 @@ Map<String, dynamic> _$GameBundleItemModelToJson(
'background': instance.background,
'description': instance.description,
'options': instance.options,
'popup': instance.popup,
};
// home_screen.dart
import 'package:flutter/material.dart';
import 'package:game_miniapp/game_miniapp.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/preference/data_preference.dart';
import 'package:mypoint_flutter_app/shared/router_gage.dart';
......@@ -16,6 +17,7 @@ class HomeScreen extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(onPressed: () => _showMiniGame(context), child: const Text('Mini Game')),
ElevatedButton(onPressed: () => _logout(context), child: const Text('Đăng xuất')),
ElevatedButton(onPressed: () => _showSetting(context), child: const Text('Setting')),
],
......@@ -24,6 +26,13 @@ class HomeScreen extends StatelessWidget {
);
}
void _showMiniGame(BuildContext context) async {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const GameMiniAppScreen()),
);
}
void _logout(BuildContext context) async {
final confirm = await showDialog<bool>(
context: context,
......
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