diff --git a/lib/constants/app_constants.dart b/lib/constants/app_constants.dart index 5c659991a16618a66a3dd1e7372ee7fa96a80d21..b1b4aa37cd83439436daae98497247a93fa6bbb6 100644 --- a/lib/constants/app_constants.dart +++ b/lib/constants/app_constants.dart @@ -21,6 +21,7 @@ class InspectionStatus { static const String newInspection = 'NEW'; static const String sentForInspection = 'SENTFORINS'; static const String inspectionCompleted = 'INSCOMPLETED'; + static const String inspectionPending = 'PENDING'; static const String returned = 'RETURNED'; } @@ -28,3 +29,18 @@ class FieldValue { static const String correct = 'Correct'; static const String inCorrect = 'Incorrect'; } + +class AppDatabase { + static const String name = 'smf_db'; + static const String applicationsTable = 'applications'; + static const String inspectionTable = 'inspections'; +} + +class Storage { + static const String userId = 'smf_user_id'; + static const String username = 'smf_user_username'; + static const String email = 'smf_user_email'; + static const String firstname = 'smf_user_first_name'; + static const String lastname = 'smf_user_last_name'; + static const String authtoken = 'smf_user_auth_token'; +} diff --git a/lib/database/offline_model.dart b/lib/database/offline_model.dart new file mode 100644 index 0000000000000000000000000000000000000000..bb6d3ec32febafb2089dcd9c5fe311fd94e8287d --- /dev/null +++ b/lib/database/offline_model.dart @@ -0,0 +1,73 @@ +import 'package:smf_mobile/constants/app_constants.dart'; +import 'package:sqflite/sqflite.dart' as sql; +import 'package:path/path.dart' as path; +import 'package:sqflite/sqlite_api.dart'; + +class OfflineModel { + static Future<Database> database() async { + final dbPath = await sql.getDatabasesPath(); + return sql.openDatabase(path.join(dbPath, AppDatabase.name), + onCreate: (db, version) async { + const String applicationsTable = AppDatabase.applicationsTable; + const String inspectionTable = AppDatabase.inspectionTable; + + await db.execute('''CREATE TABLE $applicationsTable ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + application_data TEXT + )'''); + await db.execute('''CREATE TABLE $inspectionTable ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + inspection_data TEXT + )'''); + }, version: 1); + } + + static Future<void> saveApplications(Map<String, Object> data) async { + final db = await OfflineModel.database(); + db.insert( + AppDatabase.applicationsTable, + data, + conflictAlgorithm: ConflictAlgorithm.replace, + ); + } + + static Future<Map> getApplications(int userId) async { + final db = await OfflineModel.database(); + List<dynamic> whereArgs = [userId]; + List<Map> rows = await db.query(AppDatabase.applicationsTable, + where: 'user_id = ?', orderBy: 'id DESC', whereArgs: whereArgs); + return rows[0]; + } + + static Future<void> deleteApplications(int userId) async { + Database db = await OfflineModel.database(); + List<dynamic> whereArgs = [userId]; + await db.delete(AppDatabase.applicationsTable, + where: 'user_id = ?', whereArgs: whereArgs); + } + + static Future<void> saveInspection(Map<String, Object> data) async { + final db = await OfflineModel.database(); + db.insert( + AppDatabase.inspectionTable, + data, + conflictAlgorithm: ConflictAlgorithm.replace, + ); + } + + static Future<List<Map<String, dynamic>>> getInspections(int userId) async { + final db = await OfflineModel.database(); + List<dynamic> whereArgs = [userId]; + return db.query(AppDatabase.inspectionTable, + where: 'user_id = ?', whereArgs: whereArgs); + } + + static Future<void> deleteInspections(int userId) async { + Database db = await OfflineModel.database(); + List<dynamic> whereArgs = [userId]; + await db.delete(AppDatabase.inspectionTable, + where: 'user_id = ?', whereArgs: whereArgs); + } +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index c319c9dcfa952f80a99fedf927eafe854fc400ab..785d69b5e5121d1ddf2f11fee7554086b977ae88 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1,5 +1,6 @@ { "login": "Login", + "logout": "Logout", "emailId": "Email Id", "getOtp": "GET OTP", "enterOtp": "Enter OTP", @@ -14,6 +15,9 @@ "next": "Next", "previous": "previous", "inspectionCompleted": "Inspection completed", + "completed": "Completed", + "pending": "Pending", + "noApplications": "No applications at the moment", "sessionExpiredMessage": "Your session has expired.", "isGivenInformationCorrect": "Is the given information found correct?", "typeHere": "Type here", diff --git a/lib/pages/application_details_page.dart b/lib/pages/application_details_page.dart index 63f8c16618661d1a6973531ed4d4292c57cf7a93..d96f39b96163720a799321a4289fda01058c2fe9 100644 --- a/lib/pages/application_details_page.dart +++ b/lib/pages/application_details_page.dart @@ -59,7 +59,7 @@ class _ApplicationDetailsPageState extends State<ApplicationDetailsPage> } Future<void> _checkInspectorRole() async { - String id = await Helper.getUser('smf_user_id'); + String id = await Helper.getUser(Storage.userId); int userId = int.parse(id); if (widget.application.leadInspector.isNotEmpty) { _leadInspectorId = widget.application.leadInspector[0]; @@ -67,7 +67,8 @@ class _ApplicationDetailsPageState extends State<ApplicationDetailsPage> setState(() { _isleadInspector = true; }); - } else { + } else if (widget.application.status == + InspectionStatus.inspectionCompleted) { _inspectionSummary = widget.application.inspectorSummaryDataObject['Inspection Summary'][ widget.application @@ -370,7 +371,7 @@ class _ApplicationDetailsPageState extends State<ApplicationDetailsPage> 4), ), child: Text( - 'Status: ${Helper.getInspectionStatus(widget.application.status)}', + 'Status: ${Helper.getInspectionStatus(context, widget.application.status)}', textAlign: TextAlign .center, style: GoogleFonts diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 2df0c63138a0414ce2cbe61eb18dc816cf2041e6..4636a74e0e5813bb84e86c86cf7c33a206e0dd09 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -11,6 +11,9 @@ import 'package:smf_mobile/repositories/login_repository.dart'; import 'package:smf_mobile/util/helper.dart'; import 'package:smf_mobile/widgets/application_card.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'dart:async'; +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:smf_mobile/util/connectivity_helper.dart'; // import 'dart:developer' as developer; @@ -23,6 +26,8 @@ class HomePage extends StatefulWidget { } class _HomePageState extends State<HomePage> { + Map _source = {ConnectivityResult.none: false}; + final MyConnectivity _connectivity = MyConnectivity.instance; List<Application> _allApplications = []; final List<Application> _pendingApplications = []; final List<Application> _upcomingApplications = []; @@ -30,6 +35,10 @@ class _HomePageState extends State<HomePage> { @override void initState() { super.initState(); + _connectivity.initialise(); + _connectivity.myStream.listen((source) { + setState(() => _source = source); + }); } void _validateUser() async { @@ -42,11 +51,31 @@ class _HomePageState extends State<HomePage> { } } + bool _isInternetConnected() { + bool connected; + switch (_source.keys.toList()[0]) { + case ConnectivityResult.mobile: + print('connected to mobile...'); + connected = true; + break; + case ConnectivityResult.wifi: + print('connected to wifi...'); + connected = true; + break; + case ConnectivityResult.none: + default: + print('offline...'); + connected = false; + } + return connected; + } + Future<dynamic> _getApplications() async { + print('_getApplications...'); _validateUser(); _allApplications = await Provider.of<ApplicationRespository>(context, listen: false) - .getApplications(); + .getApplications(_isInternetConnected()); String _errorMessage = Provider.of<ApplicationRespository>(context, listen: false) .errorMessage; @@ -127,7 +156,7 @@ class _HomePageState extends State<HomePage> { PopupMenuItem<String>( value: 'logout', child: Text( - 'Logout', + AppLocalizations.of(context)!.logout, style: GoogleFonts.lato( color: AppColors.black87, fontSize: 14.0, @@ -168,7 +197,9 @@ class _HomePageState extends State<HomePage> { margin: const EdgeInsets.only( top: 10, bottom: 20), child: Center( - child: Text('No applications at the moment', + child: Text( + AppLocalizations.of(context)! + .noApplications, style: GoogleFonts.lato( color: AppColors.black87, fontSize: 16.0, diff --git a/lib/repositories/application_repository.dart b/lib/repositories/application_repository.dart index 3dbe036a8a527917aec1e516017f9da7aa0cb113..d50600d52963dcb144dd3cd6bab37fc662134354 100644 --- a/lib/repositories/application_repository.dart +++ b/lib/repositories/application_repository.dart @@ -1,17 +1,34 @@ +import 'dart:async'; import 'dart:convert'; import 'package:flutter/widgets.dart'; +import 'package:smf_mobile/constants/app_constants.dart'; +import 'package:smf_mobile/database/offline_model.dart'; import 'package:smf_mobile/models/application_model.dart'; import 'package:smf_mobile/services/application_service.dart'; +import 'package:smf_mobile/util/helper.dart'; class ApplicationRespository with ChangeNotifier { late Map _data; List<Application> _applications = []; String _errorMessage = ''; - Future<dynamic> getApplications() async { + Future<dynamic> getApplications(bool internetConnected) async { + String rawUserId = await Helper.getUser(Storage.userId); + int userId = int.parse(rawUserId); try { - final request = await ApplicationService.getApplications(); - _data = json.decode(request.body); + if (internetConnected) { + final request = await ApplicationService.getApplications(); + _data = json.decode(request.body); + Map<String, Object> data = { + 'user_id': userId, + 'application_data': request.body + }; + await OfflineModel.deleteApplications(userId); + await OfflineModel.saveApplications(data); + } else { + var applications = await OfflineModel.getApplications(userId); + _data = json.decode(applications['application_data']); + } } catch (_) { return _; } diff --git a/lib/repositories/login_repository.dart b/lib/repositories/login_repository.dart index 6f733ad15f90b1d5408fb66b7b373d60cc8d6d1f..a6fa747af224b688928893e3f333c662d9ab7c5e 100644 --- a/lib/repositories/login_repository.dart +++ b/lib/repositories/login_repository.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/widgets.dart'; +import 'package:smf_mobile/constants/app_constants.dart'; import 'package:smf_mobile/models/login_model.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:smf_mobile/services/login_service.dart'; @@ -43,14 +44,12 @@ class LoginRespository with ChangeNotifier { _errorMessage = _data['statusInfo']['errorMessage']; } else { _loginDetails = Login.fromJson(_data['responseData']); - _storage.write(key: 'smf_user_id', value: '${_loginDetails.id}'); - _storage.write(key: 'smf_user_username', value: _loginDetails.username); - _storage.write(key: 'smf_user_email', value: _loginDetails.email); - _storage.write( - key: 'smf_user_first_name', value: _loginDetails.firstName); - _storage.write(key: 'smf_user_last_name', value: _loginDetails.lastName); - _storage.write( - key: 'smf_user_auth_token', value: _loginDetails.authToken); + _storage.write(key: Storage.userId, value: '${_loginDetails.id}'); + _storage.write(key: Storage.username, value: _loginDetails.username); + _storage.write(key: Storage.email, value: _loginDetails.email); + _storage.write(key: Storage.firstname, value: _loginDetails.firstName); + _storage.write(key: Storage.lastname, value: _loginDetails.lastName); + _storage.write(key: Storage.authtoken, value: _loginDetails.authToken); _firebaseMessaging.getToken().then((token) async { final request = await LoginService.updateUserDeviceToken( token.toString(), diff --git a/lib/services/base_service.dart b/lib/services/base_service.dart index ae76f9943a8ffa3a922327156df739f9f5541167..268d176570b54f9e5f1ad28616257eb9dfaf11ee 100644 --- a/lib/services/base_service.dart +++ b/lib/services/base_service.dart @@ -1,7 +1,6 @@ import 'dart:io'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; - -const _storage = FlutterSecureStorage(); +import 'package:smf_mobile/constants/app_constants.dart'; +import 'package:smf_mobile/util/helper.dart'; abstract class BaseService { final HttpClient client; @@ -12,7 +11,7 @@ abstract class BaseService { 'Accept': 'application/json', 'Content-Type': 'application/json; charset=utf-8', }; - var authToken = await _storage.read(key: 'smf_user_auth_token'); + var authToken = await Helper.getUser(Storage.authtoken); if (authToken != '' && authToken != null) { headers['Authorization'] = authToken; } diff --git a/lib/util/connectivity_helper.dart b/lib/util/connectivity_helper.dart new file mode 100644 index 0000000000000000000000000000000000000000..5704601cb1fd1070c4e9bad652f84411a5c4fb3c --- /dev/null +++ b/lib/util/connectivity_helper.dart @@ -0,0 +1,35 @@ +import 'dart:io'; +import 'dart:async'; +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:smf_mobile/constants/api_endpoints.dart'; + +class MyConnectivity { + MyConnectivity._(); + + static final _instance = MyConnectivity._(); + static MyConnectivity get instance => _instance; + final _connectivity = Connectivity(); + final _controller = StreamController.broadcast(); + Stream get myStream => _controller.stream; + + void initialise() async { + ConnectivityResult result = await _connectivity.checkConnectivity(); + _checkStatus(result); + _connectivity.onConnectivityChanged.listen((result) { + _checkStatus(result); + }); + } + + void _checkStatus(ConnectivityResult result) async { + bool isOnline = false; + try { + final result = await InternetAddress.lookup(ApiUrl.baseUrl); + isOnline = result.isNotEmpty && result[0].rawAddress.isNotEmpty; + } on SocketException catch (_) { + isOnline = false; + } + _controller.sink.add({result: isOnline}); + } + + void disposeStream() => _controller.close(); +} diff --git a/lib/util/helper.dart b/lib/util/helper.dart index cf6800d963bad4a1fd16a4139ab67b6ad7146f84..0faaf21e8e221392ada97296441d92f74d7ae041 100644 --- a/lib/util/helper.dart +++ b/lib/util/helper.dart @@ -1,11 +1,11 @@ import 'dart:math'; - import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:intl/intl.dart'; import 'package:jwt_decoder/jwt_decoder.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:smf_mobile/constants/app_constants.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; const _storage = FlutterSecureStorage(); @@ -37,7 +37,7 @@ class Helper { static Future<bool> isTokenExpired() async { bool isTokenExpired = true; - var authToken = await _storage.read(key: 'smf_user_auth_token'); + var authToken = await _storage.read(key: Storage.authtoken); if (authToken != null) { isTokenExpired = JwtDecoder.isExpired(authToken); } @@ -74,12 +74,12 @@ class Helper { ? '${string[0].toUpperCase()}${string.substring(1).toLowerCase()}' : ''; - static getInspectionStatus(String status) { + static getInspectionStatus(BuildContext context, String status) { String _inspectionStatus = ''; if (status == InspectionStatus.inspectionCompleted) { - _inspectionStatus = 'Completed'; + _inspectionStatus = AppLocalizations.of(context)!.completed; } else if (status == InspectionStatus.sentForInspection) { - _inspectionStatus = 'Pending'; + _inspectionStatus = AppLocalizations.of(context)!.pending; } else { _inspectionStatus = capitalize(_inspectionStatus); } diff --git a/lib/widgets/application_card.dart b/lib/widgets/application_card.dart index 4a3a02f36490902c4a63a017c67d91163c1c56b4..c532651081803d7b6a32fc9e8e315b606d1debfa 100644 --- a/lib/widgets/application_card.dart +++ b/lib/widgets/application_card.dart @@ -93,7 +93,8 @@ class _ApplicationCardState extends State<ApplicationCard> { Padding( padding: const EdgeInsets.only(bottom: 10), child: Text( - Helper.getInspectionStatus(widget.application.status), + Helper.getInspectionStatus( + context, widget.application.status), style: GoogleFonts.lato( color: AppColors.black60, fontSize: 14.0, diff --git a/pubspec.lock b/pubspec.lock index bac53e3df62e897c59a04b72512450a7d889e859..4816bf3640122e8b37dbc2592cbe2e8a474edd75 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -64,6 +64,48 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.15.0" + connectivity_plus: + dependency: "direct main" + description: + name: connectivity_plus + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" + connectivity_plus_linux: + dependency: transitive + description: + name: connectivity_plus_linux + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + connectivity_plus_macos: + dependency: transitive + description: + name: connectivity_plus_macos + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + connectivity_plus_platform_interface: + dependency: transitive + description: + name: connectivity_plus_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + connectivity_plus_web: + dependency: transitive + description: + name: connectivity_plus_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + connectivity_plus_windows: + dependency: transitive + description: + name: connectivity_plus_windows + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" crypto: dependency: transitive description: @@ -336,6 +378,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + nm: + dependency: transitive + description: + name: nm + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.0" otp_text_field: dependency: "direct main" description: @@ -446,6 +495,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.1" + sqflite: + dependency: "direct main" + description: + name: sqflite + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" stack_trace: dependency: transitive description: @@ -467,6 +530,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + synchronized: + dependency: transitive + description: + name: synchronized + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c9369854fe713007e093d18f8d000256a4d8dab1..206d2b94fb1b89906ca8e82c2f08e2f4faf13842 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -48,6 +48,8 @@ dependencies: firebase_core: ^1.12.0 flutter_local_notifications: ^9.3.2 unique_identifier: ^0.2.2 + sqflite: ^2.0.2 + connectivity_plus: ^2.2.1 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 0c507538547f58e428ce2941d02b3d53db7bf853..9993ebde9a80b7d5da1db8e490a03da6b197ab0c 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,9 +6,12 @@ #include "generated_plugin_registrant.h" +#include <connectivity_plus_windows/connectivity_plus_windows_plugin.h> #include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h> void RegisterPlugins(flutter::PluginRegistry* registry) { + ConnectivityPlusWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 69b5ae4f581b86acdee0917371d2e73c106fa325..7991aad270330d47c044bff62768dee85fa03c4c 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + connectivity_plus_windows flutter_secure_storage_windows )