- Murattal: Spotify-style 5-button controls [Shuffle, Prev, Play, Next, Playlist] - Murattal: Animated 7-bar equalizer visualization in player circle - Murattal: Unsplash API background with frosted glass player overlay - Murattal: Transparent AppBar with backdrop blur - Murattal: Surah playlist bottom sheet with full 114 Surah list - Murattal: Auto-play disabled on screen open, enabled on navigation - Murattal: Shuffle mode for random Surah playback - Murattal: Photographer attribution per Unsplash guidelines - Dashboard: Auto-scroll prayer schedule to next active prayer - Fix: setState lifecycle errors on Reading & Murattal screens - Setup: flutter_dotenv, cached_network_image, url_launcher deps
84 lines
3.0 KiB
Dart
84 lines
3.0 KiB
Dart
import 'dart:convert';
|
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:hive_flutter/hive_flutter.dart';
|
|
|
|
/// Service for fetching Islamic-themed photos from Unsplash.
|
|
/// Implements aggressive caching to minimize API usage (1 request/day).
|
|
class UnsplashService {
|
|
static const String _baseUrl = 'https://api.unsplash.com';
|
|
static const String _cacheBoxName = 'unsplash_cache';
|
|
static const String _cacheKey = 'cached_photo';
|
|
static const String _cacheTimestampKey = 'cached_timestamp';
|
|
static const Duration _cacheTTL = Duration(hours: 24);
|
|
|
|
static final UnsplashService instance = UnsplashService._();
|
|
UnsplashService._();
|
|
|
|
// In-memory cache for the current session
|
|
Map<String, String>? _memoryCache;
|
|
|
|
/// Get a cached or fresh Islamic photo.
|
|
/// Returns a map with keys: 'imageUrl', 'photographerName', 'photographerUrl', 'unsplashUrl'
|
|
Future<Map<String, String>?> getIslamicPhoto() async {
|
|
// 1. Check memory cache
|
|
if (_memoryCache != null) return _memoryCache;
|
|
|
|
// 2. Check Hive cache
|
|
final box = await Hive.openBox(_cacheBoxName);
|
|
final cachedData = box.get(_cacheKey);
|
|
final cachedTimestamp = box.get(_cacheTimestampKey);
|
|
|
|
if (cachedData != null && cachedTimestamp != null) {
|
|
final cachedTime = DateTime.fromMillisecondsSinceEpoch(cachedTimestamp);
|
|
if (DateTime.now().difference(cachedTime) < _cacheTTL) {
|
|
_memoryCache = Map<String, String>.from(json.decode(cachedData));
|
|
return _memoryCache;
|
|
}
|
|
}
|
|
|
|
// 3. Fetch from API
|
|
final photo = await _fetchFromApi();
|
|
if (photo != null) {
|
|
// Cache in Hive
|
|
await box.put(_cacheKey, json.encode(photo));
|
|
await box.put(_cacheTimestampKey, DateTime.now().millisecondsSinceEpoch);
|
|
_memoryCache = photo;
|
|
}
|
|
|
|
return photo;
|
|
}
|
|
|
|
Future<Map<String, String>?> _fetchFromApi() async {
|
|
final accessKey = dotenv.env['UNSPLASH_ACCESS_KEY'];
|
|
if (accessKey == null || accessKey.isEmpty || accessKey == 'YOUR_ACCESS_KEY_HERE') {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
final queries = ['masjid', 'kaabah', 'mosque', 'islamic architecture'];
|
|
// Rotate query based on the day of year for variety
|
|
final dayOfYear = DateTime.now().difference(DateTime(DateTime.now().year, 1, 1)).inDays;
|
|
final query = queries[dayOfYear % queries.length];
|
|
|
|
final response = await http.get(
|
|
Uri.parse('$_baseUrl/photos/random?query=$query&orientation=portrait&content_filter=high'),
|
|
headers: {'Authorization': 'Client-ID $accessKey'},
|
|
);
|
|
|
|
if (response.statusCode == 200) {
|
|
final data = json.decode(response.body);
|
|
return {
|
|
'imageUrl': data['urls']?['regular'] ?? '',
|
|
'photographerName': data['user']?['name'] ?? 'Unknown',
|
|
'photographerUrl': data['user']?['links']?['html'] ?? '',
|
|
'unsplashUrl': data['links']?['html'] ?? '',
|
|
};
|
|
}
|
|
} catch (e) {
|
|
// Silent fallback — show the equalizer without background
|
|
}
|
|
return null;
|
|
}
|
|
}
|