WT 1.1.5+2 remote images for menu

This commit is contained in:
bossanyit
2021-01-31 12:59:52 +01:00
parent 71efd3f349
commit c1a57dd10e
88 changed files with 743 additions and 316 deletions
+163
View File
@@ -0,0 +1,163 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:aitrainer_app/service/logging.dart';
import 'package:aitrainer_app/util/not_found_exception.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:network_image_to_byte/network_image_to_byte.dart';
import 'dart:collection';
class ImageCache with Logging {
static final ImageCache _singleton = ImageCache._internal();
final LinkedHashMap<String, String> _images = LinkedHashMap();
final LinkedHashMap<String, bool> _imageMap = LinkedHashMap();
var downloadSize = 0;
// Create storage
final storage = FlutterSecureStorage();
factory ImageCache() {
return _singleton;
}
ImageCache._internal();
LinkedHashMap getImageList() => _images;
void clearImageList() => _images.clear();
bool existsImageInMap(int id, String url) {
final String imageKey = generateMd5(url + "_" + id.toString());
return _imageMap[imageKey] != null && _imageMap[imageKey] == true;
}
String getImageString(int id, String url) {
final String imageKey = generateMd5(url + "_" + id.toString());
return _images[imageKey];
}
Future<void> putImageToList(int id, String url) async {
final String imageKey = generateMd5(url + "_" + id.toString());
// get from storage
final String imageString = await getImageAs64BaseString(id, url);
if (imageString != null) {
_images[imageKey] = imageString;
_imageMap[imageKey] = true;
}
/* final String imageString = await getImageAs64BaseString(id, url);
if (imageString != null) {
_imageMap[imageKey] = imageString;
_imageDown[imageKey] = true;
}
}
_images[imageKey] = imageString;
_imageMap[imageKey] = true; */
}
Future<void> saveImageToPrefs(String key, String value) async {
//log(" save the key " + key);
await storage.write(key: key, value: value);
return;
}
Future<void> emptyPrefs() async {
await storage.deleteAll();
return;
}
Future<String> loadImageFromPrefs(String key) async {
String value = await storage.read(key: key);
return value;
}
Future<bool> existImageInPrefs(String key) async {
return await storage.containsKey(key: key);
}
// encodes bytes list as string
static String base64String(Uint8List data) {
return base64Encode(data);
}
// decode bytes from a string
Image imageFrom64BaseString(String base64String) {
if (base64String == null) {
return null;
}
return Image.memory(
base64Decode(base64String),
fit: BoxFit.fill,
);
}
Future<Image> getImage(int id, String name) async {
if (storage == null) {
return null;
}
if (name == null || name.length == 0) {
return null;
}
final String imageKey = generateMd5(name + "_" + id.toString());
final String imageString = await storage.read(key: imageKey);
final Image image = imageFrom64BaseString(imageString);
return image;
}
Future<String> getImageAs64BaseString(int id, String name) async {
if (name == null) {
return null;
}
final String imageKey = generateMd5(name + "_" + id.toString());
String imageString;
if (await storage.containsKey(key: imageKey)) {
//log(" .. get from storage");
imageString = await storage.read(key: imageKey);
} else {
imageString = await downloadAndSaveImage(id, name);
//log(" .. downloaded");
}
return imageString;
}
Future<String> downloadAndSaveImage(int id, String url) async {
final String imageKey = generateMd5(url + "_" + id.toString());
if (!await existImageInPrefs(imageKey)) {
try {
if (url.contains("http")) {
log(" ... direct download " + url);
Uint8List byteImage = await networkImageToByte(url);
this.downloadSize += byteImage.length;
final String imageAsString = base64String(byteImage);
ImageCache().saveImageToPrefs(imageKey, imageAsString);
return imageAsString;
}
} on NotFoundException catch (_) {
print(url + " not found");
} on Exception catch (e) {
print(e);
}
} else {
//log(" .. from storage");
final String storageString = await storage.read(key: imageKey);
if (storageString != null) {
//log(" .. storage String: " + storageString);
} else {
// log(" .. storage String is NULL");
}
}
return null;
}
String generateMd5(String input) {
String converted = input.replaceAll(RegExp(r'\.'), 'a');
converted = converted.replaceAll(RegExp(r'\/'), 'b');
converted = converted.replaceAll(RegExp(r':'), 'c');
//print("key: " + converted);
return converted;
}
}
+93 -26
View File
@@ -1,11 +1,30 @@
/// Tree view widget library
library tree_view;
import 'dart:async';
import 'package:aitrainer_app/util/common.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
class TreeViewStream {
static final TreeViewStream _singleton = TreeViewStream._internal();
final StreamController<bool> streamController = StreamController<bool>.broadcast();
double positionY = 0;
Stream get stream => streamController.stream;
StreamController getStreamController() => streamController;
factory TreeViewStream() => _singleton;
TreeViewStream._internal();
void dispose() {
streamController.close();
}
}
class TreeView extends InheritedWidget {
final List<Widget> children;
final bool startExpanded;
@@ -24,33 +43,77 @@ class TreeView extends InheritedWidget {
);
static TreeView of(BuildContext context) {
//return context.inheritFromWidgetOfExactType(TreeView);
return context.dependOnInheritedWidgetOfExactType(aspect: TreeView);
}
@override
bool updateShouldNotify(TreeView oldWidget) {
if (oldWidget.children == this.children &&
oldWidget.startExpanded == this.startExpanded) {
if (oldWidget.children == this.children && oldWidget.startExpanded == this.startExpanded) {
return false;
}
return true;
}
}
class _TreeViewData extends StatelessWidget {
class _TreeViewData extends StatefulWidget {
final List<Widget> children;
_TreeViewData({
this.children,
});
@override
__TreeViewDataState createState() => __TreeViewDataState();
}
class __TreeViewDataState extends State<_TreeViewData> {
final ScrollController _controller = ScrollController();
final Stream stream = TreeViewStream().stream;
var subscription;
@override
void initState() {
super.initState();
/// We require the initializers to run after the loading screen is rendered
SchedulerBinding.instance.addPostFrameCallback((_) {
final double cHeight = MediaQuery.of(context).size.height;
subscription = stream.listen((value) {
if (value) {
final double positionY = TreeViewStream().positionY;
print("pos " +
positionY.toString() +
" height: " +
cHeight.toString() +
" controller offset " +
_controller.offset.toString() +
" controller initial " +
_controller.initialScrollOffset.toString());
if (positionY > cHeight - 190) {
final double offset = positionY + 40;
print("antimateTo " + offset.toString());
_controller.animateTo(offset, duration: Duration(milliseconds: 300), curve: Curves.easeIn);
}
}
});
});
}
@override
void dispose() {
_controller.dispose();
subscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: children.length,
scrollDirection: Axis.vertical,
controller: _controller,
itemCount: widget.children.length,
itemBuilder: (context, index) {
return children.elementAt(index);
return widget.children.elementAt(index);
},
);
}
@@ -92,14 +155,9 @@ class TreeViewChild extends StatefulWidget {
}
}
class TreeViewChildState extends State<TreeViewChild> with Common, SingleTickerProviderStateMixin {
class TreeViewChildState extends State<TreeViewChild> with Common {
bool isExpanded;
final GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
Color _color;
double _opacity = 0;
AnimationController _controller;
Animation<double> sizeAnimation;
@override
void initState() {
@@ -115,7 +173,8 @@ class TreeViewChildState extends State<TreeViewChild> with Common, SingleTicker
@override
Widget build(BuildContext context) {
return Column(
return (Column(
key: listKey,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
GestureDetector(
@@ -124,27 +183,35 @@ class TreeViewChildState extends State<TreeViewChild> with Common, SingleTicker
),
Flexible(
child: Container(
child:
AnimatedSwitcher(
duration: Duration(milliseconds:200),
reverseDuration: Duration(milliseconds:200),
switchInCurve: Curves.easeIn,
child: isExpanded ? Column(
mainAxisSize: MainAxisSize.min,
children: widget.children,
) : Offstage(),
),
child: AnimatedSwitcher(
duration: Duration(milliseconds: 200),
reverseDuration: Duration(milliseconds: 200),
switchInCurve: Curves.easeIn,
child: isExpanded
? Column(
mainAxisSize: MainAxisSize.min,
children: widget.children,
)
: Offstage(),
),
),
)
],
);
));
}
void toggleExpanded() {
setState(() {
this.isExpanded = !this.isExpanded;
_color = isExpanded ? Colors.black12 : Colors.transparent;
_opacity = isExpanded ? 1 : 0;
TreeViewStream().positionY = getPosition();
TreeViewStream().getStreamController().add(this.isExpanded);
});
}
double getPosition() {
RenderBox box = listKey.currentContext.findRenderObject();
Offset position = box.localToGlobal(Offset.zero); //this is global position
double y = position.dy;
return y;
}
}