mirror of
https://github.com/openfoodfacts/smooth-app.git
synced 2025-08-26 11:16:45 +08:00
refactor: Rename tagline*
to AppNews
(#5377)
* Rename `tagline*` to `AppNews` * Fix labeler path
This commit is contained in:
2
.github/labeler.yml
vendored
2
.github/labeler.yml
vendored
@ -240,7 +240,7 @@ User lists:
|
||||
|
||||
Product scan carousel:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'packages/smooth_app/lib/widgets/smooth_product_carousel.dart'
|
||||
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/scan/carousel/scan_carousel.dart'
|
||||
|
||||
✏️ Editing - 📦 Packaging input:
|
||||
- changed-files:
|
||||
|
@ -1,6 +1,6 @@
|
||||
part of 'tagline_provider.dart';
|
||||
part of 'newsfeed_provider.dart';
|
||||
|
||||
/// Content from the JSON and converted to what's in "tagmodel.dart"
|
||||
/// Content from the JSON and converted to what's in "newsfeed_model.dart"
|
||||
|
||||
class _TagLineJSON {
|
||||
_TagLineJSON.fromJson(Map<dynamic, dynamic> json)
|
||||
@ -15,17 +15,16 @@ class _TagLineJSON {
|
||||
final _TagLineJSONNewsList news;
|
||||
final _TaglineJSONFeed taglineFeed;
|
||||
|
||||
TagLine toTagLine(String locale) {
|
||||
final Map<String, TagLineNewsItem> tagLineNews = news.map(
|
||||
(String key, _TagLineItemNewsItem value) =>
|
||||
MapEntry<String, TagLineNewsItem>(
|
||||
AppNews toTagLine(String locale) {
|
||||
final Map<String, AppNewsItem> tagLineNews = news.map(
|
||||
(String key, _TagLineItemNewsItem value) => MapEntry<String, AppNewsItem>(
|
||||
key,
|
||||
value.toTagLineItem(locale),
|
||||
),
|
||||
);
|
||||
|
||||
final _TagLineJSONFeedLocale localizedFeed = taglineFeed.loadNews(locale);
|
||||
final Iterable<TagLineFeedItem> feed = localizedFeed.news
|
||||
final Iterable<AppNewsFeedItem> feed = localizedFeed.news
|
||||
.map((_TagLineJSONFeedLocaleItem item) {
|
||||
if (news[item.id] == null) {
|
||||
// The asked ID doesn't exist in the news
|
||||
@ -33,16 +32,16 @@ class _TagLineJSON {
|
||||
}
|
||||
return item.overrideNewsItem(news[item.id]!, locale);
|
||||
})
|
||||
.where((TagLineFeedItem? item) =>
|
||||
.where((AppNewsFeedItem? item) =>
|
||||
item != null &&
|
||||
(item.startDate == null ||
|
||||
item.startDate!.isBefore(DateTime.now())) &&
|
||||
(item.endDate == null || item.endDate!.isAfter(DateTime.now())))
|
||||
.whereNotNull();
|
||||
|
||||
return TagLine(
|
||||
news: TagLineNewsList(tagLineNews),
|
||||
feed: TagLineFeed(
|
||||
return AppNews(
|
||||
news: AppNewsList(tagLineNews),
|
||||
feed: AppNewsFeed(
|
||||
feed.toList(growable: false),
|
||||
),
|
||||
);
|
||||
@ -106,10 +105,10 @@ class _TagLineItemNewsItem {
|
||||
return _translations['default']!.merge(translation);
|
||||
}
|
||||
|
||||
TagLineNewsItem toTagLineItem(String locale) {
|
||||
AppNewsItem toTagLineItem(String locale) {
|
||||
final _TagLineItemNewsTranslation translation = loadTranslation(locale);
|
||||
// We can assume the default translation has a non-null title and message
|
||||
return TagLineNewsItem(
|
||||
return AppNewsItem(
|
||||
id: id,
|
||||
title: translation.title!,
|
||||
message: translation.message!,
|
||||
@ -224,8 +223,8 @@ class _TagLineNewsImage {
|
||||
final double? width;
|
||||
final String? alt;
|
||||
|
||||
TagLineImage toTagLineImage() {
|
||||
return TagLineImage(
|
||||
AppNewsImage toTagLineImage() {
|
||||
return AppNewsImage(
|
||||
src: url,
|
||||
width: width,
|
||||
alt: alt,
|
||||
@ -303,7 +302,7 @@ class _TagLineNewsStyle {
|
||||
);
|
||||
}
|
||||
|
||||
TagLineStyle toTagLineStyle() => TagLineStyle.fromHexa(
|
||||
AppNewsStyle toTagLineStyle() => AppNewsStyle.fromHexa(
|
||||
titleBackground: titleBackground,
|
||||
titleTextColor: titleTextColor,
|
||||
titleIndicatorColor: titleIndicatorColor,
|
||||
@ -369,7 +368,7 @@ class _TagLineJSONFeedLocaleItem {
|
||||
final String id;
|
||||
final _TagLineJSONFeedNewsItemOverride? overrideContent;
|
||||
|
||||
TagLineFeedItem overrideNewsItem(
|
||||
AppNewsFeedItem overrideNewsItem(
|
||||
_TagLineItemNewsItem newsItem,
|
||||
String locale,
|
||||
) {
|
||||
@ -384,9 +383,9 @@ class _TagLineJSONFeedLocaleItem {
|
||||
);
|
||||
}
|
||||
|
||||
final TagLineNewsItem tagLineItem = item.toTagLineItem(locale);
|
||||
final AppNewsItem tagLineItem = item.toTagLineItem(locale);
|
||||
|
||||
return TagLineFeedItem(
|
||||
return AppNewsFeedItem(
|
||||
news: tagLineItem,
|
||||
startDate: tagLineItem.startDate,
|
||||
endDate: tagLineItem.endDate,
|
@ -1,35 +1,35 @@
|
||||
import 'dart:ui';
|
||||
|
||||
class TagLine {
|
||||
const TagLine({
|
||||
class AppNews {
|
||||
const AppNews({
|
||||
required this.news,
|
||||
required this.feed,
|
||||
});
|
||||
|
||||
final TagLineNewsList news;
|
||||
final TagLineFeed feed;
|
||||
final AppNewsList news;
|
||||
final AppNewsFeed feed;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TagLine{news: $news, feed: $feed}';
|
||||
return 'AppNews{news: $news, feed: $feed}';
|
||||
}
|
||||
}
|
||||
|
||||
class TagLineNewsList {
|
||||
const TagLineNewsList(Map<String, TagLineNewsItem> news) : _news = news;
|
||||
class AppNewsList {
|
||||
const AppNewsList(Map<String, AppNewsItem> news) : _news = news;
|
||||
|
||||
final Map<String, TagLineNewsItem> _news;
|
||||
final Map<String, AppNewsItem> _news;
|
||||
|
||||
TagLineNewsItem? operator [](String key) => _news[key];
|
||||
AppNewsItem? operator [](String key) => _news[key];
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TagLineNewsList{_news: $_news}';
|
||||
return 'AppNewsList{_news: $_news}';
|
||||
}
|
||||
}
|
||||
|
||||
class TagLineNewsItem {
|
||||
const TagLineNewsItem({
|
||||
class AppNewsItem {
|
||||
const AppNewsItem({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.message,
|
||||
@ -48,17 +48,17 @@ class TagLineNewsItem {
|
||||
final String? buttonLabel;
|
||||
final DateTime? startDate;
|
||||
final DateTime? endDate;
|
||||
final TagLineImage? image;
|
||||
final TagLineStyle? style;
|
||||
final AppNewsImage? image;
|
||||
final AppNewsStyle? style;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TagLineNewsItem{id: $id, title: $title, message: $message, url: $url, buttonLabel: $buttonLabel, startDate: $startDate, endDate: $endDate, image: $image, style: $style}';
|
||||
return 'AppNewsItem{id: $id, title: $title, message: $message, url: $url, buttonLabel: $buttonLabel, startDate: $startDate, endDate: $endDate, image: $image, style: $style}';
|
||||
}
|
||||
}
|
||||
|
||||
class TagLineStyle {
|
||||
const TagLineStyle({
|
||||
class AppNewsStyle {
|
||||
const AppNewsStyle({
|
||||
this.titleBackground,
|
||||
this.titleTextColor,
|
||||
this.titleIndicatorColor,
|
||||
@ -69,7 +69,7 @@ class TagLineStyle {
|
||||
this.contentBackgroundColor,
|
||||
});
|
||||
|
||||
TagLineStyle.fromHexa({
|
||||
AppNewsStyle.fromHexa({
|
||||
String? titleBackground,
|
||||
String? titleTextColor,
|
||||
String? titleIndicatorColor,
|
||||
@ -105,12 +105,12 @@ class TagLineStyle {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TagLineStyle{titleBackground: $titleBackground, titleTextColor: $titleTextColor, titleIndicatorColor: $titleIndicatorColor, messageBackground: $messageBackground, messageTextColor: $messageTextColor, buttonBackground: $buttonBackground, buttonTextColor: $buttonTextColor, contentBackgroundColor: $contentBackgroundColor}';
|
||||
return 'AppNewsStyle{titleBackground: $titleBackground, titleTextColor: $titleTextColor, titleIndicatorColor: $titleIndicatorColor, messageBackground: $messageBackground, messageTextColor: $messageTextColor, buttonBackground: $buttonBackground, buttonTextColor: $buttonTextColor, contentBackgroundColor: $contentBackgroundColor}';
|
||||
}
|
||||
}
|
||||
|
||||
class TagLineImage {
|
||||
const TagLineImage({
|
||||
class AppNewsImage {
|
||||
const AppNewsImage({
|
||||
required this.src,
|
||||
this.width,
|
||||
this.alt,
|
||||
@ -122,14 +122,14 @@ class TagLineImage {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TagLineImage{src: $src, width: $width, alt: $alt}';
|
||||
return 'AppNewsImage{src: $src, width: $width, alt: $alt}';
|
||||
}
|
||||
}
|
||||
|
||||
class TagLineFeed {
|
||||
const TagLineFeed(this.news);
|
||||
class AppNewsFeed {
|
||||
const AppNewsFeed(this.news);
|
||||
|
||||
final List<TagLineFeedItem> news;
|
||||
final List<AppNewsFeedItem> news;
|
||||
|
||||
bool get isNotEmpty => news.isNotEmpty;
|
||||
|
||||
@ -139,15 +139,15 @@ class TagLineFeed {
|
||||
}
|
||||
}
|
||||
|
||||
class TagLineFeedItem {
|
||||
const TagLineFeedItem({
|
||||
class AppNewsFeedItem {
|
||||
const AppNewsFeedItem({
|
||||
required this.news,
|
||||
DateTime? startDate,
|
||||
DateTime? endDate,
|
||||
}) : _startDate = startDate,
|
||||
_endDate = endDate;
|
||||
|
||||
final TagLineNewsItem news;
|
||||
final AppNewsItem news;
|
||||
final DateTime? _startDate;
|
||||
final DateTime? _endDate;
|
||||
|
||||
@ -159,6 +159,6 @@ class TagLineFeedItem {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TagLineFeedItem{news: $news, _startDate: $_startDate, _endDate: $_endDate}';
|
||||
return 'AppNewsFeedItem{news: $news, _startDate: $_startDate, _endDate: $_endDate}';
|
||||
}
|
||||
}
|
@ -8,23 +8,23 @@ 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';
|
||||
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';
|
||||
part 'newsfeed_json.dart';
|
||||
|
||||
/// The TagLine provides one one side a list of news and on the other a feed
|
||||
/// containing the some of the news
|
||||
/// This provides one one side a list of news and on the other a feed of news.
|
||||
/// A feed contains some of the news?
|
||||
///
|
||||
/// The TagLine is fetched on the server and cached locally (1 day).
|
||||
/// The content is fetched on the server and cached locally (1 day).
|
||||
/// To be notified of changes, listen to this [ChangeNotifier] and more
|
||||
/// particularly to the [state] property
|
||||
class TagLineProvider extends ChangeNotifier {
|
||||
TagLineProvider(UserPreferences preferences)
|
||||
: _state = const TagLineLoading(),
|
||||
/// particularly to the [state] property.
|
||||
class AppNewsProvider extends ChangeNotifier {
|
||||
AppNewsProvider(UserPreferences preferences)
|
||||
: _state = const AppNewsStateLoading(),
|
||||
_preferences = preferences,
|
||||
_domain = preferences.getDevModeString(
|
||||
UserPreferencesDevMode.userPreferencesTestEnvDomain) ??
|
||||
@ -33,17 +33,17 @@ class TagLineProvider extends ChangeNotifier {
|
||||
.getFlag(UserPreferencesDevMode.userPreferencesFlagProd) ??
|
||||
true {
|
||||
_preferences.addListener(_onPreferencesChanged);
|
||||
loadTagLine();
|
||||
loadLatestNews();
|
||||
}
|
||||
|
||||
final UserPreferences _preferences;
|
||||
|
||||
TagLineState _state;
|
||||
AppNewsState _state;
|
||||
|
||||
bool get hasContent => _state is TagLineLoaded;
|
||||
bool get hasContent => _state is AppNewsStateLoaded;
|
||||
|
||||
Future<void> loadTagLine({bool forceUpdate = false}) async {
|
||||
_emit(const TagLineLoading());
|
||||
Future<void> loadLatestNews({bool forceUpdate = false}) async {
|
||||
_emit(const AppNewsStateLoading());
|
||||
|
||||
final String locale = ProductQuery.getLocaleString();
|
||||
if (locale.startsWith('-')) {
|
||||
@ -51,43 +51,43 @@ class TagLineProvider extends ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
|
||||
final File cacheFile = await _tagLineCacheFile;
|
||||
final File cacheFile = await _newsCacheFile;
|
||||
String? jsonString;
|
||||
// Try from the cache first
|
||||
if (!forceUpdate && _isTagLineCacheValid(cacheFile)) {
|
||||
if (!forceUpdate && _isNewsCacheValid(cacheFile)) {
|
||||
jsonString = cacheFile.readAsStringSync();
|
||||
}
|
||||
|
||||
if (jsonString == null || jsonString.isEmpty == true) {
|
||||
jsonString = await _fetchTagLine();
|
||||
jsonString = await _fetchJSON();
|
||||
}
|
||||
|
||||
if (jsonString?.isNotEmpty != true) {
|
||||
_emit(const TagLineError('JSON file is empty'));
|
||||
_emit(const AppNewsStateError('JSON news file is empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
final TagLine? tagLine = await Isolate.run(
|
||||
final AppNews? tagLine = await Isolate.run(
|
||||
() => _parseJSONAndGetLocalizedContent(jsonString!, locale));
|
||||
if (tagLine == null) {
|
||||
_emit(const TagLineError('Unable to parse the JSON file'));
|
||||
Logs.e('Unable to parse the Tagline file');
|
||||
_emit(const AppNewsStateError('Unable to parse the JSON news file'));
|
||||
Logs.e('Unable to parse the JSON news file');
|
||||
} else {
|
||||
_emit(TagLineLoaded(tagLine));
|
||||
Logs.i('TagLine reloaded');
|
||||
_emit(AppNewsStateLoaded(tagLine));
|
||||
Logs.i('News ${forceUpdate ? 're' : ''}loaded');
|
||||
}
|
||||
}
|
||||
|
||||
void _emit(TagLineState state) {
|
||||
void _emit(AppNewsState state) {
|
||||
_state = state;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
TagLineState get state => _state;
|
||||
AppNewsState get state => _state;
|
||||
|
||||
static Future<TagLine?> _parseJSONAndGetLocalizedContent(
|
||||
static Future<AppNews?> _parseJSONAndGetLocalizedContent(
|
||||
String json,
|
||||
String locale,
|
||||
) async {
|
||||
@ -102,11 +102,11 @@ class TagLineProvider 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]
|
||||
Future<String?> _fetchTagLine() async {
|
||||
Future<String?> _fetchJSON() async {
|
||||
try {
|
||||
final UriProductHelper uriProductHelper = ProductQuery.uriProductHelper;
|
||||
final Map<String, String> headers = <String, String>{};
|
||||
final Uri uri = uriProductHelper.getUri(path: _tagLineUrl);
|
||||
final Uri uri = uriProductHelper.getUri(path: _newsUrl);
|
||||
|
||||
if (uriProductHelper.userInfoForPatch != null) {
|
||||
headers['Authorization'] =
|
||||
@ -125,7 +125,7 @@ class TagLineProvider extends ChangeNotifier {
|
||||
if (!json.startsWith('[') && !json.startsWith('{')) {
|
||||
throw Exception('Invalid JSON');
|
||||
}
|
||||
await _saveTagLineToCache(json);
|
||||
await _saveNewsToCache(json);
|
||||
return json;
|
||||
} catch (_) {
|
||||
return null;
|
||||
@ -133,7 +133,7 @@ class TagLineProvider extends ChangeNotifier {
|
||||
}
|
||||
|
||||
/// Based on the platform, the URL may differ
|
||||
String get _tagLineUrl {
|
||||
String get _newsUrl {
|
||||
if (Platform.isIOS || Platform.isMacOS) {
|
||||
return '/resources/files/tagline-off-ios-v3.json';
|
||||
} else {
|
||||
@ -141,15 +141,15 @@ class TagLineProvider extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<File> get _tagLineCacheFile => getApplicationCacheDirectory()
|
||||
Future<File> get _newsCacheFile => getApplicationCacheDirectory()
|
||||
.then((Directory dir) => File(join(dir.path, 'tagline.json')));
|
||||
|
||||
Future<File> _saveTagLineToCache(final String json) async {
|
||||
final File file = await _tagLineCacheFile;
|
||||
Future<File> _saveNewsToCache(final String json) async {
|
||||
final File file = await _newsCacheFile;
|
||||
return file.writeAsString(json);
|
||||
}
|
||||
|
||||
bool _isTagLineCacheValid(File file) =>
|
||||
bool _isNewsCacheValid(File file) =>
|
||||
file.existsSync() &&
|
||||
file.lengthSync() > 0 &&
|
||||
file
|
||||
@ -172,7 +172,7 @@ class TagLineProvider extends ChangeNotifier {
|
||||
if (domain != _domain || prodEnv != _prodEnv) {
|
||||
_domain = domain;
|
||||
_prodEnv = prodEnv;
|
||||
loadTagLine(forceUpdate: true);
|
||||
loadLatestNews(forceUpdate: true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,22 +183,22 @@ class TagLineProvider extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
sealed class TagLineState {
|
||||
const TagLineState();
|
||||
sealed class AppNewsState {
|
||||
const AppNewsState();
|
||||
}
|
||||
|
||||
final class TagLineLoading extends TagLineState {
|
||||
const TagLineLoading();
|
||||
final class AppNewsStateLoading extends AppNewsState {
|
||||
const AppNewsStateLoading();
|
||||
}
|
||||
|
||||
class TagLineLoaded extends TagLineState {
|
||||
const TagLineLoaded(this.tagLineContent);
|
||||
class AppNewsStateLoaded extends AppNewsState {
|
||||
const AppNewsStateLoaded(this.tagLineContent);
|
||||
|
||||
final TagLine tagLineContent;
|
||||
final AppNews tagLineContent;
|
||||
}
|
||||
|
||||
class TagLineError extends TagLineState {
|
||||
const TagLineError(this.exception);
|
||||
class AppNewsStateError extends AppNewsState {
|
||||
const AppNewsStateError(this.exception);
|
||||
|
||||
final dynamic exception;
|
||||
}
|
@ -10,8 +10,8 @@ import 'package:smooth_app/generic_lib/design_constants.dart';
|
||||
import 'package:smooth_app/generic_lib/widgets/smooth_card.dart';
|
||||
import 'package:smooth_app/knowledge_panel/knowledge_panels/knowledge_panel_expanded_card.dart';
|
||||
import 'package:smooth_app/knowledge_panel/knowledge_panels_builder.dart';
|
||||
import 'package:smooth_app/pages/carousel_manager.dart';
|
||||
import 'package:smooth_app/pages/product/common/product_refresher.dart';
|
||||
import 'package:smooth_app/pages/scan/carousel/scan_carousel_manager.dart';
|
||||
import 'package:smooth_app/widgets/smooth_app_bar.dart';
|
||||
import 'package:smooth_app/widgets/smooth_scaffold.dart';
|
||||
|
||||
@ -103,7 +103,7 @@ class _KnowledgePanelPageState extends State<KnowledgePanelPage>
|
||||
Future<void> _refreshProduct(BuildContext context) async {
|
||||
try {
|
||||
final String? barcode =
|
||||
ExternalCarouselManager.read(context).currentBarcode;
|
||||
ExternalScanCarouselManager.read(context).currentBarcode;
|
||||
if (barcode?.isEmpty == true) {
|
||||
return;
|
||||
}
|
||||
|
@ -16,9 +16,9 @@ import 'package:provider/single_child_widget.dart';
|
||||
import 'package:scanner_shared/scanner_shared.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'package:smooth_app/data_models/continuous_scan_model.dart';
|
||||
import 'package:smooth_app/data_models/news_feed/newsfeed_provider.dart';
|
||||
import 'package:smooth_app/data_models/preferences/user_preferences.dart';
|
||||
import 'package:smooth_app/data_models/product_preferences.dart';
|
||||
import 'package:smooth_app/data_models/tagline/tagline_provider.dart';
|
||||
import 'package:smooth_app/data_models/user_management_provider.dart';
|
||||
import 'package:smooth_app/database/dao_string.dart';
|
||||
import 'package:smooth_app/database/local_database.dart';
|
||||
@ -237,8 +237,8 @@ class _SmoothAppState extends State<SmoothApp> {
|
||||
provide<ContinuousScanModel>(_continuousScanModel),
|
||||
provide<PermissionListener>(_permissionListener),
|
||||
],
|
||||
child: ChangeNotifierProvider<TagLineProvider>(
|
||||
create: (BuildContext context) => TagLineProvider(
|
||||
child: ChangeNotifierProvider<AppNewsProvider>(
|
||||
create: (BuildContext context) => AppNewsProvider(
|
||||
context.read<UserPreferences>(),
|
||||
),
|
||||
lazy: true,
|
||||
|
@ -3,12 +3,11 @@ import 'package:flutter/widgets.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:openfoodfacts/openfoodfacts.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:smooth_app/data_models/news_feed/newsfeed_provider.dart';
|
||||
import 'package:smooth_app/data_models/preferences/user_preferences.dart';
|
||||
import 'package:smooth_app/data_models/product_preferences.dart';
|
||||
import 'package:smooth_app/data_models/tagline/tagline_provider.dart';
|
||||
import 'package:smooth_app/helpers/analytics_helper.dart';
|
||||
import 'package:smooth_app/helpers/extension_on_text_helper.dart';
|
||||
import 'package:smooth_app/pages/carousel_manager.dart';
|
||||
import 'package:smooth_app/pages/guides/guide/guide_nutriscore_v2.dart';
|
||||
import 'package:smooth_app/pages/navigator/error_page.dart';
|
||||
import 'package:smooth_app/pages/navigator/external_page.dart';
|
||||
@ -18,6 +17,7 @@ import 'package:smooth_app/pages/product/add_new_product_page.dart';
|
||||
import 'package:smooth_app/pages/product/edit_product_page.dart';
|
||||
import 'package:smooth_app/pages/product/new_product_page.dart';
|
||||
import 'package:smooth_app/pages/product/product_loader_page.dart';
|
||||
import 'package:smooth_app/pages/scan/carousel/scan_carousel_manager.dart';
|
||||
import 'package:smooth_app/pages/scan/search_page.dart';
|
||||
import 'package:smooth_app/pages/scan/search_product_helper.dart';
|
||||
import 'package:smooth_app/pages/user_management/sign_up_page.dart';
|
||||
@ -145,8 +145,8 @@ class _SmoothGoRouter {
|
||||
heroTag: state.uri.queryParameters['heroTag'],
|
||||
);
|
||||
|
||||
if (ExternalCarouselManager.find(context) == null) {
|
||||
return ExternalCarouselManager(child: widget);
|
||||
if (ExternalScanCarouselManager.find(context) == null) {
|
||||
return ExternalScanCarouselManager(child: widget);
|
||||
} else {
|
||||
return widget;
|
||||
}
|
||||
@ -313,7 +313,7 @@ class _SmoothGoRouter {
|
||||
// Must be set first to ensure the method is only called once
|
||||
_appLanguageInitialized = true;
|
||||
ProductQuery.setLanguage(context, context.read<UserPreferences>());
|
||||
context.read<TagLineProvider>().loadTagLine();
|
||||
context.read<AppNewsProvider>().loadLatestNews();
|
||||
return context.read<ProductPreferences>().refresh();
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:smooth_app/data_models/preferences/user_preferences.dart';
|
||||
import 'package:smooth_app/database/local_database.dart';
|
||||
import 'package:smooth_app/pages/carousel_manager.dart';
|
||||
import 'package:smooth_app/pages/navigator/app_navigator.dart';
|
||||
import 'package:smooth_app/pages/onboarding/consent_analytics_page.dart';
|
||||
import 'package:smooth_app/pages/onboarding/permissions_page.dart';
|
||||
@ -13,6 +12,7 @@ import 'package:smooth_app/pages/onboarding/sample_health_card_page.dart';
|
||||
import 'package:smooth_app/pages/onboarding/scan_example.dart';
|
||||
import 'package:smooth_app/pages/onboarding/welcome_page.dart';
|
||||
import 'package:smooth_app/pages/page_manager.dart';
|
||||
import 'package:smooth_app/pages/scan/carousel/scan_carousel_manager.dart';
|
||||
import 'package:smooth_app/widgets/smooth_scaffold.dart';
|
||||
import 'package:smooth_app/widgets/will_pop_scope.dart';
|
||||
|
||||
@ -114,7 +114,7 @@ enum OnboardingPage {
|
||||
ConsentAnalyticsPage(backgroundColor),
|
||||
);
|
||||
case OnboardingPage.ONBOARDING_COMPLETE:
|
||||
return ExternalCarouselManager(child: PageManager());
|
||||
return ExternalScanCarouselManager(child: PageManager());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:smooth_app/data_models/preferences/user_preferences.dart';
|
||||
import 'package:smooth_app/pages/carousel_manager.dart';
|
||||
import 'package:smooth_app/pages/preferences/user_preferences_dev_mode.dart';
|
||||
import 'package:smooth_app/pages/scan/carousel/scan_carousel_manager.dart';
|
||||
import 'package:smooth_app/widgets/tab_navigator.dart';
|
||||
import 'package:smooth_app/widgets/will_pop_scope.dart';
|
||||
|
||||
@ -62,8 +62,8 @@ class PageManagerState extends State<PageManager> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final AppLocalizations appLocalizations = AppLocalizations.of(context);
|
||||
final ExternalCarouselManagerState carouselManager =
|
||||
ExternalCarouselManager.watch(context);
|
||||
final ExternalScanCarouselManagerState carouselManager =
|
||||
ExternalScanCarouselManager.watch(context);
|
||||
|
||||
if (carouselManager.forceShowScannerTab) {
|
||||
_currentPage = BottomNavigationTab.Scan;
|
||||
|
@ -3,9 +3,9 @@ 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_language_refresh.dart';
|
||||
import 'package:smooth_app/data_models/news_feed/newsfeed_provider.dart';
|
||||
import 'package:smooth_app/data_models/preferences/user_preferences.dart';
|
||||
import 'package:smooth_app/data_models/product_preferences.dart';
|
||||
import 'package:smooth_app/data_models/tagline/tagline_provider.dart';
|
||||
import 'package:smooth_app/database/local_database.dart';
|
||||
import 'package:smooth_app/generic_lib/design_constants.dart';
|
||||
import 'package:smooth_app/generic_lib/widgets/language_selector.dart';
|
||||
@ -57,9 +57,9 @@ class UserPreferencesLanguageSelector extends StatelessWidget {
|
||||
context.read<LocalDatabase>(),
|
||||
);
|
||||
|
||||
// Refresh the tagline
|
||||
// Refresh the news feed
|
||||
if (context.mounted) {
|
||||
context.read<TagLineProvider>().loadTagLine();
|
||||
context.read<AppNewsProvider>().loadLatestNews();
|
||||
}
|
||||
// TODO(monsieurtanuki): make it a background task also?
|
||||
// no await
|
||||
|
@ -21,7 +21,6 @@ import 'package:smooth_app/generic_lib/widgets/smooth_responsive.dart';
|
||||
import 'package:smooth_app/helpers/app_helper.dart';
|
||||
import 'package:smooth_app/helpers/robotoff_insight_helper.dart';
|
||||
import 'package:smooth_app/pages/all_product_list_modal.dart';
|
||||
import 'package:smooth_app/pages/carousel_manager.dart';
|
||||
import 'package:smooth_app/pages/preferences/user_preferences_dev_mode.dart';
|
||||
import 'package:smooth_app/pages/product/common/product_list_item_popup_items.dart';
|
||||
import 'package:smooth_app/pages/product/common/product_list_item_simple.dart';
|
||||
@ -29,6 +28,7 @@ import 'package:smooth_app/pages/product/common/product_list_popup_items.dart';
|
||||
import 'package:smooth_app/pages/product/common/product_query_page_helper.dart';
|
||||
import 'package:smooth_app/pages/product/common/product_refresher.dart';
|
||||
import 'package:smooth_app/pages/product_list_user_dialog_helper.dart';
|
||||
import 'package:smooth_app/pages/scan/carousel/scan_carousel_manager.dart';
|
||||
import 'package:smooth_app/query/product_query.dart';
|
||||
import 'package:smooth_app/widgets/smooth_app_bar.dart';
|
||||
import 'package:smooth_app/widgets/smooth_scaffold.dart';
|
||||
@ -133,7 +133,7 @@ class _ProductListPageState extends State<ProductListPage>
|
||||
icon: const Icon(CupertinoIcons.barcode),
|
||||
label: Text(appLocalizations.product_list_empty_title),
|
||||
onPressed: () =>
|
||||
ExternalCarouselManager.read(context).showSearchCard(),
|
||||
ExternalScanCarouselManager.read(context).showSearchCard(),
|
||||
)
|
||||
: _selectionMode
|
||||
? null
|
||||
|
@ -21,7 +21,6 @@ import 'package:smooth_app/generic_lib/duration_constants.dart';
|
||||
import 'package:smooth_app/generic_lib/widgets/smooth_back_button.dart';
|
||||
import 'package:smooth_app/generic_lib/widgets/smooth_card.dart';
|
||||
import 'package:smooth_app/helpers/analytics_helper.dart';
|
||||
import 'package:smooth_app/pages/carousel_manager.dart';
|
||||
import 'package:smooth_app/pages/preferences/user_preferences_dev_mode.dart';
|
||||
import 'package:smooth_app/pages/prices/prices_card.dart';
|
||||
import 'package:smooth_app/pages/product/common/product_list_page.dart';
|
||||
@ -34,6 +33,7 @@ import 'package:smooth_app/pages/product/standard_knowledge_panel_cards.dart';
|
||||
import 'package:smooth_app/pages/product/summary_card.dart';
|
||||
import 'package:smooth_app/pages/product/website_card.dart';
|
||||
import 'package:smooth_app/pages/product_list_user_dialog_helper.dart';
|
||||
import 'package:smooth_app/pages/scan/carousel/scan_carousel_manager.dart';
|
||||
import 'package:smooth_app/query/product_query.dart';
|
||||
import 'package:smooth_app/themes/constant_icons.dart';
|
||||
import 'package:smooth_app/widgets/smooth_scaffold.dart';
|
||||
@ -85,8 +85,8 @@ class _ProductPageState extends State<ProductPage>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ExternalCarouselManagerState carouselManager =
|
||||
ExternalCarouselManager.read(context);
|
||||
final ExternalScanCarouselManagerState carouselManager =
|
||||
ExternalScanCarouselManager.read(context);
|
||||
carouselManager.currentBarcode = barcode;
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
_productPreferences = context.watch<ProductPreferences>();
|
||||
|
@ -0,0 +1,179 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:smooth_app/data_models/news_feed/newsfeed_provider.dart';
|
||||
import 'package:smooth_app/generic_lib/design_constants.dart';
|
||||
import 'package:smooth_app/generic_lib/widgets/smooth_card.dart';
|
||||
import 'package:smooth_app/helpers/provider_helper.dart';
|
||||
import 'package:smooth_app/helpers/strings_helper.dart';
|
||||
import 'package:smooth_app/pages/navigator/app_navigator.dart';
|
||||
import 'package:smooth_app/pages/scan/carousel/main_card/scan_tagline.dart';
|
||||
import 'package:smooth_app/resources/app_icons.dart';
|
||||
import 'package:smooth_app/themes/smooth_theme_colors.dart';
|
||||
import 'package:smooth_app/themes/theme_provider.dart';
|
||||
|
||||
class ScanMainCard extends StatelessWidget {
|
||||
const ScanMainCard();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: ConsumerFilter<AppNewsProvider>(
|
||||
buildWhen:
|
||||
(AppNewsProvider? previousValue, AppNewsProvider currentValue) {
|
||||
return previousValue?.hasContent != currentValue.hasContent;
|
||||
},
|
||||
builder: (BuildContext context, AppNewsProvider newsFeed, _) {
|
||||
if (!newsFeed.hasContent) {
|
||||
return const _SearchCard(
|
||||
expandedMode: true,
|
||||
);
|
||||
} else {
|
||||
return const Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
flex: 6,
|
||||
child: _SearchCard(
|
||||
expandedMode: false,
|
||||
),
|
||||
),
|
||||
SizedBox(height: MEDIUM_SPACE),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: ScanTagLine(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SearchCard extends StatelessWidget {
|
||||
const _SearchCard({
|
||||
required this.expandedMode,
|
||||
});
|
||||
|
||||
/// Expanded is when this card is the only one (no tagline, no app review…)
|
||||
final bool expandedMode;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final AppLocalizations localizations = AppLocalizations.of(context);
|
||||
final bool lightTheme = !context.watch<ThemeProvider>().isDarkMode(context);
|
||||
|
||||
final Widget widget = SmoothCard(
|
||||
color: lightTheme ? Colors.grey.withOpacity(0.1) : Colors.black,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: MEDIUM_SPACE,
|
||||
horizontal: LARGE_SPACE,
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 0.0,
|
||||
vertical: VERY_SMALL_SPACE,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
SvgPicture.asset(
|
||||
lightTheme
|
||||
? 'assets/app/logo_text_black.svg'
|
||||
: 'assets/app/logo_text_white.svg',
|
||||
semanticsLabel: localizations.homepage_main_card_logo_description,
|
||||
),
|
||||
FormattedText(
|
||||
text: localizations.homepage_main_card_subheading,
|
||||
textAlign: TextAlign.center,
|
||||
textStyle: const TextStyle(height: 1.3),
|
||||
),
|
||||
const _SearchBar(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (expandedMode) {
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.sizeOf(context).height * 0.4,
|
||||
),
|
||||
child: widget,
|
||||
);
|
||||
} else {
|
||||
return widget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _SearchBar extends StatelessWidget {
|
||||
const _SearchBar();
|
||||
|
||||
static const double SEARCH_BAR_HEIGHT = 47.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final AppLocalizations localizations = AppLocalizations.of(context);
|
||||
final SmoothColorsThemeExtension theme =
|
||||
Theme.of(context).extension<SmoothColorsThemeExtension>()!;
|
||||
final bool lightTheme = !context.watch<ThemeProvider>().isDarkMode(context);
|
||||
|
||||
return SizedBox(
|
||||
height: SEARCH_BAR_HEIGHT,
|
||||
child: InkWell(
|
||||
onTap: () => AppNavigator.of(context).push(AppRoutes.SEARCH),
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
color: lightTheme ? Colors.white : theme.greyDark,
|
||||
border: Border.all(color: theme.primaryBlack),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(
|
||||
start: 20.0,
|
||||
end: 10.0,
|
||||
bottom: 3.0,
|
||||
),
|
||||
child: Text(
|
||||
localizations.homepage_main_card_search_field_hint,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: lightTheme ? Colors.black : Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
AspectRatio(
|
||||
aspectRatio: 1.0,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.primaryDark,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Search(
|
||||
size: 20.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -5,9 +5,9 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
import 'package:smooth_app/cards/category_cards/svg_cache.dart';
|
||||
import 'package:smooth_app/data_models/news_feed/newsfeed_model.dart';
|
||||
import 'package:smooth_app/data_models/news_feed/newsfeed_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/data_models/tagline/tagline_provider.dart';
|
||||
import 'package:smooth_app/generic_lib/design_constants.dart';
|
||||
import 'package:smooth_app/generic_lib/widgets/smooth_card.dart';
|
||||
import 'package:smooth_app/helpers/launch_url_helper.dart';
|
||||
@ -22,12 +22,12 @@ class ScanTagLine extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider<_ScanTagLineProvider>(
|
||||
create: (BuildContext context) => _ScanTagLineProvider(context),
|
||||
child: Consumer<_ScanTagLineProvider>(
|
||||
return ChangeNotifierProvider<_ScanNewsFeedProvider>(
|
||||
create: (BuildContext context) => _ScanNewsFeedProvider(context),
|
||||
child: Consumer<_ScanNewsFeedProvider>(
|
||||
builder: (
|
||||
BuildContext context,
|
||||
_ScanTagLineProvider scanTagLineProvider,
|
||||
_ScanNewsFeedProvider scanTagLineProvider,
|
||||
Widget? child,
|
||||
) {
|
||||
final _ScanTagLineState state = scanTagLineProvider.value;
|
||||
@ -70,7 +70,7 @@ class _ScanTagLineContent extends StatefulWidget {
|
||||
required this.news,
|
||||
});
|
||||
|
||||
final Iterable<TagLineNewsItem> news;
|
||||
final Iterable<AppNewsItem> news;
|
||||
|
||||
@override
|
||||
State<_ScanTagLineContent> createState() => _ScanTagLineContentState();
|
||||
@ -102,7 +102,7 @@ class _ScanTagLineContentState extends State<_ScanTagLineContent> {
|
||||
final ThemeProvider themeProvider = context.watch<ThemeProvider>();
|
||||
final SmoothColorsThemeExtension theme =
|
||||
Theme.of(context).extension<SmoothColorsThemeExtension>()!;
|
||||
final TagLineNewsItem currentNews = widget.news.elementAt(_index);
|
||||
final AppNewsItem currentNews = widget.news.elementAt(_index);
|
||||
|
||||
// Default values seem weird
|
||||
const Radius radius = Radius.circular(16.0);
|
||||
@ -241,7 +241,7 @@ class _TagLineContentBody extends StatelessWidget {
|
||||
|
||||
final String message;
|
||||
final Color? textColor;
|
||||
final TagLineImage? image;
|
||||
final AppNewsImage? image;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -346,49 +346,49 @@ class _TagLineContentButton extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Listen to [TagLineProvider] feed and provide a list of [TagLineNewsItem]
|
||||
/// Listen to [AppNewsProvider] feed and provide a list of [AppNewsItem]
|
||||
/// randomly sorted by unread, then displayed and clicked news.
|
||||
class _ScanTagLineProvider extends ValueNotifier<_ScanTagLineState> {
|
||||
_ScanTagLineProvider(BuildContext context)
|
||||
: _tagLineProvider = context.read<TagLineProvider>(),
|
||||
class _ScanNewsFeedProvider extends ValueNotifier<_ScanTagLineState> {
|
||||
_ScanNewsFeedProvider(BuildContext context)
|
||||
: _newsFeedProvider = context.read<AppNewsProvider>(),
|
||||
_userPreferences = context.read<UserPreferences>(),
|
||||
super(const _ScanTagLineStateLoading()) {
|
||||
_tagLineProvider.addListener(_onTagLineStateChanged);
|
||||
_newsFeedProvider.addListener(_onNewsFeedStateChanged);
|
||||
// Refresh with the current state
|
||||
_onTagLineStateChanged();
|
||||
_onNewsFeedStateChanged();
|
||||
}
|
||||
|
||||
final TagLineProvider _tagLineProvider;
|
||||
final AppNewsProvider _newsFeedProvider;
|
||||
final UserPreferences _userPreferences;
|
||||
|
||||
void _onTagLineStateChanged() {
|
||||
switch (_tagLineProvider.state) {
|
||||
case TagLineLoading():
|
||||
void _onNewsFeedStateChanged() {
|
||||
switch (_newsFeedProvider.state) {
|
||||
case AppNewsStateLoading():
|
||||
emit(const _ScanTagLineStateLoading());
|
||||
case TagLineError():
|
||||
case AppNewsStateError():
|
||||
emit(const _ScanTagLineStateNoContent());
|
||||
case TagLineLoaded():
|
||||
case AppNewsStateLoaded():
|
||||
_onTagLineContentAvailable(
|
||||
(_tagLineProvider.state as TagLineLoaded).tagLineContent);
|
||||
(_newsFeedProvider.state as AppNewsStateLoaded).tagLineContent);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onTagLineContentAvailable(TagLine tagLine) async {
|
||||
Future<void> _onTagLineContentAvailable(AppNews tagLine) async {
|
||||
if (!tagLine.feed.isNotEmpty) {
|
||||
emit(const _ScanTagLineStateNoContent());
|
||||
return;
|
||||
}
|
||||
|
||||
final List<TagLineNewsItem> unreadNews = <TagLineNewsItem>[];
|
||||
final List<TagLineNewsItem> displayedNews = <TagLineNewsItem>[];
|
||||
final List<TagLineNewsItem> clickedNews = <TagLineNewsItem>[];
|
||||
final List<AppNewsItem> unreadNews = <AppNewsItem>[];
|
||||
final List<AppNewsItem> displayedNews = <AppNewsItem>[];
|
||||
final List<AppNewsItem> clickedNews = <AppNewsItem>[];
|
||||
|
||||
final List<String> taglineFeedAlreadyClickedNews =
|
||||
_userPreferences.taglineFeedClickedNews;
|
||||
final List<String> taglineFeedAlreadyDisplayedNews =
|
||||
_userPreferences.taglineFeedDisplayedNews;
|
||||
|
||||
for (final TagLineFeedItem feedItem in tagLine.feed.news) {
|
||||
for (final AppNewsFeedItem feedItem in tagLine.feed.news) {
|
||||
if (taglineFeedAlreadyClickedNews.contains(feedItem.id)) {
|
||||
clickedNews.add(feedItem.news);
|
||||
} else if (taglineFeedAlreadyDisplayedNews.contains(feedItem.id)) {
|
||||
@ -400,7 +400,7 @@ class _ScanTagLineProvider extends ValueNotifier<_ScanTagLineState> {
|
||||
|
||||
emit(
|
||||
_ScanTagLineStateLoaded(
|
||||
<TagLineNewsItem>[
|
||||
<AppNewsItem>[
|
||||
...unreadNews..shuffle(),
|
||||
...displayedNews..shuffle(),
|
||||
...clickedNews..shuffle(),
|
||||
@ -411,7 +411,7 @@ class _ScanTagLineProvider extends ValueNotifier<_ScanTagLineState> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tagLineProvider.removeListener(_onTagLineStateChanged);
|
||||
_newsFeedProvider.removeListener(_onNewsFeedStateChanged);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@ -431,5 +431,5 @@ class _ScanTagLineStateNoContent extends _ScanTagLineState {
|
||||
class _ScanTagLineStateLoaded extends _ScanTagLineState {
|
||||
const _ScanTagLineStateLoaded(this.tagLine);
|
||||
|
||||
final Iterable<TagLineNewsItem> tagLine;
|
||||
final Iterable<AppNewsItem> tagLine;
|
||||
}
|
195
packages/smooth_app/lib/pages/scan/carousel/scan_carousel.dart
Normal file
195
packages/smooth_app/lib/pages/scan/carousel/scan_carousel.dart
Normal file
@ -0,0 +1,195 @@
|
||||
import 'package:carousel_slider/carousel_slider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:scanner_shared/scanner_shared.dart' hide EMPTY_WIDGET;
|
||||
import 'package:smooth_app/cards/product_cards/smooth_product_card_error.dart';
|
||||
import 'package:smooth_app/cards/product_cards/smooth_product_card_loading.dart';
|
||||
import 'package:smooth_app/cards/product_cards/smooth_product_card_not_found.dart';
|
||||
import 'package:smooth_app/cards/product_cards/smooth_product_card_thanks.dart';
|
||||
import 'package:smooth_app/data_models/continuous_scan_model.dart';
|
||||
import 'package:smooth_app/generic_lib/design_constants.dart';
|
||||
import 'package:smooth_app/pages/scan/carousel/main_card/scan_main_card.dart';
|
||||
import 'package:smooth_app/pages/scan/carousel/scan_carousel_manager.dart';
|
||||
import 'package:smooth_app/pages/scan/scan_product_card_loader.dart';
|
||||
|
||||
class ScanPageCarousel extends StatefulWidget {
|
||||
const ScanPageCarousel({
|
||||
this.onPageChangedTo,
|
||||
});
|
||||
|
||||
final Function(int page, String? productBarcode)? onPageChangedTo;
|
||||
|
||||
@override
|
||||
State<ScanPageCarousel> createState() => _ScanPageCarouselState();
|
||||
}
|
||||
|
||||
class _ScanPageCarouselState extends State<ScanPageCarousel> {
|
||||
static const double HORIZONTAL_SPACE_BETWEEN_CARDS = 5.0;
|
||||
|
||||
List<String> barcodes = <String>[];
|
||||
String? _lastConsultedBarcode;
|
||||
int? _carrouselMovingTo;
|
||||
int _lastIndex = 0;
|
||||
|
||||
late ContinuousScanModel _model;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_model = context.watch<ContinuousScanModel>();
|
||||
|
||||
if (!ExternalScanCarouselManager.read(context).controller.ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
barcodes = _model.getBarcodes();
|
||||
|
||||
if (barcodes.isEmpty) {
|
||||
// Ensure to reset all variables
|
||||
_lastConsultedBarcode = null;
|
||||
_carrouselMovingTo = null;
|
||||
_lastIndex = 0;
|
||||
return;
|
||||
} else if (_lastConsultedBarcode == _model.latestConsultedBarcode) {
|
||||
// Prevent multiple irrelevant movements
|
||||
return;
|
||||
}
|
||||
|
||||
_lastConsultedBarcode = _model.latestConsultedBarcode;
|
||||
final int cardsCount = barcodes.length + 1;
|
||||
|
||||
if (_model.latestConsultedBarcode != null &&
|
||||
_model.latestConsultedBarcode!.isNotEmpty) {
|
||||
final int indexBarcode = barcodes.indexOf(_model.latestConsultedBarcode!);
|
||||
if (indexBarcode >= 0) {
|
||||
final int indexCarousel = indexBarcode + 1;
|
||||
_moveControllerTo(indexCarousel);
|
||||
} else {
|
||||
if (_lastIndex > cardsCount) {
|
||||
_moveControllerTo(cardsCount);
|
||||
} else {
|
||||
_moveControllerTo(_lastIndex);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_moveControllerTo(0);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _moveControllerTo(int page) async {
|
||||
if (_carrouselMovingTo == null && _lastIndex != page) {
|
||||
widget.onPageChangedTo?.call(
|
||||
page,
|
||||
page >= 1 ? barcodes[page - 1] : null,
|
||||
);
|
||||
|
||||
_carrouselMovingTo = page;
|
||||
ExternalScanCarouselManager.read(context).animatePageTo(page);
|
||||
_carrouselMovingTo = null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
barcodes = _model.getBarcodes();
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
return CarouselSlider.builder(
|
||||
itemCount: barcodes.length + 1,
|
||||
itemBuilder:
|
||||
(BuildContext context, int itemIndex, int itemRealIndex) {
|
||||
return SizedBox.expand(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: HORIZONTAL_SPACE_BETWEEN_CARDS,
|
||||
),
|
||||
child: itemIndex == 0
|
||||
? const ScanMainCard()
|
||||
: _getWidget(itemIndex - 1),
|
||||
),
|
||||
);
|
||||
},
|
||||
carouselController:
|
||||
ExternalScanCarouselManager.watch(context).controller,
|
||||
options: CarouselOptions(
|
||||
enlargeCenterPage: false,
|
||||
viewportFraction: _computeViewPortFraction(),
|
||||
height: constraints.maxHeight,
|
||||
enableInfiniteScroll: false,
|
||||
onPageChanged: (int index, CarouselPageChangedReason reason) {
|
||||
_lastIndex = index;
|
||||
|
||||
if (index > 0) {
|
||||
if (reason == CarouselPageChangedReason.manual) {
|
||||
_model.lastConsultedBarcode = barcodes[index - 1];
|
||||
_lastConsultedBarcode = _model.latestConsultedBarcode;
|
||||
}
|
||||
} else if (index == 0) {
|
||||
_model.lastConsultedBarcode = null;
|
||||
_lastConsultedBarcode = null;
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Displays the card for this [index] of a list of [barcodes]
|
||||
///
|
||||
/// There are special cases when the item display is refreshed
|
||||
/// after the product disappeared and before the whole carousel is refreshed.
|
||||
/// In those cases, we don't want the app to crash and display a Container
|
||||
/// instead in the meanwhile.
|
||||
Widget _getWidget(final int index) {
|
||||
if (index >= barcodes.length) {
|
||||
return EMPTY_WIDGET;
|
||||
}
|
||||
final String barcode = barcodes[index];
|
||||
switch (_model.getBarcodeState(barcode)!) {
|
||||
case ScannedProductState.FOUND:
|
||||
case ScannedProductState.CACHED:
|
||||
return ScanProductCardLoader(barcode);
|
||||
case ScannedProductState.LOADING:
|
||||
return SmoothProductCardLoading(
|
||||
barcode: barcode,
|
||||
onRemoveProduct: (_) => _model.removeBarcode(barcode),
|
||||
);
|
||||
case ScannedProductState.NOT_FOUND:
|
||||
return SmoothProductCardNotFound(
|
||||
barcode: barcode,
|
||||
onAddProduct: () async {
|
||||
await _model.refresh();
|
||||
setState(() {});
|
||||
},
|
||||
onRemoveProduct: (_) => _model.removeBarcode(barcode),
|
||||
);
|
||||
case ScannedProductState.THANKS:
|
||||
return const SmoothProductCardThanks();
|
||||
case ScannedProductState.ERROR_INTERNET:
|
||||
return SmoothProductCardError(
|
||||
barcode: barcode,
|
||||
errorType: ScannedProductState.ERROR_INTERNET,
|
||||
);
|
||||
case ScannedProductState.ERROR_INVALID_CODE:
|
||||
return SmoothProductCardError(
|
||||
barcode: barcode,
|
||||
errorType: ScannedProductState.ERROR_INVALID_CODE,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
double _computeViewPortFraction() {
|
||||
final double screenWidth = MediaQuery.sizeOf(context).width;
|
||||
if (barcodes.isEmpty) {
|
||||
return 0.95;
|
||||
}
|
||||
|
||||
return (screenWidth -
|
||||
(SmoothBarcodeScannerVisor.CORNER_PADDING * 2) -
|
||||
(SmoothBarcodeScannerVisor.STROKE_WIDTH * 2) +
|
||||
(HORIZONTAL_SPACE_BETWEEN_CARDS * 4)) /
|
||||
screenWidth;
|
||||
}
|
||||
}
|
@ -2,36 +2,38 @@ import 'package:carousel_slider/carousel_controller.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:smooth_app/helpers/haptic_feedback_helper.dart';
|
||||
|
||||
class ExternalCarouselManager extends StatefulWidget {
|
||||
const ExternalCarouselManager({
|
||||
/// Allow to control the [ScanPageCarousel] from outside
|
||||
class ExternalScanCarouselManager extends StatefulWidget {
|
||||
const ExternalScanCarouselManager({
|
||||
super.key,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
|
||||
static ExternalCarouselManagerState watch(BuildContext context) {
|
||||
static ExternalScanCarouselManagerState watch(BuildContext context) {
|
||||
return context
|
||||
.dependOnInheritedWidgetOfExactType<_InheritedCarouselManager>()!
|
||||
.state;
|
||||
}
|
||||
|
||||
static ExternalCarouselManagerState? find(BuildContext context) {
|
||||
static ExternalScanCarouselManagerState? find(BuildContext context) {
|
||||
return context
|
||||
.findAncestorWidgetOfExactType<_InheritedCarouselManager>()
|
||||
?.state;
|
||||
}
|
||||
|
||||
static ExternalCarouselManagerState read(BuildContext context) {
|
||||
static ExternalScanCarouselManagerState read(BuildContext context) {
|
||||
return find(context)!;
|
||||
}
|
||||
|
||||
@override
|
||||
State<ExternalCarouselManager> createState() =>
|
||||
ExternalCarouselManagerState();
|
||||
State<ExternalScanCarouselManager> createState() =>
|
||||
ExternalScanCarouselManagerState();
|
||||
}
|
||||
|
||||
class ExternalCarouselManagerState extends State<ExternalCarouselManager> {
|
||||
class ExternalScanCarouselManagerState
|
||||
extends State<ExternalScanCarouselManager> {
|
||||
final CarouselController _controller = CarouselController();
|
||||
|
||||
/// A hidden attribute to force to return to the Scanner tab
|
||||
@ -75,7 +77,7 @@ class ExternalCarouselManagerState extends State<ExternalCarouselManager> {
|
||||
|
||||
CarouselController get controller => _controller;
|
||||
|
||||
bool updateShouldNotify(ExternalCarouselManagerState oldState) {
|
||||
bool updateShouldNotify(ExternalScanCarouselManagerState oldState) {
|
||||
return oldState.currentBarcode != currentBarcode || _forceShowScannerTab;
|
||||
}
|
||||
}
|
||||
@ -87,7 +89,7 @@ class _InheritedCarouselManager extends InheritedWidget {
|
||||
Key? key,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
final ExternalCarouselManagerState state;
|
||||
final ExternalScanCarouselManagerState state;
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(_InheritedCarouselManager oldWidget) {
|
@ -15,7 +15,7 @@ import 'package:smooth_app/helpers/camera_helper.dart';
|
||||
import 'package:smooth_app/helpers/haptic_feedback_helper.dart';
|
||||
import 'package:smooth_app/helpers/permission_helper.dart';
|
||||
import 'package:smooth_app/pages/scan/camera_scan_page.dart';
|
||||
import 'package:smooth_app/widgets/smooth_product_carousel.dart';
|
||||
import 'package:smooth_app/pages/scan/carousel/scan_carousel.dart';
|
||||
import 'package:smooth_app/widgets/smooth_scaffold.dart';
|
||||
|
||||
class ScanPage extends StatefulWidget {
|
||||
@ -92,8 +92,7 @@ class _ScanPageState extends State<ScanPage> {
|
||||
flex: _carouselHeightPct,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(bottom: 10.0),
|
||||
child: SmoothProductCarousel(
|
||||
containSearchCard: true,
|
||||
child: ScanPageCarousel(
|
||||
onPageChangedTo: (int page, String? barcode) async {
|
||||
if (barcode == null) {
|
||||
// We only notify for new products
|
||||
|
@ -1,375 +0,0 @@
|
||||
import 'package:carousel_slider/carousel_slider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:scanner_shared/scanner_shared.dart' hide EMPTY_WIDGET;
|
||||
import 'package:smooth_app/cards/product_cards/smooth_product_card_error.dart';
|
||||
import 'package:smooth_app/cards/product_cards/smooth_product_card_loading.dart';
|
||||
import 'package:smooth_app/cards/product_cards/smooth_product_card_not_found.dart';
|
||||
import 'package:smooth_app/cards/product_cards/smooth_product_card_thanks.dart';
|
||||
import 'package:smooth_app/data_models/continuous_scan_model.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/widgets/smooth_card.dart';
|
||||
import 'package:smooth_app/helpers/provider_helper.dart';
|
||||
import 'package:smooth_app/helpers/strings_helper.dart';
|
||||
import 'package:smooth_app/pages/carousel_manager.dart';
|
||||
import 'package:smooth_app/pages/navigator/app_navigator.dart';
|
||||
import 'package:smooth_app/pages/scan/scan_product_card_loader.dart';
|
||||
import 'package:smooth_app/pages/scan/scan_tagline.dart';
|
||||
import 'package:smooth_app/resources/app_icons.dart';
|
||||
import 'package:smooth_app/themes/smooth_theme_colors.dart';
|
||||
import 'package:smooth_app/themes/theme_provider.dart';
|
||||
|
||||
class SmoothProductCarousel extends StatefulWidget {
|
||||
const SmoothProductCarousel({
|
||||
this.containSearchCard = false,
|
||||
this.onPageChangedTo,
|
||||
});
|
||||
|
||||
final bool containSearchCard;
|
||||
final Function(int page, String? productBarcode)? onPageChangedTo;
|
||||
|
||||
@override
|
||||
State<SmoothProductCarousel> createState() => _SmoothProductCarouselState();
|
||||
}
|
||||
|
||||
class _SmoothProductCarouselState extends State<SmoothProductCarousel> {
|
||||
static const double HORIZONTAL_SPACE_BETWEEN_CARDS = 5.0;
|
||||
|
||||
List<String> barcodes = <String>[];
|
||||
String? _lastConsultedBarcode;
|
||||
int? _carrouselMovingTo;
|
||||
int _lastIndex = 0;
|
||||
|
||||
int get _searchCardAdjustment => widget.containSearchCard ? 1 : 0;
|
||||
late ContinuousScanModel _model;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_model = context.watch<ContinuousScanModel>();
|
||||
|
||||
if (!ExternalCarouselManager.read(context).controller.ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
barcodes = _model.getBarcodes();
|
||||
|
||||
if (barcodes.isEmpty) {
|
||||
// Ensure to reset all variables
|
||||
_lastConsultedBarcode = null;
|
||||
_carrouselMovingTo = null;
|
||||
_lastIndex = 0;
|
||||
return;
|
||||
} else if (_lastConsultedBarcode == _model.latestConsultedBarcode) {
|
||||
// Prevent multiple irrelevant movements
|
||||
return;
|
||||
}
|
||||
|
||||
_lastConsultedBarcode = _model.latestConsultedBarcode;
|
||||
final int cardsCount = barcodes.length + _searchCardAdjustment;
|
||||
|
||||
if (_model.latestConsultedBarcode != null &&
|
||||
_model.latestConsultedBarcode!.isNotEmpty) {
|
||||
final int indexBarcode = barcodes.indexOf(_model.latestConsultedBarcode!);
|
||||
if (indexBarcode >= 0) {
|
||||
final int indexCarousel = indexBarcode + _searchCardAdjustment;
|
||||
_moveControllerTo(indexCarousel);
|
||||
} else {
|
||||
if (_lastIndex > cardsCount) {
|
||||
_moveControllerTo(cardsCount);
|
||||
} else {
|
||||
_moveControllerTo(_lastIndex);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_moveControllerTo(0);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _moveControllerTo(int page) async {
|
||||
if (_carrouselMovingTo == null && _lastIndex != page) {
|
||||
widget.onPageChangedTo?.call(
|
||||
page,
|
||||
page >= _searchCardAdjustment
|
||||
? barcodes[page - _searchCardAdjustment]
|
||||
: null,
|
||||
);
|
||||
|
||||
_carrouselMovingTo = page;
|
||||
ExternalCarouselManager.read(context).animatePageTo(page);
|
||||
_carrouselMovingTo = null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
barcodes = _model.getBarcodes();
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
return CarouselSlider.builder(
|
||||
itemCount: barcodes.length + _searchCardAdjustment,
|
||||
itemBuilder:
|
||||
(BuildContext context, int itemIndex, int itemRealIndex) {
|
||||
return SizedBox.expand(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: HORIZONTAL_SPACE_BETWEEN_CARDS,
|
||||
),
|
||||
child: widget.containSearchCard && itemIndex == 0
|
||||
? const _MainCard()
|
||||
: _getWidget(itemIndex - _searchCardAdjustment),
|
||||
),
|
||||
);
|
||||
},
|
||||
carouselController: ExternalCarouselManager.watch(context).controller,
|
||||
options: CarouselOptions(
|
||||
enlargeCenterPage: false,
|
||||
viewportFraction: _computeViewPortFraction(),
|
||||
height: constraints.maxHeight,
|
||||
enableInfiniteScroll: false,
|
||||
onPageChanged: (int index, CarouselPageChangedReason reason) {
|
||||
_lastIndex = index;
|
||||
|
||||
if (index > 0) {
|
||||
if (reason == CarouselPageChangedReason.manual) {
|
||||
_model.lastConsultedBarcode =
|
||||
barcodes[index - _searchCardAdjustment];
|
||||
_lastConsultedBarcode = _model.latestConsultedBarcode;
|
||||
}
|
||||
} else if (index == 0) {
|
||||
_model.lastConsultedBarcode = null;
|
||||
_lastConsultedBarcode = null;
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Displays the card for this [index] of a list of [barcodes]
|
||||
///
|
||||
/// There are special cases when the item display is refreshed
|
||||
/// after the product disappeared and before the whole carousel is refreshed.
|
||||
/// In those cases, we don't want the app to crash and display a Container
|
||||
/// instead in the meanwhile.
|
||||
Widget _getWidget(final int index) {
|
||||
if (index >= barcodes.length) {
|
||||
return EMPTY_WIDGET;
|
||||
}
|
||||
final String barcode = barcodes[index];
|
||||
switch (_model.getBarcodeState(barcode)!) {
|
||||
case ScannedProductState.FOUND:
|
||||
case ScannedProductState.CACHED:
|
||||
return ScanProductCardLoader(barcode);
|
||||
case ScannedProductState.LOADING:
|
||||
return SmoothProductCardLoading(
|
||||
barcode: barcode,
|
||||
onRemoveProduct: (_) => _model.removeBarcode(barcode),
|
||||
);
|
||||
case ScannedProductState.NOT_FOUND:
|
||||
return SmoothProductCardNotFound(
|
||||
barcode: barcode,
|
||||
onAddProduct: () async {
|
||||
await _model.refresh();
|
||||
setState(() {});
|
||||
},
|
||||
onRemoveProduct: (_) => _model.removeBarcode(barcode),
|
||||
);
|
||||
case ScannedProductState.THANKS:
|
||||
return const SmoothProductCardThanks();
|
||||
case ScannedProductState.ERROR_INTERNET:
|
||||
return SmoothProductCardError(
|
||||
barcode: barcode,
|
||||
errorType: ScannedProductState.ERROR_INTERNET,
|
||||
);
|
||||
case ScannedProductState.ERROR_INVALID_CODE:
|
||||
return SmoothProductCardError(
|
||||
barcode: barcode,
|
||||
errorType: ScannedProductState.ERROR_INVALID_CODE,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
double _computeViewPortFraction() {
|
||||
final double screenWidth = MediaQuery.sizeOf(context).width;
|
||||
if (barcodes.isEmpty) {
|
||||
return 0.95;
|
||||
}
|
||||
|
||||
return (screenWidth -
|
||||
(SmoothBarcodeScannerVisor.CORNER_PADDING * 2) -
|
||||
(SmoothBarcodeScannerVisor.STROKE_WIDTH * 2) +
|
||||
(HORIZONTAL_SPACE_BETWEEN_CARDS * 4)) /
|
||||
screenWidth;
|
||||
}
|
||||
}
|
||||
|
||||
class _MainCard extends StatelessWidget {
|
||||
const _MainCard();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: ConsumerFilter<TagLineProvider>(
|
||||
buildWhen:
|
||||
(TagLineProvider? previousValue, TagLineProvider currentValue) {
|
||||
return previousValue?.hasContent != currentValue.hasContent;
|
||||
},
|
||||
builder: (BuildContext context, TagLineProvider tagLineManager, _) {
|
||||
if (!tagLineManager.hasContent) {
|
||||
return const _SearchCard(
|
||||
expandedMode: true,
|
||||
);
|
||||
} else {
|
||||
return const Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
flex: 6,
|
||||
child: _SearchCard(
|
||||
expandedMode: false,
|
||||
),
|
||||
),
|
||||
SizedBox(height: MEDIUM_SPACE),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: ScanTagLine(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SearchCard extends StatelessWidget {
|
||||
const _SearchCard({
|
||||
required this.expandedMode,
|
||||
});
|
||||
|
||||
/// Expanded is when this card is the only one (no tagline, no app review…)
|
||||
final bool expandedMode;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final AppLocalizations localizations = AppLocalizations.of(context);
|
||||
final bool lightTheme = !context.watch<ThemeProvider>().isDarkMode(context);
|
||||
|
||||
final Widget widget = SmoothCard(
|
||||
color: lightTheme ? Colors.grey.withOpacity(0.1) : Colors.black,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: MEDIUM_SPACE,
|
||||
horizontal: LARGE_SPACE,
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 0.0,
|
||||
vertical: VERY_SMALL_SPACE,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
SvgPicture.asset(
|
||||
lightTheme
|
||||
? 'assets/app/logo_text_black.svg'
|
||||
: 'assets/app/logo_text_white.svg',
|
||||
semanticsLabel: localizations.homepage_main_card_logo_description,
|
||||
),
|
||||
FormattedText(
|
||||
text: localizations.homepage_main_card_subheading,
|
||||
textAlign: TextAlign.center,
|
||||
textStyle: const TextStyle(height: 1.3),
|
||||
),
|
||||
const _SearchBar(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (expandedMode) {
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.sizeOf(context).height * 0.4,
|
||||
),
|
||||
child: widget,
|
||||
);
|
||||
} else {
|
||||
return widget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _SearchBar extends StatelessWidget {
|
||||
const _SearchBar();
|
||||
|
||||
static const double SEARCH_BAR_HEIGHT = 47.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final AppLocalizations localizations = AppLocalizations.of(context);
|
||||
final SmoothColorsThemeExtension theme =
|
||||
Theme.of(context).extension<SmoothColorsThemeExtension>()!;
|
||||
final bool lightTheme = !context.watch<ThemeProvider>().isDarkMode(context);
|
||||
|
||||
return SizedBox(
|
||||
height: SEARCH_BAR_HEIGHT,
|
||||
child: InkWell(
|
||||
onTap: () => AppNavigator.of(context).push(AppRoutes.SEARCH),
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
color: lightTheme ? Colors.white : theme.greyDark,
|
||||
border: Border.all(color: theme.primaryBlack),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(
|
||||
start: 20.0,
|
||||
end: 10.0,
|
||||
bottom: 3.0,
|
||||
),
|
||||
child: Text(
|
||||
localizations.homepage_main_card_search_field_hint,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: lightTheme ? Colors.black : Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
AspectRatio(
|
||||
aspectRatio: 1.0,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.primaryDark,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Search(
|
||||
size: 20.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user