修改火箭
This commit is contained in:
parent
671f925232
commit
409d2fc898
@ -32,7 +32,6 @@ 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';
|
||||||
import 'package:yumi/services/general/sc_app_general_manager.dart';
|
import 'package:yumi/services/general/sc_app_general_manager.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/badge/sc_user_badge_strip.dart';
|
import 'package:yumi/ui_kit/widgets/badge/sc_user_badge_strip.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/props/sc_data_card_resource_view.dart';
|
|
||||||
|
|
||||||
import '../../../app/constants/sc_screen.dart';
|
import '../../../app/constants/sc_screen.dart';
|
||||||
import '../../../shared/business_logic/models/res/sc_user_counter_res.dart';
|
import '../../../shared/business_logic/models/res/sc_user_counter_res.dart';
|
||||||
@ -580,49 +579,8 @@ class _PersonDetailPageState extends State<PersonDetailPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildProfileDataCardBackground(PropsResources? dataCard) {
|
Widget _buildProfileDataCardBackground(PropsResources? _) {
|
||||||
if (dataCard == null) {
|
return const ColoredBox(color: _profileBg);
|
||||||
return const ColoredBox(color: _profileBg);
|
|
||||||
}
|
|
||||||
return Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
|
||||||
SCDataCardResourceView(
|
|
||||||
resource: dataCard,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
fallback: DecoratedBox(decoration: _defaultDataCardDecoration()),
|
|
||||||
),
|
|
||||||
DecoratedBox(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
|
||||||
colors: [
|
|
||||||
Colors.black.withValues(alpha: 0.26),
|
|
||||||
_profileBg.withValues(alpha: 0.54),
|
|
||||||
_profileBg.withValues(alpha: 0.78),
|
|
||||||
],
|
|
||||||
stops: const [0, 0.45, 1],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
BoxDecoration _defaultDataCardDecoration() {
|
|
||||||
return const BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
|
||||||
colors: [
|
|
||||||
Color(0xff18F2B1),
|
|
||||||
Color(0xffE4FFF6),
|
|
||||||
Color(0xffF7FFFC),
|
|
||||||
Color(0xffFFFFFF),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PropsResources? _activeDataCard(SocialChatUserProfile? profile) {
|
PropsResources? _activeDataCard(SocialChatUserProfile? profile) {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:yumi/app_localizations.dart';
|
import 'package:yumi/app_localizations.dart';
|
||||||
@ -343,6 +344,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
Timer? _onlineUsersPollingTimer;
|
Timer? _onlineUsersPollingTimer;
|
||||||
Timer? _roomEntryEffectTimer;
|
Timer? _roomEntryEffectTimer;
|
||||||
Timer? _roomRocketLaunchAnimationTimer;
|
Timer? _roomRocketLaunchAnimationTimer;
|
||||||
|
ValueNotifier<String?>? _roomRocketLaunchTopAvatarNotifier;
|
||||||
Timer? _roomRocketGiftRefreshTimer;
|
Timer? _roomRocketGiftRefreshTimer;
|
||||||
final List<Timer> _roomRocketPostLaunchRefreshTimers = [];
|
final List<Timer> _roomRocketPostLaunchRefreshTimers = [];
|
||||||
Timer? _roomRedPacketPresenceTimer;
|
Timer? _roomRedPacketPresenceTimer;
|
||||||
@ -490,6 +492,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
_roomEntryEffectTimer = null;
|
_roomEntryEffectTimer = null;
|
||||||
_roomRocketLaunchAnimationTimer?.cancel();
|
_roomRocketLaunchAnimationTimer?.cancel();
|
||||||
_roomRocketLaunchAnimationTimer = null;
|
_roomRocketLaunchAnimationTimer = null;
|
||||||
|
_releaseRoomRocketLaunchTopAvatarNotifier();
|
||||||
_roomRocketGiftRefreshTimer?.cancel();
|
_roomRocketGiftRefreshTimer?.cancel();
|
||||||
_roomRocketGiftRefreshTimer = null;
|
_roomRocketGiftRefreshTimer = null;
|
||||||
_cancelRoomRocketPostLaunchStatusRefresh();
|
_cancelRoomRocketPostLaunchStatusRefresh();
|
||||||
@ -2980,6 +2983,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_roomRocketLaunchAnimationTimer?.cancel();
|
_roomRocketLaunchAnimationTimer?.cancel();
|
||||||
|
_releaseRoomRocketLaunchTopAvatarNotifier();
|
||||||
|
|
||||||
if (shouldShowRoomVisualEffects && isVoiceRoomRouteVisible) {
|
if (shouldShowRoomVisualEffects && isVoiceRoomRouteVisible) {
|
||||||
final animationUrl =
|
final animationUrl =
|
||||||
@ -2997,6 +3001,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
_roomRocketLaunchAnimationTimer = Timer(launch.displayDuration, () {
|
_roomRocketLaunchAnimationTimer = Timer(launch.displayDuration, () {
|
||||||
_roomRocketLaunchAnimationTimer = null;
|
_roomRocketLaunchAnimationTimer = null;
|
||||||
SmartDialog.dismiss(tag: _roomRocketPagLaunchDialogTag);
|
SmartDialog.dismiss(tag: _roomRocketPagLaunchDialogTag);
|
||||||
|
_releaseRoomRocketLaunchTopAvatarNotifier();
|
||||||
unawaited(refreshRoomRocketStatus(roomId: launch.roomId));
|
unawaited(refreshRoomRocketStatus(roomId: launch.roomId));
|
||||||
scheduleRoomRocketPostLaunchStatusRefresh(roomId: launch.roomId);
|
scheduleRoomRocketPostLaunchStatusRefresh(roomId: launch.roomId);
|
||||||
});
|
});
|
||||||
@ -3022,6 +3027,11 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
required RoomRocketLaunchBroadcastMessage launch,
|
required RoomRocketLaunchBroadcastMessage launch,
|
||||||
}) {
|
}) {
|
||||||
if (RoomRocketPagEffectOverlay.isPag(animationUrl)) {
|
if (RoomRocketPagEffectOverlay.isPag(animationUrl)) {
|
||||||
|
final topAvatarNotifier = ValueNotifier<String?>(
|
||||||
|
_roomRocketTop1AvatarUrl(launch.safeLevel),
|
||||||
|
);
|
||||||
|
_roomRocketLaunchTopAvatarNotifier = topAvatarNotifier;
|
||||||
|
unawaited(_loadRoomRocketTop1Avatar(launch, topAvatarNotifier));
|
||||||
SmartDialog.dismiss(tag: _roomRocketPagLaunchDialogTag);
|
SmartDialog.dismiss(tag: _roomRocketPagLaunchDialogTag);
|
||||||
SmartDialog.show(
|
SmartDialog.show(
|
||||||
tag: _roomRocketPagLaunchDialogTag,
|
tag: _roomRocketPagLaunchDialogTag,
|
||||||
@ -3033,11 +3043,12 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
builder:
|
builder:
|
||||||
(_) => RoomRocketPagEffectOverlay(
|
(_) => RoomRocketPagEffectOverlay(
|
||||||
resource: animationUrl,
|
resource: animationUrl,
|
||||||
topAvatarUrl: _roomRocketTop1AvatarUrl(),
|
topAvatarUrlListenable: topAvatarNotifier,
|
||||||
onCompleted: () {
|
onCompleted: () {
|
||||||
SmartDialog.dismiss(tag: _roomRocketPagLaunchDialogTag);
|
SmartDialog.dismiss(tag: _roomRocketPagLaunchDialogTag);
|
||||||
_roomRocketLaunchAnimationTimer?.cancel();
|
_roomRocketLaunchAnimationTimer?.cancel();
|
||||||
_roomRocketLaunchAnimationTimer = null;
|
_roomRocketLaunchAnimationTimer = null;
|
||||||
|
_releaseRoomRocketLaunchTopAvatarNotifier(topAvatarNotifier);
|
||||||
unawaited(refreshRoomRocketStatus(roomId: launch.roomId));
|
unawaited(refreshRoomRocketStatus(roomId: launch.roomId));
|
||||||
scheduleRoomRocketPostLaunchStatusRefresh(
|
scheduleRoomRocketPostLaunchStatusRefresh(
|
||||||
roomId: launch.roomId,
|
roomId: launch.roomId,
|
||||||
@ -3054,8 +3065,21 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String? _roomRocketTop1AvatarUrl() {
|
String? _roomRocketTop1AvatarUrl(int level) {
|
||||||
final kings = roomRocketStatus?.rocketKings ?? const [];
|
final kings =
|
||||||
|
roomRocketStatus?.rocketKingsForLevel(level) ??
|
||||||
|
const <SCRoomRocketKingUserRes>[];
|
||||||
|
return _roomRocketTop1AvatarUrlFromUsers(kings);
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _roomRocketTop1AvatarUrlFromKing(SCRoomRocketKingRes king) {
|
||||||
|
final source = king.podium.isNotEmpty ? king.podium : king.displayRecords;
|
||||||
|
return _roomRocketTop1AvatarUrlFromUsers(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _roomRocketTop1AvatarUrlFromUsers(
|
||||||
|
List<SCRoomRocketKingUserRes> kings,
|
||||||
|
) {
|
||||||
if (kings.isEmpty) {
|
if (kings.isEmpty) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -3072,6 +3096,90 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _loadRoomRocketTop1Avatar(
|
||||||
|
RoomRocketLaunchBroadcastMessage launch,
|
||||||
|
ValueNotifier<String?> notifier,
|
||||||
|
) async {
|
||||||
|
final roomId = launch.roomId.trim();
|
||||||
|
if (roomId.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
_roomRocketDebug(
|
||||||
|
'request launch top1 avatar roomId=$roomId '
|
||||||
|
'level=${launch.safeLevel} roundNo=${launch.roundNo}',
|
||||||
|
);
|
||||||
|
var king = await SCChatRoomRepository().roomRocketKing(
|
||||||
|
roomId,
|
||||||
|
level: launch.safeLevel,
|
||||||
|
roundNo: launch.roundNo,
|
||||||
|
cursor: 1,
|
||||||
|
limit: 3,
|
||||||
|
);
|
||||||
|
var avatar = _roomRocketTop1AvatarUrlFromKing(king);
|
||||||
|
if (avatar == null && launch.roundNo > 0) {
|
||||||
|
_roomRocketDebug(
|
||||||
|
'launch top1 avatar retry without roundNo '
|
||||||
|
'level=${launch.safeLevel} roundNo=${launch.roundNo}',
|
||||||
|
);
|
||||||
|
king = await SCChatRoomRepository().roomRocketKing(
|
||||||
|
roomId,
|
||||||
|
level: launch.safeLevel,
|
||||||
|
cursor: 1,
|
||||||
|
limit: 3,
|
||||||
|
);
|
||||||
|
avatar = _roomRocketTop1AvatarUrlFromKing(king);
|
||||||
|
}
|
||||||
|
if (_roomRocketLaunchTopAvatarNotifier != notifier) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_roomRocketDebug(
|
||||||
|
'launch top1 avatar loaded level=${launch.safeLevel} '
|
||||||
|
'roundNo=${launch.roundNo} avatar=${_shortRoomRocketDebugUrl(avatar)} '
|
||||||
|
'podium=${king.podium.length} records=${king.records.length}',
|
||||||
|
);
|
||||||
|
if (avatar != null && avatar != notifier.value) {
|
||||||
|
notifier.value = avatar;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
_roomRocketDebug(
|
||||||
|
'launch top1 avatar load failed level=${launch.safeLevel} '
|
||||||
|
'roundNo=${launch.roundNo}: $error',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _releaseRoomRocketLaunchTopAvatarNotifier([
|
||||||
|
ValueNotifier<String?>? expected,
|
||||||
|
]) {
|
||||||
|
final notifier = _roomRocketLaunchTopAvatarNotifier;
|
||||||
|
if (notifier == null || (expected != null && notifier != expected)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_roomRocketLaunchTopAvatarNotifier = null;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
notifier.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _roomRocketDebug(String message) {
|
||||||
|
if (!kDebugMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debugPrint('[RoomRocket] $message');
|
||||||
|
}
|
||||||
|
|
||||||
|
String _shortRoomRocketDebugUrl(String? url) {
|
||||||
|
final value = url?.trim() ?? '';
|
||||||
|
if (value.isEmpty) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
if (value.length <= 96) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return '${value.substring(0, 48)}...${value.substring(value.length - 24)}';
|
||||||
|
}
|
||||||
|
|
||||||
RoomRocketRewardUserProfile? roomRocketRewardUserProfileForRecord(
|
RoomRocketRewardUserProfile? roomRocketRewardUserProfileForRecord(
|
||||||
SCRoomRocketRewardRecordRes record,
|
SCRoomRocketRewardRecordRes record,
|
||||||
) {
|
) {
|
||||||
@ -3620,6 +3728,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
_roomEntryEffectTimer = null;
|
_roomEntryEffectTimer = null;
|
||||||
_roomRocketLaunchAnimationTimer?.cancel();
|
_roomRocketLaunchAnimationTimer?.cancel();
|
||||||
_roomRocketLaunchAnimationTimer = null;
|
_roomRocketLaunchAnimationTimer = null;
|
||||||
|
_releaseRoomRocketLaunchTopAvatarNotifier();
|
||||||
_roomRocketGiftRefreshTimer?.cancel();
|
_roomRocketGiftRefreshTimer?.cancel();
|
||||||
_roomRocketGiftRefreshTimer = null;
|
_roomRocketGiftRefreshTimer = null;
|
||||||
_cancelRoomRocketPostLaunchStatusRefresh();
|
_cancelRoomRocketPostLaunchStatusRefresh();
|
||||||
|
|||||||
@ -23,6 +23,8 @@ class SCDataCardResourceView extends StatelessWidget {
|
|||||||
this.fit = BoxFit.cover,
|
this.fit = BoxFit.cover,
|
||||||
this.loop = true,
|
this.loop = true,
|
||||||
this.fallback,
|
this.fallback,
|
||||||
|
this.showCoverFallback = true,
|
||||||
|
this.allowCoverAsResource = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
final PropsResources? resource;
|
final PropsResources? resource;
|
||||||
@ -33,6 +35,8 @@ class SCDataCardResourceView extends StatelessWidget {
|
|||||||
final BoxFit fit;
|
final BoxFit fit;
|
||||||
final bool loop;
|
final bool loop;
|
||||||
final Widget? fallback;
|
final Widget? fallback;
|
||||||
|
final bool showCoverFallback;
|
||||||
|
final bool allowCoverAsResource;
|
||||||
|
|
||||||
static bool isSupported(String? url) {
|
static bool isSupported(String? url) {
|
||||||
return _isSvga(url) ||
|
return _isSvga(url) ||
|
||||||
@ -50,15 +54,22 @@ class SCDataCardResourceView extends StatelessWidget {
|
|||||||
final resolvedHeight = _resolvedSize(height, constraints.maxHeight);
|
final resolvedHeight = _resolvedSize(height, constraints.maxHeight);
|
||||||
final cover = _firstNonBlank([coverUrl, resource?.cover]);
|
final cover = _firstNonBlank([coverUrl, resource?.cover]);
|
||||||
final source = _firstNonBlank([sourceUrl, resource?.sourceUrl]);
|
final source = _firstNonBlank([sourceUrl, resource?.sourceUrl]);
|
||||||
final resourceUrl = source.isNotEmpty ? source : cover;
|
final resourceUrl =
|
||||||
|
source.isNotEmpty || !allowCoverAsResource ? source : cover;
|
||||||
|
final emptyFallback = SizedBox(
|
||||||
|
width: resolvedWidth,
|
||||||
|
height: resolvedHeight,
|
||||||
|
);
|
||||||
final fallbackView =
|
final fallbackView =
|
||||||
fallback ??
|
fallback ??
|
||||||
_buildImage(
|
(showCoverFallback
|
||||||
cover,
|
? _buildImage(
|
||||||
width: resolvedWidth,
|
cover,
|
||||||
height: resolvedHeight,
|
width: resolvedWidth,
|
||||||
fit: fit,
|
height: resolvedHeight,
|
||||||
);
|
fit: fit,
|
||||||
|
)
|
||||||
|
: emptyFallback);
|
||||||
|
|
||||||
if (resourceUrl.isEmpty) {
|
if (resourceUrl.isEmpty) {
|
||||||
return fallbackView;
|
return fallbackView;
|
||||||
@ -330,10 +341,12 @@ class _LoopingDataCardVapVideo extends StatefulWidget {
|
|||||||
|
|
||||||
class _LoopingDataCardVapVideoState extends State<_LoopingDataCardVapVideo> {
|
class _LoopingDataCardVapVideoState extends State<_LoopingDataCardVapVideo> {
|
||||||
static const int _legacyRgbLeftAlphaRightMode = 3;
|
static const int _legacyRgbLeftAlphaRightMode = 3;
|
||||||
|
static const int _longRunningLoopCount = 999999;
|
||||||
|
|
||||||
VapController? _controller;
|
VapController? _controller;
|
||||||
int _playToken = 0;
|
int _playToken = 0;
|
||||||
bool _failed = false;
|
bool _failed = false;
|
||||||
|
bool _started = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant _LoopingDataCardVapVideo oldWidget) {
|
void didUpdateWidget(covariant _LoopingDataCardVapVideo oldWidget) {
|
||||||
@ -343,8 +356,11 @@ class _LoopingDataCardVapVideoState extends State<_LoopingDataCardVapVideo> {
|
|||||||
oldWidget.fit != widget.fit) {
|
oldWidget.fit != widget.fit) {
|
||||||
_playToken++;
|
_playToken++;
|
||||||
_stop();
|
_stop();
|
||||||
if (_failed) {
|
if (_failed || _started) {
|
||||||
setState(() => _failed = false);
|
setState(() {
|
||||||
|
_failed = false;
|
||||||
|
_started = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
_play();
|
_play();
|
||||||
}
|
}
|
||||||
@ -364,8 +380,11 @@ class _LoopingDataCardVapVideoState extends State<_LoopingDataCardVapVideo> {
|
|||||||
onFailed: (_, __, ___) => _markFailed(),
|
onFailed: (_, __, ___) => _markFailed(),
|
||||||
onVideoComplete: _handleVideoComplete,
|
onVideoComplete: _handleVideoComplete,
|
||||||
onVideoStart: () {
|
onVideoStart: () {
|
||||||
if (mounted && _failed) {
|
if (mounted && (_failed || !_started)) {
|
||||||
setState(() => _failed = false);
|
setState(() {
|
||||||
|
_failed = false;
|
||||||
|
_started = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -404,7 +423,7 @@ class _LoopingDataCardVapVideoState extends State<_LoopingDataCardVapVideo> {
|
|||||||
await controller.setMute(true);
|
await controller.setMute(true);
|
||||||
await controller.enableVersion1(true);
|
await controller.enableVersion1(true);
|
||||||
await controller.setVideoMode(_legacyRgbLeftAlphaRightMode);
|
await controller.setVideoMode(_legacyRgbLeftAlphaRightMode);
|
||||||
await controller.setLoop(widget.loop ? -1 : 0);
|
await controller.setLoop(widget.loop ? _longRunningLoopCount : 0);
|
||||||
await controller.setScaleType(_scaleTypeForFit(widget.fit).key);
|
await controller.setScaleType(_scaleTypeForFit(widget.fit).key);
|
||||||
if (!_isCurrent(token, controller)) {
|
if (!_isCurrent(token, controller)) {
|
||||||
return;
|
return;
|
||||||
@ -465,7 +484,10 @@ class _LoopingDataCardVapVideoState extends State<_LoopingDataCardVapVideo> {
|
|||||||
if (!mounted || _failed) {
|
if (!mounted || _failed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(() => _failed = true);
|
setState(() {
|
||||||
|
_failed = true;
|
||||||
|
_started = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ScaleType _scaleTypeForFit(BoxFit fit) {
|
ScaleType _scaleTypeForFit(BoxFit fit) {
|
||||||
@ -492,11 +514,14 @@ class _LoopingDataCardVapVideoState extends State<_LoopingDataCardVapVideo> {
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
widget.fallback,
|
if (_failed || !_started)
|
||||||
|
widget.fallback
|
||||||
|
else
|
||||||
|
const ColoredBox(color: Colors.black),
|
||||||
if (!_failed)
|
if (!_failed)
|
||||||
VapView(
|
VapView(
|
||||||
scaleType: _scaleTypeForFit(widget.fit),
|
scaleType: _scaleTypeForFit(widget.fit),
|
||||||
repeat: widget.loop ? -1 : 0,
|
repeat: widget.loop ? _longRunningLoopCount : 0,
|
||||||
mute: true,
|
mute: true,
|
||||||
onViewCreated: _handleViewCreated,
|
onViewCreated: _handleViewCreated,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -11,17 +11,21 @@ import 'package:yumi/ui_kit/components/custom_cached_image.dart';
|
|||||||
import 'package:yumi/ui_kit/components/sc_rotating_dots_loading.dart';
|
import 'package:yumi/ui_kit/components/sc_rotating_dots_loading.dart';
|
||||||
|
|
||||||
const Duration _pagMemoryTtl = Duration(minutes: 5);
|
const Duration _pagMemoryTtl = Duration(minutes: 5);
|
||||||
|
const Size _roomRocketFallbackPagRawSize = Size(750, 1624);
|
||||||
|
const Rect _roomRocketTopAvatarRawRect = Rect.fromLTWH(291, 164, 168, 168);
|
||||||
|
|
||||||
class RoomRocketPagEffectOverlay extends StatefulWidget {
|
class RoomRocketPagEffectOverlay extends StatefulWidget {
|
||||||
const RoomRocketPagEffectOverlay({
|
const RoomRocketPagEffectOverlay({
|
||||||
super.key,
|
super.key,
|
||||||
required this.resource,
|
required this.resource,
|
||||||
this.topAvatarUrl,
|
this.topAvatarUrl,
|
||||||
|
this.topAvatarUrlListenable,
|
||||||
this.onCompleted,
|
this.onCompleted,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String resource;
|
final String resource;
|
||||||
final String? topAvatarUrl;
|
final String? topAvatarUrl;
|
||||||
|
final ValueListenable<String?>? topAvatarUrlListenable;
|
||||||
final VoidCallback? onCompleted;
|
final VoidCallback? onCompleted;
|
||||||
|
|
||||||
static bool isPag(String? resource) {
|
static bool isPag(String? resource) {
|
||||||
@ -50,6 +54,7 @@ class RoomRocketPagEffectOverlay extends StatefulWidget {
|
|||||||
class _RoomRocketPagEffectOverlayState
|
class _RoomRocketPagEffectOverlayState
|
||||||
extends State<RoomRocketPagEffectOverlay> {
|
extends State<RoomRocketPagEffectOverlay> {
|
||||||
bool _completed = false;
|
bool _completed = false;
|
||||||
|
Size? _pagRawSize;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -65,10 +70,7 @@ class _RoomRocketPagEffectOverlayState
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
_RoomRocketPagTopAvatar(
|
_buildTopAvatar(size),
|
||||||
avatarUrl: widget.topAvatarUrl,
|
|
||||||
canvasSize: size,
|
|
||||||
),
|
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: _RoomRocketPagView(
|
child: _RoomRocketPagView(
|
||||||
resource: resource,
|
resource: resource,
|
||||||
@ -79,6 +81,7 @@ class _RoomRocketPagEffectOverlayState
|
|||||||
displayScale: 1,
|
displayScale: 1,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
memoryTtl: _pagMemoryTtl,
|
memoryTtl: _pagMemoryTtl,
|
||||||
|
onReady: _updatePagRawSize,
|
||||||
onCompleted: _complete,
|
onCompleted: _complete,
|
||||||
defaultBuilder:
|
defaultBuilder:
|
||||||
(_) => const Center(child: SCRotatingDotsLoading()),
|
(_) => const Center(child: SCRotatingDotsLoading()),
|
||||||
@ -99,16 +102,48 @@ class _RoomRocketPagEffectOverlayState
|
|||||||
_completed = true;
|
_completed = true;
|
||||||
widget.onCompleted?.call();
|
widget.onCompleted?.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _updatePagRawSize(Size rawSize) {
|
||||||
|
if (_pagRawSize == rawSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() => _pagRawSize = rawSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTopAvatar(Size canvasSize) {
|
||||||
|
final listenable = widget.topAvatarUrlListenable;
|
||||||
|
if (listenable == null) {
|
||||||
|
return _RoomRocketPagTopAvatar(
|
||||||
|
avatarUrl: widget.topAvatarUrl,
|
||||||
|
canvasSize: canvasSize,
|
||||||
|
pagRawSize: _pagRawSize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ValueListenableBuilder<String?>(
|
||||||
|
valueListenable: listenable,
|
||||||
|
builder:
|
||||||
|
(context, avatarUrl, _) => _RoomRocketPagTopAvatar(
|
||||||
|
avatarUrl: _firstNonBlankPagAvatar([
|
||||||
|
avatarUrl,
|
||||||
|
widget.topAvatarUrl,
|
||||||
|
]),
|
||||||
|
canvasSize: canvasSize,
|
||||||
|
pagRawSize: _pagRawSize,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RoomRocketPagTopAvatar extends StatelessWidget {
|
class _RoomRocketPagTopAvatar extends StatelessWidget {
|
||||||
const _RoomRocketPagTopAvatar({
|
const _RoomRocketPagTopAvatar({
|
||||||
required this.avatarUrl,
|
required this.avatarUrl,
|
||||||
required this.canvasSize,
|
required this.canvasSize,
|
||||||
|
required this.pagRawSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String? avatarUrl;
|
final String? avatarUrl;
|
||||||
final Size canvasSize;
|
final Size canvasSize;
|
||||||
|
final Size? pagRawSize;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -116,12 +151,13 @@ class _RoomRocketPagTopAvatar extends StatelessWidget {
|
|||||||
if (url.isEmpty) {
|
if (url.isEmpty) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
final size = (canvasSize.width * 0.15).clamp(46.0, 66.0).toDouble();
|
final rect = _mapRoomRocketPagRawRectToCoverCanvas(
|
||||||
return Positioned(
|
rawRect: _roomRocketTopAvatarRawRect,
|
||||||
left: (canvasSize.width - size) / 2,
|
rawSize: pagRawSize ?? _roomRocketFallbackPagRawSize,
|
||||||
top: canvasSize.height * 0.098,
|
canvasSize: canvasSize,
|
||||||
width: size,
|
);
|
||||||
height: size,
|
return Positioned.fromRect(
|
||||||
|
rect: rect,
|
||||||
child: ClipOval(
|
child: ClipOval(
|
||||||
child: CustomCachedImage(
|
child: CustomCachedImage(
|
||||||
imageUrl: url,
|
imageUrl: url,
|
||||||
@ -200,6 +236,7 @@ class _RoomRocketPagView extends StatefulWidget {
|
|||||||
required this.clipBehavior,
|
required this.clipBehavior,
|
||||||
required this.memoryTtl,
|
required this.memoryTtl,
|
||||||
this.underlayBuilder,
|
this.underlayBuilder,
|
||||||
|
this.onReady,
|
||||||
this.onCompleted,
|
this.onCompleted,
|
||||||
this.defaultBuilder,
|
this.defaultBuilder,
|
||||||
});
|
});
|
||||||
@ -213,6 +250,7 @@ class _RoomRocketPagView extends StatefulWidget {
|
|||||||
final Clip clipBehavior;
|
final Clip clipBehavior;
|
||||||
final Duration memoryTtl;
|
final Duration memoryTtl;
|
||||||
final WidgetBuilder? underlayBuilder;
|
final WidgetBuilder? underlayBuilder;
|
||||||
|
final ValueChanged<Size>? onReady;
|
||||||
final VoidCallback? onCompleted;
|
final VoidCallback? onCompleted;
|
||||||
final WidgetBuilder? defaultBuilder;
|
final WidgetBuilder? defaultBuilder;
|
||||||
|
|
||||||
@ -414,6 +452,7 @@ class _RoomRocketPagViewState extends State<_RoomRocketPagView> {
|
|||||||
'viewSize=${widget.width?.toStringAsFixed(1)}x'
|
'viewSize=${widget.width?.toStringAsFixed(1)}x'
|
||||||
'${widget.height?.toStringAsFixed(1)} fit=${widget.fit.name}',
|
'${widget.height?.toStringAsFixed(1)} fit=${widget.fit.name}',
|
||||||
);
|
);
|
||||||
|
widget.onReady?.call(Size(rawWidth, rawHeight));
|
||||||
unawaited(_debugLayers(state, rawWidth, rawHeight));
|
unawaited(_debugLayers(state, rawWidth, rawHeight));
|
||||||
if (!_initialized) {
|
if (!_initialized) {
|
||||||
setState(() => _initialized = true);
|
setState(() => _initialized = true);
|
||||||
@ -587,6 +626,55 @@ Timer? _schedulePagMemoryReleaseTimer(
|
|||||||
return Timer(memoryTtl, onRelease);
|
return Timer(memoryTtl, onRelease);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
Rect debugMapRoomRocketPagRawRectToCoverCanvas({
|
||||||
|
required Rect rawRect,
|
||||||
|
required Size rawSize,
|
||||||
|
required Size canvasSize,
|
||||||
|
}) {
|
||||||
|
return _mapRoomRocketPagRawRectToCoverCanvas(
|
||||||
|
rawRect: rawRect,
|
||||||
|
rawSize: rawSize,
|
||||||
|
canvasSize: canvasSize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect _mapRoomRocketPagRawRectToCoverCanvas({
|
||||||
|
required Rect rawRect,
|
||||||
|
required Size rawSize,
|
||||||
|
required Size canvasSize,
|
||||||
|
}) {
|
||||||
|
if (rawSize.width <= 0 ||
|
||||||
|
rawSize.height <= 0 ||
|
||||||
|
canvasSize.width <= 0 ||
|
||||||
|
canvasSize.height <= 0) {
|
||||||
|
return Rect.zero;
|
||||||
|
}
|
||||||
|
final scaleX = canvasSize.width / rawSize.width;
|
||||||
|
final scaleY = canvasSize.height / rawSize.height;
|
||||||
|
final scale = scaleX > scaleY ? scaleX : scaleY;
|
||||||
|
final fittedWidth = rawSize.width * scale;
|
||||||
|
final fittedHeight = rawSize.height * scale;
|
||||||
|
final offsetX = (canvasSize.width - fittedWidth) / 2;
|
||||||
|
final offsetY = (canvasSize.height - fittedHeight) / 2;
|
||||||
|
return Rect.fromLTWH(
|
||||||
|
offsetX + rawRect.left * scale,
|
||||||
|
offsetY + rawRect.top * scale,
|
||||||
|
rawRect.width * scale,
|
||||||
|
rawRect.height * scale,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _firstNonBlankPagAvatar(Iterable<String?> values) {
|
||||||
|
for (final value in values) {
|
||||||
|
final text = value?.trim() ?? '';
|
||||||
|
if (text.isNotEmpty) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
String debugNormalizeRoomRocketPagResource(String resource) {
|
String debugNormalizeRoomRocketPagResource(String resource) {
|
||||||
return _normalizePagResource(resource);
|
return _normalizePagResource(resource);
|
||||||
|
|||||||
@ -49,6 +49,11 @@ class RoomUserInfoCard extends StatefulWidget {
|
|||||||
class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
||||||
static const String _leaveMicActionIconAsset =
|
static const String _leaveMicActionIconAsset =
|
||||||
"sc_images/room/sc_icon_room_user_card_leave_mic.png";
|
"sc_images/room/sc_icon_room_user_card_leave_mic.png";
|
||||||
|
static const String _defaultProfileCardBackgroundAsset =
|
||||||
|
"sc_images/room/sc_bg_room_user_profile_card_default.png";
|
||||||
|
static const double _profileSheetHeightFactor = 0.4;
|
||||||
|
static const double _dataCardTranslateDownFactor = 0.4;
|
||||||
|
static const double _defaultBackgroundImageFactor = 0.68;
|
||||||
|
|
||||||
SocialChatUserProfileManager? userProvider;
|
SocialChatUserProfileManager? userProvider;
|
||||||
String roomId = "";
|
String roomId = "";
|
||||||
@ -85,7 +90,8 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final sheetHeight = MediaQuery.of(context).size.height * 0.6;
|
final sheetHeight =
|
||||||
|
MediaQuery.of(context).size.height * _profileSheetHeightFactor;
|
||||||
|
|
||||||
return Consumer<SocialChatUserProfileManager>(
|
return Consumer<SocialChatUserProfileManager>(
|
||||||
builder: (context, ref, child) {
|
builder: (context, ref, child) {
|
||||||
@ -123,19 +129,21 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
height: sheetHeight,
|
height: sheetHeight,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.vertical(top: Radius.circular(18.w)),
|
borderRadius: BorderRadius.vertical(top: Radius.circular(18.w)),
|
||||||
child: DecoratedBox(
|
child: Stack(
|
||||||
decoration: _sheetDecoration(),
|
children: [
|
||||||
child: Center(
|
Positioned.fill(child: _buildDefaultSheetBackground()),
|
||||||
child: Container(
|
Center(
|
||||||
width: 55.w,
|
child: Container(
|
||||||
height: 55.w,
|
width: 55.w,
|
||||||
decoration: BoxDecoration(
|
height: 55.w,
|
||||||
color: Colors.black26,
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(8.w),
|
color: Colors.black26,
|
||||||
|
borderRadius: BorderRadius.circular(8.w),
|
||||||
|
),
|
||||||
|
child: CupertinoActivityIndicator(color: Colors.white24),
|
||||||
),
|
),
|
||||||
child: CupertinoActivityIndicator(color: Colors.white24),
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -160,135 +168,209 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
ref.userCardInfo?.roomRole != SCRoomRolesType.HOMEOWNER.name &&
|
ref.userCardInfo?.roomRole != SCRoomRolesType.HOMEOWNER.name &&
|
||||||
(rtcProvider.isFz() || rtcProvider.isGL());
|
(rtcProvider.isFz() || rtcProvider.isGL());
|
||||||
|
|
||||||
|
final profileContent = _buildProfileContentLayer(
|
||||||
|
context: context,
|
||||||
|
ref: ref,
|
||||||
|
rtcProvider: rtcProvider,
|
||||||
|
profile: profile,
|
||||||
|
bottomPadding: bottomPadding,
|
||||||
|
isSelf: isSelf,
|
||||||
|
canLeaveMic: canLeaveMic,
|
||||||
|
currentMicIndex: currentMicIndex,
|
||||||
|
canShowReport: canShowReport,
|
||||||
|
canShowSetting: canShowSetting,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (dataCard == null) {
|
||||||
|
return SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: sheetHeight,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(18.w)),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned.fill(child: _buildDefaultSheetBackground()),
|
||||||
|
Positioned.fill(child: profileContent),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _buildDataCardFrame(
|
||||||
|
sheetHeight: sheetHeight,
|
||||||
|
dataCard: dataCard,
|
||||||
|
child: profileContent,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDataCardFrame({
|
||||||
|
required double sheetHeight,
|
||||||
|
required PropsResources dataCard,
|
||||||
|
required Widget child,
|
||||||
|
}) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: sheetHeight,
|
height: sheetHeight,
|
||||||
child: ClipRRect(
|
child: Stack(
|
||||||
borderRadius: BorderRadius.vertical(top: Radius.circular(18.w)),
|
clipBehavior: Clip.none,
|
||||||
child: Stack(
|
children: [
|
||||||
children: [
|
Positioned.fill(
|
||||||
Positioned.fill(child: _buildSheetBackground(dataCard)),
|
child: ClipRRect(
|
||||||
Positioned.fill(
|
borderRadius: BorderRadius.vertical(top: Radius.circular(18.w)),
|
||||||
child: SingleChildScrollView(
|
child: DecoratedBox(decoration: _sheetDecoration()),
|
||||||
physics: const ClampingScrollPhysics(),
|
|
||||||
padding: EdgeInsets.fromLTRB(
|
|
||||||
18.w,
|
|
||||||
16.w,
|
|
||||||
18.w,
|
|
||||||
bottomPadding + 10.w,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
_buildProfileHeader(context, profile, ref),
|
|
||||||
SizedBox(height: 6.w),
|
|
||||||
_buildInfoChips(context, profile),
|
|
||||||
SizedBox(height: 9.w),
|
|
||||||
_buildBadgeStrip(
|
|
||||||
profile?.id ?? widget.userId,
|
|
||||||
ref,
|
|
||||||
profile,
|
|
||||||
),
|
|
||||||
SizedBox(height: 10.w),
|
|
||||||
_buildActions(
|
|
||||||
context: context,
|
|
||||||
ref: ref,
|
|
||||||
rtcProvider: rtcProvider,
|
|
||||||
isSelf: isSelf,
|
|
||||||
canLeaveMic: canLeaveMic,
|
|
||||||
currentMicIndex: currentMicIndex,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (canShowReport)
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 14.w,
|
left: 0,
|
||||||
right: 16.w,
|
right: 0,
|
||||||
child: _buildHeaderIconButton(
|
top: sheetHeight * _dataCardTranslateDownFactor,
|
||||||
iconAsset: "sc_images/room/sc_icon_user_card_report.png",
|
height: sheetHeight,
|
||||||
onTap: () {
|
child: IgnorePointer(child: _buildSheetBackground(dataCard)),
|
||||||
final route =
|
),
|
||||||
"${SCMainRoute.report}?type=user&tageId=${widget.userId}";
|
Positioned.fill(
|
||||||
Navigator.of(context).pop();
|
child: ClipRRect(
|
||||||
SCNavigatorUtils.push(
|
borderRadius: BorderRadius.vertical(top: Radius.circular(18.w)),
|
||||||
navigatorKey.currentState?.context ?? context,
|
child: child,
|
||||||
route,
|
),
|
||||||
replace: false,
|
),
|
||||||
);
|
],
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (canShowSetting)
|
|
||||||
Positioned(
|
|
||||||
top: 14.w,
|
|
||||||
right: 52.w,
|
|
||||||
child: _buildHeaderIconButton(
|
|
||||||
iconAsset:
|
|
||||||
"sc_images/room/sc_icon_room_user_card_setting.png",
|
|
||||||
onTap: () {
|
|
||||||
if (userProvider?.userCardInfo == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
showBottomInBottomDialog(
|
|
||||||
navigatorKey.currentState?.context ?? context,
|
|
||||||
RoomUserCardSetting(
|
|
||||||
roomId: roomId,
|
|
||||||
userCardInfo: userProvider?.userCardInfo?.copyWith(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildProfileContentLayer({
|
||||||
|
required BuildContext context,
|
||||||
|
required SocialChatUserProfileManager ref,
|
||||||
|
required RtcProvider rtcProvider,
|
||||||
|
required SocialChatUserProfile? profile,
|
||||||
|
required double bottomPadding,
|
||||||
|
required bool isSelf,
|
||||||
|
required bool canLeaveMic,
|
||||||
|
required num currentMicIndex,
|
||||||
|
required bool canShowReport,
|
||||||
|
required bool canShowSetting,
|
||||||
|
}) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Positioned.fill(
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
child: SizedBox(
|
||||||
|
width: constraints.maxWidth,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(
|
||||||
|
18.w,
|
||||||
|
10.w,
|
||||||
|
18.w,
|
||||||
|
bottomPadding > 6.w ? bottomPadding : 6.w,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_buildProfileHeader(context, profile, ref),
|
||||||
|
SizedBox(height: 6.w),
|
||||||
|
_buildBadgeStrip(
|
||||||
|
profile?.id ?? widget.userId,
|
||||||
|
ref,
|
||||||
|
profile,
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.w),
|
||||||
|
_buildActions(
|
||||||
|
context: context,
|
||||||
|
ref: ref,
|
||||||
|
rtcProvider: rtcProvider,
|
||||||
|
isSelf: isSelf,
|
||||||
|
canLeaveMic: canLeaveMic,
|
||||||
|
currentMicIndex: currentMicIndex,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (canShowReport)
|
||||||
|
Positioned(
|
||||||
|
top: 14.w,
|
||||||
|
right: 16.w,
|
||||||
|
child: _buildHeaderIconButton(
|
||||||
|
iconAsset: "sc_images/room/sc_icon_user_card_report.png",
|
||||||
|
onTap: () {
|
||||||
|
final route =
|
||||||
|
"${SCMainRoute.report}?type=user&tageId=${widget.userId}";
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
SCNavigatorUtils.push(
|
||||||
|
navigatorKey.currentState?.context ?? context,
|
||||||
|
route,
|
||||||
|
replace: false,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (canShowSetting)
|
||||||
|
Positioned(
|
||||||
|
top: 14.w,
|
||||||
|
right: 52.w,
|
||||||
|
child: _buildHeaderIconButton(
|
||||||
|
iconAsset: "sc_images/room/sc_icon_room_user_card_setting.png",
|
||||||
|
onTap: () {
|
||||||
|
if (userProvider?.userCardInfo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
showBottomInBottomDialog(
|
||||||
|
navigatorKey.currentState?.context ?? context,
|
||||||
|
RoomUserCardSetting(
|
||||||
|
roomId: roomId,
|
||||||
|
userCardInfo: userProvider?.userCardInfo?.copyWith(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
BoxDecoration _sheetDecoration() {
|
BoxDecoration _sheetDecoration() {
|
||||||
return const BoxDecoration(
|
return const BoxDecoration(color: Color(0xff061F1E));
|
||||||
gradient: LinearGradient(
|
}
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
Widget _buildDefaultSheetBackground() {
|
||||||
colors: [
|
return DecoratedBox(
|
||||||
Color(0xff18F2B1),
|
decoration: _sheetDecoration(),
|
||||||
Color(0xffE4FFF6),
|
child: Center(
|
||||||
Color(0xffF7FFFC),
|
child: FractionallySizedBox(
|
||||||
Color(0xffFFFFFF),
|
widthFactor: _defaultBackgroundImageFactor,
|
||||||
],
|
heightFactor: _defaultBackgroundImageFactor,
|
||||||
|
child: Image.asset(
|
||||||
|
_defaultProfileCardBackgroundAsset,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSheetBackground(PropsResources? dataCard) {
|
Widget _buildSheetBackground(PropsResources? dataCard) {
|
||||||
if (dataCard == null) {
|
if (dataCard == null) {
|
||||||
return DecoratedBox(decoration: _sheetDecoration());
|
return _buildDefaultSheetBackground();
|
||||||
}
|
}
|
||||||
return Stack(
|
|
||||||
fit: StackFit.expand,
|
return SCDataCardResourceView(
|
||||||
children: [
|
resource: dataCard,
|
||||||
SCDataCardResourceView(
|
fit: BoxFit.cover,
|
||||||
resource: dataCard,
|
showCoverFallback: false,
|
||||||
fit: BoxFit.fill,
|
allowCoverAsResource: false,
|
||||||
fallback: DecoratedBox(decoration: _sheetDecoration()),
|
|
||||||
),
|
|
||||||
DecoratedBox(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
|
||||||
colors: [
|
|
||||||
Colors.white.withValues(alpha: 0.08),
|
|
||||||
Colors.white.withValues(alpha: 0.22),
|
|
||||||
Colors.white.withValues(alpha: 0.58),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,8 +411,8 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
onTap: () => _openProfile(context),
|
onTap: () => _openProfile(context),
|
||||||
child: head(
|
child: head(
|
||||||
url: profile?.userAvatar ?? "",
|
url: profile?.userAvatar ?? "",
|
||||||
width: 70.w,
|
width: 105.w,
|
||||||
border: Border.all(color: Colors.white, width: 2),
|
border: Border.all(color: Colors.white, width: 3),
|
||||||
headdress: profile?.getHeaddress()?.sourceUrl,
|
headdress: profile?.getHeaddress()?.sourceUrl,
|
||||||
headdressCover: profile?.getHeaddress()?.cover,
|
headdressCover: profile?.getHeaddress()?.cover,
|
||||||
),
|
),
|
||||||
@ -349,7 +431,7 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
maxWidth: 116.w,
|
maxWidth: 116.w,
|
||||||
profile?.userNickname ?? "",
|
profile?.userNickname ?? "",
|
||||||
fontSize: 18.sp,
|
fontSize: 18.sp,
|
||||||
textColor: Colors.black,
|
textColor: Colors.white,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
type: "",
|
type: "",
|
||||||
needScroll: (profile?.userNickname?.characters.length ?? 0) > 12,
|
needScroll: (profile?.userNickname?.characters.length ?? 0) > 12,
|
||||||
@ -381,6 +463,22 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 2.w),
|
SizedBox(height: 2.w),
|
||||||
|
_buildSpecialIdMetaRow(context, profile),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSpecialIdMetaRow(
|
||||||
|
BuildContext context,
|
||||||
|
SocialChatUserProfile? profile,
|
||||||
|
) {
|
||||||
|
final flagUrl = _countryFlagUrl(context, profile);
|
||||||
|
final hasSex = profile?.userSex != null;
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
SCSpecialIdBadge(
|
SCSpecialIdBadge(
|
||||||
idText: profile?.getID() ?? "",
|
idText: profile?.getID() ?? "",
|
||||||
showAnimated: profile?.hasSpecialId() ?? false,
|
showAnimated: profile?.hasSpecialId() ?? false,
|
||||||
@ -392,106 +490,62 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
showAnimatedGradientText:
|
showAnimatedGradientText:
|
||||||
profile?.shouldShowColoredSpecialIdText() ?? false,
|
profile?.shouldShowColoredSpecialIdText() ?? false,
|
||||||
animationTextStyle: TextStyle(
|
animationTextStyle: TextStyle(
|
||||||
color: Colors.black,
|
color: Colors.white,
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
normalTextStyle: TextStyle(
|
normalTextStyle: TextStyle(
|
||||||
color: Colors.black,
|
color: Colors.white,
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
animationFit: BoxFit.contain,
|
animationFit: BoxFit.contain,
|
||||||
),
|
),
|
||||||
|
if (flagUrl.isNotEmpty) ...[
|
||||||
|
SizedBox(width: 8.w),
|
||||||
|
_buildCountryFlag(flagUrl),
|
||||||
|
],
|
||||||
|
if (hasSex) ...[SizedBox(width: 7.w), _buildSexIcon(profile)],
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildInfoChips(BuildContext context, SocialChatUserProfile? profile) {
|
String _countryFlagUrl(BuildContext context, SocialChatUserProfile? profile) {
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
_buildCountryChip(context, profile),
|
|
||||||
SizedBox(width: 7.w),
|
|
||||||
_buildSexAgeChip(profile),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildCountryChip(
|
|
||||||
BuildContext context,
|
|
||||||
SocialChatUserProfile? profile,
|
|
||||||
) {
|
|
||||||
final countryName = profile?.countryName ?? "";
|
final countryName = profile?.countryName ?? "";
|
||||||
final flagUrl =
|
try {
|
||||||
Provider.of<SCAppGeneralManager>(
|
return Provider.of<SCAppGeneralManager>(
|
||||||
context,
|
context,
|
||||||
listen: false,
|
listen: false,
|
||||||
).findCountryByName(countryName)?.nationalFlag ??
|
).findCountryByName(countryName)?.nationalFlag ??
|
||||||
"";
|
"";
|
||||||
|
} catch (_) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Container(
|
Widget _buildCountryFlag(String flagUrl) {
|
||||||
constraints: BoxConstraints(maxWidth: 145.w),
|
final trimmedUrl = flagUrl.trim();
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 5.w),
|
if (trimmedUrl.isEmpty) {
|
||||||
decoration: BoxDecoration(
|
return const SizedBox.shrink();
|
||||||
color: Colors.white.withValues(alpha: 0.55),
|
}
|
||||||
borderRadius: BorderRadius.circular(16.w),
|
|
||||||
border: Border.all(
|
return ClipRRect(
|
||||||
color: const Color(0xff18F2B1).withValues(alpha: 0.18),
|
borderRadius: BorderRadius.circular(2.w),
|
||||||
),
|
child: netImage(
|
||||||
),
|
url: trimmedUrl,
|
||||||
child: Row(
|
width: 22.w,
|
||||||
mainAxisSize: MainAxisSize.min,
|
height: 15.w,
|
||||||
children: [
|
fit: BoxFit.cover,
|
||||||
netImage(
|
noDefaultImg: true,
|
||||||
url: flagUrl,
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(3.w)),
|
|
||||||
width: 19.w,
|
|
||||||
height: 14.w,
|
|
||||||
),
|
|
||||||
SizedBox(width: 5.w),
|
|
||||||
Flexible(
|
|
||||||
child: text(
|
|
||||||
countryName,
|
|
||||||
maxLines: 1,
|
|
||||||
textColor: Colors.black,
|
|
||||||
fontSize: 12.sp,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSexAgeChip(SocialChatUserProfile? profile) {
|
Widget _buildSexIcon(SocialChatUserProfile? profile) {
|
||||||
return Container(
|
if (profile?.userSex == null) {
|
||||||
height: 26.w,
|
return const SizedBox.shrink();
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10.w),
|
}
|
||||||
decoration: BoxDecoration(
|
return xb(profile?.userSex, height: 15.w, color: null);
|
||||||
image: DecorationImage(
|
|
||||||
image: AssetImage(
|
|
||||||
profile?.userSex == 0
|
|
||||||
? "sc_images/login/sc_icon_sex_woman_bg.png"
|
|
||||||
: "sc_images/login/sc_icon_sex_man_bg.png",
|
|
||||||
),
|
|
||||||
fit: BoxFit.fill,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
xb(profile?.userSex),
|
|
||||||
SizedBox(width: 3.w),
|
|
||||||
text(
|
|
||||||
"${profile?.age ?? 0}",
|
|
||||||
textColor: Colors.white,
|
|
||||||
fontSize: 12.sp,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBadgeStrip(
|
Widget _buildBadgeStrip(
|
||||||
@ -502,12 +556,12 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
return SCUserBadgeStrip(
|
return SCUserBadgeStrip(
|
||||||
userId: userId,
|
userId: userId,
|
||||||
fallbackBadges: _roomCardFallbackBadges(ref, profile),
|
fallbackBadges: _roomCardFallbackBadges(ref, profile),
|
||||||
height: 82.w,
|
height: 70.w,
|
||||||
badgeHeight: 34.5.w,
|
badgeHeight: 57.5.w,
|
||||||
longBadgeWidth: 87.w,
|
longBadgeWidth: 145.w,
|
||||||
spacing: 10.w,
|
spacing: 20.w,
|
||||||
maxBadges: 12,
|
maxBadges: 12,
|
||||||
wrap: true,
|
wrap: false,
|
||||||
reserveSpace: true,
|
reserveSpace: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -525,7 +579,7 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
return wearBadges;
|
return wearBadges;
|
||||||
}
|
}
|
||||||
return ref.userCardInfo?.useBadge
|
return ref.userCardInfo?.useBadge
|
||||||
?.map(_roomUseBadgeToWearBadge)
|
?.map<WearBadge>(_roomUseBadgeToWearBadge)
|
||||||
.where(_hasBadgeUrl)
|
.where(_hasBadgeUrl)
|
||||||
.toList() ??
|
.toList() ??
|
||||||
const <WearBadge>[];
|
const <WearBadge>[];
|
||||||
@ -640,7 +694,7 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
label,
|
label,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
textColor: const Color(0xff666666),
|
textColor: Colors.white,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
lineHeight: 1.1,
|
lineHeight: 1.1,
|
||||||
@ -695,7 +749,7 @@ class _RoomUserInfoCardState extends State<RoomUserInfoCard> {
|
|||||||
: SCAppLocalizations.of(context)!.follow,
|
: SCAppLocalizations.of(context)!.follow,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
textColor: const Color(0xff666666),
|
textColor: Colors.white,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
fontSize: 12.sp,
|
fontSize: 12.sp,
|
||||||
lineHeight: 1.1,
|
lineHeight: 1.1,
|
||||||
|
|||||||
BIN
sc_images/room/sc_bg_room_user_profile_card_default.png
Normal file
BIN
sc_images/room/sc_bg_room_user_profile_card_default.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 311 KiB |
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:yumi/app/config/app_config.dart';
|
import 'package:yumi/app/config/app_config.dart';
|
||||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||||
import 'package:yumi/shared/data_sources/models/message/room_rocket_launch_broadcast_message.dart';
|
import 'package:yumi/shared/data_sources/models/message/room_rocket_launch_broadcast_message.dart';
|
||||||
@ -60,6 +61,19 @@ void main() {
|
|||||||
timer?.cancel();
|
timer?.cancel();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('maps top avatar frame from pag raw canvas using cover fit', () {
|
||||||
|
final rect = debugMapRoomRocketPagRawRectToCoverCanvas(
|
||||||
|
rawRect: const Rect.fromLTWH(291, 164, 168, 168),
|
||||||
|
rawSize: const Size(750, 1624),
|
||||||
|
canvasSize: const Size(392.7272727, 829.090909),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(rect.left, closeTo(152.38, 0.01));
|
||||||
|
expect(rect.top, closeTo(75.23, 0.01));
|
||||||
|
expect(rect.width, closeTo(87.97, 0.01));
|
||||||
|
expect(rect.height, closeTo(87.97, 0.01));
|
||||||
|
});
|
||||||
|
|
||||||
test('extracts launch pag urls from nested resource payloads', () {
|
test('extracts launch pag urls from nested resource payloads', () {
|
||||||
final launch = RoomRocketLaunchBroadcastMessage.fromJson({
|
final launch = RoomRocketLaunchBroadcastMessage.fromJson({
|
||||||
'rocketAnimationResource': {
|
'rocketAnimationResource': {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user