flutter/lib/pages/my/user_info.dart

500 lines
19 KiB
Dart
Raw Normal View History

2025-08-21 10:50:38 +08:00
import 'package:bottom_picker/bottom_picker.dart';
import 'package:city_pickers/city_pickers.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/controller/im_user_info_controller.dart';
import 'package:loopin/api/common_api.dart';
2025-09-03 11:25:31 +08:00
import 'package:loopin/components/network_or_asset_image.dart';
2025-08-21 10:50:38 +08:00
import 'package:loopin/service/http.dart';
import 'package:loopin/styles/index.dart';
import 'package:loopin/utils/index.dart';
import 'package:loopin/utils/wxsdk.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
class UserInfo extends StatefulWidget {
const UserInfo({super.key});
@override
State<UserInfo> createState() => _UserInfoState();
}
class _UserInfoState extends State<UserInfo> {
final userInfoController = Get.find<ImUserInfoController>();
late List<Map<String, dynamic>> items;
@override
void initState() {
super.initState();
items = [
{
'title': '昵称',
'value': userInfoController.nickname,
'onTap': () => Get.toNamed('/nickName'),
},
{
'title': '简介',
'value': userInfoController.signature,
'onTap': () => Get.toNamed('/des'),
},
{
'title': '性别',
'value': userInfoController.gender,
'onTap': () => setGender(context),
},
{
'title': '生日',
'value': userInfoController.birthday,
'onTap': () => selectDate(context),
},
{
'title': '区域',
'value': userInfoController.customInfo,
'onTap': () => pickCity(),
},
{
'title': '绑定微信',
'value': userInfoController.customInfo,
'onTap': () => wechatLogin(),
},
];
}
/// 微信授权
Future<void> wechatLogin() async {
await Wxsdk.login();
}
/// 选性别
void setGender(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (BuildContext context) {
return SafeArea(
child: Container(
height: 300,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
child: Column(
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ListTile(
title: Center(child: Text('')),
onTap: () {
userInfoController.gender.value = 1;
userInfoController.updateGender();
Get.back();
},
),
Divider(),
ListTile(
title: Center(child: Text('')),
onTap: () {
userInfoController.gender.value = 2;
userInfoController.updateGender();
Get.back();
}),
Divider(),
ListTile(
title: Center(child: Text('保密')),
onTap: () {
userInfoController.gender.value = 0;
userInfoController.updateGender();
Get.back();
},
),
Container(
height: 10,
color: Colors.grey[200],
),
],
),
),
ListTile(
title: Center(child: Text('取消')),
onTap: () => Get.back(),
),
],
),
),
);
},
);
}
/// 选生日
void selectDate(BuildContext context) {
DateTime selectedDate = DateTime.now(); // 初始值设为当前时间
BottomPicker.date(
pickerTitle: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
onPressed: () {
Get.back();
},
child: Text('取消'),
),
TextButton(
onPressed: () {
// 获取选择结果逻辑
final dateStr = "${selectedDate.year}-${selectedDate.month.toString().padLeft(2, '0')}-${selectedDate.day.toString().padLeft(2, '0')}";
print(dateStr);
DateTime birthdayDate = DateTime(selectedDate.year, selectedDate.month, selectedDate.day);
userInfoController.birthday.value = birthdayDate.millisecondsSinceEpoch ~/ 1000; //秒级时间戳
userInfoController.updateBirthday();
Get.back();
},
child: Text('确认'),
),
],
),
displaySubmitButton: false,
displayCloseIcon: false,
dismissable: true, // 允许点击空白关闭
initialDateTime: DateTime.now(),
minDateTime: DateTime(1900),
maxDateTime: DateTime(
DateTime.now().year,
DateTime.now().month,
DateTime.now().day,
23,
59,
59,
),
dateOrder: DatePickerDateOrder.ymd,
pickerTextStyle: const TextStyle(fontSize: 16, color: Colors.black87),
height: 300,
onChange: (date) {
selectedDate = date as DateTime;
},
).show(context);
}
///选所在地
void pickCity() async {
final result = await CityPickers.showCityPicker(
context: context,
showType: ShowType.pca, // 显示省市区
height: 300.0,
borderRadius: 16.0,
barrierDismissible: true,
theme: Theme.of(context).copyWith(
scaffoldBackgroundColor: Colors.white,
),
);
if (result != null) {
final areaName = '${result.provinceName}-${result.cityName}-${result.areaName}';
print(result.toString());
print('${result.provinceName}-${result.cityName}-${result.areaName}-${result.areaId}');
//修改
userInfoController.customInfo['area'] = areaName;
userInfoController.customInfo['areaCode'] = '${result.areaId}';
userInfoController.updateArea();
userInfoController.customInfo.refresh();
}
}
///选背景
void pickCover(BuildContext context) async {
final pickedAssets = await AssetPicker.pickAssets(
context,
pickerConfig: AssetPickerConfig(
textDelegate: const AssetPickerTextDelegate(),
pathNameBuilder: (AssetPathEntity album) {
return Utils.translateAlbumName(album);
},
maxAssets: 1,
requestType: RequestType.image,
filterOptions: FilterOptionGroup(
imageOption: const FilterOption(),
),
),
);
if (pickedAssets != null && pickedAssets.isNotEmpty) {
final asset = pickedAssets.first;
final file = await asset.file; // 获取实际文件
if (file != null) {
final fileSizeInBytes = await file.length();
final sizeInMB = fileSizeInBytes / (1024 * 1024);
if (sizeInMB > 100) {
MyDialog.toast('图片大小不能超过100MB', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
} else {
print("图片合法,大小:$sizeInMB MB");
//走upload(file)上传图片拿到url地址
final istance = MyDialog.loading('上传中');
final res = await Http.upload(CommonApi.uploadFile, filePath: file.path);
userInfoController.customInfo['coverBg'] = res['data']['url'];
userInfoController.updateCover();
userInfoController.customInfo.refresh();
istance.close();
}
}
}
}
///选头像
void pickFaceUrl(BuildContext context) async {
final pickedAssets = await AssetPicker.pickAssets(
context,
pickerConfig: AssetPickerConfig(
textDelegate: const AssetPickerTextDelegate(),
pathNameBuilder: (AssetPathEntity album) {
return Utils.translateAlbumName(album);
},
maxAssets: 1,
requestType: RequestType.image,
filterOptions: FilterOptionGroup(
imageOption: const FilterOption(),
),
),
);
if (pickedAssets != null && pickedAssets.isNotEmpty) {
final asset = pickedAssets.first;
final file = await asset.file; // 获取实际文件
if (file != null) {
final fileSizeInBytes = await file.length();
final sizeInMB = fileSizeInBytes / (1024 * 1024);
if (sizeInMB > 100) {
MyDialog.toast('图片大小不能超过100MB', icon: const Icon(Icons.check_circle), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
} else {
print("图片合法,大小:$sizeInMB MB");
//走upload(file)上传图片拿到url地址
final istance = MyDialog.loading('上传中');
final res = await Http.upload(CommonApi.uploadFile, filePath: file.path);
userInfoController.faceUrl.value = res['data']['url'];
userInfoController.updateFaceUrl();
userInfoController.customInfo.refresh();
istance.close();
}
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
actions: [
Padding(
padding: const EdgeInsets.only(right: 12.0),
child: GestureDetector(
onTap: () => pickCover(context),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0),
decoration: BoxDecoration(
color: Colors.black.withAlpha(100),
borderRadius: BorderRadius.circular(20.0),
),
child: Row(
children: const [
Icon(Icons.camera_alt, color: Colors.white, size: 16),
SizedBox(width: 4),
Text('更换封面', style: TextStyle(color: Colors.white, fontSize: 14)),
],
),
),
),
),
],
),
body: SizedBox.expand(
child: Stack(
children: [
// 封面图
SizedBox(
height: 240,
width: double.infinity,
child: Obx(() {
final imageUrl = userInfoController.customInfo['coverBg'];
return GestureDetector(
onTap: () => pickCover(context),
2025-09-03 11:25:31 +08:00
// child: Image(
// image: (imageUrl != null && imageUrl.isNotEmpty) ? NetworkImage(imageUrl) : const AssetImage('assets/images/pic2.jpg') as ImageProvider,
// fit: BoxFit.cover,
// ),
child: NetworkOrAssetImage(
imageUrl: imageUrl,
placeholderAsset: 'assets/images/bk.jpg',
2025-08-21 10:50:38 +08:00
),
);
}),
),
// 白色内容容器
Positioned(
top: 220,
left: 0,
right: 0,
bottom: 0,
child: ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
),
child: SingleChildScrollView(
padding: const EdgeInsets.only(top: 60, bottom: 40),
child: Column(
children: [
const SizedBox(height: 0),
const SizedBox(height: 20),
Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
color: Colors.white,
child: Column(
children: items.map((item) {
return Column(
children: [
ListTile(
title: Row(
children: [
Text(
item['title'],
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(width: 50),
Expanded(child: Obx(() {
final val = item['value'];
if (val is RxString) {
return Text(
val.value,
style: const TextStyle(color: Colors.black),
overflow: TextOverflow.ellipsis,
maxLines: 1,
);
} else if (val is RxInt) {
String displayText;
if (item['title'] == '性别') {
displayText = val.value == 0
? '保密'
: val.value == 1
? ''
: val.value == 2
? ''
: '';
} else {
// 生日
// displayText = val.value == 0 ? '' : val.value.toString();
if (val.value == 0) {
displayText = '';
} else {
final date = DateTime.fromMillisecondsSinceEpoch(val.value * 1000);
displayText = "${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}";
}
}
return Text(
displayText,
style: const TextStyle(color: Colors.black),
overflow: TextOverflow.ellipsis,
maxLines: 1,
);
} else if (val is String) {
return Text(
val,
style: const TextStyle(color: Colors.black),
overflow: TextOverflow.ellipsis,
maxLines: 1,
);
} else if (val is Map) {
if (item['title'] == '区域') {
return Text(
val['area'] ?? '',
style: const TextStyle(color: Colors.black),
overflow: TextOverflow.ellipsis,
maxLines: 1,
);
} else {
2025-09-03 11:25:31 +08:00
String wxText = val['openId'] == null || val['openId'] == '' ? '未授权' : '已授权';
2025-08-21 10:50:38 +08:00
return Row(
children: [
Spacer(),
Text(
wxText,
style: const TextStyle(color: Colors.black),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
],
);
}
} else {
return const SizedBox.shrink();
}
}))
],
),
trailing: const Icon(
Icons.chevron_right,
color: FStyle.c999,
),
onTap: item['onTap'],
),
],
);
}).toList(),
),
)
],
),
),
),
),
),
// 头像层级最高放在Stack最后面覆盖白色容器和封面图
Positioned(
top: 220 - 60, // 封面图高度减去头像半径,使头像中线对齐封面底线
left: 0,
right: 0,
child: Obx(() {
final avatar = userInfoController.faceUrl.value;
return Center(
child: GestureDetector(
onTap: () => pickFaceUrl(context),
child: CircleAvatar(
radius: 60,
backgroundColor: Colors.white,
2025-09-03 11:25:31 +08:00
child: ClipOval(
child: NetworkOrAssetImage(
imageUrl: avatar,
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover,
),
2025-08-21 10:50:38 +08:00
),
),
),
);
}),
),
],
),
),
);
}
}