diff --git a/packages/smooth_app/lib/data_models/tagline/tagline_provider.dart b/packages/smooth_app/lib/data_models/tagline/tagline_provider.dart index f80f1148f16f..6489cb8ab544 100644 --- a/packages/smooth_app/lib/data_models/tagline/tagline_provider.dart +++ b/packages/smooth_app/lib/data_models/tagline/tagline_provider.dart @@ -5,10 +5,14 @@ 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/preferences/user_preferences.dart'; import 'package:smooth_app/data_models/tagline/tagline_model.dart'; +import 'package:smooth_app/pages/preferences/user_preferences_dev_mode.dart'; import 'package:smooth_app/query/product_query.dart'; +import 'package:smooth_app/services/smooth_services.dart'; part 'tagline_json.dart'; @@ -19,10 +23,21 @@ part 'tagline_json.dart'; /// To be notified of changes, listen to this [ChangeNotifier] and more /// particularly to the [state] property class TagLineProvider extends ChangeNotifier { - TagLineProvider() : _state = const TagLineLoading() { + TagLineProvider(UserPreferences preferences) + : _state = const TagLineLoading(), + _preferences = preferences, + _domain = preferences.getDevModeString( + UserPreferencesDevMode.userPreferencesTestEnvDomain) ?? + '', + _prodEnv = preferences + .getFlag(UserPreferencesDevMode.userPreferencesFlagProd) ?? + true { + _preferences.addListener(_onPreferencesChanged); loadTagLine(); } + final UserPreferences _preferences; + TagLineState _state; bool get hasContent => _state is TagLineLoaded; @@ -56,8 +71,10 @@ class TagLineProvider extends ChangeNotifier { () => _parseJSONAndGetLocalizedContent(jsonString!, locale)); if (tagLine == null) { _emit(const TagLineError('Unable to parse the JSON file')); + Logs.e('Unable to parse the Tagline file'); } else { _emit(TagLineLoaded(tagLine)); + Logs.i('TagLine reloaded'); } } @@ -83,12 +100,25 @@ class TagLineProvider extends ChangeNotifier { } } - /// API URL: [https://world.openfoodfacts.org/files/tagline-off-ios-v3.json] - /// or [https://world.openfoodfacts.org/files/tagline-off-android-v3.json] + /// 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] Future _fetchTagLine() async { try { - final http.Response response = - await http.get(Uri.https('world.openfoodfacts.org', _tagLineUrl)); + final UriProductHelper uriProductHelper = ProductQuery.uriProductHelper; + final Map headers = {}; + final Uri uri = uriProductHelper.getUri(path: _tagLineUrl); + + if (uriProductHelper.userInfoForPatch != null) { + headers['Authorization'] = + 'Basic ${base64Encode(utf8.encode(uriProductHelper.userInfoForPatch!))}'; + } + + final http.Response response = await http.get(uri, headers: headers); + + if (response.statusCode == 404) { + Logs.e("Remote file $uri doesn't exist!"); + throw Exception('Incorrect URL= $uri'); + } final String json = const Utf8Decoder().convert(response.bodyBytes); @@ -125,6 +155,32 @@ class TagLineProvider extends ChangeNotifier { file .lastModifiedSync() .isAfter(DateTime.now().add(const Duration(days: -1))); + + bool? _prodEnv; + String? _domain; + + /// [ProductQuery.uriProductHelper] is not synced yet, + /// so we have to check it manually + Future _onPreferencesChanged() async { + final String domain = _preferences.getDevModeString( + UserPreferencesDevMode.userPreferencesTestEnvDomain) ?? + ''; + final bool prodEnv = + _preferences.getFlag(UserPreferencesDevMode.userPreferencesFlagProd) ?? + true; + + if (domain != _domain || prodEnv != _prodEnv) { + _domain = domain; + _prodEnv = prodEnv; + loadTagLine(forceUpdate: true); + } + } + + @override + void dispose() { + _preferences.removeListener(_onPreferencesChanged); + super.dispose(); + } } sealed class TagLineState { diff --git a/packages/smooth_app/lib/main.dart b/packages/smooth_app/lib/main.dart index 76f7ab41574c..79d451e26f8a 100644 --- a/packages/smooth_app/lib/main.dart +++ b/packages/smooth_app/lib/main.dart @@ -106,7 +106,6 @@ late TextContrastProvider _textContrastProvider; final ContinuousScanModel _continuousScanModel = ContinuousScanModel(); final PermissionListener _permissionListener = PermissionListener(permission: Permission.camera); -final TagLineProvider _tagLineProvider = TagLineProvider(); bool _init1done = false; // Had to split init in 2 methods, for test/screenshots reasons. @@ -224,15 +223,20 @@ class _SmoothAppState extends State { provide(_userManagementProvider), provide(_continuousScanModel), provide(_permissionListener), - provide(_tagLineProvider, lazy: true), ], - child: AnimationsLoader( - child: AppNavigator( - observers: [ - SentryNavigatorObserver(), - matomoObserver, - ], - child: Builder(builder: _buildApp), + child: ChangeNotifierProvider( + create: (BuildContext context) => TagLineProvider( + context.read(), + ), + lazy: true, + child: AnimationsLoader( + child: AppNavigator( + observers: [ + SentryNavigatorObserver(), + matomoObserver, + ], + child: Builder(builder: _buildApp), + ), ), ), ); diff --git a/packages/smooth_app/lib/pages/scan/scan_page.dart b/packages/smooth_app/lib/pages/scan/scan_page.dart index d352c36efc6d..500972deb9c8 100644 --- a/packages/smooth_app/lib/pages/scan/scan_page.dart +++ b/packages/smooth_app/lib/pages/scan/scan_page.dart @@ -8,7 +8,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:provider/provider.dart'; import 'package:smooth_app/data_models/continuous_scan_model.dart'; import 'package:smooth_app/data_models/preferences/user_preferences.dart'; -import 'package:smooth_app/data_models/tagline/tagline_provider.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_card.dart'; @@ -61,88 +60,84 @@ class _ScanPageState extends State { Theme.of(context).brightness == Brightness.light && Platform.isIOS ? Brightness.dark : null, - body: ChangeNotifierProvider( - lazy: true, - create: (_) => TagLineProvider(), - child: Container( - color: Colors.white, - child: SafeArea( - child: Container( - color: Theme.of(context).colorScheme.background, - child: Column( - children: [ - if (hasACamera) - Expanded( - flex: 100 - _carouselHeightPct, - child: Consumer( - builder: ( - BuildContext context, - PermissionListener listener, - _, - ) { - switch (listener.value.status) { - case DevicePermissionStatus.checking: - return EMPTY_WIDGET; - case DevicePermissionStatus.granted: - // TODO(m123): change - return const CameraScannerPage(); - default: - return const _PermissionDeniedCard(); - } - }, - ), - ), + body: Container( + color: Colors.white, + child: SafeArea( + child: Container( + color: Theme.of(context).colorScheme.background, + child: Column( + children: [ + if (hasACamera) Expanded( - flex: _carouselHeightPct, - child: Padding( - padding: const EdgeInsetsDirectional.only(bottom: 10.0), - child: SmoothProductCarousel( - containSearchCard: true, - onPageChangedTo: (int page, String? barcode) async { - if (barcode == null) { - // We only notify for new products - return; - } + flex: 100 - _carouselHeightPct, + child: Consumer( + builder: ( + BuildContext context, + PermissionListener listener, + _, + ) { + switch (listener.value.status) { + case DevicePermissionStatus.checking: + return EMPTY_WIDGET; + case DevicePermissionStatus.granted: + // TODO(m123): change + return const CameraScannerPage(); + default: + return const _PermissionDeniedCard(); + } + }, + ), + ), + Expanded( + flex: _carouselHeightPct, + child: Padding( + padding: const EdgeInsetsDirectional.only(bottom: 10.0), + child: SmoothProductCarousel( + containSearchCard: true, + onPageChangedTo: (int page, String? barcode) async { + if (barcode == null) { + // We only notify for new products + return; + } - // Both are Future methods, but it doesn't matter to wait here - SmoothHapticFeedback.lightNotification(); + // Both are Future methods, but it doesn't matter to wait here + SmoothHapticFeedback.lightNotification(); - if (_userPreferences.playCameraSound) { - await _initSoundManagerIfNecessary(); - await _musicPlayer!.stop(); - await _musicPlayer!.play( - AssetSource('audio/beep.wav'), - volume: 0.5, - ctx: const AudioContext( - android: AudioContextAndroid( - isSpeakerphoneOn: false, - stayAwake: false, - contentType: AndroidContentType.sonification, - usageType: AndroidUsageType.notification, - audioFocus: - AndroidAudioFocus.gainTransientMayDuck, - ), - iOS: AudioContextIOS( - category: AVAudioSessionCategory.soloAmbient, - options: [ - AVAudioSessionOptions.mixWithOthers, - ], - ), + if (_userPreferences.playCameraSound) { + await _initSoundManagerIfNecessary(); + await _musicPlayer!.stop(); + await _musicPlayer!.play( + AssetSource('audio/beep.wav'), + volume: 0.5, + ctx: const AudioContext( + android: AudioContextAndroid( + isSpeakerphoneOn: false, + stayAwake: false, + contentType: AndroidContentType.sonification, + usageType: AndroidUsageType.notification, + audioFocus: + AndroidAudioFocus.gainTransientMayDuck, ), - ); - } - - SemanticsService.announce( - appLocalizations.scan_announce_new_barcode(barcode), - direction, - assertiveness: Assertiveness.assertive, + iOS: AudioContextIOS( + category: AVAudioSessionCategory.soloAmbient, + options: [ + AVAudioSessionOptions.mixWithOthers, + ], + ), + ), ); - }, - ), + } + + SemanticsService.announce( + appLocalizations.scan_announce_new_barcode(barcode), + direction, + assertiveness: Assertiveness.assertive, + ); + }, ), ), - ], - ), + ), + ], ), ), ),