diff --git a/README.md b/README.md
index 6fdd1d9..c9619c2 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
-
+
diff --git a/lib/app/app_version.dart b/lib/app/app_version.dart
index 9564949..f17701f 100644
--- a/lib/app/app_version.dart
+++ b/lib/app/app_version.dart
@@ -1,3 +1,3 @@
-const appDisplayVersion = '3.2.0';
-const appBuildNumber = 71;
+const appDisplayVersion = '3.3.0';
+const appBuildNumber = 72;
const appReleaseChannel = 'Stable';
diff --git a/lib/app/changelog.dart b/lib/app/changelog.dart
index 7341d82..eb2090b 100644
--- a/lib/app/changelog.dart
+++ b/lib/app/changelog.dart
@@ -1,4 +1,15 @@
const lunarisChangelog = [
+ LunarisChange(
+ version: '3.3.0',
+ title: 'Редизайн личных сообщений и рассылок артиста, нативная поддержка текстовых публикаций',
+ bullets: [
+ 'Центр сообщений и рассылок автора: полностью переработан диалог по кнопке-рупору на экране артиста — аккуратные карточки сообщений в стиле мессенджера, бейджи сервисов, точное время публикации и кнопка быстрого копирования текста в буфер обмена.',
+ 'Нативная поддержка текстовых постов: публикации без медиафайлов больше не вызывают ошибок и пустых плейсхолдеров — в сетке они отображаются как стильные заметки с заголовком и выдержкой текста.',
+ 'Удобное чтение статей и заметок: при открытии текстового поста отображается комфортный ридер с форматированным текстом, ссылками автора и тегами.',
+ 'Фильтрация по типу контента: на экране автора добавлен фильтр «Текст» с подсчетом количества публикаций наряду с фото, видео и GIF.',
+ 'Поддержка анонсов в Kemono и Coomer: API анонсов и персональных сообщений теперь полноценно работает для всех поддерживаемых провайдеров.',
+ ],
+ ),
LunarisChange(
version: '3.2.0',
title: 'Стабильный полноэкранный режим: сохранение воспроизведения, устранение вылетов плеера и чистый интерфейс',
diff --git a/lib/backend/models/post.dart b/lib/backend/models/post.dart
index 55e4810..930a35e 100644
--- a/lib/backend/models/post.dart
+++ b/lib/backend/models/post.dart
@@ -60,11 +60,21 @@ class Post {
return null;
}
+ String? get description => commentary;
+
+ String? get title {
+ final list = tagGroups['copyright'];
+ if (list != null && list.isNotEmpty) {
+ return list.first;
+ }
+ return null;
+ }
+
List get cleanTags {
return tags.where((tag) {
final t = tag.trim();
if (t.isEmpty) return false;
- if (t == 'cloud_mirror') return false;
+ if (t == 'cloud_mirror' || t == 'text_post') return false;
if (t.startsWith('{') && t.endsWith('}')) return false;
if (t.contains('"url"') || t.contains('"service"')) return false;
if (t.startsWith('http://') || t.startsWith('https://')) return false;
diff --git a/lib/backend/providers/kemono_provider.dart b/lib/backend/providers/kemono_provider.dart
index e6143c9..daba43d 100644
--- a/lib/backend/providers/kemono_provider.dart
+++ b/lib/backend/providers/kemono_provider.dart
@@ -230,8 +230,23 @@ class KemonoProvider
@override
Future> getArtistAnnouncements(
- String service, String artistId) async =>
- const [];
+ String service, String artistId) async {
+ final path = '/api/v1/$service/user/$artistId/announcements';
+ for (final apiBase in _apiBaseCandidates()) {
+ try {
+ final response = await _getFromApiBase(apiBase, path);
+ final items = response.data;
+ if (items is List) {
+ return items
+ .whereType