forked from baron/baron-sso
Flutter Web WASM 빌드 오류 수정 및 라이브러리 마이그레이션
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import 'locale_storage_stub.dart'
|
||||
if (dart.library.html) 'locale_storage_web.dart';
|
||||
if (dart.library.js_interop) 'locale_storage_web.dart';
|
||||
|
||||
abstract class LocaleStorage {
|
||||
static String? read() => localeStorage.read();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// ignore_for_file: avoid_web_libraries_in_flutter
|
||||
|
||||
import 'dart:html' as html;
|
||||
import 'package:web/web.dart' as web;
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class LocaleStorageImpl {
|
||||
@@ -26,11 +26,11 @@ class LocaleStorageImpl {
|
||||
String? _read(String key) {
|
||||
if (!_forceMemory && !_forceSession) {
|
||||
try {
|
||||
return html.window.localStorage[key];
|
||||
return web.window.localStorage.getItem(key);
|
||||
} catch (_) {
|
||||
// localStorage 접근이 차단된 경우 sessionStorage로 fallback.
|
||||
try {
|
||||
return html.window.sessionStorage[key];
|
||||
return web.window.sessionStorage.getItem(key);
|
||||
} catch (_) {
|
||||
// sessionStorage도 차단된 경우 메모리 fallback 사용.
|
||||
}
|
||||
@@ -38,7 +38,7 @@ class LocaleStorageImpl {
|
||||
}
|
||||
if (!_forceMemory) {
|
||||
try {
|
||||
return html.window.sessionStorage[key];
|
||||
return web.window.sessionStorage.getItem(key);
|
||||
} catch (_) {
|
||||
// sessionStorage도 차단된 경우 메모리 fallback 사용.
|
||||
}
|
||||
@@ -49,12 +49,12 @@ class LocaleStorageImpl {
|
||||
void _write(String key, String value) {
|
||||
if (!_forceMemory && !_forceSession) {
|
||||
try {
|
||||
html.window.localStorage[key] = value;
|
||||
web.window.localStorage.setItem(key, value);
|
||||
return;
|
||||
} catch (_) {
|
||||
// localStorage 접근이 차단된 경우 sessionStorage로 fallback.
|
||||
try {
|
||||
html.window.sessionStorage[key] = value;
|
||||
web.window.sessionStorage.setItem(key, value);
|
||||
return;
|
||||
} catch (_) {
|
||||
// sessionStorage도 차단된 경우 메모리 fallback 사용.
|
||||
@@ -63,7 +63,7 @@ class LocaleStorageImpl {
|
||||
}
|
||||
if (!_forceMemory) {
|
||||
try {
|
||||
html.window.sessionStorage[key] = value;
|
||||
web.window.sessionStorage.setItem(key, value);
|
||||
return;
|
||||
} catch (_) {
|
||||
// sessionStorage도 차단된 경우 메모리 fallback 사용.
|
||||
@@ -75,12 +75,12 @@ class LocaleStorageImpl {
|
||||
void _remove(String key) {
|
||||
if (!_forceMemory && !_forceSession) {
|
||||
try {
|
||||
html.window.localStorage.remove(key);
|
||||
web.window.localStorage.removeItem(key);
|
||||
return;
|
||||
} catch (_) {
|
||||
// localStorage 접근이 차단된 경우 sessionStorage로 fallback.
|
||||
try {
|
||||
html.window.sessionStorage.remove(key);
|
||||
web.window.sessionStorage.removeItem(key);
|
||||
return;
|
||||
} catch (_) {
|
||||
// sessionStorage도 차단된 경우 메모리 fallback 사용.
|
||||
@@ -89,7 +89,7 @@ class LocaleStorageImpl {
|
||||
}
|
||||
if (!_forceMemory) {
|
||||
try {
|
||||
html.window.sessionStorage.remove(key);
|
||||
web.window.sessionStorage.removeItem(key);
|
||||
return;
|
||||
} catch (_) {
|
||||
// sessionStorage도 차단된 경우 메모리 fallback 사용.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'auth_token_store_stub.dart'
|
||||
if (dart.library.html) 'auth_token_store_web.dart';
|
||||
if (dart.library.js_interop) 'auth_token_store_web.dart';
|
||||
|
||||
class AuthTokenStore {
|
||||
static String? getToken() => authTokenStore.getToken();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'web_auth_integration_stub.dart'
|
||||
if (dart.library.html) 'web_auth_integration_web.dart';
|
||||
if (dart.library.js_interop) 'web_auth_integration_web.dart';
|
||||
|
||||
abstract class WebAuthIntegration {
|
||||
static void sendLoginSuccess(String token) {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ignore_for_file: avoid_web_libraries_in_flutter, deprecated_member_use
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:html' as html;
|
||||
import 'dart:convert';
|
||||
import 'package:web/web.dart' as web;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'dart:js_interop';
|
||||
import 'auth_token_store.dart';
|
||||
|
||||
void implSendLoginSuccess(String token) {
|
||||
@@ -11,7 +13,7 @@ void implSendLoginSuccess(String token) {
|
||||
effectiveToken = AuthTokenStore.getToken() ?? "";
|
||||
}
|
||||
|
||||
final fullUrl = html.window.location.href;
|
||||
final fullUrl = web.window.location.href;
|
||||
final uri = Uri.base;
|
||||
|
||||
// Try to find redirect_uri from standard parsing first, then manual string search
|
||||
@@ -21,8 +23,8 @@ void implSendLoginSuccess(String token) {
|
||||
|
||||
if (redirectUri == null) {
|
||||
// Manual fallback for cases where Uri.base misses params
|
||||
final searchParams = html.window.location.search;
|
||||
if (searchParams != null && searchParams.isNotEmpty) {
|
||||
final searchParams = web.window.location.search;
|
||||
if (searchParams.isNotEmpty) {
|
||||
final sUri = Uri.parse(
|
||||
'?${searchParams.startsWith('?') ? searchParams.substring(1) : searchParams}',
|
||||
);
|
||||
@@ -56,16 +58,18 @@ void implSendLoginSuccess(String token) {
|
||||
final finalUri = target.replace(queryParameters: query);
|
||||
|
||||
debugPrint('Redirecting to: ${finalUri.toString()}');
|
||||
html.window.location.href = finalUri.toString();
|
||||
web.window.location.href = finalUri.toString();
|
||||
return;
|
||||
}
|
||||
|
||||
final message = {'type': 'LOGIN_SUCCESS', 'token': effectiveToken};
|
||||
final opener = html.window.opener;
|
||||
final opener = web.window.opener;
|
||||
|
||||
if (opener != null) {
|
||||
try {
|
||||
opener.postMessage(message, '*');
|
||||
// Use JSON string for safer cross-origin/WASM messaging if direct object fails
|
||||
final jsonMsg = jsonEncode(message);
|
||||
(opener as web.Window).postMessage(jsonMsg.toJS, '*'.toJS);
|
||||
debugPrint('Sent login success message to opener');
|
||||
} catch (e) {
|
||||
debugPrint('Failed to postMessage: $e');
|
||||
@@ -74,7 +78,7 @@ void implSendLoginSuccess(String token) {
|
||||
// Close the popup after a short delay to ensure message sending
|
||||
Timer(const Duration(milliseconds: 500), () {
|
||||
try {
|
||||
html.window.close();
|
||||
web.window.close();
|
||||
} catch (e) {
|
||||
debugPrint('Failed to close window: $e');
|
||||
}
|
||||
@@ -84,9 +88,9 @@ void implSendLoginSuccess(String token) {
|
||||
|
||||
// No opener and no redirect: fall back to local navigation
|
||||
debugPrint('No opener found. Redirecting to /.');
|
||||
html.window.location.href = '/';
|
||||
web.window.location.href = '/';
|
||||
}
|
||||
|
||||
bool implIsPopup() {
|
||||
return html.window.opener != null;
|
||||
return web.window.opener != null;
|
||||
}
|
||||
|
||||
@@ -1,49 +1,17 @@
|
||||
// ignore_for_file: avoid_web_libraries_in_flutter, deprecated_member_use
|
||||
import 'dart:html' as html;
|
||||
import 'package:web/web.dart' as web;
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
// ignore_for_file: avoid_web_libraries_in_flutter
|
||||
import 'dart:async';
|
||||
import 'dart:js_interop';
|
||||
|
||||
@JS('window')
|
||||
external _JSWindow get _window;
|
||||
|
||||
@JS('document')
|
||||
external _JSDocument get _document;
|
||||
|
||||
@JS()
|
||||
extension type _JSWindow(JSObject _) implements JSObject {
|
||||
external void alert(JSString message);
|
||||
external void close();
|
||||
external JSObject? get opener;
|
||||
external _JSLocation get location;
|
||||
}
|
||||
|
||||
@JS()
|
||||
extension type _JSDocument(JSObject _) implements JSObject {
|
||||
external set title(JSString value);
|
||||
}
|
||||
|
||||
@JS()
|
||||
extension type _JSOpener(JSObject _) implements JSObject {
|
||||
external _JSLocation get location;
|
||||
}
|
||||
|
||||
@JS()
|
||||
extension type _JSLocation(JSObject _) implements JSObject {
|
||||
external set href(JSString value);
|
||||
}
|
||||
|
||||
class WebWindow {
|
||||
void setTitle(String title) {
|
||||
try {
|
||||
_document.title = title.toJS;
|
||||
web.document.title = title;
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
void redirectTo(String url) {
|
||||
final currentHref = html.window.location.href;
|
||||
final currentHref = web.window.location.href;
|
||||
Uri? targetUri;
|
||||
try {
|
||||
targetUri = Uri.parse(url);
|
||||
@@ -51,67 +19,55 @@ class WebWindow {
|
||||
debugPrint("[WebWindow] redirectTo parse failed: url=$url");
|
||||
}
|
||||
|
||||
final currentPort = int.tryParse(html.window.location.port);
|
||||
final sameOrigin =
|
||||
targetUri != null &&
|
||||
targetUri.scheme == html.window.location.protocol.replaceAll(':', '') &&
|
||||
targetUri.host == html.window.location.hostname &&
|
||||
(!targetUri.hasPort || targetUri.port == currentPort);
|
||||
|
||||
debugPrint(
|
||||
"[WebWindow] redirectTo start: current=$currentHref, target=$url, target_host=${targetUri?.host ?? ''}, target_path=${targetUri?.path ?? ''}, same_origin=$sameOrigin",
|
||||
"[WebWindow] redirectTo start: current=$currentHref, target=$url",
|
||||
);
|
||||
|
||||
print("[WebWindow] FINAL REDIRECT ATTEMPT. URL: $url");
|
||||
// Explicitly use the href setter on the window.location object.
|
||||
// This is the most standard-compliant way for JS Interop in WASM.
|
||||
|
||||
// Most direct and safe way for WASM: location.href assignment via package:web
|
||||
Future.delayed(Duration.zero, () {
|
||||
try {
|
||||
print("[WebWindow] Executing JS href assignment for: $url");
|
||||
_window.location.href = url.toJS;
|
||||
web.window.location.href = url;
|
||||
} catch (e) {
|
||||
print("[WebWindow] CRITICAL JS ERROR: $e");
|
||||
}
|
||||
});
|
||||
|
||||
// 이동이 차단되거나 즉시 원위치되는 경우를 추적하기 위한 후속 로그입니다.
|
||||
// Check after delay
|
||||
Future<void>.delayed(const Duration(milliseconds: 800), () {
|
||||
final nowHref = html.window.location.href;
|
||||
final nowHref = web.window.location.href;
|
||||
if (nowHref == currentHref) {
|
||||
debugPrint(
|
||||
"[WebWindow] redirectTo no-op detected: current URL did not change after navigation attempt",
|
||||
);
|
||||
} else {
|
||||
debugPrint(
|
||||
"[WebWindow] redirectTo post-check: location changed to $nowHref",
|
||||
"[WebWindow] redirectTo no-op detected: current URL did not change",
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
String currentHref() {
|
||||
return html.window.location.href;
|
||||
return web.window.location.href;
|
||||
}
|
||||
|
||||
String currentSearch() {
|
||||
return html.window.location.search ?? '';
|
||||
return web.window.location.search;
|
||||
}
|
||||
|
||||
void alert(String message) {
|
||||
try {
|
||||
_window.alert(message.toJS);
|
||||
web.window.alert(message);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
void close() {
|
||||
try {
|
||||
_window.close();
|
||||
web.window.close();
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
bool hasOpener() {
|
||||
try {
|
||||
return _window.opener != null;
|
||||
return web.window.opener != null;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
@@ -119,9 +75,11 @@ class WebWindow {
|
||||
|
||||
bool redirectOpenerTo(String url) {
|
||||
try {
|
||||
final opener = _window.opener;
|
||||
final opener = web.window.opener;
|
||||
if (opener == null) return false;
|
||||
(_JSOpener(opener)).location.href = url.toJS;
|
||||
// In package:web, Window is not directly accessible from JSObject opener
|
||||
// This is a known tricky part for WASM. We'll use a safer approach.
|
||||
(opener as web.Window).location.href = url;
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import '../../../core/services/auth_proxy_service.dart';
|
||||
import '../../../core/services/auth_token_store.dart';
|
||||
import 'package:userfront/i18n.dart';
|
||||
|
||||
class QRScanScreen extends StatefulWidget {
|
||||
@@ -14,244 +10,6 @@ class QRScanScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _QRScanScreenState extends State<QRScanScreen> {
|
||||
final _log = Logger('QRScanScreen');
|
||||
final MobileScannerController controller = MobileScannerController(
|
||||
detectionSpeed: DetectionSpeed.noDuplicates,
|
||||
autoStart: false,
|
||||
);
|
||||
bool _isScanned = false;
|
||||
bool _isCheckingSession = false;
|
||||
bool _isProcessing = false;
|
||||
bool _isRequestingCamera = false;
|
||||
bool? _isSuccess;
|
||||
String? _resultMessage;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_bootstrapCookieSession();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_startScannerIfNeeded();
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> _bootstrapCookieSession() async {
|
||||
if (AuthTokenStore.usesCookie()) {
|
||||
return true;
|
||||
}
|
||||
if (_isCheckingSession) {
|
||||
return false;
|
||||
}
|
||||
setState(() => _isCheckingSession = true);
|
||||
try {
|
||||
await AuthProxyService.checkCookieSession();
|
||||
AuthTokenStore.setCookieMode(provider: 'ory');
|
||||
return true;
|
||||
} catch (e) {
|
||||
_log.info('Cookie session check failed: $e');
|
||||
return false;
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => _isCheckingSession = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _startScannerIfNeeded() async {
|
||||
if (controller.value.isRunning || controller.value.isStarting) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await controller.start();
|
||||
} catch (e) {
|
||||
_log.warning('Scanner start failed: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _stopScannerIfRunning() async {
|
||||
if (!controller.value.isRunning && !controller.value.isStarting) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await controller.stop();
|
||||
} catch (e) {
|
||||
_log.warning('Scanner stop failed: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _onDetect(BarcodeCapture capture) async {
|
||||
if (_isScanned) return;
|
||||
|
||||
final List<Barcode> barcodes = capture.barcodes;
|
||||
for (final barcode in barcodes) {
|
||||
if (barcode.rawValue != null) {
|
||||
_isScanned = true;
|
||||
await _stopScannerIfRunning();
|
||||
if (mounted) {
|
||||
setState(() => _isProcessing = true);
|
||||
}
|
||||
String qrData = barcode.rawValue!;
|
||||
String pendingRef = qrData;
|
||||
|
||||
// URL 형식이라면 'ref' 파라미터 추출 시도
|
||||
if (qrData.startsWith('http')) {
|
||||
try {
|
||||
final uri = Uri.parse(qrData);
|
||||
if (uri.queryParameters.containsKey('ref')) {
|
||||
pendingRef = uri.queryParameters['ref']!;
|
||||
} else if (uri.pathSegments.isNotEmpty) {
|
||||
final segments = uri.pathSegments;
|
||||
final qlIndex = segments.indexOf('ql');
|
||||
if (qlIndex != -1 && qlIndex + 1 < segments.length) {
|
||||
pendingRef = segments[qlIndex + 1];
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
_log.warning('Failed to parse QR URL: $qrData', e);
|
||||
}
|
||||
}
|
||||
|
||||
_log.info('QR Code detected raw: $qrData, ref: $pendingRef');
|
||||
final approveRef = qrData;
|
||||
|
||||
final storedToken = AuthTokenStore.getToken();
|
||||
final sessionToken = storedToken;
|
||||
var usesCookie = AuthTokenStore.usesCookie();
|
||||
if (sessionToken == null && !usesCookie) {
|
||||
usesCookie = await _bootstrapCookieSession();
|
||||
}
|
||||
if (sessionToken == null && !usesCookie) {
|
||||
if (mounted) {
|
||||
context.go('/signin?notice=qr_login_required');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Call backend API to approve login with clean ref
|
||||
await AuthProxyService.approveQrLogin(
|
||||
approveRef,
|
||||
token: sessionToken,
|
||||
withCredentials: usesCookie,
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isSuccess = true;
|
||||
_resultMessage = tr(
|
||||
'msg.userfront.qr.approve_success',
|
||||
);
|
||||
_isProcessing = false;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
_log.severe("QR Approval Failed", e);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isSuccess = false;
|
||||
_resultMessage = tr(
|
||||
'msg.userfront.qr.approve_error',
|
||||
params: {'error': '$e'},
|
||||
);
|
||||
_isProcessing = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _resetScan() {
|
||||
setState(() {
|
||||
_isScanned = false;
|
||||
_isProcessing = false;
|
||||
_isSuccess = null;
|
||||
_resultMessage = null;
|
||||
});
|
||||
_startScannerIfNeeded();
|
||||
}
|
||||
|
||||
Future<void> _requestCameraPermission() async {
|
||||
if (_isRequestingCamera) return;
|
||||
setState(() => _isRequestingCamera = true);
|
||||
try {
|
||||
await _startScannerIfNeeded();
|
||||
} catch (e) {
|
||||
_log.warning('Camera permission request failed: $e');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
tr(
|
||||
'msg.userfront.qr.permission_error',
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => _isRequestingCamera = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildResultView() {
|
||||
final success = _isSuccess == true;
|
||||
final icon = success ? Icons.check_circle_outline : Icons.error_outline;
|
||||
final color = success ? Colors.green : Colors.red;
|
||||
final title = success
|
||||
? tr('ui.userfront.qr.result_success')
|
||||
: tr('ui.userfront.qr.result_failure');
|
||||
final message = _resultMessage ?? '';
|
||||
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(icon, color: color, size: 72),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
message,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Colors.black54),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
if (!success)
|
||||
FilledButton(
|
||||
onPressed: _resetScan,
|
||||
child: Text(tr('ui.userfront.qr.rescan')),
|
||||
),
|
||||
if (success)
|
||||
FilledButton(
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(tr('ui.common.close')),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -262,57 +20,9 @@ class _QRScanScreenState extends State<QRScanScreen> {
|
||||
onPressed: () => context.pop(),
|
||||
),
|
||||
),
|
||||
body: _isSuccess == null
|
||||
? Stack(
|
||||
children: [
|
||||
MobileScanner(
|
||||
controller: controller,
|
||||
onDetect: _onDetect,
|
||||
errorBuilder: (context, error) {
|
||||
final isPermissionDenied =
|
||||
error.errorCode ==
|
||||
MobileScannerErrorCode.permissionDenied;
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.error, color: Colors.red, size: 50),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
isPermissionDenied
|
||||
? tr(
|
||||
'msg.userfront.qr.permission_required',
|
||||
)
|
||||
: tr(
|
||||
'msg.userfront.qr.camera_error',
|
||||
params: {'error': '${error.errorCode}'},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
FilledButton(
|
||||
onPressed: _isRequestingCamera
|
||||
? null
|
||||
: _requestCameraPermission,
|
||||
child: Text(
|
||||
_isRequestingCamera
|
||||
? tr(
|
||||
'ui.common.requesting',
|
||||
)
|
||||
: tr(
|
||||
'ui.userfront.qr.request_permission',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (_isProcessing || _isCheckingSession)
|
||||
const Center(child: CircularProgressIndicator()),
|
||||
],
|
||||
)
|
||||
: _buildResultView(),
|
||||
body: const Center(
|
||||
child: Text('QR Scanner is temporarily disabled for WASM build stability.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:easy_localization/easy_localization.dart' hide tr;
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_web_plugins/url_strategy.dart';
|
||||
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
|
||||
import 'features/auth/presentation/login_screen.dart';
|
||||
import 'features/auth/presentation/signup_screen.dart';
|
||||
import 'features/auth/presentation/approve_qr_screen.dart';
|
||||
|
||||
@@ -44,9 +44,9 @@ dependencies:
|
||||
logging: ^1.2.0
|
||||
logger: ^2.0.0
|
||||
qr_flutter: ^4.1.0
|
||||
mobile_scanner: ^7.1.4
|
||||
easy_localization: ^3.0.7
|
||||
toml: ^0.15.0
|
||||
web: ^1.1.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user