/ app / lib / services / gif_service.dart
gif_service.dart
 1  import 'dart:convert';
 2  import 'dart:io' show Platform;
 3  import 'dart:typed_data';
 4  
 5  import 'package:http/http.dart' as http;
 6  
 7  const _iosKey = String.fromEnvironment('GIPHY_API_KEY_IOS');
 8  const _androidKey = String.fromEnvironment('GIPHY_API_KEY_ANDROID');
 9  const _genericKey = String.fromEnvironment('GIPHY_API_KEY');
10  final String _apiKey = Platform.isIOS ? (_iosKey.isNotEmpty ? _iosKey : _genericKey)
11      : Platform.isAndroid ? (_androidKey.isNotEmpty ? _androidKey : _genericKey)
12      : _genericKey;
13  const _baseUrl = 'https://api.giphy.com/v1/gifs';
14  
15  class GifItem {
16    final String id;
17    final String previewUrl;
18    final int previewWidth;
19    final int previewHeight;
20    final String fullUrl;
21  
22    const GifItem({
23      required this.id,
24      required this.previewUrl,
25      required this.previewWidth,
26      required this.previewHeight,
27      required this.fullUrl,
28    });
29  }
30  
31  class GifService {
32    final http.Client _client = http.Client();
33  
34    bool get hasApiKey => _apiKey.isNotEmpty;
35  
36    Future<List<GifItem>> search(String query, {int limit = 20}) async {
37      final uri = Uri.parse('$_baseUrl/search').replace(queryParameters: {
38        'api_key': _apiKey,
39        'q': query,
40        'limit': '$limit',
41        'rating': 'pg-13',
42      });
43      return _fetchGifs(uri);
44    }
45  
46    Future<List<GifItem>> trending({int limit = 20}) async {
47      final uri = Uri.parse('$_baseUrl/trending').replace(queryParameters: {
48        'api_key': _apiKey,
49        'limit': '$limit',
50        'rating': 'pg-13',
51      });
52      return _fetchGifs(uri);
53    }
54  
55    Future<List<GifItem>> _fetchGifs(Uri uri) async {
56      if (!hasApiKey) {
57        throw Exception('GIPHY_API_KEY not configured. Build with --dart-define=GIPHY_API_KEY=your_key');
58      }
59  
60      final response = await _client.get(uri);
61      if (response.statusCode != 200) {
62        final body = response.body;
63        throw Exception('Giphy API error ${response.statusCode}: $body');
64      }
65  
66      final data = jsonDecode(response.body) as Map<String, dynamic>;
67      final results = data['data'] as List<dynamic>? ?? [];
68  
69      return results.map((item) {
70        final images = item['images'] as Map<String, dynamic>;
71        // fixed_width_small (~100px) for grid preview
72        final preview = images['fixed_width_small'] as Map<String, dynamic>;
73        // downsized (under 2MB) for sending — good quality, fast to decode
74        final full = (images['downsized'] ?? images['downsized_medium']) as Map<String, dynamic>;
75        return GifItem(
76          id: item['id'] as String,
77          previewUrl: preview['url'] as String,
78          previewWidth: int.parse(preview['width'] as String),
79          previewHeight: int.parse(preview['height'] as String),
80          fullUrl: full['url'] as String,
81        );
82      }).toList();
83    }
84  
85    Future<Uint8List> downloadGif(String url) async {
86      final response = await _client.get(Uri.parse(url));
87      if (response.statusCode != 200) {
88        throw Exception('Failed to download GIF: ${response.statusCode}');
89      }
90      return response.bodyBytes;
91    }
92  
93    void dispose() {
94      _client.close();
95    }
96  }