Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: 5205 - new "my prices" page #5347

Merged
merged 3 commits into from
Jun 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -258,18 +258,22 @@ Prices:
- any-glob-to-any-file: 'packages/smooth_app/lib/database/dao_osm_location.dart'
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/locations/location_map_page.dart'
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/locations/search_location_preloaded_item.dart'
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/prices/get_prices_model.dart'
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/prices/price_amount_card.dart'
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/prices/price_amount_field.dart'
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/prices/price_button.dart'
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/prices/price_count_widget.dart'
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/prices/price_currency_card.dart'
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/prices/price_currency_selector.dart'
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/prices/price_data_widget.dart'
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/prices/price_date_card.dart'
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/prices/price_location_card.dart'
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/prices/price_model.dart'
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/prices/price_product_widget.dart'
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/prices/prices_page.dart'
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/prices/price_proof_card.dart'
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/prices/prices_card.dart'
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/prices/product_price_add_page.dart'
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/prices/product_price_item.dart'
- any-glob-to-any-file: 'packages/smooth_app/lib/pages/prices/product_prices_page.dart'
- 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'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
Expand All @@ -16,6 +17,8 @@ import 'package:smooth_app/pages/preferences/account_deletion_webview.dart';
import 'package:smooth_app/pages/preferences/user_preferences_item.dart';
import 'package:smooth_app/pages/preferences/user_preferences_list_tile.dart';
import 'package:smooth_app/pages/preferences/user_preferences_page.dart';
import 'package:smooth_app/pages/prices/get_prices_model.dart';
import 'package:smooth_app/pages/prices/prices_page.dart';
import 'package:smooth_app/pages/product/common/product_query_page_helper.dart';
import 'package:smooth_app/pages/user_management/login_page.dart';
import 'package:smooth_app/query/paged_product_query.dart';
Expand Down Expand Up @@ -210,9 +213,35 @@ class UserPreferencesAccount extends AbstractUserPreferences {
localDatabase: localDatabase,
myCount: _getMyCount(UserSearchType.TO_BE_COMPLETED),
),
_getPriceListTile(
_getListTile(
appLocalizations.user_search_prices_title,
'app/dashboard/prices',
() async => Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) => PricesPage(
GetPricesModel(
parameters: GetPricesParameters()
..owner = userId
..orderBy = <OrderBy<GetPricesOrderField>>[
const OrderBy<GetPricesOrderField>(
field: GetPricesOrderField.created,
ascending: false,
),
]
..pageSize = GetPricesModel.pageSize
..pageNumber = 1,
displayOwner: false,
displayProduct: true,
uri: OpenPricesAPIClient.getUri(
path: 'app/users/${ProductQuery.getWriteUser().userId}',
uriHelper: ProductQuery.uriProductHelper,
),
title: appLocalizations.user_search_prices_title,
subtitle: ProductQuery.getWriteUser().userId,
),
),
),
),
CupertinoIcons.money_dollar_circle,
myCount: _getMyPricesCount(),
),
_getPriceListTile(
Expand Down
38 changes: 38 additions & 0 deletions packages/smooth_app/lib/pages/prices/get_prices_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:openfoodfacts/openfoodfacts.dart';

/// Model that stores what we need to know for "get latest prices" queries.
class GetPricesModel {
const GetPricesModel({
required this.parameters,
required this.displayOwner,
required this.displayProduct,
required this.uri,
required this.title,
this.subtitle,
this.addButton,
});

/// Query parameters.
final GetPricesParameters parameters;

/// Should we display the owner for each price? No if it's an owner query.
final bool displayOwner;

/// Should we display the product for each price? No if it's a product query.
final bool displayProduct;

/// Related web app URI.
final Uri uri;

/// Page title.
final String title;

/// Page subtitle.
final String? subtitle;

/// "Add a price" callback.
final VoidCallback? addButton;

static const int pageSize = 10;
}
40 changes: 40 additions & 0 deletions packages/smooth_app/lib/pages/prices/price_button.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import 'package:flutter/material.dart';

/// Simple price button: displaying data with optional action.
class PriceButton extends StatelessWidget {
const PriceButton({
this.title,
this.iconData,
this.buttonStyle,
required this.onPressed,
});

final String? title;
final IconData? iconData;
final ButtonStyle? buttonStyle;
final VoidCallback? onPressed;

@override
Widget build(BuildContext context) {
if (iconData == null) {
return ElevatedButton(
onPressed: onPressed,
style: buttonStyle,
child: Text(title!),
);
}
if (title == null) {
return ElevatedButton(
onPressed: onPressed,
style: buttonStyle,
child: Icon(iconData),
);
}
return ElevatedButton.icon(
onPressed: onPressed,
icon: Icon(iconData),
label: Text(title!),
style: buttonStyle,
);
}
}
32 changes: 32 additions & 0 deletions packages/smooth_app/lib/pages/prices/price_count_widget.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:smooth_app/pages/prices/price_button.dart';

/// Price Count display.
class PriceCountWidget extends StatelessWidget {
const PriceCountWidget(this.count);

final int count;

@override
Widget build(BuildContext context) => PriceButton(
onPressed: null,
iconData: Icons.label,
title: '$count',
buttonStyle: ElevatedButton.styleFrom(
disabledForegroundColor: _getForegroundColor(),
disabledBackgroundColor: _getBackgroundColor(),
),
);

Color? _getForegroundColor() => switch (count) {
0 => Colors.red,
1 => Colors.orange,
_ => Colors.green,
};

Color? _getBackgroundColor() => switch (count) {
0 => Colors.red[100],
1 => Colors.orange[100],
_ => Colors.green[100],
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:intl/intl.dart';
import 'package:openfoodfacts/openfoodfacts.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';
import 'package:smooth_app/pages/prices/emoji_helper.dart';
import 'package:smooth_app/pages/prices/get_prices_model.dart';
import 'package:smooth_app/pages/prices/price_button.dart';
import 'package:smooth_app/pages/product/common/product_query_page_helper.dart';
import 'package:smooth_app/query/product_query.dart';

/// Single product price widget.
class ProductPriceItem extends StatelessWidget {
const ProductPriceItem(this.price);
/// Price Data display (no product data here).
class PriceDataWidget extends StatelessWidget {
const PriceDataWidget(
this.price, {
required this.model,
});

final Price price;
final GetPricesModel model;

@override
Widget build(BuildContext context) {
Expand All @@ -31,7 +36,7 @@ class ProductPriceItem extends StatelessWidget {
if (price.product == null) {
return null;
}
if (price.product!.quantityUnit != 'g') {
if ((price.product!.quantityUnit ?? 'g') != 'g') {
return null;
}
return '${currencyFormat.format(price.price / (price.product!.quantity! / 1000))} / kg';
Expand All @@ -52,62 +57,59 @@ class ProductPriceItem extends StatelessWidget {

final String? pricePerKg = getPricePerKg();
final String? notDiscountedPrice = getNotDiscountedPrice();
return SmoothCard(
child: ListTile(
title: Text(

return Wrap(
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.center,
spacing: MEDIUM_SPACE,
children: <Widget>[
Text(
'${currencyFormat.format(price.price)}'
'${pricePerKg == null ? '' : ' ($pricePerKg)'}'
' '
'${dateFormat.format(price.date)}'
'${notDiscountedPrice == null ? '' : ' ($notDiscountedPrice)'}',
' ${pricePerKg == null ? '' : ' ($pricePerKg)'}',
),
subtitle: Wrap(
spacing: MEDIUM_SPACE,
children: <Widget>[
if (locationTitle != null)
ElevatedButton.icon(
// TODO(monsieurtanuki): open a still-to-be-done "price x location" page
onPressed: () {},
icon: const Icon(Icons.location_on_outlined),
label: Text(locationTitle),
),
ElevatedButton.icon(
// TODO(monsieurtanuki): open a still-to-be-done "price x user" page
onPressed: () {},
icon: const Icon(Icons.account_box),
label: Text(price.owner),
),
Tooltip(
message: '${dateFormat.format(price.created)}'
' '
'${timeFormat.format(price.created)}',
child: ElevatedButton.icon(
// TODO(monsieurtanuki): misleading "active" button
onPressed: () {},
icon: const Icon(Icons.history),
label: Text(
ProductQueryPageHelper.getDurationStringFromTimestamp(
price.created.millisecondsSinceEpoch,
context,
compact: true,
),
),
),
Text(dateFormat.format(price.date)),
if (notDiscountedPrice != null) Text('($notDiscountedPrice)'),
if (locationTitle != null)
// TODO(monsieurtanuki): open a still-to-be-done "price x location" page
PriceButton(
title: locationTitle,
iconData: Icons.location_on_outlined,
onPressed: () {},
),
if (model.displayOwner)
PriceButton(
// TODO(monsieurtanuki): open a still-to-be-done "price x owner" page
title: price.owner,
iconData: Icons.account_box,
onPressed: () {},
),
Tooltip(
message: '${dateFormat.format(price.created)}'
' '
'${timeFormat.format(price.created)}',
child: PriceButton(
// TODO(monsieurtanuki): misleading "active" button
onPressed: () {},
iconData: Icons.history,
title: ProductQueryPageHelper.getDurationStringFromTimestamp(
price.created.millisecondsSinceEpoch,
context,
compact: true,
),
if (price.proof?.filePath != null)
ElevatedButton(
onPressed: () async => LaunchUrlHelper.launchURL(
price.proof!
.getFileUrl(
uriProductHelper: ProductQuery.uriProductHelper,
)
.toString(),
),
child: const Icon(Icons.image),
),
],
),
),
),
if (price.proof?.filePath != null)
PriceButton(
iconData: Icons.image,
onPressed: () async => LaunchUrlHelper.launchURL(
price.proof!
.getFileUrl(
uriProductHelper: ProductQuery.uriProductHelper,
)
.toString(),
),
),
],
);
}

Expand Down
Loading
Loading