297 lines
8.7 KiB
Dart
297 lines
8.7 KiB
Dart
import 'dart:async';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
import 'package:webview_flutter/webview_flutter.dart';
|
|
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
|
import 'package:yumi/modules/room_game/bridge/baishun_js_bridge.dart';
|
|
import 'package:yumi/modules/room_game/data/models/room_game_models.dart';
|
|
import 'package:yumi/modules/room_game/data/room_game_repository.dart';
|
|
import 'package:yumi/modules/room_game/views/baishun_loading_view.dart';
|
|
import 'package:yumi/modules/wallet/wallet_route.dart';
|
|
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
|
|
|
class BaishunGamePage extends StatefulWidget {
|
|
const BaishunGamePage({
|
|
super.key,
|
|
required this.roomId,
|
|
required this.game,
|
|
required this.launchModel,
|
|
});
|
|
|
|
final String roomId;
|
|
final RoomGameListItemModel game;
|
|
final BaishunLaunchModel launchModel;
|
|
|
|
@override
|
|
State<BaishunGamePage> createState() => _BaishunGamePageState();
|
|
}
|
|
|
|
class _BaishunGamePageState extends State<BaishunGamePage> {
|
|
final RoomGameRepository _repository = RoomGameRepository();
|
|
late final WebViewController _controller;
|
|
|
|
bool _isLoading = true;
|
|
bool _isClosing = false;
|
|
String? _errorMessage;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_controller =
|
|
WebViewController()
|
|
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
|
..setBackgroundColor(Colors.black)
|
|
..addJavaScriptChannel(
|
|
BaishunJsBridge.channelName,
|
|
onMessageReceived: _handleBridgeMessage,
|
|
)
|
|
..setNavigationDelegate(
|
|
NavigationDelegate(
|
|
onPageFinished: (String _) async {
|
|
await _injectBridge();
|
|
},
|
|
onWebResourceError: (WebResourceError error) {
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
setState(() {
|
|
_errorMessage = error.description;
|
|
_isLoading = false;
|
|
});
|
|
},
|
|
),
|
|
);
|
|
unawaited(_loadGameEntry());
|
|
}
|
|
|
|
Future<void> _loadGameEntry() async {
|
|
try {
|
|
final entryUrl = widget.launchModel.entry.entryUrl.trim();
|
|
if (entryUrl.isEmpty || entryUrl.startsWith('mock://')) {
|
|
final html = await rootBundle.loadString(
|
|
'assets/debug/baishun_mock/index.html',
|
|
);
|
|
await _controller.loadHtmlString(
|
|
html,
|
|
baseUrl: 'https://baishun.mock.local/',
|
|
);
|
|
return;
|
|
}
|
|
|
|
final uri = Uri.tryParse(entryUrl);
|
|
if (uri == null) {
|
|
throw Exception('Invalid game entry url: $entryUrl');
|
|
}
|
|
await _controller.loadRequest(uri);
|
|
} catch (error) {
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
setState(() {
|
|
_errorMessage = error.toString();
|
|
_isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _injectBridge() async {
|
|
try {
|
|
await _controller.runJavaScript(BaishunJsBridge.bootstrapScript());
|
|
await _controller.runJavaScript(
|
|
BaishunJsBridge.buildConfigScript(widget.launchModel.bridgeConfig),
|
|
);
|
|
} catch (_) {}
|
|
}
|
|
|
|
Future<void> _handleBridgeMessage(JavaScriptMessage message) async {
|
|
final bridgeMessage = BaishunBridgeMessage.parse(message.message);
|
|
switch (bridgeMessage.action) {
|
|
case BaishunBridgeActions.getConfig:
|
|
await _controller.runJavaScript(
|
|
BaishunJsBridge.buildConfigScript(widget.launchModel.bridgeConfig),
|
|
);
|
|
break;
|
|
case BaishunBridgeActions.gameLoaded:
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
setState(() {
|
|
_isLoading = false;
|
|
_errorMessage = null;
|
|
});
|
|
break;
|
|
case BaishunBridgeActions.gameRecharge:
|
|
await SCNavigatorUtils.push(context, WalletRoute.recharge);
|
|
await _notifyWalletUpdate();
|
|
break;
|
|
case BaishunBridgeActions.destroy:
|
|
await _closeAndExit(reason: 'h5_destroy');
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
Future<void> _notifyWalletUpdate() async {
|
|
try {
|
|
await _controller.runJavaScript(BaishunJsBridge.buildWalletUpdateScript());
|
|
} catch (_) {}
|
|
}
|
|
|
|
Future<void> _reload() async {
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
setState(() {
|
|
_errorMessage = null;
|
|
_isLoading = true;
|
|
});
|
|
await _loadGameEntry();
|
|
}
|
|
|
|
Future<void> _closeAndExit({String reason = 'page_back'}) async {
|
|
if (_isClosing) {
|
|
return;
|
|
}
|
|
_isClosing = true;
|
|
try {
|
|
if (!widget.launchModel.gameSessionId.startsWith('bs_mock_')) {
|
|
await _repository.closeBaishunGame(
|
|
roomId: widget.roomId,
|
|
gameSessionId: widget.launchModel.gameSessionId,
|
|
reason: reason,
|
|
);
|
|
}
|
|
} catch (_) {}
|
|
if (mounted) {
|
|
Navigator.of(context).pop();
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return PopScope(
|
|
canPop: false,
|
|
onPopInvokedWithResult: (bool didPop, Object? result) async {
|
|
if (didPop) {
|
|
return;
|
|
}
|
|
await _closeAndExit();
|
|
},
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.only(
|
|
topLeft: Radius.circular(24.w),
|
|
topRight: Radius.circular(24.w),
|
|
),
|
|
child: Container(
|
|
width: ScreenUtil().screenWidth,
|
|
height: ScreenUtil().screenHeight * 0.8,
|
|
color: const Color(0xFF081915),
|
|
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),
|
|
),
|
|
),
|
|
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),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Stack(
|
|
children: [
|
|
Positioned.fill(child: WebViewWidget(controller: _controller)),
|
|
if (_errorMessage != null) _buildErrorState(),
|
|
if (_isLoading && _errorMessage == null)
|
|
const BaishunLoadingView(message: 'Waiting for gameLoaded...'),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildErrorState() {
|
|
return Positioned.fill(
|
|
child: Container(
|
|
color: Colors.black.withValues(alpha: 0.88),
|
|
padding: EdgeInsets.symmetric(horizontal: 28.w),
|
|
alignment: Alignment.center,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(Icons.error_outline, color: Colors.white70, size: 34.w),
|
|
SizedBox(height: 12.w),
|
|
Text(
|
|
'Game page failed to load',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
),
|
|
SizedBox(height: 8.w),
|
|
Text(
|
|
_errorMessage ?? '',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
color: Colors.white70,
|
|
fontSize: 12.sp,
|
|
),
|
|
),
|
|
SizedBox(height: 16.w),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
TextButton(
|
|
onPressed: _reload,
|
|
child: const Text('Retry'),
|
|
),
|
|
SizedBox(width: 10.w),
|
|
TextButton(
|
|
onPressed: () {
|
|
SCTts.show('Please check game host and package url');
|
|
},
|
|
child: const Text('Tips'),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|