Created
January 11, 2022 23:41
-
-
Save etozzato/121efa9451e245f635615d634809f285 to your computer and use it in GitHub Desktop.
Asset downloader for Flutter apps
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:io'; | |
import 'dart:math'; | |
import 'package:flutter/material.dart'; | |
import 'package:download_assets/download_assets.dart'; | |
enum DownloaderOrientation { vertical, horizontal } | |
enum DownloaderStatus { ready, downloading, downloaded, error } | |
class Downloader extends StatefulWidget { | |
final DownloaderOrientation orientation; | |
final String assetURL; | |
final String assetFileName; | |
final String notDownloaded; | |
final String downloading; | |
final String downloaded; | |
final String downloadError; | |
final String downloadDirectory; | |
final String labelCancel; | |
final String labelYes; | |
final Color textColor; | |
final Color pressedTextColor; | |
final String deleteConfirmationTitle; | |
final String deleteConfirmationMessage; | |
final String downloadConfirmationTitle; | |
final String downloadConfirmationMessage; | |
const Downloader( | |
{Key key, | |
this.orientation, | |
this.assetURL, | |
this.assetFileName, | |
this.notDownloaded = "not downloaded", | |
this.downloading = "downloading PROGRESS", | |
this.downloaded = "downloaded FILESIZE", | |
this.downloadError = "download error", | |
this.downloadDirectory = "tensorflow", | |
this.labelCancel = "Cancel", | |
this.labelYes = "Yes", | |
this.deleteConfirmationTitle = "Delete", | |
this.deleteConfirmationMessage = | |
"Are you sure you want to delete this file?", | |
this.downloadConfirmationTitle = "Download", | |
this.downloadConfirmationMessage = | |
"Are you sure you want to download this file?", | |
this.textColor = Colors.grey, | |
this.pressedTextColor = Colors.deepOrangeAccent}) | |
: super(key: key); | |
@override | |
DownloaderState createState() => DownloaderState(); | |
} | |
class DownloaderState extends State<Downloader> { | |
DownloaderStatus _status = DownloaderStatus.ready; | |
double _progress = 0; | |
String _fileSize; | |
bool isPressed = false; | |
@override | |
void initState() { | |
super.initState(); | |
prepareDownload(); | |
} | |
@override | |
void dispose() { | |
super.dispose(); | |
} | |
void prepareDownload() async { | |
await DownloadAssetsController.init(directory: widget.downloadDirectory); | |
isDownloaded(); | |
} | |
Color colorForStatus() { | |
switch (_status) { | |
case DownloaderStatus.ready: | |
return Colors.blue; | |
case DownloaderStatus.downloading: | |
return Colors.orange; | |
case DownloaderStatus.downloaded: | |
return Colors.green; | |
case DownloaderStatus.error: | |
return Colors.red; | |
default: | |
return Colors.transparent; | |
} | |
} | |
String msgForStatus() { | |
switch (_status) { | |
case DownloaderStatus.ready: | |
return widget.notDownloaded; | |
case DownloaderStatus.downloading: | |
return widget.downloading | |
.replaceAll("PROGRESS", " ${_progress.toInt()}%"); | |
case DownloaderStatus.downloaded: | |
return widget.downloaded | |
.replaceAll("FILESIZE", _fileSize != null ? " $_fileSize" : ""); | |
case DownloaderStatus.error: | |
return widget.downloadError; | |
default: | |
return ""; | |
} | |
} | |
static String formatBytes(int bytes, int decimals) { | |
if (bytes <= 0) return "0 B"; | |
const suffixes = ["B", "KB", "MB", "GB"]; | |
var i = (log(bytes) / log(1024)).floor(); | |
return ((bytes / pow(1024, i)).toStringAsFixed(decimals)) + | |
' ' + | |
suffixes[i]; | |
} | |
Future<bool> isDownloaded() async { | |
bool assetsDownloaded = | |
await DownloadAssetsController.assetsFileExists(widget.assetFileName); | |
if (assetsDownloaded) { | |
final file = | |
File("${DownloadAssetsController.assetsDir}/${widget.assetFileName}"); | |
setState(() { | |
_status = DownloaderStatus.downloaded; | |
_fileSize = formatBytes(file.lengthSync(), 2); | |
}); | |
return true; | |
} else { | |
return false; | |
} | |
} | |
void deleteAssets() async { | |
await DownloadAssetsController.clearAssets(); | |
setState(() { | |
_status = DownloaderStatus.ready; | |
_fileSize = null; | |
}); | |
} | |
void downloadAsset() async { | |
final assetsDownloaded = await isDownloaded(); | |
if (assetsDownloaded) { | |
return; | |
} | |
setState(() { | |
_status = DownloaderStatus.downloading; | |
}); | |
try { | |
await DownloadAssetsController.startDownload( | |
assetsUrl: widget.assetURL, | |
onProgress: (_progressValue) { | |
setState(() { | |
_progress = _progressValue; | |
}); | |
}, | |
onComplete: () async { | |
await isDownloaded(); | |
setState(() { | |
_status = DownloaderStatus.downloaded; | |
}); | |
}, | |
onError: (exception) { | |
setState(() { | |
_status = DownloaderStatus.error; | |
}); | |
}); | |
} on DownloadAssetsException { | |
setState(() { | |
_status = DownloaderStatus.error; | |
}); | |
} | |
} | |
Future<bool> _confirmationDialog(String title, String content) async { | |
return showDialog<bool>( | |
context: context, | |
barrierDismissible: false, | |
builder: (BuildContext context) { | |
return AlertDialog( | |
title: Text(title), | |
content: SingleChildScrollView( | |
child: ListBody( | |
children: <Widget>[ | |
Text('content'), | |
], | |
), | |
), | |
actions: <Widget>[ | |
TextButton( | |
onPressed: () => Navigator.pop(context, false), | |
child: Text(widget.labelCancel), | |
), | |
TextButton( | |
onPressed: () => Navigator.pop(context, true), | |
child: Text(widget.labelYes), | |
), | |
], | |
); | |
}, | |
); | |
} | |
void onLongPress() async { | |
final assetsDownloaded = await isDownloaded(); | |
if (assetsDownloaded) { | |
final confirmed = await _confirmationDialog( | |
widget.deleteConfirmationTitle, widget.deleteConfirmationMessage); | |
if (confirmed) { | |
deleteAssets(); | |
} | |
} else { | |
final confirmed = await _confirmationDialog( | |
widget.downloadConfirmationTitle, widget.downloadConfirmationMessage); | |
if (confirmed) { | |
downloadAsset(); | |
} | |
} | |
setState(() { | |
isPressed = false; | |
}); | |
} | |
@override | |
Widget build(BuildContext context) { | |
Widget orientedWidget(List<Widget> children) { | |
return widget.orientation == DownloaderOrientation.horizontal | |
? Row(children: children, mainAxisAlignment: MainAxisAlignment.center) | |
: Column( | |
children: children, mainAxisAlignment: MainAxisAlignment.center); | |
} | |
return GestureDetector( | |
onLongPress: () { | |
onLongPress(); | |
}, | |
onLongPressDown: (_) { | |
setState(() { | |
isPressed = true; | |
}); | |
}, | |
onLongPressCancel: () { | |
setState(() { | |
isPressed = false; | |
}); | |
}, | |
child: orientedWidget( | |
[ | |
AnimatedContainer( | |
duration: const Duration(milliseconds: 750), | |
curve: Curves.easeInOutCubic, | |
decoration: BoxDecoration( | |
borderRadius: BorderRadius.circular(16), | |
color: colorForStatus(), | |
), | |
child: const SizedBox(width: 8, height: 16)), | |
const SizedBox(width: 8), | |
Text(msgForStatus(), | |
style: TextStyle( | |
color: | |
isPressed ? widget.pressedTextColor : widget.textColor)), | |
], | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment