import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import '../l10n/l10n.dart'; class GifPicker extends StatefulWidget { final Function(String gifId) onGifSelected; const GifPicker({super.key, required this.onGifSelected}); @override State createState() => _GifPickerState(); } class _GifPickerState extends State { final TextEditingController _searchController = TextEditingController(); List> _gifs = []; bool _isLoading = false; String? _error; // Giphy API key - Using public beta key (limited usage) // For production, replace with your own Giphy API key from developers.giphy.com static const String _giphyApiKey = 'sXpGFDGZs0Dv1mmNFvYaGUvYwKX0PWIh'; @override void initState() { super.initState(); _loadTrendingGifs(); } @override void dispose() { _searchController.dispose(); super.dispose(); } Future _loadTrendingGifs() async { setState(() { _isLoading = true; _error = null; }); try { final response = await http .get( Uri.parse( 'https://api.giphy.com/v1/gifs/trending?api_key=$_giphyApiKey&limit=25&rating=g', ), ) .timeout(const Duration(seconds: 10)); if (response.statusCode == 200) { final data = json.decode(response.body); setState(() { _gifs = List>.from(data['data']); _isLoading = false; }); } else { if (!mounted) return; setState(() { _error = context.l10n.gifPicker_failedLoad; _isLoading = false; }); } } catch (e) { if (!mounted) return; setState(() { _error = context.l10n.gifPicker_noInternet; _isLoading = false; }); } } Future _searchGifs(String query) async { if (query.trim().isEmpty) { _loadTrendingGifs(); return; } setState(() { _isLoading = true; _error = null; }); try { final response = await http .get( Uri.parse( 'https://api.giphy.com/v1/gifs/search?api_key=$_giphyApiKey&q=${Uri.encodeComponent(query)}&limit=25&rating=g', ), ) .timeout(const Duration(seconds: 10)); if (response.statusCode == 200) { final data = json.decode(response.body); setState(() { _gifs = List>.from(data['data']); _isLoading = false; }); } else { if (!mounted) return; setState(() { _error = context.l10n.gifPicker_failedSearch; _isLoading = false; }); } } catch (e) { if (!mounted) return; setState(() { _error = context.l10n.gifPicker_noInternet; _isLoading = false; }); } } @override Widget build(BuildContext context) { return Container( height: MediaQuery.of(context).size.height * 0.7, padding: const EdgeInsets.all(16), child: Column( children: [ // Header Row( children: [ const Icon(Icons.gif_box, size: 28), const SizedBox(width: 8), Text( context.l10n.gifPicker_title, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), const Spacer(), IconButton( icon: const Icon(Icons.close), onPressed: () => Navigator.pop(context), ), ], ), const SizedBox(height: 16), // Search bar TextField( controller: _searchController, decoration: InputDecoration( hintText: context.l10n.gifPicker_searchHint, prefixIcon: const Icon(Icons.search), suffixIcon: _searchController.text.isNotEmpty ? IconButton( icon: const Icon(Icons.clear), onPressed: () { _searchController.clear(); _loadTrendingGifs(); }, ) : null, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), ), textInputAction: TextInputAction.search, onSubmitted: _searchGifs, onChanged: (value) { setState(() {}); // Update to show/hide clear button }, ), const SizedBox(height: 16), // GIF grid Expanded(child: _buildContent()), // Powered by Giphy attribution const SizedBox(height: 8), Text( context.l10n.gifPicker_poweredBy, style: TextStyle(fontSize: 11, color: Colors.grey[600]), ), ], ), ); } Widget _buildContent() { if (_isLoading) { return const Center(child: CircularProgressIndicator()); } if (_error != null) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error_outline, size: 64, color: Colors.grey[400]), const SizedBox(height: 16), Text( _error!, style: TextStyle(fontSize: 16, color: Colors.grey[600]), ), const SizedBox(height: 16), ElevatedButton.icon( onPressed: _loadTrendingGifs, icon: const Icon(Icons.refresh), label: Text(context.l10n.common_retry), ), ], ), ); } if (_gifs.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.search_off, size: 64, color: Colors.grey[400]), const SizedBox(height: 16), Text( context.l10n.gifPicker_noGifsFound, style: TextStyle(fontSize: 16, color: Colors.grey[600]), ), ], ), ); } return GridView.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 8, mainAxisSpacing: 8, childAspectRatio: 1.0, ), itemCount: _gifs.length, itemBuilder: (context, index) { final gif = _gifs[index]; final gifId = gif['id'] as String; final previewUrl = gif['images']?['fixed_height_small']?['url'] as String?; return GestureDetector( onTap: () { widget.onGifSelected(gifId); Navigator.pop(context); }, child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Container( color: Theme.of(context).colorScheme.surfaceContainerHighest, child: previewUrl != null ? Image.network( previewUrl, fit: BoxFit.cover, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; return Center( child: CircularProgressIndicator( value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null, ), ); }, errorBuilder: (context, error, stackTrace) { return const Center(child: Icon(Icons.error_outline)); }, ) : const Center(child: Icon(Icons.gif_box)), ), ), ); }, ); } }