Merge pull request 'problems' (#223) from problems into develop

Reviewed-on: https://repo.ravanertebat.com/TM/tm_app/pulls/223
This commit is contained in:
a.ghabeli
2026-04-16 16:48:36 +03:30
26 changed files with 516 additions and 355 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+8 -1
View File
@@ -41,7 +41,14 @@ List<SingleChildWidget> get essentialProviders {
List<SingleChildWidget> get cores {
return [
Provider(create: (context) => NetworkManager(context.read()), lazy: true),
Provider(
create: (context) {
final nm = NetworkManager(context.read());
return nm;
},
lazy: true,
),
ChangeNotifierProvider(create: (context) => ThemeProvider(), lazy: true),
ChangeNotifierProvider(
create: (context) => NotificationStateService(),
+12
View File
@@ -17,6 +17,14 @@ class NetworkManager {
bool _interceptorsAdded = false;
final SharedPreferences _prefs;
/// Callback invoked when refresh token fails (expired, missing, or errored).
/// Should clear local auth state and navigate to the login screen.
/// // Callback to be called when refresh token fails (logout callback)
Future<void> Function()? _onAuthFailed;
void setAuthFailed(Future<void> Function() callback) {
_onAuthFailed = callback;
}
NetworkManager(this._prefs)
: mainDio = Dio(
BaseOptions(
@@ -75,6 +83,10 @@ class NetworkManager {
mainDio.options.headers['Authorization'] = 'Bearer $newAccessToken';
return handler.resolve(await mainDio.fetch(error.requestOptions));
}
// Refresh token failed force logout
appLogger.error('❌ Refresh token failed, triggering logout');
await _onAuthFailed!();
}
return handler.next(error);
},
+1
View File
@@ -9,6 +9,7 @@ import '../../view_models/home_view_model.dart';
/// Wraps screens that need HomeViewModel
Widget homeProvider({required Widget child}) {
return ChangeNotifierProvider(
lazy: true,
create:
(context) => HomeViewModel(
homeRepository: context.read<HomeRepository>(),
+1
View File
@@ -37,6 +37,7 @@ class TendersService {
method: 'GET',
(json) => TendersResponse.fromJson(json),
);
print(result);
return result;
}
+24 -1
View File
@@ -10,6 +10,7 @@ import 'package:provider/single_child_widget.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tm_app/core/config/dependencies.dart';
import 'package:tm_app/core/config/web_url_strategy.dart';
import 'package:tm_app/core/network/network_manager.dart';
import 'package:tm_app/core/services/cache_init.dart';
import 'package:tm_app/core/services/firebase_service.dart';
import 'package:tm_app/core/theme/theme_provider.dart';
@@ -45,6 +46,8 @@ void main() async {
await FirebaseService().initializeApp();
runApp(MultiProvider(providers: providers, child: const MyApp()));
}
@@ -57,6 +60,24 @@ class MyApp extends StatefulWidget {
class _MyAppState extends State<MyApp> {
Future<void> setupInteractedMessage() async {
final overlayCtx = rootNavigatorKey.currentContext;
final networkManager = overlayCtx?.read<NetworkManager>();
networkManager?.setAuthFailed(() async {
debugPrint('🔄 Refresh token failed - navigating to auth screen');
// Use appRouter to navigate to auth screen
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.clear();
appRouter.go('/login');
});
// networkManager.onAuthFailure(() async {
// debugPrint('🔄 Refresh token failed - navigating to auth screen');
// // Use appRouter to navigate to auth screen
// final SharedPreferences _prefs = await SharedPreferences.getInstance();
// await _prefs.clear();
// appRouter.go(AppRoutes.auth);
// });
final initialMessage = await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) {
@@ -65,7 +86,7 @@ class _MyAppState extends State<MyApp> {
// if (kIsWeb) {
FirebaseMessaging.onMessage.listen((event) {
final overlayCtx = rootNavigatorKey.currentContext;
if (overlayCtx != null) {
if (overlayCtx.mounted) {
@@ -88,6 +109,8 @@ class _MyAppState extends State<MyApp> {
);
}
@override
void initState() {
super.initState();
+16 -2
View File
@@ -24,8 +24,8 @@ class AuthViewModel with ChangeNotifier {
bool _obscurePassword = true;
bool get obscurePassword => _obscurePassword;
bool get isLoginEnabled =>
usernameController.text.isNotEmpty && passwordController.text.isNotEmpty;
// bool get isLoginEnabled =>
// usernameController.text.isNotEmpty && passwordController.text.isNotEmpty;
// Auth state
bool _isLoading = false;
@@ -59,9 +59,23 @@ class AuthViewModel with ChangeNotifier {
}
}
// void _onTextChanged() {
// notifyListeners();
// }
bool _isLoginEnabled = false;
bool get isLoginEnabled => _isLoginEnabled;
void _onTextChanged() {
final newValue =
usernameController.text.isNotEmpty &&
passwordController.text.isNotEmpty;
if (newValue != _isLoginEnabled) {
_isLoginEnabled = newValue;
notifyListeners();
}
}
Future<void> login() async {
_isLoading = true;
+1 -1
View File
@@ -159,7 +159,7 @@ class HomeViewModel with ChangeNotifier {
);
_currentPage++;
if (metadata != null && (_currentPage >= metadata.pages!)) {
if (metadata != null && (_currentPage >= (metadata.pages ?? 0))) {
_hasMore = false;
}
} else {
+7 -6
View File
@@ -128,10 +128,11 @@ class DesktopHomePage extends StatelessWidget {
iconPath: AssetsManager.arrows,
title: HomeStrings.tenderSubmitted,
amount:
homeViewModel
.tenderApprovalsStateResponse!
.data!
.submittedTenders
(homeViewModel
.tenderApprovalsStateResponse
?.data
?.submittedTenders ??
0)
.toString(),
textColor: AppColors.mainBlue,
@@ -195,7 +196,7 @@ class DesktopHomePage extends StatelessWidget {
Widget _yourTenderText(HomeViewModel homeViewModel) {
final isYourTenders =
homeViewModel.data?.data?.tenders!.isNotEmpty ?? false;
(homeViewModel.data?.data?.tenders?.isNotEmpty ?? false);
return ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 740),
child: Align(
@@ -244,7 +245,7 @@ class DesktopHomePage extends StatelessWidget {
tender: homeViewModel.tenders[index],
onTap: () {
TenderDetailRouteData(
tenderId: homeViewModel.tenders[index].id!,
tenderId: homeViewModel.tenders[index].id ?? '',
).push(context);
},
);
+4 -3
View File
@@ -105,7 +105,8 @@ class MobileHomePage extends StatelessWidget {
iconPath: AssetsManager.arrows,
title: HomeStrings.tenderSubmitted,
amount:
tenderApprovalsStateResponse.data!.submittedTenders.toString(),
(tenderApprovalsStateResponse.data?.submittedTenders ?? 0)
.toString(),
textColor: AppColors.mainBlue,
enableTap: true,
onTap: () {
@@ -171,7 +172,7 @@ class MobileHomePage extends StatelessWidget {
Widget _yourTenderText(HomeViewModel homeViewModel) {
final isYourTenders =
homeViewModel.data?.data?.tenders!.isNotEmpty ?? false;
(homeViewModel.data?.data?.tenders?.isNotEmpty ?? false);
return Padding(
padding: EdgeInsets.symmetric(horizontal: 24.0.w()),
child: Text(
@@ -220,7 +221,7 @@ class MobileHomePage extends StatelessWidget {
tender: homeViewModel.tenders[index],
onTap: () {
TenderDetailRouteData(
tenderId: homeViewModel.tenders[index].id!,
tenderId: homeViewModel.tenders[index].id ?? '',
).push(context);
},
);
+4 -3
View File
@@ -124,7 +124,8 @@ class TabletHomePage extends StatelessWidget {
iconPath: AssetsManager.arrows,
title: HomeStrings.tenderSubmitted,
amount:
tenderApprovalsStateResponse.data!.submittedTenders.toString(),
(tenderApprovalsStateResponse.data?.submittedTenders ?? 0)
.toString(),
textColor: AppColors.mainBlue,
enableTap: true,
@@ -201,7 +202,7 @@ class TabletHomePage extends StatelessWidget {
Widget _yourTenderText(HomeViewModel homeViewModel) {
final isYourTenders =
homeViewModel.data?.data?.tenders!.isNotEmpty ?? false;
(homeViewModel.data?.data?.tenders?.isNotEmpty ?? false);
return Text(
isYourTenders ? HomeStrings.yourTenders : HomeStrings.recommendedTenders,
style: TextStyle(
@@ -241,7 +242,7 @@ class TabletHomePage extends StatelessWidget {
tender: homeViewModel.tenders[index],
onTap: () {
TenderDetailRouteData(
tenderId: homeViewModel.tenders[index].id!,
tenderId: homeViewModel.tenders[index].id ?? '',
).push(context);
},
);
+5 -5
View File
@@ -34,7 +34,7 @@ class TendersListItem extends StatelessWidget {
SizedBox(
width: double.infinity,
child: Text(
timeConvertor(tender.publicationDate!),
timeConvertor(tender.publicationDate ?? 0),
style: TextStyle(
fontSize: 12.0.sp(),
fontWeight: FontWeight.w400,
@@ -64,7 +64,7 @@ class TendersListItem extends StatelessWidget {
),
),
Text(
timeConvertor(tender.tenderDeadline!),
timeConvertor(tender.tenderDeadline ?? 0),
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w500,
@@ -76,7 +76,7 @@ class TendersListItem extends StatelessWidget {
),
SizedBox(height: 12.0.h()),
Text(
tender.title!,
tender.title ?? '',
style: TextStyle(
overflow: TextOverflow.ellipsis,
fontSize: 16.0.sp(),
@@ -88,7 +88,7 @@ class TendersListItem extends StatelessWidget {
SizedBox(
width: double.infinity,
child: Text(
tender.description!,
tender.description ?? '',
textAlign: TextAlign.start,
maxLines: 4,
overflow: TextOverflow.ellipsis,
@@ -110,7 +110,7 @@ class TendersListItem extends StatelessWidget {
SvgPicture.asset(AssetsManager.location),
SizedBox(width: 1.0.w()),
Text(
tender.countryCode!,
tender.countryCode ?? '',
style: TextStyle(
fontSize: 12.0.sp(),
fontWeight: FontWeight.w400,
@@ -9,6 +9,7 @@ import 'package:tm_app/view_models/liked_tenders_view_model.dart';
import 'package:tm_app/views/liked_tenders/widgets/liked_tenders_list_item.dart';
import 'package:tm_app/views/shared/page_selection_dialog.dart';
import '../../../core/routes/app_routes.dart';
import '../../../core/utils/app_toast.dart';
import '../../shared/desktop_navigation_widget.dart';
import '../../shared/tablet_desktop_appbar.dart';
@@ -175,7 +176,13 @@ class _LikedTendersDesktopPageState extends State<LikedTendersDesktopPage> {
),
),
SizedBox(width: 5.0.w()),
SvgPicture.asset(AssetsManager.arrowDownSmall,colorFilter: ColorFilter.mode(AppColors.grey60, BlendMode.srcIn),),
SvgPicture.asset(
AssetsManager.arrowDownSmall,
colorFilter: ColorFilter.mode(
AppColors.grey60,
BlendMode.srcIn,
),
),
SizedBox(width: 5.0.w()),
],
),
@@ -230,7 +237,13 @@ class _LikedTendersDesktopPageState extends State<LikedTendersDesktopPage> {
viewModel.removeTenderById(item.tenderId!);
}
},
child: LikedListItem(tender: item.tender!, isDesktop: true),
child: LikedListItem(
tender: item.tender!,
isDesktop: true,
onTap: () {
TenderDetailRouteData(tenderId: item.tenderId!).push(context);
},
),
),
);
},
@@ -10,6 +10,8 @@ import 'package:tm_app/views/liked_tenders/strings/liked_tenders_strings.dart';
import 'package:tm_app/views/liked_tenders/widgets/liked_tenders_list_item.dart';
import 'package:tm_app/views/login/widgets/widgets.dart';
import '../../../core/routes/app_routes.dart';
class LikedTendersMobilePage extends StatefulWidget {
const LikedTendersMobilePage({super.key});
@@ -209,7 +211,14 @@ class _LikedTendersMobilePageState extends State<LikedTendersMobilePage> {
viewModel.removeTenderById(item.tenderId!);
}
},
child: LikedListItem(tender: item.tender!),
child: LikedListItem(
tender: item.tender!,
onTap: () {
TenderDetailRouteData(
tenderId: item.tenderId!,
).push(context);
},
),
),
);
} else {
@@ -11,6 +11,7 @@ import 'package:tm_app/views/liked_tenders/widgets/liked_tenders_list_item.dart'
import 'package:tm_app/views/shared/page_selection_dialog.dart';
import 'package:tm_app/views/shared/tender_app_bar.dart';
import '../../../core/routes/app_routes.dart';
import '../../../core/utils/app_toast.dart';
import '../../shared/tablet_desktop_appbar.dart';
import '../../shared/tablet_navigation_widget.dart';
@@ -256,7 +257,12 @@ class _LikedTendersTabletPageState extends State<LikedTendersTabletPage> {
viewModel.removeTenderById(item.tenderId!);
}
},
child: LikedListItem(tender: item.tender!),
child: LikedListItem(
tender: item.tender!,
onTap: () {
TenderDetailRouteData(tenderId: item.tenderId!).push(context);
},
),
),
);
},
@@ -14,17 +14,21 @@ import '../../shared/flag.dart';
class LikedListItem extends StatelessWidget {
const LikedListItem({
required this.tender,
required this.onTap,
this.isDesktop = false,
super.key,
});
final TenderData tender;
final bool isDesktop;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final viewModel = context.read<LikedTendersViewModel>();
return Container(
return InkWell(
onTap: onTap,
child: Container(
decoration: BoxDecoration(
color: AppColors.cardBackground,
borderRadius: BorderRadius.circular(4),
@@ -33,7 +37,10 @@ class LikedListItem extends StatelessWidget {
width: double.infinity,
height: 250.0.h(),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0.w(), vertical: 16.0.h()),
padding: EdgeInsets.symmetric(
horizontal: 16.0.w(),
vertical: 16.0.h(),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -171,6 +178,7 @@ class LikedListItem extends StatelessWidget {
],
),
),
),
);
}
}
+31 -18
View File
@@ -56,9 +56,7 @@ class _LoginDesktopPageState extends State<LoginDesktopPage> {
child: Center(
child: SizedBox(
width: 364,
child: Consumer<AuthViewModel>(
builder: (context, viewModel, _) {
return Column(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(
@@ -80,33 +78,50 @@ class _LoginDesktopPageState extends State<LoginDesktopPage> {
prefixIconPadding: 7.0.w(),
),
SizedBox(height: 16.0.h()),
LoginTextField(
Selector<AuthViewModel, bool>(
selector: (_, vm) => vm.obscurePassword,
builder: (_, obscurePassword, __) {
return LoginTextField(
controller: viewModel.passwordController,
focusNode: viewModel.passwordFocus,
label: LoginStrings.passwordLabel,
iconPath: AssetsManager.passwordIcon,
prefixIconPadding: 7.0.w(),
isPassword: true,
obscureText: viewModel.obscurePassword,
fieldHeight: 60.0.h(),
obscureText: obscurePassword,
onToggleVisibility:
viewModel.togglePasswordVisibility,
borderRedus: 12,
);
},
),
SizedBox(height: 32.0.h()),
if (viewModel.isLoading)
const BaseButton(
/// ✅ Only this part rebuilds now
Selector<AuthViewModel, bool>(
selector: (_, vm) => vm.isLoading,
builder: (_, isLoading, __) {
return Selector<AuthViewModel, bool>(
selector: (_, vm) => vm.isLoginEnabled,
builder: (_, isEnabled, __) {
if (isLoading) {
return BaseButton(
isEnabled: false,
onPressed: null,
isLoading: true,
)
else
BaseButton(
isEnabled: viewModel.isLoginEnabled,
onPressed: () async {
if (viewModel.isLoginEnabled) {
textColor: AppColors.white,
);
}
return BaseButton(
isEnabled: isEnabled,
onPressed:
isEnabled
? () async {
await viewModel.login();
}
: null,
);
},
);
},
),
const SizedBox(height: 32.0),
@@ -124,8 +139,6 @@ class _LoginDesktopPageState extends State<LoginDesktopPage> {
),
),
],
);
},
),
),
),
+29 -15
View File
@@ -50,9 +50,7 @@ class _LoginMobilePageState extends State<LoginMobilePage> {
child: Center(
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 24.0.w()),
child: Consumer<AuthViewModel>(
builder: (context, viewModel, _) {
return Column(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// LoginLogo(width: 190.0.w(), height: 88.0.h()),
@@ -76,31 +74,49 @@ class _LoginMobilePageState extends State<LoginMobilePage> {
),
SizedBox(height: 16.0.h()),
LoginTextField(
Selector<AuthViewModel, bool>(
selector: (_, vm) => vm.obscurePassword,
builder: (_, obscurePassword, __) {
return LoginTextField(
controller: viewModel.passwordController,
focusNode: viewModel.passwordFocus,
label: LoginStrings.passwordLabel,
iconPath: AssetsManager.passwordIcon,
isPassword: true,
obscureText: viewModel.obscurePassword,
obscureText: obscurePassword,
onToggleVisibility: viewModel.togglePasswordVisibility,
);
},
),
SizedBox(height: 32.0.h()),
if (viewModel.isLoading)
BaseButton(
/// ✅ Only this part rebuilds now
Selector<AuthViewModel, bool>(
selector: (_, vm) => vm.isLoading,
builder: (_, isLoading, __) {
return Selector<AuthViewModel, bool>(
selector: (_, vm) => vm.isLoginEnabled,
builder: (_, isEnabled, __) {
if (isLoading) {
return BaseButton(
isEnabled: false,
onPressed: null,
isLoading: true,
textColor: AppColors.white,
)
else
BaseButton(
isEnabled: viewModel.isLoginEnabled,
onPressed: () async {
if (viewModel.isLoginEnabled) {
);
}
return BaseButton(
isEnabled: isEnabled,
onPressed:
isEnabled
? () async {
await viewModel.login();
}
: null,
);
},
);
},
),
SizedBox(height: 32.0.h()),
@@ -118,8 +134,6 @@ class _LoginMobilePageState extends State<LoginMobilePage> {
),
),
],
);
},
),
),
),
+29 -17
View File
@@ -52,9 +52,7 @@ class _LoginTabletPageState extends State<LoginTabletPage> {
padding: EdgeInsets.symmetric(horizontal: 24.0.w()),
child: SizedBox(
width: 364.0.w(),
child: Consumer<AuthViewModel>(
builder: (context, viewModel, _) {
return Column(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
@@ -78,34 +76,50 @@ class _LoginTabletPageState extends State<LoginTabletPage> {
prefixIconPadding: 4.0.w(),
),
SizedBox(height: 24.0.h()),
LoginTextField(
Selector<AuthViewModel, bool>(
selector: (_, vm) => vm.obscurePassword,
builder: (_, obscurePassword, __) {
return LoginTextField(
controller: viewModel.passwordController,
focusNode: viewModel.passwordFocus,
label: LoginStrings.passwordLabel,
iconPath: AssetsManager.passwordIcon,
prefixIconPadding: 4.0.w(),
isPassword: true,
obscureText: viewModel.obscurePassword,
fieldHeight: 60.0.h(),
obscureText: obscurePassword,
onToggleVisibility: viewModel.togglePasswordVisibility,
borderRedus: 12,
);
},
),
SizedBox(height: 70.0.h()),
if (viewModel.isLoading)
const BaseButton(
/// ✅ Only this part rebuilds now
Selector<AuthViewModel, bool>(
selector: (_, vm) => vm.isLoading,
builder: (_, isLoading, __) {
return Selector<AuthViewModel, bool>(
selector: (_, vm) => vm.isLoginEnabled,
builder: (_, isEnabled, __) {
if (isLoading) {
return BaseButton(
isEnabled: false,
onPressed: null,
isLoading: true,
)
else
BaseButton(
isEnabled: viewModel.isLoginEnabled,
textColor: AppColors.white,
);
}
return BaseButton(
isEnabled: isEnabled,
onPressed:
viewModel.isLoginEnabled
isEnabled
? () async {
await viewModel.login();
}
: null,
);
},
);
},
),
SizedBox(height: 30.0.h()),
TextButton(
@@ -122,8 +136,6 @@ class _LoginTabletPageState extends State<LoginTabletPage> {
),
),
],
);
},
),
),
),
+15 -14
View File
@@ -58,18 +58,19 @@ flutter:
- assets/icons/
- assets/svgs/
- assets/pngs/
- assets/fonts/
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package
fonts:
- family: Roboto
fonts:
- asset: assets/fonts/Roboto-Black.ttf
weight: 900
- asset: assets/fonts/Roboto-Bold.ttf
weight: 700
- asset: assets/fonts/Roboto-Medium.ttf
weight: 500
- asset: assets/fonts/Roboto-Regular.ttf
weight: 400
- asset: assets/fonts/Roboto-Light.ttf
weight: 300
- asset: assets/fonts/Roboto-Thin.ttf
+24
View File
@@ -0,0 +1,24 @@
(() => {
var P = () => navigator.vendor === "Google Inc." || navigator.agent === "Edg/", E = () => typeof ImageDecoder > "u" ? !1 : P(), L = () => typeof Intl.v8BreakIterator < "u" && typeof Intl.Segmenter < "u", W = () => { let n = [0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 95, 1, 120, 0]; return WebAssembly.validate(new Uint8Array(n)) }, w = { hasImageCodecs: E(), hasChromiumBreakIterators: L(), supportsWasmGC: W(), crossOriginIsolated: window.crossOriginIsolated }; function l(...n) { return new URL(C(...n), document.baseURI).toString() } function C(...n) { return n.filter(t => !!t).map((t, i) => i === 0 ? _(t) : j(_(t))).filter(t => t.length).join("/") } function j(n) { let t = 0; for (; t < n.length && n.charAt(t) === "/";)t++; return n.substring(t) } function _(n) { let t = n.length; for (; t > 0 && n.charAt(t - 1) === "/";)t--; return n.substring(0, t) } function T(n, t) { return n.canvasKitBaseUrl ? n.canvasKitBaseUrl : t.engineRevision && !t.useLocalCanvasKit ? C("https://www.gstatic.com/flutter-canvaskit", t.engineRevision) : "canvaskit" } var v = class { constructor() { this._scriptLoaded = !1 } setTrustedTypesPolicy(t) { this._ttPolicy = t } async loadEntrypoint(t) { let { entrypointUrl: i = l("main.dart.js"), onEntrypointLoaded: r, nonce: e } = t || {}; return this._loadJSEntrypoint(i, r, e) } async load(t, i, r, e, a) { a ??= o => { o.initializeEngine(r).then(c => c.runApp()) }; let { entryPointBaseUrl: s } = r; if (t.compileTarget === "dart2wasm") return this._loadWasmEntrypoint(t, i, s, a); { let o = t.mainJsPath ?? "main.dart.js", c = l(s, o); return this._loadJSEntrypoint(c, a, e) } } didCreateEngineInitializer(t) { typeof this._didCreateEngineInitializerResolve == "function" && (this._didCreateEngineInitializerResolve(t), this._didCreateEngineInitializerResolve = null, delete _flutter.loader.didCreateEngineInitializer), typeof this._onEntrypointLoaded == "function" && this._onEntrypointLoaded(t) } _loadJSEntrypoint(t, i, r) { let e = typeof i == "function"; if (!this._scriptLoaded) { this._scriptLoaded = !0; let a = this._createScriptTag(t, r); if (e) console.debug("Injecting <script> tag. Using callback."), this._onEntrypointLoaded = i, document.head.append(a); else return new Promise((s, o) => { console.debug("Injecting <script> tag. Using Promises. Use the callback approach instead!"), this._didCreateEngineInitializerResolve = s, a.addEventListener("error", o), document.head.append(a) }) } } async _loadWasmEntrypoint(t, i, r, e) { if (!this._scriptLoaded) { this._scriptLoaded = !0, this._onEntrypointLoaded = e; let { mainWasmPath: a, jsSupportRuntimePath: s } = t, o = l(r, a), c = l(r, s); this._ttPolicy != null && (c = this._ttPolicy.createScriptURL(c)); let d = (await import(c)).compileStreaming(fetch(o)), f; t.renderer === "skwasm" ? f = (async () => { let m = await i.skwasm; return window._flutter_skwasmInstance = m, { skwasm: m.wasmExports, skwasmWrapper: m, ffi: { memory: m.wasmMemory } } })() : f = Promise.resolve({}), await (await (await d).instantiate(await f)).invokeMain() } } _createScriptTag(t, i) { let r = document.createElement("script"); r.type = "application/javascript", i && (r.nonce = i); let e = t; return this._ttPolicy != null && (e = this._ttPolicy.createScriptURL(t)), r.src = e, r } }; async function I(n, t, i) { if (t < 0) return n; let r, e = new Promise((a, s) => { r = setTimeout(() => { s(new Error(`${i} took more than ${t}ms to resolve. Moving on.`, { cause: I })) }, t) }); return Promise.race([n, e]).finally(() => { clearTimeout(r) }) } var y = class {
setTrustedTypesPolicy(t) { this._ttPolicy = t } loadServiceWorker(t) {
if (!t) return console.debug("Null serviceWorker configuration. Skipping."), Promise.resolve(); if (!("serviceWorker" in navigator)) {
let o = "Service Worker API unavailable."; return window.isSecureContext || (o += `
The current context is NOT secure.`, o += `
Read more: https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts`), Promise.reject(new Error(o))
} let { serviceWorkerVersion: i, serviceWorkerUrl: r = l(`flutter_service_worker.js?v=${i}`), timeoutMillis: e = 4e3 } = t, a = r; this._ttPolicy != null && (a = this._ttPolicy.createScriptURL(a)); let s = navigator.serviceWorker.register(a).then(o => this._getNewServiceWorker(o, i)).then(this._waitForServiceWorkerActivation); return I(s, e, "prepareServiceWorker")
} async _getNewServiceWorker(t, i) { if (!t.active && (t.installing || t.waiting)) return console.debug("Installing/Activating first service worker."), t.installing || t.waiting; if (t.active.scriptURL.endsWith(i)) return console.debug("Loading from existing service worker."), t.active; { let r = await t.update(); return console.debug("Updating service worker."), r.installing || r.waiting || r.active } } async _waitForServiceWorkerActivation(t) { if (!t || t.state === "activated") if (t) { console.debug("Service worker already active."); return } else throw new Error("Cannot activate a null service worker!"); return new Promise((i, r) => { t.addEventListener("statechange", () => { t.state === "activated" && (console.debug("Activated new service worker."), i()) }) }) }
}; var g = class { constructor(t, i = "flutter-js") { let r = t || [/\.js$/, /\.mjs$/]; window.trustedTypes && (this.policy = trustedTypes.createPolicy(i, { createScriptURL: function (e) { if (e.startsWith("blob:")) return e; let a = new URL(e, window.location), s = a.pathname.split("/").pop(); if (r.some(c => c.test(s))) return a.toString(); console.error("URL rejected by TrustedTypes policy", i, ":", e, "(download prevented)") } })) } }; var k = n => { let t = WebAssembly.compileStreaming(fetch(n)); return (i, r) => ((async () => { let e = await t, a = await WebAssembly.instantiate(e, i); r(a, e) })(), {}) }; var b = (n, t, i, r) => (window.flutterCanvasKitLoaded = (async () => { if (window.flutterCanvasKit) return window.flutterCanvasKit; let e = i.hasChromiumBreakIterators && i.hasImageCodecs; if (!e && t.canvasKitVariant == "chromium") throw "Chromium CanvasKit variant specifically requested, but unsupported in this browser"; let a = e && t.canvasKitVariant !== "full", s = r; a && (s = l(s, "chromium")); let o = l(s, "canvaskit.js"); n.flutterTT.policy && (o = n.flutterTT.policy.createScriptURL(o)); let c = k(l(s, "canvaskit.wasm")), p = await import(o); return window.flutterCanvasKit = await p.default({ instantiateWasm: c }), window.flutterCanvasKit })(), window.flutterCanvasKitLoaded); var U = async (n, t, i, r) => { let e = i.crossOriginIsolated && !t.forceSingleThreadedSkwasm ? "skwasm" : "skwasm_st", s = l(r, `${e}.js`); n.flutterTT.policy && (s = n.flutterTT.policy.createScriptURL(s)); let o = k(l(r, `${e}.wasm`)); return await (await import(s)).default({ instantiateWasm: o, mainScriptUrlOrBlob: new Blob([`import '${s}'`], { type: "application/javascript" }) }) }; var S = class { async loadEntrypoint(t) { let { serviceWorker: i, ...r } = t || {}, e = new g, a = new y; a.setTrustedTypesPolicy(e.policy), await a.loadServiceWorker(i).catch(o => { console.warn("Exception while loading service worker:", o) }); let s = new v; return s.setTrustedTypesPolicy(e.policy), this.didCreateEngineInitializer = s.didCreateEngineInitializer.bind(s), s.loadEntrypoint(r) } async load({ serviceWorkerSettings: t, onEntrypointLoaded: i, nonce: r, config: e } = {}) { e ??= {}; let a = _flutter.buildConfig; if (!a) throw "FlutterLoader.load requires _flutter.buildConfig to be set"; let s = u => { switch (u) { case "skwasm": return w.hasChromiumBreakIterators && w.hasImageCodecs && w.supportsWasmGC; default: return !0 } }, o = (u, m) => { switch (u.renderer) { case "auto": return m == "canvaskit" || m == "html"; default: return u.renderer == m } }, c = u => u.compileTarget === "dart2wasm" && !w.supportsWasmGC || e.renderer && !o(u, e.renderer) ? !1 : s(u.renderer), p = a.builds.find(c); if (!p) throw "FlutterLoader could not find a build compatible with configuration and environment."; let d = {}; d.flutterTT = new g, t && (d.serviceWorkerLoader = new y, d.serviceWorkerLoader.setTrustedTypesPolicy(d.flutterTT.policy), await d.serviceWorkerLoader.loadServiceWorker(t).catch(u => { console.warn("Exception while loading service worker:", u) })); let f = T(e, a); p.renderer === "canvaskit" ? d.canvasKit = b(d, e, w, f) : p.renderer === "skwasm" && (d.skwasm = U(d, e, w, f)); let h = new v; return h.setTrustedTypesPolicy(d.flutterTT.policy), this.didCreateEngineInitializer = h.didCreateEngineInitializer.bind(h), h.load(p, d, e, r, i) } }; window._flutter || (window._flutter = {}); window._flutter.loader || (window._flutter.loader = new S);
})();
//# sourceMappingURL=flutter.js.map
if (!window._flutter) {
window._flutter = {};
}
_flutter.buildConfig = { "useLocalCanvasKit": true, "engineRevision": "cf56914b326edb0ccb123ffdc60f00060bd513fa", "builds": [{ "compileTarget": "dart2js", "renderer": "canvaskit", "mainJsPath": "main.dart.js" }] };
_flutter.loader.load({
serviceWorkerSettings: {
serviceWorkerVersion: "3781924539"
}
});