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
6b980613
Commit
6b980613
authored
Nov 14, 2025
by
DatHV
Browse files
update project structure
parent
bfff9e47
Changes
507
Hide whitespace changes
Inline
Side-by-side
lib/
screen
/voucher/my_voucher/my_product_list_viewmodel.dart
→
lib/
features
/voucher/my_voucher/my_product_list_viewmodel.dart
View file @
6b980613
import
'package:flutter/cupertino.dart'
;
import
'package:get/get_rx/src/rx_types/rx_types.dart'
;
import
'package:mypoint_flutter_app/network
ing
/api/product_api.dart'
deferred
as
product_api
;
import
'../../../network
ing
/restful_api_viewmodel.dart'
;
import
'../
../home/
models/my_product_model.dart'
;
import
'../../../
base
/base_response_model.dart'
;
import
'package:mypoint_flutter_app/
core/
network/api/product_api.dart'
deferred
as
product_api
;
import
'../../../
core/
network/restful_api_viewmodel.dart'
;
import
'../models/my_product_model.dart'
;
import
'../../../
shared/widgets/base_view
/base_response_model.dart'
;
class
MyProductListViewModel
extends
RestfulApiViewModel
{
final
RxInt
selectedTabIndex
=
0
.
obs
;
...
...
lib/
screen
/voucher/my_voucher/my_product_list_widget.dart
→
lib/
features
/voucher/my_voucher/my_product_list_widget.dart
View file @
6b980613
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'../../../shared/router_gage.dart'
;
import
'../../../widgets/custom_empty_widget.dart'
;
import
'../../../widgets/custom_navigation_bar.dart'
;
import
'../../../widgets/image_loader.dart'
;
import
'../
../home/
models/my_product_model.dart'
;
import
'../../../
shared/
widgets/custom_empty_widget.dart'
;
import
'../../../
shared/
widgets/custom_navigation_bar.dart'
;
import
'../../../
shared/
widgets/image_loader.dart'
;
import
'../models/my_product_model.dart'
;
import
'my_product_list_viewmodel.dart'
;
import
'package:dotted_border/dotted_border.dart'
;
...
...
@@ -26,7 +26,6 @@ class _MyVoucherListScreenState extends State<MyVoucherListScreen> {
@override
Widget
build
(
BuildContext
context
)
{
final
screenWidth
=
MediaQuery
.
of
(
context
).
size
.
width
;
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
'Ưu đãi của tôi'
),
body:
Obx
(
...
...
@@ -42,7 +41,7 @@ class _MyVoucherListScreenState extends State<MyVoucherListScreen> {
),
const
Divider
(
height:
1
),
if
(
_viewModel
.
myProducts
.
isEmpty
)
Expanded
(
child:
EmptyWidget
(
size:
Size
(
screenWidth
/
2
,
screenWidth
/
2
)
))
Expanded
(
child:
EmptyWidget
(
isLoading:
_viewModel
.
isLoading
.
value
))
else
Expanded
(
child:
RefreshIndicator
(
...
...
lib/
screen
/voucher/sub_widget/voucher_action_menu.dart
→
lib/
features
/voucher/sub_widget/voucher_action_menu.dart
View file @
6b980613
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:get/get_core/src/get_main.dart'
;
import
'../../../directional/directional_action_type.dart'
;
import
'../../../directional/directional_screen.dart'
;
import
'../../../resources/base_color.dart'
;
import
'../../../shared/router_gage.dart'
;
import
'../../../app/routing/directional_action_type.dart'
;
import
'../../../shared/navigation/directional_screen.dart'
;
class
VoucherActionMenu
extends
StatelessWidget
{
const
VoucherActionMenu
({
super
.
key
});
@override
Widget
build
(
BuildContext
context
)
{
final
screenWidth
=
MediaQuery
.
of
(
context
).
size
.
width
;
final
itemWidth
=
screenWidth
/
4
;
return
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
12
),
child:
Row
(
...
...
lib/
screen
/voucher/sub_widget/voucher_item_grid.dart
→
lib/
features
/voucher/sub_widget/voucher_item_grid.dart
View file @
6b980613
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:get/get_core/src/get_main.dart'
;
import
'../../../shared/router_gage.dart'
;
import
'../../../widgets/custom_price_tag.dart'
;
import
'../../../widgets/image_loader.dart'
;
import
'../../../
shared/
widgets/custom_price_tag.dart'
;
import
'../../../
shared/
widgets/image_loader.dart'
;
import
'../models/product_model.dart'
;
class
VoucherItemGrid
extends
StatelessWidget
{
...
...
@@ -46,7 +45,6 @@ class _VoucherGridItem extends StatelessWidget {
final
double
itemWidth
;
const
_VoucherGridItem
({
super
.
key
,
required
this
.
product
,
required
this
.
itemWidth
,
});
...
...
lib/
screen
/voucher/sub_widget/voucher_item_list.dart
→
lib/
features
/voucher/sub_widget/voucher_item_list.dart
View file @
6b980613
import
'dart:math'
;
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:get/get_core/src/get_main.dart'
;
import
'package:mypoint_flutter_app/extensions/num_extension.dart'
;
import
'package:mypoint_flutter_app/shared/router_gage.dart'
;
import
'../../../
resources
/base_color.dart'
;
import
'../../../widgets/custom_price_tag.dart'
;
import
'../../../widgets/image_loader.dart'
;
import
'../../../
core/theme
/base_color.dart'
;
import
'../../../
shared/
widgets/custom_price_tag.dart'
;
import
'../../../
shared/
widgets/image_loader.dart'
;
import
'../models/product_model.dart'
;
class
VoucherItemList
extends
StatelessWidget
{
...
...
lib/
screen
/voucher/sub_widget/voucher_section_title.dart
→
lib/
features
/voucher/sub_widget/voucher_section_title.dart
View file @
6b980613
File moved
lib/
screen
/voucher/voucher_code_card_screen.dart
→
lib/
features
/voucher/voucher_code_card_screen.dart
View file @
6b980613
import
'package:barcode_widget/barcode_widget.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
import
'package:mypoint_flutter_app/widgets/back_button.dart'
;
import
'package:mypoint_flutter_app/widgets/custom_toast_message.dart'
;
import
'package:mypoint_flutter_app/widgets/dashed_line.dart'
;
import
'package:mypoint_flutter_app/
shared/
widgets/back_button.dart'
;
import
'package:mypoint_flutter_app/
shared/
widgets/custom_toast_message.dart'
;
import
'package:mypoint_flutter_app/
shared/
widgets/dashed_line.dart'
;
import
'package:qr_flutter/qr_flutter.dart'
;
import
'../../
resources
/base_color.dart'
;
import
'../../widgets/custom_point_text_tag.dart'
;
import
'../../widgets/image_loader.dart'
;
import
'../../
core/theme
/base_color.dart'
;
import
'../../
shared/
widgets/custom_point_text_tag.dart'
;
import
'../../
shared/
widgets/image_loader.dart'
;
import
'models/product_model.dart'
;
class
VoucherCodeCardScreen
extends
StatelessWidget
{
...
...
lib/
screen
/voucher/voucher_list/voucher_list_screen.dart
→
lib/
features
/voucher/voucher_list/voucher_list_screen.dart
View file @
6b980613
import
'dart:async'
;
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'../../../
base
/base_screen.dart'
;
import
'../../../
base
/basic_state.dart'
;
import
'../../../config
s
/constants.dart'
;
import
'../../../
shared/widgets/base_view
/base_screen.dart'
;
import
'../../../
shared/widgets/base_view
/basic_state.dart'
;
import
'../../../
app/
config/constants.dart'
;
import
'../../../shared/router_gage.dart'
;
import
'../../../widgets/custom_empty_widget.dart'
;
import
'../../../widgets/custom_navigation_bar.dart'
;
import
'../../../widgets/custom_search_navigation_bar.dart'
;
import
'../../../
shared/
widgets/custom_empty_widget.dart'
;
import
'../../../
shared/
widgets/custom_navigation_bar.dart'
;
import
'../../../
shared/
widgets/custom_search_navigation_bar.dart'
;
import
'../sub_widget/voucher_item_list.dart'
;
import
'voucher_list_viewmodel.dart'
;
...
...
@@ -142,17 +142,18 @@ class _VoucherListScreenState extends BaseState<VoucherListScreen> with BasicSta
Expanded
(
child:
Obx
(()
{
if
(
_viewModel
.
products
.
isEmpty
)
{
return
const
Center
(
child:
EmptyWidget
());
return
Center
(
child:
EmptyWidget
(
isLoading:
_viewModel
.
isLoading
.
value
));
}
// Countdown start được điều phối ở initState qua ever(isLoading)
return
RefreshIndicator
(
onRefresh:
()
=>
_viewModel
.
loadData
(
reset:
true
),
child:
ListView
.
builder
(
physics:
const
AlwaysScrollableScrollPhysics
(),
itemCount:
_viewModel
.
products
.
length
+
(
_viewModel
.
hasMore
?
1
:
0
),
itemBuilder:
(
context
,
index
)
{
if
(
index
>=
_viewModel
.
products
.
length
)
{
_viewModel
.
loadData
(
reset:
false
);
if
(
index
>=
_viewModel
.
products
.
length
&&
_viewModel
.
products
.
isNotEmpty
)
{
WidgetsBinding
.
instance
.
addPostFrameCallback
((
_
)
{
_viewModel
.
loadData
(
reset:
false
);
});
return
const
Center
(
child:
Padding
(
padding:
EdgeInsets
.
all
(
16
),
child:
CircularProgressIndicator
()),
);
...
...
lib/
screen
/voucher/voucher_list/voucher_list_viewmodel.dart
→
lib/
features
/voucher/voucher_list/voucher_list_viewmodel.dart
View file @
6b980613
import
'dart:async'
;
import
'package:flutter/cupertino.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/network
ing
/api/product_api.dart'
deferred
as
product_api
;
import
'package:mypoint_flutter_app/network
ing
/restful_api_client_all_request.dart'
;
import
'../../../
base
/base_response_model.dart'
;
import
'../../../network
ing
/restful_api_viewmodel.dart'
;
import
'package:mypoint_flutter_app/
core/
network/api/product_api.dart'
deferred
as
product_api
;
import
'package:mypoint_flutter_app/
core/
network/restful_api_client_all_request.dart'
;
import
'../../../
shared/widgets/base_view
/base_response_model.dart'
;
import
'../../../
core/
network/restful_api_viewmodel.dart'
;
import
'../models/product_model.dart'
;
import
'../models/product_type.dart'
;
import
'../models/search_product_response_model.dart'
;
class
VoucherListViewModel
extends
RestfulApiViewModel
{
VoucherListViewModel
({
required
this
.
isHotProduct
,
this
.
isFavorite
=
false
});
...
...
@@ -14,16 +14,15 @@ class VoucherListViewModel extends RestfulApiViewModel {
final
bool
isHotProduct
;
Timer
?
_debounce
;
final
RxList
<
ProductModel
>
products
=
<
ProductModel
>[].
obs
;
var
isLoading
=
false
.
obs
;
var
isLoadMore
=
false
.
obs
;
int
_currentPage
=
0
;
final
int
_pageSize
=
20
;
@override
final
RxBool
isLoading
=
false
.
obs
;
bool
_hasMore
=
true
;
bool
get
hasMore
=>
_hasMore
;
String
_searchQuery
=
''
;
String
get
searchQuery
=>
_searchQuery
;
var
totalResult
=
0
.
obs
;
/// Đánh dấu đã hoàn tất lần tải đầu tiên (có dữ liệu) để UI có thể bắt đầu countdown
final
firstLoadDone
=
false
.
obs
;
void
Function
(
BaseResponseModel
<
SubmitViewVoucherCompletedResponse
>
response
)?
submitCampaignViewVoucherResponse
;
bool
_productApiLoaded
=
false
;
...
...
@@ -77,30 +76,28 @@ class VoucherListViewModel extends RestfulApiViewModel {
products
.
clear
();
}
else
{
_currentPage
=
products
.
length
;
if
(!
_hasMore
)
return
;
}
if
(!
_hasMore
)
return
;
if
(
reset
)
{
showLoading
();
}
isLoading
.
value
=
true
;
final
body
=
{
"size"
:
_pageSize
,
"index"
:
_currentPage
};
try
{
isLoading
.
value
=
true
;
isLoadMore
.
value
=
true
;
final
result
=
await
_callProductApi
((
api
)
=>
api
.
productsCustomerLikes
(
body
));
final
fetchedData
=
result
.
data
??
[];
if
(
fetchedData
.
isEmpty
||
fetchedData
.
length
<
_pageSize
)
{
await
callApi
<
List
<
ProductModel
>>(
request:
()
=>
_callProductApi
((
api
)
=>
api
.
productsCustomerLikes
(
body
)),
onSuccess:
(
data
,
_
)
{
if
(
data
.
isEmpty
||
data
.
length
<
_pageSize
)
{
_hasMore
=
false
;
}
products
.
addAll
(
data
);
},
onFailure:
(
message
,
_
,
_
)
{
_hasMore
=
false
;
}
products
.
addAll
(
fetchedData
);
}
catch
(
error
)
{
debugPrint
(
"Error fetching products:
$error
"
);
}
finally
{
hideLoading
();
isLoading
.
value
=
false
;
isLoadMore
.
value
=
false
;
// Khi lần đầu có dữ liệu, đánh dấu để UI start countdown
if
(
products
.
isNotEmpty
)
firstLoadDone
.
value
=
true
;
}
// onShowAlertError?.call(message);
},
onComplete:
()
{
isLoading
.
value
=
false
;
if
(
products
.
isNotEmpty
)
firstLoadDone
.
value
=
true
;
},
withLoading:
reset
,
);
}
Future
<
void
>
_getProducts
({
bool
reset
=
false
})
async
{
...
...
@@ -113,9 +110,6 @@ class VoucherListViewModel extends RestfulApiViewModel {
_currentPage
=
products
.
length
;
}
if
(!
_hasMore
)
return
;
if
(
reset
)
{
showLoading
();
}
final
body
=
{
"type"
:
ProductType
.
voucher
.
value
,
"size"
:
_pageSize
,
...
...
@@ -124,26 +118,27 @@ class VoucherListViewModel extends RestfulApiViewModel {
if
(
_searchQuery
.
isNotEmpty
)
"keywords"
:
_searchQuery
,
if
(
_searchQuery
.
isNotEmpty
)
"keyword"
:
_searchQuery
,
};
try
{
isLoading
.
value
=
true
;
isLoadMore
.
value
=
true
;
final
result
=
await
_callProductApi
((
api
)
=>
api
.
getSearchProducts
(
body
));
final
fetchedData
=
result
.
data
?.
products
??
[];
totalResult
.
value
=
result
.
data
?.
total
??
0
;
if
(
fetchedData
.
isEmpty
||
fetchedData
.
length
<
_pageSize
)
{
isLoading
.
value
=
true
;
await
callApi
<
SearchProductResponseModel
>(
request:
()
=>
_callProductApi
((
api
)
=>
api
.
getSearchProducts
(
body
)),
onSuccess:
(
data
,
_
)
{
final
fetchedData
=
data
.
products
??
[];
totalResult
.
value
=
data
.
total
??
0
;
if
(
fetchedData
.
isEmpty
||
fetchedData
.
length
<
_pageSize
)
{
_hasMore
=
false
;
}
products
.
addAll
(
fetchedData
);
},
onFailure:
(
message
,
_
,
_
)
{
_hasMore
=
false
;
}
products
.
addAll
(
fetchedData
);
}
catch
(
error
)
{
debugPrint
(
"Error fetching products:
$error
"
);
}
finally
{
hideLoading
();
isLoading
.
value
=
false
;
isLoadMore
.
value
=
false
;
// Khi lần đầu có dữ liệu, đánh dấu để UI start countdown
if
(
products
.
isNotEmpty
)
firstLoadDone
.
value
=
true
;
}
// onShowAlertError?.call(message);
},
onComplete:
()
{
isLoading
.
value
=
false
;
if
(
products
.
isNotEmpty
)
firstLoadDone
.
value
=
true
;
},
withLoading:
reset
,
);
}
void
submitCampaignViewVoucherComplete
()
async
{
...
...
lib/
screen
/voucher/voucher_tab_screen.dart
→
lib/
features
/voucher/voucher_tab_screen.dart
View file @
6b980613
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/screen/voucher/voucher_list/voucher_list_screen.dart'
;
import
'../../directional/directional_action_type.dart'
;
import
'../../app/routing/directional_action_type.dart'
;
import
'../../shared/router_gage.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
'voucher_tab_viewmodel.dart'
;
import
'sub_widget/voucher_action_menu.dart'
;
import
'sub_widget/voucher_item_grid.dart'
;
import
'sub_widget/voucher_item_list.dart'
;
import
'sub_widget/voucher_section_title.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'../../
shared/
widgets/custom_navigation_bar.dart'
;
class
VoucherTabScreen
extends
StatefulWidget
{
const
VoucherTabScreen
({
super
.
key
});
...
...
lib/
screen
/voucher/voucher_tab_viewmodel.dart
→
lib/
features
/voucher/voucher_tab_viewmodel.dart
View file @
6b980613
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/network
ing
/api/product_api.dart'
deferred
as
product_api
;
import
'package:mypoint_flutter_app/
screen
/voucher/models/product_type.dart'
;
import
'../../
base
/base_response_model.dart'
;
import
'../../network
ing
/restful_api_viewmodel.dart'
;
import
'package:mypoint_flutter_app/
core/
network/api/product_api.dart'
deferred
as
product_api
;
import
'package:mypoint_flutter_app/
features
/voucher/models/product_type.dart'
;
import
'../../
shared/widgets/base_view
/base_response_model.dart'
;
import
'../../
core/
network/restful_api_viewmodel.dart'
;
import
'models/product_model.dart'
;
class
VoucherTabViewModel
extends
RestfulApiViewModel
{
...
...
lib/
screen
/vplay_game_center/vplay_game_center_screen.dart
→
lib/
features
/vplay_game_center/vplay_game_center_screen.dart
View file @
6b980613
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/widgets/image_loader.dart'
;
import
'package:mypoint_flutter_app/
shared/
widgets/image_loader.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'../../
shared/
widgets/custom_navigation_bar.dart'
;
import
'../faqs/faqs_model.dart'
;
import
'../news/news_list_viewmodel.dart'
;
...
...
@@ -15,6 +15,7 @@ class VplayGameCenterScreen extends StatelessWidget {
final
width
=
MediaQuery
.
of
(
context
).
size
.
width
;
final
space
=
12.0
;
final
itemWidth
=
(
width
-
space
*
3
)
/
2
;
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
"Trung tâm trò chơi"
),
body:
Obx
(()
{
...
...
lib/
screen
/webview/payment_web_view_screen.dart
→
lib/
features
/webview/payment_web_view_screen.dart
View file @
6b980613
...
...
@@ -4,15 +4,14 @@ import 'package:flutter/material.dart';
import
'package:get/get.dart'
;
import
'package:webview_flutter/webview_flutter.dart'
;
import
'package:url_launcher/url_launcher.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../directional/directional_screen.dart'
;
import
'../../resources/base_color.dart'
;
import
'../../shared/widgets/base_view/base_screen.dart'
;
import
'../../shared/widgets/base_view/basic_state.dart'
;
import
'../../shared/navigation/directional_screen.dart'
;
import
'../../core/theme/base_color.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../widgets/alert/data_alert_model.dart'
;
import
'../../widgets/back_button.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'../../
shared/
widgets/alert/data_alert_model.dart'
;
import
'../../
shared/
widgets/back_button.dart'
;
import
'../../
shared/
widgets/custom_navigation_bar.dart'
;
enum
PaymentProcess
{
begin
,
...
...
lib/
screen
/webview/web_view_screen.dart
→
lib/
features
/webview/web_view_screen.dart
View file @
6b980613
...
...
@@ -4,19 +4,19 @@ import 'package:flutter/foundation.dart';
import
'package:flutter/gestures.dart'
;
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/widgets/back_button.dart'
;
import
'package:mypoint_flutter_app/widgets/custom_toast_message.dart'
;
import
'package:mypoint_flutter_app/
shared/
widgets/back_button.dart'
;
import
'package:mypoint_flutter_app/
shared/
widgets/custom_toast_message.dart'
;
import
'package:url_launcher/url_launcher.dart'
;
import
'package:webview_flutter/webview_flutter.dart'
;
import
'package:image_gallery_saver/image_gallery_saver.dart'
;
import
'package:permission_handler/permission_handler.dart'
;
import
'../../
base
/app_loading.dart'
;
import
'../../
base
/base_screen.dart'
;
import
'../../
base
/basic_state.dart'
;
import
'../../
direc
tion
al
/directional_screen.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'../../preference/data_preference.dart'
;
import
'../../
preferen
ce/package_info.dart'
;
import
'../../
shared/widgets/loading
/app_loading.dart'
;
import
'../../
shared/widgets/base_view
/base_screen.dart'
;
import
'../../
shared/widgets/base_view
/basic_state.dart'
;
import
'../../
shared/naviga
tion/directional_screen.dart'
;
import
'../../
shared/
widgets/custom_navigation_bar.dart'
;
import
'../../
shared/
preference
s
/data_preference.dart'
;
import
'../../
core/servi
ce
s
/package_info.dart'
;
/// Payload for launching [BaseWebViewScreen].
class
BaseWebViewInput
{
...
...
lib/main.dart
View file @
6b980613
import
'dart:async'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/
base
/app_navigator.dart'
;
import
'package:mypoint_flutter_app/
resources
/base_color.dart'
;
import
'package:mypoint_flutter_app/
screen
/splash/splash_screen.dart'
;
import
'package:mypoint_flutter_app/
app/routing
/app_navigator.dart'
;
import
'package:mypoint_flutter_app/
core/theme
/base_color.dart'
;
import
'package:mypoint_flutter_app/
features
/splash/splash_screen.dart'
;
import
'package:mypoint_flutter_app/shared/router_gage.dart'
;
import
'package:mypoint_flutter_app/
core
/app_initializer.dart'
;
import
'package:mypoint_flutter_app/
app
/app_initializer.dart'
;
import
'package:flutter_web_plugins/url_strategy.dart'
;
final
RouteObserver
<
PageRoute
>
routeObserver
=
RouteObserver
<
PageRoute
>();
...
...
@@ -14,6 +15,7 @@ void main() async {
WidgetsFlutterBinding
.
ensureInitialized
();
if
(
kIsWeb
)
{
setUrlStrategy
(
PathUrlStrategy
());
await
_precacheWebSplashImage
();
}
// Initialize all app features
await
AppInitializer
.
initialize
();
...
...
@@ -23,7 +25,6 @@ void main() async {
AppInitializer
.
setupPostInitCallbacks
();
}
class
MyApp
extends
StatelessWidget
{
const
MyApp
({
super
.
key
});
...
...
@@ -44,3 +45,30 @@ class MyApp extends StatelessWidget {
);
}
}
Future
<
void
>
_precacheWebSplashImage
()
async
{
if
(!
kIsWeb
)
return
;
try
{
final
imageProvider
=
const
AssetImage
(
'assets/images/splash_screen.webp'
);
final
stream
=
imageProvider
.
resolve
(
ImageConfiguration
.
empty
);
final
completer
=
Completer
<
void
>();
late
final
ImageStreamListener
listener
;
listener
=
ImageStreamListener
(
(
info
,
synchronousCall
)
{
stream
.
removeListener
(
listener
);
if
(!
completer
.
isCompleted
)
completer
.
complete
();
},
onError:
(
error
,
stackTrace
)
{
stream
.
removeListener
(
listener
);
if
(!
completer
.
isCompleted
)
completer
.
completeError
(
error
,
stackTrace
);
},
);
stream
.
addListener
(
listener
);
await
completer
.
future
.
timeout
(
const
Duration
(
seconds:
2
),
onTimeout:
()
{
stream
.
removeListener
(
listener
);
});
}
catch
(
error
,
stackTrace
)
{
debugPrint
(
'Failed to precache splash image:
$error
'
);
debugPrintStack
(
stackTrace:
stackTrace
);
}
}
lib/networking/api/affiliate_api.dart
deleted
100644 → 0
View file @
bfff9e47
import
'package:mypoint_flutter_app/base/base_response_model.dart'
;
import
'package:mypoint_flutter_app/configs/api_paths.dart'
;
import
'package:mypoint_flutter_app/configs/callbacks.dart'
;
import
'package:mypoint_flutter_app/networking/restful_api_client.dart'
;
import
'package:mypoint_flutter_app/preference/data_preference.dart'
;
import
'../../screen/affiliate/model/affiliate_brand_model.dart'
;
import
'../../screen/affiliate/model/affiliate_category_model.dart'
;
import
'../../screen/affiliate/model/affiliate_product_top_sale_model.dart'
;
import
'../../screen/affiliate/model/cashback_overview_model.dart'
;
import
'../../screen/affiliate_brand_detail/models/affiliate_brand_detail_model.dart'
;
class
AffiliateApi
{
AffiliateApi
(
this
.
client
);
final
RestfulAPIClient
client
;
Future
<
BaseResponseModel
<
List
<
AffiliateCategoryModel
>>>
affiliateCategoryGetList
()
async
{
final
token
=
DataPreference
.
instance
.
token
??
""
;
final
body
=
{
"access_token"
:
token
};
return
client
.
requestNormal
(
APIPaths
.
affiliateCategoryGetList
,
Method
.
POST
,
body
,
(
data
)
{
final
list
=
data
as
List
<
dynamic
>;
return
list
.
map
((
e
)
=>
AffiliateCategoryModel
.
fromJson
(
e
)).
toList
();
},
);
}
Future
<
BaseResponseModel
<
List
<
AffiliateBrandModel
>>>
affiliateBrandGetList
({
String
?
categoryCode
,
})
async
{
final
token
=
DataPreference
.
instance
.
token
??
""
;
final
body
=
{
"access_token"
:
token
};
if
((
categoryCode
??
''
).
isNotEmpty
)
{
body
[
'category_code'
]
=
categoryCode
!;
}
return
client
.
requestNormal
(
APIPaths
.
affiliateBrandGetList
,
Method
.
POST
,
body
,
(
data
)
{
final
list
=
data
as
List
<
dynamic
>;
return
list
.
map
((
e
)
=>
AffiliateBrandModel
.
fromJson
(
e
)).
toList
();
},
);
}
Future
<
BaseResponseModel
<
List
<
AffiliateProductTopSaleModel
>>>
affiliateProductTopSale
()
async
{
final
token
=
DataPreference
.
instance
.
token
??
""
;
final
body
=
{
"access_token"
:
token
};
return
client
.
requestNormal
(
APIPaths
.
affiliateProductTopSale
,
Method
.
POST
,
body
,
(
data
)
{
final
list
=
data
as
List
<
dynamic
>;
return
list
.
map
((
e
)
=>
AffiliateProductTopSaleModel
.
fromJson
(
e
))
.
toList
();
},
);
}
Future
<
BaseResponseModel
<
CashbackOverviewModel
>>
getCashBackOverview
()
async
{
return
client
.
requestNormal
(
APIPaths
.
getCashbackOverview
,
Method
.
GET
,
{},
(
data
,
)
{
return
CashbackOverviewModel
.
fromJson
(
data
as
Json
);
});
}
Future
<
BaseResponseModel
<
AffiliateBrandDetailModel
>>
getAffiliateBrandDetail
(
String
brandId
,
)
async
{
final
token
=
DataPreference
.
instance
.
token
??
""
;
final
body
=
{
"access_token"
:
token
,
"brand_id"
:
brandId
};
return
client
.
requestNormal
(
APIPaths
.
affiliateBrandGetDetail
,
Method
.
POST
,
body
,
(
data
)
{
return
AffiliateBrandDetailModel
.
fromJson
(
data
as
Json
);
},
);
}
}
lib/preference/point/header_home_model.dart
deleted
100644 → 0
View file @
bfff9e47
import
'package:json_annotation/json_annotation.dart'
;
part
'header_home_model.g.dart'
;
@JsonSerializable
()
class
HeaderHomeModel
{
final
String
?
greeting
;
@JsonKey
(
name:
'total_voucher'
)
final
int
?
totalVoucher
;
@JsonKey
(
name:
'total_point_active'
)
final
int
?
totalPointActive
;
final
String
?
background
;
HeaderHomeModel
({
this
.
greeting
,
this
.
totalVoucher
,
this
.
totalPointActive
,
this
.
background
,
});
factory
HeaderHomeModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$HeaderHomeModelFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$HeaderHomeModelToJson
(
this
);
}
lib/preference/point/header_home_model.g.dart
deleted
100644 → 0
View file @
bfff9e47
// GENERATED CODE - DO NOT MODIFY BY HAND
part of
'header_home_model.dart'
;
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
HeaderHomeModel
_$HeaderHomeModelFromJson
(
Map
<
String
,
dynamic
>
json
)
=>
HeaderHomeModel
(
greeting:
json
[
'greeting'
]
as
String
?,
totalVoucher:
(
json
[
'total_voucher'
]
as
num
?)?.
toInt
(),
totalPointActive:
(
json
[
'total_point_active'
]
as
num
?)?.
toInt
(),
background:
json
[
'background'
]
as
String
?,
);
Map
<
String
,
dynamic
>
_$HeaderHomeModelToJson
(
HeaderHomeModel
instance
)
=>
<
String
,
dynamic
>{
'greeting'
:
instance
.
greeting
,
'total_voucher'
:
instance
.
totalVoucher
,
'total_point_active'
:
instance
.
totalPointActive
,
'background'
:
instance
.
background
,
};
lib/screen/home/models/preview_flash_sale_model.dart
deleted
100644 → 0
View file @
bfff9e47
class
PreviewFlashSale
{
final
int
?
id
;
final
int
?
countdownSecond
;
final
String
?
startTime
;
final
String
?
endTime
;
final
int
?
fsQuantityTotal
;
final
int
?
fsQuantitySold
;
final
int
?
percentTag
;
final
String
?
rewardType
;
final
String
?
rewardContent
;
final
String
?
openingContent
;
final
String
?
name
;
final
String
?
rewardPopup
;
final
int
?
price
;
final
bool
?
isFlashSale
;
final
bool
?
isFlashSalePrice
;
final
String
?
headerImg
;
final
int
?
fsQuantityPerPersonTotal
;
final
int
?
fsQuantityPerPersonBought
;
const
PreviewFlashSale
({
this
.
id
,
this
.
countdownSecond
,
this
.
startTime
,
this
.
endTime
,
this
.
fsQuantityTotal
,
this
.
fsQuantitySold
,
this
.
percentTag
,
this
.
rewardType
,
this
.
rewardContent
,
this
.
openingContent
,
this
.
name
,
this
.
rewardPopup
,
this
.
price
,
this
.
isFlashSale
,
this
.
isFlashSalePrice
,
this
.
headerImg
,
this
.
fsQuantityPerPersonTotal
,
this
.
fsQuantityPerPersonBought
,
});
factory
PreviewFlashSale
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
PreviewFlashSale
(
id:
json
[
'id'
]
as
int
?,
countdownSecond:
json
[
'countdown_second'
]
as
int
?,
startTime:
json
[
'start_time'
]
as
String
?,
endTime:
json
[
'end_time'
]
as
String
?,
fsQuantityTotal:
json
[
'fs_quantity_total'
]
as
int
?,
fsQuantitySold:
json
[
'fs_quantity_sold'
]
as
int
?,
percentTag:
json
[
'percent_tag'
]
as
int
?,
rewardType:
json
[
'reward_type'
]
as
String
?,
rewardContent:
json
[
'reward_content'
]
as
String
?,
openingContent:
json
[
'opening_content'
]
as
String
?,
name:
json
[
'name'
]
as
String
?,
rewardPopup:
json
[
'reward_popup'
]
as
String
?,
price:
json
[
'price'
]
as
int
?,
isFlashSale:
json
[
'is_flash_sale'
]
as
bool
?,
isFlashSalePrice:
json
[
'is_flash_sale_price'
]
as
bool
?,
headerImg:
json
[
'header_img'
]
as
String
?,
fsQuantityPerPersonTotal:
json
[
'fs_quantity_per_person_total'
]
as
int
?,
fsQuantityPerPersonBought:
json
[
'fs_quantity_per_person_bought'
]
as
int
?,
);
}
Map
<
String
,
dynamic
>
toJson
()
=>
{
'id'
:
id
,
'countdown_second'
:
countdownSecond
,
'start_time'
:
startTime
,
'end_time'
:
endTime
,
'fs_quantity_total'
:
fsQuantityTotal
,
'fs_quantity_sold'
:
fsQuantitySold
,
'percent_tag'
:
percentTag
,
'reward_type'
:
rewardType
,
'reward_content'
:
rewardContent
,
'opening_content'
:
openingContent
,
'name'
:
name
,
'reward_popup'
:
rewardPopup
,
'price'
:
price
,
'is_flash_sale'
:
isFlashSale
,
'is_flash_sale_price'
:
isFlashSalePrice
,
'header_img'
:
headerImg
,
'fs_quantity_per_person_total'
:
fsQuantityPerPersonTotal
,
'fs_quantity_per_person_bought'
:
fsQuantityPerPersonBought
,
};
double
?
get
progress
{
if
(
fsQuantityTotal
!=
null
&&
fsQuantitySold
!=
null
&&
fsQuantityTotal
!
>
0
)
{
return
fsQuantitySold
!
/
fsQuantityTotal
!;
}
return
null
;
}
bool
get
isSoldOut
=>
fsQuantitySold
==
fsQuantityTotal
;
String
get
textQuantitySold
=>
isSoldOut
?
"Đã bán hết"
:
"Đã bán
${fsQuantitySold ?? 0}
"
;
DateTime
?
get
startDate
=>
_parseDate
(
startTime
)?.
subtract
(
Duration
(
seconds:
1
));
DateTime
?
get
endDate
=>
_parseDate
(
endTime
)?.
add
(
Duration
(
seconds:
1
));
bool
?
get
isGoingOn
{
final
now
=
DateTime
.
now
();
if
(
startDate
!=
null
&&
endDate
!=
null
&&
now
.
isBefore
(
endDate
!))
{
return
now
.
isAfter
(
startDate
!);
}
return
null
;
}
String
?
get
desTime
{
final
go
=
isGoingOn
;
if
(
go
==
null
)
return
null
;
return
go
?
"Kết thúc trong"
:
"Bắt đầu sau"
;
}
Duration
?
get
countdownLocal
{
final
now
=
DateTime
.
now
();
if
(
isGoingOn
==
true
)
{
return
endDate
?.
difference
(
now
);
}
else
{
return
startDate
?.
difference
(
now
);
}
}
int
?
get
maximumQuantityPurchased
{
if
(
fsQuantityPerPersonTotal
!=
null
)
{
final
bought
=
fsQuantityPerPersonBought
??
0
;
return
(
fsQuantityPerPersonTotal
!
-
bought
).
clamp
(
0
,
fsQuantityPerPersonTotal
!);
}
return
null
;
}
bool
get
isShowProgressSoldItemCell
=>
isFlashSale
==
true
&&
(
fsQuantityTotal
??
0
)
>
0
;
bool
get
isHidenOpeningContent
=>
openingContent
==
null
||
openingContent
!.
isEmpty
||
isGoingOn
==
true
||
isFlashSalePrice
==
true
;
bool
get
isHasReward
=>
rewardContent
!=
null
&&
rewardContent
!.
isNotEmpty
;
String
?
get
rewardImageAsset
{
return
rewardType
==
"point"
?
"assets/icons/ic_point.png"
:
"assets/icons/ic_gift_flash_sale.png"
;
}
DateTime
?
_parseDate
(
String
?
str
)
{
if
(
str
==
null
)
return
null
;
return
DateTime
.
tryParse
(
str
);
}
}
lib/screen/popup_manager/popup_manager_viewmodel.dart
deleted
100644 → 0
View file @
bfff9e47
import
'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart'
;
import
'package:mypoint_flutter_app/screen/popup_manager/popup_manager_model.dart'
;
import
'../../networking/restful_api_viewmodel.dart'
;
class
PopupManagerViewModel
extends
RestfulApiViewModel
{
PopupManagerViewModel
.
_
();
static
final
PopupManagerViewModel
instance
=
PopupManagerViewModel
.
_
();
final
Set
<
String
>
_shownIds
=
{};
List
<
PopupManagerModel
>?
_popupData
;
bool
_loaded
=
false
;
Future
<
void
>?
_loadingFuture
;
Future
<
void
>
ensureLoaded
()
async
{
if
(
_loaded
)
return
;
if
(
_loadingFuture
!=
null
)
{
return
_loadingFuture
;
}
_loadingFuture
=
_getPopupManagerDataInternal
();
await
_loadingFuture
;
}
Future
<
void
>
_getPopupManagerDataInternal
()
async
{
try
{
final
response
=
await
client
.
getPopupManagerCommonScreen
();
_popupData
=
response
.
data
??
[];
// _popupData = [
// PopupManagerModel(
// id: '1',
// screenToShow: 'APP_SCREEN_HOME',
// clickActionType: 'VIEW_PRODUCT_VOUCHER',
// clickActionParam: '50760',
// posActionID: 'action1',
// posActionCode: 'code1',
// timeToShow: '10:00-18:00',
// timeCountDown: '30',
// hourStartInDay: '9',
// hourStopInDay: '17',
// afterPosID: 'pos1',
// afterPosCode: 'posCode1',
// afterPosName: 'POS Name 1',
// marketingRequestDescription: 'Marketing description here.',
// effectiveFromDate: '2023-01-01',
// effectiveToDate: '2023-12-31',
// scheduleRunTypeCode: 'daily',
// scheduleRunTypeName: 'Daily Schedule',
// scheduleAtTime: '12:00',
// popupTitleTemplate: 'APP_SCREEN_HOME',
// popupBodyTemplate: 'Enjoy your stay and check out our features.',
// imageID: 'image123',
// imageURL: 'https://picsum.photos/1200/800',
// ),
// PopupManagerModel(
// id: '2',
// screenToShow: 'APP_SCREEN_POINTBACK',
// clickActionType: 'APP_SCREEN_SIM_SERVICE',
// clickActionParam: 'https://example.com/settings',
// posActionID: 'action2',
// posActionCode: 'code2',
// timeToShow: '08:00-20:00',
// timeCountDown: '60',
// hourStartInDay: '8',
// hourStopInDay: '20',
// afterPosID: 'pos2',
// afterPosCode: 'posCode2',
// afterPosName: 'POS Name 2',
// marketingRequestDescription: 'Settings popup description.',
// effectiveFromDate: '2023-01-01',
// effectiveToDate: '2023-12-31',
// scheduleRunTypeCode: 'weekly',
// scheduleRunTypeName: 'Weekly Schedule',
// scheduleAtTime: '10:00',
// popupTitleTemplate: 'APP_SCREEN_POINTBACK',
// popupBodyTemplate: 'Check out the new settings options.',
// imageID: 'image456',
// imageURL: 'https://picsum.photos/1200/800',
// ),
// PopupManagerModel(
// id: '3',
// screenToShow: 'APP_SCREEN_PRODUCT_VOUCHER',
// clickActionType: 'APP_SCREEN_GIFTS',
// clickActionParam: 'Profile updated successfully.',
// posActionID: 'action3',
// posActionCode: 'code3',
// timeToShow: '09:00-21:00',
// timeCountDown: '45',
// hourStartInDay: '9',
// hourStopInDay: '21',
// afterPosID: 'pos3',
// afterPosCode: 'posCode3',
// afterPosName: 'POS Name 3',
// marketingRequestDescription: 'Profile update alert.',
// effectiveFromDate: '2023-01-01',
// effectiveToDate: '2023-12-31',
// scheduleRunTypeCode: 'monthly',
// scheduleRunTypeName: 'Monthly Schedule',
// scheduleAtTime: '15:00',
// popupTitleTemplate: 'APP_SCREEN_PRODUCT_VOUCHER',
// popupBodyTemplate: 'Your profile has been updated successfully.',
// imageID: 'image789',
// imageURL: 'https://picsum.photos/1200/800',
// ),
// PopupManagerModel(
// id: '4',
// screenToShow: 'APP_SCREEN_GAME_BUNDLE',
// clickActionType: 'APP_SCREEN_CAMPAIGN_WALKING',
// clickActionParam: '1',
// posActionID: 'action3',
// posActionCode: 'code3',
// timeToShow: '09:00-21:00',
// timeCountDown: '45',
// hourStartInDay: '9',
// hourStopInDay: '21',
// afterPosID: 'pos3',
// afterPosCode: 'posCode3',
// afterPosName: 'POS Name 3',
// marketingRequestDescription: 'Profile update alert.',
// effectiveFromDate: '2023-01-01',
// effectiveToDate: '2023-12-31',
// scheduleRunTypeCode: 'monthly',
// scheduleRunTypeName: 'Monthly Schedule',
// scheduleAtTime: '15:00',
// popupTitleTemplate: 'APP_SCREEN_GAME_BUNDLE',
// popupBodyTemplate: 'Your profile has been updated successfully.',
// imageID: 'image789',
// imageURL: 'https://picsum.photos/1200/800',
// ),
// PopupManagerModel(
// id: '5',
// screenToShow: 'APP_SCREEN_PERSONAL',
// clickActionType: 'APP_SCREEN_SIM_SERVICE',
// clickActionParam: 'Profile updated successfully.',
// posActionID: 'action3',
// posActionCode: 'code3',
// timeToShow: '09:00-21:00',
// timeCountDown: '45',
// hourStartInDay: '9',
// hourStopInDay: '21',
// afterPosID: 'pos3',
// afterPosCode: 'posCode3',
// afterPosName: 'POS Name 3',
// marketingRequestDescription: 'Profile update alert.',
// effectiveFromDate: '2023-01-01',
// effectiveToDate: '2023-12-31',
// scheduleRunTypeCode: 'monthly',
// scheduleRunTypeName: 'Monthly Schedule',
// scheduleAtTime: '15:00',
// popupTitleTemplate: 'APP_SCREEN_PERSONAL',
// popupBodyTemplate: 'Your profile has been updated successfully.',
// imageID: 'image789',
// imageURL: 'https://picsum.photos/1200/800',
// ),
// ];
_loaded
=
true
;
}
catch
(
e
)
{
_popupData
=
[];
_loaded
=
true
;
rethrow
;
}
finally
{
_loadingFuture
=
null
;
}
}
PopupManagerModel
?
getForScreen
(
String
screenName
)
{
if
(
_popupData
==
null
||
_popupData
!.
isEmpty
)
return
null
;
final
idx
=
_popupData
!.
indexWhere
(
(
e
)
=>
(
e
.
screenToShow
??
''
).
trim
().
toUpperCase
()
==
screenName
.
trim
().
toUpperCase
(),
);
if
(
idx
<
0
)
return
null
;
final
found
=
_popupData
![
idx
];
if
(
_shownIds
.
contains
(
found
.
id
))
return
null
;
return
found
;
}
Future
<
void
>
markShownOnce
(
String
popupId
)
async
{
_shownIds
.
add
(
popupId
);
}
Future
<
void
>
reset
()
async
{
_shownIds
.
clear
();
_popupData
=
[];
_loaded
=
false
;
_loadingFuture
=
null
;
}
}
Prev
1
…
19
20
21
22
23
24
25
26
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