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
4c376d38
Commit
4c376d38
authored
Jul 11, 2025
by
DatHV
Browse files
cập nhật ui. logic
parent
fa01087d
Changes
25
Show whitespace changes
Inline
Side-by-side
android/app/src/main/AndroidManifest.xml
View file @
4c376d38
...
@@ -42,4 +42,5 @@
...
@@ -42,4 +42,5 @@
<data
android:mimeType=
"text/plain"
/>
<data
android:mimeType=
"text/plain"
/>
</intent>
</intent>
</queries>
</queries>
<uses-permission
android:name=
"android.permission.READ_CONTACTS"
/>
</manifest>
</manifest>
assets/images/ic_check_in_success.png
0 → 100644
View file @
4c376d38
1.61 KB
assets/images/ic_daily_checkin_smoking_campaign.png
0 → 100644
View file @
4c376d38
190 KB
assets/images/ic_pipi_checkin.png
0 → 100644
View file @
4c376d38
182 KB
assets/images/ic_pipi_couple.png
0 → 100644
View file @
4c376d38
74.9 KB
lib/configs/api_paths.dart
View file @
4c376d38
...
@@ -74,4 +74,8 @@ class APIPaths {
...
@@ -74,4 +74,8 @@ class APIPaths {
static
const
String
historyCashBackPoint
=
"/cashback/orders"
;
static
const
String
historyCashBackPoint
=
"/cashback/orders"
;
static
const
String
historyCashBackPointDetail
=
"/cashback/order/details"
;
static
const
String
historyCashBackPointDetail
=
"/cashback/order/details"
;
static
const
String
affiliateBrandGetDetail
=
"/affiliateBrandGetDetail/1.0.0"
;
static
const
String
affiliateBrandGetDetail
=
"/affiliateBrandGetDetail/1.0.0"
;
static
const
String
campaignInviteFriend
=
"/campaign/api/v3.0/invite-friend"
;
static
const
String
inviteFriendCampaigns
=
"/campaign/api/v3.0/invite-friend/campaigns"
;
static
const
String
phoneInviteFriend
=
"/campaign/api/v3.0/invite-friend/invite"
;
static
const
String
rewardOpportunityGetList
=
"/rewardOpportunityGetList/1.0.0"
;
}
}
\ No newline at end of file
lib/directional/directional_screen.dart
View file @
4c376d38
...
@@ -128,6 +128,12 @@ class DirectionalScreen {
...
@@ -128,6 +128,12 @@ class DirectionalScreen {
case
DirectionalScreenName
.
refundHistory
:
case
DirectionalScreenName
.
refundHistory
:
Get
.
toNamed
(
historyPointCashBackScreen
);
Get
.
toNamed
(
historyPointCashBackScreen
);
return
true
;
return
true
;
case
DirectionalScreenName
.
inviteFriend
:
Get
.
toNamed
(
inviteFriendCampaignScreen
);
return
true
;
case
DirectionalScreenName
.
dailyCheckin
:
Get
.
toNamed
(
dailyCheckInScreen
);
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
"
);
return
false
;
return
false
;
...
...
lib/networking/restful_api_request.dart
View file @
4c376d38
...
@@ -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/daily_checkin/daily_checkin_models.dart'
;
import
'../screen/data_network_service/product_network_data_model.dart'
;
import
'../screen/data_network_service/product_network_data_model.dart'
;
import
'../screen/faqs/faqs_model.dart'
;
import
'../screen/faqs/faqs_model.dart'
;
import
'../screen/game/models/game_bundle_item_model.dart'
;
import
'../screen/game/models/game_bundle_item_model.dart'
;
...
@@ -28,6 +29,7 @@ import '../screen/home/models/main_section_config_model.dart';
...
@@ -28,6 +29,7 @@ import '../screen/home/models/main_section_config_model.dart';
import
'../screen/home/models/my_product_model.dart'
;
import
'../screen/home/models/my_product_model.dart'
;
import
'../screen/home/models/notification_unread_model.dart'
;
import
'../screen/home/models/notification_unread_model.dart'
;
import
'../screen/home/models/pipi_detail_model.dart'
;
import
'../screen/home/models/pipi_detail_model.dart'
;
import
'../screen/invite_friend_campaign/models/invite_friend_campaign_model.dart'
;
import
'../screen/location_address/models/district_address_model.dart'
;
import
'../screen/location_address/models/district_address_model.dart'
;
import
'../screen/location_address/models/province_address_model.dart'
;
import
'../screen/location_address/models/province_address_model.dart'
;
import
'../screen/membership/models/membership_info_response.dart'
;
import
'../screen/membership/models/membership_info_response.dart'
;
...
@@ -678,4 +680,46 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
...
@@ -678,4 +680,46 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
return
AffiliateBrandDetailModel
.
fromJson
(
data
as
Json
);
return
AffiliateBrandDetailModel
.
fromJson
(
data
as
Json
);
});
});
}
}
Future
<
BaseResponseModel
<
InviteFriendDetailModel
>>
getCampaignInviteFriend
()
async
{
String
?
token
=
DataPreference
.
instance
.
token
??
""
;
final
body
=
{
"access_token"
:
token
,
};
return
requestNormal
(
APIPaths
.
campaignInviteFriend
,
Method
.
GET
,
body
,
(
data
)
{
return
InviteFriendDetailModel
.
fromJson
(
data
as
Json
);
});
}
Future
<
BaseResponseModel
<
CampaignInviteFriendDetail
>>
getDetailCampaignInviteFriend
()
async
{
String
?
token
=
DataPreference
.
instance
.
token
??
""
;
final
body
=
{
"access_token"
:
token
,
};
return
requestNormal
(
APIPaths
.
inviteFriendCampaigns
,
Method
.
GET
,
body
,
(
data
)
{
return
CampaignInviteFriendDetail
.
fromJson
(
data
as
Json
);
});
}
Future
<
BaseResponseModel
<
InviteFriendResponse
>>
phoneInviteFriend
(
String
phone
)
async
{
String
?
token
=
DataPreference
.
instance
.
token
??
""
;
final
body
=
{
"access_token"
:
token
,
'username'
:
phone
,
};
return
requestNormal
(
APIPaths
.
phoneInviteFriend
,
Method
.
POST
,
body
,
(
data
)
{
return
InviteFriendResponse
.
fromJson
(
data
as
Json
);
});
}
Future
<
BaseResponseModel
<
CheckInDataModel
>>
rewardOpportunityGetList
()
async
{
String
?
token
=
DataPreference
.
instance
.
token
??
""
;
final
body
=
{
"access_token"
:
token
,
'number_day'
:
'7'
,
};
return
requestNormal
(
APIPaths
.
rewardOpportunityGetList
,
Method
.
POST
,
body
,
(
data
)
{
return
CheckInDataModel
.
fromJson
(
data
as
Json
);
});
}
}
}
\ No newline at end of file
lib/screen/affiliate/affiliate_popup_brands.dart
View file @
4c376d38
...
@@ -19,11 +19,13 @@ void showAffiliateBrandPopup(BuildContext context, List<AffiliateBrandModel> bra
...
@@ -19,11 +19,13 @@ void showAffiliateBrandPopup(BuildContext context, List<AffiliateBrandModel> bra
const
SizedBox
(
height:
8
),
const
SizedBox
(
height:
8
),
Row
(
Row
(
children:
[
children:
[
const
SizedBox
(
width:
16
),
IconButton
(
icon:
const
Icon
(
Icons
.
close
,
color:
Colors
.
transparent
,),
onPressed:
()
=>
{}
),
Expanded
(
Expanded
(
child:
Center
(
child:
Center
(
child:
Text
(
child:
Text
(
title
??
"Thương hiệu"
,
title
??
"Thương hiệu"
,
textAlign:
TextAlign
.
center
,
maxLines:
1
,
style:
const
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
bold
,
color:
Colors
.
black
),
style:
const
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
bold
,
color:
Colors
.
black
),
),
),
),
),
...
...
lib/screen/contacts/contacts_list_screen.dart
0 → 100644
View file @
4c376d38
import
'package:flutter_contacts/flutter_contacts.dart'
;
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:url_launcher/url_launcher.dart'
;
import
'../../../widgets/custom_empty_widget.dart'
;
import
'../../../widgets/custom_search_navigation_bar.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../invite_friend_campaign/invite_friend_campaign_viewmodel.dart'
;
class
ContactsListScreen
extends
StatefulWidget
{
const
ContactsListScreen
({
super
.
key
});
@override
_ContactsListScreenState
createState
()
=>
_ContactsListScreenState
();
}
class
_ContactsListScreenState
extends
State
<
ContactsListScreen
>
{
final
InviteFriendCampaignViewModel
viewModel
=
Get
.
put
(
InviteFriendCampaignViewModel
());
List
<
Contact
>
contacts
=
[];
List
<
Contact
>
displayContacts
=
[];
String
searchQuery
=
''
;
@override
void
initState
()
{
super
.
initState
();
final
args
=
Get
.
arguments
;
if
(
args
is
Map
)
{
contacts
=
args
[
'contacts'
];
}
displayContacts
=
contacts
;
viewModel
.
phoneInviteFriendResponse
=
(
sms
,
phone
)
{
_openSMS
(
sms
,
phone
);
};
}
_openSMS
(
String
sms
,
String
phone
)
async
{
final
uri
=
Uri
(
scheme:
'sms'
,
path:
phone
,
queryParameters:
<
String
,
String
>{
'body'
:
sms
});
if
(
await
canLaunchUrl
(
uri
))
{
await
launchUrl
(
uri
);
}
}
_onSearchChanged
(
String
query
)
{
setState
(()
{
displayContacts
=
searchContacts
(
query
);
});
}
List
<
Contact
>
searchContacts
(
String
query
)
{
query
=
query
.
trim
();
if
(
query
.
isEmpty
)
return
contacts
;
final
isNumber
=
RegExp
(
r'^\d+$'
).
hasMatch
(
query
);
return
contacts
.
where
((
contact
)
{
final
name
=
contact
.
displayName
?.
toLowerCase
()
??
''
;
final
phone
=
contact
.
phones
?.
isNotEmpty
==
true
?
contact
.
phones
!.
first
.
number
?.
replaceAll
(
RegExp
(
r'\D'
),
''
)
??
''
:
''
;
if
(
isNumber
)
{
return
phone
.
contains
(
query
);
}
else
{
return
name
.
contains
(
query
.
toLowerCase
());
}
}).
toList
();
}
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
appBar:
CustomSearchNavigationBar
(
onSearchChanged:
_onSearchChanged
),
body:
displayContacts
.
isEmpty
?
EmptyWidget
(
size:
Size
(
240
,
240
))
:
ListView
.
builder
(
physics:
const
AlwaysScrollableScrollPhysics
(),
itemCount:
displayContacts
.
length
,
itemBuilder:
(
context
,
index
)
{
final
contact
=
displayContacts
[
index
];
return
GestureDetector
(
onTap:
()
{
print
(
'contact selected
${contact.phones.firstOrNull}
'
);
},
child:
Column
(
children:
[
_buildContactCard
(
contact
),
Padding
(
padding:
const
EdgeInsets
.
only
(
left:
20
,
right:
20
),
child:
const
Divider
(
height:
1
,
color:
Colors
.
black12
),
),
],
),
);
},
),
);
}
Widget
_buildContactCard
(
Contact
contact
)
{
final
name
=
contact
.
displayName
;
final
phone
=
contact
.
phones
?.
isNotEmpty
==
true
?
contact
.
phones
?.
first
.
number
??
'Không số'
:
'Không số'
;
return
ListTile
(
leading:
CircleAvatar
(
backgroundImage:
const
AssetImage
(
'assets/images/ic_pipi_02.png'
),
backgroundColor:
BaseColor
.
primary200
,
),
title:
Text
(
name
,
style:
const
TextStyle
(
fontWeight:
FontWeight
.
bold
)),
subtitle:
Text
(
phone
),
trailing:
OutlinedButton
(
style:
OutlinedButton
.
styleFrom
(
side:
const
BorderSide
(
color:
Colors
.
red
,
width:
1.5
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
12
)),
),
onPressed:
()
{
viewModel
.
phoneInviteFriend
(
phone
);
},
child:
const
Text
(
'Mời'
,
style:
TextStyle
(
color:
Colors
.
red
,
fontWeight:
FontWeight
.
w800
)),
),
);
}
}
lib/screen/daily_checkin/daily_checkin_models.dart
0 → 100644
View file @
4c376d38
class
CheckInDataModel
{
final
String
?
campaignCode
;
final
List
<
Counter
>?
counters
;
CheckInDataModel
({
this
.
campaignCode
,
this
.
counters
});
factory
CheckInDataModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
CheckInDataModel
(
campaignCode:
json
[
'campaign_code'
],
counters:
(
json
[
'counters'
]
as
List
?)
?.
map
((
e
)
=>
Counter
.
fromJson
(
e
))
.
toList
(),
);
}
}
extension
CounterExt
on
Counter
{
List
<
CounterValue
>
get
values
{
if
(
counterValues
is
List
<
CounterValue
>)
{
return
counterValues
;
}
else
if
(
counterValues
is
CounterValue
)
{
return
[
counterValues
];
}
else
{
return
[];
}
}
}
class
Counter
{
final
String
?
counterName
;
final
String
?
counterPeriodType
;
final
dynamic
counterValues
;
Counter
({
this
.
counterName
,
this
.
counterPeriodType
,
this
.
counterValues
});
factory
Counter
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
final
rawValues
=
json
[
'counter_values'
];
dynamic
parsedValues
;
if
(
rawValues
is
List
)
{
parsedValues
=
rawValues
.
map
((
e
)
=>
CounterValue
.
fromJson
(
e
as
Map
<
String
,
dynamic
>)).
toList
();
}
else
if
(
rawValues
is
Map
<
String
,
dynamic
>)
{
parsedValues
=
CounterValue
.
fromJson
(
rawValues
);
}
return
Counter
(
counterName:
json
[
'counter_name'
],
counterPeriodType:
json
[
'counter_period_type'
],
counterValues:
parsedValues
,
);
}
}
class
CounterValue
{
final
String
?
counterValue
;
final
int
?
pointReward
;
final
String
?
counterPeriodValue
;
CounterValue
({
this
.
counterValue
,
this
.
pointReward
,
this
.
counterPeriodValue
});
factory
CounterValue
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
CounterValue
(
counterValue:
json
[
'counter_value'
],
pointReward:
json
[
'point_reward'
],
counterPeriodValue:
json
[
'counter_period_value'
],
);
}
}
\ No newline at end of file
lib/screen/daily_checkin/daily_checkin_screen.dart
0 → 100644
View file @
4c376d38
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/screen/daily_checkin/daily_checkin_models.dart'
;
import
'../../preference/point/point_manager.dart'
;
import
'../../resouce/base_color.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'daily_checkin_viewmodel.dart'
;
class
DailyCheckInScreen
extends
StatefulWidget
{
const
DailyCheckInScreen
({
super
.
key
});
@override
State
<
DailyCheckInScreen
>
createState
()
=>
_DailyCheckInScreenState
();
}
class
_DailyCheckInScreenState
extends
State
<
DailyCheckInScreen
>
{
final
DailyCheckInViewModel
_viewModel
=
Get
.
put
(
DailyCheckInViewModel
());
@override
Widget
build
(
BuildContext
context
)
{
final
double
screenWidth
=
MediaQuery
.
of
(
context
).
size
.
width
;
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
"Check-in nhận quà"
),
body:
Obx
(()
{
return
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
center
,
children:
[
const
SizedBox
(
height:
32
),
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
const
Text
(
'Điểm Check-in của tôi:'
,
style:
TextStyle
(
color:
Colors
.
black54
,
fontSize:
16
)),
const
SizedBox
(
width:
4
),
Image
.
asset
(
'assets/images/ic_point.png'
,
width:
24
,
fit:
BoxFit
.
cover
,
),
const
SizedBox
(
width:
4
),
Text
(
'
${UserPointManager().point}
'
,
style:
TextStyle
(
color:
Colors
.
orange
,
fontSize:
16
,
fontWeight:
FontWeight
.
w600
),
),
],
),
const
SizedBox
(
height:
32
),
Image
.
asset
(
'assets/images/ic_pipi_checkin.png'
,
// ảnh background
width:
screenWidth
-
64
,
height:
(
screenWidth
-
64
)
*
9
/
16
,
fit:
BoxFit
.
cover
,
),
const
SizedBox
(
height:
32
),
_buildCheckInList
(),
const
SizedBox
(
height:
32
),
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
24
),
child:
SizedBox
(
width:
double
.
infinity
,
height:
48
,
child:
ElevatedButton
(
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
BaseColor
.
primary500
,
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
12
)),
),
onPressed:
()
{},
child:
const
Text
(
'Check-in để nhận điểm hôm nay'
,
style:
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
bold
,
color:
Colors
.
white
),
),
),
),
),
],
);
}),
);
}
Widget
_buildCheckInList
()
{
final
counter
=
_viewModel
.
checkInData
.
value
?.
counters
?.
first
;
final
items
=
counter
?.
values
??
[];
final
days
=
List
.
generate
(
items
.
length
,
(
index
)
{
var
item
=
items
[
index
];
String
label
;
String
point
=
'+
${item.pointReward?.toString() ?? '0'}
'
;
bool
isToday
=
index
==
0
;
if
(
index
==
0
)
{
label
=
'Hôm nay'
;
}
else
if
(
index
==
items
.
length
-
1
)
{
label
=
'Ngày 7'
;
}
else
{
label
=
'Ngày
${index + 1}
'
;
}
return
Column
(
children:
[
Row
(
children:
[
SizedBox
(
width:
16
,
child:
Divider
(
color:
index
==
0
?
Colors
.
transparent
:
Colors
.
grey
.
shade300
,
thickness:
4
),
),
isToday
?
Image
.
asset
(
'assets/images/ic_check_in_success.png'
,
width:
24
,
fit:
BoxFit
.
cover
)
:
Image
.
asset
(
'assets/images/ic_point.png'
,
width:
24
,
fit:
BoxFit
.
cover
),
SizedBox
(
width:
16
,
child:
Divider
(
color:
index
==
6
?
Colors
.
transparent
:
Colors
.
grey
.
shade300
,
thickness:
4
),
),
],
),
const
SizedBox
(
height:
4
),
Text
(
label
,
style:
TextStyle
(
fontSize:
12
,
color:
isToday
?
Colors
.
orange
:
Colors
.
black54
,
fontWeight:
isToday
?
FontWeight
.
bold
:
FontWeight
.
normal
,
),
),
const
SizedBox
(
height:
2
),
Text
(
point
,
style:
TextStyle
(
fontSize:
13
,
color:
isToday
?
Colors
.
orange
:
Colors
.
black87
,
fontWeight:
FontWeight
.
bold
,
),
),
],
);
});
return
SizedBox
(
height:
80
,
child:
SingleChildScrollView
(
scrollDirection:
Axis
.
horizontal
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
8
),
child:
Row
(
children:
[
for
(
int
i
=
0
;
i
<
days
.
length
;
i
++)
...[
days
[
i
]],
],
),
),
);
}
}
lib/screen/daily_checkin/daily_checkin_viewmodel.dart
0 → 100644
View file @
4c376d38
import
'package:get/get_rx/src/rx_types/rx_types.dart'
;
import
'package:mypoint_flutter_app/networking/restful_api_request.dart'
;
import
'../../base/restful_api_viewmodel.dart'
;
import
'daily_checkin_models.dart'
;
class
DailyCheckInViewModel
extends
RestfulApiViewModel
{
var
checkInData
=
Rxn
<
CheckInDataModel
>();
void
Function
(
String
message
)?
onShowAlertError
;
@override
onInit
()
{
super
.
onInit
();
_rewardOpportunityGetList
();
}
Future
<
void
>
_rewardOpportunityGetList
()
async
{
try
{
final
response
=
await
client
.
rewardOpportunityGetList
();
checkInData
.
value
=
response
.
data
;
}
catch
(
error
)
{
onShowAlertError
?.
call
(
"Error fetching product detail:
$error
"
);
}
}
}
\ No newline at end of file
lib/screen/data_network_service/data_network_service_screen.dart
View file @
4c376d38
...
@@ -356,7 +356,7 @@ class _DataNetworkServiceScreenState extends BaseState<DataNetworkServiceScreen>
...
@@ -356,7 +356,7 @@ class _DataNetworkServiceScreenState extends BaseState<DataNetworkServiceScreen>
}
}
}
catch
(
e
)
{
}
catch
(
e
)
{
print
(
"❌ Lỗi khi truy cập danh bạ:
$e
"
);
print
(
"❌ Lỗi khi truy cập danh bạ:
$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/invite_friend_campaign/invite_friend_campaign_list_screen.dart
0 → 100644
View file @
4c376d38
import
'package:flutter/material.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
class
InviteFriendCampaignListScreen
extends
StatefulWidget
{
const
InviteFriendCampaignListScreen
({
super
.
key
});
@override
State
<
InviteFriendCampaignListScreen
>
createState
()
=>
_InviteFriendCampaignListScreenState
();
}
class
_InviteFriendCampaignListScreenState
extends
State
<
InviteFriendCampaignListScreen
>
{
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
appBar:
CustomNavigationBar
(
title:
"title"
),
body:
Container
(),
);
}
}
lib/screen/invite_friend_campaign/invite_friend_campaign_screen.dart
0 → 100644
View file @
4c376d38
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_contacts/flutter_contacts.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/resouce/base_color.dart'
;
import
'package:mypoint_flutter_app/screen/invite_friend_campaign/popup_invite_friend_code.dart'
;
import
'package:share_plus/share_plus.dart'
;
import
'package:url_launcher/url_launcher.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/basic_state.dart'
;
import
'../../shared/router_gage.dart'
;
import
'../../widgets/custom_navigation_bar.dart'
;
import
'../../widgets/image_loader.dart'
;
import
'invite_friend_campaign_viewmodel.dart'
;
import
'models/invite_friend_campaign_model.dart'
;
class
InviteFriendCampaignScreen
extends
BaseScreen
{
const
InviteFriendCampaignScreen
({
super
.
key
});
@override
State
<
InviteFriendCampaignScreen
>
createState
()
=>
_InviteFriendCampaignScreenState
();
}
class
_InviteFriendCampaignScreenState
extends
BaseState
<
InviteFriendCampaignScreen
>
with
BasicState
{
final
InviteFriendCampaignViewModel
viewModel
=
Get
.
put
(
InviteFriendCampaignViewModel
());
List
<
Contact
>
contacts
=
[];
@override
void
initState
()
{
super
.
initState
();
fetchContacts
();
viewModel
.
onShowAlertError
=
(
message
)
{
if
(
message
.
isNotEmpty
)
{
showAlertError
(
content:
message
);
}
};
viewModel
.
phoneInviteFriendResponse
=
(
sms
,
phone
)
{
_openSMS
(
sms
,
phone
);
};
viewModel
.
loadData
();
}
_openSMS
(
String
sms
,
String
phone
)
async
{
final
uri
=
Uri
(
scheme:
'sms'
,
path:
phone
,
queryParameters:
<
String
,
String
>{
'body'
:
sms
});
if
(
await
canLaunchUrl
(
uri
))
{
await
launchUrl
(
uri
);
}
}
@override
Widget
createBody
()
{
return
Scaffold
(
appBar:
PreferredSize
(
preferredSize:
const
Size
.
fromHeight
(
kToolbarHeight
),
child:
Obx
(()
{
final
title
=
viewModel
.
inviteFriendDetail
.
value
?.
name
??
'Mời bạn bè'
;
return
CustomNavigationBar
(
title:
title
);
}),
),
backgroundColor:
BaseColor
.
primary100
,
body:
SingleChildScrollView
(
child:
Obx
(()
{
return
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
_buildHeaderInfo
(),
SizedBox
(
height:
56
),
_buildInviteCardBox
(),
_buildContactCardBox
(),
_buildCampaignBox
(),
Container
(
color:
Colors
.
grey
.
shade100
,
child:
SizedBox
(
height:
64
)),
],
);
}),
),
);
}
Future
<
void
>
fetchContacts
()
async
{
if
(!
await
FlutterContacts
.
requestPermission
())
{
debugPrint
(
"🚫 Không có quyền"
);
return
;
}
final
contacts
=
await
FlutterContacts
.
getContacts
(
withProperties:
true
);
debugPrint
(
"📋 Số lượng liên hệ:
${contacts.length}
"
);
setState
(()
{
this
.
contacts
=
contacts
;
});
}
Widget
_buildHeaderInfo
()
{
final
double
screenWidth
=
MediaQuery
.
of
(
context
).
size
.
width
;
final
double
imageHeight
=
screenWidth
/
(
16
/
9
);
return
Obx
(()
{
return
Stack
(
clipBehavior:
Clip
.
none
,
children:
[
loadNetworkImage
(
url:
viewModel
.
inviteFriendDetail
.
value
?.
bannerUrl
,
fit:
BoxFit
.
fill
,
height:
imageHeight
,
width:
double
.
infinity
,
placeholderAsset:
'assets/images/bg_header_detail_brand.png'
,
),
Positioned
(
top:
12
,
left:
16
,
right:
16
,
child:
GestureDetector
(
onTap:
()
{},
child:
Container
(
height:
42
,
decoration:
BoxDecoration
(
color:
Colors
.
transparent
,
border:
Border
.
all
(
color:
BaseColor
.
primary500
),
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
Row
(
children:
[
const
SizedBox
(
width:
8
),
Icon
(
Icons
.
person
,
color:
Colors
.
black54
),
const
SizedBox
(
width:
8
),
const
Text
(
'Nhập mã giới thiệu bạn bè ở đây'
,
style:
TextStyle
(
fontSize:
14
,
color:
Colors
.
black87
),
),
],
),
),
),
),
Positioned
(
left:
16
,
right:
16
,
child:
Transform
.
translate
(
offset:
Offset
(
0
,
imageHeight
-
36
),
child:
_buildInfoCard
()),
),
],
);
});
}
Widget
_buildInfoCard
()
{
return
Container
(
padding:
const
EdgeInsets
.
all
(
16
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
12
),
boxShadow:
const
[
BoxShadow
(
color:
Colors
.
black12
,
blurRadius:
4
,
offset:
Offset
(
0
,
2
))],
),
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
start
,
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
viewModel
.
inviteFriendDetail
.
value
?.
reward
?.
title
??
''
,
style:
const
TextStyle
(
fontWeight:
FontWeight
.
w800
,
fontSize:
18
),
),
const
SizedBox
(
height:
8
),
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
_buildRewardWidgets
(
viewModel
.
inviteFriendDetail
.
value
?.
reward
?.
rewards
??
[]),
),
],
),
);
}
List
<
Widget
>
_buildRewardWidgets
(
List
<
RewardInviteItemModel
>?
rewards
)
{
if
(
rewards
==
null
||
rewards
.
isEmpty
)
{
return
[];
}
return
rewards
.
map
((
e
)
=>
_rewardItem
(
e
)).
toList
();
}
Widget
_rewardItem
(
RewardInviteItemModel
item
)
{
return
Row
(
children:
[
loadNetworkImage
(
url:
item
.
icon
,
width:
24
,
height:
24
,
placeholderAsset:
"assets/images/ic_point.png"
),
const
SizedBox
(
width:
4
),
Text
(
item
.
quantity
.
toString
(),
style:
const
TextStyle
(
fontWeight:
FontWeight
.
bold
)),
],
);
}
Widget
_buildInviteCardBox
()
{
return
Obx
(()
{
return
Container
(
margin:
const
EdgeInsets
.
all
(
16
),
padding:
const
EdgeInsets
.
all
(
16
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
12
)),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
const
Text
(
'Gửi mã giới thiệu cho bạn bè'
,
style:
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
w800
)),
const
SizedBox
(
height:
12
),
const
Text
(
'Mã giới thiệu'
,
style:
TextStyle
(
fontSize:
14
,
color:
Colors
.
black87
,
fontWeight:
FontWeight
.
w600
),
),
const
SizedBox
(
height:
4
),
Container
(
height:
48
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
12
,
vertical:
12
),
decoration:
BoxDecoration
(
color:
Colors
.
grey
.
shade200
,
borderRadius:
BorderRadius
.
circular
(
8
)),
child:
Row
(
children:
[
Expanded
(
child:
Text
(
viewModel
.
inviteFriendDetail
.
value
?.
inviteCodeDefault
??
''
,
style:
const
TextStyle
(
fontWeight:
FontWeight
.
bold
,
color:
BaseColor
.
primary500
,
fontSize:
16
),
),
),
GestureDetector
(
onTap:
()
{
Clipboard
.
setData
(
ClipboardData
(
text:
viewModel
.
inviteFriendDetail
.
value
?.
inviteCodeDefault
??
''
),
);
ScaffoldMessenger
.
of
(
context
,
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
'Đã sao chép'
),
duration:
Duration
(
seconds:
1
)));
},
child:
SizedBox
(
width:
40
,
height:
double
.
infinity
,
child:
Center
(
child:
Icon
(
Icons
.
copy
,
color:
BaseColor
.
primary500
,
size:
20
)),
),
),
],
),
),
const
SizedBox
(
height:
12
),
const
Text
(
'Hoặc chia sẻ qua'
,
style:
TextStyle
(
fontSize:
14
,
color:
Colors
.
black87
,
fontWeight:
FontWeight
.
w600
),
),
const
SizedBox
(
height:
4
),
Row
(
children:
[
Expanded
(
child:
ElevatedButton
.
icon
(
onPressed:
()
{
showPopupInviteFriendCode
(
context
,
viewModel
.
inviteFriendDetail
.
value
?.
inviteCodeDefault
??
''
);
},
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
BaseColor
.
primary500
,
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
8
)),
),
icon:
const
Icon
(
Icons
.
qr_code
,
color:
Colors
.
white
),
label:
const
Text
(
'QR code'
,
style:
TextStyle
(
color:
Colors
.
white
)),
),
),
const
SizedBox
(
width:
12
),
Expanded
(
child:
OutlinedButton
.
icon
(
onPressed:
()
{
final
content
=
viewModel
.
inviteFriendDetail
.
value
?.
shareContent
??
''
;
if
(
content
.
isEmpty
)
return
;
Share
.
share
(
content
);
},
style:
OutlinedButton
.
styleFrom
(
side:
const
BorderSide
(
color:
BaseColor
.
primary500
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
8
)),
),
icon:
const
Icon
(
Icons
.
share
,
color:
BaseColor
.
primary500
),
label:
const
Text
(
'Chia sẻ'
,
style:
TextStyle
(
color:
BaseColor
.
primary500
)),
),
),
],
),
],
),
);
});
}
Widget
_buildContactCardBox
()
{
const
maxItems
=
7
;
final
displayedContacts
=
contacts
.
take
(
maxItems
).
toList
();
return
Container
(
margin:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
12
)),
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
12
,
vertical:
8
),
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
const
Text
(
'Danh bạ'
,
style:
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
w800
)),
GestureDetector
(
onTap:
()
{
Get
.
toNamed
(
contactsListScreen
,
arguments:
{
'contacts'
:
contacts
});
},
child:
Text
(
'Xem tất cả'
,
style:
TextStyle
(
color:
BaseColor
.
primary500
,
fontSize:
14
,
fontWeight:
FontWeight
.
w700
),
),
),
],
),
),
...
List
.
generate
(
displayedContacts
.
length
,
(
index
)
{
final
contact
=
displayedContacts
[
index
];
final
name
=
contact
.
displayName
;
final
phone
=
contact
.
phones
?.
isNotEmpty
==
true
?
contact
.
phones
?.
first
.
number
??
'Không số'
:
'Không số'
;
return
Column
(
children:
[
if
(
index
!=
0
)
const
Divider
(
height:
1
,
color:
Colors
.
black12
,
indent:
16
,
endIndent:
16
),
ListTile
(
leading:
const
CircleAvatar
(
backgroundImage:
AssetImage
(
'assets/images/ic_pipi_02.png'
),
backgroundColor:
BaseColor
.
primary200
,
),
title:
Text
(
name
,
style:
const
TextStyle
(
fontWeight:
FontWeight
.
bold
)),
subtitle:
Text
(
phone
),
trailing:
OutlinedButton
(
style:
OutlinedButton
.
styleFrom
(
side:
const
BorderSide
(
color:
BaseColor
.
primary500
,
width:
1.5
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
12
)),
),
onPressed:
()
{
viewModel
.
phoneInviteFriend
(
phone
);
},
child:
const
Text
(
'Mời'
,
style:
TextStyle
(
color:
BaseColor
.
primary500
,
fontWeight:
FontWeight
.
w800
),
),
),
),
],
);
}),
],
),
);
}
Widget
_buildCampaignBox
()
{
final
detail
=
viewModel
.
campaignDetail
.
value
;
final
campaigns
=
detail
?.
campaigns
??
[];
return
campaigns
.
isEmpty
?
SizedBox
()
:
Container
(
margin:
const
EdgeInsets
.
all
(
16
),
padding:
const
EdgeInsets
.
all
(
12
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
12
)),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
if
((
detail
?.
title
??
''
).
isNotEmpty
)
Text
(
detail
?.
title
??
''
,
maxLines:
1
,
style:
const
TextStyle
(
fontSize:
18
,
fontWeight:
FontWeight
.
w800
),
),
const
SizedBox
(
height:
12
),
ListView
.
separated
(
shrinkWrap:
true
,
physics:
const
NeverScrollableScrollPhysics
(),
itemCount:
campaigns
.
length
,
separatorBuilder:
(
_
,
__
)
=>
const
SizedBox
(
height:
8
),
itemBuilder:
(
context
,
index
)
{
final
campaign
=
campaigns
[
index
];
return
Container
(
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
8
),
border:
Border
.
all
(
color:
Colors
.
grey
.
shade300
),
),
padding:
const
EdgeInsets
.
all
(
8
),
child:
Row
(
children:
[
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
8
),
child:
loadNetworkImage
(
url:
campaign
.
avatarUrl
,
width:
82
,
height:
82
,
placeholderAsset:
"assets/images/bg_default_11.png"
,
),
),
const
SizedBox
(
width:
12
),
Expanded
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
campaign
.
name
??
''
,
maxLines:
2
,
style:
const
TextStyle
(
fontWeight:
FontWeight
.
bold
,
fontSize:
14
),
),
const
SizedBox
(
height:
4
),
Text
(
campaign
.
description
??
''
,
maxLines:
2
,
style:
const
TextStyle
(
fontSize:
12
,
color:
Colors
.
black54
),
),
],
),
),
],
),
);
},
),
],
),
);
}
}
lib/screen/invite_friend_campaign/invite_friend_campaign_viewmodel.dart
0 → 100644
View file @
4c376d38
import
'package:get/get_rx/src/rx_types/rx_types.dart'
;
import
'package:mypoint_flutter_app/configs/constants.dart'
;
import
'package:mypoint_flutter_app/networking/restful_api_request.dart'
;
import
'../../base/restful_api_viewmodel.dart'
;
import
'models/invite_friend_campaign_model.dart'
;
class
InviteFriendCampaignViewModel
extends
RestfulApiViewModel
{
var
inviteFriendDetail
=
Rxn
<
InviteFriendDetailModel
>();
var
campaignDetail
=
Rxn
<
CampaignInviteFriendDetail
>();
void
Function
(
String
message
)?
onShowAlertError
;
void
Function
(
String
,
String
)?
phoneInviteFriendResponse
;
loadData
()
async
{
showLoading
();
await
_getInviteFriendDetail
();
await
_getCampaignInviteFriendDetail
();
hideLoading
();
}
Future
<
void
>
phoneInviteFriend
(
String
phone
)
async
{
showLoading
();
try
{
final
response
=
await
client
.
phoneInviteFriend
(
phone
);
hideLoading
();
final
sms
=
response
.
data
?.
sms
??
''
;
if
(
response
.
isSuccess
&&
sms
.
isNotEmpty
)
{
phoneInviteFriendResponse
?.
call
(
sms
,
phone
);
}
else
{
onShowAlertError
?.
call
(
response
.
errorMessage
??
Constants
.
commonError
);
}
}
catch
(
error
)
{
hideLoading
();
onShowAlertError
?.
call
(
"Error fetching product detail:
$error
"
);
}
}
Future
<
void
>
_getInviteFriendDetail
()
async
{
try
{
final
response
=
await
client
.
getCampaignInviteFriend
();
inviteFriendDetail
.
value
=
response
.
data
;
}
catch
(
error
)
{
onShowAlertError
?.
call
(
"Error fetching product detail:
$error
"
);
}
}
Future
<
void
>
_getCampaignInviteFriendDetail
()
async
{
try
{
// final response = await client.getDetailCampaignInviteFriend();
final
item1
=
CampaignInviteFriendItemModel
(
name:
"Mời bạn đăng ký, cả đôi cù nđôi cùngMời bạn đăng ký, nhận gMời bạn đăng ký, nh đôi cùngMời bạn đăng ký, nhận ận quà"
,
description:
"Chỉ cần giới thiệMời bạn đăng ký, u MyPoint, cả bạn lẫn bạn Mời bạn đăng ký, bè đều sẽ nhận"
,
avatarUrl:
"https://api.sandbox.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/5223A5C210D155B8AB447D32888562CD/1732855098"
,
);
final
item2
=
CampaignInviteFriendItemModel
(
name:
"Mời bạn đăng ký, cMời bạn đăng ký,ả đôi cùng nhậMời bạn đăng ký,n quà"
,
description:
"Chỉ cần giới thiệu MyPoint, cảMời bạn đăng ký, bạn lẫn bạn bè Mời bạn đăng ký,đều sẽ nhận"
,
avatarUrl:
"https://api.sandbox.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/5223A5C210D155B8AB447D32888562CD/1732855098"
,
);
final
item3
=
CampaignInviteFriendItemModel
(
name:
"Mời bạn đăng ký, cả Mời bạn đăng ký, Mời bạn đăng ký, đôi cùng nhận quà"
,
description:
"Chỉ cần giới thiệMời bạn đăng ký, u MyPoint, cả bMời bạn đăng ký, ạn lẫn bạn bè Mời bạn đăng ký,đều sẽMời bạn đăng ký, nhận"
,
avatarUrl:
"https://api.sandbox.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/5223A5C210D155B8AB447D32888562CD/1732855098"
,
);
final
item4
=
CampaignInviteFriendItemModel
(
name:
"Mời bạn đăng ký, cả Mời bạn đăng ký, đôi cùng nhận quà"
,
description:
"Chỉ cần giới thiệu MyPoiMời bạn đăng ký, nt, cả bạn lẫn bạn bè đMời bạn đăng ký, ều sẽ nhận"
,
avatarUrl:
"https://api.sandbox.mypoint.com.vn/8854/gup2start/rest/photoReader/1.0.0/F603E24F2D46C44ADCEE955FF57A53CE/1732854874"
,
);
final
value
=
CampaignInviteFriendDetail
(
title:
"Chương trình khuyến mãi mời bạn bè"
,
campaigns:
[
item1
,
item2
,
item3
,
item4
],
);
campaignDetail
.
value
=
value
;
}
catch
(
error
)
{
onShowAlertError
?.
call
(
"Error fetching product detail:
$error
"
);
}
}
}
lib/screen/invite_friend_campaign/models/invite_friend_campaign_model.dart
0 → 100644
View file @
4c376d38
class
RewardInviteItemModel
{
String
?
type
;
String
?
icon
;
int
?
quantity
;
RewardInviteItemModel
({
this
.
type
,
this
.
icon
,
this
.
quantity
});
factory
RewardInviteItemModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
RewardInviteItemModel
(
type:
json
[
'type'
],
icon:
json
[
'icon'
],
quantity:
json
[
'quantity'
],
);
}
}
class
InviteRewardFriendModel
{
String
?
title
;
List
<
RewardInviteItemModel
>?
rewards
;
InviteRewardFriendModel
({
this
.
title
,
this
.
rewards
});
factory
InviteRewardFriendModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
InviteRewardFriendModel
(
title:
json
[
'title'
],
rewards:
(
json
[
'rewards'
]
as
List
<
dynamic
>?)
?.
map
((
e
)
=>
RewardInviteItemModel
.
fromJson
(
e
))
.
toList
(),
);
}
}
class
InviteFriendDetailModel
{
String
?
id
;
String
?
name
;
String
?
bannerUrl
;
String
?
backgroundColor
;
String
?
inviteLink
;
String
?
inviteCodeDefault
;
String
?
shareContent
;
InviteRewardFriendModel
?
reward
;
InviteFriendDetailModel
({
this
.
id
,
this
.
name
,
this
.
bannerUrl
,
this
.
backgroundColor
,
this
.
inviteLink
,
this
.
inviteCodeDefault
,
this
.
shareContent
,
this
.
reward
,
});
factory
InviteFriendDetailModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
InviteFriendDetailModel
(
id:
json
[
'id'
],
name:
json
[
'name'
],
bannerUrl:
json
[
'banner_url'
],
backgroundColor:
json
[
'background_color'
],
inviteLink:
json
[
'invite_link'
],
inviteCodeDefault:
json
[
'invite_code_default'
],
shareContent:
json
[
'share_content'
],
reward:
json
[
'reward'
]
!=
null
?
InviteRewardFriendModel
.
fromJson
(
json
[
'reward'
])
:
null
,
);
}
}
class
CampaignInviteFriendItemModel
{
String
?
id
;
String
?
name
;
String
?
description
;
String
?
avatarUrl
;
String
?
bannerUrl
;
String
?
inviteDescription
;
RewardInviteItemModel
?
reward
;
String
?
rules
;
String
?
inviteLink
;
int
?
rulesId
;
CampaignInviteFriendItemModel
({
this
.
id
,
this
.
name
,
this
.
description
,
this
.
avatarUrl
,
this
.
bannerUrl
,
this
.
inviteDescription
,
this
.
reward
,
this
.
rules
,
this
.
inviteLink
,
this
.
rulesId
,
});
factory
CampaignInviteFriendItemModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
CampaignInviteFriendItemModel
(
id:
json
[
'id'
],
name:
json
[
'name'
],
description:
json
[
'description'
],
avatarUrl:
json
[
'avatar_url'
],
bannerUrl:
json
[
'banner_url'
],
inviteDescription:
json
[
'invite_description'
],
reward:
json
[
'reward'
]
!=
null
?
RewardInviteItemModel
.
fromJson
(
json
[
'reward'
])
:
null
,
rules:
json
[
'rules'
],
inviteLink:
json
[
'invite_link'
],
rulesId:
json
[
'rules_id'
],
);
}
}
class
CampaignInviteFriendDetail
{
String
?
title
;
List
<
CampaignInviteFriendItemModel
>?
campaigns
;
CampaignInviteFriendDetail
({
this
.
title
,
this
.
campaigns
,
});
factory
CampaignInviteFriendDetail
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
CampaignInviteFriendDetail
(
title:
json
[
'title'
]
as
String
?,
campaigns:
(
json
[
'campaigns'
]
as
List
<
dynamic
>?)
?.
map
((
e
)
=>
CampaignInviteFriendItemModel
.
fromJson
(
e
))
.
toList
(),
);
}
}
class
InviteFriendResponse
{
final
String
?
sms
;
InviteFriendResponse
({
this
.
sms
});
factory
InviteFriendResponse
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
InviteFriendResponse
(
sms:
json
[
'sms'
]
as
String
?,
);
}
Map
<
String
,
dynamic
>
toJson
()
{
return
{
'sms'
:
sms
,
};
}
}
\ No newline at end of file
lib/screen/invite_friend_campaign/popup_invite_friend_code.dart
0 → 100644
View file @
4c376d38
import
'package:flutter/material.dart'
;
import
'package:qr_flutter/qr_flutter.dart'
;
void
showPopupInviteFriendCode
(
BuildContext
context
,
String
qrString
)
{
final
width
=
MediaQuery
.
of
(
context
).
size
.
width
;
showModalBottomSheet
(
context:
context
,
isScrollControlled:
true
,
backgroundColor:
Colors
.
white
,
shape:
const
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
vertical
(
top:
Radius
.
circular
(
16
))),
builder:
(
_
)
{
return
SafeArea
(
child:
SingleChildScrollView
(
child:
Padding
(
padding:
EdgeInsets
.
only
(
bottom:
MediaQuery
.
of
(
context
).
viewInsets
.
bottom
,
),
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
16
),
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
end
,
children:
[
const
Expanded
(
child:
Center
(
child:
Text
(
'Rủ bạn nhập hội, nhận quà cả đôi!'
,
style:
TextStyle
(
fontWeight:
FontWeight
.
bold
,
fontSize:
18
),
),
),
),
GestureDetector
(
onTap:
()
=>
Navigator
.
of
(
context
).
pop
(),
child:
const
Icon
(
Icons
.
close
,
size:
20
),
),
],
),
),
const
Divider
(
height:
1
),
Container
(
width:
double
.
infinity
,
margin:
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
padding:
const
EdgeInsets
.
all
(
16
),
decoration:
BoxDecoration
(
color:
Colors
.
red
.
shade50
,
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
Image
.
asset
(
'assets/images/ic_pipi_couple.png'
,
height:
72
),
const
SizedBox
(
height:
16
),
Container
(
color:
Colors
.
white
,
child:
QrImageView
(
data:
qrString
,
version:
QrVersions
.
auto
,
size:
width
/
1.7
,
embeddedImage:
const
AssetImage
(
'assets/images/ic_logo.png'
),
embeddedImageStyle:
const
QrEmbeddedImageStyle
(
size:
Size
(
40
,
40
)),
),
),
const
SizedBox
(
height:
8
),
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
const
[
Icon
(
Icons
.
download_outlined
,
color:
Colors
.
black54
),
SizedBox
(
width:
4
),
Text
(
'Lưu ảnh'
),
],
),
const
SizedBox
(
height:
8
),
const
Text
(
'Mã giới thiệu:'
),
Text
(
qrString
,
style:
const
TextStyle
(
fontWeight:
FontWeight
.
bold
,
color:
Colors
.
red
,
fontSize:
18
),
),
],
),
),
],
),
),
),
);
},
);
}
lib/screen/login/login_screen.dart
View file @
4c376d38
// login_screen.dart
import
'dart:async'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:get/get.dart'
;
import
'../../base/base_screen.dart'
;
import
'../../base/base_screen.dart'
;
...
...
Prev
1
2
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