stack_wallet/lib/pages/wallets_view/wallets_overview.dart

386 lines
13 KiB
Dart
Raw Normal View History

2023-05-26 21:21:16 +00:00
/*
* This file is part of Stack Wallet.
*
* Copyright (c) 2023 Cypher Stack
* All Rights Reserved.
* The code is distributed under GPLv3 license, see LICENSE file for details.
* Generated by Cypher Stack on 2023-05-26
*
*/
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:isar/isar.dart';
2024-05-27 23:56:22 +00:00
import '../../app_config.dart';
import '../../models/add_wallet_list_entity/sub_classes/coin_entity.dart';
import '../../models/isar/models/ethereum/eth_contract.dart';
import '../../pages_desktop_specific/my_stack_view/dialogs/desktop_expanding_wallet_card.dart';
import '../../providers/db/main_db_provider.dart';
import '../../providers/providers.dart';
import '../../services/event_bus/events/wallet_added_event.dart';
import '../../services/event_bus/global_event_bus.dart';
import '../../themes/stack_colors.dart';
import '../../utilities/assets.dart';
import '../../utilities/constants.dart';
import '../../utilities/text_styles.dart';
import '../../utilities/util.dart';
import '../../wallets/crypto_currency/crypto_currency.dart';
import '../../wallets/isar/models/wallet_info.dart';
import '../../wallets/isar/providers/wallet_info_provider.dart';
import '../../wallets/wallet/wallet.dart';
import '../../widgets/background.dart';
import '../../widgets/conditional_parent.dart';
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../widgets/icon_widgets/x_icon.dart';
import '../../widgets/master_wallet_card.dart';
import '../../widgets/rounded_white_container.dart';
import '../../widgets/stack_text_field.dart';
import '../../widgets/textfield_icon_button.dart';
import '../../widgets/wallet_card.dart';
2024-05-27 23:56:22 +00:00
import '../add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart';
2023-04-10 15:12:07 +00:00
class WalletsOverview extends ConsumerStatefulWidget {
const WalletsOverview({
2024-05-15 21:20:45 +00:00
super.key,
2023-04-10 15:12:07 +00:00
required this.coin,
this.navigatorState,
this.overrideSimpleWalletCardPopPreviousValueWith,
2024-05-15 21:20:45 +00:00
});
2023-04-10 15:12:07 +00:00
2024-05-15 21:20:45 +00:00
final CryptoCurrency coin;
final NavigatorState? navigatorState;
final bool? overrideSimpleWalletCardPopPreviousValueWith;
static const routeName = "/walletsOverview";
@override
2023-04-10 15:12:07 +00:00
ConsumerState<WalletsOverview> createState() => _EthWalletsOverviewState();
}
typedef WalletListItemData = ({Wallet wallet, List<EthContract> contracts});
2023-04-10 15:12:07 +00:00
class _EthWalletsOverviewState extends ConsumerState<WalletsOverview> {
final isDesktop = Util.isDesktop;
late final TextEditingController _searchController;
late final FocusNode searchFieldFocusNode;
String _searchString = "";
final Map<String, WalletListItemData> wallets = {};
List<WalletListItemData> _filter(String searchTerm) {
// clean out deleted wallets
final existingWalletIds = ref
.read(mainDBProvider)
.isar
.walletInfo
.where()
.walletIdProperty()
.findAllSync();
wallets.removeWhere((k, v) => !existingWalletIds.contains(k));
if (searchTerm.isEmpty) {
return wallets.values.toList()
..sort((a, b) => a.wallet.info.name.compareTo(b.wallet.info.name));
}
final Map<String, WalletListItemData> results = {};
final term = searchTerm.toLowerCase();
for (final entry in wallets.entries) {
bool includeManager = false;
// search wallet name and total balance
includeManager |= _elementContains(entry.value.wallet.info.name, term);
includeManager |= _elementContains(
entry.value.wallet.info.cachedBalance.total.decimal.toString(),
term,
);
final List<EthContract> contracts = [];
for (final contract in entry.value.contracts) {
if (_elementContains(contract.name, term)) {
contracts.add(contract);
} else if (_elementContains(contract.symbol, term)) {
contracts.add(contract);
} else if (_elementContains(contract.type.name, term)) {
contracts.add(contract);
} else if (_elementContains(contract.address, term)) {
contracts.add(contract);
}
}
if (includeManager || contracts.isNotEmpty) {
results.addEntries([entry]);
}
}
return results.values.toList()
..sort((a, b) => a.wallet.info.name.compareTo(b.wallet.info.name));
}
bool _elementContains(String element, String term) {
return element.toLowerCase().contains(term);
}
void updateWallets() {
final walletsData =
ref.read(mainDBProvider).isar.walletInfo.where().findAllSync();
walletsData.removeWhere((e) => e.coin != widget.coin);
2024-05-15 21:20:45 +00:00
if (widget.coin is Ethereum) {
for (final data in walletsData) {
final List<EthContract> contracts = [];
final contractAddresses =
ref.read(pWalletTokenAddresses(data.walletId));
// fetch each contract
for (final contractAddress in contractAddresses) {
final contract = ref
.read(
mainDBProvider,
)
.getEthContractSync(
contractAddress,
);
// add it to list if it exists in DB
if (contract != null) {
contracts.add(contract);
}
}
// add tuple to list
wallets[data.walletId] = (
wallet: ref.read(pWallets).getWallet(
data.walletId,
),
contracts: contracts,
);
}
} else {
// add non token wallet tuple to list
for (final data in walletsData) {
// desktop single coin apps may cause issues so lets just ignore the error and move on
try {
wallets[data.walletId] = (
wallet: ref.read(pWallets).getWallet(
data.walletId,
),
contracts: [],
);
} catch (_) {
// lol bandaid for single coin based apps
}
}
}
}
@override
void initState() {
_searchController = TextEditingController();
searchFieldFocusNode = FocusNode();
updateWallets();
if (AppConfig.isSingleCoinApp) {
GlobalEventBus.instance.on<WalletAddedEvent>().listen((_) {
updateWallets();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {});
}
});
});
}
super.initState();
}
@override
void dispose() {
_searchController.dispose();
searchFieldFocusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ConditionalParent(
condition: !isDesktop && !AppConfig.isSingleCoinApp,
builder: (child) => Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: const AppBarBackButton(),
title: Text(
2023-04-10 15:12:07 +00:00
"${widget.coin.prettyName} (${widget.coin.ticker}) wallets",
style: STextStyles.navBarTitle(context),
),
actions: [
AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
icon: SvgPicture.asset(
Assets.svg.plus,
width: 18,
height: 18,
color: Theme.of(context)
.extension<StackColors>()!
.topNavIconPrimary,
),
onPressed: () {
Navigator.of(context).pushNamed(
CreateOrRestoreWalletView.routeName,
arguments: CoinEntity(widget.coin),
);
},
),
),
],
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16),
child: child,
),
),
),
),
child: Column(
children: [
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,
),
2023-04-10 15:12:07 +00:00
),
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,
),
),
),
const SizedBox(
height: 16,
),
Expanded(
child: Builder(
builder: (context) {
final data = _filter(_searchString);
return ListView.separated(
itemBuilder: (_, index) {
final entry = data[index];
final wallet = entry.wallet;
if (wallet.cryptoCurrency.hasTokenSupport) {
if (isDesktop) {
return DesktopExpandingWalletCard(
key: Key(
"${wallet.walletId}_${entry.contracts.map((e) => e.address).join()}",
2024-05-27 23:56:22 +00:00
),
data: entry,
navigatorState: widget.navigatorState!,
);
} else {
return MasterWalletCard(
key: Key(wallet.walletId),
walletId: wallet.walletId,
);
}
} else {
return ConditionalParent(
key: Key(wallet.walletId),
condition: isDesktop,
builder: (child) => RoundedWhiteContainer(
padding: const EdgeInsets.symmetric(
vertical: 14,
horizontal: 20,
),
borderColor: Theme.of(context)
.extension<StackColors>()!
.backgroundAppBar,
child: child,
),
child: SimpleWalletCard(
walletId: wallet.walletId,
popPrevious: widget
.overrideSimpleWalletCardPopPreviousValueWith ==
null
? isDesktop
: widget
.overrideSimpleWalletCardPopPreviousValueWith!,
desktopNavigatorState:
isDesktop ? widget.navigatorState : null,
),
);
}
},
separatorBuilder: (_, __) => SizedBox(
height: isDesktop ? 10 : 8,
),
itemCount: data.length,
);
},
),
),
],
),
);
}
}