// lib/screens/learn_more_screen.dart import 'dart:async'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:share_plus/share_plus.dart'; import 'package:url_launcher/url_launcher.dart'; import '../features/events/models/event_models.dart'; import '../features/events/services/events_service.dart'; class LearnMoreScreen extends StatefulWidget { final int eventId; const LearnMoreScreen({Key? key, required this.eventId}) : super(key: key); @override State createState() => _LearnMoreScreenState(); } class _LearnMoreScreenState extends State { final EventsService _service = EventsService(); bool _loading = true; EventModel? _event; String? _error; // Carousel final PageController _pageController = PageController(); int _currentPage = 0; Timer? _autoScrollTimer; // About section bool _aboutExpanded = false; // Wishlist (UI-only) bool _wishlisted = false; // Google Map GoogleMapController? _mapController; MapType _mapType = MapType.normal; bool _showMapControls = false; @override void initState() { super.initState(); _loadEvent(); } @override void dispose() { _autoScrollTimer?.cancel(); _pageController.dispose(); _mapController?.dispose(); super.dispose(); } // --------------------------------------------------------------------------- // Data loading // --------------------------------------------------------------------------- Future _loadEvent() async { setState(() { _loading = true; _error = null; }); try { final ev = await _service.getEventDetails(widget.eventId); if (!mounted) return; setState(() => _event = ev); _startAutoScroll(); } catch (e) { if (!mounted) return; setState(() => _error = e.toString()); } finally { if (mounted) setState(() => _loading = false); } } // --------------------------------------------------------------------------- // Carousel helpers // --------------------------------------------------------------------------- List get _imageUrls { final list = []; if (_event == null) return list; final thumb = _event!.thumbImg; if (thumb != null && thumb.isNotEmpty) list.add(thumb); for (final img in _event!.images) { if (img.image.isNotEmpty && !list.contains(img.image)) list.add(img.image); } return list; } void _startAutoScroll() { _autoScrollTimer?.cancel(); final count = _imageUrls.length; if (count <= 1) return; _autoScrollTimer = Timer.periodic(const Duration(seconds: 3), (_) { if (!_pageController.hasClients) return; final next = (_currentPage + 1) % count; _pageController.animateToPage(next, duration: const Duration(milliseconds: 500), curve: Curves.easeInOut); }); } // --------------------------------------------------------------------------- // Date formatting // --------------------------------------------------------------------------- String _formattedDateRange() { if (_event == null) return ''; try { final s = DateTime.parse(_event!.startDate); final e = DateTime.parse(_event!.endDate); const months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]; if (s.year == e.year && s.month == e.month && s.day == e.day) { return '${s.day} ${months[s.month - 1]}'; } if (s.month == e.month && s.year == e.year) { return '${s.day} - ${e.day} ${months[s.month - 1]}'; } return '${s.day} ${months[s.month - 1]} - ${e.day} ${months[e.month - 1]}'; } catch (_) { return '${_event!.startDate} – ${_event!.endDate}'; } } // --------------------------------------------------------------------------- // Actions // --------------------------------------------------------------------------- Future _shareEvent() async { final title = _event?.title ?? _event?.name ?? 'Check out this event'; final url = 'https://uat.eventifyplus.com/events/${widget.eventId}'; await Share.share('$title\n$url', subject: title); } Future _openUrl(String url) async { final uri = Uri.parse(url); if (await canLaunchUrl(uri)) { await launchUrl(uri, mode: LaunchMode.externalApplication); } } void _viewLargerMap() { if (_event?.latitude == null || _event?.longitude == null) return; _openUrl( 'https://www.google.com/maps/search/?api=1&query=${_event!.latitude},${_event!.longitude}'); } void _getDirections() { if (_event?.latitude == null || _event?.longitude == null) return; _openUrl( 'https://www.google.com/maps/dir/?api=1&destination=${_event!.latitude},${_event!.longitude}'); } // --------------------------------------------------------------------------- // Map camera helpers // --------------------------------------------------------------------------- void _moveCamera(double latDelta, double lngDelta) { _mapController?.animateCamera(CameraUpdate.scrollBy(lngDelta * 80, -latDelta * 80)); } void _zoom(double amount) { _mapController?.animateCamera(CameraUpdate.zoomBy(amount)); } // --------------------------------------------------------------------------- // BUILD // --------------------------------------------------------------------------- @override Widget build(BuildContext context) { final theme = Theme.of(context); if (_loading) { return Scaffold( backgroundColor: theme.scaffoldBackgroundColor, body: _buildLoadingShimmer(theme), ); } if (_error != null) { return Scaffold( backgroundColor: theme.scaffoldBackgroundColor, body: Center( child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.error_outline, size: 56, color: theme.colorScheme.error), const SizedBox(height: 16), Text('Something went wrong', style: theme.textTheme.titleMedium ?.copyWith(fontWeight: FontWeight.bold)), const SizedBox(height: 8), Text(_error!, textAlign: TextAlign.center, style: theme.textTheme.bodyMedium), const SizedBox(height: 24), ElevatedButton.icon( onPressed: _loadEvent, icon: const Icon(Icons.refresh), label: const Text('Retry'), ), ], ), ), ), ); } if (_event == null) { return Scaffold( backgroundColor: theme.scaffoldBackgroundColor, body: const Center(child: Text('Event not found')), ); } final screenHeight = MediaQuery.of(context).size.height; final imageHeight = screenHeight * 0.50; final overlap = 30.0; return Scaffold( backgroundColor: theme.scaffoldBackgroundColor, body: Stack( children: [ // ── LAYER 1: Image carousel (background) ── _buildImageCarousel(theme, imageHeight), // ── LAYER 2: Scrollable content with overlapping white card ── SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Transparent spacer — shows the image behind SizedBox(height: imageHeight - overlap), // 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), ), ], ), 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) ── Positioned( top: MediaQuery.of(context).padding.top + 10, left: 16, right: 16, 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), ), ], ), ), ], ), ); } // --------------------------------------------------------------------------- // 1. LOADING SHIMMER // --------------------------------------------------------------------------- Widget _buildLoadingShimmer(ThemeData theme) { return SafeArea( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Placeholder image Container( height: MediaQuery.of(context).size.height * 0.42, decoration: BoxDecoration( color: theme.dividerColor.withOpacity(0.3), borderRadius: BorderRadius.circular(28), ), ), const SizedBox(height: 24), // Placeholder title Container( height: 28, width: 220, decoration: BoxDecoration( color: theme.dividerColor.withOpacity(0.3), borderRadius: BorderRadius.circular(8), ), ), const SizedBox(height: 12), Container( height: 16, width: 140, decoration: BoxDecoration( color: theme.dividerColor.withOpacity(0.3), borderRadius: BorderRadius.circular(6), ), ), const SizedBox(height: 20), Container( height: 16, width: double.infinity, decoration: BoxDecoration( color: theme.dividerColor.withOpacity(0.3), borderRadius: BorderRadius.circular(6), ), ), const SizedBox(height: 8), Container( height: 16, width: double.infinity, decoration: BoxDecoration( color: theme.dividerColor.withOpacity(0.3), borderRadius: BorderRadius.circular(6), ), ), ], ), ), ); } // --------------------------------------------------------------------------- // 2. IMAGE CAROUSEL WITH BLURRED BACKGROUND // --------------------------------------------------------------------------- Widget _buildImageCarousel(ThemeData theme, double carouselHeight) { final images = _imageUrls; final topPad = MediaQuery.of(context).padding.top; return SizedBox( height: carouselHeight, child: Stack( children: [ // ---- Blurred background (image or blue gradient) ---- Positioned.fill( child: images.isNotEmpty ? ClipRect( child: Stack( fit: StackFit.expand, children: [ Image.network( images[_currentPage], fit: BoxFit.cover, errorBuilder: (_, __, ___) => Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFF1A56DB), Color(0xFF3B82F6)], ), ), ), ), BackdropFilter( filter: ImageFilter.blur(sigmaX: 25, sigmaY: 25), child: Container( color: Colors.black.withOpacity(0.15), ), ), ], ), ) : Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFF1A56DB), Color(0xFF3B82F6)], ), ), ), ), // ---- Foreground image with rounded corners ---- if (images.isNotEmpty) Positioned( top: topPad + 56, // below the icon row left: 20, right: 20, bottom: 16, child: ClipRRect( borderRadius: BorderRadius.circular(20), child: PageView.builder( controller: _pageController, onPageChanged: (i) => setState(() => _currentPage = i), itemCount: images.length, itemBuilder: (_, i) => Image.network( images[i], fit: BoxFit.cover, width: double.infinity, errorBuilder: (_, __, ___) => Container( color: theme.dividerColor, child: Icon(Icons.broken_image, size: 48, color: theme.hintColor), ), ), ), ), ), // ---- No-image placeholder ---- if (images.isEmpty) Positioned( top: topPad + 56, left: 20, right: 20, bottom: 16, child: Container( decoration: BoxDecoration( color: Colors.white.withOpacity(0.15), borderRadius: BorderRadius.circular(20), ), child: const Center( child: Icon(Icons.event, size: 80, color: Colors.white70), ), ), ), ], ), ); } /// Square icon button with rounded corners and translucent white background Widget _squareIconButton({ required IconData icon, required VoidCallback onTap, Color iconColor = Colors.white, }) { return GestureDetector( onTap: onTap, child: Container( width: 42, height: 42, decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.white.withOpacity(0.3)), ), child: Icon(icon, color: iconColor, size: 22), ), ); } // --------------------------------------------------------------------------- // 3. TITLE & DATE // --------------------------------------------------------------------------- Widget _buildTitleSection(ThemeData theme) { return Padding( padding: const EdgeInsets.fromLTRB(20, 24, 20, 0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _event!.title ?? _event!.name, style: theme.textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.w800, fontSize: 26, height: 1.25, ), ), const SizedBox(height: 8), Row( children: [ Icon(Icons.calendar_today_outlined, size: 16, color: theme.hintColor), const SizedBox(width: 6), Text( _formattedDateRange(), style: theme.textTheme.bodyMedium?.copyWith( color: theme.hintColor, fontSize: 15, ), ), ], ), ], ), ); } // --------------------------------------------------------------------------- // 4. ABOUT THE EVENT // --------------------------------------------------------------------------- Widget _buildAboutSection(ThemeData theme) { final desc = _event!.description ?? ''; if (desc.isEmpty) return const SizedBox.shrink(); return Padding( padding: const EdgeInsets.fromLTRB(20, 24, 20, 0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'About the Event', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w800, fontSize: 20, ), ), const SizedBox(height: 10), AnimatedCrossFade( firstChild: Text( desc, maxLines: 4, overflow: TextOverflow.ellipsis, style: theme.textTheme.bodyMedium?.copyWith( height: 1.55, color: theme.textTheme.bodyMedium?.color?.withOpacity(0.75), ), ), secondChild: Text( desc, style: theme.textTheme.bodyMedium?.copyWith( height: 1.55, color: theme.textTheme.bodyMedium?.color?.withOpacity(0.75), ), ), crossFadeState: _aboutExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst, duration: const Duration(milliseconds: 300), ), const SizedBox(height: 6), GestureDetector( onTap: () => setState(() => _aboutExpanded = !_aboutExpanded), child: Text( _aboutExpanded ? 'Read Less' : 'Read More', style: TextStyle( color: theme.colorScheme.primary, fontWeight: FontWeight.w700, fontSize: 15, ), ), ), ], ), ); } // --------------------------------------------------------------------------- // 5. VENUE LOCATION (Google Map) // --------------------------------------------------------------------------- Widget _buildVenueSection(ThemeData theme) { final lat = _event!.latitude!; final lng = _event!.longitude!; final venueLabel = _event!.locationName ?? _event!.venueName ?? _event!.place ?? ''; return Padding( padding: const EdgeInsets.fromLTRB(20, 28, 20, 0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Venue Location', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w800, fontSize: 20, ), ), const SizedBox(height: 14), // Map container ClipRRect( borderRadius: BorderRadius.circular(20), child: SizedBox( 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), ), }, myLocationButtonEnabled: false, zoomControlsEnabled: false, scrollGesturesEnabled: true, rotateGesturesEnabled: false, tiltGesturesEnabled: false, onMapCreated: (c) => _mapController = c, ), // "View larger map" – top left Positioned( top: 10, left: 10, child: GestureDetector( onTap: _viewLargerMap, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.12), blurRadius: 6, ), ], ), child: Text( 'View larger map', style: TextStyle( color: theme.colorScheme.primary, fontWeight: FontWeight.w600, fontSize: 13, ), ), ), ), ), // 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 controls toggle – bottom right Positioned( bottom: 12, right: 12, child: _mapControlButton( icon: Icons.open_with_rounded, onTap: () => setState(() => _showMapControls = !_showMapControls), ), ), // Directional pad overlay if (_showMapControls) Positioned.fill( child: Container( decoration: BoxDecoration( color: Colors.black.withOpacity(0.25), borderRadius: BorderRadius.circular(20), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // Top row: Up + Zoom In Row( mainAxisAlignment: MainAxisAlignment.center, children: [ _mapControlButton( icon: Icons.keyboard_arrow_up, onTap: () => _moveCamera(1, 0)), const SizedBox(width: 16), _mapControlButton( icon: Icons.add, onTap: () => _zoom(1)), ], ), const SizedBox(height: 10), // Middle row: Left + Right Row( mainAxisAlignment: MainAxisAlignment.center, children: [ _mapControlButton( icon: Icons.keyboard_arrow_left, onTap: () => _moveCamera(0, -1)), const SizedBox(width: 60), _mapControlButton( icon: Icons.keyboard_arrow_right, onTap: () => _moveCamera(0, 1)), ], ), const SizedBox(height: 10), // Bottom row: Down + Zoom Out + Close Row( mainAxisAlignment: MainAxisAlignment.center, children: [ _mapControlButton( icon: Icons.keyboard_arrow_down, onTap: () => _moveCamera(-1, 0)), const SizedBox(width: 16), _mapControlButton( icon: Icons.remove, onTap: () => _zoom(-1)), const SizedBox(width: 16), _mapControlButton( icon: Icons.close, onTap: () => setState(() => _showMapControls = false)), ], ), ], ), ), ), ], ), ), ), // Venue name card if (venueLabel.isNotEmpty) Container( width: double.infinity, margin: const EdgeInsets.only(top: 14), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: theme.cardColor, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: theme.shadowColor.withOpacity(0.06), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( venueLabel, style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), ), if (_event!.place != null && _event!.place != venueLabel) Padding( padding: const EdgeInsets.only(top: 4), child: Text( _event!.place!, style: theme.textTheme.bodyMedium?.copyWith( color: theme.hintColor, ), ), ), ], ), ), ], ), ); } Widget _mapControlButton({ required IconData icon, required VoidCallback onTap, }) { return GestureDetector( onTap: onTap, child: Container( width: 44, height: 44, decoration: BoxDecoration( color: Colors.white.withOpacity(0.92), shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.15), blurRadius: 6, ), ], ), child: Icon(icon, color: Colors.grey.shade700, size: 22), ), ); } // --------------------------------------------------------------------------- // 6. GET DIRECTIONS BUTTON // --------------------------------------------------------------------------- Widget _buildGetDirectionsButton(ThemeData theme) { return Padding( padding: const EdgeInsets.fromLTRB(20, 18, 20, 0), child: SizedBox( width: double.infinity, height: 54, child: ElevatedButton.icon( onPressed: _getDirections, style: ElevatedButton.styleFrom( backgroundColor: theme.colorScheme.primary, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), elevation: 2, ), icon: const Icon(Icons.directions, size: 22), label: const Text( 'Get Directions', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700), ), ), ), ); } // --------------------------------------------------------------------------- // 7. IMPORTANT INFORMATION (structured list) // --------------------------------------------------------------------------- Widget _buildImportantInfoSection(ThemeData theme) { return Padding( padding: const EdgeInsets.fromLTRB(20, 28, 20, 0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Important Information', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w800, fontSize: 20, ), ), const SizedBox(height: 14), for (final info in _event!.importantInfo) Container( width: double.infinity, margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: theme.colorScheme.primary.withOpacity(0.05), borderRadius: BorderRadius.circular(16), border: Border.all( color: theme.colorScheme.primary.withOpacity(0.12), ), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 36, height: 36, decoration: BoxDecoration( color: theme.colorScheme.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(10), ), child: Icon(Icons.info_outline, size: 20, color: theme.colorScheme.primary), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( info['title'] ?? '', style: theme.textTheme.bodyLarge?.copyWith( fontWeight: FontWeight.w700, ), ), const SizedBox(height: 4), Text( info['value'] ?? '', style: theme.textTheme.bodyMedium?.copyWith( color: theme.hintColor, height: 1.4, ), ), ], ), ), ], ), ), ], ), ); } // --------------------------------------------------------------------------- // 7b. IMPORTANT INFO FALLBACK (parse HTML string into cards) // --------------------------------------------------------------------------- /// Strip HTML tags and decode common HTML entities String _stripHtml(String html) { // Remove all HTML tags var text = html.replaceAll(RegExp(r'<[^>]*>'), ''); // Decode common HTML entities text = text .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll(''', "'") .replaceAll(' ', ' '); return text.trim(); } /// Parse an HTML important_information string into a list of {title, value} maps List> _parseHtmlImportantInfo(String raw) { // Strip HTML tags, preserving
as a newline separator first var text = raw .replaceAll(RegExp(r'', caseSensitive: false), '\n') .replaceAll(RegExp(r'<[^>]*>'), ''); // Decode entities text = text .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll(''', "'") .replaceAll(' ', ' '); // Split by newlines first var lines = text .split('\n') .map((l) => l.trim()) .where((l) => l.isNotEmpty) .toList(); // If we only have 1 line, items might be separated by emoji characters // (some categories don't use
between items, e.g. "...etc.🚌 Bus:") if (lines.length <= 1 && text.trim().isNotEmpty) { final parts = text.trim().split( RegExp(r'(?=[\u2600-\u27BF]|[\u{1F300}-\u{1FFFF}])', unicode: true), ); final emojiLines = parts .map((l) => l.trim()) .where((l) => l.isNotEmpty) .toList(); if (emojiLines.length > 1) { lines = emojiLines; } } final items = >[]; for (final line in lines) { // Split on first colon to get title:value final colonIdx = line.indexOf(':'); if (colonIdx > 0 && colonIdx < line.length - 1) { items.add({ 'title': line.substring(0, colonIdx + 1).trim(), 'value': line.substring(colonIdx + 1).trim(), }); } else { items.add({'title': line, 'value': ''}); } } return items; } Widget _buildImportantInfoFallback(ThemeData theme) { final parsed = _parseHtmlImportantInfo(_event!.importantInformation!); if (parsed.isEmpty) return const SizedBox.shrink(); return Padding( padding: const EdgeInsets.fromLTRB(20, 28, 20, 0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Important Information', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w800, fontSize: 20, ), ), const SizedBox(height: 14), for (final info in parsed) Container( width: double.infinity, margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: theme.colorScheme.primary.withOpacity(0.05), borderRadius: BorderRadius.circular(16), border: Border.all( color: theme.colorScheme.primary.withOpacity(0.12), ), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 36, height: 36, decoration: BoxDecoration( color: theme.colorScheme.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(10), ), child: Icon(Icons.info_outline, size: 20, color: theme.colorScheme.primary), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( info['title'] ?? '', style: theme.textTheme.bodyLarge?.copyWith( fontWeight: FontWeight.w700, ), ), if ((info['value'] ?? '').isNotEmpty) ...[ const SizedBox(height: 4), Text( info['value']!, style: theme.textTheme.bodyMedium?.copyWith( color: theme.hintColor, height: 1.4, ), ), ], ], ), ), ], ), ), ], ), ); } }