WT1.1.3 build 2 iOS. Apple SignIn, Google SignIn

This commit is contained in:
bossanyit
2021-01-17 09:30:57 +01:00
parent 4bd1950ffd
commit d44fefe9af
31 changed files with 811 additions and 249 deletions
+43 -1
View File
@@ -21,6 +21,7 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> with Trans {
final BuildContext context;
final bool isRegistration;
bool dataPolicyAllowed = false;
bool obscure = true;
LoginBloc({this.accountBloc, this.userRepository, this.context, this.isRegistration}) : super(LoginInitial());
@override
@@ -51,6 +52,20 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> with Trans {
Flurry.logEvent("Login");
Flurry.logEvent("LoginFB");
yield LoginSuccess();
} else if (event is LoginGoogle) {
yield LoginLoading();
await userRepository.getUserByGoogle();
accountBloc.add(AccountLogInFinished(customer: Cache().userLoggedIn));
Flurry.logEvent("Login");
Flurry.logEvent("LoginGoogle");
yield LoginSuccess();
} else if (event is LoginApple) {
yield LoginLoading();
await userRepository.getUserByApple();
accountBloc.add(AccountLogInFinished(customer: Cache().userLoggedIn));
Flurry.logEvent("Login");
Flurry.logEvent("LoginApple");
yield LoginSuccess();
} else if (event is RegistrationSubmit) {
yield LoginLoading();
if (!this.dataPolicyAllowed) {
@@ -75,9 +90,36 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> with Trans {
Flurry.logEvent("RegistrationFB");
Flurry.logEvent("Registration");
yield LoginSuccess();
} else if (event is RegistrationGoogle) {
yield LoginLoading();
if (!this.dataPolicyAllowed) {
yield LoginError();
throw Exception("Please accept our data policy");
}
await userRepository.addUserGoogle();
accountBloc.add(AccountLogInFinished(customer: Cache().userLoggedIn));
await saveCustomer();
Flurry.logEvent("RegistrationGoogle");
Flurry.logEvent("Registration");
yield LoginSuccess();
} else if (event is RegistrationApple) {
yield LoginLoading();
if (!this.dataPolicyAllowed) {
yield LoginError();
throw Exception("Please accept our data policy");
}
await userRepository.addUserApple();
accountBloc.add(AccountLogInFinished(customer: Cache().userLoggedIn));
await saveCustomer();
Flurry.logEvent("RegistrationApple");
Flurry.logEvent("Registration");
yield LoginSuccess();
} else if (event is DataProtectionClicked) {
this.dataPolicyAllowed = event.marked;
yield LoginLoading();
this.dataPolicyAllowed = !dataPolicyAllowed;
yield LoginReady();
} else if (event is LoginPasswordChangeObscure) {
this.obscure = !this.obscure;
}
} on Exception catch (e) {
yield LoginError(message: e.toString());
+16
View File
@@ -35,6 +35,14 @@ class LoginFB extends LoginEvent {
const LoginFB();
}
class LoginGoogle extends LoginEvent {
const LoginGoogle();
}
class LoginApple extends LoginEvent {
const LoginApple();
}
class DataProtectionClicked extends LoginEvent {
final bool marked;
const DataProtectionClicked({this.marked});
@@ -47,3 +55,11 @@ class RegistrationSubmit extends LoginEvent {
class RegistrationFB extends LoginEvent {
const RegistrationFB();
}
class RegistrationGoogle extends LoginEvent {
const RegistrationGoogle();
}
class RegistrationApple extends LoginEvent {
const RegistrationApple();
}
+5 -5
View File
@@ -29,14 +29,14 @@ class SalesBloc extends Bloc<SalesEvent, SalesState> with Logging {
if (event is SalesLoad) {
yield SalesLoading();
//Flurry.logEvent("SalesPageOpen");
await PlatformPurchaseApi().initPurchasePlatformState();
this.getProductSet();
// await PlatformPurchaseApi().initPurchasePlatformState();
// this.getProductSet();
yield SalesReady();
} else if (event is SalesPurchase) {
final int productId = event.productId;
trace("Requesting purchase for" + productId.toString());
//Flurry.logEvent("PurchaseRequest");
PlatformPurchaseApi().purchase(getSelectedProduct(productId));
// PlatformPurchaseApi().purchase(getSelectedProduct(productId));
}
} on Exception catch (ex) {
yield SalesError(message: ex.toString());
@@ -53,7 +53,7 @@ class SalesBloc extends Bloc<SalesEvent, SalesState> with Logging {
return prod;
}
String getLocalizedPrice(String productId) {
/* String getLocalizedPrice(String productId) {
String price = "";
for (var product in PlatformPurchaseApi().getIAPItems()) {
if (Platform.isAndroid) {
@@ -109,5 +109,5 @@ class SalesBloc extends Bloc<SalesEvent, SalesState> with Logging {
productTest.dateView = DateTime.now();
//ProductTestApi().saveProductTest(productTest);
//Cache().productTests.add(productTest);
}
} */
}
+1 -1
View File
@@ -42,7 +42,7 @@ class SessionBloc extends Bloc<SessionEvent, SessionState> with Logging {
@override
Future<void> close() async {
await this.close();
PlatformPurchaseApi().close();
//PlatformPurchaseApi().close();
super.close();
}
}
+4 -2
View File
@@ -32,6 +32,8 @@ import 'package:aitrainer_app/view/reset_password.dart';
import 'package:aitrainer_app/view/sales_page.dart';
import 'package:aitrainer_app/view/settings.dart';
import 'package:aitrainer_app/widgets/home.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_analytics/observer.dart';
import 'package:flurry/flurry.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -163,7 +165,7 @@ class WorkoutTestApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
//final FirebaseAnalytics analytics = FirebaseAnalytics();
final FirebaseAnalytics analytics = FirebaseAnalytics();
initFlurry();
PushNotificationsManager().init();
return MaterialApp(
@@ -233,7 +235,7 @@ class WorkoutTestApp extends StatelessWidget {
bodyText1: GoogleFonts.openSans(textStyle: TextStyle(fontSize: 14.0)),
)),
navigatorObservers: [
//FirebaseAnalyticsObserver(analytics: analytics),
FirebaseAnalyticsObserver(analytics: analytics),
],
home: AitrainerHome(),
);
+2 -6
View File
@@ -73,7 +73,6 @@ class Cache with Logging {
AccessToken accessTokenFacebook;
Customer userLoggedIn;
String firebaseUid;
String facebookUid;
bool hasPurchased = false;
@@ -201,7 +200,7 @@ class Cache with Logging {
Future<SharedPreferences> prefs = SharedPreferences.getInstance();
userLoggedIn = customer;
final String uid = Cache().firebaseUid == null ? Cache().facebookUid : Cache().firebaseUid;
final String uid = Cache().firebaseUid;
await setPreferences(prefs, SharePrefsChange.registration, customer.customerId, uid);
}
@@ -219,7 +218,7 @@ class Cache with Logging {
afterFacebookLogin() async {
Future<SharedPreferences> prefs = SharedPreferences.getInstance();
await setPreferences(prefs, SharePrefsChange.login, userLoggedIn.customerId, Cache().facebookUid);
await setPreferences(prefs, SharePrefsChange.login, userLoggedIn.customerId, Cache().firebaseUid);
}
logout() async {
@@ -457,7 +456,4 @@ class Cache with Logging {
AccessToken get getAccessTokenFacebook => accessTokenFacebook;
set setAccessTokenFacebook(AccessToken accessTokenFacebook) => this.accessTokenFacebook = accessTokenFacebook;
String get getFacebookUid => facebookUid;
set setFacebookUid(String facebookUid) => this.facebookUid = facebookUid;
}
+153 -12
View File
@@ -2,8 +2,12 @@ import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/model/user.dart';
import 'package:aitrainer_app/service/customer_service.dart';
import 'package:aitrainer_app/service/firebase_api.dart';
import 'package:aitrainer_app/service/logging.dart';
import 'package:aitrainer_app/util/not_found_exception.dart';
import 'package:firebase_auth/firebase_auth.dart' as auth;
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
class UserRepository {
class UserRepository with Logging {
User user;
UserRepository() {
@@ -31,29 +35,166 @@ class UserRepository {
await CustomerApi().addUser(modelUser);
}
} catch (e) {
print(e.toString());
log(e.toString());
throw new Exception(e);
}
}
Future<void> addUserFB() async {
final User modelUser = this.user;
try {
Map<String, dynamic> userData = await FirebaseApi().registerWithFacebook();
if (userData != null) {
modelUser.email = userData['email'];
if (modelUser.email == null) {
throw new Exception("Facebook signup was not successful. Please try another method");
}
modelUser.password = Cache().firebaseUid;
modelUser.firebaseUid = Cache().firebaseUid;
await CustomerApi().addUser(modelUser);
} else {
throw new Exception("Facebook signup was not successful. Please try another method");
}
} on auth.FirebaseAuthException catch (e) {
if (e.code == 'email-already-in-use') {
log('The account already exists for that email.');
throw Exception("The email address has been registered already");
}
} on WorkoutTestException catch (ex) {
if (ex.code == WorkoutTestException.CUSTOMER_EXISTS) {
log('The account already exists for that email.');
throw Exception("The email address has been registered already");
}
} on Exception catch (ex) {
log("Google exception: " + ex.toString());
throw Exception("Google Sign In failed");
}
}
Map<String, dynamic> userData = await FirebaseApi().signInWithFacebook();
if (userData != null) {
modelUser.email = userData['email'];
modelUser.password = "1234567";
modelUser.firebaseUid = Cache().facebookUid;
await CustomerApi().addUser(modelUser);
Future<void> addUserGoogle() async {
final User modelUser = this.user;
try {
Map<String, dynamic> userData = await FirebaseApi().registerWithGoogle();
if (userData != null) {
modelUser.email = userData['email'];
if (modelUser.email == null) {
throw new Exception("Google signup was not successful. Please try another method");
}
modelUser.password = Cache().firebaseUid;
modelUser.firebaseUid = Cache().firebaseUid;
await CustomerApi().addUser(modelUser);
} else {
throw new Exception("Google signup was not successful. Please try another method");
}
} on auth.FirebaseAuthException catch (e) {
if (e.code == 'email-already-in-use') {
log('The account already exists for that email.');
throw Exception("The email address has been registered already");
}
} on WorkoutTestException catch (ex) {
if (ex.code == WorkoutTestException.CUSTOMER_EXISTS) {
log('The account already exists for that email.');
throw Exception("The email address has been registered already");
}
} on Exception catch (ex) {
log("Google exception: " + ex.toString());
throw Exception("Google Sign In failed");
}
}
Future<void> addUserApple() async {
final User modelUser = this.user;
try {
Map<String, dynamic> userData = await FirebaseApi().registerWithApple();
if (userData != null) {
modelUser.email = userData['email'];
if (modelUser.email == null) {
throw new Exception("Apple signup was not successful. Please try another method");
}
modelUser.password = Cache().firebaseUid;
modelUser.firebaseUid = Cache().firebaseUid;
await CustomerApi().addUser(modelUser);
} else {
throw new Exception("Apple signup was not successful. Please try another method");
}
} on auth.FirebaseAuthException catch (e) {
if (e.code == 'email-already-in-use') {
log('The account already exists for that email.');
throw Exception("The email address has been registered already");
}
} on WorkoutTestException catch (ex) {
if (ex.code == WorkoutTestException.CUSTOMER_EXISTS) {
log('The account already exists for that email.');
throw Exception("The email address has been registered already");
}
} on Exception catch (ex) {
log("Apple exception: " + ex.toString());
throw Exception(ex);
}
}
Future<void> getUserByFB() async {
final User modelUser = this.user;
Map<String, dynamic> userData = await FirebaseApi().signInWithFacebook();
try {
Map<String, dynamic> userData = await FirebaseApi().signInWithFacebook();
if (userData == null) {
throw new Exception("Facebook login was not successful");
}
modelUser.email = userData['email'];
await CustomerApi().getUserByEmail(modelUser.email);
await Cache().afterFirebaseLogin();
} on FacebookAuthException catch (e) {
switch (e.errorCode) {
case FacebookAuthErrorCode.OPERATION_IN_PROGRESS:
throw Exception("You have a previous Facebook login operation in progress");
break;
case FacebookAuthErrorCode.CANCELLED:
throw Exception("Facebook login cancelled");
break;
case FacebookAuthErrorCode.FAILED:
throw Exception("Facebook login failed");
break;
}
} on NotFoundException catch (ex) {
log("FB exception: " + ex.toString());
throw Exception("Customer does not exist or the password is wrong");
} on Exception catch (e) {
log(e.toString());
throw new Exception(e);
}
}
Future<void> getUserByGoogle() async {
final User modelUser = this.user;
Map<String, dynamic> userData = await FirebaseApi().signInWithGoogle();
if (userData == null || userData['email'] == null) {
throw new Exception("Google login was not successful");
}
modelUser.email = userData['email'];
await CustomerApi().getUserByEmail(modelUser.email);
await Cache().afterFacebookLogin();
try {
await CustomerApi().getUserByEmail(modelUser.email);
await Cache().afterFirebaseLogin();
} on Exception catch (ex) {
log("Google exception: " + ex.toString());
throw Exception("Customer does not exist or the password is wrong");
}
}
Future<void> getUserByApple() async {
final User modelUser = this.user;
Map<String, dynamic> userData = await FirebaseApi().signInWithApple();
if (userData == null || userData['email'] == null) {
throw new Exception("Apple login was not successful");
}
modelUser.email = userData['email'];
try {
await CustomerApi().getUserByEmail(modelUser.email);
await Cache().afterFirebaseLogin();
} on Exception catch (ex) {
log("Apple exception: " + ex.toString());
throw Exception("Customer does not exist or the password is wrong");
}
}
Future<void> getUser() async {
@@ -64,7 +205,7 @@ class UserRepository {
await CustomerApi().getUserByEmail(modelUser.email);
await Cache().afterFirebaseLogin();
} else {
print("Exception: user not found or password is wrong");
log("Exception: user not found or password is wrong");
throw Exception("Customer does not exist or the password is wrong");
}
}
+6 -1
View File
@@ -7,6 +7,7 @@ import 'package:aitrainer_app/model/user.dart';
import 'package:aitrainer_app/service/api.dart';
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/service/logging.dart';
import 'package:aitrainer_app/util/not_found_exception.dart';
class CustomerApi with Logging {
final APIClient _client = new APIClient();
@@ -44,12 +45,16 @@ class CustomerApi with Logging {
try {
int status = jsonDecode(responseBody)['status'];
if (status != null) {
throw new Exception(jsonDecode(responseBody)['error']);
String error = jsonDecode(responseBody)['error'];
throw new Exception(error);
} else {
customer = Customer.fromJson(jsonDecode(responseBody));
await Cache().afterRegistration(customer);
}
} on FormatException {
if (responseBody == "Customer exists") {
throw WorkoutTestException(code: WorkoutTestException.CUSTOMER_EXISTS, message: responseBody);
}
throw new Exception(responseBody);
}
}
+197 -27
View File
@@ -1,10 +1,13 @@
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/service/logging.dart';
import 'package:aitrainer_app/service/logging.dart' as logging;
import 'package:apple_sign_in/apple_sign_in.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
class FirebaseApi with Logging {
class FirebaseApi with logging.Logging {
bool appleSignInAvailable = false;
static FirebaseApi _instance;
static final FirebaseAuth auth = FirebaseAuth.instance;
@@ -28,6 +31,7 @@ class FirebaseApi with Logging {
try {
// Wait for Firebase to initialize and set `_initialized` state to true
await Firebase.initializeApp();
this.appleSignInAvailable = await AppleSignIn.isAvailable();
} catch (e) {
// Set `_error` state to true if Firebase initialization fails
log("Error initializing Firebase");
@@ -81,34 +85,200 @@ class FirebaseApi with Logging {
return rc;
}
Future<Map<String, dynamic>> signInWithApple() async {
Map<String, dynamic> userData = Map();
final AuthorizationResult result = await AppleSignIn.performRequests([
AppleIdRequest(requestedScopes: [Scope.email, Scope.fullName])
]);
switch (result.status) {
case AuthorizationStatus.authorized:
print('User authorized');
break;
case AuthorizationStatus.error:
print('User error');
throw Exception("Apple Sign-In failed");
break;
case AuthorizationStatus.cancelled:
print('User cancelled');
throw Exception("Apple Sign-In cancelled");
break;
}
// Create an `OAuthCredential` from the credential returned by Apple.
final oauthCredential = OAuthProvider("apple.com").credential(
idToken: String.fromCharCodes(result.credential.identityToken),
accessToken: String.fromCharCodes(result.credential.authorizationCode));
// Sign in the user with Firebase. If the nonce we generated earlier does
// not match the nonce in `appleCredential.identityToken`, sign in will fail.
UserCredential userCredential = await FirebaseAuth.instance.signInWithCredential(oauthCredential);
log("userCredential: " + userCredential.toString());
log("Apple Credentials: " +
result.credential.user.toString() +
" state " +
result.credential.state.toString() +
" email " +
userCredential.user.email);
userData['email'] = userCredential.user.email;
return userData;
}
Future<Map<String, dynamic>> registerWithApple() async {
Map<String, dynamic> userData = Map();
final AuthorizationResult result = await AppleSignIn.performRequests([
AppleIdRequest(requestedScopes: [Scope.email, Scope.fullName])
]);
switch (result.status) {
case AuthorizationStatus.authorized:
print('Apple User authorized');
break;
case AuthorizationStatus.error:
print('Apple User error');
throw Exception("Apple Sign-In failed");
break;
case AuthorizationStatus.cancelled:
print('User cancelled');
throw Exception("Apple Sign-In cancelled");
break;
}
// Create an `OAuthCredential` from the credential returned by Apple.
final oauthCredential = OAuthProvider("apple.com").credential(
idToken: String.fromCharCodes(result.credential.identityToken),
accessToken: String.fromCharCodes(result.credential.authorizationCode));
// Sign in the user with Firebase. If the nonce we generated earlier does
// not match the nonce in `appleCredential.identityToken`, sign in will fail.
UserCredential userCredential = await FirebaseAuth.instance.signInWithCredential(oauthCredential);
Cache().firebaseUid = userCredential.user.uid;
userData['email'] = userCredential.user.email;
return userData;
}
Future<Map<String, dynamic>> signInWithGoogle() async {
Map<String, dynamic> userData = Map();
try {
// Trigger the authentication flow
GoogleSignIn _googleSignIn = GoogleSignIn(
scopes: [
'email',
'https://www.googleapis.com/auth/contacts.readonly',
],
);
final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
if (googleUser == null) {
throw Exception("Google Sign In failed");
}
// Obtain the auth details from the request
final GoogleSignInAuthentication googleAuth = await googleUser.authentication;
// Create a new credential
final GoogleAuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
await FirebaseAuth.instance.signInWithCredential(credential);
if (googleUser != null) {
log("GoogleUser: " + googleUser.toString());
userData['email'] = googleUser.email;
userData['id'] = googleUser.id;
userData['name'] = googleUser.displayName;
}
} on Exception catch (ex) {
log("Google exception: " + ex.toString());
throw Exception("Google Sign In failed");
}
return userData;
}
Future<Map<String, dynamic>> registerWithGoogle() async {
Map<String, dynamic> userData = Map();
// Trigger the authentication flow
GoogleSignIn _googleSignIn = GoogleSignIn(
scopes: [
'email',
'https://www.googleapis.com/auth/contacts.readonly',
],
);
final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
if (googleUser == null) {
throw Exception("Google Sign In failed");
}
// Obtain the auth details from the request
final GoogleSignInAuthentication googleAuth = await googleUser.authentication;
// Create a new credential
final GoogleAuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
final userCredential = await FirebaseAuth.instance.signInWithCredential(credential);
log("Google credentials: " + credential.toString() + " GoogleUser: " + googleUser.toString());
Cache().firebaseUid = userCredential.user.uid;
userData['email'] = googleUser.email;
return userData;
}
Future<Map<String, dynamic>> signInWithFacebook() async {
Map<String, dynamic> userData;
try {
// by default the login method has the next permissions ['email','public_profile']
AccessToken accessToken = await FacebookAuth.instance.login();
if (accessToken != null) {
log(accessToken.toJson().toString());
Cache().accessTokenFacebook = accessToken;
// get the user data
userData = await FacebookAuth.instance.getUserData();
Cache().facebookUid = userData['id'];
log(userData.toString());
} else {
throw Exception("Facebook login was not successful");
}
} on FacebookAuthException catch (e) {
switch (e.errorCode) {
case FacebookAuthErrorCode.OPERATION_IN_PROGRESS:
throw Exception("You have a previous Facebook login operation in progress");
break;
case FacebookAuthErrorCode.CANCELLED:
throw Exception("Facebook login cancelled");
break;
case FacebookAuthErrorCode.FAILED:
throw Exception("Facebook login failed");
break;
}
// by default the login method has the next permissions ['email','public_profile']
AccessToken accessToken = await FacebookAuth.instance.login();
if (accessToken != null) {
log(accessToken.toJson().toString());
Cache().accessTokenFacebook = accessToken;
// get the user data
userData = await FacebookAuth.instance.getUserData();
Cache().firebaseUid = userData['id'];
log(userData.toString());
} else {
throw Exception("Facebook login was not successful");
}
return userData;
}
Future<Map<String, dynamic>> registerWithFacebook() async {
Map<String, dynamic> userData;
// by default the login method has the next permissions ['email','public_profile']
AccessToken accessToken = await FacebookAuth.instance.login();
if (accessToken != null) {
Cache().accessTokenFacebook = accessToken;
// get the user data
userData = await FacebookAuth.instance.getUserData();
// Create a credential from the access token
final FacebookAuthCredential facebookAuthCredential = FacebookAuthProvider.credential(accessToken.token);
// Once signed in, return the UserCredential
final userCredential = await FirebaseAuth.instance.signInWithCredential(facebookAuthCredential);
Cache().firebaseUid = userCredential.user.uid;
log(userData.toString());
} else {
throw Exception("Facebook login was not successful");
}
return userData;
}
+10 -1
View File
@@ -1,4 +1,13 @@
class NotFoundException implements Exception {
final String message;
const NotFoundException({this.message});
}
}
class WorkoutTestException implements Exception {
static const String CUSTOMER_EXISTS = "customer-exists";
static const String LOGIN_CANCELLED = "login-cancelled";
final String message;
final String code;
const WorkoutTestException({this.message, this.code});
}
+3 -10
View File
@@ -1,6 +1,6 @@
import 'dart:async';
import 'dart:io';
/*
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/model/product.dart';
import 'package:aitrainer_app/service/logging.dart';
@@ -21,15 +21,7 @@ class PlatformPurchaseApi with Logging {
List getProductList() => _productList;
/* Platform.isAndroid
? [
'android.test.purchased',
'point_1000',
'5000_point',
'android.test.canceled',
]
: ['com.cooni.point1000', 'com.cooni.point5000'];
*/
factory PlatformPurchaseApi() {
return _singleton;
@@ -167,3 +159,4 @@ class PlatformPurchaseApi with Logging {
this._purchases = items;
}
}
*/
+9 -7
View File
@@ -10,6 +10,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:modal_progress_hud/modal_progress_hud.dart';
// ignore: must_be_immutable
class CustomerExerciseDevicePage extends StatelessWidget with Trans {
@@ -37,19 +38,20 @@ class CustomerExerciseDevicePage extends StatelessWidget with Trans {
..add(CustomerExerciseDeviceLoad()),
child: BlocConsumer<CustomerExerciseDeviceBloc, CustomerExerciseDeviceState>(
listener: (context, state) {
if (state is CustomerExerciseDeviceLoading) {
Scaffold.of(context).showSnackBar(SnackBar(
duration: Duration(milliseconds: 100),
backgroundColor: Colors.transparent,
content: Container(child: Center(child: CircularProgressIndicator()))));
} else if (state is CustomerExerciseDeviceError) {
if (state is CustomerExerciseDeviceError) {
Scaffold.of(context).showSnackBar(
SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white))));
}
},
builder: (context, state) {
final bloc = BlocProvider.of<CustomerExerciseDeviceBloc>(context);
return getPage(bloc, cWidth, cHeight);
return ModalProgressHUD(
child: getPage(bloc, cWidth, cHeight),
inAsyncCall: state is CustomerExerciseDeviceLoading,
opacity: 0.5,
color: Colors.black54,
progressIndicator: CircularProgressIndicator(),
);
},
))));
}
+33 -20
View File
@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:aitrainer_app/bloc/account/account_bloc.dart';
import 'package:aitrainer_app/bloc/login/login_bloc.dart';
import 'package:aitrainer_app/localization/app_localization.dart';
@@ -68,29 +70,40 @@ class LoginPage extends StatelessWidget with Trans {
return Form(
key: _scaffoldKey,
child: Container(
padding: const EdgeInsets.only(left: 15, right: 50),
child: ListView(shrinkWrap: false, padding: EdgeInsets.only(top: 100.0), children: <Widget>[
FlatButton(
child: new Image.asset(
'asset/image/login_fb.png',
width: MediaQuery.of(context).size.width * .85,
),
onPressed: () => {loginBloc.add(LoginFB())},
),
Text(AppLocalizations.of(context).translate("OR")),
Divider(),
padding: const EdgeInsets.only(left: 20, right: 20),
child: ListView(shrinkWrap: false, padding: EdgeInsets.only(top: 150.0), children: <Widget>[
ListTile(title: Text(t("Login"), style: GoogleFonts.inter())),
Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
new InkWell(
child: new Text(AppLocalizations.of(context).translate('Login'),
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 24)),
FlatButton(
child: Image.asset(
'asset/image/button_fb.png',
width: 60,
),
onPressed: () => {loginBloc.add(LoginFB())},
),
FlatButton(
child: Image.asset(
'asset/image/button_google.png',
width: 60,
),
onPressed: () => {loginBloc.add(LoginGoogle())},
),
Platform.isIOS
? FlatButton(
child: Image.asset(
'asset/image/button_apple.png',
width: 60,
),
onPressed: () => {loginBloc.add(LoginApple())},
)
: Offstage(),
],
),
Divider(
color: Colors.transparent,
),
Divider(),
ListTile(title: Text(t("OR"), style: GoogleFonts.inter())),
Divider(),
TextFormField(
key: LibraryKeys.loginEmailField,
decoration: InputDecoration(
@@ -118,7 +131,7 @@ class LoginPage extends StatelessWidget with Trans {
),
TextFormField(
key: LibraryKeys.loginPasswordField,
obscureText: true,
obscureText: loginBloc.obscure,
decoration: InputDecoration(
labelStyle: TextStyle(fontSize: 14),
contentPadding: EdgeInsets.only(left: 15, top: 15, bottom: 15),
@@ -168,7 +181,7 @@ class LoginPage extends StatelessWidget with Trans {
),
Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: <Widget>[
InkWell(
child: Text(AppLocalizations.of(context).translate('SignUp')),
child: Text(AppLocalizations.of(context).translate('SignUpLink')),
onTap: () => Navigator.of(context).pushNamed('registration'),
),
Spacer(flex: 2),
+51 -33
View File
@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:aitrainer_app/bloc/account/account_bloc.dart';
import 'package:aitrainer_app/bloc/login/login_bloc.dart';
import 'package:aitrainer_app/localization/app_localization.dart';
@@ -11,7 +13,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:modal_progress_hud/modal_progress_hud.dart';
import 'package:toggle_switch/toggle_switch.dart';
import '../library_keys.dart';
@@ -74,28 +75,48 @@ class RegistrationPage extends StatelessWidget with Trans {
return Form(
key: _scaffoldKey,
child: Container(
padding: const EdgeInsets.only(left: 15, right: 50),
padding: const EdgeInsets.only(left: 20, right: 20),
child: ListView(shrinkWrap: false, padding: EdgeInsets.only(top: 150.0), children: <Widget>[
FlatButton(
child: Image.asset(
'asset/image/fb_registration.png',
width: MediaQuery.of(context).size.width * .85,
),
onPressed: () => {loginBloc.add(RegistrationFB())},
),
ListTile(title: Text(AppLocalizations.of(context).translate("OR"))),
ListTile(title: Text(t("SignUp"), style: GoogleFonts.inter())),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FlatButton(
child: Image.asset(
'asset/image/button_fb.png',
width: 60,
),
onPressed: () => {loginBloc.add(RegistrationFB())},
),
FlatButton(
child: Image.asset(
'asset/image/button_google.png',
width: 60,
),
onPressed: () => {loginBloc.add(RegistrationGoogle())},
),
Platform.isIOS
? FlatButton(
child: Image.asset(
'asset/image/button_apple.png',
width: 60,
),
onPressed: () => {loginBloc.add(RegistrationApple())},
)
: Offstage(),
],
),
ListTile(title: Text(t("OR"), style: GoogleFonts.inter())),
/* Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
InkWell(
child:
Text(AppLocalizations.of(context).translate('SignUp'), style: TextStyle(fontWeight: FontWeight.bold, fontSize: 24)),
child: Text(AppLocalizations.of(context).translate('SignUp with Email'),
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 24)),
),
],
),
Divider(
color: Colors.transparent,
),
), */
TextFormField(
key: LibraryKeys.loginEmailField,
decoration: InputDecoration(
@@ -123,7 +144,7 @@ class RegistrationPage extends StatelessWidget with Trans {
),
TextFormField(
key: LibraryKeys.loginPasswordField,
obscureText: true,
obscureText: loginBloc.obscure,
decoration: InputDecoration(
labelStyle: TextStyle(fontSize: 14),
contentPadding: EdgeInsets.only(left: 15, top: 15, bottom: 15),
@@ -153,6 +174,9 @@ class RegistrationPage extends StatelessWidget with Trans {
color: Colors.transparent,
),
getDataProtection(loginBloc),
Divider(
color: Colors.transparent,
),
Row(mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[
FlatButton(
key: LibraryKeys.loginOKButton,
@@ -196,22 +220,16 @@ class RegistrationPage extends StatelessWidget with Trans {
}
Widget getDataProtection(LoginBloc loginBloc) {
return ListTile(
subtitle: Text(t("Please accept our data protection policy. For more information please click on 'Privacy'")),
title: ToggleSwitch(
minWidth: 100.0,
minHeight: 30.0,
fontSize: 14.0,
initialLabelIndex: loginBloc.dataPolicyAllowed ? 0 : 1,
activeBgColor: Colors.indigo,
activeFgColor: Colors.white,
inactiveBgColor: Colors.white30,
inactiveFgColor: Colors.black,
labels: [t('Yes'), t('No')],
onToggle: (index) {
loginBloc.add(DataProtectionClicked(marked: index == 0));
},
),
return CheckboxListTile(
title: Text(t("Please accept our data protection policy.")),
subtitle: Text(t("For more information please click on 'Privacy'")),
dense: true,
value: loginBloc.dataPolicyAllowed,
activeColor: Colors.indigo,
onChanged: (value) {
loginBloc.add(DataProtectionClicked(marked: value));
},
controlAffinity: ListTileControlAffinity.leading, // <-- leading Checkbox
);
}
}
+76 -73
View File
@@ -12,7 +12,7 @@ class DialogPremium extends StatefulWidget {
String description, function, unlockedText;
final int unlockRound;
final bool unlocked;
bool unlocked;
DialogPremium(
{Key key,
@@ -27,6 +27,8 @@ class DialogPremium extends StatefulWidget {
description = description ?? "";
function = function ?? "";
unlockedText = unlockedText ?? "";
unlocked = true;
}
@override
@@ -88,9 +90,9 @@ class _DialogPremiumState extends State<DialogPremium> with Trans {
alignment: AlignmentDirectional.topEnd,
children: [
Text(
t("Go Premium") + " ",
widget.unlocked ? t("Keep testing") : t("Go Premium") + " ",
style: GoogleFonts.archivoBlack(
fontSize: 24,
fontSize: widget.unlocked ? 18 : 24,
color: Colors.yellow[400],
shadows: <Shadow>[
Shadow(
@@ -106,32 +108,34 @@ class _DialogPremiumState extends State<DialogPremium> with Trans {
],
),
),
Positioned(
right: 3,
top: 0,
child: AnimatedSwitcher(
duration: Duration(milliseconds: 900),
//reverseDuration: Duration(milliseconds: 200),
transitionBuilder: (Widget child, Animation<double> animation) {
return FadeTransition(child: child, opacity: animation);
},
child: isStart
? Icon(
CustomIcon.star_2,
color: Colors.yellow[300],
)
: Offstage() /* Icon(
!widget.unlocked
? Positioned(
right: 3,
top: 0,
child: AnimatedSwitcher(
duration: Duration(milliseconds: 900),
//reverseDuration: Duration(milliseconds: 200),
transitionBuilder: (Widget child, Animation<double> animation) {
return FadeTransition(child: child, opacity: animation);
},
child: isStart
? Icon(
CustomIcon.star_2,
color: Colors.yellow[300],
)
: Offstage() /* Icon(
CustomIcon.exclamation_circle,
color: Colors.yellow[300],
) */
)),
))
: Offstage(),
],
),
SizedBox(
height: 35,
),
Text(
t("Unleash your potential with WorkoutTest Premium!"),
widget.unlocked ? "" : t("Unleash your potential with WorkoutTest Premium!"),
style: GoogleFonts.inter(
fontSize: 14,
color: Colors.white,
@@ -160,7 +164,7 @@ class _DialogPremiumState extends State<DialogPremium> with Trans {
Align(
alignment: Alignment.center,
child: GestureDetector(
onTap: () => Navigator.of(context).pushNamed("salesPage"),
onTap: () => widget.unlocked ? Navigator.of(context).pop() : Navigator.of(context).pushNamed("salesPage"),
child: Stack(
alignment: Alignment.center,
children: [
@@ -222,61 +226,60 @@ class _DialogPremiumState extends State<DialogPremium> with Trans {
List<TextSpan> getDescriptionText() {
List<TextSpan> list = List();
if (widget.unlocked) {
/* if (widget.unlocked) {
list.add(TextSpan(text: widget.unlockedText));
} else {
list.add(TextSpan(text: t("The")));
list.add(TextSpan(text: t(" ")));
list.add(
TextSpan(
text: t(widget.function),
style: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.yellow[300],
shadows: <Shadow>[
Shadow(
offset: Offset(5.0, 5.0),
blurRadius: 12.0,
color: Colors.black54,
),
Shadow(
offset: Offset(-3.0, 3.0),
blurRadius: 12.0,
color: Colors.black54,
),
],
),
} */
list.add(TextSpan(text: t("The")));
list.add(TextSpan(text: t(" ")));
list.add(
TextSpan(
text: t(widget.function),
style: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.yellow[300],
shadows: <Shadow>[
Shadow(
offset: Offset(5.0, 5.0),
blurRadius: 12.0,
color: Colors.black54,
),
Shadow(
offset: Offset(-3.0, 3.0),
blurRadius: 12.0,
color: Colors.black54,
),
],
),
);
list.add(TextSpan(text: t(" ")));
list.add(TextSpan(text: t("feature is reachable after you finished")));
list.add(TextSpan(text: t(" ")));
list.add(
TextSpan(
text: widget.unlockRound == 1 ? t("the first") : t("the second"),
style: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.yellow[300],
shadows: <Shadow>[
Shadow(
offset: Offset(5.0, 5.0),
blurRadius: 12.0,
color: Colors.black54,
),
Shadow(
offset: Offset(-3.0, 3.0),
blurRadius: 12.0,
color: Colors.black54,
),
],
),
),
);
list.add(TextSpan(text: t(" ")));
list.add(TextSpan(text: t("feature is reachable after you finished")));
list.add(TextSpan(text: t(" ")));
list.add(
TextSpan(
text: widget.unlockRound == 1 ? t("the first") : t("the second"),
style: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.yellow[300],
shadows: <Shadow>[
Shadow(
offset: Offset(5.0, 5.0),
blurRadius: 12.0,
color: Colors.black54,
),
Shadow(
offset: Offset(-3.0, 3.0),
blurRadius: 12.0,
color: Colors.black54,
),
],
),
);
list.add(TextSpan(text: t(" ")));
list.add(TextSpan(text: t("100% test circles")));
}
),
);
list.add(TextSpan(text: t(" ")));
list.add(TextSpan(text: t("100% test circles")));
return list;
}
+1 -1
View File
@@ -91,6 +91,6 @@ class _HomePageState extends State<AitrainerHome> with Logging {
@override
void dispose() async {
super.dispose();
await PlatformPurchaseApi().close();
//await PlatformPurchaseApi().close();
}
}
+4 -4
View File
@@ -54,7 +54,7 @@ class ImageButton extends StatelessWidget {
//print("Top: " + top.toStringAsFixed(0) + " length: " + ((style.fontSize - 5) * text.length).toString());
}
final double width = MediaQuery.of(context).size.width;
print("Mediawidth: " + width.toStringAsFixed(0));
//print("Mediawidth: " + width.toStringAsFixed(0));
return Stack(alignment: AlignmentDirectional.bottomStart, children: [
FlatButton(
child: image == null
@@ -152,11 +152,11 @@ class ImageButton extends StatelessWidget {
imageName,
fit: BoxFit.fitWidth,
alignment: Alignment.center,
errorBuilder: (context, error, stackTrace) {
String url = Cache.mediaUrl + '/' + imageName; //.substring(11);
/* errorBuilder: (context, error, stackTrace) {
String url = Cache.mediaUrl + imageName; //.substring(11);
Widget image = FadeInImage.assetNetwork(placeholder: 'asset/image/dots.gif', image: url, height: this.height);
return image;
},
}, */
);
} on Exception catch (_) {
String url = Cache.mediaUrl + '/images/' + imageName;