import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:yumi/app_localizations.dart'; import 'package:yumi/ui_kit/components/sc_debounce_widget.dart'; import 'package:yumi/shared/tools/sc_loading_manager.dart'; import 'package:yumi/shared/data_sources/sources/local/user_manager.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart'; import 'package:yumi/services/auth/user_profile_manager.dart'; import 'package:provider/provider.dart'; import 'package:yumi/ui_kit/components/appbar/socialchat_appbar.dart'; import 'package:yumi/ui_kit/components/sc_compontent.dart'; import 'package:yumi/ui_kit/components/text/sc_text.dart'; import 'package:yumi/ui_kit/components/sc_tts.dart'; import 'package:yumi/app/routes/sc_fluro_navigator.dart'; import 'package:yumi/shared/tools/sc_lk_dialog_util.dart'; import 'package:yumi/services/audio/rtc_manager.dart'; import 'package:yumi/ui_kit/theme/socialchat_theme.dart'; import '../../../shared/tools/sc_pick_utils.dart'; import '../../../shared/business_logic/models/res/login_res.dart'; import '../../../shared/business_logic/usecases/sc_accurate_length_limiting_textInput_formatter.dart'; class EditUserInfoPage2 extends StatefulWidget { const EditUserInfoPage2({super.key}); @override State createState() => _EditUserInfoPage2State(); } class _EditUserInfoPage2State extends State with SingleTickerProviderStateMixin { static const Object _noChange = Object(); String? userCover; String? bornMonth; String? autograph; String? hobby; String? bornDay; String? bornYear; String nickName = ""; DateTime? birthdayDate; num? sex; num? age; bool _isSubmitting = false; @override void initState() { super.initState(); final currentProfile = AccountStorage().getCurrentUser()?.userProfile; userCover = currentProfile?.userAvatar ?? ""; nickName = currentProfile?.userNickname ?? ""; autograph = currentProfile?.autograph ?? ""; hobby = currentProfile?.hobby ?? ""; birthdayDate = _birthdayFromProfile(currentProfile); bornMonth = _formatTwoDigits(currentProfile?.bornMonth); bornDay = _formatTwoDigits(currentProfile?.bornDay); bornYear = _formatYear(currentProfile?.bornYear); age = currentProfile?.age ?? _ageFromBirthday(birthdayDate); sex = currentProfile?.userSex; } String? _preferNonEmpty(String? primary, String? fallback) { if ((primary ?? "").trim().isNotEmpty) { return primary; } if ((fallback ?? "").trim().isNotEmpty) { return fallback; } return primary ?? fallback; } bool _isBrokenLocalMediaUrl(String? url) { return (url ?? "").contains("/external/oss/local/"); } String? _preferUsableAvatar(String? primary, String? fallback) { if ((primary ?? "").trim().isNotEmpty && !_isBrokenLocalMediaUrl(primary)) { return primary; } if ((fallback ?? "").trim().isNotEmpty && !_isBrokenLocalMediaUrl(fallback)) { return fallback; } return _preferNonEmpty(primary, fallback); } DateTime? _birthdayFromProfile([SocialChatUserProfile? profile]) { final targetProfile = profile ?? AccountStorage().getCurrentUser()?.userProfile; final year = targetProfile?.bornYear?.toInt() ?? 0; final month = targetProfile?.bornMonth?.toInt() ?? 0; final day = targetProfile?.bornDay?.toInt() ?? 0; if (year <= 0 || month <= 0 || day <= 0) { return null; } try { return DateTime(year, month, day); } catch (_) { return null; } } String _formatTwoDigits(num? value) { final intValue = value?.toInt() ?? 0; if (intValue <= 0) { return ""; } return intValue < 10 ? "0$intValue" : "$intValue"; } String _formatYear(num? value) { final intValue = value?.toInt() ?? 0; if (intValue <= 0) { return ""; } return "$intValue"; } num? _ageFromBirthday(DateTime? date) { if (date == null) { return null; } return (DateTime.now().year - date.year).abs(); } String _birthdayDisplayText() { if ((bornYear ?? "").isEmpty || (bornMonth ?? "").isEmpty || (bornDay ?? "").isEmpty) { return ""; } return "$bornYear-$bornMonth-$bornDay"; } bool _isSameDate(DateTime first, DateTime second) { return first.year == second.year && first.month == second.month && first.day == second.day; } bool _hasChanges(List values) { return values.any((value) => !identical(value, _noChange)); } void _syncLocalProfileState(SocialChatUserProfile profile) { userCover = _preferUsableAvatar(profile.userAvatar, userCover); nickName = profile.userNickname ?? nickName; autograph = profile.autograph ?? autograph; hobby = profile.hobby ?? hobby; sex = profile.userSex ?? sex; birthdayDate = _birthdayFromProfile(profile) ?? birthdayDate; bornYear = _formatYear(profile.bornYear); bornMonth = _formatTwoDigits(profile.bornMonth); bornDay = _formatTwoDigits(profile.bornDay); age = profile.age ?? _ageFromBirthday(birthdayDate) ?? age; } @override Widget build(BuildContext context) { return Stack( children: [ Image.asset( "sc_images/person/sc_icon_edit_userinfo_bg.png", width: ScreenUtil().screenWidth, height: ScreenUtil().screenHeight, fit: BoxFit.fill, ), Scaffold( backgroundColor: Colors.transparent, resizeToAvoidBottomInset: true, appBar: SocialChatStandardAppBar( title: SCAppLocalizations.of(context)!.editProfile, actions: [], ), body: Consumer( builder: (context, ref, child) { return Column( spacing: 10.w, children: [ SCDebounceWidget( child: Row( children: [ SizedBox(width: 15.w), text( SCAppLocalizations.of(context)!.profilePhoto, textColor: Colors.white, fontSize: 15.sp, fontWeight: FontWeight.w600, ), Spacer(), Stack( alignment: AlignmentDirectional.bottomEnd, children: [ netImage( url: _preferNonEmpty( userCover, UserManager() .getCurrentUser() ?.userProfile ?.userAvatar, ) ?? "", width: 45.w, height: 45.w, shape: BoxShape.circle, ), ], ), Icon( Icons.keyboard_arrow_right, size: 20.w, color: Colors.white70, ), SizedBox(width: 8.w), ], ), onTap: () { SCPickUtils.pickImage(context, ( bool success, String url, ) { if (success) { debugPrint("[Profile Avatar] uploaded url: $url"); userCover = url; setState(() {}); submitAvatarOnly(); } }); }, ), SizedBox(height: 3.w), _buildItem( "${SCAppLocalizations.of(context)!.userName}:", nickName, () { _showInputBioHobby(nickName, 3); }, ), _buildItem( "${SCAppLocalizations.of(context)!.gender}:", sex == 1 ? SCAppLocalizations.of(context)!.man : SCAppLocalizations.of(context)!.woman, () { _showSexDialog(); }, ), _buildItem( "${SCAppLocalizations.of(context)!.birthday}:", _birthdayDisplayText(), () { _selectDate(); }, ), _buildItem( "${SCAppLocalizations.of(context)!.bio}:", autograph ?? "", () { _showInputBioHobby(autograph ?? "", 1); }, ), _buildItem( "${SCAppLocalizations.of(context)!.hobby}:", hobby ?? "", () { _showInputBioHobby(hobby ?? "", 2); }, ), ], ); }, ), ), ], ); } Future _selectDate() async { final initialDate = birthdayDate ?? _getBefor18(); DateTime selectedDate = initialDate; SmartDialog.show( tag: "showSelectDate", alignment: Alignment.bottomCenter, animationType: SmartAnimationType.fade, builder: (_) { return SafeArea( top: false, child: Container( height: 240.w, padding: EdgeInsets.only(top: 6), color: CupertinoColors.systemBackground, child: Column( children: [ SizedBox(height: 10.w), Row( children: [ SizedBox(width: 10.w), GestureDetector( child: Container( alignment: Alignment.topCenter, child: text( SCAppLocalizations.of(context)!.cancel, textColor: Colors.black87, fontSize: 15.sp, fontWeight: FontWeight.w600, ), ), onTap: () { SmartDialog.dismiss(tag: "showSelectDate"); }, ), Expanded( child: Container( alignment: Alignment.topCenter, child: text( SCAppLocalizations.of(context)!.birthday, textColor: Colors.black87, fontSize: 15.sp, fontWeight: FontWeight.w600, ), ), ), GestureDetector( child: Container( alignment: Alignment.topCenter, child: text( SCAppLocalizations.of(context)!.confirm, textColor: Color(0xff7726FF), fontSize: 15.sp, fontWeight: FontWeight.w600, ), ), onTap: () { final hasChanged = !_isSameDate(initialDate, selectedDate); birthdayDate = selectedDate; bornMonth = _formatTwoDigits(selectedDate.month); bornDay = _formatTwoDigits(selectedDate.day); bornYear = "${selectedDate.year}"; age = _ageFromBirthday(selectedDate); setState(() {}); SmartDialog.dismiss(tag: "showSelectDate"); if (hasChanged) { submit( ageValue: age, bornYearValue: selectedDate.year, bornMonthValue: selectedDate.month, bornDayValue: selectedDate.day, ); } }, ), SizedBox(width: 10.w), ], ), Expanded( child: CupertinoDatePicker( mode: CupertinoDatePickerMode.date, initialDateTime: initialDate, maximumDate: _getBefor18(), onDateTimeChanged: (date) { selectedDate = date; }, ), ), ], ), ), ); }, ); } DateTime _getBefor18() { DateTime currentDate = DateTime.now(); DateTime eighteenYearsAgo = DateTime( currentDate.year - 18, currentDate.month, currentDate.day, currentDate.hour, currentDate.minute, currentDate.second, currentDate.millisecond, currentDate.microsecond, ); return eighteenYearsAgo; } void submitAvatarOnly() { if ((userCover ?? "").trim().isEmpty) { return; } submit(userAvatarValue: userCover); } void submit({ Object? userAvatarValue = _noChange, Object? userNicknameValue = _noChange, Object? userSexValue = _noChange, Object? ageValue = _noChange, Object? bornYearValue = _noChange, Object? bornMonthValue = _noChange, Object? bornDayValue = _noChange, Object? hobbyValue = _noChange, Object? autographValue = _noChange, }) async { if (!_hasChanges([ userAvatarValue, userNicknameValue, userSexValue, ageValue, bornYearValue, bornMonthValue, bornDayValue, hobbyValue, autographValue, ])) { return; } if (!identical(userNicknameValue, _noChange) && ((userNicknameValue as String?) ?? "").isEmpty) { SCTts.show(SCAppLocalizations.of(context)!.pleaseEnterNickname); return; } if (_isSubmitting) { return; } _isSubmitting = true; SCLoadingManager.show(); debugPrint( "[Profile Edit] incremental payload: {userAvatar: ${identical(userAvatarValue, _noChange) ? "" : userAvatarValue}, userNickname: ${identical(userNicknameValue, _noChange) ? "" : userNicknameValue}, userSex: ${identical(userSexValue, _noChange) ? "" : userSexValue}, age: ${identical(ageValue, _noChange) ? "" : ageValue}, bornYear: ${identical(bornYearValue, _noChange) ? "" : bornYearValue}, bornMonth: ${identical(bornMonthValue, _noChange) ? "" : bornMonthValue}, bornDay: ${identical(bornDayValue, _noChange) ? "" : bornDayValue}, hobby: ${identical(hobbyValue, _noChange) ? "" : hobbyValue}, autograph: ${identical(autographValue, _noChange) ? "" : autographValue}}", ); try { final updatedProfile = await SCAccountRepository().updateUserInfo( userAvatar: identical(userAvatarValue, _noChange) ? null : userAvatarValue as String?, userSex: identical(userSexValue, _noChange) ? null : userSexValue as num?, userNickname: identical(userNicknameValue, _noChange) ? null : userNicknameValue as String?, age: identical(ageValue, _noChange) ? null : ageValue as num?, bornDay: identical(bornDayValue, _noChange) ? null : bornDayValue as num?, bornMonth: identical(bornMonthValue, _noChange) ? null : bornMonthValue as num?, bornYear: identical(bornYearValue, _noChange) ? null : bornYearValue as num?, hobby: identical(hobbyValue, _noChange) ? null : hobbyValue as String?, autograph: identical(autographValue, _noChange) ? null : autographValue as String?, ); final mergedProfile = updatedProfile.copyWith( userAvatar: _preferUsableAvatar( updatedProfile.userAvatar, identical(userAvatarValue, _noChange) ? userCover : userAvatarValue as String?, ), userNickname: _preferNonEmpty( updatedProfile.userNickname, identical(userNicknameValue, _noChange) ? nickName : userNicknameValue as String?, ), autograph: _preferNonEmpty( updatedProfile.autograph, identical(autographValue, _noChange) ? autograph : autographValue as String?, ), hobby: _preferNonEmpty( updatedProfile.hobby, identical(hobbyValue, _noChange) ? hobby : hobbyValue as String?, ), userSex: updatedProfile.userSex ?? (identical(userSexValue, _noChange) ? sex : userSexValue as num?), age: updatedProfile.age ?? (identical(ageValue, _noChange) ? age : ageValue as num?), bornDay: updatedProfile.bornDay ?? (identical(bornDayValue, _noChange) ? birthdayDate?.day : bornDayValue as num?), bornMonth: updatedProfile.bornMonth ?? (identical(bornMonthValue, _noChange) ? birthdayDate?.month : bornMonthValue as num?), bornYear: updatedProfile.bornYear ?? (identical(bornYearValue, _noChange) ? birthdayDate?.year : bornYearValue as num?), ); debugPrint( "[Profile Edit] merged profile avatar: ${mergedProfile.userAvatar ?? ""}", ); _syncLocalProfileState(mergedProfile); if (!mounted) { return; } Provider.of( context, listen: false, ).syncCurrentUserProfile(mergedProfile); Provider.of(context, listen: false).needUpDataUserInfo = true; setState(() {}); SCTts.show(SCAppLocalizations.of(context)!.operationSuccessful); } catch (e) { debugPrint(e.toString()); } finally { _isSubmitting = false; SCLoadingManager.hide(); } } void _showSexDialog() { showCenterDialog( context, Container( height: 150.w, width: ScreenUtil().screenWidth * 0.7, decoration: BoxDecoration( color: Color(0xff09372E), borderRadius: BorderRadius.all(Radius.circular(12)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox(height: 15.w), text( SCAppLocalizations.of(context)!.pleaseSelectYourGender, fontSize: 15.sp, textColor: Colors.white, ), SizedBox(height: 5.w), Divider(color: Colors.black12, thickness: 0.5, height: 14.w), SizedBox(height: 8.w), GestureDetector( onTap: () { SCNavigatorUtils.goBack(context); if (sex == 1) { return; } sex = 1; setState(() {}); submit(userSexValue: sex); }, behavior: HitTestBehavior.opaque, child: Row( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.max, children: [ xb(1, color: Colors.white, height: 18.w), SizedBox(width: 5.w), text( SCAppLocalizations.of(context)!.man, textColor: Colors.white, fontSize: 15.sp, ), ], ), ), SizedBox(height: 8.w), Divider(color: Colors.black12, thickness: 0.5, height: 14.w), SizedBox(height: 8.w), GestureDetector( onTap: () { SCNavigatorUtils.goBack(context); if (sex == 0) { return; } sex = 0; setState(() {}); submit(userSexValue: sex); }, behavior: HitTestBehavior.opaque, child: Row( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.max, children: [ xb(0, color: Colors.white, height: 18.w), SizedBox(width: 5.w), text( SCAppLocalizations.of(context)!.woman, textColor: Colors.white, fontSize: 15.sp, ), ], ), ), ], ), ), ); } _buildItem(String title, String value, Function() func) { return SCDebounceWidget( child: Column( children: [ Row( children: [ SizedBox(width: 15.w), text( title, textColor: Colors.white, fontSize: 15.sp, fontWeight: FontWeight.w600, ), Expanded( child: Container( alignment: AlignmentDirectional.centerEnd, child: text(value, textColor: Colors.white, fontSize: 15.sp), ), ), Icon( Icons.keyboard_arrow_right, size: 20.w, color: Colors.white70, ), SizedBox(width: 8.w), ], ), SizedBox(height: 8.w), Container( margin: EdgeInsets.only(left: 15.w, right: 15.w), color: Colors.white12, height: 0.5.w, width: ScreenUtil().screenWidth, ), ], ), onTap: () { func.call(); }, ); } void _showInputBioHobby(String content, int type) { SmartDialog.dismiss(tag: "showInputBioHobby"); TextEditingController inputController = TextEditingController(); inputController.text = content; SmartDialog.show( tag: "showInputBioHobby", alignment: Alignment.center, debounce: true, animationType: SmartAnimationType.fade, builder: (_) { return Container( height: 250.w, margin: EdgeInsets.symmetric(horizontal: 20.w), decoration: BoxDecoration( color: Color(0xff09372E), borderRadius: BorderRadius.circular(10.w), ), child: Column( children: [ SizedBox(height: 8.w), Row( children: [ SizedBox(width: 10.w), GestureDetector( child: Icon(Icons.close, color: Colors.white, size: 20.w), onTap: () { SmartDialog.dismiss(tag: "showInputBioHobby"); }, ), Spacer(), text( SCAppLocalizations.of(context)!.pleaseEnterContent, textColor: Colors.white, fontSize: 15.sp, ), Spacer(), SizedBox(width: 20.w), ], ), SizedBox(height: 15.w), Container( margin: EdgeInsets.symmetric(horizontal: 25.w), padding: EdgeInsets.only(top: 5.w, left: 12.w, right: 12.w), alignment: AlignmentDirectional.centerStart, height: 110.w, width: ScreenUtil().screenWidth, decoration: BoxDecoration( color: Colors.black26, borderRadius: BorderRadius.all(Radius.circular(8.w)), border: Border.all(color: Color(0xffE6E6E6), width: 0.5.w), ), child: TextField( controller: inputController, maxLines: 5, inputFormatters: [ SCAccurateLengthLimitingTextInputFormatter(50), ], decoration: InputDecoration( isDense: true, hintText: "", hintStyle: TextStyle(color: Colors.white, fontSize: 14.sp), contentPadding: EdgeInsets.only(top: 0.w), counterText: '', filled: false, focusColor: Colors.transparent, hoverColor: Colors.transparent, border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, disabledBorder: InputBorder.none, errorBorder: InputBorder.none, focusedErrorBorder: InputBorder.none, // enabledBorder: UnderlineInputBorder( // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), // focusedBorder: UnderlineInputBorder( // borderSide: BorderSide(width: 0.5,color: Colors.white,style: BorderStyle.solid),), // prefixIcon: Padding(padding: EdgeInsets.all(8.w), child: Image.asset("images/login/sc_icon_phone.png",width: 20.w, height: 20.w,fit: BoxFit.fill,),), fillColor: Colors.white54, ), style: TextStyle( fontSize: 15.w, color: Colors.white, textBaseline: TextBaseline.alphabetic, ), ), ), SizedBox(height: 15.w), SCDebounceWidget( child: Container( alignment: Alignment.center, margin: EdgeInsets.symmetric(horizontal: 60.w), height: 42.w, decoration: BoxDecoration( borderRadius: BorderRadius.circular(20.w), color: SocialChatTheme.primaryLight, ), child: text( SCAppLocalizations.of(context)!.confirm, textColor: Colors.white, fontSize: 16.sp, fontWeight: FontWeight.w600, ), ), onTap: () { SmartDialog.dismiss(tag: "showInputBioHobby"); if (type == 1) { if (inputController.text == autograph) { return; } autograph = inputController.text; setState(() {}); submit(autographValue: autograph); } else if (type == 2) { if (inputController.text == hobby) { return; } hobby = inputController.text; setState(() {}); submit(hobbyValue: hobby); } else if (type == 3) { if (inputController.text == nickName) { return; } nickName = inputController.text; setState(() {}); submit(userNicknameValue: nickName); } }, ), ], ), ); }, ); } }