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
+10 -1
View File
@@ -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());
+41 -9
View File
@@ -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 -1
View File
@@ -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();
}
}
+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;
}
}
+8 -2
View File
@@ -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();
+1 -1
View File
@@ -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;
+4
View File
@@ -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) {
+4 -14
View File
@@ -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) {
+10 -8
View File
@@ -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;
});
}
+7 -4
View File
@@ -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 {
+1 -1
View File
@@ -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 = [
+13 -2
View File
@@ -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
View File
@@ -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,
),
),
),
);
}
}
+1 -1
View File
@@ -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()},
+1 -1
View File
@@ -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
View File
@@ -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,
),
)
],
);
}
+28
View File
@@ -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,
),
],
)),
]))))));
}
+14 -3
View File
@@ -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,
+1 -1
View File
@@ -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,
),
+5 -1
View File
@@ -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,
-1
View File
@@ -89,6 +89,5 @@ class _HomePageState extends State<AitrainerHome> with Logging {
@override
void dispose() async {
super.dispose();
//await PlatformPurchaseApi().close();
}
}
+70 -45
View File
@@ -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();
}
}