WT 1.1.5+2 remote images for menu
This commit is contained in:
@@ -4,6 +4,8 @@ import 'package:aitrainer_app/bloc/account/account_bloc.dart';
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/repository/customer_repository.dart';
|
||||
import 'package:aitrainer_app/repository/user_repository.dart';
|
||||
import 'package:aitrainer_app/service/exercise_tree_service.dart';
|
||||
import 'package:aitrainer_app/service/exercisetype_service.dart';
|
||||
import 'package:aitrainer_app/util/common.dart';
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
@@ -22,7 +24,12 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> with Trans {
|
||||
final bool isRegistration;
|
||||
bool dataPolicyAllowed = false;
|
||||
bool obscure = true;
|
||||
LoginBloc({this.accountBloc, this.userRepository, this.context, this.isRegistration}) : super(LoginInitial());
|
||||
LoginBloc({this.accountBloc, this.userRepository, this.context, this.isRegistration}) : super(LoginInitial()) {
|
||||
if (isRegistration) {
|
||||
ExerciseTreeApi().getExerciseTree();
|
||||
ExerciseTypeApi().getExerciseTypes();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<LoginState> mapEventToState(
|
||||
@@ -119,7 +126,9 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> with Trans {
|
||||
this.dataPolicyAllowed = !dataPolicyAllowed;
|
||||
yield LoginReady();
|
||||
} else if (event is LoginPasswordChangeObscure) {
|
||||
yield LoginLoading();
|
||||
this.obscure = !this.obscure;
|
||||
yield LoginReady();
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
yield LoginError(message: e.toString());
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:aitrainer_app/library/image_cache.dart' as wt;
|
||||
|
||||
part 'menu_event.dart';
|
||||
part 'menu_state.dart';
|
||||
@@ -21,9 +22,9 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> with Trans, Logging {
|
||||
final WorkoutTreeRepository menuTreeRepository;
|
||||
final ExerciseRepository exerciseRepository = ExerciseRepository();
|
||||
ExerciseDeviceRepository exerciseDeviceRepository = ExerciseDeviceRepository();
|
||||
int parent;
|
||||
int parent = 0;
|
||||
WorkoutMenuTree workoutItem;
|
||||
List<int> listFilterDevice = List();
|
||||
final List<int> listFilterDevice = List();
|
||||
|
||||
String infoTitle = "";
|
||||
String infoText = "";
|
||||
@@ -95,25 +96,27 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> with Trans, Logging {
|
||||
if (event is MenuCreate) {
|
||||
yield MenuLoading();
|
||||
//await menuTreeRepository.createTree();
|
||||
//menuTreeRepository.getBranch(this.parent);
|
||||
setMenuInfo();
|
||||
exerciseDeviceRepository.setDevices(Cache().getDevices());
|
||||
/* exerciseDeviceRepository.getGymDevices().forEach((element) {
|
||||
listFilterDevice.add(element.exerciseDeviceId);
|
||||
}); */
|
||||
yield MenuReady();
|
||||
} else if (event is MenuRecreateTree) {
|
||||
yield MenuLoading();
|
||||
// ie. at language changes
|
||||
await menuTreeRepository.createTree();
|
||||
yield MenuReady();
|
||||
} else if (event is MenuTreeDown) {
|
||||
// get child menus or exercises
|
||||
yield MenuLoading();
|
||||
parent = event.parent;
|
||||
workoutItem = event.item;
|
||||
|
||||
if (workoutItem != null) {
|
||||
setAbility(workoutItem.nameEnglish);
|
||||
}
|
||||
menuTreeRepository.getBranch(event.parent);
|
||||
final LinkedHashMap<String, WorkoutMenuTree> branch = menuTreeRepository.getBranch(event.parent);
|
||||
|
||||
await getImages(branch);
|
||||
//await Future.delayed(Duration(seconds: 2));
|
||||
yield MenuReady();
|
||||
} else if (event is MenuTreeUp) {
|
||||
yield MenuLoading();
|
||||
@@ -121,10 +124,13 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> with Trans, Logging {
|
||||
parent = event.parent;
|
||||
workoutItem = menuTreeRepository.getParentItem(parent);
|
||||
|
||||
LinkedHashMap<String, WorkoutMenuTree> branch;
|
||||
if (workoutItem != null) {
|
||||
menuTreeRepository.getBranch(workoutItem.parent);
|
||||
setAbility(workoutItem.nameEnglish);
|
||||
branch = menuTreeRepository.getBranch(workoutItem.parent);
|
||||
await getImages(branch);
|
||||
}
|
||||
|
||||
yield MenuReady();
|
||||
} else if (event is MenuTreeJump) {
|
||||
yield MenuLoading();
|
||||
@@ -132,9 +138,12 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> with Trans, Logging {
|
||||
workoutItem = menuTreeRepository.getParentItem(parent);
|
||||
|
||||
if (workoutItem != null) {
|
||||
menuTreeRepository.getBranch(workoutItem.parent);
|
||||
setAbility(workoutItem.nameEnglish);
|
||||
}
|
||||
final LinkedHashMap<String, WorkoutMenuTree> branch = menuTreeRepository.getBranch(workoutItem.parent);
|
||||
if (branch != null) {
|
||||
await getImages(branch);
|
||||
}
|
||||
yield MenuReady();
|
||||
} else if (event is MenuClickExercise) {
|
||||
yield MenuLoading();
|
||||
@@ -171,6 +180,29 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> with Trans, Logging {
|
||||
log("Ability: " + ability.toString() + " name:" + name);
|
||||
}
|
||||
|
||||
Future<void> getImages(LinkedHashMap<String, WorkoutMenuTree> branch) async {
|
||||
wt.ImageCache().clearImageList();
|
||||
await putBranchImageToHash(branch);
|
||||
log("downloaded image size in KB: " + wt.ImageCache().downloadSize.toString());
|
||||
}
|
||||
|
||||
Future<void> putBranchImageToHash(LinkedHashMap<String, WorkoutMenuTree> branch) async {
|
||||
await Future.forEach(branch.keys, (key) async {
|
||||
final WorkoutMenuTree value = branch[key];
|
||||
if (!value.imageName.contains("asset")) {
|
||||
await wt.ImageCache().putImageToList(value.id, value.imageName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
String getImage(int id, String name) {
|
||||
String imageString;
|
||||
if (name.contains("http")) {
|
||||
imageString = wt.ImageCache().getImageString(id, name);
|
||||
}
|
||||
return imageString;
|
||||
}
|
||||
|
||||
bool selectedDevice(int deviceId) {
|
||||
return !listFilterDevice.contains(deviceId);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import 'dart:async';
|
||||
|
||||
import 'package:aitrainer_app/bloc/settings/settings_bloc.dart';
|
||||
import 'package:aitrainer_app/localization/app_language.dart';
|
||||
import 'package:aitrainer_app/service/exercise_tree_service.dart';
|
||||
import 'package:aitrainer_app/service/exercisetype_service.dart';
|
||||
import 'package:aitrainer_app/service/logging.dart';
|
||||
import 'package:aitrainer_app/util/session.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
@@ -41,7 +43,6 @@ class SessionBloc extends Bloc<SessionEvent, SessionState> with Logging {
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await this.close();
|
||||
//PlatformPurchaseApi().close();
|
||||
super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,8 +439,14 @@ class Cache with Logging {
|
||||
Flurry.setUserId(customerId.toString());
|
||||
final customerDevices = await CustomerExerciseDeviceApi().getDevices(customerId);
|
||||
Cache().setCustomerDevices(customerDevices);
|
||||
await ExerciseTypeApi().getExerciseTypes();
|
||||
await ExerciseTreeApi().getExerciseTree();
|
||||
|
||||
if (this._exerciseTree == null) {
|
||||
await ExerciseTreeApi().getExerciseTree();
|
||||
}
|
||||
if (this._exerciseTypes == null) {
|
||||
await ExerciseTypeApi().getExerciseTypes();
|
||||
}
|
||||
|
||||
await ExerciseDeviceApi().getDevices();
|
||||
|
||||
ExerciseRepository exerciseRepository = ExerciseRepository();
|
||||
|
||||
@@ -11,7 +11,7 @@ class ExerciseTree {
|
||||
ExerciseTree.fromJson(Map json) {
|
||||
this.treeId = json['treeId'];
|
||||
this.name = json['name'];
|
||||
this.parentId = -1;
|
||||
this.parentId = 0;
|
||||
this.imageUrl = json['imageUrl'];
|
||||
this.active = json['active'];
|
||||
this.nameTranslation = json['translations'] != null && (json['translations']).length > 0 ? json['translations'][0]['name'] : this.name;
|
||||
|
||||
@@ -62,7 +62,11 @@ class UserRepository with Logging {
|
||||
} else if (e.code == 'weak-password') {
|
||||
log('The password provided is too weak.');
|
||||
throw Exception("Password too short");
|
||||
} else if (e.code == 'account-exists-with-different-credential') {
|
||||
log(e.code);
|
||||
throw Exception("The account exists with different credential");
|
||||
} else {
|
||||
print(e.code);
|
||||
throw Exception(e);
|
||||
}
|
||||
} on WorkoutTestException catch (ex) {
|
||||
|
||||
@@ -47,22 +47,12 @@ class WorkoutTreeRepository with Logging {
|
||||
Antagonist.calf: Antagonist.calfNr
|
||||
};
|
||||
|
||||
/* Future<String> _buildImage(String imageUrl) async {
|
||||
String assetImage = 'asset/menu/' + imageUrl.substring(7);
|
||||
//print("Loading image " + assetImage);
|
||||
return rootBundle.load(assetImage).then((value) {
|
||||
return assetImage;
|
||||
}).catchError((_) {
|
||||
String imagePath = assetImage.substring(10);
|
||||
String url = Cache.mediaUrl + 'images' + imagePath;
|
||||
//print("Exception: " + assetImage + " will be loaded from the network " + url);
|
||||
return url;
|
||||
});
|
||||
} */
|
||||
|
||||
Future<void> createTree() async {
|
||||
isEnglish = AppLanguage().appLocal == Locale('en');
|
||||
log("** Start creating tree on lang: " + AppLanguage().appLocal.languageCode);
|
||||
log("** Start creating tree on lang: " +
|
||||
AppLanguage().appLocal.languageCode +
|
||||
" tree length: " +
|
||||
Cache().getExerciseTree().length.toString());
|
||||
|
||||
List<ExerciseTree> exerciseTree = Cache().getExerciseTree();
|
||||
if (exerciseTree == null || exerciseTree.length == 0) {
|
||||
|
||||
@@ -3,10 +3,11 @@ import 'dart:convert';
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/model/exercise_tree.dart';
|
||||
import 'package:aitrainer_app/model/exercise_tree_parents.dart';
|
||||
import 'package:aitrainer_app/service/logging.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'api.dart';
|
||||
|
||||
class ExerciseTreeApi {
|
||||
class ExerciseTreeApi with Logging {
|
||||
final APIClient _client = new APIClient();
|
||||
|
||||
Future<List<ExerciseTree>> getExerciseTree() async {
|
||||
@@ -17,23 +18,24 @@ class ExerciseTreeApi {
|
||||
exerciseTree = await getExerciseTreeParents(exerciseTree);
|
||||
|
||||
if (exerciseTree != null) {
|
||||
exerciseTree.forEach((element) async {
|
||||
element.imageUrl = await _buildImage(element.imageUrl);
|
||||
await Future.forEach(exerciseTree, (element) async {
|
||||
//exerciseTree.forEach((element) async {
|
||||
element.imageUrl = await _buildImage(element.imageUrl, element.treeId);
|
||||
});
|
||||
log("ExerciseTree downloaded");
|
||||
Cache().setExerciseTree(exerciseTree);
|
||||
}
|
||||
Cache().setExerciseTree(exerciseTree);
|
||||
|
||||
return exerciseTree;
|
||||
}
|
||||
|
||||
Future<String> _buildImage(String imageUrl) async {
|
||||
Future<String> _buildImage(String imageUrl, int treeId) async {
|
||||
String assetImage = 'asset/menu/' + imageUrl.substring(7);
|
||||
//print("Loading image " + assetImage);
|
||||
return rootBundle.load(assetImage).then((value) {
|
||||
return await rootBundle.load(assetImage).then((value) {
|
||||
return assetImage;
|
||||
}).catchError((_) {
|
||||
String imagePath = assetImage.substring(10);
|
||||
String url = Cache.mediaUrl + 'images' + imagePath;
|
||||
//print("Exception: " + assetImage + " will be loaded from the network " + url);
|
||||
return url;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'package:aitrainer_app/library/image_cache.dart';
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/model/exercise_type.dart';
|
||||
import 'package:aitrainer_app/service/api.dart';
|
||||
@@ -14,14 +15,17 @@ class ExerciseTypeApi with Logging {
|
||||
final List<ExerciseType> exerciseTypes = json.map((exerciseType) => ExerciseType.fromJson(exerciseType)).toList();
|
||||
if (exerciseTypes != null) {
|
||||
exerciseTypes.forEach((element) async {
|
||||
element.imageUrl = await _buildImage(element.imageUrl);
|
||||
element.imageUrl = await _buildImage(element.imageUrl, element.exerciseTypeId);
|
||||
//ImageCache().downloadAndSaveImage(element.exerciseTypeId, element.imageUrl);
|
||||
});
|
||||
log("ExerciseTypes downloaded");
|
||||
Cache().setExerciseTypes(exerciseTypes);
|
||||
}
|
||||
Cache().setExerciseTypes(exerciseTypes);
|
||||
|
||||
return exerciseTypes;
|
||||
}
|
||||
|
||||
Future<String> _buildImage(String imageUrl) async {
|
||||
Future<String> _buildImage(String imageUrl, int exerciseTypeId) async {
|
||||
if (imageUrl.length > 8) {
|
||||
String assetImage = 'asset/menu/' + imageUrl.substring(7);
|
||||
return rootBundle.load(assetImage).then((value) {
|
||||
@@ -29,7 +33,6 @@ class ExerciseTypeApi with Logging {
|
||||
}).catchError((_) {
|
||||
String imagePath = assetImage.substring(10);
|
||||
String url = Cache.mediaUrl + 'images' + imagePath;
|
||||
//print("Exception: " + assetImage + " will be loaded from the network " + url);
|
||||
return url;
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -267,7 +267,7 @@ class _ExerciseControlPage extends State<ExerciseControlPage> with Trans {
|
||||
"times!",
|
||||
);
|
||||
|
||||
String title = step.toString() + ". " + t("Control Exercise:");
|
||||
String title = (step + 1).toString() + "/4 " + t("Control Exercise:");
|
||||
LinkedHashMap args = LinkedHashMap();
|
||||
|
||||
List<Widget> listWidgets = [
|
||||
|
||||
@@ -195,7 +195,7 @@ class _ExerciseNewPageState extends State<ExerciseNewPage> with Trans, Logging {
|
||||
Text(
|
||||
exerciseDescription,
|
||||
style: GoogleFonts.inter(fontSize: 12, color: Colors.yellow[300]),
|
||||
maxLines: 4,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: true,
|
||||
),
|
||||
@@ -231,10 +231,21 @@ class _ExerciseNewPageState extends State<ExerciseNewPage> with Trans, Logging {
|
||||
color: Colors.transparent,
|
||||
),
|
||||
columnQuantity(exerciseBloc),
|
||||
Divider(),
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
Text(
|
||||
t("Step" + ": " + "1/4"),
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 22,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
maxLines: 3,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: true,
|
||||
),
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
|
||||
+23
-73
@@ -1,4 +1,3 @@
|
||||
|
||||
import 'package:aitrainer_app/bloc/menu/menu_bloc.dart';
|
||||
|
||||
import 'package:aitrainer_app/widgets/app_bar.dart';
|
||||
@@ -6,13 +5,10 @@ import 'package:aitrainer_app/widgets/bottom_nav.dart';
|
||||
import 'package:aitrainer_app/widgets/menu_page_widget.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class MenuPage extends StatefulWidget {
|
||||
static const routeName = '/menu_page';
|
||||
int parent;
|
||||
|
||||
MenuPage({this.parent});
|
||||
@@ -24,81 +20,35 @@ class _MenuPage extends State<MenuPage> {
|
||||
// ignore: close_sinks
|
||||
MenuBloc menuBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
menuBloc = BlocProvider.of<MenuBloc>(context);
|
||||
menuBloc.parent = widget.parent;
|
||||
return Scaffold(
|
||||
appBar: AppBarNav(isMenu: true,),
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage('asset/image/WT_menu_dark.png'),
|
||||
fit: BoxFit.fill,
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
appBar: AppBarNav(
|
||||
isMenu: true,
|
||||
),
|
||||
child: BlocConsumer<MenuBloc, MenuState>(
|
||||
listener: (context, state) {
|
||||
if (state is MenuError) {
|
||||
Scaffold.of(context).showSnackBar(SnackBar(
|
||||
backgroundColor: Colors.orange,
|
||||
content: Text(state.message, style: TextStyle(color: Colors.white))));
|
||||
} else if ( state is MenuLoading ) {
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage('asset/image/WT_menu_dark.png'),
|
||||
fit: BoxFit.fill,
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
),
|
||||
child: BlocConsumer<MenuBloc, MenuState>(listener: (context, state) {
|
||||
if (state is MenuError) {
|
||||
Scaffold.of(context).showSnackBar(
|
||||
SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white))));
|
||||
}
|
||||
}, builder: (context, state) {
|
||||
return MenuPageWidget();
|
||||
}
|
||||
},
|
||||
// ignore: missing_return
|
||||
builder: (context, state) {
|
||||
if ( state is MenuInitial ) {
|
||||
return LoadingMenuDialog();
|
||||
} else if (state is MenuReady ) {
|
||||
return MenuPageWidget();
|
||||
} else if ( state is MenuLoading ) {
|
||||
return LoadingMenuDialog();
|
||||
}
|
||||
}
|
||||
)
|
||||
),
|
||||
bottomNavigationBar: BottomNavigator(bottomNavIndex: 0)
|
||||
);
|
||||
})),
|
||||
bottomNavigationBar: BottomNavigator(bottomNavIndex: 0));
|
||||
}
|
||||
}
|
||||
|
||||
class LoadingMenuDialog extends StatefulWidget {
|
||||
|
||||
LoadingMenuDialog({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _LoadingMenuDialog();
|
||||
}
|
||||
|
||||
class _LoadingMenuDialog extends State<LoadingMenuDialog> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
/// We require the initializers to run after the loading screen is rendered
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
BlocProvider.of<MenuBloc>(context).add(MenuCreate());
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: Center(
|
||||
child: Card(
|
||||
child: Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
padding: EdgeInsets.all(12.0),
|
||||
child: CircularProgressIndicator(),
|
||||
color: Colors.transparent,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ class _MyExercisePlanPage extends State<MyExercisePlanPage> with Trans, Logging
|
||||
builder: (BuildContext context) {
|
||||
return DialogPremium(
|
||||
unlocked: Cache().hasPurchased,
|
||||
unlockRound: 1,
|
||||
unlockRound: 2,
|
||||
function: "Suggested Training Plan",
|
||||
unlockedText: null,
|
||||
onTap: () => {Navigator.of(context).pop()},
|
||||
|
||||
@@ -149,7 +149,7 @@ class SalesPage extends StatelessWidget with Trans, Logging {
|
||||
|
||||
bloc.product2Display.forEach((element) {
|
||||
final String title = element.sort == 3 ? t("Montly") : t("Annual");
|
||||
final String desc4 = element.sort == 1 ? "" : t("AI driven predictions");
|
||||
final String desc4 = element.sort == 1 ? "" : t("Predictions with Artificial Intelligence");
|
||||
String badge;
|
||||
if (element.sort == 2) {
|
||||
badge = t("14% discount");
|
||||
|
||||
+32
-17
@@ -5,6 +5,7 @@ import 'package:aitrainer_app/localization/app_localization.dart';
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/repository/exercise_repository.dart';
|
||||
import 'package:aitrainer_app/util/common.dart';
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -12,6 +13,8 @@ import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:percent_indicator/linear_percent_indicator.dart';
|
||||
import 'package:rainbow_color/rainbow_color.dart';
|
||||
|
||||
import 'dialog_html.dart';
|
||||
|
||||
class AppBarNav extends StatefulWidget implements PreferredSizeWidget {
|
||||
final MenuBloc menuBloc;
|
||||
final bool isMenu;
|
||||
@@ -146,6 +149,9 @@ class _AppBarNav extends State<AppBarNav> with SingleTickerProviderStateMixin, C
|
||||
percent = 0;
|
||||
}
|
||||
}
|
||||
if (percent == null) {
|
||||
percent = 0;
|
||||
}
|
||||
int sizeExerciseList = Cache().getExercises() == null ? 0 : Cache().getExercises().length;
|
||||
if (sizeExerciseList == 0) {
|
||||
String text = AppLocalizations.of(context).translate("Make your first test");
|
||||
@@ -155,29 +161,38 @@ class _AppBarNav extends State<AppBarNav> with SingleTickerProviderStateMixin, C
|
||||
text,
|
||||
style: TextStyle(fontSize: fontSize, color: colorAnim.value, shadows: [Shadow(color: Colors.purple, blurRadius: 15)]),
|
||||
),
|
||||
//TestProgress(animation: sizeAnim),
|
||||
]);
|
||||
} else {
|
||||
return Stack(
|
||||
alignment: Alignment.topLeft,
|
||||
children: [
|
||||
LinearPercentIndicator(
|
||||
width: cWidth / 4,
|
||||
lineHeight: 14.0,
|
||||
percent: percent,
|
||||
center: Text(
|
||||
(percent * 100).toStringAsFixed(0) + "% " + AppLocalizations.of(context).translate("finished"),
|
||||
style: new TextStyle(fontSize: 10.0),
|
||||
GestureDetector(
|
||||
onTap: () => showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return DialogHTML(
|
||||
title: AppLocalizations.of(context).translate("Progressindicator for the tests"),
|
||||
htmlData: AppLocalizations.of(context).translate("Progressindicator_desc"),
|
||||
);
|
||||
}),
|
||||
child: LinearPercentIndicator(
|
||||
width: cWidth / 4,
|
||||
lineHeight: 14.0,
|
||||
percent: percent,
|
||||
center: Text(
|
||||
(percent * 100).toStringAsFixed(0) + "% " + AppLocalizations.of(context).translate("finished"),
|
||||
style: new TextStyle(fontSize: 10.0),
|
||||
),
|
||||
trailing: Icon(
|
||||
percent > 0.6 ? Icons.mood : Icons.mood_bad,
|
||||
color: colorAnim.value,
|
||||
),
|
||||
linearStrokeCap: LinearStrokeCap.roundAll,
|
||||
backgroundColor: colorAnim.value,
|
||||
progressColor: Color(0xff73e600),
|
||||
animation: true,
|
||||
),
|
||||
trailing: Icon(
|
||||
percent > 0.6 ? Icons.mood : Icons.mood_bad,
|
||||
color: colorAnim.value,
|
||||
),
|
||||
linearStrokeCap: LinearStrokeCap.roundAll,
|
||||
backgroundColor: colorAnim.value,
|
||||
progressColor: Color(0xff73e600),
|
||||
animation: true,
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -172,6 +172,13 @@ class _BMIState extends State<BMI> with Trans {
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontSize: 20,
|
||||
color: Colors.orange[200],
|
||||
shadows: <Shadow>[
|
||||
Shadow(
|
||||
offset: Offset(2.0, 2.0),
|
||||
blurRadius: 5.0,
|
||||
color: Colors.black54,
|
||||
),
|
||||
],
|
||||
)),
|
||||
Text(
|
||||
t("Bodyweight") +
|
||||
@@ -183,16 +190,37 @@ class _BMIState extends State<BMI> with Trans {
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontSize: 20,
|
||||
color: Colors.orange[200],
|
||||
shadows: <Shadow>[
|
||||
Shadow(
|
||||
offset: Offset(2.0, 2.0),
|
||||
blurRadius: 5.0,
|
||||
color: Colors.black87,
|
||||
),
|
||||
],
|
||||
)),
|
||||
Text("BMI" + " " + t("goal") + ": " + widget.exerciseBloc.goalBMI.toStringAsFixed(1),
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontSize: 20,
|
||||
color: Colors.orange[500],
|
||||
shadows: <Shadow>[
|
||||
Shadow(
|
||||
offset: Offset(2.0, 2.0),
|
||||
blurRadius: 5.0,
|
||||
color: Colors.black87,
|
||||
),
|
||||
],
|
||||
)),
|
||||
Text(t("Bodyweight") + " " + t("goal") + ": " + widget.exerciseBloc.goalWeight.toStringAsFixed(0) + " kg",
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontSize: 20,
|
||||
color: Colors.orange[500],
|
||||
shadows: <Shadow>[
|
||||
Shadow(
|
||||
offset: Offset(2.0, 2.0),
|
||||
blurRadius: 5.0,
|
||||
color: Colors.black54,
|
||||
),
|
||||
],
|
||||
)),
|
||||
]))))));
|
||||
}
|
||||
|
||||
@@ -141,6 +141,17 @@ class _BMRState extends State<BMR> with Trans {
|
||||
fontSize: 30,
|
||||
color: Colors.orange[300],
|
||||
)),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 65, right: 65),
|
||||
alignment: Alignment.center,
|
||||
child:
|
||||
Text(t("Resting metabolic rate is the rate at which your body burns energy when it is at complete rest."),
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 16,
|
||||
color: Colors.yellow[200],
|
||||
)),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 35, right: 35),
|
||||
@@ -189,7 +200,7 @@ class _BMRState extends State<BMR> with Trans {
|
||||
child: TextFormField(
|
||||
focusNode: _nodeText2,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: EdgeInsets.only(left: 15, top: 5, bottom: 5),
|
||||
contentPadding: EdgeInsets.only(left: 10, top: 5, bottom: 5),
|
||||
labelText: AppLocalizations.of(context).translate("Actual Height"),
|
||||
labelStyle: GoogleFonts.inter(fontSize: 16, color: Colors.yellow[50]),
|
||||
fillColor: Colors.black38,
|
||||
@@ -216,7 +227,7 @@ class _BMRState extends State<BMR> with Trans {
|
||||
child: TextFormField(
|
||||
focusNode: _nodeText3,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: EdgeInsets.only(left: 15, top: 5, bottom: 5),
|
||||
contentPadding: EdgeInsets.only(left: 10, top: 5, bottom: 5),
|
||||
labelText: AppLocalizations.of(context).translate("Birth Year"),
|
||||
labelStyle: GoogleFonts.inter(fontSize: 16, color: Colors.yellow[50]),
|
||||
fillColor: Colors.black38,
|
||||
@@ -338,7 +349,7 @@ class _BMRState extends State<BMR> with Trans {
|
||||
child: TextFormField(
|
||||
focusNode: _nodeText1,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: EdgeInsets.only(left: 15, top: 5, bottom: 5),
|
||||
contentPadding: EdgeInsets.only(left: 10, top: 5, bottom: 5),
|
||||
labelText: AppLocalizations.of(context).translate("Actual Weight"),
|
||||
labelStyle: GoogleFonts.inter(fontSize: 16, color: Colors.yellow[50]),
|
||||
fillColor: Colors.black38,
|
||||
|
||||
@@ -40,7 +40,7 @@ class _DialogPremiumState extends State<DialogHTML> with Trans {
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [BoxShadow(color: Colors.black, offset: Offset(0, 10), blurRadius: 10)],
|
||||
image: DecorationImage(
|
||||
image: AssetImage('asset/image/WT_black_G_background.png'),
|
||||
image: AssetImage('asset/image/WT_results_background.jpg'),
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
|
||||
@@ -256,7 +256,11 @@ class _DialogPremiumState extends State<DialogPremium> with Trans {
|
||||
list.add(TextSpan(text: t(" ")));
|
||||
list.add(
|
||||
TextSpan(
|
||||
text: widget.unlockRound == 1 ? t("the first") : t("the second"),
|
||||
text: widget.unlockRound == 1
|
||||
? t("the first")
|
||||
: widget.unlockRound == 2
|
||||
? t("the second")
|
||||
: t("the third"),
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
||||
@@ -89,6 +89,5 @@ class _HomePageState extends State<AitrainerHome> with Logging {
|
||||
@override
|
||||
void dispose() async {
|
||||
super.dispose();
|
||||
//await PlatformPurchaseApi().close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:aitrainer_app/bloc/menu/menu_bloc.dart';
|
||||
@@ -10,12 +10,16 @@ import 'package:aitrainer_app/model/workout_menu_tree.dart';
|
||||
import 'package:aitrainer_app/service/logging.dart';
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:aitrainer_app/widgets/dialog_common.dart';
|
||||
import 'package:auto_animated/auto_animated.dart';
|
||||
import 'package:badges/badges.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:transparent_image/transparent_image.dart';
|
||||
import 'package:aitrainer_app/library/image_cache.dart' as wt;
|
||||
|
||||
import 'dialog_html.dart';
|
||||
import 'menu_info_widget.dart';
|
||||
@@ -35,35 +39,33 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
final double baseHeight = 675.2;
|
||||
bool isFirst = true;
|
||||
bool wait = false;
|
||||
Timer _timer, _waitTimer;
|
||||
MenuBloc menuBloc;
|
||||
final scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
isFirst = true;
|
||||
_timer = Timer.periodic(
|
||||
Duration(milliseconds: 800),
|
||||
(Timer timer) => setState(() {
|
||||
if (!wait) {
|
||||
isFirst = !isFirst;
|
||||
//wait = true;
|
||||
}
|
||||
}));
|
||||
_waitTimer = Timer.periodic(
|
||||
Duration(milliseconds: 5000),
|
||||
(Timer timer) => setState(() {
|
||||
wait = !wait;
|
||||
}));
|
||||
|
||||
/// We require the initializers to run after the loading screen is rendered
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
menuBloc.add(MenuCreate());
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
MenuBloc menuBloc = BlocProvider.of<MenuBloc>(context);
|
||||
menuBloc = BlocProvider.of<MenuBloc>(context);
|
||||
setContext(context);
|
||||
double cWidth = MediaQuery.of(context).size.width;
|
||||
double cHeight = MediaQuery.of(context).size.height;
|
||||
|
||||
return CustomScrollView(scrollDirection: Axis.vertical, slivers: buildMenuColumn(widget.parent, context, menuBloc, cWidth, cHeight));
|
||||
return CustomScrollView(
|
||||
// Must add scrollController to sliver root
|
||||
controller: scrollController,
|
||||
scrollDirection: Axis.vertical,
|
||||
slivers: buildMenuColumn(widget.parent, context, menuBloc, cWidth, cHeight));
|
||||
}
|
||||
|
||||
List<Widget> buildMenuColumn(int parent, BuildContext context, MenuBloc menuBloc, double cWidth, double cHeight) {
|
||||
@@ -90,8 +92,8 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
menuBloc.getFilteredBranch(menuBloc.parent).forEach((treeName, value) {
|
||||
WorkoutMenuTree workoutTree = value;
|
||||
_columnChildren.add(Container(
|
||||
padding: EdgeInsets.only(top: 15.0, left: 15, right: 15),
|
||||
height: 225, //cHeight / 3 * distortionHeight,
|
||||
padding: EdgeInsets.only(top: 15.0, left: cWidth * 0.04, right: cWidth * 0.04),
|
||||
height: (cHeight / 3) - cWidth * 0.16,
|
||||
child: Badge(
|
||||
padding: EdgeInsets.all(8),
|
||||
position: BadgePosition.bottomEnd(end: 0),
|
||||
@@ -141,9 +143,16 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
])))));
|
||||
});
|
||||
}
|
||||
|
||||
SliverList sliverList = SliverList(
|
||||
delegate: SliverChildListDelegate(_columnChildren),
|
||||
//delegate: SliverChildListDelegate(_columnChildren),
|
||||
LiveSliverList sliverList = LiveSliverList(
|
||||
itemCount: _columnChildren.length,
|
||||
reAnimateOnVisibility: false,
|
||||
showItemDuration: Duration(milliseconds: 250),
|
||||
itemBuilder: (BuildContext context, int index, Animation<double> animation) => FadeTransition(
|
||||
opacity: animation,
|
||||
child: _columnChildren[index],
|
||||
),
|
||||
controller: scrollController,
|
||||
);
|
||||
|
||||
slivers.add(sliverList);
|
||||
@@ -332,33 +341,51 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
}
|
||||
|
||||
Widget _getButtonImage(WorkoutMenuTree workoutTree, double cWidth, double cHeight) {
|
||||
Widget image;
|
||||
|
||||
if (workoutTree.imageName.startsWith('https')) {
|
||||
image = ClipRRect(
|
||||
//print("_getButtonImage " + workoutTree.imageName);
|
||||
String imageString = menuBloc.getImage(workoutTree.id, workoutTree.imageName);
|
||||
Widget widget;
|
||||
if (imageString != null) {
|
||||
print(" -- get Image from MEMORY " + workoutTree.imageName);
|
||||
widget = ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
child: Container(
|
||||
color: Colors.black87,
|
||||
color: Colors.transparent,
|
||||
child: FadeInImage(
|
||||
image: NetworkImage(workoutTree.imageName),
|
||||
placeholder: AssetImage("asset/image/dots.gif"),
|
||||
fadeInDuration: Duration(milliseconds: 100),
|
||||
image: MemoryImage(base64Decode(imageString)),
|
||||
placeholder: MemoryImage(kTransparentImage),
|
||||
),
|
||||
));
|
||||
} else {
|
||||
image = Container(
|
||||
//width: cWidth - 30,
|
||||
//height: 210.0,
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
fit: BoxFit.cover,
|
||||
image: AssetImage(workoutTree.imageName),
|
||||
),
|
||||
borderRadius: BorderRadius.all(Radius.circular(24.0)),
|
||||
),
|
||||
);
|
||||
if (workoutTree.imageName.contains("https")) {
|
||||
if (!wt.ImageCache().existsImageInMap(workoutTree.id, workoutTree.imageName)) {
|
||||
print(" -- get Image from network " + workoutTree.imageName);
|
||||
widget = ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: FadeInImage(
|
||||
fadeInDuration: Duration(milliseconds: 500),
|
||||
image: NetworkImage(workoutTree.imageName),
|
||||
placeholder: MemoryImage(kTransparentImage),
|
||||
),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
//print(" -- get Image from asset " + workoutTree.imageName);
|
||||
widget = ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: FadeInImage(
|
||||
fadeInDuration: Duration(milliseconds: 200),
|
||||
image: AssetImage(workoutTree.imageName),
|
||||
placeholder: MemoryImage(kTransparentImage),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
return widget;
|
||||
}
|
||||
|
||||
Widget badgedIcon(WorkoutMenuTree workoutMenuTree, double cWidth, double cHeight) {
|
||||
@@ -380,7 +407,7 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
)),
|
||||
child: buttonImage == null
|
||||
? Container(
|
||||
color: Colors.red,
|
||||
color: Colors.transparent,
|
||||
)
|
||||
: buttonImage,
|
||||
);
|
||||
@@ -388,8 +415,6 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer.cancel();
|
||||
_waitTimer.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user