mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 23:21:59 +01:00
v2
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:palette_generator/palette_generator.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:worker_manager/worker_manager.dart';
|
||||
|
||||
import '../cache/image_cache.dart';
|
||||
import '../models/support.dart';
|
||||
import '../services/cache_service.dart';
|
||||
import 'audio.dart';
|
||||
import 'music.dart';
|
||||
|
||||
part 'theme.g.dart';
|
||||
|
||||
const kDarkBackgroundValue = 0.28;
|
||||
const kDarkerBackgroundLightness = 0.13;
|
||||
const kOnDarkerBackgroundLightness = 0.6;
|
||||
|
||||
PaletteColor? _rankedByLuminance(
|
||||
List<PaletteColor?> colors, [
|
||||
double minLuminance = 0.0,
|
||||
double maxLuminance = 1.0,
|
||||
]) {
|
||||
for (var color in colors) {
|
||||
if (color == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final luminance = color.color.computeLuminance();
|
||||
if (luminance >= minLuminance && luminance <= maxLuminance) {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
PaletteColor? _rankedWithValue(double value, List<PaletteColor?> colors) {
|
||||
for (var color in colors) {
|
||||
if (color == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final hsv = HSVColor.fromColor(color.color);
|
||||
if (hsv.value > value) {
|
||||
return PaletteColor(hsv.withValue(value).toColor(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@riverpod
|
||||
ColorTheme _colorTheme(_ColorThemeRef ref, Palette palette) {
|
||||
final base = ref.watch(baseThemeProvider);
|
||||
|
||||
final primary = _rankedByLuminance([
|
||||
palette.dominantColor,
|
||||
palette.vibrantColor,
|
||||
palette.mutedColor,
|
||||
palette.darkVibrantColor,
|
||||
], 0.2);
|
||||
final vibrant = _rankedByLuminance([
|
||||
palette.vibrantColor,
|
||||
palette.darkVibrantColor,
|
||||
palette.dominantColor,
|
||||
], 0.05);
|
||||
final secondary = _rankedByLuminance([
|
||||
palette.lightMutedColor,
|
||||
palette.mutedColor,
|
||||
palette.darkMutedColor,
|
||||
]);
|
||||
|
||||
final background = _rankedWithValue(0.5, [
|
||||
palette.vibrantColor,
|
||||
palette.darkVibrantColor,
|
||||
palette.darkMutedColor,
|
||||
palette.dominantColor,
|
||||
palette.mutedColor,
|
||||
palette.lightVibrantColor,
|
||||
palette.lightMutedColor,
|
||||
]);
|
||||
|
||||
final colorScheme = ColorScheme.fromSeed(
|
||||
brightness: Brightness.dark,
|
||||
seedColor: background?.color ?? Colors.purple[800]!,
|
||||
background: background?.color,
|
||||
primaryContainer: primary?.color,
|
||||
onPrimaryContainer: primary?.bodyTextColor,
|
||||
secondaryContainer: secondary?.color,
|
||||
onSecondaryContainer: secondary?.bodyTextColor,
|
||||
surface: background?.color,
|
||||
surfaceTint: vibrant?.color,
|
||||
);
|
||||
|
||||
final hsv = HSVColor.fromColor(colorScheme.background);
|
||||
final hsl = HSLColor.fromColor(colorScheme.background);
|
||||
|
||||
return base.copyWith(
|
||||
theme: ThemeData(
|
||||
colorScheme: colorScheme,
|
||||
useMaterial3: base.theme.useMaterial3,
|
||||
brightness: base.theme.brightness,
|
||||
cardTheme: base.theme.cardTheme,
|
||||
),
|
||||
gradientHigh: colorScheme.background,
|
||||
darkBackground: hsv.withValue(kDarkBackgroundValue).toColor(),
|
||||
darkerBackground: hsl.withLightness(kDarkerBackgroundLightness).toColor(),
|
||||
onDarkerBackground:
|
||||
hsl.withLightness(kOnDarkerBackgroundLightness).toColor(),
|
||||
);
|
||||
}
|
||||
|
||||
@riverpod
|
||||
ColorTheme baseTheme(BaseThemeRef ref) {
|
||||
final theme = ThemeData(
|
||||
useMaterial3: true,
|
||||
colorSchemeSeed: Colors.purple[800],
|
||||
brightness: Brightness.dark,
|
||||
cardTheme: CardTheme(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final hsv = HSVColor.fromColor(theme.colorScheme.background);
|
||||
final hsl = HSLColor.fromColor(theme.colorScheme.background);
|
||||
|
||||
return ColorTheme(
|
||||
theme: theme,
|
||||
gradientHigh: theme.colorScheme.background,
|
||||
gradientLow: HSLColor.fromColor(theme.colorScheme.background)
|
||||
.withLightness(0.06)
|
||||
.toColor(),
|
||||
darkBackground: hsv.withValue(kDarkBackgroundValue).toColor(),
|
||||
darkerBackground: hsl.withLightness(kDarkerBackgroundLightness).toColor(),
|
||||
onDarkerBackground:
|
||||
hsl.withLightness(kOnDarkerBackgroundLightness).toColor(),
|
||||
);
|
||||
}
|
||||
|
||||
@riverpod
|
||||
FutureOr<Palette> albumArtPalette(
|
||||
AlbumArtPaletteRef ref,
|
||||
String id,
|
||||
) async {
|
||||
final album = await ref.watch(albumProvider(id).future);
|
||||
final cache = ref.watch(cacheServiceProvider);
|
||||
final info = cache.albumArt(album, thumbnail: true);
|
||||
|
||||
return Palette.fromPaletteGenerator(await _generateFromCache(info));
|
||||
}
|
||||
|
||||
Future<PaletteGenerator> _generateFromCache(
|
||||
UriCacheInfo cacheInfo,
|
||||
) async {
|
||||
var file = await cacheInfo.cacheManager.getSingleFile(
|
||||
cacheInfo.uri.toString(),
|
||||
key: cacheInfo.cacheKey,
|
||||
);
|
||||
|
||||
final memoryImage = ResizeImage(
|
||||
MemoryImage(await file.readAsBytes()),
|
||||
height: 32,
|
||||
width: 32,
|
||||
);
|
||||
final stream = memoryImage.resolve(const ImageConfiguration(
|
||||
devicePixelRatio: 1.0,
|
||||
));
|
||||
|
||||
Completer<ui.Image> imageCompleter = Completer<ui.Image>();
|
||||
late ImageStreamListener listener;
|
||||
listener = ImageStreamListener((ImageInfo info, bool synchronousCall) {
|
||||
stream.removeListener(listener);
|
||||
imageCompleter.complete(info.image);
|
||||
});
|
||||
|
||||
stream.addListener(listener);
|
||||
final image = await imageCompleter.future;
|
||||
final byteData = (await image.toByteData())!;
|
||||
final height = image.height;
|
||||
final width = image.width;
|
||||
|
||||
return await Executor().execute(
|
||||
arg1: byteData,
|
||||
arg2: height,
|
||||
arg3: width,
|
||||
fun3: _computePalette,
|
||||
);
|
||||
}
|
||||
|
||||
Future<PaletteGenerator> _computePalette(
|
||||
ByteData byteData,
|
||||
int height,
|
||||
int width,
|
||||
TypeSendPort port,
|
||||
) async {
|
||||
return await PaletteGenerator.fromByteData(
|
||||
EncodedImage(byteData, height: height, width: width),
|
||||
);
|
||||
}
|
||||
|
||||
@riverpod
|
||||
FutureOr<Palette> playlistArtPalette(
|
||||
PlaylistArtPaletteRef ref,
|
||||
String id,
|
||||
) async {
|
||||
final playlist = await ref.watch(playlistProvider(id).future);
|
||||
final cache = ref.watch(cacheServiceProvider);
|
||||
final info = cache.playlistArt(playlist, thumbnail: true);
|
||||
|
||||
return Palette.fromPaletteGenerator(await _generateFromCache(info));
|
||||
}
|
||||
|
||||
@riverpod
|
||||
FutureOr<Palette> mediaItemPalette(MediaItemPaletteRef ref) async {
|
||||
final item = ref.watch(mediaItemProvider).valueOrNull;
|
||||
final itemData = ref.watch(mediaItemDataProvider);
|
||||
final imageCache = ref.watch(imageCacheProvider);
|
||||
|
||||
if (item == null || item.artUri == null || itemData == null) {
|
||||
return const Palette();
|
||||
}
|
||||
|
||||
return Palette.fromPaletteGenerator(await _generateFromCache(
|
||||
UriCacheInfo(
|
||||
uri: item.artUri!,
|
||||
cacheKey: itemData.artCache!.thumbnailArtCacheKey,
|
||||
cacheManager: imageCache,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
@riverpod
|
||||
FutureOr<ColorTheme> mediaItemTheme(MediaItemThemeRef ref) async {
|
||||
final palette = await ref.watch(mediaItemPaletteProvider.future);
|
||||
return ref.watch(_colorThemeProvider(palette));
|
||||
}
|
||||
|
||||
@riverpod
|
||||
FutureOr<ColorTheme> albumArtTheme(AlbumArtThemeRef ref, String id) async {
|
||||
final palette = await ref.watch(albumArtPaletteProvider(id).future);
|
||||
return ref.watch(_colorThemeProvider(palette));
|
||||
}
|
||||
|
||||
@riverpod
|
||||
FutureOr<ColorTheme> playlistArtTheme(
|
||||
PlaylistArtThemeRef ref,
|
||||
String id,
|
||||
) async {
|
||||
final palette = await ref.watch(playlistArtPaletteProvider(id).future);
|
||||
return ref.watch(_colorThemeProvider(palette));
|
||||
}
|
||||
Reference in New Issue
Block a user