freezer/lib/ui/cached_image.dart

210 lines
5.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:palette_generator/palette_generator.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'package:crypto/crypto.dart';
import 'package:dio/dio.dart';
import 'dart:io';
import 'dart:convert';
ImagesDatabase imagesDatabase = ImagesDatabase();
class ImagesDatabase {
/*
images.db:
Table: images
Fields:
id - id
name - md5 hash of url. also filename
url - url
permanent - 0/1 - if image is cached or offline
*/
Database db;
String imagesPath;
ImageProvider placeholderThumb = new AssetImage('assets/cover_thumb.jpg');
//Prepare database
Future init() async {
String dir = await getDatabasesPath();
String path = p.join(dir, 'images.db');
db = await openDatabase(
path,
version: 1,
singleInstance: false,
onCreate: (Database db, int version) async {
//Create table on db created
await db.execute('CREATE TABLE images (id INTEGER PRIMARY KEY, name TEXT, url TEXT, permanent INTEGER)');
}
);
//Prepare folders
imagesPath = p.join((await getApplicationDocumentsDirectory()).path, 'images/');
Directory imagesDir = Directory(imagesPath);
await imagesDir.create(recursive: true);
}
String getPath(String name) {
return p.join(imagesPath, name);
}
//Get image url/path, cache it
Future<String> getImage(String url, {bool permanent = false}) async {
//Already file
if (!url.startsWith('http')) {
url = url.replaceFirst('file://', '');
if (!permanent) return url;
//Update in db to permanent
String name = p.basenameWithoutExtension(url);
await db.rawUpdate('UPDATE images SET permanent == 1 WHERE name == ?', [name]);
}
//Filename = md5 hash
String hash = md5.convert(utf8.encode(url)).toString();
List<Map> results = await db.rawQuery('SELECT * FROM images WHERE name == ?', [hash]);
String path = getPath(hash);
if (results.length > 0) {
//Image in database
return path;
}
//Save image
Dio dio = Dio();
try {
await dio.download(url, path);
await db.insert('images', {'url': url, 'name': hash, 'permanent': permanent?1:0});
return path;
} catch (e) {
return null;
}
}
Future<PaletteGenerator> getPaletteGenerator(String url) async {
String path = await getImage(url);
//Get image provider
ImageProvider provider = placeholderThumb;
if (path != null) {
provider = FileImage(File(path));
}
PaletteGenerator paletteGenerator = await PaletteGenerator.fromImageProvider(provider);
return paletteGenerator;
}
//Get primary color from album art
Future<Color> getPrimaryColor(String url) async {
PaletteGenerator paletteGenerator = await getPaletteGenerator(url);
return paletteGenerator.colors.first;
}
//Check if is dark
Future<bool> isDark(String url) async {
PaletteGenerator paletteGenerator = await getPaletteGenerator(url);
return paletteGenerator.colors.first.computeLuminance() > 0.5 ? false : true;
}
}
class CachedImage extends StatefulWidget {
final String url;
final double width;
final double height;
final bool circular;
const CachedImage({Key key, this.url, this.height, this.width, this.circular = false}): super(key: key);
@override
_CachedImageState createState() => _CachedImageState();
}
class _CachedImageState extends State<CachedImage> {
ImageProvider _image = imagesDatabase.placeholderThumb;
double _opacity = 0.0;
bool _disposed = false;
String _prevUrl;
Future<ImageProvider> _getImage() async {
//Image already path
if (!widget.url.startsWith('http')) {
//Remove file://, if used in audio_service
if (widget.url.startsWith('/')) return FileImage(File(widget.url));
return FileImage(File(widget.url.replaceFirst('file://', '')));
}
//Load image from db
String path = await imagesDatabase.getImage(widget.url);
if (path == null) return imagesDatabase.placeholderThumb;
return FileImage(File(path));
}
//Load image and fade
void _load() async {
if (_prevUrl == widget.url) return;
ImageProvider image = await _getImage();
if (_disposed) return;
setState(() {
_image = image;
_opacity = 1.0;
});
_prevUrl = widget.url;
}
@override
void dispose() {
_disposed = true;
super.dispose();
}
@override
void initState() {
_load();
super.initState();
}
@override
void didUpdateWidget(CachedImage oldWidget) {
_load();
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
widget.circular ?
CircleAvatar(
radius: (widget.width??widget.height),
backgroundImage: imagesDatabase.placeholderThumb,
):
Image(
image: imagesDatabase.placeholderThumb,
height: widget.height,
width: widget.width,
),
AnimatedOpacity(
duration: Duration(milliseconds: 250),
opacity: _opacity,
child: widget.circular ?
CircleAvatar(
radius: (widget.width??widget.height),
backgroundImage: _image,
):
Image(
image: _image,
height: widget.height,
width: widget.width,
),
)
],
);
}
}