> allOffsets,
+) {
+ final object = TransactionBlockExplorer(
+ ticker: reader.readString(offsets[0]),
+ url: reader.readString(offsets[1]),
+ );
+ object.id = id;
+ return object;
+}
+
+P _transactionBlockExplorerDeserializeProp(
+ IsarReader reader,
+ int propertyId,
+ int offset,
+ Map> allOffsets,
+) {
+ switch (propertyId) {
+ case 0:
+ return (reader.readString(offset)) as P;
+ case 1:
+ return (reader.readString(offset)) as P;
+ default:
+ throw IsarError('Unknown property with id $propertyId');
+ }
+}
+
+Id _transactionBlockExplorerGetId(TransactionBlockExplorer object) {
+ return object.id;
+}
+
+List> _transactionBlockExplorerGetLinks(
+ TransactionBlockExplorer object) {
+ return [];
+}
+
+void _transactionBlockExplorerAttach(
+ IsarCollection col, Id id, TransactionBlockExplorer object) {
+ object.id = id;
+}
+
+extension TransactionBlockExplorerByIndex
+ on IsarCollection {
+ Future getByTicker(String ticker) {
+ return getByIndex(r'ticker', [ticker]);
+ }
+
+ TransactionBlockExplorer? getByTickerSync(String ticker) {
+ return getByIndexSync(r'ticker', [ticker]);
+ }
+
+ Future deleteByTicker(String ticker) {
+ return deleteByIndex(r'ticker', [ticker]);
+ }
+
+ bool deleteByTickerSync(String ticker) {
+ return deleteByIndexSync(r'ticker', [ticker]);
+ }
+
+ Future> getAllByTicker(
+ List tickerValues) {
+ final values = tickerValues.map((e) => [e]).toList();
+ return getAllByIndex(r'ticker', values);
+ }
+
+ List getAllByTickerSync(
+ List tickerValues) {
+ final values = tickerValues.map((e) => [e]).toList();
+ return getAllByIndexSync(r'ticker', values);
+ }
+
+ Future deleteAllByTicker(List tickerValues) {
+ final values = tickerValues.map((e) => [e]).toList();
+ return deleteAllByIndex(r'ticker', values);
+ }
+
+ int deleteAllByTickerSync(List tickerValues) {
+ final values = tickerValues.map((e) => [e]).toList();
+ return deleteAllByIndexSync(r'ticker', values);
+ }
+
+ Future putByTicker(TransactionBlockExplorer object) {
+ return putByIndex(r'ticker', object);
+ }
+
+ Id putByTickerSync(TransactionBlockExplorer object, {bool saveLinks = true}) {
+ return putByIndexSync(r'ticker', object, saveLinks: saveLinks);
+ }
+
+ Future> putAllByTicker(List objects) {
+ return putAllByIndex(r'ticker', objects);
+ }
+
+ List putAllByTickerSync(List objects,
+ {bool saveLinks = true}) {
+ return putAllByIndexSync(r'ticker', objects, saveLinks: saveLinks);
+ }
+}
+
+extension TransactionBlockExplorerQueryWhereSort on QueryBuilder<
+ TransactionBlockExplorer, TransactionBlockExplorer, QWhere> {
+ QueryBuilder
+ anyId() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addWhereClause(const IdWhereClause.any());
+ });
+ }
+}
+
+extension TransactionBlockExplorerQueryWhere on QueryBuilder<
+ TransactionBlockExplorer, TransactionBlockExplorer, QWhereClause> {
+ QueryBuilder idEqualTo(Id id) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addWhereClause(IdWhereClause.between(
+ lower: id,
+ upper: id,
+ ));
+ });
+ }
+
+ QueryBuilder idNotEqualTo(Id id) {
+ return QueryBuilder.apply(this, (query) {
+ if (query.whereSort == Sort.asc) {
+ return query
+ .addWhereClause(
+ IdWhereClause.lessThan(upper: id, includeUpper: false),
+ )
+ .addWhereClause(
+ IdWhereClause.greaterThan(lower: id, includeLower: false),
+ );
+ } else {
+ return query
+ .addWhereClause(
+ IdWhereClause.greaterThan(lower: id, includeLower: false),
+ )
+ .addWhereClause(
+ IdWhereClause.lessThan(upper: id, includeUpper: false),
+ );
+ }
+ });
+ }
+
+ QueryBuilder idGreaterThan(Id id, {bool include = false}) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addWhereClause(
+ IdWhereClause.greaterThan(lower: id, includeLower: include),
+ );
+ });
+ }
+
+ QueryBuilder idLessThan(Id id, {bool include = false}) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addWhereClause(
+ IdWhereClause.lessThan(upper: id, includeUpper: include),
+ );
+ });
+ }
+
+ QueryBuilder idBetween(
+ Id lowerId,
+ Id upperId, {
+ bool includeLower = true,
+ bool includeUpper = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addWhereClause(IdWhereClause.between(
+ lower: lowerId,
+ includeLower: includeLower,
+ upper: upperId,
+ includeUpper: includeUpper,
+ ));
+ });
+ }
+
+ QueryBuilder tickerEqualTo(String ticker) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addWhereClause(IndexWhereClause.equalTo(
+ indexName: r'ticker',
+ value: [ticker],
+ ));
+ });
+ }
+
+ QueryBuilder tickerNotEqualTo(String ticker) {
+ return QueryBuilder.apply(this, (query) {
+ if (query.whereSort == Sort.asc) {
+ return query
+ .addWhereClause(IndexWhereClause.between(
+ indexName: r'ticker',
+ lower: [],
+ upper: [ticker],
+ includeUpper: false,
+ ))
+ .addWhereClause(IndexWhereClause.between(
+ indexName: r'ticker',
+ lower: [ticker],
+ includeLower: false,
+ upper: [],
+ ));
+ } else {
+ return query
+ .addWhereClause(IndexWhereClause.between(
+ indexName: r'ticker',
+ lower: [ticker],
+ includeLower: false,
+ upper: [],
+ ))
+ .addWhereClause(IndexWhereClause.between(
+ indexName: r'ticker',
+ lower: [],
+ upper: [ticker],
+ includeUpper: false,
+ ));
+ }
+ });
+ }
+}
+
+extension TransactionBlockExplorerQueryFilter on QueryBuilder<
+ TransactionBlockExplorer, TransactionBlockExplorer, QFilterCondition> {
+ QueryBuilder idEqualTo(Id value) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.equalTo(
+ property: r'id',
+ value: value,
+ ));
+ });
+ }
+
+ QueryBuilder idGreaterThan(
+ Id value, {
+ bool include = false,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.greaterThan(
+ include: include,
+ property: r'id',
+ value: value,
+ ));
+ });
+ }
+
+ QueryBuilder idLessThan(
+ Id value, {
+ bool include = false,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.lessThan(
+ include: include,
+ property: r'id',
+ value: value,
+ ));
+ });
+ }
+
+ QueryBuilder idBetween(
+ Id lower,
+ Id upper, {
+ bool includeLower = true,
+ bool includeUpper = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.between(
+ property: r'id',
+ lower: lower,
+ includeLower: includeLower,
+ upper: upper,
+ includeUpper: includeUpper,
+ ));
+ });
+ }
+
+ QueryBuilder tickerEqualTo(
+ String value, {
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.equalTo(
+ property: r'ticker',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder tickerGreaterThan(
+ String value, {
+ bool include = false,
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.greaterThan(
+ include: include,
+ property: r'ticker',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder tickerLessThan(
+ String value, {
+ bool include = false,
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.lessThan(
+ include: include,
+ property: r'ticker',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder tickerBetween(
+ String lower,
+ String upper, {
+ bool includeLower = true,
+ bool includeUpper = true,
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.between(
+ property: r'ticker',
+ lower: lower,
+ includeLower: includeLower,
+ upper: upper,
+ includeUpper: includeUpper,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder tickerStartsWith(
+ String value, {
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.startsWith(
+ property: r'ticker',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder tickerEndsWith(
+ String value, {
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.endsWith(
+ property: r'ticker',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder
+ tickerContains(String value, {bool caseSensitive = true}) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.contains(
+ property: r'ticker',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder
+ tickerMatches(String pattern, {bool caseSensitive = true}) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.matches(
+ property: r'ticker',
+ wildcard: pattern,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder tickerIsEmpty() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.equalTo(
+ property: r'ticker',
+ value: '',
+ ));
+ });
+ }
+
+ QueryBuilder tickerIsNotEmpty() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.greaterThan(
+ property: r'ticker',
+ value: '',
+ ));
+ });
+ }
+
+ QueryBuilder urlEqualTo(
+ String value, {
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.equalTo(
+ property: r'url',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder urlGreaterThan(
+ String value, {
+ bool include = false,
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.greaterThan(
+ include: include,
+ property: r'url',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder urlLessThan(
+ String value, {
+ bool include = false,
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.lessThan(
+ include: include,
+ property: r'url',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder urlBetween(
+ String lower,
+ String upper, {
+ bool includeLower = true,
+ bool includeUpper = true,
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.between(
+ property: r'url',
+ lower: lower,
+ includeLower: includeLower,
+ upper: upper,
+ includeUpper: includeUpper,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder urlStartsWith(
+ String value, {
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.startsWith(
+ property: r'url',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder urlEndsWith(
+ String value, {
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.endsWith(
+ property: r'url',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder
+ urlContains(String value, {bool caseSensitive = true}) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.contains(
+ property: r'url',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder
+ urlMatches(String pattern, {bool caseSensitive = true}) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.matches(
+ property: r'url',
+ wildcard: pattern,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder urlIsEmpty() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.equalTo(
+ property: r'url',
+ value: '',
+ ));
+ });
+ }
+
+ QueryBuilder urlIsNotEmpty() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.greaterThan(
+ property: r'url',
+ value: '',
+ ));
+ });
+ }
+}
+
+extension TransactionBlockExplorerQueryObject on QueryBuilder<
+ TransactionBlockExplorer, TransactionBlockExplorer, QFilterCondition> {}
+
+extension TransactionBlockExplorerQueryLinks on QueryBuilder<
+ TransactionBlockExplorer, TransactionBlockExplorer, QFilterCondition> {}
+
+extension TransactionBlockExplorerQuerySortBy on QueryBuilder<
+ TransactionBlockExplorer, TransactionBlockExplorer, QSortBy> {
+ QueryBuilder
+ sortByTicker() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'ticker', Sort.asc);
+ });
+ }
+
+ QueryBuilder
+ sortByTickerDesc() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'ticker', Sort.desc);
+ });
+ }
+
+ QueryBuilder
+ sortByUrl() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'url', Sort.asc);
+ });
+ }
+
+ QueryBuilder
+ sortByUrlDesc() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'url', Sort.desc);
+ });
+ }
+}
+
+extension TransactionBlockExplorerQuerySortThenBy on QueryBuilder<
+ TransactionBlockExplorer, TransactionBlockExplorer, QSortThenBy> {
+ QueryBuilder
+ thenById() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'id', Sort.asc);
+ });
+ }
+
+ QueryBuilder
+ thenByIdDesc() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'id', Sort.desc);
+ });
+ }
+
+ QueryBuilder
+ thenByTicker() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'ticker', Sort.asc);
+ });
+ }
+
+ QueryBuilder
+ thenByTickerDesc() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'ticker', Sort.desc);
+ });
+ }
+
+ QueryBuilder
+ thenByUrl() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'url', Sort.asc);
+ });
+ }
+
+ QueryBuilder
+ thenByUrlDesc() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'url', Sort.desc);
+ });
+ }
+}
+
+extension TransactionBlockExplorerQueryWhereDistinct on QueryBuilder<
+ TransactionBlockExplorer, TransactionBlockExplorer, QDistinct> {
+ QueryBuilder
+ distinctByTicker({bool caseSensitive = true}) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addDistinctBy(r'ticker', caseSensitive: caseSensitive);
+ });
+ }
+
+ QueryBuilder
+ distinctByUrl({bool caseSensitive = true}) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addDistinctBy(r'url', caseSensitive: caseSensitive);
+ });
+ }
+}
+
+extension TransactionBlockExplorerQueryProperty on QueryBuilder<
+ TransactionBlockExplorer, TransactionBlockExplorer, QQueryProperty> {
+ QueryBuilder idProperty() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addPropertyName(r'id');
+ });
+ }
+
+ QueryBuilder
+ tickerProperty() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addPropertyName(r'ticker');
+ });
+ }
+
+ QueryBuilder
+ urlProperty() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addPropertyName(r'url');
+ });
+ }
+}
diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart
index f61a880e2..3285d61cf 100644
--- a/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart
+++ b/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart
@@ -1,15 +1,18 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/debug_view.dart';
+import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart';
import 'package:stackwallet/pages/stack_privacy_calls.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/widgets/background.dart';
+import 'package:stackwallet/widgets/choose_coin_view.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
+import 'package:tuple/tuple.dart';
class AdvancedSettingsView extends StatelessWidget {
const AdvancedSettingsView({
@@ -221,6 +224,43 @@ class AdvancedSettingsView extends StatelessWidget {
},
),
),
+ const SizedBox(
+ height: 8,
+ ),
+ RoundedWhiteContainer(
+ padding: const EdgeInsets.all(0),
+ child: RawMaterialButton(
+ // splashColor: Theme.of(context).extension()!.highlight,
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ ),
+ onPressed: () {
+ Navigator.of(context).pushNamed(ChooseCoinView.routeName,
+ arguments: const Tuple3(
+ "Manage block explorers",
+ "block explorer",
+ ManageExplorerView.routeName));
+ },
+ child: Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 12,
+ vertical: 20,
+ ),
+ child: Row(
+ children: [
+ Text(
+ "Change block explorer",
+ style: STextStyles.titleBold12(context),
+ textAlign: TextAlign.left,
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
],
),
),
diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart
new file mode 100644
index 000000000..6cf02b148
--- /dev/null
+++ b/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart
@@ -0,0 +1,117 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:stackwallet/utilities/block_explorers.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/background.dart';
+import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
+import 'package:stackwallet/widgets/rounded_white_container.dart';
+
+class ManageExplorerView extends ConsumerStatefulWidget {
+ const ManageExplorerView({
+ Key? key,
+ required this.coin,
+ }) : super(key: key);
+
+ static const String routeName = "/manageExplorer";
+
+ final Coin coin;
+
+ @override
+ ConsumerState createState() => _ManageExplorerViewState();
+}
+
+class _ManageExplorerViewState extends ConsumerState {
+ late TextEditingController textEditingController;
+
+ @override
+ void initState() {
+ super.initState();
+ textEditingController = TextEditingController(
+ text:
+ getBlockExplorerTransactionUrlFor(coin: widget.coin, txid: "[TXID]")
+ .toString()
+ .replaceAll("%5BTXID%5D", "[TXID]"));
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Background(
+ child: Scaffold(
+ backgroundColor: Theme.of(context).extension()!.background,
+ appBar: AppBar(
+ leading: AppBarBackButton(
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ ),
+ title: Text(
+ "${widget.coin.prettyName} block explorer",
+ style: STextStyles.navBarTitle(context),
+ ),
+ ),
+ body: Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ children: [
+ Expanded(
+ child: Column(
+ children: [
+ TextField(
+ controller: textEditingController,
+ decoration: const InputDecoration(
+ border: OutlineInputBorder(),
+ ),
+ ),
+ const SizedBox(
+ height: 8,
+ ),
+ RoundedWhiteContainer(
+ child: Center(
+ child: Text(
+ "Edit your block explorer above. Keep in mind that "
+ "every block explorer has a slightly different URL "
+ "scheme.\n\nPaste in your block explorer of choice,"
+ " then edit in [TXID] where the transaction ID "
+ "should go, and Stack Wallet will auto fill the "
+ "transaction ID in that place of URL.",
+ style: STextStyles.itemSubtitle(context),
+ ),
+ ),
+ ),
+ ],
+ )),
+ Align(
+ alignment: Alignment.bottomCenter,
+ child: ConstrainedBox(
+ constraints: const BoxConstraints(
+ minWidth: 480,
+ minHeight: 70,
+ ),
+ child: TextButton(
+ style: Theme.of(context)
+ .extension()!
+ .getPrimaryEnabledButtonStyle(context),
+ onPressed: () {
+ textEditingController.text =
+ textEditingController.text.trim();
+ setBlockExplorerForCoin(
+ coin: widget.coin,
+ url: Uri.parse(textEditingController.text))
+ .then((value) => Navigator.of(context).pop());
+ },
+ child: Text(
+ "Save",
+ style: STextStyles.button(context),
+ ),
+ ),
+ ),
+ )
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/wallet_view/sub_widgets/transactions_list.dart b/lib/pages/wallet_view/sub_widgets/transactions_list.dart
index a15fbbde5..e68b443ed 100644
--- a/lib/pages/wallet_view/sub_widgets/transactions_list.dart
+++ b/lib/pages/wallet_view/sub_widgets/transactions_list.dart
@@ -94,7 +94,10 @@ class _TransactionsListState extends ConsumerState {
TransactionCard(
// this may mess with combined firo transactions
key: isConfirmed
- ? Key(tx.txid + tx.type.name + tx.address.value.toString())
+ ? Key(tx.txid +
+ tx.type.name +
+ tx.address.value.toString() +
+ tx.height.toString())
: UniqueKey(), //
transaction: tx,
walletId: widget.walletId,
@@ -191,7 +194,10 @@ class _TransactionsListState extends ConsumerState {
child: TransactionCard(
// this may mess with combined firo transactions
key: isConfirmed
- ? Key(tx.txid + tx.type.name + tx.address.value.toString())
+ ? Key(tx.txid +
+ tx.type.name +
+ tx.address.value.toString() +
+ tx.height.toString())
: UniqueKey(),
transaction: tx,
walletId: widget.walletId,
diff --git a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart
index 19a9b6776..5b8e04e0e 100644
--- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart
+++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart
@@ -112,10 +112,11 @@ class _TransactionDetailsViewState
super.dispose();
}
- String whatIsIt(TransactionType type, int height) {
+ String whatIsIt(Transaction tx, int height) {
+ final type = tx.type;
if (coin == Coin.firo || coin == Coin.firoTestNet) {
- if (_transaction.subType == TransactionSubType.mint) {
- if (_transaction.isConfirmed(height, coin.requiredConfirmations)) {
+ if (tx.subType == TransactionSubType.mint) {
+ if (tx.isConfirmed(height, coin.requiredConfirmations)) {
return "Minted";
} else {
return "Minting";
@@ -127,13 +128,13 @@ class _TransactionDetailsViewState
// if (_transaction.isMinting) {
// return "Minting";
// } else
- if (_transaction.isConfirmed(height, coin.requiredConfirmations)) {
+ if (tx.isConfirmed(height, coin.requiredConfirmations)) {
return "Received";
} else {
return "Receiving";
}
} else if (type == TransactionType.outgoing) {
- if (_transaction.isConfirmed(height, coin.requiredConfirmations)) {
+ if (tx.isConfirmed(height, coin.requiredConfirmations)) {
return "Sent";
} else {
return "Sending";
@@ -428,7 +429,7 @@ class _TransactionDetailsViewState
_transaction.isCancelled
? "Cancelled"
: whatIsIt(
- _transaction.type,
+ _transaction,
currentHeight,
),
style:
@@ -545,7 +546,7 @@ class _TransactionDetailsViewState
_transaction.isCancelled
? "Cancelled"
: whatIsIt(
- _transaction.type,
+ _transaction,
currentHeight,
),
style: isDesktop
diff --git a/lib/route_generator.dart b/lib/route_generator.dart
index 732d8f9a9..2c079377b 100644
--- a/lib/route_generator.dart
+++ b/lib/route_generator.dart
@@ -63,6 +63,7 @@ import 'package:stackwallet/pages/send_view/token_send_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/about_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/debug_view.dart';
+import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/manage_themes.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/system_brightness_theme_selection_view.dart';
@@ -100,6 +101,7 @@ import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_set
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rename_wallet_view.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart';
import 'package:stackwallet/pages/stack_privacy_calls.dart';
+import 'package:stackwallet/widgets/choose_coin_view.dart';
import 'package:stackwallet/pages/token_view/my_tokens_view.dart';
import 'package:stackwallet/pages/token_view/token_contract_details_view.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
@@ -207,6 +209,36 @@ class RouteGenerator {
builder: (_) => const StackPrivacyCalls(isSettings: false),
settings: RouteSettings(name: settings.name));
+ case ChooseCoinView.routeName:
+ if (args is Tuple3) {
+ return getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => ChooseCoinView(
+ title: args.item1,
+ coinAdditional: args.item2,
+ nextRouteName: args.item3,
+ ),
+ settings: RouteSettings(
+ name: settings.name,
+ ),
+ );
+ }
+ return _routeError("${settings.name} invalid args: ${args.toString()}");
+
+ case ManageExplorerView.routeName:
+ if (args is Coin) {
+ return getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => ManageExplorerView(
+ coin: args,
+ ),
+ settings: RouteSettings(
+ name: settings.name,
+ ),
+ );
+ }
+ return _routeError("${settings.name} invalid args: ${args.toString()}");
+
case WalletsView.routeName:
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart
index b6dbca26d..5820dcc59 100644
--- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart
+++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart
@@ -1960,7 +1960,8 @@ class BitcoinCashWallet extends CoinServiceAPI
if (storedTx == null ||
storedTx.address.value == null ||
- storedTx.height == null
+ storedTx.height == null ||
+ (storedTx.height != null && storedTx.height! <= 0)
// zero conf messes this up
// !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)
) {
diff --git a/lib/utilities/block_explorers.dart b/lib/utilities/block_explorers.dart
index c628dbbb4..a5619587e 100644
--- a/lib/utilities/block_explorers.dart
+++ b/lib/utilities/block_explorers.dart
@@ -1,6 +1,8 @@
+import 'package:stackwallet/db/isar/main_db.dart';
+import 'package:stackwallet/models/isar/models/block_explorer.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
-Uri getBlockExplorerTransactionUrlFor({
+Uri getDefaultBlockExplorerUrlFor({
required Coin coin,
required String txid,
}) {
@@ -41,3 +43,29 @@ Uri getBlockExplorerTransactionUrlFor({
return Uri.parse("https://chainz.cryptoid.info/part/tx.dws?$txid.htm");
}
}
+
+/// returns internal Isar ID for the inserted object/record
+Future setBlockExplorerForCoin({
+ required Coin coin,
+ required Uri url,
+}) async {
+ return await MainDB.instance.putTransactionBlockExplorer(
+ TransactionBlockExplorer(
+ ticker: coin.ticker,
+ url: url.toString(),
+ ),
+ );
+}
+
+Uri getBlockExplorerTransactionUrlFor({
+ required Coin coin,
+ required String txid,
+}) {
+ String? url = MainDB.instance.getTransactionBlockExplorer(coin: coin)?.url;
+ if (url == null) {
+ return getDefaultBlockExplorerUrlFor(coin: coin, txid: txid);
+ } else {
+ url = url.replaceAll("%5BTXID%5D", txid);
+ return Uri.parse(url);
+ }
+}
diff --git a/lib/widgets/choose_coin_view.dart b/lib/widgets/choose_coin_view.dart
new file mode 100644
index 000000000..baced09fe
--- /dev/null
+++ b/lib/widgets/choose_coin_view.dart
@@ -0,0 +1,138 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:stackwallet/providers/providers.dart';
+import 'package:stackwallet/utilities/assets.dart';
+import 'package:stackwallet/utilities/constants.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/background.dart';
+import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
+import 'package:stackwallet/widgets/rounded_white_container.dart';
+
+class ChooseCoinView extends ConsumerStatefulWidget {
+ const ChooseCoinView({
+ Key? key,
+ required this.title,
+ required this.coinAdditional,
+ required this.nextRouteName,
+ }) : super(key: key);
+
+ static const String routeName = "/chooseCoin";
+
+ final String title;
+ final String coinAdditional;
+ final String nextRouteName;
+
+ @override
+ ConsumerState createState() => _ChooseCoinViewState();
+}
+
+class _ChooseCoinViewState extends ConsumerState {
+ List _coins = [...Coin.values];
+
+ @override
+ void initState() {
+ _coins = _coins.toList();
+ _coins.remove(Coin.firoTestNet);
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ bool showTestNet = ref.watch(
+ prefsChangeNotifierProvider.select((value) => value.showTestNetCoins),
+ );
+
+ List coins = showTestNet
+ ? _coins
+ : _coins.sublist(0, _coins.length - kTestNetCoinCount);
+
+ return Background(
+ child: Scaffold(
+ backgroundColor: Theme.of(context).extension()!.background,
+ appBar: AppBar(
+ leading: AppBarBackButton(
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ ),
+ title: Text(
+ widget.title,
+ style: STextStyles.navBarTitle(context),
+ ),
+ ),
+ body: Padding(
+ padding: const EdgeInsets.only(
+ top: 12,
+ left: 12,
+ right: 12,
+ ),
+ child: SingleChildScrollView(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ ...coins.map(
+ (coin) {
+ return Padding(
+ padding: const EdgeInsets.all(4),
+ child: RoundedWhiteContainer(
+ padding: const EdgeInsets.all(0),
+ child: RawMaterialButton(
+ // splashColor: Theme.of(context).extension()!.highlight,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ ),
+ materialTapTargetSize:
+ MaterialTapTargetSize.shrinkWrap,
+ onPressed: () {
+ Navigator.of(context).pushNamed(
+ widget.nextRouteName,
+ arguments: coin,
+ );
+ },
+ child: Padding(
+ padding: const EdgeInsets.all(12),
+ child: Row(
+ children: [
+ SvgPicture.asset(
+ Assets.svg.iconFor(coin: coin),
+ width: 24,
+ height: 24,
+ ),
+ const SizedBox(
+ width: 12,
+ ),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ "${coin.prettyName} ${widget.coinAdditional}",
+ style: STextStyles.titleBold12(context),
+ ),
+ ],
+ )
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 9b48c2cf9..7beff062b 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -11,7 +11,7 @@ description: Stack Wallet
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
-version: 1.7.6+168
+version: 1.7.7+169
environment:
sdk: ">=2.17.0 <3.0.0"