修改
This commit is contained in:
parent
d8ef488a28
commit
cbb811a18a
@ -49,7 +49,6 @@ import 'package:yumi/ui_kit/components/dialog/dialog_base.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
||||
import 'package:yumi/app/constants/sc_global_config.dart';
|
||||
import 'package:yumi/app/constants/sc_room_msg_type.dart';
|
||||
import 'package:yumi/app/config/business_logic_strategy.dart';
|
||||
import 'package:yumi/app/constants/sc_screen.dart';
|
||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
@ -61,12 +60,10 @@ 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/repositories/sc_vip_repository_imp.dart';
|
||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||
import 'package:yumi/services/audio/rtc_manager.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/room/cp/room_cp_invite_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/cp/room_cp_relation_formed_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
||||
import 'package:yumi/ui_kit/widgets/svga/sc_network_svga_widget.dart';
|
||||
|
||||
import '../../shared/business_logic/models/res/sc_system_invit_message_res.dart';
|
||||
@ -1764,91 +1761,14 @@ class _MessageItem extends StatelessWidget {
|
||||
return;
|
||||
}
|
||||
SmartDialog.dismiss(tag: RoomCpInviteDialog.dialogTag);
|
||||
final currentProfile = AccountStorage().getCurrentUser()?.userProfile;
|
||||
final rtmProvider = Provider.of<RtmProvider>(context, listen: false);
|
||||
if (agree) {
|
||||
unawaited(
|
||||
rtmProvider.sendCpRelationEstablishedSystemMessage(
|
||||
applyId: applyId,
|
||||
invite: invite,
|
||||
currentProfile: currentProfile,
|
||||
),
|
||||
);
|
||||
_sendCpRelationEstablishedRoomNotice(
|
||||
invite: invite,
|
||||
currentProfile: currentProfile,
|
||||
);
|
||||
} else {
|
||||
unawaited(
|
||||
rtmProvider.sendCpInviteRejectedSystemMessage(
|
||||
applyId: applyId,
|
||||
invite: invite,
|
||||
currentProfile: currentProfile,
|
||||
),
|
||||
);
|
||||
}
|
||||
_cpMessageLog(
|
||||
'process submitted applyId=$applyId agree=$agree '
|
||||
'wait_backend_result=true',
|
||||
);
|
||||
unawaited(profileManager.refreshLoadedCpProfiles());
|
||||
updateCall();
|
||||
}
|
||||
|
||||
void _sendCpRelationEstablishedRoomNotice({
|
||||
required SCSystemInvitMessageRes invite,
|
||||
required SocialChatUserProfile? currentProfile,
|
||||
}) {
|
||||
try {
|
||||
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
||||
final groupId =
|
||||
rtcProvider.currenRoom?.roomProfile?.roomProfile?.roomAccount
|
||||
?.trim() ??
|
||||
"";
|
||||
if (groupId.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final currentName = _cpDisplayName(
|
||||
nickname: currentProfile?.userNickname,
|
||||
account: currentProfile?.account,
|
||||
fallback: currentProfile?.id,
|
||||
);
|
||||
final inviteName = _cpDisplayName(
|
||||
nickname: invite.userNickname,
|
||||
account: invite.account,
|
||||
actualAccount: invite.actualAccount,
|
||||
);
|
||||
final relationType = scNormalizeCpRelationType(invite.relationType);
|
||||
Provider.of<RtmProvider>(context, listen: false).dispatchMessage(
|
||||
Msg(
|
||||
groupId: groupId,
|
||||
msg: scBuildCpRelationEstablishedNotice(
|
||||
userName1: inviteName,
|
||||
userName2: currentName,
|
||||
relationType: relationType,
|
||||
),
|
||||
type: SCRoomMsgType.cpSystemNotice,
|
||||
noticeAction: "RELATION_ESTABLISHED",
|
||||
user: currentProfile,
|
||||
toUser: SocialChatUserProfile(
|
||||
account: _firstCpInviteValue([
|
||||
invite.actualAccount,
|
||||
invite.account,
|
||||
]),
|
||||
userNickname: inviteName,
|
||||
userAvatar: invite.userAvatar ?? "",
|
||||
),
|
||||
targetUserIds: _compactCpTargetIds([
|
||||
currentProfile?.id,
|
||||
currentProfile?.account,
|
||||
invite.actualAccount,
|
||||
invite.account,
|
||||
]),
|
||||
role: relationType,
|
||||
),
|
||||
addLocal: true,
|
||||
);
|
||||
} catch (error) {
|
||||
_cpMessageLog('send room established notice skipped error=$error');
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic>? _customDataFromMessage(V2TimMessage msg) {
|
||||
if (msg.elemType != MessageElemType.V2TIM_ELEM_TYPE_CUSTOM) {
|
||||
return null;
|
||||
@ -1932,6 +1852,15 @@ class _MessageItem extends StatelessWidget {
|
||||
content["result"]?.toString() ?? "",
|
||||
content["state"]?.toString() ?? "",
|
||||
].map((value) => value.trim().toUpperCase()).join("|");
|
||||
final expiredValue = _cpInviteBoolValue([
|
||||
data["expired"],
|
||||
data["isExpired"],
|
||||
content["expired"],
|
||||
content["isExpired"],
|
||||
]);
|
||||
if (expiredValue == true) {
|
||||
return _CpInviteMessageState.expired;
|
||||
}
|
||||
final agreeValue = _cpInviteBoolValue([
|
||||
data["agree"],
|
||||
data["accepted"],
|
||||
@ -2157,24 +2086,6 @@ class _MessageItem extends StatelessWidget {
|
||||
return "User";
|
||||
}
|
||||
|
||||
List<String> _compactCpTargetIds(Iterable<String?> values) {
|
||||
return values
|
||||
.map((value) => value?.trim() ?? "")
|
||||
.where((value) => value.isNotEmpty)
|
||||
.toSet()
|
||||
.toList();
|
||||
}
|
||||
|
||||
String? _firstCpInviteValue(Iterable<String?> values) {
|
||||
for (final value in values) {
|
||||
final text = value?.trim() ?? "";
|
||||
if (text.isNotEmpty) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String _firstNonBlankString(
|
||||
Iterable<String?> values, {
|
||||
String fallback = "",
|
||||
|
||||
@ -26,7 +26,6 @@ import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart';
|
||||
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
|
||||
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/app/constants/sc_room_msg_type.dart';
|
||||
import 'package:yumi/ui_kit/components/appbar/socialchat_appbar.dart';
|
||||
import 'package:yumi/ui_kit/components/dialog/dialog_base.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||
@ -35,7 +34,6 @@ import 'package:yumi/app/constants/sc_screen.dart';
|
||||
import 'package:yumi/shared/tools/sc_date_utils.dart';
|
||||
import 'package:yumi/shared/tools/sc_cp_relation_notice_utils.dart';
|
||||
import 'package:yumi/services/general/sc_app_general_manager.dart';
|
||||
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||
import 'package:yumi/services/audio/rtm_manager.dart';
|
||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||
@ -44,11 +42,9 @@ import 'package:yumi/app/constants/sc_global_config.dart';
|
||||
import 'package:yumi/app/config/business_logic_strategy.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/cp/room_cp_relation_formed_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/cp/room_cp_invite_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
||||
|
||||
import '../../../shared/data_sources/models/enum/sc_sysytem_message_type.dart';
|
||||
import '../../../shared/business_logic/models/res/sc_system_invit_message_res.dart';
|
||||
import '../../../shared/business_logic/models/res/login_res.dart';
|
||||
|
||||
enum _CpInviteMessageState { pending, expired, rejected, accepted }
|
||||
|
||||
@ -103,10 +99,7 @@ class _MessageSystemPageState extends State<MessageSystemPage> {
|
||||
count: 100,
|
||||
lastMsgID: null,
|
||||
);
|
||||
List<V2TimMessage> messages =
|
||||
(v2timValueCallback.data ?? [])
|
||||
.where((message) => !_isCpBuildSystemMessage(message))
|
||||
.toList();
|
||||
List<V2TimMessage> messages = v2timValueCallback.data ?? [];
|
||||
// List<V2TimMessage> messages = await FTIM.getMessageManager().getMessages(conversation: currentConversation);
|
||||
currentConversationMessageList ??= [];
|
||||
currentConversationMessageList?.clear();
|
||||
@ -128,14 +121,6 @@ class _MessageSystemPageState extends State<MessageSystemPage> {
|
||||
return;
|
||||
}
|
||||
if (message.userID != currentConversation?.userID) return;
|
||||
if (_isCpBuildSystemMessage(message)) {
|
||||
debugPrint(
|
||||
'[CP][Invite] skip system conversation CP_BUILD '
|
||||
'msgId=${message.msgID}',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
int? index;
|
||||
for (var element in currentConversationMessageList) {
|
||||
if (msgId != null) {
|
||||
@ -193,26 +178,6 @@ class _MessageSystemPageState extends State<MessageSystemPage> {
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
bool _isCpBuildSystemMessage(V2TimMessage message) {
|
||||
if (message.elemType != MessageElemType.V2TIM_ELEM_TYPE_CUSTOM) {
|
||||
return false;
|
||||
}
|
||||
final content = message.customElem?.data;
|
||||
if (content == null || content.trim().isEmpty) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
final data = jsonDecode(content);
|
||||
if (data is! Map) {
|
||||
return false;
|
||||
}
|
||||
final noticeType = (data["noticeType"] ?? data["type"])?.toString();
|
||||
return noticeType == SCSysytemMessageType.CP_BUILD.name;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
@ -938,6 +903,29 @@ class _MessageItem extends StatelessWidget {
|
||||
content["result"]?.toString() ?? "",
|
||||
content["state"]?.toString() ?? "",
|
||||
].map((value) => value.trim().toUpperCase()).join("|");
|
||||
final expiredValue = _cpInviteBoolValue([
|
||||
data["expired"],
|
||||
data["isExpired"],
|
||||
content["expired"],
|
||||
content["isExpired"],
|
||||
]);
|
||||
if (expiredValue == true) {
|
||||
return _CpInviteMessageState.expired;
|
||||
}
|
||||
final agreeValue = _cpInviteBoolValue([
|
||||
data["agree"],
|
||||
data["accepted"],
|
||||
data["isAgree"],
|
||||
content["agree"],
|
||||
content["accepted"],
|
||||
content["isAgree"],
|
||||
]);
|
||||
if (agreeValue == true) {
|
||||
return _CpInviteMessageState.accepted;
|
||||
}
|
||||
if (agreeValue == false) {
|
||||
return _CpInviteMessageState.rejected;
|
||||
}
|
||||
if (values.contains("TIMEOUT") || values.contains("EXPIRE")) {
|
||||
return _CpInviteMessageState.expired;
|
||||
}
|
||||
@ -956,6 +944,30 @@ class _MessageItem extends StatelessWidget {
|
||||
return _CpInviteMessageState.pending;
|
||||
}
|
||||
|
||||
bool? _cpInviteBoolValue(Iterable<dynamic> values) {
|
||||
for (final value in values) {
|
||||
if (value is bool) {
|
||||
return value;
|
||||
}
|
||||
final text = value?.toString().trim().toLowerCase() ?? "";
|
||||
if (text.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
if (text == "true" || text == "1" || text == "yes" || text == "agree") {
|
||||
return true;
|
||||
}
|
||||
if (text == "false" ||
|
||||
text == "0" ||
|
||||
text == "no" ||
|
||||
text == "reject" ||
|
||||
text == "decline" ||
|
||||
text == "refuse") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
bool _isCpInvitePayload(Map<String, dynamic> data) {
|
||||
final content = _cpContentMap(data);
|
||||
final text =
|
||||
@ -1848,136 +1860,22 @@ class _MessageItem extends StatelessWidget {
|
||||
return null;
|
||||
}
|
||||
|
||||
String _cpNoticeDisplayName({
|
||||
String? nickname,
|
||||
String? account,
|
||||
String? actualAccount,
|
||||
String? fallback,
|
||||
}) {
|
||||
for (final value in [nickname, actualAccount, account, fallback]) {
|
||||
final text = value?.trim() ?? "";
|
||||
if (text.isNotEmpty) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
return "User";
|
||||
}
|
||||
|
||||
void _handleCpInviteAccepted(
|
||||
String applyId,
|
||||
SCSystemInvitMessageRes? invite,
|
||||
) {
|
||||
if (invite == null) {
|
||||
return;
|
||||
}
|
||||
final currentProfile = AccountStorage().getCurrentUser()?.userProfile;
|
||||
final currentName = _cpNoticeDisplayName(
|
||||
nickname: currentProfile?.userNickname,
|
||||
account: currentProfile?.account,
|
||||
fallback: currentProfile?.id,
|
||||
);
|
||||
final inviteName = _cpNoticeDisplayName(
|
||||
nickname: invite.userNickname,
|
||||
account: invite.account,
|
||||
actualAccount: invite.actualAccount,
|
||||
);
|
||||
final relationType = scNormalizeCpRelationType(invite.relationType);
|
||||
unawaited(
|
||||
Provider.of<RtmProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).sendCpRelationEstablishedSystemMessage(
|
||||
applyId: applyId,
|
||||
invite: invite,
|
||||
currentProfile: currentProfile,
|
||||
),
|
||||
);
|
||||
_sendCpRelationEstablishedRoomNotice(
|
||||
leftName: inviteName,
|
||||
rightName: currentName,
|
||||
relationType: relationType,
|
||||
currentProfile: currentProfile,
|
||||
inviteAccount: invite.account,
|
||||
inviteActualAccount: invite.actualAccount,
|
||||
inviteAvatar: invite.userAvatar ?? "",
|
||||
);
|
||||
}
|
||||
|
||||
void _sendCpRelationEstablishedRoomNotice({
|
||||
required String leftName,
|
||||
required String rightName,
|
||||
required String relationType,
|
||||
required SocialChatUserProfile? currentProfile,
|
||||
required String? inviteAccount,
|
||||
required String? inviteActualAccount,
|
||||
required String inviteAvatar,
|
||||
}) {
|
||||
try {
|
||||
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
||||
final groupId =
|
||||
rtcProvider.currenRoom?.roomProfile?.roomProfile?.roomAccount
|
||||
?.trim() ??
|
||||
"";
|
||||
if (groupId.isEmpty) {
|
||||
return;
|
||||
}
|
||||
Provider.of<RtmProvider>(context, listen: false).dispatchMessage(
|
||||
Msg(
|
||||
groupId: groupId,
|
||||
msg: scBuildCpRelationEstablishedNotice(
|
||||
userName1: leftName,
|
||||
userName2: rightName,
|
||||
relationType: relationType,
|
||||
),
|
||||
type: SCRoomMsgType.cpSystemNotice,
|
||||
noticeAction: "RELATION_ESTABLISHED",
|
||||
user: currentProfile,
|
||||
toUser: SocialChatUserProfile(
|
||||
account: _firstCpNoticeValue([inviteActualAccount, inviteAccount]),
|
||||
userNickname: leftName,
|
||||
userAvatar: inviteAvatar,
|
||||
),
|
||||
targetUserIds: _compactCpTargetIds([
|
||||
currentProfile?.id,
|
||||
currentProfile?.account,
|
||||
inviteActualAccount,
|
||||
inviteAccount,
|
||||
]),
|
||||
role: relationType,
|
||||
),
|
||||
addLocal: true,
|
||||
);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
List<String> _compactCpTargetIds(Iterable<String?> values) {
|
||||
return values
|
||||
.map((value) => value?.trim() ?? "")
|
||||
.where((value) => value.isNotEmpty)
|
||||
.toSet()
|
||||
.toList();
|
||||
}
|
||||
|
||||
String? _firstCpNoticeValue(Iterable<String?> values) {
|
||||
for (final value in values) {
|
||||
final text = value?.trim() ?? "";
|
||||
if (text.isNotEmpty) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> _acceptCpInviteDirect(
|
||||
String id,
|
||||
SCSystemInvitMessageRes? cpInvite,
|
||||
) async {
|
||||
final success = await Provider.of<SocialChatUserProfileManager>(
|
||||
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).cpRlationshipProcessApply(context, id, true);
|
||||
);
|
||||
final success = await profileManager.cpRlationshipProcessApply(
|
||||
context,
|
||||
id,
|
||||
true,
|
||||
);
|
||||
if (success && context.mounted) {
|
||||
_handleCpInviteAccepted(id, cpInvite);
|
||||
SmartDialog.dismiss(tag: RoomCpInviteDialog.dialogTag);
|
||||
unawaited(profileManager.refreshLoadedCpProfiles());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1985,21 +1883,18 @@ class _MessageItem extends StatelessWidget {
|
||||
String id,
|
||||
SCSystemInvitMessageRes? cpInvite,
|
||||
) async {
|
||||
final success = await Provider.of<SocialChatUserProfileManager>(
|
||||
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).cpRlationshipProcessApply(context, id, false);
|
||||
if (success && cpInvite != null && context.mounted) {
|
||||
unawaited(
|
||||
Provider.of<RtmProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).sendCpInviteRejectedSystemMessage(
|
||||
applyId: id,
|
||||
invite: cpInvite,
|
||||
currentProfile: AccountStorage().getCurrentUser()?.userProfile,
|
||||
),
|
||||
);
|
||||
);
|
||||
final success = await profileManager.cpRlationshipProcessApply(
|
||||
context,
|
||||
id,
|
||||
false,
|
||||
);
|
||||
if (success && context.mounted) {
|
||||
SmartDialog.dismiss(tag: RoomCpInviteDialog.dialogTag);
|
||||
unawaited(profileManager.refreshLoadedCpProfiles());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -25,7 +25,6 @@ import 'package:yumi/main.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:yumi/app/constants/sc_room_msg_type.dart';
|
||||
import 'package:yumi/app/constants/sc_screen.dart';
|
||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
import 'package:yumi/shared/tools/sc_dialog_utils.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/floating_screen_manager.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/gift_res.dart';
|
||||
@ -36,7 +35,6 @@ import 'package:yumi/services/audio/rtm_manager.dart';
|
||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||
import 'package:yumi/ui_kit/widgets/gift/sc_gift_combo_send_button.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
||||
import 'package:yumi/modules/wallet/wallet_route.dart';
|
||||
import 'package:yumi/modules/gift/gift_tab_page.dart';
|
||||
import 'package:yumi/modules/gift/cp_rights/cp_rights_guide_page.dart';
|
||||
import 'package:yumi/services/gift/room_gift_combo_send_controller.dart';
|
||||
@ -870,14 +868,7 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
children: [
|
||||
SizedBox(width: 15.w),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
SmartDialog.dismiss(tag: "showGiftControl");
|
||||
SCNavigatorUtils.push(
|
||||
navigatorKey.currentState!.context,
|
||||
WalletRoute.recharge,
|
||||
replace: false,
|
||||
);
|
||||
},
|
||||
onTap: _openRoomRechargeBottomSheet,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 8.w,
|
||||
@ -1569,7 +1560,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
animationCount: request.clickCount,
|
||||
);
|
||||
}
|
||||
_syncLocalCpInviteAfterGift(request);
|
||||
await _refreshGiftBackpack();
|
||||
_refreshCpStateAfterGift(request, profileManager);
|
||||
return;
|
||||
@ -1635,7 +1625,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
);
|
||||
}
|
||||
profileManager?.updateBalance(result);
|
||||
_syncLocalCpInviteAfterGift(request);
|
||||
_refreshCpStateAfterGift(request, profileManager);
|
||||
} catch (e) {
|
||||
final errorMessage =
|
||||
@ -1679,41 +1668,6 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
unawaited(profileManager?.refreshLoadedCpProfiles());
|
||||
}
|
||||
|
||||
void _syncLocalCpInviteAfterGift(_GiftSendRequest request) {
|
||||
final relationType = request.cpRelationType;
|
||||
if (relationType == null || request.acceptUserIds.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final context = navigatorKey.currentState?.context;
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
final receiverId = request.acceptUserIds.first.trim();
|
||||
if (receiverId.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final receiverProfile =
|
||||
request.acceptUsers.isNotEmpty
|
||||
? request.acceptUsers.first.user
|
||||
: SocialChatUserProfile(id: receiverId, account: receiverId);
|
||||
_giftFxLog(
|
||||
'sync local CP invite after gift '
|
||||
'receiver=$receiverId relationType=$relationType giftId=${request.gift.id}',
|
||||
);
|
||||
unawaited(
|
||||
Provider.of<RtmProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).insertLocalOutgoingCpGiftInvite(
|
||||
receiverId: receiverId,
|
||||
receiverProfile: receiverProfile,
|
||||
relationType: relationType,
|
||||
gift: request.gift,
|
||||
giftCount: request.quantity,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<MicRes>> _giveBackpackGiftToRecipients(
|
||||
SCChatRoomRepository repository,
|
||||
_GiftSendRequest request,
|
||||
@ -2044,6 +1998,17 @@ class _GiftPageState extends State<GiftPage> with TickerProviderStateMixin {
|
||||
});
|
||||
}
|
||||
|
||||
void _openRoomRechargeBottomSheet() {
|
||||
SmartDialog.dismiss(tag: "showGiftControl");
|
||||
Future<void>.delayed(const Duration(milliseconds: 80), () {
|
||||
final navigator = navigatorKey.currentState;
|
||||
if (navigator == null || !navigator.mounted) {
|
||||
return;
|
||||
}
|
||||
RoomRechargeBottomSheet.show(navigator.context);
|
||||
});
|
||||
}
|
||||
|
||||
void _openCpRightsGuide() {
|
||||
SmartDialog.dismiss(tag: "showGiftControl");
|
||||
Future<void>.delayed(const Duration(milliseconds: 80), () {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_compontent.dart';
|
||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||
@ -14,7 +13,6 @@ import 'package:yumi/shared/data_sources/sources/repositories/sc_store_repositor
|
||||
import 'package:yumi/shared/business_logic/models/res/bags_list_res.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||
import 'package:yumi/ui_kit/widgets/bag/props_bag_chatbox_detail_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/countdown_timer.dart';
|
||||
import 'package:yumi/modules/store/store_route.dart';
|
||||
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
||||
@ -237,27 +235,6 @@ class _BagsChatboxPageState
|
||||
),
|
||||
),
|
||||
),
|
||||
PositionedDirectional(
|
||||
bottom: 5.w,
|
||||
end: 5.w,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
selecteChatbox = res;
|
||||
setState(() {});
|
||||
_showDetail(res);
|
||||
},
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(5.w),
|
||||
child: Image.asset(
|
||||
"sc_images/store/sc_icon_shop_item_search.png",
|
||||
width: 18.w,
|
||||
height: 18.w,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -305,21 +282,6 @@ class _BagsChatboxPageState
|
||||
}
|
||||
}
|
||||
|
||||
void _showDetail(BagsListRes res) {
|
||||
SmartDialog.show(
|
||||
tag: "showPropsDetail",
|
||||
alignment: Alignment.center,
|
||||
animationType: SmartAnimationType.fade,
|
||||
builder: (_) {
|
||||
return PropsBagChatboxDetailDialog(res);
|
||||
},
|
||||
onDismiss: () {
|
||||
myChatbox = AccountStorage().getChatbox();
|
||||
setState(() {});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> _use(BagsListRes res, bool unload) async {
|
||||
SCLoadingManager.show(context: context);
|
||||
final localizations = SCAppLocalizations.of(context)!;
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
@ -18,7 +17,6 @@ import 'package:yumi/ui_kit/components/sc_page_list.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
||||
import 'package:yumi/ui_kit/widgets/bag/props_bag_data_card_detail_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/countdown_timer.dart';
|
||||
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
||||
|
||||
@ -249,27 +247,6 @@ class BagsDataCardPageState
|
||||
),
|
||||
),
|
||||
),
|
||||
PositionedDirectional(
|
||||
bottom: 5.w,
|
||||
end: 5.w,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
selectedDataCard = res;
|
||||
setState(() {});
|
||||
_showDetail(res);
|
||||
},
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(5.w),
|
||||
child: Image.asset(
|
||||
"sc_images/store/sc_icon_shop_item_search.png",
|
||||
width: 18.w,
|
||||
height: 18.w,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -326,21 +303,6 @@ class BagsDataCardPageState
|
||||
}
|
||||
}
|
||||
|
||||
void _showDetail(BagsListRes res) {
|
||||
SmartDialog.show(
|
||||
tag: "showPropsDetail",
|
||||
alignment: Alignment.center,
|
||||
animationType: SmartAnimationType.fade,
|
||||
builder: (_) {
|
||||
return PropsBagDataCardDetailDialog(res);
|
||||
},
|
||||
onDismiss: () {
|
||||
myDataCard = AccountStorage().getDataCard();
|
||||
setState(() {});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> _use(BagsListRes res, bool unload) async {
|
||||
SCLoadingManager.show(context: context);
|
||||
final profileManager = Provider.of<SocialChatUserProfileManager>(
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
||||
@ -16,7 +15,6 @@ import 'package:yumi/ui_kit/components/sc_page_list.dart';
|
||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||||
import 'package:yumi/ui_kit/theme/socialchat_theme.dart';
|
||||
import 'package:yumi/ui_kit/widgets/bag/props_bag_headdress_detail_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/countdown_timer.dart';
|
||||
import 'package:yumi/ui_kit/widgets/store/store_bag_page_helpers.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@ -237,27 +235,6 @@ class _BagsHeaddressPageState
|
||||
),
|
||||
),
|
||||
),
|
||||
PositionedDirectional(
|
||||
bottom: 5.w,
|
||||
end: 5.w,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
selecteHeaddress = res;
|
||||
setState(() {});
|
||||
_showDetail(res);
|
||||
},
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(5.w),
|
||||
child: Image.asset(
|
||||
"sc_images/store/sc_icon_shop_item_search.png",
|
||||
width: 13.w,
|
||||
height: 13.w,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -306,21 +283,6 @@ class _BagsHeaddressPageState
|
||||
}
|
||||
}
|
||||
|
||||
void _showDetail(BagsListRes res) {
|
||||
SmartDialog.show(
|
||||
tag: "showPropsDetail",
|
||||
alignment: Alignment.center,
|
||||
animationType: SmartAnimationType.fade,
|
||||
builder: (_) {
|
||||
return PropsBagHeaddressDetailDialog(res);
|
||||
},
|
||||
onDismiss: () {
|
||||
myHeaddress = AccountStorage().getHeaddress();
|
||||
setState(() {});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> _use(BagsListRes res, bool unload) async {
|
||||
SCLoadingManager.show(context: context);
|
||||
final localizations = SCAppLocalizations.of(context)!;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -141,6 +141,7 @@ class SCFirstRechargeRewardItem {
|
||||
const SCFirstRechargeRewardItem({
|
||||
required this.id,
|
||||
required this.type,
|
||||
required this.typeName,
|
||||
required this.name,
|
||||
required this.content,
|
||||
required this.quantity,
|
||||
@ -152,33 +153,58 @@ class SCFirstRechargeRewardItem {
|
||||
factory SCFirstRechargeRewardItem.fromJson(dynamic json) {
|
||||
final map = _asMap(json);
|
||||
final content = _readString(map['content']);
|
||||
final explicitDays = _firstInt(map, const [
|
||||
final badgeText = _firstString(map, const [
|
||||
'badgeText',
|
||||
'cornerText',
|
||||
'tagText',
|
||||
'durationText',
|
||||
'validityText',
|
||||
]);
|
||||
final explicitDays = _firstDays(map, const [
|
||||
'days',
|
||||
'day',
|
||||
'durationDays',
|
||||
'validDays',
|
||||
'validityDays',
|
||||
'validity',
|
||||
'validDay',
|
||||
'expireDays',
|
||||
'effectiveDays',
|
||||
'duration',
|
||||
'validPeriod',
|
||||
]);
|
||||
return SCFirstRechargeRewardItem(
|
||||
id: _readString(map['id']),
|
||||
type: _readString(map['type']),
|
||||
typeName: _firstString(map, const [
|
||||
'typeName',
|
||||
'rewardTypeName',
|
||||
'itemTypeName',
|
||||
'propTypeName',
|
||||
'goodsTypeName',
|
||||
'categoryName',
|
||||
'displayTypeName',
|
||||
'typeLabel',
|
||||
'typeDesc',
|
||||
]),
|
||||
name: _readString(map['name']),
|
||||
content: content,
|
||||
quantity: _readInt(map['quantity']),
|
||||
cover: _readString(map['cover']),
|
||||
badgeText: _firstString(map, const [
|
||||
'badgeText',
|
||||
'cornerText',
|
||||
'tagText',
|
||||
'durationText',
|
||||
'validityText',
|
||||
]),
|
||||
days: explicitDays > 0 ? explicitDays : _readDaysFromText(content),
|
||||
badgeText: badgeText,
|
||||
days:
|
||||
explicitDays > 0
|
||||
? explicitDays
|
||||
: _firstPositiveInt([
|
||||
_readDaysFromText(badgeText),
|
||||
_readDaysFromText(content),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
final String id;
|
||||
final String type;
|
||||
final String typeName;
|
||||
final String name;
|
||||
final String content;
|
||||
final int quantity;
|
||||
@ -193,27 +219,37 @@ class SCFirstRechargeRewardItem {
|
||||
normalizedType.contains('CANDY');
|
||||
}
|
||||
|
||||
String get displayTitle {
|
||||
if (isCoinReward && quantity > 0) {
|
||||
return '${_formatNumber(quantity)} Coins';
|
||||
int get coinAmount {
|
||||
if (!isCoinReward) {
|
||||
return 0;
|
||||
}
|
||||
return _firstNonEmpty([name, content, quantity > 0 ? 'x$quantity' : '']);
|
||||
return _firstPositiveInt([
|
||||
quantity,
|
||||
_readAmountFromText(content),
|
||||
_readAmountFromText(name),
|
||||
_readAmountFromText(badgeText),
|
||||
]);
|
||||
}
|
||||
|
||||
String get displayTitle {
|
||||
if (isCoinReward) {
|
||||
final amount = coinAmount;
|
||||
return amount > 0 ? '${_formatNumber(amount)} Coins' : '';
|
||||
}
|
||||
return _firstNonEmpty([
|
||||
typeName,
|
||||
_formatRewardTypeName(type),
|
||||
name,
|
||||
content,
|
||||
quantity > 0 ? 'x$quantity' : '',
|
||||
]);
|
||||
}
|
||||
|
||||
String get displayBadgeText {
|
||||
if (isCoinReward) {
|
||||
return '';
|
||||
}
|
||||
if (badgeText.trim().isNotEmpty) {
|
||||
return badgeText.trim();
|
||||
}
|
||||
if (days > 0) {
|
||||
return '${days}Days';
|
||||
}
|
||||
if (quantity > 1) {
|
||||
return 'x$quantity';
|
||||
}
|
||||
return '';
|
||||
return '${days > 0 ? days : 1}d';
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,9 +306,18 @@ int _readInt(dynamic value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _firstInt(Map<String, dynamic> map, List<String> keys) {
|
||||
int _firstDays(Map<String, dynamic> map, List<String> keys) {
|
||||
for (final key in keys) {
|
||||
final value = _readInt(map[key]);
|
||||
final value = _readDaysValue(map[key]);
|
||||
if (value > 0) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _firstPositiveInt(List<int> values) {
|
||||
for (final value in values) {
|
||||
if (value > 0) {
|
||||
return value;
|
||||
}
|
||||
@ -300,6 +345,31 @@ String _firstNonEmpty(List<String> values) {
|
||||
return '';
|
||||
}
|
||||
|
||||
int _readDaysValue(dynamic value) {
|
||||
if (value is num) {
|
||||
return value.toInt();
|
||||
}
|
||||
final text = _readString(value);
|
||||
if (text.isEmpty) {
|
||||
return 0;
|
||||
}
|
||||
final numeric = int.tryParse(text);
|
||||
if (numeric != null) {
|
||||
return numeric;
|
||||
}
|
||||
return _readDaysFromText(text);
|
||||
}
|
||||
|
||||
int _readAmountFromText(String text) {
|
||||
final match = RegExp(r'\d[\d,\.]*').firstMatch(text);
|
||||
if (match == null) {
|
||||
return 0;
|
||||
}
|
||||
final normalized = (match.group(0) ?? '').replaceAll(',', '');
|
||||
final value = num.tryParse(normalized);
|
||||
return value == null ? 0 : value.round();
|
||||
}
|
||||
|
||||
String _readDiscountText(Map<String, dynamic> map) {
|
||||
for (final key in const [
|
||||
'discountText',
|
||||
@ -370,6 +440,21 @@ int _readDaysFromText(String text) {
|
||||
return int.tryParse(match.group(1) ?? '') ?? 0;
|
||||
}
|
||||
|
||||
String _formatRewardTypeName(String type) {
|
||||
final normalized = type.trim();
|
||||
if (normalized.isEmpty) {
|
||||
return '';
|
||||
}
|
||||
return normalized
|
||||
.split(RegExp(r'[_\-\s]+'))
|
||||
.where((segment) => segment.isNotEmpty)
|
||||
.map((segment) {
|
||||
final lower = segment.toLowerCase();
|
||||
return lower[0].toUpperCase() + lower.substring(1);
|
||||
})
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
String _formatNumber(num value) {
|
||||
final text = value.toInt().toString();
|
||||
final buffer = StringBuffer();
|
||||
|
||||
@ -88,13 +88,13 @@ class FirstRechargeDialogData {
|
||||
),
|
||||
FirstRechargeReward(
|
||||
title: 'Vehicle',
|
||||
badgeText: '3Days',
|
||||
badgeText: '3d',
|
||||
cover: _FirstRechargeAssets.rewardVehicle,
|
||||
style: FirstRechargeRewardStyle.prop,
|
||||
),
|
||||
FirstRechargeReward(
|
||||
title: 'Frame',
|
||||
badgeText: '3Days',
|
||||
badgeText: '3d',
|
||||
cover: _FirstRechargeAssets.rewardFrame,
|
||||
style: FirstRechargeRewardStyle.prop,
|
||||
),
|
||||
@ -113,13 +113,13 @@ class FirstRechargeDialogData {
|
||||
rewards: [
|
||||
FirstRechargeReward(
|
||||
title: 'Avatar Frame',
|
||||
badgeText: '7Days',
|
||||
badgeText: '7d',
|
||||
cover: _FirstRechargeAssets.rewardAvatarFrame,
|
||||
style: FirstRechargeRewardStyle.prop,
|
||||
),
|
||||
FirstRechargeReward(
|
||||
title: 'Room Frame',
|
||||
badgeText: '7Days',
|
||||
badgeText: '7d',
|
||||
cover: _FirstRechargeAssets.rewardFrame,
|
||||
style: FirstRechargeRewardStyle.prop,
|
||||
),
|
||||
@ -130,7 +130,7 @@ class FirstRechargeDialogData {
|
||||
),
|
||||
FirstRechargeReward(
|
||||
title: 'Vehicle',
|
||||
badgeText: '7Days',
|
||||
badgeText: '7d',
|
||||
cover: _FirstRechargeAssets.rewardVehicle,
|
||||
style: FirstRechargeRewardStyle.prop,
|
||||
),
|
||||
@ -142,7 +142,7 @@ class FirstRechargeDialogData {
|
||||
),
|
||||
FirstRechargeReward(
|
||||
title: 'Profile Frame',
|
||||
badgeText: '7Days',
|
||||
badgeText: '7d',
|
||||
cover: _FirstRechargeAssets.rewardAvatarFrame,
|
||||
style: FirstRechargeRewardStyle.prop,
|
||||
),
|
||||
@ -197,16 +197,16 @@ class FirstRechargeDialogData {
|
||||
|
||||
static bool _isDisplayableRewardItem(SCFirstRechargeRewardItem item) {
|
||||
if (item.isCoinReward) {
|
||||
return item.quantity > 0 || item.displayTitle.trim().isNotEmpty;
|
||||
return item.coinAmount > 0;
|
||||
}
|
||||
return item.name.trim().isNotEmpty || item.cover.trim().isNotEmpty;
|
||||
return item.displayTitle.trim().isNotEmpty || item.cover.trim().isNotEmpty;
|
||||
}
|
||||
|
||||
static String _rewardTitle(SCFirstRechargeRewardItem item) {
|
||||
if (item.isCoinReward) {
|
||||
return item.displayTitle;
|
||||
}
|
||||
return item.name.trim();
|
||||
return item.displayTitle;
|
||||
}
|
||||
|
||||
static String _formatPriceText(SCFirstRechargeLevelConfig level) {
|
||||
|
||||
@ -11,7 +11,6 @@ import 'package:yumi/main.dart';
|
||||
import 'package:yumi/modules/wallet/recharge/recharge_page.dart';
|
||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||
import 'package:yumi/services/payment/google_payment_manager.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/sc_product_config_res.dart';
|
||||
import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
||||
import 'package:yumi/shared/tools/sc_room_top_layer_guard.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_debounce_widget.dart';
|
||||
@ -279,8 +278,7 @@ class _RoomRechargeBottomSheetState extends State<RoomRechargeBottomSheet> {
|
||||
Widget _buildFallbackProductItem(int index) {
|
||||
return _RechargeProductCard(
|
||||
selected: index == _fallbackSelectedIndex,
|
||||
amount: '150,000',
|
||||
originalAmount: '100,000',
|
||||
amount: '100,000',
|
||||
price: 'USD 4.99',
|
||||
onTap: () => setState(() => _fallbackSelectedIndex = index),
|
||||
);
|
||||
@ -294,32 +292,12 @@ class _RoomRechargeBottomSheetState extends State<RoomRechargeBottomSheet> {
|
||||
final product = processor.productMap[productConfig.produc.id];
|
||||
return _RechargeProductCard(
|
||||
selected: productConfig.isSelecte,
|
||||
amount: _formatAmount(_totalCandy(product)),
|
||||
originalAmount: _formatOriginalAmount(product),
|
||||
amount: _formatAmount((product?.obtainCandy ?? 0).round()),
|
||||
price: productConfig.produc.price,
|
||||
onTap: () => processor.chooseProductConfig(index),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatOriginalAmount(SCProductConfigRes? product) {
|
||||
if (product == null) {
|
||||
return '';
|
||||
}
|
||||
final obtainCandy = (product.obtainCandy ?? 0).round();
|
||||
final rewardCandy = (product.rewardCandy ?? 0).round();
|
||||
if (obtainCandy <= 0 || rewardCandy <= 0) {
|
||||
return '';
|
||||
}
|
||||
return _formatAmount(obtainCandy);
|
||||
}
|
||||
|
||||
int _totalCandy(SCProductConfigRes? product) {
|
||||
if (product == null) {
|
||||
return 0;
|
||||
}
|
||||
return ((product.obtainCandy ?? 0) + (product.rewardCandy ?? 0)).round();
|
||||
}
|
||||
|
||||
String _formatAmount(int value) {
|
||||
final sign = value < 0 ? '-' : '';
|
||||
final digits = value.abs().toString();
|
||||
@ -415,14 +393,12 @@ class _RechargeProductCard extends StatelessWidget {
|
||||
const _RechargeProductCard({
|
||||
required this.selected,
|
||||
required this.amount,
|
||||
required this.originalAmount,
|
||||
required this.price,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final bool selected;
|
||||
final String amount;
|
||||
final String originalAmount;
|
||||
final String price;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@ -476,26 +452,6 @@ class _RechargeProductCard extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 4.w),
|
||||
Visibility(
|
||||
visible: originalAmount.isNotEmpty,
|
||||
maintainAnimation: true,
|
||||
maintainSize: true,
|
||||
maintainState: true,
|
||||
child: Text(
|
||||
originalAmount,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.6),
|
||||
fontSize: 10.sp,
|
||||
height: 1,
|
||||
fontWeight: FontWeight.w400,
|
||||
decoration: TextDecoration.lineThrough,
|
||||
decorationColor: Colors.white.withValues(alpha: 0.6),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
24
需求进度.md
24
需求进度.md
@ -1014,3 +1014,27 @@
|
||||
- [ ] 18:06:26 检查 iOS 模拟器设备,准备进行运行验证。
|
||||
|
||||
- [x] 18:06:55 已确认本机可用 iPhone 17 Pro 与 iPhone 17 Pro Max 模拟器,开始用 iPhone 17 Pro 编译运行验证。
|
||||
|
||||
- [ ] 20:27:16 新增调整:首充弹窗非金币奖励文本改用类型名称,左上角天数显示为 Xd,缺省 1d。
|
||||
|
||||
- [ ] 20:27:44 已定位当前实现:非金币标题取 name,角标取 badgeText/Days;开始确认接口字段并调整模型。
|
||||
|
||||
- [ ] 20:28:12 接口文档示例未包含 typeName,按兼容字段解析类型名称并保留 type 可读兜底。
|
||||
|
||||
- [x] 20:29:02 已修改首充奖励模型与 UI 映射:非金币标题优先类型名称,角标统一 Xd 且默认 1d。
|
||||
|
||||
- [ ] 20:29:12 开始格式化并检查首充奖励模型与弹窗文件。
|
||||
|
||||
- [ ] 20:29:34 静态检查发现旧的 _firstInt 已无引用,开始移除死代码。
|
||||
|
||||
- [x] 20:29:56 已完成格式化与静态检查:首充奖励模型、弹窗文件 flutter analyze 无问题。
|
||||
|
||||
- [ ] 20:38:55 反馈修正:金币类型标题必须显示金币金额,非金币才显示类型名称;开始调整解析兜底。
|
||||
|
||||
- [ ] 20:39:21 已确认金币 quantity 为空时会误用类型兜底;开始新增 coinAmount 并限制金币 item 只显示金额。
|
||||
|
||||
- [x] 20:39:46 已修正金币标题逻辑:金币只显示解析出的金额 Coins,非金币才走类型名称。
|
||||
|
||||
- [ ] 20:39:56 开始格式化并静态检查金币金额显示修正。
|
||||
|
||||
- [x] 20:41:16 金币金额显示修正已通过格式化与 flutter analyze 检查。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user