From 71e87ef7e948790a8ce6d71cc7bd948e0aed5c28 Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Fri, 29 Mar 2024 16:07:01 +0530 Subject: [PATCH 1/5] feat: context menu horz --- auth/lib/ui/code_widget.dart | 196 ++++++++++++++++++++++++++--------- 1 file changed, 149 insertions(+), 47 deletions(-) diff --git a/auth/lib/ui/code_widget.dart b/auth/lib/ui/code_widget.dart index 0a74ad1ce2..8f7d1c34a4 100644 --- a/auth/lib/ui/code_widget.dart +++ b/auth/lib/ui/code_widget.dart @@ -1,3 +1,4 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:async'; import 'dart:io'; @@ -87,6 +88,7 @@ class _CodeWidgetState extends State { return Container( margin: const EdgeInsets.only(left: 16, right: 16, bottom: 8, top: 8), child: Slidable( + enabled: PlatformUtil.isMobile(), key: ValueKey(widget.code.hashCode), endActionPane: ActionPane( extentRatio: 0.60, @@ -136,58 +138,56 @@ class _CodeWidgetState extends State { ], ), child: Builder( - builder: (context) { - return RawGestureDetector( - gestures: { - PanGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - () => PanGestureRecognizer( - debugOwner: this, - // This recognizer accepts any button press made with a secondary button. - allowedButtonsFilter: (int buttons) => - buttons & kSecondaryButton != 0, - ), - (PanGestureRecognizer instance) { - instance - ..dragStartBehavior = DragStartBehavior.down - ..onEnd = (DragEndDetails details) { - Slidable.of(context)?.openEndActionPane(); - }; - }, + builder: (context) => RawGestureDetector( + gestures: { + PanGestureRecognizer: + GestureRecognizerFactoryWithHandlers( + () => PanGestureRecognizer( + debugOwner: this, + // This recognizer accepts any button press made with a secondary button. + allowedButtonsFilter: (int buttons) => + buttons & kSecondaryButton != 0, ), - }, - child: ClipRRect( - borderRadius: BorderRadius.circular(8), - child: Container( - color: Theme.of(context).colorScheme.codeCardBackgroundColor, - child: Material( - color: Colors.transparent, - child: InkWell( - customBorder: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - onTap: () { - _copyCurrentOTPToClipboard(); - }, - onDoubleTap: isMaskingEnabled - ? () { - setState( - () { - _hideCode = !_hideCode; - }, - ); - } - : null, - onLongPress: () { - _copyCurrentOTPToClipboard(); - }, - child: _getCardContents(l10n), + (PanGestureRecognizer instance) { + instance + ..dragStartBehavior = DragStartBehavior.down + ..onEnd = (DragEndDetails details) { + Slidable.of(context)?.openEndActionPane(); + }; + }, + ), + }, + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Container( + color: Theme.of(context).colorScheme.codeCardBackgroundColor, + child: Material( + color: Colors.transparent, + child: InkWell( + customBorder: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), ), + onTap: () { + _copyCurrentOTPToClipboard(); + }, + onDoubleTap: isMaskingEnabled + ? () { + setState( + () { + _hideCode = !_hideCode; + }, + ); + } + : null, + onLongPress: () { + _copyCurrentOTPToClipboard(); + }, + child: _getCardContents(l10n), ), ), ), - ); - }, + ), + ), ), ), ); @@ -223,6 +223,55 @@ class _CodeWidgetState extends State { const SizedBox( height: 20, ), + if (PlatformUtil.isDesktop()) + Row( + children: [ + Expanded( + child: SlideAction( + onPressed: _onShowQrPressed, + backgroundColor: Colors.grey.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(12.0)), + foregroundColor: + Theme.of(context).colorScheme.inverseBackgroundColor, + icon: Icons.qr_code_2_outlined, + label: "QR", + padding: const EdgeInsets.only(left: 4, right: 0), + spacing: 8, + ), + ), + const SizedBox( + width: 4, + ), + Expanded( + child: SlideAction( + onPressed: _onEditPressed, + backgroundColor: Colors.grey.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(12.0)), + foregroundColor: + Theme.of(context).colorScheme.inverseBackgroundColor, + icon: Icons.edit_outlined, + label: l10n.edit, + padding: const EdgeInsets.only(left: 4, right: 0), + spacing: 8, + ), + ), + const SizedBox( + width: 4, + ), + Expanded( + child: SlideAction( + onPressed: _onDeletePressed, + backgroundColor: Colors.grey.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(12.0)), + foregroundColor: const Color(0xFFFE4A49), + icon: Icons.delete, + label: l10n.delete, + padding: const EdgeInsets.only(left: 0, right: 0), + spacing: 8, + ), + ), + ], + ), ], ), ); @@ -485,3 +534,56 @@ class _CodeWidgetState extends State { return code; } } + +class SlideAction extends StatelessWidget { + const SlideAction({ + super.key, + required this.onPressed, + required this.backgroundColor, + required this.borderRadius, + required this.foregroundColor, + required this.icon, + required this.label, + required this.padding, + required this.spacing, + }); + + final void Function(dynamic) onPressed; + final Color backgroundColor; + final BorderRadius borderRadius; + final Color foregroundColor; + final IconData icon; + final String label; + final EdgeInsets padding; + final double spacing; + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () => onPressed(0), + child: Container( + color: backgroundColor, + height: 32, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + icon, + size: 16, + color: foregroundColor, + ), + const SizedBox( + width: 4, + ), + Text( + label, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: foregroundColor, + ), + ), + ], + ), + ), + ); + } +} From 056e29a5f5893838515ad32ac503202da9dd2cc6 Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Fri, 29 Mar 2024 16:07:09 +0530 Subject: [PATCH 2/5] feat: context menu vert --- auth/lib/ui/code_widget.dart | 72 +++++++++++++++++------------------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/auth/lib/ui/code_widget.dart b/auth/lib/ui/code_widget.dart index 8f7d1c34a4..874d5879b7 100644 --- a/auth/lib/ui/code_widget.dart +++ b/auth/lib/ui/code_widget.dart @@ -225,50 +225,45 @@ class _CodeWidgetState extends State { ), if (PlatformUtil.isDesktop()) Row( + mainAxisAlignment: MainAxisAlignment.end, children: [ - Expanded( - child: SlideAction( - onPressed: _onShowQrPressed, - backgroundColor: Colors.grey.withOpacity(0.1), - borderRadius: const BorderRadius.all(Radius.circular(12.0)), - foregroundColor: - Theme.of(context).colorScheme.inverseBackgroundColor, - icon: Icons.qr_code_2_outlined, - label: "QR", - padding: const EdgeInsets.only(left: 4, right: 0), - spacing: 8, - ), + SlideAction( + onPressed: _onShowQrPressed, + backgroundColor: Colors.grey.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(12.0)), + foregroundColor: + Theme.of(context).colorScheme.inverseBackgroundColor, + icon: Icons.qr_code_2_outlined, + label: "QR", + padding: const EdgeInsets.only(left: 4, right: 0), + spacing: 8, ), const SizedBox( width: 4, ), - Expanded( - child: SlideAction( - onPressed: _onEditPressed, - backgroundColor: Colors.grey.withOpacity(0.1), - borderRadius: const BorderRadius.all(Radius.circular(12.0)), - foregroundColor: - Theme.of(context).colorScheme.inverseBackgroundColor, - icon: Icons.edit_outlined, - label: l10n.edit, - padding: const EdgeInsets.only(left: 4, right: 0), - spacing: 8, - ), + SlideAction( + onPressed: _onEditPressed, + backgroundColor: Colors.grey.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(12.0)), + foregroundColor: + Theme.of(context).colorScheme.inverseBackgroundColor, + icon: Icons.edit_outlined, + label: l10n.edit, + padding: const EdgeInsets.only(left: 4, right: 0), + spacing: 8, ), const SizedBox( width: 4, ), - Expanded( - child: SlideAction( - onPressed: _onDeletePressed, - backgroundColor: Colors.grey.withOpacity(0.1), - borderRadius: const BorderRadius.all(Radius.circular(12.0)), - foregroundColor: const Color(0xFFFE4A49), - icon: Icons.delete, - label: l10n.delete, - padding: const EdgeInsets.only(left: 0, right: 0), - spacing: 8, - ), + SlideAction( + onPressed: _onDeletePressed, + backgroundColor: Colors.grey.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(12.0)), + foregroundColor: const Color(0xFFFE4A49), + icon: Icons.delete, + label: l10n.delete, + padding: const EdgeInsets.only(left: 0, right: 0), + spacing: 8, ), ], ), @@ -563,8 +558,9 @@ class SlideAction extends StatelessWidget { onTap: () => onPressed(0), child: Container( color: backgroundColor, - height: 32, - child: Row( + height: 52, + width: 52, + child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( @@ -573,7 +569,7 @@ class SlideAction extends StatelessWidget { color: foregroundColor, ), const SizedBox( - width: 4, + height: 4, ), Text( label, From f8fbedfe107c38dd14fdc1b96f06857060579e2e Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Fri, 29 Mar 2024 16:21:30 +0530 Subject: [PATCH 3/5] fix: use context menu on desktop --- auth/lib/ui/code_widget.dart | 309 +++++++++++++---------------------- auth/pubspec.lock | 16 ++ auth/pubspec.yaml | 1 + 3 files changed, 130 insertions(+), 196 deletions(-) diff --git a/auth/lib/ui/code_widget.dart b/auth/lib/ui/code_widget.dart index 874d5879b7..9b95f07b40 100644 --- a/auth/lib/ui/code_widget.dart +++ b/auth/lib/ui/code_widget.dart @@ -18,8 +18,8 @@ import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/platform_util.dart'; import 'package:ente_auth/utils/toast_util.dart'; import 'package:ente_auth/utils/totp_util.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_context_menu/flutter_context_menu.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:logging/logging.dart'; import 'package:move_to_background/move_to_background.dart'; @@ -87,106 +87,121 @@ class _CodeWidgetState extends State { final l10n = context.l10n; return Container( margin: const EdgeInsets.only(left: 16, right: 16, bottom: 8, top: 8), - child: Slidable( - enabled: PlatformUtil.isMobile(), - key: ValueKey(widget.code.hashCode), - endActionPane: ActionPane( - extentRatio: 0.60, - motion: const ScrollMotion(), - children: [ - const SizedBox( - width: 4, - ), - SlidableAction( - onPressed: _onShowQrPressed, - backgroundColor: Colors.grey.withOpacity(0.1), - borderRadius: const BorderRadius.all(Radius.circular(12.0)), - foregroundColor: - Theme.of(context).colorScheme.inverseBackgroundColor, - icon: Icons.qr_code_2_outlined, - label: "QR", - padding: const EdgeInsets.only(left: 4, right: 0), - spacing: 8, - ), - const SizedBox( - width: 4, - ), - SlidableAction( - onPressed: _onEditPressed, - backgroundColor: Colors.grey.withOpacity(0.1), - borderRadius: const BorderRadius.all(Radius.circular(12.0)), - foregroundColor: - Theme.of(context).colorScheme.inverseBackgroundColor, - icon: Icons.edit_outlined, - label: l10n.edit, - padding: const EdgeInsets.only(left: 4, right: 0), - spacing: 8, - ), - const SizedBox( - width: 4, - ), - SlidableAction( - onPressed: _onDeletePressed, - backgroundColor: Colors.grey.withOpacity(0.1), - borderRadius: const BorderRadius.all(Radius.circular(12.0)), - foregroundColor: const Color(0xFFFE4A49), - icon: Icons.delete, - label: l10n.delete, - padding: const EdgeInsets.only(left: 0, right: 0), - spacing: 8, - ), - ], - ), - child: Builder( - builder: (context) => RawGestureDetector( - gestures: { - PanGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - () => PanGestureRecognizer( - debugOwner: this, - // This recognizer accepts any button press made with a secondary button. - allowedButtonsFilter: (int buttons) => - buttons & kSecondaryButton != 0, - ), - (PanGestureRecognizer instance) { - instance - ..dragStartBehavior = DragStartBehavior.down - ..onEnd = (DragEndDetails details) { - Slidable.of(context)?.openEndActionPane(); - }; - }, - ), - }, - child: ClipRRect( - borderRadius: BorderRadius.circular(8), - child: Container( - color: Theme.of(context).colorScheme.codeCardBackgroundColor, - child: Material( - color: Colors.transparent, - child: InkWell( - customBorder: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - onTap: () { - _copyCurrentOTPToClipboard(); - }, - onDoubleTap: isMaskingEnabled - ? () { - setState( - () { - _hideCode = !_hideCode; - }, - ); - } - : null, - onLongPress: () { - _copyCurrentOTPToClipboard(); - }, - child: _getCardContents(l10n), + child: Builder( + builder: (context) { + if (PlatformUtil.isDesktop()) { + return ContextMenuRegion( + contextMenu: ContextMenu( + entries: [ + MenuItem( + label: 'QR', + icon: Icons.qr_code, + onSelected: () => _onShowQrPressed(null), ), - ), + MenuItem( + label: 'Edit', + icon: Icons.edit, + onSelected: () => _onEditPressed(null), + ), + const MenuDivider(), + MenuItem( + label: 'Delete', + value: "Delete", + icon: Icons.delete, + onSelected: () => _onDeletePressed(null), + ), + ], + padding: const EdgeInsets.all(8.0), ), + child: _clippedCard(l10n), + ); + } + + return Slidable( + key: ValueKey(widget.code.hashCode), + endActionPane: ActionPane( + extentRatio: 0.60, + motion: const ScrollMotion(), + children: [ + const SizedBox( + width: 4, + ), + SlidableAction( + onPressed: _onShowQrPressed, + backgroundColor: Colors.grey.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(12.0)), + foregroundColor: + Theme.of(context).colorScheme.inverseBackgroundColor, + icon: Icons.qr_code_2_outlined, + label: "QR", + padding: const EdgeInsets.only(left: 4, right: 0), + spacing: 8, + ), + const SizedBox( + width: 4, + ), + SlidableAction( + onPressed: _onEditPressed, + backgroundColor: Colors.grey.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(12.0)), + foregroundColor: + Theme.of(context).colorScheme.inverseBackgroundColor, + icon: Icons.edit_outlined, + label: l10n.edit, + padding: const EdgeInsets.only(left: 4, right: 0), + spacing: 8, + ), + const SizedBox( + width: 4, + ), + SlidableAction( + onPressed: _onDeletePressed, + backgroundColor: Colors.grey.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(12.0)), + foregroundColor: const Color(0xFFFE4A49), + icon: Icons.delete, + label: l10n.delete, + padding: const EdgeInsets.only(left: 0, right: 0), + spacing: 8, + ), + ], ), + child: Builder( + builder: (context) => _clippedCard(l10n), + ), + ); + }, + ), + ); + } + + Widget _clippedCard(AppLocalizations l10n) { + return ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Container( + color: Theme.of(context).colorScheme.codeCardBackgroundColor, + child: Material( + color: Colors.transparent, + child: InkWell( + customBorder: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + onTap: () { + _copyCurrentOTPToClipboard(); + }, + onDoubleTap: isMaskingEnabled + ? () { + setState( + () { + _hideCode = !_hideCode; + }, + ); + } + : null, + onLongPress: () { + _copyCurrentOTPToClipboard(); + }, + child: _getCardContents(l10n), ), ), ), @@ -223,50 +238,6 @@ class _CodeWidgetState extends State { const SizedBox( height: 20, ), - if (PlatformUtil.isDesktop()) - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - SlideAction( - onPressed: _onShowQrPressed, - backgroundColor: Colors.grey.withOpacity(0.1), - borderRadius: const BorderRadius.all(Radius.circular(12.0)), - foregroundColor: - Theme.of(context).colorScheme.inverseBackgroundColor, - icon: Icons.qr_code_2_outlined, - label: "QR", - padding: const EdgeInsets.only(left: 4, right: 0), - spacing: 8, - ), - const SizedBox( - width: 4, - ), - SlideAction( - onPressed: _onEditPressed, - backgroundColor: Colors.grey.withOpacity(0.1), - borderRadius: const BorderRadius.all(Radius.circular(12.0)), - foregroundColor: - Theme.of(context).colorScheme.inverseBackgroundColor, - icon: Icons.edit_outlined, - label: l10n.edit, - padding: const EdgeInsets.only(left: 4, right: 0), - spacing: 8, - ), - const SizedBox( - width: 4, - ), - SlideAction( - onPressed: _onDeletePressed, - backgroundColor: Colors.grey.withOpacity(0.1), - borderRadius: const BorderRadius.all(Radius.circular(12.0)), - foregroundColor: const Color(0xFFFE4A49), - icon: Icons.delete, - label: l10n.delete, - padding: const EdgeInsets.only(left: 0, right: 0), - spacing: 8, - ), - ], - ), ], ), ); @@ -529,57 +500,3 @@ class _CodeWidgetState extends State { return code; } } - -class SlideAction extends StatelessWidget { - const SlideAction({ - super.key, - required this.onPressed, - required this.backgroundColor, - required this.borderRadius, - required this.foregroundColor, - required this.icon, - required this.label, - required this.padding, - required this.spacing, - }); - - final void Function(dynamic) onPressed; - final Color backgroundColor; - final BorderRadius borderRadius; - final Color foregroundColor; - final IconData icon; - final String label; - final EdgeInsets padding; - final double spacing; - - @override - Widget build(BuildContext context) { - return InkWell( - onTap: () => onPressed(0), - child: Container( - color: backgroundColor, - height: 52, - width: 52, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - icon, - size: 16, - color: foregroundColor, - ), - const SizedBox( - height: 4, - ), - Text( - label, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: foregroundColor, - ), - ), - ], - ), - ), - ); - } -} diff --git a/auth/pubspec.lock b/auth/pubspec.lock index df8506d8ec..35dd963563 100644 --- a/auth/pubspec.lock +++ b/auth/pubspec.lock @@ -347,6 +347,14 @@ packages: url: "https://github.com/ente-io/ente_crypto_dart.git" source: git version: "1.0.0" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" event_bus: dependency: "direct main" description: @@ -440,6 +448,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.5" + flutter_context_menu: + dependency: "direct main" + description: + name: flutter_context_menu + sha256: "9f220a8fa0290c68e38000d6d62a0dc4555d490c15a5bd856a6e6d255d81b8dc" + url: "https://pub.dev" + source: hosted + version: "0.1.3" flutter_displaymode: dependency: "direct main" description: diff --git a/auth/pubspec.yaml b/auth/pubspec.yaml index 5f0cdb1d55..c1f0e4806f 100644 --- a/auth/pubspec.yaml +++ b/auth/pubspec.yaml @@ -41,6 +41,7 @@ dependencies: flutter: sdk: flutter flutter_bloc: ^8.0.1 + flutter_context_menu: ^0.1.3 flutter_displaymode: ^0.6.0 flutter_email_sender: ^6.0.2 flutter_inappwebview: ^6.0.0 From 4830a69aad324c9c0ad7a5a53c9feba61e2bb4de Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Fri, 29 Mar 2024 16:23:04 +0530 Subject: [PATCH 4/5] chore: remove lint --- auth/lib/ui/code_widget.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/auth/lib/ui/code_widget.dart b/auth/lib/ui/code_widget.dart index 9b95f07b40..104215d31a 100644 --- a/auth/lib/ui/code_widget.dart +++ b/auth/lib/ui/code_widget.dart @@ -1,4 +1,3 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:async'; import 'dart:io'; From cb7a2445e10ff757a6a6b52acfc6824589b570f8 Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Fri, 29 Mar 2024 16:23:55 +0530 Subject: [PATCH 5/5] fix: qr image --- auth/lib/ui/code_widget.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth/lib/ui/code_widget.dart b/auth/lib/ui/code_widget.dart index 104215d31a..c579def120 100644 --- a/auth/lib/ui/code_widget.dart +++ b/auth/lib/ui/code_widget.dart @@ -94,7 +94,7 @@ class _CodeWidgetState extends State { entries: [ MenuItem( label: 'QR', - icon: Icons.qr_code, + icon: Icons.qr_code_2_outlined, onSelected: () => _onShowQrPressed(null), ), MenuItem(