Skip to content

Commit

Permalink
iOS popup menu
Browse files Browse the repository at this point in the history
  • Loading branch information
g123k committed Jul 20, 2024
1 parent f823621 commit 493f3d2
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 23 deletions.
4 changes: 4 additions & 0 deletions packages/smooth_app/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -2956,5 +2956,9 @@
"prices_feedback_form": "Click here to send us your feedback about this new feature!",
"@prices_feedback_form": {
"description": "A button to send feedback about the prices feature"
},
"menu_button_list_actions": "Select an action",
"@menu_button_list_actions": {
"description": "Button to select an action in a list (eg: Share, Delete, …)"
}
}
9 changes: 5 additions & 4 deletions packages/smooth_app/lib/pages/personalized_ranking_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'package:smooth_app/pages/product/common/loading_status.dart';
import 'package:smooth_app/pages/product/common/product_list_item_simple.dart';
import 'package:smooth_app/pages/product_list_user_dialog_helper.dart';
import 'package:smooth_app/widgets/smooth_app_bar.dart';
import 'package:smooth_app/widgets/smooth_menu_button.dart';
import 'package:smooth_app/widgets/smooth_scaffold.dart';

class PersonalizedRankingPage extends StatefulWidget {
Expand Down Expand Up @@ -97,13 +98,13 @@ class _PersonalizedRankingPageState extends State<PersonalizedRankingPage>
appBar: SmoothAppBar(
title: Text(widget.title, overflow: TextOverflow.fade),
actions: <Widget>[
PopupMenuButton<String>(
SmoothPopupMenuButton<String>(
onSelected: _handlePopUpClick,
itemBuilder: (BuildContext context) {
return <PopupMenuEntry<String>>[
PopupMenuItem<String>(
return <SmoothPopupMenuItem<String>>[
SmoothPopupMenuItem<String>(
value: 'add_to_list',
child: Text(appLocalizations.user_list_button_add_product),
label: appLocalizations.user_list_button_add_product,
),
];
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart';
import 'package:smooth_app/pages/personalized_ranking_page.dart';
import 'package:smooth_app/pages/product/compare_products3_page.dart';
import 'package:smooth_app/pages/product/ordered_nutrients_cache.dart';
import 'package:smooth_app/widgets/smooth_menu_button.dart';

/// Popup menu item entries for the product list page, for selected items.
enum ProductListItemPopupMenuEntry {
Expand All @@ -26,6 +27,9 @@ abstract class ProductListItemPopupItem {
/// IconData of the popup menu item.
IconData getIconData();

/// Is-it a destructive action?
bool isDestructive() => false;

/// Action of the popup menu item.
///
/// Returns true if the caller must refresh (setState) (e.g. after deleting).
Expand All @@ -37,17 +41,16 @@ abstract class ProductListItemPopupItem {
});

/// Returns the popup menu item.
PopupMenuItem<ProductListItemPopupItem> getMenuItem(
SmoothPopupMenuItem<ProductListItemPopupItem> getMenuItem(
final AppLocalizations appLocalizations,
final bool enabled,
) =>
PopupMenuItem<ProductListItemPopupItem>(
SmoothPopupMenuItem<ProductListItemPopupItem>(
value: this,
icon: getIconData(),
label: getTitle(appLocalizations),
enabled: enabled,
child: ListTile(
leading: Icon(getIconData()),
title: Text(getTitle(appLocalizations)),
),
type: isDestructive() ? SmoothPopupMenuItemType.destructive : null,
);
}

Expand Down Expand Up @@ -139,6 +142,9 @@ class ProductListItemPopupDelete extends ProductListItemPopupItem {
@override
IconData getIconData() => Icons.delete;

@override
bool isDestructive() => true;

@override
Future<bool> doSomething({
required final ProductList productList,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ 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_menu_button.dart';
import 'package:smooth_app/widgets/smooth_scaffold.dart';
import 'package:smooth_app/widgets/will_pop_scope.dart';

Expand Down Expand Up @@ -181,7 +182,7 @@ class _ProductListPageState extends State<ProductListPage>
}
},
),
PopupMenuButton<ProductListPopupItem>(
SmoothPopupMenuButton<ProductListPopupItem>(
onSelected: (final ProductListPopupItem action) async {
final ProductList? differentProductList =
await action.doSomething(
Expand All @@ -193,8 +194,7 @@ class _ProductListPageState extends State<ProductListPage>
setState(() => productList = differentProductList);
}
},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<ProductListPopupItem>>[
itemBuilder: (_) => <SmoothPopupMenuItem<ProductListPopupItem>>[
if (enableRename) _rename.getMenuItem(appLocalizations),
_share.getMenuItem(appLocalizations),
_openInWeb.getMenuItem(appLocalizations),
Expand All @@ -215,7 +215,7 @@ class _ProductListPageState extends State<ProductListPage>
},
actionModeTitle: Text('${_selectedBarcodes.length}'),
actionModeActions: <Widget>[
PopupMenuButton<ProductListItemPopupItem>(
SmoothPopupMenuButton<ProductListItemPopupItem>(
onSelected: (final ProductListItemPopupItem action) async {
final bool andThenSetState = await action.doSomething(
productList: productList,
Expand All @@ -229,8 +229,7 @@ class _ProductListPageState extends State<ProductListPage>
}
}
},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<ProductListItemPopupItem>>[
itemBuilder: (_) => <SmoothPopupMenuItem<ProductListItemPopupItem>>[
if (userPreferences.getFlag(UserPreferencesDevMode
.userPreferencesFlagBoostedComparison) ==
true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart';
import 'package:smooth_app/helpers/analytics_helper.dart';
import 'package:smooth_app/helpers/temp_product_list_share_helper.dart';
import 'package:smooth_app/pages/product_list_user_dialog_helper.dart';
import 'package:smooth_app/widgets/smooth_menu_button.dart';
import 'package:url_launcher/url_launcher.dart';

/// Popup menu item entries for the product list page.
Expand All @@ -30,6 +31,9 @@ abstract class ProductListPopupItem {
/// Popup menu entry of the popup menu item.
ProductListPopupMenuEntry getEntry();

/// Is-it a destructive action?
bool isDestructive() => false;

/// Action of the popup menu item.
///
/// Returns a different product list if there are changes, else null.
Expand All @@ -40,15 +44,14 @@ abstract class ProductListPopupItem {
});

/// Returns the popup menu item.
PopupMenuItem<ProductListPopupItem> getMenuItem(
SmoothPopupMenuItem<ProductListPopupItem> getMenuItem(
final AppLocalizations appLocalizations,
) =>
PopupMenuItem<ProductListPopupItem>(
SmoothPopupMenuItem<ProductListPopupItem>(
value: this,
child: ListTile(
leading: Icon(getIconData()),
title: Text(getTitle(appLocalizations)),
),
icon: getIconData(),
label: getTitle(appLocalizations),
type: isDestructive() ? SmoothPopupMenuItemType.destructive : null,
);
}

Expand All @@ -64,6 +67,9 @@ class ProductListPopupClear extends ProductListPopupItem {
@override
ProductListPopupMenuEntry getEntry() => ProductListPopupMenuEntry.clear;

@override
bool isDestructive() => true;

@override
Future<ProductList?> doSomething({
required final ProductList productList,
Expand Down
112 changes: 112 additions & 0 deletions packages/smooth_app/lib/widgets/smooth_menu_button.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import 'dart:io';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class SmoothPopupMenuButton<T> extends StatefulWidget {
const SmoothPopupMenuButton({
required this.onSelected,
required this.itemBuilder,
this.actionsTitle,
this.buttonIcon,
this.buttonLabel,
}) : assert(buttonLabel == null || buttonLabel.length > 0),
assert(actionsTitle == null || actionsTitle.length > 0);

final Icon? buttonIcon;
final String? buttonLabel;
final String? actionsTitle;
final void Function(T value) onSelected;
final Iterable<SmoothPopupMenuItem<T>> Function(BuildContext context)
itemBuilder;

@override
State<SmoothPopupMenuButton<T>> createState() =>
_SmoothPopupMenuButtonState<T>();
}

class _SmoothPopupMenuButtonState<T> extends State<SmoothPopupMenuButton<T>> {
@override
Widget build(BuildContext context) {
if (Platform.isIOS || Platform.isMacOS) {
return IconButton(
icon: widget.buttonIcon ?? Icon(Icons.adaptive.more),
tooltip: widget.buttonLabel ??
MaterialLocalizations.of(context).showMenuTooltip,
onPressed: _openModalSheet,
);
} else {
return PopupMenuButton<T>(
icon: widget.buttonIcon ?? Icon(Icons.adaptive.more),
tooltip: widget.buttonLabel ??
MaterialLocalizations.of(context).showMenuTooltip,
onSelected: widget.onSelected,
itemBuilder: (BuildContext context) {
return widget.itemBuilder(context).map((SmoothPopupMenuItem<T> item) {
return PopupMenuItem<T>(
value: item.value,
enabled: item.enabled,
child: ListTile(
leading: Icon(item.icon),
title: Text(item.label),
),
);
}).toList(growable: false);
},
);
}
}

// iOS and macOS behavior
void _openModalSheet() {
showCupertinoModalPopup(
context: context,
builder: (BuildContext context) {
return CupertinoActionSheet(
title: Text(
widget.actionsTitle ??
AppLocalizations.of(context).menu_button_list_actions,
),
actions: widget
.itemBuilder(context)
.where((SmoothPopupMenuItem<T> item) => item.enabled)
.map((SmoothPopupMenuItem<T> item) {
return CupertinoActionSheetAction(
isDefaultAction:
item.type == SmoothPopupMenuItemType.highlighted,
isDestructiveAction:
item.type == SmoothPopupMenuItemType.destructive,
onPressed: () {
widget.onSelected(item.value);
Navigator.of(context).maybePop();
},
child: Text(item.label),
);
}).toList(growable: false),
);
});
}
}

class SmoothPopupMenuItem<T> {
const SmoothPopupMenuItem({
required this.value,
required this.label,
this.icon,
this.type,
this.enabled = true,
}) : assert(label.length > 0);

final T value;
final String label;
final IconData? icon;
final SmoothPopupMenuItemType? type;
final bool enabled;
}

enum SmoothPopupMenuItemType {
normal,
highlighted,
destructive,
}
2 changes: 1 addition & 1 deletion packages/smooth_app/macos/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ SPEC CHECKSUMS:
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
in_app_review: a850789fad746e89bce03d4aeee8078b45a53fd0
mobile_scanner: 54ceceae0c8da2457e26a362a6be5c61154b1829
package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce
package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
ReachabilitySwift: 7f151ff156cea1481a8411701195ac6a984f4979
rive_common: cf5ab646aa576b2d742d0e2d528126fbf032c856
Expand Down

0 comments on commit 493f3d2

Please sign in to comment.