Commit 4c376d38 authored by DatHV's avatar DatHV
Browse files

cập nhật ui. logic

parent fa01087d
......@@ -42,4 +42,5 @@
<data android:mimeType="text/plain"/>
</intent>
</queries>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
</manifest>
......@@ -74,4 +74,8 @@ class APIPaths {
static const String historyCashBackPoint = "/cashback/orders";
static const String historyCashBackPointDetail = "/cashback/order/details";
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
......@@ -128,6 +128,12 @@ class DirectionalScreen {
case DirectionalScreenName.refundHistory:
Get.toNamed(historyPointCashBackScreen);
return true;
case DirectionalScreenName.inviteFriend:
Get.toNamed(inviteFriendCampaignScreen);
return true;
case DirectionalScreenName.dailyCheckin:
Get.toNamed(dailyCheckInScreen);
return true;
default:
print("Không nhận diện được action type: $clickActionType");
return false;
......
......@@ -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/cashback_overview_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/faqs/faqs_model.dart';
import '../screen/game/models/game_bundle_item_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/notification_unread_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/province_address_model.dart';
import '../screen/membership/models/membership_info_response.dart';
......@@ -678,4 +680,46 @@ extension RestfullAPIClientAllApi on RestfulAPIClient {
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
......@@ -19,11 +19,13 @@ void showAffiliateBrandPopup(BuildContext context, List<AffiliateBrandModel> bra
const SizedBox(height: 8),
Row(
children: [
const SizedBox(width: 16),
IconButton(icon: const Icon(Icons.close, color: Colors.transparent,), onPressed: () => {}),
Expanded(
child: Center(
child: Text(
title ?? "Thương hiệu",
textAlign: TextAlign.center,
maxLines: 1,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black),
),
),
......
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)),
),
);
}
}
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
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]],
],
),
),
);
}
}
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
......@@ -356,7 +356,7 @@ class _DataNetworkServiceScreenState extends BaseState<DataNetworkServiceScreen>
}
} catch (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ạ")));
}
}
}
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(),
);
}
}
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),
),
],
),
),
],
),
);
},
),
],
),
);
}
}
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");
}
}
}
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
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),
),
],
),
),
],
),
),
),
);
},
);
}
// login_screen.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../base/base_screen.dart';
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment