- 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
109 lines
3.2 KiB
Dart
109 lines
3.2 KiB
Dart
import 'dart:convert';
|
|
import 'package:http/http.dart' as http;
|
|
|
|
/// Service for EQuran.id v2 API.
|
|
/// Provides complete Quran data: Arabic, Indonesian translation,
|
|
/// tafsir, and audio from 6 qari.
|
|
class EQuranService {
|
|
static const String _baseUrl = 'https://equran.id/api/v2';
|
|
static final EQuranService instance = EQuranService._();
|
|
EQuranService._();
|
|
|
|
// In-memory cache
|
|
List<Map<String, dynamic>>? _surahListCache;
|
|
|
|
/// Get list of all 114 surahs.
|
|
Future<List<Map<String, dynamic>>> getAllSurahs() async {
|
|
if (_surahListCache != null) return _surahListCache!;
|
|
try {
|
|
final response = await http.get(Uri.parse('$_baseUrl/surat'));
|
|
if (response.statusCode == 200) {
|
|
final data = json.decode(response.body);
|
|
if (data['code'] == 200) {
|
|
_surahListCache =
|
|
List<Map<String, dynamic>>.from(data['data']);
|
|
return _surahListCache!;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// silent fallback
|
|
}
|
|
return [];
|
|
}
|
|
|
|
/// Get full surah with all ayat, audio, etc.
|
|
/// Returns the full surah data object.
|
|
Future<Map<String, dynamic>?> getSurah(int number) async {
|
|
try {
|
|
final response =
|
|
await http.get(Uri.parse('$_baseUrl/surat/$number'));
|
|
if (response.statusCode == 200) {
|
|
final data = json.decode(response.body);
|
|
if (data['code'] == 200) {
|
|
return Map<String, dynamic>.from(data['data']);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// silent fallback
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Get tafsir for a surah.
|
|
Future<Map<String, dynamic>?> getTafsir(int number) async {
|
|
try {
|
|
final response =
|
|
await http.get(Uri.parse('$_baseUrl/tafsir/$number'));
|
|
if (response.statusCode == 200) {
|
|
final data = json.decode(response.body);
|
|
if (data['code'] == 200) {
|
|
return Map<String, dynamic>.from(data['data']);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// silent fallback
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Get deterministic daily ayat from API
|
|
Future<Map<String, dynamic>?> getDailyAyat() async {
|
|
try {
|
|
final now = DateTime.now();
|
|
final dayOfYear = int.parse(now.difference(DateTime(now.year, 1, 1)).inDays.toString());
|
|
|
|
// Pick surah 1-114
|
|
int surahId = (dayOfYear % 114) + 1;
|
|
|
|
final surahData = await getSurah(surahId);
|
|
if (surahData != null && surahData['ayat'] != null) {
|
|
int totalAyat = surahData['jumlahAyat'] ?? 1;
|
|
int ayatIndex = dayOfYear % totalAyat;
|
|
|
|
final targetAyat = surahData['ayat'][ayatIndex];
|
|
|
|
return {
|
|
'surahName': surahData['namaLatin'],
|
|
'nomorSurah': surahId,
|
|
'nomorAyat': targetAyat['nomorAyat'],
|
|
'teksArab': targetAyat['teksArab'],
|
|
'teksIndonesia': targetAyat['teksIndonesia'],
|
|
};
|
|
}
|
|
} catch (e) {
|
|
// silent fallback
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Available qari names mapped to audio key index.
|
|
static const Map<String, String> qariNames = {
|
|
'01': 'Abdullah Al-Juhany',
|
|
'02': 'Abdul Muhsin Al-Qasim',
|
|
'03': 'Abdurrahman As-Sudais',
|
|
'04': 'Ibrahim Al-Dossari',
|
|
'05': 'Misyari Rasyid Al-Afasi',
|
|
'06': 'Yasser Al-Dosari',
|
|
};
|
|
}
|