From 53271316296caa2398a258beb09ecdbaa68fb051 Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Mon, 29 Jan 2024 11:25:38 +0100 Subject: [PATCH] Add a feature to copy from clipboard in the search page --- packages/smooth_app/lib/l10n/app_en.arb | 8 + .../lib/pages/scan/search_history_view.dart | 145 +++++++++++++----- 2 files changed, 115 insertions(+), 38 deletions(-) diff --git a/packages/smooth_app/lib/l10n/app_en.arb b/packages/smooth_app/lib/l10n/app_en.arb index 3f85a6abf845..bdbefdcbe583 100644 --- a/packages/smooth_app/lib/l10n/app_en.arb +++ b/packages/smooth_app/lib/l10n/app_en.arb @@ -1974,6 +1974,14 @@ "@copy_to_clipboard": { "description": "Copy to clipboard button description" }, + "copy_from_clipboard": "Copy from clipboard", + "@copy_from_clipboard": { + "description": "Copy the content of the clipboard" + }, + "no_data_available_in_clipboard": "No data available in your clipboard", + "@no_data_available_in_clipboard": { + "description": "No data available in your clipboard" + }, "clipboard_barcode_copy": "Copy barcode to clipboard", "@clipboard_barcode_copied": { "description": "Snackbar label after clipboard copy", diff --git a/packages/smooth_app/lib/pages/scan/search_history_view.dart b/packages/smooth_app/lib/pages/scan/search_history_view.dart index 47877f468b52..b3983d70a723 100644 --- a/packages/smooth_app/lib/pages/scan/search_history_view.dart +++ b/packages/smooth_app/lib/pages/scan/search_history_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:provider/provider.dart'; import 'package:smooth_app/database/dao_string_list.dart'; @@ -36,21 +37,83 @@ class _SearchHistoryViewState extends State { @override Widget build(BuildContext context) { - return ListView.builder( - itemCount: _queries.length, - itemBuilder: (BuildContext context, int i) => - _buildSearchHistoryTile(context, _queries[i]), + return ListTileTheme( + data: const ListTileThemeData( + titleTextStyle: TextStyle(fontSize: 20.0), + minLeadingWidth: 18.0, + ), + child: ListView.builder( + itemBuilder: (BuildContext context, int i) { + if (i == 0) { + return _SearchItemCopyFromClipboard( + onData: (String data) => widget.onTap?.call(data), + ); + } + + final String query = _queries[i - 1]; + + return _SearchHistoryTile( + query: query, + onTap: () => widget.onTap?.call(query), + onEditItem: () => _onEditItem(query), + onDismissItem: () async { + // we need an immediate action for the display refresh + _queries.remove(query); + // and we need to impact the database too + final LocalDatabase localDatabase = context.read(); + await DaoStringList(localDatabase) + .remove(DaoStringList.keySearchHistory, query); + setState(() {}); + }, + ); + }, + itemCount: _queries.length + 1, // +1 for the "Copy to clipboard" + ), + ); + } + + void _onEditItem(String query) { + final TextEditingController controller = Provider.of( + context, + listen: false, ); + + controller.text = query; + controller.selection = + TextSelection.fromPosition(TextPosition(offset: query.length)); + + // If the keyboard is hidden, show it. + if (View.of(context).viewInsets.bottom == 0) { + widget.focusNode?.unfocus(); + + WidgetsBinding.instance.addPostFrameCallback((_) { + FocusScope.of(context).requestFocus(widget.focusNode); + }); + } } +} + +class _SearchHistoryTile extends StatelessWidget { + const _SearchHistoryTile({ + required this.query, + required this.onTap, + required this.onEditItem, + required this.onDismissItem, + }); + + final String query; + final VoidCallback onTap; + final VoidCallback onEditItem; + final VoidCallback onDismissItem; - Widget _buildSearchHistoryTile(BuildContext context, String query) { + @override + Widget build(BuildContext context) { final AppLocalizations localizations = AppLocalizations.of(context); return Dismissible( key: Key(query), direction: DismissDirection.endToStart, - onDismissed: (DismissDirection direction) async => - _handleDismissed(context, query), + onDismissed: (DismissDirection direction) async => onDismissItem(), background: Container( color: RED_COLOR, alignment: AlignmentDirectional.centerEnd, @@ -61,7 +124,7 @@ class _SearchHistoryViewState extends State { ), ), child: InkWell( - onTap: () => widget.onTap?.call(query), + onTap: () => onTap, child: Padding( padding: const EdgeInsetsDirectional.only(start: 18.0, end: 13.0), child: ListTile( @@ -69,31 +132,11 @@ class _SearchHistoryViewState extends State { padding: EdgeInsetsDirectional.only(top: VERY_SMALL_SPACE), child: Icon( Icons.search, - size: 18.0, ), ), trailing: InkWell( customBorder: const CircleBorder(), - onTap: () { - final TextEditingController controller = - Provider.of( - context, - listen: false, - ); - - controller.text = query; - controller.selection = TextSelection.fromPosition( - TextPosition(offset: query.length)); - - // If the keyboard is hidden, show it. - if (View.of(context).viewInsets.bottom == 0) { - widget.focusNode?.unfocus(); - - WidgetsBinding.instance.addPostFrameCallback((_) { - FocusScope.of(context).requestFocus(widget.focusNode); - }); - } - }, + onTap: onEditItem, child: Tooltip( message: localizations.search_history_item_edit_tooltip, enableFeedback: true, @@ -104,20 +147,46 @@ class _SearchHistoryViewState extends State { ), ), minLeadingWidth: 10.0, - title: Text(query, style: const TextStyle(fontSize: 20.0)), + title: Text(query), ), ), ), ); } +} + +class _SearchItemCopyFromClipboard extends StatelessWidget { + const _SearchItemCopyFromClipboard({ + required this.onData, + }); + + final Function(String) onData; + + @override + Widget build(BuildContext context) { + final AppLocalizations localizations = AppLocalizations.of(context); - Future _handleDismissed(BuildContext context, String query) async { - // we need an immediate action for the display refresh - _queries.remove(query); - // and we need to impact the database too - final LocalDatabase localDatabase = context.read(); - await DaoStringList(localDatabase) - .remove(DaoStringList.keySearchHistory, query); - setState(() {}); + return InkWell( + onTap: () async { + final ClipboardData? data = await Clipboard.getData('text/plain'); + if (data?.text?.isNotEmpty == true) { + onData(data!.text!); + } else if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(localizations.no_data_available_in_clipboard), + ), + ); + } + }, + child: Padding( + padding: const EdgeInsetsDirectional.only(start: 18.0, end: 13.0), + child: ListTile( + title: Text(localizations.copy_from_clipboard), + leading: const Icon(Icons.copy), + minLeadingWidth: 10.0, + ), + ), + ); } }