Files
tuiche/lib/pages/mh_page/edit_address_page.dart
2026-04-07 14:49:31 +08:00

1187 lines
81 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:ef/ef.dart';
import 'package:flutter/material.dart';
import 'package:flutter_city_picker/listener/picker_listener.dart';
import 'package:flutter_city_picker/model/address.dart';
import 'package:flutter_svg/svg.dart';
import 'package:flutterflow_ui/flutterflow_ui.dart';
import 'package:vbvs_app/common/color/appConstants.dart';
import 'package:vbvs_app/common/color/appFontsize.dart';
import 'package:vbvs_app/common/util/FitTool.dart';
import 'package:vbvs_app/common/util/MyUtils.dart';
import 'package:vbvs_app/component/tool/CustomCard.dart';
import 'package:vbvs_app/controller/mh_controller/address_controller.dart';
import 'package:vbvs_app/controller/mh_controller/address_list_controller.dart';
import 'package:vbvs_app/pages/mh_page/homepage/component/citypicker.dart';
import 'package:flutter/services.dart';
class EditAddressPage extends GetView<AddressController>
implements CityPickerListener {
final scaffoldKey = GlobalKey<ScaffoldState>();
BoxConstraints? bodysize;
/// 0: 省
/// 1: 市
/// 2: 地区
/// 3: 街道
String _addressProvince = "请选择省".tr;
String _addressCity = "请选择市".tr;
String _addressArea = "请选择地区".tr;
String _addressStreet = "请选择街道".tr;
List<AddressNode> _selectProvince = [];
List<AddressNode> _selectCity = [];
List<AddressNode> _selectArea = [];
List<AddressNode> _selectStreet = [];
@override
Widget build(BuildContext context) {
AddressListController addressListController = Get.find();
var address =
Map<String, dynamic>.from(addressListController.model.address);
controller.model.default_ = address['default'];
controller.model.all_address = getAddressDesc(address);
controller.model.address = address['address'];
controller.model.name = address['name'];
controller.model.tel = address['tel'];
return LayoutBuilder(builder: (context, cc) {
bodysize = cc;
return GestureDetector(
// onTap: () => FocusScope.of(context).unfocus(),,
child: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/new_background.png'), // 本地图片
fit: BoxFit.fill, // 填满整个 Container
),
),
child: Scaffold(
// key: scaffoldKey,
backgroundColor: Colors.transparent,
appBar: AppBar(
systemOverlayStyle: SystemUiOverlayStyle(
statusBarColor: Colors.transparent, // 状态栏背景色
statusBarIconBrightness: Brightness.light, // 图标颜色Android
statusBarBrightness: Brightness.light, // 图标颜色iOS
),
backgroundColor: Colors.transparent,
automaticallyImplyLeading: false,
iconTheme: IconThemeData(color: Colors.white),
titleSpacing: 0,
title: Container(
width: double.infinity,
height: 180.rpx,
child: Stack(
alignment: Alignment.center,
children: [
// 中间居中的标题
Text(
'编辑地址'.tr,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 30.rpx,
),
),
// 左侧图标
Positioned(
left: 0.rpx,
child: returnIconButtomNew(),
),
],
),
),
actions: [],
centerTitle: false,
),
body: Container(
width: bodysize!.maxWidth,
height: bodysize!.maxHeight * 1,
child: Column(
// mainAxisSize: MainAxisSize.max,
children: [
// TitleComponentWidget(
// titleName: '编辑收货地址',
// ),
Expanded(
child: Padding(
padding:
EdgeInsetsDirectional.fromSTEB(0, 28.rpx, 0, 0),
child: Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height * 0.907,
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 30.rpx),
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Container(
width: MediaQuery.sizeOf(context).width,
decoration: BoxDecoration(
color: Color(0xFF003058),
borderRadius:
BorderRadius.circular(16.rpx),
),
child: Align(
alignment:
const AlignmentDirectional(0, -1),
child: Container(
width: MediaQuery.sizeOf(context).width,
child: Padding(
padding: const EdgeInsetsDirectional
.fromSTEB(15, 0, 15, 0),
child: Container(
width: MediaQuery.sizeOf(context)
.width,
// decoration: BoxDecoration(
// color: Color(0XFF003058),
// ),
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Container(
width:
MediaQuery.sizeOf(context)
.width,
height:
MediaQuery.sizeOf(context)
.height *
0.038,
constraints:
const BoxConstraints(
minHeight: 46,
),
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(
16),
),
child: Row(
mainAxisSize:
MainAxisSize.max,
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Expanded(
child: Container(
width:
MediaQuery.sizeOf(
context)
.width *
0.1,
height: 100,
decoration:
BoxDecoration(),
child: Align(
alignment:
const AlignmentDirectional(
-1, 0),
child: Text(
'地址信息'.tr,
style: TextStyle(
fontFamily:
'Readex Pro',
color: Colors
.white,
fontSize:
AppFontsize
.title_size,
letterSpacing:
0,
fontWeight:
FontWeight
.w600,
),
),
),
),
),
Container(
height:
MediaQuery.sizeOf(
context)
.height *
0.038,
constraints:
BoxConstraints(
minWidth: 104.rpx,
),
decoration:
BoxDecoration(),
child: Row(
mainAxisSize:
MainAxisSize.max,
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Align(
alignment:
const AlignmentDirectional(
0, 0),
child: Padding(
padding:
const EdgeInsetsDirectional
.fromSTEB(
0,
3,
0,
0),
child:
Container(
width: 60.rpx,
height:
60.rpx,
decoration:
const BoxDecoration(),
child: Align(
alignment:
const AlignmentDirectional(
0,
0),
child:
Theme(
data:
ThemeData(
checkboxTheme:
CheckboxThemeData(
visualDensity:
VisualDensity.compact,
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
shape:
RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(64),
),
),
unselectedWidgetColor:
const Color(0xFFD3D3D3),
),
child: Obx(
() {
return Checkbox(
value:
controller.model.default_ == 1,
onChanged:
(newValue) {
controller.model.default_ = (newValue ?? false)
? 1
: 0;
controller.updateAll();
},
side:
const BorderSide(
width:
1.5,
color:
Colors.white,
),
activeColor:
const Color(0xFF84F5FF),
checkColor:
const Color(0xFF011D33),
// Add this to prevent double triggers
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
);
}),
),
),
),
),
),
Obx(
() => Text(
'默认'.tr,
style:
TextStyle(
fontFamily:
'Readex Pro',
fontSize:
26.rpx,
letterSpacing:
0,
color: controller
.model.default_ ==
1
? const Color(
0XFF84F5FF)
: Colors
.white,
),
),
)
],
),
),
],
// .divide(const SizedBox(
// width: 12)),
),
),
Container(
// width: MediaQuery.sizeOf(
// context)
// .width,
height: bodysize!.maxHeight *
0.038,
constraints: BoxConstraints(
minHeight: 61.rpx,
),
decoration: BoxDecoration(),
child: Row(
mainAxisSize:
MainAxisSize.max,
children: [
Container(
// width:
// MediaQuery.sizeOf(
// context)
// .width *
// 0.17,
decoration:
BoxDecoration(),
constraints:
BoxConstraints(
minWidth:
145.rpx,
maxWidth:
145.rpx),
child: Text(
'收件人'.tr,
maxLines: 2,
style: TextStyle(
fontFamily:
'Readex Pro',
fontSize: AppFontsize
.normal_text_size,
letterSpacing: 0,
color:
Colors.white),
),
),
Expanded(
child: Container(
decoration:
BoxDecoration(
color: Colors.white,
borderRadius:
BorderRadius
.circular(
8),
),
width:
double.infinity,
child: Padding(
padding:
EdgeInsetsDirectional
.fromSTEB(
35.rpx,
0,
35.rpx,
0),
child: Row(
mainAxisSize:
MainAxisSize
.max,
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Expanded(
child:
Container(
child:
Align(
alignment:
AlignmentDirectional(
-1,
0),
child:
TextFormField(
initialValue:
address['name'],
onChanged:
(value) {
controller
.model
.name = value;
},
autofocus:
false,
obscureText:
false,
decoration:
InputDecoration(
contentPadding:
EdgeInsets.all(0),
isDense:
true,
labelStyle:
TextStyle(
fontFamily:
'Inter',
fontSize:
26.rpx,
letterSpacing:
0.0,
),
hintStyle:
TextStyle(
fontFamily:
'Inter',
fontSize:
26.rpx,
letterSpacing:
0.0,
color:
themeController.currentColor.sc4,
),
enabledBorder:
OutlineInputBorder(
borderSide:
BorderSide(
color: Color(0x00000000),
width: 1.rpx,
),
borderRadius:
BorderRadius.circular(8.rpx),
),
focusedBorder:
OutlineInputBorder(
borderSide:
BorderSide(
color: Color(0x00000000),
width: 1.rpx,
),
borderRadius:
BorderRadius.circular(8.rpx),
),
errorBorder:
OutlineInputBorder(
borderSide:
BorderSide(
color: Colors.red,
width: 1.rpx,
),
borderRadius:
BorderRadius.circular(8.rpx),
),
focusedErrorBorder:
OutlineInputBorder(
borderSide:
BorderSide(
color: Colors.red,
width: 1.rpx,
),
borderRadius:
BorderRadius.circular(8.rpx),
),
filled:
false,
fillColor:
Colors.white,
),
style:
TextStyle(
fontFamily:
'Readex Pro',
letterSpacing:
0,
color:
Colors.black,
fontSize:
26.rpx,
),
// cursorColor:
// Colors.black,
// validator: _model
// .textControllerValidator
// .asValidator(context),
),
),
),
),
],
),
),
),
),
],
// .divide(const SizedBox(
// width: 15)),
),
),
Container(
// width: MediaQuery.sizeOf(
// context)
// .width,
height: bodysize!.maxHeight *
0.038,
constraints: BoxConstraints(
minHeight: 61.rpx,
),
decoration: BoxDecoration(),
child: Row(
mainAxisSize:
MainAxisSize.max,
children: [
Container(
// width:
// MediaQuery.sizeOf(
// context)
// .width *
// 0.17,
decoration:
BoxDecoration(),
constraints:
BoxConstraints(
minWidth:
145.rpx,
maxWidth:
145.rpx),
child: Text(
'手机号'.tr,
style: TextStyle(
fontFamily:
'Readex Pro',
fontSize: AppFontsize
.normal_text_size,
letterSpacing: 0,
color:
Colors.white),
),
),
Expanded(
child: Container(
decoration:
BoxDecoration(
color: Colors.white,
borderRadius:
BorderRadius
.circular(
8),
),
width:
double.infinity,
child: Padding(
padding:
EdgeInsetsDirectional
.fromSTEB(
35.rpx,
0,
35.rpx,
0),
child: Row(
mainAxisSize:
MainAxisSize
.max,
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Expanded(
child:
Container(
child:
Align(
alignment:
AlignmentDirectional(
-1,
0),
child:
TextFormField(
initialValue:
address['tel'],
onChanged:
(value) {
controller
.model
.tel = value;
},
autofocus:
false,
obscureText:
false,
decoration:
InputDecoration(
contentPadding:
EdgeInsets.all(0),
isDense:
true,
labelStyle:
TextStyle(
fontFamily:
'Inter',
fontSize:
26.rpx,
letterSpacing:
0.0,
),
hintStyle:
TextStyle(
fontFamily:
'Inter',
fontSize:
26.rpx,
letterSpacing:
0.0,
color:
themeController.currentColor.sc4,
),
enabledBorder:
OutlineInputBorder(
borderSide:
BorderSide(
color: Color(0x00000000),
width: 1.rpx,
),
borderRadius:
BorderRadius.circular(8.rpx),
),
focusedBorder:
OutlineInputBorder(
borderSide:
BorderSide(
color: Color(0x00000000),
width: 1.rpx,
),
borderRadius:
BorderRadius.circular(8.rpx),
),
errorBorder:
OutlineInputBorder(
borderSide:
BorderSide(
color: Colors.red,
width: 1.rpx,
),
borderRadius:
BorderRadius.circular(8.rpx),
),
focusedErrorBorder:
OutlineInputBorder(
borderSide:
BorderSide(
color: Colors.red,
width: 1.rpx,
),
borderRadius:
BorderRadius.circular(8.rpx),
),
filled:
false,
fillColor:
Colors.white,
),
style:
TextStyle(
fontFamily:
'Readex Pro',
letterSpacing:
0,
color:
Colors.black,
fontSize:
26.rpx,
),
// cursorColor:
// Colors.black,
// validator: _model
// .textControllerValidator
// .asValidator(context),
),
),
),
),
],
),
),
),
),
],
// .divide(const SizedBox(
// width: 15)),
),
),
Container(
width:
MediaQuery.sizeOf(context)
.width,
height:
MediaQuery.sizeOf(context)
.height *
0.038,
constraints:
const BoxConstraints(
minHeight: 31,
),
child: Row(
mainAxisSize:
MainAxisSize.max,
children: [
Container(
// width:
// MediaQuery.sizeOf(
// context)
// .width *
// 0.17,
height:
MediaQuery.sizeOf(
context)
.height *
0.038,
constraints:
BoxConstraints(
minWidth:
145.rpx,
maxWidth:
145.rpx),
child: Text(
'所在地区'.tr,
style: TextStyle(
fontFamily:
'Readex Pro',
fontSize: AppFontsize
.normal_text_size,
letterSpacing: 0,
color: Colors.white,
),
),
),
Expanded(
child: InkWell(
onTap: () {
CityPicker.show(
context: context,
cityPickerListener:
this,
);
},
child: Container(
width: 100,
height: 100,
decoration:
BoxDecoration(
color: const Color(
0xFFF3F5F6),
borderRadius:
BorderRadius
.circular(
8),
),
alignment: Alignment
.center,
child: Obx(() {
return Row(
children: [
Expanded(
child:
Padding(
padding: EdgeInsets.only(
left: 27
.rpx,
right: 10
.rpx),
child: Text(
controller
.model
.all_address ??
'',
maxLines:
1,
overflow:
TextOverflow
.ellipsis,
style:
TextStyle(
fontFamily:
'Readex Pro',
letterSpacing:
0,
color: Color(
0xFF333333),
fontSize:
26.rpx,
),
),
)),
Padding(
padding: EdgeInsets.only(
right:
27.rpx),
child: Container(
height: 30.rpx,
width: 30.rpx,
child: SvgPicture.asset(
'assets/img/icon/expand_more.svg',
color:
Colors.black,
)))
],
);
}),
),
),
)
],
// .divide(const SizedBox(
// width: 15)),
),
),
Padding(
padding:
const EdgeInsetsDirectional
.fromSTEB(
0, 0, 0, 15),
child: Container(
width: MediaQuery.sizeOf(
context)
.width,
height: MediaQuery.sizeOf(
context)
.height *
0.093,
constraints:
const BoxConstraints(
minHeight: 76,
),
child: Row(
mainAxisSize:
MainAxisSize.max,
children: [
Align(
alignment: Alignment
.topLeft,
child: Container(
// width: MediaQuery.sizeOf(
// context)
// .width *
// 0.17,
height: MediaQuery
.sizeOf(
context)
.height *
0.038,
constraints:
BoxConstraints(
minWidth: 145
.rpx,
maxWidth:
145.rpx),
child: Text(
'详细地址'.tr,
maxLines: 2,
style: TextStyle(
fontFamily:
'Readex Pro',
fontSize:
AppFontsize
.normal_text_size,
letterSpacing:
0,
height: 1,
color: Colors
.white),
),
),
),
Expanded(
child: Container(
width: 100,
height: MediaQuery
.sizeOf(
context)
.height *
0.093,
decoration:
BoxDecoration(
color: const Color(
0xFFF3F5F6),
borderRadius:
BorderRadius
.circular(
8),
),
child:
TextFormField(
// autofocus: true,
onChanged:
(value) {
controller
.model
.address =
value;
},
initialValue:
address[
'address'],
maxLines: 2,
obscureText:
false,
decoration:
InputDecoration(
contentPadding:
EdgeInsets
.symmetric(
vertical:
10.rpx,
horizontal:
35.rpx,
),
labelStyle:
TextStyle(
fontFamily:
'Readex Pro',
letterSpacing:
0,
),
hintStyle:
TextStyle(
fontFamily:
'Readex Pro',
letterSpacing:
0,
color: themeController
.currentColor
.sc4,
),
enabledBorder:
UnderlineInputBorder(
borderSide:
const BorderSide(
color: Color(
0x00000000),
width: 2,
),
borderRadius:
BorderRadius
.circular(8),
),
focusedBorder:
UnderlineInputBorder(
borderSide:
const BorderSide(
color: Color(
0x00000000),
width: 2,
),
borderRadius:
BorderRadius
.circular(8),
),
errorBorder:
UnderlineInputBorder(
borderSide:
const BorderSide(
color: Color(
0x00000000),
width: 2,
),
borderRadius:
BorderRadius
.circular(8),
),
focusedErrorBorder:
UnderlineInputBorder(
borderSide:
const BorderSide(
color: Color(
0x00000000),
width: 2,
),
borderRadius:
BorderRadius
.circular(8),
),
),
style:
TextStyle(
fontFamily:
'Readex Pro',
letterSpacing:
0,
color: Colors
.black,
fontSize:
26.rpx,
),
// cursorColor:
// Colors
// .black,
),
),
),
]),
),
),
].divide(
SizedBox(height: 30.rpx)),
),
),
),
),
),
),
],
),
)),
),
),
),
Padding(
padding: const EdgeInsetsDirectional.fromSTEB(15, 0,
15, AppConstants.page_button_bottom_padding),
child: CustomCard(
borderRadius: 16.rpx,
gradientDirection: GradientDirection.vertical,
onTap: () async {
if (controller.model.all_address == null ||
controller.model.all_address!.isEmpty) {
showToast("地址不能为空".tr);
return;
}
if (controller.model.name == null ||
controller.model.name!.isEmpty) {
showToast("名字不能为空".tr);
return;
}
if (controller.model.address == null ||
controller.model.address!.isEmpty) {
showToast("详细地址不能为空".tr);
return;
}
if (controller.model.tel == null ||
controller.model.tel!.isEmpty) {
showToast("手机号不能为空".tr);
return;
}
if (!MyUtils.isValidPhoneNumber(
controller.model.tel!)) {
showToast("无效的手机号码".tr);
return;
}
if (addressListController.model.type == 1) {
await controller.addAddress(
controller.model, context);
} else {
await controller.updateAddress(
address, controller.model);
}
await addressListController.getAddressList();
Get.back();
controller.model = AddressModel();
controller.updateAll();
},
colors: const [
Color(0xFFFCFCFC),
Color(0xFFF8FAF9),
Color(0XFFECF6F3),
Color(0XFFD9F0E9),
Color(0xFFCEECE3)
],
child: Container(
width: double.infinity,
height: 90.rpx,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
),
child: Text(
"保存".tr,
style: TextStyle(
fontFamily: 'Readex Pro',
color: stringToColor("#011D33"),
letterSpacing: 0,
fontSize: 30.rpx,
),
),
),
)),
],
),
),
)));
});
}
@override
Future<List<AddressNode>> onDataLoad(
int index, String code, String name) async {
debugPrint("onDataLoad ---> index=$index, code=$code, name=$name");
return HttpUtils.getCityData(code, index);
}
@override
void onFinish(List<AddressNode> data) {
debugPrint("onFinish");
String add = "";
for (var node in data) {
add += "${node.name} ";
}
if (controller.model.currentType == 0) {
_addressProvince = add;
_selectProvince = data;
} else if (controller.model.currentType == 1) {
_addressCity = add;
_selectCity = data;
} else if (controller.model.currentType == 2) {
_addressArea = add;
_selectArea = data;
} else {
_addressStreet = add;
_selectStreet = data;
}
controller.model.all_address = add;
controller.model.addressList = data;
controller.updateAll();
}
String getAddressDesc(Map<String, dynamic> address) {
// 提取地址的各个字段
String? province = address['province'];
String? city = address['city'];
String? area = address['county'];
String? street = address['street'];
// 使用 where 过滤掉 null 或空字符串的值,并用空格连接有效的字段
return [province, city, area, street]
.where((element) => element != null && element.isNotEmpty)
.join(' ');
}
}
// class HttpUtils {
// static Future<List<AddressNode>> getCityData(String code, int index) async {
// final AddressController addressController = Get.find<AddressController>();
// addressController.model.currentType = 1;
// if (code.isEmpty) {
// addressController.updateAll();
// return addressController.getData();
// }
// addressController.model.currentType = index + 1;
// //控制选择区域层级 1.省 2.市 3.区 4.街道
// if (addressController.model.currentType > 3) {
// return [];
// }
// return addressController.getData();
// }
// }
class HttpUtils {
static Future<List<AddressNode>> getCityData(String code, int index) async {
final AddressController controller = Get.find<AddressController>();
controller.model.currentType = index;
return controller.getData(parentCode: code, level: index);
}
}