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'; class EditAddressPage extends GetView implements CityPickerListener { final scaffoldKey = GlobalKey(); BoxConstraints? bodysize; /// 0: 省 /// 1: 市 /// 2: 地区 /// 3: 街道 String _addressProvince = "请选择省".tr; String _addressCity = "请选择市".tr; String _addressArea = "请选择地区".tr; String _addressStreet = "请选择街道".tr; List _selectProvince = []; List _selectCity = []; List _selectArea = []; List _selectStreet = []; @override Widget build(BuildContext context) { AddressListController addressListController = Get.find(); var address = Map.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( 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> 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 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 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> getCityData(String code, int index) async { // final AddressController addressController = Get.find(); // 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> getCityData(String code, int index) async { final AddressController controller = Get.find(); controller.model.currentType = index; return controller.getData(parentCode: code, level: index); } }