feat: integrate WordPress PDF Aussendungen with in-app reader into News
Build Android APK / Build Release APKs (push) Successful in 11m39s
Build Android APK / Build Release APKs (push) Successful in 11m39s
This commit is contained in:
@@ -11,6 +11,9 @@ class PostItem {
|
||||
final String? street;
|
||||
final String? category;
|
||||
final String? status;
|
||||
final String? pdfUrl;
|
||||
final String? webUrl;
|
||||
final bool isAussendung;
|
||||
|
||||
PostItem({
|
||||
required this.id,
|
||||
@@ -23,6 +26,9 @@ class PostItem {
|
||||
this.street,
|
||||
this.category,
|
||||
this.status = 'Gemeldet',
|
||||
this.pdfUrl,
|
||||
this.webUrl,
|
||||
this.isAussendung = false,
|
||||
}) : imageUrls = imageUrls ?? (imageUrl.isNotEmpty ? [imageUrl] : []);
|
||||
|
||||
factory PostItem.fromFirestore(DocumentSnapshot doc) {
|
||||
@@ -54,6 +60,9 @@ class PostItem {
|
||||
street: data['street'] as String?,
|
||||
category: data['category'] as String?,
|
||||
status: data['status'] as String? ?? 'Gemeldet',
|
||||
pdfUrl: data['pdfUrl'] as String?,
|
||||
webUrl: data['webUrl'] as String?,
|
||||
isAussendung: data['isAussendung'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -68,6 +77,163 @@ class PostItem {
|
||||
if (street != null) 'street': street,
|
||||
if (category != null) 'category': category,
|
||||
if (status != null) 'status': status,
|
||||
if (pdfUrl != null) 'pdfUrl': pdfUrl,
|
||||
if (webUrl != null) 'webUrl': webUrl,
|
||||
if (isAussendung) 'isAussendung': isAussendung,
|
||||
};
|
||||
}
|
||||
|
||||
/// Convert to standard JSON map for local caching
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'imageUrl': imageUrl,
|
||||
'imageUrls': imageUrls,
|
||||
'createdDate': createdDate.toIso8601String(),
|
||||
'authorUid': authorUid,
|
||||
'street': street,
|
||||
'category': category,
|
||||
'status': status,
|
||||
'pdfUrl': pdfUrl,
|
||||
'webUrl': webUrl,
|
||||
'isAussendung': isAussendung,
|
||||
};
|
||||
}
|
||||
|
||||
/// Create from standard JSON map (local cache)
|
||||
factory PostItem.fromJson(Map<String, dynamic> map) {
|
||||
DateTime date;
|
||||
if (map['createdDate'] is String) {
|
||||
date = DateTime.tryParse(map['createdDate'] as String) ?? DateTime.now();
|
||||
} else {
|
||||
date = DateTime.now();
|
||||
}
|
||||
|
||||
final String legacyImage = map['imageUrl'] as String? ?? '';
|
||||
List<String> images = [];
|
||||
if (map['imageUrls'] is List) {
|
||||
images = List<String>.from(map['imageUrls'] as List);
|
||||
} else if (legacyImage.isNotEmpty) {
|
||||
images = [legacyImage];
|
||||
}
|
||||
|
||||
return PostItem(
|
||||
id: map['id'] as String? ?? '',
|
||||
title: map['title'] as String? ?? '',
|
||||
description: map['description'] as String? ?? '',
|
||||
imageUrl: legacyImage.isNotEmpty ? legacyImage : (images.isNotEmpty ? images.first : ''),
|
||||
imageUrls: images,
|
||||
createdDate: date,
|
||||
authorUid: map['authorUid'] as String?,
|
||||
street: map['street'] as String?,
|
||||
category: map['category'] as String?,
|
||||
status: map['status'] as String? ?? 'Gemeldet',
|
||||
pdfUrl: map['pdfUrl'] as String?,
|
||||
webUrl: map['webUrl'] as String?,
|
||||
isAussendung: map['isAussendung'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
/// Decode HTML entities commonly present in WordPress feeds
|
||||
static String unescapeHtml(String text) {
|
||||
var result = text
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll(''', "'")
|
||||
.replaceAll(''', "'")
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll(' ', ' ')
|
||||
.replaceAll('–', '–')
|
||||
.replaceAll('—', '—')
|
||||
.replaceAll('‘', '‘')
|
||||
.replaceAll('’', '’')
|
||||
.replaceAll('“', '“')
|
||||
.replaceAll('”', '”')
|
||||
.replaceAll('…', '…');
|
||||
|
||||
// Decode numerical entities &#DDD;
|
||||
result = result.replaceAllMapped(RegExp(r'&#(\d+);'), (match) {
|
||||
final code = int.tryParse(match.group(1)!);
|
||||
return code != null ? String.fromCharCode(code) : match.group(0)!;
|
||||
});
|
||||
|
||||
// Decode hex numerical entities &#xHH;
|
||||
result = result.replaceAllMapped(RegExp(r'&#x([0-9a-fA-F]+);'), (match) {
|
||||
final code = int.tryParse(match.group(1)!, radix: 16);
|
||||
return code != null ? String.fromCharCode(code) : match.group(0)!;
|
||||
});
|
||||
|
||||
return result.trim();
|
||||
}
|
||||
|
||||
/// Extracts the direct PDF URL from WordPress post HTML content
|
||||
static String? extractPdfUrl(String htmlContent) {
|
||||
final pdfRegExp = RegExp(
|
||||
r'https?://[^\s"<>]+?\.pdf',
|
||||
caseSensitive: false,
|
||||
);
|
||||
final match = pdfRegExp.firstMatch(htmlContent);
|
||||
if (match != null) {
|
||||
return match.group(0);
|
||||
}
|
||||
// Fallback: check docx or doc files
|
||||
final docRegExp = RegExp(
|
||||
r'https?://[^\s"<>]+?\.(?:docx|doc)',
|
||||
caseSensitive: false,
|
||||
);
|
||||
final docMatch = docRegExp.firstMatch(htmlContent);
|
||||
return docMatch?.group(0);
|
||||
}
|
||||
|
||||
/// Strip HTML tags and clean whitespace for preview text
|
||||
static String extractCleanText(String htmlContent) {
|
||||
var clean = htmlContent.replaceAll(RegExp(r'<style[^>]*>.*?</style>', dotAll: true), '');
|
||||
clean = clean.replaceAll(RegExp(r'<script[^>]*>.*?</script>', dotAll: true), '');
|
||||
clean = clean.replaceAll(RegExp(r'<[^>]*>'), ' ');
|
||||
clean = unescapeHtml(clean);
|
||||
clean = clean.replaceAll(RegExp(r'\s+'), ' ').trim();
|
||||
return clean;
|
||||
}
|
||||
|
||||
/// Parse a post from the WordPress REST API (/wp-json/wp/v2/posts)
|
||||
factory PostItem.fromWordPressJson(Map<String, dynamic> json) {
|
||||
final rawTitle = (json['title'] is Map)
|
||||
? (json['title']['rendered'] as String? ?? '')
|
||||
: (json['title'] as String? ?? '');
|
||||
final title = unescapeHtml(rawTitle);
|
||||
|
||||
final rawContent = (json['content'] is Map)
|
||||
? (json['content']['rendered'] as String? ?? '')
|
||||
: (json['content'] as String? ?? '');
|
||||
|
||||
final pdfUrl = extractPdfUrl(rawContent);
|
||||
final webUrl = json['link'] as String?;
|
||||
|
||||
DateTime date;
|
||||
if (json['date'] is String) {
|
||||
date = DateTime.tryParse(json['date'] as String) ?? DateTime.now();
|
||||
} else {
|
||||
date = DateTime.now();
|
||||
}
|
||||
|
||||
String desc = extractCleanText(rawContent);
|
||||
if (desc.isEmpty || desc == title) {
|
||||
desc = 'Offizielle Aussendung des Bürgerforums Oggau (PDF).';
|
||||
}
|
||||
|
||||
return PostItem(
|
||||
id: 'wp_${json['id']}',
|
||||
title: title.isNotEmpty ? title : 'Aussendung',
|
||||
description: desc,
|
||||
createdDate: date,
|
||||
category: 'Aussendung',
|
||||
status: 'Aussendung',
|
||||
pdfUrl: pdfUrl,
|
||||
webUrl: webUrl,
|
||||
isAussendung: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user