mirror of
https://github.com/lifegpc/eh_downloader_flutter.git
synced 2026-06-06 05:49:03 +08:00
Update
This commit is contained in:
@@ -26,6 +26,22 @@ final _pbkdf2b = Pbkdf2(
|
||||
const _utf8Encoder = Utf8Encoder();
|
||||
final _salt = _utf8Encoder.convert("eh-downloader-salt");
|
||||
|
||||
enum ThumbnailMethod {
|
||||
unknown,
|
||||
cover,
|
||||
contain,
|
||||
fill;
|
||||
}
|
||||
|
||||
enum ThumbnailAlign {
|
||||
left,
|
||||
center,
|
||||
right;
|
||||
|
||||
static const top = left;
|
||||
static const bottom = right;
|
||||
}
|
||||
|
||||
@RestApi()
|
||||
abstract class _EHApi {
|
||||
factory _EHApi(Dio dio, {required String baseUrl}) = __EHApi;
|
||||
@@ -64,19 +80,31 @@ abstract class _EHApi {
|
||||
{@Query("token") String? token});
|
||||
|
||||
@GET('/file/{id}')
|
||||
Future<HttpResponse> getFile(@Path("id") int id);
|
||||
@DioResponseType(ResponseType.bytes)
|
||||
Future<HttpResponse<List<int>>> getFile(@Path("id") int id);
|
||||
@GET('/file/{id}')
|
||||
// ignore: unused_element
|
||||
Future<ApiResult<EhFileExtend>> _getFileData(
|
||||
@Path("id") int id, @Query("data") bool data);
|
||||
@GET('/file/random')
|
||||
Future<HttpResponse> getRandomFile(
|
||||
@DioResponseType(ResponseType.bytes)
|
||||
Future<HttpResponse<List<int>>> getRandomFile(
|
||||
{@Query("is_nsfw") bool? isNsfw,
|
||||
@Query("is_ad") bool? isAd,
|
||||
@Query("thumb") bool? thumb});
|
||||
@GET('/files/{token}')
|
||||
// ignore: unused_element
|
||||
Future<ApiResult<EhFiles>> _getFiles(@Path("token") String token);
|
||||
@GET('/thumbnail/{id}')
|
||||
@DioResponseType(ResponseType.bytes)
|
||||
Future<HttpResponse<List<int>>> getThumbnail(@Path("id") int id,
|
||||
{@Query("max") int? max,
|
||||
@Query("width") int? width,
|
||||
@Query("height") int? height,
|
||||
@Query("quality") int? quality,
|
||||
@Query("force") bool? force,
|
||||
@Query("method") ThumbnailMethod? method,
|
||||
@Query("align") ThumbnailAlign? align});
|
||||
|
||||
@GET('/gallery/{gid}')
|
||||
Future<ApiResult<GalleryData>> getGallery(@Path("gid") int gid);
|
||||
|
||||
@@ -281,16 +281,17 @@ class __EHApi implements _EHApi {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<HttpResponse<dynamic>> getFile(int id) async {
|
||||
Future<HttpResponse<List<int>>> getFile(int id) async {
|
||||
const _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
final Map<String, dynamic>? _data = null;
|
||||
final _result =
|
||||
await _dio.fetch(_setStreamType<HttpResponse<dynamic>>(Options(
|
||||
final _result = await _dio
|
||||
.fetch<List<dynamic>>(_setStreamType<HttpResponse<List<int>>>(Options(
|
||||
method: 'GET',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
responseType: ResponseType.bytes,
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
@@ -303,7 +304,7 @@ class __EHApi implements _EHApi {
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
))));
|
||||
final value = _result.data;
|
||||
final value = _result.data!.cast<int>();
|
||||
final httpResponse = HttpResponse(value, _result);
|
||||
return httpResponse;
|
||||
}
|
||||
@@ -342,7 +343,7 @@ class __EHApi implements _EHApi {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<HttpResponse<dynamic>> getRandomFile({
|
||||
Future<HttpResponse<List<int>>> getRandomFile({
|
||||
bool? isNsfw,
|
||||
bool? isAd,
|
||||
bool? thumb,
|
||||
@@ -356,11 +357,12 @@ class __EHApi implements _EHApi {
|
||||
queryParameters.removeWhere((k, v) => v == null);
|
||||
final _headers = <String, dynamic>{};
|
||||
final Map<String, dynamic>? _data = null;
|
||||
final _result =
|
||||
await _dio.fetch(_setStreamType<HttpResponse<dynamic>>(Options(
|
||||
final _result = await _dio
|
||||
.fetch<List<dynamic>>(_setStreamType<HttpResponse<List<int>>>(Options(
|
||||
method: 'GET',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
responseType: ResponseType.bytes,
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
@@ -373,7 +375,7 @@ class __EHApi implements _EHApi {
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
))));
|
||||
final value = _result.data;
|
||||
final value = _result.data!.cast<int>();
|
||||
final httpResponse = HttpResponse(value, _result);
|
||||
return httpResponse;
|
||||
}
|
||||
@@ -408,6 +410,53 @@ class __EHApi implements _EHApi {
|
||||
return value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<HttpResponse<List<int>>> getThumbnail(
|
||||
int id, {
|
||||
int? max,
|
||||
int? width,
|
||||
int? height,
|
||||
int? quality,
|
||||
bool? force,
|
||||
ThumbnailMethod? method,
|
||||
ThumbnailAlign? align,
|
||||
}) async {
|
||||
const _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{
|
||||
r'max': max,
|
||||
r'width': width,
|
||||
r'height': height,
|
||||
r'quality': quality,
|
||||
r'force': force,
|
||||
r'method': method?.name,
|
||||
r'align': align?.name,
|
||||
};
|
||||
queryParameters.removeWhere((k, v) => v == null);
|
||||
final _headers = <String, dynamic>{};
|
||||
final Map<String, dynamic>? _data = null;
|
||||
final _result = await _dio
|
||||
.fetch<List<dynamic>>(_setStreamType<HttpResponse<List<int>>>(Options(
|
||||
method: 'GET',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
responseType: ResponseType.bytes,
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/thumbnail/${id}',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(
|
||||
baseUrl: _combineBaseUrls(
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
))));
|
||||
final value = _result.data!.cast<int>();
|
||||
final httpResponse = HttpResponse(value, _result);
|
||||
return httpResponse;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ApiResult<GalleryData>> getGallery(int gid) async {
|
||||
const _extra = <String, dynamic>{};
|
||||
|
||||
@@ -42,11 +42,14 @@ class EhFileExtend {
|
||||
|
||||
class EhFiles {
|
||||
const EhFiles({required this.files});
|
||||
final Map<String, EhFileBasic> files;
|
||||
final Map<String, List<EhFileBasic>> files;
|
||||
factory EhFiles.fromJson(Map<String, dynamic> json) => EhFiles(
|
||||
files: (json).map(
|
||||
(k, e) =>
|
||||
MapEntry(k, EhFileBasic.fromJson(e as Map<String, dynamic>)),
|
||||
(k, e) => MapEntry(
|
||||
k,
|
||||
(e as List<dynamic>)
|
||||
.map((e) => EhFileBasic.fromJson(e as Map<String, dynamic>))
|
||||
.toList()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
99
lib/components/thumbnail.dart
Normal file
99
lib/components/thumbnail.dart
Normal file
@@ -0,0 +1,99 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import '../api/client.dart';
|
||||
import '../api/gallery.dart';
|
||||
import '../globals.dart';
|
||||
|
||||
final _log = Logger("Thumbnail");
|
||||
|
||||
class Thumbnail extends StatefulWidget {
|
||||
const Thumbnail(ExtendedPMeta pMeta,
|
||||
{Key? key, int? max, int? width, int? height, int? fileId})
|
||||
: _pMeta = pMeta,
|
||||
_max = max ?? 1200,
|
||||
_width = width,
|
||||
_height = height,
|
||||
_fileId = fileId,
|
||||
super(key: key);
|
||||
final ExtendedPMeta _pMeta;
|
||||
final int _max;
|
||||
final int? _width;
|
||||
final int? _height;
|
||||
final int? _fileId;
|
||||
|
||||
int get height => _height != null
|
||||
? _height!
|
||||
: _pMeta.height > _pMeta.width
|
||||
? _max
|
||||
: _max * _pMeta.height ~/ _pMeta.width;
|
||||
int get width => _width != null
|
||||
? _width!
|
||||
: _pMeta.width > _pMeta.height
|
||||
? _max
|
||||
: _max * _pMeta.width ~/ _pMeta.height;
|
||||
|
||||
@override
|
||||
State<Thumbnail> createState() => _Thumbnail();
|
||||
}
|
||||
|
||||
class _Thumbnail extends State<Thumbnail> {
|
||||
Uint8List? _data;
|
||||
bool _isLoading = false;
|
||||
Object? _error;
|
||||
int? _fileId;
|
||||
Future<void> _fetchData() async {
|
||||
try {
|
||||
_isLoading = true;
|
||||
if (_fileId == null) {
|
||||
final token = widget._pMeta.token;
|
||||
_fileId = (await api.getFiles([token])).unwrap().files[token]![0]!.id;
|
||||
}
|
||||
final re = await api.getThumbnail(_fileId!,
|
||||
max: widget._max,
|
||||
width: widget._width,
|
||||
height: widget._height,
|
||||
method: ThumbnailMethod.contain,
|
||||
align: ThumbnailAlign.center);
|
||||
if (re.response.statusCode != 200) {
|
||||
throw Exception(
|
||||
'Failed to get thumbnail: ${re.response.statusCode} ${re.response.statusMessage}');
|
||||
}
|
||||
final data = Uint8List.fromList(re.data);
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_data = data;
|
||||
});
|
||||
} catch (e) {
|
||||
_log.warning("Failed to get file data:", e);
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_error = e;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_data = null;
|
||||
_isLoading = false;
|
||||
_error = null;
|
||||
_fileId = widget._fileId;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isLoading = _data == null && _error == null;
|
||||
if (isLoading && !_isLoading) _fetchData();
|
||||
return SizedBox(
|
||||
width: widget.width.toDouble(),
|
||||
height: widget.height.toDouble(),
|
||||
child: isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _data != null
|
||||
? Image.memory(_data!)
|
||||
: Text("Error $_error"));
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'api/gallery.dart';
|
||||
import 'components/thumbnail.dart';
|
||||
import 'globals.dart';
|
||||
|
||||
final _log = Logger("GalleryPage");
|
||||
@@ -79,7 +80,7 @@ class _GalleryPage extends State<GalleryPage> with ThemeModeWidget {
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _data != null
|
||||
? Center(
|
||||
child: Text("Gallery $_gid"),
|
||||
child: Thumbnail(_data!.pages[0]!),
|
||||
)
|
||||
: Center(
|
||||
child: Text("Error: $_error"),
|
||||
|
||||
Reference in New Issue
Block a user