mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-17 09:47:37 +00:00
WIP address details
This commit is contained in:
parent
172429f94a
commit
c35eaf0b0f
10 changed files with 778 additions and 183 deletions
|
@ -122,7 +122,26 @@ enum AddressType {
|
|||
cryptonote,
|
||||
mimbleWimble,
|
||||
unknown,
|
||||
nonWallet,
|
||||
nonWallet;
|
||||
|
||||
String get readableName {
|
||||
switch (this) {
|
||||
case AddressType.p2pkh:
|
||||
return "Legacy";
|
||||
case AddressType.p2sh:
|
||||
return "Wrapped segwit";
|
||||
case AddressType.p2wpkh:
|
||||
return "Segwit";
|
||||
case AddressType.cryptonote:
|
||||
return "Cryptonote";
|
||||
case AddressType.mimbleWimble:
|
||||
return "Mimble Wimble";
|
||||
case AddressType.unknown:
|
||||
return "Unknown";
|
||||
case AddressType.nonWallet:
|
||||
return "Non wallet/unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// do not modify
|
||||
|
@ -133,7 +152,26 @@ enum AddressSubType {
|
|||
paynymSend,
|
||||
paynymReceive,
|
||||
unknown,
|
||||
nonWallet,
|
||||
nonWallet;
|
||||
|
||||
String get prettyName {
|
||||
switch (this) {
|
||||
case AddressSubType.receiving:
|
||||
return "Receiving";
|
||||
case AddressSubType.change:
|
||||
return "Change";
|
||||
case AddressSubType.paynymNotification:
|
||||
return "PayNym Notification";
|
||||
case AddressSubType.paynymSend:
|
||||
return "PayNym Send";
|
||||
case AddressSubType.paynymReceive:
|
||||
return "PayNym Receiving";
|
||||
case AddressSubType.unknown:
|
||||
return "Unknown";
|
||||
case AddressSubType.nonWallet:
|
||||
return "Non wallet/unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Embedded(inheritance: false)
|
||||
|
|
|
@ -196,7 +196,11 @@ class _SingleFieldEditViewState extends State<SingleFieldEditView> {
|
|||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
),
|
||||
if (!isDesktop)
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -1,21 +1,13 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/db/main_db.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/address_qr_popup.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/edit_address_label_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/address_tag.dart';
|
||||
import 'package:stackwallet/utilities/clipboard_interface.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||
import 'package:stackwallet/widgets/icon_widgets/copy_icon.dart';
|
||||
import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
|
||||
class AddressCard extends StatefulWidget {
|
||||
|
@ -24,6 +16,7 @@ class AddressCard extends StatefulWidget {
|
|||
required this.addressId,
|
||||
required this.walletId,
|
||||
required this.coin,
|
||||
this.onPressed,
|
||||
this.clipboard = const ClipboardWrapper(),
|
||||
}) : super(key: key);
|
||||
|
||||
|
@ -31,6 +24,7 @@ class AddressCard extends StatefulWidget {
|
|||
final String walletId;
|
||||
final Coin coin;
|
||||
final ClipboardInterface clipboard;
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
@override
|
||||
State<AddressCard> createState() => _AddressCardState();
|
||||
|
@ -72,106 +66,126 @@ class _AddressCardState extends State<AddressCard> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RoundedWhiteContainer(
|
||||
onPressed: widget.onPressed,
|
||||
child: StreamBuilder<AddressLabel?>(
|
||||
stream: stream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
label = snapshot.data!;
|
||||
}
|
||||
stream: stream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
label = snapshot.data!;
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
label!.value,
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
CustomTextButton(
|
||||
text: "Edit label",
|
||||
textSize: 14,
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
EditAddressLabelView.routeName,
|
||||
arguments: label!.id,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (label!.value.isNotEmpty)
|
||||
Text(
|
||||
label!.value,
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
// Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// children: [
|
||||
//
|
||||
// CustomTextButton(
|
||||
// text: "Edit label",
|
||||
// textSize: 14,
|
||||
// onTap: () {
|
||||
// Navigator.of(context).pushNamed(
|
||||
// EditAddressLabelView.routeName,
|
||||
// arguments: label!.id,
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
if (label!.value.isNotEmpty)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SelectableText(
|
||||
address.value,
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Copy address",
|
||||
icon: CopyIcon(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextSecondary,
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SelectableText(
|
||||
address.value,
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
|
||||
if (label!.tags != null && label!.tags!.isNotEmpty)
|
||||
Wrap(
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
children: label!.tags!
|
||||
.map(
|
||||
(e) => AddressTag(
|
||||
tag: e,
|
||||
),
|
||||
onPressed: () async {
|
||||
await widget.clipboard.setData(
|
||||
ClipboardData(
|
||||
text: address.value,
|
||||
),
|
||||
);
|
||||
if (mounted) {
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.info,
|
||||
message: "Copied to clipboard",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Show QR Code",
|
||||
icon: QrCodeIcon(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextSecondary,
|
||||
),
|
||||
onPressed: () {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AddressQrPopup(
|
||||
addressString: address.value,
|
||||
coin: widget.coin,
|
||||
clipboard: widget.clipboard,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
// Row(
|
||||
// children: [
|
||||
// Expanded(
|
||||
// child: SecondaryButton(
|
||||
// label: "Copy address",
|
||||
// icon: CopyIcon(
|
||||
// color: Theme.of(context)
|
||||
// .extension<StackColors>()!
|
||||
// .buttonTextSecondary,
|
||||
// ),
|
||||
// onPressed: () async {
|
||||
// await widget.clipboard.setData(
|
||||
// ClipboardData(
|
||||
// text: address.value,
|
||||
// ),
|
||||
// );
|
||||
// if (mounted) {
|
||||
// unawaited(
|
||||
// showFloatingFlushBar(
|
||||
// type: FlushBarType.info,
|
||||
// message: "Copied to clipboard",
|
||||
// context: context,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(
|
||||
// width: 12,
|
||||
// ),
|
||||
// Expanded(
|
||||
// child: SecondaryButton(
|
||||
// label: "Show QR Code",
|
||||
// icon: QrCodeIcon(
|
||||
// color: Theme.of(context)
|
||||
// .extension<StackColors>()!
|
||||
// .buttonTextSecondary,
|
||||
// ),
|
||||
// onPressed: () {
|
||||
// showDialog<void>(
|
||||
// context: context,
|
||||
// builder: (context) => AddressQrPopup(
|
||||
// addressString: address.value,
|
||||
// coin: widget.coin,
|
||||
// clipboard: widget.clipboard,
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// )
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
346
lib/pages/receive_view/addresses/address_details_view.dart
Normal file
346
lib/pages/receive_view/addresses/address_details_view.dart
Normal file
|
@ -0,0 +1,346 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:stackwallet/db/main_db.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/address_tag.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/sub_widgets/transactions_list.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/utilities/address_utils.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/simple_edit_button.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
|
||||
class AddressDetailsView extends ConsumerStatefulWidget {
|
||||
const AddressDetailsView({
|
||||
Key? key,
|
||||
required this.addressId,
|
||||
required this.walletId,
|
||||
}) : super(key: key);
|
||||
|
||||
static const String routeName = "/addressDetailsView";
|
||||
|
||||
final Id addressId;
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
ConsumerState<AddressDetailsView> createState() => _AddressDetailsViewState();
|
||||
}
|
||||
|
||||
class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
|
||||
final _qrKey = GlobalKey();
|
||||
final isDesktop = Util.isDesktop;
|
||||
|
||||
late Stream<AddressLabel?> stream;
|
||||
late final Address address;
|
||||
|
||||
AddressLabel? label;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
address = MainDB.instance.isar.addresses
|
||||
.where()
|
||||
.idEqualTo(widget.addressId)
|
||||
.findFirstSync()!;
|
||||
|
||||
label = MainDB.instance.getAddressLabelSync(widget.walletId, address.value);
|
||||
Id? id = label?.id;
|
||||
if (id == null) {
|
||||
label = AddressLabel(
|
||||
walletId: widget.walletId,
|
||||
addressString: address.value,
|
||||
value: "",
|
||||
tags: address.subType == AddressSubType.receiving
|
||||
? ["receiving"]
|
||||
: address.subType == AddressSubType.change
|
||||
? ["change"]
|
||||
: null,
|
||||
);
|
||||
id = MainDB.instance.putAddressLabelSync(label!);
|
||||
}
|
||||
stream = MainDB.instance.watchAddressLabel(id: id);
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final coin = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(widget.walletId).coin));
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) => Background(
|
||||
child: Scaffold(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.backgroundAppBar,
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
titleSpacing: 0,
|
||||
title: Text(
|
||||
"Wallet addresses",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: NestedScrollView(
|
||||
floatHeaderSlivers: true,
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) {
|
||||
return [
|
||||
SliverOverlapAbsorber(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
|
||||
context),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
top: 16,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
)
|
||||
];
|
||||
},
|
||||
body: TransactionsList(
|
||||
walletId: widget.walletId,
|
||||
managerProvider: ref.watch(
|
||||
walletsChangeNotifierProvider.select((value) =>
|
||||
value.getManagerProvider(widget.walletId)))),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: StreamBuilder<AddressLabel?>(
|
||||
stream: stream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
label = snapshot.data!;
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Center(
|
||||
child: RepaintBoundary(
|
||||
key: _qrKey,
|
||||
child: QrImage(
|
||||
data: AddressUtils.buildUriString(
|
||||
coin,
|
||||
address.value,
|
||||
{},
|
||||
),
|
||||
size: 220,
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
foregroundColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
_Item(
|
||||
title: "Address",
|
||||
data: address.value,
|
||||
button: SimpleCopyButton(
|
||||
data: address.value,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
_Item(
|
||||
title: "Label",
|
||||
data: label!.value,
|
||||
button: SimpleEditButton(
|
||||
editValue: label!.value,
|
||||
editLabel: 'label',
|
||||
onValueChanged: (value) {
|
||||
MainDB.instance.putAddressLabel(
|
||||
label!.copyWith(
|
||||
label: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
_Tags(
|
||||
tags: label!.tags,
|
||||
),
|
||||
if (address.derivationPath != null)
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
if (address.derivationPath != null)
|
||||
_Item(
|
||||
title: "Derivation path",
|
||||
data: address.derivationPath!.value,
|
||||
button: SimpleEditButton(
|
||||
editValue: label!.value,
|
||||
editLabel: 'label',
|
||||
onValueChanged: (value) {
|
||||
MainDB.instance.putAddressLabel(
|
||||
label!.copyWith(
|
||||
label: value,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
_Item(
|
||||
title: "Type",
|
||||
data: address.type.readableName,
|
||||
button: Container(),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
_Item(
|
||||
title: "Sub type",
|
||||
data: address.subType.prettyName,
|
||||
button: Container(),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text(
|
||||
"Transactions",
|
||||
style: STextStyles.itemSubtitle(context).copyWith(
|
||||
color: Theme.of(context).extension<StackColors>()!.textDark3,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Tags extends StatelessWidget {
|
||||
const _Tags({
|
||||
Key? key,
|
||||
required this.tags,
|
||||
}) : super(key: key);
|
||||
|
||||
final List<String>? tags;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RoundedWhiteContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Tags",
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
SimpleEditButton(
|
||||
onPressedOverride: () {
|
||||
// TODO edit tags
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
tags != null && tags!.isNotEmpty
|
||||
? Wrap(
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
children: tags!
|
||||
.map(
|
||||
(e) => AddressTag(
|
||||
tag: e,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
)
|
||||
: Text(
|
||||
"Tags will appear here",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle3,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Item extends StatelessWidget {
|
||||
const _Item({
|
||||
Key? key,
|
||||
required this.title,
|
||||
required this.data,
|
||||
required this.button,
|
||||
}) : super(key: key);
|
||||
|
||||
final String title;
|
||||
final String data;
|
||||
final Widget button;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RoundedWhiteContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
button,
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
data.isNotEmpty
|
||||
? SelectableText(
|
||||
data,
|
||||
style: STextStyles.w500_14(context),
|
||||
)
|
||||
: Text(
|
||||
"$title will appear here",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle3,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
26
lib/pages/receive_view/addresses/address_tag.dart
Normal file
26
lib/pages/receive_view/addresses/address_tag.dart
Normal file
|
@ -0,0 +1,26 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_native_splash/cli_commands.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/widgets/rounded_container.dart';
|
||||
|
||||
class AddressTag extends StatelessWidget {
|
||||
const AddressTag({Key? key, required this.tag}) : super(key: key);
|
||||
|
||||
final String tag;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RoundedContainer(
|
||||
radiusMultiplier: 0.5,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 5,
|
||||
horizontal: 7,
|
||||
),
|
||||
color: Colors.black,
|
||||
child: Text(
|
||||
tag.capitalize(),
|
||||
style: STextStyles.w500_14(context),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/db/main_db.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/address_card.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/address_details_view.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
|
@ -13,7 +15,12 @@ import 'package:stackwallet/widgets/background.dart';
|
|||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/loading_indicator.dart';
|
||||
import 'package:stackwallet/widgets/toggle.dart';
|
||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../../../utilities/assets.dart';
|
||||
import '../../../widgets/icon_widgets/x_icon.dart';
|
||||
import '../../../widgets/textfield_icon_button.dart';
|
||||
|
||||
class WalletAddressesView extends ConsumerStatefulWidget {
|
||||
const WalletAddressesView({
|
||||
|
@ -33,7 +40,88 @@ class WalletAddressesView extends ConsumerStatefulWidget {
|
|||
class _WalletAddressesViewState extends ConsumerState<WalletAddressesView> {
|
||||
final bool isDesktop = Util.isDesktop;
|
||||
|
||||
bool _showChange = false;
|
||||
String _searchString = "";
|
||||
|
||||
late final TextEditingController _searchController;
|
||||
final searchFieldFocusNode = FocusNode();
|
||||
|
||||
Future<List<int>> _search(String term) async {
|
||||
if (term.isEmpty) {
|
||||
return MainDB.instance
|
||||
.getAddresses(widget.walletId)
|
||||
.filter()
|
||||
.group((q) => q
|
||||
.subTypeEqualTo(AddressSubType.change)
|
||||
.or()
|
||||
.subTypeEqualTo(AddressSubType.receiving)
|
||||
.or()
|
||||
.subTypeEqualTo(AddressSubType.paynymReceive)
|
||||
.or()
|
||||
.subTypeEqualTo(AddressSubType.paynymNotification))
|
||||
.and()
|
||||
.not()
|
||||
.typeEqualTo(AddressType.nonWallet)
|
||||
.sortByDerivationIndex()
|
||||
.idProperty()
|
||||
.findAll();
|
||||
}
|
||||
|
||||
final labels = await MainDB.instance
|
||||
.getAddressLabels(widget.walletId)
|
||||
.filter()
|
||||
.group(
|
||||
(q) => q
|
||||
.valueContains(term, caseSensitive: false)
|
||||
.or()
|
||||
.addressStringContains(term, caseSensitive: false)
|
||||
.or()
|
||||
.group(
|
||||
(q) => q
|
||||
.tagsIsNotNull()
|
||||
.and()
|
||||
.tagsElementContains(term, caseSensitive: false),
|
||||
),
|
||||
)
|
||||
.findAll();
|
||||
|
||||
if (labels.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return MainDB.instance
|
||||
.getAddresses(widget.walletId)
|
||||
.filter()
|
||||
.anyOf<AddressLabel, Address>(
|
||||
labels, (q, e) => q.valueEqualTo(e.addressString))
|
||||
.group((q) => q
|
||||
.subTypeEqualTo(AddressSubType.change)
|
||||
.or()
|
||||
.subTypeEqualTo(AddressSubType.receiving)
|
||||
.or()
|
||||
.subTypeEqualTo(AddressSubType.paynymReceive)
|
||||
.or()
|
||||
.subTypeEqualTo(AddressSubType.paynymNotification))
|
||||
.and()
|
||||
.not()
|
||||
.typeEqualTo(AddressType.nonWallet)
|
||||
.sortByDerivationIndex()
|
||||
.idProperty()
|
||||
.findAll();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_searchController = TextEditingController();
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchController.dispose();
|
||||
searchFieldFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -68,26 +156,66 @@ class _WalletAddressesViewState extends ConsumerState<WalletAddressesView> {
|
|||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: isDesktop ? 56 : 48,
|
||||
width: isDesktop ? 490 : null,
|
||||
child: Toggle(
|
||||
key: UniqueKey(),
|
||||
onColor: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
onText: "Receiving",
|
||||
offColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
offText: "Change",
|
||||
isOn: _showChange,
|
||||
onValueChanged: (value) {
|
||||
setState(() {
|
||||
_showChange = value;
|
||||
});
|
||||
},
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
autocorrect: !isDesktop,
|
||||
enableSuggestions: !isDesktop,
|
||||
controller: _searchController,
|
||||
focusNode: searchFieldFocusNode,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_searchString = value;
|
||||
});
|
||||
},
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveText,
|
||||
height: 1.8,
|
||||
)
|
||||
: STextStyles.field(context),
|
||||
decoration: standardInputDecoration(
|
||||
"Search...",
|
||||
searchFieldFocusNode,
|
||||
context,
|
||||
desktopMed: isDesktop,
|
||||
).copyWith(
|
||||
prefixIcon: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: isDesktop ? 12 : 10,
|
||||
vertical: isDesktop ? 18 : 16,
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.search,
|
||||
width: isDesktop ? 20 : 16,
|
||||
height: isDesktop ? 20 : 16,
|
||||
),
|
||||
),
|
||||
suffixIcon: _searchController.text.isNotEmpty
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(right: 0),
|
||||
child: UnconstrainedBox(
|
||||
child: Row(
|
||||
children: [
|
||||
TextFieldIconButton(
|
||||
child: const XIcon(),
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
_searchController.text = "";
|
||||
_searchString = "";
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -97,23 +225,7 @@ class _WalletAddressesViewState extends ConsumerState<WalletAddressesView> {
|
|||
),
|
||||
Expanded(
|
||||
child: FutureBuilder(
|
||||
future: MainDB.instance
|
||||
.getAddresses(widget.walletId)
|
||||
.filter()
|
||||
.group(
|
||||
(q) => _showChange
|
||||
? q.subTypeEqualTo(AddressSubType.change)
|
||||
: q
|
||||
.subTypeEqualTo(AddressSubType.receiving)
|
||||
.or()
|
||||
.subTypeEqualTo(AddressSubType.paynymReceive),
|
||||
)
|
||||
.and()
|
||||
.not()
|
||||
.typeEqualTo(AddressType.nonWallet)
|
||||
.sortByDerivationIndex()
|
||||
.idProperty()
|
||||
.findAll(),
|
||||
future: _search(_searchString),
|
||||
builder: (context, AsyncSnapshot<List<int>> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done &&
|
||||
snapshot.data != null) {
|
||||
|
@ -127,6 +239,15 @@ class _WalletAddressesViewState extends ConsumerState<WalletAddressesView> {
|
|||
walletId: widget.walletId,
|
||||
addressId: snapshot.data![index],
|
||||
coin: coin,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
AddressDetailsView.routeName,
|
||||
arguments: Tuple2(
|
||||
snapshot.data![index],
|
||||
widget.walletId,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -48,6 +48,7 @@ import 'package:stackwallet/pages/paynym/add_new_paynym_follow_view.dart';
|
|||
import 'package:stackwallet/pages/paynym/paynym_claim_view.dart';
|
||||
import 'package:stackwallet/pages/paynym/paynym_home_view.dart';
|
||||
import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/address_details_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/edit_address_label_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/wallet_addresses_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart';
|
||||
|
@ -928,6 +929,21 @@ class RouteGenerator {
|
|||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case AddressDetailsView.routeName:
|
||||
if (args is Tuple2<Id, String>) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => AddressDetailsView(
|
||||
walletId: args.item2,
|
||||
addressId: args.item1,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case SendView.routeName:
|
||||
if (args is Tuple2<String, Coin>) {
|
||||
return getRoute(
|
||||
|
|
|
@ -221,11 +221,27 @@ class DbVersionMigrator with WalletDB {
|
|||
|
||||
final List<isar_models.AddressLabel> labels = [];
|
||||
for (final address in addresses) {
|
||||
final tags = address.subType == AddressSubType.receiving
|
||||
? ["receiving"]
|
||||
: address.subType == AddressSubType.change
|
||||
? ["change"]
|
||||
: null;
|
||||
List<String>? tags;
|
||||
switch (address.subType) {
|
||||
case AddressSubType.receiving:
|
||||
tags = ["receiving"];
|
||||
break;
|
||||
case AddressSubType.change:
|
||||
tags = ["change"];
|
||||
break;
|
||||
case AddressSubType.paynymNotification:
|
||||
tags = ["paynym notification"];
|
||||
break;
|
||||
case AddressSubType.paynymSend:
|
||||
break;
|
||||
case AddressSubType.paynymReceive:
|
||||
tags = ["paynym receiving"];
|
||||
break;
|
||||
case AddressSubType.unknown:
|
||||
break;
|
||||
case AddressSubType.nonWallet:
|
||||
break;
|
||||
}
|
||||
|
||||
// update/create label if tags is not empty
|
||||
if (tags != null) {
|
||||
|
|
|
@ -13,14 +13,23 @@ import '../icon_widgets/pencil_icon.dart';
|
|||
class SimpleEditButton extends StatelessWidget {
|
||||
const SimpleEditButton({
|
||||
Key? key,
|
||||
required this.editValue,
|
||||
required this.editLabel,
|
||||
required this.onValueChanged,
|
||||
}) : super(key: key);
|
||||
this.editValue,
|
||||
this.editLabel,
|
||||
this.onValueChanged,
|
||||
this.onPressedOverride,
|
||||
}) : assert(
|
||||
(editLabel != null && editValue != null && onValueChanged != null) ||
|
||||
(editLabel == null &&
|
||||
editValue == null &&
|
||||
onValueChanged == null &&
|
||||
onPressedOverride != null),
|
||||
),
|
||||
super(key: key);
|
||||
|
||||
final String editValue;
|
||||
final String editLabel;
|
||||
final void Function(String) onValueChanged;
|
||||
final String? editValue;
|
||||
final String? editLabel;
|
||||
final void Function(String)? onValueChanged;
|
||||
final VoidCallback? onPressedOverride;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -36,24 +45,25 @@ class SimpleEditButton extends StatelessWidget {
|
|||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
onPressed: () async {
|
||||
final result = await showDialog<String?>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return DesktopDialog(
|
||||
maxWidth: 580,
|
||||
maxHeight: 360,
|
||||
child: SingleFieldEditView(
|
||||
initialValue: editValue,
|
||||
label: editLabel,
|
||||
),
|
||||
onPressed: onPressedOverride ??
|
||||
() async {
|
||||
final result = await showDialog<String?>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return DesktopDialog(
|
||||
maxWidth: 580,
|
||||
maxHeight: 360,
|
||||
child: SingleFieldEditView(
|
||||
initialValue: editValue!,
|
||||
label: editLabel!,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result is String && result != editValue!) {
|
||||
onValueChanged?.call(result);
|
||||
}
|
||||
},
|
||||
);
|
||||
if (result is String && result != editValue) {
|
||||
onValueChanged(result);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: PencilIcon(
|
||||
|
@ -66,18 +76,19 @@ class SimpleEditButton extends StatelessWidget {
|
|||
);
|
||||
} else {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
final result = await Navigator.of(context).pushNamed(
|
||||
SingleFieldEditView.routeName,
|
||||
arguments: Tuple2(
|
||||
editValue,
|
||||
editLabel,
|
||||
),
|
||||
);
|
||||
if (result is String && result != editValue) {
|
||||
onValueChanged(result);
|
||||
}
|
||||
},
|
||||
onTap: onPressedOverride ??
|
||||
() async {
|
||||
final result = await Navigator.of(context).pushNamed(
|
||||
SingleFieldEditView.routeName,
|
||||
arguments: Tuple2(
|
||||
editValue!,
|
||||
editLabel!,
|
||||
),
|
||||
);
|
||||
if (result is String && result != editValue!) {
|
||||
onValueChanged?.call(result);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
|
|
|
@ -12,6 +12,7 @@ class RoundedWhiteContainer extends StatelessWidget {
|
|||
this.height,
|
||||
this.borderColor,
|
||||
this.boxShadow,
|
||||
this.onPressed,
|
||||
}) : super(key: key);
|
||||
|
||||
final Widget? child;
|
||||
|
@ -21,6 +22,7 @@ class RoundedWhiteContainer extends StatelessWidget {
|
|||
final double? height;
|
||||
final Color? borderColor;
|
||||
final List<BoxShadow>? boxShadow;
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -32,6 +34,7 @@ class RoundedWhiteContainer extends StatelessWidget {
|
|||
height: height,
|
||||
borderColor: borderColor,
|
||||
boxShadow: boxShadow,
|
||||
onPressed: onPressed,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue