Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Hoàng Văn Đạt
mypoint_flutter_app
Commits
417358c5
"ios/NotificationServices/NotificationServices.entitlements" did not exist on "e41fc4fe4b6799e5d07be8edcaffbc6ede444dbf"
Commit
417358c5
authored
Aug 15, 2025
by
DatHV
Browse files
update authen 401, device manager, interestied category
parent
efb4662c
Changes
99
Hide whitespace changes
Inline
Side-by-side
lib/screen/login/login_screen.dart
View file @
417358c5
...
...
@@ -3,7 +3,7 @@ import 'package:get/get.dart';
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../permission/biometric_manager.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../resou
r
ce
s
/base_color.dart'
;
import
'../../widgets/alert/custom_alert_dialog.dart'
;
import
'../../widgets/alert/data_alert_model.dart'
;
import
'../../widgets/back_button.dart'
;
...
...
@@ -29,11 +29,9 @@ class _LoginScreenState extends BaseState<LoginScreen> with BasicState {
void
initState
()
{
super
.
initState
();
final
args
=
Get
.
arguments
;
if
(
args
is
String
)
{
phoneNumber
=
args
;
}
else
if
(
args
is
Map
)
{
if
(
args
is
Map
)
{
phoneNumber
=
args
[
'phone'
];
fullName
=
args
[
'fullName'
];
fullName
=
args
[
'fullName'
]
??
''
;
}
loginVM
.
onShowChangePass
=
(
message
)
{
Get
.
dialog
(
...
...
lib/screen/main_tab_screen/main_tab_screen.dart
View file @
417358c5
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../resou
r
ce
s
/base_color.dart'
;
import
'../affiliate/affiliate_tab_screen.dart'
;
import
'../game/game_tab_screen.dart'
;
import
'../home/header_home_viewmodel.dart'
;
...
...
lib/screen/membership/member_level_header_widget.dart
View file @
417358c5
...
...
@@ -3,7 +3,7 @@ import 'package:intl/intl.dart';
import
'package:mypoint_flutter_app/extensions/string_extension.dart'
;
import
'../../preference/data_preference.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../resou
r
ce
s
/base_color.dart'
;
import
'../../widgets/image_loader.dart'
;
import
'../../widgets/measure_size.dart'
;
import
'models/membership_level_model.dart'
;
...
...
lib/screen/mobile_card/product_mobile_card_screen.dart
View file @
417358c5
...
...
@@ -3,13 +3,13 @@ import 'package:get/get.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: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/usable_mobile_card_popup.dart'
;
import
'package:mypoint_flutter_app/widgets/custom_app_bar.dart'
;
import
'package:mypoint_flutter_app/widgets/image_loader.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../resources/base_color.dart'
;
import
'../../widgets/alert/custom_alert_dialog.dart'
;
import
'../../widgets/alert/data_alert_model.dart'
;
...
...
lib/screen/mobile_card/usable_mobile_card_popup.dart
View file @
417358c5
...
...
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import
'package:flutter/services.dart'
;
import
'package:mypoint_flutter_app/extensions/datetime_extensions.dart'
;
import
'package:mypoint_flutter_app/extensions/string_extension.dart'
;
import
'
package:mypoint_flutter_app
/resouce/base_color.dart'
;
import
'
../..
/resou
r
ce
s
/base_color.dart'
;
import
'models/usable_voucher_model.dart'
;
class
UsableMobileCardPopup
extends
StatelessWidget
{
...
...
lib/screen/notification/notification_screen.dart
View file @
417358c5
...
...
@@ -3,7 +3,7 @@ import 'package:flutter/services.dart';
import
'package:get/get.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../resou
r
ce
s
/base_color.dart'
;
import
'../../widgets/alert/data_alert_model.dart'
;
import
'../../widgets/back_button.dart'
;
import
'../../widgets/custom_empty_widget.dart'
;
...
...
lib/screen/onboarding/onboarding_screen.dart
View file @
417358c5
...
...
@@ -7,7 +7,7 @@ import 'package:mypoint_flutter_app/shared/router_gage.dart';
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../configs/constants.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../resou
r
ce
s
/base_color.dart'
;
import
'../biometric/biometric_screen.dart'
;
import
'../faqs/faqs_screen.dart'
;
import
'../login/login_screen.dart'
;
...
...
@@ -73,7 +73,7 @@ class _OnboardingScreenState extends BaseState<OnboardingScreen> with BasicState
return
;
}
if
(
response
.
nextAction
==
"login"
)
{
Get
.
toNamed
(
loginScreen
,
arguments:
_viewModel
.
phoneNumber
.
value
);
Get
.
toNamed
(
loginScreen
,
arguments:
{
'phone'
:
_viewModel
.
phoneNumber
.
value
}
);
}
}
...
...
lib/screen/otp/otp_screen.dart
View file @
417358c5
...
...
@@ -3,7 +3,7 @@ import 'package:get/get.dart';
import
'package:pin_code_fields/pin_code_fields.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../resou
r
ce
s
/base_color.dart'
;
import
'../../widgets/back_button.dart'
;
import
'../../widgets/support_button.dart'
;
import
'otp_viewmodel.dart'
;
...
...
lib/screen/otp/verify_otp_repository.dart
View file @
417358c5
...
...
@@ -31,7 +31,7 @@ class VerifyOtpRepository extends RestfulApiViewModel implements IOtpRepository
if
(
value
.
data
?.
claim
?.
action
==
"signup"
)
{
Get
.
off
(()
=>
CreatePasswordScreen
(
repository:
SignUpCreatePasswordRepository
(
phoneNumber
)));
}
else
if
(
value
.
data
?.
claim
?.
action
==
"login"
)
{
Get
.
offNamed
(
loginScreen
,
arguments:
phoneNumber
);
Get
.
offNamed
(
loginScreen
,
arguments:
{
'phone'
:
phoneNumber
}
);
}
return
value
;
});
...
...
lib/screen/pageDetail/campaign_detail_screen.dart
View file @
417358c5
...
...
@@ -4,7 +4,7 @@ import 'package:get/get.dart';
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../extensions/string_extension.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../resou
r
ce
s
/base_color.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../widgets/back_button.dart'
;
import
'../../widgets/network_image_with_aspect_ratio.dart'
;
...
...
lib/screen/personal/personal_edit_screen.dart
View file @
417358c5
...
...
@@ -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
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../resou
r
ce
s
/base_color.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../widgets/alert/data_alert_model.dart'
;
import
'../../widgets/bottom_sheet_helper.dart'
;
...
...
lib/screen/personal/personal_screen.dart
View file @
417358c5
...
...
@@ -7,7 +7,7 @@ import '../../base/base_screen.dart';
import
'../../base/basic_state.dart'
;
import
'../../preference/package_info.dart'
;
import
'../../preference/point/header_home_model.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../resou
r
ce
s
/base_color.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../widgets/alert/data_alert_model.dart'
;
import
'../home/header_home_viewmodel.dart'
;
...
...
@@ -115,12 +115,17 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState {
const
Spacer
(),
Row
(
children:
[
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
)),
],
GestureDetector
(
onTap:
()
{
Get
.
toNamed
(
membershipScreen
);
},
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
(),
Row
(
...
...
@@ -286,14 +291,13 @@ class _PersonalScreenState extends BaseState<PersonalScreen> with BasicState {
if
(
matched
)
found
=
true
;
return
matched
;
});
final
phone
=
DataPreference
.
instance
.
phone
;
if
(
phone
!=
null
)
{
if
(!
found
)
{
Get
.
offAllNamed
(
loginScreen
,
arguments:
phone
);
}
final
phone
=
DataPreference
.
instance
.
phoneNumberUsedForLoginScreen
;
final
displayName
=
DataPreference
.
instance
.
displayName
;
if
(
phone
!=
null
&&
!
found
)
{
Get
.
offAllNamed
(
loginScreen
,
arguments:
{
"phone"
:
phone
,
'fullName'
:
displayName
});
}
else
{
DataPreference
.
instance
.
clearData
();
Get
.
offAllNamed
(
log
inScreen
);
Get
.
offAllNamed
(
onboard
in
g
Screen
);
}
}
}
lib/screen/popup_manager/popup_manager_model.dart
0 → 100644
View file @
417358c5
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
);
}
lib/screen/popup_manager/popup_manager_model.g.dart
0 → 100644
View file @
417358c5
// 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
,
};
lib/screen/popup_manager/popup_manager_popup.dart
0 → 100644
View file @
417358c5
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'
),
),
],
),
),
);
}
}
lib/screen/quiz_campaign/quiz_campaign_screen.dart
View file @
417358c5
...
...
@@ -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
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../resou
r
ce
s
/base_color.dart'
;
import
'../../widgets/alert/data_alert_model.dart'
;
import
'../../widgets/custom_empty_widget.dart'
;
...
...
lib/screen/register_campaign/register_form_input_screen.dart
View file @
417358c5
import
'package:flutter/material.dart'
;
import
'package:flutter_widget_from_html/flutter_widget_from_html.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
'../../resources/base_color.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../widgets/custom_app_bar.dart'
;
import
'../voucher/models/product_model.dart'
;
...
...
lib/screen/setting/setting_screen.dart
View file @
417358c5
...
...
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import
'package:get/get.dart'
;
import
'package:get/get_core/src/get_main.dart'
;
import
'package:mypoint_flutter_app/screen/setting/setting_viewmodel.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../widgets/bottom_sheet_helper.dart'
;
import
'../../widgets/custom_app_bar.dart'
;
import
'../change_pass/change_pass_screen.dart'
;
...
...
@@ -46,7 +47,9 @@ class _SettingScreenState extends State<SettingScreen> {
_buildSettingItem
(
icon:
Icons
.
apps
,
title:
'Các lĩnh vực quan tâm'
,
onTap:
()
{},
onTap:
()
{
Get
.
toNamed
(
interestCategoriesScreen
);
},
),
_buildDivider
(),
_buildSettingItem
(
...
...
@@ -78,7 +81,9 @@ class _SettingScreenState extends State<SettingScreen> {
_buildSettingItem
(
icon:
Icons
.
devices_other
,
title:
'Quản lý thiết bị đăng nhập'
,
onTap:
()
{},
onTap:
()
{
Get
.
toNamed
(
deviceManagerScreen
);
},
),
_buildDivider
(),
_buildSettingItem
(
...
...
lib/screen/splash/splash_screen.dart
View file @
417358c5
...
...
@@ -10,7 +10,7 @@ import 'package:mypoint_flutter_app/widgets/alert/custom_alert_dialog.dart';
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../model/check_update_response_model.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../resou
r
ce
s
/base_color.dart'
;
import
'../../widgets/alert/data_alert_model.dart'
;
import
'../onboarding/onboarding_screen.dart'
;
...
...
lib/screen/support/support_screen.dart
View file @
417358c5
...
...
@@ -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/shared/router_gage.dart'
;
import
'package:url_launcher/url_launcher.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../resources/base_color.dart'
;
import
'../../widgets/back_button.dart'
;
import
'../faqs/faqs_screen.dart'
;
import
'../pageDetail/campaign_detail_screen.dart'
;
...
...
Prev
1
2
3
4
5
Next
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment