游戏 以及app id
This commit is contained in:
parent
77ce33bac9
commit
456642ff04
@ -9,7 +9,7 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.org.yumi"
|
||||
namespace = "com.org.yumiparty"
|
||||
compileSdk = 36
|
||||
ndkVersion = "28.2.13676358"
|
||||
|
||||
@ -58,7 +58,7 @@ android {
|
||||
manifestPlaceholders["appName"] = "Yumi"
|
||||
manifestPlaceholders["appIcon"] = "@mipmap/ic_launcher"
|
||||
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.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:980005024266:android:581aa38059aa318d9c65f3",
|
||||
"android_client_info": {
|
||||
"package_name": "com.org.yumi"
|
||||
"package_name": "com.org.yumiparty"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
@ -17,7 +17,7 @@
|
||||
"client_id": "980005024266-o9pjdmdbqqt1julbh1q1ovafcvmr1mv1.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.org.yumi",
|
||||
"package_name": "com.org.yumiparty",
|
||||
"certificate_hash": "3fe178dcc3294f7420df2754cfaf4646d6a19478"
|
||||
}
|
||||
},
|
||||
@ -25,7 +25,7 @@
|
||||
"client_id": "980005024266-sl5h466pe90jsjmoi4jcd7bqhmckieec.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.org.yumi",
|
||||
"package_name": "com.org.yumiparty",
|
||||
"certificate_hash": "9ab21924bfbe2c56555860ebabf23c4099fd7412"
|
||||
}
|
||||
},
|
||||
@ -33,7 +33,7 @@
|
||||
"client_id": "980005024266-vrrp2us89svbqgc93oe7q4ad7uoh8fl6.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.org.yumi",
|
||||
"package_name": "com.org.yumiparty",
|
||||
"certificate_hash": "c180afbf6e1ff2f449587287a2bc5407e7d2d9a3"
|
||||
}
|
||||
},
|
||||
|
||||
2
android/app/proguard-rules.pro
vendored
2
android/app/proguard-rules.pro
vendored
@ -28,7 +28,7 @@
|
||||
-keep class io.flutter.embedding.engine.deferredcomponents.** { *; }
|
||||
|
||||
# 应用特定
|
||||
-keep class com.org.yumi.** { *; }
|
||||
-keep class com.org.yumiparty.** { *; }
|
||||
|
||||
# Provider
|
||||
-keep class * extends ChangeNotifier { *; }
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
package com.org.yumi
|
||||
package com.org.yumiparty
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
|
||||
class MainActivity : FlutterActivity() {
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
@ -2,4 +2,4 @@
|
||||
#include "Generated.xcconfig"
|
||||
|
||||
APP_DISPLAY_NAME=yumi
|
||||
PRODUCT_BUNDLE_IDENTIFIER=com.org.yumi
|
||||
PRODUCT_BUNDLE_IDENTIFIER=com.org.yumiparty
|
||||
|
||||
@ -2,4 +2,4 @@
|
||||
#include "Generated.xcconfig"
|
||||
|
||||
APP_DISPLAY_NAME=yumi
|
||||
PRODUCT_BUNDLE_IDENTIFIER=com.org.yumi
|
||||
PRODUCT_BUNDLE_IDENTIFIER=com.org.yumiparty
|
||||
|
||||
@ -512,7 +512,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.2.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumi;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@ -703,7 +703,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.2.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumi;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@ -732,7 +732,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.2.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumi;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
<key>PLIST_VERSION</key>
|
||||
<string>1</string>
|
||||
<key>BUNDLE_ID</key>
|
||||
<string>com.org.yumi</string>
|
||||
<string>com.org.yumiparty</string>
|
||||
<key>PROJECT_ID</key>
|
||||
<string>yumi-7b503</string>
|
||||
<key>STORAGE_BUCKET</key>
|
||||
|
||||
@ -11,7 +11,7 @@ class SCVariant1Config implements AppConfig {
|
||||
String get appName => 'yumi'; // 马甲包应用名称
|
||||
|
||||
@override
|
||||
String get packageName => 'com.org.yumi';
|
||||
String get packageName => 'com.org.yumiparty';
|
||||
|
||||
@override
|
||||
String get apiHost => const String.fromEnvironment(
|
||||
|
||||
@ -7,6 +7,7 @@ import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||
import '../../../../app_localizations.dart';
|
||||
import '../../../../app/constants/sc_global_config.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/data_sources/sources/repositories/sc_room_repository_imp.dart';
|
||||
import '../../../../shared/business_logic/models/res/follow_room_res.dart';
|
||||
@ -37,6 +38,7 @@ class _HomePartyPageState extends State<SCHomePartyPage>
|
||||
List<FollowRoomRes> historyRooms = [];
|
||||
String? lastId;
|
||||
bool isLoading = false;
|
||||
bool _isBannerLoading = false;
|
||||
bool _isLeaderboardLoading = false;
|
||||
final RefreshController _refreshController = RefreshController(
|
||||
initialRefresh: false,
|
||||
@ -81,17 +83,26 @@ class _HomePartyPageState extends State<SCHomePartyPage>
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
children: [
|
||||
Consumer<SCAppGeneralManager>(
|
||||
builder: (context, ref, child) {
|
||||
return _banner(ref);
|
||||
},
|
||||
),
|
||||
if (!_shouldShowBannerSection(ref)) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: 12.w),
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
children: [
|
||||
if (_shouldShowBannerSkeleton(ref))
|
||||
_buildBannerSkeleton()
|
||||
else
|
||||
_banner(ref),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Consumer<SCAppGeneralManager>(
|
||||
builder: (context, ref, child) {
|
||||
if (_shouldShowLeaderboardSkeleton(ref)) {
|
||||
@ -111,7 +122,9 @@ class _HomePartyPageState extends State<SCHomePartyPage>
|
||||
}
|
||||
|
||||
_banner(SCAppGeneralManager ref) {
|
||||
if (ref.exploreBanners.isEmpty) {
|
||||
final banners = _partyBanners(ref);
|
||||
final hasMultipleBanners = banners.length > 1;
|
||||
if (banners.isEmpty) {
|
||||
return Container();
|
||||
}
|
||||
return Stack(
|
||||
@ -120,15 +133,19 @@ class _HomePartyPageState extends State<SCHomePartyPage>
|
||||
CarouselSlider(
|
||||
options: CarouselOptions(
|
||||
height: 95.w,
|
||||
autoPlay: true,
|
||||
autoPlay: hasMultipleBanners,
|
||||
// 启用自动播放
|
||||
enlargeCenterPage: false,
|
||||
// 居中放大当前页面
|
||||
enableInfiniteScroll: true,
|
||||
enableInfiniteScroll: hasMultipleBanners,
|
||||
// 启用无限循环
|
||||
autoPlayAnimationDuration: Duration(milliseconds: 800),
|
||||
// 自动播放动画时长
|
||||
viewportFraction: 1,
|
||||
scrollPhysics:
|
||||
hasMultipleBanners
|
||||
? const BouncingScrollPhysics()
|
||||
: const NeverScrollableScrollPhysics(),
|
||||
// 视口分数
|
||||
onPageChanged: (index, reason) {
|
||||
_currentIndex = index;
|
||||
@ -136,7 +153,7 @@ class _HomePartyPageState extends State<SCHomePartyPage>
|
||||
},
|
||||
),
|
||||
items:
|
||||
ref.exploreBanners.map((item) {
|
||||
banners.map((item) {
|
||||
return GestureDetector(
|
||||
child: netImage(
|
||||
url: item.cover ?? "",
|
||||
@ -154,7 +171,7 @@ class _HomePartyPageState extends State<SCHomePartyPage>
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children:
|
||||
ref.exploreBanners.asMap().entries.map((entry) {
|
||||
banners.asMap().entries.map((entry) {
|
||||
return Container(
|
||||
width: 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) {
|
||||
String roomGiftOneAvatar = "";
|
||||
String roomGiftTwoAvatar = "";
|
||||
@ -410,6 +434,14 @@ class _HomePartyPageState extends State<SCHomePartyPage>
|
||||
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) {
|
||||
final appLeaderResult = ref.appLeaderResult;
|
||||
return (appLeaderResult?.giftsSendLeaderboard?.weekly?.isNotEmpty ??
|
||||
@ -419,6 +451,68 @@ class _HomePartyPageState extends State<SCHomePartyPage>
|
||||
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() {
|
||||
return SizedBox(
|
||||
height: 130.w,
|
||||
@ -672,6 +766,9 @@ class _HomePartyPageState extends State<SCHomePartyPage>
|
||||
);
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
_isBannerLoading =
|
||||
generalManager.exploreBanners.isEmpty &&
|
||||
generalManager.homeBanners.isEmpty;
|
||||
_isLeaderboardLoading = generalManager.appLeaderResult == null;
|
||||
});
|
||||
SCChatRoomRepository()
|
||||
@ -689,7 +786,14 @@ class _HomePartyPageState extends State<SCHomePartyPage>
|
||||
isLoading = false;
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
generalManager.loadMainBanner();
|
||||
generalManager.loadMainBanner().whenComplete(() {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_isBannerLoading = false;
|
||||
});
|
||||
});
|
||||
generalManager.appLeaderboard().whenComplete(() {
|
||||
if (!mounted) {
|
||||
return;
|
||||
|
||||
@ -174,8 +174,10 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
||||
bool success,
|
||||
String url,
|
||||
) {
|
||||
debugPrint(
|
||||
"[Room Cover] upload callback success=$success url=$url",
|
||||
);
|
||||
if (success) {
|
||||
debugPrint("[Room Cover] uploaded url: $url");
|
||||
setState(() {
|
||||
roomCover = url;
|
||||
isEdit = true;
|
||||
@ -523,7 +525,9 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
||||
return;
|
||||
}
|
||||
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(
|
||||
rtcProvider?.currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||||
submittedRoomCover,
|
||||
@ -548,6 +552,9 @@ class _RoomEditPageState extends State<RoomEditPage> {
|
||||
);
|
||||
roomManager.updateMyRoomInfo(mergedRoomInfo);
|
||||
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(
|
||||
Msg(
|
||||
groupId:
|
||||
|
||||
@ -22,7 +22,8 @@ class CropImagePage extends StatefulWidget {
|
||||
final File image; // 原始图片文件
|
||||
final bool needUpload;
|
||||
|
||||
const CropImagePage(this.image, {
|
||||
const CropImagePage(
|
||||
this.image, {
|
||||
Key? key,
|
||||
this.onUpLoadCallBack,
|
||||
this.aspectRatio,
|
||||
@ -82,7 +83,8 @@ class _CropImageRouteState extends State<CropImagePage> {
|
||||
alignment: Alignment.centerLeft,
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () => SCNavigatorUtils.goBackWithParams(context, ''),
|
||||
onTap:
|
||||
() => SCNavigatorUtils.goBackWithParams(context, ''),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Icon(
|
||||
@ -114,8 +116,12 @@ class _CropImageRouteState extends State<CropImagePage> {
|
||||
color: const Color(0xff666666),
|
||||
alignment: Alignment.centerRight,
|
||||
child: GestureDetector(
|
||||
onTap: () =>
|
||||
_cropAndUpload(context, widget.image, needUpload:widget.needUpload),
|
||||
onTap:
|
||||
() => _cropAndUpload(
|
||||
context,
|
||||
widget.image,
|
||||
needUpload: widget.needUpload,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0).copyWith(right: 18),
|
||||
child: Text(
|
||||
@ -133,10 +139,16 @@ class _CropImageRouteState extends State<CropImagePage> {
|
||||
}
|
||||
|
||||
/// 使用 image_cropper 进行裁剪并上传
|
||||
Future<void> _cropAndUpload(BuildContext context,
|
||||
Future<void> _cropAndUpload(
|
||||
BuildContext context,
|
||||
File originalFile, {
|
||||
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(
|
||||
sourcePath: originalFile.path,
|
||||
@ -168,12 +180,21 @@ class _CropImageRouteState extends State<CropImagePage> {
|
||||
if (cropped == null) {
|
||||
if (widget.backOriginalFile ?? true) {
|
||||
fileToUpload = originalFile;
|
||||
debugPrint(
|
||||
"[Room Cover Upload] crop cancelled, fallback to original file path=${fileToUpload.path}",
|
||||
);
|
||||
} else {
|
||||
debugPrint("[Room Cover Upload] crop cancelled, no fallback file");
|
||||
widget.onUpLoadCallBack?.call(false, "");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
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) {
|
||||
// 弹出上传中的 loading
|
||||
@ -188,12 +209,22 @@ class _CropImageRouteState extends State<CropImagePage> {
|
||||
/// 上传文件至oss
|
||||
Future<void> upload(File file) async {
|
||||
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);
|
||||
debugPrint("[Room Cover Upload] upload success fileUrl=$fileUrl");
|
||||
SCLoadingManager.hide();
|
||||
SCNavigatorUtils.goBack(context);
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
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");
|
||||
SCLoadingManager.hide();
|
||||
}
|
||||
|
||||
@ -934,8 +934,12 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
|
||||
Future<bool> loadRoomInfo(String roomId) async {
|
||||
try {
|
||||
debugPrint("[Room Cover Sync] loadRoomInfo start roomId=$roomId");
|
||||
final value = await SCChatRoomRepository().specific(roomId);
|
||||
final currentRoomProfile = currenRoom?.roomProfile?.roomProfile;
|
||||
debugPrint(
|
||||
"[Room Cover Sync] loadRoomInfo fetched roomId=${value.id ?? roomId} roomCover=${value.roomCover ?? ""} roomName=${value.roomName ?? ""}",
|
||||
);
|
||||
currenRoom = currenRoom?.copyWith(
|
||||
roomProfile: currenRoom?.roomProfile?.copyWith(
|
||||
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 =
|
||||
|
||||
@ -784,6 +784,11 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
// 发送消息(房间)
|
||||
Future<void> _sendRoomMessage(Msg msg) async {
|
||||
try {
|
||||
if (msg.type == SCRoomMsgType.roomSettingUpdate) {
|
||||
debugPrint(
|
||||
"[Room Cover Sync] send roomSettingUpdate groupId=${msg.groupId ?? ""} roomId=${msg.msg ?? ""}",
|
||||
);
|
||||
}
|
||||
var user = msg.user?.copyWith();
|
||||
var toUser = msg.toUser?.copyWith();
|
||||
user?.cleanWearHonor();
|
||||
@ -1073,6 +1078,9 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
}
|
||||
|
||||
if (msg.type == SCRoomMsgType.roomSettingUpdate) {
|
||||
debugPrint(
|
||||
"[Room Cover Sync] recv roomSettingUpdate groupId=$groupID roomId=${msg.msg ?? ""}",
|
||||
);
|
||||
Provider.of<RealTimeCommunicationManager>(
|
||||
context!,
|
||||
listen: false,
|
||||
|
||||
@ -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/res/sc_gold_record_res.dart';
|
||||
import 'package:yumi/shared/business_logic/models/res/sc_prop_coupon_list_res.dart';
|
||||
@ -239,9 +240,7 @@ class SCAccountRepository implements SocialChatUserRepository {
|
||||
String roomDesc,
|
||||
String event,
|
||||
) async {
|
||||
final result = await http.put(
|
||||
"1e41384e55cbd6c2374608b129e2ed27",
|
||||
data: {
|
||||
final payload = {
|
||||
"id": roomId,
|
||||
"roomId": roomId,
|
||||
"roomCover": roomCover,
|
||||
@ -249,9 +248,14 @@ class SCAccountRepository implements SocialChatUserRepository {
|
||||
"roomName": roomName,
|
||||
"roomDesc": roomDesc,
|
||||
"event": event,
|
||||
},
|
||||
};
|
||||
debugPrint("[Room Cover Save] request payload=$payload");
|
||||
final result = await http.put(
|
||||
"1e41384e55cbd6c2374608b129e2ed27",
|
||||
data: payload,
|
||||
fromJson: (json) => SCEditRoomInfoRes.fromJson(json),
|
||||
);
|
||||
debugPrint("[Room Cover Save] response=${result.toJson()}");
|
||||
await SCRoomProfileCache.saveRoomProfile(
|
||||
roomId: roomId,
|
||||
roomCover: roomCover,
|
||||
|
||||
220
lib/ui_kit/widgets/room/room_game_bottom_sheet.dart
Normal file
220
lib/ui_kit/widgets/room/room_game_bottom_sheet.dart
Normal 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;
|
||||
}
|
||||
@ -1,60 +1,33 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:yumi/ui_kit/components/sc_debounce_widget.dart';
|
||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||||
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/room_banner_view.dart';
|
||||
import '../../../modules/index/main_route.dart';
|
||||
import '../../../services/audio/rtm_manager.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/room_game_bottom_sheet.dart';
|
||||
|
||||
class RoomPlayWidget extends StatefulWidget {
|
||||
const RoomPlayWidget({super.key});
|
||||
|
||||
@override
|
||||
_RoomPlayWidgetState createState() => _RoomPlayWidgetState();
|
||||
State<RoomPlayWidget> createState() => _RoomPlayWidgetState();
|
||||
}
|
||||
|
||||
class _RoomPlayWidgetState extends State<RoomPlayWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<RtcProvider>(
|
||||
builder: (context, ref, child) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
return SizedBox.expand(
|
||||
child: Stack(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
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),
|
||||
],
|
||||
),
|
||||
PositionedDirectional(
|
||||
end: 15.w,
|
||||
bottom: 100.w,
|
||||
child: const RoomGameEntryButton(),
|
||||
),
|
||||
PositionedDirectional(
|
||||
end: 10.w,
|
||||
bottom: 15.w,
|
||||
child: SizedBox(width: 46.w, child: RoomBannerView()),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
6
需求进度.md
6
需求进度.md
@ -4,6 +4,9 @@
|
||||
- 控制当前 Flutter Android 发包体积,持续定位冗余组件、超大资源和不合理构建配置,并把每一步处理结果落盘记录。
|
||||
|
||||
## 已完成模块
|
||||
- 已为语言房页面补充右下方 `game` 悬浮入口:入口位于聊天区右下侧、距右侧约 `15.w`、位于底部操作栏上方约 `100.w`,当前先使用代码绘制的 `🎮` 占位图标,并已在实现处标注后续替换为正式 UI 图片资源。
|
||||
- 已新增语言房游戏底部弹窗:点击 `game` 入口后会从底部弹出约三分之一屏高的面板,采用房间页现有半透明磨砂风格,便于后续继续沿用当前视觉体系。
|
||||
- 已补齐语言房游戏列表占位结构:弹窗内部先用静态游戏数据驱动,并按“`ListView` 可上下滑动 + 每行 `5` 个图标位”的方式实现,后续只需替换入口图、列表图标和真实接口数据即可继续开发。
|
||||
- 创建并持续维护进度跟踪文件。
|
||||
- 已继续排查语言房 gift 动画链路:确认送礼后会同时走本地房间消息、滚屏礼物条和大额礼物全局飘屏三条路径,并修复动画管理器在控制器尚未绑定完成时提前消费队列导致后续动画不再播放的问题。
|
||||
- 已定位并修复语言房礼物飘屏资源引用错误:代码里误写 `sc_icon_gift_flosc_bg` / `sc_icon_luck_gift_flosc_*`,实际资源文件名为 `float`,导致点击送礼后飘屏背景图加载失败,相关动画无法正常显示。
|
||||
@ -25,6 +28,9 @@
|
||||
- 已继续细化首页房卡体验:`My` 板块房卡骨架数量已从 `4` 个补齐到 `6` 个,与首页 Party 首屏保持一致;同时将房卡右下角原本的静态绿色“直播中”图片替换为代码驱动的三段式动态音频条,首页、我的、搜索结果和房间浮窗现都会展示跳动效果,不再依赖静态资源图;最新已把动效调整为“单周期完整峰谷 + A/B/C 错峰起伏”的连续波形,观感更接近直播音频电平。
|
||||
- 已继续微调首页房卡信息密度:房卡底部房名与在线人数字号已统一调小,和动态音频条更协调;同时已移除 `My` 板块顶部 `my room` 标题及其占位,让房间卡片整体上移,对齐更紧凑。
|
||||
- 已继续微调首页视觉细节:房卡底栏房名再次缩小并轻微上移,和国旗、动态音频条、人数的中线更一致;首页顶部排行骨架也已简化为“3 个圆形头像 + 1 根文本条”,更贴近真实卡片结构。
|
||||
- 已补回首页 Party 顶部 banner 展示链路:顶部现优先显示原有 `explore` banner,并在该分组为空时自动兜底使用首页 `home` banner,避免整块区域消失;同时首屏 banner 空数据加载阶段已补充同风格骨架占位,不再直接塌陷为零高度。
|
||||
- 已继续微调首页 Party 顶部 banner 体验:banner 与下方排行组件之间已补齐接近房卡区的纵向间距;当 banner 只有 `1` 张时,会关闭无限轮播与横向回弹,左右滑动不再弹回中间。
|
||||
- 已继续收窄首页 Party 顶部 banner 骨架样式:banner 骨架现改为更干净的整块占位,不再在中间叠加长方形白条,首屏观感更接近真实 banner 的整体加载。
|
||||
- 已优化语言房顶部成员入口的点击范围:右上角在线成员区域现已将左侧头像堆叠区与右侧成员图标统一为同一点击热区,用户点击头像区也可直接打开成员列表页。
|
||||
- 已继续修复房间封面跨用户不同步问题:当列表或进房返回空封面时,客户端会自动补拉 `room/profile/specific` 详情并缓存真实封面;同时房主保存房间资料后会向房间内其他用户派发资料刷新消息,避免只有自己能看到新封面、其他用户仍显示 `no data`。
|
||||
- 已修复个人主页编辑页头像上传后无变化的问题:上传成功后会先本地回写新头像地址并立即提交;资料更新接口现同时兼容 `userAvatar/avatar` 字段,且不再被紧接着的旧资料回拉覆盖,返回个人主页后头像会即时更新。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user