500 lines
19 KiB
Dart
500 lines
19 KiB
Dart
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';
|
||
import 'package:loopin/components/network_or_asset_image.dart';
|
||
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),
|
||
// 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',
|
||
),
|
||
);
|
||
}),
|
||
),
|
||
|
||
// 白色内容容器
|
||
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 {
|
||
String wxText = val['openId'] == null || val['openId'] == '' ? '未授权' : '已授权';
|
||
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,
|
||
child: ClipOval(
|
||
child: NetworkOrAssetImage(
|
||
imageUrl: avatar,
|
||
width: double.infinity,
|
||
height: double.infinity,
|
||
fit: BoxFit.cover,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|