diff --git a/lib/modules/room_game/views/baishun_game_page.dart b/lib/modules/room_game/views/baishun_game_page.dart index 036e77d..4d1c06c 100644 --- a/lib/modules/room_game/views/baishun_game_page.dart +++ b/lib/modules/room_game/views/baishun_game_page.dart @@ -411,6 +411,7 @@ class _BaishunGamePageState extends State { @override Widget build(BuildContext context) { + final topCrop = 28.w; return PopScope( canPop: false, onPopInvokedWithResult: (bool didPop, Object? result) async { @@ -430,69 +431,28 @@ class _BaishunGamePageState extends State { ), child: Container( width: ScreenUtil().screenWidth, - height: ScreenUtil().screenHeight * 0.75, + height: ScreenUtil().screenHeight * 0.5, color: const Color(0xFF081915), - child: Column( + child: Stack( 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), + Positioned( + top: -topCrop, + left: 0, + right: 0, + bottom: 0, + child: WebViewWidget( + controller: _controller, + gestureRecognizers: _webGestureRecognizers, ), ), - SizedBox(height: 6.w), - Padding( - padding: EdgeInsets.symmetric(horizontal: 10.w), - child: Row( - children: [ - IconButton( - onPressed: _closeAndExit, - icon: const Icon( - Icons.arrow_back_ios, - color: Colors.white, - ), - ), - Expanded( - child: Text( - widget.game.name, - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - fontSize: 16.sp, - fontWeight: FontWeight.w700, - ), - ), - ), - IconButton( - onPressed: _closeAndExit, - icon: const Icon(Icons.close, color: Colors.white), - ), - ], + if (_errorMessage != null) _buildErrorState(), + if (_isLoading && _errorMessage == null) + const IgnorePointer( + ignoring: true, + child: BaishunLoadingView( + message: 'Waiting for gameLoaded...', + ), ), - ), - Expanded( - child: Stack( - children: [ - Positioned.fill( - child: WebViewWidget( - controller: _controller, - gestureRecognizers: _webGestureRecognizers, - ), - ), - if (_errorMessage != null) _buildErrorState(), - if (_isLoading && _errorMessage == null) - const IgnorePointer( - ignoring: true, - child: BaishunLoadingView( - message: 'Waiting for gameLoaded...', - ), - ), - ], - ), - ), ], ), ), diff --git a/lib/modules/room_game/views/room_game_list_sheet.dart b/lib/modules/room_game/views/room_game_list_sheet.dart index a33d70d..91d87ac 100644 --- a/lib/modules/room_game/views/room_game_list_sheet.dart +++ b/lib/modules/room_game/views/room_game_list_sheet.dart @@ -1,5 +1,4 @@ import 'dart:io'; -import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; @@ -23,7 +22,11 @@ class RoomGameListSheet extends StatefulWidget { } class _RoomGameListSheetState extends State { - static const int _itemsPerRow = 5; + static const int _itemsPerRow = 4; + static const String _sheetFrameAsset = + 'sc_images/room/sc_room_game_sheet_frame.png'; + static const String _dividerLayerAsset = + 'sc_images/room/sc_room_game_divider_layer.png'; final RoomGameRepository _repository = RoomGameRepository(); late Future> _gamesFuture; @@ -93,7 +96,7 @@ class _RoomGameListSheetState extends State { widget.roomContext, BaishunGamePage(roomId: _roomId, game: game, launchModel: launchModel), barrierColor: Colors.black54, - barrierDismissible: false, + barrierDismissible: true, ); } catch (error) { SCTts.show('Launch failed'); @@ -110,48 +113,30 @@ class _RoomGameListSheetState extends State { Widget build(BuildContext context) { 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( + child: SizedBox( + width: ScreenUtil().screenWidth, + height: ScreenUtil().screenHeight * 0.5, + child: LayoutBuilder( + builder: (context, constraints) { + final innerLeft = constraints.maxWidth * 0.05; + final innerRight = constraints.maxWidth * 0.05; + final innerTop = constraints.maxHeight * 0.10; + final innerBottom = constraints.maxHeight * 0.0001; + + return Stack( 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), - ), + Positioned( + left: innerLeft, + right: innerRight, + top: innerTop, + bottom: innerBottom, + child: Container(color: const Color(0xFF0C331E)), ), - 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( - _roomId.isEmpty ? 'Debug room' : 'Room $_roomId', - fontSize: 11, - textColor: Colors.white70, - ), - ], - ), - ), - Expanded( + Positioned( + left: innerLeft + 5.w, + right: innerRight + 5.w, + top: innerTop + 50.w, + bottom: innerBottom + 2.w, child: FutureBuilder>( future: _gamesFuture, builder: (context, snapshot) { @@ -171,38 +156,22 @@ class _RoomGameListSheetState extends State { final rowCount = (items.length / _itemsPerRow).ceil(); return ListView.builder( - padding: EdgeInsets.fromLTRB(12.w, 0, 12.w, 20.w), + padding: EdgeInsets.zero, itemCount: rowCount, itemBuilder: (context, rowIndex) { + final startIndex = rowIndex * _itemsPerRow; + final endIndex = + (startIndex + _itemsPerRow) > items.length + ? items.length + : (startIndex + _itemsPerRow); + final rowItems = items.sublist(startIndex, endIndex); 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 < items.length - ? _RoomGameTile( - item: items[itemIndex], - loading: - _launchingGameId == - items[itemIndex].gameId, - onTap: - () => _openGame( - items[itemIndex], - ), - ) - : const SizedBox.shrink(), - ), - ); - }), + padding: EdgeInsets.only(bottom: 5.w), + child: _RoomGameRow( + dividerAsset: _dividerLayerAsset, + items: rowItems, + launchingGameId: _launchingGameId, + onTap: _openGame, ), ); }, @@ -210,9 +179,14 @@ class _RoomGameListSheetState extends State { }, ), ), + Positioned.fill( + child: IgnorePointer( + child: Image.asset(_sheetFrameAsset, fit: BoxFit.fill), + ), + ), ], - ), - ), + ); + }, ), ), ); @@ -223,7 +197,11 @@ class _RoomGameListSheetState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - text('Game list failed', fontSize: 13, textColor: Colors.white), + text( + 'Game list failed', + fontSize: 13, + textColor: const Color(0xFFFFF2D6), + ), SizedBox(height: 8.w), TextButton(onPressed: _retry, child: const Text('Retry')), ], @@ -236,7 +214,65 @@ class _RoomGameListSheetState extends State { child: text( 'No game configured', fontSize: 13, - textColor: Colors.white70, + textColor: const Color(0xFFFFF2D6), + ), + ); + } +} + +class _RoomGameRow extends StatelessWidget { + const _RoomGameRow({ + required this.dividerAsset, + required this.items, + required this.launchingGameId, + required this.onTap, + }); + + final String dividerAsset; + final List items; + final String? launchingGameId; + final ValueChanged onTap; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 108.w, + child: Stack( + clipBehavior: Clip.none, + children: [ + Positioned( + left: 0, + right: 0, + top: 50.w, + child: SizedBox( + height: 30.w, + child: Image.asset(dividerAsset, fit: BoxFit.fill), + ), + ), + Positioned.fill( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: List.generate(_RoomGameListSheetState._itemsPerRow, ( + index, + ) { + final item = index < items.length ? items[index] : null; + return Expanded( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 6.w), + child: + item == null + ? const SizedBox.shrink() + : _RoomGameTile( + item: item, + loading: launchingGameId == item.gameId, + onTap: () => onTap(item), + ), + ), + ); + }), + ), + ), + ], ), ); } @@ -265,20 +301,13 @@ class _RoomGameTile extends StatelessWidget { alignment: Alignment.center, children: [ Container( - width: 48.w, - height: 48.w, + width: 52.w, + height: 52.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), - ], - ), + color: const Color(0xFFD9D9D9), + borderRadius: BorderRadius.circular(5.w), border: Border.all( - color: Colors.white.withValues(alpha: 0.2), + color: Colors.white.withValues(alpha: 0.55), width: 1.w, ), ), @@ -287,10 +316,10 @@ class _RoomGameTile extends StatelessWidget { ), if (loading) Container( - width: 48.w, - height: 48.w, + width: 52.w, + height: 52.w, decoration: BoxDecoration( - shape: BoxShape.circle, + borderRadius: BorderRadius.circular(5.w), color: Colors.black.withValues(alpha: 0.45), ), alignment: Alignment.center, @@ -305,14 +334,17 @@ class _RoomGameTile extends StatelessWidget { ), ], ), - SizedBox(height: 6.w), - text( - item.name, - fontSize: 11, - textColor: Colors.white, - fontWeight: FontWeight.w500, - textAlign: TextAlign.center, - maxLines: 1, + SizedBox(height: 8.w), + SizedBox( + height: 18.w, + child: text( + item.name, + fontSize: 10, + textColor: const Color(0xFF4D3727), + fontWeight: FontWeight.w500, + textAlign: TextAlign.center, + maxLines: 1, + ), ), ], ), @@ -323,12 +355,16 @@ class _RoomGameTile extends StatelessWidget { if (item.cover.startsWith('http://') || item.cover.startsWith('https://')) { return CustomCachedImage( imageUrl: item.cover, - width: 48.w, - height: 48.w, + width: 52.w, + height: 52.w, fit: BoxFit.cover, - borderRadius: 24.w, + borderRadius: 5.w, ); } - return Icon(Icons.sports_esports_outlined, size: 22.w, color: Colors.white); + return Icon( + Icons.sports_esports_outlined, + size: 24.w, + color: const Color(0xFF6A6A6A), + ); } } diff --git a/sc_images/room/sc_room_game_divider_layer.png b/sc_images/room/sc_room_game_divider_layer.png new file mode 100644 index 0000000..e629acb Binary files /dev/null and b/sc_images/room/sc_room_game_divider_layer.png differ diff --git a/sc_images/room/sc_room_game_sheet_frame.png b/sc_images/room/sc_room_game_sheet_frame.png new file mode 100644 index 0000000..716a28b Binary files /dev/null and b/sc_images/room/sc_room_game_sheet_frame.png differ