Speed Phase 1+2: Deferred loading for 8 heavy screens (code-split for web), branded pre-Flutter splash screen with spinner, preconnect hints, remove dead imports
This commit is contained in:
parent
6c0de3b439
commit
96468521cf
BIN
media/facebook/banner.png
Normal file
BIN
media/facebook/banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
|
|
@ -4,24 +4,28 @@ import 'package:go_router/go_router.dart';
|
|||
import 'package:latlong2/latlong.dart';
|
||||
import '../services/auth_service.dart';
|
||||
import '../services/api_service.dart';
|
||||
|
||||
// ── Eager (critical path) ─────────────────────────────────────────────
|
||||
import '../screens/home/feed_personal_screen.dart';
|
||||
import '../screens/home/home_shell.dart';
|
||||
import '../screens/auth/auth_gate.dart';
|
||||
|
||||
// ── Eager (admin – small, behind role check) ──────────────────────────
|
||||
import '../screens/admin/admin_dashboard_screen.dart';
|
||||
import '../screens/admin/admin_scaffold.dart';
|
||||
import '../screens/admin/admin_user_base_screen.dart';
|
||||
import '../screens/admin/moderation_queue_screen.dart';
|
||||
import '../screens/admin/admin_content_tools_screen.dart';
|
||||
import '../screens/beacon/beacon_screen.dart';
|
||||
import '../screens/home/feed_personal_screen.dart';
|
||||
import '../screens/home/home_shell.dart';
|
||||
import '../screens/quips/create/quip_creation_flow.dart';
|
||||
import '../screens/quips/feed/quips_feed_screen.dart';
|
||||
import '../screens/profile/viewable_profile_screen.dart';
|
||||
import '../screens/profile/blocked_users_screen.dart';
|
||||
import '../screens/auth/auth_gate.dart';
|
||||
import '../screens/discover/discover_screen.dart';
|
||||
import '../screens/secure_chat/secure_chat_full_screen.dart';
|
||||
import '../screens/secure_chat/secure_chat_loader_screen.dart';
|
||||
import '../screens/post/threaded_conversation_screen.dart';
|
||||
import '../screens/notifications/notifications_screen.dart';
|
||||
|
||||
// ── Deferred (code-split for web) ─────────────────────────────────────
|
||||
import '../screens/beacon/beacon_screen.dart' deferred as beacon_lib;
|
||||
import '../screens/quips/create/quip_creation_flow.dart' deferred as quip_create_lib;
|
||||
import '../screens/quips/feed/quips_feed_screen.dart' deferred as quips_feed_lib;
|
||||
import '../screens/profile/viewable_profile_screen.dart' deferred as profile_lib;
|
||||
import '../screens/profile/blocked_users_screen.dart' deferred as blocked_lib;
|
||||
import '../screens/secure_chat/secure_chat_full_screen.dart' deferred as secure_chat_lib;
|
||||
import '../screens/secure_chat/secure_chat_loader_screen.dart' deferred as chat_loader_lib;
|
||||
import '../screens/post/threaded_conversation_screen.dart' deferred as threaded_lib;
|
||||
|
||||
/// App routing config (GoRouter).
|
||||
class AppRoutes {
|
||||
|
|
@ -54,25 +58,37 @@ class AppRoutes {
|
|||
GoRoute(
|
||||
path: '$userPrefix/:username',
|
||||
parentNavigatorKey: rootNavigatorKey,
|
||||
builder: (_, state) => UnifiedProfileScreen(
|
||||
handle: state.pathParameters['username'] ?? '',
|
||||
builder: (_, state) => _deferred(
|
||||
profile_lib.loadLibrary,
|
||||
() => profile_lib.UnifiedProfileScreen(
|
||||
handle: state.pathParameters['username'] ?? '',
|
||||
),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: quipCreate,
|
||||
parentNavigatorKey: rootNavigatorKey,
|
||||
builder: (_, __) => const QuipCreationFlow(),
|
||||
builder: (_, __) => _deferred(
|
||||
quip_create_lib.loadLibrary,
|
||||
() => quip_create_lib.QuipCreationFlow(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: secureChat,
|
||||
parentNavigatorKey: rootNavigatorKey,
|
||||
builder: (_, __) => const SecureChatFullScreen(),
|
||||
builder: (_, __) => _deferred(
|
||||
secure_chat_lib.loadLibrary,
|
||||
() => secure_chat_lib.SecureChatFullScreen(),
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: ':id',
|
||||
parentNavigatorKey: rootNavigatorKey,
|
||||
builder: (_, state) => SecureChatLoaderScreen(
|
||||
conversationId: state.pathParameters['id'] ?? '',
|
||||
builder: (_, state) => _deferred(
|
||||
chat_loader_lib.loadLibrary,
|
||||
() => chat_loader_lib.SecureChatLoaderScreen(
|
||||
conversationId: state.pathParameters['id'] ?? '',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -80,8 +96,11 @@ class AppRoutes {
|
|||
GoRoute(
|
||||
path: '$postPrefix/:id',
|
||||
parentNavigatorKey: rootNavigatorKey,
|
||||
builder: (_, state) => ThreadedConversationScreen(
|
||||
rootPostId: state.pathParameters['id'] ?? '',
|
||||
builder: (_, state) => _deferred(
|
||||
threaded_lib.loadLibrary,
|
||||
() => threaded_lib.ThreadedConversationScreen(
|
||||
rootPostId: state.pathParameters['id'] ?? '',
|
||||
),
|
||||
),
|
||||
),
|
||||
StatefulShellRoute.indexedStack(
|
||||
|
|
@ -101,8 +120,11 @@ class AppRoutes {
|
|||
routes: [
|
||||
GoRoute(
|
||||
path: quips,
|
||||
builder: (_, state) => QuipsFeedScreen(
|
||||
initialPostId: state.uri.queryParameters['postId'],
|
||||
builder: (_, state) => _deferred(
|
||||
quips_feed_lib.loadLibrary,
|
||||
() => quips_feed_lib.QuipsFeedScreen(
|
||||
initialPostId: state.uri.queryParameters['postId'],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
|
|
@ -112,7 +134,10 @@ class AppRoutes {
|
|||
routes: [
|
||||
GoRoute(
|
||||
path: '/beacon',
|
||||
builder: (_, __) => const BeaconScreen(),
|
||||
builder: (_, __) => _deferred(
|
||||
beacon_lib.loadLibrary,
|
||||
() => beacon_lib.BeaconScreen(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -120,11 +145,17 @@ class AppRoutes {
|
|||
routes: [
|
||||
GoRoute(
|
||||
path: profile,
|
||||
builder: (_, __) => const UnifiedProfileScreen(),
|
||||
builder: (_, __) => _deferred(
|
||||
profile_lib.loadLibrary,
|
||||
() => profile_lib.UnifiedProfileScreen(),
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'blocked',
|
||||
builder: (_, __) => const BlockedUsersScreen(),
|
||||
builder: (_, __) => _deferred(
|
||||
blocked_lib.loadLibrary,
|
||||
() => blocked_lib.BlockedUsersScreen(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -259,3 +290,27 @@ class AuthRefreshNotifier extends ChangeNotifier {
|
|||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Deferred loading helper ───────────────────────────────────────────
|
||||
/// Wraps a deferred import in a FutureBuilder that shows a loading
|
||||
/// spinner until the JS chunk is fetched, then builds the real screen.
|
||||
Widget _deferred(Future<void> Function() load, Widget Function() build) {
|
||||
return FutureBuilder<void>(
|
||||
future: load(),
|
||||
builder: (_, snap) {
|
||||
if (snap.connectionState != ConnectionState.done) {
|
||||
return const Scaffold(
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
if (snap.hasError) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Text('Failed to load. Please go back and try again.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
return build();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!--
|
||||
If you are serving your web app in a path other than the root, change the
|
||||
href value below to reflect the base path you are serving from.
|
||||
|
||||
The path provided below has to start and end with a slash "/" in order for
|
||||
it to work correctly.
|
||||
|
||||
For more details:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
||||
|
||||
This is a placeholder for base href that will be replaced by the value of
|
||||
the `--base-href` argument provided to `flutter build`.
|
||||
-->
|
||||
<base href="$FLUTTER_BASE_HREF">
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||
<meta name="description" content="Sojorn - Friend's Only. A product of MPLS LLC.">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="theme-color" content="#0A1128">
|
||||
|
||||
<!-- iOS meta tags & icons -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
|
|
@ -30,14 +18,65 @@
|
|||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
|
||||
<!-- Preconnect to CDN origins for faster sub-resource fetching -->
|
||||
<link rel="preconnect" href="https://www.gstatic.com" crossorigin>
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
|
||||
<title>sojorn</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
|
||||
<!-- Pre-Flutter loading screen (renders before any JS executes) -->
|
||||
<style>
|
||||
body { margin: 0; padding: 0; }
|
||||
#loading {
|
||||
position: fixed; inset: 0; z-index: 9999;
|
||||
display: flex; flex-direction: column;
|
||||
align-items: center; justify-content: center;
|
||||
background: #0A1128;
|
||||
transition: opacity .3s ease;
|
||||
}
|
||||
#loading .logo {
|
||||
width: 64px; height: 64px;
|
||||
border-radius: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
#loading .spinner {
|
||||
width: 28px; height: 28px;
|
||||
border: 3px solid rgba(255,255,255,.15);
|
||||
border-top-color: #4A90D9;
|
||||
border-radius: 50%;
|
||||
animation: spin .8s linear infinite;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
</style>
|
||||
|
||||
<script src="https://www.gstatic.com/firebasejs/10.7.0/firebase-app-compat.js"></script>
|
||||
<script src="https://www.gstatic.com/firebasejs/10.7.0/firebase-messaging-compat.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Shown instantly while Flutter engine + app JS loads -->
|
||||
<div id="loading">
|
||||
<img class="logo" src="icons/Icon-192.png" alt="sojorn">
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
|
||||
<script src="flutter_bootstrap.js" async></script>
|
||||
<script>
|
||||
// Remove loading screen once Flutter's glass pane is in the DOM
|
||||
(function() {
|
||||
var observer = new MutationObserver(function() {
|
||||
if (document.querySelector('flt-glass-pane')) {
|
||||
var el = document.getElementById('loading');
|
||||
if (el) {
|
||||
el.style.opacity = '0';
|
||||
setTimeout(function() { el.remove(); }, 300);
|
||||
}
|
||||
observer.disconnect();
|
||||
}
|
||||
});
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
})();
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/firebase-messaging-sw.js');
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue