Skip to content

Commit 4030002

Browse files
committed
ui(spl): desktop sol token wallet nav
1 parent 75895a2 commit 4030002

File tree

5 files changed

+466
-61
lines changed

5 files changed

+466
-61
lines changed

lib/pages/wallets_view/wallets_overview.dart

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,25 @@
88
*
99
*/
1010

11+
import 'dart:async';
12+
1113
import 'package:flutter/material.dart';
1214
import 'package:flutter_riverpod/flutter_riverpod.dart';
1315
import 'package:flutter_svg/svg.dart';
1416
import 'package:isar_community/isar.dart';
1517

1618
import '../../app_config.dart';
1719
import '../../models/add_wallet_list_entity/sub_classes/coin_entity.dart';
18-
import '../../models/isar/models/ethereum/eth_contract.dart';
20+
import '../../models/isar/models/contract.dart';
21+
import '../../pages_desktop_specific/my_stack_view/dialogs/desktop_expanding_solana_wallet_card.dart';
1922
import '../../pages_desktop_specific/my_stack_view/dialogs/desktop_expanding_wallet_card.dart';
2023
import '../../providers/providers.dart';
2124
import '../../services/event_bus/events/wallet_added_event.dart';
2225
import '../../services/event_bus/global_event_bus.dart';
2326
import '../../themes/stack_colors.dart';
2427
import '../../utilities/assets.dart';
2528
import '../../utilities/constants.dart';
29+
import '../../utilities/default_spl_tokens.dart';
2630
import '../../utilities/text_styles.dart';
2731
import '../../utilities/util.dart';
2832
import '../../wallets/crypto_currency/crypto_currency.dart';
@@ -59,7 +63,7 @@ class WalletsOverview extends ConsumerStatefulWidget {
5963
ConsumerState<WalletsOverview> createState() => _EthWalletsOverviewState();
6064
}
6165

62-
typedef WalletListItemData = ({Wallet wallet, List<EthContract> contracts});
66+
typedef WalletListItemData = ({Wallet wallet, List<Contract> contracts});
6367

6468
class _EthWalletsOverviewState extends ConsumerState<WalletsOverview> {
6569
final isDesktop = Util.isDesktop;
@@ -99,15 +103,13 @@ class _EthWalletsOverviewState extends ConsumerState<WalletsOverview> {
99103
term,
100104
);
101105

102-
final List<EthContract> contracts = [];
106+
final List<Contract> contracts = [];
103107

104108
for (final contract in entry.value.contracts) {
105109
if (_elementContains(contract.name, term)) {
106110
contracts.add(contract);
107111
} else if (_elementContains(contract.symbol, term)) {
108112
contracts.add(contract);
109-
} else if (_elementContains(contract.type.name, term)) {
110-
contracts.add(contract);
111113
} else if (_elementContains(contract.address, term)) {
112114
contracts.add(contract);
113115
}
@@ -133,7 +135,7 @@ class _EthWalletsOverviewState extends ConsumerState<WalletsOverview> {
133135

134136
if (widget.coin is Ethereum) {
135137
for (final data in walletsData) {
136-
final List<EthContract> contracts = [];
138+
final List<Contract> contracts = [];
137139
final contractAddresses = ref.read(
138140
pWalletTokenAddresses(data.walletId),
139141
);
@@ -150,6 +152,51 @@ class _EthWalletsOverviewState extends ConsumerState<WalletsOverview> {
150152
}
151153
}
152154

155+
// add tuple to list
156+
wallets[data.walletId] = (
157+
wallet: ref.read(pWallets).getWallet(data.walletId),
158+
contracts: contracts,
159+
);
160+
}
161+
} else if (widget.coin is Solana) {
162+
// Ensure default Solana tokens are loaded into database.
163+
final dbProvider = ref.read(mainDBProvider);
164+
for (final defaultToken in DefaultSplTokens.list) {
165+
final existingToken = dbProvider.getSplTokenSync(defaultToken.address);
166+
if (existingToken == null) {
167+
// Token not in database, add it asynchronously.
168+
unawaited(dbProvider.putSplToken(defaultToken));
169+
}
170+
}
171+
172+
for (final data in walletsData) {
173+
final List<Contract> contracts = [];
174+
final tokenMintAddresses = ref.read(
175+
pWalletTokenAddresses(data.walletId),
176+
);
177+
178+
// fetch each token
179+
for (final tokenAddress in tokenMintAddresses) {
180+
final token = dbProvider.getSplTokenSync(tokenAddress);
181+
182+
// add it to list if it exists in DB or in default tokens
183+
if (token != null) {
184+
contracts.add(token);
185+
} else {
186+
// Try to find in default tokens.
187+
try {
188+
final defaultToken = DefaultSplTokens.list.firstWhere(
189+
(t) => t.address == tokenAddress,
190+
);
191+
contracts.add(defaultToken);
192+
} catch (_) {
193+
// Token not found anywhere.
194+
//
195+
// Might want to throw here or something.
196+
}
197+
}
198+
}
199+
153200
// add tuple to list
154201
wallets[data.walletId] = (
155202
wallet: ref.read(pWallets).getWallet(data.walletId),
@@ -319,13 +366,23 @@ class _EthWalletsOverviewState extends ConsumerState<WalletsOverview> {
319366

320367
if (wallet.cryptoCurrency.hasTokenSupport) {
321368
if (isDesktop) {
322-
return DesktopExpandingWalletCard(
323-
key: Key(
324-
"${wallet.walletId}_${entry.contracts.map((e) => e.address).join()}",
325-
),
326-
data: entry,
327-
navigatorState: widget.navigatorState!,
328-
);
369+
if (wallet.cryptoCurrency is Solana) {
370+
return DesktopExpandingSolanaWalletCard(
371+
key: Key(
372+
"${wallet.walletId}_${entry.contracts.map((e) => e.address).join()}",
373+
),
374+
data: entry,
375+
navigatorState: widget.navigatorState!,
376+
);
377+
} else {
378+
return DesktopExpandingWalletCard(
379+
key: Key(
380+
"${wallet.walletId}_${entry.contracts.map((e) => e.address).join()}",
381+
),
382+
data: entry,
383+
navigatorState: widget.navigatorState!,
384+
);
385+
}
329386
} else {
330387
return MasterWalletCard(
331388
key: Key(wallet.walletId),
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/*
2+
* This file is part of Stack Wallet.
3+
*
4+
* Copyright (c) 2025 Cypher Stack
5+
* All Rights Reserved.
6+
* The code is distributed under GPLv3 license, see LICENSE file for details.
7+
* Generated by Cypher Stack on 2025-11-20
8+
*
9+
*/
10+
11+
import 'package:flutter/material.dart';
12+
import 'package:flutter_svg/flutter_svg.dart';
13+
import 'package:flutter_svg/svg.dart';
14+
15+
import '../../../pages/wallets_view/wallets_overview.dart';
16+
import '../../../themes/stack_colors.dart';
17+
import '../../../utilities/assets.dart';
18+
import '../../../utilities/constants.dart';
19+
import '../../../utilities/text_styles.dart';
20+
import '../../../widgets/animated_widgets/rotate_icon.dart';
21+
import '../../../widgets/expandable.dart';
22+
import '../../../widgets/rounded_white_container.dart';
23+
import '../../../widgets/wallet_card.dart';
24+
import '../../../widgets/wallet_info_row/sub_widgets/wallet_info_row_balance.dart';
25+
import '../../../widgets/wallet_info_row/sub_widgets/wallet_info_row_coin_icon.dart';
26+
27+
class DesktopExpandingSolanaWalletCard extends StatefulWidget {
28+
const DesktopExpandingSolanaWalletCard({
29+
super.key,
30+
required this.data,
31+
required this.navigatorState,
32+
});
33+
34+
final WalletListItemData data;
35+
final NavigatorState navigatorState;
36+
37+
@override
38+
State<DesktopExpandingSolanaWalletCard> createState() =>
39+
_DesktopExpandingSolanaWalletCardState();
40+
}
41+
42+
class _DesktopExpandingSolanaWalletCardState
43+
extends State<DesktopExpandingSolanaWalletCard> {
44+
final expandableController = ExpandableController();
45+
final rotateIconController = RotateIconController();
46+
final List<String> tokenMintAddresses = [];
47+
48+
@override
49+
void initState() {
50+
if (widget.data.wallet.cryptoCurrency.hasTokenSupport) {
51+
tokenMintAddresses.addAll(
52+
widget.data.contracts.map((e) => e.address),
53+
);
54+
}
55+
56+
super.initState();
57+
}
58+
59+
@override
60+
Widget build(BuildContext context) {
61+
return RoundedWhiteContainer(
62+
padding: EdgeInsets.zero,
63+
borderColor: Theme.of(context).extension<StackColors>()!.backgroundAppBar,
64+
child: Expandable(
65+
initialState: widget.data.wallet.cryptoCurrency.hasTokenSupport
66+
? ExpandableState.expanded
67+
: ExpandableState.collapsed,
68+
controller: expandableController,
69+
onExpandWillChange: (toState) {
70+
if (toState == ExpandableState.expanded) {
71+
rotateIconController.forward?.call();
72+
} else {
73+
rotateIconController.reverse?.call();
74+
}
75+
},
76+
header: Padding(
77+
padding: const EdgeInsets.symmetric(
78+
horizontal: 20,
79+
vertical: 14,
80+
),
81+
child: Row(
82+
children: [
83+
Expanded(
84+
child: Row(
85+
children: [
86+
Expanded(
87+
flex: 2,
88+
child: Row(
89+
children: [
90+
WalletInfoCoinIcon(
91+
coin: widget.data.wallet.info.coin,
92+
),
93+
const SizedBox(
94+
width: 12,
95+
),
96+
Text(
97+
widget.data.wallet.info.name,
98+
style: STextStyles.desktopTextExtraSmall(context)
99+
.copyWith(
100+
color: Theme.of(context)
101+
.extension<StackColors>()!
102+
.textDark,
103+
),
104+
),
105+
],
106+
),
107+
),
108+
Expanded(
109+
flex: 4,
110+
child: WalletInfoRowBalance(
111+
walletId: widget.data.wallet.walletId,
112+
),
113+
),
114+
],
115+
),
116+
),
117+
MaterialButton(
118+
padding: const EdgeInsets.all(5),
119+
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
120+
minWidth: 32,
121+
height: 32,
122+
color: Theme.of(context)
123+
.extension<StackColors>()!
124+
.textFieldDefaultBG,
125+
elevation: 0,
126+
hoverElevation: 0,
127+
disabledElevation: 0,
128+
highlightElevation: 0,
129+
shape: RoundedRectangleBorder(
130+
borderRadius: BorderRadius.circular(
131+
Constants.size.circularBorderRadius,
132+
),
133+
),
134+
onPressed: () {
135+
if (expandableController.state == ExpandableState.collapsed) {
136+
rotateIconController.forward?.call();
137+
} else {
138+
rotateIconController.reverse?.call();
139+
}
140+
expandableController.toggle?.call();
141+
},
142+
child: RotateIcon(
143+
controller: rotateIconController,
144+
icon: RotatedBox(
145+
quarterTurns: 2,
146+
child: SvgPicture.asset(
147+
Assets.svg.chevronDown,
148+
width: 14,
149+
),
150+
),
151+
curve: Curves.easeInOut,
152+
),
153+
),
154+
],
155+
),
156+
),
157+
body: ListView(
158+
shrinkWrap: true,
159+
primary: false,
160+
children: [
161+
Container(
162+
width: double.infinity,
163+
height: 1,
164+
color:
165+
Theme.of(context).extension<StackColors>()!.backgroundAppBar,
166+
),
167+
Padding(
168+
padding: const EdgeInsets.only(
169+
left: 32,
170+
right: 14,
171+
top: 14,
172+
bottom: 14,
173+
),
174+
child: SimpleWalletCard(
175+
walletId: widget.data.wallet.walletId,
176+
popPrevious: true,
177+
desktopNavigatorState: widget.navigatorState,
178+
),
179+
),
180+
...tokenMintAddresses.map(
181+
(e) => Padding(
182+
padding: const EdgeInsets.only(
183+
left: 32,
184+
right: 14,
185+
top: 14,
186+
bottom: 14,
187+
),
188+
child: SimpleWalletCard(
189+
walletId: widget.data.wallet.walletId,
190+
contractAddress: e,
191+
popPrevious: true,
192+
desktopNavigatorState: widget.navigatorState,
193+
),
194+
),
195+
),
196+
],
197+
),
198+
),
199+
);
200+
}
201+
}

0 commit comments

Comments
 (0)