Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add offline mode data manager #2322

Closed
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
f8e9002
added new page for offline mode in dev mode
AshAman999 Jun 17, 2022
4526ad9
created a method to get the size and number of
AshAman999 Jun 17, 2022
96afeec
created a page to keep the menu for local db
AshAman999 Jun 17, 2022
1786b66
made ui tweaks
AshAman999 Jun 18, 2022
748ebc2
added method to get fresh local db
AshAman999 Jun 18, 2022
b7c1a4f
added method to clear string list when clearing
AshAman999 Jun 18, 2022
16838e2
refactor and todo's added
AshAman999 Jun 19, 2022
70e668f
clear method and todo added
AshAman999 Jun 19, 2022
996cb33
added method to update the local db with
AshAman999 Jun 19, 2022
c38c954
used refresh indicator + changes requested by marvin
AshAman999 Jun 19, 2022
240b12f
added comments and formatting
AshAman999 Jun 19, 2022
22c5c91
requested changes done
AshAman999 Jun 20, 2022
b7edb5a
code format
AshAman999 Jun 20, 2022
57d35a4
moved size method to local_database.dart
AshAman999 Jun 20, 2022
999a754
Merge branch 'develop' into new-menu-for-offline-mode
AshAman999 Jun 21, 2022
ac635a2
Merge branch 'develop' into new-menu-for-offline-mode
AshAman999 Jun 25, 2022
8a66caf
methods for updation,deletetion and fetching keys
AshAman999 Jun 25, 2022
be0b44e
making sure about what to delete and
AshAman999 Jun 25, 2022
9b2dfc0
code formatting
AshAman999 Jun 25, 2022
d604001
Merge branch 'develop' into new-menu-for-offline-mode
AshAman999 Jun 25, 2022
d203118
moved fucntions to dao's and updated page size
AshAman999 Jun 26, 2022
96eb626
write 500 products at a time
AshAman999 Jun 27, 2022
cbe9e3c
Merge branch 'develop' into new-menu-for-offline-mode
AshAman999 Jul 4, 2022
f414347
package not found fixed
AshAman999 Jul 5, 2022
bc47207
Merge branch 'develop' of https://github.com/openfoodfacts/smooth-app…
AshAman999 Jul 5, 2022
bf3945c
Merge branch 'develop' into new-menu-for-offline-mode
AshAman999 Jul 5, 2022
f72d4eb
import from model/Product
AshAman999 Jul 5, 2022
327e167
Merge branch 'develop' of https://github.com/openfoodfacts/smooth-app…
AshAman999 Jul 12, 2022
c498ea5
new local names for table
AshAman999 Jul 12, 2022
fe811a2
refactorings
AshAman999 Jul 12, 2022
3a96937
Merge branch 'develop' of https://github.com/openfoodfacts/smooth-app…
AshAman999 Jul 12, 2022
1323f99
Update packages/smooth_app/lib/pages/offline_data_page.dart
teolemon Jul 20, 2022
02e5de5
Update packages/smooth_app/lib/database/dao_product_list.dart
teolemon Jul 20, 2022
f435840
Merge branch 'develop' into new-menu-for-offline-mode
teolemon Jul 27, 2022
e182bd6
Merge branch 'develop' into new-menu-for-offline-mode
teolemon Aug 2, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion packages/smooth_app/lib/database/dao_product.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import 'dart:async';
import 'dart:convert';
import 'package:openfoodfacts/model/Product.dart';
import 'dart:io';
AshAman999 marked this conversation as resolved.
Show resolved Hide resolved
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:openfoodfacts/utils/ProductListQueryConfiguration.dart';
import 'package:smooth_app/database/abstract_sql_dao.dart';
import 'package:smooth_app/database/bulk_deletable.dart';
import 'package:smooth_app/database/bulk_manager.dart';
import 'package:smooth_app/database/local_database.dart';
import 'package:smooth_app/database/product_query.dart';
import 'package:sqflite/sqflite.dart';

class DaoProduct extends AbstractSqlDao implements BulkDeletable {
Expand Down Expand Up @@ -60,6 +63,26 @@ class DaoProduct extends AbstractSqlDao implements BulkDeletable {
return result;
}

// returns the no of rows deleted/effected from the table
Future<int> clearAll() async {
AshAman999 marked this conversation as resolved.
Show resolved Hide resolved
final int count = await localDatabase.database.delete(TABLE_PRODUCT);
return count;
}

//returns the approximate size of the database in MB
AshAman999 marked this conversation as resolved.
Show resolved Hide resolved
Future<double> getSize() async {
AshAman999 marked this conversation as resolved.
Show resolved Hide resolved
final String path = localDatabase.database.path;
final File file = File(path);
final double size = file.lengthSync() / 1024 / 1024;
return double.parse(size.floor().toStringAsFixed(2));
}

Future<int> getLength() async {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A getter would be better here

return Sqflite.firstIntValue(await localDatabase.database
.rawQuery('SELECT COUNT(*) FROM $TABLE_PRODUCT')) ??
0;
}

Future<void> put(final Product product) async => putAll(<Product>[product]);

/// Replaces products in database
Expand Down Expand Up @@ -130,4 +153,29 @@ class DaoProduct extends AbstractSqlDao implements BulkDeletable {
json.decode(encodedJson) as Map<String, dynamic>;
return Product.fromJson(decodedJson);
}

Future<String> getFreshLocalDataBase() async {
try {
final List<String> barcodes = await getAllKeys();
if (barcodes.isEmpty) {
return 'List is Empty\nNothing to Refresh';
}
final ProductListQueryConfiguration configuration =
AshAman999 marked this conversation as resolved.
Show resolved Hide resolved
ProductListQueryConfiguration(
barcodes,
fields: ProductQuery.fields,
language: ProductQuery.getLanguage(),
country: ProductQuery.getCountry(),
);
final User user = ProductQuery.getUser();
// TODO(ashaman999): find a better way to do this not sure at what length it will break
final SearchResult products =
await OpenFoodAPIClient.getProductList(user, configuration);
final List<Product>? productList = products.products;
await putAll(productList!);
AshAman999 marked this conversation as resolved.
Show resolved Hide resolved
return 'OK';
} catch (e) {
return 'Error: $e';
}
AshAman999 marked this conversation as resolved.
Show resolved Hide resolved
}
}
10 changes: 10 additions & 0 deletions packages/smooth_app/lib/database/dao_product_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ class DaoProductList extends AbstractDao {
_put(_getKey(productList), newList);
}

void updateTimeStamp(final ProductList productList) {
// TODO(ashaman999): update the timestamp for all the entries with the current timestamp
}

void clear(final ProductList productList) {
final _BarcodeList newList = _BarcodeList.now(<String>[]);
_put(_getKey(productList), newList);
Expand Down Expand Up @@ -289,6 +293,12 @@ class DaoProductList extends AbstractDao {
return result;
}

// returns the number of items cleared from the box
AshAman999 marked this conversation as resolved.
Show resolved Hide resolved
Future<int> clearAll() async {
final int clearedItems = await _getBox().clear();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use a single line instruction here

return clearedItems;
}

/// Returns a write-safe copy of [_BarcodeList] barcodes.
///
/// cf. https://github.com/openfoodfacts/smooth-app/issues/1786
Expand Down
6 changes: 6 additions & 0 deletions packages/smooth_app/lib/database/dao_string_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,10 @@ class DaoStringList extends AbstractDao {
}
return false;
}

// returns the number of items removed from the box
AshAman999 marked this conversation as resolved.
Show resolved Hide resolved
Future<int> removeAll() async {
AshAman999 marked this conversation as resolved.
Show resolved Hide resolved
final int count = await _getBox().clear();
return count;
}
AshAman999 marked this conversation as resolved.
Show resolved Hide resolved
}
183 changes: 183 additions & 0 deletions packages/smooth_app/lib/pages/offline_data.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
import 'package:smooth_app/database/dao_product.dart';
import 'package:smooth_app/database/dao_product_list.dart';
import 'package:smooth_app/database/local_database.dart';
import 'package:smooth_app/generic_lib/loading_dialog.dart';
import 'package:smooth_app/pages/preferences/user_preferences_list_tile.dart';
import 'package:url_launcher/url_launcher.dart';

class OfflineDataScreen extends StatefulWidget {
AshAman999 marked this conversation as resolved.
Show resolved Hide resolved
const OfflineDataScreen();
@override
State<OfflineDataScreen> createState() => _OfflineDataScreenState();
}

int length = 0;
double size = 0;
AshAman999 marked this conversation as resolved.
Show resolved Hide resolved

// TODO(ashaman999): update all the applocalizations
class _OfflineDataScreenState extends State<OfflineDataScreen> {
Future<void> _refreshDetials(DaoProduct daoProduct) async {
AshAman999 marked this conversation as resolved.
Show resolved Hide resolved
length = await daoProduct.getLength();
size = await daoProduct.getSize();
setState(() {});
AshAman999 marked this conversation as resolved.
Show resolved Hide resolved
}

@override
Widget build(BuildContext context) {
final DaoProduct daoProduct = DaoProduct(context.watch<LocalDatabase>());
final DaoProductList daoProductList =
DaoProductList(context.watch<LocalDatabase>());
final MediaQueryData mediaQueryData = MediaQuery.of(context);
final bool dark = Theme.of(context).brightness == Brightness.dark;
const double titleHeightInExpandedMode = 50;
final double backgroundHeight = mediaQueryData.size.height * .20;
final Color? foregroundColor = dark ? null : Colors.black;
return Scaffold(
body: SafeArea(
child: RefreshIndicator(
onRefresh: () async {
await _refreshDetials(daoProduct);
},
child: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: true,
snap: false,
floating: false,
stretch: true,
backgroundColor: dark ? null : Colors.white,
expandedHeight: backgroundHeight + titleHeightInExpandedMode,
foregroundColor: foregroundColor,
systemOverlayStyle: const SystemUiOverlayStyle(
statusBarIconBrightness: Brightness.light,
statusBarBrightness: Brightness.dark,
),
flexibleSpace: FlexibleSpaceBar(
title: Text(
'Offline Mode',
style: TextStyle(color: foregroundColor),
),
background: Padding(
padding: const EdgeInsets.only(
bottom: titleHeightInExpandedMode),
child: SvgPicture.asset(
// TODO(ashaman999): add a proper header image replacing this
'assets/preferences/main.svg',
height: backgroundHeight,
),
),
),
),
SliverList(
delegate: SliverChildListDelegate(
<Widget>[
UserPreferencesListTile(
title: const Text(
'Offline Product Data',
),
subtitle: FutureBuilder<int>(
future: daoProduct.getLength(),
builder: (BuildContext context,
AsyncSnapshot<int> snapshot) {
if (snapshot.hasData) {
return Text(
'${snapshot.data} products available for immediate scanning',
);
} else {
return const Text('Loading...');
}
},
),
trailing: FutureBuilder<double>(
future: daoProduct.getSize(),
builder: (BuildContext context,
AsyncSnapshot<double> snapshot) {
if (snapshot.hasData) {
return Text(
'${snapshot.data} MB',
);
} else {
return const CircularProgressIndicator();
}
},
),
),
UserPreferencesListTile(
trailing: const Icon(Icons.refresh),
onTap: () async {
String? status = await LoadingDialog.run<String>(
context: context,
future: daoProduct.getFreshLocalDataBase(),
title: 'Refreshing \n This may take a while',
dismissible: true,
);
if (status == 'OK') {
AshAman999 marked this conversation as resolved.
Show resolved Hide resolved
status = '$length items refreshed';
}
// TODO(ashaman999): dapproductlist.updateTimestamp();
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(status!),
),
);
},
title: const Text(
'Update Offline Data',
),
subtitle: const Text(
'Update the databse with the latest data from the server',
),
),
UserPreferencesListTile(
trailing: const Icon(Icons.delete),
onTap: () async {
final int noOdDeletedProducts =
await daoProduct.clearAll();
await daoProductList.clearAll();
AshAman999 marked this conversation as resolved.
Show resolved Hide resolved
await _refreshDetials(daoProduct);
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'$noOdDeletedProducts products deleted, freed $size Mb'),
),
);
},
title: const Text(
'Clear Offline Data',
),
subtitle: const Text(
'Clear All Offline Data and Free up space',
),
),
UserPreferencesListTile(
trailing: const Icon(Icons.info),
title: const Text(
'Know More ',
),
subtitle: const Text(
'Click to know more about the offline mode',
),
onTap: () {
// TODO(ashaman999): refrence the acutal link
// Or maybe show something as an alert dialog
launchUrl(
Uri.parse('https://openfoodfacts.org/'),
);
},
),
],
),
),
],
),
),
),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:smooth_app/database/product_query.dart';
import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart';
import 'package:smooth_app/helpers/data_importer/product_list_import_export.dart';
import 'package:smooth_app/helpers/data_importer/smooth_app_data_importer.dart';
import 'package:smooth_app/pages/offline_data.dart';
import 'package:smooth_app/pages/onboarding/onboarding_flow_navigator.dart';
import 'package:smooth_app/pages/preferences/abstract_user_preferences.dart';
import 'package:smooth_app/pages/preferences/user_preferences_dialog_editor.dart';
Expand Down Expand Up @@ -140,6 +141,17 @@ class UserPreferencesDevMode extends AbstractUserPreferences {
title: const Text('Change camera post frame callback duration'),
onTap: () async => _changeCameraPostFrameCallbackDuration(),
),
ListTile(
title: const Text('Offline Mode'),
onTap: () {
Navigator.push<Widget>(
context,
MaterialPageRoute<Widget>(
builder: (BuildContext context) => const OfflineDataScreen(),
),
);
},
),
SwitchListTile(
title: Text(
appLocalizations.dev_preferences_product_additional_features_title,
Expand Down