diff --git a/.github/labeler.yml b/.github/labeler.yml
index 4fca91ccd05..0000fd33d44 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -284,5 +284,6 @@ Prices:
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/onboarding/currency_selector.dart'
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/onboarding/currency_selector_helper.dart'
-
-
+router:
+- changed-files:
+ - any-glob-to-any-file: 'packages/smooth_app/lib/pages/navigator/app_navigator.dart'
diff --git a/.github/workflows/android-release-to-org-openfoodfacts-scanner.yml b/.github/workflows/android-release-to-org-openfoodfacts-scanner.yml
index 6da9452f366..5bef5c55971 100644
--- a/.github/workflows/android-release-to-org-openfoodfacts-scanner.yml
+++ b/.github/workflows/android-release-to-org-openfoodfacts-scanner.yml
@@ -63,7 +63,7 @@ jobs:
- run: echo Release type ${{ inputs.RELEASE_TYPE }} + Build type ${{ inputs.BUILD_TYPE }} + Tag name null if not github release ${{ inputs.TAG_NAME }}
- name: Setup Java JDK
- uses: actions/setup-java@v4.4.0
+ uses: actions/setup-java@v4.5.0
with:
distribution: 'zulu'
java-version: ${{ env.JAVA_VERSION }}
diff --git a/.github/workflows/dartdoc.yml b/.github/workflows/dartdoc.yml
new file mode 100644
index 00000000000..25b8000db46
--- /dev/null
+++ b/.github/workflows/dartdoc.yml
@@ -0,0 +1,36 @@
+ name: GitHub Pages Deploy Action
+ on:
+ push:
+ branches:
+ - "develop"
+ #- "dartdoc-smoothie"
+ jobs:
+ deploy-pages:
+ name: Deploy to GitHub Pages
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: ./packages/smooth_app
+ steps:
+
+ - name: Chekout code
+ uses: actions/checkout@v4
+
+ - name: Setup Flutter
+ uses: actions/cache@v4
+ with:
+ path: ${{ runner.tool_cache }}/flutter
+ key: flutter-2.5.0-stable
+ - uses: subosito/flutter-action@v2
+ with:
+ channel: stable
+ flutter-version: 2.5.0
+
+ - name: Run Dartdoc
+ run: pub global activate dartdoc && dartdoc
+
+ - name: Deploy API documentation to Github Pages
+ uses: JamesIves/github-pages-deploy-action@v4.6.8
+ with:
+ BRANCH: gh-pages
+ FOLDER: packages/smooth_app/doc/api/
diff --git a/.github/workflows/ios-release-to-org-openfoodfacts-scanner.yml b/.github/workflows/ios-release-to-org-openfoodfacts-scanner.yml
index e77d4592f88..566dd1869a4 100644
--- a/.github/workflows/ios-release-to-org-openfoodfacts-scanner.yml
+++ b/.github/workflows/ios-release-to-org-openfoodfacts-scanner.yml
@@ -61,7 +61,7 @@ jobs:
run: bundle install
working-directory: ./packages/smooth_app/android/
- name: Setup Java JDK
- uses: actions/setup-java@v4.4.0
+ uses: actions/setup-java@v4.5.0
with:
distribution: 'zulu'
java-version: ${{ env.JAVA_VERSION }}
diff --git a/.github/workflows/postsubmit.yml b/.github/workflows/postsubmit.yml
index 243cc88c9f2..fed9ccddf9b 100644
--- a/.github/workflows/postsubmit.yml
+++ b/.github/workflows/postsubmit.yml
@@ -16,7 +16,7 @@ jobs:
uses: actions/checkout@v4
- name: Setup Java JDK
- uses: actions/setup-java@v4.4.0
+ uses: actions/setup-java@v4.5.0
with:
distribution: 'zulu'
java-version: 17
diff --git a/.github/workflows/waldo_sessions.yml b/.github/workflows/waldo_sessions.yml
index b7f0ea59a15..81eb0d98e1d 100644
--- a/.github/workflows/waldo_sessions.yml
+++ b/.github/workflows/waldo_sessions.yml
@@ -27,7 +27,7 @@ jobs:
fetch-depth: 0
- name: Setup Java JDK
- uses: actions/setup-java@v4.4.0
+ uses: actions/setup-java@v4.5.0
with:
distribution: 'zulu'
java-version: 17
diff --git a/flutter-version.txt b/flutter-version.txt
index ffba2c8db92..057aa0d01e9 100644
--- a/flutter-version.txt
+++ b/flutter-version.txt
@@ -1 +1 @@
-3.24.3
\ No newline at end of file
+3.24.4
\ No newline at end of file
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 00000000000..a95314760b6
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,26 @@
+# Where to find documentation
+docs_dir: doc
+
+# Link to Github on every page
+repo_url: https://github.com/openfoodfacts/smooth-app
+edit_uri: blob/develop/docs/
+
+site_name: Open Food Facts mobile app
+site_dir: gh_pages
+
+theme:
+ name: material
+ palette:
+ primary: beige
+ text: black
+ logo: assets/app/logo_text_black.svg
+
+markdown_extensions:
+ - footnotes
+ - mdx_truly_sane_lists
+ - pymdownx.highlight
+ - pymdownx.superfences
+
+plugins:
+ - awesome-pages
+ - search
diff --git a/packages/smooth_app/android/Gemfile.lock b/packages/smooth_app/android/Gemfile.lock
index b087ba1e0f8..444c18b1c6e 100644
--- a/packages/smooth_app/android/Gemfile.lock
+++ b/packages/smooth_app/android/Gemfile.lock
@@ -16,17 +16,17 @@ GEM
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.0)
- aws-partitions (1.984.0)
- aws-sdk-core (3.209.1)
+ aws-partitions (1.992.0)
+ aws-sdk-core (3.210.0)
aws-eventstream (~> 1, >= 1.3.0)
- aws-partitions (~> 1, >= 1.651.0)
+ aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
jmespath (~> 1, >= 1.6.1)
- aws-sdk-kms (1.94.0)
- aws-sdk-core (~> 3, >= 3.207.0)
+ aws-sdk-kms (1.95.0)
+ aws-sdk-core (~> 3, >= 3.210.0)
aws-sigv4 (~> 1.5)
- aws-sdk-s3 (1.167.0)
- aws-sdk-core (~> 3, >= 3.207.0)
+ aws-sdk-s3 (1.169.0)
+ aws-sdk-core (~> 3, >= 3.210.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.10.0)
@@ -74,7 +74,7 @@ GEM
faraday_middleware (1.2.1)
faraday (~> 1.0)
fastimage (2.3.1)
- fastlane (2.224.0)
+ fastlane (2.225.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@@ -90,6 +90,7 @@ GEM
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
+ fastlane-sirp (>= 1.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
@@ -117,6 +118,8 @@ GEM
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
fastlane-plugin-flutter_dart_version_manager (0.1.5)
fastlane-plugin-versioning (0.6.0)
+ fastlane-sirp (1.0.0)
+ sysrandom (~> 1.0)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.54.0)
google-apis-core (>= 0.11.0, < 2.a)
@@ -192,6 +195,7 @@ GEM
simctl (1.6.10)
CFPropertyList
naturally
+ sysrandom (1.0.5)
terminal-notifier (2.0.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
diff --git a/packages/smooth_app/android/app/src/main/AndroidManifest.xml b/packages/smooth_app/android/app/src/main/AndroidManifest.xml
index 8cd6614a446..212f5f042f2 100644
--- a/packages/smooth_app/android/app/src/main/AndroidManifest.xml
+++ b/packages/smooth_app/android/app/src/main/AndroidManifest.xml
@@ -52,7 +52,11 @@
-
+
+
+
+
+
diff --git a/packages/smooth_app/ios/Gemfile.lock b/packages/smooth_app/ios/Gemfile.lock
index f11bf6f002a..b47a9626f5f 100644
--- a/packages/smooth_app/ios/Gemfile.lock
+++ b/packages/smooth_app/ios/Gemfile.lock
@@ -16,17 +16,17 @@ GEM
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.0)
- aws-partitions (1.984.0)
- aws-sdk-core (3.209.1)
+ aws-partitions (1.992.0)
+ aws-sdk-core (3.210.0)
aws-eventstream (~> 1, >= 1.3.0)
- aws-partitions (~> 1, >= 1.651.0)
+ aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
jmespath (~> 1, >= 1.6.1)
- aws-sdk-kms (1.94.0)
- aws-sdk-core (~> 3, >= 3.207.0)
+ aws-sdk-kms (1.95.0)
+ aws-sdk-core (~> 3, >= 3.210.0)
aws-sigv4 (~> 1.5)
- aws-sdk-s3 (1.167.0)
- aws-sdk-core (~> 3, >= 3.207.0)
+ aws-sdk-s3 (1.169.0)
+ aws-sdk-core (~> 3, >= 3.210.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.10.0)
@@ -75,7 +75,7 @@ GEM
faraday_middleware (1.2.1)
faraday (~> 1.0)
fastimage (2.3.1)
- fastlane (2.224.0)
+ fastlane (2.225.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@@ -91,6 +91,7 @@ GEM
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
+ fastlane-sirp (>= 1.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
@@ -118,6 +119,8 @@ GEM
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
fastlane-plugin-flutter_dart_version_manager (0.1.5)
fastlane-plugin-versioning (0.6.0)
+ fastlane-sirp (1.0.0)
+ sysrandom (~> 1.0)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.54.0)
google-apis-core (>= 0.11.0, < 2.a)
@@ -193,6 +196,7 @@ GEM
simctl (1.6.10)
CFPropertyList
naturally
+ sysrandom (1.0.5)
terminal-notifier (2.0.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
diff --git a/packages/smooth_app/ios/Podfile.lock b/packages/smooth_app/ios/Podfile.lock
index c2373332d6c..657bc696e02 100644
--- a/packages/smooth_app/ios/Podfile.lock
+++ b/packages/smooth_app/ios/Podfile.lock
@@ -140,13 +140,14 @@ PODS:
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - sqflite (0.0.3):
+ - sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
- url_launcher_ios (0.0.1):
- Flutter
- webview_flutter_wkwebview (0.0.1):
- Flutter
+ - FlutterMacOS
DEPENDENCIES:
- app_settings (from `.symlinks/plugins/app_settings/ios`)
@@ -174,9 +175,9 @@ DEPENDENCIES:
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- - sqflite (from `.symlinks/plugins/sqflite/darwin`)
+ - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
+ - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`)
SPEC REPOS:
trunk:
@@ -251,12 +252,12 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
- sqflite:
- :path: ".symlinks/plugins/sqflite/darwin"
+ sqflite_darwin:
+ :path: ".symlinks/plugins/sqflite_darwin/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
webview_flutter_wkwebview:
- :path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
+ :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin"
SPEC CHECKSUMS:
app_settings: 017320c6a680cdc94c799949d95b84cb69389ebc
@@ -303,9 +304,9 @@ SPEC CHECKSUMS:
sentry_flutter: 0eb93e5279eb41e2392212afe1ccd2fecb4f8cbe
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
- sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
+ sqflite_darwin: a553b1fd6fe66f53bbb0fe5b4f5bab93f08d7a13
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
- webview_flutter_wkwebview: 2a23822e9039b7b1bc52e5add778e5d89ad488d1
+ webview_flutter_wkwebview: 0982481e3d9c78fd5c6f62a002fcd24fc791f1e4
PODFILE CHECKSUM: e840dd57ba2b03bcb6fdba293a27c5f82151a80a
diff --git a/packages/smooth_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/black_icon-1024.png b/packages/smooth_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/black_icon-1024.png
index 930ffbc2101..371d4f94af3 100644
Binary files a/packages/smooth_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/black_icon-1024.png and b/packages/smooth_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/black_icon-1024.png differ
diff --git a/packages/smooth_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/tinted_icon-1024.png b/packages/smooth_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/tinted_icon-1024.png
index 11efe483231..68b75ce62cc 100644
Binary files a/packages/smooth_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/tinted_icon-1024.png and b/packages/smooth_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/tinted_icon-1024.png differ
diff --git a/packages/smooth_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/white_icon-1024.png b/packages/smooth_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/white_icon-1024.png
index 9e931a597f2..cbb8af90586 100644
Binary files a/packages/smooth_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/white_icon-1024.png and b/packages/smooth_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/white_icon-1024.png differ
diff --git a/packages/smooth_app/ios/Runner/Runner.entitlements b/packages/smooth_app/ios/Runner/Runner.entitlements
index a6d5cc08c4a..550e1759ddd 100644
--- a/packages/smooth_app/ios/Runner/Runner.entitlements
+++ b/packages/smooth_app/ios/Runner/Runner.entitlements
@@ -4,6 +4,9 @@
com.apple.developer.associated-domains
+ applinks:prices.openfoodfacts.org
+ applinks:prices.openfoodfacts.net
+
applinks:ae.openfoodfacts.org
applinks:ar.openfoodfacts.org
applinks:at-en.openfoodfacts.org
@@ -201,6 +204,303 @@
applinks:www.openfoodfacts.net
applinks:za.openfoodfacts.net
applinks:zh.openfoodfacts.net
+
+ applinks:ae.openbeautyfacts.org
+ applinks:ar.openbeautyfacts.org
+ applinks:at-en.openbeautyfacts.org
+ applinks:at.openbeautyfacts.org
+ applinks:au.openbeautyfacts.org
+ applinks:be-de.openbeautyfacts.org
+ applinks:be-en.openbeautyfacts.org
+ applinks:be-fr.openbeautyfacts.org
+ applinks:be.openbeautyfacts.org
+ applinks:bg.openbeautyfacts.org
+ applinks:br.openbeautyfacts.org
+ applinks:ca-fr.openbeautyfacts.org
+ applinks:ca.openbeautyfacts.org
+ applinks:ch-en.openbeautyfacts.org
+ applinks:ch-fr.openbeautyfacts.org
+ applinks:ch-it.openbeautyfacts.org
+ applinks:ch.openbeautyfacts.org
+ applinks:cl.openbeautyfacts.org
+ applinks:cn-en.openbeautyfacts.org
+ applinks:cn.openbeautyfacts.org
+ applinks:co.openbeautyfacts.org
+ applinks:cz.openbeautyfacts.org
+ applinks:de-en.openbeautyfacts.org
+ applinks:de.openbeautyfacts.org
+ applinks:dk.openbeautyfacts.org
+ applinks:dz-fr.openbeautyfacts.org
+ applinks:dz.openbeautyfacts.org
+ applinks:en.openbeautyfacts.org
+ applinks:es-ca.openbeautyfacts.org
+ applinks:es-en.openbeautyfacts.org
+ applinks:es-eu.openbeautyfacts.org
+ applinks:es-gl.openbeautyfacts.org
+ applinks:es.openbeautyfacts.org
+ applinks:fi.openbeautyfacts.org
+ applinks:fr-en.openbeautyfacts.org
+ applinks:fr-es.openbeautyfacts.org
+ applinks:fr.openbeautyfacts.org
+ applinks:gf.openbeautyfacts.org
+ applinks:gp.openbeautyfacts.org
+ applinks:gr.openbeautyfacts.org
+ applinks:hk.openbeautyfacts.org
+ applinks:hk-zh.openbeautyfacts.org
+ applinks:hu.openbeautyfacts.org
+ applinks:id.openbeautyfacts.org
+ applinks:ie.openbeautyfacts.org
+ applinks:il.openbeautyfacts.org
+ applinks:in.openbeautyfacts.org
+ applinks:ir.openbeautyfacts.org
+ applinks:it.openbeautyfacts.org
+ applinks:jp.openbeautyfacts.org
+ applinks:ke.openbeautyfacts.org
+ applinks:kh.openbeautyfacts.org
+ applinks:lu.openbeautyfacts.org
+ applinks:ma-fr.openbeautyfacts.org
+ applinks:ma.openbeautyfacts.org
+ applinks:mq.openbeautyfacts.org
+ applinks:mx.openbeautyfacts.org
+ applinks:nl-en.openbeautyfacts.org
+ applinks:nl.openbeautyfacts.org
+ applinks:no.openbeautyfacts.org
+ applinks:nz.openbeautyfacts.org
+ applinks:pe.openbeautyfacts.org
+ applinks:pf.openbeautyfacts.org
+ applinks:ph.openbeautyfacts.org
+ applinks:pl-en.openbeautyfacts.org
+ applinks:pl.openbeautyfacts.org
+ applinks:openbeautyfacts.org
+ applinks:pt-en.openbeautyfacts.org
+ applinks:pt.openbeautyfacts.org
+ applinks:re.openbeautyfacts.org
+ applinks:ro.openbeautyfacts.org
+ applinks:ru-en.openbeautyfacts.org
+ applinks:ru.openbeautyfacts.org
+ applinks:sa.openbeautyfacts.org
+ applinks:se.openbeautyfacts.org
+ applinks:sg.openbeautyfacts.org
+ applinks:sg-zh.openbeautyfacts.org
+ applinks:sk.openbeautyfacts.org
+ applinks:th.openbeautyfacts.org
+ applinks:tn-en.openbeautyfacts.org
+ applinks:tn.openbeautyfacts.org
+ applinks:tr.openbeautyfacts.org
+ applinks:tw.openbeautyfacts.org
+ applinks:uk.openbeautyfacts.org
+ applinks:us-es.openbeautyfacts.org
+ applinks:us.openbeautyfacts.org
+ applinks:world-de.openbeautyfacts.org
+ applinks:world-en.openbeautyfacts.org
+ applinks:world-es.openbeautyfacts.org
+ applinks:world-fr.openbeautyfacts.org
+ applinks:world-it.openbeautyfacts.org
+ applinks:world.openbeautyfacts.org
+ applinks:world-pt.openbeautyfacts.org
+ applinks:world-ru.openbeautyfacts.org
+ applinks:world-zh.openbeautyfacts.org
+ applinks:www.openbeautyfacts.org
+ applinks:za.openbeautyfacts.org
+ applinks:zh.openbeautyfacts.org
+
+ applinks:ae.openproductsfacts.org
+ applinks:ar.openproductsfacts.org
+ applinks:at-en.openproductsfacts.org
+ applinks:at.openproductsfacts.org
+ applinks:au.openproductsfacts.org
+ applinks:be-de.openproductsfacts.org
+ applinks:be-en.openproductsfacts.org
+ applinks:be-fr.openproductsfacts.org
+ applinks:be.openproductsfacts.org
+ applinks:bg.openproductsfacts.org
+ applinks:br.openproductsfacts.org
+ applinks:ca-fr.openproductsfacts.org
+ applinks:ca.openproductsfacts.org
+ applinks:ch-en.openproductsfacts.org
+ applinks:ch-fr.openproductsfacts.org
+ applinks:ch-it.openproductsfacts.org
+ applinks:ch.openproductsfacts.org
+ applinks:cl.openproductsfacts.org
+ applinks:cn-en.openproductsfacts.org
+ applinks:cn.openproductsfacts.org
+ applinks:co.openproductsfacts.org
+ applinks:cz.openproductsfacts.org
+ applinks:de-en.openproductsfacts.org
+ applinks:de.openproductsfacts.org
+ applinks:dk.openproductsfacts.org
+ applinks:dz-fr.openproductsfacts.org
+ applinks:dz.openproductsfacts.org
+ applinks:en.openproductsfacts.org
+ applinks:es-ca.openproductsfacts.org
+ applinks:es-en.openproductsfacts.org
+ applinks:es-eu.openproductsfacts.org
+ applinks:es-gl.openproductsfacts.org
+ applinks:es.openproductsfacts.org
+ applinks:fi.openproductsfacts.org
+ applinks:fr-en.openproductsfacts.org
+ applinks:fr-es.openproductsfacts.org
+ applinks:fr.openproductsfacts.org
+ applinks:gf.openproductsfacts.org
+ applinks:gp.openproductsfacts.org
+ applinks:gr.openproductsfacts.org
+ applinks:hk.openproductsfacts.org
+ applinks:hk-zh.openproductsfacts.org
+ applinks:hu.openproductsfacts.org
+ applinks:id.openproductsfacts.org
+ applinks:ie.openproductsfacts.org
+ applinks:il.openproductsfacts.org
+ applinks:in.openproductsfacts.org
+ applinks:ir.openproductsfacts.org
+ applinks:it.openproductsfacts.org
+ applinks:jp.openproductsfacts.org
+ applinks:ke.openproductsfacts.org
+ applinks:kh.openproductsfacts.org
+ applinks:lu.openproductsfacts.org
+ applinks:ma-fr.openproductsfacts.org
+ applinks:ma.openproductsfacts.org
+ applinks:mq.openproductsfacts.org
+ applinks:mx.openproductsfacts.org
+ applinks:nl-en.openproductsfacts.org
+ applinks:nl.openproductsfacts.org
+ applinks:no.openproductsfacts.org
+ applinks:nz.openproductsfacts.org
+ applinks:pe.openproductsfacts.org
+ applinks:pf.openproductsfacts.org
+ applinks:ph.openproductsfacts.org
+ applinks:pl-en.openproductsfacts.org
+ applinks:pl.openproductsfacts.org
+ applinks:openproductsfacts.org
+ applinks:pt-en.openproductsfacts.org
+ applinks:pt.openproductsfacts.org
+ applinks:re.openproductsfacts.org
+ applinks:ro.openproductsfacts.org
+ applinks:ru-en.openproductsfacts.org
+ applinks:ru.openproductsfacts.org
+ applinks:sa.openproductsfacts.org
+ applinks:se.openproductsfacts.org
+ applinks:sg.openproductsfacts.org
+ applinks:sg-zh.openproductsfacts.org
+ applinks:sk.openproductsfacts.org
+ applinks:th.openproductsfacts.org
+ applinks:tn-en.openproductsfacts.org
+ applinks:tn.openproductsfacts.org
+ applinks:tr.openproductsfacts.org
+ applinks:tw.openproductsfacts.org
+ applinks:uk.openproductsfacts.org
+ applinks:us-es.openproductsfacts.org
+ applinks:us.openproductsfacts.org
+ applinks:world-de.openproductsfacts.org
+ applinks:world-en.openproductsfacts.org
+ applinks:world-es.openproductsfacts.org
+ applinks:world-fr.openproductsfacts.org
+ applinks:world-it.openproductsfacts.org
+ applinks:world.openproductsfacts.org
+ applinks:world-pt.openproductsfacts.org
+ applinks:world-ru.openproductsfacts.org
+ applinks:world-zh.openproductsfacts.org
+ applinks:www.openproductsfacts.org
+ applinks:za.openproductsfacts.org
+ applinks:zh.openproductsfacts.org
+
+ applinks:ae.openpetfoodfacts.org
+ applinks:ar.openpetfoodfacts.org
+ applinks:at-en.openpetfoodfacts.org
+ applinks:at.openpetfoodfacts.org
+ applinks:au.openpetfoodfacts.org
+ applinks:be-de.openpetfoodfacts.org
+ applinks:be-en.openpetfoodfacts.org
+ applinks:be-fr.openpetfoodfacts.org
+ applinks:be.openpetfoodfacts.org
+ applinks:bg.openpetfoodfacts.org
+ applinks:br.openpetfoodfacts.org
+ applinks:ca-fr.openpetfoodfacts.org
+ applinks:ca.openpetfoodfacts.org
+ applinks:ch-en.openpetfoodfacts.org
+ applinks:ch-fr.openpetfoodfacts.org
+ applinks:ch-it.openpetfoodfacts.org
+ applinks:ch.openpetfoodfacts.org
+ applinks:cl.openpetfoodfacts.org
+ applinks:cn-en.openpetfoodfacts.org
+ applinks:cn.openpetfoodfacts.org
+ applinks:co.openpetfoodfacts.org
+ applinks:cz.openpetfoodfacts.org
+ applinks:de-en.openpetfoodfacts.org
+ applinks:de.openpetfoodfacts.org
+ applinks:dk.openpetfoodfacts.org
+ applinks:dz-fr.openpetfoodfacts.org
+ applinks:dz.openpetfoodfacts.org
+ applinks:en.openpetfoodfacts.org
+ applinks:es-ca.openpetfoodfacts.org
+ applinks:es-en.openpetfoodfacts.org
+ applinks:es-eu.openpetfoodfacts.org
+ applinks:es-gl.openpetfoodfacts.org
+ applinks:es.openpetfoodfacts.org
+ applinks:fi.openpetfoodfacts.org
+ applinks:fr-en.openpetfoodfacts.org
+ applinks:fr-es.openpetfoodfacts.org
+ applinks:fr.openpetfoodfacts.org
+ applinks:gf.openpetfoodfacts.org
+ applinks:gp.openpetfoodfacts.org
+ applinks:gr.openpetfoodfacts.org
+ applinks:hk.openpetfoodfacts.org
+ applinks:hk-zh.openpetfoodfacts.org
+ applinks:hu.openpetfoodfacts.org
+ applinks:id.openpetfoodfacts.org
+ applinks:ie.openpetfoodfacts.org
+ applinks:il.openpetfoodfacts.org
+ applinks:in.openpetfoodfacts.org
+ applinks:ir.openpetfoodfacts.org
+ applinks:it.openpetfoodfacts.org
+ applinks:jp.openpetfoodfacts.org
+ applinks:ke.openpetfoodfacts.org
+ applinks:kh.openpetfoodfacts.org
+ applinks:lu.openpetfoodfacts.org
+ applinks:ma-fr.openpetfoodfacts.org
+ applinks:ma.openpetfoodfacts.org
+ applinks:mq.openpetfoodfacts.org
+ applinks:mx.openpetfoodfacts.org
+ applinks:nl-en.openpetfoodfacts.org
+ applinks:nl.openpetfoodfacts.org
+ applinks:no.openpetfoodfacts.org
+ applinks:nz.openpetfoodfacts.org
+ applinks:pe.openpetfoodfacts.org
+ applinks:pf.openpetfoodfacts.org
+ applinks:ph.openpetfoodfacts.org
+ applinks:pl-en.openpetfoodfacts.org
+ applinks:pl.openpetfoodfacts.org
+ applinks:openpetfoodfacts.org
+ applinks:pt-en.openpetfoodfacts.org
+ applinks:pt.openpetfoodfacts.org
+ applinks:re.openpetfoodfacts.org
+ applinks:ro.openpetfoodfacts.org
+ applinks:ru-en.openpetfoodfacts.org
+ applinks:ru.openpetfoodfacts.org
+ applinks:sa.openpetfoodfacts.org
+ applinks:se.openpetfoodfacts.org
+ applinks:sg.openpetfoodfacts.org
+ applinks:sg-zh.openpetfoodfacts.org
+ applinks:sk.openpetfoodfacts.org
+ applinks:th.openpetfoodfacts.org
+ applinks:tn-en.openpetfoodfacts.org
+ applinks:tn.openpetfoodfacts.org
+ applinks:tr.openpetfoodfacts.org
+ applinks:tw.openpetfoodfacts.org
+ applinks:uk.openpetfoodfacts.org
+ applinks:us-es.openpetfoodfacts.org
+ applinks:us.openpetfoodfacts.org
+ applinks:world-de.openpetfoodfacts.org
+ applinks:world-en.openpetfoodfacts.org
+ applinks:world-es.openpetfoodfacts.org
+ applinks:world-fr.openpetfoodfacts.org
+ applinks:world-it.openpetfoodfacts.org
+ applinks:world.openpetfoodfacts.org
+ applinks:world-pt.openpetfoodfacts.org
+ applinks:world-ru.openpetfoodfacts.org
+ applinks:world-zh.openpetfoodfacts.org
+ applinks:www.openpetfoodfacts.org
+ applinks:za.openpetfoodfacts.org
+ applinks:zh.openpetfoodfacts.org
diff --git a/packages/smooth_app/lib/background/background_task.dart b/packages/smooth_app/lib/background/background_task.dart
index 5344986e3b6..09a166808a8 100644
--- a/packages/smooth_app/lib/background/background_task.dart
+++ b/packages/smooth_app/lib/background/background_task.dart
@@ -18,6 +18,7 @@ abstract class BackgroundTask {
required this.stamp,
final OpenFoodFactsLanguage? language,
}) // TODO(monsieurtanuki): don't store the password in a clear format...
+// TODO(monsieurtanuki): store the uriProductHelper as well
: user = jsonEncode(ProductQuery.getWriteUser().toJson()),
country = ProductQuery.getCountry().offTag,
languageCode = (language ?? ProductQuery.getLanguage()).offTag;
@@ -181,10 +182,6 @@ abstract class BackgroundTask {
/// subtasks that call the next one at the end.
bool get hasImmediateNextTask => false;
-// TODO(monsieurtanuki): store the uriProductHelper as well
- @protected
- UriProductHelper get uriProductHelper => ProductQuery.getUriProductHelper();
-
/// Returns true if tasks with the same stamp would overwrite each-other.
bool isDeduplicable() => true;
}
diff --git a/packages/smooth_app/lib/background/background_task_barcode.dart b/packages/smooth_app/lib/background/background_task_barcode.dart
index dcc36e0d1d4..203e5f4393c 100644
--- a/packages/smooth_app/lib/background/background_task_barcode.dart
+++ b/packages/smooth_app/lib/background/background_task_barcode.dart
@@ -56,7 +56,7 @@ abstract class BackgroundTaskBarcode extends BackgroundTask {
localDatabase: localDatabase,
);
- @override
+ @protected
UriProductHelper get uriProductHelper => ProductQuery.getUriProductHelper(
productType: productType,
);
diff --git a/packages/smooth_app/lib/background/background_task_download_products.dart b/packages/smooth_app/lib/background/background_task_download_products.dart
index 99244ed3cd8..f3ad234c055 100644
--- a/packages/smooth_app/lib/background/background_task_download_products.dart
+++ b/packages/smooth_app/lib/background/background_task_download_products.dart
@@ -18,6 +18,7 @@ class BackgroundTaskDownloadProducts extends BackgroundTaskProgressing {
required super.work,
required super.pageSize,
required super.totalSize,
+ required super.productType,
required this.downloadFlag,
});
@@ -49,12 +50,14 @@ class BackgroundTaskDownloadProducts extends BackgroundTaskProgressing {
required final int totalSize,
required final int soFarSize,
required final int downloadFlag,
+ required final ProductType productType,
}) async {
final String uniqueId = await _operationType.getNewKey(
localDatabase,
soFarSize: soFarSize,
totalSize: totalSize,
work: work,
+ productType: productType,
);
final BackgroundTask task = _getNewTask(
uniqueId,
@@ -62,6 +65,7 @@ class BackgroundTaskDownloadProducts extends BackgroundTaskProgressing {
pageSize,
totalSize,
downloadFlag,
+ productType,
);
await task.addToManager(localDatabase);
}
@@ -77,6 +81,7 @@ class BackgroundTaskDownloadProducts extends BackgroundTaskProgressing {
final int pageSize,
final int totalSize,
final int downloadFlag,
+ final ProductType productType,
) =>
BackgroundTaskDownloadProducts._(
processName: _operationType.processName,
@@ -86,6 +91,7 @@ class BackgroundTaskDownloadProducts extends BackgroundTaskProgressing {
pageSize: pageSize,
totalSize: totalSize,
downloadFlag: downloadFlag,
+ productType: productType,
);
@override
@@ -133,8 +139,6 @@ class BackgroundTaskDownloadProducts extends BackgroundTaskProgressing {
throw Exception('Something bad happened downloading products');
}
final DaoProduct daoProduct = DaoProduct(localDatabase);
- final ProductType? productType =
- ProductQuery.extractProductType(uriProductHelper);
for (final Product product in downloadedProducts) {
if (await _shouldBeUpdated(daoProduct, product.barcode!)) {
await daoProduct.put(
@@ -159,6 +163,7 @@ class BackgroundTaskDownloadProducts extends BackgroundTaskProgressing {
totalSize: totalSize,
soFarSize: totalSize - remaining,
downloadFlag: downloadFlag,
+ productType: productType,
);
}
}
diff --git a/packages/smooth_app/lib/background/background_task_full_refresh.dart b/packages/smooth_app/lib/background/background_task_full_refresh.dart
index b11ddd1c030..a03f27dd362 100644
--- a/packages/smooth_app/lib/background/background_task_full_refresh.dart
+++ b/packages/smooth_app/lib/background/background_task_full_refresh.dart
@@ -5,8 +5,8 @@ import 'package:provider/provider.dart';
import 'package:smooth_app/background/background_task.dart';
import 'package:smooth_app/background/background_task_download_products.dart';
import 'package:smooth_app/background/background_task_paged.dart';
-import 'package:smooth_app/background/background_task_progressing.dart';
import 'package:smooth_app/background/operation_type.dart';
+import 'package:smooth_app/background/work_type.dart';
import 'package:smooth_app/database/dao_product.dart';
import 'package:smooth_app/database/dao_work_barcode.dart';
import 'package:smooth_app/database/local_database.dart';
@@ -66,34 +66,44 @@ class BackgroundTaskFullRefresh extends BackgroundTaskPaged {
final DaoProduct daoProduct = DaoProduct(localDatabase);
final DaoWorkBarcode daoWorkBarcode = DaoWorkBarcode(localDatabase);
- await daoWorkBarcode
- .deleteWork(BackgroundTaskProgressing.workFreshWithoutKP);
- await daoWorkBarcode.deleteWork(BackgroundTaskProgressing.workFreshWithKP);
+ for (final ProductType productType in ProductType.values) {
+ await daoWorkBarcode.deleteWork(
+ WorkType.freshKP.getWorkTag(productType),
+ );
+ await daoWorkBarcode.deleteWork(
+ WorkType.freshNoKP.getWorkTag(productType),
+ );
+ }
- // We separate the products into two lists, products with or without
- // knowledge panels
- final List barcodes = await daoProduct.getAllKeys();
- final List productsWithoutKP = [];
- final List productsWithKP = [];
- for (final String barcode in barcodes) {
- if (await _shouldBeDownloadedWithoutKP(daoProduct, barcode)) {
- productsWithoutKP.add(barcode);
- } else {
- productsWithKP.add(barcode);
+ // We separate the products into lists, products with or without
+ // knowledge panels, and split by product types.
+ final Map> split = await daoProduct.splitAllProducts(
+ (Product product) {
+ final bool noKP = product.knowledgePanels == null;
+ final WorkType workType = noKP ? WorkType.freshNoKP : WorkType.freshKP;
+ final ProductType productType = product.productType ?? ProductType.food;
+ return workType.getWorkTag(productType);
+ },
+ );
+ for (int i = 0; i <= 1; i++) {
+ final bool noKP = i == 0;
+ final WorkType workType = noKP ? WorkType.freshNoKP : WorkType.freshKP;
+ for (final ProductType productType in ProductType.values) {
+ final String tag = workType.getWorkTag(productType);
+ final List? barcodes = split[tag];
+ if (barcodes == null) {
+ continue;
+ }
+ await _startDownloadTask(
+ barcodes: barcodes,
+ work: tag,
+ localDatabase: localDatabase,
+ downloadFlag:
+ noKP ? BackgroundTaskDownloadProducts.flagMaskExcludeKP : 0,
+ productType: productType,
+ );
}
}
- await _startDownloadTask(
- barcodes: productsWithoutKP,
- work: BackgroundTaskProgressing.workFreshWithoutKP,
- localDatabase: localDatabase,
- downloadFlag: BackgroundTaskDownloadProducts.flagMaskExcludeKP,
- );
- await _startDownloadTask(
- barcodes: productsWithKP,
- work: BackgroundTaskProgressing.workFreshWithKP,
- localDatabase: localDatabase,
- downloadFlag: 0,
- );
}
@override
@@ -102,24 +112,12 @@ class BackgroundTaskFullRefresh extends BackgroundTaskPaged {
@override
bool hasImmediateNextTask = false;
- /// Returns true if we should download data without KP.
- ///
- /// That happens in one case:
- /// * we already have a corresponding local product that does not have
- /// populated knowledge panel fields.
- static Future _shouldBeDownloadedWithoutKP(
- final DaoProduct daoProduct,
- final String barcode,
- ) async {
- final Product? product = await daoProduct.get(barcode);
- return product != null && product.knowledgePanels == null;
- }
-
Future _startDownloadTask({
required final List barcodes,
required final String work,
required final LocalDatabase localDatabase,
required final int downloadFlag,
+ required final ProductType productType,
}) async {
if (barcodes.isEmpty) {
return;
@@ -134,6 +132,7 @@ class BackgroundTaskFullRefresh extends BackgroundTaskPaged {
totalSize: barcodes.length,
soFarSize: 0,
downloadFlag: downloadFlag,
+ productType: productType,
);
}
}
diff --git a/packages/smooth_app/lib/background/background_task_language_refresh.dart b/packages/smooth_app/lib/background/background_task_language_refresh.dart
index cec03a1f8aa..cd0c88056c3 100644
--- a/packages/smooth_app/lib/background/background_task_language_refresh.dart
+++ b/packages/smooth_app/lib/background/background_task_language_refresh.dart
@@ -14,10 +14,15 @@ class BackgroundTaskLanguageRefresh extends BackgroundTask {
required super.uniqueId,
required super.stamp,
required this.excludeBarcodes,
+ required this.productType,
});
BackgroundTaskLanguageRefresh.fromJson(super.json)
: excludeBarcodes = _getStringList(json, _jsonTagExcludeBarcodes),
+ productType =
+ ProductType.fromOffTag(json[_jsonTagProductType] as String?) ??
+// for legacy reason (not refreshed products = no product type)
+ ProductType.food,
super.fromJson();
static List _getStringList(
@@ -32,28 +37,48 @@ class BackgroundTaskLanguageRefresh extends BackgroundTask {
}
final List excludeBarcodes;
+ final ProductType productType;
static const String _jsonTagExcludeBarcodes = 'excludeBarcodes';
+ static const String _jsonTagProductType = 'productType';
@override
Map toJson() {
final Map result = super.toJson();
result[_jsonTagExcludeBarcodes] = excludeBarcodes;
+ result[_jsonTagProductType] = productType.offTag;
return result;
}
static const OperationType _operationType = OperationType.languageRefresh;
+ UriProductHelper get _uriProductHelper => ProductQuery.getUriProductHelper(
+ productType: productType,
+ );
+
static Future addTask(
final LocalDatabase localDatabase, {
final List excludeBarcodes = const [],
+ final ProductType? productType,
}) async {
+ if (productType == null) {
+ for (final ProductType item in ProductType.values) {
+ await addTask(
+ localDatabase,
+ excludeBarcodes: excludeBarcodes,
+ productType: item,
+ );
+ }
+ return;
+ }
final String uniqueId = await _operationType.getNewKey(
localDatabase,
+ productType: productType,
);
final BackgroundTask task = _getNewTask(
uniqueId,
excludeBarcodes,
+ productType,
);
await task.addToManager(localDatabase);
}
@@ -66,12 +91,14 @@ class BackgroundTaskLanguageRefresh extends BackgroundTask {
static BackgroundTask _getNewTask(
final String uniqueId,
final List excludeBarcodes,
+ final ProductType productType,
) =>
BackgroundTaskLanguageRefresh._(
processName: _operationType.processName,
uniqueId: uniqueId,
- stamp: ';languageRefresh',
+ stamp: ';languageRefresh;${productType.offTag}',
excludeBarcodes: excludeBarcodes,
+ productType: productType,
);
@override
@@ -91,6 +118,7 @@ class BackgroundTaskLanguageRefresh extends BackgroundTask {
language,
limit: _pageSize,
excludeBarcodes: excludeBarcodes,
+ productType: productType,
);
if (barcodes.isEmpty) {
return;
@@ -108,7 +136,7 @@ class BackgroundTaskLanguageRefresh extends BackgroundTask {
country: ProductQuery.getCountry(),
version: ProductQuery.productQueryVersion,
),
- uriHelper: uriProductHelper,
+ uriHelper: _uriProductHelper,
);
if (searchResult.products == null || searchResult.count == null) {
throw Exception('Cannot refresh language');
diff --git a/packages/smooth_app/lib/background/background_task_manager.dart b/packages/smooth_app/lib/background/background_task_manager.dart
index 66704385daf..2ca1815d627 100644
--- a/packages/smooth_app/lib/background/background_task_manager.dart
+++ b/packages/smooth_app/lib/background/background_task_manager.dart
@@ -40,7 +40,7 @@ class BackgroundTaskManager {
);
await DaoStringList(localDatabase).add(DaoStringList.keyTasks, taskId);
await task.preExecute(localDatabase);
- run();
+ run(forceNowIfPossible: true);
}
/// Finishes a task cleanly.
@@ -103,11 +103,13 @@ class BackgroundTaskManager {
static const int _minimumDurationBetweenRuns = 5 * 1000;
/// Returns the "now" timestamp if we can run now, or `null`.
- int? _canStartNow() {
- final DaoInt daoInt = DaoInt(localDatabase);
+ ///
+ /// With [forceNowIfPossible] we can be more aggressive and force the decision
+ /// of running now or at least just after the current running block.
+ int? _canStartNow(final bool forceNowIfPossible) {
final int now = LocalDatabase.nowInMillis();
- final int? latestRunStart = daoInt.get(_lastStartTimestampKey);
- final int? latestRunStop = daoInt.get(_lastStopTimestampKey);
+ final int? latestRunStart = localDatabase.daoIntGet(_lastStartTimestampKey);
+ final int? latestRunStop = localDatabase.daoIntGet(_lastStopTimestampKey);
if (_running) {
// if pretending to be running but started a very very long time ago
if (latestRunStart != null &&
@@ -115,6 +117,10 @@ class BackgroundTaskManager {
// we assume we can run now.
return now;
}
+ // let's try again at the end of the current run.
+ if (forceNowIfPossible) {
+ _forceRunAgain = true;
+ }
return null;
}
// if the last run stopped correctly or was started a long time ago.
@@ -123,7 +129,10 @@ class BackgroundTaskManager {
// if the last run stopped not enough time ago.
if (latestRunStop != null &&
latestRunStop + _minimumDurationBetweenRuns >= now) {
- return null;
+ // let's apply that minimum duration if there's no rush
+ if (!forceNowIfPossible) {
+ return null;
+ }
}
return now;
}
@@ -132,8 +141,8 @@ class BackgroundTaskManager {
/// Signals we've just finished working and that we're ready for a new run.
Future _justFinished() async {
- await DaoInt(localDatabase).put(_lastStartTimestampKey, null);
- await DaoInt(localDatabase).put(
+ await localDatabase.daoIntPut(_lastStartTimestampKey, null);
+ await localDatabase.daoIntPut(
_lastStopTimestampKey,
LocalDatabase.nowInMillis(),
);
@@ -141,11 +150,18 @@ class BackgroundTaskManager {
bool _running = false;
+ /// Flag to say: I know you're running, please try again, it's worth it.
+ bool _forceRunAgain = false;
+
/// Runs all the pending tasks, and then smoothly ends, without awaiting.
- void run() {
- // no await
- _runAsync();
- }
+ ///
+ /// Can be called in 2 cases:
+ /// 1. we've just created a task and we really want it to be executed ASAP
+ /// `forceNowIfPossible = true`
+ /// 2. we're just checking casually if there are pending tasks
+ /// `forceNowIfPossible = false`
+ void run({final bool forceNowIfPossible = false}) =>
+ unawaited(_runAsync(forceNowIfPossible));
/// Runs all the pending tasks, and then smoothly ends.
///
@@ -154,19 +170,17 @@ class BackgroundTaskManager {
/// If a task fails and another task with the same stamp comes after,
/// we can remove the failed task from the list: it would have been
/// overwritten anyway.
- Future _runAsync() async {
- final int? now = _canStartNow();
+ Future _runAsync(final bool forceNowIfPossible) async {
+ final int? now = _canStartNow(forceNowIfPossible);
if (now == null) {
return;
}
_running = true;
- ///
/// Will also set the "latest start timestamp".
/// With this, we can detect a run that went wrong.
/// Like, still running 1 hour later.
- final DaoInt daoInt = DaoInt(localDatabase);
- await daoInt.put(_lastStartTimestampKey, now);
+ await localDatabase.daoIntPut(_lastStartTimestampKey, now);
bool runAgain = true;
while (runAgain) {
runAgain = false;
@@ -196,6 +210,12 @@ class BackgroundTaskManager {
}
}
await _justFinished();
+ if (!runAgain) {
+ if (_forceRunAgain) {
+ runAgain = true;
+ _forceRunAgain = false;
+ }
+ }
}
_running = false;
}
diff --git a/packages/smooth_app/lib/background/background_task_offline.dart b/packages/smooth_app/lib/background/background_task_offline.dart
index a8e2b03929f..fba57b31924 100644
--- a/packages/smooth_app/lib/background/background_task_offline.dart
+++ b/packages/smooth_app/lib/background/background_task_offline.dart
@@ -1,10 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:provider/provider.dart';
import 'package:smooth_app/background/background_task.dart';
import 'package:smooth_app/background/background_task_progressing.dart';
import 'package:smooth_app/background/background_task_top_barcodes.dart';
import 'package:smooth_app/background/operation_type.dart';
+import 'package:smooth_app/background/work_type.dart';
import 'package:smooth_app/database/dao_work_barcode.dart';
import 'package:smooth_app/database/local_database.dart';
@@ -17,6 +19,7 @@ class BackgroundTaskOffline extends BackgroundTaskProgressing {
required super.work,
required super.pageSize,
required super.totalSize,
+ required super.productType,
});
BackgroundTaskOffline.fromJson(super.json) : super.fromJson();
@@ -27,6 +30,7 @@ class BackgroundTaskOffline extends BackgroundTaskProgressing {
required final BuildContext context,
required final int pageSize,
required final int totalSize,
+ required final ProductType productType,
}) async {
final LocalDatabase localDatabase = context.read();
final String uniqueId = await _operationType.getNewKey(
@@ -36,9 +40,10 @@ class BackgroundTaskOffline extends BackgroundTaskProgressing {
);
final BackgroundTask task = _getNewTask(
uniqueId,
- BackgroundTaskProgressing.workOffline,
+ WorkType.offline.getWorkTag(productType),
pageSize,
totalSize,
+ productType,
);
if (!context.mounted) {
return;
@@ -59,6 +64,7 @@ class BackgroundTaskOffline extends BackgroundTaskProgressing {
final String work,
final int pageSize,
final int totalSize,
+ final ProductType productType,
) =>
BackgroundTaskOffline._(
processName: _operationType.processName,
@@ -67,6 +73,7 @@ class BackgroundTaskOffline extends BackgroundTaskProgressing {
work: work,
pageSize: pageSize,
totalSize: totalSize,
+ productType: productType,
);
@override
@@ -85,6 +92,7 @@ class BackgroundTaskOffline extends BackgroundTaskProgressing {
pageSize: pageSize,
totalSize: totalSize,
soFarSize: 0,
+ productType: productType,
);
}
}
diff --git a/packages/smooth_app/lib/background/background_task_progressing.dart b/packages/smooth_app/lib/background/background_task_progressing.dart
index dca7c12b3ac..c7e92dabf47 100644
--- a/packages/smooth_app/lib/background/background_task_progressing.dart
+++ b/packages/smooth_app/lib/background/background_task_progressing.dart
@@ -1,4 +1,7 @@
+import 'package:flutter/foundation.dart';
+import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:smooth_app/background/background_task_paged.dart';
+import 'package:smooth_app/query/product_query.dart';
/// Abstract background task with work in progress actions.
abstract class BackgroundTaskProgressing extends BackgroundTaskPaged {
@@ -9,35 +12,39 @@ abstract class BackgroundTaskProgressing extends BackgroundTaskPaged {
required super.pageSize,
required this.work,
required this.totalSize,
+ required this.productType,
});
BackgroundTaskProgressing.fromJson(super.json)
: work = json[_jsonTagWork] as String,
totalSize = json[_jsonTagTotalSize] as int,
+ productType =
+ ProductType.fromOffTag(json[_jsonTagProductType] as String?) ??
+// for legacy reason (not refreshed products = no product type)
+ ProductType.food,
super.fromJson();
final String work;
final int totalSize;
+ final ProductType productType;
static const String _jsonTagWork = 'work';
static const String _jsonTagTotalSize = 'totalSize';
+ static const String _jsonTagProductType = 'productType';
@override
Map toJson() {
final Map result = super.toJson();
result[_jsonTagWork] = work;
result[_jsonTagTotalSize] = totalSize;
+ result[_jsonTagProductType] = productType.offTag;
return result;
}
- static const String noBarcode = 'NO_BARCODE';
-
- /// Work about downloading top products.
- static const String workOffline = 'O';
+ @protected
+ UriProductHelper get uriProductHelper => ProductQuery.getUriProductHelper(
+ productType: productType,
+ );
- /// Work about downloading fresh products with Knowledge Panels.
- static const String workFreshWithKP = 'K';
-
- /// Work about downloading fresh products without Knowledge Panels.
- static const String workFreshWithoutKP = 'w';
+ static const String noBarcode = 'NO_BARCODE';
}
diff --git a/packages/smooth_app/lib/background/background_task_top_barcodes.dart b/packages/smooth_app/lib/background/background_task_top_barcodes.dart
index bf00f099e56..846422bf81d 100644
--- a/packages/smooth_app/lib/background/background_task_top_barcodes.dart
+++ b/packages/smooth_app/lib/background/background_task_top_barcodes.dart
@@ -18,6 +18,7 @@ class BackgroundTaskTopBarcodes extends BackgroundTaskProgressing {
required super.work,
required super.pageSize,
required super.totalSize,
+ required super.productType,
required this.pageNumber,
});
@@ -44,12 +45,14 @@ class BackgroundTaskTopBarcodes extends BackgroundTaskProgressing {
required final int pageSize,
required final int totalSize,
required final int soFarSize,
+ required final ProductType productType,
final int pageNumber = 1,
}) async {
final String uniqueId = await _operationType.getNewKey(
localDatabase,
totalSize: totalSize,
soFarSize: soFarSize,
+ productType: productType,
);
final BackgroundTask task = _getNewTask(
uniqueId,
@@ -57,6 +60,7 @@ class BackgroundTaskTopBarcodes extends BackgroundTaskProgressing {
pageSize,
totalSize,
pageNumber,
+ productType,
);
await task.addToManager(localDatabase);
}
@@ -72,6 +76,7 @@ class BackgroundTaskTopBarcodes extends BackgroundTaskProgressing {
final int pageSize,
final int totalSize,
final int pageNumber,
+ final ProductType productType,
) =>
BackgroundTaskTopBarcodes._(
processName: _operationType.processName,
@@ -81,6 +86,7 @@ class BackgroundTaskTopBarcodes extends BackgroundTaskProgressing {
pageSize: pageSize,
totalSize: totalSize,
pageNumber: pageNumber,
+ productType: productType,
);
@override
@@ -131,6 +137,7 @@ class BackgroundTaskTopBarcodes extends BackgroundTaskProgressing {
totalSize: newTotalSize,
soFarSize: soFarAfter,
pageNumber: pageNumber + 1,
+ productType: productType,
);
} else {
// we have all the barcodes; now we need to download the products.
@@ -141,6 +148,7 @@ class BackgroundTaskTopBarcodes extends BackgroundTaskProgressing {
totalSize: soFarAfter,
soFarSize: 0,
downloadFlag: BackgroundTaskDownloadProducts.flagMaskExcludeKP,
+ productType: productType,
);
}
}
diff --git a/packages/smooth_app/lib/background/operation_type.dart b/packages/smooth_app/lib/background/operation_type.dart
index f1d83e874ce..c86ca37b6d2 100644
--- a/packages/smooth_app/lib/background/operation_type.dart
+++ b/packages/smooth_app/lib/background/operation_type.dart
@@ -1,4 +1,5 @@
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:smooth_app/background/background_task.dart';
import 'package:smooth_app/background/background_task_add_other_price.dart';
import 'package:smooth_app/background/background_task_add_price.dart';
@@ -58,6 +59,7 @@ enum OperationType {
final int? totalSize,
final int? soFarSize,
final String? work,
+ final ProductType? productType,
}) async {
final int sequentialId =
await getNextSequenceNumber(DaoInt(localDatabase), _uniqueSequenceKey);
@@ -66,7 +68,8 @@ enum OperationType {
'$_transientHeaderSeparator$barcode'
'$_transientHeaderSeparator${totalSize == null ? '' : totalSize.toString()}'
'$_transientHeaderSeparator${soFarSize == null ? '' : soFarSize.toString()}'
- '$_transientHeaderSeparator${work ?? ''}';
+ '$_transientHeaderSeparator${work ?? ''}'
+ '$_transientHeaderSeparator${productType == null ? '' : productType.offTag}';
}
BackgroundTask fromJson(Map map) => switch (this) {
diff --git a/packages/smooth_app/lib/background/work_type.dart b/packages/smooth_app/lib/background/work_type.dart
new file mode 100644
index 00000000000..257a6e8af1e
--- /dev/null
+++ b/packages/smooth_app/lib/background/work_type.dart
@@ -0,0 +1,62 @@
+import 'package:openfoodfacts/openfoodfacts.dart';
+
+/// Type of long download work for some background tasks.
+enum WorkType {
+ /// Top products.
+ offline(
+ tag: 'O',
+ englishLabel: 'Top products',
+ ),
+
+ /// Fresh products with Knowledge Panels.
+ freshKP(
+ tag: 'K',
+ englishLabel: 'Refresh products with KP',
+ ),
+
+ /// Fresh products without Knowledge Panels.
+ freshNoKP(
+ tag: 'w',
+ englishLabel: 'Refresh products without KP',
+ );
+
+ const WorkType({
+ required this.tag,
+ required this.englishLabel,
+ });
+
+ final String tag;
+ final String englishLabel;
+
+ String getWorkTag(final ProductType productType) =>
+ '$tag:${productType.offTag}';
+
+ static (WorkType, ProductType)? extract(final String string) {
+ if (string.isEmpty) {
+ return null;
+ }
+ final List strings = string.split(':');
+ if (strings.length > 2) {
+ return null;
+ }
+ final ProductType productType;
+ if (strings.length == 1) {
+ productType = ProductType.food;
+ } else {
+ productType = ProductType.fromOffTag(strings[1])!;
+ }
+ final WorkType workType = fromTag(strings[0])!;
+ return (workType, productType);
+ }
+
+ static WorkType? fromTag(
+ final String tag,
+ ) {
+ for (final WorkType workType in values) {
+ if (workType.tag == tag) {
+ return workType;
+ }
+ }
+ return null;
+ }
+}
diff --git a/packages/smooth_app/lib/data_models/news_feed/newsfeed_provider.dart b/packages/smooth_app/lib/data_models/news_feed/newsfeed_provider.dart
index 2fe18d8d600..3ee10f2fe91 100644
--- a/packages/smooth_app/lib/data_models/news_feed/newsfeed_provider.dart
+++ b/packages/smooth_app/lib/data_models/news_feed/newsfeed_provider.dart
@@ -5,7 +5,6 @@ import 'dart:isolate';
import 'package:collection/collection.dart';
import 'package:flutter/widgets.dart';
import 'package:http/http.dart' as http;
-import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:smooth_app/data_models/news_feed/newsfeed_model.dart';
@@ -28,8 +27,6 @@ class AppNewsProvider extends ChangeNotifier {
_preferences = preferences,
_uriOverride = preferences.getDevModeString(
UserPreferencesDevMode.userPreferencesCustomNewsJSONURI),
- _domain = preferences.getDevModeString(
- UserPreferencesDevMode.userPreferencesTestEnvDomain),
_prodEnv = preferences
.getFlag(UserPreferencesDevMode.userPreferencesFlagProd) {
_preferences.addListener(_onPreferencesChanged);
@@ -102,27 +99,20 @@ class AppNewsProvider extends ChangeNotifier {
}
}
- /// API URL: [https://world.openfoodfacts.[org/net]/resources/files/tagline-off-ios-v3.json]
- /// or [https://world.openfoodfacts.[org/net]/resources/files/tagline-off-android-v3.json]
+ /// The content is stored on GitHub with two static files: one for Android,
+ /// and the other for iOS.
+ /// [https://github.com/openfoodfacts/smooth-app_assets/tree/main/prod/tagline]
Future _fetchJSON() async {
try {
- final UriProductHelper uriProductHelper =
- ProductQuery.getUriProductHelper();
- final Map headers = {};
final Uri uri;
if (_uriOverride?.isNotEmpty == true) {
uri = Uri.parse(_uriOverride!);
} else {
- uri = uriProductHelper.getUri(path: _newsUrl);
+ uri = Uri.parse(_newsUrl);
}
- if (uriProductHelper.userInfoForPatch != null) {
- headers['Authorization'] =
- 'Basic ${base64Encode(utf8.encode(uriProductHelper.userInfoForPatch!))}';
- }
-
- final http.Response response = await http.get(uri, headers: headers);
+ final http.Response response = await http.get(uri);
if (response.statusCode == 404) {
Logs.e("Remote file $uri doesn't exist!");
@@ -143,10 +133,12 @@ class AppNewsProvider extends ChangeNotifier {
/// Based on the platform, the URL may differ
String get _newsUrl {
+ final String env = _prodEnv != false ? 'prod' : 'dev';
+
if (Platform.isIOS || Platform.isMacOS) {
- return '/resources/files/tagline-off-ios-v3.json';
+ return 'https://raw.githubusercontent.com/openfoodfacts/smooth-app_assets/refs/heads/main/$env/tagline/ios/main.json';
} else {
- return '/resources/files/tagline-off-android-v3.json';
+ return 'https://raw.githubusercontent.com/openfoodfacts/smooth-app_assets/refs/heads/main/$env/tagline/android/main.json';
}
}
@@ -166,24 +158,19 @@ class AppNewsProvider extends ChangeNotifier {
.isAfter(DateTime.now().add(const Duration(days: -1)));
bool? _prodEnv;
- String? _domain;
String? _uriOverride;
- /// [ProductQuery.uriProductHelper] is not synced yet,
+ /// [ProductQuery._uriProductHelper] is not synced yet,
/// so we have to check it manually
Future _onPreferencesChanged() async {
final String jsonURI = _preferences.getDevModeString(
UserPreferencesDevMode.userPreferencesCustomNewsJSONURI) ??
'';
- final String domain = _preferences.getDevModeString(
- UserPreferencesDevMode.userPreferencesTestEnvDomain) ??
- '';
final bool prodEnv =
_preferences.getFlag(UserPreferencesDevMode.userPreferencesFlagProd) ??
true;
- if (domain != _domain || prodEnv != _prodEnv || jsonURI != _uriOverride) {
- _domain = domain;
+ if (prodEnv != _prodEnv || jsonURI != _uriOverride) {
_prodEnv = prodEnv;
_uriOverride = jsonURI;
loadLatestNews(forceUpdate: true);
diff --git a/packages/smooth_app/lib/database/dao_product.dart b/packages/smooth_app/lib/database/dao_product.dart
index de35e277776..1b677ae7ae9 100644
--- a/packages/smooth_app/lib/database/dao_product.dart
+++ b/packages/smooth_app/lib/database/dao_product.dart
@@ -92,6 +92,65 @@ class DaoProduct extends AbstractSqlDao implements BulkDeletable {
return result;
}
+ /// Returns the local products split by product type.
+ Future