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 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 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 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 getPrimaryColor(String url) async { PaletteGenerator paletteGenerator = await getPaletteGenerator(url); return paletteGenerator.colors.first; } //Check if is dark Future 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 { ImageProvider _image = imagesDatabase.placeholderThumb; double _opacity = 0.0; bool _disposed = false; String _prevUrl; Future _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.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, ), ) ], ); } }