diff --git a/android/app/build.gradle b/android/app/build.gradle
index 9ced3334..ad105f02 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -25,12 +25,35 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+// Keys for the android play store
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('app/key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
+// Key for wger.de REST API
+def wgerProperties = new Properties()
+def localMapsPropertiesFile = rootProject.file('app/wger.properties')
+if (localMapsPropertiesFile.exists()) {
+ project.logger.info('Load maps properties from local file')
+ localMapsPropertiesFile.withReader('UTF-8') { reader ->
+ wgerProperties.load(reader)
+ }
+} else {
+ project.logger.info('Load maps properties from environment')
+ try {
+ wgerProperties['WGER_API_KEY'] = System.getenv('WGER_API_KEY')
+ } catch(NullPointerException e) {
+ project.logger.warn('Failed to load WGER_API_KEY from environment.', e)
+ }
+}
+def wgerApiKey = wgerProperties.getProperty('WGER_API_KEY')
+if(wgerApiKey == null){
+ wgerApiKey = ""
+ project.logger.error('Wger Api Key not configured. Set it in `app/wger.properties` or in the environment variable `WGER_API_KEY`')
+}
+
android {
compileSdkVersion 29
@@ -49,6 +72,7 @@ android {
targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
+ manifestPlaceholders = [WGER_API_KEY: wgerApiKey]
}
signingConfigs {
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 7ae1c970..cb64563b 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -11,6 +11,9 @@
android:name="io.flutter.app.FlutterApplication"
android:label="wger"
android:icon="@mipmap/ic_launcher">
+
+
+
errorList = [];
for (var key in exception.errors.keys) {
// Error headers
@@ -59,6 +58,7 @@ void showHttpExceptionErrorDialog(WgerHttpException exception, BuildContext cont
for (var value in exception.errors[key]) {
errorList.add(Text(value));
}
+ errorList.add(SizedBox(height: 8));
}
showDialog(
context: context,
diff --git a/lib/models/http_exception.dart b/lib/models/http_exception.dart
index 8dcd1c35..8bb02201 100644
--- a/lib/models/http_exception.dart
+++ b/lib/models/http_exception.dart
@@ -24,14 +24,14 @@ class WgerHttpException implements Exception {
/// Custom http exception.
/// Expects the response body of the REST call and will try to parse it to
/// JSON. Will use the response as-is if it fails.
- WgerHttpException(String responseBody) {
+ WgerHttpException(dynamic responseBody) {
if (responseBody == null) {
errors = {'unknown_error': 'An unknown error occurred, no further information available'};
} else {
try {
errors = json.decode(responseBody);
} catch (e) {
- errors = {'error': responseBody};
+ errors = responseBody;
}
}
this.errors = errors;
diff --git a/lib/providers/auth.dart b/lib/providers/auth.dart
index 0b5b3a6f..20e745dd 100644
--- a/lib/providers/auth.dart
+++ b/lib/providers/auth.dart
@@ -19,8 +19,11 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
+import 'dart:io';
+import 'package:android_metadata/android_metadata.dart';
import 'package:flutter/cupertino.dart';
+import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:http/http.dart' as http;
import 'package:package_info/package_info.dart';
@@ -71,7 +74,46 @@ class Auth with ChangeNotifier {
applicationVersion = packageInfo;
}
- Future _authenticate(String username, String password, String serverUrl) async {
+ /// Registers a new user
+ Future register({String username, String password, String email, String serverUrl}) async {
+ var url = '$serverUrl/api/v2/register/';
+ Map metadata = Map();
+
+ // Read the api key from the manifest file
+ try {
+ metadata = await AndroidMetadata.metaDataAsMap;
+ } on PlatformException {
+ throw Exception('An error occurred reading the API key');
+ }
+
+ // Register
+ try {
+ Map data = {'username': username, 'password': password};
+ if (email != '') {
+ data['email'] = email;
+ }
+ final response = await http.post(
+ url,
+ headers: {
+ HttpHeaders.contentTypeHeader: 'application/json; charset=UTF-8',
+ HttpHeaders.authorizationHeader: "Token ${metadata['wger.api_key']}"
+ },
+ body: json.encode(data),
+ );
+ final responseData = json.decode(response.body);
+
+ if (response.statusCode >= 400) {
+ throw WgerHttpException(responseData);
+ }
+
+ login(username, password, serverUrl);
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ /// Authenticates a user
+ Future login(String username, String password, String serverUrl) async {
var url = '$serverUrl/api/v2/login/';
try {
@@ -80,7 +122,7 @@ class Auth with ChangeNotifier {
final response = await http.post(
url,
headers: {
- 'Content-Type': 'application/json; charset=UTF-8',
+ HttpHeaders.contentTypeHeader: 'application/json; charset=UTF-8',
},
body: json.encode({'username': username, 'password': password}),
);
@@ -120,14 +162,6 @@ class Auth with ChangeNotifier {
}
}
- Future signUp(String email, String password) async {
- return _authenticate(email, password, 'signUp');
- }
-
- Future signIn(String username, String password, String serverUrl) async {
- return _authenticate(username, password, serverUrl);
- }
-
Future tryAutoLogin() async {
final prefs = await SharedPreferences.getInstance();
if (!prefs.containsKey('userData')) {
diff --git a/lib/providers/nutrition.dart b/lib/providers/nutrition.dart
index 896f24f9..29ef735c 100644
--- a/lib/providers/nutrition.dart
+++ b/lib/providers/nutrition.dart
@@ -57,7 +57,9 @@ class Nutrition extends WgerBaseProvider with ChangeNotifier {
/// Returns the current active nutritional plan. At the moment this is just
/// the latest, but this might change in the future.
NutritionalPlan get currentPlan {
- return _plans.first;
+ if (_plans.length > 0) {
+ return _plans.first;
+ }
}
NutritionalPlan findById(int id) {
diff --git a/lib/providers/workout_plans.dart b/lib/providers/workout_plans.dart
index c1ed18d9..da07cc77 100644
--- a/lib/providers/workout_plans.dart
+++ b/lib/providers/workout_plans.dart
@@ -104,7 +104,9 @@ class WorkoutPlans extends WgerBaseProvider with ChangeNotifier {
/// Returns the current active workout plan. At the moment this is just
/// the latest, but this might change in the future.
WorkoutPlan get activePlan {
- return _workoutPlans.first;
+ if (_workoutPlans.length > 0) {
+ return _workoutPlans.first;
+ }
}
/*
diff --git a/lib/screens/auth_screen.dart b/lib/screens/auth_screen.dart
index 4d307fa0..bc3e6062 100644
--- a/lib/screens/auth_screen.dart
+++ b/lib/screens/auth_screen.dart
@@ -19,8 +19,8 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wger/helpers/ui.dart';
+import 'package:wger/models/http_exception.dart';
-import '../models/http_exception.dart';
import '../providers/auth.dart';
enum AuthMode {
@@ -116,18 +116,22 @@ class _AuthCardState extends State {
_isLoading = true;
});
try {
+ // Login existing user
if (_authMode == AuthMode.Login) {
- // Login existing user
- await Provider.of(context, listen: false).signIn(
+ await Provider.of(context, listen: false).login(
_authData['username'],
_authData['password'],
_authData['serverUrl'],
);
- } else {
- // Register new user
- // await Provider.of(context, listen: false)
- // .register(_authData['email'], _authData['password']);
+ // Register new user
+ } else {
+ await Provider.of(context, listen: false).register(
+ username: _authData['username'],
+ password: _authData['password'],
+ email: _authData['email'],
+ serverUrl: _authData['serverUrl'],
+ );
}
} on WgerHttpException catch (error) {
showHttpExceptionErrorDialog(error, context);
@@ -197,8 +201,10 @@ class _AuthCardState extends State {
controller: _emailController,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
+
+ // Email is not required
validator: (value) {
- if (value.isEmpty || !value.contains('@')) {
+ if (value.isNotEmpty && !value.contains('@')) {
return 'Invalid email!';
}
return null;
diff --git a/lib/widgets/dashboard/widgets.dart b/lib/widgets/dashboard/widgets.dart
index f88c2679..f6e426af 100644
--- a/lib/widgets/dashboard/widgets.dart
+++ b/lib/widgets/dashboard/widgets.dart
@@ -83,7 +83,11 @@ class _DashboardNutritionWidgetState extends State {
)
],
)
- : Text('You have no nutritional plans'),
+ : Container(
+ alignment: Alignment.center,
+ height: 150,
+ child: Text('You have no nutritional plans'),
+ ),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
@@ -140,7 +144,11 @@ class _DashboardWeightWidgetState extends State {
height: 180,
child: WeightChartWidget(weightEntriesData.items),
)
- : Text('You have no weight entries'),
+ : Container(
+ alignment: Alignment.center,
+ height: 150,
+ child: Text('You have no weight entries'),
+ ),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
@@ -199,51 +207,57 @@ class _DashboardWorkoutWidgetState extends State {
style: Theme.of(context).textTheme.headline4,
),
_workoutPlan != null
- ? Column(children: [
- Text(
- DateFormat.yMd().format(_workoutPlan.creationDate),
- style: Theme.of(context).textTheme.headline6,
- ),
- TextButton(
- child: Text(
- _workoutPlan.description,
- style: TextStyle(fontSize: 20),
+ ? Column(
+ children: [
+ Text(
+ DateFormat.yMd().format(_workoutPlan.creationDate),
+ style: Theme.of(context).textTheme.headline6,
),
- onPressed: () {
- return Navigator.of(context)
- .pushNamed(WorkoutPlanScreen.routeName, arguments: _workoutPlan);
- },
- ),
- ..._workoutPlan.days.map((workoutDay) {
- return Column(children: [
- const SizedBox(height: 10),
- showDetail == true
- ? Text(
- workoutDay.description,
- style: TextStyle(fontWeight: FontWeight.bold),
- )
- : Text(workoutDay.description),
- if (showDetail)
- ...workoutDay.sets
- .map((set) => Text(set.exercisesObj.map((e) => e.name).join(',')))
- .toList(),
- ]);
- }).toList(),
- Row(
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- TextButton(
- child: Text(AppLocalizations.of(context).toggleDetails),
- onPressed: () {
- setState(() {
- showDetail = !showDetail;
- });
- },
+ TextButton(
+ child: Text(
+ _workoutPlan.description,
+ style: TextStyle(fontSize: 20),
),
- ],
- )
- ])
- : Text('you have no workouts'),
+ onPressed: () {
+ return Navigator.of(context)
+ .pushNamed(WorkoutPlanScreen.routeName, arguments: _workoutPlan);
+ },
+ ),
+ ..._workoutPlan.days.map((workoutDay) {
+ return Column(children: [
+ const SizedBox(height: 10),
+ showDetail == true
+ ? Text(
+ workoutDay.description,
+ style: TextStyle(fontWeight: FontWeight.bold),
+ )
+ : Text(workoutDay.description),
+ if (showDetail)
+ ...workoutDay.sets
+ .map((set) => Text(set.exercisesObj.map((e) => e.name).join(',')))
+ .toList(),
+ ]);
+ }).toList(),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ TextButton(
+ child: Text(AppLocalizations.of(context).toggleDetails),
+ onPressed: () {
+ setState(() {
+ showDetail = !showDetail;
+ });
+ },
+ ),
+ ],
+ )
+ ],
+ )
+ : Container(
+ alignment: Alignment.center,
+ height: 150,
+ child: Text('you have no workouts'),
+ ),
],
),
);
diff --git a/pubspec.lock b/pubspec.lock
index 13246a1e..41420a25 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -15,6 +15,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.39.17"
+ android_metadata:
+ dependency: "direct main"
+ description:
+ name: android_metadata
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.1.3"
archive:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 2a3a4070..58a206f1 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -38,6 +38,7 @@ dependencies:
flutter_typeahead: ^2.0.0
table_calendar: ^2.3.3
package_info: ^0.4.3+2
+ android_metadata: ^0.1.3
dev_dependencies:
flutter_test: