This commit is contained in:
NIGGER SLAYER 2026-04-14 19:21:53 +08:00
parent a6fe7c7c48
commit 37a04d5ad7
8 changed files with 1328 additions and 1108 deletions

View File

@ -1,18 +1,18 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:yumi/app_localizations.dart'; import 'package:yumi/app_localizations.dart';
import 'package:yumi/ui_kit/components/sc_compontent.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/text/sc_text.dart';
import 'package:yumi/ui_kit/components/sc_tts.dart'; import 'package:yumi/ui_kit/components/sc_tts.dart';
import 'package:yumi/shared/tools/sc_loading_manager.dart'; import 'package:yumi/shared/tools/sc_loading_manager.dart';
import 'package:yumi/shared/tools/sc_pick_utils.dart'; import 'package:yumi/shared/tools/sc_pick_utils.dart';
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart'; import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
import 'package:yumi/services/auth/authentication_manager.dart'; import 'package:yumi/services/auth/authentication_manager.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:yumi/app/constants/sc_screen.dart'; import 'package:yumi/app/constants/sc_screen.dart';
import 'package:yumi/app/routes/sc_routes.dart'; import 'package:yumi/app/routes/sc_routes.dart';
import 'package:yumi/app/routes/sc_fluro_navigator.dart'; import 'package:yumi/app/routes/sc_fluro_navigator.dart';
import 'package:yumi/shared/tools/sc_date_utils.dart'; import 'package:yumi/shared/tools/sc_date_utils.dart';
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart'; import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
import 'package:yumi/shared/business_logic/models/res/login_res.dart'; import 'package:yumi/shared/business_logic/models/res/login_res.dart';
@ -21,9 +21,9 @@ import 'package:yumi/services/auth/user_profile_manager.dart';
import 'package:yumi/services/audio/rtc_manager.dart'; import 'package:yumi/services/audio/rtc_manager.dart';
import 'package:yumi/services/audio/rtm_manager.dart'; import 'package:yumi/services/audio/rtm_manager.dart';
import 'package:yumi/app/constants/sc_global_config.dart'; import 'package:yumi/app/constants/sc_global_config.dart';
import '../../../shared/business_logic/usecases/sc_custom_filtering_textinput_formatter.dart'; import '../../../shared/business_logic/usecases/sc_custom_filtering_textinput_formatter.dart';
import '../../country/country_route.dart'; import '../../country/country_route.dart';
/// ///
class SCEditProfilePage extends StatefulWidget { class SCEditProfilePage extends StatefulWidget {
@ -33,7 +33,7 @@ class SCEditProfilePage extends StatefulWidget {
_SCEditProfilePageState createState() => _SCEditProfilePageState(); _SCEditProfilePageState createState() => _SCEditProfilePageState();
} }
class _SCEditProfilePageState extends State<SCEditProfilePage> { class _SCEditProfilePageState extends State<SCEditProfilePage> {
/// ///
int type = 0; int type = 0;
DateTime birthdayDate = DateTime(2006); DateTime birthdayDate = DateTime(2006);
@ -484,7 +484,7 @@ class _SCEditProfilePageState extends State<SCEditProfilePage> {
); );
} }
DateTime _getBefor18() { DateTime _getBefor18() {
DateTime currentDate = DateTime.now(); DateTime currentDate = DateTime.now();
DateTime eighteenYearsAgo = DateTime( DateTime eighteenYearsAgo = DateTime(
currentDate.year - 18, currentDate.year - 18,
@ -496,31 +496,49 @@ class _SCEditProfilePageState extends State<SCEditProfilePage> {
currentDate.millisecond, currentDate.millisecond,
currentDate.microsecond, currentDate.microsecond,
); );
return eighteenYearsAgo; return eighteenYearsAgo;
} }
String? _preferNonEmpty(String? primary, String? fallback) { String? _preferNonEmpty(String? primary, String? fallback) {
if (primary != null && primary.isNotEmpty) { if (primary != null && primary.isNotEmpty) {
return primary; return primary;
} }
if (fallback != null && fallback.isNotEmpty) { if (fallback != null && fallback.isNotEmpty) {
return fallback; return fallback;
} }
return primary ?? fallback; return primary ?? fallback;
} }
num? _preferNonZero(num? primary, num? fallback) { bool _isBrokenLocalMediaUrl(String? url) {
if (primary != null && primary != 0) { return (url ?? "").contains("/external/oss/local/");
return primary; }
}
if (fallback != null && fallback != 0) { String? _preferUsableAvatar(String? primary, String? fallback) {
return fallback; if (primary != null &&
} primary.isNotEmpty &&
return primary ?? fallback; !_isBrokenLocalMediaUrl(primary)) {
} return primary;
}
/// if (fallback != null &&
void _submitUserProfile() async { fallback.isNotEmpty &&
!_isBrokenLocalMediaUrl(fallback)) {
return fallback;
}
return _preferNonEmpty(primary, fallback);
}
num? _preferNonZero(num? primary, num? fallback) {
if (primary != null && primary != 0) {
return primary;
}
if (fallback != null && fallback != 0) {
return fallback;
}
return primary ?? fallback;
}
///
void _submitUserProfile() async {
if (nicknameController.text.isEmpty) { if (nicknameController.text.isEmpty) {
SCTts.show(SCAppLocalizations.of(context)!.pleaseEnterNickname); SCTts.show(SCAppLocalizations.of(context)!.pleaseEnterNickname);
return; return;
@ -555,67 +573,67 @@ class _SCEditProfilePageState extends State<SCEditProfilePage> {
context, context,
listen: false, listen: false,
).uid; ).uid;
SocialChatLoginRes user = await SCAccountRepository().regist( SocialChatLoginRes user = await SCAccountRepository().regist(
authType, authType,
idToken, idToken,
userProvider!.editUser!, userProvider!.editUser!,
); );
final submittedProfile = userProvider?.editUser; final submittedProfile = userProvider?.editUser;
if (submittedProfile != null) { if (submittedProfile != null) {
final mergedProfile = final mergedProfile = (user.userProfile ?? SocialChatUserProfile())
(user.userProfile ?? SocialChatUserProfile()).copyWith( .copyWith(
userAvatar: _preferNonEmpty( userAvatar: _preferUsableAvatar(
user.userProfile?.userAvatar, user.userProfile?.userAvatar,
submittedProfile.userAvatar, submittedProfile.userAvatar,
), ),
userNickname: _preferNonEmpty( userNickname: _preferNonEmpty(
user.userProfile?.userNickname, user.userProfile?.userNickname,
submittedProfile.userNickname, submittedProfile.userNickname,
), ),
userSex: _preferNonZero( userSex: _preferNonZero(
user.userProfile?.userSex, user.userProfile?.userSex,
submittedProfile.userSex, submittedProfile.userSex,
), ),
bornYear: _preferNonZero( bornYear: _preferNonZero(
user.userProfile?.bornYear, user.userProfile?.bornYear,
submittedProfile.bornYear, submittedProfile.bornYear,
), ),
bornMonth: _preferNonZero( bornMonth: _preferNonZero(
user.userProfile?.bornMonth, user.userProfile?.bornMonth,
submittedProfile.bornMonth, submittedProfile.bornMonth,
), ),
bornDay: _preferNonZero( bornDay: _preferNonZero(
user.userProfile?.bornDay, user.userProfile?.bornDay,
submittedProfile.bornDay, submittedProfile.bornDay,
), ),
age: _preferNonZero(user.userProfile?.age, submittedProfile.age), age: _preferNonZero(user.userProfile?.age, submittedProfile.age),
countryCode: _preferNonEmpty( countryCode: _preferNonEmpty(
user.userProfile?.countryCode, user.userProfile?.countryCode,
submittedProfile.countryCode, submittedProfile.countryCode,
), ),
countryId: _preferNonEmpty( countryId: _preferNonEmpty(
user.userProfile?.countryId, user.userProfile?.countryId,
submittedProfile.countryId, submittedProfile.countryId,
), ),
countryName: _preferNonEmpty( countryName: _preferNonEmpty(
user.userProfile?.countryName, user.userProfile?.countryName,
submittedProfile.countryName, submittedProfile.countryName,
), ),
); );
user.setUserProfile(mergedProfile); user.setUserProfile(mergedProfile);
} }
if (!mounted) { if (!mounted) {
return; return;
} }
await Provider.of<RtcProvider>(context, listen: false).resetLocalRoomState( await Provider.of<RtcProvider>(context, listen: false).resetLocalRoomState(
fallbackRtmProvider: Provider.of<RtmProvider>(context, listen: false), fallbackRtmProvider: Provider.of<RtmProvider>(context, listen: false),
); );
if (!mounted) { if (!mounted) {
return; return;
} }
AccountStorage().setCurrentUser(user); AccountStorage().setCurrentUser(user);
userProvider?.syncCurrentUserProfile(user.userProfile); userProvider?.syncCurrentUserProfile(user.userProfile);
SCLoadingManager.hide(); SCLoadingManager.hide();
SCNavigatorUtils.push(context, SCRoutes.home, clearStack: true); SCNavigatorUtils.push(context, SCRoutes.home, clearStack: true);
} }
} }

View File

@ -14,9 +14,11 @@ import 'package:yumi/ui_kit/components/appbar/socialchat_appbar.dart';
import 'package:yumi/ui_kit/components/dialog/dialog_base.dart'; import 'package:yumi/ui_kit/components/dialog/dialog_base.dart';
import 'package:yumi/app/constants/sc_screen.dart'; import 'package:yumi/app/constants/sc_screen.dart';
import 'package:yumi/app/routes/sc_fluro_navigator.dart'; import 'package:yumi/app/routes/sc_fluro_navigator.dart';
import 'package:yumi/app/constants/sc_room_msg_type.dart';
import 'package:yumi/shared/tools/sc_pick_utils.dart'; import 'package:yumi/shared/tools/sc_pick_utils.dart';
import 'package:yumi/services/room/rc_room_manager.dart'; import 'package:yumi/services/room/rc_room_manager.dart';
import 'package:yumi/services/audio/rtm_manager.dart'; import 'package:yumi/services/audio/rtm_manager.dart';
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
import '../../../shared/tools/sc_lk_dialog_util.dart'; import '../../../shared/tools/sc_lk_dialog_util.dart';
import '../../../shared/data_sources/models/enum/sc_room_info_event_type.dart'; import '../../../shared/data_sources/models/enum/sc_room_info_event_type.dart';
import '../../../shared/business_logic/usecases/sc_accurate_length_limiting_textInput_formatter.dart'; import '../../../shared/business_logic/usecases/sc_accurate_length_limiting_textInput_formatter.dart';
@ -545,6 +547,21 @@ class _RoomEditPageState extends State<RoomEditPage> {
roomDesc: mergedRoomInfo.roomDesc, roomDesc: mergedRoomInfo.roomDesc,
); );
roomManager.updateMyRoomInfo(mergedRoomInfo); roomManager.updateMyRoomInfo(mergedRoomInfo);
if (widget.needRestCurrentRoomInfo != "true") {
currentRtmProvider.dispatchMessage(
Msg(
groupId:
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount ??
"",
msg:
mergedRoomInfo.id ??
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ??
"",
type: SCRoomMsgType.roomSettingUpdate,
),
addLocal: false,
);
}
if (widget.needRestCurrentRoomInfo == "true") { if (widget.needRestCurrentRoomInfo == "true") {
/// ///
var c = await currentRtmProvider.createRoomGroup( var c = await currentRtmProvider.createRoomGroup(

View File

@ -85,6 +85,31 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
super.dispose(); super.dispose();
} }
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);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Stack( return Stack(
@ -123,10 +148,13 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
children: [ children: [
netImage( netImage(
url: url:
UserManager() _preferNonEmpty(
.getCurrentUser() userCover,
?.userProfile UserManager()
?.userAvatar ?? .getCurrentUser()
?.userProfile
?.userAvatar,
) ??
"", "",
width: 45.w, width: 45.w,
height: 45.w, height: 45.w,
@ -143,9 +171,15 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
], ],
), ),
onTap: () { onTap: () {
SCPickUtils.pickImage(context, (bool success, String url) { SCPickUtils.pickImage(context, (
bool success,
String url,
) {
if (success) { if (success) {
submit(context); debugPrint("[Profile Avatar] uploaded url: $url");
userCover = url;
setState(() {});
submitAvatarOnly(context);
} }
}); });
}, },
@ -153,8 +187,7 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
SizedBox(height: 3.w), SizedBox(height: 3.w),
_buildItem( _buildItem(
"${SCAppLocalizations.of(context)!.userName}:", "${SCAppLocalizations.of(context)!.userName}:",
nickName ?? nickName,
"",
() { () {
_showInputBioHobby(nickName, 3); _showInputBioHobby(nickName, 3);
}, },
@ -315,6 +348,50 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
int sTime = 0; int sTime = 0;
void submitAvatarOnly(BuildContext context) async {
if ((userCover ?? "").trim().isEmpty) {
return;
}
int bTime = DateTime.now().millisecondsSinceEpoch;
if (bTime - sTime <= 5000) {
return;
}
sTime = bTime;
SCLoadingManager.show();
debugPrint("[Profile Avatar] submit avatar only: {userAvatar: $userCover}");
try {
final updatedProfile = await SCAccountRepository().updateUserInfo(
userAvatar: userCover,
);
final mergedProfile = updatedProfile.copyWith(
userAvatar: _preferUsableAvatar(updatedProfile.userAvatar, userCover),
);
debugPrint(
"[Profile Avatar] merged avatar-only profile avatar: ${mergedProfile.userAvatar ?? ""}",
);
userCover = mergedProfile.userAvatar;
if (!mounted) {
SCLoadingManager.hide();
return;
}
Provider.of<SocialChatUserProfileManager>(
context,
listen: false,
).syncCurrentUserProfile(mergedProfile);
Provider.of<RtcProvider>(context, listen: false).needUpDataUserInfo =
true;
SCTts.show(SCAppLocalizations.of(context)!.operationSuccessful);
} catch (e) {
debugPrint(e.toString());
SCLoadingManager.hide();
return;
}
SCLoadingManager.hide();
if (mounted) {
setState(() {});
}
}
void submit(BuildContext context) async { void submit(BuildContext context) async {
if (nickName.isEmpty) { if (nickName.isEmpty) {
SCTts.show(SCAppLocalizations.of(context)!.pleaseEnterNickname); SCTts.show(SCAppLocalizations.of(context)!.pleaseEnterNickname);
@ -323,40 +400,67 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
int bTime = DateTime.now().millisecondsSinceEpoch; int bTime = DateTime.now().millisecondsSinceEpoch;
if (bTime - sTime > 5000) { if (bTime - sTime > 5000) {
sTime = bTime; sTime = bTime;
SCLoadingManager.show(); SCLoadingManager.show();
try { debugPrint(
final updatedProfile = await SCAccountRepository().updateUserInfo( "[Profile Avatar] submit payload: {userAvatar: $userCover, userNickname: $nickName, userSex: $sex, age: $age, bornYear: ${birthdayDate?.year}, bornMonth: ${birthdayDate?.month}, bornDay: ${birthdayDate?.day}, countryId: ${country?.id}, hobby: $hobby, autograph: $autograph}",
userAvatar: userCover, );
userSex: sex, try {
userNickname: nickName, final updatedProfile = await SCAccountRepository().updateUserInfo(
age: age, userAvatar: userCover,
userSex: sex,
userNickname: nickName,
age: age,
bornDay: birthdayDate?.day, bornDay: birthdayDate?.day,
bornMonth: birthdayDate?.month, bornMonth: birthdayDate?.month,
bornYear: birthdayDate?.year, bornYear: birthdayDate?.year,
hobby: hobby, hobby: hobby,
autograph: autograph, autograph: autograph,
countryId: country?.id, countryId: country?.id,
); );
if (!mounted) { final mergedProfile = updatedProfile.copyWith(
SCLoadingManager.hide(); userAvatar: _preferUsableAvatar(updatedProfile.userAvatar, userCover),
return; userNickname: _preferNonEmpty(updatedProfile.userNickname, nickName),
} autograph: _preferNonEmpty(updatedProfile.autograph, autograph),
Provider.of<SocialChatUserProfileManager>( hobby: _preferNonEmpty(updatedProfile.hobby, hobby),
context, countryName: _preferNonEmpty(
listen: false, updatedProfile.countryName,
).syncCurrentUserProfile(updatedProfile); country?.countryName,
Provider.of<RtcProvider>(context, listen: false).needUpDataUserInfo = ),
true; countryCode: _preferNonEmpty(
SCTts.show(SCAppLocalizations.of(context)!.operationSuccessful); updatedProfile.countryCode,
} catch (e) { country?.alphaTwo,
),
countryId: _preferNonEmpty(updatedProfile.countryId, country?.id),
userSex: updatedProfile.userSex ?? sex,
age: updatedProfile.age ?? age,
bornDay: updatedProfile.bornDay ?? birthdayDate?.day,
bornMonth: updatedProfile.bornMonth ?? birthdayDate?.month,
bornYear: updatedProfile.bornYear ?? birthdayDate?.year,
);
debugPrint(
"[Profile Avatar] merged profile avatar: ${mergedProfile.userAvatar ?? ""}",
);
userCover = mergedProfile.userAvatar;
if (!mounted) {
SCLoadingManager.hide();
return;
}
Provider.of<SocialChatUserProfileManager>(
context,
listen: false,
).syncCurrentUserProfile(mergedProfile);
Provider.of<RtcProvider>(context, listen: false).needUpDataUserInfo =
true;
SCTts.show(SCAppLocalizations.of(context)!.operationSuccessful);
} catch (e) {
debugPrint(e.toString()); debugPrint(e.toString());
SCLoadingManager.hide(); SCLoadingManager.hide();
return;
} }
Provider.of<SocialChatUserProfileManager>(
context,
listen: false,
).fetchUserProfileData();
SCLoadingManager.hide(); SCLoadingManager.hide();
if (mounted) {
setState(() {});
}
} }
} }
@ -451,11 +555,7 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
Expanded( Expanded(
child: Container( child: Container(
alignment: AlignmentDirectional.centerEnd, alignment: AlignmentDirectional.centerEnd,
child: text( child: text(value, textColor: Colors.white, fontSize: 15.sp),
value,
textColor: Colors.white,
fontSize: 15.sp,
),
), ),
), ),
Icon( Icon(
@ -530,20 +630,22 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
], ],
), ),
onTap: () { onTap: () {
SCNavigatorUtils.push(context, CountryRoute.country, replace: false).then( SCNavigatorUtils.push(
(res) { context,
var c = CountryRoute.country,
Provider.of<SCAppGeneralManager>( replace: false,
navigatorKey.currentState!.context, ).then((res) {
listen: false, var c =
).selectCountryInfo; Provider.of<SCAppGeneralManager>(
if (c != null) { navigatorKey.currentState!.context,
country = c; listen: false,
submit(navigatorKey.currentState!.context); ).selectCountryInfo;
setState(() {}); if (c != null) {
} country = c;
}, submit(navigatorKey.currentState!.context);
); setState(() {});
}
});
}, },
); );
} }
@ -608,10 +710,7 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
decoration: InputDecoration( decoration: InputDecoration(
isDense: true, isDense: true,
hintText: "", hintText: "",
hintStyle: TextStyle( hintStyle: TextStyle(color: Colors.white, fontSize: 14.sp),
color: Colors.white,
fontSize: 14.sp,
),
contentPadding: EdgeInsets.only(top: 0.w), contentPadding: EdgeInsets.only(top: 0.w),
counterText: '', counterText: '',
filled: false, filled: false,
@ -662,7 +761,7 @@ class _EditUserInfoPage2State extends State<EditUserInfoPage2>
} else if (type == 2) { } else if (type == 2) {
hobby = _inputController.text; hobby = _inputController.text;
setState(() {}); setState(() {});
}else if (type == 3) { } else if (type == 3) {
nickName = _inputController.text; nickName = _inputController.text;
setState(() {}); setState(() {});
} }

View File

@ -21,6 +21,8 @@ class SCGeneralRepositoryImp implements SocialChatGeneralRepository {
filePath.lastIndexOf("/") + 1, filePath.lastIndexOf("/") + 1,
filePath.length, filePath.length,
); );
debugPrint("[上传头像图片地址] filePath: $filePath");
debugPrint("[上传头像图片] fileName: $name");
FormData formData = FormData.fromMap({ FormData formData = FormData.fromMap({
"file": await MultipartFile.fromFile(filePath, filename: name), "file": await MultipartFile.fromFile(filePath, filename: name),
}); });
@ -38,8 +40,8 @@ class SCGeneralRepositoryImp implements SocialChatGeneralRepository {
response.data as Map<String, dynamic>, response.data as Map<String, dynamic>,
fromJsonT: (json) => json as String, fromJsonT: (json) => json as String,
); );
debugPrint("[OSS Upload] response.data: ${response.data}"); debugPrint("[返回值] response.data: ${response.data}");
debugPrint("[OSS Upload] parsed file url: ${baseResponse.body ?? ""}"); debugPrint("[返回图片地址] parsed file url: ${baseResponse.body ?? ""}");
return baseResponse.body ?? ""; return baseResponse.body ?? "";
} }
} }

View File

@ -14,7 +14,6 @@ import 'package:yumi/shared/business_logic/models/res/sc_room_reward_info_res.da
import 'package:yumi/shared/business_logic/models/res/sc_room_rocket_config_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_room_rocket_config_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_room_rocket_status_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_room_rocket_status_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_room_task_list_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_room_task_list_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_top_four_with_reward_res.dart';
import 'package:yumi/shared/business_logic/models/res/user_count_guard_res.dart'; import 'package:yumi/shared/business_logic/models/res/user_count_guard_res.dart';
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart'; import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -45,6 +44,45 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
return _instance ??= SCChatRoomRepository._internal(); return _instance ??= SCChatRoomRepository._internal();
} }
bool _isBrokenLocalMediaUrl(String? url) {
return (url ?? "").contains("/external/oss/local/");
}
bool _shouldHydrateRoomProfile(String? roomId, String? roomCover) {
return (roomId ?? "").trim().isNotEmpty &&
((roomCover ?? "").trim().isEmpty || _isBrokenLocalMediaUrl(roomCover));
}
Future<SocialChatRoomRes> _hydrateRoomRes(SocialChatRoomRes room) async {
final cachedRoom = SCRoomProfileCache.applyToSocialChatRoomRes(room);
if (!_shouldHydrateRoomProfile(cachedRoom.id, cachedRoom.roomCover)) {
return cachedRoom;
}
debugPrint(
"[Room Cover][room-list] hydrate roomId=${cachedRoom.id} remoteCover=${cachedRoom.roomCover ?? ""}",
);
final specificRoom = await specific(cachedRoom.id ?? "");
debugPrint(
"[Room Cover][room-list] specific roomId=${cachedRoom.id} specificCover=${specificRoom.roomCover ?? ""}",
);
return SCRoomProfileCache.applyToSocialChatRoomRes(
cachedRoom.copyWith(
roomCover: SCRoomProfileCache.preferNonEmpty(
specificRoom.roomCover,
cachedRoom.roomCover,
),
roomName: SCRoomProfileCache.preferNonEmpty(
specificRoom.roomName,
cachedRoom.roomName,
),
roomDesc: SCRoomProfileCache.preferNonEmpty(
specificRoom.roomDesc,
cachedRoom.roomDesc,
),
),
);
}
///room/live-voice/discovery ///room/live-voice/discovery
@override @override
Future<List<SocialChatRoomRes>> discovery({bool? allRegion}) async { Future<List<SocialChatRoomRes>> discovery({bool? allRegion}) async {
@ -59,7 +97,7 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
(json) => (json) =>
(json as List).map((e) => SocialChatRoomRes.fromJson(e)).toList(), (json as List).map((e) => SocialChatRoomRes.fromJson(e)).toList(),
); );
return result.map(SCRoomProfileCache.applyToSocialChatRoomRes).toList(); return Future.wait(result.map(_hydrateRoomRes));
} }
///live/mic/list ///live/mic/list
@ -86,6 +124,15 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
queryParams: queryParams, queryParams: queryParams,
fromJson: (json) => MyRoomRes.fromJson(json), fromJson: (json) => MyRoomRes.fromJson(json),
); );
debugPrint(
"[Room Cover][specific] roomId=${result.id ?? roomId} roomCover=${result.roomCover ?? ""} roomName=${result.roomName ?? ""}",
);
await SCRoomProfileCache.saveRoomProfile(
roomId: result.id ?? roomId,
roomCover: result.roomCover,
roomName: result.roomName,
roomDesc: result.roomDesc,
);
return SCRoomProfileCache.applyToMyRoomRes(result); return SCRoomProfileCache.applyToMyRoomRes(result);
} }
@ -328,7 +375,7 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
(json) => (json) =>
(json as List).map((e) => SocialChatRoomRes.fromJson(e)).toList(), (json as List).map((e) => SocialChatRoomRes.fromJson(e)).toList(),
); );
return result.map(SCRoomProfileCache.applyToSocialChatRoomRes).toList(); return Future.wait(result.map(_hydrateRoomRes));
} }
///room/blacklist/join ///room/blacklist/join

File diff suppressed because it is too large Load Diff

View File

@ -127,41 +127,38 @@ class _MsgItemState extends State<MsgItem> {
Flexible( Flexible(
// 使Flexible替代Expanded // 使Flexible替代Expanded
child: GestureDetector( child: GestureDetector(
child:Container( child: Container(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: maxWidth: ScreenUtil().screenWidth * 0.7, //
ScreenUtil().screenWidth * 0.7, // ),
), decoration: BoxDecoration(
decoration: BoxDecoration( color: Colors.black38,
color: Colors.black38, borderRadius: BorderRadius.only(
borderRadius: BorderRadius.only( topRight: Radius.circular(15.w),
topRight: Radius.circular(15.w), bottomLeft: Radius.circular(15.w),
bottomLeft: Radius.circular(15.w), bottomRight: Radius.circular(15.w),
bottomRight: Radius.circular(15.w), ),
), ),
), padding: EdgeInsets.symmetric(
padding: EdgeInsets.symmetric( horizontal: 10.w,
horizontal: 10.w, vertical: 4.w,
vertical: 4.w, ),
), margin: EdgeInsetsDirectional.only(
margin: EdgeInsetsDirectional.only( start: 54.w,
start: 54.w, ).copyWith(bottom: 8.w),
).copyWith(bottom: 8.w), child: ExtendedText(
child: ExtendedText( widget.msg?.msg ?? '',
widget.msg?.msg ?? '', style: TextStyle(
style: TextStyle( fontSize: sp(13),
fontSize: sp(13), color: Color(_msgColor(widget.msg.type ?? "")),
color: Color( fontWeight: FontWeight.w500,
_msgColor(widget.msg.type ?? ""), ),
), softWrap: true, //
fontWeight: FontWeight.w500, specialTextSpanBuilder: AtTextSpanBuilder(
), onTapCall: (userId) {},
softWrap: true, // ),
specialTextSpanBuilder: AtTextSpanBuilder( ),
onTapCall: (userId) {}, ),
),
),
),
onLongPress: () { onLongPress: () {
if (!(widget.msg?.msg ?? "").startsWith(AtText.flag)) { if (!(widget.msg?.msg ?? "").startsWith(AtText.flag)) {
Clipboard.setData( Clipboard.setData(
@ -568,68 +565,66 @@ class _MsgItemState extends State<MsgItem> {
children: [ children: [
Flexible( Flexible(
// 使Flexible替代Expanded // 使Flexible替代Expanded
child:Container( child: Container(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: ScreenUtil().screenWidth * 0.7, // maxWidth: ScreenUtil().screenWidth * 0.7, //
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black38, color: Colors.black38,
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topRight: Radius.circular(15.w), topRight: Radius.circular(15.w),
bottomLeft: Radius.circular(15.w), bottomLeft: Radius.circular(15.w),
bottomRight: Radius.circular(15.w), bottomRight: Radius.circular(15.w),
), ),
), ),
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: 10.w, horizontal: 10.w,
vertical: 4.w, vertical: 4.w,
), ),
margin: EdgeInsetsDirectional.only( margin: EdgeInsetsDirectional.only(
start: 54.w, start: 54.w,
).copyWith(bottom: 8.w), ).copyWith(bottom: 8.w),
child: Text.rich( child: Text.rich(
TextSpan( TextSpan(
children: [ children: [
TextSpan( TextSpan(
text: SCAppLocalizations.of(context)!.sendTo, text: SCAppLocalizations.of(context)!.sendTo,
style: TextStyle( style: TextStyle(
fontSize: 13.sp, fontSize: 13.sp,
color: Colors.white, color: Colors.white,
),
),
TextSpan(text: " "),
TextSpan(
text: widget.msg.toUser?.userNickname ?? "",
style: TextStyle(
fontSize: 13.sp,
color: SocialChatTheme.primaryColor,
),
recognizer:
TapGestureRecognizer()..onTap = () {},
),
TextSpan(text: " "),
WidgetSpan(
child: netImage(
url: widget.msg.gift?.giftPhoto ?? "",
width: 22.w,
height: 22.w,
),
),
TextSpan(text: " "),
TextSpan(
text: "x${widget.msg.number}",
style: TextStyle(
fontSize: 13.sp,
color: Colors.white,
),
recognizer:
TapGestureRecognizer()..onTap = () {},
),
],
),
textAlign: TextAlign.left,
), ),
), ),
TextSpan(text: " "),
TextSpan(
text: widget.msg.toUser?.userNickname ?? "",
style: TextStyle(
fontSize: 13.sp,
color: SocialChatTheme.primaryColor,
),
recognizer: TapGestureRecognizer()..onTap = () {},
),
TextSpan(text: " "),
WidgetSpan(
child: netImage(
url: widget.msg.gift?.giftPhoto ?? "",
width: 22.w,
height: 22.w,
),
),
TextSpan(text: " "),
TextSpan(
text: "x${widget.msg.number}",
style: TextStyle(
fontSize: 13.sp,
color: Colors.white,
),
recognizer: TapGestureRecognizer()..onTap = () {},
),
],
),
textAlign: TextAlign.left,
),
),
), ),
], ],
), ),
@ -829,28 +824,27 @@ class _MsgItemState extends State<MsgItem> {
Flexible( Flexible(
// 使Flexible替代Expanded // 使Flexible替代Expanded
child: GestureDetector( child: GestureDetector(
child:Container( child: Container(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: maxWidth: ScreenUtil().screenWidth * 0.5, //
ScreenUtil().screenWidth * 0.5, // ),
), decoration: BoxDecoration(
decoration: BoxDecoration( color: Colors.black38,
color: Colors.black38, borderRadius: BorderRadius.only(
borderRadius: BorderRadius.only( topRight: Radius.circular(15.w),
topRight: Radius.circular(15.w), bottomLeft: Radius.circular(15.w),
bottomLeft: Radius.circular(15.w), bottomRight: Radius.circular(15.w),
bottomRight: Radius.circular(15.w), ),
), ),
), padding: EdgeInsets.symmetric(
padding: EdgeInsets.symmetric( horizontal: 10.w,
horizontal: 10.w, vertical: 4.w,
vertical: 4.w, ),
), margin: EdgeInsetsDirectional.only(
margin: EdgeInsetsDirectional.only( start: 54.w,
start: 54.w, ).copyWith(bottom: 8.w),
).copyWith(bottom: 8.w), child: netImage(url: widget.msg.msg ?? ""),
child: netImage(url: widget.msg.msg ?? ""), ),
),
onTap: () { onTap: () {
String encodedUrls = Uri.encodeComponent( String encodedUrls = Uri.encodeComponent(
jsonEncode([widget.msg.msg]), jsonEncode([widget.msg.msg]),
@ -890,59 +884,57 @@ class _MsgItemState extends State<MsgItem> {
Flexible( Flexible(
// 使Flexible替代Expanded // 使Flexible替代Expanded
child: GestureDetector( child: GestureDetector(
child:Container( child: Container(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: maxWidth: ScreenUtil().screenWidth * 0.5, //
ScreenUtil().screenWidth * 0.5, // ),
), decoration: BoxDecoration(
decoration: BoxDecoration( color: Colors.black38,
color: Colors.black38, borderRadius: BorderRadius.only(
borderRadius: BorderRadius.only( topRight: Radius.circular(15.w),
topRight: Radius.circular(15.w), bottomLeft: Radius.circular(15.w),
bottomLeft: Radius.circular(15.w), bottomRight: Radius.circular(15.w),
bottomRight: Radius.circular(15.w), ),
),
padding: EdgeInsets.symmetric(
horizontal: 10.w,
vertical: 4.w,
),
margin: EdgeInsetsDirectional.only(
start: 54.w,
).copyWith(bottom: 8.w),
child:
(widget.msg.hasPlayedAnimation ?? false)
? Image.asset(
"sc_images/room/sc_icon_dice_${widget.msg.msg}.png",
height: 35.w,
)
: FutureBuilder<void>(
future: Future.delayed(
Duration(milliseconds: 2000),
), ),
// Future
builder: (
BuildContext context,
AsyncSnapshot<void> snapshot,
) {
// snapshot的状态来构建UI
if (snapshot.connectionState ==
ConnectionState.done) {
widget.msg.hasPlayedAnimation = true;
return Image.asset(
"sc_images/room/sc_icon_dice_${widget.msg.msg}.png",
height: 35.w,
);
} else {
return Image.asset(
"sc_images/room/sc_icon_dice_animl.webp",
height: 35.w,
);
}
},
), ),
padding: EdgeInsets.symmetric( ),
horizontal: 10.w,
vertical: 4.w,
),
margin: EdgeInsetsDirectional.only(
start: 54.w,
).copyWith(bottom: 8.w),
child:
(widget.msg.hasPlayedAnimation ?? false)
? Image.asset(
"sc_images/room/sc_icon_dice_${widget.msg.msg}.png",
height: 35.w,
)
: FutureBuilder<void>(
future: Future.delayed(
Duration(milliseconds: 2000),
),
// Future
builder: (
BuildContext context,
AsyncSnapshot<void> snapshot,
) {
// snapshot的状态来构建UI
if (snapshot.connectionState ==
ConnectionState.done) {
widget.msg.hasPlayedAnimation =
true;
return Image.asset(
"sc_images/room/sc_icon_dice_${widget.msg.msg}.png",
height: 35.w,
);
} else {
return Image.asset(
"sc_images/room/sc_icon_dice_animl.webp",
height: 35.w,
);
}
},
),
),
onTap: () {}, onTap: () {},
), ),
), ),
@ -974,59 +966,57 @@ class _MsgItemState extends State<MsgItem> {
Flexible( Flexible(
// 使Flexible替代Expanded // 使Flexible替代Expanded
child: GestureDetector( child: GestureDetector(
child:Container( child: Container(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: maxWidth: ScreenUtil().screenWidth * 0.5, //
ScreenUtil().screenWidth * 0.5, // ),
), decoration: BoxDecoration(
decoration: BoxDecoration( color: Colors.black38,
color: Colors.black38, borderRadius: BorderRadius.only(
borderRadius: BorderRadius.only( topRight: Radius.circular(15.w),
topRight: Radius.circular(15.w), bottomLeft: Radius.circular(15.w),
bottomLeft: Radius.circular(15.w), bottomRight: Radius.circular(15.w),
bottomRight: Radius.circular(15.w), ),
),
padding: EdgeInsets.symmetric(
horizontal: 10.w,
vertical: 4.w,
),
margin: EdgeInsetsDirectional.only(
start: 54.w,
).copyWith(bottom: 8.w),
child:
(widget.msg.hasPlayedAnimation ?? false)
? Image.asset(
"sc_images/room/sc_icon_rps_${widget.msg.msg}.png",
height: 35.w,
)
: FutureBuilder<void>(
future: Future.delayed(
Duration(milliseconds: 2000),
), ),
// Future
builder: (
BuildContext context,
AsyncSnapshot<void> snapshot,
) {
// snapshot的状态来构建UI
if (snapshot.connectionState ==
ConnectionState.done) {
widget.msg.hasPlayedAnimation = true;
return Image.asset(
"sc_images/room/sc_icon_rps_${widget.msg.msg}.png",
height: 35.w,
);
} else {
return Image.asset(
"sc_images/room/sc_icon_rps_animal.webp",
height: 35.w,
);
}
},
), ),
padding: EdgeInsets.symmetric( ),
horizontal: 10.w,
vertical: 4.w,
),
margin: EdgeInsetsDirectional.only(
start: 54.w,
).copyWith(bottom: 8.w),
child:
(widget.msg.hasPlayedAnimation ?? false)
? Image.asset(
"sc_images/room/sc_icon_rps_${widget.msg.msg}.png",
height: 35.w,
)
: FutureBuilder<void>(
future: Future.delayed(
Duration(milliseconds: 2000),
),
// Future
builder: (
BuildContext context,
AsyncSnapshot<void> snapshot,
) {
// snapshot的状态来构建UI
if (snapshot.connectionState ==
ConnectionState.done) {
widget.msg.hasPlayedAnimation =
true;
return Image.asset(
"sc_images/room/sc_icon_rps_${widget.msg.msg}.png",
height: 35.w,
);
} else {
return Image.asset(
"sc_images/room/sc_icon_rps_animal.webp",
height: 35.w,
);
}
},
),
),
onTap: () {}, onTap: () {},
), ),
), ),
@ -1058,45 +1048,44 @@ class _MsgItemState extends State<MsgItem> {
Flexible( Flexible(
// 使Flexible替代Expanded // 使Flexible替代Expanded
child: GestureDetector( child: GestureDetector(
child:Container( child: Container(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: maxWidth: ScreenUtil().screenWidth * 0.7, //
ScreenUtil().screenWidth * 0.7, // ),
decoration: BoxDecoration(
color: Colors.black38,
borderRadius: BorderRadius.only(
topRight: Radius.circular(15.w),
bottomLeft: Radius.circular(15.w),
bottomRight: Radius.circular(15.w),
),
),
padding: EdgeInsets.symmetric(
horizontal: 10.w,
vertical: 4.w,
),
margin: EdgeInsetsDirectional.only(
start: 54.w,
).copyWith(bottom: 8.w),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SCGlobalConfig.lang == "ar"
? Image.asset(
"sc_images/room/sc_icon_luck_num_text_ar.png",
height: 20.w,
)
: Image.asset(
"sc_images/room/sc_icon_luck_num_text_en.png",
height: 20.w,
), ),
decoration: BoxDecoration( buildNumForRoomLuckNum(
color:Colors.black38, widget.msg.msg ?? "",
borderRadius: BorderRadius.only( size: 20.w,
topRight: Radius.circular(15.w), ),
bottomLeft: Radius.circular(15.w), ],
bottomRight: Radius.circular(15.w), ),
), ),
),
padding: EdgeInsets.symmetric(
horizontal: 10.w,
vertical: 4.w,
),
margin: EdgeInsetsDirectional.only(
start: 54.w,
).copyWith(bottom: 8.w),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SCGlobalConfig.lang == "ar"
? Image.asset(
"sc_images/room/sc_icon_luck_num_text_ar.png",
height: 20.w,
)
: Image.asset(
"sc_images/room/sc_icon_luck_num_text_en.png",
height: 20.w,
),
buildNumForRoomLuckNum(
widget.msg.msg ?? "",
size: 20.w,
),
],
),
),
onTap: () {}, onTap: () {},
), ),
), ),
@ -1110,7 +1099,7 @@ class _MsgItemState extends State<MsgItem> {
_buildUserInfoLine(BuildContext context) { _buildUserInfoLine(BuildContext context) {
if ((widget.msg.needUpDataUserInfo) ?? false) { if ((widget.msg.needUpDataUserInfo) ?? false) {
SCRoomUtils.roomUsersMap[widget.msg.user?.id ?? ""] == null; SCRoomUtils.roomUsersMap.remove(widget.msg.user?.id ?? "");
} }
return SCRoomUtils.roomUsersMap[widget.msg.user?.id ?? ""] == null return SCRoomUtils.roomUsersMap[widget.msg.user?.id ?? ""] == null
? FutureBuilder( ? FutureBuilder(
@ -1349,11 +1338,17 @@ class Msg {
needUpDataUserInfo = json['needUpDataUserInfo']; needUpDataUserInfo = json['needUpDataUserInfo'];
userWheat = userWheat =
json['userWheat'] != null ? MicRes.fromJson(json['userWheat']) : null; json['userWheat'] != null ? MicRes.fromJson(json['userWheat']) : null;
user = json['user'] != null ? SocialChatUserProfile.fromJson(json['user']) : null; user =
json['user'] != null
? SocialChatUserProfile.fromJson(json['user'])
: null;
toUser = toUser =
json['toUser'] != null ? SocialChatUserProfile.fromJson(json['toUser']) : null; json['toUser'] != null
? SocialChatUserProfile.fromJson(json['toUser'])
: null;
gift = json['gift'] != null ? SocialChatGiftRes.fromJson(json['gift']) : null; gift =
json['gift'] != null ? SocialChatGiftRes.fromJson(json['gift']) : null;
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {

View File

@ -19,6 +19,13 @@
- 已修复个人主页首屏黑屏与内容区多余圆角问题:为个人页补充稳定的底图/底色占位,避免进入页面时先出现黑屏;同时移除资料内容区顶部多余圆角。 - 已修复个人主页首屏黑屏与内容区多余圆角问题:为个人页补充稳定的底图/底色占位,避免进入页面时先出现黑屏;同时移除资料内容区顶部多余圆角。
- 已将个人主页首屏占位升级为骨架屏进入个人页时会先按真实版式展示头像、昵称、计数区、Tab 和资料内容的骨架块,不再先露默认背景等待约 1 秒。 - 已将个人主页首屏占位升级为骨架屏进入个人页时会先按真实版式展示头像、昵称、计数区、Tab 和资料内容的骨架块,不再先露默认背景等待约 1 秒。
- 已优化语言房顶部成员入口的点击范围:右上角在线成员区域现已将左侧头像堆叠区与右侧成员图标统一为同一点击热区,用户点击头像区也可直接打开成员列表页。 - 已优化语言房顶部成员入口的点击范围:右上角在线成员区域现已将左侧头像堆叠区与右侧成员图标统一为同一点击热区,用户点击头像区也可直接打开成员列表页。
- 已继续修复房间封面跨用户不同步问题:当列表或进房返回空封面时,客户端会自动补拉 `room/profile/specific` 详情并缓存真实封面;同时房主保存房间资料后会向房间内其他用户派发资料刷新消息,避免只有自己能看到新封面、其他用户仍显示 `no data`
- 已修复个人主页编辑页头像上传后无变化的问题:上传成功后会先本地回写新头像地址并立即提交;资料更新接口现同时兼容 `userAvatar/avatar` 字段,且不再被紧接着的旧资料回拉覆盖,返回个人主页后头像会即时更新。
- 已进一步定位图片 `no data` 根因并修复客户端加载链:日志确认房间封面详情接口可返回正确公网 `media...` 地址,但用户头像接口会返回 `jvapi.../external/oss/local/...` 这类受保护资源地址;共享图片组件现已对这类地址自动补齐鉴权请求头,避免其它用户侧加载时直接失败为 `no data`
- 已直接校验头像异常地址的服务端返回:`jvapi.../external/oss/local/...` 当前会返回 `500 local disk file does not exist`,并非单纯鉴权缺失;前端现已在编辑资料成功后与本人资料回拉时优先保留刚上传成功的公网头像,避免再次被这类坏地址覆盖,同时修复了房间消息用户资料缓存未真正失效的笔误。
- 已继续修正个人资料更新接口的提交结构:排查发现注册接口使用嵌套 `profile`,而资料更新接口此前只发扁平字段;现已改为同时提交 `id + 扁平字段 + profile`,并在 `profile` 内同步带上 `userAvatar/avatar`,用于兼容后端可能按嵌套对象取值的情况。
- 已按最新联调结果再次收窄个人资料更新参数:日志已证明后端能收到请求但会忽略新头像地址,当前已改为头像相关只提交单一 `userAvatar` 字段,并移除 `avatar/profile` 冗余结构,便于继续验证后端是否对字段名或重复参数敏感。
- 已继续将个人页头像上传链路与通用资料提交拆开:上传成功后当前会单独触发一次仅含 `userAvatar` 的更新请求,不再混带昵称、性别、年龄、国家等字段,便于排除其它参数对头像更新接口的干扰。
- 完成仓库结构、依赖引用、资源体积和 APK 组成的第一轮排查。 - 完成仓库结构、依赖引用、资源体积和 APK 组成的第一轮排查。
- 移除 4 个当前未在业务层直接使用的插件依赖:`loading_indicator_view_plus``social_sharing_plus``flutter_foreground_task``on_audio_query`,并清理相关平台声明。 - 移除 4 个当前未在业务层直接使用的插件依赖:`loading_indicator_view_plus``social_sharing_plus``flutter_foreground_task``on_audio_query`,并清理相关平台声明。
- 修补 `image_cropper 5.0.1` Android 兼容问题,切换到本地 path 依赖以恢复构建。 - 修补 `image_cropper 5.0.1` Android 兼容问题,切换到本地 path 依赖以恢复构建。