From 236dc759f3df34df9f167f21cc8b32e2e818db46 Mon Sep 17 00:00:00 2001 From: AutoComplete Date: Fri, 10 Nov 2023 16:02:23 +0100 Subject: [PATCH] feat: new token list view for better usability; --- .../drag_target_divider.dart | 1 - .../edit_day_password_token_action.dart | 30 +-- .../day_password_token_widget.dart | 2 - .../day_password_token_widget_tile.dart | 7 +- .../default_delete_action.dart | 32 +-- .../default_edit_action.dart | 31 +-- .../default_lock_action.dart | 32 +-- .../actions/edit_hotp_token_action.dart | 30 +-- .../hotp_token_widgets/hotp_token_widget.dart | 2 - .../hotp_token_widget_tile.dart | 7 +- .../actions/edit_push_token_action.dart | 28 +-- .../push_token_widgets/push_token_widget.dart | 1 - .../push_token_widget_tile.dart | 6 +- .../token_widgets/token_action.dart | 9 +- .../token_widgets/token_widget_base.dart | 35 +--- .../token_widgets/token_widget_slideable.dart | 19 +- .../token_widgets/token_widget_tile.dart | 185 +++++++++++++++--- .../actions/edit_totp_token_action.dart | 28 +-- .../totp_token_widgets/totp_token_widget.dart | 2 - .../totp_token_widget_tile.dart | 7 +- 20 files changed, 223 insertions(+), 271 deletions(-) diff --git a/lib/views/main_view/main_view_widgets/drag_target_divider.dart b/lib/views/main_view/main_view_widgets/drag_target_divider.dart index 173f69817..d68b5103f 100644 --- a/lib/views/main_view/main_view_widgets/drag_target_divider.dart +++ b/lib/views/main_view/main_view_widgets/drag_target_divider.dart @@ -119,7 +119,6 @@ class _DragTargetDividerState extends ConsumerState CustomSlidableAction( - backgroundColor: Theme.of(context).extension()!.editColor, - foregroundColor: Theme.of(context).extension()!.foregroundColor, - onPressed: (context) async { - if (token.isLocked && await lockAuth(context: context, localizedReason: AppLocalizations.of(context)!.editLockedToken) == false) { - return; - } - _showDialog(); - }, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon(Icons.edit), - Text( - AppLocalizations.of(context)!.edit, - overflow: TextOverflow.fade, - softWrap: false, - ), - ], - )); + void handle(BuildContext context) async { + if (token.isLocked && await lockAuth(context: context, localizedReason: AppLocalizations.of(context)!.editLockedToken) == false) { + return; + } + _showDialog(); + } void _showDialog() { final tokenLabel = TextEditingController(text: token.label); diff --git a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget.dart b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget.dart index efd1524aa..853759c1d 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import '../../../../../model/tokens/day_password_token.dart'; import '../token_widget.dart'; import '../token_widget_base.dart'; -import 'actions/edit_day_password_token_action.dart'; import 'day_password_token_widget_tile.dart'; class DayPasswordTokenWidget extends TokenWidget { @@ -17,7 +16,6 @@ class DayPasswordTokenWidget extends TokenWidget { token: token, tile: DayPasswordTokenWidgetTile(token), dragIcon: Icons.calendar_month, - editAction: EditDayPassowrdTokenAction(token: token, key: Key('${token.id}editAction')), ); } } diff --git a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart index 152fc381e..516d998df 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; +import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/actions/edit_day_password_token_action.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/day_password_token.dart'; @@ -75,6 +76,8 @@ class _DayPasswordTokenWidgetTileState extends ConsumerState()!.deleteColor, - foregroundColor: Theme.of(context).extension()!.foregroundColor, - onPressed: (_) async { - if (token.isLocked && await lockAuth(context: context, localizedReason: AppLocalizations.of(context)?.deleteLockedToken ?? '') == false) { - return; - } - _showDialog(); - }, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon(Icons.delete), - Text( - AppLocalizations.of(context)!.delete, - overflow: TextOverflow.fade, - softWrap: false, - ), - ], - ), - ); + void handle(BuildContext context) async { + if (token.isLocked && await lockAuth(context: context, localizedReason: AppLocalizations.of(context)?.deleteLockedToken ?? '') == false) { + return; + } + _showDialog(); } void _showDialog() => globalNavigatorKey.currentContext == null diff --git a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action.dart b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action.dart index 6779b52cf..ebde3f859 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_edit_action.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/token.dart'; -import '../../../../../utils/app_customizer.dart'; import '../../../../../utils/customizations.dart'; import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/logger.dart'; @@ -13,31 +11,14 @@ import '../token_action.dart'; class DefaultEditAction extends TokenAction { final Token token; - const DefaultEditAction({required this.token, super.key}); + const DefaultEditAction({required this.token}); @override - CustomSlidableAction build(BuildContext context) { - return CustomSlidableAction( - backgroundColor: Theme.of(context).extension()!.editColor, - foregroundColor: Theme.of(context).extension()!.foregroundColor, - onPressed: (context) async { - if (token.isLocked && await lockAuth(context: context, localizedReason: AppLocalizations.of(context)!.editLockedToken) == false) { - return; - } - _showDialog(); - }, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon(Icons.edit), - Text( - AppLocalizations.of(context)!.rename, - overflow: TextOverflow.fade, - softWrap: false, - ), - ], - )); + void handle(BuildContext context) async { + if (token.isLocked && await lockAuth(context: context, localizedReason: AppLocalizations.of(context)!.editLockedToken) == false) { + return; + } + _showDialog(); } void _showDialog() { diff --git a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_lock_action.dart b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_lock_action.dart index ce8735496..7565813de 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_lock_action.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/default_token_actions/default_lock_action.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/token.dart'; -import '../../../../../utils/app_customizer.dart'; import '../../../../../utils/lock_auth.dart'; import '../../../../../utils/logger.dart'; import '../../../../../utils/riverpod_providers.dart'; @@ -12,31 +10,15 @@ import '../token_action.dart'; class DefaultLockAction extends TokenAction { final Token token; - const DefaultLockAction({required this.token, super.key}); + const DefaultLockAction({required this.token}); @override - CustomSlidableAction build(BuildContext context) { - return CustomSlidableAction( - backgroundColor: Theme.of(context).extension()!.lockColor, - foregroundColor: Theme.of(context).extension()!.foregroundColor, - onPressed: (context) async { - Logger.info('Changing lock status of token.', name: 'token_widgets.dart#_changeLockStatus'); - if (await lockAuth(context: context, localizedReason: AppLocalizations.of(context)!.authenticateToUnLockToken) == false) return; + void handle(BuildContext context) async { + Logger.info('Changing lock status of token.', name: 'token_widgets.dart#_changeLockStatus'); + if (await lockAuth(context: context, localizedReason: AppLocalizations.of(context)!.authenticateToUnLockToken) == false) { + return; + } - globalRef?.read(tokenProvider.notifier).updateToken(token, (p0) => p0.copyWith(isLocked: !token.isLocked)); - }, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon(Icons.lock), - Text( - token.isLocked ? AppLocalizations.of(context)!.unlock : AppLocalizations.of(context)!.lock, - overflow: TextOverflow.fade, - softWrap: false, - ), - ], - ), - ); + globalRef?.read(tokenProvider.notifier).updateToken(token, (p0) => p0.copyWith(isLocked: !token.isLocked)); } } diff --git a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/actions/edit_hotp_token_action.dart b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/actions/edit_hotp_token_action.dart index 2e2d98a54..1cb0506d1 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/actions/edit_hotp_token_action.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/actions/edit_hotp_token_action.dart @@ -1,11 +1,9 @@ import 'dart:ui'; import 'package:flutter/material.dart'; -import 'package:flutter_slidable/flutter_slidable.dart'; import '../../../../../../l10n/app_localizations.dart'; import '../../../../../../model/tokens/hotp_token.dart'; -import '../../../../../../utils/app_customizer.dart'; import '../../../../../../utils/customizations.dart'; import '../../../../../../utils/lock_auth.dart'; import '../../../../../../utils/riverpod_providers.dart'; @@ -17,32 +15,16 @@ class EditHOTPTokenAction extends TokenAction { final HOTPToken token; const EditHOTPTokenAction({ - super.key, required this.token, }); @override - CustomSlidableAction build(BuildContext context) => CustomSlidableAction( - backgroundColor: Theme.of(context).extension()!.editColor, - foregroundColor: Theme.of(context).extension()!.foregroundColor, - onPressed: (context) async { - if (token.isLocked && await lockAuth(context: context, localizedReason: AppLocalizations.of(context)!.editLockedToken) == false) { - return; - } - _showDialog(); - }, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon(Icons.edit), - Text( - AppLocalizations.of(context)!.edit, - overflow: TextOverflow.fade, - softWrap: false, - ), - ], - )); + void handle(BuildContext context) async { + if (token.isLocked && await lockAuth(context: context, localizedReason: AppLocalizations.of(context)!.editLockedToken) == false) { + return; + } + _showDialog(); + } void _showDialog() { final tokenLabel = TextEditingController(text: token.label); diff --git a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget.dart b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget.dart index 19c39354b..6c8ac2eec 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import '../../../../../model/tokens/hotp_token.dart'; import '../token_widget.dart'; import '../token_widget_base.dart'; -import 'actions/edit_hotp_token_action.dart'; import 'hotp_token_widget_tile.dart'; class HOTPTokenWidget extends TokenWidget { @@ -21,7 +20,6 @@ class HOTPTokenWidget extends TokenWidget { token: token, tile: HOTPTokenWidgetTile(token: token, key: ValueKey(token.id)), dragIcon: Icons.replay, - editAction: EditHOTPTokenAction(token: token), ); } } diff --git a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart index 3f26e8d21..eb77fe481 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/hotp_token_widget_tile.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/hotp_token_widgets/actions/edit_hotp_token_action.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/tokens/hotp_token.dart'; @@ -79,6 +80,8 @@ class _HOTPTokenWidgetTileState extends ConsumerState { @override Widget build(BuildContext context) { return TokenWidgetTile( + token: widget.token, + editAction: EditHOTPTokenAction(token: widget.token), key: Key('${widget.token.hashCode}TokenWidgetTile'), tokenImage: widget.token.tokenImage, tokenIsLocked: widget.token.isLocked, @@ -100,10 +103,6 @@ class _HOTPTokenWidgetTileState extends ConsumerState { ), ), ), - subtitles: [ - if (widget.token.label.isNotEmpty) widget.token.label, - if (widget.token.issuer.isNotEmpty) widget.token.issuer, - ], trailing: HideableWidget( token: widget.token, isHiddenNotifier: isHidden, diff --git a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/actions/edit_push_token_action.dart b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/actions/edit_push_token_action.dart index e2ee62e4c..dd328eb05 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/actions/edit_push_token_action.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/actions/edit_push_token_action.dart @@ -16,32 +16,16 @@ class EditPushTokenAction extends TokenAction { final PushToken token; const EditPushTokenAction({ - super.key, required this.token, }); @override - CustomSlidableAction build(BuildContext context) => CustomSlidableAction( - backgroundColor: Theme.of(context).extension()!.editColor, - foregroundColor: Theme.of(context).extension()!.foregroundColor, - onPressed: (context) async { - if (token.isLocked && await lockAuth(context: context, localizedReason: AppLocalizations.of(context)!.editLockedToken) == false) { - return; - } - _showDialog(); - }, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon(Icons.edit), - Text( - AppLocalizations.of(context)!.edit, - overflow: TextOverflow.fade, - softWrap: false, - ), - ], - )); + void handle(BuildContext context) async { + if (token.isLocked && await lockAuth(context: context, localizedReason: AppLocalizations.of(context)!.editLockedToken) == false) { + return; + } + _showDialog(); + } void _showDialog() { final tokenLabel = TextEditingController(text: token.label); diff --git a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget.dart b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget.dart index cceb41355..74d52394a 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget.dart @@ -37,7 +37,6 @@ class PushTokenWidget extends TokenWidget { token: token, tile: PushTokenWidgetTile(token), dragIcon: Icons.notifications, - editAction: EditPushTokenAction(token: token, key: Key('${token.id}editAction')), stack: [ if (!token.isRolledOut) Positioned.fill( diff --git a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget_tile.dart index 3816fc902..68b831bf7 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/push_token_widgets/push_token_widget_tile.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/push_token_widgets/actions/edit_push_token_action.dart'; import '../../../../../model/tokens/push_token.dart'; import '../token_widget_tile.dart'; @@ -11,6 +12,8 @@ class PushTokenWidgetTile extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return TokenWidgetTile( + token: token, + editAction: EditPushTokenAction(token: token), key: Key('${token.hashCode}TokenWidgetTile'), tokenIsLocked: token.isLocked, tokenImage: token.tokenImage, @@ -20,9 +23,6 @@ class PushTokenWidgetTile extends ConsumerWidget { overflow: TextOverflow.ellipsis, maxLines: 2, ), - subtitles: [ - if (token.issuer.isNotEmpty) token.issuer, - ], trailing: const Icon( Icons.notifications, size: 26, diff --git a/lib/views/main_view/main_view_widgets/token_widgets/token_action.dart b/lib/views/main_view/main_view_widgets/token_widgets/token_action.dart index 3f090f818..5c4c44f9f 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/token_action.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/token_action.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:flutter_slidable/flutter_slidable.dart'; -abstract class TokenAction extends StatelessWidget { - const TokenAction({super.key}); - @override - CustomSlidableAction build(BuildContext context); +abstract class TokenAction { + const TokenAction(); + + void handle(BuildContext context); } diff --git a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart index 444408ab6..622d21502 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_base.dart @@ -7,27 +7,16 @@ import '../../../../model/mixins/sortable_mixin.dart'; import '../../../../model/tokens/token.dart'; import '../../../../utils/riverpod_providers.dart'; import '../../../../utils/text_size.dart'; -import 'default_token_actions/default_delete_action.dart'; -import 'default_token_actions/default_edit_action.dart'; -import 'default_token_actions/default_lock_action.dart'; -import 'token_action.dart'; -import 'token_widget_slideable.dart'; class TokenWidgetBase extends ConsumerWidget { final Widget tile; final Token token; - final TokenAction? deleteAction; - final TokenAction? editAction; - final TokenAction? lockAction; final List stack; final IconData dragIcon; const TokenWidgetBase({ required this.tile, required this.token, - this.deleteAction, - this.editAction, - this.lockAction, this.stack = const [], this.dragIcon = Icons.drag_handle, super.key, @@ -36,15 +25,7 @@ class TokenWidgetBase extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final SortableMixin? draggingSortable = ref.watch(draggingSortableProvider); - final List actions = [ - deleteAction ?? DefaultDeleteAction(token: token, key: Key('${token.id}deleteAction')), - editAction ?? DefaultEditAction(token: token, key: Key('${token.id}editAction')), - ]; - if ((token.pin == false)) { - actions.add( - lockAction ?? DefaultLockAction(token: token, key: Key('${token.id}lockAction')), - ); - } + return draggingSortable == null ? LongPressDraggable( maxSimultaneousDrags: 1, @@ -76,20 +57,10 @@ class TokenWidgetBase extends ConsumerWidget { ], ), data: token, - child: TokenWidgetSlideable( - token: token, - actions: actions, - stack: stack, - tile: tile, - ), + child: tile, ) : draggingSortable == token ? const SizedBox() - : TokenWidgetSlideable( - token: token, - actions: actions, - stack: stack, - tile: tile, - ); + : tile; } } diff --git a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_slideable.dart b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_slideable.dart index 071b3fc7e..d1a640974 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_slideable.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_slideable.dart @@ -19,19 +19,10 @@ class TokenWidgetSlideable extends StatelessWidget { }); @override - Widget build(BuildContext context) => Slidable( - key: ValueKey(token.id), - groupTag: 'myTag', // This is used to only let one be open at a time. - endActionPane: ActionPane( - motion: const DrawerMotion(), - extentRatio: 1, - children: actions, - ), - child: Stack( - children: [ - tile, - for (var item in stack) item, - ], - ), + Widget build(BuildContext context) => Stack( + children: [ + tile, + for (var item in stack) item, + ], ); } diff --git a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_tile.dart index ee2e13ac9..b3ec3daa5 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/token_widget_tile.dart @@ -2,14 +2,23 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart' as http; +import 'package:privacyidea_authenticator/model/tokens/token.dart'; +import 'package:privacyidea_authenticator/views/main_view/main_view_widgets/token_widgets/token_action.dart'; +import '../../../../l10n/app_localizations.dart'; import '../../../../widgets/custom_trailing.dart'; +import 'default_token_actions/default_delete_action.dart'; +import 'default_token_actions/default_edit_action.dart'; +import 'default_token_actions/default_lock_action.dart'; final disableCopyOtpProvider = StateProvider((ref) => false); class TokenWidgetTile extends StatelessWidget { final Widget? title; - final List subtitles; + final Token token; + final TokenAction? deleteAction; + final TokenAction? editAction; + final TokenAction? lockAction; final Widget? leading; final Widget? trailing; final Function()? onTap; @@ -17,9 +26,12 @@ class TokenWidgetTile extends StatelessWidget { final String? tokenImage; const TokenWidgetTile({ + required this.token, this.leading, this.title, - this.subtitles = const [], + this.deleteAction, + this.editAction, + this.lockAction, this.trailing, this.onTap, this.tokenIsLocked = false, @@ -28,39 +40,154 @@ class TokenWidgetTile extends StatelessWidget { }); @override - Widget build(BuildContext context) => ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 8.0), - horizontalTitleGap: 8.0, - leading: (leading != null) ? leading! : null, - onTap: onTap, - title: title, - subtitle: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TokenImage(tokenImage: tokenImage), - Expanded( - child: Padding( - padding: const EdgeInsets.only(left: 4.0), - child: Column( + Widget build(BuildContext context) => Container( + margin: const EdgeInsets.only(left: 20.0, right: 20.0), + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(10), + bottomRight: Radius.circular(10)), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + spreadRadius: 5, + blurRadius: 7, + offset: const Offset(0, 3), + ), + ], + ), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).cardColor, + borderRadius: const BorderRadius.all(Radius.circular(10)), + ), + child: Column( + children: [ + Container( + decoration: BoxDecoration( + color: Theme.of(context).cardColor, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(10), + bottomRight: Radius.circular(10)), + ), + ), + Container( + color: Colors.transparent, + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).highlightColor, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(10), + topRight: Radius.circular(10)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + margin: const EdgeInsets.all(10.0), + child: Text(token.label, + style: const TextStyle(fontSize: 18))), + PopupMenuButton( + onSelected: (String choice) { + if (choice == 'delete') { + (deleteAction ?? DefaultDeleteAction(token: token)) + .handle(context); + } else if (choice == 'edit') { + (editAction ?? DefaultEditAction(token: token)) + .handle(context); + } else if (choice == 'lock') { + (lockAction ?? DefaultLockAction(token: token)) + .handle(context); + } + }, + itemBuilder: (BuildContext context) { + List> actions = []; + + actions.add(PopupMenuItem( + value: 'delete', + child: ListTile( + leading: const Icon(Icons.delete), + title: Text(AppLocalizations.of(context)!.delete), + ), + )); + + actions.add(PopupMenuItem( + value: 'edit', + child: ListTile( + leading: const Icon(Icons.edit), + title: Text(AppLocalizations.of(context)!.edit), + ), + )); + + if (token.pin == false) { + if (token.isLocked) { + actions.add(PopupMenuItem( + value: 'lock', + child: ListTile( + leading: const Icon(Icons.lock), + title: Text( + AppLocalizations.of(context)!.unlock), + ), + )); + } else { + actions.add(PopupMenuItem( + value: 'lock', + child: ListTile( + leading: const Icon(Icons.lock), + title: + Text(AppLocalizations.of(context)!.lock), + ), + )); + } + } + + return actions; + }, + icon: const Icon(Icons.more_horiz), + offset: const Offset(0, 50), + ) + ], + ), + ), + ), + ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 8.0), + horizontalTitleGap: 8.0, + leading: (leading != null) ? leading! : null, + onTap: onTap, + title: title, + subtitle: Row( crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.max, children: [ - for (var line in subtitles) - Text( - line, - style: Theme.of(context).listTileTheme.subtitleTextStyle, - textAlign: TextAlign.left, - overflow: TextOverflow.fade, - softWrap: false, + TokenImage(tokenImage: tokenImage), + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 4.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Text( + token.issuer, + style: Theme.of(context) + .listTileTheme + .subtitleTextStyle, + textAlign: TextAlign.left, + overflow: TextOverflow.fade, + softWrap: false, + ), + ], + ), ), + ), ], ), + trailing: CustomTrailing( + child: trailing ?? const SizedBox(), + ), ), - ), - ], - ), - trailing: CustomTrailing( - child: trailing ?? const SizedBox(), + ], + ), ), ); } diff --git a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/actions/edit_totp_token_action.dart b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/actions/edit_totp_token_action.dart index 5a358db20..76238d346 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/actions/edit_totp_token_action.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/actions/edit_totp_token_action.dart @@ -15,32 +15,16 @@ class EditTOTPTokenAction extends TokenAction { final TOTPToken token; const EditTOTPTokenAction({ - super.key, required this.token, }); @override - CustomSlidableAction build(BuildContext context) => CustomSlidableAction( - backgroundColor: Theme.of(context).extension()!.editColor, - foregroundColor: Theme.of(context).extension()!.foregroundColor, - onPressed: (context) async { - if (token.isLocked && await lockAuth(context: context, localizedReason: AppLocalizations.of(context)!.editLockedToken) == false) { - return; - } - _showDialog(); - }, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Icon(Icons.edit), - Text( - AppLocalizations.of(context)!.edit, - overflow: TextOverflow.fade, - softWrap: false, - ), - ], - )); + void handle(BuildContext context) async { + if (token.isLocked && await lockAuth(context: context, localizedReason: AppLocalizations.of(context)!.editLockedToken) == false) { + return; + } + _showDialog(); + } void _showDialog() { final tokenLabel = TextEditingController(text: token.label); diff --git a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget.dart b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget.dart index 20e062e1a..deb00d0b5 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget.dart @@ -4,7 +4,6 @@ import '../../../../../model/mixins/sortable_mixin.dart'; import '../../../../../model/tokens/totp_token.dart'; import '../token_widget.dart'; import '../token_widget_base.dart'; -import 'actions/edit_totp_token_action.dart'; import 'totp_token_widget_tile.dart'; class TOTPTokenWidget extends TokenWidget { @@ -25,7 +24,6 @@ class TOTPTokenWidget extends TokenWidget { token: token, tile: TOTPTokenWidgetTile(token), dragIcon: Icons.alarm, - editAction: EditTOTPTokenAction(token: token), ); } } diff --git a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart index e7cd5a9c4..08257e6e6 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/totp_token_widgets/totp_token_widget_tile.dart @@ -11,6 +11,7 @@ import '../../../../../utils/utils.dart'; import '../../../../../widgets/custom_texts.dart'; import '../../../../../widgets/hideable_widget_.dart'; import '../token_widget_tile.dart'; +import 'actions/edit_totp_token_action.dart'; class TOTPTokenWidgetTile extends ConsumerStatefulWidget { final TOTPToken token; @@ -106,6 +107,8 @@ class _TOTPTokenWidgetTileState extends ConsumerState with final appstate = ref.watch(appStateProvider); _onAppStateChange(appstate); return TokenWidgetTile( + token: widget.token, + editAction: EditTOTPTokenAction(token: widget.token), key: Key('${widget.token.hashCode}TokenWidgetTile'), tokenImage: widget.token.tokenImage, tokenIsLocked: widget.token.isLocked, @@ -128,10 +131,6 @@ class _TOTPTokenWidgetTileState extends ConsumerState with ), ), ), - subtitles: [ - if (widget.token.label.isNotEmpty) widget.token.label, - if (widget.token.issuer.isNotEmpty) widget.token.issuer, - ], trailing: HideableWidget( token: widget.token, isHiddenNotifier: isHidden,