feat: update login, event detail, theme, and API client
- Improved event detail page with carousel, map, and layout fixes - Updated login screen with video background and glassmorphism - API client development mode with mock responses - Theme manager and main app updates Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// lib/screens/learn_more_screen.dart
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
@@ -222,107 +223,125 @@ class _LearnMoreScreenState extends State<LearnMoreScreen> {
|
||||
}
|
||||
|
||||
final screenHeight = MediaQuery.of(context).size.height;
|
||||
final imageHeight = screenHeight * 0.50;
|
||||
final overlap = 30.0;
|
||||
final imageHeight = screenHeight * 0.45;
|
||||
final topPadding = MediaQuery.of(context).padding.top;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: theme.scaffoldBackgroundColor,
|
||||
body: Stack(
|
||||
children: [
|
||||
// ── LAYER 1: Image carousel (background) ──
|
||||
_buildImageCarousel(theme, imageHeight),
|
||||
|
||||
// ── LAYER 2: Scrollable content with overlapping white card ──
|
||||
// ── Scrollable content (carousel + card scroll together) ──
|
||||
SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Transparent spacer — shows the image behind
|
||||
SizedBox(height: imageHeight - overlap),
|
||||
// Image carousel (scrolls with content)
|
||||
_buildImageCarousel(theme, imageHeight),
|
||||
|
||||
// White card with rounded top corners overlapping image
|
||||
Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.scaffoldBackgroundColor,
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(28),
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.08),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, -6),
|
||||
// Content card with rounded top corners overlapping carousel
|
||||
Transform.translate(
|
||||
offset: const Offset(0, -28),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.scaffoldBackgroundColor,
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(28),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildTitleSection(theme),
|
||||
_buildAboutSection(theme),
|
||||
if (_event!.latitude != null && _event!.longitude != null) ...[
|
||||
_buildVenueSection(theme),
|
||||
_buildGetDirectionsButton(theme),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.08),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, -6),
|
||||
),
|
||||
],
|
||||
if (_event!.importantInfo.isNotEmpty)
|
||||
_buildImportantInfoSection(theme),
|
||||
if (_event!.importantInfo.isEmpty &&
|
||||
(_event!.importantInformation ?? '').isNotEmpty)
|
||||
_buildImportantInfoFallback(theme),
|
||||
const SizedBox(height: 100),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildTitleSection(theme),
|
||||
_buildAboutSection(theme),
|
||||
if (_event!.latitude != null && _event!.longitude != null) ...[
|
||||
_buildVenueSection(theme),
|
||||
_buildGetDirectionsButton(theme),
|
||||
],
|
||||
if (_event!.importantInfo.isNotEmpty)
|
||||
_buildImportantInfoSection(theme),
|
||||
if (_event!.importantInfo.isEmpty &&
|
||||
(_event!.importantInformation ?? '').isNotEmpty)
|
||||
_buildImportantInfoFallback(theme),
|
||||
const SizedBox(height: 100),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// ── LAYER 3: Floating icon row (above scrollview so taps work) ──
|
||||
// ── Fixed top bar with back/share/heart buttons ──
|
||||
Positioned(
|
||||
top: MediaQuery.of(context).padding.top + 10,
|
||||
left: 16,
|
||||
right: 16,
|
||||
child: Row(
|
||||
children: [
|
||||
_squareIconButton(
|
||||
icon: Icons.arrow_back,
|
||||
onTap: () => Navigator.pop(context),
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(
|
||||
top: topPadding + 10,
|
||||
bottom: 10,
|
||||
left: 16,
|
||||
right: 16,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.black.withOpacity(0.5),
|
||||
Colors.black.withOpacity(0.0),
|
||||
],
|
||||
),
|
||||
// Pill-shaped page indicators (centered)
|
||||
Expanded(
|
||||
child: _imageUrls.length > 1
|
||||
? Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: List.generate(_imageUrls.length, (i) {
|
||||
final active = i == _currentPage;
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 3),
|
||||
width: active ? 18 : 8,
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
color: active
|
||||
? Colors.white
|
||||
: Colors.white.withOpacity(0.45),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
);
|
||||
}),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
_squareIconButton(
|
||||
icon: Icons.ios_share_outlined,
|
||||
onTap: _shareEvent,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
_squareIconButton(
|
||||
icon: _wishlisted ? Icons.favorite : Icons.favorite_border,
|
||||
iconColor: _wishlisted ? Colors.redAccent : Colors.white,
|
||||
onTap: () => setState(() => _wishlisted = !_wishlisted),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
_squareIconButton(
|
||||
icon: Icons.arrow_back,
|
||||
onTap: () => Navigator.pop(context),
|
||||
),
|
||||
// Pill-shaped page indicators (centered)
|
||||
Expanded(
|
||||
child: _imageUrls.length > 1
|
||||
? Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: List.generate(_imageUrls.length, (i) {
|
||||
final active = i == _currentPage;
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 3),
|
||||
width: active ? 18 : 8,
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
color: active
|
||||
? Colors.white
|
||||
: Colors.white.withOpacity(0.45),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
);
|
||||
}),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
_squareIconButton(
|
||||
icon: Icons.ios_share_outlined,
|
||||
onTap: _shareEvent,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
_squareIconButton(
|
||||
icon: _wishlisted ? Icons.favorite : Icons.favorite_border,
|
||||
iconColor: _wishlisted ? Colors.redAccent : Colors.white,
|
||||
onTap: () => setState(() => _wishlisted = !_wishlisted),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -492,7 +511,7 @@ class _LearnMoreScreenState extends State<LearnMoreScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Square icon button with rounded corners and translucent white background
|
||||
/// Square icon button with rounded corners and prominent background
|
||||
Widget _squareIconButton({
|
||||
required IconData icon,
|
||||
required VoidCallback onTap,
|
||||
@@ -501,12 +520,12 @@ class _LearnMoreScreenState extends State<LearnMoreScreen> {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
width: 42,
|
||||
height: 42,
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
color: Colors.black.withOpacity(0.35),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.white.withOpacity(0.3)),
|
||||
border: Border.all(color: Colors.white.withOpacity(0.4)),
|
||||
),
|
||||
child: Icon(icon, color: iconColor, size: 22),
|
||||
),
|
||||
@@ -640,26 +659,66 @@ class _LearnMoreScreenState extends State<LearnMoreScreen> {
|
||||
height: 280,
|
||||
child: Stack(
|
||||
children: [
|
||||
GoogleMap(
|
||||
initialCameraPosition: CameraPosition(
|
||||
target: LatLng(lat, lng),
|
||||
zoom: 15,
|
||||
),
|
||||
mapType: _mapType,
|
||||
markers: {
|
||||
Marker(
|
||||
markerId: const MarkerId('event'),
|
||||
position: LatLng(lat, lng),
|
||||
infoWindow: InfoWindow(title: venueLabel),
|
||||
// Use static map image on web (Google Maps JS SDK not configured),
|
||||
// native GoogleMap widget on mobile
|
||||
if (kIsWeb)
|
||||
GestureDetector(
|
||||
onTap: _viewLargerMap,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade200,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Image.network(
|
||||
'https://maps.googleapis.com/maps/api/staticmap?center=$lat,$lng&zoom=15&size=600x300&markers=color:red%7C$lat,$lng&key=',
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, __, ___) => Container(
|
||||
color: const Color(0xFFE8EAF6),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.map_outlined, size: 48, color: theme.colorScheme.primary),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Tap to view on Google Maps',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.primary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
},
|
||||
myLocationButtonEnabled: false,
|
||||
zoomControlsEnabled: false,
|
||||
scrollGesturesEnabled: true,
|
||||
rotateGesturesEnabled: false,
|
||||
tiltGesturesEnabled: false,
|
||||
onMapCreated: (c) => _mapController = c,
|
||||
),
|
||||
)
|
||||
else
|
||||
GoogleMap(
|
||||
initialCameraPosition: CameraPosition(
|
||||
target: LatLng(lat, lng),
|
||||
zoom: 15,
|
||||
),
|
||||
mapType: _mapType,
|
||||
markers: {
|
||||
Marker(
|
||||
markerId: const MarkerId('event'),
|
||||
position: LatLng(lat, lng),
|
||||
infoWindow: InfoWindow(title: venueLabel),
|
||||
),
|
||||
},
|
||||
myLocationButtonEnabled: false,
|
||||
zoomControlsEnabled: false,
|
||||
scrollGesturesEnabled: true,
|
||||
rotateGesturesEnabled: false,
|
||||
tiltGesturesEnabled: false,
|
||||
onMapCreated: (c) => _mapController = c,
|
||||
),
|
||||
|
||||
// "View larger map" – top left
|
||||
Positioned(
|
||||
@@ -691,36 +750,38 @@ class _LearnMoreScreenState extends State<LearnMoreScreen> {
|
||||
),
|
||||
),
|
||||
|
||||
// Map type toggle – bottom left
|
||||
Positioned(
|
||||
bottom: 12,
|
||||
left: 12,
|
||||
child: _mapControlButton(
|
||||
icon: _mapType == MapType.normal
|
||||
? Icons.satellite_alt
|
||||
: Icons.map_outlined,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_mapType = _mapType == MapType.normal
|
||||
? MapType.satellite
|
||||
: MapType.normal;
|
||||
});
|
||||
},
|
||||
// Map type toggle – bottom left (native only)
|
||||
if (!kIsWeb)
|
||||
Positioned(
|
||||
bottom: 12,
|
||||
left: 12,
|
||||
child: _mapControlButton(
|
||||
icon: _mapType == MapType.normal
|
||||
? Icons.satellite_alt
|
||||
: Icons.map_outlined,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_mapType = _mapType == MapType.normal
|
||||
? MapType.satellite
|
||||
: MapType.normal;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Map controls toggle – bottom right
|
||||
Positioned(
|
||||
bottom: 12,
|
||||
right: 12,
|
||||
child: _mapControlButton(
|
||||
icon: Icons.open_with_rounded,
|
||||
onTap: () => setState(() => _showMapControls = !_showMapControls),
|
||||
// Map controls toggle – bottom right (native only)
|
||||
if (!kIsWeb)
|
||||
Positioned(
|
||||
bottom: 12,
|
||||
right: 12,
|
||||
child: _mapControlButton(
|
||||
icon: Icons.open_with_rounded,
|
||||
onTap: () => setState(() => _showMapControls = !_showMapControls),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Directional pad overlay
|
||||
if (_showMapControls)
|
||||
// Directional pad overlay (native only)
|
||||
if (!kIsWeb && _showMapControls)
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
@@ -730,7 +791,6 @@ class _LearnMoreScreenState extends State<LearnMoreScreen> {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Top row: Up + Zoom In
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
@@ -744,7 +804,6 @@ class _LearnMoreScreenState extends State<LearnMoreScreen> {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
// Middle row: Left + Right
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
@@ -758,7 +817,6 @@ class _LearnMoreScreenState extends State<LearnMoreScreen> {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
// Bottom row: Down + Zoom Out + Close
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
|
||||
Reference in New Issue
Block a user