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
This commit is contained in:
83
lib/data/services/unsplash_service.dart
Normal file
83
lib/data/services/unsplash_service.dart
Normal file
@@ -0,0 +1,83 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user