Skip to content

Commit

Permalink
add unit test to login, portal and register page
Browse files Browse the repository at this point in the history
  • Loading branch information
ezzabuzaid committed Nov 23, 2019
1 parent ee0cb4b commit 417bf1b
Show file tree
Hide file tree
Showing 52 changed files with 948 additions and 444 deletions.
25 changes: 25 additions & 0 deletions android/app/src/main/AndroidManifest.xml
@@ -1,5 +1,30 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.learning_flutter">
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.WRITE_CALL_LOG" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<uses-permission android:name="android.permission.ADD_VOICEMAIL" />
<uses-permission android:name="android.permission.USE_SIP" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
<uses-permission android:name="android.permission.BODY_SENSORS" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" />
<uses-permission android:name="android.permission.RECEIVE_MMS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
Expand Down
16 changes: 16 additions & 0 deletions ios/Runner/Info.plist
Expand Up @@ -27,6 +27,22 @@
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<key>NSCalendarsUsageDescription</key>
<string>Your prompt</string>
<key>NSCameraUsageDescription</key>
<string>Your prompt</string>
<key>NSContactsUsageDescription</key>
<string>Your prompt</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Your prompt</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Your prompt</string>
<key>NSMicrophoneUsageDescription</key>
<string>Your prompt</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Your prompt</string>
<key>NSRemindersUsageDescription</key>
<string>Your prompt</string>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
Expand Down
62 changes: 35 additions & 27 deletions lib/app/app.dart
@@ -1,20 +1,21 @@
import 'package:flutter/material.dart';
import 'package:learning_flutter/app/core/constants/index.dart';
import 'package:learning_flutter/app/core/helpers/logger.dart';
import 'package:learning_flutter/app/core/helpers/storage.dart';
import 'package:learning_flutter/app/locator.dart';
import 'package:learning_flutter/app/pages/home/home.view.dart';
import 'package:learning_flutter/app/pages/meals/melas.view.dart';
import 'package:learning_flutter/app/pages/portal/portal.view.dart';
import 'package:learning_flutter/app/pages/settings/index.dart';
import 'package:learning_flutter/app/routes.dart';
import 'package:learning_flutter/app/shared/services/user/user.service.dart';
import 'package:provider/provider.dart';

class App extends StatelessWidget {
// TODO: theme functions should be moved to another class
static final settingBloc = locator<SettingsBloc>();

App({Key key}) : super(key: key) {
settingBloc.getSettings();
// settingBloc.getSettings();
}

ThemeData lightTheme(BuildContext context) {
Expand Down Expand Up @@ -51,31 +52,38 @@ class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: The setting should be available after user is logged in
return ChangeNotifierProvider<SettingsBloc>(
builder: (_) => settingBloc,
child: Selector<SettingsBloc, bool>(
selector: (buildContext, bloc) {
// TODO: there's an error that the settings is und
return bloc.settings.darkMode;
},
builder: (context, darkMode, child) {
return MaterialApp(
theme: prepareTheme(context, darkMode),
title: AppplicationConstants.appName,
supportedLocales: [
// TODO: setup localization
const Locale('en'),
const Locale('ar'),
],
routes: routes,
home: FutureBuilder(
future: User().isAuthenticated(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return snapshot.data == true ? HomeView() : PortalView();
},
),
);
},
// return ChangeNotifierProvider<SettingsBloc>(
// builder: (_) => settingBloc,
// child: Selector<SettingsBloc, bool>(
// selector: (buildContext, bloc) {
// return bloc.settings.darkMode;
// },
// builder: (context, darkMode, child) {

// },
// ),
// );
return MaterialApp(
theme: prepareTheme(context, false),
title: AppplicationConstants.appName,
supportedLocales: [
// TODO: setup localization
const Locale('en'),
const Locale('ar'),
],
routes: routes,
home: Scaffold(
body: FutureProvider<bool>(
updateShouldNotify: (z, x) => true,
catchError: (context, error) => false,
initialData: false,
builder: (BuildContext context) => locator<UserService>().isAuthenticated(),
child: Consumer<bool>(
builder: (context, isAuthenticated, child) {
return isAuthenticated ? HomeView() : PortalView();
},
),
),
),
);
}
Expand Down
30 changes: 15 additions & 15 deletions lib/app/core/auth.service.dart
@@ -1,5 +1,5 @@
import 'package:learning_flutter/app/core/helpers/logger.dart';
import 'package:local_auth/local_auth.dart';
// import 'package:local_auth/local_auth.dart';
import 'package:flutter/services.dart';
// FIXME biometric not working
class BugFix {
Expand All @@ -15,24 +15,24 @@ class BugFix {
// REVIEW Storage && Service Locator
@BugFix(number: 1000000000000000000000)
class LocalAuthenticationService {
final _auth = LocalAuthentication();
final canCheckBiometrics = LocalAuthentication().canCheckBiometrics;
bool isProtectionEnabled = false;
// final _auth = LocalAuthentication();
// final canCheckBiometrics = LocalAuthentication().canCheckBiometrics;
// bool isProtectionEnabled = false;

// NOTE: save this in the storage and let the user choice to use this authentication or not
bool isAuthenticated = false;

Future<void> authenticate() async {
if (isProtectionEnabled) {
try {
isAuthenticated = await _auth.authenticateWithBiometrics(
localizedReason: 'authenticate to access',
useErrorDialogs: true,
stickyAuth: true,
);
} on PlatformException catch (e) {
logger.e(e);
}
}
// if (isProtectionEnabled) {
// try {
// isAuthenticated = await _auth.authenticateWithBiometrics(
// localizedReason: 'authenticate to access',
// useErrorDialogs: true,
// stickyAuth: true,
// );
// } on PlatformException catch (e) {
// logger.e(e);
// }
// }
}
}
2 changes: 2 additions & 0 deletions lib/app/core/constants/app.constant.dart
@@ -1,4 +1,6 @@
class AppplicationConstants {
static final baseEndpoint = 'https://node-buildozer.herokuapp.com/api/';
static final appName = 'Learning Flutter';
static final tokenKey = 'token';

}
22 changes: 10 additions & 12 deletions lib/app/core/helpers/storage.dart
@@ -1,26 +1,24 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';

class Storage {
final _storage = FlutterSecureStorage();
factory Storage() => Storage._internal();
Storage._internal();
final _storage = SharedPreferences.getInstance();

/// Get specific field
Future<String> get(String key) {
return _storage.read(key: key);
Future<String> get(String key) async {
return (await _storage).getString(key);
}

Future<void> set(String key, String payload) {
return _storage.write(key: key, value: payload);
Future<void> set(String key, String payload) async {
return (await _storage).setString(key, payload);
}

/// Remove specific field
Future<void> remove(String key) {
return _storage.delete(key: key);
Future<void> remove(String key) async {
return (await _storage).remove(key);
}

/// Remove all storage
Future<void> clear(String key) {
return _storage.deleteAll();
Future<void> clear(String key) async {
return (await _storage).clear();
}
}
20 changes: 9 additions & 11 deletions lib/app/core/helpers/token.dart
@@ -1,20 +1,18 @@
import 'package:learning_flutter/app/core/constants/index.dart';
import 'package:learning_flutter/app/core/helpers/storage.dart';

const token_key = 'token';
import 'package:learning_flutter/app/locator.dart';

class TokenHelper {
factory TokenHelper() => const TokenHelper._internal();
const TokenHelper._internal();

Future<void> removeToken() async {
return await Storage().remove(token_key);
final _storage = locator<Storage>();
Future<void> removeToken() {
return _storage.remove(AppplicationConstants.tokenKey);
}

Future<void> addToken(String token) async {
return await Storage().set(token_key, token);
Future<void> setToken(String token) {
return _storage.set(AppplicationConstants.tokenKey, token);
}

Future<String> getToken() async {
return await Storage().get(token_key);
Future<String> getToken() {
return _storage.get(AppplicationConstants.tokenKey);
}
}
4 changes: 2 additions & 2 deletions lib/app/layout/navigation.dart
Expand Up @@ -126,7 +126,7 @@ class Navigation extends StatelessWidget {
),
Expanded(
child: FutureBuilder(
future: User().isAuthenticated(),
future: UserService().isAuthenticated(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.data == false) {
list.removeWhere((item) => item.needAuth);
Expand All @@ -152,7 +152,7 @@ class Navigation extends StatelessWidget {
),
),
FutureBuilder(
future: User().isAuthenticated(),
future: UserService().isAuthenticated(),
builder: (context, AsyncSnapshot<bool> snapshot) {
Widget widget;
if (snapshot.data == true) {
Expand Down
38 changes: 37 additions & 1 deletion lib/app/locator.dart
@@ -1,17 +1,53 @@
import 'package:geolocator/geolocator.dart';
import 'package:get_it/get_it.dart';
import 'package:learning_flutter/app/core/auth.service.dart';
import 'package:learning_flutter/app/core/helpers/storage.dart';
import 'package:learning_flutter/app/core/helpers/token.dart';
import 'package:learning_flutter/app/pages/home/home.bloc.dart';
import 'package:learning_flutter/app/pages/meals/index.dart';
import 'package:learning_flutter/app/pages/menus/index.dart';
import 'package:learning_flutter/app/pages/settings/settings.bloc.dart';
import 'package:learning_flutter/app/shared/models/portal.model.dart';
import 'package:learning_flutter/app/shared/services/menus.service.dart';
import 'package:learning_flutter/app/shared/services/user/user.service.dart';
import 'package:mockito/mockito.dart';

GetIt locator = GetIt();

// TODO: Create base class to include the child class in the locator directly
void setupLocator() {
// REVIEW Locator has one critical issue which is cannot know which class depends on another so it should be order depend on that
// TODO: try to make function like `locateMe()` inside each class that should be singelton
locator.registerSingleton(Storage());
locator.registerSingleton(TokenHelper());
locator.registerSingleton<UserService>(UserService());
locator.registerSingleton(Geolocator());
locator.registerSingleton(LocalAuthenticationService());
locator.registerSingleton(MenusService());
locator.registerSingleton(MealsService());
// FIXME: Bloc's shouldn't be used in get it
locator.registerSingleton(HomeBloc());
locator.registerSingleton(MealsBloc());
locator.registerSingleton(SettingsBloc());
}

class MockUserService extends Mock implements UserService {}
class MockMealsService extends Mock implements MealsService {}
class MockStorage extends Mock implements Storage {}
class MockMenusService extends Mock implements MenusService {}

void setupMockLocator() {
// locator.registerSingleton<Storage>(MockStorage());
// locator.registerSingleton(TokenHelper());
locator.registerSingleton<UserService>(MockUserService());
// locator.registerSingleton(Geolocator());
// locator.registerSingleton(LocalAuthenticationService());
// locator.registerSingleton<MenusService>(MenusService());
// locator.registerSingleton<MealsService>(MealsService());
// FIXME: Bloc's shouldn't be used in get it
// locator.registerSingleton(HomeBloc());
// locator.registerSingleton(MealsBloc());
// locator.registerSingleton(SettingsBloc());
// locator.registerSingleton(HomeBloc());
// locator.registerSingleton(MealsBloc());
// locator.registerSingleton(SettingsBloc());
}
4 changes: 2 additions & 2 deletions lib/app/pages/account/account.view.dart
Expand Up @@ -7,12 +7,12 @@ import '../../layout/toolbar.dart';

class _AccountBody extends StatelessWidget {
_AccountBody() {
User().setInformation(UserModel());
UserService().setInformation(UserModel());
}
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: User().getInformation(),
stream: UserService().getInformation(),
builder: (context, AsyncSnapshot<UserModel> snapshot) {
return Column(
children: <Widget>[
Expand Down
17 changes: 6 additions & 11 deletions lib/app/pages/face-detection/face-detection.dart
Expand Up @@ -4,22 +4,17 @@ import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:learning_flutter/app/core/helpers/logger.dart';
import 'package:learning_flutter/app/layout/index.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:permission/permission.dart';
import 'package:camera/camera.dart' as camera;

final streamController = StreamController();

Future<bool> checkAndRequestCameraPermissions() async {
final permissionHandler = PermissionHandler();
PermissionStatus permission = await permissionHandler.checkPermissionStatus(
PermissionGroup.camera,
);
if (permission != PermissionStatus.granted) {
final permissions = await permissionHandler.requestPermissions([
PermissionGroup.camera,
]);
logger.i("permissions ${permissions.toString()}");
return permissions[PermissionGroup.camera] == PermissionStatus.granted;
final permission =
await Permission.getSinglePermissionStatus(PermissionName.Camera);
if (permission != PermissionStatus.allow) {
final newPermission = await Permission.requestSinglePermission(PermissionName.Camera);
return newPermission == PermissionStatus.allow;
} else {
return true;
}
Expand Down
6 changes: 4 additions & 2 deletions lib/app/pages/home/home.bloc.dart
@@ -1,14 +1,16 @@
import 'package:flutter/material.dart';
import 'package:learning_flutter/app/locator.dart';
import 'package:learning_flutter/app/pages/meals/index.dart';
import 'package:learning_flutter/app/pages/meals/meals.model.dart';
import 'package:learning_flutter/app/pages/menus/menus.model.dart';
import 'package:learning_flutter/app/pages/menus/menus.service.dart';
import 'package:learning_flutter/app/shared/services/menus.service.dart';

class HomeBloc with ChangeNotifier {
int menuIndex = 0;
List<MenusModel> menus = [];
List<MealsModel> meals = [];

final menusService = locator<MenusService>();
final mealsService = locator<MealsService>();
changeMenu(int index) async {
menuIndex = index;
this.meals = await mealsService.fetchMealsByMenuId(menus[index].sId);
Expand Down

0 comments on commit 417bf1b

Please sign in to comment.