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
cc202df4
Commit
cc202df4
authored
Aug 25, 2025
by
DatHV
Browse files
update change id, popup common manager
parent
417358c5
Changes
43
Show whitespace changes
Inline
Side-by-side
android/app/build.gradle.kts
View file @
cc202df4
...
@@ -6,9 +6,9 @@ plugins {
...
@@ -6,9 +6,9 @@ plugins {
}
}
android
{
android
{
namespace
=
"com.icom.
vn.
mypoint
_flutter_app
"
namespace
=
"com.icom.mypoint"
compileSdk
=
flutter
.
compileSdkVersion
compileSdk
=
flutter
.
compileSdkVersion
ndkVersion
=
flutter
.
ndkVersion
ndkVersion
=
"27.0.12077973"
compileOptions
{
compileOptions
{
sourceCompatibility
=
JavaVersion
.
VERSION_11
sourceCompatibility
=
JavaVersion
.
VERSION_11
...
@@ -21,7 +21,7 @@ android {
...
@@ -21,7 +21,7 @@ android {
defaultConfig
{
defaultConfig
{
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId
=
"com.icom.
vn.
mypoint
_flutter_app
"
applicationId
=
"com.icom.mypoint"
// You can update the following values to match your application needs.
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk
=
flutter
.
minSdkVersion
minSdk
=
flutter
.
minSdkVersion
...
...
android/app/src/main/AndroidManifest.xml
View file @
cc202df4
<manifest
xmlns:android=
"http://schemas.android.com/apk/res/android"
>
<manifest
xmlns:android=
"http://schemas.android.com/apk/res/android"
>
<uses-permission
android:name=
"android.permission.READ_CONTACTS"
/>
<uses-permission
android:name=
"android.permission.WRITE_CONTACTS"
/>
<application
<application
android:label=
"mypoint_flutter_app"
android:label=
"mypoint_flutter_app"
android:name=
"${applicationName}"
android:name=
"${applicationName}"
...
@@ -41,6 +44,15 @@
...
@@ -41,6 +44,15 @@
<action
android:name=
"android.intent.action.PROCESS_TEXT"
/>
<action
android:name=
"android.intent.action.PROCESS_TEXT"
/>
<data
android:mimeType=
"text/plain"
/>
<data
android:mimeType=
"text/plain"
/>
</intent>
</intent>
<intent>
<action
android:name=
"android.intent.action.VIEW"
/>
<category
android:name=
"android.intent.category.BROWSABLE"
/>
<data
android:scheme=
"http"
/>
</intent>
<intent>
<action
android:name=
"android.intent.action.VIEW"
/>
<category
android:name=
"android.intent.category.BROWSABLE"
/>
<data
android:scheme=
"https"
/>
</intent>
</queries>
</queries>
<uses-permission
android:name=
"android.permission.READ_CONTACTS"
/>
</manifest>
</manifest>
android/app/src/main/kotlin/com/icom/
vn/
mypoint
_flutter_app
/MainActivity.kt
→
android/app/src/main/kotlin/com/icom/mypoint/MainActivity.kt
View file @
cc202df4
package
com.icom.
vn.
mypoint
_flutter_app
package
com.icom.mypoint
import
io.flutter.embedding.android.FlutterActivity
import
io.flutter.embedding.android.FlutterActivity
...
...
lib/configs/api_paths.dart
View file @
cc202df4
...
@@ -101,4 +101,8 @@ class APIPaths {
...
@@ -101,4 +101,8 @@ class APIPaths {
static
const
String
categoryUnsubscribeList
=
"/categoryUnsubscribeList/1.0.0"
;
static
const
String
categoryUnsubscribeList
=
"/categoryUnsubscribeList/1.0.0"
;
static
const
String
customerEvnPaymentGatewayRequest
=
"/customerEvnPaymentGatewayRequest/1.0.0"
;
static
const
String
customerEvnPaymentGatewayRequest
=
"/customerEvnPaymentGatewayRequest/1.0.0"
;
static
const
String
getPopup
=
"/getPopup/1.0.0"
;
static
const
String
getPopup
=
"/getPopup/1.0.0"
;
static
const
String
getMyProductGetExpiredList
=
"/myProductGetExpiredList/2.0.0"
;
static
const
String
getMyProductGetUsedList
=
"/myProductGetUsedList/2.0.0"
;
static
const
String
getMyProductGetWaitingList
=
"/myProductGetWaitingList/2.0.0"
;
static
const
String
orderPaymentMyAccounts
=
"order/api/v1.0/payment/my-accounts"
;
}
}
\ No newline at end of file
lib/directional/directional_screen.dart
View file @
cc202df4
...
@@ -118,9 +118,9 @@ class DirectionalScreen {
...
@@ -118,9 +118,9 @@ class DirectionalScreen {
if
(
uri
==
null
)
return
true
;
if
(
uri
==
null
)
return
true
;
final
requestId
=
const
Uuid
().
v4
();
// Cần package `uuid`
final
requestId
=
const
Uuid
().
v4
();
// Cần package `uuid`
final
updatedUri
=
uri
.
replace
(
queryParameters:
{...
uri
.
queryParameters
,
'aff_sub3'
:
requestId
});
final
updatedUri
=
uri
.
replace
(
queryParameters:
{...
uri
.
queryParameters
,
'aff_sub3'
:
requestId
});
LaunchMode
mode
=
LaunchMode
mode
=
type
==
DirectionalScreenName
.
viewDeepLink
?
LaunchMode
.
externalApplication
:
LaunchMode
.
inAppWebView
;
type
==
DirectionalScreenName
.
viewDeepLink
?
LaunchMode
.
externalApplication
:
LaunchMode
.
platformDefault
;
// forceOpen(url: updatedUri, mode: mode)
;
forc
eOpen
(
url:
updatedUri
,
mode
:
mode
);
saf
eOpen
Url
(
updatedUri
,
preferred
:
mode
);
return
true
;
return
true
;
case
DirectionalScreenName
.
refundHistory
:
case
DirectionalScreenName
.
refundHistory
:
Get
.
toNamed
(
historyPointCashBackScreen
);
Get
.
toNamed
(
historyPointCashBackScreen
);
...
@@ -153,8 +153,14 @@ class DirectionalScreen {
...
@@ -153,8 +153,14 @@ class DirectionalScreen {
Get
.
toNamed
(
personalEditScreen
);
Get
.
toNamed
(
personalEditScreen
);
return
true
;
return
true
;
case
DirectionalScreenName
.
surveyCampaign
:
case
DirectionalScreenName
.
surveyCampaign
:
// if ((clickActionParam ?? '').isEmpty) return false;
if
((
clickActionParam
??
''
).
isEmpty
)
return
false
;
Get
.
toNamed
(
surveyQuestionScreen
,
arguments:
{
"quizId"
:
"1"
});
Get
.
toNamed
(
surveyQuestionScreen
,
arguments:
{
"quizId"
:
clickActionParam
??
''
});
return
true
;
case
DirectionalScreenName
.
myMobileCard
:
Get
.
toNamed
(
myMobileCardListScreen
);
return
true
;
case
DirectionalScreenName
.
bankAccountManager
:
Get
.
toNamed
(
bankAccountManagerScreen
);
return
true
;
return
true
;
default
:
default
:
print
(
"Không nhận diện được action type:
$clickActionType
"
);
print
(
"Không nhận diện được action type:
$clickActionType
"
);
...
@@ -168,7 +174,7 @@ Future<bool> forceOpen({required Uri url, LaunchMode mode = LaunchMode.platformD
...
@@ -168,7 +174,7 @@ Future<bool> forceOpen({required Uri url, LaunchMode mode = LaunchMode.platformD
if
(
await
canLaunchUrl
(
url
))
{
if
(
await
canLaunchUrl
(
url
))
{
await
launchUrl
(
await
launchUrl
(
url
,
url
,
mode:
LaunchMode
.
externalApplication
,
mode:
LaunchMode
.
inAppBrowserView
,
webViewConfiguration:
const
WebViewConfiguration
(
enableJavaScript:
true
,
headers:
<
String
,
String
>{}),
webViewConfiguration:
const
WebViewConfiguration
(
enableJavaScript:
true
,
headers:
<
String
,
String
>{}),
);
);
return
true
;
return
true
;
...
@@ -187,3 +193,53 @@ Future<void> openAppStore(String url) async {
...
@@ -187,3 +193,53 @@ Future<void> openAppStore(String url) async {
debugPrint
(
"⚠️ Không thể mở URL:
$url
"
);
debugPrint
(
"⚠️ Không thể mở URL:
$url
"
);
}
}
}
}
Future
<
bool
>
safeOpenUrl
(
Uri
url
,
{
LaunchMode
preferred
=
LaunchMode
.
platformDefault
,})
async
{
try
{
// 1) Thử theo mode ưa thích
if
(
await
canLaunchUrl
(
url
))
{
final
ok
=
await
launchUrl
(
url
,
mode:
preferred
,
webViewConfiguration:
const
WebViewConfiguration
(
enableJavaScript:
true
,
headers:
<
String
,
String
>{},
),
);
if
(
ok
)
return
true
;
}
// 2) Fallback: mở bằng app ngoài (trình duyệt hệ thống)
if
(
await
canLaunchUrl
(
url
))
{
final
ok
=
await
launchUrl
(
url
,
mode:
LaunchMode
.
externalApplication
,
webViewConfiguration:
const
WebViewConfiguration
(
enableJavaScript:
true
,
headers:
<
String
,
String
>{},
),
);
if
(
ok
)
return
true
;
}
// 3) Fallback: mở trong webview của app (Custom Tabs / SFSafariViewController)
if
(
await
canLaunchUrl
(
url
))
{
final
ok
=
await
launchUrl
(
url
,
mode:
LaunchMode
.
inAppBrowserView
,
// hoặc inAppWebView (tuỳ version url_launcher)
webViewConfiguration:
const
WebViewConfiguration
(
enableJavaScript:
true
,
headers:
<
String
,
String
>{},
),
);
if
(
ok
)
return
true
;
}
// 4) Fallback cuối
if
(
await
canLaunchUrl
(
url
))
{
final
ok
=
await
launchUrl
(
url
,
mode:
LaunchMode
.
platformDefault
);
if
(
ok
)
return
true
;
}
}
catch
(
e
)
{
// ghi log lỗi nếu có
// debugPrint('safeOpenUrl error: $e');
}
return
false
;
}
lib/networking/restful_api_request.dart
View file @
cc202df4
...
@@ -18,6 +18,7 @@ import '../screen/affiliate/model/affiliate_category_model.dart';
...
@@ -18,6 +18,7 @@ import '../screen/affiliate/model/affiliate_category_model.dart';
import
'../screen/affiliate/model/affiliate_product_top_sale_model.dart'
;
import
'../screen/affiliate/model/affiliate_product_top_sale_model.dart'
;
import
'../screen/affiliate/model/cashback_overview_model.dart'
;
import
'../screen/affiliate/model/cashback_overview_model.dart'
;
import
'../screen/affiliate_brand_detail/models/affiliate_brand_detail_model.dart'
;
import
'../screen/affiliate_brand_detail/models/affiliate_brand_detail_model.dart'
;
import
'../screen/bank_account_manager/bank_account_info_model.dart'
;
import
'../screen/campaign7day/models/campaign_7day_info_model.dart'
;
import
'../screen/campaign7day/models/campaign_7day_info_model.dart'
;
import
'../screen/campaign7day/models/campaign_7day_mission_model.dart'
;
import
'../screen/campaign7day/models/campaign_7day_mission_model.dart'
;
import
'../screen/campaign7day/models/campaign_7day_reward_model.dart'
;
import
'../screen/campaign7day/models/campaign_7day_reward_model.dart'
;
...
@@ -64,9 +65,10 @@ import '../screen/transaction/history/transaction_history_response_model.dart';
...
@@ -64,9 +65,10 @@ import '../screen/transaction/history/transaction_history_response_model.dart';
import
'../screen/transaction/model/order_product_payment_response_model.dart'
;
import
'../screen/transaction/model/order_product_payment_response_model.dart'
;
import
'../screen/transaction/model/payment_bank_account_info_model.dart'
;
import
'../screen/transaction/model/payment_bank_account_info_model.dart'
;
import
'../screen/transaction/model/payment_method_model.dart'
;
import
'../screen/transaction/model/payment_method_model.dart'
;
import
'../screen/transaction/model/payment_method_type.dart'
;
import
'../screen/transaction/model/preview_order_payment_model.dart'
;
import
'../screen/transaction/model/preview_order_payment_model.dart'
;
import
'../screen/voucher/models/like_product_reponse_model.dart'
;
import
'../screen/voucher/models/like_product_reponse_model.dart'
;
import
'../screen/voucher/models/my_mobile_card_response.dart'
;
import
'../screen/voucher/models/my_product_status_type.dart'
;
import
'../screen/voucher/models/product_brand_model.dart'
;
import
'../screen/voucher/models/product_brand_model.dart'
;
import
'../screen/voucher/models/product_store_model.dart'
;
import
'../screen/voucher/models/product_store_model.dart'
;
import
'../screen/voucher/models/product_type.dart'
;
import
'../screen/voucher/models/product_type.dart'
;
...
@@ -76,7 +78,7 @@ import 'model_maker.dart';
...
@@ -76,7 +78,7 @@ import 'model_maker.dart';
extension
RestfullAPIClientAllApi
on
RestfulAPIClient
{
extension
RestfullAPIClientAllApi
on
RestfulAPIClient
{
Future
<
BaseResponseModel
<
UpdateResponseModel
>>
checkUpdateApp
()
async
{
Future
<
BaseResponseModel
<
UpdateResponseModel
>>
checkUpdateApp
()
async
{
String
version
=
Platform
.
version
;
String
version
=
Platform
.
version
;
final
body
=
{
"operating_system"
:
"iOS"
,
"software_model"
:
"MyPoint"
,
"version"
:
"1.21.7"
,
"build_number"
:
"1"
};
final
body
=
{
"operating_system"
:
"iOS"
,
"software_model"
:
"MyPoint"
,
"version"
:
version
,
"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
));
}
}
...
@@ -170,7 +172,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
...
@@ -170,7 +172,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
}
}
Future
<
BaseResponseModel
<
CreateOTPResponseModel
>>
otpCreateNew
(
String
ownerId
)
async
{
Future
<
BaseResponseModel
<
CreateOTPResponseModel
>>
otpCreateNew
(
String
ownerId
)
async
{
var
deviceKey
=
await
DeviceInfo
.
getDeviceId
();
//
var deviceKey = await DeviceInfo.getDeviceId();
final
body
=
{
"owner_id"
:
ownerId
,
"ttl"
:
Constants
.
otpTtl
,
"resend_after_second"
:
Constants
.
otpTtl
};
final
body
=
{
"owner_id"
:
ownerId
,
"ttl"
:
Constants
.
otpTtl
,
"resend_after_second"
:
Constants
.
otpTtl
};
return
requestNormal
(
return
requestNormal
(
APIPaths
.
otpCreateNew
,
APIPaths
.
otpCreateNew
,
...
@@ -241,7 +243,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
...
@@ -241,7 +243,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
}
}
Future
<
BaseResponseModel
<
EmptyCodable
>>
accountPasswordChange
(
String
phone
,
String
password
)
async
{
Future
<
BaseResponseModel
<
EmptyCodable
>>
accountPasswordChange
(
String
phone
,
String
password
)
async
{
String
?
token
=
await
DataPreference
.
instance
.
token
??
""
;
String
?
token
=
DataPreference
.
instance
.
token
??
""
;
final
body
=
{
"login_name"
:
phone
,
"password"
:
password
.
toSha256
(),
"access_token"
:
token
};
final
body
=
{
"login_name"
:
phone
,
"password"
:
password
.
toSha256
(),
"access_token"
:
token
};
return
requestNormal
(
return
requestNormal
(
APIPaths
.
accountPasswordChange
,
APIPaths
.
accountPasswordChange
,
...
@@ -252,7 +254,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
...
@@ -252,7 +254,7 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
}
}
Future
<
BaseResponseModel
<
EmptyCodable
>>
accountLoginForPasswordChange
(
String
phone
,
String
password
)
async
{
Future
<
BaseResponseModel
<
EmptyCodable
>>
accountLoginForPasswordChange
(
String
phone
,
String
password
)
async
{
String
?
token
=
await
DataPreference
.
instance
.
token
??
""
;
String
?
token
=
DataPreference
.
instance
.
token
??
""
;
final
body
=
{
"login_name"
:
phone
,
"password"
:
password
.
toSha256
(),
"access_token"
:
token
};
final
body
=
{
"login_name"
:
phone
,
"password"
:
password
.
toSha256
(),
"access_token"
:
token
};
return
requestNormal
(
return
requestNormal
(
APIPaths
.
accountLoginForPasswordChange
,
APIPaths
.
accountLoginForPasswordChange
,
...
@@ -893,10 +895,42 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
...
@@ -893,10 +895,42 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
});
});
}
}
Future
<
BaseResponseModel
<
List
<
PopupManagerModel
>>>
getPopup
()
async
{
Future
<
BaseResponseModel
<
List
<
PopupManagerModel
>>>
getPopupManagerCommonScreen
()
async
{
return
requestNormal
(
APIPaths
.
getPopup
,
Method
.
POST
,
{},
(
data
)
{
String
?
token
=
DataPreference
.
instance
.
token
??
""
;
final
body
=
{
"access_token"
:
token
,
"lang"
:
"vi"
};
return
requestNormal
(
APIPaths
.
getPopup
,
Method
.
POST
,
body
,
(
data
)
{
final
list
=
data
as
List
<
dynamic
>;
final
list
=
data
as
List
<
dynamic
>;
return
list
.
map
((
e
)
=>
PopupManagerModel
.
fromJson
(
e
)).
toList
();
return
list
.
map
((
e
)
=>
PopupManagerModel
.
fromJson
(
e
)).
toList
();
});
});
}
}
Future
<
BaseResponseModel
<
MyVoucherResponse
>>
getMyMobileCards
(
MyProductStatusType
status
,
Json
body
)
async
{
String
?
token
=
DataPreference
.
instance
.
token
??
""
;
body
[
"access_token"
]
=
token
;
body
[
"product_model_code"
]
=
"PRODUCT_MODEL_MOBILE_CARD"
;
body
[
"list_order"
]
=
"DESC"
;
var
path
=
''
;
switch
(
status
)
{
case
MyProductStatusType
.
waiting
:
path
=
APIPaths
.
getMyProductGetWaitingList
;
break
;
case
MyProductStatusType
.
used
:
path
=
APIPaths
.
getMyProductGetUsedList
;
break
;
case
MyProductStatusType
.
expired
:
path
=
APIPaths
.
getMyProductGetExpiredList
;
}
return
requestNormal
(
path
,
Method
.
POST
,
body
,
(
data
)
{
return
MyVoucherResponse
.
fromJson
(
data
as
Json
);
});
}
Future
<
BaseResponseModel
<
List
<
BankAccountInfoModel
>>>
getOrderPaymentMyAccounts
()
async
{
// String? token = DataPreference.instance.token ?? "";
// final body = {"access_token": token, "lang": "vi"};
return
requestNormal
(
APIPaths
.
orderPaymentMyAccounts
,
Method
.
GET
,
{},
(
data
)
{
final
list
=
data
as
List
<
dynamic
>;
return
list
.
map
((
e
)
=>
BankAccountInfoModel
.
fromJson
(
e
)).
toList
();
});
}
}
}
lib/preference/data_preference.dart
View file @
cc202df4
...
@@ -2,6 +2,7 @@ import 'dart:convert';
...
@@ -2,6 +2,7 @@ import 'dart:convert';
import
'package:shared_preferences/shared_preferences.dart'
;
import
'package:shared_preferences/shared_preferences.dart'
;
import
'../model/auth/login_token_response_model.dart'
;
import
'../model/auth/login_token_response_model.dart'
;
import
'../model/auth/profile_response_model.dart'
;
import
'../model/auth/profile_response_model.dart'
;
import
'../screen/popup_manager/popup_manager_viewmodel.dart'
;
class
DataPreference
{
class
DataPreference
{
static
final
DataPreference
_instance
=
DataPreference
.
_internal
();
static
final
DataPreference
_instance
=
DataPreference
.
_internal
();
...
@@ -55,6 +56,7 @@ class DataPreference {
...
@@ -55,6 +56,7 @@ class DataPreference {
_loginToken
=
null
;
_loginToken
=
null
;
final
prefs
=
await
SharedPreferences
.
getInstance
();
final
prefs
=
await
SharedPreferences
.
getInstance
();
await
prefs
.
remove
(
'login_token'
);
await
prefs
.
remove
(
'login_token'
);
await
PopupManagerViewModel
.
instance
.
reset
();
}
}
Future
<
void
>
clearUserProfile
()
async
{
Future
<
void
>
clearUserProfile
()
async
{
...
...
lib/screen/affiliate/affiliate_tab_screen.dart
View file @
cc202df4
...
@@ -3,13 +3,18 @@ import 'package:get/get.dart';
...
@@ -3,13 +3,18 @@ import 'package:get/get.dart';
import
'package:mypoint_flutter_app/screen/affiliate/sub_widget/build_affiliate_brand.dart'
;
import
'package:mypoint_flutter_app/screen/affiliate/sub_widget/build_affiliate_brand.dart'
;
import
'package:mypoint_flutter_app/screen/affiliate/sub_widget/build_affiliate_category.dart'
;
import
'package:mypoint_flutter_app/screen/affiliate/sub_widget/build_affiliate_category.dart'
;
import
'package:mypoint_flutter_app/screen/affiliate/sub_widget/build_affiliate_product_topsale.dart'
;
import
'package:mypoint_flutter_app/screen/affiliate/sub_widget/build_affiliate_product_topsale.dart'
;
import
'package:mypoint_flutter_app/widgets/back_button.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../directional/directional_action_type.dart'
;
import
'../../resources/base_color.dart'
;
import
'../../resources/base_color.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../widgets/bottom_sheet_helper.dart'
;
import
'../../widgets/bottom_sheet_helper.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'../home/header_home_viewmodel.dart'
;
import
'../home/header_home_viewmodel.dart'
;
import
'../popup_manager/popup_manager_screen.dart'
;
import
'../popup_manager/popup_manager_viewmodel.dart'
;
import
'../popup_manager/popup_runner_helper.dart'
;
import
'affiliate_overview.dart'
;
import
'affiliate_overview.dart'
;
import
'affiliate_popup_brands.dart'
;
import
'affiliate_popup_brands.dart'
;
import
'affiliate_tab_viewmodel.dart'
;
import
'affiliate_tab_viewmodel.dart'
;
...
@@ -22,7 +27,7 @@ class AffiliateTabScreen extends BaseScreen {
...
@@ -22,7 +27,7 @@ class AffiliateTabScreen extends BaseScreen {
State
<
AffiliateTabScreen
>
createState
()
=>
_AffiliateTabScreenState
();
State
<
AffiliateTabScreen
>
createState
()
=>
_AffiliateTabScreenState
();
}
}
class
_AffiliateTabScreenState
extends
BaseState
<
AffiliateTabScreen
>
with
BasicState
{
class
_AffiliateTabScreenState
extends
BaseState
<
AffiliateTabScreen
>
with
BasicState
,
PopupOnInit
{
final
AffiliateTabViewModel
viewModel
=
Get
.
put
(
AffiliateTabViewModel
());
final
AffiliateTabViewModel
viewModel
=
Get
.
put
(
AffiliateTabViewModel
());
final
_headerHomeVM
=
Get
.
find
<
HeaderHomeViewModel
>();
final
_headerHomeVM
=
Get
.
find
<
HeaderHomeViewModel
>();
late
var
_canBackButton
=
false
;
late
var
_canBackButton
=
false
;
...
@@ -37,6 +42,7 @@ class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicS
...
@@ -37,6 +42,7 @@ class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicS
viewModel
.
onShowAffiliateBrandPopup
=
(
data
)
{
viewModel
.
onShowAffiliateBrandPopup
=
(
data
)
{
showAffiliateBrandPopup
(
context
,
data
.
$
1
,
title:
data
.
$
2
);
showAffiliateBrandPopup
(
context
,
data
.
$
1
,
title:
data
.
$
2
);
};
};
runPopupCheck
(
DirectionalScreenName
.
pointBack
);
}
}
@override
@override
...
@@ -45,8 +51,8 @@ class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicS
...
@@ -45,8 +51,8 @@ class _AffiliateTabScreenState extends BaseState<AffiliateTabScreen> with BasicS
backgroundColor:
Colors
.
grey
.
shade50
,
backgroundColor:
Colors
.
grey
.
shade50
,
appBar:
CustomNavigationBar
(
appBar:
CustomNavigationBar
(
title:
"Mua sắm"
,
title:
"Mua sắm"
,
showBackButton:
_canBackButton
,
backgroundImage:
_headerHomeVM
.
headerData
.
background
??
"assets/images/bg_header_navi.png"
,
backgroundImage:
_headerHomeVM
.
headerData
.
background
??
"assets/images/bg_header_navi.png"
,
leftButtons:
_canBackButton
?
[
CustomBackButton
()]
:
[],
rightButtons:
[
rightButtons:
[
IconButton
(
IconButton
(
icon:
const
Icon
(
Icons
.
info
,
color:
Colors
.
white
),
icon:
const
Icon
(
Icons
.
info
,
color:
Colors
.
white
),
...
...
lib/screen/bank_account_manager/bank_account_detail_screen.dart
0 → 100644
View file @
cc202df4
import
'package:flutter/material.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'bank_account_info_model.dart'
;
class
BankAccountDetailScreen
extends
StatefulWidget
{
final
BankAccountInfoModel
model
;
const
BankAccountDetailScreen
({
super
.
key
,
required
this
.
model
});
@override
State
<
BankAccountDetailScreen
>
createState
()
=>
_BankAccountDetailScreenState
();
}
class
_BankAccountDetailScreenState
extends
State
<
BankAccountDetailScreen
>
{
late
bool
_isDefault
;
@override
void
initState
()
{
super
.
initState
();
_isDefault
=
widget
.
model
.
isDefault
??
false
;
}
@override
Widget
build
(
BuildContext
context
)
{
final
title
=
widget
.
model
.
cardName
?.
isNotEmpty
==
true
?
widget
.
model
.
cardName
!.
toUpperCase
()
:
(
widget
.
model
.
paymentMethod
??
'THẺ/TÀI KHOẢN'
);
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
'Thông tin thẻ'
),
body:
Column
(
children:
[
const
SizedBox
(
height:
8
),
_CardPreview
(
title:
title
,
maskedNumber:
widget
.
model
.
cardNumber
??
''
,
),
const
SizedBox
(
height:
12
),
_defaultRow
(
context
),
const
Spacer
(),
_deleteButton
(
context
),
const
SizedBox
(
height:
12
),
SafeArea
(
top:
false
,
child:
const
SizedBox
(
height:
0
)),
],
),
backgroundColor:
const
Color
(
0xFFF7F7F7
),
);
}
Widget
_defaultRow
(
BuildContext
context
)
{
return
Container
(
margin:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
height:
56
,
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
Row
(
children:
[
const
Expanded
(
child:
Text
(
'Đặt làm mặc định'
,
style:
TextStyle
(
fontSize:
16
),
),
),
Switch
(
value:
_isDefault
,
activeColor:
Colors
.
white
,
activeTrackColor:
const
Color
(
0xFF34C759
),
onChanged:
(
val
)
async
{
setState
(()
=>
_isDefault
=
val
);
// if (widget.onMakeDefault != null) {
// await widget.onMakeDefault!(widget.model);
// }
},
),
],
),
);
}
Widget
_deleteButton
(
BuildContext
context
)
{
return
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
child:
ElevatedButton
(
onPressed:
()
async
{
final
ok
=
await
showDialog
<
bool
>(
context:
context
,
builder:
(
_
)
=>
AlertDialog
(
title:
const
Text
(
'Xoá thẻ?'
),
content:
Text
(
widget
.
model
.
formMessageDelete
??
'Bạn có chắc muốn xoá thẻ/tài khoản này?'
,
),
actions:
[
TextButton
(
onPressed:
()
=>
Navigator
.
pop
(
context
,
false
),
child:
const
Text
(
'Huỷ'
)),
TextButton
(
onPressed:
()
=>
Navigator
.
pop
(
context
,
true
),
child:
const
Text
(
'Xoá'
)),
],
),
);
// if (ok == true && widget.onDelete != null) {
// await widget.onDelete!(widget.model);
// if (context.mounted) Navigator.pop(context);
// }
},
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
const
Color
(
0xFFF2F2F2
),
elevation:
0
,
minimumSize:
const
Size
.
fromHeight
(
52
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
16
)),
),
child:
const
Text
(
'Xoá'
,
style:
TextStyle
(
color:
Colors
.
red
,
fontWeight:
FontWeight
.
w600
,
fontSize:
16
),
),
),
);
}
}
class
_CardPreview
extends
StatelessWidget
{
final
String
title
;
final
String
maskedNumber
;
const
_CardPreview
({
required
this
.
title
,
required
this
.
maskedNumber
,
});
@override
Widget
build
(
BuildContext
context
)
{
return
Container
(
height:
180
,
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
16
),
gradient:
const
LinearGradient
(
begin:
Alignment
.
topLeft
,
end:
Alignment
.
bottomRight
,
colors:
[
Color
(
0xFF3E3A6D
),
Color
(
0xFF6E5DAA
),
Color
(
0xFF8E77C7
),
],
),
boxShadow:
const
[
BoxShadow
(
color:
Colors
.
black26
,
blurRadius:
12
,
offset:
Offset
(
0
,
6
)),
],
),
margin:
const
EdgeInsets
.
all
(
16
),
padding:
const
EdgeInsets
.
all
(
16
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
title
,
// VISA / MASTER / BANK NAME
style:
const
TextStyle
(
color:
Colors
.
white
,
fontSize:
20
,
fontWeight:
FontWeight
.
w700
,
letterSpacing:
1.2
),
),
const
Spacer
(),
const
Text
(
'Số thẻ'
,
style:
TextStyle
(
color:
Colors
.
white70
,
fontSize:
13
)),
const
SizedBox
(
height:
6
),
Text
(
maskedNumber
,
style:
const
TextStyle
(
color:
Colors
.
white
,
fontSize:
22
,
fontWeight:
FontWeight
.
w700
,
letterSpacing:
2
),
),
],
),
);
}
}
\ No newline at end of file
lib/screen/bank_account_manager/bank_account_info_model.dart
0 → 100644
View file @
cc202df4
import
'package:json_annotation/json_annotation.dart'
;
part
'bank_account_info_model.g.dart'
;
@JsonSerializable
()
class
BankAccountInfoModel
{
int
?
id
;
String
?
paymentMethod
;
String
?
cardName
;
String
?
cardNumber
;
String
?
cardDate
;
String
?
bankLogo
;
String
?
bankName
;
String
?
bankShortName
;
String
?
bankCode
;
bool
?
isDefault
;
bool
isSelected
;
String
?
formBankTitle
;
String
?
formBankNumberTitle
;
String
?
formMessageDelete
;
String
?
formBankBackground
;
String
?
formBankName
;
String
?
formBankNumber
;
BankAccountInfoModel
({
this
.
id
,
this
.
paymentMethod
,
this
.
cardName
,
this
.
cardNumber
,
this
.
cardDate
,
this
.
bankLogo
,
this
.
bankName
,
this
.
bankShortName
,
this
.
bankCode
,
this
.
isDefault
,
this
.
isSelected
=
false
,
this
.
formBankTitle
,
this
.
formBankNumberTitle
,
this
.
formMessageDelete
,
this
.
formBankBackground
,
this
.
formBankName
,
this
.
formBankNumber
,
});
factory
BankAccountInfoModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$BankAccountInfoModelFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$BankAccountInfoModelToJson
(
this
);
}
lib/screen/bank_account_manager/bank_account_info_model.g.dart
0 → 100644
View file @
cc202df4
// GENERATED CODE - DO NOT MODIFY BY HAND
part of
'bank_account_info_model.dart'
;
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
BankAccountInfoModel
_$BankAccountInfoModelFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
BankAccountInfoModel
(
id:
(
json
[
'id'
]
as
num
?)?.
toInt
(),
paymentMethod:
json
[
'paymentMethod'
]
as
String
?,
cardName:
json
[
'cardName'
]
as
String
?,
cardNumber:
json
[
'cardNumber'
]
as
String
?,
cardDate:
json
[
'cardDate'
]
as
String
?,
bankLogo:
json
[
'bankLogo'
]
as
String
?,
bankName:
json
[
'bankName'
]
as
String
?,
bankShortName:
json
[
'bankShortName'
]
as
String
?,
bankCode:
json
[
'bankCode'
]
as
String
?,
isDefault:
json
[
'isDefault'
]
as
bool
?,
isSelected:
json
[
'isSelected'
]
as
bool
?
??
false
,
formBankTitle:
json
[
'formBankTitle'
]
as
String
?,
formBankNumberTitle:
json
[
'formBankNumberTitle'
]
as
String
?,
formMessageDelete:
json
[
'formMessageDelete'
]
as
String
?,
formBankBackground:
json
[
'formBankBackground'
]
as
String
?,
formBankName:
json
[
'formBankName'
]
as
String
?,
formBankNumber:
json
[
'formBankNumber'
]
as
String
?,
);
Map
<
String
,
dynamic
>
_$BankAccountInfoModelToJson
(
BankAccountInfoModel
instance
,
)
=>
<
String
,
dynamic
>{
'id'
:
instance
.
id
,
'paymentMethod'
:
instance
.
paymentMethod
,
'cardName'
:
instance
.
cardName
,
'cardNumber'
:
instance
.
cardNumber
,
'cardDate'
:
instance
.
cardDate
,
'bankLogo'
:
instance
.
bankLogo
,
'bankName'
:
instance
.
bankName
,
'bankShortName'
:
instance
.
bankShortName
,
'bankCode'
:
instance
.
bankCode
,
'isDefault'
:
instance
.
isDefault
,
'isSelected'
:
instance
.
isSelected
,
'formBankTitle'
:
instance
.
formBankTitle
,
'formBankNumberTitle'
:
instance
.
formBankNumberTitle
,
'formMessageDelete'
:
instance
.
formMessageDelete
,
'formBankBackground'
:
instance
.
formBankBackground
,
'formBankName'
:
instance
.
formBankName
,
'formBankNumber'
:
instance
.
formBankNumber
,
};
lib/screen/bank_account_manager/bank_account_manager_screen.dart
0 → 100644
View file @
cc202df4
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/widgets/custom_empty_widget.dart'
;
import
'package:mypoint_flutter_app/widgets/image_loader.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'bank_account_detail_screen.dart'
;
import
'bank_account_info_model.dart'
;
import
'bank_account_manager_viewmodel.dart'
;
class
BankAccountManagerScreen
extends
StatefulWidget
{
const
BankAccountManagerScreen
({
super
.
key
});
@override
State
<
BankAccountManagerScreen
>
createState
()
=>
_BankAccountManagerScreenState
();
}
class
_BankAccountManagerScreenState
extends
State
<
BankAccountManagerScreen
>
{
final
BankAccountManagerViewModel
viewModel
=
Get
.
put
(
BankAccountManagerViewModel
());
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
'Quản lý tài khoản/ thẻ'
),
body:
Obx
(()
{
if
(
viewModel
.
bankAccounts
.
isEmpty
)
{
return
EmptyWidget
(
content:
"Bạn hiện chưa có tài khoản/ thẻ đã lưu"
);
}
return
Container
(
color:
const
Color
(
0xffff9fafb
),
child:
ListView
.
separated
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
8
),
itemCount:
viewModel
.
bankAccounts
.
length
,
separatorBuilder:
(
_
,
__
)
=>
const
Divider
(
height:
1
,
thickness:
1
),
itemBuilder:
(
item
,
index
)
=>
_BankAccountItem
(
model:
viewModel
.
bankAccounts
.
value
[
index
],
onTap:
()
{
Get
.
to
(()
=>
BankAccountDetailScreen
(
model:
viewModel
.
bankAccounts
.
value
[
index
],
));
}),
),
);
}),
);
}
}
class
_BankAccountItem
extends
StatelessWidget
{
final
BankAccountInfoModel
model
;
final
VoidCallback
?
onTap
;
const
_BankAccountItem
({
required
this
.
model
,
this
.
onTap
});
@override
Widget
build
(
BuildContext
context
)
{
final
title
=
model
.
bankShortName
?.
isNotEmpty
==
true
?
model
.
bankShortName
!
:
(
model
.
paymentMethod
??
'Tài khoản/thẻ'
);
return
Material
(
color:
Colors
.
white
,
child:
InkWell
(
onTap:
onTap
,
child:
Column
(
children:
[
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
24
,
vertical:
16
),
child:
Row
(
children:
[
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
4
),
child:
loadNetworkImage
(
url:
model
.
bankLogo
,
width:
32
,
height:
32
),
),
const
SizedBox
(
width:
12
),
Expanded
(
child:
Row
(
children:
[
Text
(
'
$title
${model.cardNumber}
'
,
maxLines:
1
,
overflow:
TextOverflow
.
ellipsis
,
style:
const
TextStyle
(
fontSize:
15
,
color:
Color
(
0xFF1D2129
),
fontWeight:
FontWeight
.
w500
),
),
if
(
model
.
isDefault
==
true
)
...[
const
SizedBox
(
width:
8
),
const
_DefaultBadge
()],
],
),
),
const
Icon
(
Icons
.
chevron_right
,
color:
Color
(
0xFF8B8E95
)),
],
),
),
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
24
),
child:
Divider
(
height:
1
,
thickness:
1
,
color:
const
Color
(
0xFFEBEDF0
),
),
),
],
),
),
);
}
}
class
_DefaultBadge
extends
StatelessWidget
{
const
_DefaultBadge
();
@override
Widget
build
(
BuildContext
context
)
{
return
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
6
,
vertical:
2
),
decoration:
BoxDecoration
(
color:
const
Color
(
0xFFFFE9EA
),
borderRadius:
BorderRadius
.
circular
(
16
)),
child:
const
Text
(
' Mặc định '
,
style:
TextStyle
(
fontSize:
11
,
color:
Color
(
0xFFEB4B54
),
fontWeight:
FontWeight
.
w600
),
),
);
}
}
lib/screen/bank_account_manager/bank_account_manager_viewmodel.dart
0 → 100644
View file @
cc202df4
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/networking/restful_api_request.dart'
;
import
'../../base/restful_api_viewmodel.dart'
;
import
'bank_account_info_model.dart'
;
class
BankAccountManagerViewModel
extends
RestfulApiViewModel
{
final
RxList
<
BankAccountInfoModel
>
bankAccounts
=
<
BankAccountInfoModel
>[].
obs
;
@override
onInit
()
{
super
.
onInit
();
getBankAccountList
();
}
getBankAccountList
()
async
{
showLoading
();
try
{
Future
.
delayed
(
const
Duration
(
milliseconds:
500
));
// final result = await client.getOrderPaymentMyAccounts();
// bankAccounts.value = result.data ?? [];
bankAccounts
.
value
=
[
BankAccountInfoModel
(
id:
1
,
paymentMethod:
"VISA"
,
cardName:
"Nguyen Van A"
,
cardNumber:
"1234 5678 9012 3456"
,
cardDate:
"12/25"
,
bankLogo:
"https://example.com/logo.png"
,
bankName:
"Bank A"
,
bankShortName:
"BA"
,
bankCode:
"BA123"
,
isDefault:
true
,
isSelected:
false
,
),
BankAccountInfoModel
(
id:
2
,
paymentMethod:
"MasterCard"
,
cardName:
"Nguyen Van B"
,
cardNumber:
"2345 6789 0123 4567"
,
cardDate:
"11/24"
,
bankLogo:
"https://example.com/logo2.png"
,
bankName:
"Bank B"
,
bankShortName:
"BB"
,
bankCode:
"BB456"
,
isDefault:
false
,
isSelected:
false
,
),
BankAccountInfoModel
(
id:
3
,
paymentMethod:
"ATM"
,
cardName:
"Nguyen Van C"
,
cardNumber:
"3456 7890 1234 5678"
,
cardDate:
"10/23"
,
bankLogo:
"https://example.com/logo3.png"
,
bankName:
"Bank C"
,
bankShortName:
"BC"
,
bankCode:
"BC789"
,
isDefault:
false
,
isSelected:
false
,
),
];
hideLoading
();
}
catch
(
error
)
{
hideLoading
();
print
(
"Error fetching bank accounts:
$error
"
);
}
}
}
\ No newline at end of file
lib/screen/contacts/contacts_picker.dart
0 → 100644
View file @
cc202df4
import
'package:flutter/material.dart'
;
import
'package:flutter_contacts/flutter_contacts.dart'
;
import
'package:mypoint_flutter_app/resources/base_color.dart'
;
/// Mở bottom sheet hiển thị danh sách danh bạ để chọn
Future
<
Contact
?>
showContactPicker
(
BuildContext
context
)
async
{
final
granted
=
await
FlutterContacts
.
requestPermission
();
if
(!
granted
)
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
'Không có quyền truy cập danh bạ'
)),
);
return
null
;
}
// Lấy contacts (kèm số điện thoại)
final
contacts
=
await
FlutterContacts
.
getContacts
(
withProperties:
true
,
withPhoto:
false
,
);
if
(
contacts
.
isEmpty
)
{
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
'Không có danh bạ nào'
)),
);
return
null
;
}
// Mở bottom sheet để user chọn
return
showModalBottomSheet
<
Contact
>(
context:
context
,
isScrollControlled:
true
,
backgroundColor:
Colors
.
white
,
shape:
const
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
vertical
(
top:
Radius
.
circular
(
16
)),
),
builder:
(
_
)
=>
_ContactPickerSheet
(
contacts:
contacts
),
);
}
class
_ContactPickerSheet
extends
StatefulWidget
{
final
List
<
Contact
>
contacts
;
const
_ContactPickerSheet
({
required
this
.
contacts
});
@override
State
<
_ContactPickerSheet
>
createState
()
=>
_ContactPickerSheetState
();
}
class
_ContactPickerSheetState
extends
State
<
_ContactPickerSheet
>
{
late
List
<
Contact
>
_filtered
;
final
_searchCtrl
=
TextEditingController
();
@override
void
initState
()
{
super
.
initState
();
_filtered
=
widget
.
contacts
;
_searchCtrl
.
addListener
(
_onSearchChanged
);
}
@override
void
dispose
()
{
_searchCtrl
.
removeListener
(
_onSearchChanged
);
_searchCtrl
.
dispose
();
super
.
dispose
();
}
void
_onSearchChanged
()
{
final
q
=
_searchCtrl
.
text
.
trim
().
toLowerCase
();
if
(
q
.
isEmpty
)
{
setState
(()
=>
_filtered
=
widget
.
contacts
);
}
else
{
setState
(()
{
_filtered
=
widget
.
contacts
.
where
((
c
)
{
final
name
=
c
.
displayName
.
toLowerCase
();
final
phones
=
c
.
phones
.
map
((
e
)
=>
e
.
number
).
join
(
' '
).
toLowerCase
();
return
name
.
contains
(
q
)
||
phones
.
contains
(
q
);
}).
toList
();
});
}
}
@override
Widget
build
(
BuildContext
context
)
{
final
bottomInset
=
MediaQuery
.
of
(
context
).
viewInsets
.
bottom
;
return
SafeArea
(
top:
false
,
child:
Padding
(
padding:
EdgeInsets
.
only
(
bottom:
bottomInset
),
child:
SizedBox
(
height:
MediaQuery
.
of
(
context
).
size
.
height
*
0.75
,
child:
Column
(
children:
[
const
SizedBox
(
height:
8
),
Container
(
width:
40
,
height:
4
,
decoration:
BoxDecoration
(
color:
Colors
.
black26
,
borderRadius:
BorderRadius
.
circular
(
2
))),
const
SizedBox
(
height:
12
),
const
Text
(
'Chọn liên hệ'
,
style:
TextStyle
(
fontWeight:
FontWeight
.
w700
,
fontSize:
18
)),
const
SizedBox
(
height:
8
),
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
child:
TextField
(
controller:
_searchCtrl
,
decoration:
InputDecoration
(
hintText:
'Tìm tên hoặc số'
,
prefixIcon:
const
Icon
(
Icons
.
search
),
contentPadding:
const
EdgeInsets
.
symmetric
(
horizontal:
12
,
vertical:
10
),
border:
OutlineInputBorder
(
borderRadius:
BorderRadius
.
circular
(
12
),
borderSide:
BorderSide
(
color:
Colors
.
grey
.
shade400
,
width:
1
),),
),
),
),
const
SizedBox
(
height:
8
),
Expanded
(
child:
ListView
.
separated
(
itemCount:
_filtered
.
length
,
separatorBuilder:
(
_
,
__
)
=>
const
Divider
(
height:
1
),
itemBuilder:
(
context
,
index
)
{
final
c
=
_filtered
[
index
];
final
subtitle
=
c
.
phones
.
isNotEmpty
?
c
.
phones
.
first
.
number
:
'Không có số'
;
return
ListTile
(
leading:
CircleAvatar
(
backgroundColor:
BaseColor
.
primary400
,
child:
Text
(
c
.
displayName
.
isNotEmpty
?
c
.
displayName
[
0
].
toUpperCase
()
:
'?'
),
// màu nền cho avatar
),
title:
Text
(
c
.
displayName
),
subtitle:
Text
(
subtitle
),
onTap:
()
=>
Navigator
.
of
(
context
).
pop
<
Contact
>(
c
),
// trả về contact
);
},
),
),
],
),
),
),
);
}
}
lib/screen/data_network_service/data_network_service_screen.dart
View file @
cc202df4
import
'package:contacts_service/contacts_service.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/extensions/num_extension.dart'
;
import
'package:mypoint_flutter_app/extensions/num_extension.dart'
;
...
@@ -13,6 +12,7 @@ import '../../preference/point/point_manager.dart';
...
@@ -13,6 +12,7 @@ import '../../preference/point/point_manager.dart';
import
'../../resources/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
'../contacts/contacts_picker.dart'
;
import
'../topup/brand_select_sheet_widget.dart'
;
import
'../topup/brand_select_sheet_widget.dart'
;
import
'data_network_service_viewmodel.dart'
;
import
'data_network_service_viewmodel.dart'
;
...
@@ -341,22 +341,19 @@ class _DataNetworkServiceScreenState extends BaseState<DataNetworkServiceScreen>
...
@@ -341,22 +341,19 @@ class _DataNetworkServiceScreenState extends BaseState<DataNetworkServiceScreen>
Future
<
void
>
pickContact
(
BuildContext
context
)
async
{
Future
<
void
>
pickContact
(
BuildContext
context
)
async
{
try
{
try
{
// Gọi sẽ tự động hiện dialog yêu cầu quyền (nếu cần)
final
contact
=
await
showContactPicker
(
context
);
final
Contact
?
contact
=
await
ContactsService
.
openDeviceContactPicker
()
;
if
(
contact
==
null
)
return
;
if
(
contact
!=
null
&&
contact
.
phones
!=
null
&&
contact
.
phones
!.
isNotEmpty
)
{
if
(
contact
.
phones
.
isEmpty
)
return
;
String
phone
=
contact
.
phones
!
.
first
.
value
??
''
;
String
phone
=
contact
.
phones
.
first
.
number
;
phone
=
phone
.
replaceAll
(
RegExp
(
r'[\s\-\(\)]'
),
''
);
phone
=
phone
.
replaceAll
(
RegExp
(
r'[\s\-\(\)]'
),
''
);
_phoneController
.
text
=
phone
;
_phoneController
.
text
=
phone
;
_viewModel
.
phoneNumber
.
value
=
phone
;
_viewModel
.
phoneNumber
.
value
=
phone
;
_viewModel
.
checkMobileNetwork
();
_viewModel
.
checkMobileNetwork
();
}
else
{
ScaffoldMessenger
.
of
(
context
,
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
"Không tìm thấy số điện thoại hợp lệ"
)));
}
}
catch
(
e
)
{
}
catch
(
e
)
{
print
(
"❌ Lỗi khi truy cập danh bạ:
$e
"
);
debugPrint
(
'❌ pickContact error:
$e
'
);
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Không thể truy cập danh bạ")));
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
'Không thể truy cập danh bạ'
)),
);
}
}
}
}
}
}
lib/screen/faqs/faqs_screen.dart
View file @
cc202df4
...
@@ -7,6 +7,7 @@ import '../../base/base_screen.dart';
...
@@ -7,6 +7,7 @@ import '../../base/base_screen.dart';
import
'../../base/basic_state.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../resources/base_color.dart'
;
import
'../../resources/base_color.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'faqs_viewmodel.dart'
;
import
'faqs_viewmodel.dart'
;
class
FAQScreen
extends
BaseScreen
{
class
FAQScreen
extends
BaseScreen
{
...
@@ -22,13 +23,7 @@ class _FAQScreenState extends BaseState<FAQScreen> with BasicState {
...
@@ -22,13 +23,7 @@ class _FAQScreenState extends BaseState<FAQScreen> with BasicState {
@override
@override
Widget
createBody
()
{
Widget
createBody
()
{
return
Scaffold
(
return
Scaffold
(
appBar:
AppBar
(
appBar:
CustomNavigationBar
(
title:
"Câu hỏi thường gặp"
),
leading:
CustomBackButton
(),
title:
const
Text
(
"Câu hỏi thường gặp"
,
style:
TextStyle
(
fontWeight:
FontWeight
.
bold
)),
backgroundColor:
Colors
.
white
,
foregroundColor:
Colors
.
black
,
elevation:
0
,
),
body:
Column
(
body:
Column
(
children:
[
children:
[
Obx
(()
{
Obx
(()
{
...
...
lib/screen/game/game_tab_screen.dart
View file @
cc202df4
...
@@ -3,10 +3,15 @@ import 'package:get/get.dart';
...
@@ -3,10 +3,15 @@ import 'package:get/get.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
'../../directional/directional_action_type.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../widgets/back_button.dart'
;
import
'../../widgets/custom_empty_widget.dart'
;
import
'../../widgets/custom_empty_widget.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'../home/header_home_viewmodel.dart'
;
import
'../home/header_home_viewmodel.dart'
;
import
'../popup_manager/popup_manager_screen.dart'
;
import
'../popup_manager/popup_manager_viewmodel.dart'
;
import
'../popup_manager/popup_runner_helper.dart'
;
import
'game_tab_viewmodel.dart'
;
import
'game_tab_viewmodel.dart'
;
class
GameTabScreen
extends
BaseScreen
{
class
GameTabScreen
extends
BaseScreen
{
...
@@ -16,7 +21,7 @@ class GameTabScreen extends BaseScreen {
...
@@ -16,7 +21,7 @@ class GameTabScreen extends BaseScreen {
State
<
GameTabScreen
>
createState
()
=>
_GameTabScreenState
();
State
<
GameTabScreen
>
createState
()
=>
_GameTabScreenState
();
}
}
class
_GameTabScreenState
extends
BaseState
<
GameTabScreen
>
with
BasicState
{
class
_GameTabScreenState
extends
BaseState
<
GameTabScreen
>
with
BasicState
,
PopupOnInit
{
final
_headerHomeVM
=
Get
.
find
<
HeaderHomeViewModel
>();
final
_headerHomeVM
=
Get
.
find
<
HeaderHomeViewModel
>();
final
GameTabViewModel
_viewModel
=
Get
.
put
(
GameTabViewModel
());
final
GameTabViewModel
_viewModel
=
Get
.
put
(
GameTabViewModel
());
final
LayerLink
_layerLink
=
LayerLink
();
final
LayerLink
_layerLink
=
LayerLink
();
...
@@ -45,6 +50,7 @@ class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState {
...
@@ -45,6 +50,7 @@ class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState {
Get
.
toNamed
(
gameCardScreen
,
arguments:
data
);
Get
.
toNamed
(
gameCardScreen
,
arguments:
data
);
}
}
};
};
runPopupCheck
(
DirectionalScreenName
.
productVoucher
);
}
}
@override
@override
...
@@ -52,7 +58,7 @@ class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState {
...
@@ -52,7 +58,7 @@ class _GameTabScreenState extends BaseState<GameTabScreen> with BasicState {
return
Scaffold
(
return
Scaffold
(
appBar:
CustomNavigationBar
(
appBar:
CustomNavigationBar
(
title:
"Games"
,
title:
"Games"
,
showBackButton:
_can
BackButton
,
leftButtons:
_canBackButton
?
[
Custom
BackButton
()]
:
[]
,
backgroundImage:
_headerHomeVM
.
headerData
.
background
??
"assets/images/bg_header_navi.png"
,
backgroundImage:
_headerHomeVM
.
headerData
.
background
??
"assets/images/bg_header_navi.png"
,
rightButtons:
[
rightButtons:
[
CompositedTransformTarget
(
CompositedTransformTarget
(
...
...
lib/screen/home/home_screen.dart
View file @
cc202df4
...
@@ -6,9 +6,12 @@ import 'package:mypoint_flutter_app/screen/home/custom_widget/header_home.dart';
...
@@ -6,9 +6,12 @@ import 'package:mypoint_flutter_app/screen/home/custom_widget/header_home.dart';
import
'package:mypoint_flutter_app/screen/home/custom_widget/product_grid_widget.dart'
;
import
'package:mypoint_flutter_app/screen/home/custom_widget/product_grid_widget.dart'
;
import
'package:mypoint_flutter_app/screen/home/pipi_detail_screen.dart'
;
import
'package:mypoint_flutter_app/screen/home/pipi_detail_screen.dart'
;
import
'package:mypoint_flutter_app/shared/router_gage.dart'
;
import
'package:mypoint_flutter_app/shared/router_gage.dart'
;
import
'../../directional/directional_action_type.dart'
;
import
'../../preference/point/header_home_model.dart'
;
import
'../../preference/point/header_home_model.dart'
;
import
'../popup_manager/popup_manager_model.dart'
;
import
'../popup_manager/popup_manager_model.dart'
;
import
'../popup_manager/popup_manager_popup.dart'
;
import
'../popup_manager/popup_manager_screen.dart'
;
import
'../popup_manager/popup_manager_viewmodel.dart'
;
import
'../popup_manager/popup_runner_helper.dart'
;
import
'../voucher/sub_widget/voucher_section_title.dart'
;
import
'../voucher/sub_widget/voucher_section_title.dart'
;
import
'custom_widget/achievement_carousel_widget.dart'
;
import
'custom_widget/achievement_carousel_widget.dart'
;
import
'custom_widget/affiliate_brand_grid_widget.dart'
;
import
'custom_widget/affiliate_brand_grid_widget.dart'
;
...
@@ -30,13 +33,15 @@ class HomeScreen extends StatefulWidget {
...
@@ -30,13 +33,15 @@ class HomeScreen extends StatefulWidget {
State
<
HomeScreen
>
createState
()
=>
_HomeScreenState
();
State
<
HomeScreen
>
createState
()
=>
_HomeScreenState
();
}
}
class
_HomeScreenState
extends
State
<
HomeScreen
>
{
class
_HomeScreenState
extends
State
<
HomeScreen
>
with
PopupOnInit
{
final
HomeTabViewModel
_viewModel
=
Get
.
put
(
HomeTabViewModel
());
final
HomeTabViewModel
_viewModel
=
Get
.
put
(
HomeTabViewModel
());
final
_headerHomeVM
=
Get
.
find
<
HeaderHomeViewModel
>();
final
_headerHomeVM
=
Get
.
find
<
HeaderHomeViewModel
>();
final
RxBool
_showHover
=
true
.
obs
;
final
RxBool
_showHover
=
true
.
obs
;
@override
@override
void
initState
()
{
void
initState
()
{
super
.
initState
();
super
.
initState
();
runPopupCheck
(
DirectionalScreenName
.
home
);
}
}
Widget
_buildSliverHeader
(
double
heightHeader
)
{
Widget
_buildSliverHeader
(
double
heightHeader
)
{
...
@@ -225,44 +230,15 @@ class _HomeScreenState extends State<HomeScreen> {
...
@@ -225,44 +230,15 @@ class _HomeScreenState extends State<HomeScreen> {
}
}
void
_handleHoverViewTap
()
{
void
_handleHoverViewTap
()
{
showPopup
(
final
result
=
_viewModel
.
hoverData
.
value
?.
direction
?.
begin
();
context
,
if
(
result
!=
true
)
{
modelPopup:
PopupManagerModel
(
showModalBottomSheet
(
timeCountDown:
'10'
,
context:
context
,
id:
'popup123'
,
backgroundColor:
Colors
.
transparent
,
requestId:
'req_abc'
,
isScrollControlled:
true
,
popupTitleTemplate:
'Khuyến mãi đặc biệt'
,
builder:
(
_
)
=>
PipiDetailScreen
(),
popupBodyTemplate:
'Giảm 50% cho đơn hàng hôm nay.'
,
imageURL:
'https://picsum.photos/1200/800'
,
clickActionType:
'VIEW_GIFT'
,
clickActionParam:
'gift_999'
,
),
onNavigate:
({
required
String
name
,
required
String
identifier
,
String
?
title
,
String
?
body
,
})
{
// Tự nối qua router của bạn
// ví dụ:
// context.pushNamed(name, extra: {'id': identifier, 'title': title, 'body': body});
debugPrint
(
'Navigate ->
$name
, id=
$identifier
'
);
},
onDismissed:
()
{
// Thay cho NotificationCenter.dismissPopup
debugPrint
(
'Popup dismissed'
);
},
);
);
}
// final result = _viewModel.hoverData.value?.direction?.begin();
// if (result != true) {
// showModalBottomSheet(
// context: context,
// backgroundColor: Colors.transparent,
// isScrollControlled: true,
// builder: (_) => PipiDetailScreen(),
// );
// }
}
}
void
_handleCloseHoverView
()
{
void
_handleCloseHoverView
()
{
...
...
lib/screen/location_address/location_address_screen.dart
View file @
cc202df4
import
'package:flutter/cupertino.dart'
;
import
'package:flutter/cupertino.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:get/get.dart'
;
import
'../../widgets/custom_
app
_bar.dart'
;
import
'../../widgets/custom_
navigation
_bar.dart'
;
import
'location_address_viewmodel.dart'
;
import
'location_address_viewmodel.dart'
;
enum
LocationAddressType
{
enum
LocationAddressType
{
...
@@ -54,7 +54,7 @@ class _LocationAddressScreenState extends State<LocationAddressScreen> {
...
@@ -54,7 +54,7 @@ class _LocationAddressScreenState extends State<LocationAddressScreen> {
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
return
Scaffold
(
appBar:
Custom
AppBar
.
back
(
title:
"Location Address
"
),
appBar:
Custom
NavigationBar
(
title:
"Địa chỉ
"
),
// backgroundColor: Colors.transparent,
// backgroundColor: Colors.transparent,
body:
SafeArea
(
body:
SafeArea
(
child:
Column
(
child:
Column
(
...
...
lib/screen/main_tab_screen/main_tab_screen.dart
View file @
cc202df4
...
@@ -53,7 +53,7 @@ class _MainTabScreenState extends State<MainTabScreen> {
...
@@ -53,7 +53,7 @@ class _MainTabScreenState extends State<MainTabScreen> {
mainAxisAlignment:
MainAxisAlignment
.
center
,
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
children:
[
_buildTabItem
(
icon:
Icons
.
home
,
label:
'Trang chủ'
,
index:
0
),
_buildTabItem
(
icon:
Icons
.
home
,
label:
'Trang chủ'
,
index:
0
),
_buildTabItem
(
icon:
Icons
.
st
ar
,
label:
'Ưu đãi'
,
index:
1
),
_buildTabItem
(
icon:
Icons
.
confirmation_number_sh
ar
p
,
label:
'Ưu đãi'
,
index:
1
),
_buildTabItem
(
icon:
null
,
label:
'Game'
,
index:
2
,
gift:
'assets/images/ic_tabbar_game.gif'
),
_buildTabItem
(
icon:
null
,
label:
'Game'
,
index:
2
,
gift:
'assets/images/ic_tabbar_game.gif'
),
_buildTabItem
(
icon:
Icons
.
shopping_cart
,
label:
'Mua sắm'
,
index:
3
),
_buildTabItem
(
icon:
Icons
.
shopping_cart
,
label:
'Mua sắm'
,
index:
3
),
_buildTabItem
(
icon:
Icons
.
person
,
label:
'Cá nhân'
,
index:
4
),
_buildTabItem
(
icon:
Icons
.
person
,
label:
'Cá nhân'
,
index:
4
),
...
@@ -95,7 +95,7 @@ class _MainTabScreenState extends State<MainTabScreen> {
...
@@ -95,7 +95,7 @@ class _MainTabScreenState extends State<MainTabScreen> {
style:
TextStyle
(
style:
TextStyle
(
color:
isSelected
?
Colors
.
white
:
Colors
.
white70
,
color:
isSelected
?
Colors
.
white
:
Colors
.
white70
,
fontWeight:
isSelected
?
FontWeight
.
bold
:
FontWeight
.
normal
,
fontWeight:
isSelected
?
FontWeight
.
bold
:
FontWeight
.
normal
,
fontSize:
1
2
,
fontSize:
1
4
,
),
),
),
),
],
],
...
...
Prev
1
2
3
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