Add view/copy CP links to client and invoice previews

This commit is contained in:
Hillel Coren 2022-06-07 09:23:49 +03:00
parent b02f913146
commit a1518eb10b
8 changed files with 113 additions and 91 deletions

View File

@ -112,7 +112,7 @@ class EntityHeader extends StatelessWidget {
), ),
Padding( Padding(
padding: statusLabel != null || !entity.isActive padding: statusLabel != null || !entity.isActive
? const EdgeInsets.only(top: 25, bottom: 5) ? const EdgeInsets.only(top: 25)
: const EdgeInsets.all(0), : const EdgeInsets.all(0),
child: Row( child: Row(
children: [ children: [

View File

@ -17,7 +17,7 @@ class AppListTile extends StatelessWidget {
this.dense = false, this.dense = false,
this.onTap, this.onTap,
this.copyValue, this.copyValue,
this.buttons, this.buttonRow,
}); });
final IconData icon; final IconData icon;
@ -26,7 +26,7 @@ class AppListTile extends StatelessWidget {
final bool dense; final bool dense;
final Function onTap; final Function onTap;
final String copyValue; final String copyValue;
final List<Widget> buttons; final Widget buttonRow;
void _onLongPress(BuildContext context) { void _onLongPress(BuildContext context) {
if ((copyValue ?? title ?? '').isEmpty) { if ((copyValue ?? title ?? '').isEmpty) {
@ -47,18 +47,16 @@ class AppListTile extends StatelessWidget {
contentPadding: EdgeInsets.symmetric(horizontal: 25, vertical: 16), contentPadding: EdgeInsets.symmetric(horizontal: 25, vertical: 16),
leading: Icon(icon), leading: Icon(icon),
title: Text(title), title: Text(title),
subtitle: buttons != null || subtitle != null subtitle: buttonRow != null || subtitle != null
? Column( ? Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (subtitle != null) Text(subtitle), if (subtitle != null) Text(subtitle),
if (buttons != null) if (buttonRow != null)
Padding( Padding(
padding: const EdgeInsets.only(top: 8), padding: const EdgeInsets.only(top: 8),
child: Row( child: buttonRow,
children: buttons,
),
) )
], ],
) )

View File

@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/client_model.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:url_launcher/url_launcher.dart';
class PortalLinks extends StatelessWidget {
const PortalLinks({
Key key,
@required this.viewLink,
@required this.copyLink,
@required this.client,
}) : super(key: key);
final String viewLink;
final String copyLink;
final ClientEntity client;
@override
Widget build(BuildContext context) {
final localization = AppLocalization.of(context);
var viewLinkWithHash = viewLink;
if (!viewLink.contains('?')) {
viewLinkWithHash += '?';
}
viewLinkWithHash += '&client_hash=${client.clientHash}';
var copyLinkWithHash = copyLink;
if (!copyLink.contains('?')) {
copyLinkWithHash += '?';
}
copyLinkWithHash += '&client_hash=${client.clientHash}';
return Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () => launch(viewLinkWithHash),
child: Text(
localization.viewPortal,
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
)),
),
SizedBox(width: 8),
Expanded(
child: OutlinedButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: copyLinkWithHash));
showToast(
localization.copiedToClipboard.replaceFirst(':value ', ''));
},
child: Text(
localization.copyLink,
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
)),
),
],
);
}
}

View File

@ -3,16 +3,14 @@ import 'dart:async';
// Flutter imports: // Flutter imports:
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
// Package imports: // Package imports:
import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/ui/app/portal_links.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
// Project imports: // Project imports:
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/app/lists/app_list_tile.dart'; import 'package:invoiceninja_flutter/ui/app/lists/app_list_tile.dart';
import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart'; import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart';
@ -61,26 +59,11 @@ class _ClientViewDetailsState extends State<ClientViewDetails> {
contacts.forEach((contact) { contacts.forEach((contact) {
listTiles.add(AppListTile( listTiles.add(AppListTile(
buttons: [ buttonRow: PortalLinks(
Expanded( viewLink: contact.silentLink,
child: OutlinedButton( copyLink: contact.link,
child: Text(localization.viewPortal.toUpperCase()), client: client,
onPressed: () { ),
launch(
'${contact.silentLink}&client_hash=${client.clientHash}');
},
)),
SizedBox(width: kTableColumnGap),
Expanded(
child: OutlinedButton(
child: Text(localization.copyLink.toUpperCase()),
onPressed: () {
Clipboard.setData(ClipboardData(text: contact.link));
showToast(
localization.copiedToClipboard.replaceFirst(':value ', ''));
},
)),
],
icon: Icons.email, icon: Icons.email,
title: contact.fullName.isEmpty title: contact.fullName.isEmpty
? localization.blankContact ? localization.blankContact

View File

@ -1,7 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:invoiceninja_flutter/constants.dart'; import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/ui/app/copy_to_clipboard.dart'; import 'package:invoiceninja_flutter/ui/app/copy_to_clipboard.dart';
@ -9,6 +7,7 @@ import 'package:invoiceninja_flutter/ui/app/entity_header.dart';
import 'package:invoiceninja_flutter/ui/app/form_card.dart'; import 'package:invoiceninja_flutter/ui/app/form_card.dart';
import 'package:invoiceninja_flutter/ui/app/forms/app_tab_bar.dart'; import 'package:invoiceninja_flutter/ui/app/forms/app_tab_bar.dart';
import 'package:invoiceninja_flutter/ui/app/icon_text.dart'; import 'package:invoiceninja_flutter/ui/app/icon_text.dart';
import 'package:invoiceninja_flutter/ui/app/portal_links.dart';
import 'package:invoiceninja_flutter/ui/client/view/client_view_activity.dart'; import 'package:invoiceninja_flutter/ui/client/view/client_view_activity.dart';
import 'package:invoiceninja_flutter/ui/client/view/client_view_documents.dart'; import 'package:invoiceninja_flutter/ui/client/view/client_view_documents.dart';
import 'package:invoiceninja_flutter/ui/client/view/client_view_ledger.dart'; import 'package:invoiceninja_flutter/ui/client/view/client_view_ledger.dart';
@ -268,37 +267,10 @@ class _ClientViewFullwidthState extends State<ClientViewFullwidth>
), ),
), ),
SizedBox(height: 8), SizedBox(height: 8),
Row( PortalLinks(
children: [ viewLink: contact.silentLink,
Flexible( copyLink: contact.link,
child: OutlinedButton( client: client,
onPressed: () => launch(
'${contact.silentLink}&client_hash=${client.clientHash}'),
child: Text(
localization.clientPortal,
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
)),
),
SizedBox(width: 4),
Flexible(
child: OutlinedButton(
onPressed: () {
final url =
'${contact.link}&client_hash=${client.clientHash}';
Clipboard.setData(ClipboardData(text: url));
showToast(localization.copiedToClipboard
.replaceFirst(':value ', ''));
},
child: Text(
localization.copyLink,
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
)),
),
],
), ),
SizedBox(height: 16), SizedBox(height: 16),
], ],

View File

@ -1,5 +1,6 @@
// Flutter imports: // Flutter imports:
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/ui/app/portal_links.dart';
// Package imports: // Package imports:
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@ -47,6 +48,7 @@ class ClientOverview extends StatelessWidget {
final statics = state.staticState; final statics = state.staticState;
final fields = <String, String>{}; final fields = <String, String>{};
final group = client.hasGroup ? state.groupState.map[client.groupId] : null; final group = client.hasGroup ? state.groupState.map[client.groupId] : null;
final contact = client.primaryContact;
final user = final user =
client.hasUser ? state.userState.get(client.assignedUserId) : null; client.hasUser ? state.userState.get(client.assignedUserId) : null;
@ -127,6 +129,15 @@ class ClientOverview extends StatelessWidget {
formatNumber(client.balance, context, clientId: client.id), formatNumber(client.balance, context, clientId: client.id),
), ),
ListDivider(), ListDivider(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
child: PortalLinks(
viewLink: contact.silentLink,
copyLink: contact.link,
client: client,
),
),
ListDivider(),
if ((client.privateNotes ?? '').isNotEmpty) ...[ if ((client.privateNotes ?? '').isNotEmpty) ...[
IconMessage(client.privateNotes, iconData: Icons.lock), IconMessage(client.privateNotes, iconData: Icons.lock),
ListDivider() ListDivider()

View File

@ -1,13 +1,10 @@
// Flutter imports: // Flutter imports:
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// Package imports: // Package imports:
import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'package:invoiceninja_flutter/ui/app/portal_links.dart';
import 'package:url_launcher/url_launcher.dart';
// Project imports: // Project imports:
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart'; import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart';
import 'package:invoiceninja_flutter/ui/invoice/view/invoice_view_vm.dart'; import 'package:invoiceninja_flutter/ui/invoice/view/invoice_view_vm.dart';
@ -115,29 +112,10 @@ class _InvitationListTile extends StatelessWidget {
), ),
), ),
SizedBox(height: 8), SizedBox(height: 8),
Row( PortalLinks(
children: [ viewLink: invitation.silentLink,
Expanded( copyLink: invitation.link,
child: OutlinedButton( client: client,
child: Text(localization.viewPortal.toUpperCase()),
onPressed: () {
launch(
'${invitation.silentLink}&client_hash=${client.clientHash}',
forceWebView: false,
forceSafariVC: false);
},
)),
SizedBox(width: kTableColumnGap),
Expanded(
child: OutlinedButton(
child: Text(localization.copyLink.toUpperCase()),
onPressed: () {
Clipboard.setData(ClipboardData(text: invitation.link));
showToast(localization.copiedToClipboard
.replaceFirst(':value ', ''));
},
)),
],
) )
], ],
), ),

View File

@ -21,6 +21,7 @@ import 'package:invoiceninja_flutter/ui/app/entity_header.dart';
import 'package:invoiceninja_flutter/ui/app/icon_message.dart'; import 'package:invoiceninja_flutter/ui/app/icon_message.dart';
import 'package:invoiceninja_flutter/ui/app/invoice/invoice_item_view.dart'; import 'package:invoiceninja_flutter/ui/app/invoice/invoice_item_view.dart';
import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart'; import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart';
import 'package:invoiceninja_flutter/ui/app/portal_links.dart';
import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart'; import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart';
import 'package:invoiceninja_flutter/ui/invoice/view/invoice_view_vm.dart'; import 'package:invoiceninja_flutter/ui/invoice/view/invoice_view_vm.dart';
import 'package:invoiceninja_flutter/utils/formatting.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart';
@ -113,6 +114,18 @@ class InvoiceOverview extends StatelessWidget {
ListDivider(), ListDivider(),
]; ];
widgets.addAll([
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
child: PortalLinks(
viewLink: invoice.invitationSilentLink,
copyLink: invoice.invitationLink,
client: client,
),
),
ListDivider(),
]);
if ((invoice.privateNotes ?? '').isNotEmpty) { if ((invoice.privateNotes ?? '').isNotEmpty) {
widgets.addAll([ widgets.addAll([
IconMessage( IconMessage(