Compare commits
No commits in common. "fa516a94e142755f58611711dafe9b68db880859" and "36ba0f3573d36102927299f1135b5902ffa81c47" have entirely different histories.
fa516a94e1
...
36ba0f3573
@ -502,7 +502,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 9;
|
CURRENT_PROJECT_VERSION = 8;
|
||||||
DEVELOPMENT_TEAM = S9X2AJ2US9;
|
DEVELOPMENT_TEAM = S9X2AJ2US9;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@ -511,7 +511,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.2.7;
|
MARKETING_VERSION = 1.2.6;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@ -693,7 +693,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 9;
|
CURRENT_PROJECT_VERSION = 8;
|
||||||
DEVELOPMENT_TEAM = F33K8VUZ62;
|
DEVELOPMENT_TEAM = F33K8VUZ62;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@ -702,7 +702,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.2.7;
|
MARKETING_VERSION = 1.2.6;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@ -722,7 +722,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 9;
|
CURRENT_PROJECT_VERSION = 8;
|
||||||
DEVELOPMENT_TEAM = F33K8VUZ62;
|
DEVELOPMENT_TEAM = F33K8VUZ62;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@ -731,7 +731,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.2.7;
|
MARKETING_VERSION = 1.2.6;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
|||||||
@ -15,10 +15,8 @@ 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/tools/sc_google_auth_utils.dart';
|
|
||||||
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
|
import 'package:yumi/shared/data_sources/sources/local/data_persistence.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/data_sources/models/enum/sc_auth_type.dart';
|
|
||||||
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
||||||
import 'package:yumi/services/general/sc_app_general_manager.dart';
|
import 'package:yumi/services/general/sc_app_general_manager.dart';
|
||||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||||
@ -614,42 +612,16 @@ class _SCEditProfilePageState extends State<SCEditProfilePage> {
|
|||||||
userProvider?.updateBornDay(birthdayDate.day);
|
userProvider?.updateBornDay(birthdayDate.day);
|
||||||
num age = DateTime.now().year - birthdayDate.year;
|
num age = DateTime.now().year - birthdayDate.year;
|
||||||
userProvider?.updateAge(age.abs());
|
userProvider?.updateAge(age.abs());
|
||||||
final authProvider = Provider.of<SocialChatAuthenticationManager>(
|
String authType =
|
||||||
context,
|
Provider.of<SocialChatAuthenticationManager>(
|
||||||
listen: false,
|
context,
|
||||||
);
|
listen: false,
|
||||||
String authType = authProvider.authType.trim();
|
).authType;
|
||||||
String idToken = authProvider.uid.trim();
|
String idToken =
|
||||||
if (authType.isEmpty || idToken.isEmpty) {
|
Provider.of<SocialChatAuthenticationManager>(
|
||||||
final cachedAuthType = DataPersistence.getPendingChannelAuthType().trim();
|
context,
|
||||||
final cachedOpenId = DataPersistence.getPendingChannelAuthOpenId().trim();
|
listen: false,
|
||||||
if (authType.isEmpty) {
|
).uid;
|
||||||
authType = cachedAuthType;
|
|
||||||
}
|
|
||||||
if (idToken.isEmpty) {
|
|
||||||
idToken = cachedOpenId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (authType.isEmpty || idToken.isEmpty) {
|
|
||||||
try {
|
|
||||||
final googleUser =
|
|
||||||
SCGoogleAuthService.currentUser ??
|
|
||||||
await SCGoogleAuthService.signInSilently();
|
|
||||||
final googleUid = googleUser?.uid.trim() ?? "";
|
|
||||||
if (googleUid.isNotEmpty) {
|
|
||||||
authType = SCAuthType.GOOGLE.name;
|
|
||||||
idToken = googleUid;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint('resolve register auth failed: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (authType.isEmpty || idToken.isEmpty) {
|
|
||||||
SCLoadingManager.hide();
|
|
||||||
SCTts.show("Please sign in again.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await DataPersistence.setPendingChannelAuth(authType, idToken);
|
|
||||||
final invitationCode = invitationCodeController.text.trim();
|
final invitationCode = invitationCodeController.text.trim();
|
||||||
SocialChatLoginRes user = await SCAccountRepository().regist(
|
SocialChatLoginRes user = await SCAccountRepository().regist(
|
||||||
authType,
|
authType,
|
||||||
@ -711,7 +683,6 @@ class _SCEditProfilePageState extends State<SCEditProfilePage> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AccountStorage().setCurrentUser(user);
|
AccountStorage().setCurrentUser(user);
|
||||||
await DataPersistence.clearPendingChannelAuth();
|
|
||||||
userProvider?.syncCurrentUserProfile(user.userProfile);
|
userProvider?.syncCurrentUserProfile(user.userProfile);
|
||||||
await DataPersistence.clearPendingRegisterRewardDialog();
|
await DataPersistence.clearPendingRegisterRewardDialog();
|
||||||
await DataPersistence.setAwaitRegisterRewardSocket(true);
|
await DataPersistence.setAwaitRegisterRewardSocket(true);
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math' as math;
|
|
||||||
|
|
||||||
import 'package:extended_image/extended_image.dart'
|
import 'package:extended_image/extended_image.dart'
|
||||||
show ExtendedImage, ExtendedRawImage, ExtendedImageState, LoadState;
|
show ExtendedImage, ExtendedRawImage, ExtendedImageState, LoadState;
|
||||||
import 'package:extended_text/extended_text.dart';
|
import 'package:extended_text/extended_text.dart';
|
||||||
@ -56,8 +54,6 @@ import 'package:yumi/shared/tools/sc_path_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/services/auth/user_profile_manager.dart';
|
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||||
import 'package:yumi/modules/index/main_route.dart';
|
import 'package:yumi/modules/index/main_route.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/sc_nine_patch_image.dart';
|
|
||||||
import 'package:yumi/ui_kit/widgets/svga/sc_network_svga_widget.dart';
|
|
||||||
|
|
||||||
import '../../shared/business_logic/models/res/sc_user_red_packet_send_res.dart';
|
import '../../shared/business_logic/models/res/sc_user_red_packet_send_res.dart';
|
||||||
|
|
||||||
@ -1249,7 +1245,31 @@ class _MessageItem extends StatelessWidget {
|
|||||||
return Builder(
|
return Builder(
|
||||||
builder: (ct) {
|
builder: (ct) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
child: _buildTextBubble(content),
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Color(0xff18F2B1).withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft:
|
||||||
|
message.isSelf! ? Radius.circular(8) : Radius.circular(0),
|
||||||
|
topRight: Radius.circular(8.w),
|
||||||
|
bottomLeft: Radius.circular(8.w),
|
||||||
|
bottomRight:
|
||||||
|
message.isSelf! ? Radius.circular(0) : Radius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.all(8.0.w),
|
||||||
|
child: Container(
|
||||||
|
constraints: BoxConstraints(maxWidth: width(220)),
|
||||||
|
child: ExtendedText(
|
||||||
|
content,
|
||||||
|
// specialTextSpanBuilder: MySpecialTextSpanBuilder(),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: sp(14),
|
||||||
|
color: message.isSelf! ? Colors.white : Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
_showMsgItemMenu(ct, content);
|
_showMsgItemMenu(ct, content);
|
||||||
},
|
},
|
||||||
@ -1258,286 +1278,6 @@ class _MessageItem extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTextBubble(String content) {
|
|
||||||
final chatBubble = _senderProfile()?.getChatBox();
|
|
||||||
final chatBubbleUrl = _resolveChatBubbleImageUrl(chatBubble);
|
|
||||||
if (chatBubbleUrl.isNotEmpty) {
|
|
||||||
return _buildVipTextBubble(content: content, imageUrl: chatBubbleUrl);
|
|
||||||
}
|
|
||||||
return _buildDefaultTextBubble(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
SocialChatUserProfile? _senderProfile() {
|
|
||||||
if (message.isSelf ?? false) {
|
|
||||||
return AccountStorage().getCurrentUser()?.userProfile;
|
|
||||||
}
|
|
||||||
return friend;
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildDefaultTextBubble(String content) {
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Color(0xff18F2B1).withValues(alpha: 0.1),
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topLeft: message.isSelf! ? Radius.circular(8) : Radius.circular(0),
|
|
||||||
topRight: Radius.circular(8.w),
|
|
||||||
bottomLeft: Radius.circular(8.w),
|
|
||||||
bottomRight:
|
|
||||||
message.isSelf! ? Radius.circular(0) : Radius.circular(8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
padding: EdgeInsets.all(8.0.w),
|
|
||||||
child: Container(
|
|
||||||
constraints: BoxConstraints(maxWidth: width(220)),
|
|
||||||
child: ExtendedText(
|
|
||||||
content,
|
|
||||||
style: TextStyle(fontSize: sp(14), color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildVipTextBubble({
|
|
||||||
required String content,
|
|
||||||
required String imageUrl,
|
|
||||||
}) {
|
|
||||||
final textStyle = TextStyle(fontSize: sp(14), color: Colors.white);
|
|
||||||
final multilineMaxBubbleWidth = math.min(
|
|
||||||
ScreenUtil().screenWidth * 0.64,
|
|
||||||
260.w,
|
|
||||||
);
|
|
||||||
final multilineMaxTextWidth = math.max(
|
|
||||||
90.w,
|
|
||||||
multilineMaxBubbleWidth - 58.w,
|
|
||||||
);
|
|
||||||
final multilineTextPainter = TextPainter(
|
|
||||||
text: TextSpan(text: content, style: textStyle),
|
|
||||||
textDirection: Directionality.of(context),
|
|
||||||
)..layout(maxWidth: multilineMaxTextWidth);
|
|
||||||
if (multilineTextPainter.computeLineMetrics().length > 1) {
|
|
||||||
return _buildMultilineVipTextBubble(
|
|
||||||
content: content,
|
|
||||||
imageUrl: imageUrl,
|
|
||||||
textStyle: textStyle,
|
|
||||||
textSize: multilineTextPainter.size,
|
|
||||||
maxBubbleWidth: multilineMaxBubbleWidth,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _buildSingleLineVipTextBubble(
|
|
||||||
content: content,
|
|
||||||
imageUrl: imageUrl,
|
|
||||||
textStyle: textStyle,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildMultilineVipTextBubble({
|
|
||||||
required String content,
|
|
||||||
required String imageUrl,
|
|
||||||
required TextStyle textStyle,
|
|
||||||
required Size textSize,
|
|
||||||
required double maxBubbleWidth,
|
|
||||||
}) {
|
|
||||||
final bubbleWidth = math
|
|
||||||
.max(126.w, textSize.width + 58.w)
|
|
||||||
.clamp(126.w, maxBubbleWidth);
|
|
||||||
final textMaxWidth = math.max(90.w, bubbleWidth - 58.w);
|
|
||||||
final bubbleHeight = math.max(58.w, textSize.height + 26.w);
|
|
||||||
|
|
||||||
return SizedBox(
|
|
||||||
width: bubbleWidth,
|
|
||||||
height: bubbleHeight,
|
|
||||||
child: Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
|
||||||
_buildVipBubbleBackground(
|
|
||||||
url: imageUrl,
|
|
||||||
width: bubbleWidth,
|
|
||||||
height: bubbleHeight,
|
|
||||||
fallback: _buildVipBubbleFallback(),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsetsDirectional.fromSTEB(29.w, 11.w, 29.w, 11.w),
|
|
||||||
child: Align(
|
|
||||||
alignment: AlignmentDirectional.centerStart,
|
|
||||||
child: SizedBox(
|
|
||||||
width: textMaxWidth,
|
|
||||||
child: ExtendedText(content, style: textStyle),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildSingleLineVipTextBubble({
|
|
||||||
required String content,
|
|
||||||
required String imageUrl,
|
|
||||||
required TextStyle textStyle,
|
|
||||||
}) {
|
|
||||||
final horizontalPadding = 18.w;
|
|
||||||
final verticalPadding = 8.w;
|
|
||||||
final minSkinWidth = 132.w;
|
|
||||||
final minSkinHeight = 44.w;
|
|
||||||
final maxTextWidth = width(220);
|
|
||||||
final maxBubbleWidth = maxTextWidth + horizontalPadding * 2;
|
|
||||||
final textSize = _measureTextSize(
|
|
||||||
text: content,
|
|
||||||
style: textStyle,
|
|
||||||
maxWidth: maxTextWidth,
|
|
||||||
);
|
|
||||||
final bubbleWidth = math.min(
|
|
||||||
maxBubbleWidth,
|
|
||||||
math.max(horizontalPadding * 2, textSize.width + horizontalPadding * 2),
|
|
||||||
);
|
|
||||||
final textMaxWidth = math.max(1.0, bubbleWidth - horizontalPadding * 2);
|
|
||||||
final bubbleHeight = math.max(
|
|
||||||
verticalPadding * 2,
|
|
||||||
textSize.height + verticalPadding * 2,
|
|
||||||
);
|
|
||||||
final skinAlignment =
|
|
||||||
message.isSelf!
|
|
||||||
? AlignmentDirectional.centerEnd
|
|
||||||
: AlignmentDirectional.centerStart;
|
|
||||||
|
|
||||||
return SizedBox(
|
|
||||||
width: bubbleWidth,
|
|
||||||
height: bubbleHeight,
|
|
||||||
child: Stack(
|
|
||||||
clipBehavior: Clip.none,
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
|
||||||
Positioned.fill(child: _buildVipBubbleFallback()),
|
|
||||||
Positioned.fill(
|
|
||||||
child: _buildVipBubbleSkin(
|
|
||||||
url: imageUrl,
|
|
||||||
width: math.max(bubbleWidth, minSkinWidth),
|
|
||||||
height: math.max(bubbleHeight, minSkinHeight),
|
|
||||||
alignment: skinAlignment,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsetsDirectional.symmetric(
|
|
||||||
horizontal: horizontalPadding,
|
|
||||||
vertical: verticalPadding,
|
|
||||||
),
|
|
||||||
child: Align(
|
|
||||||
alignment: AlignmentDirectional.centerStart,
|
|
||||||
child: SizedBox(
|
|
||||||
width: textMaxWidth,
|
|
||||||
child: ExtendedText(content, style: textStyle),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildVipBubbleSkin({
|
|
||||||
required String url,
|
|
||||||
required double width,
|
|
||||||
required double height,
|
|
||||||
required AlignmentGeometry alignment,
|
|
||||||
}) {
|
|
||||||
return OverflowBox(
|
|
||||||
alignment: alignment,
|
|
||||||
minWidth: width,
|
|
||||||
maxWidth: width,
|
|
||||||
minHeight: height,
|
|
||||||
maxHeight: height,
|
|
||||||
child:
|
|
||||||
SCNetworkSvgaWidget.isSvga(url)
|
|
||||||
? SCNetworkSvgaWidget(
|
|
||||||
resource: url,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
fit: BoxFit.fill,
|
|
||||||
fallback: SizedBox(width: width, height: height),
|
|
||||||
)
|
|
||||||
: SCNinePatchImage(
|
|
||||||
url: url,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
centerSliceRatio: const EdgeInsets.fromLTRB(
|
|
||||||
0.34,
|
|
||||||
0.34,
|
|
||||||
0.34,
|
|
||||||
0.34,
|
|
||||||
),
|
|
||||||
fallback: SizedBox(width: width, height: height),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildVipBubbleBackground({
|
|
||||||
required String url,
|
|
||||||
required double width,
|
|
||||||
required double height,
|
|
||||||
required Widget fallback,
|
|
||||||
}) {
|
|
||||||
if (SCNetworkSvgaWidget.isSvga(url)) {
|
|
||||||
return SCNetworkSvgaWidget(
|
|
||||||
resource: url,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
fit: BoxFit.fill,
|
|
||||||
fallback: fallback,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return SCNinePatchImage(
|
|
||||||
url: url,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
centerSliceRatio: const EdgeInsets.fromLTRB(0.34, 0.34, 0.34, 0.34),
|
|
||||||
fallback: fallback,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Size _measureTextSize({
|
|
||||||
required String text,
|
|
||||||
required TextStyle style,
|
|
||||||
required double maxWidth,
|
|
||||||
}) {
|
|
||||||
final textPainter = TextPainter(
|
|
||||||
text: TextSpan(text: text, style: style),
|
|
||||||
textDirection: Directionality.of(context),
|
|
||||||
)..layout(maxWidth: maxWidth);
|
|
||||||
return textPainter.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildVipBubbleFallback() {
|
|
||||||
return DecoratedBox(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Color(0xff18F2B1).withValues(alpha: 0.1),
|
|
||||||
borderRadius: BorderRadius.circular(8.w),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _resolveChatBubbleImageUrl(PropsResources? resource) {
|
|
||||||
return _firstNonBlank([
|
|
||||||
resource?.imSendCoverUrl,
|
|
||||||
resource?.imOpenedUrl,
|
|
||||||
resource?.imNotOpenedUrl,
|
|
||||||
resource?.roomSendCoverUrl,
|
|
||||||
resource?.roomOpenedUrl,
|
|
||||||
resource?.roomNotOpenedUrl,
|
|
||||||
resource?.cover,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _firstNonBlank(Iterable<String?> values) {
|
|
||||||
for (final value in values) {
|
|
||||||
final text = value?.trim();
|
|
||||||
if (text != null && text.isNotEmpty) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showMsgItemMenu(BuildContext ct, String content) {
|
void _showMsgItemMenu(BuildContext ct, String content) {
|
||||||
SmartDialog.showAttach(
|
SmartDialog.showAttach(
|
||||||
tag: "showMsgItemMenu",
|
tag: "showMsgItemMenu",
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
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:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
@ -10,6 +11,7 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:yumi/ui_kit/components/appbar/socialchat_appbar.dart';
|
import 'package:yumi/ui_kit/components/appbar/socialchat_appbar.dart';
|
||||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||||
import 'package:yumi/ui_kit/components/custom_cached_image.dart';
|
import 'package:yumi/ui_kit/components/custom_cached_image.dart';
|
||||||
|
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/country_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/country_res.dart';
|
||||||
import 'package:yumi/services/general/sc_app_general_manager.dart';
|
import 'package:yumi/services/general/sc_app_general_manager.dart';
|
||||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||||
@ -19,17 +21,19 @@ import '../../app/config/business_logic_strategy.dart';
|
|||||||
|
|
||||||
///国家
|
///国家
|
||||||
class CountryPage extends StatefulWidget {
|
class CountryPage extends StatefulWidget {
|
||||||
final bool isDialog;
|
bool isDialog = false;
|
||||||
|
|
||||||
const CountryPage({super.key, this.isDialog = false});
|
CountryPage({this.isDialog = false});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CountryPage> createState() => _CountryPageState();
|
_CountryPageState createState() => _CountryPageState(isDialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CountryPageState extends State<CountryPage> {
|
class _CountryPageState extends State<CountryPage> {
|
||||||
|
bool isDialog = false;
|
||||||
Country? selectCountryInfo;
|
Country? selectCountryInfo;
|
||||||
bool _didConfirmSelection = false;
|
|
||||||
|
_CountryPageState(this.isDialog);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -39,12 +43,10 @@ class _CountryPageState extends State<CountryPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
if (!_didConfirmSelection) {
|
Provider.of<SCAppGeneralManager>(
|
||||||
Provider.of<SCAppGeneralManager>(
|
context,
|
||||||
context,
|
listen: false,
|
||||||
listen: false,
|
).clearCountrySelection();
|
||||||
).clearCountrySelection();
|
|
||||||
}
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +98,6 @@ class _CountryPageState extends State<CountryPage> {
|
|||||||
provider.updateCurrentCountry(
|
provider.updateCurrentCountry(
|
||||||
selectCountryInfo,
|
selectCountryInfo,
|
||||||
);
|
);
|
||||||
_didConfirmSelection = true;
|
|
||||||
SCNavigatorUtils.goBack(context);
|
SCNavigatorUtils.goBack(context);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -158,7 +159,6 @@ class _CountryPageState extends State<CountryPage> {
|
|||||||
provider.updateCurrentCountry(
|
provider.updateCurrentCountry(
|
||||||
selectCountryInfo,
|
selectCountryInfo,
|
||||||
);
|
);
|
||||||
_didConfirmSelection = true;
|
|
||||||
SmartDialog.dismiss(tag: "showCountryPage");
|
SmartDialog.dismiss(tag: "showCountryPage");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -136,7 +136,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
required int quantity,
|
required int quantity,
|
||||||
int animationCount = 1,
|
int animationCount = 1,
|
||||||
}) {
|
}) {
|
||||||
final targetUserIds = _resolveAcceptUserIds(acceptUsers);
|
|
||||||
final rtmProvider = Provider.of<RtmProvider>(
|
final rtmProvider = Provider.of<RtmProvider>(
|
||||||
navigatorKey.currentState!.context,
|
navigatorKey.currentState!.context,
|
||||||
listen: false,
|
listen: false,
|
||||||
@ -174,7 +173,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
gift: gift,
|
gift: gift,
|
||||||
user: currentUser,
|
user: currentUser,
|
||||||
toUser: targetUser,
|
toUser: targetUser,
|
||||||
targetUserIds: targetUserIds,
|
|
||||||
number: quantity,
|
number: quantity,
|
||||||
customAnimationCount: animationCount,
|
customAnimationCount: animationCount,
|
||||||
),
|
),
|
||||||
@ -222,13 +220,19 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
Provider.of<SocialChatUserProfileManager>(context, listen: false).balance();
|
Provider.of<SocialChatUserProfileManager>(context, listen: false).balance();
|
||||||
rtcProvider?.roomWheatMap.forEach((k, v) {
|
rtcProvider?.roomWheatMap.forEach((k, v) {
|
||||||
if (v.user != null) {
|
if (v.user != null) {
|
||||||
listMai.add(HeadSelect(widget.toUser == null, v));
|
if (v.user?.id == AccountStorage().getCurrentUser()?.userProfile?.id) {
|
||||||
|
listMai.add(HeadSelect(true, v));
|
||||||
|
} else {
|
||||||
|
listMai.add(HeadSelect(false, v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isAll = true;
|
||||||
|
for (var mai in listMai) {
|
||||||
|
if (!mai.isSelect) {
|
||||||
|
isAll = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
isAll =
|
|
||||||
widget.toUser == null &&
|
|
||||||
listMai.isNotEmpty &&
|
|
||||||
listMai.every((mai) => mai.isSelect);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1393,7 +1397,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
int animationCount = 1,
|
int animationCount = 1,
|
||||||
}) {
|
}) {
|
||||||
///发送一条IM消息
|
///发送一条IM消息
|
||||||
final targetUserIds = _resolveAcceptUserIds(acceptUsers);
|
|
||||||
for (var u in acceptUsers) {
|
for (var u in acceptUsers) {
|
||||||
final special = gift.special ?? "";
|
final special = gift.special ?? "";
|
||||||
final giftSourceUrl = gift.giftSourceUrl ?? "";
|
final giftSourceUrl = gift.giftSourceUrl ?? "";
|
||||||
@ -1425,7 +1428,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
gift: gift,
|
gift: gift,
|
||||||
user: AccountStorage().getCurrentUser()?.userProfile,
|
user: AccountStorage().getCurrentUser()?.userProfile,
|
||||||
toUser: u.user,
|
toUser: u.user,
|
||||||
targetUserIds: targetUserIds,
|
|
||||||
number: quantity,
|
number: quantity,
|
||||||
customAnimationCount: animationCount,
|
customAnimationCount: animationCount,
|
||||||
type: SCRoomMsgType.gift,
|
type: SCRoomMsgType.gift,
|
||||||
@ -1601,7 +1603,11 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
required int quantity,
|
required int quantity,
|
||||||
int animationCount = 1,
|
int animationCount = 1,
|
||||||
}) async {
|
}) async {
|
||||||
final targetUserIds = _resolveAcceptUserIds(acceptUsers);
|
final targetUserIds =
|
||||||
|
acceptUsers
|
||||||
|
.map((u) => u.user?.id ?? "")
|
||||||
|
.where((id) => id.isNotEmpty)
|
||||||
|
.toList();
|
||||||
final firstTargetUser =
|
final firstTargetUser =
|
||||||
acceptUsers.isNotEmpty ? acceptUsers.first.user : null;
|
acceptUsers.isNotEmpty ? acceptUsers.first.user : null;
|
||||||
_giftFxLog(
|
_giftFxLog(
|
||||||
@ -1625,7 +1631,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
gift: gift,
|
gift: gift,
|
||||||
user: AccountStorage().getCurrentUser()?.userProfile,
|
user: AccountStorage().getCurrentUser()?.userProfile,
|
||||||
toUser: firstTargetUser,
|
toUser: firstTargetUser,
|
||||||
targetUserIds: targetUserIds,
|
|
||||||
number: quantity,
|
number: quantity,
|
||||||
customAnimationCount: animationCount,
|
customAnimationCount: animationCount,
|
||||||
type: SCRoomMsgType.luckGiftAnimOther,
|
type: SCRoomMsgType.luckGiftAnimOther,
|
||||||
@ -1639,18 +1644,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
|||||||
'targetUserIds=${targetUserIds.join(",")}',
|
'targetUserIds=${targetUserIds.join(",")}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> _resolveAcceptUserIds(List<MicRes> acceptUsers) {
|
|
||||||
final targetUserIds = <String>[];
|
|
||||||
for (final acceptUser in acceptUsers) {
|
|
||||||
final userId = (acceptUser.user?.id ?? "").trim();
|
|
||||||
if (userId.isEmpty || targetUserIds.contains(userId)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
targetUserIds.add(userId);
|
|
||||||
}
|
|
||||||
return targetUserIds;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class CheckNumber extends StatelessWidget {
|
class CheckNumber extends StatelessWidget {
|
||||||
|
|||||||
@ -9,10 +9,9 @@ import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository
|
|||||||
import 'package:yumi/services/audio/rtc_manager.dart';
|
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:yumi/app/constants/sc_room_msg_type.dart';
|
import 'package:yumi/app/constants/sc_room_msg_type.dart';
|
||||||
import 'package:yumi/app/constants/sc_screen.dart';
|
import 'package:yumi/app/constants/sc_screen.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/room_member_res.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/room_member_res.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 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
||||||
|
|
||||||
@ -94,10 +93,9 @@ class _RoomMemberPageState
|
|||||||
list.isNotEmpty ? buildTag(list.first.roles) : Container(),
|
list.isNotEmpty ? buildTag(list.first.roles) : Container(),
|
||||||
SizedBox(height: 3.w),
|
SizedBox(height: 3.w),
|
||||||
Column(
|
Column(
|
||||||
children: List.generate(list.length, (cIndex) {
|
children: List.generate(list.length, (cIndex) {
|
||||||
SocialChatRoomMemberRes userInfo = list[cIndex];
|
SocialChatRoomMemberRes userInfo = list[cIndex];
|
||||||
final userProfile = _mergeCurrentUserVip(userInfo.userProfile);
|
return GestureDetector(
|
||||||
return GestureDetector(
|
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: EdgeInsets.symmetric(horizontal: 10.w, vertical: 5.w),
|
margin: EdgeInsets.symmetric(horizontal: 10.w, vertical: 5.w),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.w),
|
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.w),
|
||||||
@ -108,9 +106,10 @@ class _RoomMemberPageState
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
head(
|
head(
|
||||||
url: userProfile?.userAvatar ?? "",
|
url: userInfo.userProfile?.userAvatar ?? "",
|
||||||
width: 52.w,
|
width: 52.w,
|
||||||
headdress: userProfile?.getHeaddress()?.sourceUrl,
|
headdress:
|
||||||
|
userInfo.userProfile?.getHeaddress()?.sourceUrl,
|
||||||
),
|
),
|
||||||
SizedBox(width: 3.w),
|
SizedBox(width: 3.w),
|
||||||
Column(
|
Column(
|
||||||
@ -130,47 +129,57 @@ class _RoomMemberPageState
|
|||||||
SizedBox(width: 3.w),
|
SizedBox(width: 3.w),
|
||||||
socialchatNickNameText(
|
socialchatNickNameText(
|
||||||
maxWidth: 160.w,
|
maxWidth: 160.w,
|
||||||
userProfile?.userNickname ?? "",
|
userInfo.userProfile?.userNickname ?? "",
|
||||||
fontSize: 14.sp,
|
fontSize: 14.sp,
|
||||||
textColor: Colors.black,
|
textColor: Colors.black,
|
||||||
type:
|
type:
|
||||||
userProfile?.getVIP()?.name ?? "",
|
userInfo.userProfile?.getVIP()?.name ??
|
||||||
needScroll:
|
"",
|
||||||
(userProfile
|
needScroll:
|
||||||
?.userNickname
|
(userInfo
|
||||||
?.characters
|
.userProfile
|
||||||
.length ??
|
?.userNickname
|
||||||
0) >
|
?.characters
|
||||||
|
.length ??
|
||||||
|
0) >
|
||||||
14,
|
14,
|
||||||
),
|
),
|
||||||
SizedBox(width: 3.w),
|
SizedBox(width: 3.w),
|
||||||
userProfile?.getVIP() != null
|
userInfo.userProfile?.getVIP() != null
|
||||||
? netImage(
|
? netImage(
|
||||||
url: userProfile?.getVIP()?.cover ?? "",
|
url:
|
||||||
|
userInfo.userProfile
|
||||||
|
?.getVIP()
|
||||||
|
?.cover ??
|
||||||
|
"",
|
||||||
width: 25.w,
|
width: 25.w,
|
||||||
height: 25.w,
|
height: 25.w,
|
||||||
)
|
)
|
||||||
: Container(),
|
: Container(),
|
||||||
SizedBox(width: 5.w),
|
SizedBox(width: 5.w),
|
||||||
userProfile?.wearBadge?.isNotEmpty ??
|
userInfo.userProfile?.wearBadge?.isNotEmpty ??
|
||||||
false
|
false
|
||||||
? SizedBox(
|
? SizedBox(
|
||||||
height: 25.w,
|
height: 25.w,
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount:
|
itemCount:
|
||||||
userProfile?.wearBadge?.length ??
|
userInfo
|
||||||
0,
|
.userProfile
|
||||||
|
?.wearBadge
|
||||||
|
?.length ??
|
||||||
|
0,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return netImage(
|
return netImage(
|
||||||
width: 25.w,
|
width: 25.w,
|
||||||
height: 25.w,
|
height: 25.w,
|
||||||
url:
|
url:
|
||||||
userProfile
|
userInfo
|
||||||
?.wearBadge?[index]
|
.userProfile
|
||||||
.selectUrl ??
|
?.wearBadge?[index]
|
||||||
"",
|
.selectUrl ??
|
||||||
|
"",
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (
|
separatorBuilder: (
|
||||||
@ -188,17 +197,18 @@ class _RoomMemberPageState
|
|||||||
),
|
),
|
||||||
SizedBox(height: 3.w),
|
SizedBox(height: 3.w),
|
||||||
SCSpecialIdBadge(
|
SCSpecialIdBadge(
|
||||||
idText: userProfile?.getID() ?? "",
|
idText: userInfo.userProfile?.getID() ?? "",
|
||||||
showAnimated: userProfile?.hasSpecialId() ?? false,
|
showAnimated:
|
||||||
|
userInfo.userProfile?.hasSpecialId() ?? false,
|
||||||
assetPath: SCSpecialIdAssets.userId,
|
assetPath: SCSpecialIdAssets.userId,
|
||||||
animationWidth: 62.w,
|
animationWidth: 62.w,
|
||||||
animationHeight: 24.w,
|
animationHeight: 24.w,
|
||||||
showTextBesideAnimated: true,
|
showTextBesideAnimated: true,
|
||||||
animatedTextSpacing: 0,
|
animatedTextSpacing: 0,
|
||||||
showAnimatedGradientText:
|
showAnimatedGradientText:
|
||||||
userProfile
|
userInfo.userProfile
|
||||||
?.shouldShowColoredSpecialIdText() ??
|
?.shouldShowColoredSpecialIdText() ??
|
||||||
false,
|
false,
|
||||||
animationTextStyle: TextStyle(
|
animationTextStyle: TextStyle(
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
@ -352,29 +362,10 @@ class _RoomMemberPageState
|
|||||||
if (onErr != null) {
|
if (onErr != null) {
|
||||||
onErr();
|
onErr();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SocialChatUserProfile? _mergeCurrentUserVip(
|
Widget buildTag(String? roles) {
|
||||||
SocialChatUserProfile? profile,
|
|
||||||
) {
|
|
||||||
if (profile == null || profile.vipLevelForColoredId > 0) {
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
final currentProfile = AccountStorage().getCurrentUser()?.userProfile;
|
|
||||||
if (currentProfile == null ||
|
|
||||||
currentProfile.vipLevelForColoredId <= 0 ||
|
|
||||||
currentProfile.id != profile.id) {
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
return profile.copyWith(
|
|
||||||
vipLevel:
|
|
||||||
currentProfile.vipLevel ??
|
|
||||||
currentProfile.vipLevelForColoredId.toString(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildTag(String? roles) {
|
|
||||||
if (roles == SCRoomRolesType.HOMEOWNER.name) {
|
if (roles == SCRoomRolesType.HOMEOWNER.name) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
|
||||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart';
|
|
||||||
import 'package:yumi/main.dart';
|
import 'package:yumi/main.dart';
|
||||||
|
|
||||||
import 'package:yumi/app_localizations.dart';
|
import 'package:yumi/app_localizations.dart';
|
||||||
@ -49,10 +47,9 @@ class _RoomGiftRankTabPageState
|
|||||||
return buildList(context);
|
return buildList(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget buildItemOne(RoomGiftRankRes userInfo, int index) {
|
Widget buildItemOne(RoomGiftRankRes userInfo, int index) {
|
||||||
final userProfile = _mergeCurrentUserVip(userInfo.userProfile);
|
return GestureDetector(
|
||||||
return GestureDetector(
|
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: EdgeInsets.symmetric(vertical: 3.w),
|
margin: EdgeInsets.symmetric(vertical: 3.w),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16.w),
|
padding: EdgeInsets.symmetric(horizontal: 16.w),
|
||||||
@ -93,14 +90,14 @@ class _RoomGiftRankTabPageState
|
|||||||
SizedBox(width: 10.w),
|
SizedBox(width: 10.w),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
child: head(
|
child: head(
|
||||||
url: userProfile?.userAvatar ?? "",
|
url: userInfo.userProfile?.userAvatar ?? "",
|
||||||
width: 55.w,
|
width: 55.w,
|
||||||
// headdress: userInfo.userProfile?.getHeaddress()?.sourceUrl,
|
// headdress: userInfo.userProfile?.getHeaddress()?.sourceUrl,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showBottomInCenterDialog(
|
showBottomInCenterDialog(
|
||||||
navigatorKey.currentState!.context,
|
navigatorKey.currentState!.context,
|
||||||
RoomUserInfoCard(userId: userProfile?.id),
|
RoomUserInfoCard(userId: userInfo.userProfile?.id),
|
||||||
);
|
);
|
||||||
SmartDialog.dismiss(tag: "showRoomGiftRankPage");
|
SmartDialog.dismiss(tag: "showRoomGiftRankPage");
|
||||||
},
|
},
|
||||||
@ -113,12 +110,16 @@ class _RoomGiftRankTabPageState
|
|||||||
children: [
|
children: [
|
||||||
socialchatNickNameText(
|
socialchatNickNameText(
|
||||||
maxWidth: 170.w,
|
maxWidth: 170.w,
|
||||||
userProfile?.userNickname ?? "",
|
userInfo.userProfile?.userNickname ?? "",
|
||||||
fontSize: 14.sp,
|
fontSize: 14.sp,
|
||||||
type: userProfile?.getVIP()?.name ?? "",
|
type: userInfo.userProfile?.getVIP()?.name ?? "",
|
||||||
needScroll:
|
needScroll:
|
||||||
(userProfile?.userNickname?.characters.length ??
|
(userInfo
|
||||||
0) >
|
.userProfile
|
||||||
|
?.userNickname
|
||||||
|
?.characters
|
||||||
|
.length ??
|
||||||
|
0) >
|
||||||
14,
|
14,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -130,17 +131,18 @@ class _RoomGiftRankTabPageState
|
|||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
children: [
|
children: [
|
||||||
SCSpecialIdBadge(
|
SCSpecialIdBadge(
|
||||||
idText: userProfile?.getID() ?? "",
|
idText: userInfo.userProfile?.getID() ?? "",
|
||||||
showAnimated: userProfile?.hasSpecialId() ?? false,
|
showAnimated:
|
||||||
|
userInfo.userProfile?.hasSpecialId() ?? false,
|
||||||
assetPath: SCSpecialIdAssets.userId,
|
assetPath: SCSpecialIdAssets.userId,
|
||||||
animationWidth: 62.w,
|
animationWidth: 62.w,
|
||||||
animationHeight: 24.w,
|
animationHeight: 24.w,
|
||||||
showTextBesideAnimated: true,
|
showTextBesideAnimated: true,
|
||||||
animatedTextSpacing: 0,
|
animatedTextSpacing: 0,
|
||||||
showAnimatedGradientText:
|
showAnimatedGradientText:
|
||||||
userProfile
|
userInfo.userProfile
|
||||||
?.shouldShowColoredSpecialIdText() ??
|
?.shouldShowColoredSpecialIdText() ??
|
||||||
false,
|
false,
|
||||||
animationTextStyle: TextStyle(
|
animationTextStyle: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
@ -163,7 +165,7 @@ class _RoomGiftRankTabPageState
|
|||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Clipboard.setData(
|
Clipboard.setData(
|
||||||
ClipboardData(text: userProfile?.getID() ?? ""),
|
ClipboardData(text: userInfo.userProfile?.getID() ?? ""),
|
||||||
);
|
);
|
||||||
SCTts.show(
|
SCTts.show(
|
||||||
SCAppLocalizations.of(context)!.copiedToClipboard,
|
SCAppLocalizations.of(context)!.copiedToClipboard,
|
||||||
@ -189,30 +191,11 @@ class _RoomGiftRankTabPageState
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {},
|
onTap: () {},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
SocialChatUserProfile? _mergeCurrentUserVip(
|
@override
|
||||||
SocialChatUserProfile? profile,
|
empty() {
|
||||||
) {
|
|
||||||
if (profile == null || profile.vipLevelForColoredId > 0) {
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
final currentProfile = AccountStorage().getCurrentUser()?.userProfile;
|
|
||||||
if (currentProfile == null ||
|
|
||||||
currentProfile.vipLevelForColoredId <= 0 ||
|
|
||||||
currentProfile.id != profile.id) {
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
return profile.copyWith(
|
|
||||||
vipLevel:
|
|
||||||
currentProfile.vipLevel ??
|
|
||||||
currentProfile.vipLevelForColoredId.toString(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
empty() {
|
|
||||||
return mainEmpty(
|
return mainEmpty(
|
||||||
image: Image.asset(
|
image: Image.asset(
|
||||||
'sc_images/general/sc_icon_loading.png',
|
'sc_images/general/sc_icon_loading.png',
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'package:yumi/app/constants/sc_room_msg_type.dart';
|
import 'dart:async';
|
||||||
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:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
@ -58,9 +56,6 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
static const Duration _giftAnimationQueueDrainWindow =
|
static const Duration _giftAnimationQueueDrainWindow =
|
||||||
_luckyGiftQueueDrainWindow;
|
_luckyGiftQueueDrainWindow;
|
||||||
static const int _maxTrackedGiftAnimations = _maxLuckyGiftTrackedAnimations;
|
static const int _maxTrackedGiftAnimations = _maxLuckyGiftTrackedAnimations;
|
||||||
static const Duration _giftFlightBatchDedupeWindow = Duration(
|
|
||||||
milliseconds: 650,
|
|
||||||
);
|
|
||||||
|
|
||||||
late TabController _tabController;
|
late TabController _tabController;
|
||||||
final List<Widget> _pages = [AllChatPage(), ChatPage(), GiftChatPage()];
|
final List<Widget> _pages = [AllChatPage(), ChatPage(), GiftChatPage()];
|
||||||
@ -69,7 +64,6 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
RoomGiftSeatFlightController();
|
RoomGiftSeatFlightController();
|
||||||
final Map<String, _LuckyGiftComboSession> _luckyGiftComboSessions =
|
final Map<String, _LuckyGiftComboSession> _luckyGiftComboSessions =
|
||||||
<String, _LuckyGiftComboSession>{};
|
<String, _LuckyGiftComboSession>{};
|
||||||
final Map<String, Timer> _giftFlightBatchDedupeTimers = <String, Timer>{};
|
|
||||||
int _shownRoomStartupFailureToken = 0;
|
int _shownRoomStartupFailureToken = 0;
|
||||||
RtcProvider? _rtcProvider;
|
RtcProvider? _rtcProvider;
|
||||||
bool _roomProviderRebuildScheduled = false;
|
bool _roomProviderRebuildScheduled = false;
|
||||||
@ -90,7 +84,6 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
listen: false,
|
listen: false,
|
||||||
).toggleGiftAnimationVisibility(false);
|
).toggleGiftAnimationVisibility(false);
|
||||||
_clearLuckyGiftComboSessions();
|
_clearLuckyGiftComboSessions();
|
||||||
_clearGiftFlightBatchDedupeTimers();
|
|
||||||
_giftSeatFlightController.clear();
|
_giftSeatFlightController.clear();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -176,7 +169,6 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
|
|
||||||
RoomEntranceHelper.clearQueue();
|
RoomEntranceHelper.clearQueue();
|
||||||
_clearLuckyGiftComboSessions();
|
_clearLuckyGiftComboSessions();
|
||||||
_clearGiftFlightBatchDedupeTimers();
|
|
||||||
_giftSeatFlightController.clear();
|
_giftSeatFlightController.clear();
|
||||||
OverlayManager().removeRoom();
|
OverlayManager().removeRoom();
|
||||||
SCRoomEffectScheduler().clearDeferredTasks(reason: 'voice_room_suspend');
|
SCRoomEffectScheduler().clearDeferredTasks(reason: 'voice_room_suspend');
|
||||||
@ -322,10 +314,7 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [_buildChatView(), RoomBottomWidget()],
|
||||||
_buildChatView(),
|
|
||||||
const RoomBottomWidget(showGiftComboButton: false),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
LGiftAnimalPage(),
|
LGiftAnimalPage(),
|
||||||
Transform.translate(
|
Transform.translate(
|
||||||
@ -347,7 +336,6 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
// _buildPlayViews(),
|
// _buildPlayViews(),
|
||||||
///幸运礼物中奖动画
|
///幸运礼物中奖动画
|
||||||
LuckGiftNomorAnimWidget(),
|
LuckGiftNomorAnimWidget(),
|
||||||
const RoomGiftComboFloatingLayer(),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -503,27 +491,16 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
).enqueueGiftAnimation(giftModel);
|
).enqueueGiftAnimation(giftModel);
|
||||||
|
|
||||||
final giftPhoto = (msg.gift?.giftPhoto ?? "").trim();
|
final giftPhoto = (msg.gift?.giftPhoto ?? "").trim();
|
||||||
final targetUserIds = _resolveGiftTargetUserIds(msg);
|
final targetUserId = _resolveGiftTargetUserId(msg);
|
||||||
final targetGroupKey = _buildGiftTargetGroupKey(targetUserIds);
|
|
||||||
if (_supportsComboMilestoneEffects(msg)) {
|
if (_supportsComboMilestoneEffects(msg)) {
|
||||||
_handleComboMilestoneVisuals(msg, targetGroupKey);
|
_handleComboMilestoneVisuals(msg, targetUserId);
|
||||||
}
|
}
|
||||||
final isLuckyGift = _isLuckyGiftMessage(msg);
|
final isLuckyGift = _isLuckyGiftMessage(msg);
|
||||||
if (isLuckyGift) {
|
if (isLuckyGift) {
|
||||||
_handleLuckyGiftComboVisuals(
|
_handleLuckyGiftComboVisuals(msg, giftPhoto, targetUserId);
|
||||||
msg,
|
|
||||||
giftPhoto,
|
|
||||||
targetGroupKey,
|
|
||||||
targetUserIds,
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_handleStandardGiftComboVisuals(
|
_handleStandardGiftComboVisuals(msg, giftPhoto, targetUserId);
|
||||||
msg,
|
|
||||||
giftPhoto,
|
|
||||||
targetGroupKey,
|
|
||||||
targetUserIds,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _luckyGiftRewardTickerListener(Msg msg) {
|
void _luckyGiftRewardTickerListener(Msg msg) {
|
||||||
@ -635,24 +612,17 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
void _handleStandardGiftComboVisuals(
|
void _handleStandardGiftComboVisuals(
|
||||||
Msg msg,
|
Msg msg,
|
||||||
String giftPhoto,
|
String giftPhoto,
|
||||||
String? targetGroupKey,
|
String? targetUserId,
|
||||||
List<String> targetUserIds,
|
|
||||||
) {
|
) {
|
||||||
if ((msg.number ?? 0) <= 0) {
|
if ((msg.number ?? 0) <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!_shouldPlaySeatFlightGiftAnimation(msg) ||
|
if (!_shouldPlaySeatFlightGiftAnimation(msg) ||
|
||||||
targetGroupKey == null ||
|
targetUserId == null ||
|
||||||
targetUserIds.isEmpty ||
|
|
||||||
giftPhoto.isEmpty) {
|
giftPhoto.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final flightBatchKey = _buildGiftFlightBatchKey(msg, targetUserIds);
|
final sessionKey = _buildLuckyGiftComboSessionKey(msg, targetUserId);
|
||||||
if (targetUserIds.length > 1 &&
|
|
||||||
!_markGiftFlightBatchForPlayback(flightBatchKey)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final sessionKey = _buildLuckyGiftComboSessionKey(msg, targetGroupKey);
|
|
||||||
final session = _luckyGiftComboSessions.putIfAbsent(
|
final session = _luckyGiftComboSessions.putIfAbsent(
|
||||||
sessionKey,
|
sessionKey,
|
||||||
() => _LuckyGiftComboSession(),
|
() => _LuckyGiftComboSession(),
|
||||||
@ -663,9 +633,8 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
_enqueueTrackedSeatFlightAnimations(
|
_enqueueTrackedSeatFlightAnimations(
|
||||||
sessionKey: sessionKey,
|
sessionKey: sessionKey,
|
||||||
giftPhoto: giftPhoto,
|
giftPhoto: giftPhoto,
|
||||||
targetUserIds: targetUserIds,
|
targetUserId: targetUserId,
|
||||||
animationCount: _resolveTrackedAnimationCount(msg),
|
animationCount: _resolveTrackedAnimationCount(msg),
|
||||||
batchKey: flightBatchKey,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
_scheduleGiftAnimationSessionEnd(sessionKey);
|
_scheduleGiftAnimationSessionEnd(sessionKey);
|
||||||
@ -674,16 +643,12 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
void _handleLuckyGiftComboVisuals(
|
void _handleLuckyGiftComboVisuals(
|
||||||
Msg msg,
|
Msg msg,
|
||||||
String giftPhoto,
|
String giftPhoto,
|
||||||
String? targetGroupKey,
|
String? targetUserId,
|
||||||
List<String> targetUserIds,
|
|
||||||
) {
|
) {
|
||||||
if ((msg.number ?? 0) <= 0) {
|
if ((msg.number ?? 0) <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (targetGroupKey == null || targetUserIds.isEmpty) {
|
final sessionKey = _buildLuckyGiftComboSessionKey(msg, targetUserId);
|
||||||
return;
|
|
||||||
}
|
|
||||||
final sessionKey = _buildLuckyGiftComboSessionKey(msg, targetGroupKey);
|
|
||||||
final session = _luckyGiftComboSessions[sessionKey];
|
final session = _luckyGiftComboSessions[sessionKey];
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
return;
|
return;
|
||||||
@ -691,19 +656,14 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
session.endTimer?.cancel();
|
session.endTimer?.cancel();
|
||||||
session.clearQueueTimer?.cancel();
|
session.clearQueueTimer?.cancel();
|
||||||
|
|
||||||
if (_shouldPlaySeatFlightGiftAnimation(msg) && giftPhoto.isNotEmpty) {
|
if (_shouldPlaySeatFlightGiftAnimation(msg) &&
|
||||||
final flightBatchKey = _buildGiftFlightBatchKey(msg, targetUserIds);
|
targetUserId != null &&
|
||||||
if (targetUserIds.length > 1 &&
|
giftPhoto.isNotEmpty) {
|
||||||
!_markGiftFlightBatchForPlayback(flightBatchKey)) {
|
|
||||||
_scheduleGiftAnimationSessionEnd(sessionKey);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_enqueueTrackedSeatFlightAnimations(
|
_enqueueTrackedSeatFlightAnimations(
|
||||||
sessionKey: sessionKey,
|
sessionKey: sessionKey,
|
||||||
giftPhoto: giftPhoto,
|
giftPhoto: giftPhoto,
|
||||||
targetUserIds: targetUserIds,
|
targetUserId: targetUserId,
|
||||||
animationCount: _resolveTrackedAnimationCount(msg),
|
animationCount: _resolveTrackedAnimationCount(msg),
|
||||||
batchKey: flightBatchKey,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_scheduleGiftAnimationSessionEnd(sessionKey);
|
_scheduleGiftAnimationSessionEnd(sessionKey);
|
||||||
@ -729,42 +689,28 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
void _enqueueTrackedSeatFlightAnimations({
|
void _enqueueTrackedSeatFlightAnimations({
|
||||||
required String sessionKey,
|
required String sessionKey,
|
||||||
required String giftPhoto,
|
required String giftPhoto,
|
||||||
required List<String> targetUserIds,
|
required String targetUserId,
|
||||||
required int animationCount,
|
required int animationCount,
|
||||||
required String batchKey,
|
|
||||||
}) {
|
}) {
|
||||||
unawaited(
|
unawaited(
|
||||||
warmImageResource(giftPhoto, logicalWidth: 96.w, logicalHeight: 96.w),
|
warmImageResource(giftPhoto, logicalWidth: 96.w, logicalHeight: 96.w),
|
||||||
);
|
);
|
||||||
final normalizedTargetUserIds = _normalizeGiftTargetUserIds(targetUserIds);
|
|
||||||
if (normalizedTargetUserIds.isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final normalizedAnimationCount = math.max(animationCount, 1);
|
final normalizedAnimationCount = math.max(animationCount, 1);
|
||||||
final cappedAnimationCount = math.min(
|
final cappedAnimationCount = math.min(
|
||||||
normalizedAnimationCount,
|
normalizedAnimationCount,
|
||||||
_maxTrackedGiftAnimations,
|
_maxTrackedGiftAnimations,
|
||||||
);
|
);
|
||||||
final maxTrackedRequests = math.max(
|
|
||||||
_maxTrackedGiftAnimations * normalizedTargetUserIds.length,
|
|
||||||
_maxTrackedGiftAnimations,
|
|
||||||
);
|
|
||||||
for (var index = 0; index < cappedAnimationCount; index += 1) {
|
for (var index = 0; index < cappedAnimationCount; index += 1) {
|
||||||
final roundBatchTag =
|
_giftSeatFlightController.enqueueLimited(
|
||||||
normalizedTargetUserIds.length > 1 ? '$batchKey|round:$index' : null;
|
RoomGiftSeatFlightRequest(
|
||||||
for (final targetUserId in normalizedTargetUserIds) {
|
imagePath: giftPhoto,
|
||||||
_giftSeatFlightController.enqueueLimited(
|
targetUserId: targetUserId,
|
||||||
RoomGiftSeatFlightRequest(
|
beginSize: 96.w,
|
||||||
imagePath: giftPhoto,
|
endSize: 28.w,
|
||||||
targetUserId: targetUserId,
|
queueTag: sessionKey,
|
||||||
beginSize: 96.w,
|
),
|
||||||
endSize: 28.w,
|
maxTrackedRequests: _maxTrackedGiftAnimations,
|
||||||
queueTag: sessionKey,
|
);
|
||||||
batchTag: roundBatchTag,
|
|
||||||
),
|
|
||||||
maxTrackedRequests: maxTrackedRequests,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -796,96 +742,6 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> _resolveGiftTargetUserIds(Msg msg) {
|
|
||||||
final targetUserIds = <String>[];
|
|
||||||
void addTargetUserId(String? userId) {
|
|
||||||
final normalizedUserId = (userId ?? "").trim();
|
|
||||||
if (normalizedUserId.isEmpty ||
|
|
||||||
targetUserIds.contains(normalizedUserId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
targetUserIds.add(normalizedUserId);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final userId in msg.targetUserIds ?? const <String>[]) {
|
|
||||||
addTargetUserId(userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.type == SCRoomMsgType.luckGiftAnimOther) {
|
|
||||||
final rawTargetUserIds = (msg.msg ?? "").trim();
|
|
||||||
if (rawTargetUserIds.startsWith("[")) {
|
|
||||||
try {
|
|
||||||
final decoded = jsonDecode(rawTargetUserIds);
|
|
||||||
if (decoded is List) {
|
|
||||||
for (final userId in decoded) {
|
|
||||||
addTargetUserId(userId?.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addTargetUserId(_resolveGiftTargetUserId(msg));
|
|
||||||
return targetUserIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> _normalizeGiftTargetUserIds(List<String> targetUserIds) {
|
|
||||||
final normalizedTargetUserIds = <String>[];
|
|
||||||
for (final targetUserId in targetUserIds) {
|
|
||||||
final normalizedTargetUserId = targetUserId.trim();
|
|
||||||
if (normalizedTargetUserId.isEmpty ||
|
|
||||||
normalizedTargetUserIds.contains(normalizedTargetUserId)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
normalizedTargetUserIds.add(normalizedTargetUserId);
|
|
||||||
}
|
|
||||||
return normalizedTargetUserIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
String? _buildGiftTargetGroupKey(List<String> targetUserIds) {
|
|
||||||
final normalizedTargetUserIds = _normalizeGiftTargetUserIds(targetUserIds);
|
|
||||||
if (normalizedTargetUserIds.isEmpty) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
normalizedTargetUserIds.sort();
|
|
||||||
return normalizedTargetUserIds.join(",");
|
|
||||||
}
|
|
||||||
|
|
||||||
String _buildGiftFlightBatchKey(Msg msg, List<String> targetUserIds) {
|
|
||||||
final normalizedTargetUserIds = _normalizeGiftTargetUserIds(targetUserIds);
|
|
||||||
normalizedTargetUserIds.sort();
|
|
||||||
final timeBucket =
|
|
||||||
((msg.time ?? DateTime.now().millisecondsSinceEpoch) / 650).floor();
|
|
||||||
return [
|
|
||||||
msg.type ?? "",
|
|
||||||
msg.gift?.id ?? "",
|
|
||||||
msg.user?.id ?? "",
|
|
||||||
msg.number?.toString() ?? "",
|
|
||||||
msg.customAnimationCount?.toString() ?? "",
|
|
||||||
normalizedTargetUserIds.join(","),
|
|
||||||
timeBucket.toString(),
|
|
||||||
].join("|");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _markGiftFlightBatchForPlayback(String batchKey) {
|
|
||||||
final normalizedBatchKey = batchKey.trim();
|
|
||||||
if (normalizedBatchKey.isEmpty) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (_giftFlightBatchDedupeTimers.containsKey(normalizedBatchKey)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
late final Timer timer;
|
|
||||||
timer = Timer(_giftFlightBatchDedupeWindow, () {
|
|
||||||
if (identical(_giftFlightBatchDedupeTimers[normalizedBatchKey], timer)) {
|
|
||||||
_giftFlightBatchDedupeTimers.remove(normalizedBatchKey);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
_giftFlightBatchDedupeTimers[normalizedBatchKey] = timer;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
String _buildLuckyGiftComboSessionKey(Msg msg, String? targetUserId) {
|
String _buildLuckyGiftComboSessionKey(Msg msg, String? targetUserId) {
|
||||||
final senderId = (msg.user?.id ?? '').trim();
|
final senderId = (msg.user?.id ?? '').trim();
|
||||||
final giftId = (msg.gift?.id ?? '').trim();
|
final giftId = (msg.gift?.id ?? '').trim();
|
||||||
@ -899,13 +755,6 @@ class _VoiceRoomPageState extends State<VoiceRoomPage>
|
|||||||
_luckyGiftComboSessions.clear();
|
_luckyGiftComboSessions.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _clearGiftFlightBatchDedupeTimers() {
|
|
||||||
for (final timer in _giftFlightBatchDedupeTimers.values) {
|
|
||||||
timer.cancel();
|
|
||||||
}
|
|
||||||
_giftFlightBatchDedupeTimers.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _shouldPlaySeatFlightGiftAnimation(Msg msg) {
|
bool _shouldPlaySeatFlightGiftAnimation(Msg msg) {
|
||||||
final gift = msg.gift;
|
final gift = msg.gift;
|
||||||
if (gift == null) {
|
if (gift == null) {
|
||||||
|
|||||||
@ -67,6 +67,10 @@ double resolveRoomGameCompatibilityBottomHeight(BuildContext context) {
|
|||||||
if (mediaQuery == null) {
|
if (mediaQuery == null) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
final isClassicScreen = mediaQuery.padding.bottom <= 0.5;
|
||||||
|
if (!isClassicScreen) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
return (mediaQuery.size.width * 0.19).clamp(56.0, 88.0).toDouble();
|
return (mediaQuery.size.width * 0.19).clamp(56.0, 88.0).toDouble();
|
||||||
}
|
}
|
||||||
@ -93,7 +97,12 @@ class RoomGameCompatibilityBottomFrame extends StatelessWidget {
|
|||||||
class _RoomGameBottomFramePainter extends CustomPainter {
|
class _RoomGameBottomFramePainter extends CustomPainter {
|
||||||
@override
|
@override
|
||||||
void paint(Canvas canvas, Size size) {
|
void paint(Canvas canvas, Size size) {
|
||||||
final frameRect = Offset.zero & size;
|
final radius = Radius.circular(size.height * 0.18);
|
||||||
|
final frameRect = RRect.fromRectAndCorners(
|
||||||
|
Offset.zero & size,
|
||||||
|
topLeft: radius,
|
||||||
|
topRight: radius,
|
||||||
|
);
|
||||||
|
|
||||||
final backgroundPaint =
|
final backgroundPaint =
|
||||||
Paint()
|
Paint()
|
||||||
@ -101,11 +110,11 @@ class _RoomGameBottomFramePainter extends CustomPainter {
|
|||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter,
|
end: Alignment.bottomCenter,
|
||||||
colors: <Color>[Color(0xFF0E392E), Color(0xFF082A23)],
|
colors: <Color>[Color(0xFF0E392E), Color(0xFF082A23)],
|
||||||
).createShader(frameRect);
|
).createShader(Offset.zero & size);
|
||||||
canvas.drawRect(frameRect, backgroundPaint);
|
canvas.drawRRect(frameRect, backgroundPaint);
|
||||||
|
|
||||||
canvas.save();
|
canvas.save();
|
||||||
canvas.clipRect(frameRect);
|
canvas.clipRRect(frameRect);
|
||||||
final patternPaint =
|
final patternPaint =
|
||||||
Paint()
|
Paint()
|
||||||
..color = const Color(0xFF2C5B4A).withValues(alpha: 0.20)
|
..color = const Color(0xFF2C5B4A).withValues(alpha: 0.20)
|
||||||
@ -147,8 +156,8 @@ class _RoomGameBottomFramePainter extends CustomPainter {
|
|||||||
..style = PaintingStyle.stroke
|
..style = PaintingStyle.stroke
|
||||||
..strokeWidth = 1;
|
..strokeWidth = 1;
|
||||||
|
|
||||||
canvas.drawRect(frameRect.deflate(1.5), borderPaint);
|
canvas.drawRRect(frameRect.deflate(1.5), borderPaint);
|
||||||
canvas.drawRect(frameRect.deflate(5), innerBorderPaint);
|
canvas.drawRRect(frameRect.deflate(5), innerBorderPaint);
|
||||||
|
|
||||||
final highlightPaint =
|
final highlightPaint =
|
||||||
Paint()
|
Paint()
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import 'package:yumi/app_localizations.dart';
|
|||||||
import 'package:yumi/shared/tools/sc_version_utils.dart';
|
import 'package:yumi/shared/tools/sc_version_utils.dart';
|
||||||
import 'package:yumi/shared/tools/sc_google_auth_utils.dart';
|
import 'package:yumi/shared/tools/sc_google_auth_utils.dart';
|
||||||
import 'package:yumi/shared/data_sources/models/enum/sc_auth_type.dart';
|
import 'package:yumi/shared/data_sources/models/enum/sc_auth_type.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/local/data_persistence.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/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';
|
||||||
@ -486,13 +485,11 @@ class _SplashPageState extends State<SplashPage> {
|
|||||||
if (uid.isEmpty) {
|
if (uid.isEmpty) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
await DataPersistence.setPendingChannelAuth(SCAuthType.GOOGLE.name, uid);
|
|
||||||
final user = await SCAccountRepository().loginForChannel(
|
final user = await SCAccountRepository().loginForChannel(
|
||||||
SCAuthType.GOOGLE.name,
|
SCAuthType.GOOGLE.name,
|
||||||
uid,
|
uid,
|
||||||
);
|
);
|
||||||
AccountStorage().setCurrentUser(user);
|
AccountStorage().setCurrentUser(user);
|
||||||
await DataPersistence.clearPendingChannelAuth();
|
|
||||||
return user;
|
return user;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('restore login session failed: $e');
|
debugPrint('restore login session failed: $e');
|
||||||
|
|||||||
@ -86,7 +86,6 @@ class _MePage2State extends State<MePage2> {
|
|||||||
'badge=${_vipEntryBadgeLogValue(status.badge)}',
|
'badge=${_vipEntryBadgeLogValue(status.badge)}',
|
||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
_syncCurrentProfileVipStatus(status);
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_vipStatus = status;
|
_vipStatus = status;
|
||||||
_isVipStatusLoading = false;
|
_isVipStatusLoading = false;
|
||||||
@ -114,25 +113,6 @@ class _MePage2State extends State<MePage2> {
|
|||||||
return repository.vipStatus();
|
return repository.vipStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _syncCurrentProfileVipStatus(SCVipStatusRes status) {
|
|
||||||
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
|
||||||
context,
|
|
||||||
listen: false,
|
|
||||||
);
|
|
||||||
final profile = profileManager.currentUserProfile;
|
|
||||||
if (profile == null) return;
|
|
||||||
|
|
||||||
final nextVipLevel =
|
|
||||||
status.active == false || status.levelInt <= 0
|
|
||||||
? '0'
|
|
||||||
: status.levelInt.toString();
|
|
||||||
if (profile.vipLevel == nextVipLevel) return;
|
|
||||||
|
|
||||||
profileManager.syncCurrentUserProfile(
|
|
||||||
profile.copyWith(vipLevel: nextVipLevel),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _refreshAfterVipReturn(SocialChatUserProfileManager profileManager) {
|
void _refreshAfterVipReturn(SocialChatUserProfileManager profileManager) {
|
||||||
debugPrint('[VIP][MeEntry] refresh user data after vip page return');
|
debugPrint('[VIP][MeEntry] refresh user data after vip page return');
|
||||||
profileManager.fetchUserProfileData(loadGuardCount: false);
|
profileManager.fetchUserProfileData(loadGuardCount: false);
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import 'package:yumi/shared/tools/sc_permission_utils.dart';
|
|||||||
import 'package:yumi/shared/tools/sc_room_utils.dart';
|
import 'package:yumi/shared/tools/sc_room_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/data_sources/sources/repositories/sc_room_repository_imp.dart';
|
import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_vip_repository_imp.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/services/auth/user_profile_manager.dart';
|
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||||
@ -44,7 +43,6 @@ import '../../shared/business_logic/models/res/sc_is_follow_room_res.dart';
|
|||||||
import '../../shared/business_logic/models/res/sc_room_red_packet_list_res.dart';
|
import '../../shared/business_logic/models/res/sc_room_red_packet_list_res.dart';
|
||||||
import '../../shared/business_logic/models/res/sc_room_rocket_status_res.dart';
|
import '../../shared/business_logic/models/res/sc_room_rocket_status_res.dart';
|
||||||
import '../../shared/business_logic/models/res/sc_room_theme_list_res.dart';
|
import '../../shared/business_logic/models/res/sc_room_theme_list_res.dart';
|
||||||
import '../../shared/business_logic/models/res/sc_vip_res.dart';
|
|
||||||
import '../../shared/tools/sc_room_profile_cache.dart';
|
import '../../shared/tools/sc_room_profile_cache.dart';
|
||||||
import '../../ui_kit/components/sc_float_ichart.dart';
|
import '../../ui_kit/components/sc_float_ichart.dart';
|
||||||
|
|
||||||
@ -249,7 +247,6 @@ class RoomEntryPreviewData {
|
|||||||
),
|
),
|
||||||
roomSetting: roomSetting,
|
roomSetting: roomSetting,
|
||||||
userProfile: ownerProfile,
|
userProfile: ownerProfile,
|
||||||
userSVipLevel: ownerProfile?.vipLevel,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -687,7 +684,6 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
previous.userNickname == next.userNickname &&
|
previous.userNickname == next.userNickname &&
|
||||||
previous.userSex == next.userSex &&
|
previous.userSex == next.userSex &&
|
||||||
previous.roles == next.roles &&
|
previous.roles == next.roles &&
|
||||||
previous.vipLevel == next.vipLevel &&
|
|
||||||
previous.heartbeatVal == next.heartbeatVal;
|
previous.heartbeatVal == next.heartbeatVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -752,8 +748,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
: previousMic?.number,
|
: previousMic?.number,
|
||||||
)
|
)
|
||||||
: roomWheat;
|
: roomWheat;
|
||||||
final hydratedMic = _mergeKnownVipIntoMic(mergedMic);
|
final normalizedUserId = (mergedMic.user?.id ?? "").trim();
|
||||||
final normalizedUserId = (hydratedMic.user?.id ?? "").trim();
|
|
||||||
if (normalizedUserId.isNotEmpty) {
|
if (normalizedUserId.isNotEmpty) {
|
||||||
final existingIndex = userSeatMap[normalizedUserId];
|
final existingIndex = userSeatMap[normalizedUserId];
|
||||||
if (existingIndex != null) {
|
if (existingIndex != null) {
|
||||||
@ -770,7 +765,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
userSeatMap[normalizedUserId] = micIndex;
|
userSeatMap[normalizedUserId] = micIndex;
|
||||||
}
|
}
|
||||||
nextMap[micIndex] = hydratedMic;
|
nextMap[micIndex] = mergedMic;
|
||||||
}
|
}
|
||||||
return _stabilizeSelfMicSnapshot(nextMap, previousMap: previousMap);
|
return _stabilizeSelfMicSnapshot(nextMap, previousMap: previousMap);
|
||||||
}
|
}
|
||||||
@ -2098,9 +2093,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
unawaited(_cleanupStaleEnteredRoom(enteredRoom));
|
unawaited(_cleanupStaleEnteredRoom(enteredRoom));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currenRoom = _mergeVoiceRoomVipHints(enteredRoom, currenRoom);
|
currenRoom = enteredRoom;
|
||||||
_syncCurrentUserVipHintFromRoom(currenRoom);
|
|
||||||
unawaited(_refreshCurrentUserVipHintForRoom(entryRequestSerial));
|
|
||||||
_currentRoomIsEntryPreview = false;
|
_currentRoomIsEntryPreview = false;
|
||||||
_previewRoomSeatCount = null;
|
_previewRoomSeatCount = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@ -2119,322 +2112,6 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JoinRoomRes _mergeVoiceRoomVipHints(
|
|
||||||
JoinRoomRes enteredRoom,
|
|
||||||
JoinRoomRes? previewRoom,
|
|
||||||
) {
|
|
||||||
final roomProfile = enteredRoom.roomProfile;
|
|
||||||
if (roomProfile == null) {
|
|
||||||
return enteredRoom;
|
|
||||||
}
|
|
||||||
|
|
||||||
final enteredOwner = roomProfile.userProfile;
|
|
||||||
if (_profileHasPositiveVipLevelHint(enteredOwner)) {
|
|
||||||
return enteredRoom;
|
|
||||||
}
|
|
||||||
|
|
||||||
final roomId = _firstNonBlankText([roomProfile.roomProfile?.id]);
|
|
||||||
final ownerId = _firstNonBlankText([
|
|
||||||
roomProfile.roomProfile?.userId,
|
|
||||||
enteredOwner?.id,
|
|
||||||
]);
|
|
||||||
final previewRoomProfile = previewRoom?.roomProfile;
|
|
||||||
final previewOwner = previewRoomProfile?.userProfile;
|
|
||||||
final previewRoomId = _firstNonBlankText([
|
|
||||||
previewRoomProfile?.roomProfile?.id,
|
|
||||||
]);
|
|
||||||
final previewOwnerId = _firstNonBlankText([
|
|
||||||
previewRoomProfile?.roomProfile?.userId,
|
|
||||||
previewOwner?.id,
|
|
||||||
]);
|
|
||||||
final currentProfile = AccountStorage().getCurrentUser()?.userProfile;
|
|
||||||
|
|
||||||
SocialChatUserProfile? resolvedOwner = enteredOwner;
|
|
||||||
String? vipLevel;
|
|
||||||
|
|
||||||
final canUsePreviewOwner =
|
|
||||||
previewOwner != null &&
|
|
||||||
_sameNonBlankId(roomId, previewRoomId) &&
|
|
||||||
(ownerId == null || _sameNonBlankId(ownerId, previewOwnerId));
|
|
||||||
if (canUsePreviewOwner) {
|
|
||||||
vipLevel = _profileVipLevelHint(previewOwner);
|
|
||||||
resolvedOwner ??= previewOwner;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vipLevel == null && _sameNonBlankId(ownerId, currentProfile?.id)) {
|
|
||||||
vipLevel =
|
|
||||||
_profileVipLevelHint(currentProfile) ??
|
|
||||||
_roomVipLevelHintFromEntrants(enteredRoom.entrants);
|
|
||||||
resolvedOwner ??= currentProfile;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vipLevel == null || resolvedOwner == null) {
|
|
||||||
return enteredRoom;
|
|
||||||
}
|
|
||||||
|
|
||||||
return enteredRoom.copyWith(
|
|
||||||
roomProfile: roomProfile.copyWith(
|
|
||||||
userProfile: resolvedOwner.copyWith(vipLevel: vipLevel),
|
|
||||||
userSVipLevel: vipLevel,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _syncCurrentUserVipHintFromRoom(JoinRoomRes? room) {
|
|
||||||
final currentUser = AccountStorage().getCurrentUser();
|
|
||||||
final currentProfile = currentUser?.userProfile;
|
|
||||||
if (currentUser == null ||
|
|
||||||
currentProfile == null ||
|
|
||||||
_profileHasPositiveVipLevelHint(currentProfile)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final ownerProfile = room?.roomProfile?.userProfile;
|
|
||||||
final ownerId = _firstNonBlankText([
|
|
||||||
room?.roomProfile?.roomProfile?.userId,
|
|
||||||
ownerProfile?.id,
|
|
||||||
]);
|
|
||||||
String? vipLevel;
|
|
||||||
if (_sameNonBlankId(ownerId, currentProfile.id)) {
|
|
||||||
vipLevel = _profileVipLevelHint(ownerProfile);
|
|
||||||
}
|
|
||||||
vipLevel ??= _roomVipLevelHintFromEntrants(room?.entrants);
|
|
||||||
if (vipLevel == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AccountStorage().setCurrentUser(
|
|
||||||
currentUser.copyWith(
|
|
||||||
userProfile: currentProfile.copyWith(vipLevel: vipLevel),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _refreshCurrentUserVipHintForRoom(int entryRequestSerial) async {
|
|
||||||
final roomId = currenRoom?.roomProfile?.roomProfile?.id ?? "";
|
|
||||||
if (roomId.isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final currentUser = AccountStorage().getCurrentUser();
|
|
||||||
final currentProfile = currentUser?.userProfile;
|
|
||||||
if (currentUser == null || currentProfile == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final existingVipLevel = _positiveProfileVipLevelHint(currentProfile);
|
|
||||||
final vipLevel =
|
|
||||||
existingVipLevel ?? await _loadCurrentVipLevelForColoredId();
|
|
||||||
if (vipLevel == null || vipLevel.isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final updatedProfile = currentProfile.copyWith(vipLevel: vipLevel);
|
|
||||||
if (!_profileHasPositiveVipLevelHint(currentProfile)) {
|
|
||||||
AccountStorage().setCurrentUser(
|
|
||||||
currentUser.copyWith(userProfile: updatedProfile),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_isCurrentRoomEntrySession(entryRequestSerial, roomId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var changed = false;
|
|
||||||
final mergedRoom = _mergeKnownVipIntoCurrentRoom(
|
|
||||||
currenRoom,
|
|
||||||
updatedProfile,
|
|
||||||
);
|
|
||||||
if (!identical(mergedRoom, currenRoom)) {
|
|
||||||
currenRoom = mergedRoom;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final mergedOnlineUsers =
|
|
||||||
onlineUsers
|
|
||||||
.map((user) => _mergeKnownVipIntoProfile(user) ?? user)
|
|
||||||
.toList();
|
|
||||||
if (!_sameOnlineUsers(onlineUsers, mergedOnlineUsers)) {
|
|
||||||
onlineUsers = mergedOnlineUsers;
|
|
||||||
_refreshManagerUsers(onlineUsers);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final mergedMicMap = roomWheatMap.map(
|
|
||||||
(index, mic) => MapEntry(index, _mergeKnownVipIntoMic(mic)),
|
|
||||||
);
|
|
||||||
if (!_sameMicMaps(roomWheatMap, mergedMicMap)) {
|
|
||||||
roomWheatMap = mergedMicMap;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changed) {
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String?> _loadCurrentVipLevelForColoredId() async {
|
|
||||||
final repository = SCVipRepositoryImp();
|
|
||||||
try {
|
|
||||||
final home = await repository.vipHome();
|
|
||||||
final homeVipLevel = _vipLevelFromStatus(home.state);
|
|
||||||
if (homeVipLevel != null) {
|
|
||||||
return homeVipLevel;
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return _vipLevelFromStatus(await repository.vipStatus());
|
|
||||||
} catch (_) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String? _vipLevelFromStatus(SCVipStatusRes? status) {
|
|
||||||
if (status == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (status.active == false) {
|
|
||||||
return '0';
|
|
||||||
}
|
|
||||||
final level = status.levelInt;
|
|
||||||
return level > 0 ? level.toString() : '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
JoinRoomRes? _mergeKnownVipIntoCurrentRoom(
|
|
||||||
JoinRoomRes? room,
|
|
||||||
SocialChatUserProfile currentProfile,
|
|
||||||
) {
|
|
||||||
final roomProfile = room?.roomProfile;
|
|
||||||
final ownerProfile = roomProfile?.userProfile;
|
|
||||||
if (room == null || roomProfile == null || ownerProfile == null) {
|
|
||||||
return room;
|
|
||||||
}
|
|
||||||
|
|
||||||
final ownerId = _firstNonBlankText([
|
|
||||||
roomProfile.roomProfile?.userId,
|
|
||||||
ownerProfile.id,
|
|
||||||
]);
|
|
||||||
if (!_sameNonBlankId(ownerId, currentProfile.id) ||
|
|
||||||
_profileHasPositiveVipLevelHint(ownerProfile)) {
|
|
||||||
return room;
|
|
||||||
}
|
|
||||||
|
|
||||||
final vipLevel = _positiveProfileVipLevelHint(currentProfile);
|
|
||||||
if (vipLevel == null) {
|
|
||||||
return room;
|
|
||||||
}
|
|
||||||
|
|
||||||
return room.copyWith(
|
|
||||||
roomProfile: roomProfile.copyWith(
|
|
||||||
userProfile: ownerProfile.copyWith(vipLevel: vipLevel),
|
|
||||||
userSVipLevel: vipLevel,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
MicRes _mergeKnownVipIntoMic(MicRes mic) {
|
|
||||||
final mergedUser = _mergeKnownVipIntoProfile(mic.user);
|
|
||||||
if (identical(mergedUser, mic.user)) {
|
|
||||||
return mic;
|
|
||||||
}
|
|
||||||
return mic.copyWith(user: mergedUser);
|
|
||||||
}
|
|
||||||
|
|
||||||
SocialChatUserProfile? _mergeKnownVipIntoProfile(
|
|
||||||
SocialChatUserProfile? profile,
|
|
||||||
) {
|
|
||||||
if (profile == null || _profileHasPositiveVipLevelHint(profile)) {
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
final currentProfile = AccountStorage().getCurrentUser()?.userProfile;
|
|
||||||
final currentVipLevel = _positiveProfileVipLevelHint(currentProfile);
|
|
||||||
if (_sameNonBlankId(profile.id, currentProfile?.id) &&
|
|
||||||
currentVipLevel != null) {
|
|
||||||
return profile.copyWith(vipLevel: currentVipLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
final ownerProfile = currenRoom?.roomProfile?.userProfile;
|
|
||||||
final ownerVipLevel = _positiveProfileVipLevelHint(ownerProfile);
|
|
||||||
final ownerId = _firstNonBlankText([
|
|
||||||
currenRoom?.roomProfile?.roomProfile?.userId,
|
|
||||||
ownerProfile?.id,
|
|
||||||
]);
|
|
||||||
if (_sameNonBlankId(profile.id, ownerId) && ownerVipLevel != null) {
|
|
||||||
return profile.copyWith(vipLevel: ownerVipLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _profileHasPositiveVipLevelHint(SocialChatUserProfile? profile) {
|
|
||||||
return _positiveProfileVipLevelHint(profile) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String? _profileVipLevelHint(SocialChatUserProfile? profile) {
|
|
||||||
return _firstNonBlankText([profile?.vipLevel]);
|
|
||||||
}
|
|
||||||
|
|
||||||
String? _positiveProfileVipLevelHint(SocialChatUserProfile? profile) {
|
|
||||||
final vipLevel = _profileVipLevelHint(profile);
|
|
||||||
if (profile == null || profile.vipLevelForColoredId <= 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return vipLevel ?? profile.vipLevelForColoredId.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
String? _roomVipLevelHintFromEntrants(Entrants? entrants) {
|
|
||||||
final abilities = entrants?.nobleVipAbility ?? const <String>[];
|
|
||||||
var hasAbility = false;
|
|
||||||
for (final ability in abilities) {
|
|
||||||
final text = ability.trim();
|
|
||||||
final normalized = text.toLowerCase();
|
|
||||||
if (text.isEmpty || normalized == '0' || normalized == 'false') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
hasAbility = true;
|
|
||||||
if (_looksLikeVipLevelText(text)) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hasAbility ? '1' : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _looksLikeVipLevelText(String text) {
|
|
||||||
final plainLevel = int.tryParse(text);
|
|
||||||
if ((plainLevel ?? 0) > 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (RegExp(
|
|
||||||
r'(?:S?VIP|NOBLE[_\s-]*VIP)[^\d]*[1-9]\d*',
|
|
||||||
caseSensitive: false,
|
|
||||||
).hasMatch(text)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return RegExp(r'[1-9]\d*$').hasMatch(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
String? _firstNonBlankText(Iterable<dynamic> values) {
|
|
||||||
for (final value in values) {
|
|
||||||
final text = value?.toString().trim();
|
|
||||||
if (text != null && text.isNotEmpty) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _sameNonBlankId(dynamic left, dynamic right) {
|
|
||||||
final leftText = left?.toString().trim();
|
|
||||||
final rightText = right?.toString().trim();
|
|
||||||
return leftText != null &&
|
|
||||||
leftText.isNotEmpty &&
|
|
||||||
rightText != null &&
|
|
||||||
rightText.isNotEmpty &&
|
|
||||||
leftText == rightText;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _isActiveRoomEntryRequest(int entryRequestSerial) {
|
bool _isActiveRoomEntryRequest(int entryRequestSerial) {
|
||||||
return entryRequestSerial == _roomEntryRequestSerial &&
|
return entryRequestSerial == _roomEntryRequestSerial &&
|
||||||
_roomStartupStatus == RoomStartupStatus.loading;
|
_roomStartupStatus == RoomStartupStatus.loading;
|
||||||
@ -2678,11 +2355,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
!_isCurrentRoomEntrySession(entryRequestSerial, roomId)) {
|
!_isCurrentRoomEntrySession(entryRequestSerial, roomId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SCGiftVapSvgaManager().play(
|
SCGiftVapSvgaManager().play(sourceUrl, priority: 100, type: 1);
|
||||||
sourceUrl,
|
|
||||||
priority: SCGiftVapSvgaManager.entryEffectPriority,
|
|
||||||
type: SCGiftVapSvgaManager.entryEffectType,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2732,10 +2405,8 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
if (roomId != currenRoom?.roomProfile?.roomProfile?.id) {
|
if (roomId != currenRoom?.roomProfile?.roomProfile?.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final mergedUsers =
|
final changed = !_sameOnlineUsers(onlineUsers, fetchedUsers);
|
||||||
fetchedUsers.map((user) => _mergeKnownVipIntoProfile(user)!).toList();
|
onlineUsers = fetchedUsers;
|
||||||
final changed = !_sameOnlineUsers(onlineUsers, mergedUsers);
|
|
||||||
onlineUsers = mergedUsers;
|
|
||||||
_refreshManagerUsers(onlineUsers);
|
_refreshManagerUsers(onlineUsers);
|
||||||
if (changed || notifyIfUnchanged) {
|
if (changed || notifyIfUnchanged) {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@ -3728,19 +3399,15 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
if (groupId != currenRoom?.roomProfile?.roomProfile?.roomAccount) {
|
if (groupId != currenRoom?.roomProfile?.roomProfile?.roomAccount) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final mergedUser = _mergeKnownVipIntoProfile(user) ?? user;
|
|
||||||
bool isExtOnlineList = false;
|
bool isExtOnlineList = false;
|
||||||
for (var us in onlineUsers) {
|
for (var us in onlineUsers) {
|
||||||
if (us.id == mergedUser.id) {
|
if (us.id == user.id) {
|
||||||
isExtOnlineList = true;
|
isExtOnlineList = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isExtOnlineList) {
|
if (!isExtOnlineList) {
|
||||||
Provider.of<RtcProvider>(
|
Provider.of<RtcProvider>(context!, listen: false).onlineUsers.add(user);
|
||||||
context!,
|
|
||||||
listen: false,
|
|
||||||
).onlineUsers.add(mergedUser);
|
|
||||||
_refreshManagerUsers(onlineUsers);
|
_refreshManagerUsers(onlineUsers);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1517,8 +1517,8 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
shouldShowRoomVisualEffects) {
|
shouldShowRoomVisualEffects) {
|
||||||
SCGiftVapSvgaManager().play(
|
SCGiftVapSvgaManager().play(
|
||||||
msg.user?.getMountains()?.sourceUrl ?? "",
|
msg.user?.getMountains()?.sourceUrl ?? "",
|
||||||
priority: SCGiftVapSvgaManager.entryEffectPriority,
|
priority: 100,
|
||||||
type: SCGiftVapSvgaManager.entryEffectType,
|
type: 1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
|
import 'package:sign_in_with_apple/sign_in_with_apple.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_google_auth_utils.dart';
|
import 'package:yumi/shared/tools/sc_google_auth_utils.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_version_utils.dart';
|
import 'package:yumi/shared/tools/sc_version_utils.dart';
|
||||||
import 'package:yumi/shared/data_sources/sources/local/data_persistence.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/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/shared/business_logic/models/res/login_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
||||||
@ -39,11 +39,6 @@ class SocialChatAuthenticationManager extends ChangeNotifier {
|
|||||||
Future<void> _verifyExistingSession() async {
|
Future<void> _verifyExistingSession() async {
|
||||||
final user = await SCGoogleAuthService.signInSilently();
|
final user = await SCGoogleAuthService.signInSilently();
|
||||||
_user = user;
|
_user = user;
|
||||||
if (user != null) {
|
|
||||||
authType = SCAuthType.GOOGLE.name;
|
|
||||||
uid = user.uid;
|
|
||||||
await DataPersistence.setPendingChannelAuth(authType, uid);
|
|
||||||
}
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +53,6 @@ class SocialChatAuthenticationManager extends ChangeNotifier {
|
|||||||
uid = _user!.uid;
|
uid = _user!.uid;
|
||||||
SCLoadingManager.show();
|
SCLoadingManager.show();
|
||||||
if (uid.isNotEmpty) {
|
if (uid.isNotEmpty) {
|
||||||
await DataPersistence.setPendingChannelAuth(authType, uid);
|
|
||||||
final res = await Future.wait([
|
final res = await Future.wait([
|
||||||
SCAccountRepository().loginForChannel(authType, uid),
|
SCAccountRepository().loginForChannel(authType, uid),
|
||||||
SCVersionUtils.checkReview(),
|
SCVersionUtils.checkReview(),
|
||||||
@ -80,10 +74,6 @@ class SocialChatAuthenticationManager extends ChangeNotifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AccountStorage().setCurrentUser(user);
|
AccountStorage().setCurrentUser(user);
|
||||||
await DataPersistence.clearPendingChannelAuth();
|
|
||||||
if (!context.mounted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SCLoadingManager.hide();
|
SCLoadingManager.hide();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
SCNavigatorUtils.push(context, SCRoutes.home, replace: true);
|
SCNavigatorUtils.push(context, SCRoutes.home, replace: true);
|
||||||
@ -103,7 +93,6 @@ class SocialChatAuthenticationManager extends ChangeNotifier {
|
|||||||
uid = appleCredential.userIdentifier ?? "";
|
uid = appleCredential.userIdentifier ?? "";
|
||||||
SCLoadingManager.show();
|
SCLoadingManager.show();
|
||||||
if (uid.isNotEmpty) {
|
if (uid.isNotEmpty) {
|
||||||
await DataPersistence.setPendingChannelAuth(authType, uid);
|
|
||||||
final res = await Future.wait([
|
final res = await Future.wait([
|
||||||
SCAccountRepository().loginForChannel(authType, uid),
|
SCAccountRepository().loginForChannel(authType, uid),
|
||||||
SCVersionUtils.checkReview(),
|
SCVersionUtils.checkReview(),
|
||||||
@ -125,10 +114,6 @@ class SocialChatAuthenticationManager extends ChangeNotifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AccountStorage().setCurrentUser(user);
|
AccountStorage().setCurrentUser(user);
|
||||||
await DataPersistence.clearPendingChannelAuth();
|
|
||||||
if (!context.mounted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SCLoadingManager.hide();
|
SCLoadingManager.hide();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
SCNavigatorUtils.push(context, SCRoutes.home, replace: true);
|
SCNavigatorUtils.push(context, SCRoutes.home, replace: true);
|
||||||
@ -142,7 +127,6 @@ class SocialChatAuthenticationManager extends ChangeNotifier {
|
|||||||
|
|
||||||
Future<void> logoutUser() async {
|
Future<void> logoutUser() async {
|
||||||
await SCGoogleAuthService.signOut();
|
await SCGoogleAuthService.signOut();
|
||||||
await DataPersistence.clearPendingChannelAuth();
|
|
||||||
_user = null;
|
_user = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,13 +4,11 @@ import 'package:yumi/ui_kit/components/sc_tts.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/data_sources/sources/repositories/sc_room_repository_imp.dart';
|
import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.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/shared/data_sources/sources/repositories/sc_vip_repository_imp.dart';
|
|
||||||
import 'package:yumi/shared/business_logic/models/req/sc_user_profile_cmd.dart';
|
import 'package:yumi/shared/business_logic/models/req/sc_user_profile_cmd.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/country_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/country_res.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/room_user_card_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/room_user_card_res.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/sc_user_identity_res.dart';
|
import 'package:yumi/shared/business_logic/models/res/sc_user_identity_res.dart';
|
||||||
import 'package:yumi/shared/business_logic/models/res/sc_vip_res.dart';
|
|
||||||
|
|
||||||
class SocialChatUserProfileManager extends ChangeNotifier {
|
class SocialChatUserProfileManager extends ChangeNotifier {
|
||||||
SCUserProfileCmd? editUser;
|
SCUserProfileCmd? editUser;
|
||||||
@ -88,7 +86,6 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var userInfo = await SCAccountRepository().loadUserInfo(userId);
|
var userInfo = await SCAccountRepository().loadUserInfo(userId);
|
||||||
userInfo = await _withCurrentVipLevel(userInfo, userId);
|
|
||||||
syncCurrentUserProfile(userInfo);
|
syncCurrentUserProfile(userInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,8 +101,7 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
|||||||
|
|
||||||
void getUserInfoById(String userId) async {
|
void getUserInfoById(String userId) async {
|
||||||
userProfile = null;
|
userProfile = null;
|
||||||
final loadedProfile = await SCAccountRepository().loadUserInfo(userId);
|
userProfile = await SCAccountRepository().loadUserInfo(userId);
|
||||||
userProfile = await _withCurrentVipLevel(loadedProfile, userId);
|
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
@ -118,85 +114,9 @@ class SocialChatUserProfileManager extends ChangeNotifier {
|
|||||||
void roomUserCard(String roomId, String userId) async {
|
void roomUserCard(String roomId, String userId) async {
|
||||||
userCardInfo = null;
|
userCardInfo = null;
|
||||||
userCardInfo = await SCChatRoomRepository().roomUserCard(roomId, userId);
|
userCardInfo = await SCChatRoomRepository().roomUserCard(roomId, userId);
|
||||||
userCardInfo = await _withCurrentVipLevelForCard(userCardInfo, userId);
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<RoomUserCardRes?> _withCurrentVipLevelForCard(
|
|
||||||
RoomUserCardRes? card,
|
|
||||||
String userId,
|
|
||||||
) async {
|
|
||||||
final profile = card?.userProfile;
|
|
||||||
if (card == null || profile == null) {
|
|
||||||
return card;
|
|
||||||
}
|
|
||||||
final mergedProfile = await _withCurrentVipLevel(profile, userId);
|
|
||||||
if (mergedProfile == profile) {
|
|
||||||
return card;
|
|
||||||
}
|
|
||||||
return card.copyWith(userProfile: mergedProfile);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SocialChatUserProfile> _withCurrentVipLevel(
|
|
||||||
SocialChatUserProfile profile,
|
|
||||||
String userId,
|
|
||||||
) async {
|
|
||||||
if (!_isCurrentUserId(userId)) {
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
final profileVipLevel = profile.vipLevel?.trim();
|
|
||||||
if (profileVipLevel != null && profileVipLevel.isNotEmpty) {
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
final currentVipLevel = currentUserProfile?.vipLevel?.trim();
|
|
||||||
final resolvedVipLevel =
|
|
||||||
currentVipLevel != null && currentVipLevel.isNotEmpty
|
|
||||||
? currentVipLevel
|
|
||||||
: await _loadCurrentVipLevel();
|
|
||||||
if (resolvedVipLevel == null || resolvedVipLevel.isEmpty) {
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
return profile.copyWith(vipLevel: resolvedVipLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _isCurrentUserId(String userId) {
|
|
||||||
final currentUserId = AccountStorage().getCurrentUser()?.userProfile?.id;
|
|
||||||
return currentUserId != null &&
|
|
||||||
currentUserId.isNotEmpty &&
|
|
||||||
currentUserId == userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String?> _loadCurrentVipLevel() async {
|
|
||||||
final repository = SCVipRepositoryImp();
|
|
||||||
try {
|
|
||||||
final home = await repository.vipHome();
|
|
||||||
final homeVipLevel = _vipLevelFromStatus(home.state);
|
|
||||||
if (homeVipLevel != null) {
|
|
||||||
return homeVipLevel;
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return _vipLevelFromStatus(await repository.vipStatus());
|
|
||||||
} catch (_) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String? _vipLevelFromStatus(SCVipStatusRes? status) {
|
|
||||||
if (status == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (status.active == false) {
|
|
||||||
return '0';
|
|
||||||
}
|
|
||||||
final level = status.levelInt;
|
|
||||||
return level > 0 ? level.toString() : '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
void followUser(String userId) async {
|
void followUser(String userId) async {
|
||||||
var result = await SCAccountRepository().followUser(userId);
|
var result = await SCAccountRepository().followUser(userId);
|
||||||
if (result) {
|
if (result) {
|
||||||
|
|||||||
@ -53,8 +53,8 @@ class FollowRoomRes {
|
|||||||
/// userProfile : {"account":"","accountStatus":"","age":0,"bornDay":0,"bornMonth":0,"bornYear":0,"countryCode":"","countryId":0,"countryName":"","createTime":0,"del":false,"freezingTime":0,"id":"s","originSys":"","ownSpecialId":{"account":"","expiredTime":0},"sameRegion":false,"sysOriginChild":"","useProps":[{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}],"userAvatar":"","userNickname":"","userSex":0,"wearBadge":[{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}]}
|
/// userProfile : {"account":"","accountStatus":"","age":0,"bornDay":0,"bornMonth":0,"bornYear":0,"countryCode":"","countryId":0,"countryName":"","createTime":0,"del":false,"freezingTime":0,"id":"s","originSys":"","ownSpecialId":{"account":"","expiredTime":0},"sameRegion":false,"sysOriginChild":"","useProps":[{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}],"userAvatar":"","userNickname":"","userSex":0,"wearBadge":[{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}]}
|
||||||
/// userSVipLevel : ""
|
/// userSVipLevel : ""
|
||||||
|
|
||||||
class RoomProfile {
|
class RoomProfile {
|
||||||
RoomProfile({
|
RoomProfile({
|
||||||
String? countryCode,
|
String? countryCode,
|
||||||
String? countryName,
|
String? countryName,
|
||||||
String? event,
|
String? event,
|
||||||
@ -69,12 +69,12 @@ class RoomProfile {
|
|||||||
String? roomGameIcon,
|
String? roomGameIcon,
|
||||||
String? roomName,
|
String? roomName,
|
||||||
String? sysOrigin,
|
String? sysOrigin,
|
||||||
String? userId,
|
String? userId,
|
||||||
SocialChatUserProfile? userProfile,
|
SocialChatUserProfile? userProfile,
|
||||||
String? userSVipLevel,
|
String? userSVipLevel,
|
||||||
RoomMemberCounter? roomCounter,
|
RoomMemberCounter? roomCounter,
|
||||||
ExtValues? extValues,
|
ExtValues? extValues,
|
||||||
}) {
|
}) {
|
||||||
_countryCode = countryCode;
|
_countryCode = countryCode;
|
||||||
_countryName = countryName;
|
_countryName = countryName;
|
||||||
_event = event;
|
_event = event;
|
||||||
@ -89,16 +89,12 @@ class RoomProfile {
|
|||||||
_roomGameIcon = roomGameIcon;
|
_roomGameIcon = roomGameIcon;
|
||||||
_roomName = roomName;
|
_roomName = roomName;
|
||||||
_sysOrigin = sysOrigin;
|
_sysOrigin = sysOrigin;
|
||||||
_userId = userId;
|
_userId = userId;
|
||||||
_userSVipLevel = userSVipLevel;
|
_userProfile = userProfile;
|
||||||
final normalizedVipLevel = userSVipLevel?.trim();
|
_userSVipLevel = userSVipLevel;
|
||||||
_userProfile =
|
_roomCounter = roomCounter;
|
||||||
normalizedVipLevel == null || normalizedVipLevel.isEmpty
|
_extValues = extValues;
|
||||||
? userProfile
|
}
|
||||||
: userProfile?.copyWith(vipLevel: normalizedVipLevel);
|
|
||||||
_roomCounter = roomCounter;
|
|
||||||
_extValues = extValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
RoomProfile.fromJson(dynamic json) {
|
RoomProfile.fromJson(dynamic json) {
|
||||||
_countryCode = json['countryCode'];
|
_countryCode = json['countryCode'];
|
||||||
@ -119,22 +115,18 @@ class RoomProfile {
|
|||||||
_roomName = json['roomName'];
|
_roomName = json['roomName'];
|
||||||
_sysOrigin = json['sysOrigin'];
|
_sysOrigin = json['sysOrigin'];
|
||||||
_userId = json['userId'];
|
_userId = json['userId'];
|
||||||
_userProfile =
|
_userProfile =
|
||||||
json['userProfile'] != null
|
json['userProfile'] != null
|
||||||
? SocialChatUserProfile.fromJson(json['userProfile'])
|
? SocialChatUserProfile.fromJson(json['userProfile'])
|
||||||
: null;
|
: null;
|
||||||
_userSVipLevel = socialChatVipLevelHintFromJson(json);
|
_userSVipLevel = json['userSVipLevel'];
|
||||||
final normalizedVipLevel = _userSVipLevel?.trim();
|
final counterJson = json['roomCounter'] ?? json['counter'];
|
||||||
if (normalizedVipLevel != null && normalizedVipLevel.isNotEmpty) {
|
_roomCounter =
|
||||||
_userProfile = _userProfile?.copyWith(vipLevel: normalizedVipLevel);
|
counterJson != null ? RoomMemberCounter.fromJson(counterJson) : null;
|
||||||
}
|
_extValues =
|
||||||
final counterJson = json['roomCounter'] ?? json['counter'];
|
json['extValues'] != null
|
||||||
_roomCounter =
|
? ExtValues.fromJson(json['extValues'])
|
||||||
counterJson != null ? RoomMemberCounter.fromJson(counterJson) : null;
|
: null;
|
||||||
_extValues =
|
|
||||||
json['extValues'] != null
|
|
||||||
? ExtValues.fromJson(json['extValues'])
|
|
||||||
: null;
|
|
||||||
}
|
}
|
||||||
String? _countryCode;
|
String? _countryCode;
|
||||||
String? _countryName;
|
String? _countryName;
|
||||||
@ -150,11 +142,11 @@ class RoomProfile {
|
|||||||
String? _roomGameIcon;
|
String? _roomGameIcon;
|
||||||
String? _roomName;
|
String? _roomName;
|
||||||
String? _sysOrigin;
|
String? _sysOrigin;
|
||||||
String? _userId;
|
String? _userId;
|
||||||
SocialChatUserProfile? _userProfile;
|
SocialChatUserProfile? _userProfile;
|
||||||
String? _userSVipLevel;
|
String? _userSVipLevel;
|
||||||
RoomMemberCounter? _roomCounter;
|
RoomMemberCounter? _roomCounter;
|
||||||
ExtValues? _extValues;
|
ExtValues? _extValues;
|
||||||
RoomProfile copyWith({
|
RoomProfile copyWith({
|
||||||
String? countryCode,
|
String? countryCode,
|
||||||
String? countryName,
|
String? countryName,
|
||||||
@ -170,12 +162,12 @@ class RoomProfile {
|
|||||||
String? roomGameIcon,
|
String? roomGameIcon,
|
||||||
String? roomName,
|
String? roomName,
|
||||||
String? sysOrigin,
|
String? sysOrigin,
|
||||||
String? userId,
|
String? userId,
|
||||||
SocialChatUserProfile? userProfile,
|
SocialChatUserProfile? userProfile,
|
||||||
String? userSVipLevel,
|
String? userSVipLevel,
|
||||||
RoomMemberCounter? roomCounter,
|
RoomMemberCounter? roomCounter,
|
||||||
ExtValues? extValues,
|
ExtValues? extValues,
|
||||||
}) => RoomProfile(
|
}) => RoomProfile(
|
||||||
countryCode: countryCode ?? _countryCode,
|
countryCode: countryCode ?? _countryCode,
|
||||||
countryName: countryName ?? _countryName,
|
countryName: countryName ?? _countryName,
|
||||||
event: event ?? _event,
|
event: event ?? _event,
|
||||||
@ -190,12 +182,12 @@ class RoomProfile {
|
|||||||
roomGameIcon: roomGameIcon ?? _roomGameIcon,
|
roomGameIcon: roomGameIcon ?? _roomGameIcon,
|
||||||
roomName: roomName ?? _roomName,
|
roomName: roomName ?? _roomName,
|
||||||
sysOrigin: sysOrigin ?? _sysOrigin,
|
sysOrigin: sysOrigin ?? _sysOrigin,
|
||||||
userId: userId ?? _userId,
|
userId: userId ?? _userId,
|
||||||
userProfile: userProfile ?? _userProfile,
|
userProfile: userProfile ?? _userProfile,
|
||||||
userSVipLevel: userSVipLevel ?? _userSVipLevel,
|
userSVipLevel: userSVipLevel ?? _userSVipLevel,
|
||||||
roomCounter: roomCounter ?? _roomCounter,
|
roomCounter: roomCounter ?? _roomCounter,
|
||||||
extValues: extValues ?? _extValues,
|
extValues: extValues ?? _extValues,
|
||||||
);
|
);
|
||||||
String? get countryCode => _countryCode;
|
String? get countryCode => _countryCode;
|
||||||
String? get countryName => _countryName;
|
String? get countryName => _countryName;
|
||||||
String? get event => _event;
|
String? get event => _event;
|
||||||
@ -211,25 +203,25 @@ class RoomProfile {
|
|||||||
String? get roomName => _roomName;
|
String? get roomName => _roomName;
|
||||||
String? get sysOrigin => _sysOrigin;
|
String? get sysOrigin => _sysOrigin;
|
||||||
String? get userId => _userId;
|
String? get userId => _userId;
|
||||||
SocialChatUserProfile? get userProfile => _userProfile;
|
SocialChatUserProfile? get userProfile => _userProfile;
|
||||||
String? get userSVipLevel => _userSVipLevel;
|
String? get userSVipLevel => _userSVipLevel;
|
||||||
RoomMemberCounter? get roomCounter => _roomCounter;
|
RoomMemberCounter? get roomCounter => _roomCounter;
|
||||||
ExtValues? get extValues => _extValues;
|
ExtValues? get extValues => _extValues;
|
||||||
|
|
||||||
String get displayMemberCount {
|
String get displayMemberCount {
|
||||||
final memberCount = roomCounter?.memberCount;
|
final memberCount = roomCounter?.memberCount;
|
||||||
if (memberCount != null) {
|
if (memberCount != null) {
|
||||||
if (memberCount == memberCount.roundToDouble()) {
|
if (memberCount == memberCount.roundToDouble()) {
|
||||||
return memberCount.toInt().toString();
|
return memberCount.toInt().toString();
|
||||||
}
|
}
|
||||||
return memberCount.toString();
|
return memberCount.toString();
|
||||||
}
|
}
|
||||||
final memberQuantity = extValues?.memberQuantity;
|
final memberQuantity = extValues?.memberQuantity;
|
||||||
if ((memberQuantity ?? "").trim().isNotEmpty) {
|
if ((memberQuantity ?? "").trim().isNotEmpty) {
|
||||||
return memberQuantity!;
|
return memberQuantity!;
|
||||||
}
|
}
|
||||||
return "0";
|
return "0";
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final map = <String, dynamic>{};
|
final map = <String, dynamic>{};
|
||||||
@ -248,16 +240,16 @@ class RoomProfile {
|
|||||||
map['roomName'] = _roomName;
|
map['roomName'] = _roomName;
|
||||||
map['sysOrigin'] = _sysOrigin;
|
map['sysOrigin'] = _sysOrigin;
|
||||||
map['userId'] = _userId;
|
map['userId'] = _userId;
|
||||||
if (_userProfile != null) {
|
if (_userProfile != null) {
|
||||||
map['userProfile'] = _userProfile?.toJson();
|
map['userProfile'] = _userProfile?.toJson();
|
||||||
}
|
}
|
||||||
map['userSVipLevel'] = _userSVipLevel;
|
map['userSVipLevel'] = _userSVipLevel;
|
||||||
if (_roomCounter != null) {
|
if (_roomCounter != null) {
|
||||||
map['roomCounter'] = _roomCounter?.toJson();
|
map['roomCounter'] = _roomCounter?.toJson();
|
||||||
}
|
}
|
||||||
if (_extValues != null) {
|
if (_extValues != null) {
|
||||||
map['extValues'] = _extValues?.toJson();
|
map['extValues'] = _extValues?.toJson();
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,31 +17,17 @@ class JoinRoomRes {
|
|||||||
_roomProps = roomProps;
|
_roomProps = roomProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
JoinRoomRes.fromJson(dynamic json) {
|
JoinRoomRes.fromJson(dynamic json) {
|
||||||
_entrants =
|
_entrants =
|
||||||
json['entrants'] != null ? Entrants.fromJson(json['entrants']) : null;
|
json['entrants'] != null ? Entrants.fromJson(json['entrants']) : null;
|
||||||
_roomProfile =
|
_roomProfile =
|
||||||
json['roomProfile'] != null
|
json['roomProfile'] != null
|
||||||
? RoomProfile.fromJson(json['roomProfile'])
|
? RoomProfile.fromJson(json['roomProfile'])
|
||||||
: null;
|
: null;
|
||||||
final roomVipLevel = _firstNonBlankRoomVipLevel([
|
_roomProps =
|
||||||
json['userSVipLevel'],
|
json['roomProps'] != null
|
||||||
json['supperVip'],
|
? RoomProps.fromJson(json['roomProps'])
|
||||||
json['superVip'],
|
: null;
|
||||||
json['vipLevel'],
|
|
||||||
json['svipLevel'],
|
|
||||||
json['sVipLevel'],
|
|
||||||
json['nobleVipLevel'],
|
|
||||||
json['vipLevelCode'],
|
|
||||||
json['levelCode'],
|
|
||||||
]);
|
|
||||||
if (roomVipLevel != null) {
|
|
||||||
_roomProfile = _roomProfile?.copyWith(userSVipLevel: roomVipLevel);
|
|
||||||
}
|
|
||||||
_roomProps =
|
|
||||||
json['roomProps'] != null
|
|
||||||
? RoomProps.fromJson(json['roomProps'])
|
|
||||||
: null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Entrants? _entrants;
|
Entrants? _entrants;
|
||||||
@ -76,23 +62,13 @@ class JoinRoomRes {
|
|||||||
map['roomProps'] = _roomProps?.toJson();
|
map['roomProps'] = _roomProps?.toJson();
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String? _firstNonBlankRoomVipLevel(Iterable<dynamic> values) {
|
/// layoutCode : ""
|
||||||
for (final value in values) {
|
/// roomTheme : {"expireTime":0,"id":0,"themeBack":"","themeStatus":"","useTheme":false}
|
||||||
final text = value?.toString().trim();
|
|
||||||
if (text != null && text.isNotEmpty) {
|
class RoomProps {
|
||||||
return text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// layoutCode : ""
|
|
||||||
/// roomTheme : {"expireTime":0,"id":0,"themeBack":"","themeStatus":"","useTheme":false}
|
|
||||||
|
|
||||||
class RoomProps {
|
|
||||||
RoomProps({String? layoutCode, RoomTheme? roomTheme}) {
|
RoomProps({String? layoutCode, RoomTheme? roomTheme}) {
|
||||||
_layoutCode = layoutCode;
|
_layoutCode = layoutCode;
|
||||||
_roomTheme = roomTheme;
|
_roomTheme = roomTheme;
|
||||||
@ -205,33 +181,26 @@ class RoomTheme {
|
|||||||
/// regionCode : ""
|
/// regionCode : ""
|
||||||
/// roomCounter : {"adminCount":0,"memberCount":0}
|
/// roomCounter : {"adminCount":0,"memberCount":0}
|
||||||
/// roomProfile : {"activeTime":0,"countryCode":"","countryName":"","createTime":0,"del":false,"event":"","id":"0","langCode":"","nationalFlag":"","roomAccount":"","roomCover":"","roomDesc":"","roomName":"","sysOrigin":"","updateTime":0,"userId":"0"}
|
/// roomProfile : {"activeTime":0,"countryCode":"","countryName":"","createTime":0,"del":false,"event":"","id":"0","langCode":"","nationalFlag":"","roomAccount":"","roomCover":"","roomDesc":"","roomName":"","sysOrigin":"","updateTime":0,"userId":"0"}
|
||||||
/// roomSetting : {"adminLockSeat":false,"allowMusic":false,"joinGolds":0,"lockLocation":"","maxAdmin":0,"maxMember":0,"mikeSize":0,"openKtvMode":false,"password":"","roomId":0,"roomSpecialMikeType":"","showHeartbeat":false,"takeMicRole":"","touristMike":false,"touristMsg":false,"userId":"0"}
|
/// roomSetting : {"adminLockSeat":false,"allowMusic":false,"joinGolds":0,"lockLocation":"","maxAdmin":0,"maxMember":0,"mikeSize":0,"openKtvMode":false,"password":"","roomId":0,"roomSpecialMikeType":"","showHeartbeat":false,"takeMicRole":"","touristMike":false,"touristMsg":false,"userId":"0"}
|
||||||
/// userProfile : {"account":"","accountStatus":"","age":0,"bornDay":0,"bornMonth":0,"bornYear":0,"countryCode":"","countryId":0,"countryName":"","createTime":0,"del":false,"freezingTime":0,"id":"0","originSys":"","ownSpecialId":{"account":"","expiredTime":0},"sameRegion":false,"sysOriginChild":"","useProps":[{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}],"userAvatar":"","userNickname":"","userSex":0,"wearBadge":[{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}]}
|
/// userProfile : {"account":"","accountStatus":"","age":0,"bornDay":0,"bornMonth":0,"bornYear":0,"countryCode":"","countryId":0,"countryName":"","createTime":0,"del":false,"freezingTime":0,"id":"0","originSys":"","ownSpecialId":{"account":"","expiredTime":0},"sameRegion":false,"sysOriginChild":"","useProps":[{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}],"userAvatar":"","userNickname":"","userSex":0,"wearBadge":[{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}]}
|
||||||
/// userSVipLevel : ""
|
|
||||||
|
class RoomProfile {
|
||||||
class RoomProfile {
|
RoomProfile({
|
||||||
RoomProfile({
|
String? regionCode,
|
||||||
String? regionCode,
|
RoomCounter? roomCounter,
|
||||||
RoomCounter? roomCounter,
|
RoomProfile2? roomProfile,
|
||||||
RoomProfile2? roomProfile,
|
RoomSetting? roomSetting,
|
||||||
RoomSetting? roomSetting,
|
SocialChatUserProfile? userProfile,
|
||||||
SocialChatUserProfile? userProfile,
|
}) {
|
||||||
String? userSVipLevel,
|
_regionCode = regionCode;
|
||||||
}) {
|
_roomCounter = roomCounter;
|
||||||
_regionCode = regionCode;
|
_roomProfile = roomProfile;
|
||||||
_roomCounter = roomCounter;
|
_roomSetting = roomSetting;
|
||||||
_roomProfile = roomProfile;
|
_userProfile = userProfile;
|
||||||
_roomSetting = roomSetting;
|
}
|
||||||
_userSVipLevel = userSVipLevel;
|
|
||||||
final normalizedVipLevel = userSVipLevel?.trim();
|
RoomProfile.fromJson(dynamic json) {
|
||||||
_userProfile =
|
_regionCode = json['regionCode'];
|
||||||
normalizedVipLevel == null || normalizedVipLevel.isEmpty
|
|
||||||
? userProfile
|
|
||||||
: userProfile?.copyWith(vipLevel: normalizedVipLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
RoomProfile.fromJson(dynamic json) {
|
|
||||||
_regionCode = json['regionCode'];
|
|
||||||
_roomCounter =
|
_roomCounter =
|
||||||
json['roomCounter'] != null
|
json['roomCounter'] != null
|
||||||
? RoomCounter.fromJson(json['roomCounter'])
|
? RoomCounter.fromJson(json['roomCounter'])
|
||||||
@ -244,52 +213,31 @@ class RoomProfile {
|
|||||||
json['roomSetting'] != null
|
json['roomSetting'] != null
|
||||||
? RoomSetting.fromJson(json['roomSetting'])
|
? RoomSetting.fromJson(json['roomSetting'])
|
||||||
: null;
|
: null;
|
||||||
_userProfile =
|
_userProfile =
|
||||||
json['userProfile'] != null
|
json['userProfile'] != null
|
||||||
? SocialChatUserProfile.fromJson(json['userProfile'])
|
? SocialChatUserProfile.fromJson(json['userProfile'])
|
||||||
: null;
|
: null;
|
||||||
_userSVipLevel = _firstNonBlankRoomVipLevel([
|
}
|
||||||
json['userSVipLevel'],
|
|
||||||
json['supperVip'],
|
String? _regionCode;
|
||||||
json['superVip'],
|
RoomCounter? _roomCounter;
|
||||||
json['vipLevel'],
|
RoomProfile2? _roomProfile;
|
||||||
json['svipLevel'],
|
RoomSetting? _roomSetting;
|
||||||
json['sVipLevel'],
|
SocialChatUserProfile? _userProfile;
|
||||||
json['nobleVipLevel'],
|
|
||||||
json['vipLevelCode'],
|
RoomProfile copyWith({
|
||||||
json['levelCode'],
|
String? regionCode,
|
||||||
json['roomProfile'] is Map ? json['roomProfile']['userSVipLevel'] : null,
|
RoomCounter? roomCounter,
|
||||||
json['roomProfile'] is Map ? json['roomProfile']['supperVip'] : null,
|
RoomProfile2? roomProfile,
|
||||||
json['roomProfile'] is Map ? json['roomProfile']['vipLevel'] : null,
|
RoomSetting? roomSetting,
|
||||||
]);
|
SocialChatUserProfile? userProfile,
|
||||||
final normalizedVipLevel = _userSVipLevel?.trim();
|
}) => RoomProfile(
|
||||||
if (normalizedVipLevel != null && normalizedVipLevel.isNotEmpty) {
|
regionCode: regionCode ?? _regionCode,
|
||||||
_userProfile = _userProfile?.copyWith(vipLevel: normalizedVipLevel);
|
roomCounter: roomCounter ?? _roomCounter,
|
||||||
}
|
roomProfile: roomProfile ?? _roomProfile,
|
||||||
}
|
roomSetting: roomSetting ?? _roomSetting,
|
||||||
|
userProfile: userProfile ?? _userProfile,
|
||||||
String? _regionCode;
|
);
|
||||||
RoomCounter? _roomCounter;
|
|
||||||
RoomProfile2? _roomProfile;
|
|
||||||
RoomSetting? _roomSetting;
|
|
||||||
SocialChatUserProfile? _userProfile;
|
|
||||||
String? _userSVipLevel;
|
|
||||||
|
|
||||||
RoomProfile copyWith({
|
|
||||||
String? regionCode,
|
|
||||||
RoomCounter? roomCounter,
|
|
||||||
RoomProfile2? roomProfile,
|
|
||||||
RoomSetting? roomSetting,
|
|
||||||
SocialChatUserProfile? userProfile,
|
|
||||||
String? userSVipLevel,
|
|
||||||
}) => RoomProfile(
|
|
||||||
regionCode: regionCode ?? _regionCode,
|
|
||||||
roomCounter: roomCounter ?? _roomCounter,
|
|
||||||
roomProfile: roomProfile ?? _roomProfile,
|
|
||||||
roomSetting: roomSetting ?? _roomSetting,
|
|
||||||
userProfile: userProfile ?? _userProfile,
|
|
||||||
userSVipLevel: userSVipLevel ?? _userSVipLevel,
|
|
||||||
);
|
|
||||||
|
|
||||||
String? get regionCode => _regionCode;
|
String? get regionCode => _regionCode;
|
||||||
|
|
||||||
@ -299,13 +247,11 @@ class RoomProfile {
|
|||||||
|
|
||||||
RoomSetting? get roomSetting => _roomSetting;
|
RoomSetting? get roomSetting => _roomSetting;
|
||||||
|
|
||||||
SocialChatUserProfile? get userProfile => _userProfile;
|
SocialChatUserProfile? get userProfile => _userProfile;
|
||||||
|
|
||||||
String? get userSVipLevel => _userSVipLevel;
|
Map<String?, dynamic> toJson() {
|
||||||
|
final map = <String?, dynamic>{};
|
||||||
Map<String?, dynamic> toJson() {
|
map['regionCode'] = _regionCode;
|
||||||
final map = <String?, dynamic>{};
|
|
||||||
map['regionCode'] = _regionCode;
|
|
||||||
if (_roomCounter != null) {
|
if (_roomCounter != null) {
|
||||||
map['roomCounter'] = _roomCounter?.toJson();
|
map['roomCounter'] = _roomCounter?.toJson();
|
||||||
}
|
}
|
||||||
@ -315,12 +261,11 @@ class RoomProfile {
|
|||||||
if (_roomSetting != null) {
|
if (_roomSetting != null) {
|
||||||
map['roomSetting'] = _roomSetting?.toJson();
|
map['roomSetting'] = _roomSetting?.toJson();
|
||||||
}
|
}
|
||||||
if (_userProfile != null) {
|
if (_userProfile != null) {
|
||||||
map['userProfile'] = _userProfile?.toJson();
|
map['userProfile'] = _userProfile?.toJson();
|
||||||
}
|
}
|
||||||
map['userSVipLevel'] = _userSVipLevel;
|
return map;
|
||||||
return map;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void setRoomSetting(RoomSetting roomSetting) {
|
void setRoomSetting(RoomSetting roomSetting) {
|
||||||
_roomSetting = roomSetting;
|
_roomSetting = roomSetting;
|
||||||
|
|||||||
@ -121,7 +121,6 @@ class SocialChatUserProfile {
|
|||||||
num? heartbeatVal,
|
num? heartbeatVal,
|
||||||
String? originSys,
|
String? originSys,
|
||||||
OwnSpecialId? ownSpecialId,
|
OwnSpecialId? ownSpecialId,
|
||||||
String? vipLevel,
|
|
||||||
bool? sameRegion,
|
bool? sameRegion,
|
||||||
int? firstRechargeEndTime,
|
int? firstRechargeEndTime,
|
||||||
String? sysOriginChild,
|
String? sysOriginChild,
|
||||||
@ -161,7 +160,6 @@ class SocialChatUserProfile {
|
|||||||
_wealthLevel = wealthLevel;
|
_wealthLevel = wealthLevel;
|
||||||
_heartbeatVal = heartbeatVal;
|
_heartbeatVal = heartbeatVal;
|
||||||
_ownSpecialId = ownSpecialId;
|
_ownSpecialId = ownSpecialId;
|
||||||
_vipLevel = vipLevel;
|
|
||||||
_sameRegion = sameRegion;
|
_sameRegion = sameRegion;
|
||||||
_isCpRelation = isCpRelation;
|
_isCpRelation = isCpRelation;
|
||||||
_firstRechargeEndTime = firstRechargeEndTime;
|
_firstRechargeEndTime = firstRechargeEndTime;
|
||||||
@ -180,14 +178,10 @@ class SocialChatUserProfile {
|
|||||||
_backgroundPhotos = backgroundPhotos;
|
_backgroundPhotos = backgroundPhotos;
|
||||||
_personalPhotos = personalPhotos;
|
_personalPhotos = personalPhotos;
|
||||||
_cpList = cpList;
|
_cpList = cpList;
|
||||||
}
|
}
|
||||||
|
|
||||||
SocialChatUserProfile.fromJson(dynamic json) {
|
SocialChatUserProfile.fromJson(dynamic json) {
|
||||||
final sourceJson = json;
|
_account = json['account'];
|
||||||
if (sourceJson is Map && sourceJson['userProfile'] is Map) {
|
|
||||||
json = sourceJson['userProfile'];
|
|
||||||
}
|
|
||||||
_account = json['account'];
|
|
||||||
_accountStatus = json['accountStatus'];
|
_accountStatus = json['accountStatus'];
|
||||||
_age = json['age'];
|
_age = json['age'];
|
||||||
_bornDay = json['bornDay'];
|
_bornDay = json['bornDay'];
|
||||||
@ -210,10 +204,6 @@ class SocialChatUserProfile {
|
|||||||
json['ownSpecialId'] != null
|
json['ownSpecialId'] != null
|
||||||
? OwnSpecialId.fromJson(json['ownSpecialId'])
|
? OwnSpecialId.fromJson(json['ownSpecialId'])
|
||||||
: null;
|
: null;
|
||||||
_vipLevel = _firstNonBlankString([
|
|
||||||
..._vipLevelHintValues(json),
|
|
||||||
..._vipLevelHintValues(sourceJson),
|
|
||||||
]);
|
|
||||||
_sameRegion = json['sameRegion'];
|
_sameRegion = json['sameRegion'];
|
||||||
_isCpRelation = json['isCpRelation'];
|
_isCpRelation = json['isCpRelation'];
|
||||||
_firstRechargeEndTime = json['firstRechargeEndTime'];
|
_firstRechargeEndTime = json['firstRechargeEndTime'];
|
||||||
@ -284,7 +274,6 @@ class SocialChatUserProfile {
|
|||||||
num? _wealthLevel;
|
num? _wealthLevel;
|
||||||
num? _heartbeatVal;
|
num? _heartbeatVal;
|
||||||
OwnSpecialId? _ownSpecialId;
|
OwnSpecialId? _ownSpecialId;
|
||||||
String? _vipLevel;
|
|
||||||
bool? _sameRegion;
|
bool? _sameRegion;
|
||||||
bool? _isCpRelation;
|
bool? _isCpRelation;
|
||||||
int? _firstRechargeEndTime;
|
int? _firstRechargeEndTime;
|
||||||
@ -325,7 +314,6 @@ class SocialChatUserProfile {
|
|||||||
num? wealthLevel,
|
num? wealthLevel,
|
||||||
num? heartbeatVal,
|
num? heartbeatVal,
|
||||||
OwnSpecialId? ownSpecialId,
|
OwnSpecialId? ownSpecialId,
|
||||||
String? vipLevel,
|
|
||||||
bool? sameRegion,
|
bool? sameRegion,
|
||||||
bool? isCpRelation,
|
bool? isCpRelation,
|
||||||
int? firstRechargeEndTime,
|
int? firstRechargeEndTime,
|
||||||
@ -365,7 +353,6 @@ class SocialChatUserProfile {
|
|||||||
wealthLevel: wealthLevel ?? _wealthLevel,
|
wealthLevel: wealthLevel ?? _wealthLevel,
|
||||||
heartbeatVal: heartbeatVal ?? _heartbeatVal,
|
heartbeatVal: heartbeatVal ?? _heartbeatVal,
|
||||||
ownSpecialId: ownSpecialId ?? _ownSpecialId,
|
ownSpecialId: ownSpecialId ?? _ownSpecialId,
|
||||||
vipLevel: vipLevel ?? _vipLevel,
|
|
||||||
sameRegion: sameRegion ?? _sameRegion,
|
sameRegion: sameRegion ?? _sameRegion,
|
||||||
isCpRelation: isCpRelation ?? _isCpRelation,
|
isCpRelation: isCpRelation ?? _isCpRelation,
|
||||||
firstRechargeEndTime: firstRechargeEndTime ?? _firstRechargeEndTime,
|
firstRechargeEndTime: firstRechargeEndTime ?? _firstRechargeEndTime,
|
||||||
@ -426,8 +413,6 @@ class SocialChatUserProfile {
|
|||||||
|
|
||||||
OwnSpecialId? get ownSpecialId => _ownSpecialId;
|
OwnSpecialId? get ownSpecialId => _ownSpecialId;
|
||||||
|
|
||||||
String? get vipLevel => _vipLevel;
|
|
||||||
|
|
||||||
bool? get sameRegion => _sameRegion;
|
bool? get sameRegion => _sameRegion;
|
||||||
|
|
||||||
bool? get isCpRelation => _isCpRelation;
|
bool? get isCpRelation => _isCpRelation;
|
||||||
@ -509,26 +494,6 @@ class SocialChatUserProfile {
|
|||||||
return pr;
|
return pr;
|
||||||
}
|
}
|
||||||
|
|
||||||
PropsResources? getFloatPicture() {
|
|
||||||
PropsResources? pr;
|
|
||||||
_useProps?.forEach((value) {
|
|
||||||
if (value.propsResources?.type == SCPropsType.FLOAT_PICTURE.name) {
|
|
||||||
pr = value.propsResources;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return pr;
|
|
||||||
}
|
|
||||||
|
|
||||||
PropsResources? getVipBadge() {
|
|
||||||
PropsResources? pr;
|
|
||||||
_useProps?.forEach((value) {
|
|
||||||
if (value.propsResources?.type == SCPropsType.BADGE.name) {
|
|
||||||
pr = value.propsResources;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return pr;
|
|
||||||
}
|
|
||||||
|
|
||||||
PropsResources? getMountains() {
|
PropsResources? getMountains() {
|
||||||
PropsResources? pr;
|
PropsResources? pr;
|
||||||
_useProps?.forEach((value) {
|
_useProps?.forEach((value) {
|
||||||
@ -582,9 +547,6 @@ class SocialChatUserProfile {
|
|||||||
if (_ownSpecialId != null) {
|
if (_ownSpecialId != null) {
|
||||||
map['ownSpecialId'] = _ownSpecialId?.toJson();
|
map['ownSpecialId'] = _ownSpecialId?.toJson();
|
||||||
}
|
}
|
||||||
if (_vipLevel != null) {
|
|
||||||
map['vipLevel'] = _vipLevel;
|
|
||||||
}
|
|
||||||
map['sameRegion'] = _sameRegion;
|
map['sameRegion'] = _sameRegion;
|
||||||
map['isCpRelation'] = _isCpRelation;
|
map['isCpRelation'] = _isCpRelation;
|
||||||
map['firstRecharge'] = _firstRecharge;
|
map['firstRecharge'] = _firstRecharge;
|
||||||
@ -629,212 +591,22 @@ class SocialChatUserProfile {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int get vipLevelForColoredId {
|
/// ID 数字渐变字的 VIP 条件预留。
|
||||||
final hasExplicitVipLevel = _vipLevel?.trim().isNotEmpty ?? false;
|
/// 当前先复用 useProps 里的 NOBLE_VIP,道具接口稳定后可切到独立字段。
|
||||||
final explicitLevel = _parseVipLevelForColoredId(_vipLevel) ?? 0;
|
bool hasVipPrivilegeForColoredId() {
|
||||||
if (hasExplicitVipLevel && explicitLevel <= 0) {
|
return _useProps?.any(
|
||||||
return 0;
|
(value) => value.propsResources?.type == SCPropsType.NOBLE_VIP.name,
|
||||||
}
|
|
||||||
if (explicitLevel > 0) {
|
|
||||||
return explicitLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
final vipResource = getVIP();
|
|
||||||
final resourceLevel =
|
|
||||||
_parseVipLevelForColoredId(
|
|
||||||
vipResource?.name,
|
|
||||||
allowPlainNumber: false,
|
|
||||||
) ??
|
) ??
|
||||||
_parseVipLevelForColoredId(
|
false;
|
||||||
vipResource?.code,
|
}
|
||||||
allowPlainNumber: false,
|
|
||||||
allowTrailingNumber: false,
|
|
||||||
);
|
|
||||||
if ((resourceLevel ?? 0) > 0) {
|
|
||||||
return resourceLevel!;
|
|
||||||
}
|
|
||||||
|
|
||||||
return vipResource == null ? 0 : 1;
|
bool shouldShowColoredSpecialIdText() {
|
||||||
}
|
return hasVipPrivilegeForColoredId();
|
||||||
|
}
|
||||||
int get vipVisualResourceLevelForColoredId {
|
|
||||||
for (final resource in [
|
|
||||||
getVipBadge(),
|
|
||||||
getHeaddress(),
|
|
||||||
getChatBox(),
|
|
||||||
getFloatPicture(),
|
|
||||||
getDataCard(),
|
|
||||||
getMountains(),
|
|
||||||
]) {
|
|
||||||
final level = _vipLevelFromVisualResource(resource);
|
|
||||||
if ((level ?? 0) > 0) {
|
|
||||||
return level!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ID 数字渐变字的 VIP 条件。
|
|
||||||
/// 新 VIP 接口优先读等级字段;旧数据继续兼容 useProps 里的 NOBLE_VIP。
|
|
||||||
bool hasVipPrivilegeForColoredId() {
|
|
||||||
return vipLevelForColoredId > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasVipVisualResourceForColoredId() {
|
|
||||||
return vipVisualResourceLevelForColoredId > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool shouldShowColoredSpecialIdText() {
|
|
||||||
return hasVipPrivilegeForColoredId() || hasVipVisualResourceForColoredId();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setHeartbeatVal(num? heartbeatVal) {
|
void setHeartbeatVal(num? heartbeatVal) {
|
||||||
_heartbeatVal = (_heartbeatVal ?? 0) + (heartbeatVal ?? 0);
|
_heartbeatVal = (_heartbeatVal ?? 0) + (heartbeatVal ?? 0);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
String? socialChatVipLevelHintFromJson(dynamic json) {
|
|
||||||
return _firstNonBlankString(_vipLevelHintValues(json));
|
|
||||||
}
|
|
||||||
|
|
||||||
SocialChatUserProfile? socialChatProfileWithVipLevelHint(
|
|
||||||
SocialChatUserProfile? profile,
|
|
||||||
dynamic json,
|
|
||||||
) {
|
|
||||||
final vipLevel = socialChatVipLevelHintFromJson(json);
|
|
||||||
if (profile == null || vipLevel == null) {
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
return profile.copyWith(vipLevel: vipLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
SocialChatUserProfile? socialChatUserProfileFromJsonWithVipHint(
|
|
||||||
dynamic json, {
|
|
||||||
String userProfileKey = 'userProfile',
|
|
||||||
}) {
|
|
||||||
if (json == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (json is Map && json[userProfileKey] != null) {
|
|
||||||
return socialChatProfileWithVipLevelHint(
|
|
||||||
SocialChatUserProfile.fromJson(json[userProfileKey]),
|
|
||||||
json,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return SocialChatUserProfile.fromJson(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterable<dynamic> _vipLevelHintValues(dynamic json) sync* {
|
|
||||||
if (json is! Map) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
yield json['vipLevel'];
|
|
||||||
yield json['svipLevel'];
|
|
||||||
yield json['sVipLevel'];
|
|
||||||
yield json['userSVipLevel'];
|
|
||||||
yield json['supperVip'];
|
|
||||||
yield json['superVip'];
|
|
||||||
yield json['nobleVipLevel'];
|
|
||||||
yield json['vipLevelCode'];
|
|
||||||
yield json['levelCode'];
|
|
||||||
yield _nestedVipLevel(json['vipStatus']);
|
|
||||||
yield _nestedVipLevel(json['vipState']);
|
|
||||||
yield _nestedVipLevel(json['vip']);
|
|
||||||
final nestedProfile = json['userProfile'];
|
|
||||||
if (nestedProfile is Map) {
|
|
||||||
yield* _vipLevelHintValues(nestedProfile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String? _firstNonBlankString(Iterable<dynamic> values) {
|
|
||||||
for (final value in values) {
|
|
||||||
final text = value?.toString().trim();
|
|
||||||
if (text != null && text.isNotEmpty) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
dynamic _nestedVipLevel(dynamic value) {
|
|
||||||
if (value is Map) {
|
|
||||||
final active = value['active'];
|
|
||||||
if (active == false ||
|
|
||||||
active?.toString().trim().toLowerCase() == 'false') {
|
|
||||||
return '0';
|
|
||||||
}
|
|
||||||
return value['level'] ??
|
|
||||||
value['vipLevel'] ??
|
|
||||||
value['userSVipLevel'] ??
|
|
||||||
value['supperVip'] ??
|
|
||||||
value['levelCode'];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
int? _vipLevelFromVisualResource(PropsResources? resource) {
|
|
||||||
if (resource == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return _parseVipLevelForColoredId(
|
|
||||||
resource.name,
|
|
||||||
allowPlainNumber: false,
|
|
||||||
) ??
|
|
||||||
_parseVipLevelForColoredId(
|
|
||||||
resource.code,
|
|
||||||
allowPlainNumber: false,
|
|
||||||
) ??
|
|
||||||
_parseVipLevelForColoredId(
|
|
||||||
resource.cover,
|
|
||||||
allowPlainNumber: false,
|
|
||||||
) ??
|
|
||||||
_parseVipLevelForColoredId(
|
|
||||||
resource.sourceUrl,
|
|
||||||
allowPlainNumber: false,
|
|
||||||
) ??
|
|
||||||
_parseVipLevelForColoredId(
|
|
||||||
resource.roomOpenedUrl,
|
|
||||||
allowPlainNumber: false,
|
|
||||||
) ??
|
|
||||||
_parseVipLevelForColoredId(
|
|
||||||
resource.roomSendCoverUrl,
|
|
||||||
allowPlainNumber: false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
int? _parseVipLevelForColoredId(
|
|
||||||
dynamic value, {
|
|
||||||
bool allowPlainNumber = true,
|
|
||||||
bool allowTrailingNumber = true,
|
|
||||||
}) {
|
|
||||||
final text = value?.toString().trim();
|
|
||||||
if (text == null || text.isEmpty) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allowPlainNumber) {
|
|
||||||
final plainLevel = int.tryParse(text);
|
|
||||||
if ((plainLevel ?? 0) > 0) {
|
|
||||||
return plainLevel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final vipLevelMatch = RegExp(
|
|
||||||
r'(?:S?VIP|NOBLE[_\s-]*VIP)[^\d]*([1-9]\d*)',
|
|
||||||
caseSensitive: false,
|
|
||||||
).firstMatch(text);
|
|
||||||
if (vipLevelMatch != null) {
|
|
||||||
return int.tryParse(vipLevelMatch.group(1) ?? '');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allowTrailingNumber) {
|
|
||||||
final trailingLevelMatch = RegExp(r'([1-9]\d*)$').firstMatch(text);
|
|
||||||
if (trailingLevelMatch != null) {
|
|
||||||
return int.tryParse(trailingLevelMatch.group(1) ?? '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// animationUrl : ""
|
/// animationUrl : ""
|
||||||
@ -1220,11 +992,11 @@ class PropsResources {
|
|||||||
PropsResources.fromJson(dynamic json) {
|
PropsResources.fromJson(dynamic json) {
|
||||||
_amount = json['amount'];
|
_amount = json['amount'];
|
||||||
_code = json['code'];
|
_code = json['code'];
|
||||||
_cover = json['cover'] ?? json['coverUrl'];
|
_cover = json['cover'];
|
||||||
_createTime = json['createTime'];
|
_createTime = json['createTime'];
|
||||||
_del = json['del'];
|
_del = json['del'];
|
||||||
_expand = json['expand'];
|
_expand = json['expand'];
|
||||||
_id = json['id'] ?? json['resourceId'];
|
_id = json['id'];
|
||||||
_imNotOpenedUrl = json['imNotOpenedUrl'];
|
_imNotOpenedUrl = json['imNotOpenedUrl'];
|
||||||
_imOpenedUrl = json['imOpenedUrl'];
|
_imOpenedUrl = json['imOpenedUrl'];
|
||||||
_imOpenedUrlTwo = json['imOpenedUrlTwo'];
|
_imOpenedUrlTwo = json['imOpenedUrlTwo'];
|
||||||
@ -1233,7 +1005,7 @@ class PropsResources {
|
|||||||
_roomNotOpenedUrl = json['roomNotOpenedUrl'];
|
_roomNotOpenedUrl = json['roomNotOpenedUrl'];
|
||||||
_roomOpenedUrl = json['roomOpenedUrl'];
|
_roomOpenedUrl = json['roomOpenedUrl'];
|
||||||
_roomSendCoverUrl = json['roomSendCoverUrl'];
|
_roomSendCoverUrl = json['roomSendCoverUrl'];
|
||||||
_sourceUrl = json['sourceUrl'] ?? json['resourceUrl'] ?? json['url'];
|
_sourceUrl = json['sourceUrl'];
|
||||||
_sysOrigin = json['sysOrigin'];
|
_sysOrigin = json['sysOrigin'];
|
||||||
_type = json['type'];
|
_type = json['type'];
|
||||||
_updateTime = json['updateTime'];
|
_updateTime = json['updateTime'];
|
||||||
|
|||||||
@ -34,14 +34,11 @@ class MicRes {
|
|||||||
_roomId = json['roomId'];
|
_roomId = json['roomId'];
|
||||||
_micIndex = json['micIndex'];
|
_micIndex = json['micIndex'];
|
||||||
_micLock = json['micLock'];
|
_micLock = json['micLock'];
|
||||||
_micMute = json['micMute'];
|
_micMute = json['micMute'];
|
||||||
_user =
|
_user =
|
||||||
json['user'] != null
|
json['user'] != null
|
||||||
? socialChatProfileWithVipLevelHint(
|
? SocialChatUserProfile.fromJson(json['user'])
|
||||||
SocialChatUserProfile.fromJson(json['user']),
|
: null;
|
||||||
json,
|
|
||||||
)
|
|
||||||
: null;
|
|
||||||
_roomToken = json['roomToken'];
|
_roomToken = json['roomToken'];
|
||||||
_type = json['type'];
|
_type = json['type'];
|
||||||
_number = json['number'];
|
_number = json['number'];
|
||||||
@ -87,15 +84,13 @@ class MicRes {
|
|||||||
|
|
||||||
bool? get micLock => _micLock;
|
bool? get micLock => _micLock;
|
||||||
|
|
||||||
bool? get micMute => _micMute;
|
bool? get micMute => _micMute;
|
||||||
|
|
||||||
SocialChatUserProfile? get user => _user;
|
SocialChatUserProfile? get user => _user;
|
||||||
|
|
||||||
String? get roomToken => _roomToken;
|
String? get roomToken => _roomToken;
|
||||||
|
|
||||||
int? get volume => _volume;
|
String? get type => _type;
|
||||||
|
|
||||||
String? get type => _type;
|
|
||||||
|
|
||||||
String? get emojiPath => _emojiPath;
|
String? get emojiPath => _emojiPath;
|
||||||
|
|
||||||
|
|||||||
@ -4,24 +4,21 @@ import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
|||||||
/// totalFormat : ""
|
/// totalFormat : ""
|
||||||
/// total : 0
|
/// total : 0
|
||||||
|
|
||||||
class RoomGiftRankRes {
|
class RoomGiftRankRes {
|
||||||
RoomGiftRankRes({
|
RoomGiftRankRes({
|
||||||
SocialChatUserProfile? userProfile,
|
SocialChatUserProfile? userProfile,
|
||||||
String? totalFormat,
|
String? totalFormat,
|
||||||
String? total,}){
|
String? total,}){
|
||||||
_userProfile = userProfile;
|
_userProfile = userProfile;
|
||||||
_totalFormat = totalFormat;
|
_totalFormat = totalFormat;
|
||||||
_total = total;
|
_total = total;
|
||||||
}
|
}
|
||||||
|
|
||||||
RoomGiftRankRes.fromJson(dynamic json) {
|
RoomGiftRankRes.fromJson(dynamic json) {
|
||||||
_userProfile =
|
_userProfile = json['userProfile'] != null ? SocialChatUserProfile.fromJson(json['userProfile']) : null;
|
||||||
json['userProfile'] != null
|
_totalFormat = json['totalFormat'];
|
||||||
? socialChatUserProfileFromJsonWithVipHint(json)
|
_total = json['total'];
|
||||||
: null;
|
}
|
||||||
_totalFormat = json['totalFormat'];
|
|
||||||
_total = json['total'];
|
|
||||||
}
|
|
||||||
SocialChatUserProfile? _userProfile;
|
SocialChatUserProfile? _userProfile;
|
||||||
String? _totalFormat;
|
String? _totalFormat;
|
||||||
String? _total;
|
String? _total;
|
||||||
|
|||||||
@ -11,14 +11,14 @@ class SocialChatRoomMemberRes {
|
|||||||
_roles = roles;
|
_roles = roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
SocialChatRoomMemberRes.fromJson(dynamic json) {
|
SocialChatRoomMemberRes.fromJson(dynamic json) {
|
||||||
_id = json['id'];
|
_id = json['id'];
|
||||||
_userProfile =
|
_userProfile =
|
||||||
json['userProfile'] != null
|
json['userProfile'] != null
|
||||||
? socialChatUserProfileFromJsonWithVipHint(json)
|
? SocialChatUserProfile.fromJson(json['userProfile'])
|
||||||
: null;
|
: null;
|
||||||
_roles = json['roles'];
|
_roles = json['roles'];
|
||||||
}
|
}
|
||||||
|
|
||||||
String? _id;
|
String? _id;
|
||||||
SocialChatUserProfile? _userProfile;
|
SocialChatUserProfile? _userProfile;
|
||||||
|
|||||||
@ -18,8 +18,8 @@ import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
|||||||
/// userProfile : {"account":"","accountStatus":"","age":0,"bornDay":0,"bornMonth":0,"bornYear":0,"countryCode":"","countryId":0,"countryName":"","createTime":0,"del":false,"freezingTime":0,"id":0,"originSys":"","ownSpecialId":{"account":"","expiredTime":0},"sameRegion":false,"sysOriginChild":"","useProps":[{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}],"userAvatar":"","userNickname":"","userSex":0,"wearBadge":[{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}]}
|
/// userProfile : {"account":"","accountStatus":"","age":0,"bornDay":0,"bornMonth":0,"bornYear":0,"countryCode":"","countryId":0,"countryName":"","createTime":0,"del":false,"freezingTime":0,"id":0,"originSys":"","ownSpecialId":{"account":"","expiredTime":0},"sameRegion":false,"sysOriginChild":"","useProps":[{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}],"userAvatar":"","userNickname":"","userSex":0,"wearBadge":[{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}]}
|
||||||
/// userSVipLevel : ""
|
/// userSVipLevel : ""
|
||||||
|
|
||||||
class SocialChatRoomRes {
|
class SocialChatRoomRes {
|
||||||
SocialChatRoomRes({
|
SocialChatRoomRes({
|
||||||
String? countryCode,
|
String? countryCode,
|
||||||
String? countryName,
|
String? countryName,
|
||||||
String? event,
|
String? event,
|
||||||
@ -34,12 +34,12 @@ class SocialChatRoomRes {
|
|||||||
String? roomGameIcon,
|
String? roomGameIcon,
|
||||||
String? roomName,
|
String? roomName,
|
||||||
String? sysOrigin,
|
String? sysOrigin,
|
||||||
String? userId,
|
String? userId,
|
||||||
SocialChatUserProfile? userProfile,
|
SocialChatUserProfile? userProfile,
|
||||||
String? userSVipLevel,
|
String? userSVipLevel,
|
||||||
RoomMemberCounter? roomCounter,
|
RoomMemberCounter? roomCounter,
|
||||||
ExtValues? extValues,
|
ExtValues? extValues,
|
||||||
}) {
|
}) {
|
||||||
_countryCode = countryCode;
|
_countryCode = countryCode;
|
||||||
_countryName = countryName;
|
_countryName = countryName;
|
||||||
_event = event;
|
_event = event;
|
||||||
@ -54,16 +54,12 @@ class SocialChatRoomRes {
|
|||||||
_roomGameIcon = roomGameIcon;
|
_roomGameIcon = roomGameIcon;
|
||||||
_roomName = roomName;
|
_roomName = roomName;
|
||||||
_sysOrigin = sysOrigin;
|
_sysOrigin = sysOrigin;
|
||||||
_userId = userId;
|
_userId = userId;
|
||||||
_userSVipLevel = userSVipLevel;
|
_userProfile = userProfile;
|
||||||
final normalizedVipLevel = userSVipLevel?.trim();
|
_userSVipLevel = userSVipLevel;
|
||||||
_userProfile =
|
_roomCounter = roomCounter;
|
||||||
normalizedVipLevel == null || normalizedVipLevel.isEmpty
|
_extValues = extValues;
|
||||||
? userProfile
|
}
|
||||||
: userProfile?.copyWith(vipLevel: normalizedVipLevel);
|
|
||||||
_roomCounter = roomCounter;
|
|
||||||
_extValues = extValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
SocialChatRoomRes.fromJson(dynamic json) {
|
SocialChatRoomRes.fromJson(dynamic json) {
|
||||||
_countryCode = json['countryCode'];
|
_countryCode = json['countryCode'];
|
||||||
@ -86,22 +82,18 @@ class SocialChatRoomRes {
|
|||||||
_roomName = json['roomName'];
|
_roomName = json['roomName'];
|
||||||
_sysOrigin = json['sysOrigin'];
|
_sysOrigin = json['sysOrigin'];
|
||||||
_userId = json['userId'];
|
_userId = json['userId'];
|
||||||
_userProfile =
|
_userProfile =
|
||||||
json['userProfile'] != null
|
json['userProfile'] != null
|
||||||
? SocialChatUserProfile.fromJson(json['userProfile'])
|
? SocialChatUserProfile.fromJson(json['userProfile'])
|
||||||
: null;
|
: null;
|
||||||
_userSVipLevel = socialChatVipLevelHintFromJson(json);
|
_userSVipLevel = json['userSVipLevel'];
|
||||||
final normalizedVipLevel = _userSVipLevel?.trim();
|
final counterJson = json['roomCounter'] ?? json['counter'];
|
||||||
if (normalizedVipLevel != null && normalizedVipLevel.isNotEmpty) {
|
_roomCounter =
|
||||||
_userProfile = _userProfile?.copyWith(vipLevel: normalizedVipLevel);
|
counterJson != null ? RoomMemberCounter.fromJson(counterJson) : null;
|
||||||
}
|
_extValues =
|
||||||
final counterJson = json['roomCounter'] ?? json['counter'];
|
json['extValues'] != null
|
||||||
_roomCounter =
|
? ExtValues.fromJson(json['extValues'])
|
||||||
counterJson != null ? RoomMemberCounter.fromJson(counterJson) : null;
|
: null;
|
||||||
_extValues =
|
|
||||||
json['extValues'] != null
|
|
||||||
? ExtValues.fromJson(json['extValues'])
|
|
||||||
: null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String? _countryCode;
|
String? _countryCode;
|
||||||
@ -118,11 +110,11 @@ class SocialChatRoomRes {
|
|||||||
String? _roomGameIcon;
|
String? _roomGameIcon;
|
||||||
String? _roomName;
|
String? _roomName;
|
||||||
String? _sysOrigin;
|
String? _sysOrigin;
|
||||||
String? _userId;
|
String? _userId;
|
||||||
SocialChatUserProfile? _userProfile;
|
SocialChatUserProfile? _userProfile;
|
||||||
String? _userSVipLevel;
|
String? _userSVipLevel;
|
||||||
RoomMemberCounter? _roomCounter;
|
RoomMemberCounter? _roomCounter;
|
||||||
ExtValues? _extValues;
|
ExtValues? _extValues;
|
||||||
|
|
||||||
SocialChatRoomRes copyWith({
|
SocialChatRoomRes copyWith({
|
||||||
String? countryCode,
|
String? countryCode,
|
||||||
@ -139,12 +131,12 @@ class SocialChatRoomRes {
|
|||||||
String? roomGameIcon,
|
String? roomGameIcon,
|
||||||
String? roomName,
|
String? roomName,
|
||||||
String? sysOrigin,
|
String? sysOrigin,
|
||||||
String? userId,
|
String? userId,
|
||||||
SocialChatUserProfile? userProfile,
|
SocialChatUserProfile? userProfile,
|
||||||
String? userSVipLevel,
|
String? userSVipLevel,
|
||||||
RoomMemberCounter? roomCounter,
|
RoomMemberCounter? roomCounter,
|
||||||
ExtValues? extValues,
|
ExtValues? extValues,
|
||||||
}) => SocialChatRoomRes(
|
}) => SocialChatRoomRes(
|
||||||
countryCode: countryCode ?? _countryCode,
|
countryCode: countryCode ?? _countryCode,
|
||||||
countryName: countryName ?? _countryName,
|
countryName: countryName ?? _countryName,
|
||||||
event: event ?? _event,
|
event: event ?? _event,
|
||||||
@ -159,12 +151,12 @@ class SocialChatRoomRes {
|
|||||||
roomGameIcon: roomGameIcon ?? _roomGameIcon,
|
roomGameIcon: roomGameIcon ?? _roomGameIcon,
|
||||||
roomName: roomName ?? _roomName,
|
roomName: roomName ?? _roomName,
|
||||||
sysOrigin: sysOrigin ?? _sysOrigin,
|
sysOrigin: sysOrigin ?? _sysOrigin,
|
||||||
userId: userId ?? _userId,
|
userId: userId ?? _userId,
|
||||||
userProfile: userProfile ?? _userProfile,
|
userProfile: userProfile ?? _userProfile,
|
||||||
userSVipLevel: userSVipLevel ?? _userSVipLevel,
|
userSVipLevel: userSVipLevel ?? _userSVipLevel,
|
||||||
roomCounter: roomCounter ?? _roomCounter,
|
roomCounter: roomCounter ?? _roomCounter,
|
||||||
extValues: extValues ?? _extValues,
|
extValues: extValues ?? _extValues,
|
||||||
);
|
);
|
||||||
|
|
||||||
String? get countryCode => _countryCode;
|
String? get countryCode => _countryCode;
|
||||||
|
|
||||||
@ -196,25 +188,25 @@ class SocialChatRoomRes {
|
|||||||
|
|
||||||
String? get userId => _userId;
|
String? get userId => _userId;
|
||||||
|
|
||||||
SocialChatUserProfile? get userProfile => _userProfile;
|
SocialChatUserProfile? get userProfile => _userProfile;
|
||||||
|
|
||||||
String? get userSVipLevel => _userSVipLevel;
|
String? get userSVipLevel => _userSVipLevel;
|
||||||
|
|
||||||
RoomMemberCounter? get roomCounter => _roomCounter;
|
RoomMemberCounter? get roomCounter => _roomCounter;
|
||||||
|
|
||||||
ExtValues? get extValues => _extValues;
|
ExtValues? get extValues => _extValues;
|
||||||
|
|
||||||
String get displayMemberCount {
|
String get displayMemberCount {
|
||||||
final memberCount = roomCounter?.memberCount;
|
final memberCount = roomCounter?.memberCount;
|
||||||
if (memberCount != null) {
|
if (memberCount != null) {
|
||||||
return _formatRoomMemberCount(memberCount);
|
return _formatRoomMemberCount(memberCount);
|
||||||
}
|
}
|
||||||
final memberQuantity = extValues?.memberQuantity;
|
final memberQuantity = extValues?.memberQuantity;
|
||||||
if ((memberQuantity ?? "").trim().isNotEmpty) {
|
if ((memberQuantity ?? "").trim().isNotEmpty) {
|
||||||
return memberQuantity!;
|
return memberQuantity!;
|
||||||
}
|
}
|
||||||
return "0";
|
return "0";
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final map = <String, dynamic>{};
|
final map = <String, dynamic>{};
|
||||||
@ -233,58 +225,58 @@ class SocialChatRoomRes {
|
|||||||
map['roomName'] = _roomName;
|
map['roomName'] = _roomName;
|
||||||
map['sysOrigin'] = _sysOrigin;
|
map['sysOrigin'] = _sysOrigin;
|
||||||
map['userId'] = _userId;
|
map['userId'] = _userId;
|
||||||
if (_userProfile != null) {
|
if (_userProfile != null) {
|
||||||
map['userProfile'] = _userProfile?.toJson();
|
map['userProfile'] = _userProfile?.toJson();
|
||||||
}
|
}
|
||||||
map['userSVipLevel'] = _userSVipLevel;
|
map['userSVipLevel'] = _userSVipLevel;
|
||||||
if (_roomCounter != null) {
|
if (_roomCounter != null) {
|
||||||
map['roomCounter'] = _roomCounter?.toJson();
|
map['roomCounter'] = _roomCounter?.toJson();
|
||||||
}
|
}
|
||||||
if (_extValues != null) {
|
if (_extValues != null) {
|
||||||
map['extValues'] = _extValues?.toJson();
|
map['extValues'] = _extValues?.toJson();
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _formatRoomMemberCount(num value) {
|
String _formatRoomMemberCount(num value) {
|
||||||
if (value == value.roundToDouble()) {
|
if (value == value.roundToDouble()) {
|
||||||
return value.toInt().toString();
|
return value.toInt().toString();
|
||||||
}
|
}
|
||||||
return value.toString();
|
return value.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoomMemberCounter {
|
class RoomMemberCounter {
|
||||||
RoomMemberCounter({num? adminCount, num? memberCount}) {
|
RoomMemberCounter({num? adminCount, num? memberCount}) {
|
||||||
_adminCount = adminCount;
|
_adminCount = adminCount;
|
||||||
_memberCount = memberCount;
|
_memberCount = memberCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
RoomMemberCounter.fromJson(dynamic json) {
|
RoomMemberCounter.fromJson(dynamic json) {
|
||||||
_adminCount = json['adminCount'];
|
_adminCount = json['adminCount'];
|
||||||
_memberCount = json['memberCount'];
|
_memberCount = json['memberCount'];
|
||||||
}
|
}
|
||||||
|
|
||||||
num? _adminCount;
|
num? _adminCount;
|
||||||
num? _memberCount;
|
num? _memberCount;
|
||||||
|
|
||||||
RoomMemberCounter copyWith({num? adminCount, num? memberCount}) =>
|
RoomMemberCounter copyWith({num? adminCount, num? memberCount}) =>
|
||||||
RoomMemberCounter(
|
RoomMemberCounter(
|
||||||
adminCount: adminCount ?? _adminCount,
|
adminCount: adminCount ?? _adminCount,
|
||||||
memberCount: memberCount ?? _memberCount,
|
memberCount: memberCount ?? _memberCount,
|
||||||
);
|
);
|
||||||
|
|
||||||
num? get adminCount => _adminCount;
|
num? get adminCount => _adminCount;
|
||||||
|
|
||||||
num? get memberCount => _memberCount;
|
num? get memberCount => _memberCount;
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final map = <String, dynamic>{};
|
final map = <String, dynamic>{};
|
||||||
map['adminCount'] = _adminCount;
|
map['adminCount'] = _adminCount;
|
||||||
map['memberCount'] = _memberCount;
|
map['memberCount'] = _memberCount;
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// account : ""
|
/// account : ""
|
||||||
/// accountStatus : ""
|
/// accountStatus : ""
|
||||||
|
|||||||
@ -12,19 +12,19 @@ import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
|||||||
/// userLevel : {"charmLevel":0,"extValues":{"":{}},"wealthLevel":0}
|
/// userLevel : {"charmLevel":0,"extValues":{"":{}},"wealthLevel":0}
|
||||||
/// userProfile : {"account":"","accountStatus":"","age":0,"bornDay":0,"bornMonth":0,"bornYear":0,"countryCode":"","countryId":0,"countryName":"","createTime":0,"del":false,"freezingTime":0,"id":"0","originSys":"","ownSpecialId":{"account":"","expiredTime":0},"sameRegion":false,"sysOriginChild":"","useProps":[{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}],"userAvatar":"","userNickname":"","userSex":0,"wearBadge":[{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}]}
|
/// userProfile : {"account":"","accountStatus":"","age":0,"bornDay":0,"bornMonth":0,"bornYear":0,"countryCode":"","countryId":0,"countryName":"","createTime":0,"del":false,"freezingTime":0,"id":"0","originSys":"","ownSpecialId":{"account":"","expiredTime":0},"sameRegion":false,"sysOriginChild":"","useProps":[{"expireTime":0,"propsResources":{"amount":0.0,"code":"","cover":"","expand":"","id":0,"name":"","sourceUrl":"","type":""},"userId":0}],"userAvatar":"","userNickname":"","userSex":0,"wearBadge":[{"animationUrl":"","badgeKey":"","badgeLevel":0,"badgeName":"","expireTime":0,"id":0,"milestone":0,"notSelectUrl":"","selectUrl":"","type":"","userId":0}]}
|
||||||
|
|
||||||
class RoomUserCardRes {
|
class RoomUserCardRes {
|
||||||
RoomUserCardRes({
|
RoomUserCardRes({
|
||||||
bool? agent,
|
bool? agent,
|
||||||
bool? anchor,
|
bool? anchor,
|
||||||
bool? follow,
|
bool? follow,
|
||||||
bool? friend,
|
bool? friend,
|
||||||
String? ktvIntegral,
|
String? ktvIntegral,
|
||||||
String? roomRole,
|
String? roomRole,
|
||||||
String? supperVip,
|
String? supperVip,
|
||||||
List<UseBadge>? useBadge,
|
List<UseBadge>? useBadge,
|
||||||
List<UseProps>? useProps,
|
List<UseProps>? useProps,
|
||||||
UserLevel? userLevel,
|
UserLevel? userLevel,
|
||||||
SocialChatUserProfile? userProfile,}){
|
SocialChatUserProfile? userProfile,}){
|
||||||
_agent = agent;
|
_agent = agent;
|
||||||
_anchor = anchor;
|
_anchor = anchor;
|
||||||
_follow = follow;
|
_follow = follow;
|
||||||
@ -35,12 +35,8 @@ class RoomUserCardRes {
|
|||||||
_useBadge = useBadge;
|
_useBadge = useBadge;
|
||||||
_useProps = useProps;
|
_useProps = useProps;
|
||||||
_userLevel = userLevel;
|
_userLevel = userLevel;
|
||||||
final normalizedVipLevel = supperVip?.trim();
|
_userProfile = userProfile;
|
||||||
_userProfile =
|
}
|
||||||
normalizedVipLevel == null || normalizedVipLevel.isEmpty
|
|
||||||
? userProfile
|
|
||||||
: userProfile?.copyWith(vipLevel: normalizedVipLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
RoomUserCardRes.fromJson(dynamic json) {
|
RoomUserCardRes.fromJson(dynamic json) {
|
||||||
_agent = json['agent'];
|
_agent = json['agent'];
|
||||||
@ -49,7 +45,7 @@ class RoomUserCardRes {
|
|||||||
_friend = json['friend'];
|
_friend = json['friend'];
|
||||||
_ktvIntegral = json['ktvIntegral'];
|
_ktvIntegral = json['ktvIntegral'];
|
||||||
_roomRole = json['roomRole'];
|
_roomRole = json['roomRole'];
|
||||||
_supperVip = socialChatVipLevelHintFromJson(json);
|
_supperVip = json['supperVip'];
|
||||||
if (json['useBadge'] != null) {
|
if (json['useBadge'] != null) {
|
||||||
_useBadge = [];
|
_useBadge = [];
|
||||||
json['useBadge'].forEach((v) {
|
json['useBadge'].forEach((v) {
|
||||||
@ -62,15 +58,8 @@ class RoomUserCardRes {
|
|||||||
_useProps?.add(UseProps.fromJson(v));
|
_useProps?.add(UseProps.fromJson(v));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
_userLevel = json['userLevel'] != null ? UserLevel.fromJson(json['userLevel']) : null;
|
_userLevel = json['userLevel'] != null ? UserLevel.fromJson(json['userLevel']) : null;
|
||||||
_userProfile =
|
_userProfile = json['userProfile'] != null ? SocialChatUserProfile.fromJson(json['userProfile']) : null;
|
||||||
json['userProfile'] != null
|
|
||||||
? socialChatUserProfileFromJsonWithVipHint(json)
|
|
||||||
: null;
|
|
||||||
final normalizedVipLevel = _supperVip?.trim();
|
|
||||||
if (normalizedVipLevel != null && normalizedVipLevel.isNotEmpty) {
|
|
||||||
_userProfile = _userProfile?.copyWith(vipLevel: normalizedVipLevel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
bool? _agent;
|
bool? _agent;
|
||||||
bool? _anchor;
|
bool? _anchor;
|
||||||
@ -83,29 +72,29 @@ class RoomUserCardRes {
|
|||||||
List<UseProps>? _useProps;
|
List<UseProps>? _useProps;
|
||||||
UserLevel? _userLevel;
|
UserLevel? _userLevel;
|
||||||
SocialChatUserProfile? _userProfile;
|
SocialChatUserProfile? _userProfile;
|
||||||
RoomUserCardRes copyWith({ bool? agent,
|
RoomUserCardRes copyWith({ bool? agent,
|
||||||
bool? anchor,
|
bool? anchor,
|
||||||
bool? follow,
|
bool? follow,
|
||||||
bool? friend,
|
bool? friend,
|
||||||
String? ktvIntegral,
|
String? ktvIntegral,
|
||||||
String? roomRole,
|
String? roomRole,
|
||||||
String? supperVip,
|
String? supperVip,
|
||||||
List<UseBadge>? useBadge,
|
List<UseBadge>? useBadge,
|
||||||
List<UseProps>? useProps,
|
List<UseProps>? useProps,
|
||||||
UserLevel? userLevel,
|
UserLevel? userLevel,
|
||||||
SocialChatUserProfile? userProfile,
|
SocialChatUserProfile? userProfile,
|
||||||
}) => RoomUserCardRes( agent: agent ?? _agent,
|
}) => RoomUserCardRes( agent: agent ?? _agent,
|
||||||
anchor: anchor ?? _anchor,
|
anchor: anchor ?? _anchor,
|
||||||
follow: follow ?? _follow,
|
follow: follow ?? _follow,
|
||||||
friend: friend ?? _friend,
|
friend: friend ?? _friend,
|
||||||
ktvIntegral: ktvIntegral ?? _ktvIntegral,
|
ktvIntegral: ktvIntegral ?? _ktvIntegral,
|
||||||
roomRole: roomRole ?? _roomRole,
|
roomRole: roomRole ?? _roomRole,
|
||||||
supperVip: supperVip ?? _supperVip,
|
supperVip: supperVip ?? _supperVip,
|
||||||
useBadge: useBadge ?? _useBadge,
|
useBadge: useBadge ?? _useBadge,
|
||||||
useProps: useProps ?? _useProps,
|
useProps: useProps ?? _useProps,
|
||||||
userLevel: userLevel ?? _userLevel,
|
userLevel: userLevel ?? _userLevel,
|
||||||
userProfile: userProfile ?? _userProfile,
|
userProfile: userProfile ?? _userProfile,
|
||||||
);
|
);
|
||||||
bool? get agent => _agent;
|
bool? get agent => _agent;
|
||||||
bool? get anchor => _anchor;
|
bool? get anchor => _anchor;
|
||||||
bool? get follow => _follow;
|
bool? get follow => _follow;
|
||||||
@ -140,9 +129,9 @@ RoomUserCardRes copyWith({ bool? agent,
|
|||||||
map['userProfile'] = _userProfile?.toJson();
|
map['userProfile'] = _userProfile?.toJson();
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// account : ""
|
/// account : ""
|
||||||
/// accountStatus : ""
|
/// accountStatus : ""
|
||||||
@ -179,19 +168,19 @@ RoomUserCardRes copyWith({ bool? agent,
|
|||||||
/// type : ""
|
/// type : ""
|
||||||
/// userId : 0
|
/// userId : 0
|
||||||
|
|
||||||
class WearBadge {
|
class WearBadge {
|
||||||
WearBadge({
|
WearBadge({
|
||||||
String? animationUrl,
|
String? animationUrl,
|
||||||
String? badgeKey,
|
String? badgeKey,
|
||||||
num? badgeLevel,
|
num? badgeLevel,
|
||||||
String? badgeName,
|
String? badgeName,
|
||||||
num? expireTime,
|
num? expireTime,
|
||||||
num? id,
|
num? id,
|
||||||
num? milestone,
|
num? milestone,
|
||||||
String? notSelectUrl,
|
String? notSelectUrl,
|
||||||
String? selectUrl,
|
String? selectUrl,
|
||||||
String? type,
|
String? type,
|
||||||
num? userId,}){
|
num? userId,}){
|
||||||
_animationUrl = animationUrl;
|
_animationUrl = animationUrl;
|
||||||
_badgeKey = badgeKey;
|
_badgeKey = badgeKey;
|
||||||
_badgeLevel = badgeLevel;
|
_badgeLevel = badgeLevel;
|
||||||
@ -200,10 +189,10 @@ class WearBadge {
|
|||||||
_id = id;
|
_id = id;
|
||||||
_milestone = milestone;
|
_milestone = milestone;
|
||||||
_notSelectUrl = notSelectUrl;
|
_notSelectUrl = notSelectUrl;
|
||||||
_selectUrl = selectUrl;
|
_selectUrl = selectUrl;
|
||||||
_type = type;
|
_type = type;
|
||||||
_userId = userId;
|
_userId = userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
WearBadge.fromJson(dynamic json) {
|
WearBadge.fromJson(dynamic json) {
|
||||||
_animationUrl = json['animationUrl'];
|
_animationUrl = json['animationUrl'];
|
||||||
@ -229,29 +218,29 @@ class WearBadge {
|
|||||||
String? _selectUrl;
|
String? _selectUrl;
|
||||||
String? _type;
|
String? _type;
|
||||||
num? _userId;
|
num? _userId;
|
||||||
WearBadge copyWith({ String? animationUrl,
|
WearBadge copyWith({ String? animationUrl,
|
||||||
String? badgeKey,
|
String? badgeKey,
|
||||||
num? badgeLevel,
|
num? badgeLevel,
|
||||||
String? badgeName,
|
String? badgeName,
|
||||||
num? expireTime,
|
num? expireTime,
|
||||||
num? id,
|
num? id,
|
||||||
num? milestone,
|
num? milestone,
|
||||||
String? notSelectUrl,
|
String? notSelectUrl,
|
||||||
String? selectUrl,
|
String? selectUrl,
|
||||||
String? type,
|
String? type,
|
||||||
num? userId,
|
num? userId,
|
||||||
}) => WearBadge( animationUrl: animationUrl ?? _animationUrl,
|
}) => WearBadge( animationUrl: animationUrl ?? _animationUrl,
|
||||||
badgeKey: badgeKey ?? _badgeKey,
|
badgeKey: badgeKey ?? _badgeKey,
|
||||||
badgeLevel: badgeLevel ?? _badgeLevel,
|
badgeLevel: badgeLevel ?? _badgeLevel,
|
||||||
badgeName: badgeName ?? _badgeName,
|
badgeName: badgeName ?? _badgeName,
|
||||||
expireTime: expireTime ?? _expireTime,
|
expireTime: expireTime ?? _expireTime,
|
||||||
id: id ?? _id,
|
id: id ?? _id,
|
||||||
milestone: milestone ?? _milestone,
|
milestone: milestone ?? _milestone,
|
||||||
notSelectUrl: notSelectUrl ?? _notSelectUrl,
|
notSelectUrl: notSelectUrl ?? _notSelectUrl,
|
||||||
selectUrl: selectUrl ?? _selectUrl,
|
selectUrl: selectUrl ?? _selectUrl,
|
||||||
type: type ?? _type,
|
type: type ?? _type,
|
||||||
userId: userId ?? _userId,
|
userId: userId ?? _userId,
|
||||||
);
|
);
|
||||||
String? get animationUrl => _animationUrl;
|
String? get animationUrl => _animationUrl;
|
||||||
String? get badgeKey => _badgeKey;
|
String? get badgeKey => _badgeKey;
|
||||||
num? get badgeLevel => _badgeLevel;
|
num? get badgeLevel => _badgeLevel;
|
||||||
@ -278,17 +267,17 @@ WearBadge copyWith({ String? animationUrl,
|
|||||||
map['type'] = _type;
|
map['type'] = _type;
|
||||||
map['userId'] = _userId;
|
map['userId'] = _userId;
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserLevel {
|
class UserLevel {
|
||||||
UserLevel({
|
UserLevel({
|
||||||
num? charmLevel,
|
num? charmLevel,
|
||||||
num? wealthLevel,}){
|
num? wealthLevel,}){
|
||||||
_charmLevel = charmLevel;
|
_charmLevel = charmLevel;
|
||||||
_wealthLevel = wealthLevel;
|
_wealthLevel = wealthLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
UserLevel.fromJson(dynamic json) {
|
UserLevel.fromJson(dynamic json) {
|
||||||
_charmLevel = json['charmLevel'];
|
_charmLevel = json['charmLevel'];
|
||||||
@ -296,11 +285,11 @@ class UserLevel {
|
|||||||
}
|
}
|
||||||
num? _charmLevel;
|
num? _charmLevel;
|
||||||
num? _wealthLevel;
|
num? _wealthLevel;
|
||||||
UserLevel copyWith({ num? charmLevel,
|
UserLevel copyWith({ num? charmLevel,
|
||||||
num? wealthLevel,
|
num? wealthLevel,
|
||||||
}) => UserLevel( charmLevel: charmLevel ?? _charmLevel,
|
}) => UserLevel( charmLevel: charmLevel ?? _charmLevel,
|
||||||
wealthLevel: wealthLevel ?? _wealthLevel,
|
wealthLevel: wealthLevel ?? _wealthLevel,
|
||||||
);
|
);
|
||||||
num? get charmLevel => _charmLevel;
|
num? get charmLevel => _charmLevel;
|
||||||
num? get wealthLevel => _wealthLevel;
|
num? get wealthLevel => _wealthLevel;
|
||||||
|
|
||||||
@ -309,23 +298,23 @@ UserLevel copyWith({ num? charmLevel,
|
|||||||
map['charmLevel'] = _charmLevel;
|
map['charmLevel'] = _charmLevel;
|
||||||
map['wealthLevel'] = _wealthLevel;
|
map['wealthLevel'] = _wealthLevel;
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class UseBadge {
|
class UseBadge {
|
||||||
UseBadge({
|
UseBadge({
|
||||||
String? animationUrl,
|
String? animationUrl,
|
||||||
String? badgeKey,
|
String? badgeKey,
|
||||||
num? badgeLevel,
|
num? badgeLevel,
|
||||||
String? badgeName,
|
String? badgeName,
|
||||||
num? expireTime,
|
num? expireTime,
|
||||||
String? id,
|
String? id,
|
||||||
String? milestone,
|
String? milestone,
|
||||||
String? notSelectUrl,
|
String? notSelectUrl,
|
||||||
String? selectUrl,
|
String? selectUrl,
|
||||||
String? type,
|
String? type,
|
||||||
String? userId,}){
|
String? userId,}){
|
||||||
_animationUrl = animationUrl;
|
_animationUrl = animationUrl;
|
||||||
_badgeKey = badgeKey;
|
_badgeKey = badgeKey;
|
||||||
_badgeLevel = badgeLevel;
|
_badgeLevel = badgeLevel;
|
||||||
@ -334,10 +323,10 @@ class UseBadge {
|
|||||||
_id = id;
|
_id = id;
|
||||||
_milestone = milestone;
|
_milestone = milestone;
|
||||||
_notSelectUrl = notSelectUrl;
|
_notSelectUrl = notSelectUrl;
|
||||||
_selectUrl = selectUrl;
|
_selectUrl = selectUrl;
|
||||||
_type = type;
|
_type = type;
|
||||||
_userId = userId;
|
_userId = userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
UseBadge.fromJson(dynamic json) {
|
UseBadge.fromJson(dynamic json) {
|
||||||
_animationUrl = json['animationUrl'];
|
_animationUrl = json['animationUrl'];
|
||||||
@ -363,29 +352,29 @@ class UseBadge {
|
|||||||
String? _selectUrl;
|
String? _selectUrl;
|
||||||
String? _type;
|
String? _type;
|
||||||
String? _userId;
|
String? _userId;
|
||||||
UseBadge copyWith({ String? animationUrl,
|
UseBadge copyWith({ String? animationUrl,
|
||||||
String? badgeKey,
|
String? badgeKey,
|
||||||
num? badgeLevel,
|
num? badgeLevel,
|
||||||
String? badgeName,
|
String? badgeName,
|
||||||
num? expireTime,
|
num? expireTime,
|
||||||
String? id,
|
String? id,
|
||||||
String? milestone,
|
String? milestone,
|
||||||
String? notSelectUrl,
|
String? notSelectUrl,
|
||||||
String? selectUrl,
|
String? selectUrl,
|
||||||
String? type,
|
String? type,
|
||||||
String? userId,
|
String? userId,
|
||||||
}) => UseBadge( animationUrl: animationUrl ?? _animationUrl,
|
}) => UseBadge( animationUrl: animationUrl ?? _animationUrl,
|
||||||
badgeKey: badgeKey ?? _badgeKey,
|
badgeKey: badgeKey ?? _badgeKey,
|
||||||
badgeLevel: badgeLevel ?? _badgeLevel,
|
badgeLevel: badgeLevel ?? _badgeLevel,
|
||||||
badgeName: badgeName ?? _badgeName,
|
badgeName: badgeName ?? _badgeName,
|
||||||
expireTime: expireTime ?? _expireTime,
|
expireTime: expireTime ?? _expireTime,
|
||||||
id: id ?? _id,
|
id: id ?? _id,
|
||||||
milestone: milestone ?? _milestone,
|
milestone: milestone ?? _milestone,
|
||||||
notSelectUrl: notSelectUrl ?? _notSelectUrl,
|
notSelectUrl: notSelectUrl ?? _notSelectUrl,
|
||||||
selectUrl: selectUrl ?? _selectUrl,
|
selectUrl: selectUrl ?? _selectUrl,
|
||||||
type: type ?? _type,
|
type: type ?? _type,
|
||||||
userId: userId ?? _userId,
|
userId: userId ?? _userId,
|
||||||
);
|
);
|
||||||
String? get animationUrl => _animationUrl;
|
String? get animationUrl => _animationUrl;
|
||||||
String? get badgeKey => _badgeKey;
|
String? get badgeKey => _badgeKey;
|
||||||
num? get badgeLevel => _badgeLevel;
|
num? get badgeLevel => _badgeLevel;
|
||||||
@ -412,6 +401,6 @@ UseBadge copyWith({ String? animationUrl,
|
|||||||
map['type'] = _type;
|
map['type'] = _type;
|
||||||
map['userId'] = _userId;
|
map['userId'] = _userId;
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -7,7 +7,6 @@ class SCFloatingMessage {
|
|||||||
String? userName; // 用户昵称
|
String? userName; // 用户昵称
|
||||||
String? toUserName; // 用户昵称
|
String? toUserName; // 用户昵称
|
||||||
String? giftUrl; // 礼物图标
|
String? giftUrl; // 礼物图标
|
||||||
String? fallbackUrl; // 兜底资源
|
|
||||||
String? giftId; // 礼物id
|
String? giftId; // 礼物id
|
||||||
int? type;
|
int? type;
|
||||||
int? rocketLevel;
|
int? rocketLevel;
|
||||||
@ -27,7 +26,6 @@ class SCFloatingMessage {
|
|||||||
this.userName = '',
|
this.userName = '',
|
||||||
this.toUserName = '',
|
this.toUserName = '',
|
||||||
this.giftUrl = '',
|
this.giftUrl = '',
|
||||||
this.fallbackUrl = '',
|
|
||||||
this.giftId = '',
|
this.giftId = '',
|
||||||
this.number = 0,
|
this.number = 0,
|
||||||
this.coins = 0,
|
this.coins = 0,
|
||||||
@ -46,7 +44,6 @@ class SCFloatingMessage {
|
|||||||
userName = json['userName'];
|
userName = json['userName'];
|
||||||
toUserName = json['toUserName'];
|
toUserName = json['toUserName'];
|
||||||
giftUrl = json['giftUrl'];
|
giftUrl = json['giftUrl'];
|
||||||
fallbackUrl = json['fallbackUrl'];
|
|
||||||
giftId = json['giftId'];
|
giftId = json['giftId'];
|
||||||
coins = json['coins'];
|
coins = json['coins'];
|
||||||
number = json['number'];
|
number = json['number'];
|
||||||
@ -66,7 +63,6 @@ class SCFloatingMessage {
|
|||||||
map['userName'] = userName;
|
map['userName'] = userName;
|
||||||
map['toUserName'] = toUserName;
|
map['toUserName'] = toUserName;
|
||||||
map['giftUrl'] = giftUrl;
|
map['giftUrl'] = giftUrl;
|
||||||
map['fallbackUrl'] = fallbackUrl;
|
|
||||||
map['giftId'] = giftId;
|
map['giftId'] = giftId;
|
||||||
map['coins'] = coins;
|
map['coins'] = coins;
|
||||||
map['number'] = number;
|
map['number'] = number;
|
||||||
|
|||||||
@ -10,9 +10,6 @@ class DataPersistence {
|
|||||||
"await_register_reward_socket";
|
"await_register_reward_socket";
|
||||||
static const String _pendingRegisterRewardDialogKey =
|
static const String _pendingRegisterRewardDialogKey =
|
||||||
"pending_register_reward_dialog";
|
"pending_register_reward_dialog";
|
||||||
static const String _pendingChannelAuthTypeKey = "pending_channel_auth_type";
|
|
||||||
static const String _pendingChannelAuthOpenIdKey =
|
|
||||||
"pending_channel_auth_open_id";
|
|
||||||
static SharedPreferences? _prefs;
|
static SharedPreferences? _prefs;
|
||||||
static bool _isInitialized = false;
|
static bool _isInitialized = false;
|
||||||
static Completer<void>? _initializationCompleter;
|
static Completer<void>? _initializationCompleter;
|
||||||
@ -218,31 +215,6 @@ class DataPersistence {
|
|||||||
return await remove(_awaitRegisterRewardSocketKey);
|
return await remove(_awaitRegisterRewardSocketKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> setPendingChannelAuth(
|
|
||||||
String authType,
|
|
||||||
String openId,
|
|
||||||
) async {
|
|
||||||
await Future.wait([
|
|
||||||
setString(_pendingChannelAuthTypeKey, authType),
|
|
||||||
setString(_pendingChannelAuthOpenIdKey, openId),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getPendingChannelAuthType() {
|
|
||||||
return getString(_pendingChannelAuthTypeKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getPendingChannelAuthOpenId() {
|
|
||||||
return getString(_pendingChannelAuthOpenIdKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> clearPendingChannelAuth() async {
|
|
||||||
await Future.wait([
|
|
||||||
remove(_pendingChannelAuthTypeKey),
|
|
||||||
remove(_pendingChannelAuthOpenIdKey),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
///获取用户已经添加的房间音乐
|
///获取用户已经添加的房间音乐
|
||||||
static String getUserRoomMusic() {
|
static String getUserRoomMusic() {
|
||||||
final currentUser = AccountStorage().getCurrentUser()?.userProfile?.account;
|
final currentUser = AccountStorage().getCurrentUser()?.userProfile?.account;
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import 'package:yumi/ui_kit/widgets/room/floating/floating_gift_screen_widget.da
|
|||||||
import 'package:yumi/ui_kit/widgets/room/floating/floating_luck_gift_screen_widget.dart';
|
import 'package:yumi/ui_kit/widgets/room/floating/floating_luck_gift_screen_widget.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/room/floating/floating_room_redenvelope_screen_widget.dart';
|
import 'package:yumi/ui_kit/widgets/room/floating/floating_room_redenvelope_screen_widget.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/room/floating/floating_room_rocket_screen_widget.dart';
|
import 'package:yumi/ui_kit/widgets/room/floating/floating_room_rocket_screen_widget.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/room/floating/floating_vip_entry_screen_widget.dart';
|
|
||||||
import 'package:yumi/shared/data_sources/models/message/sc_floating_message.dart';
|
import 'package:yumi/shared/data_sources/models/message/sc_floating_message.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/svga/sc_svga_asset_widget.dart';
|
import 'package:yumi/ui_kit/widgets/svga/sc_svga_asset_widget.dart';
|
||||||
|
|
||||||
@ -77,8 +76,8 @@ class OverlayManager {
|
|||||||
unawaited(
|
unawaited(
|
||||||
warmImageResource(
|
warmImageResource(
|
||||||
message.giftUrl ?? "",
|
message.giftUrl ?? "",
|
||||||
logicalWidth: message.type == 5 ? 350 : 96,
|
logicalWidth: 96,
|
||||||
logicalHeight: message.type == 5 ? 84 : 96,
|
logicalHeight: 96,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (message.type == 0) {
|
if (message.type == 0) {
|
||||||
@ -210,12 +209,6 @@ class OverlayManager {
|
|||||||
message: message,
|
message: message,
|
||||||
onAnimationCompleted: onComplete,
|
onAnimationCompleted: onComplete,
|
||||||
);
|
);
|
||||||
case 5:
|
|
||||||
//VIP进房飘窗
|
|
||||||
return FloatingVipEntryScreenWidget(
|
|
||||||
message: message,
|
|
||||||
onAnimationCompleted: onComplete,
|
|
||||||
);
|
|
||||||
default:
|
default:
|
||||||
onComplete();
|
onComplete();
|
||||||
return Container();
|
return Container();
|
||||||
@ -240,7 +233,6 @@ class OverlayManager {
|
|||||||
_removeActiveRoomMessage();
|
_removeActiveRoomMessage();
|
||||||
_removeMessagesByType(1);
|
_removeMessagesByType(1);
|
||||||
_removeMessagesByType(0);
|
_removeMessagesByType(0);
|
||||||
_removeMessagesByType(5);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 辅助方法:移除特定类型的消息
|
// 辅助方法:移除特定类型的消息
|
||||||
@ -274,7 +266,7 @@ class OverlayManager {
|
|||||||
if (message == null) {
|
if (message == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (message.type != 0 && message.type != 1 && message.type != 5) {
|
if (message.type != 0 && message.type != 1) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,9 +289,7 @@ class OverlayManager {
|
|||||||
void _removeActiveRoomMessage() {
|
void _removeActiveRoomMessage() {
|
||||||
final activeMessage = _currentMessage;
|
final activeMessage = _currentMessage;
|
||||||
if (activeMessage == null ||
|
if (activeMessage == null ||
|
||||||
(activeMessage.type != 0 &&
|
(activeMessage.type != 0 && activeMessage.type != 1)) {
|
||||||
activeMessage.type != 1 &&
|
|
||||||
activeMessage.type != 5)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_currentOverlayEntry?.remove();
|
_currentOverlayEntry?.remove();
|
||||||
|
|||||||
@ -161,7 +161,6 @@ class AccountStorage {
|
|||||||
_currentUser = null;
|
_currentUser = null;
|
||||||
DataPersistence.setToken("");
|
DataPersistence.setToken("");
|
||||||
DataPersistence.setCurrentUser("");
|
DataPersistence.setCurrentUser("");
|
||||||
unawaited(DataPersistence.clearPendingChannelAuth());
|
|
||||||
unawaited(DurableAuthStorage.clearSession());
|
unawaited(DurableAuthStorage.clearSession());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -196,8 +196,8 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
|
|||||||
num mickIndex, {
|
num mickIndex, {
|
||||||
String? eventType,
|
String? eventType,
|
||||||
String? inviterId,
|
String? inviterId,
|
||||||
}) async {
|
}) async {
|
||||||
Map<String, dynamic> params = {};
|
Map<String, dynamic> params = {};
|
||||||
params["roomId"] = roomId;
|
params["roomId"] = roomId;
|
||||||
params["mickIndex"] = mickIndex;
|
params["mickIndex"] = mickIndex;
|
||||||
if (eventType != null) {
|
if (eventType != null) {
|
||||||
@ -262,12 +262,12 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
|
|||||||
silentErrorToast
|
silentErrorToast
|
||||||
? const {BaseNetworkClient.silentErrorToastKey: true}
|
? const {BaseNetworkClient.silentErrorToastKey: true}
|
||||||
: null,
|
: null,
|
||||||
fromJson:
|
fromJson:
|
||||||
(json) =>
|
(json) =>
|
||||||
(json as List)
|
(json as List)
|
||||||
.map((e) => socialChatUserProfileFromJsonWithVipHint(e)!)
|
.map((e) => SocialChatUserProfile.fromJson(e))
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,8 +316,8 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
|
|||||||
String? roomId,
|
String? roomId,
|
||||||
SCGiveAwayGiftRoomAcceptsCmd? accepts,
|
SCGiveAwayGiftRoomAcceptsCmd? accepts,
|
||||||
String? dynamicContentId,
|
String? dynamicContentId,
|
||||||
}) async {
|
}) async {
|
||||||
Map<String, dynamic> params = {};
|
Map<String, dynamic> params = {};
|
||||||
if (roomId != null) {
|
if (roomId != null) {
|
||||||
params["roomId"] = roomId;
|
params["roomId"] = roomId;
|
||||||
}
|
}
|
||||||
@ -512,10 +512,9 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
|
|||||||
bool? adminLockSeat,
|
bool? adminLockSeat,
|
||||||
bool? showHeartbeat,
|
bool? showHeartbeat,
|
||||||
bool? openKtvMode,
|
bool? openKtvMode,
|
||||||
String? roomSpecialMikeType,
|
String? roomSpecialMikeType,
|
||||||
}) async {
|
}) async {
|
||||||
final rtmProvider = Provider.of<RtmProvider>(context, listen: false);
|
Map<String, dynamic> params = {};
|
||||||
Map<String, dynamic> params = {};
|
|
||||||
params["roomId"] = roomId;
|
params["roomId"] = roomId;
|
||||||
if (touristMike != null) {
|
if (touristMike != null) {
|
||||||
params["touristMike"] = touristMike;
|
params["touristMike"] = touristMike;
|
||||||
@ -553,7 +552,7 @@ class SCChatRoomRepository implements SocialChatRoomRepository {
|
|||||||
fromJson: (json) => json as bool,
|
fromJson: (json) => json as bool,
|
||||||
);
|
);
|
||||||
|
|
||||||
rtmProvider.dispatchMessage(
|
Provider.of<RtmProvider>(context, listen: false).dispatchMessage(
|
||||||
Msg(
|
Msg(
|
||||||
groupId: roomAcount,
|
groupId: roomAcount,
|
||||||
msg: roomId,
|
msg: roomId,
|
||||||
|
|||||||
@ -14,15 +14,7 @@ import 'package:tancent_vap/widgets/vap_view.dart';
|
|||||||
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
|
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
|
||||||
|
|
||||||
class SCGiftVapSvgaManager {
|
class SCGiftVapSvgaManager {
|
||||||
static const int giftEffectType = 0;
|
static const int _maxPendingTaskCount = 18;
|
||||||
static const int entryEffectType = 1;
|
|
||||||
static const int giftEffectPriority = 100;
|
|
||||||
static const int entryEffectPriority = 1000;
|
|
||||||
static const int _maxPendingGiftTaskCount = 24;
|
|
||||||
static const int _maxPendingEntryTaskCount = 5;
|
|
||||||
static const int _maxConsecutiveEntryTasksBeforeGift = 2;
|
|
||||||
static const Duration _taskWatchdogTimeout = Duration(seconds: 20);
|
|
||||||
static const Duration _entryTaskTtl = Duration(seconds: 6);
|
|
||||||
Map<String, MovieEntity> videoItemCache = {};
|
Map<String, MovieEntity> videoItemCache = {};
|
||||||
static SCGiftVapSvgaManager? _inst;
|
static SCGiftVapSvgaManager? _inst;
|
||||||
static const int _maxPreloadConcurrency = 1;
|
static const int _maxPreloadConcurrency = 1;
|
||||||
@ -34,10 +26,7 @@ class SCGiftVapSvgaManager {
|
|||||||
|
|
||||||
factory SCGiftVapSvgaManager() => _inst ??= SCGiftVapSvgaManager._internal();
|
factory SCGiftVapSvgaManager() => _inst ??= SCGiftVapSvgaManager._internal();
|
||||||
|
|
||||||
final SCPriorityQueue<SCVapTask> _giftQueue = SCPriorityQueue<SCVapTask>(
|
final SCPriorityQueue<SCVapTask> _tq = SCPriorityQueue<SCVapTask>(
|
||||||
(a, b) => a.compareTo(b), // 调用 SCVapTask 的 compareTo 方法
|
|
||||||
);
|
|
||||||
final SCPriorityQueue<SCVapTask> _entryQueue = SCPriorityQueue<SCVapTask>(
|
|
||||||
(a, b) => a.compareTo(b), // 调用 SCVapTask 的 compareTo 方法
|
(a, b) => a.compareTo(b), // 调用 SCVapTask 的 compareTo 方法
|
||||||
);
|
);
|
||||||
VapController? _rgc;
|
VapController? _rgc;
|
||||||
@ -45,14 +34,12 @@ class SCGiftVapSvgaManager {
|
|||||||
bool _play = false;
|
bool _play = false;
|
||||||
bool _dis = false;
|
bool _dis = false;
|
||||||
SCVapTask? _currentTask;
|
SCVapTask? _currentTask;
|
||||||
Timer? _currentTaskWatchdogTimer;
|
|
||||||
final Queue<String> _preloadQueue = Queue<String>();
|
final Queue<String> _preloadQueue = Queue<String>();
|
||||||
final Set<String> _queuedPreloadPaths = <String>{};
|
final Set<String> _queuedPreloadPaths = <String>{};
|
||||||
final Map<String, Future<MovieEntity>> _svgaLoadTasks = {};
|
final Map<String, Future<MovieEntity>> _svgaLoadTasks = {};
|
||||||
final Map<String, Future<String>> _playablePathTasks = {};
|
final Map<String, Future<String>> _playablePathTasks = {};
|
||||||
final Map<String, String> _playablePathCache = {};
|
final Map<String, String> _playablePathCache = {};
|
||||||
int _activePreloadCount = 0;
|
int _activePreloadCount = 0;
|
||||||
int _consecutiveEntryTaskCount = 0;
|
|
||||||
|
|
||||||
bool _pause = false;
|
bool _pause = false;
|
||||||
|
|
||||||
@ -76,26 +63,6 @@ class SCGiftVapSvgaManager {
|
|||||||
return SCPathUtils.getFileExtension(path).toLowerCase() == ".svga";
|
return SCPathUtils.getFileExtension(path).toLowerCase() == ".svga";
|
||||||
}
|
}
|
||||||
|
|
||||||
static int resolveEffectPriority({
|
|
||||||
int priority = 0,
|
|
||||||
int type = giftEffectType,
|
|
||||||
}) {
|
|
||||||
if (type == entryEffectType) {
|
|
||||||
return priority > entryEffectPriority ? priority : entryEffectPriority;
|
|
||||||
}
|
|
||||||
if (priority != 0) {
|
|
||||||
return priority;
|
|
||||||
}
|
|
||||||
return giftEffectPriority;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _isEntryTask(SCVapTask task) => task.type == entryEffectType;
|
|
||||||
|
|
||||||
int get _pendingTaskCount => _giftQueue.length + _entryQueue.length;
|
|
||||||
|
|
||||||
String get _queueSummary =>
|
|
||||||
'entry=${_entryQueue.length} gift=${_giftQueue.length}';
|
|
||||||
|
|
||||||
bool _isControllerReady(SCVapTask task) {
|
bool _isControllerReady(SCVapTask task) {
|
||||||
if (_needsSvgaController(task.path)) {
|
if (_needsSvgaController(task.path)) {
|
||||||
return _rsc != null;
|
return _rsc != null;
|
||||||
@ -286,96 +253,6 @@ class SCGiftVapSvgaManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _enqueueTask(SCVapTask task) {
|
|
||||||
if (_isEntryTask(task)) {
|
|
||||||
_dropExpiredEntryTasks();
|
|
||||||
_entryQueue.add(task);
|
|
||||||
_trimEntryQueue();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_giftQueue.add(task);
|
|
||||||
_trimGiftQueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _trimEntryQueue() {
|
|
||||||
while (_entryQueue.length > _maxPendingEntryTaskCount) {
|
|
||||||
final removedTask = _removeEntryOverflowTask();
|
|
||||||
_completeDroppedQueuedTask(removedTask, 'trim entry queue');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SCVapTask _removeEntryOverflowTask() {
|
|
||||||
final tasks = _entryQueue.unorderedElements.cast<SCVapTask>();
|
|
||||||
var candidate = tasks.first;
|
|
||||||
for (final task in tasks.skip(1)) {
|
|
||||||
if (task.priority < candidate.priority ||
|
|
||||||
(task.priority == candidate.priority &&
|
|
||||||
task.createdAt.isBefore(candidate.createdAt))) {
|
|
||||||
candidate = task;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_entryQueue.remove(candidate);
|
|
||||||
return candidate;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _trimGiftQueue() {
|
|
||||||
while (_giftQueue.length > _maxPendingGiftTaskCount) {
|
|
||||||
final removedTask = _giftQueue.removeLast();
|
|
||||||
_completeDroppedQueuedTask(removedTask, 'trim gift queue');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _dropExpiredEntryTasks() {
|
|
||||||
if (_entryQueue.isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final now = DateTime.now();
|
|
||||||
final expiredTasks =
|
|
||||||
_entryQueue.unorderedElements
|
|
||||||
.cast<SCVapTask>()
|
|
||||||
.where((task) => now.difference(task.createdAt) > _entryTaskTtl)
|
|
||||||
.toList();
|
|
||||||
for (final task in expiredTasks) {
|
|
||||||
_entryQueue.remove(task);
|
|
||||||
_completeDroppedQueuedTask(
|
|
||||||
task,
|
|
||||||
'drop expired entry task ageMs=${now.difference(task.createdAt).inMilliseconds}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _completeDroppedQueuedTask(SCVapTask task, String reason) {
|
|
||||||
SCRoomEffectScheduler().completeHighCostTask(debugLabel: task.path);
|
|
||||||
_log(
|
|
||||||
'$reason path=${task.path} priority=${task.priority} '
|
|
||||||
'type=${task.type} queue=$_queueSummary',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
SCVapTask? _peekNextQueuedTask() {
|
|
||||||
_dropExpiredEntryTasks();
|
|
||||||
if (_entryQueue.isNotEmpty) {
|
|
||||||
final shouldYieldToGift =
|
|
||||||
_giftQueue.isNotEmpty &&
|
|
||||||
_consecutiveEntryTaskCount >= _maxConsecutiveEntryTasksBeforeGift;
|
|
||||||
if (!shouldYieldToGift) {
|
|
||||||
return _entryQueue.first;
|
|
||||||
}
|
|
||||||
return _giftQueue.first;
|
|
||||||
}
|
|
||||||
return _giftQueue.first;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _removeQueuedTask(SCVapTask task) {
|
|
||||||
if (_isEntryTask(task)) {
|
|
||||||
_entryQueue.remove(task);
|
|
||||||
_consecutiveEntryTaskCount++;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_giftQueue.remove(task);
|
|
||||||
_consecutiveEntryTaskCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _scheduleNextTask({Duration delay = Duration.zero}) {
|
void _scheduleNextTask({Duration delay = Duration.zero}) {
|
||||||
if (_dis) {
|
if (_dis) {
|
||||||
return;
|
return;
|
||||||
@ -391,14 +268,8 @@ class SCGiftVapSvgaManager {
|
|||||||
if (_dis) {
|
if (_dis) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final finishedTask = _currentTask;
|
|
||||||
if (!_play && finishedTask == null) {
|
|
||||||
_scheduleNextTask(delay: delay);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_cancelCurrentTaskWatchdog();
|
|
||||||
SCRoomEffectScheduler().completeHighCostTask(
|
SCRoomEffectScheduler().completeHighCostTask(
|
||||||
debugLabel: finishedTask?.path,
|
debugLabel: _currentTask?.path,
|
||||||
);
|
);
|
||||||
_play = false;
|
_play = false;
|
||||||
_currentTask = null;
|
_currentTask = null;
|
||||||
@ -417,7 +288,7 @@ class SCGiftVapSvgaManager {
|
|||||||
_rgc = vapController;
|
_rgc = vapController;
|
||||||
_log(
|
_log(
|
||||||
'bindVapCtrl hasVapCtrl=${_rgc != null} '
|
'bindVapCtrl hasVapCtrl=${_rgc != null} '
|
||||||
'hasSvgaCtrl=${_rsc != null} queue=$_queueSummary mute=$_mute',
|
'hasSvgaCtrl=${_rsc != null} queue=${_tq.length} mute=$_mute',
|
||||||
);
|
);
|
||||||
_rgc?.setAnimListener(
|
_rgc?.setAnimListener(
|
||||||
onVideoStart: () {
|
onVideoStart: () {
|
||||||
@ -443,13 +314,18 @@ class SCGiftVapSvgaManager {
|
|||||||
_rsc = svgaController;
|
_rsc = svgaController;
|
||||||
_log(
|
_log(
|
||||||
'bindSvgaCtrl hasSvgaCtrl=${_rsc != null} '
|
'bindSvgaCtrl hasSvgaCtrl=${_rsc != null} '
|
||||||
'hasVapCtrl=${_rgc != null} queue=$_queueSummary',
|
'hasVapCtrl=${_rgc != null} queue=${_tq.length}',
|
||||||
);
|
);
|
||||||
_rsc?.addStatusListener((AnimationStatus status) {
|
_rsc?.addStatusListener((AnimationStatus status) {
|
||||||
if (status.isCompleted) {
|
if (status.isCompleted) {
|
||||||
_log('svga completed path=${_currentTask?.path}');
|
_log('svga completed path=${_currentTask?.path}');
|
||||||
_rsc?.reset();
|
_rsc?.reset();
|
||||||
_finishCurrentTask();
|
SCRoomEffectScheduler().completeHighCostTask(
|
||||||
|
debugLabel: _currentTask?.path,
|
||||||
|
);
|
||||||
|
_play = false;
|
||||||
|
_currentTask = null;
|
||||||
|
_pn();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_scheduleNextTask();
|
_scheduleNextTask();
|
||||||
@ -461,22 +337,18 @@ class SCGiftVapSvgaManager {
|
|||||||
String path, {
|
String path, {
|
||||||
int priority = 0,
|
int priority = 0,
|
||||||
Map<String, VAPContent>? customResources,
|
Map<String, VAPContent>? customResources,
|
||||||
int type = giftEffectType,
|
int type = 0,
|
||||||
}) {
|
}) {
|
||||||
if (path.isEmpty) {
|
if (path.isEmpty) {
|
||||||
_log('play ignored because path is empty');
|
_log('play ignored because path is empty');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final resolvedPriority = resolveEffectPriority(
|
|
||||||
priority: priority,
|
|
||||||
type: type,
|
|
||||||
);
|
|
||||||
_log(
|
_log(
|
||||||
'play request path=$path ext=${SCPathUtils.getFileExtension(path)} '
|
'play request path=$path ext=${SCPathUtils.getFileExtension(path)} '
|
||||||
'priority=$priority resolvedPriority=$resolvedPriority type=$type '
|
'priority=$priority type=$type '
|
||||||
'sdkInt=${SCGlobalConfig.sdkInt} maxSdkNoAnim=${SCGlobalConfig.maxSdkNoAnim} '
|
'sdkInt=${SCGlobalConfig.sdkInt} maxSdkNoAnim=${SCGlobalConfig.maxSdkNoAnim} '
|
||||||
'lowPerformance=${SCGlobalConfig.isLowPerformanceDevice} '
|
'lowPerformance=${SCGlobalConfig.isLowPerformanceDevice} '
|
||||||
'disposed=$_dis playing=$_play queueBefore=$_queueSummary '
|
'disposed=$_dis playing=$_play queueBefore=${_tq.length} '
|
||||||
'hasSvgaCtrl=${_rsc != null} hasVapCtrl=${_rgc != null}',
|
'hasSvgaCtrl=${_rsc != null} hasVapCtrl=${_rgc != null}',
|
||||||
);
|
);
|
||||||
if (SCGlobalConfig.allowsHighCostAnimations) {
|
if (SCGlobalConfig.allowsHighCostAnimations) {
|
||||||
@ -486,15 +358,31 @@ class SCGiftVapSvgaManager {
|
|||||||
}
|
}
|
||||||
final task = SCVapTask(
|
final task = SCVapTask(
|
||||||
path: path,
|
path: path,
|
||||||
type: type,
|
priority: priority,
|
||||||
priority: resolvedPriority,
|
|
||||||
customResources: customResources,
|
customResources: customResources,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (_tq.length >= _maxPendingTaskCount && priority <= 0) {
|
||||||
|
_log(
|
||||||
|
'drop play request because queue is full path=$path '
|
||||||
|
'priority=$priority queue=${_tq.length}',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_tq.add(task);
|
||||||
SCRoomEffectScheduler().registerHighCostTaskQueued(debugLabel: path);
|
SCRoomEffectScheduler().registerHighCostTaskQueued(debugLabel: path);
|
||||||
_enqueueTask(task);
|
|
||||||
unawaited(preload(path));
|
unawaited(preload(path));
|
||||||
_log('task enqueued path=$path queueAfter=$_queueSummary');
|
while (_tq.length > _maxPendingTaskCount) {
|
||||||
|
final removedTask = _tq.removeLast();
|
||||||
|
SCRoomEffectScheduler().completeHighCostTask(
|
||||||
|
debugLabel: removedTask.path,
|
||||||
|
);
|
||||||
|
_log(
|
||||||
|
'trim queued task path=${removedTask.path} '
|
||||||
|
'priority=${removedTask.priority} queue=${_tq.length}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_log('task enqueued path=$path queueAfter=${_tq.length}');
|
||||||
if (!_play) {
|
if (!_play) {
|
||||||
_pn();
|
_pn();
|
||||||
}
|
}
|
||||||
@ -510,34 +398,32 @@ class SCGiftVapSvgaManager {
|
|||||||
// 播放下一个任务
|
// 播放下一个任务
|
||||||
Future<void> _pn() async {
|
Future<void> _pn() async {
|
||||||
if (_pause) {
|
if (_pause) {
|
||||||
_log('skip _pn because paused queue=$_queueSummary');
|
_log('skip _pn because paused queue=${_tq.length}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_dis || _pendingTaskCount == 0 || _play) return;
|
if (_dis || _tq.isEmpty || _play) return;
|
||||||
|
|
||||||
final task = _peekNextQueuedTask();
|
final task = _tq.first;
|
||||||
if (task == null || !_isControllerReady(task)) {
|
if (task == null || !_isControllerReady(task)) {
|
||||||
_log(
|
_log(
|
||||||
'controller not ready for path=${task?.path} '
|
'controller not ready for path=${task?.path} '
|
||||||
'needSvga=${task != null ? _needsSvgaController(task.path) : "unknown"} '
|
'needSvga=${task != null ? _needsSvgaController(task.path) : "unknown"} '
|
||||||
'hasSvgaCtrl=${_rsc != null} hasVapCtrl=${_rgc != null} '
|
'hasSvgaCtrl=${_rsc != null} hasVapCtrl=${_rgc != null} '
|
||||||
'queue=$_queueSummary',
|
'queue=${_tq.length}',
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_removeQueuedTask(task);
|
_tq.removeFirst();
|
||||||
_play = true;
|
_play = true;
|
||||||
_currentTask = task;
|
_currentTask = task;
|
||||||
_armCurrentTaskWatchdog(task);
|
|
||||||
try {
|
try {
|
||||||
final pathType = SCPathUtils.getPathType(task.path);
|
final pathType = SCPathUtils.getPathType(task.path);
|
||||||
_log(
|
_log(
|
||||||
'start task path=${task.path} '
|
'start task path=${task.path} '
|
||||||
'pathType=$pathType '
|
'pathType=$pathType '
|
||||||
'queueRemaining=$_queueSummary '
|
'queueRemaining=${_tq.length} '
|
||||||
'needSvga=${_needsSvgaController(task.path)} '
|
'needSvga=${_needsSvgaController(task.path)}',
|
||||||
'type=${task.type} consecutiveEntry=$_consecutiveEntryTaskCount',
|
|
||||||
);
|
);
|
||||||
if (pathType == PathType.asset) {
|
if (pathType == PathType.asset) {
|
||||||
await _pa(task);
|
await _pa(task);
|
||||||
@ -561,9 +447,6 @@ class SCGiftVapSvgaManager {
|
|||||||
if (SCPathUtils.getFileExtension(task.path).toLowerCase() == ".svga") {
|
if (SCPathUtils.getFileExtension(task.path).toLowerCase() == ".svga") {
|
||||||
_log('play asset svga path=${task.path}');
|
_log('play asset svga path=${task.path}');
|
||||||
final entity = await _loadSvgaEntity(task.path);
|
final entity = await _loadSvgaEntity(task.path);
|
||||||
if (!_isCurrentTask(task)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_log('use prepared asset svga path=${task.path}');
|
_log('use prepared asset svga path=${task.path}');
|
||||||
_rsc?.videoItem = entity;
|
_rsc?.videoItem = entity;
|
||||||
_rsc?.reset();
|
_rsc?.reset();
|
||||||
@ -587,9 +470,6 @@ class SCGiftVapSvgaManager {
|
|||||||
}
|
}
|
||||||
if (SCPathUtils.getFileExtension(task.path).toLowerCase() == ".svga") {
|
if (SCPathUtils.getFileExtension(task.path).toLowerCase() == ".svga") {
|
||||||
final entity = await _loadSvgaEntity(task.path);
|
final entity = await _loadSvgaEntity(task.path);
|
||||||
if (!_isCurrentTask(task)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_log('play local svga file path=${task.path}');
|
_log('play local svga file path=${task.path}');
|
||||||
_rsc?.videoItem = entity;
|
_rsc?.videoItem = entity;
|
||||||
_rsc?.reset();
|
_rsc?.reset();
|
||||||
@ -603,17 +483,11 @@ class SCGiftVapSvgaManager {
|
|||||||
}
|
}
|
||||||
_log('play local vap/mp4 file path=${task.path}');
|
_log('play local vap/mp4 file path=${task.path}');
|
||||||
await _rgc?.setMute(_mute);
|
await _rgc?.setMute(_mute);
|
||||||
if (!_isCurrentTask(task)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (task.customResources != null) {
|
if (task.customResources != null) {
|
||||||
task.customResources?.forEach((k, v) async {
|
task.customResources?.forEach((k, v) async {
|
||||||
await _rgc?.setVapTagContent(k, v);
|
await _rgc?.setVapTagContent(k, v);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!_isCurrentTask(task)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await _rgc!.playFile(task.path);
|
await _rgc!.playFile(task.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -630,9 +504,6 @@ class SCGiftVapSvgaManager {
|
|||||||
_finishCurrentTask();
|
_finishCurrentTask();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!_isCurrentTask(task)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_log('use prepared network svga path=${task.path}');
|
_log('use prepared network svga path=${task.path}');
|
||||||
_rsc?.videoItem = entity;
|
_rsc?.videoItem = entity;
|
||||||
_rsc?.reset();
|
_rsc?.reset();
|
||||||
@ -641,15 +512,11 @@ class SCGiftVapSvgaManager {
|
|||||||
} else {
|
} else {
|
||||||
_log('download network vap/mp4 path=${task.path}');
|
_log('download network vap/mp4 path=${task.path}');
|
||||||
final playablePath = await _ensurePlayableFilePath(task.path);
|
final playablePath = await _ensurePlayableFilePath(task.path);
|
||||||
if (!_isCurrentTask(task)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!_dis) {
|
if (!_dis) {
|
||||||
_log('use prepared network vap/mp4 local path=$playablePath');
|
_log('use prepared network vap/mp4 local path=$playablePath');
|
||||||
await _pf(
|
await _pf(
|
||||||
SCVapTask(
|
SCVapTask(
|
||||||
path: playablePath,
|
path: playablePath,
|
||||||
type: task.type,
|
|
||||||
priority: task.priority,
|
priority: task.priority,
|
||||||
customResources: task.customResources,
|
customResources: task.customResources,
|
||||||
),
|
),
|
||||||
@ -666,52 +533,25 @@ class SCGiftVapSvgaManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isCurrentTask(SCVapTask task) {
|
|
||||||
return !_dis && _play && identical(_currentTask, task);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _armCurrentTaskWatchdog(SCVapTask task) {
|
|
||||||
_cancelCurrentTaskWatchdog();
|
|
||||||
_currentTaskWatchdogTimer = Timer(_taskWatchdogTimeout, () {
|
|
||||||
if (!_isCurrentTask(task)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_log('task watchdog timeout path=${task.path}');
|
|
||||||
_rgc?.stop();
|
|
||||||
_rsc?.stop();
|
|
||||||
_rsc?.reset();
|
|
||||||
_rsc?.videoItem = null;
|
|
||||||
_finishCurrentTask(delay: const Duration(milliseconds: 50));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _cancelCurrentTaskWatchdog() {
|
|
||||||
_currentTaskWatchdogTimer?.cancel();
|
|
||||||
_currentTaskWatchdogTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
//暂停动画播放
|
//暂停动画播放
|
||||||
void pauseAnim() {
|
void pauseAnim() {
|
||||||
_pause = true;
|
_pause = true;
|
||||||
_log('pauseAnim queue=$_queueSummary');
|
_log('pauseAnim queue=${_tq.length}');
|
||||||
}
|
}
|
||||||
|
|
||||||
//恢复动画播放
|
//恢复动画播放
|
||||||
void resumeAnim() {
|
void resumeAnim() {
|
||||||
_pause = false;
|
_pause = false;
|
||||||
_log('resumeAnim queue=$_queueSummary');
|
_log('resumeAnim queue=${_tq.length}');
|
||||||
_pn();
|
_pn();
|
||||||
}
|
}
|
||||||
|
|
||||||
void stopPlayback() {
|
void stopPlayback() {
|
||||||
_log('stopPlayback queue=$_queueSummary currentPath=${_currentTask?.path}');
|
_log('stopPlayback queue=${_tq.length} currentPath=${_currentTask?.path}');
|
||||||
_play = false;
|
_play = false;
|
||||||
_currentTask = null;
|
_currentTask = null;
|
||||||
_pause = false;
|
_pause = false;
|
||||||
_consecutiveEntryTaskCount = 0;
|
_tq.clear();
|
||||||
_cancelCurrentTaskWatchdog();
|
|
||||||
_giftQueue.clear();
|
|
||||||
_entryQueue.clear();
|
|
||||||
_preloadQueue.clear();
|
_preloadQueue.clear();
|
||||||
_queuedPreloadPaths.clear();
|
_queuedPreloadPaths.clear();
|
||||||
_activePreloadCount = 0;
|
_activePreloadCount = 0;
|
||||||
@ -724,7 +564,7 @@ class SCGiftVapSvgaManager {
|
|||||||
|
|
||||||
// 释放资源
|
// 释放资源
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_log('dispose queue=$_queueSummary currentPath=${_currentTask?.path}');
|
_log('dispose queue=${_tq.length} currentPath=${_currentTask?.path}');
|
||||||
_dis = true;
|
_dis = true;
|
||||||
stopPlayback();
|
stopPlayback();
|
||||||
_svgaLoadTasks.clear();
|
_svgaLoadTasks.clear();
|
||||||
@ -740,7 +580,7 @@ class SCGiftVapSvgaManager {
|
|||||||
// 清除所有任务
|
// 清除所有任务
|
||||||
void clearTasks() {
|
void clearTasks() {
|
||||||
_log(
|
_log(
|
||||||
'clearTasks queueBefore=$_queueSummary currentPath=${_currentTask?.path}',
|
'clearTasks queueBefore=${_tq.length} currentPath=${_currentTask?.path}',
|
||||||
);
|
);
|
||||||
stopPlayback();
|
stopPlayback();
|
||||||
}
|
}
|
||||||
@ -754,7 +594,6 @@ class SCVapTask implements Comparable<SCVapTask> {
|
|||||||
final int type;
|
final int type;
|
||||||
final int priority;
|
final int priority;
|
||||||
final Map<String, VAPContent>? customResources;
|
final Map<String, VAPContent>? customResources;
|
||||||
final DateTime createdAt;
|
|
||||||
final int _seq;
|
final int _seq;
|
||||||
|
|
||||||
static int _nextSeq = 0;
|
static int _nextSeq = 0;
|
||||||
@ -764,9 +603,7 @@ class SCVapTask implements Comparable<SCVapTask> {
|
|||||||
this.priority = 0,
|
this.priority = 0,
|
||||||
this.customResources,
|
this.customResources,
|
||||||
this.type = 0,
|
this.type = 0,
|
||||||
DateTime? createdAt,
|
}) : _seq = _nextSeq++;
|
||||||
}) : createdAt = createdAt ?? DateTime.now(),
|
|
||||||
_seq = _nextSeq++;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int compareTo(SCVapTask other) {
|
int compareTo(SCVapTask other) {
|
||||||
@ -781,7 +618,7 @@ class SCVapTask implements Comparable<SCVapTask> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SCVapTask{path: $path, type: $type, priority: $priority, seq: $_seq}';
|
return 'SCVapTask{path: $path, priority: $priority, seq: $_seq}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -812,8 +649,6 @@ class SCPriorityQueue<E> {
|
|||||||
|
|
||||||
void clear() => _els.clear();
|
void clear() => _els.clear();
|
||||||
|
|
||||||
bool remove(E element) => _els.remove(element);
|
|
||||||
|
|
||||||
E removeLast() {
|
E removeLast() {
|
||||||
if (isEmpty) throw StateError("No elements");
|
if (isEmpty) throw StateError("No elements");
|
||||||
return _els.removeLast();
|
return _els.removeLast();
|
||||||
|
|||||||
@ -90,21 +90,7 @@ class SCPathUtils {
|
|||||||
|
|
||||||
/// 获取路径的文件扩展名
|
/// 获取路径的文件扩展名
|
||||||
static String getFileExtension(String filePath) {
|
static String getFileExtension(String filePath) {
|
||||||
final value = filePath.trim();
|
return path.extension(filePath).toLowerCase();
|
||||||
if (value.isEmpty) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
final uri = Uri.tryParse(value);
|
|
||||||
if (uri != null && (uri.scheme.isNotEmpty || uri.host.isNotEmpty)) {
|
|
||||||
final uriPathExtension = path.extension(uri.path).toLowerCase();
|
|
||||||
if (uriPathExtension.isNotEmpty) {
|
|
||||||
return uriPathExtension;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final normalizedValue = value.split('?').first.split('#').first;
|
|
||||||
return path.extension(normalizedValue).toLowerCase();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 从网络地址中提取文件名
|
/// 从网络地址中提取文件名
|
||||||
|
|||||||
@ -9,9 +9,6 @@ import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.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/shared/business_logic/models/res/login_res.dart';
|
|
||||||
import 'package:yumi/shared/data_sources/models/message/sc_floating_message.dart';
|
|
||||||
import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart';
|
|
||||||
import 'package:yumi/shared/tools/sc_network_image_utils.dart';
|
import 'package:yumi/shared/tools/sc_network_image_utils.dart';
|
||||||
import 'package:yumi/shared/tools/sc_room_effect_scheduler.dart';
|
import 'package:yumi/shared/tools/sc_room_effect_scheduler.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
||||||
@ -43,7 +40,6 @@ class _RoomAnimationQueueScreenState extends State<RoomAnimationQueueScreen> {
|
|||||||
if (!_effectsEnabled) {
|
if (!_effectsEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_enqueueVipEntryFloatingMessage(msg);
|
|
||||||
unawaited(
|
unawaited(
|
||||||
warmImageResource(
|
warmImageResource(
|
||||||
msg.user?.userAvatar ?? "",
|
msg.user?.userAvatar ?? "",
|
||||||
@ -63,72 +59,6 @@ class _RoomAnimationQueueScreenState extends State<RoomAnimationQueueScreen> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _enqueueVipEntryFloatingMessage(Msg msg) {
|
|
||||||
final floatPicture = msg.user?.getFloatPicture();
|
|
||||||
final floatPictureUrl = _resolveVipFloatPictureUrl(floatPicture);
|
|
||||||
final floatPictureFallbackUrl = _resolveVipFloatPictureFallbackUrl(
|
|
||||||
floatPicture,
|
|
||||||
);
|
|
||||||
if (floatPictureUrl.isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final roomId =
|
|
||||||
Provider.of<RtcProvider>(
|
|
||||||
context,
|
|
||||||
listen: false,
|
|
||||||
).currenRoom?.roomProfile?.roomProfile?.id?.trim() ??
|
|
||||||
"";
|
|
||||||
if (roomId.isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
debugPrint(
|
|
||||||
'[VIP][RoomFloat] enqueue entry float '
|
|
||||||
'userId=${msg.user?.id} roomId=$roomId resourceId=${floatPicture?.id} '
|
|
||||||
'url=$floatPictureUrl',
|
|
||||||
);
|
|
||||||
OverlayManager().addMessage(
|
|
||||||
SCFloatingMessage(
|
|
||||||
type: 5,
|
|
||||||
userId: msg.user?.id,
|
|
||||||
roomId: roomId,
|
|
||||||
userAvatarUrl: msg.user?.userAvatar,
|
|
||||||
userName: msg.user?.userNickname,
|
|
||||||
giftUrl: floatPictureUrl,
|
|
||||||
fallbackUrl: floatPictureFallbackUrl,
|
|
||||||
priority: 900,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _resolveVipFloatPictureUrl(PropsResources? resource) {
|
|
||||||
return _firstNonBlank([
|
|
||||||
resource?.sourceUrl,
|
|
||||||
resource?.roomSendCoverUrl,
|
|
||||||
resource?.roomOpenedUrl,
|
|
||||||
resource?.cover,
|
|
||||||
resource?.roomNotOpenedUrl,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _resolveVipFloatPictureFallbackUrl(PropsResources? resource) {
|
|
||||||
return _firstNonBlank([
|
|
||||||
resource?.roomSendCoverUrl,
|
|
||||||
resource?.roomOpenedUrl,
|
|
||||||
resource?.cover,
|
|
||||||
resource?.roomNotOpenedUrl,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _firstNonBlank(Iterable<String?> values) {
|
|
||||||
for (final value in values) {
|
|
||||||
final text = value?.trim();
|
|
||||||
if (text != null && text.isNotEmpty) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
void _addToQueue(Msg msg) {
|
void _addToQueue(Msg msg) {
|
||||||
if (!_effectsEnabled || !mounted) {
|
if (!_effectsEnabled || !mounted) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -17,7 +16,6 @@ class RoomGiftSeatFlightRequest {
|
|||||||
this.beginSize = 96,
|
this.beginSize = 96,
|
||||||
this.endSize = 34,
|
this.endSize = 34,
|
||||||
this.queueTag,
|
this.queueTag,
|
||||||
this.batchTag,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final String imagePath;
|
final String imagePath;
|
||||||
@ -27,7 +25,6 @@ class RoomGiftSeatFlightRequest {
|
|||||||
final double beginSize;
|
final double beginSize;
|
||||||
final double endSize;
|
final double endSize;
|
||||||
final String? queueTag;
|
final String? queueTag;
|
||||||
final String? batchTag;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoomGiftSeatFlightController {
|
class RoomGiftSeatFlightController {
|
||||||
@ -124,7 +121,6 @@ class RoomGiftSeatFlightController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final queueTag = request.queueTag?.trim();
|
final queueTag = request.queueTag?.trim();
|
||||||
final batchTag = request.batchTag?.trim();
|
|
||||||
return RoomGiftSeatFlightRequest(
|
return RoomGiftSeatFlightRequest(
|
||||||
imagePath: imagePath,
|
imagePath: imagePath,
|
||||||
targetUserId: targetUserId,
|
targetUserId: targetUserId,
|
||||||
@ -133,7 +129,6 @@ class RoomGiftSeatFlightController {
|
|||||||
beginSize: request.beginSize,
|
beginSize: request.beginSize,
|
||||||
endSize: request.endSize,
|
endSize: request.endSize,
|
||||||
queueTag: queueTag == null || queueTag.isEmpty ? null : queueTag,
|
queueTag: queueTag == null || queueTag.isEmpty ? null : queueTag,
|
||||||
batchTag: batchTag == null || batchTag.isEmpty ? null : batchTag,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,12 +216,12 @@ class _RoomGiftSeatFlightOverlayState extends State<RoomGiftSeatFlightOverlay>
|
|||||||
final GlobalKey _overlayKey = GlobalKey();
|
final GlobalKey _overlayKey = GlobalKey();
|
||||||
|
|
||||||
late final AnimationController _controller;
|
late final AnimationController _controller;
|
||||||
Timer? _activeFlightWatchdogTimer;
|
|
||||||
|
|
||||||
RoomGiftSeatFlightRequest? _centerRequest;
|
RoomGiftSeatFlightRequest? _centerRequest;
|
||||||
ImageProvider<Object>? _centerImageProvider;
|
ImageProvider<Object>? _centerImageProvider;
|
||||||
List<_ActiveRoomGiftSeatFlight> _activeFlights =
|
RoomGiftSeatFlightRequest? _activeRequest;
|
||||||
const <_ActiveRoomGiftSeatFlight>[];
|
ImageProvider<Object>? _activeImageProvider;
|
||||||
|
Offset? _activeTargetOffset;
|
||||||
bool _isPlaying = false;
|
bool _isPlaying = false;
|
||||||
int _sessionToken = 0;
|
int _sessionToken = 0;
|
||||||
|
|
||||||
@ -253,7 +248,6 @@ class _RoomGiftSeatFlightOverlayState extends State<RoomGiftSeatFlightOverlay>
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
widget.controller._detach(this);
|
widget.controller._detach(this);
|
||||||
_cancelActiveFlightWatchdog();
|
|
||||||
_controller.dispose();
|
_controller.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@ -289,13 +283,14 @@ class _RoomGiftSeatFlightOverlayState extends State<RoomGiftSeatFlightOverlay>
|
|||||||
void _clear() {
|
void _clear() {
|
||||||
_sessionToken += 1;
|
_sessionToken += 1;
|
||||||
_queue.clear();
|
_queue.clear();
|
||||||
_cancelActiveFlightWatchdog();
|
|
||||||
_controller.stop();
|
_controller.stop();
|
||||||
_controller.reset();
|
_controller.reset();
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
_centerRequest = null;
|
_centerRequest = null;
|
||||||
_centerImageProvider = null;
|
_centerImageProvider = null;
|
||||||
_activeFlights = const <_ActiveRoomGiftSeatFlight>[];
|
_activeRequest = null;
|
||||||
|
_activeImageProvider = null;
|
||||||
|
_activeTargetOffset = null;
|
||||||
_imageProviderCache.clear();
|
_imageProviderCache.clear();
|
||||||
_isPlaying = false;
|
_isPlaying = false;
|
||||||
return;
|
return;
|
||||||
@ -303,17 +298,17 @@ class _RoomGiftSeatFlightOverlayState extends State<RoomGiftSeatFlightOverlay>
|
|||||||
setState(() {
|
setState(() {
|
||||||
_centerRequest = null;
|
_centerRequest = null;
|
||||||
_centerImageProvider = null;
|
_centerImageProvider = null;
|
||||||
_activeFlights = const <_ActiveRoomGiftSeatFlight>[];
|
_activeRequest = null;
|
||||||
|
_activeImageProvider = null;
|
||||||
|
_activeTargetOffset = null;
|
||||||
_imageProviderCache.clear();
|
_imageProviderCache.clear();
|
||||||
_isPlaying = false;
|
_isPlaying = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _hasTrackedRequests(String queueTag) {
|
bool _hasTrackedRequests(String queueTag) {
|
||||||
for (final activeFlight in _activeFlights) {
|
if ((_activeRequest?.queueTag ?? '').trim() == queueTag) {
|
||||||
if ((activeFlight.request.queueTag ?? '').trim() == queueTag) {
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (final queuedRequest in _queue) {
|
for (final queuedRequest in _queue) {
|
||||||
if ((queuedRequest.request.queueTag ?? '').trim() == queueTag) {
|
if ((queuedRequest.request.queueTag ?? '').trim() == queueTag) {
|
||||||
@ -339,10 +334,8 @@ class _RoomGiftSeatFlightOverlayState extends State<RoomGiftSeatFlightOverlay>
|
|||||||
|
|
||||||
int _countTrackedRequests(String queueTag) {
|
int _countTrackedRequests(String queueTag) {
|
||||||
var count = 0;
|
var count = 0;
|
||||||
for (final activeFlight in _activeFlights) {
|
if ((_activeRequest?.queueTag ?? '').trim() == queueTag) {
|
||||||
if ((activeFlight.request.queueTag ?? '').trim() == queueTag) {
|
count += 1;
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (final queuedRequest in _queue) {
|
for (final queuedRequest in _queue) {
|
||||||
if ((queuedRequest.request.queueTag ?? '').trim() == queueTag) {
|
if ((queuedRequest.request.queueTag ?? '').trim() == queueTag) {
|
||||||
@ -368,33 +361,6 @@ class _RoomGiftSeatFlightOverlayState extends State<RoomGiftSeatFlightOverlay>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<_QueuedRoomGiftSeatFlightRequest> _removeNextQueuedBatch() {
|
|
||||||
if (_queue.isEmpty) {
|
|
||||||
return const <_QueuedRoomGiftSeatFlightRequest>[];
|
|
||||||
}
|
|
||||||
|
|
||||||
final firstRequest = _queue.removeFirst();
|
|
||||||
final batchTag = (firstRequest.request.batchTag ?? '').trim();
|
|
||||||
if (batchTag.isEmpty) {
|
|
||||||
return <_QueuedRoomGiftSeatFlightRequest>[firstRequest];
|
|
||||||
}
|
|
||||||
|
|
||||||
final batch = <_QueuedRoomGiftSeatFlightRequest>[firstRequest];
|
|
||||||
while (_queue.isNotEmpty &&
|
|
||||||
(_queue.first.request.batchTag ?? '').trim() == batchTag) {
|
|
||||||
batch.add(_queue.removeFirst());
|
|
||||||
}
|
|
||||||
return batch;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _requeueBatchAtFront(
|
|
||||||
List<_QueuedRoomGiftSeatFlightRequest> queuedBatch,
|
|
||||||
) {
|
|
||||||
for (final queuedRequest in queuedBatch.reversed) {
|
|
||||||
_queue.addFirst(queuedRequest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _scheduleNextAnimation() {
|
void _scheduleNextAnimation() {
|
||||||
if (_isPlaying || _queue.isEmpty || !mounted) {
|
if (_isPlaying || _queue.isEmpty || !mounted) {
|
||||||
return;
|
return;
|
||||||
@ -415,131 +381,78 @@ class _RoomGiftSeatFlightOverlayState extends State<RoomGiftSeatFlightOverlay>
|
|||||||
}
|
}
|
||||||
|
|
||||||
final activeSessionToken = _sessionToken;
|
final activeSessionToken = _sessionToken;
|
||||||
final queuedBatch = _removeNextQueuedBatch();
|
final queuedRequest = _queue.removeFirst();
|
||||||
final activeFlights = <_ActiveRoomGiftSeatFlight>[];
|
final targetOffset = _resolveTargetOffset(
|
||||||
final unresolvedBatch = <_QueuedRoomGiftSeatFlightRequest>[];
|
queuedRequest.request.targetUserId,
|
||||||
|
|
||||||
for (final queuedRequest in queuedBatch) {
|
|
||||||
final targetOffset = _resolveTargetOffset(
|
|
||||||
queuedRequest.request.targetUserId,
|
|
||||||
);
|
|
||||||
if (targetOffset == null) {
|
|
||||||
unresolvedBatch.add(queuedRequest);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final imageProvider = _resolveImageProvider(
|
|
||||||
queuedRequest.request.imagePath,
|
|
||||||
request: queuedRequest.request,
|
|
||||||
);
|
|
||||||
activeFlights.add(
|
|
||||||
_ActiveRoomGiftSeatFlight(
|
|
||||||
request: queuedRequest.request,
|
|
||||||
imageProvider: imageProvider,
|
|
||||||
targetOffset: targetOffset,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final shouldRetryBatch = unresolvedBatch.any(
|
|
||||||
(queuedRequest) => queuedRequest.retryCount < 6,
|
|
||||||
);
|
);
|
||||||
if (shouldRetryBatch) {
|
if (targetOffset == null) {
|
||||||
final retryBatch =
|
if (queuedRequest.retryCount < 6) {
|
||||||
queuedBatch
|
|
||||||
.map(
|
|
||||||
(queuedRequest) => queuedRequest.copyWith(
|
|
||||||
retryCount: queuedRequest.retryCount + 1,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
if (retryBatch.isNotEmpty) {
|
|
||||||
Future.delayed(const Duration(milliseconds: 120), () {
|
Future.delayed(const Duration(milliseconds: 120), () {
|
||||||
if (!mounted || activeSessionToken != _sessionToken) {
|
if (!mounted || activeSessionToken != _sessionToken) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_requeueBatchAtFront(retryBatch);
|
_queue.addFirst(
|
||||||
|
queuedRequest.copyWith(retryCount: queuedRequest.retryCount + 1),
|
||||||
|
);
|
||||||
_scheduleNextAnimation();
|
_scheduleNextAnimation();
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
_syncIdleCenterVisual();
|
||||||
|
_scheduleNextAnimation();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeFlights.isEmpty) {
|
final imageProvider = _resolveImageProvider(
|
||||||
_syncIdleCenterVisual();
|
queuedRequest.request.imagePath,
|
||||||
_scheduleNextAnimation();
|
);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isPlaying = true;
|
_isPlaying = true;
|
||||||
final centerFlight = activeFlights.first;
|
_controller.duration = queuedRequest.request.flightDuration;
|
||||||
_controller.duration = centerFlight.request.flightDuration;
|
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_centerRequest = centerFlight.request;
|
_centerRequest = queuedRequest.request;
|
||||||
_centerImageProvider = centerFlight.imageProvider;
|
_centerImageProvider = imageProvider;
|
||||||
_activeFlights = activeFlights;
|
_activeRequest = queuedRequest.request;
|
||||||
|
_activeTargetOffset = targetOffset;
|
||||||
|
_activeImageProvider = imageProvider;
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Future.wait(
|
await precacheImage(
|
||||||
activeFlights.map(
|
imageProvider,
|
||||||
(flight) => precacheImage(
|
context,
|
||||||
flight.imageProvider,
|
).timeout(const Duration(milliseconds: 250));
|
||||||
context,
|
|
||||||
).timeout(const Duration(milliseconds: 250)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
if (!mounted ||
|
if (!mounted ||
|
||||||
activeSessionToken != _sessionToken ||
|
activeSessionToken != _sessionToken ||
|
||||||
!identical(_activeFlights, activeFlights)) {
|
_activeRequest != queuedRequest.request) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_controller.reset();
|
_controller.reset();
|
||||||
_armActiveFlightWatchdog(centerFlight.request.flightDuration);
|
|
||||||
_controller.forward();
|
_controller.forward();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _finishCurrentAnimation() {
|
void _finishCurrentAnimation() {
|
||||||
_cancelActiveFlightWatchdog();
|
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
_activeFlights = const <_ActiveRoomGiftSeatFlight>[];
|
_activeRequest = null;
|
||||||
|
_activeImageProvider = null;
|
||||||
|
_activeTargetOffset = null;
|
||||||
_isPlaying = false;
|
_isPlaying = false;
|
||||||
_syncIdleCenterVisual();
|
_syncIdleCenterVisual();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_activeFlights = const <_ActiveRoomGiftSeatFlight>[];
|
_activeRequest = null;
|
||||||
|
_activeImageProvider = null;
|
||||||
|
_activeTargetOffset = null;
|
||||||
_isPlaying = false;
|
_isPlaying = false;
|
||||||
});
|
});
|
||||||
_syncIdleCenterVisual();
|
_syncIdleCenterVisual();
|
||||||
_scheduleNextAnimation();
|
_scheduleNextAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _armActiveFlightWatchdog(Duration flightDuration) {
|
|
||||||
_cancelActiveFlightWatchdog();
|
|
||||||
final timeout = flightDuration + const Duration(milliseconds: 900);
|
|
||||||
_activeFlightWatchdogTimer = Timer(timeout, () {
|
|
||||||
_activeFlightWatchdogTimer = null;
|
|
||||||
if (!_isPlaying || _activeFlights.isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
debugPrint(
|
|
||||||
'[GiftSeatFlight] watchdog timeout active=${_activeFlights.length}',
|
|
||||||
);
|
|
||||||
_controller.stop();
|
|
||||||
_finishCurrentAnimation();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _cancelActiveFlightWatchdog() {
|
|
||||||
_activeFlightWatchdogTimer?.cancel();
|
|
||||||
_activeFlightWatchdogTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _ensureCenterVisual(RoomGiftSeatFlightRequest request) {
|
void _ensureCenterVisual(RoomGiftSeatFlightRequest request) {
|
||||||
if (_centerRequest != null && _centerImageProvider != null) {
|
if (_centerRequest != null && _centerImageProvider != null) {
|
||||||
return;
|
return;
|
||||||
@ -560,7 +473,7 @@ class _RoomGiftSeatFlightOverlayState extends State<RoomGiftSeatFlightOverlay>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _syncIdleCenterVisual() {
|
void _syncIdleCenterVisual() {
|
||||||
if (_isPlaying || _activeFlights.isNotEmpty) {
|
if (_isPlaying || _activeRequest != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -619,10 +532,7 @@ class _RoomGiftSeatFlightOverlayState extends State<RoomGiftSeatFlightOverlay>
|
|||||||
String imagePath, {
|
String imagePath, {
|
||||||
RoomGiftSeatFlightRequest? request,
|
RoomGiftSeatFlightRequest? request,
|
||||||
}) {
|
}) {
|
||||||
final currentRequest =
|
final currentRequest = request ?? _activeRequest ?? _centerRequest;
|
||||||
request ??
|
|
||||||
(_activeFlights.isNotEmpty ? _activeFlights.first.request : null) ??
|
|
||||||
_centerRequest;
|
|
||||||
final logicalSize =
|
final logicalSize =
|
||||||
currentRequest == null
|
currentRequest == null
|
||||||
? null
|
? null
|
||||||
@ -651,17 +561,16 @@ class _RoomGiftSeatFlightOverlayState extends State<RoomGiftSeatFlightOverlay>
|
|||||||
key: _overlayKey,
|
key: _overlayKey,
|
||||||
child:
|
child:
|
||||||
(_centerRequest == null || _centerImageProvider == null) &&
|
(_centerRequest == null || _centerImageProvider == null) &&
|
||||||
_activeFlights.isEmpty
|
(_activeRequest == null ||
|
||||||
|
_activeImageProvider == null ||
|
||||||
|
_activeTargetOffset == null)
|
||||||
? const SizedBox.shrink()
|
? const SizedBox.shrink()
|
||||||
: AnimatedBuilder(
|
: AnimatedBuilder(
|
||||||
animation: _controller,
|
animation: _controller,
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
final center = overlaySize.center(Offset.zero);
|
final center = overlaySize.center(Offset.zero);
|
||||||
final currentCenterRequest =
|
final currentCenterRequest =
|
||||||
_centerRequest ??
|
_centerRequest ?? _activeRequest;
|
||||||
(_activeFlights.isNotEmpty
|
|
||||||
? _activeFlights.first.request
|
|
||||||
: null);
|
|
||||||
final children = <Widget>[];
|
final children = <Widget>[];
|
||||||
|
|
||||||
if (currentCenterRequest != null &&
|
if (currentCenterRequest != null &&
|
||||||
@ -676,38 +585,40 @@ class _RoomGiftSeatFlightOverlayState extends State<RoomGiftSeatFlightOverlay>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_activeFlights.isNotEmpty) {
|
final activeRequest = _activeRequest;
|
||||||
|
final activeImageProvider = _activeImageProvider;
|
||||||
|
final activeTargetOffset = _activeTargetOffset;
|
||||||
|
if (activeRequest != null &&
|
||||||
|
activeImageProvider != null &&
|
||||||
|
activeTargetOffset != null) {
|
||||||
final travelProgress = Curves.easeInOutCubic.transform(
|
final travelProgress = Curves.easeInOutCubic.transform(
|
||||||
_controller.value.clamp(0.0, 1.0),
|
_controller.value.clamp(0.0, 1.0),
|
||||||
);
|
);
|
||||||
|
final nodeCenter =
|
||||||
|
Offset.lerp(
|
||||||
|
center,
|
||||||
|
activeTargetOffset,
|
||||||
|
travelProgress,
|
||||||
|
)!;
|
||||||
|
final nodeSize =
|
||||||
|
lerpDouble(
|
||||||
|
activeRequest.beginSize,
|
||||||
|
activeRequest.endSize,
|
||||||
|
travelProgress,
|
||||||
|
)!;
|
||||||
final fadeOut =
|
final fadeOut =
|
||||||
1 -
|
1 -
|
||||||
Curves.easeOut.transform(
|
Curves.easeOut.transform(
|
||||||
((travelProgress - 0.82) / 0.18).clamp(0.0, 1.0),
|
((travelProgress - 0.82) / 0.18).clamp(0.0, 1.0),
|
||||||
);
|
);
|
||||||
for (final activeFlight in _activeFlights) {
|
children.add(
|
||||||
final activeRequest = activeFlight.request;
|
_buildGiftNode(
|
||||||
final nodeCenter =
|
imageProvider: activeImageProvider,
|
||||||
Offset.lerp(
|
center: nodeCenter,
|
||||||
center,
|
size: nodeSize,
|
||||||
activeFlight.targetOffset,
|
opacity: fadeOut,
|
||||||
travelProgress,
|
),
|
||||||
)!;
|
);
|
||||||
final nodeSize =
|
|
||||||
lerpDouble(
|
|
||||||
activeRequest.beginSize,
|
|
||||||
activeRequest.endSize,
|
|
||||||
travelProgress,
|
|
||||||
)!;
|
|
||||||
children.add(
|
|
||||||
_buildGiftNode(
|
|
||||||
imageProvider: activeFlight.imageProvider,
|
|
||||||
center: nodeCenter,
|
|
||||||
size: nodeSize,
|
|
||||||
opacity: fadeOut,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return RepaintBoundary(
|
return RepaintBoundary(
|
||||||
@ -766,18 +677,6 @@ class _RoomGiftSeatFlightOverlayState extends State<RoomGiftSeatFlightOverlay>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ActiveRoomGiftSeatFlight {
|
|
||||||
const _ActiveRoomGiftSeatFlight({
|
|
||||||
required this.request,
|
|
||||||
required this.imageProvider,
|
|
||||||
required this.targetOffset,
|
|
||||||
});
|
|
||||||
|
|
||||||
final RoomGiftSeatFlightRequest request;
|
|
||||||
final ImageProvider<Object> imageProvider;
|
|
||||||
final Offset targetOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _QueuedRoomGiftSeatFlightRequest {
|
class _QueuedRoomGiftSeatFlightRequest {
|
||||||
const _QueuedRoomGiftSeatFlightRequest({
|
const _QueuedRoomGiftSeatFlightRequest({
|
||||||
required this.request,
|
required this.request,
|
||||||
|
|||||||
@ -1,401 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:ui' as ui;
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_debouncer/flutter_debouncer.dart';
|
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
||||||
import 'package:flutter_svga/flutter_svga.dart';
|
|
||||||
import 'package:yumi/app_localizations.dart';
|
|
||||||
import 'package:yumi/main.dart';
|
|
||||||
import 'package:yumi/shared/data_sources/models/message/sc_floating_message.dart';
|
|
||||||
import 'package:yumi/shared/tools/sc_network_image_utils.dart';
|
|
||||||
import 'package:yumi/shared/tools/sc_room_utils.dart';
|
|
||||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
|
||||||
import 'package:yumi/ui_kit/widgets/svga/sc_network_svga_widget.dart';
|
|
||||||
|
|
||||||
class FloatingVipEntryScreenWidget extends StatefulWidget {
|
|
||||||
const FloatingVipEntryScreenWidget({
|
|
||||||
super.key,
|
|
||||||
required this.message,
|
|
||||||
required this.onAnimationCompleted,
|
|
||||||
});
|
|
||||||
|
|
||||||
final SCFloatingMessage message;
|
|
||||||
final VoidCallback onAnimationCompleted;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<FloatingVipEntryScreenWidget> createState() =>
|
|
||||||
_FloatingVipEntryScreenWidgetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _FloatingVipEntryScreenWidgetState
|
|
||||||
extends State<FloatingVipEntryScreenWidget>
|
|
||||||
with TickerProviderStateMixin {
|
|
||||||
static const double _bannerWidth = 350;
|
|
||||||
static const double _bannerHeight = 84;
|
|
||||||
static const String _avatarDynamicKey = "avatar";
|
|
||||||
static const String _nicknameDynamicKey = "nickname";
|
|
||||||
|
|
||||||
late final AnimationController _controller;
|
|
||||||
late final Animation<Offset> _offsetAnimation;
|
|
||||||
late final AnimationController _swipeController;
|
|
||||||
late final Animation<Offset> _swipeAnimation;
|
|
||||||
final Debouncer _debouncer = Debouncer();
|
|
||||||
|
|
||||||
bool _isSwipeAnimating = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_controller = AnimationController(
|
|
||||||
duration: const Duration(seconds: 5),
|
|
||||||
vsync: this,
|
|
||||||
);
|
|
||||||
_swipeController = AnimationController(
|
|
||||||
duration: const Duration(milliseconds: 550),
|
|
||||||
vsync: this,
|
|
||||||
);
|
|
||||||
_offsetAnimation = Tween<Offset>(
|
|
||||||
begin: const Offset(1, 0),
|
|
||||||
end: const Offset(-1, 0),
|
|
||||||
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
|
|
||||||
_swipeAnimation = Tween<Offset>(
|
|
||||||
begin: Offset.zero,
|
|
||||||
end: const Offset(-1.5, 0),
|
|
||||||
).animate(CurvedAnimation(parent: _swipeController, curve: Curves.easeIn));
|
|
||||||
|
|
||||||
_controller.addStatusListener((status) {
|
|
||||||
if (status == AnimationStatus.completed && !_isSwipeAnimating) {
|
|
||||||
widget.onAnimationCompleted();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
_swipeController.addStatusListener((status) {
|
|
||||||
if (status == AnimationStatus.completed) {
|
|
||||||
widget.onAnimationCompleted();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
if (mounted) {
|
|
||||||
_controller.forward();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_controller.dispose();
|
|
||||||
_swipeController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: _goRoom,
|
|
||||||
onHorizontalDragEnd: _handleHorizontalDragEnd,
|
|
||||||
child: SlideTransition(
|
|
||||||
position: _isSwipeAnimating ? _swipeAnimation : _offsetAnimation,
|
|
||||||
child: SizedBox(
|
|
||||||
width: _bannerWidth.w,
|
|
||||||
height: _bannerHeight.w,
|
|
||||||
child: _buildBanner(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildBanner() {
|
|
||||||
final url = widget.message.giftUrl?.trim();
|
|
||||||
if (url != null && url.isNotEmpty) {
|
|
||||||
if (SCNetworkSvgaWidget.isSvga(url)) {
|
|
||||||
return SCNetworkSvgaWidget(
|
|
||||||
resource: url,
|
|
||||||
width: _bannerWidth.w,
|
|
||||||
height: _bannerHeight.w,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
dynamicIdentity: [
|
|
||||||
url,
|
|
||||||
widget.message.userAvatarUrl ?? "",
|
|
||||||
widget.message.userName ?? "",
|
|
||||||
].join("|"),
|
|
||||||
movieConfigurer: _configureVipEntrySvga,
|
|
||||||
fallback: _buildFallbackBanner(widget.message.fallbackUrl),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return _buildFallbackBanner(url);
|
|
||||||
}
|
|
||||||
return _buildFallbackBanner(widget.message.fallbackUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildFallbackBanner(String? imageUrl) {
|
|
||||||
return Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
|
||||||
_buildImageBackground(imageUrl),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsetsDirectional.only(start: 72.w, end: 20.w),
|
|
||||||
child: Row(
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
children: [
|
|
||||||
head(
|
|
||||||
url: widget.message.userAvatarUrl ?? "",
|
|
||||||
width: 50.w,
|
|
||||||
showDefault: true,
|
|
||||||
),
|
|
||||||
SizedBox(width: 10.w),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
widget.message.userName ?? "",
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 14.sp,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
decoration: TextDecoration.none,
|
|
||||||
height: 1.1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 3.w),
|
|
||||||
Text(
|
|
||||||
SCAppLocalizations.of(context)!.enterTheRoom,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white.withValues(alpha: 0.92),
|
|
||||||
fontSize: 12.sp,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
decoration: TextDecoration.none,
|
|
||||||
height: 1.1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildImageBackground(String? url) {
|
|
||||||
final imageUrl = url?.trim();
|
|
||||||
if (imageUrl == null || imageUrl.isEmpty) {
|
|
||||||
return _buildFallbackBackground();
|
|
||||||
}
|
|
||||||
return ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(_bannerHeight.w / 2),
|
|
||||||
child: netImage(
|
|
||||||
url: imageUrl,
|
|
||||||
width: _bannerWidth.w,
|
|
||||||
height: _bannerHeight.w,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
noDefaultImg: true,
|
|
||||||
errorWidget: _buildFallbackBackground(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildFallbackBackground() {
|
|
||||||
return DecoratedBox(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(_bannerHeight.w / 2),
|
|
||||||
gradient: const LinearGradient(
|
|
||||||
colors: [Color(0xFF6573FF), Color(0xFF8D5CFF)],
|
|
||||||
),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withValues(alpha: 0.18),
|
|
||||||
blurRadius: 10.w,
|
|
||||||
offset: Offset(0, 4.w),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _configureVipEntrySvga(MovieEntity movieEntity) async {
|
|
||||||
_logMissingDynamicKeys(movieEntity);
|
|
||||||
|
|
||||||
final avatarUrl = (widget.message.userAvatarUrl ?? "").trim();
|
|
||||||
if (avatarUrl.isNotEmpty) {
|
|
||||||
try {
|
|
||||||
final avatarImage = await _loadCircularUiImage(
|
|
||||||
avatarUrl,
|
|
||||||
logicalSize: 96,
|
|
||||||
);
|
|
||||||
if (avatarImage != null) {
|
|
||||||
movieEntity.dynamicItem.setImage(avatarImage, _avatarDynamicKey);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
debugPrint("[VIP][RoomFloat][SVGA] avatar sync failed: $error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final nickname = (widget.message.userName ?? "").trim();
|
|
||||||
if (nickname.isNotEmpty) {
|
|
||||||
try {
|
|
||||||
movieEntity.dynamicItem.setImage(
|
|
||||||
await _buildTransparentImage(),
|
|
||||||
_nicknameDynamicKey,
|
|
||||||
);
|
|
||||||
movieEntity.dynamicItem.setText(
|
|
||||||
TextPainter(
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
maxLines: 1,
|
|
||||||
ellipsis: "…",
|
|
||||||
text: TextSpan(
|
|
||||||
text: nickname,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 26,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
height: 1,
|
|
||||||
decoration: TextDecoration.none,
|
|
||||||
shadows: [
|
|
||||||
Shadow(
|
|
||||||
color: Color(0x99000000),
|
|
||||||
blurRadius: 2,
|
|
||||||
offset: Offset(0, 1),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)..layout(maxWidth: 220),
|
|
||||||
_nicknameDynamicKey,
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
debugPrint("[VIP][RoomFloat][SVGA] nickname sync failed: $error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _logMissingDynamicKeys(MovieEntity movieEntity) {
|
|
||||||
final keys =
|
|
||||||
movieEntity.sprites
|
|
||||||
.map((sprite) => sprite.imageKey)
|
|
||||||
.where((key) => key.isNotEmpty)
|
|
||||||
.toSet();
|
|
||||||
final missing = <String>[
|
|
||||||
if (!keys.contains(_avatarDynamicKey)) _avatarDynamicKey,
|
|
||||||
if (!keys.contains(_nicknameDynamicKey)) _nicknameDynamicKey,
|
|
||||||
];
|
|
||||||
if (missing.isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
debugPrint(
|
|
||||||
"[VIP][RoomFloat][SVGA] dynamic keys missing=${missing.join(",")} "
|
|
||||||
"available=${keys.take(30).join(",")}",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ui.Image?> _loadCircularUiImage(
|
|
||||||
String resource, {
|
|
||||||
required int logicalSize,
|
|
||||||
}) async {
|
|
||||||
final image = await _loadUiImage(
|
|
||||||
resource,
|
|
||||||
logicalWidth: logicalSize.toDouble(),
|
|
||||||
logicalHeight: logicalSize.toDouble(),
|
|
||||||
);
|
|
||||||
if (image == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final size = logicalSize.toDouble();
|
|
||||||
final recorder = ui.PictureRecorder();
|
|
||||||
final canvas = Canvas(recorder);
|
|
||||||
final rect = Rect.fromLTWH(0, 0, size, size);
|
|
||||||
canvas.clipPath(Path()..addOval(rect));
|
|
||||||
paintImage(
|
|
||||||
canvas: canvas,
|
|
||||||
rect: rect,
|
|
||||||
image: image,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
filterQuality: FilterQuality.medium,
|
|
||||||
);
|
|
||||||
return recorder.endRecording().toImage(logicalSize, logicalSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ui.Image?> _loadUiImage(
|
|
||||||
String resource, {
|
|
||||||
double? logicalWidth,
|
|
||||||
double? logicalHeight,
|
|
||||||
}) async {
|
|
||||||
final target = resource.trim();
|
|
||||||
if (target.isEmpty) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final provider = buildCachedImageProvider(
|
|
||||||
target,
|
|
||||||
logicalWidth: logicalWidth,
|
|
||||||
logicalHeight: logicalHeight,
|
|
||||||
);
|
|
||||||
final stream = provider.resolve(ImageConfiguration.empty);
|
|
||||||
final completer = Completer<ui.Image?>();
|
|
||||||
late final ImageStreamListener listener;
|
|
||||||
listener = ImageStreamListener(
|
|
||||||
(imageInfo, synchronousCall) {
|
|
||||||
if (!completer.isCompleted) {
|
|
||||||
completer.complete(imageInfo.image);
|
|
||||||
}
|
|
||||||
stream.removeListener(listener);
|
|
||||||
},
|
|
||||||
onError: (Object error, StackTrace? stackTrace) {
|
|
||||||
if (!completer.isCompleted) {
|
|
||||||
completer.complete(null);
|
|
||||||
}
|
|
||||||
stream.removeListener(listener);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
stream.addListener(listener);
|
|
||||||
return completer.future.timeout(
|
|
||||||
const Duration(seconds: 2),
|
|
||||||
onTimeout: () {
|
|
||||||
stream.removeListener(listener);
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ui.Image> _buildTransparentImage() {
|
|
||||||
final recorder = ui.PictureRecorder();
|
|
||||||
final canvas = Canvas(recorder);
|
|
||||||
canvas.drawColor(Colors.transparent, BlendMode.clear);
|
|
||||||
return recorder.endRecording().toImage(1, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _goRoom() {
|
|
||||||
_debouncer.debounce(
|
|
||||||
duration: const Duration(milliseconds: 350),
|
|
||||||
onDebounce: () {
|
|
||||||
final roomId = widget.message.roomId ?? "";
|
|
||||||
if (roomId.isEmpty) return;
|
|
||||||
SCRoomUtils.goRoom(
|
|
||||||
roomId,
|
|
||||||
navigatorKey.currentState!.context,
|
|
||||||
fromFloting: true,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleHorizontalDragEnd(DragEndDetails details) {
|
|
||||||
final textDirection = Directionality.of(context);
|
|
||||||
final velocity =
|
|
||||||
textDirection == TextDirection.rtl
|
|
||||||
? -(details.primaryVelocity ?? 0)
|
|
||||||
: (details.primaryVelocity ?? 0);
|
|
||||||
if (velocity >= 0 || _isSwipeAnimating) return;
|
|
||||||
setState(() => _isSwipeAnimating = true);
|
|
||||||
_controller.stop();
|
|
||||||
_swipeController.reset();
|
|
||||||
_swipeController.forward();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -22,53 +22,25 @@ import '../../../services/audio/rtm_manager.dart';
|
|||||||
import '../../components/sc_debounce_widget.dart';
|
import '../../components/sc_debounce_widget.dart';
|
||||||
|
|
||||||
class RoomBottomWidget extends StatefulWidget {
|
class RoomBottomWidget extends StatefulWidget {
|
||||||
const RoomBottomWidget({super.key, this.showGiftComboButton = true});
|
const RoomBottomWidget({super.key});
|
||||||
|
|
||||||
final bool showGiftComboButton;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<RoomBottomWidget> createState() => _RoomBottomWidgetState();
|
State<RoomBottomWidget> createState() => _RoomBottomWidgetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoomGiftComboFloatingLayer extends StatelessWidget {
|
|
||||||
const RoomGiftComboFloatingLayer({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Selector<RtcProvider, bool>(
|
|
||||||
selector: (context, provider) => _shouldShowRoomBottomMic(provider),
|
|
||||||
builder: (context, showMic, child) {
|
|
||||||
return LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
final inputWidth = _resolveRoomBottomInputWidth(
|
|
||||||
maxWidth: constraints.maxWidth,
|
|
||||||
showMic: showMic,
|
|
||||||
);
|
|
||||||
final giftCenterX = _resolveRoomGiftCenterX(
|
|
||||||
maxWidth: constraints.maxWidth,
|
|
||||||
inputWidth: inputWidth,
|
|
||||||
showMic: showMic,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Stack(
|
|
||||||
clipBehavior: Clip.none,
|
|
||||||
children: [
|
|
||||||
Positioned(
|
|
||||||
left: giftCenterX - (_floatingButtonWidth.w / 2),
|
|
||||||
bottom: _floatingButtonBottomOffset.w,
|
|
||||||
child: const RoomGiftComboFloatingButton(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RoomBottomWidgetState extends State<RoomBottomWidget> {
|
class _RoomBottomWidgetState extends State<RoomBottomWidget> {
|
||||||
int roomMenuStime1 = 0;
|
int roomMenuStime1 = 0;
|
||||||
|
static const double _bottomBarHeight = 72;
|
||||||
|
static const double _floatingButtonHostHeight = 122;
|
||||||
|
static const double _floatingButtonWidth =
|
||||||
|
RoomGiftComboFloatingButton.hostSize;
|
||||||
|
static const double _floatingButtonBottomOffset = 54;
|
||||||
|
static const double _giftActionWidth = 52;
|
||||||
|
static const double _circleActionWidth = 46;
|
||||||
|
static const double _emojiActionWidth = 38;
|
||||||
|
static const double _chatEmojiGap = 8;
|
||||||
|
static const double _compactGap = 14;
|
||||||
|
static const double _horizontalPadding = 16;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@ -83,17 +55,17 @@ class _RoomBottomWidgetState extends State<RoomBottomWidget> {
|
|||||||
child: Selector<RtcProvider, _RoomBottomSnapshot>(
|
child: Selector<RtcProvider, _RoomBottomSnapshot>(
|
||||||
selector:
|
selector:
|
||||||
(context, provider) => _RoomBottomSnapshot(
|
(context, provider) => _RoomBottomSnapshot(
|
||||||
showMic: _shouldShowRoomBottomMic(provider),
|
showMic: _shouldShowMic(provider),
|
||||||
isMic: provider.isMic,
|
isMic: provider.isMic,
|
||||||
),
|
),
|
||||||
builder: (context, bottomSnapshot, child) {
|
builder: (context, bottomSnapshot, child) {
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
final inputWidth = _resolveRoomBottomInputWidth(
|
final inputWidth = _resolveInputWidth(
|
||||||
maxWidth: constraints.maxWidth,
|
maxWidth: constraints.maxWidth,
|
||||||
showMic: bottomSnapshot.showMic,
|
showMic: bottomSnapshot.showMic,
|
||||||
);
|
);
|
||||||
final giftCenterX = _resolveRoomGiftCenterX(
|
final giftCenterX = _resolveGiftCenterX(
|
||||||
maxWidth: constraints.maxWidth,
|
maxWidth: constraints.maxWidth,
|
||||||
inputWidth: inputWidth,
|
inputWidth: inputWidth,
|
||||||
showMic: bottomSnapshot.showMic,
|
showMic: bottomSnapshot.showMic,
|
||||||
@ -102,12 +74,11 @@ class _RoomBottomWidgetState extends State<RoomBottomWidget> {
|
|||||||
return Stack(
|
return Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
if (widget.showGiftComboButton)
|
Positioned(
|
||||||
Positioned(
|
left: giftCenterX - (_floatingButtonWidth.w / 2),
|
||||||
left: giftCenterX - (_floatingButtonWidth.w / 2),
|
bottom: _floatingButtonBottomOffset.w,
|
||||||
bottom: _floatingButtonBottomOffset.w,
|
child: const RoomGiftComboFloatingButton(),
|
||||||
child: const RoomGiftComboFloatingButton(),
|
),
|
||||||
),
|
|
||||||
Positioned(
|
Positioned(
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
@ -228,6 +199,58 @@ class _RoomBottomWidgetState extends State<RoomBottomWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double _resolveInputWidth({required double maxWidth, required bool showMic}) {
|
||||||
|
final contentWidth = maxWidth - (_horizontalPadding.w * 2);
|
||||||
|
final actionWidth =
|
||||||
|
_emojiActionWidth.w +
|
||||||
|
_chatEmojiGap.w +
|
||||||
|
_giftActionWidth.w +
|
||||||
|
_circleActionWidth.w * (showMic ? 3 : 2) +
|
||||||
|
(showMic ? 16.w : _compactGap.w * 2);
|
||||||
|
final availableWidth = contentWidth - actionWidth;
|
||||||
|
return availableWidth.clamp(82.w, 112.w).toDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
double _resolveGiftCenterX({
|
||||||
|
required double maxWidth,
|
||||||
|
required double inputWidth,
|
||||||
|
required bool showMic,
|
||||||
|
}) {
|
||||||
|
final contentWidth = maxWidth - (_horizontalPadding.w * 2);
|
||||||
|
if (showMic) {
|
||||||
|
final occupiedWidth =
|
||||||
|
inputWidth +
|
||||||
|
_chatEmojiGap.w +
|
||||||
|
_emojiActionWidth.w +
|
||||||
|
_giftActionWidth.w +
|
||||||
|
_circleActionWidth.w * 3;
|
||||||
|
final gapCount = 4;
|
||||||
|
final gap = ((contentWidth - occupiedWidth) / gapCount).clamp(
|
||||||
|
0.0,
|
||||||
|
double.infinity,
|
||||||
|
);
|
||||||
|
return _horizontalPadding.w +
|
||||||
|
inputWidth +
|
||||||
|
_chatEmojiGap.w +
|
||||||
|
_emojiActionWidth.w +
|
||||||
|
gap +
|
||||||
|
(_giftActionWidth.w / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
final fixedWidth =
|
||||||
|
inputWidth +
|
||||||
|
_chatEmojiGap.w +
|
||||||
|
_emojiActionWidth.w +
|
||||||
|
_giftActionWidth.w +
|
||||||
|
_circleActionWidth.w * 2 +
|
||||||
|
_compactGap.w * 2;
|
||||||
|
final spacerWidth = (contentWidth - fixedWidth).clamp(0.0, double.infinity);
|
||||||
|
return _horizontalPadding.w +
|
||||||
|
inputWidth +
|
||||||
|
spacerWidth +
|
||||||
|
(_giftActionWidth.w / 2);
|
||||||
|
}
|
||||||
|
|
||||||
void _showGiftPanel() {
|
void _showGiftPanel() {
|
||||||
SmartDialog.show(
|
SmartDialog.show(
|
||||||
tag: "showGiftControl",
|
tag: "showGiftControl",
|
||||||
@ -331,76 +354,10 @@ class _RoomBottomWidgetState extends State<RoomBottomWidget> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const double _bottomBarHeight = 72;
|
bool _shouldShowMic(RtcProvider provider) {
|
||||||
const double _floatingButtonHostHeight = 122;
|
return provider.isOnMai();
|
||||||
const double _floatingButtonWidth = RoomGiftComboFloatingButton.hostSize;
|
|
||||||
const double _floatingButtonBottomOffset = 54;
|
|
||||||
const double _giftActionWidth = 52;
|
|
||||||
const double _circleActionWidth = 46;
|
|
||||||
const double _emojiActionWidth = 38;
|
|
||||||
const double _chatEmojiGap = 8;
|
|
||||||
const double _compactGap = 14;
|
|
||||||
const double _horizontalPadding = 16;
|
|
||||||
|
|
||||||
double _resolveRoomBottomInputWidth({
|
|
||||||
required double maxWidth,
|
|
||||||
required bool showMic,
|
|
||||||
}) {
|
|
||||||
final contentWidth = maxWidth - (_horizontalPadding.w * 2);
|
|
||||||
final actionWidth =
|
|
||||||
_emojiActionWidth.w +
|
|
||||||
_chatEmojiGap.w +
|
|
||||||
_giftActionWidth.w +
|
|
||||||
_circleActionWidth.w * (showMic ? 3 : 2) +
|
|
||||||
(showMic ? 16.w : _compactGap.w * 2);
|
|
||||||
final availableWidth = contentWidth - actionWidth;
|
|
||||||
return availableWidth.clamp(82.w, 112.w).toDouble();
|
|
||||||
}
|
|
||||||
|
|
||||||
double _resolveRoomGiftCenterX({
|
|
||||||
required double maxWidth,
|
|
||||||
required double inputWidth,
|
|
||||||
required bool showMic,
|
|
||||||
}) {
|
|
||||||
final contentWidth = maxWidth - (_horizontalPadding.w * 2);
|
|
||||||
if (showMic) {
|
|
||||||
final occupiedWidth =
|
|
||||||
inputWidth +
|
|
||||||
_chatEmojiGap.w +
|
|
||||||
_emojiActionWidth.w +
|
|
||||||
_giftActionWidth.w +
|
|
||||||
_circleActionWidth.w * 3;
|
|
||||||
final gapCount = 4;
|
|
||||||
final gap = ((contentWidth - occupiedWidth) / gapCount).clamp(
|
|
||||||
0.0,
|
|
||||||
double.infinity,
|
|
||||||
);
|
|
||||||
return _horizontalPadding.w +
|
|
||||||
inputWidth +
|
|
||||||
_chatEmojiGap.w +
|
|
||||||
_emojiActionWidth.w +
|
|
||||||
gap +
|
|
||||||
(_giftActionWidth.w / 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final fixedWidth =
|
|
||||||
inputWidth +
|
|
||||||
_chatEmojiGap.w +
|
|
||||||
_emojiActionWidth.w +
|
|
||||||
_giftActionWidth.w +
|
|
||||||
_circleActionWidth.w * 2 +
|
|
||||||
_compactGap.w * 2;
|
|
||||||
final spacerWidth = (contentWidth - fixedWidth).clamp(0.0, double.infinity);
|
|
||||||
return _horizontalPadding.w +
|
|
||||||
inputWidth +
|
|
||||||
spacerWidth +
|
|
||||||
(_giftActionWidth.w / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _shouldShowRoomBottomMic(RtcProvider provider) {
|
|
||||||
return provider.isOnMai();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RoomBottomSnapshot {
|
class _RoomBottomSnapshot {
|
||||||
|
|||||||
@ -31,45 +31,32 @@ class RoomMsgInput extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _RoomMsgInputState extends State<RoomMsgInput> {
|
class _RoomMsgInputState extends State<RoomMsgInput> {
|
||||||
static const List<_RoomEmojiCategory> _roomEmojiCategories = [
|
static const List<String> _roomEmojiAssets = [
|
||||||
_RoomEmojiCategory(
|
"sc_images/room/emoji/fluent_emoji_01.webp",
|
||||||
id: "fluent",
|
"sc_images/room/emoji/fluent_emoji_02.webp",
|
||||||
iconAsset: "sc_images/room/emoji/fluent_emoji_01.webp",
|
"sc_images/room/emoji/fluent_emoji_03.webp",
|
||||||
assets: [
|
"sc_images/room/emoji/fluent_emoji_04.webp",
|
||||||
"sc_images/room/emoji/fluent_emoji_01.webp",
|
"sc_images/room/emoji/fluent_emoji_05.webp",
|
||||||
"sc_images/room/emoji/fluent_emoji_02.webp",
|
"sc_images/room/emoji/fluent_emoji_06.webp",
|
||||||
"sc_images/room/emoji/fluent_emoji_03.webp",
|
"sc_images/room/emoji/fluent_emoji_07.webp",
|
||||||
"sc_images/room/emoji/fluent_emoji_04.webp",
|
"sc_images/room/emoji/fluent_emoji_08.webp",
|
||||||
"sc_images/room/emoji/fluent_emoji_05.webp",
|
"sc_images/room/emoji/fluent_emoji_09.webp",
|
||||||
"sc_images/room/emoji/fluent_emoji_06.webp",
|
"sc_images/room/emoji/fluent_emoji_10.webp",
|
||||||
"sc_images/room/emoji/fluent_emoji_07.webp",
|
"sc_images/room/emoji/monkey_1.gif",
|
||||||
"sc_images/room/emoji/fluent_emoji_08.webp",
|
"sc_images/room/emoji/monkey_2.gif",
|
||||||
"sc_images/room/emoji/fluent_emoji_09.webp",
|
"sc_images/room/emoji/monkey_3.gif",
|
||||||
"sc_images/room/emoji/fluent_emoji_10.webp",
|
"sc_images/room/emoji/monkey_4.gif",
|
||||||
],
|
"sc_images/room/emoji/monkey_5.gif",
|
||||||
),
|
"sc_images/room/emoji/monkey_6.gif",
|
||||||
_RoomEmojiCategory(
|
"sc_images/room/emoji/monkey_7.gif",
|
||||||
id: "monkey",
|
"sc_images/room/emoji/monkey_8.gif",
|
||||||
iconAsset: "sc_images/room/emoji/monkey_1.gif",
|
"sc_images/room/emoji/monkey_9.gif",
|
||||||
assets: [
|
"sc_images/room/emoji/monkey_10.gif",
|
||||||
"sc_images/room/emoji/monkey_1.gif",
|
|
||||||
"sc_images/room/emoji/monkey_2.gif",
|
|
||||||
"sc_images/room/emoji/monkey_3.gif",
|
|
||||||
"sc_images/room/emoji/monkey_4.gif",
|
|
||||||
"sc_images/room/emoji/monkey_5.gif",
|
|
||||||
"sc_images/room/emoji/monkey_6.gif",
|
|
||||||
"sc_images/room/emoji/monkey_7.gif",
|
|
||||||
"sc_images/room/emoji/monkey_8.gif",
|
|
||||||
"sc_images/room/emoji/monkey_9.gif",
|
|
||||||
"sc_images/room/emoji/monkey_10.gif",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
bool showSend = false;
|
bool showSend = false;
|
||||||
bool showEmoji = false;
|
bool showEmoji = false;
|
||||||
bool _emojiAssetsPrecached = false;
|
bool _emojiAssetsPrecached = false;
|
||||||
int _selectedEmojiCategoryIndex = 0;
|
|
||||||
|
|
||||||
final FocusNode msgNode = FocusNode();
|
final FocusNode msgNode = FocusNode();
|
||||||
final TextEditingController controller = TextEditingController();
|
final TextEditingController controller = TextEditingController();
|
||||||
@ -247,103 +234,33 @@ class _RoomMsgInputState extends State<RoomMsgInput> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildEmojiPanel() {
|
Widget _buildEmojiPanel() {
|
||||||
final selectedCategory = _roomEmojiCategories[_selectedEmojiCategoryIndex];
|
|
||||||
return Container(
|
return Container(
|
||||||
height: 220.w,
|
height: 150.w,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _panelBackgroundColor,
|
color: _panelBackgroundColor,
|
||||||
borderRadius: BorderRadius.circular(height(5)),
|
borderRadius: BorderRadius.circular(height(5)),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: GridView.builder(
|
||||||
children: [
|
padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 10.w),
|
||||||
Expanded(
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
child: GridView.builder(
|
crossAxisCount: 8,
|
||||||
key: ValueKey(selectedCategory.id),
|
crossAxisSpacing: 8.w,
|
||||||
padding: EdgeInsets.fromLTRB(18.w, 14.w, 18.w, 10.w),
|
mainAxisSpacing: 8.w,
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
crossAxisCount: 4,
|
|
||||||
crossAxisSpacing: 18.w,
|
|
||||||
mainAxisSpacing: 12.w,
|
|
||||||
),
|
|
||||||
itemCount: selectedCategory.assets.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final emojiAsset = selectedCategory.assets[index];
|
|
||||||
return GestureDetector(
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
onTap: () {
|
|
||||||
_sendRoomEmoji(emojiAsset);
|
|
||||||
},
|
|
||||||
child: Center(
|
|
||||||
child: RoomEmojiAssetImage(
|
|
||||||
key: ValueKey(emojiAsset),
|
|
||||||
asset: emojiAsset,
|
|
||||||
width: 42.w,
|
|
||||||
height: 42.w,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_buildEmojiCategoryBar(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildEmojiCategoryBar() {
|
|
||||||
return Container(
|
|
||||||
height: 50.w,
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 7.w),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.black.withValues(alpha: 0.08),
|
|
||||||
border: Border(
|
|
||||||
top: BorderSide(color: Colors.white.withValues(alpha: 0.08)),
|
|
||||||
),
|
),
|
||||||
),
|
itemCount: _roomEmojiAssets.length,
|
||||||
child: ListView.separated(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
itemCount: _roomEmojiCategories.length,
|
|
||||||
separatorBuilder: (_, __) => SizedBox(width: 10.w),
|
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final category = _roomEmojiCategories[index];
|
final emojiAsset = _roomEmojiAssets[index];
|
||||||
final selected = index == _selectedEmojiCategoryIndex;
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (selected) {
|
_sendRoomEmoji(emojiAsset);
|
||||||
return;
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_selectedEmojiCategoryIndex = index;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
child: AnimatedContainer(
|
child: Center(
|
||||||
duration: const Duration(milliseconds: 160),
|
child: RoomEmojiAssetImage(
|
||||||
curve: Curves.easeOut,
|
key: ValueKey(emojiAsset),
|
||||||
width: 64.w,
|
asset: emojiAsset,
|
||||||
height: 36.w,
|
width: 30.w,
|
||||||
decoration: BoxDecoration(
|
height: 30.w,
|
||||||
color:
|
|
||||||
selected
|
|
||||||
? Colors.white.withValues(alpha: 0.22)
|
|
||||||
: Colors.white.withValues(alpha: 0.08),
|
|
||||||
borderRadius: BorderRadius.circular(18.w),
|
|
||||||
border: Border.all(
|
|
||||||
color:
|
|
||||||
selected
|
|
||||||
? Colors.white.withValues(alpha: 0.30)
|
|
||||||
: Colors.transparent,
|
|
||||||
width: 1.w,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: RoomEmojiAssetImage(
|
|
||||||
key: ValueKey("emoji_category_${category.id}"),
|
|
||||||
asset: category.iconAsset,
|
|
||||||
width: 26.w,
|
|
||||||
height: 26.w,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -373,11 +290,8 @@ class _RoomMsgInputState extends State<RoomMsgInput> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_emojiAssetsPrecached = true;
|
_emojiAssetsPrecached = true;
|
||||||
for (final category in _roomEmojiCategories) {
|
for (final asset in _roomEmojiAssets) {
|
||||||
precacheImage(AssetImage(category.iconAsset), context);
|
precacheImage(AssetImage(asset), context);
|
||||||
for (final asset in category.assets) {
|
|
||||||
precacheImage(AssetImage(asset), context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -432,18 +346,6 @@ class _RoomMsgInputState extends State<RoomMsgInput> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RoomEmojiCategory {
|
|
||||||
const _RoomEmojiCategory({
|
|
||||||
required this.id,
|
|
||||||
required this.iconAsset,
|
|
||||||
required this.assets,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String id;
|
|
||||||
final String iconAsset;
|
|
||||||
final List<String> assets;
|
|
||||||
}
|
|
||||||
|
|
||||||
class PopRoute extends PopupRoute {
|
class PopRoute extends PopupRoute {
|
||||||
final Duration _duration = Duration(milliseconds: 350);
|
final Duration _duration = Duration(milliseconds: 350);
|
||||||
Widget child;
|
Widget child;
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math' as math;
|
|
||||||
|
|
||||||
import 'package:extended_text/extended_text.dart';
|
import 'package:extended_text/extended_text.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -20,8 +18,6 @@ import 'package:yumi/shared/business_logic/models/res/mic_res.dart';
|
|||||||
import 'package:yumi/shared/business_logic/usecases/sc_case.dart';
|
import 'package:yumi/shared/business_logic/usecases/sc_case.dart';
|
||||||
import 'package:yumi/modules/index/main_route.dart';
|
import 'package:yumi/modules/index/main_route.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/room/room_emoji_asset_image.dart';
|
import 'package:yumi/ui_kit/widgets/room/room_emoji_asset_image.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/sc_nine_patch_image.dart';
|
|
||||||
import 'package:yumi/ui_kit/widgets/svga/sc_network_svga_widget.dart';
|
|
||||||
|
|
||||||
import '../../../shared/data_sources/models/enum/sc_activity_reward_props_type.dart';
|
import '../../../shared/data_sources/models/enum/sc_activity_reward_props_type.dart';
|
||||||
import '../../../shared/data_sources/models/enum/sc_room_roles_type.dart';
|
import '../../../shared/data_sources/models/enum/sc_room_roles_type.dart';
|
||||||
@ -32,12 +28,13 @@ import '../../components/text/sc_text.dart';
|
|||||||
///消息item
|
///消息item
|
||||||
class MsgItem extends StatefulWidget {
|
class MsgItem extends StatefulWidget {
|
||||||
final Function(SocialChatUserProfile? user) onClick;
|
final Function(SocialChatUserProfile? user) onClick;
|
||||||
final Msg msg;
|
Msg msg;
|
||||||
|
|
||||||
const MsgItem({super.key, required this.msg, required this.onClick});
|
MsgItem({Key? key, required this.msg, required this.onClick})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MsgItem> createState() => _MsgItemState();
|
_MsgItemState createState() => _MsgItemState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MsgItemState extends State<MsgItem> {
|
class _MsgItemState extends State<MsgItem> {
|
||||||
@ -133,11 +130,42 @@ class _MsgItemState extends State<MsgItem> {
|
|||||||
Flexible(
|
Flexible(
|
||||||
// 使用Flexible替代Expanded
|
// 使用Flexible替代Expanded
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
child: _buildTextMessageBubble(),
|
child: Container(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: 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: ExtendedText(
|
||||||
|
widget.msg?.msg ?? '',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: sp(13),
|
||||||
|
color: Color(_msgColor(widget.msg.type ?? "")),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
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(
|
||||||
ClipboardData(text: widget.msg.msg ?? ''),
|
ClipboardData(text: widget.msg?.msg ?? ''),
|
||||||
);
|
);
|
||||||
SCTts.show("Copied");
|
SCTts.show("Copied");
|
||||||
}
|
}
|
||||||
@ -166,7 +194,7 @@ class _MsgItemState extends State<MsgItem> {
|
|||||||
borderRadius: BorderRadius.all(Radius.circular(15.w)),
|
borderRadius: BorderRadius.all(Radius.circular(15.w)),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.msg.msg ?? '',
|
widget.msg?.msg ?? '',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: sp(13),
|
fontSize: sp(13),
|
||||||
color: Color(_msgColor(widget.msg.type ?? "")),
|
color: Color(_msgColor(widget.msg.type ?? "")),
|
||||||
@ -179,295 +207,6 @@ class _MsgItemState extends State<MsgItem> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTextMessageBubble() {
|
|
||||||
final chatBubble = widget.msg.user?.getChatBox();
|
|
||||||
final hasChatBubble = _resolveRoomChatBubbleImageUrl(chatBubble).isNotEmpty;
|
|
||||||
if (hasChatBubble) {
|
|
||||||
return _buildVipTextMessageBubble(chatBubble);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
constraints: BoxConstraints(maxWidth: 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: ExtendedText(
|
|
||||||
widget.msg.msg ?? '',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: sp(13),
|
|
||||||
color: Color(_msgColor(widget.msg.type ?? "")),
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
softWrap: true,
|
|
||||||
specialTextSpanBuilder: AtTextSpanBuilder(onTapCall: (userId) {}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildVipTextMessageBubble(PropsResources? resource) {
|
|
||||||
final messageText = widget.msg.msg ?? "";
|
|
||||||
final textStyle = TextStyle(
|
|
||||||
fontSize: sp(13),
|
|
||||||
color: Color(_msgColor(widget.msg.type ?? "")),
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
);
|
|
||||||
final imageUrl = _resolveRoomChatBubbleImageUrl(resource);
|
|
||||||
final multilineMaxBubbleWidth = math.min(
|
|
||||||
ScreenUtil().screenWidth * 0.72,
|
|
||||||
300.w,
|
|
||||||
);
|
|
||||||
final multilineMaxTextWidth = math.max(
|
|
||||||
90.w,
|
|
||||||
multilineMaxBubbleWidth - 58.w,
|
|
||||||
);
|
|
||||||
final multilineTextPainter = TextPainter(
|
|
||||||
text: TextSpan(text: messageText, style: textStyle),
|
|
||||||
textDirection: Directionality.of(context),
|
|
||||||
)..layout(maxWidth: multilineMaxTextWidth);
|
|
||||||
if (multilineTextPainter.computeLineMetrics().length > 1) {
|
|
||||||
return _buildMultilineVipTextMessageBubble(
|
|
||||||
messageText: messageText,
|
|
||||||
textStyle: textStyle,
|
|
||||||
imageUrl: imageUrl,
|
|
||||||
textSize: multilineTextPainter.size,
|
|
||||||
maxBubbleWidth: multilineMaxBubbleWidth,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _buildSingleLineVipTextMessageBubble(
|
|
||||||
messageText: messageText,
|
|
||||||
textStyle: textStyle,
|
|
||||||
imageUrl: imageUrl,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildMultilineVipTextMessageBubble({
|
|
||||||
required String messageText,
|
|
||||||
required TextStyle textStyle,
|
|
||||||
required String imageUrl,
|
|
||||||
required Size textSize,
|
|
||||||
required double maxBubbleWidth,
|
|
||||||
}) {
|
|
||||||
final bubbleWidth = math
|
|
||||||
.max(126.w, textSize.width + 58.w)
|
|
||||||
.clamp(126.w, maxBubbleWidth);
|
|
||||||
final textMaxWidth = math.max(90.w, bubbleWidth - 58.w);
|
|
||||||
final bubbleHeight = math.max(58.w, textSize.height + 26.w);
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
width: bubbleWidth,
|
|
||||||
height: bubbleHeight,
|
|
||||||
margin: EdgeInsetsDirectional.only(start: 54.w).copyWith(bottom: 8.w),
|
|
||||||
child: Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
|
||||||
_buildVipBubbleBackground(
|
|
||||||
url: imageUrl,
|
|
||||||
width: bubbleWidth,
|
|
||||||
height: bubbleHeight,
|
|
||||||
fallback: _buildVipBubbleFallbackBackground(),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsetsDirectional.fromSTEB(29.w, 11.w, 29.w, 11.w),
|
|
||||||
child: Align(
|
|
||||||
alignment: AlignmentDirectional.centerStart,
|
|
||||||
child: SizedBox(
|
|
||||||
width: textMaxWidth,
|
|
||||||
child: ExtendedText(
|
|
||||||
messageText,
|
|
||||||
style: textStyle,
|
|
||||||
softWrap: true,
|
|
||||||
specialTextSpanBuilder: AtTextSpanBuilder(
|
|
||||||
onTapCall: (userId) {},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildSingleLineVipTextMessageBubble({
|
|
||||||
required String messageText,
|
|
||||||
required TextStyle textStyle,
|
|
||||||
required String imageUrl,
|
|
||||||
}) {
|
|
||||||
final horizontalPadding = 18.w;
|
|
||||||
final verticalPadding = 4.w;
|
|
||||||
final minSkinWidth = 132.w;
|
|
||||||
final minSkinHeight = 42.w;
|
|
||||||
final maxBubbleWidth = ScreenUtil().screenWidth * 0.7;
|
|
||||||
final maxTextWidth = math.max(1.0, maxBubbleWidth - horizontalPadding * 2);
|
|
||||||
final textSize = _measureMessageTextSize(
|
|
||||||
text: messageText,
|
|
||||||
style: textStyle,
|
|
||||||
maxWidth: maxTextWidth,
|
|
||||||
);
|
|
||||||
final bubbleWidth = math.min(
|
|
||||||
maxBubbleWidth,
|
|
||||||
math.max(horizontalPadding * 2, textSize.width + horizontalPadding * 2),
|
|
||||||
);
|
|
||||||
final textMaxWidth = math.max(1.0, bubbleWidth - horizontalPadding * 2);
|
|
||||||
final bubbleHeight = math.max(
|
|
||||||
verticalPadding * 2,
|
|
||||||
textSize.height + verticalPadding * 2,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
width: bubbleWidth,
|
|
||||||
height: bubbleHeight,
|
|
||||||
margin: EdgeInsetsDirectional.only(start: 54.w).copyWith(bottom: 8.w),
|
|
||||||
child: Stack(
|
|
||||||
clipBehavior: Clip.none,
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
|
||||||
Positioned.fill(child: _buildVipBubbleFallbackBackground()),
|
|
||||||
Positioned.fill(
|
|
||||||
child: _buildVipBubbleSkin(
|
|
||||||
url: imageUrl,
|
|
||||||
width: math.max(bubbleWidth, minSkinWidth),
|
|
||||||
height: math.max(bubbleHeight, minSkinHeight),
|
|
||||||
alignment: AlignmentDirectional.centerStart,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsetsDirectional.symmetric(
|
|
||||||
horizontal: horizontalPadding,
|
|
||||||
vertical: verticalPadding,
|
|
||||||
),
|
|
||||||
child: Align(
|
|
||||||
alignment: AlignmentDirectional.centerStart,
|
|
||||||
child: SizedBox(
|
|
||||||
width: textMaxWidth,
|
|
||||||
child: ExtendedText(
|
|
||||||
messageText,
|
|
||||||
style: textStyle,
|
|
||||||
softWrap: true,
|
|
||||||
specialTextSpanBuilder: AtTextSpanBuilder(
|
|
||||||
onTapCall: (userId) {},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildVipBubbleSkin({
|
|
||||||
required String url,
|
|
||||||
required double width,
|
|
||||||
required double height,
|
|
||||||
required AlignmentGeometry alignment,
|
|
||||||
}) {
|
|
||||||
return OverflowBox(
|
|
||||||
alignment: alignment,
|
|
||||||
minWidth: width,
|
|
||||||
maxWidth: width,
|
|
||||||
minHeight: height,
|
|
||||||
maxHeight: height,
|
|
||||||
child:
|
|
||||||
SCNetworkSvgaWidget.isSvga(url)
|
|
||||||
? SCNetworkSvgaWidget(
|
|
||||||
resource: url,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
fit: BoxFit.fill,
|
|
||||||
fallback: SizedBox(width: width, height: height),
|
|
||||||
)
|
|
||||||
: SCNinePatchImage(
|
|
||||||
url: url,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
centerSliceRatio: const EdgeInsets.fromLTRB(
|
|
||||||
0.34,
|
|
||||||
0.34,
|
|
||||||
0.34,
|
|
||||||
0.34,
|
|
||||||
),
|
|
||||||
fallback: SizedBox(width: width, height: height),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildVipBubbleBackground({
|
|
||||||
required String url,
|
|
||||||
required double width,
|
|
||||||
required double height,
|
|
||||||
required Widget fallback,
|
|
||||||
}) {
|
|
||||||
if (SCNetworkSvgaWidget.isSvga(url)) {
|
|
||||||
return SCNetworkSvgaWidget(
|
|
||||||
resource: url,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
fit: BoxFit.fill,
|
|
||||||
fallback: fallback,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return SCNinePatchImage(
|
|
||||||
url: url,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
centerSliceRatio: const EdgeInsets.fromLTRB(0.34, 0.34, 0.34, 0.34),
|
|
||||||
fallback: fallback,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Size _measureMessageTextSize({
|
|
||||||
required String text,
|
|
||||||
required TextStyle style,
|
|
||||||
required double maxWidth,
|
|
||||||
}) {
|
|
||||||
final textPainter = TextPainter(
|
|
||||||
text: TextSpan(text: text, style: style),
|
|
||||||
textDirection: Directionality.of(context),
|
|
||||||
)..layout(maxWidth: maxWidth);
|
|
||||||
return textPainter.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildVipBubbleFallbackBackground() {
|
|
||||||
return DecoratedBox(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.black.withValues(alpha: 0.38),
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topRight: Radius.circular(18.w),
|
|
||||||
bottomLeft: Radius.circular(18.w),
|
|
||||||
bottomRight: Radius.circular(18.w),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _resolveRoomChatBubbleImageUrl(PropsResources? resource) {
|
|
||||||
return _firstNonBlank([
|
|
||||||
resource?.roomSendCoverUrl,
|
|
||||||
resource?.roomOpenedUrl,
|
|
||||||
resource?.roomNotOpenedUrl,
|
|
||||||
resource?.cover,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _firstNonBlank(Iterable<String?> values) {
|
|
||||||
for (final value in values) {
|
|
||||||
final text = value?.trim();
|
|
||||||
if (text != null && text.isNotEmpty) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
///系统提示
|
///系统提示
|
||||||
_systemTipsRoomItem(BuildContext context) {
|
_systemTipsRoomItem(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
@ -485,7 +224,7 @@ class _MsgItemState extends State<MsgItem> {
|
|||||||
borderRadius: BorderRadius.all(Radius.circular(15.w)),
|
borderRadius: BorderRadius.all(Radius.circular(15.w)),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.msg.msg ?? '',
|
widget.msg?.msg ?? '',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: sp(13),
|
fontSize: sp(13),
|
||||||
height: 1.5.w,
|
height: 1.5.w,
|
||||||
@ -919,7 +658,7 @@ class _MsgItemState extends State<MsgItem> {
|
|||||||
///进入房间
|
///进入房间
|
||||||
_enterRoomItem(BuildContext context) {
|
_enterRoomItem(BuildContext context) {
|
||||||
List<InlineSpan> spans = [];
|
List<InlineSpan> spans = [];
|
||||||
if (widget.msg.role != null && widget.msg.role != "0") {
|
if (widget.msg.role != null && widget.msg.role != 0) {
|
||||||
spans.add(
|
spans.add(
|
||||||
WidgetSpan(
|
WidgetSpan(
|
||||||
alignment: PlaceholderAlignment.middle,
|
alignment: PlaceholderAlignment.middle,
|
||||||
@ -932,7 +671,7 @@ class _MsgItemState extends State<MsgItem> {
|
|||||||
}
|
}
|
||||||
spans.add(
|
spans.add(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "${widget.msg.user?.userNickname ?? ''} ",
|
text: "${widget.msg?.user?.userNickname ?? ''} ",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: sp(13),
|
fontSize: sp(13),
|
||||||
color: _roleNameColor(widget.msg.role ?? ""),
|
color: _roleNameColor(widget.msg.role ?? ""),
|
||||||
@ -979,7 +718,7 @@ class _MsgItemState extends State<MsgItem> {
|
|||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: wearBadge.length,
|
itemCount: wearBadge.length ?? 0,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return netImage(
|
return netImage(
|
||||||
width: 25.w,
|
width: 25.w,
|
||||||
@ -1010,6 +749,7 @@ class _MsgItemState extends State<MsgItem> {
|
|||||||
case SCRoomMsgType.qcfj:
|
case SCRoomMsgType.qcfj:
|
||||||
case SCRoomMsgType.killXiaMai:
|
case SCRoomMsgType.killXiaMai:
|
||||||
return 0xFFFFFFFF;
|
return 0xFFFFFFFF;
|
||||||
|
break;
|
||||||
// case type.text:
|
// case type.text:
|
||||||
// return _mapTextColor();
|
// return _mapTextColor();
|
||||||
// break;
|
// break;
|
||||||
@ -1663,7 +1403,6 @@ class Msg {
|
|||||||
|
|
||||||
// 送礼给谁
|
// 送礼给谁
|
||||||
SocialChatUserProfile? toUser;
|
SocialChatUserProfile? toUser;
|
||||||
List<String>? targetUserIds;
|
|
||||||
int? time = 0;
|
int? time = 0;
|
||||||
|
|
||||||
//动画是否已经播放(骰子、石头剪刀布、随机数字)
|
//动画是否已经播放(骰子、石头剪刀布、随机数字)
|
||||||
@ -1683,7 +1422,6 @@ class Msg {
|
|||||||
this.number,
|
this.number,
|
||||||
this.customAnimationCount,
|
this.customAnimationCount,
|
||||||
this.awardAmount,
|
this.awardAmount,
|
||||||
this.targetUserIds,
|
|
||||||
this.hasPlayedAnimation,
|
this.hasPlayedAnimation,
|
||||||
this.needUpDataUserInfo,
|
this.needUpDataUserInfo,
|
||||||
}) {
|
}) {
|
||||||
@ -1699,14 +1437,6 @@ class Msg {
|
|||||||
number = json['number'];
|
number = json['number'];
|
||||||
customAnimationCount = json['customAnimationCount'];
|
customAnimationCount = json['customAnimationCount'];
|
||||||
awardAmount = json['awardAmount'];
|
awardAmount = json['awardAmount'];
|
||||||
final dynamic rawTargetUserIds = json['targetUserIds'];
|
|
||||||
if (rawTargetUserIds is List) {
|
|
||||||
targetUserIds =
|
|
||||||
rawTargetUserIds
|
|
||||||
.map((item) => item?.toString().trim() ?? "")
|
|
||||||
.where((id) => id.isNotEmpty)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
hasPlayedAnimation = json['hasPlayedAnimation'];
|
hasPlayedAnimation = json['hasPlayedAnimation'];
|
||||||
needUpDataUserInfo = json['needUpDataUserInfo'];
|
needUpDataUserInfo = json['needUpDataUserInfo'];
|
||||||
userWheat =
|
userWheat =
|
||||||
@ -1736,9 +1466,6 @@ class Msg {
|
|||||||
map['customAnimationCount'] = customAnimationCount;
|
map['customAnimationCount'] = customAnimationCount;
|
||||||
map['hasPlayedAnimation'] = hasPlayedAnimation;
|
map['hasPlayedAnimation'] = hasPlayedAnimation;
|
||||||
map['needUpDataUserInfo'] = needUpDataUserInfo;
|
map['needUpDataUserInfo'] = needUpDataUserInfo;
|
||||||
if (targetUserIds != null) {
|
|
||||||
map['targetUserIds'] = targetUserIds;
|
|
||||||
}
|
|
||||||
if (userWheat != null) {
|
if (userWheat != null) {
|
||||||
map['userWheat'] = userWheat?.toJson();
|
map['userWheat'] = userWheat?.toJson();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,209 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:ui' as ui;
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:yumi/shared/tools/sc_network_image_utils.dart';
|
|
||||||
|
|
||||||
class SCNinePatchImage extends StatefulWidget {
|
|
||||||
const SCNinePatchImage({
|
|
||||||
super.key,
|
|
||||||
required this.url,
|
|
||||||
required this.width,
|
|
||||||
required this.height,
|
|
||||||
this.centerSliceRatio = const EdgeInsets.fromLTRB(0.34, 0.34, 0.34, 0.34),
|
|
||||||
this.sourceEdgeInset = const EdgeInsets.all(2),
|
|
||||||
this.filterQuality = FilterQuality.low,
|
|
||||||
this.fallback,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String url;
|
|
||||||
final double width;
|
|
||||||
final double height;
|
|
||||||
final EdgeInsets centerSliceRatio;
|
|
||||||
final EdgeInsets sourceEdgeInset;
|
|
||||||
final FilterQuality filterQuality;
|
|
||||||
final Widget? fallback;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<SCNinePatchImage> createState() => _SCNinePatchImageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SCNinePatchImageState extends State<SCNinePatchImage> {
|
|
||||||
ImageStream? _imageStream;
|
|
||||||
ImageStreamListener? _imageListener;
|
|
||||||
ui.Image? _image;
|
|
||||||
bool _hasError = false;
|
|
||||||
int _resolveSerial = 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_resolveImage();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(covariant SCNinePatchImage oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
if (oldWidget.url != widget.url ||
|
|
||||||
oldWidget.width != widget.width ||
|
|
||||||
oldWidget.height != widget.height ||
|
|
||||||
oldWidget.sourceEdgeInset != widget.sourceEdgeInset) {
|
|
||||||
_resolveImage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_resolveSerial++;
|
|
||||||
_removeListener();
|
|
||||||
_image?.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _resolveImage() {
|
|
||||||
_removeListener();
|
|
||||||
_image?.dispose();
|
|
||||||
_image = null;
|
|
||||||
final serial = ++_resolveSerial;
|
|
||||||
final url = widget.url.trim();
|
|
||||||
if (url.isEmpty) {
|
|
||||||
setState(() {
|
|
||||||
_hasError = true;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final provider = buildCachedImageProvider(
|
|
||||||
url,
|
|
||||||
logicalWidth: widget.width,
|
|
||||||
logicalHeight: widget.height,
|
|
||||||
);
|
|
||||||
final stream = provider.resolve(ImageConfiguration.empty);
|
|
||||||
late final ImageStreamListener listener;
|
|
||||||
listener = ImageStreamListener(
|
|
||||||
(imageInfo, synchronousCall) {
|
|
||||||
unawaited(_cropAndUseImage(imageInfo.image, serial));
|
|
||||||
},
|
|
||||||
onError: (error, stackTrace) {
|
|
||||||
if (!mounted || serial != _resolveSerial) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
debugPrint("[SCNinePatchImage] load failed url=$url error=$error");
|
|
||||||
setState(() {
|
|
||||||
_image = null;
|
|
||||||
_hasError = true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
_imageStream = stream;
|
|
||||||
_imageListener = listener;
|
|
||||||
stream.addListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _cropAndUseImage(ui.Image sourceImage, int serial) async {
|
|
||||||
try {
|
|
||||||
final croppedImage = await _cropImage(sourceImage);
|
|
||||||
if (!mounted || serial != _resolveSerial) {
|
|
||||||
croppedImage.dispose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_image?.dispose();
|
|
||||||
_image = croppedImage;
|
|
||||||
_hasError = false;
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
debugPrint("[SCNinePatchImage] crop failed error=$error");
|
|
||||||
if (!mounted || serial != _resolveSerial) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_image = null;
|
|
||||||
_hasError = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ui.Image> _cropImage(ui.Image sourceImage) async {
|
|
||||||
final imageWidth = sourceImage.width.toDouble();
|
|
||||||
final imageHeight = sourceImage.height.toDouble();
|
|
||||||
final left = widget.sourceEdgeInset.left.clamp(0.0, imageWidth / 4);
|
|
||||||
final top = widget.sourceEdgeInset.top.clamp(0.0, imageHeight / 4);
|
|
||||||
final right = widget.sourceEdgeInset.right.clamp(0.0, imageWidth / 4);
|
|
||||||
final bottom = widget.sourceEdgeInset.bottom.clamp(0.0, imageHeight / 4);
|
|
||||||
final sourceRect = Rect.fromLTRB(
|
|
||||||
left,
|
|
||||||
top,
|
|
||||||
imageWidth - right,
|
|
||||||
imageHeight - bottom,
|
|
||||||
);
|
|
||||||
final croppedWidth = sourceRect.width.round().clamp(1, sourceImage.width);
|
|
||||||
final croppedHeight = sourceRect.height.round().clamp(
|
|
||||||
1,
|
|
||||||
sourceImage.height,
|
|
||||||
);
|
|
||||||
|
|
||||||
final recorder = ui.PictureRecorder();
|
|
||||||
final canvas = Canvas(recorder);
|
|
||||||
canvas.drawImageRect(
|
|
||||||
sourceImage,
|
|
||||||
sourceRect,
|
|
||||||
Rect.fromLTWH(0, 0, croppedWidth.toDouble(), croppedHeight.toDouble()),
|
|
||||||
Paint()..filterQuality = FilterQuality.low,
|
|
||||||
);
|
|
||||||
final picture = recorder.endRecording();
|
|
||||||
final croppedImage = await picture.toImage(croppedWidth, croppedHeight);
|
|
||||||
picture.dispose();
|
|
||||||
return croppedImage;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _removeListener() {
|
|
||||||
final stream = _imageStream;
|
|
||||||
final listener = _imageListener;
|
|
||||||
if (stream != null && listener != null) {
|
|
||||||
stream.removeListener(listener);
|
|
||||||
}
|
|
||||||
_imageStream = null;
|
|
||||||
_imageListener = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final image = _image;
|
|
||||||
if (_hasError || image == null) {
|
|
||||||
return widget.fallback ??
|
|
||||||
SizedBox(width: widget.width, height: widget.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
return RawImage(
|
|
||||||
image: image,
|
|
||||||
width: widget.width,
|
|
||||||
height: widget.height,
|
|
||||||
fit: BoxFit.fill,
|
|
||||||
centerSlice: _buildCenterSlice(image),
|
|
||||||
filterQuality: widget.filterQuality,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Rect _buildCenterSlice(ui.Image image) {
|
|
||||||
final imageWidth = image.width.toDouble();
|
|
||||||
final imageHeight = image.height.toDouble();
|
|
||||||
final left = (imageWidth * widget.centerSliceRatio.left).clamp(
|
|
||||||
1.0,
|
|
||||||
imageWidth - 2.0,
|
|
||||||
);
|
|
||||||
final top = (imageHeight * widget.centerSliceRatio.top).clamp(
|
|
||||||
1.0,
|
|
||||||
imageHeight - 2.0,
|
|
||||||
);
|
|
||||||
final right = (imageWidth * widget.centerSliceRatio.right).clamp(
|
|
||||||
1.0,
|
|
||||||
imageWidth - left - 1.0,
|
|
||||||
);
|
|
||||||
final bottom = (imageHeight * widget.centerSliceRatio.bottom).clamp(
|
|
||||||
1.0,
|
|
||||||
imageHeight - top - 1.0,
|
|
||||||
);
|
|
||||||
return Rect.fromLTRB(left, top, imageWidth - right, imageHeight - bottom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,179 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_svga/flutter_svga.dart';
|
|
||||||
import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart';
|
|
||||||
|
|
||||||
typedef SCNetworkSvgaMovieConfigurer =
|
|
||||||
FutureOr<void> Function(MovieEntity movieEntity);
|
|
||||||
|
|
||||||
class SCNetworkSvgaWidget extends StatefulWidget {
|
|
||||||
const SCNetworkSvgaWidget({
|
|
||||||
super.key,
|
|
||||||
required this.resource,
|
|
||||||
this.width,
|
|
||||||
this.height,
|
|
||||||
this.active = true,
|
|
||||||
this.loop = false,
|
|
||||||
this.fit = BoxFit.contain,
|
|
||||||
this.filterQuality = FilterQuality.low,
|
|
||||||
this.allowDrawingOverflow = false,
|
|
||||||
this.fallback,
|
|
||||||
this.dynamicIdentity,
|
|
||||||
this.movieConfigurer,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String resource;
|
|
||||||
final double? width;
|
|
||||||
final double? height;
|
|
||||||
final bool active;
|
|
||||||
final bool loop;
|
|
||||||
final BoxFit fit;
|
|
||||||
final FilterQuality filterQuality;
|
|
||||||
final bool allowDrawingOverflow;
|
|
||||||
final Widget? fallback;
|
|
||||||
final Object? dynamicIdentity;
|
|
||||||
final SCNetworkSvgaMovieConfigurer? movieConfigurer;
|
|
||||||
|
|
||||||
static bool isSvga(String? resource) {
|
|
||||||
final lower = resource?.trim().toLowerCase() ?? "";
|
|
||||||
return lower.endsWith(".svga") || lower.contains(".svga?");
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<SCNetworkSvgaWidget> createState() => _SCNetworkSvgaWidgetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SCNetworkSvgaWidgetState extends State<SCNetworkSvgaWidget>
|
|
||||||
with SingleTickerProviderStateMixin {
|
|
||||||
late final SVGAAnimationController _controller;
|
|
||||||
String? _loadedResource;
|
|
||||||
bool _hasError = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_controller = SVGAAnimationController(vsync: this);
|
|
||||||
_loadResource();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(covariant SCNetworkSvgaWidget oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
if (oldWidget.resource != widget.resource ||
|
|
||||||
oldWidget.dynamicIdentity != widget.dynamicIdentity) {
|
|
||||||
_loadResource();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (oldWidget.active != widget.active || oldWidget.loop != widget.loop) {
|
|
||||||
_syncPlayback(restartIfActive: widget.active && !oldWidget.active);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _loadResource() async {
|
|
||||||
final resource = widget.resource.trim();
|
|
||||||
if (resource.isEmpty || !SCNetworkSvgaWidget.isSvga(resource)) {
|
|
||||||
setState(() {
|
|
||||||
_hasError = true;
|
|
||||||
_loadedResource = null;
|
|
||||||
_controller.videoItem = null;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_hasError = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
final cache = SCGiftVapSvgaManager().videoItemCache;
|
|
||||||
var movieEntity = cache[resource];
|
|
||||||
if (movieEntity == null) {
|
|
||||||
movieEntity =
|
|
||||||
resource.startsWith("http")
|
|
||||||
? await SVGAParser.shared.decodeFromURL(resource)
|
|
||||||
: await SVGAParser.shared.decodeFromAssets(resource);
|
|
||||||
movieEntity.autorelease = false;
|
|
||||||
cache[resource] = movieEntity;
|
|
||||||
}
|
|
||||||
if (widget.movieConfigurer != null) {
|
|
||||||
movieEntity.dynamicItem.reset();
|
|
||||||
await widget.movieConfigurer!(movieEntity);
|
|
||||||
}
|
|
||||||
if (!mounted || widget.resource.trim() != resource) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_loadedResource = resource;
|
|
||||||
_controller.videoItem = movieEntity;
|
|
||||||
});
|
|
||||||
_syncPlayback(restartIfActive: true);
|
|
||||||
} catch (error) {
|
|
||||||
debugPrint("[SCNetworkSVGA] load failed resource=$resource error=$error");
|
|
||||||
if (!mounted || widget.resource.trim() != resource) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_hasError = true;
|
|
||||||
_loadedResource = null;
|
|
||||||
_controller.videoItem = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _syncPlayback({bool restartIfActive = false}) {
|
|
||||||
if (!widget.active || _controller.videoItem == null) {
|
|
||||||
_controller.stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (widget.loop) {
|
|
||||||
if (restartIfActive || !_controller.isAnimating) {
|
|
||||||
_controller.reset();
|
|
||||||
_controller.repeat();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (restartIfActive || !_controller.isAnimating) {
|
|
||||||
_controller.reset();
|
|
||||||
_controller.repeat(count: 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_controller.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (_hasError ||
|
|
||||||
_controller.videoItem == null ||
|
|
||||||
_loadedResource != widget.resource.trim()) {
|
|
||||||
return _buildFallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
return SizedBox(
|
|
||||||
width: widget.width,
|
|
||||||
height: widget.height,
|
|
||||||
child: IgnorePointer(
|
|
||||||
child: SVGAImage(
|
|
||||||
_controller,
|
|
||||||
fit: widget.fit,
|
|
||||||
clearsAfterStop: false,
|
|
||||||
filterQuality: widget.filterQuality,
|
|
||||||
allowDrawingOverflow: widget.allowDrawingOverflow,
|
|
||||||
preferredSize:
|
|
||||||
widget.width != null && widget.height != null
|
|
||||||
? Size(widget.width!, widget.height!)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildFallback() {
|
|
||||||
return widget.fallback ??
|
|
||||||
SizedBox(width: widget.width, height: widget.height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 1.2.7+9
|
version: 1.2.6+8
|
||||||
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@ -1,53 +0,0 @@
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('SCGiftVapSvgaManager priorities', () {
|
|
||||||
test('entry effects keep the highest default priority', () {
|
|
||||||
final giftPriority = SCGiftVapSvgaManager.resolveEffectPriority();
|
|
||||||
final entryPriority = SCGiftVapSvgaManager.resolveEffectPriority(
|
|
||||||
type: SCGiftVapSvgaManager.entryEffectType,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(entryPriority, greaterThan(giftPriority));
|
|
||||||
expect(entryPriority, SCGiftVapSvgaManager.entryEffectPriority);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('explicit gift priority is kept below default entry priority', () {
|
|
||||||
expect(SCGiftVapSvgaManager.resolveEffectPriority(priority: 200), 200);
|
|
||||||
expect(
|
|
||||||
SCGiftVapSvgaManager.resolveEffectPriority(
|
|
||||||
priority: 200,
|
|
||||||
type: SCGiftVapSvgaManager.entryEffectType,
|
|
||||||
),
|
|
||||||
SCGiftVapSvgaManager.entryEffectPriority,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test(
|
|
||||||
'priority queue still orders entry before gift in the same channel',
|
|
||||||
() {
|
|
||||||
final queue = SCPriorityQueue<SCVapTask>((a, b) => a.compareTo(b));
|
|
||||||
|
|
||||||
queue.add(
|
|
||||||
SCVapTask(
|
|
||||||
path: 'entry.svga',
|
|
||||||
type: SCGiftVapSvgaManager.entryEffectType,
|
|
||||||
priority: SCGiftVapSvgaManager.resolveEffectPriority(
|
|
||||||
type: SCGiftVapSvgaManager.entryEffectType,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
queue.add(
|
|
||||||
SCVapTask(
|
|
||||||
path: 'gift.svga',
|
|
||||||
priority: SCGiftVapSvgaManager.resolveEffectPriority(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(queue.removeFirst().path, 'entry.svga');
|
|
||||||
expect(queue.removeFirst().path, 'gift.svga');
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:yumi/shared/tools/sc_path_utils.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('SCPathUtils.getFileExtension', () {
|
|
||||||
test('normalizes query and fragment from svga urls', () {
|
|
||||||
expect(
|
|
||||||
SCPathUtils.getFileExtension(
|
|
||||||
'https://example.test/gifts/effect.svga?Expires=1&Signature=abc',
|
|
||||||
),
|
|
||||||
'.svga',
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
SCPathUtils.getFileExtension(
|
|
||||||
'https://example.test/gifts/effect.SVGA#preview',
|
|
||||||
),
|
|
||||||
'.svga',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('keeps local and asset paths unchanged', () {
|
|
||||||
expect(
|
|
||||||
SCPathUtils.getFileExtension('sc_images/room/effect.svga'),
|
|
||||||
'.svga',
|
|
||||||
);
|
|
||||||
expect(SCPathUtils.getFileExtension('/tmp/effect.mp4'), '.mp4');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,185 +0,0 @@
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:yumi/shared/business_logic/models/res/join_room_res.dart';
|
|
||||||
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
|
||||||
import 'package:yumi/shared/business_logic/models/res/mic_res.dart';
|
|
||||||
import 'package:yumi/shared/business_logic/models/res/room_gift_rank_res.dart';
|
|
||||||
import 'package:yumi/shared/business_logic/models/res/room_member_res.dart';
|
|
||||||
import 'package:yumi/shared/business_logic/models/res/room_res.dart';
|
|
||||||
import 'package:yumi/shared/business_logic/models/res/room_user_card_res.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('VIP colored ID', () {
|
|
||||||
test('uses explicit VIP level from user profile', () {
|
|
||||||
final profile = SocialChatUserProfile.fromJson({
|
|
||||||
'account': '10001',
|
|
||||||
'vipLevel': 3,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(profile.vipLevelForColoredId, 3);
|
|
||||||
expect(profile.shouldShowColoredSpecialIdText(), isTrue);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('VIP level zero disables legacy prop fallback', () {
|
|
||||||
final profile = SocialChatUserProfile.fromJson({
|
|
||||||
'account': '10001',
|
|
||||||
'vipLevel': 0,
|
|
||||||
'useProps': [
|
|
||||||
{
|
|
||||||
'propsResources': {'type': 'NOBLE_VIP', 'code': '10026'},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(profile.vipLevelForColoredId, 0);
|
|
||||||
expect(profile.shouldShowColoredSpecialIdText(), isFalse);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('keeps legacy NOBLE_VIP prop support', () {
|
|
||||||
final profile = SocialChatUserProfile.fromJson({
|
|
||||||
'account': '10001',
|
|
||||||
'useProps': [
|
|
||||||
{
|
|
||||||
'propsResources': {'type': 'NOBLE_VIP', 'code': '10026'},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(profile.vipLevelForColoredId, 1);
|
|
||||||
expect(profile.shouldShowColoredSpecialIdText(), isTrue);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('uses VIP visual resources for colored ID on user cards', () {
|
|
||||||
final profile = SocialChatUserProfile.fromJson({
|
|
||||||
'account': '10001',
|
|
||||||
'useProps': [
|
|
||||||
{
|
|
||||||
'propsResources': {
|
|
||||||
'type': 'AVATAR_FRAME',
|
|
||||||
'name': 'VIP5 avatar frame',
|
|
||||||
'sourceUrl': 'https://example.test/vip_5_frame.svga',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(profile.vipVisualResourceLevelForColoredId, 5);
|
|
||||||
expect(profile.shouldShowColoredSpecialIdText(), isTrue);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('does not color inactive nested VIP status', () {
|
|
||||||
final profile = SocialChatUserProfile.fromJson({
|
|
||||||
'account': '10001',
|
|
||||||
'vipStatus': {'active': false, 'level': 5},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(profile.vipLevelForColoredId, 0);
|
|
||||||
expect(profile.shouldShowColoredSpecialIdText(), isFalse);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('pipes room user card supperVip into nested profile', () {
|
|
||||||
final card = RoomUserCardRes.fromJson({
|
|
||||||
'supperVip': 'VIP4',
|
|
||||||
'userProfile': {'account': '10001'},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(card.userProfile?.vipLevelForColoredId, 4);
|
|
||||||
expect(card.userProfile?.shouldShowColoredSpecialIdText(), isTrue);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('pipes room owner userSVipLevel into nested profile', () {
|
|
||||||
final room = SocialChatRoomRes.fromJson({
|
|
||||||
'userSVipLevel': '5',
|
|
||||||
'userProfile': {'account': '10001'},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(room.userProfile?.vipLevelForColoredId, 5);
|
|
||||||
expect(room.userProfile?.shouldShowColoredSpecialIdText(), isTrue);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('pipes joined voice room userSVipLevel into owner profile', () {
|
|
||||||
final room = JoinRoomRes.fromJson({
|
|
||||||
'roomProfile': {
|
|
||||||
'userSVipLevel': 'VIP5',
|
|
||||||
'userProfile': {'account': '10001'},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(room.roomProfile?.userProfile?.vipLevelForColoredId, 5);
|
|
||||||
expect(
|
|
||||||
room.roomProfile?.userProfile?.shouldShowColoredSpecialIdText(),
|
|
||||||
isTrue,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('pipes joined voice room supperVip into owner profile', () {
|
|
||||||
final room = JoinRoomRes.fromJson({
|
|
||||||
'roomProfile': {
|
|
||||||
'supperVip': '4',
|
|
||||||
'userProfile': {'account': '10001'},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(room.roomProfile?.userProfile?.vipLevelForColoredId, 4);
|
|
||||||
expect(
|
|
||||||
room.roomProfile?.userProfile?.shouldShowColoredSpecialIdText(),
|
|
||||||
isTrue,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('pipes top-level joined voice room VIP into owner profile', () {
|
|
||||||
final room = JoinRoomRes.fromJson({
|
|
||||||
'userSVipLevel': 'SVIP6',
|
|
||||||
'roomProfile': {
|
|
||||||
'userProfile': {'account': '10001'},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(room.roomProfile?.userProfile?.vipLevelForColoredId, 6);
|
|
||||||
expect(
|
|
||||||
room.roomProfile?.userProfile?.shouldShowColoredSpecialIdText(),
|
|
||||||
isTrue,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('reads VIP level from wrapped user profile payloads', () {
|
|
||||||
final profile = SocialChatUserProfile.fromJson({
|
|
||||||
'userSVipLevel': 'VIP5',
|
|
||||||
'userProfile': {'account': '10001'},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(profile.account, '10001');
|
|
||||||
expect(profile.vipLevelForColoredId, 5);
|
|
||||||
expect(profile.shouldShowColoredSpecialIdText(), isTrue);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('pipes room rank VIP into nested profile', () {
|
|
||||||
final rank = RoomGiftRankRes.fromJson({
|
|
||||||
'userSVipLevel': 'VIP5',
|
|
||||||
'userProfile': {'account': '10001'},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(rank.userProfile?.vipLevelForColoredId, 5);
|
|
||||||
expect(rank.userProfile?.shouldShowColoredSpecialIdText(), isTrue);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('pipes room member VIP into nested profile', () {
|
|
||||||
final member = SocialChatRoomMemberRes.fromJson({
|
|
||||||
'supperVip': '5',
|
|
||||||
'userProfile': {'account': '10001'},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(member.userProfile?.vipLevelForColoredId, 5);
|
|
||||||
expect(member.userProfile?.shouldShowColoredSpecialIdText(), isTrue);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('pipes microphone VIP into nested user', () {
|
|
||||||
final mic = MicRes.fromJson({
|
|
||||||
'userSVipLevel': 'VIP5',
|
|
||||||
'user': {'account': '10001'},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(mic.user?.vipLevelForColoredId, 5);
|
|
||||||
expect(mic.user?.shouldShowColoredSpecialIdText(), isTrue);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user