139 lines
4.3 KiB
Dart
139 lines
4.3 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:http/http.dart' as http;
|
|
|
|
import '../models/models.dart';
|
|
import '../repositories/report_repository.dart';
|
|
import '../state/report_query.dart';
|
|
|
|
abstract class ReportDataSource extends ReportRepository {
|
|
Future<List<ReportCardModel>> recommended();
|
|
Future<List<ReportCardModel>> reports();
|
|
Future<List<Institution>> institutions();
|
|
Future<Institution> institutionDetail(String institutionId);
|
|
Future<List<AudioItem>> listen();
|
|
Future<ReportDetail> reportDetail(String reportId);
|
|
Future<ModuleDetail> moduleDetail(String reportId, String moduleId);
|
|
|
|
@override
|
|
Future<List<ReportCardModel>> getRecommended({String? topic}) async {
|
|
final items = await recommended();
|
|
if (topic == null || topic == '全部') return items;
|
|
return items.where((item) => item.topics.contains(topic)).toList();
|
|
}
|
|
|
|
@override
|
|
Future<List<ReportCardModel>> getReports(ReportQuery query) async {
|
|
final currentSearch = query.search.trim().toLowerCase();
|
|
final items = await reports();
|
|
final filtered = items.where((item) {
|
|
final haystack =
|
|
'${item.titleCn} ${item.subtitleCn} ${item.oneLiner} '
|
|
'${item.institution.nameCn} ${item.institution.nameEn} '
|
|
'${item.topics.join(' ')}'
|
|
.toLowerCase();
|
|
if (currentSearch.isNotEmpty && !haystack.contains(currentSearch)) {
|
|
return false;
|
|
}
|
|
if (query.topic != null && !item.topics.contains(query.topic)) {
|
|
return false;
|
|
}
|
|
if (query.institutionId != null &&
|
|
item.institution.id != query.institutionId) {
|
|
return false;
|
|
}
|
|
if (query.hasAudio && !item.hasAudio) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}).toList();
|
|
filtered.sort((a, b) {
|
|
final result = (b.releasedAt ?? '').compareTo(a.releasedAt ?? '');
|
|
return query.sort == ReportSort.oldest ? -result : result;
|
|
});
|
|
return filtered;
|
|
}
|
|
|
|
@override
|
|
Future<ReportDetail> getReportDetail(String reportId) =>
|
|
reportDetail(reportId);
|
|
|
|
@override
|
|
Future<List<Institution>> getInstitutions() => institutions();
|
|
|
|
@override
|
|
Future<Institution> getInstitutionDetail(String institutionId) =>
|
|
institutionDetail(institutionId);
|
|
|
|
@override
|
|
Future<List<AudioItem>> getListenItems() => listen();
|
|
|
|
@override
|
|
Future<ModuleDetail> getModuleDetail(String reportId, String moduleId) =>
|
|
moduleDetail(reportId, moduleId);
|
|
}
|
|
|
|
class RnbApiDataSource extends ReportDataSource {
|
|
RnbApiDataSource({
|
|
http.Client? client,
|
|
this.baseUrl = const String.fromEnvironment('RNB_API_BASE'),
|
|
}) : client = client ?? http.Client();
|
|
|
|
final http.Client client;
|
|
final String baseUrl;
|
|
|
|
Future<JsonMap> _get(String path) async {
|
|
if (baseUrl.isEmpty) {
|
|
throw StateError('RNB_API_BASE is required for live API requests.');
|
|
}
|
|
final response = await client.get(Uri.parse('$baseUrl$path'));
|
|
if (response.statusCode != 200) {
|
|
throw StateError('Request failed: ${response.statusCode}');
|
|
}
|
|
return jsonDecode(utf8.decode(response.bodyBytes)) as JsonMap;
|
|
}
|
|
|
|
List<JsonMap> _items(JsonMap body) => asMapList(body['items']);
|
|
|
|
@override
|
|
Future<List<ReportCardModel>> recommended() async {
|
|
final body = await _get('/feed/recommended');
|
|
return _items(body).map(ReportCardModel.fromJson).toList();
|
|
}
|
|
|
|
@override
|
|
Future<List<ReportCardModel>> reports() async {
|
|
final body = await _get('/reports');
|
|
return _items(body).map(ReportCardModel.fromJson).toList();
|
|
}
|
|
|
|
@override
|
|
Future<List<Institution>> institutions() async {
|
|
final body = await _get('/institutions');
|
|
return _items(body).map(Institution.fromJson).toList();
|
|
}
|
|
|
|
@override
|
|
Future<Institution> institutionDetail(String institutionId) async {
|
|
return Institution.fromJson(await _get('/institutions/$institutionId'));
|
|
}
|
|
|
|
@override
|
|
Future<List<AudioItem>> listen() async {
|
|
final body = await _get('/listen');
|
|
return _items(body).map(AudioItem.fromJson).toList();
|
|
}
|
|
|
|
@override
|
|
Future<ReportDetail> reportDetail(String reportId) async {
|
|
return ReportDetail.fromJson(await _get('/reports/$reportId'));
|
|
}
|
|
|
|
@override
|
|
Future<ModuleDetail> moduleDetail(String reportId, String moduleId) async {
|
|
return ModuleDetail.fromJson(
|
|
await _get('/reports/$reportId/modules/$moduleId'),
|
|
);
|
|
}
|
|
}
|