feat(ios): 首次启动询问无线数据权限

iOS 在首个接口请求前等待系统无线数据选择,避免未授权时请求被静默拒绝。
This commit is contained in:
Zhou Weidong committed 2026-10-25 21:23:25 +08:00
1 parent 47df002d8e
commit b5e9deb4bd
6 files changed
+199 -1

No files matched your search

@@ -0,0 +1,62 @@
// Author: fruit.com.cn
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
/// iOS 首次启动等待系统无线数据选择,再发首个接口请求。
abstract final class WirelessDataPrompt {
const WirelessDataPrompt._();
/// 系统选择超过此时长仍未回调时继续启动。
static const Duration decisionTimeout = Duration(seconds: 15);
/// 与 iOS `WirelessDataPrompt.channelName` 保持一致。
static const String channelName = 'com.stormg.lumadrama/wireless-data';
static const String _awaitDecisionMethod = 'awaitDecision';
static const MethodChannel _channel = MethodChannel(channelName);
static Future<void>? _pending;
/// 非 iOS 立即返回。iOS 只等待一次,失败或超时后放行。
static Future<void> awaitDecision({
required bool enabled,
Future<void> Function()? ask,
Duration? timeout,
}) {
if (!enabled) return Future<void>.value();
return _pending ??= _askOnce(ask, timeout ?? decisionTimeout);
}
/// 测试隔离用,清掉已完成的等待。
@visibleForTesting
static void resetForTest() {
_pending = null;
}
static Future<void> _askOnce(
Future<void> Function()? ask,
Duration timeout,
) async {
try {
await (ask ?? _invokeChannel).call().timeout(timeout);
} on TimeoutException {
_log('decision timed out');
} catch (error) {
_log('decision skipped: $error');
}
}
static Future<void> _invokeChannel() {
return _channel.invokeMethod<void>(_awaitDecisionMethod);
}
static void _log(String message) {
if (kDebugMode) {
debugPrint('[wireless-data] $message');
}
}
}
+5 -1
View File
@@ -2,17 +2,21 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'app.dart';
import 'core/config/app_config.dart';
import 'core/config/server_endpoint.dart';
import 'core/platform/wireless_data_prompt.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await ServerEndpoint.load();
AppConfig.ready = AppConfig.probeAndApplyBaseUrl();
AppConfig.ready = WirelessDataPrompt.awaitDecision(
enabled: !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS,
).then((_) => AppConfig.probeAndApplyBaseUrl());
runApp(const LumaDramaApp());
unawaited(
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]),