Files
xtremobile/lib/core/utils/image_cache_config.dart
T
MichaelandClaude Opus 5 694b1257b9 fix(theme): remove every hardcoded Apple/Material colour left in the UI
Porting the palette was not enough: the Warm Cinema charter only reached the
widgets that read from AppColors. Nineteen screen and widget files still painted
Apple TV values inline, so the app kept showing blue — most visibly on the
selected navigation pill.

Swept out:
- 0xFF0A84FF (Apple blue) on the selected nav pill, category card icons and the
  "add playlist" button
- 0xFF4FC3F7 and 0xFF1565C0 in the player overlays
- Colors.blueAccent / Colors.blue in dialogs
- Apple greys 1C1C1E / 2C2C2E / 3A3A3C / 8E8E93 -> warm charcoals
- 128 Colors.white* -> the onSurfaceNN cream ladder (a pure white on a warm
  ground reads blue, which is the charter's whole point)
- Colors.red / green / amber / yellow / grey* -> error, success, ratingGold,
  outlineVariant
- pure black ink on light fills -> onPrimaryFixed, a warm near-black
- the white focus glow -> an ember glow

Deliberately left true black: the video surface backgrounds and the scrims over
posters and video, where a warm tint would clash with the picture.

Adds the onPrimaryFixed / primaryFixed / onSurface70 tokens the sweep needed.

Verified: no Material or Apple colour literal remains outside lib/core/theme;
flutter analyze reports no errors; 19/19 tests pass; release APK builds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 20:39:58 +02:00

188 lines
5.0 KiB
Dart

/// [P1-2 FIX] Standardized image cache configuration
/// Prevents memory bloat from full-resolution images displayed at small sizes
///
/// Problem: CachedNetworkImage downloads full-resolution images (1000x1500px)
/// but displays them at 40-100px sizes, wasting memory and disk space.
///
/// Solution: Specify memCacheWidth/Height for display size, limiting disk cache.
library;
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:xtremobile/core/theme/app_colors.dart';
/// Standard cache manager with 200MB size limit
class AppCacheManager {
static const int _cacheSizeInBytes = 200 * 1024 * 1024; // 200 MB
static final CacheManager instance = CacheManager(
Config(
'xtremflow_image_cache',
stalePeriod: const Duration(days: 7),
maxNrOfCacheObjects: 500,
),
);
}
/// Image cache configuration presets for common use cases
class ImageCacheConfigs {
/// Small channel icons (40x40 display size)
static const ImageCacheConfig channelIcon = ImageCacheConfig(
displayWidth: 40,
displayHeight: 40,
cacheWidth: 50, // Slight buffer for upscaling
cacheHeight: 50,
);
/// Medium poster images (100x150 display size)
static const ImageCacheConfig posterThumbnail = ImageCacheConfig(
displayWidth: 100,
displayHeight: 150,
cacheWidth: 120,
cacheHeight: 180,
);
/// Large poster images (200x300 display size)
static const ImageCacheConfig posterLarge = ImageCacheConfig(
displayWidth: 200,
displayHeight: 300,
cacheWidth: 240,
cacheHeight: 360,
);
/// Hero carousel images (full width, ~500px)
static const ImageCacheConfig heroCarousel = ImageCacheConfig(
displayWidth: 500,
displayHeight: 300,
cacheWidth: 600,
cacheHeight: 360,
);
/// Series detail cover (400x600 display size)
static const ImageCacheConfig seriesCover = ImageCacheConfig(
displayWidth: 400,
displayHeight: 600,
cacheWidth: 480,
cacheHeight: 720,
);
}
/// Configuration for a single CachedNetworkImage
class ImageCacheConfig {
final int displayWidth;
final int displayHeight;
final int cacheWidth;
final int cacheHeight;
const ImageCacheConfig({
required this.displayWidth,
required this.displayHeight,
required this.cacheWidth,
required this.cacheHeight,
});
/// Get a CachedNetworkImage builder function
/// Usage: imageUrl.isEmpty ? placeholder : getCachedImage(imageUrl, config)
CachedNetworkImage buildImage({
required String imageUrl,
double? width,
double? height,
BoxFit fit = BoxFit.cover,
}) {
return CachedNetworkImage(
imageUrl: imageUrl,
width: width,
height: height,
fit: fit,
memCacheWidth: cacheWidth,
memCacheHeight: cacheHeight,
maxHeightDiskCache: cacheHeight,
maxWidthDiskCache: cacheWidth,
cacheManager: AppCacheManager.instance,
placeholder: (context, url) => Container(
width: width,
height: height,
decoration: BoxDecoration(
color: AppColors.surfaceContainer,
borderRadius: BorderRadius.circular(8),
),
),
errorWidget: (context, url, error) => Container(
width: width,
height: height,
decoration: BoxDecoration(
color: AppColors.surfaceContainerHigh,
borderRadius: BorderRadius.circular(8),
),
child: const Icon(Icons.image_not_supported, color: AppColors.textTertiary),
),
);
}
}
/// Helper extension for quick image caching
extension ImageCacheHelper on String {
/// Load as channel icon with standard sizing
CachedNetworkImage asChannelIcon({double? size}) {
return ImageCacheConfigs.channelIcon.buildImage(
imageUrl: this,
width: size ?? 40,
height: size ?? 40,
fit: BoxFit.cover,
);
}
/// Load as poster thumbnail
CachedNetworkImage asPosterThumbnail({
double width = 100,
double height = 150,
}) {
return ImageCacheConfigs.posterThumbnail.buildImage(
imageUrl: this,
width: width,
height: height,
fit: BoxFit.cover,
);
}
/// Load as large poster
CachedNetworkImage asPosterLarge({
double width = 200,
double height = 300,
}) {
return ImageCacheConfigs.posterLarge.buildImage(
imageUrl: this,
width: width,
height: height,
fit: BoxFit.cover,
);
}
/// Load as hero carousel image
CachedNetworkImage asHeroImage({
double width = 500,
double height = 300,
}) {
return ImageCacheConfigs.heroCarousel.buildImage(
imageUrl: this,
width: width,
height: height,
fit: BoxFit.cover,
);
}
/// Load as series cover
CachedNetworkImage asSeriesCover({
double width = 400,
double height = 600,
}) {
return ImageCacheConfigs.seriesCover.buildImage(
imageUrl: this,
width: width,
height: height,
fit: BoxFit.cover,
);
}
}