diff --git a/lib/pages_desktop_specific/addresses/desktop_wallet_addresses_view.dart b/lib/pages_desktop_specific/addresses/desktop_wallet_addresses_view.dart new file mode 100644 index 000000000..bf02e4b6a --- /dev/null +++ b/lib/pages_desktop_specific/addresses/desktop_wallet_addresses_view.dart @@ -0,0 +1,151 @@ +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_details_view.dart'; +import 'package:stackwallet/pages_desktop_specific/addresses/sub_widgets/desktop_address_list.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; + +final desktopSelectedAddressId = StateProvider.autoDispose((ref) => null); + +class DesktopWalletAddressesView extends ConsumerStatefulWidget { + const DesktopWalletAddressesView({ + Key? key, + required this.walletId, + }) : super(key: key); + + static const String routeName = "/desktopWalletAddressesView"; + + final String walletId; + + @override + ConsumerState createState() => + _DesktopWalletAddressesViewState(); +} + +class _DesktopWalletAddressesViewState + extends ConsumerState { + static const _headerHeight = 70.0; + static const _columnWidth0 = 489.0; + + late final Stream addressCollectionWatcher; + + void _onAddressCollectionWatcherEvent() { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() {}); + }); + } + + @override + void initState() { + addressCollectionWatcher = + MainDB.instance.isar.addresses.watchLazy(fireImmediately: true); + addressCollectionWatcher.listen((_) => _onAddressCollectionWatcherEvent()); + + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final manager = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(widget.walletId))); + + return DesktopScaffold( + appBar: DesktopAppBar( + background: Theme.of(context).extension()!.popupBG, + leading: Expanded( + child: Row( + children: [ + const SizedBox( + width: 32, + ), + AppBarIconButton( + size: 32, + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + shadows: const [], + icon: SvgPicture.asset( + Assets.svg.arrowLeft, + width: 18, + height: 18, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: Navigator.of(context).pop, + ), + const SizedBox( + width: 12, + ), + Text( + "Address list", + style: STextStyles.desktopH3(context), + ), + const Spacer(), + ], + ), + ), + useSpacers: false, + isCompactHeight: true, + ), + body: Padding( + padding: const EdgeInsets.all(24), + child: Column( + children: [ + Expanded( + child: Row( + children: [ + SizedBox( + width: _columnWidth0, + child: DesktopAddressList( + searchHeight: _headerHeight, + walletId: widget.walletId, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: Column( + children: [ + const SizedBox( + height: _headerHeight, + ), + if (ref.watch(desktopSelectedAddressId.state).state != + null) + Expanded( + child: AddressDetailsView( + key: Key( + "currentDesktopAddressDetails_key_${ref.watch(desktopSelectedAddressId.state).state}"), + walletId: widget.walletId, + addressId: ref + .watch(desktopSelectedAddressId.state) + .state!, + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages_desktop_specific/addresses/sub_widgets/desktop_address_list.dart b/lib/pages_desktop_specific/addresses/sub_widgets/desktop_address_list.dart new file mode 100644 index 000000000..6e0cf3794 --- /dev/null +++ b/lib/pages_desktop_specific/addresses/sub_widgets/desktop_address_list.dart @@ -0,0 +1,219 @@ +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_desktop_specific/addresses/desktop_wallet_addresses_view.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.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/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; + +class DesktopAddressList extends ConsumerStatefulWidget { + const DesktopAddressList({ + Key? key, + required this.walletId, + this.searchHeight, + }) : super(key: key); + + final String walletId; + final double? searchHeight; + + @override + ConsumerState createState() => _DesktopAddressListState(); +} + +class _DesktopAddressListState extends ConsumerState { + final bool isDesktop = Util.isDesktop; + + String _searchString = ""; + + late final TextEditingController _searchController; + final searchFieldFocusNode = FocusNode(); + + List _search(String term) { + 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() + .findAllSync(); + } + + final labels = 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), + ), + ) + .findAllSync(); + + if (labels.isEmpty) { + return []; + } + + return MainDB.instance + .getAddresses(widget.walletId) + .filter() + .anyOf( + 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() + .findAllSync(); + } + + @override + void initState() { + _searchController = TextEditingController(); + + super.initState(); + } + + @override + void dispose() { + _searchController.dispose(); + searchFieldFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final coin = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(widget.walletId).coin)); + + final ids = _search(_searchString); + + return Column( + children: [ + SizedBox( + height: widget.searchHeight!, + child: Center( + 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()! + .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, + ), + ), + ), + ), + ), + SizedBox( + height: isDesktop ? 20 : 16, + ), + Expanded( + child: ListView.separated( + shrinkWrap: true, + itemCount: ids.length, + separatorBuilder: (_, __) => Container( + height: 10, + ), + itemBuilder: (_, index) => AddressCard( + key: Key("addressCardDesktop_key_${ids[index]}"), + walletId: widget.walletId, + addressId: ids[index], + coin: coin, + onPressed: () { + ref.read(desktopSelectedAddressId.state).state = ids[index]; + }, + ), + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart index e4be9b030..f91448dda 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart @@ -1,6 +1,9 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/pages_desktop_specific/addresses/desktop_wallet_addresses_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/utilities/assets.dart'; @@ -74,8 +77,12 @@ class _WalletOptionsButtonState extends ConsumerState { if (mounted && func != null) { switch (func) { case _WalletOptions.addressList: - print("Address list pressed"); - // TODO: Handle this case. + unawaited( + Navigator.of(context).pushNamed( + DesktopWalletAddressesView.routeName, + arguments: walletId, + ), + ); break; case _WalletOptions.deleteWallet: final result = await showDialog( diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 524639e54..5d5599902 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -99,6 +99,7 @@ import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_sear import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; import 'package:stackwallet/pages/wallets_view/wallets_view.dart'; import 'package:stackwallet/pages_desktop_specific/address_book_view/desktop_address_book.dart'; +import 'package:stackwallet/pages_desktop_specific/addresses/desktop_wallet_addresses_view.dart'; import 'package:stackwallet/pages_desktop_specific/coin_control/desktop_coin_control_view.dart'; // import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_all_buys_view.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_buy/desktop_buy_view.dart'; @@ -1320,6 +1321,20 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case DesktopWalletAddressesView.routeName: + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => DesktopWalletAddressesView( + walletId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case DesktopCoinControlView.routeName: if (args is String) { return getRoute(