mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-04-01 11:59:06 +00:00
multi select utxo ui
This commit is contained in:
parent
f844b7eef2
commit
3d04dc9ef8
4 changed files with 336 additions and 87 deletions
lib
pages
route_generator.dart
|
@ -5,40 +5,67 @@ import 'package:stackwallet/db/main_db.dart';
|
|||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/pages/coin_control/utxo_card.dart';
|
||||
import 'package:stackwallet/pages/coin_control/utxo_details_view.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/format.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
import 'package:stackwallet/widgets/toggle.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
enum CoinControlViewType {
|
||||
manage,
|
||||
use;
|
||||
}
|
||||
|
||||
class CoinControlView extends ConsumerStatefulWidget {
|
||||
const CoinControlView({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
required this.type,
|
||||
this.requestedTotal,
|
||||
}) : super(key: key);
|
||||
|
||||
static const routeName = "/coinControl";
|
||||
|
||||
final String walletId;
|
||||
final CoinControlViewType type;
|
||||
final int? requestedTotal;
|
||||
|
||||
@override
|
||||
ConsumerState<CoinControlView> createState() => _CoinControlViewState();
|
||||
}
|
||||
|
||||
class _CoinControlViewState extends ConsumerState<CoinControlView> {
|
||||
bool _showAvailable = false;
|
||||
bool _showBlocked = false;
|
||||
|
||||
final Set<UTXO> _selectedAvailable = {};
|
||||
final Set<UTXO> _selectedBlocked = {};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
|
||||
final coin = ref.watch(
|
||||
walletsChangeNotifierProvider.select(
|
||||
(value) => value
|
||||
.getManager(
|
||||
widget.walletId,
|
||||
)
|
||||
.coin,
|
||||
),
|
||||
);
|
||||
|
||||
final ids = MainDB.instance
|
||||
.getUTXOs(widget.walletId)
|
||||
.filter()
|
||||
.isBlockedEqualTo(_showAvailable)
|
||||
.isBlockedEqualTo(_showBlocked)
|
||||
.idProperty()
|
||||
.findAllSync();
|
||||
|
||||
|
@ -57,89 +84,284 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
|
|||
),
|
||||
titleSpacing: 0,
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
child: Text(
|
||||
"This option allows you to control, freeze, and utilize "
|
||||
"outputs at your discretion. Tap the output circle to "
|
||||
"select.",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
child: Text(
|
||||
"This option allows you to control, freeze, and utilize "
|
||||
"outputs at your discretion. Tap the output circle to "
|
||||
"select.",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: Toggle(
|
||||
key: UniqueKey(),
|
||||
onColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.popupBG,
|
||||
onText: "Available outputs",
|
||||
offColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
offText: "Frozen outputs",
|
||||
isOn: _showBlocked,
|
||||
onValueChanged: (value) {
|
||||
setState(() {
|
||||
_showBlocked = value;
|
||||
});
|
||||
},
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
itemCount: ids.length,
|
||||
separatorBuilder: (context, _) => const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final utxo = MainDB.instance.isar.utxos
|
||||
.where()
|
||||
.idEqualTo(ids[index])
|
||||
.findFirstSync()!;
|
||||
|
||||
return UtxoCard(
|
||||
key: Key("${utxo.walletId}_${utxo.id}"),
|
||||
walletId: widget.walletId,
|
||||
utxo: utxo,
|
||||
initialSelectedState: _showBlocked
|
||||
? _selectedBlocked.contains(utxo)
|
||||
: _selectedAvailable.contains(utxo),
|
||||
onSelectedChanged: (value) {
|
||||
if (value) {
|
||||
_showBlocked
|
||||
? _selectedBlocked.add(utxo)
|
||||
: _selectedAvailable.add(utxo);
|
||||
} else {
|
||||
_showBlocked
|
||||
? _selectedBlocked.remove(utxo)
|
||||
: _selectedAvailable.remove(utxo);
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
onPressed: () async {
|
||||
final result =
|
||||
await Navigator.of(context).pushNamed(
|
||||
UtxoDetailsView.routeName,
|
||||
arguments: Tuple2(
|
||||
utxo.id,
|
||||
widget.walletId,
|
||||
),
|
||||
);
|
||||
if (mounted && result == "refresh") {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: Toggle(
|
||||
key: UniqueKey(),
|
||||
onColor: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
onText: "Available outputs",
|
||||
offColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
offText: "Frozen outputs",
|
||||
isOn: _showAvailable,
|
||||
onValueChanged: (value) {
|
||||
setState(() {
|
||||
_showAvailable = value;
|
||||
});
|
||||
},
|
||||
if (((_showBlocked && _selectedBlocked.isNotEmpty) ||
|
||||
(!_showBlocked && _selectedAvailable.isNotEmpty)) &&
|
||||
widget.type == CoinControlViewType.manage)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.backgroundAppBar,
|
||||
boxShadow: [
|
||||
Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.standardBoxShadow,
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: SecondaryButton(
|
||||
label: _showBlocked ? "Unfreeze" : "Freeze",
|
||||
onPressed: () async {
|
||||
if (_showBlocked) {
|
||||
await MainDB.instance.putUTXOs(_selectedBlocked
|
||||
.map(
|
||||
(e) => e.copyWith(
|
||||
isBlocked: false,
|
||||
),
|
||||
)
|
||||
.toList());
|
||||
_selectedBlocked.clear();
|
||||
} else {
|
||||
await MainDB.instance.putUTXOs(_selectedAvailable
|
||||
.map(
|
||||
(e) => e.copyWith(
|
||||
isBlocked: true,
|
||||
),
|
||||
)
|
||||
.toList());
|
||||
_selectedAvailable.clear();
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
itemCount: ids.length,
|
||||
separatorBuilder: (context, _) => const SizedBox(
|
||||
height: 10,
|
||||
if (!_showBlocked && widget.type == CoinControlViewType.use)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.backgroundAppBar,
|
||||
boxShadow: [
|
||||
Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.standardBoxShadow,
|
||||
],
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final utxo = MainDB.instance.isar.utxos
|
||||
.where()
|
||||
.idEqualTo(ids[index])
|
||||
.findFirstSync()!;
|
||||
|
||||
return UtxoCard(
|
||||
key: Key("${utxo.walletId}_${utxo.id}"),
|
||||
walletId: widget.walletId,
|
||||
utxo: utxo,
|
||||
onPressed: () async {
|
||||
final result = await Navigator.of(context).pushNamed(
|
||||
UtxoDetailsView.routeName,
|
||||
arguments: Tuple2(
|
||||
utxo.id,
|
||||
widget.walletId,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
RoundedWhiteContainer(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Selected amount",
|
||||
style: STextStyles.w600_14(context),
|
||||
),
|
||||
Builder(builder: (context) {
|
||||
int selectedSum = _selectedAvailable
|
||||
.map((e) => e.value)
|
||||
.reduce(
|
||||
(value, element) => value += element,
|
||||
);
|
||||
return Text(
|
||||
"${Format.satoshisToAmount(
|
||||
selectedSum,
|
||||
coin: coin,
|
||||
).toStringAsFixed(
|
||||
coin.decimals,
|
||||
)} ${coin.ticker}",
|
||||
style: widget.requestedTotal == null
|
||||
? STextStyles.w600_14(context)
|
||||
: STextStyles.w600_14(context)
|
||||
.copyWith(
|
||||
color: selectedSum >=
|
||||
widget.requestedTotal!
|
||||
? Theme.of(context)
|
||||
.extension<
|
||||
StackColors>()!
|
||||
.accentColorGreen
|
||||
: Theme.of(context)
|
||||
.extension<
|
||||
StackColors>()!
|
||||
.accentColorRed),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
if (widget.requestedTotal != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12,
|
||||
),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 2,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.backgroundAppBar,
|
||||
),
|
||||
),
|
||||
if (widget.requestedTotal != null)
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Amount to send",
|
||||
style: STextStyles.w600_14(context),
|
||||
),
|
||||
Text(
|
||||
"${Format.satoshisToAmount(
|
||||
widget.requestedTotal!,
|
||||
coin: coin,
|
||||
).toStringAsFixed(
|
||||
coin.decimals,
|
||||
)} ${coin.ticker}",
|
||||
style: STextStyles.w600_14(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (mounted && result == "refresh") {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
PrimaryButton(
|
||||
label: "Use coins",
|
||||
onPressed: () async {
|
||||
if (_showBlocked) {
|
||||
await MainDB.instance.putUTXOs(_selectedBlocked
|
||||
.map(
|
||||
(e) => e.copyWith(
|
||||
isBlocked: false,
|
||||
),
|
||||
)
|
||||
.toList());
|
||||
_selectedBlocked.clear();
|
||||
} else {
|
||||
await MainDB.instance.putUTXOs(_selectedAvailable
|
||||
.map(
|
||||
(e) => e.copyWith(
|
||||
isBlocked: true,
|
||||
),
|
||||
)
|
||||
.toList());
|
||||
_selectedAvailable.clear();
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -19,13 +19,15 @@ class UtxoCard extends ConsumerStatefulWidget {
|
|||
Key? key,
|
||||
required this.utxo,
|
||||
required this.walletId,
|
||||
this.selectable = false,
|
||||
required this.onSelectedChanged,
|
||||
required this.initialSelectedState,
|
||||
this.onPressed,
|
||||
}) : super(key: key);
|
||||
|
||||
final String walletId;
|
||||
final UTXO utxo;
|
||||
final bool selectable;
|
||||
final void Function(bool) onSelectedChanged;
|
||||
final bool initialSelectedState;
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
@override
|
||||
|
@ -35,10 +37,11 @@ class UtxoCard extends ConsumerStatefulWidget {
|
|||
class _UtxoCardState extends ConsumerState<UtxoCard> {
|
||||
late final UTXO utxo;
|
||||
|
||||
bool _selected = false;
|
||||
late bool _selected;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_selected = widget.initialSelectedState;
|
||||
utxo = widget.utxo;
|
||||
super.initState();
|
||||
}
|
||||
|
@ -87,14 +90,21 @@ class _UtxoCardState extends ConsumerState<UtxoCard> {
|
|||
: Colors.transparent,
|
||||
child: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
_selected
|
||||
? Assets.svg.coinControl.selected
|
||||
: utxo.isBlocked
|
||||
? Assets.svg.coinControl.blocked
|
||||
: Assets.svg.coinControl.unBlocked,
|
||||
width: 32,
|
||||
height: 32,
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
_selected = !_selected;
|
||||
widget.onSelectedChanged(_selected);
|
||||
setState(() {});
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
_selected
|
||||
? Assets.svg.coinControl.selected
|
||||
: utxo.isBlocked
|
||||
? Assets.svg.coinControl.blocked
|
||||
: Assets.svg.coinControl.unBlocked,
|
||||
width: 32,
|
||||
height: 32,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
|
|
|
@ -17,6 +17,7 @@ import 'package:stackwallet/utilities/logger.dart';
|
|||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/widgets/loading_indicator.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class WalletNavigationBar extends ConsumerStatefulWidget {
|
||||
const WalletNavigationBar({
|
||||
|
@ -118,7 +119,10 @@ class _WalletNavigationBarState extends ConsumerState<WalletNavigationBar> {
|
|||
|
||||
Navigator.of(context).pushNamed(
|
||||
CoinControlView.routeName,
|
||||
arguments: widget.walletId,
|
||||
arguments: Tuple2(
|
||||
widget.walletId,
|
||||
CoinControlViewType.manage,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -218,11 +218,24 @@ class RouteGenerator {
|
|||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case CoinControlView.routeName:
|
||||
if (args is String) {
|
||||
if (args is Tuple2<String, CoinControlViewType>) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => CoinControlView(
|
||||
walletId: args,
|
||||
walletId: args.item1,
|
||||
type: args.item2,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
} else if (args is Tuple3<String, CoinControlViewType, int?>) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => CoinControlView(
|
||||
walletId: args.item1,
|
||||
type: args.item2,
|
||||
requestedTotal: args.item3,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
|
|
Loading…
Reference in a new issue