7 Commits
10 changed files with 253 additions and 236 deletions
+6 -1
View File
@@ -1 +1,6 @@
flutter build apk --release --split-per-abi
#!/bin/sh
set -e
flutter build apk --release --split-per-abi
echo "构建完成时间: $(date '+%Y-%m-%d %H:%M:%S')"
+4 -3
View File
@@ -32,17 +32,18 @@ class AppConfig {
current = switch (environment) {
AppEnvironment.dev => const EnvironmentValues(
environment: AppEnvironment.dev,
baseUrl: 'http://192.168.1.104:8000',
// baseUrl: 'http://192.168.1.104:8000',
baseUrl: 'https://apitest.dronex.cc',
enableNetworkLog: true,
),
AppEnvironment.staging => const EnvironmentValues(
environment: AppEnvironment.staging,
baseUrl: 'https://example.com/api',
baseUrl: 'https://apitest.dronex.cc',
enableNetworkLog: true,
),
AppEnvironment.prod => const EnvironmentValues(
environment: AppEnvironment.prod,
baseUrl: 'https://example.com/api',
baseUrl: 'https://api.dronex.cc',
enableNetworkLog: false,
),
};
+1 -1
View File
@@ -27,7 +27,7 @@ class _AuthPageWidgetState extends ConsumerState<AuthPageWidget> {
WidgetsBinding.instance.addPostFrameCallback((_) async {
final token = AppStorage.getString(StorageKeys.authToken);
if (token?.isNotEmpty ?? false) {
AppNavigator.pushReplacement(const ScanQrCodePage());
AppNavigator.push(const ScanQrCodePage());
}
});
}
@@ -65,4 +65,10 @@ class AuthViewModel extends StateNotifier<AuthState> {
state = state.copyWith(recordList: data.items);
return true;
}
/// 清空授权信息(本地 token + 内存状态)
Future<void> clearAuth() async {
await AppStorage.remove(StorageKeys.authToken);
state = const AuthState();
}
}
+57 -107
View File
@@ -91,7 +91,7 @@ class EventRegistrationList {
return EventProfile(
name: name,
phone: '',
eventTitle: items.isEmpty ? '赛事信息' : items.first.eventTitle,
eventTitle: items.isEmpty ? '赛事信息' : items.first.eventName,
avatarUrl: avatar,
);
}
@@ -101,40 +101,45 @@ class EventRegistrationItem {
const EventRegistrationItem({
required this.eventId,
required this.itemId,
required this.userId,
required this.eventTitle,
required this.name,
required this.group,
required this.venue,
required this.time,
required this.playerName,
required this.playerPhone,
this.avatarLabel = '头像',
required this.scheduleId,
required this.eventName,
required this.itemName,
required this.groupName,
required this.matchPlace,
this.matchStartTime = '',
this.matchEndTime = '',
this.completed = false,
this.laneNo,
this.status,
this.rawData = const {},
this.opponentId = '',
this.opponentName = '',
this.teamMembers,
this.userId = '',
this.playerName = '',
this.playerPhone = '',
});
final String eventId;
final String itemId;
final String userId;
final String eventTitle;
final String name;
final String group;
final String venue;
final String time;
final String playerName;
final String playerPhone;
final String avatarLabel;
final String scheduleId;
final String eventName;
final String itemName;
final String groupName;
final String matchPlace;
final String matchStartTime;
final String matchEndTime;
final bool completed;
final String? laneNo;
final String? status;
final Map<String, dynamic> rawData;
final String opponentId;
final String opponentName;
final List<dynamic>? teamMembers;
/// 来自报名列表父级,不在 item JSON 内
final String userId;
final String playerName;
final String playerPhone;
String get scheduleTime =>
_formatScheduleTime(matchStartTime, matchEndTime);
String? get statusLabel => completed ? '已完成' : null;
factory EventRegistrationItem.fromJson(
Map<dynamic, dynamic> json, {
@@ -142,94 +147,48 @@ class EventRegistrationItem {
String playerName = '',
}) {
final map = Map<String, dynamic>.from(json);
final startTime = _readString(map, const ['matchStartTime', 'startTime']);
final endTime = _readString(map, const ['matchEndTime', 'endTime']);
final completed = _readBool(map, const ['completed']);
final startTime = _readString(map, const ['matchStartTime']);
final endTime = _readString(map, const ['matchEndTime']);
final teamMembersRaw = map['teamMembers'];
return EventRegistrationItem(
eventId: _readString(map, const ['eventId', 'eventsId', 'eventID']),
itemId: _readString(map, const ['itemId', 'eventItemId', 'itemID']),
userId: _readString(map, const [
'userId',
'playerId',
'registrationId',
], fallback: userId),
eventTitle: _readString(map, const [
'eventTitle',
'eventName',
'competitionName',
'matchTitle',
], fallback: '赛事信息'),
name: _readString(map, const [
'name',
'itemName',
'eventItemName',
'matchName',
'projectName',
], fallback: '未命名项目'),
group: _readString(map, const [
'group',
'groupName',
'categoryName',
'levelName',
]),
venue: _readString(map, const [
'matchPlace',
'venue',
'venueName',
'siteName',
'fieldName',
'placeName',
]),
time: _readScheduleTime(map, startTime, endTime),
playerName: _readString(map, const [
'playerName',
'userName',
'nameCn',
'realName',
'athleteName',
], fallback: playerName),
playerPhone: _readString(map, const [
'playerPhone',
'phone',
'mobile',
'telephone',
'userPhone',
]),
laneNo: _readNullableString(map, const [
'laneNo',
'laneNumber',
'trackNo',
'number',
'serialNo',
]),
eventId: _readString(map, const ['eventId']),
itemId: _readString(map, const ['itemId']),
scheduleId: _readString(map, const ['scheduleId']),
eventName: _readString(map, const ['eventName'], fallback: '赛事信息'),
itemName: _readString(map, const ['itemName'], fallback: '未命名项目'),
groupName: _readString(map, const ['groupName']),
matchPlace: _readString(map, const ['matchPlace']),
matchStartTime: startTime,
matchEndTime: endTime,
completed: completed,
status: completed ? '已完成' : null,
rawData: map,
completed: _readBool(map, const ['completed']),
opponentId: _readString(map, const ['opponentId']),
opponentName: _readString(map, const ['opponentName']),
teamMembers: teamMembersRaw is List
? List<dynamic>.from(teamMembersRaw)
: null,
userId: userId,
playerName: playerName,
);
}
EventProfile toProfile() {
return EventProfile(
avatarLabel: avatarLabel,
name: playerName,
phone: playerPhone,
eventTitle: eventTitle,
eventTitle: eventName,
);
}
RecordingContext toRecordingContext({EventProfile? profile}) {
return RecordingContext(
eventTitle: eventTitle,
matchName: name,
group: group,
venue: venue,
time: time,
eventTitle: eventName,
matchName: itemName,
group: groupName,
venue: matchPlace,
time: scheduleTime,
playerName: profile?.name ?? playerName,
playerPhone: profile?.phone ?? playerPhone,
laneNo: laneNo,
status: status,
status: statusLabel,
);
}
}
@@ -283,22 +242,13 @@ String _readString(
return _readNullableString(map, keys) ?? fallback;
}
String _readScheduleTime(
Map<String, dynamic> map,
String startTime,
String endTime,
) {
String _formatScheduleTime(String startTime, String endTime) {
if (startTime.isNotEmpty && endTime.isNotEmpty) {
return '$startTime-$endTime';
}
if (startTime.isNotEmpty) return startTime;
if (endTime.isNotEmpty) return endTime;
return _readString(map, const [
'time',
'competitionTime',
'matchTime',
'scheduleTime',
]);
return '';
}
int _readInt(Map<String, dynamic> map, List<String> keys, {int fallback = 0}) {
+19 -26
View File
@@ -5,14 +5,12 @@ import 'package:recording_tool/app/router/app_navigator.dart';
import 'package:recording_tool/features/events/model/model_event_info.dart';
import 'package:recording_tool/features/events/state/state_event_info.dart';
import 'package:recording_tool/features/events/view_model/view_model_event_info.dart';
import 'package:recording_tool/features/recording/pages/page_record.dart';
import 'package:recording_tool/shared/widgets/app_webview.dart';
import 'package:recording_tool/shared/widgets/widgets.dart';
class EventInfoPage extends ConsumerStatefulWidget {
const EventInfoPage({super.key, required this.playerId});
final String playerId;
static const mockRtmpUrl =
'rtmp://192.168.1.245:19090/蔡依婷vs夏志豪_空中格斗赛_高中组/蔡依婷vs夏志豪_空中格斗赛_高中组';
@override
ConsumerState<EventInfoPage> createState() => _EventInfoPageState();
@@ -22,21 +20,20 @@ class _EventInfoPageState extends ConsumerState<EventInfoPage> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
ref.read(eventInfoProvider.notifier).loadRegistrationList();
});
}
Future<void> onItemTap(EventRegistrationItem item) async {
debugPrint('item tapped: ${item.name}');
await ref.read(eventInfoProvider.notifier).requestStreamKey(item);
// debugPrint('item tapped: ${item.itemName}');
// await ref.read(eventInfoProvider.notifier).requestStreamKey(item);
if (!mounted) return;
final profile = ref.read(eventInfoProvider).profile;
// final profile = ref.read(eventInfoProvider).profile;
/// 个人赛
AppNavigator.push(
RecordingPage(
recordingContext: item.toRecordingContext(profile: profile),
streamUrl: EventInfoPage.mockRtmpUrl,
WebviewPage(
url: 'https://drone.apptest.sportsx.cc/#/pages/h5/score-entry',
eventRegistrationItem: item,
playerId: widget.playerId,
),
);
}
@@ -179,17 +176,13 @@ class _ScheduleList extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (state.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (state.items.isEmpty) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
state.errorMessage ?? '暂无赛事报名信息',
'暂无赛事报名信息',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18.sp, color: const Color(0xFF2F2F2F)),
),
@@ -236,9 +229,9 @@ class _ScheduleCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.group.isEmpty
? item.name
: '${item.name} ${item.group}',
item.groupName.isEmpty
? item.itemName
: '${item.itemName} ${item.groupName}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
@@ -250,7 +243,7 @@ class _ScheduleCard extends StatelessWidget {
),
SizedBox(height: 26.h),
Text(
item.venue,
item.matchPlace,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
@@ -261,7 +254,7 @@ class _ScheduleCard extends StatelessWidget {
),
SizedBox(height: 26.h),
Text(
item.time,
item.scheduleTime,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
@@ -297,7 +290,7 @@ class _ScheduleBadge extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (item.status != null) {
if (item.statusLabel != null) {
return CustomPaint(
painter: _CutCornerBorderPainter(color: const Color(0xFF7A7A7A)),
child: SizedBox(
@@ -305,7 +298,7 @@ class _ScheduleBadge extends StatelessWidget {
height: 90.h,
child: Center(
child: Text(
item.status!,
item.statusLabel!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 20.sp, color: const Color(0xFF2F2F2F)),
@@ -316,7 +309,7 @@ class _ScheduleBadge extends StatelessWidget {
}
return Text(
item.laneNo ?? '',
item.matchPlace,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
@@ -2,40 +2,29 @@ import 'package:recording_tool/features/events/model/model_event_info.dart';
class EventInfoState {
const EventInfoState({
this.isLoading = false,
this.isRequestingStreamKey = false,
this.errorMessage,
this.profile = const EventProfile(name: '', phone: '', eventTitle: '赛事信息'),
this.total = 0,
this.items = const [],
});
final bool isLoading;
final bool isRequestingStreamKey;
final String? errorMessage;
final EventProfile profile;
final int total;
final List<EventRegistrationItem> items;
String get eventTitle =>
items.isEmpty ? profile.eventTitle : items.first.eventTitle;
items.isEmpty ? profile.eventTitle : items.first.eventName;
EventInfoState copyWith({
bool? isLoading,
bool? isRequestingStreamKey,
String? errorMessage,
bool clearErrorMessage = false,
EventProfile? profile,
int? total,
List<EventRegistrationItem>? items,
}) {
return EventInfoState(
isLoading: isLoading ?? this.isLoading,
isRequestingStreamKey:
isRequestingStreamKey ?? this.isRequestingStreamKey,
errorMessage: clearErrorMessage
? null
: (errorMessage ?? this.errorMessage),
profile: profile ?? this.profile,
total: total ?? this.total,
items: items ?? this.items,
@@ -5,6 +5,7 @@ import 'package:recording_tool/core/network/api_exception.dart';
import 'package:recording_tool/features/events/model/model_event_info.dart';
import 'package:recording_tool/features/events/server/server_events.dart';
import 'package:recording_tool/features/events/state/state_event_info.dart';
import 'package:recording_tool/shared/widgets/app_toast.dart';
final eventInfoProvider =
StateNotifierProvider<EventInfoViewModel, EventInfoState>((ref) {
@@ -15,44 +16,42 @@ class EventInfoViewModel extends StateNotifier<EventInfoState> {
EventInfoViewModel(this._ref) : super(const EventInfoState());
static const defaultRegistrationRequest = PlayerRegistrationListReq(
userId: '74300708945530955',
userId: '',
status: '',
);
static const _fallbackEventId = '10224';
static const _fallbackItemId = '2318';
static const _fallbackStreamUserId = '74300708949725207';
static const _fallbackEventId = '';
static const _fallbackItemId = '';
static const _fallbackStreamUserId = '';
final Ref _ref;
Future<void> loadRegistrationList({
Future<bool> loadRegistrationList({
PlayerRegistrationListReq request = defaultRegistrationRequest,
}) async {
state = state.copyWith(isLoading: true, clearErrorMessage: true);
if (request.userId.isEmpty) {
return false;
}
try {
final result = await _ref
.read(eventsServerProvider)
.fetchPlayerRegistrationList(request);
debugPrint('报名列表请求成功: $result');
state = state.copyWith(
isLoading: false,
profile: result.toProfile(),
total: result.total,
items: result.items,
clearErrorMessage: true,
);
} on ApiException catch (error) {
state = state.copyWith(isLoading: false, errorMessage: error.message);
return true;
} catch (error) {
debugPrint('报名列表请求失败: $error');
state = state.copyWith(isLoading: false, errorMessage: '报名列表加载失败');
AppToast.show('报名列表加载失败');
return false;
}
}
Future<void> requestStreamKey(EventRegistrationItem item) async {
state = state.copyWith(
isRequestingStreamKey: true,
clearErrorMessage: true,
);
state = state.copyWith(isRequestingStreamKey: true);
final req = StreamKeyReq(
eventId: item.eventId.isNotEmpty ? item.eventId : _fallbackEventId,
itemId: item.itemId.isNotEmpty ? item.itemId : _fallbackItemId,
@@ -64,22 +63,15 @@ class EventInfoViewModel extends StateNotifier<EventInfoState> {
.read(eventsServerProvider)
.fetchStreamKey(req);
debugPrint('推流 Key 接口响应: $response');
state = state.copyWith(
isRequestingStreamKey: false,
clearErrorMessage: true,
);
state = state.copyWith(isRequestingStreamKey: false);
} on ApiException catch (error) {
debugPrint('推流 Key 接口请求失败: ${error.message}');
state = state.copyWith(
isRequestingStreamKey: false,
errorMessage: error.message,
);
state = state.copyWith(isRequestingStreamKey: false);
AppToast.show(error.message);
} catch (error) {
debugPrint('推流 Key 接口请求失败: $error');
state = state.copyWith(
isRequestingStreamKey: false,
errorMessage: '推流 Key 获取失败',
);
state = state.copyWith(isRequestingStreamKey: false);
AppToast.show('推流 Key 获取失败');
}
}
}
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:recording_tool/app/router/app_navigator.dart';
@@ -6,7 +7,9 @@ import 'package:recording_tool/core/cache/app_storage.dart';
import 'package:recording_tool/core/cache/storage_keys.dart';
import 'package:recording_tool/features/auth/pages/page_auth.dart';
import 'package:recording_tool/features/auth/view_model_auth/view_model_auth.dart';
import 'package:recording_tool/features/events/model/model_event_info.dart';
import 'package:recording_tool/features/events/pages/page_event_info.dart';
import 'package:recording_tool/features/events/view_model/view_model_event_info.dart';
import 'package:recording_tool/features/recording/model/model_recording_context.dart';
import 'package:recording_tool/shared/widgets/app_bar.dart';
import 'package:recording_tool/shared/widgets/app_button.dart';
@@ -54,64 +57,108 @@ class _AuthPageWidgetState extends ConsumerState<ScanQrCodePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: myAppBar(context: context, title: '扫码'),
body: Center(
child: Column(
children: [
SizedBox(height: 100.h),
Text('裁判工作台', style: TextStyle(fontSize: 30, color: Colors.black)),
SizedBox(height: 100.h),
Text(
'扫描选手参赛凭证进行执裁',
style: TextStyle(fontSize: 30, color: Colors.black),
),
SizedBox(height: 20.h),
return PopScope(
canPop: true,
onPopInvokedWithResult: (didPop, result) async {
if (!didPop) return;
await ref.read(authProvider.notifier).clearAuth();
},
child: Scaffold(
appBar: myAppBar(context: context, title: '扫码'),
body: Center(
child: Column(
children: [
SizedBox(height: 100.h),
Text(
'裁判工作台',
style: TextStyle(fontSize: 30, color: Colors.black),
),
SizedBox(height: 100.h),
Text(
'扫描选手参赛凭证进行执裁',
style: TextStyle(fontSize: 30, color: Colors.black),
),
SizedBox(height: 20.h),
Consumer(
builder: (context, ref, child) {
return SizedBox(
width: 280.w,
height: 80.h,
child: AppButton(
label: '查看录像',
onPressed: () async {
final data = ref.watch(
authProvider.select((state) => state.jwtDecodedData),
);
if (data == null) return;
debugPrint('赛事名字: ${data.eventName}');
final eventName = data.eventName ?? '';
await ref
.read(authProvider.notifier)
.getRecordList(eventName);
},
variant: AppButtonVariant.secondary,
),
);
},
),
SizedBox(height: 16.h),
SizedBox(
width: 280.w,
height: 80.h,
child: AppButton(
label: '扫码',
onPressed: () async {
final String? playerId = await AppQrScannerDialog.show(
context,
);
if (playerId == null || playerId.isEmpty) return;
/// TODO
AppNavigator.push(
EventInfoPage(playerId: '74300708945530955'),
Consumer(
builder: (context, ref, child) {
return SizedBox(
width: 280.w,
height: 80.h,
child: AppButton(
label: '查看录像',
onPressed: () async {
final data = ref.watch(
authProvider.select((state) => state.jwtDecodedData),
);
if (data == null) return;
debugPrint('赛事名字: ${data.eventName}');
final eventName = data.eventName ?? '';
await ref
.read(authProvider.notifier)
.getRecordList(eventName);
},
variant: AppButtonVariant.secondary,
),
);
},
variant: AppButtonVariant.secondary,
),
),
],
SizedBox(height: 16.h),
Consumer(
builder: (context, ref, child) {
return SizedBox(
width: 280.w,
height: 80.h,
child: AppButton(
label: '参赛队伍',
onPressed: () async {},
variant: AppButtonVariant.secondary,
),
);
},
),
SizedBox(height: 16.h),
SizedBox(
width: 280.w,
height: 80.h,
child: AppButton(
label: '扫码',
onPressed: () async {
final String? playerId = await AppQrScannerDialog.show(
context,
);
if (playerId == null || playerId.isEmpty) {
AppToast.show('查询选手信息失败');
return;
}
EasyLoading.show(status: '查询选手信息...');
try {
final success = await ref
.read(eventInfoProvider.notifier)
.loadRegistrationList(
request: PlayerRegistrationListReq(
userId: playerId,
),
);
EasyLoading.dismiss();
if (!success) {
AppToast.show('查询选手信息失败');
return;
}
if (!mounted) return;
AppNavigator.push(EventInfoPage(playerId: playerId));
} catch (error) {
AppToast.show('查询选手信息失败');
EasyLoading.dismiss();
}
},
variant: AppButtonVariant.secondary,
),
),
],
),
),
),
);
+39 -5
View File
@@ -4,6 +4,9 @@ import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:recording_tool/app/router/app_navigator.dart';
import 'package:recording_tool/core/cache/app_storage.dart';
import 'package:recording_tool/core/cache/storage_keys.dart';
import 'package:recording_tool/features/events/model/model_event_info.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:webview_flutter_android/webview_flutter_android.dart';
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
@@ -13,6 +16,8 @@ class WebviewPage extends StatefulWidget {
final String? title;
final String? assetPath;
final bool? canPop;
final EventRegistrationItem? eventRegistrationItem;
final String? playerId;
const WebviewPage({
super.key,
@@ -20,6 +25,8 @@ class WebviewPage extends StatefulWidget {
this.title,
this.assetPath,
this.canPop,
this.eventRegistrationItem,
this.playerId,
});
@override
@@ -57,7 +64,7 @@ class WebviewPageState extends State<WebviewPage> {
late final WebViewController controller;
controller = WebViewController.fromPlatformCreationParams(params)
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(Colors.transparent)
..setBackgroundColor(Colors.white)
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {
@@ -183,8 +190,7 @@ class WebviewPageState extends State<WebviewPage> {
/// 脚本注入传输给h5 数据
Future<void> _injectH5NeedData(WebViewController controller) async {
final token =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjgxNDI4NTczNTY2NjY4ODAwLCJkYXRhIjp7fSwic3ViIjoiODE0Mjg1NzM1NjY2Njg4MDAiLCJleHAiOjE3ODM5MzM1ODQsIm5iZiI6MTc4MzMyODc4NCwiaWF0IjoxNzgzMzI4Nzg0fQ.r1wYfvpV-5HIgwUuvq1Or3jPgjc3AhfJmoV1PNP23-Y';
final token = AppStorage.getString(StorageKeys.authToken);
try {
await controller.runJavaScript('''
(function () {
@@ -194,7 +200,20 @@ class WebviewPageState extends State<WebviewPage> {
window.__appInstallData = ${true};
window.AppBridge.token = '$token';
window.AppBridge.scoreEntryContext = {
eventId: '${widget.eventRegistrationItem?.eventId}',
itemId: '${widget.eventRegistrationItem?.itemId}',
scheduleId: '${widget.eventRegistrationItem?.scheduleId}',
userId: '${widget.playerId}',
opponentId: '${widget.eventRegistrationItem?.opponentId}',
};
// console.log('window.AppBridge?.scoreEntryContext.eventId', window.AppBridge?.scoreEntryContext.eventId);
// console.log('window.AppBridge?.scoreEntryContext.itemId', window.AppBridge?.scoreEntryContext.itemId);
// console.log('window.AppBridge?.scoreEntryContext.scheduleId', window.AppBridge?.scoreEntryContext.scheduleId);
// console.log('window.AppBridge?.scoreEntryContext.userId', window.AppBridge?.scoreEntryContext.userId);
// console.log('window.AppBridge?.scoreEntryContext.opponentId', window.AppBridge?.scoreEntryContext.opponentId);
// confirm('已经注入完成');
})();
''');
} catch (e) {
@@ -212,6 +231,21 @@ class WebviewPageState extends State<WebviewPage> {
return const PlatformWebViewControllerCreationParams();
}
Widget _buildWebView(WebViewController controller) {
PlatformWebViewWidgetCreationParams params =
PlatformWebViewWidgetCreationParams(controller: controller.platform);
if (WebViewPlatform.instance is AndroidWebViewPlatform) {
params =
AndroidWebViewWidgetCreationParams.fromPlatformWebViewWidgetCreationParams(
params,
displayWithHybridComposition: true,
);
}
return WebViewWidget.fromPlatformCreationParams(params: params);
}
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -275,7 +309,7 @@ class WebviewPageState extends State<WebviewPage> {
Expanded(
child: Stack(
children: [
Positioned.fill(child: WebViewWidget(controller: controller)),
Positioned.fill(child: _buildWebView(controller)),
if (_isLoading)
const Center(child: CircularProgressIndicator()),
],