cyberhybridhub/lib/screens/landing_screen.dart
2026-05-20 10:22:58 -05:00

465 lines
14 KiB
Dart

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import '../theme/app_theme.dart';
import '../widgets/benefit_row.dart';
import '../widgets/google_sign_in_button.dart';
class LandingScreen extends StatefulWidget {
const LandingScreen({super.key});
@override
State<LandingScreen> createState() => _LandingScreenState();
}
class _LandingScreenState extends State<LandingScreen> {
String? _errorMessage;
@override
Widget build(BuildContext context) {
return Scaffold(
body: DecoratedBox(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: <Color>[
AppColors.background,
Color(0xFF0C1222),
Color(0xFF0A1628),
],
),
),
child: Stack(
children: <Widget>[
const _BackgroundGlow(),
SafeArea(
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final bool wide = constraints.maxWidth >= 720;
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 960),
child: wide
? _WideLayout(
errorMessage: _errorMessage,
onError: _handleError,
)
: _NarrowLayout(
errorMessage: _errorMessage,
onError: _handleError,
),
),
);
},
),
),
],
),
),
);
}
void _handleError(Object error) {
setState(() {
_errorMessage = _friendlyError(error);
});
}
String _friendlyError(Object error) {
if (error is FirebaseAuthException) {
switch (error.code) {
case 'unauthorized-domain':
final String origin = kIsWeb ? Uri.base.origin : 'this app';
return 'Add $origin under Firebase Console → Authentication → '
'Settings → Authorized domains.';
case 'operation-not-allowed':
return 'Google sign-in is not enabled. Turn on the Google provider '
'in Firebase Console → Authentication → Sign-in method.';
case 'popup-blocked':
return 'The sign-in popup was blocked. Allow popups for this site '
'or try again.';
case 'popup-closed-by-user':
case 'cancelled-popup-request':
return 'Sign-in was cancelled. Tap below to try again.';
}
}
final String raw = error.toString();
if (raw.contains('missing initial state') ||
raw.contains('sessionStorage')) {
return 'This browser blocked sign-in storage (common in Firefox). '
'Set googleWebOAuthClientId in lib/config/auth_config.dart, allow '
'popups for this site, or try Chrome.';
}
if (raw.contains('canceled') || raw.contains('cancelled')) {
return 'Sign-in was cancelled. Tap below to try again.';
}
if (raw.contains('network')) {
return 'Network error. Check your connection and try again.';
}
if (raw.contains('googleWebOAuthClientId')) {
return raw.replaceFirst('Bad state: ', '');
}
return 'Could not sign in. Please try again.';
}
}
class _BackgroundGlow extends StatelessWidget {
const _BackgroundGlow();
@override
Widget build(BuildContext context) {
return IgnorePointer(
child: Stack(
children: <Widget>[
Positioned(
top: -120,
right: -80,
child: Container(
width: 320,
height: 320,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: <Color>[
AppColors.accent.withValues(alpha: 0.18),
Colors.transparent,
],
),
),
),
),
Positioned(
bottom: -100,
left: -60,
child: Container(
width: 280,
height: 280,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: <Color>[
AppColors.accentMuted.withValues(alpha: 0.12),
Colors.transparent,
],
),
),
),
),
],
),
);
}
}
class _NarrowLayout extends StatelessWidget {
const _NarrowLayout({required this.errorMessage, required this.onError});
final String? errorMessage;
final void Function(Object error) onError;
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
const _BrandHeader(),
const SizedBox(height: 32),
const _HeroCopy(),
const SizedBox(height: 28),
const _BenefitsList(),
const SizedBox(height: 32),
_CtaSection(errorMessage: errorMessage, onError: onError),
const SizedBox(height: 24),
const _TrustFooter(),
],
),
);
}
}
class _WideLayout extends StatelessWidget {
const _WideLayout({required this.errorMessage, required this.onError});
final String? errorMessage;
final void Function(Object error) onError;
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 24),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Expanded(
flex: 5,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_BrandHeader(),
SizedBox(height: 40),
_HeroCopy(),
SizedBox(height: 36),
_BenefitsList(),
],
),
),
const SizedBox(width: 48),
Expanded(
flex: 4,
child: _CtaCard(errorMessage: errorMessage, onError: onError),
),
],
),
);
}
}
class _BrandHeader extends StatelessWidget {
const _BrandHeader();
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: <Color>[AppColors.accent, AppColors.accentMuted],
),
borderRadius: BorderRadius.circular(10),
),
child: const Icon(
Icons.shield_outlined,
color: AppColors.background,
size: 22,
),
),
const SizedBox(width: 12),
Text(
'Cyber Hybrid Hub',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
letterSpacing: -0.2,
),
),
],
);
}
}
class _HeroCopy extends StatelessWidget {
const _HeroCopy();
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: AppColors.success.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: AppColors.success.withValues(alpha: 0.35),
),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(Icons.bolt, size: 16, color: AppColors.success),
SizedBox(width: 6),
Text(
'Free to get started',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: AppColors.success,
),
),
],
),
),
const SizedBox(height: 20),
Text(
'Your hybrid security\ncommand center',
style: Theme.of(context).textTheme.headlineLarge,
),
const SizedBox(height: 16),
Text(
'Unify threat monitoring, team collaboration, and compliance '
'workflows in one secure workspace — built for modern hybrid teams.',
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontSize: 17,
),
),
],
);
}
}
class _BenefitsList extends StatelessWidget {
const _BenefitsList();
static const List<({IconData icon, String title, String subtitle})> _items =
<({IconData icon, String title, String subtitle})>[
(
icon: Icons.radar,
title: 'Real-time threat visibility',
subtitle: 'See alerts and incidents as they happen, not hours later.',
),
(
icon: Icons.groups_outlined,
title: 'Built for hybrid teams',
subtitle: 'On-site and remote staff share one source of truth.',
),
(
icon: Icons.verified_user_outlined,
title: 'Sign in securely with Google',
subtitle: 'No new passwords — enterprise-ready OAuth in one tap.',
),
];
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
for (int i = 0; i < _items.length; i++) ...<Widget>[
if (i > 0) const SizedBox(height: 20),
BenefitRow(
icon: _items[i].icon,
title: _items[i].title,
subtitle: _items[i].subtitle,
),
],
],
);
}
}
class _CtaSection extends StatelessWidget {
const _CtaSection({required this.errorMessage, required this.onError});
final String? errorMessage;
final void Function(Object error) onError;
@override
Widget build(BuildContext context) {
return _CtaCard(errorMessage: errorMessage, onError: onError);
}
}
class _CtaCard extends StatelessWidget {
const _CtaCard({required this.errorMessage, required this.onError});
final String? errorMessage;
final void Function(Object error) onError;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: AppColors.surfaceElevated,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.white.withValues(alpha: 0.08)),
boxShadow: <BoxShadow>[
BoxShadow(
color: AppColors.accent.withValues(alpha: 0.08),
blurRadius: 40,
offset: const Offset(0, 12),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
'Get started in seconds',
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
fontSize: 22,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'Use your Google account — no credit card required.',
style: Theme.of(context).textTheme.bodyLarge,
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
if (errorMessage != null) ...<Widget>[
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.error.withValues(
alpha: 0.12,
),
borderRadius: BorderRadius.circular(8),
),
child: Text(
errorMessage!,
style: TextStyle(
color: Theme.of(context).colorScheme.error,
fontSize: 14,
),
textAlign: TextAlign.center,
),
),
const SizedBox(height: 16),
],
GoogleSignInButton(
label: 'Get started with Google',
onError: onError,
),
const SizedBox(height: 16),
const _SocialProof(),
],
),
);
}
}
class _SocialProof extends StatelessWidget {
const _SocialProof();
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Icon(Icons.lock_outline, size: 14, color: AppColors.textSecondary),
const SizedBox(width: 6),
Flexible(
child: Text(
'Secured by Firebase · Google OAuth',
style: Theme.of(context).textTheme.bodyLarge?.copyWith(fontSize: 12),
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
),
],
);
}
}
class _TrustFooter extends StatelessWidget {
const _TrustFooter();
@override
Widget build(BuildContext context) {
return Text(
'By continuing, you agree to our Terms of Service and Privacy Policy.',
style: Theme.of(context).textTheme.bodyLarge?.copyWith(fontSize: 12),
textAlign: TextAlign.center,
);
}
}