Polish mobile UX downloads and comments
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import '../models/post.dart';
|
||||
import '../models/post_comment.dart';
|
||||
import '../models/provider_health.dart';
|
||||
import '../models/top_period_filter.dart';
|
||||
|
||||
@@ -19,3 +20,7 @@ abstract class ContentProvider {
|
||||
|
||||
Future<ProviderHealth> checkHealth();
|
||||
}
|
||||
|
||||
abstract class CommentProvider {
|
||||
Future<List<PostComment>> getComments(String postId);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import '../../core/errors/app_exception.dart';
|
||||
import '../models/post.dart';
|
||||
import '../models/post_comment.dart';
|
||||
import '../models/provider_health.dart';
|
||||
import '../models/top_period_filter.dart';
|
||||
import 'content_provider.dart';
|
||||
|
||||
class CustomProvider implements ContentProvider {
|
||||
class CustomProvider implements ContentProvider, CommentProvider {
|
||||
CustomProvider(this._delegate);
|
||||
|
||||
final ContentProvider _delegate;
|
||||
@@ -37,6 +38,15 @@ class CustomProvider implements ContentProvider {
|
||||
|
||||
@override
|
||||
Future<ProviderHealth> checkHealth() => _delegate.checkHealth();
|
||||
|
||||
@override
|
||||
Future<List<PostComment>> getComments(String postId) {
|
||||
final delegate = _delegate;
|
||||
if (delegate is CommentProvider) {
|
||||
return (delegate as CommentProvider).getComments(postId);
|
||||
}
|
||||
return Future.value(const []);
|
||||
}
|
||||
}
|
||||
|
||||
class UnsupportedCustomProvider implements ContentProvider {
|
||||
|
||||
@@ -3,11 +3,12 @@ import 'package:dio/dio.dart';
|
||||
import '../../core/http/dio_client.dart';
|
||||
import '../mappers/danbooru_mapper.dart';
|
||||
import '../models/post.dart';
|
||||
import '../models/post_comment.dart';
|
||||
import '../models/provider_health.dart';
|
||||
import '../models/top_period_filter.dart';
|
||||
import 'content_provider.dart';
|
||||
|
||||
class DanbooruProvider implements ContentProvider {
|
||||
class DanbooruProvider implements ContentProvider, CommentProvider {
|
||||
DanbooruProvider({
|
||||
required this.id,
|
||||
required this.name,
|
||||
@@ -96,6 +97,38 @@ class DanbooruProvider implements ContentProvider {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<PostComment>> getComments(String postId) async {
|
||||
final response = await _dio.get<dynamic>(
|
||||
'/comments.json',
|
||||
queryParameters: {
|
||||
'search[post_id]': postId,
|
||||
'limit': 50,
|
||||
..._queryParameters,
|
||||
},
|
||||
);
|
||||
final data = response.data;
|
||||
final items = data is List ? data : const [];
|
||||
return items
|
||||
.whereType<Map>()
|
||||
.map((item) {
|
||||
final json = Map<String, dynamic>.from(item);
|
||||
return PostComment(
|
||||
id: (json['id'] ?? '').toString(),
|
||||
postId: (json['post_id'] ?? postId).toString(),
|
||||
providerId: id,
|
||||
authorName: (json['creator_name'] ?? json['creator_id'] ?? 'user')
|
||||
.toString(),
|
||||
body: (json['body'] ?? '').toString(),
|
||||
createdAt:
|
||||
DateTime.tryParse((json['created_at'] ?? '').toString()) ??
|
||||
DateTime.fromMillisecondsSinceEpoch(0),
|
||||
);
|
||||
})
|
||||
.where((comment) => comment.body.trim().isNotEmpty)
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<String> _topTags(TopPeriodFilter period) {
|
||||
return switch (period) {
|
||||
TopPeriodFilter.none => const [],
|
||||
|
||||
@@ -4,11 +4,12 @@ import '../../core/errors/app_exception.dart';
|
||||
import '../../core/http/dio_client.dart';
|
||||
import '../mappers/gelbooru_mapper.dart';
|
||||
import '../models/post.dart';
|
||||
import '../models/post_comment.dart';
|
||||
import '../models/provider_health.dart';
|
||||
import '../models/top_period_filter.dart';
|
||||
import 'content_provider.dart';
|
||||
|
||||
class GelbooruProvider implements ContentProvider {
|
||||
class GelbooruProvider implements ContentProvider, CommentProvider {
|
||||
GelbooruProvider({
|
||||
required this.id,
|
||||
required this.name,
|
||||
@@ -117,6 +118,22 @@ class GelbooruProvider implements ContentProvider {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<PostComment>> getComments(String postId) async {
|
||||
final response = await _dio.get<dynamic>(
|
||||
'/index.php',
|
||||
queryParameters: {
|
||||
'page': 'dapi',
|
||||
's': 'comment',
|
||||
'q': 'index',
|
||||
'json': '1',
|
||||
'post_id': postId,
|
||||
..._queryParameters,
|
||||
},
|
||||
);
|
||||
return _commentsFromResponse(response.data, postId);
|
||||
}
|
||||
|
||||
Never unavailable(String message) =>
|
||||
throw ProviderUnavailableException(message);
|
||||
|
||||
@@ -129,4 +146,36 @@ class GelbooruProvider implements ContentProvider {
|
||||
const ['sort:score:desc'],
|
||||
};
|
||||
}
|
||||
|
||||
List<PostComment> _commentsFromResponse(dynamic data, String postId) {
|
||||
final raw = switch (data) {
|
||||
List() => data,
|
||||
{'comments': final List comments} => comments,
|
||||
{'@attributes': _, 'comment': final List comments} => comments,
|
||||
{'comment': final List comments} => comments,
|
||||
_ => const [],
|
||||
};
|
||||
return raw
|
||||
.whereType<Map>()
|
||||
.map((item) {
|
||||
final json = Map<String, dynamic>.from(item);
|
||||
return PostComment(
|
||||
id: (json['id'] ?? json['comment_id'] ?? '').toString(),
|
||||
postId: (json['post_id'] ?? postId).toString(),
|
||||
providerId: id,
|
||||
authorName: (json['creator'] ??
|
||||
json['creator_name'] ??
|
||||
json['author'] ??
|
||||
'')
|
||||
.toString(),
|
||||
body: (json['body'] ?? json['comment'] ?? '').toString(),
|
||||
createdAt: DateTime.tryParse(
|
||||
(json['created_at'] ?? json['created'] ?? '').toString(),
|
||||
) ??
|
||||
DateTime.fromMillisecondsSinceEpoch(0),
|
||||
);
|
||||
})
|
||||
.where((comment) => comment.body.trim().isNotEmpty)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,34 +20,38 @@ class ProviderFactory {
|
||||
timeout: Duration(seconds: config.timeoutSeconds),
|
||||
headers: headers,
|
||||
);
|
||||
return switch (config.apiType.toLowerCase()) {
|
||||
'gelbooru' => GelbooruProvider(
|
||||
switch (config.apiType.toLowerCase()) {
|
||||
case 'gelbooru':
|
||||
return GelbooruProvider(
|
||||
id: config.id,
|
||||
name: config.name,
|
||||
baseUrl: config.baseUrl,
|
||||
dioClient: client,
|
||||
queryParameters: queryParameters,
|
||||
),
|
||||
'rule34' => Rule34Provider(
|
||||
);
|
||||
case 'rule34':
|
||||
return Rule34Provider(
|
||||
id: config.id,
|
||||
name: config.name,
|
||||
baseUrl: config.baseUrl,
|
||||
dioClient: client,
|
||||
queryParameters: queryParameters,
|
||||
),
|
||||
'danbooru' => DanbooruProvider(
|
||||
);
|
||||
case 'danbooru':
|
||||
return DanbooruProvider(
|
||||
id: config.id,
|
||||
name: config.name,
|
||||
baseUrl: config.baseUrl,
|
||||
dioClient: client,
|
||||
queryParameters: queryParameters,
|
||||
),
|
||||
_ => UnsupportedCustomProvider(
|
||||
);
|
||||
default:
|
||||
return UnsupportedCustomProvider(
|
||||
id: config.id,
|
||||
name: config.name,
|
||||
baseUrl: config.baseUrl,
|
||||
apiType: config.apiType,
|
||||
),
|
||||
};
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import '../../core/errors/failure.dart';
|
||||
import '../../core/utils/result.dart';
|
||||
import '../models/content_provider_config.dart';
|
||||
import '../models/post.dart';
|
||||
import '../models/post_comment.dart';
|
||||
import '../models/provider_health.dart';
|
||||
import '../models/top_period_filter.dart';
|
||||
import '../repositories/provider_repository.dart';
|
||||
@@ -96,11 +97,6 @@ class ProviderManager {
|
||||
|
||||
final posts = <Post>[];
|
||||
for (final provider in providers) {
|
||||
final health = await _repository.getHealth(provider.id);
|
||||
final offline = health is Success<ProviderHealth?> &&
|
||||
health.data?.status == ProviderStatus.offline;
|
||||
if (offline) continue;
|
||||
|
||||
try {
|
||||
final providerPosts = await provider.searchPosts(
|
||||
tags: tags,
|
||||
@@ -125,6 +121,36 @@ class ProviderManager {
|
||||
return Success(posts);
|
||||
}
|
||||
|
||||
Future<Result<List<PostComment>>> getComments(
|
||||
String providerId,
|
||||
String postId,
|
||||
) async {
|
||||
final providersResult = await activeProviders();
|
||||
if (providersResult is Error<List<ContentProvider>>) {
|
||||
return Error(providersResult.failure);
|
||||
}
|
||||
final providers = (providersResult as Success<List<ContentProvider>>).data;
|
||||
final matches = providers.where((provider) => provider.id == providerId);
|
||||
if (matches.isEmpty) {
|
||||
return const Error(
|
||||
Failure(code: 'not_found', message: 'Provider not found'),
|
||||
);
|
||||
}
|
||||
final provider = matches.first;
|
||||
if (provider is! CommentProvider) return const Success([]);
|
||||
try {
|
||||
return Success(await (provider as CommentProvider).getComments(postId));
|
||||
} catch (error) {
|
||||
return Error(
|
||||
Failure(
|
||||
code: 'comments_unavailable',
|
||||
message: 'Comments unavailable',
|
||||
details: error,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<Post?>> getPost(String providerId, String postId) async {
|
||||
final providersResult = await activeProviders();
|
||||
if (providersResult is Error<List<ContentProvider>>) {
|
||||
|
||||
Reference in New Issue
Block a user