diff --git a/ios/Podfile b/ios/Podfile
new file mode 100644
index 00000000..d077b08b
--- /dev/null
+++ b/ios/Podfile
@@ -0,0 +1,69 @@
+# Uncomment this line to define a global platform for your project
+# platform :ios, '9.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+ 'Debug' => :debug,
+ 'Profile' => :release,
+ 'Release' => :release,
+}
+
+def parse_KV_file(file, separator='=')
+ file_abs_path = File.expand_path(file)
+ if !File.exists? file_abs_path
+ return [];
+ end
+ pods_ary = []
+ skip_line_start_symbols = ["#", "/"]
+ File.foreach(file_abs_path) { |line|
+ next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
+ plugin = line.split(pattern=separator)
+ if plugin.length == 2
+ podname = plugin[0].strip()
+ path = plugin[1].strip()
+ podpath = File.expand_path("#{path}", file_abs_path)
+ pods_ary.push({:name => podname, :path => podpath});
+ else
+ puts "Invalid plugin specification: #{line}"
+ end
+ }
+ return pods_ary
+end
+
+target 'Runner' do
+ # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
+ # referring to absolute paths on developers' machines.
+ system('rm -rf .symlinks')
+ system('mkdir -p .symlinks/plugins')
+
+ # Flutter Pods
+ generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig')
+ if generated_xcode_build_settings.empty?
+ puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first."
+ end
+ generated_xcode_build_settings.map { |p|
+ if p[:name] == 'FLUTTER_FRAMEWORK_DIR'
+ symlink = File.join('.symlinks', 'flutter')
+ File.symlink(File.dirname(p[:path]), symlink)
+ pod 'Flutter', :path => File.join(symlink, File.basename(p[:path]))
+ end
+ }
+
+ # Plugin Pods
+ plugin_pods = parse_KV_file('../.flutter-plugins')
+ plugin_pods.map { |p|
+ symlink = File.join('.symlinks', 'plugins', p[:name])
+ File.symlink(p[:path], symlink)
+ pod p[:name], :path => File.join(symlink, 'ios')
+ }
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ target.build_configurations.each do |config|
+ config.build_settings['ENABLE_BITCODE'] = 'NO'
+ end
+ end
+end
diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 00000000..949b6789
--- /dev/null
+++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ BuildSystemType
+ Original
+
+
diff --git a/lib/constants/app_theme.dart b/lib/constants/app_theme.dart
index 531d7dfc..fb2884be 100644
--- a/lib/constants/app_theme.dart
+++ b/lib/constants/app_theme.dart
@@ -23,16 +23,23 @@
import 'package:flutter/material.dart';
-final ThemeData themeData = new ThemeData(
+final ThemeData themeData = ThemeData(
fontFamily: 'ProductSans',
brightness: Brightness.light,
- primarySwatch: MaterialColor(
- AppColors.green[500].value, AppColors.green),
+ primarySwatch: MaterialColor(AppColors.green[500].value, AppColors.green),
primaryColor: AppColors.green[500],
primaryColorBrightness: Brightness.light,
accentColor: AppColors.green[500],
- accentColorBrightness: Brightness.light
-);
+ accentColorBrightness: Brightness.light);
+
+final ThemeData themeDataDark = ThemeData(
+ fontFamily: 'ProductSans',
+ brightness: Brightness.dark,
+ primarySwatch: MaterialColor(AppColors.green[900].value, AppColors.green),
+ primaryColor: AppColors.green[500],
+ primaryColorBrightness: Brightness.dark,
+ accentColor: AppColors.green[500],
+ accentColorBrightness: Brightness.dark);
class AppColors {
AppColors._(); // this basically makes it so you can instantiate this class
@@ -49,4 +56,4 @@ class AppColors {
800: const Color(0xFF76af60),
900: const Color(0xFF64a24d)
};
-}
\ No newline at end of file
+}
diff --git a/lib/constants/dimens.dart b/lib/constants/dimens.dart
index 20b774d9..d509316e 100644
--- a/lib/constants/dimens.dart
+++ b/lib/constants/dimens.dart
@@ -4,4 +4,7 @@ class Dimens {
//for all screens
static const double horizontal_padding = 12.0;
static const double vertical_padding = 12.0;
-}
\ No newline at end of file
+
+ static const double tablet_breakpoint = 720.0;
+ static const double tablet_list_width = 400.0;
+}
diff --git a/lib/constants/index.dart b/lib/constants/index.dart
new file mode 100644
index 00000000..961e1f8c
--- /dev/null
+++ b/lib/constants/index.dart
@@ -0,0 +1,2 @@
+export 'app_theme.dart';
+export 'dimens.dart';
diff --git a/lib/constants/strings.dart b/lib/constants/strings.dart
deleted file mode 100644
index 2c049d91..00000000
--- a/lib/constants/strings.dart
+++ /dev/null
@@ -1,12 +0,0 @@
-class Strings {
- Strings._();
-
- //General
- static const String appName = "Boilerplate Project";
-
- //Login
- static const String login_et_user_email = "Enter user email";
- static const String login_et_user_password = "Enter password";
- static const String login_btn_forgot_password = "Forgot Password?";
- static const String login_btn_sign_in = "Sign In";
-}
diff --git a/lib/data/index.dart b/lib/data/index.dart
new file mode 100644
index 00000000..e06c9336
--- /dev/null
+++ b/lib/data/index.dart
@@ -0,0 +1 @@
+export 'repository.dart';
diff --git a/lib/data/local/constants/index.dart b/lib/data/local/constants/index.dart
new file mode 100644
index 00000000..a8f34230
--- /dev/null
+++ b/lib/data/local/constants/index.dart
@@ -0,0 +1 @@
+export 'db_constants.dart';
diff --git a/lib/data/local/datasources/post/index.dart b/lib/data/local/datasources/post/index.dart
new file mode 100644
index 00000000..340edc22
--- /dev/null
+++ b/lib/data/local/datasources/post/index.dart
@@ -0,0 +1 @@
+export 'post_datasource.dart';
diff --git a/lib/data/local/datasources/post/post_datasource.dart b/lib/data/local/datasources/post/post_datasource.dart
index 543ea155..3e2e0d5e 100644
--- a/lib/data/local/datasources/post/post_datasource.dart
+++ b/lib/data/local/datasources/post/post_datasource.dart
@@ -85,4 +85,4 @@ class PostDataSource {
return post;
}).toList();
}
-}
\ No newline at end of file
+}
diff --git a/lib/data/local/index.dart b/lib/data/local/index.dart
new file mode 100644
index 00000000..b930557d
--- /dev/null
+++ b/lib/data/local/index.dart
@@ -0,0 +1 @@
+export 'app_database.dart';
diff --git a/lib/data/network/apis/posts/index.dart b/lib/data/network/apis/posts/index.dart
new file mode 100644
index 00000000..9d952e97
--- /dev/null
+++ b/lib/data/network/apis/posts/index.dart
@@ -0,0 +1 @@
+export 'post_api.dart';
diff --git a/lib/data/network/apis/posts/post_api.dart b/lib/data/network/apis/posts/post_api.dart
index 43d63c90..093974b3 100644
--- a/lib/data/network/apis/posts/post_api.dart
+++ b/lib/data/network/apis/posts/post_api.dart
@@ -25,14 +25,13 @@ class PostApi {
/// Returns list of post in response
Future getPosts() {
-
return _dioClient
.get(Endpoints.getPosts)
.then((dynamic res) => PostsList.fromJson(res))
.catchError((error) => throw error);
}
-/// sample api call with default rest client
+ /// sample api call with default rest client
// Future getPosts() {
//
// return _restClient
diff --git a/lib/data/network/constants/endpoints.dart b/lib/data/network/constants/endpoints.dart
index 25fe2584..e260482b 100644
--- a/lib/data/network/constants/endpoints.dart
+++ b/lib/data/network/constants/endpoints.dart
@@ -12,4 +12,4 @@ class Endpoints {
// booking endpoints
static const String getPosts = baseUrl + "/posts";
-}
\ No newline at end of file
+}
diff --git a/lib/data/network/constants/index.dart b/lib/data/network/constants/index.dart
new file mode 100644
index 00000000..bf657c52
--- /dev/null
+++ b/lib/data/network/constants/index.dart
@@ -0,0 +1 @@
+export 'endpoints.dart';
diff --git a/lib/data/network/dio_client.dart b/lib/data/network/dio_client.dart
index 7b2c3cfd..92cf2ad9 100644
--- a/lib/data/network/dio_client.dart
+++ b/lib/data/network/dio_client.dart
@@ -24,7 +24,6 @@ final Dio dio = new Dio()
}));
class DioClient {
-
// singleton object
static final DioClient _singleton = DioClient._();
@@ -37,7 +36,7 @@ class DioClient {
// Singleton accessor
static DioClient get instance => DioClient();
-
+
// Get:-----------------------------------------------------------------------
Future get(String uri) async {
try {
diff --git a/lib/data/network/exceptions/index.dart b/lib/data/network/exceptions/index.dart
new file mode 100644
index 00000000..608e3305
--- /dev/null
+++ b/lib/data/network/exceptions/index.dart
@@ -0,0 +1 @@
+export 'network_exceptions.dart';
diff --git a/lib/data/network/exceptions/network_exceptions.dart b/lib/data/network/exceptions/network_exceptions.dart
index f0ba1f8d..5400292e 100644
--- a/lib/data/network/exceptions/network_exceptions.dart
+++ b/lib/data/network/exceptions/network_exceptions.dart
@@ -5,5 +5,6 @@ class NetworkException implements Exception {
}
class AuthException extends NetworkException {
- AuthException({message, statusCode}) : super(message: message, statusCode: statusCode);
-}
\ No newline at end of file
+ AuthException({message, statusCode})
+ : super(message: message, statusCode: statusCode);
+}
diff --git a/lib/data/network/index.dart b/lib/data/network/index.dart
new file mode 100644
index 00000000..16ebca69
--- /dev/null
+++ b/lib/data/network/index.dart
@@ -0,0 +1,2 @@
+export 'dio_client.dart';
+export 'rest_client.dart';
diff --git a/lib/data/network/rest_client.dart b/lib/data/network/rest_client.dart
index ebfcc559..01549bb5 100644
--- a/lib/data/network/rest_client.dart
+++ b/lib/data/network/rest_client.dart
@@ -14,10 +14,10 @@ class RestClient {
// factory method to return the same object each time its needed
factory RestClient() => _singleton;
-
+
// Singleton accessor
static RestClient get instance => RestClient();
-
+
// instantiate json decoder for json serialization
final JsonDecoder _decoder = JsonDecoder();
@@ -28,7 +28,8 @@ class RestClient {
final int statusCode = response.statusCode;
if (statusCode < 200 || statusCode > 400 || json == null) {
- throw NetworkException(message:"Error fetching data from server", statusCode: statusCode);
+ throw NetworkException(
+ message: "Error fetching data from server", statusCode: statusCode);
}
print(res);
@@ -45,7 +46,8 @@ class RestClient {
final int statusCode = response.statusCode;
if (statusCode < 200 || statusCode > 400 || json == null) {
- throw NetworkException(message:"Error fetching data from server", statusCode: statusCode);
+ throw NetworkException(
+ message: "Error fetching data from server", statusCode: statusCode);
}
return _decoder.convert(res);
});
diff --git a/lib/data/repository.dart b/lib/data/repository.dart
index 9409c66d..7906a223 100644
--- a/lib/data/repository.dart
+++ b/lib/data/repository.dart
@@ -39,14 +39,12 @@ class Repository {
.catchError((error) => throw error);
Future> findPostById(int id) {
-
//creating filter
List filters = List();
//check to see if dataLogsType is not null
if (id != null) {
- Filter dataLogTypeFilter =
- Filter.equal(DBConstants.FIELD_ID, id);
+ Filter dataLogTypeFilter = Filter.equal(DBConstants.FIELD_ID, id);
filters.add(dataLogTypeFilter);
}
diff --git a/lib/data/sharedpref/constants/index.dart b/lib/data/sharedpref/constants/index.dart
new file mode 100644
index 00000000..38d4eaf1
--- /dev/null
+++ b/lib/data/sharedpref/constants/index.dart
@@ -0,0 +1 @@
+export 'preferences.dart';
diff --git a/lib/data/sharedpref/constants/preferences.dart b/lib/data/sharedpref/constants/preferences.dart
index 7eaaf94b..8d16edcf 100644
--- a/lib/data/sharedpref/constants/preferences.dart
+++ b/lib/data/sharedpref/constants/preferences.dart
@@ -3,4 +3,4 @@ class Preferences {
static const String is_logged_in = "isLoggedIn";
static const String auth_token = "authToken";
-}
\ No newline at end of file
+}
diff --git a/lib/locale/index.dart b/lib/locale/index.dart
new file mode 100644
index 00000000..e51b0a22
--- /dev/null
+++ b/lib/locale/index.dart
@@ -0,0 +1 @@
+export 'localizations.dart';
diff --git a/lib/locale/localizations.dart b/lib/locale/localizations.dart
new file mode 100644
index 00000000..4d3e90f9
--- /dev/null
+++ b/lib/locale/localizations.dart
@@ -0,0 +1,106 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// A simple "rough and ready" example of localizing a Flutter app.
+// Spanish and English (locale language codes 'en' and 'es') are
+// supported.
+
+// The pubspec.yaml file must include flutter_localizations in its
+// dependencies section. For example:
+//
+// dependencies:
+// flutter:
+// sdk: flutter
+// flutter_localizations:
+// sdk: flutter
+
+// If you run this app with the device's locale set to anything but
+// English or Spanish, the app's locale will be English. If you
+// set the device's locale to Spanish, the app's locale will be
+// Spanish.
+
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:flutter/foundation.dart' show SynchronousFuture;
+
+class AppLocalizations {
+ AppLocalizations(this.locale);
+
+ final Locale locale;
+
+ static AppLocalizations of(BuildContext context) {
+ return Localizations.of(context, AppLocalizations);
+ }
+
+ static Map> _localizedValues = {
+ 'en': {
+ 'title': 'Boilerplate Project',
+ 'login_et_user_email': 'Enter user email',
+ 'login_et_user_password': 'Enter password',
+ 'login_btn_forgot_password': 'Forgot Password?',
+ 'login_btn_sign_in': 'Sign In',
+ 'login_validation_error': 'Please fill in all fields',
+ 'posts_title': 'Posts',
+ 'posts_not_found': 'No Posts Found',
+ 'settings_title': 'Settings',
+ },
+ 'es': {
+ 'title': 'Proyecto repetitivo',
+ 'login_et_user_email': 'Ingrese el email del usuario',
+ 'login_et_user_password': 'introducir la contraseña',
+ 'login_btn_forgot_password': 'Se te olvidó tu contraseña',
+ 'login_btn_sign_in': 'Registrarse',
+ 'login_validation_error': 'Por favor rellena todos los campos',
+ 'posts_title': 'Mensajes',
+ 'posts_not_found': 'No se han encontrado publicacionesd',
+ 'settings_title': 'Ajustes',
+ },
+ };
+
+ String get title => _localizedValues[locale.languageCode]['title'];
+ String get login_et_user_email =>
+ _localizedValues[locale.languageCode]['login_et_user_email'];
+ String get login_et_user_password =>
+ _localizedValues[locale.languageCode]['login_et_user_password'];
+ String get login_btn_forgot_password =>
+ _localizedValues[locale.languageCode]['login_btn_forgot_password'];
+ String get login_btn_sign_in =>
+ _localizedValues[locale.languageCode]['login_btn_sign_in'];
+ String get login_validation_error =>
+ _localizedValues[locale.languageCode]['login_validation_error'];
+ String get posts_title =>
+ _localizedValues[locale.languageCode]['posts_title'];
+ String get posts_not_found =>
+ _localizedValues[locale.languageCode]['posts_not_found'];
+ String get settings_title =>
+ _localizedValues[locale.languageCode]['settings_title'];
+}
+
+class AppLocalizationsDelegate extends LocalizationsDelegate {
+ const AppLocalizationsDelegate();
+
+ static List get supportedLocales => [
+ Locale('en', ''),
+ Locale('es', ''),
+ ];
+
+ @override
+ bool isSupported(Locale locale) {
+ return supportedLocales
+ .map((l) => l.languageCode)
+ .toList()
+ .contains(locale.languageCode);
+ }
+
+ @override
+ Future load(Locale locale) {
+ // Returning a SynchronousFuture here because an async "load" operation
+ // isn't needed to produce an instance of AppLocalizations.
+ return SynchronousFuture(AppLocalizations(locale));
+ }
+
+ @override
+ bool shouldReload(AppLocalizationsDelegate old) => false;
+}
diff --git a/lib/main.dart b/lib/main.dart
index 5e2ab6ad..77f12972 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,9 +1,10 @@
-import 'package:boilerplate/routes.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
+import 'package:flutter_localizations/flutter_localizations.dart';
import 'constants/app_theme.dart';
-import 'constants/strings.dart';
+import 'locale/index.dart';
+import 'routes.dart';
import 'ui/splash/splash.dart';
void main() {
@@ -22,9 +23,19 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
+ localizationsDelegates: [
+ // ... app-specific localization delegate[s] here
+ GlobalMaterialLocalizations.delegate,
+ GlobalWidgetsLocalizations.delegate,
+ const AppLocalizationsDelegate(),
+ ],
+ supportedLocales: AppLocalizationsDelegate.supportedLocales,
debugShowCheckedModeBanner: false,
- title: Strings.appName,
+ onGenerateTitle: (BuildContext context) {
+ return AppLocalizations.of(context).title;
+ },
theme: themeData,
+ darkTheme: themeDataDark,
routes: Routes.routes,
home: SplashScreen(),
);
diff --git a/lib/models/post/index.dart b/lib/models/post/index.dart
new file mode 100644
index 00000000..d6c0a6ab
--- /dev/null
+++ b/lib/models/post/index.dart
@@ -0,0 +1,2 @@
+export 'post.dart';
+export 'post_list.dart';
diff --git a/lib/models/post/post.dart b/lib/models/post/post.dart
index aa9acd2b..317c9e5b 100644
--- a/lib/models/post/post.dart
+++ b/lib/models/post/post.dart
@@ -1,4 +1,3 @@
-
class Post {
int userId;
int id;
@@ -25,5 +24,4 @@ class Post {
"title": title,
"body": body,
};
-
}
diff --git a/lib/routes.dart b/lib/routes.dart
index b4fbe8bb..1694def1 100644
--- a/lib/routes.dart
+++ b/lib/routes.dart
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'ui/home/home.dart';
import 'ui/login/login.dart';
+import 'ui/navigation.dart';
+import 'ui/settings/settings.dart';
import 'ui/splash/splash.dart';
class Routes {
@@ -15,9 +17,6 @@ class Routes {
static final routes = {
splash: (BuildContext context) => SplashScreen(),
login: (BuildContext context) => LoginScreen(),
- home: (BuildContext context) => HomeScreen(),
+ home: (BuildContext context) => AppNavigation(),
};
}
-
-
-
diff --git a/lib/stores/error/error_store.dart b/lib/stores/error/error_store.dart
index de18af79..b7c81d38 100644
--- a/lib/stores/error/error_store.dart
+++ b/lib/stores/error/error_store.dart
@@ -5,11 +5,10 @@ part 'error_store.g.dart';
class ErrorStore = _ErrorStore with _$ErrorStore;
abstract class _ErrorStore implements Store {
-
// store variables:-----------------------------------------------------------
@observable
String errorMessage;
@observable
bool showError = false;
-}
\ No newline at end of file
+}
diff --git a/lib/stores/form/form_store.dart b/lib/stores/form/form_store.dart
index 1a73c1bb..5498a0cb 100644
--- a/lib/stores/form/form_store.dart
+++ b/lib/stores/form/form_store.dart
@@ -50,7 +50,9 @@ abstract class _FormStore implements Store {
@computed
bool get canLogin =>
- !formErrorStore.hasErrorsInLogin && userEmail.isNotEmpty && password.isNotEmpty;
+ !formErrorStore.hasErrorsInLogin &&
+ userEmail.isNotEmpty &&
+ password.isNotEmpty;
@computed
bool get canRegister =>
diff --git a/lib/stores/post/post_store.dart b/lib/stores/post/post_store.dart
index cd01acf8..d59d4aa3 100644
--- a/lib/stores/post/post_store.dart
+++ b/lib/stores/post/post_store.dart
@@ -9,7 +9,6 @@ part 'post_store.g.dart';
class PostStore = _PostStore with _$PostStore;
abstract class _PostStore implements Store {
-
// store for handling errors
final ErrorStore errorStore = ErrorStore();
@@ -41,4 +40,4 @@ abstract class _PostStore implements Store {
print(e);
});
}
-}
\ No newline at end of file
+}
diff --git a/lib/ui/home/home.dart b/lib/ui/home/home.dart
index 232ec470..cfbd2131 100644
--- a/lib/ui/home/home.dart
+++ b/lib/ui/home/home.dart
@@ -1,12 +1,16 @@
-import 'package:boilerplate/data/sharedpref/constants/preferences.dart';
-import 'package:boilerplate/routes.dart';
-import 'package:boilerplate/stores/post/post_store.dart';
-import 'package:boilerplate/widgets/progress_indicator_widget.dart';
+import 'package:boilerplate/constants/index.dart';
+import 'package:boilerplate/models/post/index.dart';
import 'package:flushbar/flushbar_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
+import '../../data/sharedpref/constants/preferences.dart';
+import '../../locale/index.dart';
+import '../../routes.dart';
+import '../../stores/post/post_store.dart';
+import '../../widgets/progress_indicator_widget.dart';
+
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
@@ -24,56 +28,86 @@ class _HomeScreenState extends State {
_store.getPosts();
}
+ int _selectedIndex = 0;
@override
Widget build(BuildContext context) {
- return Scaffold(
- appBar: _buildAppBar(context),
- body: _buildBody(),
- );
- }
-
- Widget _buildAppBar(BuildContext context) {
- return AppBar(
- title: Text('Posts'),
- actions: [
- IconButton(
- onPressed: () {
- SharedPreferences.getInstance().then((preference) {
- preference.setBool(Preferences.is_logged_in, false);
- Navigator.of(context).pushReplacementNamed(Routes.login);
- });
- },
- icon: Icon(
- Icons.power_settings_new,
- ),
- )
- ],
- );
- }
-
- Widget _buildBody() {
- return Stack(
- children: [
- Observer(
- builder: (context) {
- return _store.loading
- ? CustomProgressIndicatorWidget()
- : Material(child: _buildListView());
- },
- ),
- Observer(
- name: 'error',
- builder: (context) {
- return _store.success
- ? Container()
- : showErrorMessage(context, _store.errorStore.errorMessage);
- },
- )
- ],
+ return LayoutBuilder(
+ builder: (context, dimens) {
+ if (MediaQuery.of(context).orientation == Orientation.landscape &&
+ dimens.maxWidth >= Dimens.tablet_breakpoint) {
+ return Row(
+ children: [
+ Container(
+ width: Dimens.tablet_list_width,
+ child: Stack(
+ children: [
+ Observer(
+ builder: (context) {
+ return _store.loading
+ ? CustomProgressIndicatorWidget()
+ : Material(
+ child: _buildListView((val) {
+ if (mounted)
+ setState(() {
+ _selectedIndex = val;
+ });
+ }, true));
+ },
+ ),
+ Observer(
+ name: 'error',
+ builder: (context) {
+ return _store.success
+ ? Container()
+ : showErrorMessage(
+ context, _store.errorStore.errorMessage);
+ },
+ )
+ ],
+ ),
+ ),
+ Expanded(
+ child: PostDetailsScreen(
+ post: _store.postsList.posts[_selectedIndex],
+ showAppBar: false,
+ ),
+ ),
+ ],
+ );
+ }
+ return Stack(
+ children: [
+ Observer(
+ builder: (context) {
+ return _store.loading
+ ? CustomProgressIndicatorWidget()
+ : Material(
+ child: _buildListView((val) {
+ Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (_) => PostDetailsScreen(
+ post: _store.postsList.posts[_selectedIndex],
+ ),
+ ),
+ );
+ }, false));
+ },
+ ),
+ Observer(
+ name: 'error',
+ builder: (context) {
+ return _store.success
+ ? Container()
+ : showErrorMessage(context, _store.errorStore.errorMessage);
+ },
+ )
+ ],
+ );
+ },
);
}
- Widget _buildListView() {
+ Widget _buildListView(ValueChanged selected, bool tablet) {
return _store.postsList != null
? ListView.separated(
itemCount: _store.postsList.posts.length,
@@ -82,6 +116,7 @@ class _HomeScreenState extends State {
},
itemBuilder: (context, position) {
return ListTile(
+ selected: tablet ? _selectedIndex == position : false,
leading: Icon(Icons.cloud_circle),
title: Text(
'${_store.postsList.posts[position].title}',
@@ -96,10 +131,11 @@ class _HomeScreenState extends State {
overflow: TextOverflow.ellipsis,
softWrap: false,
),
+ onTap: () => selected(position),
);
},
)
- : Center(child: Text('No posts found'));
+ : Center(child: Text(AppLocalizations.of(context).posts_not_found));
}
// General Methods:-----------------------------------------------------------
@@ -117,3 +153,39 @@ class _HomeScreenState extends State {
return Container();
}
}
+
+class PostDetailsScreen extends StatelessWidget {
+ const PostDetailsScreen({
+ @required this.post,
+ this.showAppBar = true,
+ });
+ final Post post;
+ final bool showAppBar;
+ @override
+ Widget build(BuildContext context) {
+ final _textTheme = Theme.of(context).textTheme;
+ return Scaffold(
+ appBar: showAppBar
+ ? AppBar(
+ title: Text('Details'),
+ )
+ : null,
+ body: ListView(
+ children: [
+ ListTile(
+ title: Text(
+ post.title,
+ style: _textTheme.title,
+ ),
+ ),
+ ListTile(
+ title: Text(
+ post.body,
+ style: _textTheme.subtitle,
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/ui/login/login.dart b/lib/ui/login/login.dart
index a5b1e6b2..cd24cae1 100644
--- a/lib/ui/login/login.dart
+++ b/lib/ui/login/login.dart
@@ -1,16 +1,18 @@
-import 'package:boilerplate/constants/strings.dart';
-import 'package:boilerplate/data/sharedpref/constants/preferences.dart';
-import 'package:boilerplate/routes.dart';
-import 'package:boilerplate/stores/form/form_store.dart';
-import 'package:boilerplate/widgets/app_icon_widget.dart';
-import 'package:boilerplate/widgets/empty_app_bar_widget.dart';
-import 'package:boilerplate/widgets/progress_indicator_widget.dart';
-import 'package:boilerplate/widgets/rounded_button_widget.dart';
-import 'package:boilerplate/widgets/textfield_widget.dart';
+import 'package:boilerplate/constants/index.dart';
+import 'package:flushbar/flushbar_helper.dart';
import 'package:flutter/material.dart';
-import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
-import 'package:flushbar/flushbar_helper.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+import '../../data/sharedpref/constants/preferences.dart';
+import '../../locale/index.dart';
+import '../../routes.dart';
+import '../../stores/form/form_store.dart';
+import '../../widgets/app_icon_widget.dart';
+import '../../widgets/empty_app_bar_widget.dart';
+import '../../widgets/progress_indicator_widget.dart';
+import '../../widgets/rounded_button_widget.dart';
+import '../../widgets/textfield_widget.dart';
class LoginScreen extends StatefulWidget {
@override
@@ -69,29 +71,27 @@ class _LoginScreenState extends State {
return Material(
child: Stack(
children: [
- OrientationBuilder(
- builder: (context, orientation) {
- //variable to hold widget
- var child;
-
+ LayoutBuilder(
+ builder: (context, dimens) {
//check to see whether device is in landscape or portrait
- //load widgets based on device orientation
- orientation == Orientation.landscape
- ? child = Row(
- children: [
- Expanded(
- flex: 1,
- child: _buildLeftSide(),
- ),
- Expanded(
- flex: 1,
- child: _buildRightSide(),
- ),
- ],
- )
- : child = Center(child: _buildRightSide());
-
- return child;
+ //load widgets based on device orientation and max width
+ if (MediaQuery.of(context).orientation == Orientation.landscape ||
+ dimens.maxWidth >= Dimens.tablet_breakpoint) {
+ return Row(
+ children: [
+ Expanded(
+ flex: 1,
+ child: _buildLeftSide(),
+ ),
+ Expanded(
+ flex: 1,
+ child: _buildRightSide(),
+ ),
+ ],
+ );
+ }
+
+ return Center(child: _buildRightSide());
},
),
Observer(
@@ -153,7 +153,7 @@ class _LoginScreenState extends State {
return Observer(
builder: (context) {
return TextFieldWidget(
- hint: Strings.login_et_user_email,
+ hint: AppLocalizations.of(context).login_et_user_email,
inputType: TextInputType.emailAddress,
icon: Icons.person,
iconColor: Colors.black54,
@@ -172,7 +172,7 @@ class _LoginScreenState extends State {
return Observer(
builder: (context) {
return TextFieldWidget(
- hint: Strings.login_et_user_password,
+ hint: AppLocalizations.of(context).login_et_user_password,
isObscure: true,
padding: EdgeInsets.only(top: 16.0),
icon: Icons.lock,
@@ -191,7 +191,7 @@ class _LoginScreenState extends State {
child: FlatButton(
padding: EdgeInsets.all(0.0),
child: Text(
- Strings.login_btn_forgot_password,
+ AppLocalizations.of(context).login_btn_forgot_password,
style: Theme.of(context)
.textTheme
.caption
@@ -204,14 +204,15 @@ class _LoginScreenState extends State {
Widget _buildSignInButton() {
return RoundedButtonWidget(
- buttonText: Strings.login_btn_sign_in,
+ buttonText: AppLocalizations.of(context).login_btn_sign_in,
buttonColor: Colors.orangeAccent,
textColor: Colors.white,
onPressed: () async {
if (_store.canLogin) {
_store.login();
} else {
- showErrorMessage(context, 'Please fill in all fields');
+ showErrorMessage(
+ context, AppLocalizations.of(context).login_validation_error);
}
},
);
@@ -219,13 +220,12 @@ class _LoginScreenState extends State {
// General Methods:-----------------------------------------------------------
showErrorMessage(BuildContext context, String message) {
- if(message != null) {
+ if (message != null) {
FlushbarHelper.createError(
message: message,
title: 'Error',
duration: Duration(seconds: 3),
- )
- ..show(context);
+ )..show(context);
}
return Container();
diff --git a/lib/ui/navigation.dart b/lib/ui/navigation.dart
new file mode 100644
index 00000000..626ffd36
--- /dev/null
+++ b/lib/ui/navigation.dart
@@ -0,0 +1,42 @@
+import 'package:flutter/material.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+import '../data/sharedpref/constants/index.dart';
+import '../locale/index.dart';
+import '../routes.dart';
+import '../widgets/index.dart';
+import 'home/home.dart';
+import 'settings/settings.dart';
+
+class AppNavigation extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return DynamicNavigation(
+ type: NavigationType.bottomTabs,
+ children: [
+ Screen(
+ iconData: Icons.home,
+ title: AppLocalizations.of(context).posts_title,
+ child: HomeScreen(),
+ actions: [
+ IconButton(
+ onPressed: () {
+ SharedPreferences.getInstance().then((preference) {
+ preference.setBool(Preferences.is_logged_in, false);
+ Navigator.of(context).pushReplacementNamed(Routes.login);
+ });
+ },
+ icon: Icon(
+ Icons.power_settings_new,
+ ),
+ ),
+ ]),
+ Screen(
+ iconData: Icons.settings,
+ title: AppLocalizations.of(context).settings_title,
+ child: SettingsScreen(),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/ui/settings/settings.dart b/lib/ui/settings/settings.dart
new file mode 100644
index 00000000..a537c7b0
--- /dev/null
+++ b/lib/ui/settings/settings.dart
@@ -0,0 +1,8 @@
+import 'package:flutter/material.dart';
+
+class SettingsScreen extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return Container();
+ }
+}
diff --git a/lib/ui/splash/splash.dart b/lib/ui/splash/splash.dart
index e5b616ec..f9f035b1 100644
--- a/lib/ui/splash/splash.dart
+++ b/lib/ui/splash/splash.dart
@@ -1,16 +1,19 @@
import 'dart:async';
-import 'package:boilerplate/data/sharedpref/constants/preferences.dart';
-import 'package:boilerplate/routes.dart';
-import 'package:boilerplate/widgets/app_icon_widget.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
+import '../../data/sharedpref/constants/preferences.dart';
+import '../../routes.dart';
+import '../../widgets/app_icon_widget.dart';
+
class SplashScreen extends StatefulWidget {
@override
State createState() => _SplashScreenState();
}
+const bool autoLogin = true;
+
class _SplashScreenState extends State {
@override
void initState() {
@@ -33,8 +36,8 @@ class _SplashScreenState extends State {
navigate() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
- if (preferences.getBool(Preferences.is_logged_in) ?? false) {
- Navigator.of(context).pushReplacementNamed(Routes.login);
+ if (autoLogin && (preferences?.getBool(Preferences.is_logged_in) ?? false)) {
+ Navigator.of(context).pushReplacementNamed(Routes.home);
} else {
Navigator.of(context).pushReplacementNamed(Routes.login);
}
diff --git a/lib/utils/dio/dio_error_util.dart b/lib/utils/dio/dio_error_util.dart
index dbbebc8f..b5e9d816 100644
--- a/lib/utils/dio/dio_error_util.dart
+++ b/lib/utils/dio/dio_error_util.dart
@@ -14,14 +14,14 @@ class DioErrorUtil {
break;
case DioErrorType.DEFAULT:
errorDescription =
- "Connection to API server failed due to internet connection";
+ "Connection to API server failed due to internet connection";
break;
case DioErrorType.RECEIVE_TIMEOUT:
errorDescription = "Receive timeout in connection with API server";
break;
case DioErrorType.RESPONSE:
errorDescription =
- "Received invalid status code: ${error.response.statusCode}";
+ "Received invalid status code: ${error.response.statusCode}";
break;
case DioErrorType.SEND_TIMEOUT:
errorDescription = "Send timeout in connection with API server";
@@ -32,4 +32,4 @@ class DioErrorUtil {
}
return errorDescription;
}
-}
\ No newline at end of file
+}
diff --git a/lib/utils/dio/index.dart b/lib/utils/dio/index.dart
new file mode 100644
index 00000000..361976fa
--- /dev/null
+++ b/lib/utils/dio/index.dart
@@ -0,0 +1 @@
+export 'dio_error_util.dart';
diff --git a/lib/utils/encryption/index.dart b/lib/utils/encryption/index.dart
new file mode 100644
index 00000000..cfae3b8d
--- /dev/null
+++ b/lib/utils/encryption/index.dart
@@ -0,0 +1 @@
+export 'xxtea.dart';
diff --git a/lib/widgets/index.dart b/lib/widgets/index.dart
new file mode 100644
index 00000000..d0ac5d01
--- /dev/null
+++ b/lib/widgets/index.dart
@@ -0,0 +1,6 @@
+export 'app_icon_widget.dart';
+export 'empty_app_bar_widget.dart';
+export 'navigation.dart';
+export 'progress_indicator_widget.dart';
+export 'rounded_button_widget.dart';
+export 'textfield_widget.dart';
diff --git a/lib/widgets/navigation.dart b/lib/widgets/navigation.dart
new file mode 100644
index 00000000..9d9d6bdc
--- /dev/null
+++ b/lib/widgets/navigation.dart
@@ -0,0 +1,162 @@
+import 'dart:io';
+
+import 'package:flutter/material.dart';
+
+class DynamicNavigation extends StatefulWidget {
+ DynamicNavigation({
+ @required this.children,
+ this.type = NavigationType.bottomTabs,
+ });
+
+ final List children;
+ final NavigationType type;
+
+ @override
+ _DynamicNavigationState createState() => _DynamicNavigationState();
+}
+
+class _DynamicNavigationState extends State {
+ int _currentIndex = 0;
+
+ void tabSelected(val) {
+ if (mounted)
+ setState(() {
+ _currentIndex = val;
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ if (widget.type == NavigationType.sideDrawer) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(widget.children[_currentIndex].title),
+ actions: widget.children[_currentIndex]?.actions,
+ ),
+ drawer: Container(
+ child: Drawer(
+ child: SingleChildScrollView(
+ child: SafeArea(
+ child: Column(children: [
+ for (var tab in widget.children) ...[
+ ListTile(
+ selected: _currentIndex == widget.children.indexOf(tab),
+ leading: Icon(tab.icon),
+ title: Text(tab.title),
+ subtitle:
+ tab?.description != null ? Text(tab.description) : null,
+ onTap: () => tabSelected(widget.children.indexOf(tab)),
+ ),
+ ],
+ ]),
+ ),
+ )),
+ ),
+ body: Stack(
+ children: [
+ for (int i = 0; i < widget.children.length; i++) ...[
+ Offstage(
+ offstage: _currentIndex != i,
+ child: widget.children[i].child,
+ ),
+ ],
+ ],
+ ),
+ );
+ }
+ if (widget.type == NavigationType.bottomTabs) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(widget.children[_currentIndex].title),
+ actions: widget.children[_currentIndex]?.actions,
+ ),
+ body: Stack(
+ children: [
+ for (int i = 0; i < widget.children.length; i++) ...[
+ Offstage(
+ offstage: _currentIndex != i,
+ child: widget.children[i].child,
+ ),
+ ],
+ ],
+ ),
+ bottomNavigationBar: BottomNavigationBar(
+ currentIndex: _currentIndex,
+ onTap: tabSelected,
+ items: [
+ for (var tab in widget.children) ...[
+ BottomNavigationBarItem(
+ icon: Icon(tab.icon),
+ title: Text(tab.title),
+ ),
+ ],
+ ],
+ ),
+ );
+ }
+
+ if (widget.type == NavigationType.pages) {
+ return DefaultTabController(
+ initialIndex: _currentIndex,
+ length: widget.children.length,
+ child: Scaffold(
+ appBar: AppBar(
+ title: Text(widget.children[_currentIndex].title),
+ actions: widget.children[_currentIndex]?.actions,
+ bottom: TabBar(
+ onTap: tabSelected,
+ tabs: [
+ for (var tab in widget.children) ...[tab.tab],
+ ],
+ ),
+ ),
+ body: Stack(
+ children: [
+ for (int i = 0; i < widget.children.length; i++) ...[
+ Offstage(
+ offstage: _currentIndex != i,
+ child: widget.children[i].child,
+ ),
+ ],
+ ],
+ ),
+ ),
+ );
+ }
+
+ return Container();
+ }
+}
+
+enum NavigationType {
+ bottomTabs,
+ sideDrawer,
+ pages,
+}
+
+class Screen {
+ const Screen({
+ @required this.title,
+ @required this.iconData,
+ @required this.child,
+ this.description,
+ this.iosIconData,
+ this.pageTab,
+ this.actions,
+ });
+
+ final List actions;
+ final Widget child;
+ final String title, description;
+ final IconData iconData, iosIconData;
+ final Tab pageTab;
+
+ IconData get icon {
+ if (Platform.isIOS || Platform.isMacOS) {
+ return iosIconData ?? iconData;
+ }
+ return iconData;
+ }
+
+ Tab get tab => pageTab ?? Tab(text: title);
+}
diff --git a/lib/widgets/progress_indicator_widget.dart b/lib/widgets/progress_indicator_widget.dart
index 0bc91600..420d4ecc 100644
--- a/lib/widgets/progress_indicator_widget.dart
+++ b/lib/widgets/progress_indicator_widget.dart
@@ -27,8 +27,7 @@ class CustomProgressIndicatorWidget extends StatelessWidget {
),
),
),
- decoration: BoxDecoration(
- color: Color.fromARGB(100, 105, 105, 105)),
+ decoration: BoxDecoration(color: Color.fromARGB(100, 105, 105, 105)),
),
);
}
diff --git a/lib/widgets/textfield_widget.dart b/lib/widgets/textfield_widget.dart
index 4962d3e5..8a8923d1 100644
--- a/lib/widgets/textfield_widget.dart
+++ b/lib/widgets/textfield_widget.dart
@@ -58,5 +58,4 @@ class TextFieldWidget extends StatelessWidget {
),
);
}
-
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 41ab3b53..c525d377 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -10,19 +10,22 @@ description: A flutter boilerplate project created using MobX and Provider.
version: 1.0.0+1
environment:
- sdk: ">=2.1.0 <3.0.0"
+ sdk: ">=2.2.2 <3.0.0"
dependencies:
flutter:
sdk: flutter
-
+ flutter_localizations:
+ sdk: flutter
+
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
# The following adds the shared pref as a dependency in your application
shared_preferences: ^0.4.3
-
+ localstorage: ^2.0.0
+
# A composable, Future-based library for making HTTP requests.
http: ^0.12.0+1
@@ -60,12 +63,14 @@ dependencies:
# A flexible widget for user notification.
flushbar: 1.5.3
+dependency_overrides:
+ f_logs:
+ git: "https://github.com/AppleEducate/Flogs"
dev_dependencies:
flutter_test:
sdk: flutter
- flutter_launcher_icons: "^0.7.0"
build_runner: ^1.3.0
flutter_icons:
diff --git a/test/widget_test.dart b/test/widget_test.dart
index a48deac4..40aa4800 100644
--- a/test/widget_test.dart
+++ b/test/widget_test.dart
@@ -9,7 +9,6 @@ import 'package:boilerplate/main.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
-
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.