Files
jamshalat-diary/lib/data/services/unsplash_service.dart
dwindown faadc1865d feat: Murattal player enhancements & prayer schedule auto-scroll
- 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
2026-03-13 15:42:17 +07:00

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;
}
}