Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: get notified about cover store changes #1011

Merged
merged 5 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 5 additions & 46 deletions lib/common/view/four_images_grid.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import 'dart:typed_data';

import 'package:flutter/material.dart';

class FourImagesGrid extends StatelessWidget {
const FourImagesGrid({
super.key,
required this.images,
this.fit = BoxFit.cover,
});

final Set<Uint8List> images;
final BoxFit fit;
final List<Widget> images;

@override
Widget build(BuildContext context) {
Expand All @@ -20,16 +16,10 @@ class FourImagesGrid extends StatelessWidget {
child: Row(
children: [
Expanded(
child: _Image(
bytes: images.elementAt(0),
fit: fit,
),
child: images.elementAt(0),
),
Expanded(
child: _Image(
bytes: images.elementAt(1),
fit: fit,
),
child: images.elementAt(1),
),
],
),
Expand All @@ -38,16 +28,10 @@ class FourImagesGrid extends StatelessWidget {
child: Row(
children: [
Expanded(
child: _Image(
bytes: images.elementAt(2),
fit: fit,
),
child: images.elementAt(2),
),
Expanded(
child: _Image(
bytes: images.elementAt(3),
fit: fit,
),
child: images.elementAt(3),
),
],
),
Expand All @@ -56,28 +40,3 @@ class FourImagesGrid extends StatelessWidget {
);
}
}

class _Image extends StatelessWidget {
final Uint8List bytes;

const _Image({
required this.bytes,
required this.fit,
});

final BoxFit fit;

@override
Widget build(BuildContext context) {
const quality = FilterQuality.medium;
const height = double.infinity;
const width = double.infinity;
return Image.memory(
bytes,
height: height,
width: width,
fit: fit,
filterQuality: quality,
);
}
}
53 changes: 15 additions & 38 deletions lib/common/view/round_image_container.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:yaru/yaru.dart';

Expand All @@ -10,11 +8,11 @@ import 'theme.dart';
class RoundImageContainer extends StatelessWidget {
const RoundImageContainer({
super.key,
this.images,
required this.images,
required this.fallBackText,
});

final Set<Uint8List>? images;
final List<Widget> images;
final String fallBackText;

@override
Expand All @@ -27,52 +25,31 @@ class RoundImageContainer extends StatelessWidget {
color: theme.shadowColor.withOpacity(0.4),
);

if (images?.length == 1) {
return Container(
decoration: BoxDecoration(
image: DecorationImage(
image: MemoryImage(images!.first),
fit: BoxFit.fitHeight,
filterQuality: FilterQuality.medium,
),
boxShadow: [
boxShadow,
],
),
);
if (images.length == 1) {
return images.first;
}

if (images?.isNotEmpty == true) {
if (images!.length >= 4) {
if (images.isNotEmpty) {
if (images.length >= 4) {
return Container(
decoration: BoxDecoration(
boxShadow: [
boxShadow,
],
),
child: FourImagesGrid(
images: images!,
images: images,
),
);
} else if (images!.length >= 2) {
return Container(
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.fitHeight,
image: MemoryImage(images!.first),
} else if (images.length >= 2) {
return Stack(
children: [
images.first,
YaruClip.diagonal(
position: YaruDiagonalClip.bottomLeft,
child: images.elementAt(1),
),
boxShadow: [
boxShadow,
],
),
child: YaruClip.diagonal(
position: YaruDiagonalClip.bottomLeft,
child: Image.memory(
images!.elementAt(1),
fit: BoxFit.fitHeight,
filterQuality: FilterQuality.medium,
),
),
],
);
}
}
Expand Down
48 changes: 0 additions & 48 deletions lib/local_audio/cover_store.dart

This file was deleted.

3 changes: 3 additions & 0 deletions lib/local_audio/local_audio_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ class LocalAudioModel extends SafeChangeNotifier {
}) =>
_service.findLocalCovers(audios: audios, limit: limit);

List<Audio> findUniqueAlbumAudios(List<Audio> audios) =>
_service.findUniqueAlbumAudios(audios);

List<String>? get failedImports => _service.failedImports;

List<String>? findAllAlbums({
Expand Down
29 changes: 19 additions & 10 deletions lib/local_audio/local_audio_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import '../common/logging.dart';
import '../common/view/audio_filter.dart';
import '../extensions/media_file_x.dart';
import '../settings/settings_service.dart';
import 'cover_store.dart';
import 'local_cover_service.dart';

typedef LocalSearchResult = ({
List<Audio>? titles,
Expand All @@ -22,9 +22,13 @@ typedef LocalSearchResult = ({

class LocalAudioService {
final SettingsService? _settingsService;
final LocalCoverService _localCoverService;

LocalAudioService({SettingsService? settingsService})
: _settingsService = settingsService;
LocalAudioService({
required LocalCoverService localCoverService,
SettingsService? settingsService,
}) : _settingsService = settingsService,
_localCoverService = localCoverService;

List<Audio>? _audios;
List<Audio>? get audios => _audios;
Expand Down Expand Up @@ -161,15 +165,10 @@ class LocalAudioService {
}) {
final images = <Uint8List>{};

final albumAudios = <Audio>[];
for (var audio in audios) {
if (albumAudios.none((a) => a.album == audio.album)) {
albumAudios.add(audio);
}
}
List<Audio> albumAudios = findUniqueAlbumAudios(audios);

for (var audio in albumAudios) {
var uint8list = CoverStore().get(audio.albumId);
var uint8list = _localCoverService.get(audio.albumId);
if (uint8list != null && images.length < limit) {
images.add(uint8list);
}
Expand All @@ -178,6 +177,16 @@ class LocalAudioService {
return images;
}

List<Audio> findUniqueAlbumAudios(List<Audio> audios) {
final albumAudios = <Audio>[];
for (var audio in audios) {
if (albumAudios.none((a) => a.album == audio.album)) {
albumAudios.add(audio);
}
}
return albumAudios;
}

LocalSearchResult? search(String? query) {
if (query == null) return null;
if (query.isEmpty) {
Expand Down
31 changes: 31 additions & 0 deletions lib/local_audio/local_cover_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import 'local_cover_service.dart';
import 'dart:async';
import 'dart:typed_data';
import 'package:safe_change_notifier/safe_change_notifier.dart';

class LocalCoverModel extends SafeChangeNotifier {
LocalCoverModel({
required LocalCoverService localCoverService,
}) : _localCoverService = localCoverService;

final LocalCoverService _localCoverService;
StreamSubscription<bool>? _propertiesChangedSub;

int get storeLength => _localCoverService.storeLength;
Uint8List? get(String? albumId) => _localCoverService.get(albumId);

Future<Uint8List?> getCover({
required String albumId,
required String path,
}) async =>
_localCoverService.getCover(albumId: albumId, path: path);

void init() => _propertiesChangedSub ??=
_localCoverService.propertiesChanged.listen((_) => notifyListeners());

@override
Future<void> dispose() async {
await _propertiesChangedSub?.cancel();
super.dispose();
}
}
55 changes: 55 additions & 0 deletions lib/local_audio/local_cover_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';

import 'package:audio_metadata_reader/audio_metadata_reader.dart';
import 'package:collection/collection.dart';

import '../compute_isolate.dart';
import '../constants.dart';
import '../persistence_utils.dart';

class LocalCoverService {
final _propertiesChangedController = StreamController<bool>.broadcast();
Stream<bool> get propertiesChanged => _propertiesChangedController.stream;

var _store = <String, Uint8List?>{};
int get storeLength => _store.length;

Future<Uint8List?> getCover({
required String albumId,
required String path,
}) async {
if (albumId.isNotEmpty == true) {
final metadata = await readMetadata(File(path), getImage: true);
final cover = _put(
albumId: albumId,
data: metadata.pictures
.firstWhereOrNull((e) => e.bytes.isNotEmpty)
?.bytes,
);
if (cover != null) {
_propertiesChangedController.add(true);
}
return cover;
}
return null;
}

Uint8List? _put({required String albumId, Uint8List? data}) {
return _store.containsKey(albumId)
? _store.update(albumId, (value) => data)
: _store.putIfAbsent(albumId, () => data);
}

Uint8List? get(String? albumId) => albumId == null ? null : _store[albumId];

Future<void> write() async => writeUint8ListMap(_store, kCoverStore);

// This does not make much sense except for small libs, where the store is filled
// fast anyways. Let's keep it for eventual use...
Future<void> read() async =>
_store = await computeIsolate(() => readUint8ListMap(kCoverStore)) ?? [];

Future<void> dispose() async => _propertiesChangedController.close();
}
Loading