Commit 417358c5 authored by DatHV's avatar DatHV
Browse files

update authen 401, device manager, interestied category

parent efb4662c
...@@ -3,7 +3,7 @@ import 'package:get/get.dart'; ...@@ -3,7 +3,7 @@ import 'package:get/get.dart';
import '../../base/base_screen.dart'; import '../../base/base_screen.dart';
import '../../base/basic_state.dart'; import '../../base/basic_state.dart';
import '../../permission/biometric_manager.dart'; import '../../permission/biometric_manager.dart';
import '../../resouce/base_color.dart'; import '../../resources/base_color.dart';
import '../../widgets/alert/custom_alert_dialog.dart'; import '../../widgets/alert/custom_alert_dialog.dart';
import '../../widgets/alert/data_alert_model.dart'; import '../../widgets/alert/data_alert_model.dart';
import '../../widgets/back_button.dart'; import '../../widgets/back_button.dart';
...@@ -29,11 +29,9 @@ class _LoginScreenState extends BaseState<LoginScreen> with BasicState { ...@@ -29,11 +29,9 @@ class _LoginScreenState extends BaseState<LoginScreen> with BasicState {
void initState() { void initState() {
super.initState(); super.initState();
final args = Get.arguments; final args = Get.arguments;
if (args is String) { if (args is Map) {
phoneNumber = args;
} else if (args is Map) {
phoneNumber = args['phone']; phoneNumber = args['phone'];
fullName = args['fullName']; fullName = args['fullName'] ?? '';
} }
loginVM.onShowChangePass = (message) { loginVM.onShowChangePass = (message) {
Get.dialog( Get.dialog(
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../resouce/base_color.dart'; import '../../resources/base_color.dart';
import '../affiliate/affiliate_tab_screen.dart'; import '../affiliate/affiliate_tab_screen.dart';
import '../game/game_tab_screen.dart'; import '../game/game_tab_screen.dart';
import '../home/header_home_viewmodel.dart'; import '../home/header_home_viewmodel.dart';
......
...@@ -3,7 +3,7 @@ import 'package:intl/intl.dart'; ...@@ -3,7 +3,7 @@ import 'package:intl/intl.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.dart'; import 'package:mypoint_flutter_app/extensions/string_extension.dart';
import '../../preference/data_preference.dart'; import '../../preference/data_preference.dart';
import '../../resouce/base_color.dart'; import '../../resources/base_color.dart';
import '../../widgets/image_loader.dart'; import '../../widgets/image_loader.dart';
import '../../widgets/measure_size.dart'; import '../../widgets/measure_size.dart';
import 'models/membership_level_model.dart'; import 'models/membership_level_model.dart';
......
...@@ -3,13 +3,13 @@ import 'package:get/get.dart'; ...@@ -3,13 +3,13 @@ import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart'; import 'package:get/get_core/src/get_main.dart';
import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart'; import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart';
import 'package:mypoint_flutter_app/extensions/num_extension.dart'; import 'package:mypoint_flutter_app/extensions/num_extension.dart';
import 'package:mypoint_flutter_app/resouce/base_color.dart';
import 'package:mypoint_flutter_app/screen/mobile_card/product_mobile_card_viewmodel.dart'; import 'package:mypoint_flutter_app/screen/mobile_card/product_mobile_card_viewmodel.dart';
import 'package:mypoint_flutter_app/screen/mobile_card/usable_mobile_card_popup.dart'; import 'package:mypoint_flutter_app/screen/mobile_card/usable_mobile_card_popup.dart';
import 'package:mypoint_flutter_app/widgets/custom_app_bar.dart'; import 'package:mypoint_flutter_app/widgets/custom_app_bar.dart';
import 'package:mypoint_flutter_app/widgets/image_loader.dart'; import 'package:mypoint_flutter_app/widgets/image_loader.dart';
import '../../base/base_screen.dart'; import '../../base/base_screen.dart';
import '../../base/basic_state.dart'; import '../../base/basic_state.dart';
import '../../resources/base_color.dart';
import '../../widgets/alert/custom_alert_dialog.dart'; import '../../widgets/alert/custom_alert_dialog.dart';
import '../../widgets/alert/data_alert_model.dart'; import '../../widgets/alert/data_alert_model.dart';
......
...@@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; ...@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:mypoint_flutter_app/extensions/datetime_extensions.dart'; import 'package:mypoint_flutter_app/extensions/datetime_extensions.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.dart'; import 'package:mypoint_flutter_app/extensions/string_extension.dart';
import 'package:mypoint_flutter_app/resouce/base_color.dart'; import '../../resources/base_color.dart';
import 'models/usable_voucher_model.dart'; import 'models/usable_voucher_model.dart';
class UsableMobileCardPopup extends StatelessWidget { class UsableMobileCardPopup extends StatelessWidget {
......
...@@ -3,7 +3,7 @@ import 'package:flutter/services.dart'; ...@@ -3,7 +3,7 @@ import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.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 '../../resources/base_color.dart';
import '../../widgets/alert/data_alert_model.dart'; import '../../widgets/alert/data_alert_model.dart';
import '../../widgets/back_button.dart'; import '../../widgets/back_button.dart';
import '../../widgets/custom_empty_widget.dart'; import '../../widgets/custom_empty_widget.dart';
......
...@@ -7,7 +7,7 @@ import 'package:mypoint_flutter_app/shared/router_gage.dart'; ...@@ -7,7 +7,7 @@ import 'package:mypoint_flutter_app/shared/router_gage.dart';
import '../../base/base_screen.dart'; import '../../base/base_screen.dart';
import '../../base/basic_state.dart'; import '../../base/basic_state.dart';
import '../../configs/constants.dart'; import '../../configs/constants.dart';
import '../../resouce/base_color.dart'; import '../../resources/base_color.dart';
import '../biometric/biometric_screen.dart'; import '../biometric/biometric_screen.dart';
import '../faqs/faqs_screen.dart'; import '../faqs/faqs_screen.dart';
import '../login/login_screen.dart'; import '../login/login_screen.dart';
...@@ -73,7 +73,7 @@ class _OnboardingScreenState extends BaseState<OnboardingScreen> with BasicState ...@@ -73,7 +73,7 @@ class _OnboardingScreenState extends BaseState<OnboardingScreen> with BasicState
return; return;
} }
if (response.nextAction == "login") { if (response.nextAction == "login") {
Get.toNamed(loginScreen, arguments: _viewModel.phoneNumber.value); Get.toNamed(loginScreen, arguments: {'phone': _viewModel.phoneNumber.value});
} }
} }
......
...@@ -3,7 +3,7 @@ import 'package:get/get.dart'; ...@@ -3,7 +3,7 @@ import 'package:get/get.dart';
import 'package:pin_code_fields/pin_code_fields.dart'; import 'package:pin_code_fields/pin_code_fields.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 '../../resources/base_color.dart';
import '../../widgets/back_button.dart'; import '../../widgets/back_button.dart';
import '../../widgets/support_button.dart'; import '../../widgets/support_button.dart';
import 'otp_viewmodel.dart'; import 'otp_viewmodel.dart';
......
...@@ -31,7 +31,7 @@ class VerifyOtpRepository extends RestfulApiViewModel implements IOtpRepository ...@@ -31,7 +31,7 @@ class VerifyOtpRepository extends RestfulApiViewModel implements IOtpRepository
if (value.data?.claim?.action == "signup") { if (value.data?.claim?.action == "signup") {
Get.off(() => CreatePasswordScreen(repository: SignUpCreatePasswordRepository(phoneNumber))); Get.off(() => CreatePasswordScreen(repository: SignUpCreatePasswordRepository(phoneNumber)));
} else if (value.data?.claim?.action == "login") { } else if (value.data?.claim?.action == "login") {
Get.offNamed(loginScreen, arguments: phoneNumber); Get.offNamed(loginScreen, arguments: {'phone': phoneNumber});
} }
return value; return value;
}); });
......
...@@ -4,7 +4,7 @@ import 'package:get/get.dart'; ...@@ -4,7 +4,7 @@ import 'package:get/get.dart';
import '../../base/base_screen.dart'; import '../../base/base_screen.dart';
import '../../base/basic_state.dart'; import '../../base/basic_state.dart';
import '../../extensions/string_extension.dart'; import '../../extensions/string_extension.dart';
import '../../resouce/base_color.dart'; import '../../resources/base_color.dart';
import '../../shared/router_gage.dart'; import '../../shared/router_gage.dart';
import '../../widgets/back_button.dart'; import '../../widgets/back_button.dart';
import '../../widgets/network_image_with_aspect_ratio.dart'; import '../../widgets/network_image_with_aspect_ratio.dart';
......
...@@ -7,7 +7,7 @@ import 'package:mypoint_flutter_app/screen/personal/personal_gender.dart'; ...@@ -7,7 +7,7 @@ import 'package:mypoint_flutter_app/screen/personal/personal_gender.dart';
import 'package:mypoint_flutter_app/widgets/custom_app_bar.dart'; import 'package:mypoint_flutter_app/widgets/custom_app_bar.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 '../../resources/base_color.dart';
import '../../shared/router_gage.dart'; import '../../shared/router_gage.dart';
import '../../widgets/alert/data_alert_model.dart'; import '../../widgets/alert/data_alert_model.dart';
import '../../widgets/bottom_sheet_helper.dart'; import '../../widgets/bottom_sheet_helper.dart';
......
...@@ -7,7 +7,7 @@ import '../../base/base_screen.dart'; ...@@ -7,7 +7,7 @@ import '../../base/base_screen.dart';
import '../../base/basic_state.dart'; import '../../base/basic_state.dart';
import '../../preference/package_info.dart'; import '../../preference/package_info.dart';
import '../../preference/point/header_home_model.dart'; import '../../preference/point/header_home_model.dart';
import '../../resouce/base_color.dart'; import '../../resources/base_color.dart';
import '../../shared/router_gage.dart'; import '../../shared/router_gage.dart';
import '../../widgets/alert/data_alert_model.dart'; import '../../widgets/alert/data_alert_model.dart';
import '../home/header_home_viewmodel.dart'; import '../home/header_home_viewmodel.dart';
...@@ -115,12 +115,17 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState { ...@@ -115,12 +115,17 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState {
const Spacer(), const Spacer(),
Row( Row(
children: [ children: [
Row( GestureDetector(
children: [ onTap: () {
Image.asset("assets/images/ic_rank_gray.png", width: 30, height: 30, color: Colors.white), Get.toNamed(membershipScreen);
const SizedBox(width: 4), },
Text(level, style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)), child: Row(
], children: [
Image.asset("assets/images/ic_rank_gray.png", width: 30, height: 30, color: Colors.white),
const SizedBox(width: 4),
Text(level, style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)),
],
),
), ),
const Spacer(), const Spacer(),
Row( Row(
...@@ -286,14 +291,13 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState { ...@@ -286,14 +291,13 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState {
if (matched) found = true; if (matched) found = true;
return matched; return matched;
}); });
final phone = DataPreference.instance.phone; final phone = DataPreference.instance.phoneNumberUsedForLoginScreen;
if (phone != null) { final displayName = DataPreference.instance.displayName;
if (!found) { if (phone != null && !found) {
Get.offAllNamed(loginScreen, arguments: phone); Get.offAllNamed(loginScreen, arguments: {"phone": phone, 'fullName': displayName});
}
} else { } else {
DataPreference.instance.clearData(); DataPreference.instance.clearData();
Get.offAllNamed(loginScreen); Get.offAllNamed(onboardingScreen);
} }
} }
} }
import 'package:json_annotation/json_annotation.dart';
part 'popup_manager_model.g.dart';
@JsonSerializable()
class PopupManagerModel {
final String? id;
@JsonKey(name: 'screen_to_show')
final String? screenToShow;
@JsonKey(name: 'click_action_type')
final String? clickActionType;
@JsonKey(name: 'click_action_param')
final String? clickActionParam;
@JsonKey(name: 'pos_action_id')
final String? posActionID;
@JsonKey(name: 'pos_action_code')
final String? posActionCode;
@JsonKey(name: 'time_to_show')
String? timeToShow;
@JsonKey(name: 'time_count_down')
final String? timeCountDown;
@JsonKey(name: 'hour_start_in_day')
final String? hourStartInDay;
@JsonKey(name: 'hour_stop_in_day')
final String? hourStopInDay;
@JsonKey(name: 'after_pos_id')
final String? afterPosID;
@JsonKey(name: 'after_pos_code')
final String? afterPosCode;
@JsonKey(name: 'after_pos_name')
final String? afterPosName;
@JsonKey(name: 'marketing_request_description')
final String? marketingRequestDescription;
@JsonKey(name: 'effective_from_date')
final String? effectiveFromDate;
@JsonKey(name: 'effective_to_date')
final String? effectiveToDate;
@JsonKey(name: 'schedule_run_type_code')
final String? scheduleRunTypeCode;
@JsonKey(name: 'schedule_run_type_name')
final String? scheduleRunTypeName;
@JsonKey(name: 'schedule_at_time')
final String? scheduleAtTime;
@JsonKey(name: 'popup_title_template')
final String? popupTitleTemplate;
@JsonKey(name: 'popup_body_template')
final String? popupBodyTemplate;
@JsonKey(name: 'image_id')
final String? imageID;
@JsonKey(name: 'image_url')
final String? imageURL;
@JsonKey(name: 'message_name')
final String? messageName;
@JsonKey(name: 'request_id')
final String? requestId;
PopupManagerModel({
this.id,
this.screenToShow,
this.clickActionType,
this.clickActionParam,
this.posActionID,
this.posActionCode,
this.timeToShow,
this.timeCountDown,
this.hourStartInDay,
this.hourStopInDay,
this.afterPosID,
this.afterPosCode,
this.afterPosName,
this.marketingRequestDescription,
this.effectiveFromDate,
this.effectiveToDate,
this.scheduleRunTypeCode,
this.scheduleRunTypeName,
this.scheduleAtTime,
this.popupTitleTemplate,
this.popupBodyTemplate,
this.imageID,
this.imageURL,
this.messageName,
this.requestId,
});
factory PopupManagerModel.fromJson(Map<String, dynamic> json) =>
_$PopupManagerModelFromJson(json);
Map<String, dynamic> toJson() => _$PopupManagerModelToJson(this);
}
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'popup_manager_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
PopupManagerModel _$PopupManagerModelFromJson(Map<String, dynamic> json) =>
PopupManagerModel(
id: json['id'] as String?,
screenToShow: json['screen_to_show'] as String?,
clickActionType: json['click_action_type'] as String?,
clickActionParam: json['click_action_param'] as String?,
posActionID: json['pos_action_id'] as String?,
posActionCode: json['pos_action_code'] as String?,
timeToShow: json['time_to_show'] as String?,
timeCountDown: json['time_count_down'] as String?,
hourStartInDay: json['hour_start_in_day'] as String?,
hourStopInDay: json['hour_stop_in_day'] as String?,
afterPosID: json['after_pos_id'] as String?,
afterPosCode: json['after_pos_code'] as String?,
afterPosName: json['after_pos_name'] as String?,
marketingRequestDescription:
json['marketing_request_description'] as String?,
effectiveFromDate: json['effective_from_date'] as String?,
effectiveToDate: json['effective_to_date'] as String?,
scheduleRunTypeCode: json['schedule_run_type_code'] as String?,
scheduleRunTypeName: json['schedule_run_type_name'] as String?,
scheduleAtTime: json['schedule_at_time'] as String?,
popupTitleTemplate: json['popup_title_template'] as String?,
popupBodyTemplate: json['popup_body_template'] as String?,
imageID: json['image_id'] as String?,
imageURL: json['image_url'] as String?,
messageName: json['message_name'] as String?,
requestId: json['request_id'] as String?,
);
Map<String, dynamic> _$PopupManagerModelToJson(PopupManagerModel instance) =>
<String, dynamic>{
'id': instance.id,
'screen_to_show': instance.screenToShow,
'click_action_type': instance.clickActionType,
'click_action_param': instance.clickActionParam,
'pos_action_id': instance.posActionID,
'pos_action_code': instance.posActionCode,
'time_to_show': instance.timeToShow,
'time_count_down': instance.timeCountDown,
'hour_start_in_day': instance.hourStartInDay,
'hour_stop_in_day': instance.hourStopInDay,
'after_pos_id': instance.afterPosID,
'after_pos_code': instance.afterPosCode,
'after_pos_name': instance.afterPosName,
'marketing_request_description': instance.marketingRequestDescription,
'effective_from_date': instance.effectiveFromDate,
'effective_to_date': instance.effectiveToDate,
'schedule_run_type_code': instance.scheduleRunTypeCode,
'schedule_run_type_name': instance.scheduleRunTypeName,
'schedule_at_time': instance.scheduleAtTime,
'popup_title_template': instance.popupTitleTemplate,
'popup_body_template': instance.popupBodyTemplate,
'image_id': instance.imageID,
'image_url': instance.imageURL,
'message_name': instance.messageName,
'request_id': instance.requestId,
};
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:mypoint_flutter_app/screen/popup_manager/popup_manager_model.dart';
/// Giao diện điều hướng để bạn nối với router của app
typedef PopupNavigator = void Function({
required String name,
required String identifier,
String? title,
String? body,
});
/// Logger tuỳ bạn hook vào hệ thống hiện có (Firebase, MoEngage, …)
void logPopupShowing({required String popupId, required String requestId}) {
// UserFollowLogger(.popup_showing, data: trackingInfo).log()
// FirebaseAnalyticManager.shared.trackEvent(.popupShowing, info: trackingInfo)
debugPrint('[Popup] showing: popup_id=$popupId, request_id=$requestId');
}
void logPopupClick({required String popupId}) {
// FirebaseAnalyticManager.shared.add(event: LogEventFollowInfo(name: .popup, id: popupId))
debugPrint('[Popup] click: popup_id=$popupId');
}
/// ==== API hiển thị popup (gọi giống hàm Swift) ====
Future<void> showPopup(
BuildContext context, {
required PopupManagerModel modelPopup,
required PopupNavigator onNavigate,
VoidCallback? onDismissed, // thay cho NotificationCenter
}) async {
int timeCountDown = int.tryParse(modelPopup.timeCountDown ?? '1000000') ?? 1000000;
final popupId = modelPopup.id ?? '';
final requestId = modelPopup.requestId ?? '';
logPopupShowing(popupId: popupId, requestId: requestId);
await showGeneralDialog(
context: context,
barrierDismissible: false, // giống SwiftEntryKit, user không chạm ra ngoài để tắt
barrierLabel: 'popup',
transitionDuration: const Duration(milliseconds: 220),
pageBuilder: (_, __, ___) {
return _BasePopupView(
model: modelPopup,
initialCountdown: timeCountDown,
onNavigate: onNavigate,
onDismissed: () {
onDismissed?.call();
},
);
},
transitionBuilder: (_, anim, __, child) {
return Transform.scale(
scale: 0.96 + (0.04 * anim.value),
child: Opacity(opacity: anim.value, child: child),
);
},
);
}
/// ==== Widget nền của popup (tương đương BasePopupView + SwiftEntryKit display) ====
class _BasePopupView extends StatefulWidget {
final PopupManagerModel model;
final int initialCountdown;
final PopupNavigator onNavigate;
final VoidCallback onDismissed;
const _BasePopupView({
required this.model,
required this.initialCountdown,
required this.onNavigate,
required this.onDismissed,
});
@override
State<_BasePopupView> createState() => _BasePopupViewState();
}
class _BasePopupViewState extends State<_BasePopupView> {
Timer? _timer;
late int _countdown;
double? _imageAspectRatio; // width / height
bool _imageLoaded = false;
@override
void initState() {
super.initState();
_countdown = widget.initialCountdown;
_startTimer();
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
void _startTimer() {
_timer = Timer.periodic(const Duration(seconds: 1), (t) {
if (!mounted) return;
if (_countdown > 0) {
setState(() => _countdown -= 1);
} else {
_dismiss();
}
});
}
void _dismiss() {
_timer?.cancel();
widget.onDismissed();
if (mounted) Navigator.of(context).pop();
}
void _onImageTap() {
final model = widget.model;
if ((model.clickActionType ?? '').isEmpty) return;
_timer?.cancel();
logPopupClick(popupId: model.id ?? '');
// Điều hướng tương đương DirectionalScreen.begin(...)
widget.onNavigate(
name: model.clickActionType!,
identifier: model.clickActionParam ?? '',
title: model.popupTitleTemplate ?? '',
body: model.popupBodyTemplate ?? '',
);
_dismiss();
}
void _onCancelTap() async {
final model = widget.model;
if ((model.clickActionType ?? '') == 'VIEW_GIFT') {
// Show "GiftMessageView" dạng bottom sheet
await showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
builder: (_) => _GiftMessageSheet(model: model, onNavigate: widget.onNavigate),
);
}
_dismiss();
}
@override
Widget build(BuildContext context) {
final model = widget.model;
final title = (model.popupTitleTemplate ?? '').trim();
final body = (model.popupBodyTemplate ?? '').trim();
final hasTitle = title.isNotEmpty;
final hasBody = body.isNotEmpty;
// Tính phần height phụ theo Swift (55 + 50, trừ bớt khi ẩn)
int extra = 55 + 50;
if (!hasTitle) extra -= 55;
if (!hasBody) extra -= 50;
final media = MediaQuery.of(context);
final screenW = media.size.width;
final maxPopupHeight = media.size.height - 200;
// Card radius phụ thuộc điều kiện
final imageCornerRadius = (!hasTitle && !hasBody) ? 12.0 : 12.0; // ảnh trên cùng vẫn bo 12
final subHeaderRadius = (hasBody && !hasTitle) ? 12.0 : 0.0;
return Material(
color: Colors.black54,
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: LayoutBuilder(
builder: (context, constraints) {
// Nếu đã biết tỷ lệ ảnh: tính height theo công thức Swift
double imageHeight = 0;
if (_imageAspectRatio != null && _imageAspectRatio! > 0) {
// image.size.height*(screenW - 40)/image.size.width
imageHeight = (screenW - 40) / _imageAspectRatio!;
}
final content = Column(
mainAxisSize: MainAxisSize.min,
children: [
// Nút đóng
Align(
alignment: Alignment.topRight,
child: IconButton(
icon: const Icon(Icons.close, color: Colors.white),
onPressed: _onCancelTap,
),
),
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: maxPopupHeight,
minWidth: double.infinity,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Material(
color: Colors.white,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
// Ảnh
_buildImage(
url: model.imageURL,
heightHint: imageHeight > 0 ? imageHeight : null,
cornerRadius: imageCornerRadius,
),
// Header
if (hasTitle)
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
child: Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
),
// SubHeader
if (hasBody)
Container(
decoration: BoxDecoration(
color: const Color(0xFFF6F7F9),
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(subHeaderRadius),
),
),
padding: const EdgeInsets.fromLTRB(16, 10, 16, 16),
child: Text(
body,
style: const TextStyle(fontSize: 15, height: 1.4),
),
),
// Dòng trạng thái đếm ngược (tuỳ chọn)
Padding(
padding: const EdgeInsets.fromLTRB(16, 10, 16, 16),
child: Text(
_countdown > 0
? '$_countdown seconds dismiss popup'
: 'Closing…',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
textAlign: TextAlign.right,
),
),
],
),
),
),
),
),
],
);
return content;
},
),
),
),
);
}
Widget _buildImage({
required String? url,
double? heightHint,
required double cornerRadius,
}) {
final imageUrl = (url ?? '').trim();
if (imageUrl.isEmpty) {
// Placeholder fallback
return Container(
height: 160,
color: const Color(0xFFE9ECF1),
alignment: Alignment.center,
child: const Icon(Icons.image, size: 48, color: Colors.grey),
);
}
return GestureDetector(
onTap: _onImageTap,
child: ClipRRect(
borderRadius: BorderRadius.vertical(top: Radius.circular(cornerRadius)),
child: _NetworkImageWithInfo(
imageUrl: imageUrl,
heightHint: heightHint,
onResolved: (width, height) {
if (!_imageLoaded && width > 0 && height > 0) {
setState(() {
_imageLoaded = true;
_imageAspectRatio = width / height; // width/height
});
}
},
),
),
);
}
}
/// Image.network nhưng lấy được kích thước ảnh thật để tính tỉ lệ
class _NetworkImageWithInfo extends StatefulWidget {
final String imageUrl;
final double? heightHint;
final void Function(int width, int height) onResolved;
const _NetworkImageWithInfo({
required this.imageUrl,
required this.onResolved,
this.heightHint,
});
@override
State<_NetworkImageWithInfo> createState() => _NetworkImageWithInfoState();
}
class _NetworkImageWithInfoState extends State<_NetworkImageWithInfo> {
ImageStream? _stream;
ImageInfo? _info;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_resolve();
}
@override
void didUpdateWidget(covariant _NetworkImageWithInfo oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.imageUrl != widget.imageUrl) _resolve();
}
void _resolve() {
_stream?.removeListener(ImageStreamListener(_handleImage));
final provider = NetworkImage(widget.imageUrl);
final stream = provider.resolve(createLocalImageConfiguration(context));
_stream = stream;
stream.addListener(ImageStreamListener(_handleImage));
}
void _handleImage(ImageInfo info, bool _) {
_info = info;
widget.onResolved(info.image.width, info.image.height);
setState(() {});
}
@override
void dispose() {
_stream?.removeListener(ImageStreamListener(_handleImage));
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_info == null && widget.heightHint == null) {
// loading skeleton
return AspectRatio(
aspectRatio: 16 / 9,
child: Container(color: const Color(0xFFE9ECF1)),
);
}
final height = widget.heightHint;
return Image.network(
widget.imageUrl,
height: height,
width: double.infinity,
fit: BoxFit.cover,
);
}
}
/// ==== GiftMessageView tương đương (bottom sheet) ====
class _GiftMessageSheet extends StatelessWidget {
final PopupManagerModel model;
final PopupNavigator onNavigate;
const _GiftMessageSheet({
required this.model,
required this.onNavigate,ff
});
@override
Widget build(BuildContext context) {
final radius = const Radius.circular(16);
return Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
padding: const EdgeInsets.fromLTRB(16, 16, 16, 16 + 24),
child: SafeArea(
top: false,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(width: 36, height: 4, decoration: BoxDecoration(color: Colors.grey.shade300, borderRadius: BorderRadius.circular(2))),
const SizedBox(height: 12),
Text(
model.popupTitleTemplate?.isNotEmpty == true ? model.popupTitleTemplate! : 'Quà tặng của bạn',
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w700),
),
const SizedBox(height: 8),
Text(
model.popupBodyTemplate?.isNotEmpty == true ? model.popupBodyTemplate! : 'Nhấn "Sử dụng ngay" để tiếp tục.',
style: const TextStyle(fontSize: 14, color: Colors.black87),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
if ((model.clickActionType ?? '').isEmpty) {
Navigator.of(context).pop();
return;
}
onNavigate(
name: model.clickActionType!,
identifier: model.clickActionParam ?? '',
title: model.popupTitleTemplate ?? '',
body: model.popupBodyTemplate ?? '',
);
Navigator.of(context).pop();
},
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(vertical: 14),
),
child: const Text('Sử dụng ngay'),
),
),
const SizedBox(height: 8),
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Để sau'),
),
],
),
),
);
}
}
...@@ -6,7 +6,7 @@ import 'package:mypoint_flutter_app/screen/quiz_campaign/quiz_campaign_model.dar ...@@ -6,7 +6,7 @@ import 'package:mypoint_flutter_app/screen/quiz_campaign/quiz_campaign_model.dar
import 'package:mypoint_flutter_app/screen/quiz_campaign/quiz_campaign_viewmodel.dart'; import 'package:mypoint_flutter_app/screen/quiz_campaign/quiz_campaign_viewmodel.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 '../../resources/base_color.dart';
import '../../widgets/alert/data_alert_model.dart'; import '../../widgets/alert/data_alert_model.dart';
import '../../widgets/custom_empty_widget.dart'; import '../../widgets/custom_empty_widget.dart';
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/resouce/base_color.dart';
import 'package:mypoint_flutter_app/screen/register_campaign/register_form_input_viewmodel.dart'; import 'package:mypoint_flutter_app/screen/register_campaign/register_form_input_viewmodel.dart';
import '../../resources/base_color.dart';
import '../../shared/router_gage.dart'; import '../../shared/router_gage.dart';
import '../../widgets/custom_app_bar.dart'; import '../../widgets/custom_app_bar.dart';
import '../voucher/models/product_model.dart'; import '../voucher/models/product_model.dart';
......
...@@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; ...@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart'; import 'package:get/get_core/src/get_main.dart';
import 'package:mypoint_flutter_app/screen/setting/setting_viewmodel.dart'; import 'package:mypoint_flutter_app/screen/setting/setting_viewmodel.dart';
import '../../shared/router_gage.dart';
import '../../widgets/bottom_sheet_helper.dart'; import '../../widgets/bottom_sheet_helper.dart';
import '../../widgets/custom_app_bar.dart'; import '../../widgets/custom_app_bar.dart';
import '../change_pass/change_pass_screen.dart'; import '../change_pass/change_pass_screen.dart';
...@@ -46,7 +47,9 @@ class _SettingScreenState extends State<SettingScreen> { ...@@ -46,7 +47,9 @@ class _SettingScreenState extends State<SettingScreen> {
_buildSettingItem( _buildSettingItem(
icon: Icons.apps, icon: Icons.apps,
title: 'Các lĩnh vực quan tâm', title: 'Các lĩnh vực quan tâm',
onTap: () {}, onTap: () {
Get.toNamed(interestCategoriesScreen);
},
), ),
_buildDivider(), _buildDivider(),
_buildSettingItem( _buildSettingItem(
...@@ -78,7 +81,9 @@ class _SettingScreenState extends State<SettingScreen> { ...@@ -78,7 +81,9 @@ class _SettingScreenState extends State<SettingScreen> {
_buildSettingItem( _buildSettingItem(
icon: Icons.devices_other, icon: Icons.devices_other,
title: 'Quản lý thiết bị đăng nhập', title: 'Quản lý thiết bị đăng nhập',
onTap: () {}, onTap: () {
Get.toNamed(deviceManagerScreen);
},
), ),
_buildDivider(), _buildDivider(),
_buildSettingItem( _buildSettingItem(
......
...@@ -10,7 +10,7 @@ import 'package:mypoint_flutter_app/widgets/alert/custom_alert_dialog.dart'; ...@@ -10,7 +10,7 @@ import 'package:mypoint_flutter_app/widgets/alert/custom_alert_dialog.dart';
import '../../base/base_screen.dart'; import '../../base/base_screen.dart';
import '../../base/basic_state.dart'; import '../../base/basic_state.dart';
import '../../model/check_update_response_model.dart'; import '../../model/check_update_response_model.dart';
import '../../resouce/base_color.dart'; import '../../resources/base_color.dart';
import '../../widgets/alert/data_alert_model.dart'; import '../../widgets/alert/data_alert_model.dart';
import '../onboarding/onboarding_screen.dart'; import '../onboarding/onboarding_screen.dart';
......
...@@ -4,8 +4,7 @@ import 'package:mypoint_flutter_app/screen/support/support_item_model.dart'; ...@@ -4,8 +4,7 @@ import 'package:mypoint_flutter_app/screen/support/support_item_model.dart';
import 'package:mypoint_flutter_app/screen/support/support_screen_viewmodel.dart'; import 'package:mypoint_flutter_app/screen/support/support_screen_viewmodel.dart';
import 'package:mypoint_flutter_app/shared/router_gage.dart'; import 'package:mypoint_flutter_app/shared/router_gage.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../../resources/base_color.dart';
import '../../resouce/base_color.dart';
import '../../widgets/back_button.dart'; import '../../widgets/back_button.dart';
import '../faqs/faqs_screen.dart'; import '../faqs/faqs_screen.dart';
import '../pageDetail/campaign_detail_screen.dart'; import '../pageDetail/campaign_detail_screen.dart';
......
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