Add Supa account and search changes

This commit is contained in:
Fabian Freund
2026-05-22 18:10:22 +02:00
parent 3a19865b2e
commit 51289f1266
374 changed files with 54061 additions and 5013 deletions
@@ -17,15 +17,19 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'dart:ui';
import 'package:fast_equatable/hash.dart';
import 'package:flutter_svg/flutter_svg.dart' as svg;
import 'package:weblibre/core/logger.dart';
import 'package:weblibre/domain/entities/equatable_image.dart';
import 'package:weblibre/utils/lru_cache.dart';
final _cache = LRUCache<int, EquatableImage>(100);
const _defaultSvgIconSize = 32;
/// Clears the global image decode cache.
void clearImageCache() {
@@ -63,8 +67,141 @@ Future<EquatableImage?> tryDecodeImage(
return image;
}
} catch (e, s) {
if (_isLikelySvg(bytes)) {
try {
final svgImage = await _tryDecodeSvg(
bytes,
hash: digest,
targetWidth: targetWidth,
targetHeight: targetHeight,
);
if (svgImage != null) {
_cache.set(digest, svgImage);
return svgImage;
}
} catch (svgError, svgStackTrace) {
logger.w(
'Failed to rasterize SVG image',
error: svgError,
stackTrace: svgStackTrace,
);
}
}
logger.w('Failed to decode image', error: e, stackTrace: s);
}
return null;
}
bool _isLikelySvg(Uint8List bytes) {
final prefix = utf8
.decode(bytes.sublist(0, min(bytes.length, 512)), allowMalformed: true)
.trimLeft();
final normalized = prefix.isNotEmpty && prefix.codeUnitAt(0) == 0xFEFF
? prefix.substring(1)
: prefix;
final lower = normalized.toLowerCase();
return lower.contains('<svg') ||
lower.contains('<!doctype svg') ||
(lower.contains('<?xml') && lower.contains('<svg'));
}
Future<EquatableImage?> _tryDecodeSvg(
Uint8List bytes, {
required int hash,
int? targetWidth,
int? targetHeight,
}) async {
final pictureInfo = await svg.vg.loadPicture(svg.SvgBytesLoader(bytes), null);
try {
final dimensions = _resolveSvgRasterDimensions(
pictureInfo.size,
targetWidth: targetWidth,
targetHeight: targetHeight,
);
final sourceWidth = pictureInfo.size.width > 0
? pictureInfo.size.width
: dimensions.width.toDouble();
final sourceHeight = pictureInfo.size.height > 0
? pictureInfo.size.height
: dimensions.height.toDouble();
final recorder = PictureRecorder();
final canvas = Canvas(recorder);
canvas.clipRect(
Rect.fromLTWH(
0,
0,
dimensions.width.toDouble(),
dimensions.height.toDouble(),
),
);
canvas.scale(
dimensions.width / sourceWidth,
dimensions.height / sourceHeight,
);
canvas.drawPicture(pictureInfo.picture);
final scaledPicture = recorder.endRecording();
try {
final rasterized = await scaledPicture.toImage(
dimensions.width,
dimensions.height,
);
return EquatableImage(rasterized, hash: hash);
} finally {
scaledPicture.dispose();
}
} finally {
pictureInfo.picture.dispose();
}
}
({int width, int height}) _resolveSvgRasterDimensions(
Size sourceSize, {
int? targetWidth,
int? targetHeight,
}) {
if (targetWidth != null && targetHeight != null) {
return (width: max(1, targetWidth), height: max(1, targetHeight));
}
final fallbackSize = targetWidth ?? targetHeight ?? _defaultSvgIconSize;
final sourceWidth = sourceSize.width > 0
? sourceSize.width
: fallbackSize.toDouble();
final sourceHeight = sourceSize.height > 0
? sourceSize.height
: fallbackSize.toDouble();
if (targetWidth != null) {
return (
width: max(1, targetWidth),
height: max(1, (targetWidth * sourceHeight / sourceWidth).round()),
);
}
if (targetHeight != null) {
return (
width: max(1, (targetHeight * sourceWidth / sourceHeight).round()),
height: max(1, targetHeight),
);
}
if (sourceWidth >= sourceHeight) {
return (
width: fallbackSize,
height: max(1, (fallbackSize * sourceHeight / sourceWidth).round()),
);
}
return (
width: max(1, (fallbackSize * sourceWidth / sourceHeight).round()),
height: fallbackSize,
);
}