19 changed files with 1362 additions and 337 deletions
Split View
Diff Options
-
16lib/controller/home/report_controller.dart
-
23lib/controller/home/user_information_controller.dart
-
2lib/controller/message/chat_settings_controller.dart
-
247lib/controller/mine/real_feedback_controller.dart
-
85lib/controller/setting/blacklist_controller.dart
-
72lib/model/mine/blacklist_data.dart
-
17lib/network/api_urls.dart
-
5lib/network/home_api.dart
-
34lib/network/home_api.g.dart
-
22lib/network/user_api.dart
-
139lib/network/user_api.g.dart
-
6lib/pages/home/report_page.dart
-
2lib/pages/home/timeline_info.dart
-
2lib/pages/home/timeline_item.dart
-
3lib/pages/home/user_information_page.dart
-
339lib/pages/mine/feedback_page.dart
-
4lib/pages/mine/mine_page.dart
-
460lib/pages/mine/real_feedback_page.dart
-
221lib/pages/setting/blacklist_page.dart
@ -0,0 +1,247 @@ |
|||
import 'dart:io'; |
|||
|
|||
import 'package:dating_touchme_app/network/user_api.dart'; |
|||
import 'package:dating_touchme_app/oss/oss_manager.dart'; |
|||
import 'package:flustars/flustars.dart'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; |
|||
import 'package:get/get.dart'; |
|||
import 'package:image_picker/image_picker.dart'; |
|||
import 'package:permission_handler/permission_handler.dart'; |
|||
|
|||
class RealFeedbackController extends GetxController { |
|||
final int type; |
|||
RealFeedbackController({required this.type}); |
|||
|
|||
|
|||
late UserApi _userApi; |
|||
final message = ''.obs; |
|||
final messageController = TextEditingController().obs; |
|||
|
|||
final tagList = <Map>[].obs; |
|||
|
|||
final active = 0.obs; |
|||
|
|||
final imgList = <String>[].obs; |
|||
|
|||
changeActive(int i){ |
|||
active.value = i; |
|||
} |
|||
|
|||
@override |
|||
void onInit() { |
|||
super.onInit(); |
|||
_userApi = Get.find<UserApi>(); |
|||
if(type == 1){ |
|||
tagList.value = [ |
|||
{"label": "账号异常", "value": 101}, |
|||
{"label": "违规与封禁", "value": 102}, |
|||
{"label": "头像/签名/实名认证", "value": 103}, |
|||
]; |
|||
} else if(type == 2){ |
|||
tagList.value = [ |
|||
{"label": "私信相关", "value": 201}, |
|||
{"label": "资料相关", "value": 202}, |
|||
{"label": "动态相关", "value": 203}, |
|||
{"label": "直播间相关", "value": 204}, |
|||
]; |
|||
} else if(type == 3){ |
|||
tagList.value = [ |
|||
{"label": "举报有人诈骗", "value": 301}, |
|||
{"label": "举报有人涉嫌诱导", "value": 302}, |
|||
{"label": "其他不良行为举报", "value": 303}, |
|||
{"label": "虚假宣传广告投诉", "value": 304}, |
|||
{"label": "未成年问题", "value": 305}, |
|||
{"label": "智能推荐相关", "value": 306}, |
|||
]; |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
// 选择头像 - 业务逻辑处理 |
|||
Future<void> handleCameraCapture() async { |
|||
try { |
|||
// 请求相机权限 |
|||
final ok = await _ensurePermission( |
|||
Permission.camera, |
|||
denyToast: '相机权限被拒绝,请在设置中允许访问相机', |
|||
); |
|||
if (!ok) return; |
|||
|
|||
// 请求麦克风权限(部分设备拍照/录像会一并请求建议预授权) |
|||
await _ensurePermission(Permission.microphone, denyToast: '麦克风权限被拒绝'); |
|||
|
|||
// 权限通过后拍照 |
|||
final ImagePicker picker = ImagePicker(); |
|||
final XFile? photo = await picker.pickImage(source: ImageSource.camera); |
|||
|
|||
if (photo != null) { |
|||
await processSelectedImage(File(photo.path)); |
|||
} |
|||
} catch (e) { |
|||
print('拍照失败: $e'); |
|||
// 更友好的错误提示 |
|||
if (e.toString().contains('permission') || e.toString().contains('权限')) { |
|||
SmartDialog.showToast('相机权限被拒绝,请在设置中允许访问相机'); |
|||
} else if (e.toString().contains('camera') || |
|||
e.toString().contains('相机')) { |
|||
SmartDialog.showToast('设备没有可用的相机'); |
|||
} else { |
|||
SmartDialog.showToast('拍照失败,请重试'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
Future<void> handleGallerySelection() async { |
|||
try { |
|||
// 请求相册/照片权限 |
|||
// final ok = await _ensurePermission( |
|||
// Permission.photos, |
|||
// // Android 上 photos 等价于 storage/mediaLibrary,permission_handler 会映射 |
|||
// denyToast: '相册权限被拒绝,请在设置中允许访问相册', |
|||
|
|||
// ); |
|||
// if (!ok) return; |
|||
|
|||
// 从相册选择图片 |
|||
final ImagePicker picker = ImagePicker(); |
|||
final XFile? image = await picker.pickImage(source: ImageSource.gallery); |
|||
|
|||
if (image != null) { |
|||
await processSelectedImage(File(image.path)); |
|||
} |
|||
} catch (e) { |
|||
print('选择图片失败: $e'); |
|||
// 更友好的错误提示 |
|||
if (e.toString().contains('permission') || e.toString().contains('权限')) { |
|||
SmartDialog.showToast('相册权限被拒绝,请在设置中允许访问相册'); |
|||
} else { |
|||
SmartDialog.showToast('选择图片失败,请重试'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
Future<void> handleMultiGallerySelection() async { |
|||
try { |
|||
// 请求相册/照片权限 |
|||
// final ok = await _ensurePermission( |
|||
// Permission.photos, |
|||
// // Android 上 photos 等价于 storage/mediaLibrary,permission_handler 会映射 |
|||
// denyToast: '相册权限被拒绝,请在设置中允许访问相册', |
|||
|
|||
// ); |
|||
// if (!ok) return; |
|||
|
|||
// 从相册选择图片 |
|||
final ImagePicker picker = ImagePicker(); |
|||
final List<XFile>? image = await picker.pickMultiImage(limit: 9 - imgList.length); |
|||
|
|||
if (image != null) { |
|||
final futures = image.map((e){ |
|||
return processSelectedMoreImage(File(e.path)); |
|||
}); |
|||
final list = await Future.wait(futures); |
|||
imgList.addAll(list); |
|||
print(imgList); |
|||
SmartDialog.dismiss(); |
|||
SmartDialog.showToast('上传相册成功'); |
|||
|
|||
|
|||
} |
|||
} catch (e) { |
|||
print('选择图片失败: $e'); |
|||
// 更友好的错误提示 |
|||
if (e.toString().contains('permission') || e.toString().contains('权限')) { |
|||
SmartDialog.showToast('相册权限被拒绝,请在设置中允许访问相册'); |
|||
} else { |
|||
SmartDialog.showToast('选择图片失败,请重试'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 通用权限申请 |
|||
Future<bool> _ensurePermission(Permission permission, {String? denyToast}) async { |
|||
var status = await permission.status; |
|||
if (status.isGranted) return true; |
|||
|
|||
if (status.isDenied || status.isRestricted || status.isLimited) { |
|||
status = await permission.request(); |
|||
if (status.isGranted) return true; |
|||
if (denyToast != null) SmartDialog.showToast(denyToast); |
|||
return false; |
|||
} |
|||
|
|||
if (status.isPermanentlyDenied) { |
|||
if (denyToast != null) SmartDialog.showToast('$denyToast,前往系统设置开启'); |
|||
// 延迟弹设置,避免与弹窗冲突 |
|||
Future.delayed(const Duration(milliseconds: 300), openAppSettings); |
|||
return false; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
|
|||
// 处理选中的图片 |
|||
Future<void> processSelectedImage(File imageFile) async { |
|||
try { |
|||
// 显示加载提示 |
|||
SmartDialog.showLoading(msg: '上传相册中...'); |
|||
String objectName = '${DateUtil.getNowDateMs()}.${imageFile.path.split('.').last}'; |
|||
String imageUrl = await OSSManager.instance.uploadFile(imageFile.readAsBytesSync(), objectName); |
|||
print('上传成功,图片URL: $imageUrl'); |
|||
imgList.add(imageUrl); |
|||
SmartDialog.dismiss(); |
|||
SmartDialog.showToast('相册上传成功'); |
|||
|
|||
|
|||
} catch (e) { |
|||
SmartDialog.dismiss(); |
|||
print('处理图片失败: $e'); |
|||
SmartDialog.showToast('上传相册失败,请重试'); |
|||
} |
|||
} |
|||
// 处理选中的图片 |
|||
Future<String> processSelectedMoreImage(File imageFile) async { |
|||
try { |
|||
// 显示加载提示 |
|||
SmartDialog.showLoading(msg: '上传相册中...'); |
|||
String objectName = '${DateUtil.getNowDateMs()}.${imageFile.path.split('.').last}'; |
|||
String imageUrl = await OSSManager.instance.uploadFile(imageFile.readAsBytesSync(), objectName); |
|||
print('上传成功,图片URL: $imageUrl'); |
|||
return imageUrl; |
|||
} catch (e) { |
|||
SmartDialog.dismiss(); |
|||
print('处理图片失败: $e'); |
|||
SmartDialog.showToast('上传相册失败,请重试'); |
|||
return ""; |
|||
} |
|||
} |
|||
|
|||
|
|||
sendFeedback() async { |
|||
try { |
|||
final response = await _userApi.userCreateUserFeedback({ |
|||
"images": imgList.isNotEmpty ? imgList.join(",") : "", |
|||
"content": message.value, |
|||
"feedbackType": active.value, |
|||
}); |
|||
if (response.data.isSuccess) { |
|||
|
|||
SmartDialog.showToast('反馈已提交成功'); |
|||
Get.until((route) => route.isFirst); |
|||
} else { |
|||
|
|||
// 响应失败,抛出异常 |
|||
throw Exception(response.data.message ?? '获取数据失败'); |
|||
} |
|||
} catch(e){ |
|||
print('反馈提交失败: $e'); |
|||
SmartDialog.showToast('反馈提交失败'); |
|||
rethrow; |
|||
|
|||
} finally { |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,85 @@ |
|||
import 'package:dating_touchme_app/network/user_api.dart'; |
|||
import 'package:easy_refresh/easy_refresh.dart'; |
|||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; |
|||
import 'package:get/get.dart'; |
|||
import 'package:dating_touchme_app/model/mine/blacklist_data.dart'; |
|||
|
|||
class BlacklistController extends GetxController { |
|||
|
|||
late UserApi _userApi; |
|||
|
|||
final page = 1.obs; |
|||
final size = 10.obs; |
|||
|
|||
late final EasyRefreshController listRefreshController; |
|||
|
|||
final blackList = <Records>[].obs; |
|||
|
|||
|
|||
@override |
|||
void onInit() { |
|||
super.onInit(); |
|||
|
|||
listRefreshController = EasyRefreshController( |
|||
controlFinishRefresh: true, |
|||
controlFinishLoad: true, |
|||
); |
|||
_userApi = Get.find<UserApi>(); |
|||
getBlackList(); |
|||
} |
|||
|
|||
|
|||
|
|||
getBlackList() async { |
|||
try{ |
|||
final response = await _userApi.userPageUserBlacklist( |
|||
pageNum: page.value, |
|||
pageSize: size.value, |
|||
); |
|||
if (response.data.isSuccess && response.data.data != null) { |
|||
final data = response.data.data?.records ?? []; |
|||
|
|||
blackList.addAll(data.toList()); |
|||
if((data.length ?? 0) == size.value){ |
|||
|
|||
listRefreshController.finishLoad(IndicatorResult.success); |
|||
} else { |
|||
listRefreshController.finishLoad(IndicatorResult.noMore); |
|||
} |
|||
} else { |
|||
|
|||
// 响应失败,抛出异常 |
|||
throw Exception(response.data.message ?? '获取数据失败'); |
|||
} |
|||
} catch(e) { |
|||
print('玫瑰记录获取失败: $e'); |
|||
SmartDialog.showToast('玫瑰记录获取失败'); |
|||
rethrow; |
|||
} |
|||
} |
|||
|
|||
unBlack(String blackUserId) async { |
|||
try { |
|||
final response = await _userApi.userDeleteUserBlacklist({ |
|||
"blackUserId": blackUserId, |
|||
}); |
|||
if (response.data.isSuccess) { |
|||
|
|||
SmartDialog.showToast('已解除拉黑'); |
|||
blackList.value = blackList.where((e) => e.blackUserId != blackUserId).toList(); |
|||
} else { |
|||
|
|||
// 响应失败,抛出异常 |
|||
throw Exception(response.data.message ?? '获取数据失败'); |
|||
} |
|||
} catch(e){ |
|||
print('解除拉黑失败: $e'); |
|||
SmartDialog.showToast('解除拉黑失败'); |
|||
rethrow; |
|||
|
|||
} finally { |
|||
|
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
class BlacklistData { |
|||
List<Records>? records; |
|||
int? total; |
|||
int? size; |
|||
int? current; |
|||
int? pages; |
|||
|
|||
BlacklistData( |
|||
{this.records, this.total, this.size, this.current, this.pages}); |
|||
|
|||
BlacklistData.fromJson(Map<String, dynamic> json) { |
|||
if (json['records'] != null) { |
|||
records = <Records>[]; |
|||
json['records'].forEach((v) { |
|||
records!.add(new Records.fromJson(v)); |
|||
}); |
|||
} |
|||
total = json['total']; |
|||
size = json['size']; |
|||
current = json['current']; |
|||
pages = json['pages']; |
|||
} |
|||
|
|||
Map<String, dynamic> toJson() { |
|||
final Map<String, dynamic> data = new Map<String, dynamic>(); |
|||
if (this.records != null) { |
|||
data['records'] = this.records!.map((v) => v.toJson()).toList(); |
|||
} |
|||
data['total'] = this.total; |
|||
data['size'] = this.size; |
|||
data['current'] = this.current; |
|||
data['pages'] = this.pages; |
|||
return data; |
|||
} |
|||
} |
|||
|
|||
class Records { |
|||
String? id; |
|||
String? blackUserId; |
|||
String? miId; |
|||
String? nickName; |
|||
String? profilePhoto; |
|||
String? createTime; |
|||
|
|||
Records( |
|||
{this.id, |
|||
this.blackUserId, |
|||
this.miId, |
|||
this.nickName, |
|||
this.profilePhoto, |
|||
this.createTime}); |
|||
|
|||
Records.fromJson(Map<String, dynamic> json) { |
|||
id = json['id']; |
|||
blackUserId = json['blackUserId']; |
|||
miId = json['miId']; |
|||
nickName = json['nickName']; |
|||
profilePhoto = json['profilePhoto']; |
|||
createTime = json['createTime']; |
|||
} |
|||
|
|||
Map<String, dynamic> toJson() { |
|||
final Map<String, dynamic> data = new Map<String, dynamic>(); |
|||
data['id'] = this.id; |
|||
data['blackUserId'] = this.blackUserId; |
|||
data['miId'] = this.miId; |
|||
data['nickName'] = this.nickName; |
|||
data['profilePhoto'] = this.profilePhoto; |
|||
data['createTime'] = this.createTime; |
|||
return data; |
|||
} |
|||
} |
|||
@ -0,0 +1,460 @@ |
|||
import 'package:cached_network_image/cached_network_image.dart'; |
|||
import 'package:dating_touchme_app/components/page_appbar.dart'; |
|||
import 'package:dating_touchme_app/controller/mine/real_feedback_controller.dart'; |
|||
import 'package:dating_touchme_app/extension/ex_widget.dart'; |
|||
import 'package:dating_touchme_app/generated/assets.dart'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
|||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; |
|||
import 'package:get/get.dart'; |
|||
import 'package:tdesign_flutter/tdesign_flutter.dart'; |
|||
|
|||
|
|||
class RealFeedbackPage extends StatelessWidget { |
|||
final int type; |
|||
const RealFeedbackPage({super.key, required this.type}); |
|||
|
|||
|
|||
|
|||
|
|||
void _showAvatarPopup(RealFeedbackController controller){ |
|||
Navigator.of(Get.context!).push( |
|||
TDSlidePopupRoute( |
|||
slideTransitionFrom: SlideTransitionFrom.bottom, |
|||
builder: (context) { |
|||
return Container( |
|||
height: 176, |
|||
decoration: BoxDecoration( |
|||
color: Colors.white, |
|||
borderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(12.0), |
|||
topRight: Radius.circular(12.0), |
|||
), |
|||
), |
|||
child: Column( |
|||
children: [ |
|||
ClipRRect( |
|||
borderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(12.0), |
|||
topRight: Radius.circular(12.0), |
|||
), |
|||
child: TDCell( |
|||
arrow: false, |
|||
titleWidget: Center( |
|||
child: Text('拍照', style: TextStyle(fontSize: 16.w, color: const Color.fromRGBO(51, 51, 51, 1))), |
|||
), |
|||
style: TDCellStyle( |
|||
padding: EdgeInsets.all(TDTheme.of(context).spacer16), |
|||
clickBackgroundColor: TDTheme.of(context).bgColorContainerHover, |
|||
cardBorderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(12.0), |
|||
topRight: Radius.circular(12.0), |
|||
) |
|||
), |
|||
onClick: (cell) async{ |
|||
Navigator.pop(context); |
|||
|
|||
if(9 - controller.imgList.length == 1){ |
|||
|
|||
await controller.handleCameraCapture(); |
|||
} else { |
|||
if(controller.imgList.length >= 9){ |
|||
|
|||
SmartDialog.showToast('超出数量限制,请先删除再尝试上传'); |
|||
return; |
|||
} |
|||
await controller.handleCameraCapture(); |
|||
} |
|||
}, |
|||
), |
|||
), |
|||
const TDDivider(), |
|||
TDCell( |
|||
arrow: false, |
|||
titleWidget: Center( |
|||
child: Text('从相册选择'), |
|||
), |
|||
onClick: (cell) async{ |
|||
Navigator.pop(context); |
|||
if(9 - controller.imgList.length == 1){ |
|||
await controller.handleGallerySelection(); |
|||
} else { |
|||
if(controller.imgList.length >= 9){ |
|||
|
|||
SmartDialog.showToast('超出数量限制,请先删除再尝试上传'); |
|||
return; |
|||
} |
|||
await controller.handleMultiGallerySelection(); |
|||
} |
|||
}, |
|||
), |
|||
Expanded( |
|||
child: Container( |
|||
color: Color(0xFFF3F3F3), |
|||
), |
|||
), |
|||
TDCell( |
|||
arrow: false, |
|||
titleWidget: Center( |
|||
child: Text('取消'), |
|||
), |
|||
onClick: (cell){ |
|||
Navigator.pop(context); |
|||
}, |
|||
), |
|||
], |
|||
), |
|||
); |
|||
}), |
|||
); |
|||
} |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return GetX<RealFeedbackController>( |
|||
init: RealFeedbackController(type: type), |
|||
builder: (controller){ |
|||
return Stack( |
|||
children: [ |
|||
Container( |
|||
width: 375.w, |
|||
height: 812.h, |
|||
color: Colors.white, |
|||
), |
|||
Positioned( |
|||
top: -320.w, |
|||
left: -270.w, |
|||
child: Image.asset( |
|||
Assets.imagesFeedbackBg, |
|||
width: 971.75.w, |
|||
height: 781.25.w, |
|||
fit: BoxFit.cover, |
|||
), |
|||
), |
|||
Positioned( |
|||
top: 53.w, |
|||
left: 207.w, |
|||
child: Image.asset( |
|||
Assets.imagesFeedbackIcon, |
|||
width: 140.w, |
|||
height: 140.w, |
|||
), |
|||
), |
|||
Scaffold( |
|||
appBar: PageAppbar(title: "", backgroundColor: Colors.transparent,), |
|||
backgroundColor: Colors.transparent, |
|||
body: Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: [ |
|||
Container( |
|||
padding: EdgeInsets.symmetric( |
|||
vertical: 22.w, |
|||
horizontal: 14.w |
|||
), |
|||
child: Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: [ |
|||
Text( |
|||
"意见反馈", |
|||
style: TextStyle( |
|||
fontSize: 21.w, |
|||
fontWeight: FontWeight.w500 |
|||
), |
|||
), |
|||
SizedBox(height: 5.w,), |
|||
Text( |
|||
"Hi,给出你的小建议吧~", |
|||
style: TextStyle( |
|||
fontSize: 13.w, |
|||
fontWeight: FontWeight.w500 |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
Expanded( |
|||
child: Container( |
|||
width: 375.w, |
|||
padding: EdgeInsets.symmetric( |
|||
vertical: 11.w, |
|||
horizontal: 14.w |
|||
), |
|||
decoration: BoxDecoration( |
|||
borderRadius: BorderRadius.vertical(top: Radius.circular(9.w)), |
|||
color: Colors.white |
|||
), |
|||
child: SingleChildScrollView( |
|||
child: Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: [ |
|||
RichText( |
|||
text: TextSpan( |
|||
style: TextStyle( |
|||
fontSize: 16.w, |
|||
fontWeight: FontWeight.w500 |
|||
), |
|||
children: [ |
|||
TextSpan( |
|||
text: "选择问题的类型", |
|||
style: TextStyle( |
|||
color: const Color.fromRGBO(51, 51, 51, 1) |
|||
) |
|||
), |
|||
TextSpan( |
|||
text: "*", |
|||
style: TextStyle( |
|||
color: const Color.fromRGBO(248, 85, 66, 1) |
|||
) |
|||
) |
|||
] |
|||
), |
|||
), |
|||
SizedBox(height: 11.w,), |
|||
Wrap( |
|||
alignment: WrapAlignment.spaceBetween, |
|||
spacing: 10.w, |
|||
runSpacing: 10.w, |
|||
children: [ |
|||
...controller.tagList.asMap().entries.map((entry){ |
|||
return TagItem(item: entry.value, index: entry.key, active: controller.active.value, changeActive: controller.changeActive,); |
|||
}), |
|||
], |
|||
), |
|||
SizedBox(height: 20.w,), |
|||
RichText( |
|||
text: TextSpan( |
|||
style: TextStyle( |
|||
fontSize: 16.w, |
|||
fontWeight: FontWeight.w500 |
|||
), |
|||
children: [ |
|||
TextSpan( |
|||
text: "问题描述", |
|||
style: TextStyle( |
|||
color: const Color.fromRGBO(51, 51, 51, 1) |
|||
) |
|||
), |
|||
TextSpan( |
|||
text: "*", |
|||
style: TextStyle( |
|||
color: const Color.fromRGBO(248, 85, 66, 1) |
|||
) |
|||
) |
|||
] |
|||
), |
|||
), |
|||
SizedBox(height: 11.w,), |
|||
Container( |
|||
padding: EdgeInsets.all(14.w), |
|||
decoration: BoxDecoration( |
|||
borderRadius: BorderRadius.all(Radius.circular(8.w)), |
|||
color: const Color.fromRGBO(245, 245, 245, 1) |
|||
), |
|||
child: TextField( |
|||
controller: controller.messageController.value, |
|||
maxLength: 500, // 上限 |
|||
minLines: 5, // 多行 |
|||
maxLines: 5, // 自适应高度 |
|||
style: TextStyle( |
|||
fontSize: ScreenUtil().setWidth(13), |
|||
height: 1 |
|||
), |
|||
decoration: InputDecoration( |
|||
contentPadding: EdgeInsets.symmetric( |
|||
vertical: 0, |
|||
horizontal: 0 |
|||
), |
|||
hintText: "请输入交友心声", |
|||
|
|||
border: const OutlineInputBorder( |
|||
borderSide: BorderSide.none, // 这将移除边框 // 可选:设置圆角 |
|||
), |
|||
// 如果你希望聚焦时和未聚焦时都没有边框,也可以设置 focusedBorder 和 enabledBorder |
|||
focusedBorder: const OutlineInputBorder( |
|||
borderSide: BorderSide.none, |
|||
borderRadius: BorderRadius.all(Radius.circular(8.0)), |
|||
), |
|||
enabledBorder: const OutlineInputBorder( |
|||
borderSide: BorderSide.none, |
|||
borderRadius: BorderRadius.all(Radius.circular(8.0)), |
|||
), |
|||
), |
|||
onChanged: (value){ |
|||
controller.message.value = value; |
|||
}, |
|||
), |
|||
), |
|||
SizedBox(height: 10.w,), |
|||
Wrap( |
|||
spacing: 10.w, |
|||
runSpacing: 10.w, |
|||
children: [ |
|||
...controller.imgList.map((e){ |
|||
return Stack( |
|||
children: [ |
|||
CachedNetworkImage( |
|||
imageUrl: e, |
|||
width: 70.w, |
|||
height: 70.w, |
|||
fit: BoxFit.cover, |
|||
), |
|||
Positioned( |
|||
left: 5.w, |
|||
top: 5.w, |
|||
child: Container( |
|||
width: 20.w, |
|||
height: 20.w, |
|||
decoration: BoxDecoration( |
|||
borderRadius: BorderRadius.all(Radius.circular(20.w)), |
|||
color: const Color.fromRGBO(0, 0, 0, .3) |
|||
), |
|||
child: Icon( |
|||
Icons.close, |
|||
size: 20.w, |
|||
), |
|||
).onTap((){ |
|||
controller.imgList.remove(e); |
|||
|
|||
}), |
|||
) |
|||
], |
|||
); |
|||
}), |
|||
Container( |
|||
width: 70.w, |
|||
height: 70.w, |
|||
decoration: BoxDecoration( |
|||
borderRadius: BorderRadius.all(Radius.circular(8.w)), |
|||
border: Border.all(width: 1, color: const Color.fromRGBO(224, 224, 224, 1)) |
|||
), |
|||
child: Center( |
|||
child: Icon( |
|||
Icons.add, |
|||
size: 18.w, |
|||
color: const Color.fromRGBO(144, 144, 144, 1), |
|||
), |
|||
), |
|||
).onTap((){ |
|||
_showAvatarPopup(controller); |
|||
}) |
|||
], |
|||
), |
|||
SizedBox(height: 10.w,), |
|||
Text( |
|||
"上传问题截图可以让问题快速解决!", |
|||
style: TextStyle( |
|||
fontSize: 11.w, |
|||
color: const Color.fromRGBO(189, 189, 189, 1) |
|||
), |
|||
), |
|||
Container( |
|||
margin: EdgeInsets.symmetric(vertical: 30.w), |
|||
child: Container( |
|||
width: 350.w, |
|||
height: 45.w, |
|||
decoration: BoxDecoration( |
|||
borderRadius: BorderRadius.all(Radius.circular(45.w)), |
|||
gradient: LinearGradient( |
|||
begin: Alignment.centerLeft, // 90deg: 从左到右 |
|||
end: Alignment.centerRight, |
|||
colors: [ |
|||
Color.fromRGBO(131, 89, 255, 1), // 起点颜色 |
|||
Color.fromRGBO(77, 127, 231, 1), // 中间颜色 |
|||
Color.fromRGBO(61, 138, 224, 1), // 终点颜色 |
|||
], |
|||
stops: [0.0, 0.7753, 1.0], // 对应 0%、77.53%、100% |
|||
), |
|||
), |
|||
child: Center( |
|||
child: Text( |
|||
"确认", |
|||
style: TextStyle( |
|||
fontSize: 18.w, |
|||
color: Colors.white, |
|||
fontWeight: FontWeight.w500 |
|||
), |
|||
), |
|||
), |
|||
), |
|||
).onTap((){ |
|||
controller.sendFeedback(); |
|||
}) |
|||
], |
|||
), |
|||
), |
|||
), |
|||
) |
|||
], |
|||
), |
|||
) |
|||
], |
|||
); |
|||
}, |
|||
); |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
class TagItem extends StatefulWidget { |
|||
final Map item; |
|||
final int index; |
|||
final int active; |
|||
final void Function(int) changeActive; |
|||
const TagItem({super.key, required this.item, required this.index, required this.active, required this.changeActive}); |
|||
|
|||
@override |
|||
State<TagItem> createState() => _TagItemState(); |
|||
} |
|||
|
|||
class _TagItemState extends State<TagItem> { |
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return Stack( |
|||
children: [ |
|||
Container( |
|||
padding: EdgeInsets.symmetric( |
|||
vertical: 9.w, |
|||
horizontal: 14.w |
|||
), |
|||
decoration: BoxDecoration( |
|||
borderRadius: BorderRadius.all(Radius.circular(8.w)), |
|||
border: Border.all(width: 1, color: widget.item["value"] == widget.active ? const Color.fromRGBO(117, 98, 249, 1) : const Color.fromRGBO(207, 207, 207, 1)), |
|||
color: widget.item["value"] == widget.active ? const Color.fromRGBO(194, 195, 255, 0.2) : Colors.transparent |
|||
), |
|||
child: Text( |
|||
widget.item["label"], |
|||
style: TextStyle( |
|||
fontSize: 13.w |
|||
), |
|||
), |
|||
).onTap((){ |
|||
widget.changeActive(widget.item["value"]); |
|||
}), |
|||
if(widget.active == widget.item["value"])Positioned( |
|||
bottom: 0, |
|||
right: 0, |
|||
child: Container( |
|||
width: 17.w, |
|||
height: 13.w, |
|||
decoration: BoxDecoration( |
|||
borderRadius: BorderRadius.only( |
|||
topLeft: Radius.circular(8.w), |
|||
bottomRight: Radius.circular(8.w) |
|||
), |
|||
color: const Color.fromRGBO(117, 98, 249, 1) |
|||
), |
|||
child: Center( |
|||
child: Image.asset( |
|||
Assets.imagesCheck, |
|||
width: 6.w, |
|||
), |
|||
), |
|||
), |
|||
) |
|||
], |
|||
); |
|||
} |
|||
} |
|||
|
|||
Write
Preview
Loading…
Cancel
Save