- 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.3 KiB
Dart
109 lines
3.3 KiB
Dart
import 'dart:convert';
|
|
import 'package:http/http.dart' as http;
|
|
|
|
/// Service for myQuran.com v3 Sholat API.
|
|
/// Provides Kemenag-accurate prayer times for Indonesian cities.
|
|
class MyQuranSholatService {
|
|
static const String _baseUrl = 'https://api.myquran.com/v3/sholat';
|
|
static final MyQuranSholatService instance = MyQuranSholatService._();
|
|
MyQuranSholatService._();
|
|
|
|
/// Search for a city/kabupaten by name.
|
|
/// Returns list of {id, lokasi}.
|
|
Future<List<Map<String, dynamic>>> searchCity(String query) async {
|
|
try {
|
|
final response = await http.get(
|
|
Uri.parse('$_baseUrl/kota/cari/$query'),
|
|
);
|
|
if (response.statusCode == 200) {
|
|
final data = json.decode(response.body);
|
|
if (data['status'] == true) {
|
|
return List<Map<String, dynamic>>.from(data['data']);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// silent fallback
|
|
}
|
|
return [];
|
|
}
|
|
|
|
/// Get prayer times for a specific city and date.
|
|
/// [cityId] = myQuran city ID (hash string)
|
|
/// [date] = 'yyyy-MM-dd' format
|
|
/// Returns map: {tanggal, imsak, subuh, terbit, dhuha, dzuhur, ashar, maghrib, isya}
|
|
Future<Map<String, String>?> getDailySchedule(
|
|
String cityId, String date) async {
|
|
try {
|
|
final response = await http.get(
|
|
Uri.parse('$_baseUrl/jadwal/$cityId/$date'),
|
|
);
|
|
if (response.statusCode == 200) {
|
|
final data = json.decode(response.body);
|
|
if (data['status'] == true) {
|
|
final jadwal = data['data']['jadwal'][date];
|
|
if (jadwal != null) {
|
|
return Map<String, String>.from(
|
|
jadwal.map((k, v) => MapEntry(k.toString(), v.toString())),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// silent fallback
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Get monthly prayer schedule.
|
|
/// [month] = 'yyyy-MM' format
|
|
/// Returns map of date → jadwal.
|
|
Future<Map<String, Map<String, String>>> getMonthlySchedule(
|
|
String cityId, String month) async {
|
|
try {
|
|
final response = await http.get(
|
|
Uri.parse('$_baseUrl/jadwal/$cityId/$month'),
|
|
);
|
|
if (response.statusCode == 200) {
|
|
final data = json.decode(response.body);
|
|
if (data['status'] == true) {
|
|
final jadwalMap = data['data']['jadwal'] as Map<String, dynamic>;
|
|
final result = <String, Map<String, String>>{};
|
|
for (final entry in jadwalMap.entries) {
|
|
result[entry.key] = Map<String, String>.from(
|
|
(entry.value as Map).map(
|
|
(k, v) => MapEntry(k.toString(), v.toString())),
|
|
);
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// silent fallback
|
|
}
|
|
return {};
|
|
}
|
|
|
|
/// Get city info (kabko, prov) from a jadwal response.
|
|
Future<Map<String, String>?> getCityInfo(String cityId) async {
|
|
final today =
|
|
DateTime.now().toIso8601String().substring(0, 10);
|
|
try {
|
|
final response = await http.get(
|
|
Uri.parse('$_baseUrl/jadwal/$cityId/$today'),
|
|
);
|
|
if (response.statusCode == 200) {
|
|
final data = json.decode(response.body);
|
|
if (data['status'] == true) {
|
|
return {
|
|
'kabko': data['data']['kabko']?.toString() ?? '',
|
|
'prov': data['data']['prov']?.toString() ?? '',
|
|
};
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// silent fallback
|
|
}
|
|
return null;
|
|
}
|
|
}
|