游戏 以及app id

This commit is contained in:
NIGGER SLAYER 2026-04-15 14:44:27 +08:00
parent 77ce33bac9
commit 456642ff04
18 changed files with 485 additions and 128 deletions

View File

@ -9,7 +9,7 @@ plugins {
} }
android { android {
namespace = "com.org.yumi" namespace = "com.org.yumiparty"
compileSdk = 36 compileSdk = 36
ndkVersion = "28.2.13676358" ndkVersion = "28.2.13676358"
@ -58,7 +58,7 @@ android {
manifestPlaceholders["appName"] = "Yumi" manifestPlaceholders["appName"] = "Yumi"
manifestPlaceholders["appIcon"] = "@mipmap/ic_launcher" manifestPlaceholders["appIcon"] = "@mipmap/ic_launcher"
manifestPlaceholders["appIconRound"] = "@mipmap/icon_logo_round" manifestPlaceholders["appIconRound"] = "@mipmap/icon_logo_round"
applicationId = "com.org.yumi" applicationId = "com.org.yumiparty"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config. // For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion minSdk = flutter.minSdkVersion

View File

@ -9,7 +9,7 @@
"client_info": { "client_info": {
"mobilesdk_app_id": "1:980005024266:android:581aa38059aa318d9c65f3", "mobilesdk_app_id": "1:980005024266:android:581aa38059aa318d9c65f3",
"android_client_info": { "android_client_info": {
"package_name": "com.org.yumi" "package_name": "com.org.yumiparty"
} }
}, },
"oauth_client": [ "oauth_client": [
@ -17,7 +17,7 @@
"client_id": "980005024266-o9pjdmdbqqt1julbh1q1ovafcvmr1mv1.apps.googleusercontent.com", "client_id": "980005024266-o9pjdmdbqqt1julbh1q1ovafcvmr1mv1.apps.googleusercontent.com",
"client_type": 1, "client_type": 1,
"android_info": { "android_info": {
"package_name": "com.org.yumi", "package_name": "com.org.yumiparty",
"certificate_hash": "3fe178dcc3294f7420df2754cfaf4646d6a19478" "certificate_hash": "3fe178dcc3294f7420df2754cfaf4646d6a19478"
} }
}, },
@ -25,7 +25,7 @@
"client_id": "980005024266-sl5h466pe90jsjmoi4jcd7bqhmckieec.apps.googleusercontent.com", "client_id": "980005024266-sl5h466pe90jsjmoi4jcd7bqhmckieec.apps.googleusercontent.com",
"client_type": 1, "client_type": 1,
"android_info": { "android_info": {
"package_name": "com.org.yumi", "package_name": "com.org.yumiparty",
"certificate_hash": "9ab21924bfbe2c56555860ebabf23c4099fd7412" "certificate_hash": "9ab21924bfbe2c56555860ebabf23c4099fd7412"
} }
}, },
@ -33,7 +33,7 @@
"client_id": "980005024266-vrrp2us89svbqgc93oe7q4ad7uoh8fl6.apps.googleusercontent.com", "client_id": "980005024266-vrrp2us89svbqgc93oe7q4ad7uoh8fl6.apps.googleusercontent.com",
"client_type": 1, "client_type": 1,
"android_info": { "android_info": {
"package_name": "com.org.yumi", "package_name": "com.org.yumiparty",
"certificate_hash": "c180afbf6e1ff2f449587287a2bc5407e7d2d9a3" "certificate_hash": "c180afbf6e1ff2f449587287a2bc5407e7d2d9a3"
} }
}, },

View File

@ -28,7 +28,7 @@
-keep class io.flutter.embedding.engine.deferredcomponents.** { *; } -keep class io.flutter.embedding.engine.deferredcomponents.** { *; }
# 应用特定 # 应用特定
-keep class com.org.yumi.** { *; } -keep class com.org.yumiparty.** { *; }
# Provider # Provider
-keep class * extends ChangeNotifier { *; } -keep class * extends ChangeNotifier { *; }
@ -36,4 +36,4 @@
# 其他保留 # 其他保留
-keepattributes *Annotation* -keepattributes *Annotation*
-keepattributes Signature -keepattributes Signature

View File

@ -1,13 +1,10 @@
package com.org.yumi package com.org.yumiparty
import android.content.Intent import io.flutter.embedding.android.FlutterActivity
import android.net.Uri import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine class MainActivity : FlutterActivity() {
import io.flutter.plugin.common.MethodChannel override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine) // 这行必须存在!
class MainActivity: FlutterActivity() { }
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { }
super.configureFlutterEngine(flutterEngine) // 这行必须存在!
}
}

View File

@ -2,4 +2,4 @@
#include "Generated.xcconfig" #include "Generated.xcconfig"
APP_DISPLAY_NAME=yumi APP_DISPLAY_NAME=yumi
PRODUCT_BUNDLE_IDENTIFIER=com.org.yumi PRODUCT_BUNDLE_IDENTIFIER=com.org.yumiparty

View File

@ -2,4 +2,4 @@
#include "Generated.xcconfig" #include "Generated.xcconfig"
APP_DISPLAY_NAME=yumi APP_DISPLAY_NAME=yumi
PRODUCT_BUNDLE_IDENTIFIER=com.org.yumi PRODUCT_BUNDLE_IDENTIFIER=com.org.yumiparty

View File

@ -512,7 +512,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.2.0; MARKETING_VERSION = 1.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumi; PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -703,7 +703,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.2.0; MARKETING_VERSION = 1.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumi; PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -732,7 +732,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.2.0; MARKETING_VERSION = 1.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumi; PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";

View File

@ -15,7 +15,7 @@
<key>PLIST_VERSION</key> <key>PLIST_VERSION</key>
<string>1</string> <string>1</string>
<key>BUNDLE_ID</key> <key>BUNDLE_ID</key>
<string>com.org.yumi</string> <string>com.org.yumiparty</string>
<key>PROJECT_ID</key> <key>PROJECT_ID</key>
<string>yumi-7b503</string> <string>yumi-7b503</string>
<key>STORAGE_BUCKET</key> <key>STORAGE_BUCKET</key>
@ -33,4 +33,4 @@
<key>GOOGLE_APP_ID</key> <key>GOOGLE_APP_ID</key>
<string>1:52796773508:ios:7dfa56d19e1711ab65ded1</string> <string>1:52796773508:ios:7dfa56d19e1711ab65ded1</string>
</dict> </dict>
</plist> </plist>

View File

@ -11,7 +11,7 @@ class SCVariant1Config implements AppConfig {
String get appName => 'yumi'; // String get appName => 'yumi'; //
@override @override
String get packageName => 'com.org.yumi'; String get packageName => 'com.org.yumiparty';
@override @override
String get apiHost => const String.fromEnvironment( String get apiHost => const String.fromEnvironment(

View File

@ -7,6 +7,7 @@ import 'package:pull_to_refresh/pull_to_refresh.dart';
import '../../../../app_localizations.dart'; import '../../../../app_localizations.dart';
import '../../../../app/constants/sc_global_config.dart'; import '../../../../app/constants/sc_global_config.dart';
import '../../../../app/routes/sc_fluro_navigator.dart'; import '../../../../app/routes/sc_fluro_navigator.dart';
import '../../../../shared/business_logic/models/res/sc_index_banner_res.dart';
import '../../../../shared/tools/sc_banner_utils.dart'; import '../../../../shared/tools/sc_banner_utils.dart';
import '../../../../shared/data_sources/sources/repositories/sc_room_repository_imp.dart'; import '../../../../shared/data_sources/sources/repositories/sc_room_repository_imp.dart';
import '../../../../shared/business_logic/models/res/follow_room_res.dart'; import '../../../../shared/business_logic/models/res/follow_room_res.dart';
@ -37,6 +38,7 @@ class _HomePartyPageState extends State<SCHomePartyPage>
List<FollowRoomRes> historyRooms = []; List<FollowRoomRes> historyRooms = [];
String? lastId; String? lastId;
bool isLoading = false; bool isLoading = false;
bool _isBannerLoading = false;
bool _isLeaderboardLoading = false; bool _isLeaderboardLoading = false;
final RefreshController _refreshController = RefreshController( final RefreshController _refreshController = RefreshController(
initialRefresh: false, initialRefresh: false,
@ -82,15 +84,24 @@ class _HomePartyPageState extends State<SCHomePartyPage>
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Stack( Consumer<SCAppGeneralManager>(
alignment: AlignmentDirectional.center, builder: (context, ref, child) {
children: [ if (!_shouldShowBannerSection(ref)) {
Consumer<SCAppGeneralManager>( return const SizedBox.shrink();
builder: (context, ref, child) { }
return _banner(ref); return Padding(
}, padding: EdgeInsets.only(bottom: 12.w),
), child: Stack(
], alignment: AlignmentDirectional.center,
children: [
if (_shouldShowBannerSkeleton(ref))
_buildBannerSkeleton()
else
_banner(ref),
],
),
);
},
), ),
Consumer<SCAppGeneralManager>( Consumer<SCAppGeneralManager>(
builder: (context, ref, child) { builder: (context, ref, child) {
@ -111,7 +122,9 @@ class _HomePartyPageState extends State<SCHomePartyPage>
} }
_banner(SCAppGeneralManager ref) { _banner(SCAppGeneralManager ref) {
if (ref.exploreBanners.isEmpty) { final banners = _partyBanners(ref);
final hasMultipleBanners = banners.length > 1;
if (banners.isEmpty) {
return Container(); return Container();
} }
return Stack( return Stack(
@ -120,15 +133,19 @@ class _HomePartyPageState extends State<SCHomePartyPage>
CarouselSlider( CarouselSlider(
options: CarouselOptions( options: CarouselOptions(
height: 95.w, height: 95.w,
autoPlay: true, autoPlay: hasMultipleBanners,
// //
enlargeCenterPage: false, enlargeCenterPage: false,
// //
enableInfiniteScroll: true, enableInfiniteScroll: hasMultipleBanners,
// //
autoPlayAnimationDuration: Duration(milliseconds: 800), autoPlayAnimationDuration: Duration(milliseconds: 800),
// //
viewportFraction: 1, viewportFraction: 1,
scrollPhysics:
hasMultipleBanners
? const BouncingScrollPhysics()
: const NeverScrollableScrollPhysics(),
// //
onPageChanged: (index, reason) { onPageChanged: (index, reason) {
_currentIndex = index; _currentIndex = index;
@ -136,7 +153,7 @@ class _HomePartyPageState extends State<SCHomePartyPage>
}, },
), ),
items: items:
ref.exploreBanners.map((item) { banners.map((item) {
return GestureDetector( return GestureDetector(
child: netImage( child: netImage(
url: item.cover ?? "", url: item.cover ?? "",
@ -154,7 +171,7 @@ class _HomePartyPageState extends State<SCHomePartyPage>
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: children:
ref.exploreBanners.asMap().entries.map((entry) { banners.asMap().entries.map((entry) {
return Container( return Container(
width: 6.0, width: 6.0,
height: 6.0, height: 6.0,
@ -171,6 +188,13 @@ class _HomePartyPageState extends State<SCHomePartyPage>
); );
} }
List<SCIndexBannerRes> _partyBanners(SCAppGeneralManager ref) {
if (ref.exploreBanners.isNotEmpty) {
return ref.exploreBanners;
}
return ref.homeBanners;
}
_banner2(SCAppGeneralManager ref) { _banner2(SCAppGeneralManager ref) {
String roomGiftOneAvatar = ""; String roomGiftOneAvatar = "";
String roomGiftTwoAvatar = ""; String roomGiftTwoAvatar = "";
@ -410,6 +434,14 @@ class _HomePartyPageState extends State<SCHomePartyPage>
return _isLeaderboardLoading && !_hasLeaderboardData(ref); return _isLeaderboardLoading && !_hasLeaderboardData(ref);
} }
bool _shouldShowBannerSection(SCAppGeneralManager ref) {
return _shouldShowBannerSkeleton(ref) || _partyBanners(ref).isNotEmpty;
}
bool _shouldShowBannerSkeleton(SCAppGeneralManager ref) {
return _isBannerLoading && _partyBanners(ref).isEmpty;
}
bool _hasLeaderboardData(SCAppGeneralManager ref) { bool _hasLeaderboardData(SCAppGeneralManager ref) {
final appLeaderResult = ref.appLeaderResult; final appLeaderResult = ref.appLeaderResult;
return (appLeaderResult?.giftsSendLeaderboard?.weekly?.isNotEmpty ?? return (appLeaderResult?.giftsSendLeaderboard?.weekly?.isNotEmpty ??
@ -419,6 +451,68 @@ class _HomePartyPageState extends State<SCHomePartyPage>
false); false);
} }
Widget _buildBannerSkeleton() {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 18.w),
child: SizedBox(
height: 95.w,
child: Stack(
alignment: Alignment.bottomCenter,
children: [
Positioned.fill(
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.w),
border: Border.all(color: _kPartySkeletonBorder, width: 1.w),
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [_kPartySkeletonShellStrong, _kPartySkeletonShell],
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.14),
blurRadius: 18.w,
offset: Offset(0, 8.w),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12.w),
child: Stack(
children: [
Positioned.fill(
child: Opacity(
opacity: 0.45,
child: _buildSkeletonPanel(),
),
),
],
),
),
),
),
Positioned(
bottom: 10.w,
child: Row(
children: List.generate(3, (index) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 4.w),
child: _buildSkeletonBone(
width: 6.w,
height: 6.w,
shape: BoxShape.circle,
),
);
}),
),
),
],
),
),
);
}
Widget _buildLeaderboardSkeleton() { Widget _buildLeaderboardSkeleton() {
return SizedBox( return SizedBox(
height: 130.w, height: 130.w,
@ -672,6 +766,9 @@ class _HomePartyPageState extends State<SCHomePartyPage>
); );
setState(() { setState(() {
isLoading = true; isLoading = true;
_isBannerLoading =
generalManager.exploreBanners.isEmpty &&
generalManager.homeBanners.isEmpty;
_isLeaderboardLoading = generalManager.appLeaderResult == null; _isLeaderboardLoading = generalManager.appLeaderResult == null;
}); });
SCChatRoomRepository() SCChatRoomRepository()
@ -689,7 +786,14 @@ class _HomePartyPageState extends State<SCHomePartyPage>
isLoading = false; isLoading = false;
if (mounted) setState(() {}); if (mounted) setState(() {});
}); });
generalManager.loadMainBanner(); generalManager.loadMainBanner().whenComplete(() {
if (!mounted) {
return;
}
setState(() {
_isBannerLoading = false;
});
});
generalManager.appLeaderboard().whenComplete(() { generalManager.appLeaderboard().whenComplete(() {
if (!mounted) { if (!mounted) {
return; return;

View File

@ -174,8 +174,10 @@ class _RoomEditPageState extends State<RoomEditPage> {
bool success, bool success,
String url, String url,
) { ) {
debugPrint(
"[Room Cover] upload callback success=$success url=$url",
);
if (success) { if (success) {
debugPrint("[Room Cover] uploaded url: $url");
setState(() { setState(() {
roomCover = url; roomCover = url;
isEdit = true; isEdit = true;
@ -523,7 +525,9 @@ class _RoomEditPageState extends State<RoomEditPage> {
return; return;
} }
SCLoadingManager.show(context: context); SCLoadingManager.show(context: context);
debugPrint("[Room Cover] submit roomCover: $submittedRoomCover"); debugPrint(
"[Room Cover] submit roomId=${rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? ""} roomCover=$submittedRoomCover roomName=$submittedRoomName roomDesc=$submittedRoomDesc",
);
var roomInfo = await SCAccountRepository().editRoomInfo( var roomInfo = await SCAccountRepository().editRoomInfo(
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "", rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "",
submittedRoomCover, submittedRoomCover,
@ -548,6 +552,9 @@ class _RoomEditPageState extends State<RoomEditPage> {
); );
roomManager.updateMyRoomInfo(mergedRoomInfo); roomManager.updateMyRoomInfo(mergedRoomInfo);
if (widget.needRestCurrentRoomInfo != "true") { if (widget.needRestCurrentRoomInfo != "true") {
debugPrint(
"[Room Cover Sync] dispatch roomSettingUpdate groupId=${rtcProvider?.currenRoom?.roomProfile?.roomProfile?.roomAccount ?? ""} roomId=${mergedRoomInfo.id ?? rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? ""}",
);
currentRtmProvider.dispatchMessage( currentRtmProvider.dispatchMessage(
Msg( Msg(
groupId: groupId:

View File

@ -22,7 +22,8 @@ class CropImagePage extends StatefulWidget {
final File image; // final File image; //
final bool needUpload; final bool needUpload;
const CropImagePage(this.image, { const CropImagePage(
this.image, {
Key? key, Key? key,
this.onUpLoadCallBack, this.onUpLoadCallBack,
this.aspectRatio, this.aspectRatio,
@ -58,7 +59,7 @@ class _CropImageRouteState extends State<CropImagePage> {
child: Container( child: Container(
width: double.infinity, width: double.infinity,
height: height:
SCScreen.designHeight - SCScreen.designHeight -
(kToolbarHeight * 2) - (kToolbarHeight * 2) -
ScreenUtil().bottomBarHeight - ScreenUtil().bottomBarHeight -
ScreenUtil().statusBarHeight, ScreenUtil().statusBarHeight,
@ -82,7 +83,8 @@ class _CropImageRouteState extends State<CropImagePage> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
children: <Widget>[ children: <Widget>[
GestureDetector( GestureDetector(
onTap: () => SCNavigatorUtils.goBackWithParams(context, ''), onTap:
() => SCNavigatorUtils.goBackWithParams(context, ''),
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Icon( child: Icon(
@ -114,8 +116,12 @@ class _CropImageRouteState extends State<CropImagePage> {
color: const Color(0xff666666), color: const Color(0xff666666),
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: GestureDetector( child: GestureDetector(
onTap: () => onTap:
_cropAndUpload(context, widget.image, needUpload:widget.needUpload), () => _cropAndUpload(
context,
widget.image,
needUpload: widget.needUpload,
),
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0).copyWith(right: 18), padding: const EdgeInsets.all(8.0).copyWith(right: 18),
child: Text( child: Text(
@ -133,17 +139,23 @@ class _CropImageRouteState extends State<CropImagePage> {
} }
/// 使 image_cropper /// 使 image_cropper
Future<void> _cropAndUpload(BuildContext context, Future<void> _cropAndUpload(
File originalFile, { BuildContext context,
bool needUpload = true, File originalFile, {
}) async { bool needUpload = true,
}) async {
final originalExists = originalFile.existsSync();
final originalSize = originalExists ? originalFile.lengthSync() : -1;
debugPrint(
"[Room Cover Upload] crop start originalPath=${originalFile.path} originalExists=$originalExists originalSize=$originalSize needUpload=$needUpload aspectRatio=${widget.aspectRatio}",
);
// //
CroppedFile? cropped = await ImageCropper().cropImage( CroppedFile? cropped = await ImageCropper().cropImage(
sourcePath: originalFile.path, sourcePath: originalFile.path,
aspectRatio: aspectRatio:
widget.aspectRatio != null widget.aspectRatio != null
? CropAspectRatio(ratioX: widget.aspectRatio!, ratioY: 1) ? CropAspectRatio(ratioX: widget.aspectRatio!, ratioY: 1)
: null, : null,
uiSettings: [ uiSettings: [
AndroidUiSettings( AndroidUiSettings(
toolbarTitle: SCAppLocalizations.of(context)!.crop, toolbarTitle: SCAppLocalizations.of(context)!.crop,
@ -168,12 +180,21 @@ class _CropImageRouteState extends State<CropImagePage> {
if (cropped == null) { if (cropped == null) {
if (widget.backOriginalFile ?? true) { if (widget.backOriginalFile ?? true) {
fileToUpload = originalFile; fileToUpload = originalFile;
debugPrint(
"[Room Cover Upload] crop cancelled, fallback to original file path=${fileToUpload.path}",
);
} else { } else {
debugPrint("[Room Cover Upload] crop cancelled, no fallback file");
widget.onUpLoadCallBack?.call(false, ""); widget.onUpLoadCallBack?.call(false, "");
return; return;
} }
} else { } else {
fileToUpload = File(cropped.path); fileToUpload = File(cropped.path);
final croppedExists = fileToUpload.existsSync();
final croppedSize = croppedExists ? fileToUpload.lengthSync() : -1;
debugPrint(
"[Room Cover Upload] crop result path=${fileToUpload.path} exists=$croppedExists size=$croppedSize",
);
} }
if (needUpload) { if (needUpload) {
// loading // loading
@ -188,12 +209,22 @@ class _CropImageRouteState extends State<CropImagePage> {
/// oss /// oss
Future<void> upload(File file) async { Future<void> upload(File file) async {
try { try {
final fileExists = file.existsSync();
final fileSize = fileExists ? file.lengthSync() : -1;
debugPrint(
"[Room Cover Upload] local file before request path=${file.path} exists=$fileExists size=$fileSize",
);
String fileUrl = await SCGeneralRepositoryImp().upload(file); String fileUrl = await SCGeneralRepositoryImp().upload(file);
debugPrint("[Room Cover Upload] upload success fileUrl=$fileUrl");
SCLoadingManager.hide(); SCLoadingManager.hide();
SCNavigatorUtils.goBack(context); SCNavigatorUtils.goBack(context);
await Future.delayed(const Duration(milliseconds: 100)); await Future.delayed(const Duration(milliseconds: 100));
widget.onUpLoadCallBack?.call(true, fileUrl); widget.onUpLoadCallBack?.call(true, fileUrl);
} catch (e) { } catch (e, stackTrace) {
debugPrint(
"[Room Cover Upload] upload failed path=${file.path} error=$e",
);
debugPrint("[Room Cover Upload] stackTrace=$stackTrace");
SCTts.show("upload fail $e"); SCTts.show("upload fail $e");
SCLoadingManager.hide(); SCLoadingManager.hide();
} }

View File

@ -934,8 +934,12 @@ class RealTimeCommunicationManager extends ChangeNotifier {
Future<bool> loadRoomInfo(String roomId) async { Future<bool> loadRoomInfo(String roomId) async {
try { try {
debugPrint("[Room Cover Sync] loadRoomInfo start roomId=$roomId");
final value = await SCChatRoomRepository().specific(roomId); final value = await SCChatRoomRepository().specific(roomId);
final currentRoomProfile = currenRoom?.roomProfile?.roomProfile; final currentRoomProfile = currenRoom?.roomProfile?.roomProfile;
debugPrint(
"[Room Cover Sync] loadRoomInfo fetched roomId=${value.id ?? roomId} roomCover=${value.roomCover ?? ""} roomName=${value.roomName ?? ""}",
);
currenRoom = currenRoom?.copyWith( currenRoom = currenRoom?.copyWith(
roomProfile: currenRoom?.roomProfile?.copyWith( roomProfile: currenRoom?.roomProfile?.copyWith(
roomCounter: value.counter ?? currenRoom?.roomProfile?.roomCounter, roomCounter: value.counter ?? currenRoom?.roomProfile?.roomCounter,
@ -956,6 +960,9 @@ class RealTimeCommunicationManager extends ChangeNotifier {
), ),
), ),
); );
debugPrint(
"[Room Cover Sync] loadRoomInfo applied roomId=$roomId roomCover=${currenRoom?.roomProfile?.roomProfile?.roomCover ?? ""}",
);
/// ///
bool touristMike = bool touristMike =

View File

@ -784,6 +784,11 @@ class RealTimeMessagingManager extends ChangeNotifier {
// //
Future<void> _sendRoomMessage(Msg msg) async { Future<void> _sendRoomMessage(Msg msg) async {
try { try {
if (msg.type == SCRoomMsgType.roomSettingUpdate) {
debugPrint(
"[Room Cover Sync] send roomSettingUpdate groupId=${msg.groupId ?? ""} roomId=${msg.msg ?? ""}",
);
}
var user = msg.user?.copyWith(); var user = msg.user?.copyWith();
var toUser = msg.toUser?.copyWith(); var toUser = msg.toUser?.copyWith();
user?.cleanWearHonor(); user?.cleanWearHonor();
@ -1073,6 +1078,9 @@ class RealTimeMessagingManager extends ChangeNotifier {
} }
if (msg.type == SCRoomMsgType.roomSettingUpdate) { if (msg.type == SCRoomMsgType.roomSettingUpdate) {
debugPrint(
"[Room Cover Sync] recv roomSettingUpdate groupId=$groupID roomId=${msg.msg ?? ""}",
);
Provider.of<RealTimeCommunicationManager>( Provider.of<RealTimeCommunicationManager>(
context!, context!,
listen: false, listen: false,

View File

@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:yumi/shared/business_logic/models/req/sc_user_profile_cmd.dart'; import 'package:yumi/shared/business_logic/models/req/sc_user_profile_cmd.dart';
import 'package:yumi/shared/business_logic/models/res/sc_gold_record_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_gold_record_res.dart';
import 'package:yumi/shared/business_logic/models/res/sc_prop_coupon_list_res.dart'; import 'package:yumi/shared/business_logic/models/res/sc_prop_coupon_list_res.dart';
@ -239,19 +240,22 @@ class SCAccountRepository implements SocialChatUserRepository {
String roomDesc, String roomDesc,
String event, String event,
) async { ) async {
final payload = {
"id": roomId,
"roomId": roomId,
"roomCover": roomCover,
"cover": roomCover,
"roomName": roomName,
"roomDesc": roomDesc,
"event": event,
};
debugPrint("[Room Cover Save] request payload=$payload");
final result = await http.put( final result = await http.put(
"1e41384e55cbd6c2374608b129e2ed27", "1e41384e55cbd6c2374608b129e2ed27",
data: { data: payload,
"id": roomId,
"roomId": roomId,
"roomCover": roomCover,
"cover": roomCover,
"roomName": roomName,
"roomDesc": roomDesc,
"event": event,
},
fromJson: (json) => SCEditRoomInfoRes.fromJson(json), fromJson: (json) => SCEditRoomInfoRes.fromJson(json),
); );
debugPrint("[Room Cover Save] response=${result.toJson()}");
await SCRoomProfileCache.saveRoomProfile( await SCRoomProfileCache.saveRoomProfile(
roomId: roomId, roomId: roomId,
roomCover: roomCover, roomCover: roomCover,

View File

@ -0,0 +1,220 @@
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:yumi/shared/tools/sc_lk_dialog_util.dart';
import 'package:yumi/ui_kit/components/sc_debounce_widget.dart';
import 'package:yumi/ui_kit/components/text/sc_text.dart';
class RoomGameEntryButton extends StatelessWidget {
const RoomGameEntryButton({super.key});
@override
Widget build(BuildContext context) {
return SCDebounceWidget(
onTap: () {
showBottomInBottomDialog(
context,
const RoomGameBottomSheet(),
barrierColor: Colors.black54,
);
},
child: Container(
width: 44.w,
height: 44.w,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xff09372E).withValues(alpha: 0.72),
border: Border.all(
color: Colors.white.withValues(alpha: 0.22),
width: 1.w,
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.18),
blurRadius: 10.w,
offset: Offset(0, 4.w),
),
],
),
child: Text(
// `🎮`
// UI
'🎮',
style: TextStyle(fontSize: 22.sp),
),
),
);
}
}
class RoomGameBottomSheet extends StatelessWidget {
const RoomGameBottomSheet({super.key});
static const int _itemsPerRow = 5;
///
static const List<_RoomGameItem> _mockGames = [
_RoomGameItem(name: 'Ludo', icon: Icons.casino_outlined),
_RoomGameItem(name: 'Dice', icon: Icons.casino),
_RoomGameItem(name: 'UNO', icon: Icons.style_outlined),
_RoomGameItem(name: 'Quiz', icon: Icons.psychology_outlined),
_RoomGameItem(name: 'Race', icon: Icons.sports_motorsports_outlined),
_RoomGameItem(name: 'Poker', icon: Icons.style),
_RoomGameItem(name: 'Lucky', icon: Icons.auto_awesome_outlined),
_RoomGameItem(name: 'Bingo', icon: Icons.grid_on_outlined),
_RoomGameItem(name: '2048', icon: Icons.apps_outlined),
_RoomGameItem(name: 'Chess', icon: Icons.extension_outlined),
_RoomGameItem(name: 'Keno', icon: Icons.confirmation_number_outlined),
_RoomGameItem(name: 'Cards', icon: Icons.view_carousel_outlined),
_RoomGameItem(name: 'Darts', icon: Icons.gps_fixed),
_RoomGameItem(name: 'Puzzle', icon: Icons.interests_outlined),
_RoomGameItem(name: 'Slots', icon: Icons.local_activity_outlined),
_RoomGameItem(name: 'Spin', icon: Icons.blur_circular_outlined),
_RoomGameItem(name: 'Snake', icon: Icons.route_outlined),
_RoomGameItem(name: 'Party', icon: Icons.celebration_outlined),
_RoomGameItem(name: 'Hero', icon: Icons.shield_outlined),
_RoomGameItem(name: 'Arena', icon: Icons.sports_esports_outlined),
];
@override
Widget build(BuildContext context) {
final rowCount = (_mockGames.length / _itemsPerRow).ceil();
return SafeArea(
top: false,
child: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(18.w),
topRight: Radius.circular(18.w),
),
child: BackdropFilter(
filter: ui.ImageFilter.blur(sigmaX: 16, sigmaY: 16),
child: Container(
width: ScreenUtil().screenWidth,
height: ScreenUtil().screenHeight / 3,
color: const Color(0xff09372E).withValues(alpha: 0.88),
child: Column(
children: [
SizedBox(height: 10.w),
Container(
width: 34.w,
height: 4.w,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.28),
borderRadius: BorderRadius.circular(99.w),
),
),
Padding(
padding: EdgeInsets.fromLTRB(16.w, 14.w, 16.w, 10.w),
child: Row(
children: [
text(
'Games',
fontSize: 16,
fontWeight: FontWeight.w700,
textColor: Colors.white,
),
const Spacer(),
text(
'Static mock data',
fontSize: 11,
textColor: Colors.white70,
),
],
),
),
Expanded(
child: ListView.builder(
padding: EdgeInsets.fromLTRB(12.w, 0, 12.w, 20.w),
itemCount: rowCount,
itemBuilder: (context, rowIndex) {
return Padding(
padding: EdgeInsets.only(bottom: 12.w),
child: Row(
children: List.generate(_itemsPerRow, (columnIndex) {
final itemIndex =
rowIndex * _itemsPerRow + columnIndex;
return Expanded(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 4.w),
child:
itemIndex < _mockGames.length
? _RoomGameTile(
item: _mockGames[itemIndex],
)
: const SizedBox.shrink(),
),
);
}),
),
);
},
),
),
],
),
),
),
),
);
}
}
class _RoomGameTile extends StatelessWidget {
const _RoomGameTile({required this.item});
final _RoomGameItem item;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 48.w,
height: 48.w,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
const Color(0xff18F2B1).withValues(alpha: 0.26),
Colors.white.withValues(alpha: 0.08),
],
),
border: Border.all(
color: Colors.white.withValues(alpha: 0.2),
width: 1.w,
),
),
alignment: Alignment.center,
child: Icon(
// Icon UI /
item.icon,
size: 22.w,
color: Colors.white,
),
),
SizedBox(height: 6.w),
text(
item.name,
fontSize: 11,
textColor: Colors.white,
fontWeight: FontWeight.w500,
textAlign: TextAlign.center,
maxLines: 1,
),
],
);
}
}
class _RoomGameItem {
const _RoomGameItem({required this.name, required this.icon});
final String name;
final IconData icon;
}

View File

@ -1,60 +1,33 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart';
import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:yumi/ui_kit/widgets/room/room_banner_view.dart';
import 'package:provider/provider.dart'; import 'package:yumi/ui_kit/widgets/room/room_game_bottom_sheet.dart';
import 'package:yumi/ui_kit/components/sc_debounce_widget.dart';
import 'package:yumi/ui_kit/components/text/sc_text.dart'; class RoomPlayWidget extends StatefulWidget {
import 'package:yumi/app/routes/sc_fluro_navigator.dart'; const RoomPlayWidget({super.key});
import 'package:yumi/services/audio/rtc_manager.dart';
import 'package:yumi/ui_kit/widgets/room/room_banner_view.dart'; @override
import '../../../modules/index/main_route.dart'; State<RoomPlayWidget> createState() => _RoomPlayWidgetState();
import '../../../services/audio/rtm_manager.dart'; }
class RoomPlayWidget extends StatefulWidget { class _RoomPlayWidgetState extends State<RoomPlayWidget> {
@override @override
_RoomPlayWidgetState createState() => _RoomPlayWidgetState(); Widget build(BuildContext context) {
} return SizedBox.expand(
child: Stack(
class _RoomPlayWidgetState extends State<RoomPlayWidget> { children: [
@override PositionedDirectional(
Widget build(BuildContext context) { end: 15.w,
return Consumer<RtcProvider>( bottom: 100.w,
builder: (context, ref, child) { child: const RoomGameEntryButton(),
return Column( ),
crossAxisAlignment: CrossAxisAlignment.end, PositionedDirectional(
children: [ end: 10.w,
Expanded( bottom: 15.w,
child: Row( child: SizedBox(width: 46.w, child: RoomBannerView()),
mainAxisAlignment: MainAxisAlignment.end, ),
children: [ ],
Container( ),
alignment: Alignment.bottomCenter, );
child: SingleChildScrollView( }
child: Row( }
mainAxisSize: MainAxisSize.min,
children: [
Column(
children: [
Container(
height: 56.w,
margin: EdgeInsets.symmetric(vertical: 5.w),
child: RoomBannerView(),
),
SizedBox(height: 10.w),
],
),
SizedBox(width: 10.w),
],
),
),
),
],
),
),
],
);
;
},
);
}
}

View File

@ -4,6 +4,9 @@
- 控制当前 Flutter Android 发包体积,持续定位冗余组件、超大资源和不合理构建配置,并把每一步处理结果落盘记录。 - 控制当前 Flutter Android 发包体积,持续定位冗余组件、超大资源和不合理构建配置,并把每一步处理结果落盘记录。
## 已完成模块 ## 已完成模块
- 已为语言房页面补充右下方 `game` 悬浮入口:入口位于聊天区右下侧、距右侧约 `15.w`、位于底部操作栏上方约 `100.w`,当前先使用代码绘制的 `🎮` 占位图标,并已在实现处标注后续替换为正式 UI 图片资源。
- 已新增语言房游戏底部弹窗:点击 `game` 入口后会从底部弹出约三分之一屏高的面板,采用房间页现有半透明磨砂风格,便于后续继续沿用当前视觉体系。
- 已补齐语言房游戏列表占位结构:弹窗内部先用静态游戏数据驱动,并按“`ListView` 可上下滑动 + 每行 `5` 个图标位”的方式实现,后续只需替换入口图、列表图标和真实接口数据即可继续开发。
- 创建并持续维护进度跟踪文件。 - 创建并持续维护进度跟踪文件。
- 已继续排查语言房 gift 动画链路:确认送礼后会同时走本地房间消息、滚屏礼物条和大额礼物全局飘屏三条路径,并修复动画管理器在控制器尚未绑定完成时提前消费队列导致后续动画不再播放的问题。 - 已继续排查语言房 gift 动画链路:确认送礼后会同时走本地房间消息、滚屏礼物条和大额礼物全局飘屏三条路径,并修复动画管理器在控制器尚未绑定完成时提前消费队列导致后续动画不再播放的问题。
- 已定位并修复语言房礼物飘屏资源引用错误:代码里误写 `sc_icon_gift_flosc_bg` / `sc_icon_luck_gift_flosc_*`,实际资源文件名为 `float`,导致点击送礼后飘屏背景图加载失败,相关动画无法正常显示。 - 已定位并修复语言房礼物飘屏资源引用错误:代码里误写 `sc_icon_gift_flosc_bg` / `sc_icon_luck_gift_flosc_*`,实际资源文件名为 `float`,导致点击送礼后飘屏背景图加载失败,相关动画无法正常显示。
@ -25,6 +28,9 @@
- 已继续细化首页房卡体验:`My` 板块房卡骨架数量已从 `4` 个补齐到 `6` 个,与首页 Party 首屏保持一致;同时将房卡右下角原本的静态绿色“直播中”图片替换为代码驱动的三段式动态音频条,首页、我的、搜索结果和房间浮窗现都会展示跳动效果,不再依赖静态资源图;最新已把动效调整为“单周期完整峰谷 + A/B/C 错峰起伏”的连续波形,观感更接近直播音频电平。 - 已继续细化首页房卡体验:`My` 板块房卡骨架数量已从 `4` 个补齐到 `6` 个,与首页 Party 首屏保持一致;同时将房卡右下角原本的静态绿色“直播中”图片替换为代码驱动的三段式动态音频条,首页、我的、搜索结果和房间浮窗现都会展示跳动效果,不再依赖静态资源图;最新已把动效调整为“单周期完整峰谷 + A/B/C 错峰起伏”的连续波形,观感更接近直播音频电平。
- 已继续微调首页房卡信息密度:房卡底部房名与在线人数字号已统一调小,和动态音频条更协调;同时已移除 `My` 板块顶部 `my room` 标题及其占位,让房间卡片整体上移,对齐更紧凑。 - 已继续微调首页房卡信息密度:房卡底部房名与在线人数字号已统一调小,和动态音频条更协调;同时已移除 `My` 板块顶部 `my room` 标题及其占位,让房间卡片整体上移,对齐更紧凑。
- 已继续微调首页视觉细节房卡底栏房名再次缩小并轻微上移和国旗、动态音频条、人数的中线更一致首页顶部排行骨架也已简化为“3 个圆形头像 + 1 根文本条”,更贴近真实卡片结构。 - 已继续微调首页视觉细节房卡底栏房名再次缩小并轻微上移和国旗、动态音频条、人数的中线更一致首页顶部排行骨架也已简化为“3 个圆形头像 + 1 根文本条”,更贴近真实卡片结构。
- 已补回首页 Party 顶部 banner 展示链路:顶部现优先显示原有 `explore` banner并在该分组为空时自动兜底使用首页 `home` banner避免整块区域消失同时首屏 banner 空数据加载阶段已补充同风格骨架占位,不再直接塌陷为零高度。
- 已继续微调首页 Party 顶部 banner 体验banner 与下方排行组件之间已补齐接近房卡区的纵向间距;当 banner 只有 `1` 张时,会关闭无限轮播与横向回弹,左右滑动不再弹回中间。
- 已继续收窄首页 Party 顶部 banner 骨架样式banner 骨架现改为更干净的整块占位,不再在中间叠加长方形白条,首屏观感更接近真实 banner 的整体加载。
- 已优化语言房顶部成员入口的点击范围:右上角在线成员区域现已将左侧头像堆叠区与右侧成员图标统一为同一点击热区,用户点击头像区也可直接打开成员列表页。 - 已优化语言房顶部成员入口的点击范围:右上角在线成员区域现已将左侧头像堆叠区与右侧成员图标统一为同一点击热区,用户点击头像区也可直接打开成员列表页。
- 已继续修复房间封面跨用户不同步问题:当列表或进房返回空封面时,客户端会自动补拉 `room/profile/specific` 详情并缓存真实封面;同时房主保存房间资料后会向房间内其他用户派发资料刷新消息,避免只有自己能看到新封面、其他用户仍显示 `no data` - 已继续修复房间封面跨用户不同步问题:当列表或进房返回空封面时,客户端会自动补拉 `room/profile/specific` 详情并缓存真实封面;同时房主保存房间资料后会向房间内其他用户派发资料刷新消息,避免只有自己能看到新封面、其他用户仍显示 `no data`
- 已修复个人主页编辑页头像上传后无变化的问题:上传成功后会先本地回写新头像地址并立即提交;资料更新接口现同时兼容 `userAvatar/avatar` 字段,且不再被紧接着的旧资料回拉覆盖,返回个人主页后头像会即时更新。 - 已修复个人主页编辑页头像上传后无变化的问题:上传成功后会先本地回写新头像地址并立即提交;资料更新接口现同时兼容 `userAvatar/avatar` 字段,且不再被紧接着的旧资料回拉覆盖,返回个人主页后头像会即时更新。