Commit a030a6e7 authored by DatHV's avatar DatHV
Browse files

update fix bug, selected avatar image

parent 682ab1de
...@@ -3,6 +3,9 @@ ...@@ -3,6 +3,9 @@
<uses-permission android:name="android.permission.WRITE_CONTACTS"/> <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application <application
android:label="@string/app_name" android:label="@string/app_name"
...@@ -104,5 +107,15 @@ ...@@ -104,5 +107,15 @@
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
</intent> </intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="mailto" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="tel" />
</intent>
</queries> </queries>
</manifest> </manifest>
...@@ -42,6 +42,11 @@ post_install do |installer| ...@@ -42,6 +42,11 @@ post_install do |installer|
flutter_additional_ios_build_settings(target) flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config| target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0' config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
'PERMISSION_CAMERA=1',
'PERMISSION_PHOTOS=1',
]
end end
end end
end end
...@@ -2,14 +2,12 @@ ...@@ -2,14 +2,12 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>$(APP_DISPLAY_NAME)</string> <string>MyPoint</string>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>MyPoint</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
...@@ -33,7 +31,7 @@ ...@@ -33,7 +31,7 @@
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>Ứng dụng cần quyền Camera để quét mã.</string> <string>Ứng dụng cần quyền Camera để xử dụng tính năng này</string>
<key>NSContactsUsageDescription</key> <key>NSContactsUsageDescription</key>
<string>Ứng dụng cần quyền để truy cập danh bạ</string> <string>Ứng dụng cần quyền để truy cập danh bạ</string>
<key>NSFaceIDUsageDescription</key> <key>NSFaceIDUsageDescription</key>
...@@ -44,6 +42,8 @@ ...@@ -44,6 +42,8 @@
<string>Ứng dụng cần Micro khi quay video.</string> <string>Ứng dụng cần Micro khi quay video.</string>
<key>NSPhotoLibraryAddUsageDescription</key> <key>NSPhotoLibraryAddUsageDescription</key>
<string>Ứng dụng cần quyền Lưu ảnh vào thư viện.</string> <string>Ứng dụng cần quyền Lưu ảnh vào thư viện.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Ứng dụng cần quyền truy cập thư viện ảnh.</string>
<key>UIApplicationSupportsIndirectInputEvents</key> <key>UIApplicationSupportsIndirectInputEvents</key>
<true/> <true/>
<key>UIBackgroundModes</key> <key>UIBackgroundModes</key>
......
...@@ -126,4 +126,5 @@ class APIPaths {//sandbox ...@@ -126,4 +126,5 @@ class APIPaths {//sandbox
static const String myProductMarkAsNotUsedYet = "/myProductMarkAsNotUsedYet/1.0.0"; static const String myProductMarkAsNotUsedYet = "/myProductMarkAsNotUsedYet/1.0.0";
static const String submitCampaignViewVoucherComplete = "/campaign/api/v3.0/view-voucher/complete"; static const String submitCampaignViewVoucherComplete = "/campaign/api/v3.0/view-voucher/complete";
static const String webUpdateProfile = "/user/api/v2.0/account/users/loginPartner/updateProfile"; static const String webUpdateProfile = "/user/api/v2.0/account/users/loginPartner/updateProfile";
static const String updateImageRequest = "/feedbackAddImageRequest/1.0.0";
} }
...@@ -4,6 +4,9 @@ import 'package:dio/dio.dart'; ...@@ -4,6 +4,9 @@ import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:mypoint_flutter_app/shared/preferences/data_preference.dart'; import 'package:mypoint_flutter_app/shared/preferences/data_preference.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import '../../services/device_info.dart';
import '../../services/package_info.dart';
import '../../../app/config/api_paths.dart';
class RequestInterceptor extends Interceptor { class RequestInterceptor extends Interceptor {
@override @override
...@@ -28,6 +31,8 @@ class RequestInterceptor extends Interceptor { ...@@ -28,6 +31,8 @@ class RequestInterceptor extends Interceptor {
options.headers.addAll(headers); options.headers.addAll(headers);
await _applyDeleteAccountHeadersIfNeeded(options);
if (kIsWeb) { if (kIsWeb) {
_normalizeWebBody(options); _normalizeWebBody(options);
} }
...@@ -74,4 +79,23 @@ class RequestInterceptor extends Interceptor { ...@@ -74,4 +79,23 @@ class RequestInterceptor extends Interceptor {
debugPrint('⚠️ RequestInterceptor: failed to normalize JSON body - $e'); debugPrint('⚠️ RequestInterceptor: failed to normalize JSON body - $e');
} }
} }
Future<void> _applyDeleteAccountHeadersIfNeeded(RequestOptions options) async {
if (!options.path.contains(APIPaths.verifyDeleteAccount)) return;
try {
final deviceKey = await DeviceInfo.getDeviceId();
final details = await DeviceInfo.getDetails();
final version = await AppInfoHelper.version;
options.headers.addAll({
'device-key': deviceKey,
'operating-system': details.operatingSystem ?? 'Unknown',
'device-name': details.userDeviceName ?? 'Unknown',
'version': version,
'requestId': const Uuid().v4(),
'lang': 'vi',
});
} catch (e) {
debugPrint('⚠️ RequestInterceptor: failed to add delete headers - $e');
}
}
} }
...@@ -71,6 +71,56 @@ class RestfulAPIClient { ...@@ -71,6 +71,56 @@ class RestfulAPIClient {
} }
} }
Future<BaseResponseModel<T>> requestMultipart<T>(
String path,
FormData formData,
T Function(dynamic json) parser, {
bool silent = false,
bool allowRetry = false,
}) async {
final opt = _opts(Method.POST, silent, allowRetry)
.copyWith(contentType: 'multipart/form-data')
.compose(_dio.options, path, data: formData);
try {
final res = await _dio.fetch<dynamic>(opt);
final status = res.statusCode ?? 0;
final map = _asJson(res.data);
try {
final model = BaseResponseModel<T>.fromJson(map, parser);
return model;
} catch (_) {
final msg = _extractMessage(map, status) ?? 'HTTP $status';
if (_isOk(status)) {
T? data;
try {
data = parser(map);
} catch (_) {}
return BaseResponseModel<T>(
status: "success",
message: map['message']?.toString(),
data: data,
code: status,
);
} else {
return BaseResponseModel<T>(status: "fail", message: msg, data: null, code: status);
}
}
} on DioException catch (e) {
_debug('DioException: $e');
final status = e.response?.statusCode;
final map = _asJson(e.response?.data);
final errorCode = map['error_code']?.toString() ?? map['errorCode']?.toString() ?? e.error?.toString();
if (errorCode != null && ErrorCodes.tokenInvalidCodes.contains(errorCode)) {
rethrow;
}
final msg = _extractMessage(map, status) ?? e.message ?? Constants.commonError;
return BaseResponseModel<T>(status: "fail", message: msg, data: null, code: status);
} catch (e) {
_debug('Unknown exception: $e');
return BaseResponseModel<T>(status: "fail", message: Constants.commonError, data: null);
}
}
Options _opts(Method m, bool silent, bool allowRetry) => Options( Options _opts(Method m, bool silent, bool allowRetry) => Options(
method: m.name, method: m.name,
validateStatus: (_) => true, validateStatus: (_) => true,
......
import 'dart:io'; import 'dart:io';
import 'package:dio/dio.dart';
import 'package:mypoint_flutter_app/app/config/api_paths.dart'; import 'package:mypoint_flutter_app/app/config/api_paths.dart';
import 'package:mypoint_flutter_app/shared/widgets/base_view/base_response_model.dart'; import 'package:mypoint_flutter_app/shared/widgets/base_view/base_response_model.dart';
import 'package:mypoint_flutter_app/app/config/constants.dart'; import 'package:mypoint_flutter_app/app/config/constants.dart';
...@@ -7,6 +8,7 @@ import 'package:mypoint_flutter_app/core/network/restful_api_client.dart'; ...@@ -7,6 +8,7 @@ import 'package:mypoint_flutter_app/core/network/restful_api_client.dart';
import 'package:mypoint_flutter_app/shared/preferences/data_preference.dart'; import 'package:mypoint_flutter_app/shared/preferences/data_preference.dart';
import 'package:mypoint_flutter_app/features/voucher/models/product_model.dart'; import 'package:mypoint_flutter_app/features/voucher/models/product_model.dart';
import '../../app/config/callbacks.dart'; import '../../app/config/callbacks.dart';
import '../../features/personal/personal_edit_item_model.dart';
import '../services/device_info.dart'; import '../services/device_info.dart';
import '../../shared/navigation/directional_screen.dart'; import '../../shared/navigation/directional_screen.dart';
import '../../features/home/models/header_home_model.dart'; import '../../features/home/models/header_home_model.dart';
...@@ -49,8 +51,34 @@ import '../../features/transaction/history/transaction_category_model.dart'; ...@@ -49,8 +51,34 @@ import '../../features/transaction/history/transaction_category_model.dart';
import '../../features/transaction/history/transaction_history_model.dart'; import '../../features/transaction/history/transaction_history_model.dart';
import '../../features/transaction/history/transaction_history_response_model.dart'; import '../../features/transaction/history/transaction_history_response_model.dart';
extension RestfulAPIClientAllRequest on RestfulAPIClient { extension RestfulAPIClientAllRequest on RestfulAPIClient {
Future<BaseResponseModel<UpdateImageResponseModel>> uploadImage(String imagePath, String feedbackId) async {
final token = DataPreference.instance.token ?? "";
final formData = FormData.fromMap({
"access_token": token,
"feedback_id": feedbackId,
"lang": "vi",
"image_data": await MultipartFile.fromFile(
imagePath,
filename: imagePath.split('/').last,
),
});
return requestMultipart(APIPaths.updateImageRequest, formData, _parseUpdateImageResponse);
}
UpdateImageResponseModel _parseUpdateImageResponse(dynamic data) {
if (data is Json) {
if (data['image_id'] != null) {
return UpdateImageResponseModel.fromJson(data);
}
final nested = data['data'];
if (nested is Json) {
return UpdateImageResponseModel.fromJson(nested);
}
}
return UpdateImageResponseModel(imageId: null);
}
Future<BaseResponseModel<UpdateResponseModel>> checkUpdateApp() async { Future<BaseResponseModel<UpdateResponseModel>> checkUpdateApp() async {
final operatingSystem = Platform.operatingSystem; final operatingSystem = Platform.operatingSystem;
final version = await AppInfoHelper.version; final version = await AppInfoHelper.version;
......
...@@ -71,7 +71,7 @@ class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicS ...@@ -71,7 +71,7 @@ class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicS
await viewModel.refreshData(isShowLoading: false); await viewModel.refreshData(isShowLoading: false);
}, },
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 100), padding: const EdgeInsets.fromLTRB(16, 12, 16, 140),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
......
...@@ -59,12 +59,19 @@ class CreatePasswordScreen extends StatelessWidget { ...@@ -59,12 +59,19 @@ class CreatePasswordScreen extends StatelessWidget {
return Text(err, style: const TextStyle(color: Colors.red)); return Text(err, style: const TextStyle(color: Colors.red));
} }
}), }),
const SizedBox(height: 8), Obx(() {
_buildInfoGuide(icon: Icons.info_outline, text: "Mật khẩu gồm 6 chữ số"), return Column(
const SizedBox(height: 4), crossAxisAlignment: CrossAxisAlignment.stretch,
_buildInfoGuide(icon: Icons.info_outline, text: "Không bao gồm dãy số trùng nhau"), children: [
const SizedBox(height: 4), const SizedBox(height: 8),
_buildInfoGuide(icon: Icons.info_outline, text: "Không bao gồm dãy số liên tiếp"), _buildInfoGuide(icon: Icons.info_outline, text: "Mật khẩu gồm 6 chữ số", isValidate: vm.isSixDigits.value),
const SizedBox(height: 4),
_buildInfoGuide(icon: Icons.info_outline, text: "Không bao gồm dãy số trùng nhau", isValidate: vm.isNotRepeatedSequence.value),
const SizedBox(height: 4),
_buildInfoGuide(icon: Icons.info_outline, text: "Không bao gồm dãy số liên tiếp", isValidate: vm.isNotSequential.value),
],
);
}),
], ],
), ),
), ),
...@@ -100,13 +107,14 @@ class CreatePasswordScreen extends StatelessWidget { ...@@ -100,13 +107,14 @@ class CreatePasswordScreen extends StatelessWidget {
}); });
} }
Widget _buildInfoGuide({required IconData icon, required String text}) { Widget _buildInfoGuide({required IconData icon, required String text, required bool isValidate}) {
final color = isValidate ? BaseColor.second400 : BaseColor.primary300;
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Icon(icon, color: BaseColor.second400, size: 24), Icon(icon, color: color, size: 24),
SizedBox(width: 8), SizedBox(width: 8),
Expanded(child: Text(text, style: const TextStyle(color: BaseColor.second400, fontSize: 14))), Expanded(child: Text(text, style: TextStyle(color: color, fontSize: 14))),
], ],
); );
} }
...@@ -118,7 +126,11 @@ class CreatePasswordScreen extends StatelessWidget { ...@@ -118,7 +126,11 @@ class CreatePasswordScreen extends StatelessWidget {
required ValueChanged<String> onChanged, required ValueChanged<String> onChanged,
}) { }) {
return TextField( return TextField(
inputFormatters: [LengthLimitingTextInputFormatter(6)], keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(6),
],
obscureText: obscureText, obscureText: obscureText,
onChanged: onChanged, onChanged: onChanged,
decoration: InputDecoration( decoration: InputDecoration(
......
...@@ -7,6 +7,9 @@ class CreatePasswordViewModel extends GetxController { ...@@ -7,6 +7,9 @@ class CreatePasswordViewModel extends GetxController {
var confirmPassword = "".obs; var confirmPassword = "".obs;
var errorMessage = "".obs; var errorMessage = "".obs;
var isButtonEnabled = false.obs; var isButtonEnabled = false.obs;
var isSixDigits = false.obs;
var isNotRepeatedSequence = false.obs;
var isNotSequential = false.obs;
CreatePasswordViewModel(this.repository); CreatePasswordViewModel(this.repository);
...@@ -21,7 +24,12 @@ class CreatePasswordViewModel extends GetxController { ...@@ -21,7 +24,12 @@ class CreatePasswordViewModel extends GetxController {
} }
void _validate() { void _validate() {
if (newPassword.value.length != 6) { isSixDigits.value = _isSixDigits(newPassword.value);
isNotRepeatedSequence.value = _isNotAllSameDigits(newPassword.value);
isNotSequential.value = _isNotSequentialDigits(newPassword.value);
if (!isSixDigits.value || !isNotRepeatedSequence.value || !isNotSequential.value) {
errorMessage.value = "";
isButtonEnabled.value = false; isButtonEnabled.value = false;
return; return;
} }
...@@ -39,6 +47,29 @@ class CreatePasswordViewModel extends GetxController { ...@@ -39,6 +47,29 @@ class CreatePasswordViewModel extends GetxController {
} }
} }
bool _isSixDigits(String value) => RegExp(r'^\d{6}$').hasMatch(value);
bool _isNotAllSameDigits(String value) {
if (!_isSixDigits(value)) return false;
final first = value[0];
for (var i = 1; i < value.length; i++) {
if (value[i] != first) return true;
}
return false;
}
bool _isNotSequentialDigits(String value) {
if (!_isSixDigits(value)) return false;
final digits = value.codeUnits.map((c) => c - 48).toList();
var asc = true;
var desc = true;
for (var i = 1; i < digits.length; i++) {
if (digits[i] != digits[i - 1] + 1) asc = false;
if (digits[i] != digits[i - 1] - 1) desc = false;
}
return !(asc || desc);
}
Future<void> onSubmit() async { Future<void> onSubmit() async {
if (!isButtonEnabled.value) return; if (!isButtonEnabled.value) return;
try { try {
......
import 'package:flutter/widgets.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/core/network/restful_api_client_all_request.dart'; import 'package:mypoint_flutter_app/core/network/restful_api_client_all_request.dart';
import 'package:mypoint_flutter_app/features/create_pass/signup_create_password_repository.dart'; import 'package:mypoint_flutter_app/features/create_pass/signup_create_password_repository.dart';
...@@ -18,7 +19,9 @@ class ResetCreatePasswordRepository extends RestfulApiViewModel implements ICrea ...@@ -18,7 +19,9 @@ class ResetCreatePasswordRepository extends RestfulApiViewModel implements ICrea
return client.accountPasswordReset(phoneNumber, password).then((value) { return client.accountPasswordReset(phoneNumber, password).then((value) {
hideLoading(); hideLoading();
if (value.status == "success" || value.code == 200) { if (value.status == "success" || value.code == 200) {
Get.offNamed(loginScreen, arguments: {'phone': phoneNumber, 'password': password}); WidgetsBinding.instance.addPostFrameCallback((_) {
Get.offNamed(loginScreen, arguments: {'phone': phoneNumber, 'password': password});
});
showToastMessage("Đặt lại mật khẩu thành công."); showToastMessage("Đặt lại mật khẩu thành công.");
} }
return value; return value;
......
...@@ -35,6 +35,10 @@ class SignUpCreatePasswordRepository extends RestfulApiViewModel implements ICre ...@@ -35,6 +35,10 @@ class SignUpCreatePasswordRepository extends RestfulApiViewModel implements ICre
}); });
} }
void _postFrame(VoidCallback action) {
WidgetsBinding.instance.addPostFrameCallback((_) => action());
}
void _autoLogin(String password) { void _autoLogin(String password) {
showLoading(); showLoading();
client.login(phoneNumber, password).then((response) async { client.login(phoneNumber, password).then((response) async {
...@@ -44,7 +48,9 @@ class SignUpCreatePasswordRepository extends RestfulApiViewModel implements ICre ...@@ -44,7 +48,9 @@ class SignUpCreatePasswordRepository extends RestfulApiViewModel implements ICre
await PushTokenService.uploadIfLogged(); await PushTokenService.uploadIfLogged();
_getUserProfile(); _getUserProfile();
} else { } else {
Get.offNamed(loginScreen, arguments: {'phone': phoneNumber}); _postFrame(() {
Get.offNamed(loginScreen, arguments: {'phone': phoneNumber});
});
} }
}); });
} }
...@@ -57,13 +63,19 @@ class SignUpCreatePasswordRepository extends RestfulApiViewModel implements ICre ...@@ -57,13 +63,19 @@ class SignUpCreatePasswordRepository extends RestfulApiViewModel implements ICre
if (value.isSuccess && userProfile != null) { if (value.isSuccess && userProfile != null) {
await DataPreference.instance.saveUserProfile(userProfile); await DataPreference.instance.saveUserProfile(userProfile);
if (await _biometricManager.canCheckBiometrics()) { if (await _biometricManager.canCheckBiometrics()) {
Get.to(BiometricAuthScreen()); _postFrame(() {
Get.to(BiometricAuthScreen());
});
} else { } else {
Get.toNamed(mainScreen); _postFrame(() {
Get.toNamed(mainScreen);
});
} }
} else { } else {
DataPreference.instance.clearLoginToken(); DataPreference.instance.clearLoginToken();
Get.offNamed(loginScreen, arguments: {'phone': phoneNumber}); _postFrame(() {
Get.offNamed(loginScreen, arguments: {'phone': phoneNumber});
});
} }
}); });
} }
......
...@@ -6,11 +6,11 @@ class CheckInDataModel { ...@@ -6,11 +6,11 @@ class CheckInDataModel {
final List<Counter>? counters; final List<Counter>? counters;
Counter? get dailyCounter { Counter? get dailyCounter {
return counters?.firstWhereOrNull((counter) => counter.counterName == 'CUSTOMER_CHECKIN_DAILY_TFC'); return counters?.where((counter) => counter.counterName == "CUSTOMER_CHECKIN_DAILY_TFC").last;
} }
Counter? get currentCounter { Counter? get currentCounter {
return counters?.firstWhereOrNull((counter) => counter.counterName == 'CUSTOMER_CHECKIN_NE_TFC'); return counters?.where((counter) => counter.counterName == "CUSTOMER_CHECKIN_NE_TFC").last;
} }
CheckInDataModel({this.campaignCode, this.counters}); CheckInDataModel({this.campaignCode, this.counters});
......
...@@ -40,7 +40,8 @@ class _DailyCheckInScreenState extends BaseState<DailyCheckInScreen> with BasicS ...@@ -40,7 +40,8 @@ class _DailyCheckInScreenState extends BaseState<DailyCheckInScreen> with BasicS
return Scaffold( return Scaffold(
appBar: CustomNavigationBar(title: "Check-in nhận quà"), appBar: CustomNavigationBar(title: "Check-in nhận quà"),
body: Obx(() { body: Obx(() {
int point = _viewModel.submitData.value?.customerBalance?.amountActive?.toInt() ?? UserPointManager().point; final reward = _viewModel.submitData.value?.reward?.toInt() ?? 0;
final point = _viewModel.submitData.value?.customerBalance?.amountActive?.toInt() ?? (UserPointManager().point + reward);
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
......
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