Merge remote-tracking branch 'origin_SW/staging' into themes

# Conflicts:
#	lib/db/isar/main_db.dart
This commit is contained in:
julian 2023-05-09 18:00:17 -06:00
commit 4213eca928
16 changed files with 1304 additions and 19 deletions

64
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View file

@ -0,0 +1,64 @@
name: 🐞 Bug Report
description: File a new bug report
title: 'Bug: <title>'
labels: [Bug]
body:
- type: markdown
attributes:
value: ':stop_sign: _For support questions, please visit the [Discord](https://discord.gg/mRPZuXx3At), [Telegram](https://t.me/stackwallet) or [Reddit](https://www.reddit.com/r/stackwallet/) instead._'
- type: checkboxes
attributes:
label: 'Is there an existing issue for this?'
description: 'Please [search :mag: the issues](https://github.com/cypherstack/stack_wallet/issues) to check if this bug has already been reported.'
options:
- label: 'I have searched the existing issues'
required: true
- type: textarea
attributes:
label: 'Current Behavior'
description: 'Describe the problem you are experiencing. **Please do not paste your logs here.** Screenshots are welcome.'
validations:
required: true
- type: textarea
attributes:
label: 'Expected Behavior'
description: 'Describe what you expect to happen instead.'
validations:
required: true
- type: textarea
attributes:
label: 'Reproduce Steps'
description: |
Please provide a the _smallest, complete steps_ that Stack Wallet's maintainers can run to reproduce the issue ([read more about what this entails](https://stackoverflow.com/help/minimal-reproducible-example)). Failing this, any sort of reproduction steps are better than nothing!
validations:
required: true
- type: textarea
attributes:
label: 'Environment'
description: 'Please provide the following information about your environment.'
value: |
- Operating system and version:
- Device platform and version:
- Real device or emulator/simulator:
validations:
required: true
- type: input
attributes:
label: 'Logs'
description: |
Create a [Gist](https://gist.github.com) which contains your _full_ Stack Wallet logs and link it here.
:warning: _Remember to redact or remove any sensitive information!_
placeholder: 'https://gist.github.com/...'
validations:
required: false
- type: textarea
attributes:
label: 'Further Information'
description: |
Links? References? Anything that will give us more context about the issue you are encountering!
validations:
required: false
- type: markdown
attributes:
value: ':stop_sign: _For support questions, please visit the [Discord](https://discord.gg/mRPZuXx3At), [Telegram](https://t.me/stackwallet) or [Reddit](https://www.reddit.com/r/stackwallet/) instead._'

View file

@ -0,0 +1,37 @@
name: Feature request
description: Suggest an idea for this project
title: 'FR: <title>'
labels: [Feature Request]
body:
- type: textarea
attributes:
label: 'Is Problem'
description: 'Is your feature request related to a problem? Please describe.'
value: |
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
validations:
required: false
- type: textarea
attributes:
label: 'Solution'
description: 'Describe the solution you'd like.'
value: |
A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
attributes:
label: 'Alternatives'
description: 'Describe alternatives you've considered.'
value: |
A clear and concise description of any alternative solutions or features you've considered.
validations:
required: true
- type: textarea
attributes:
label: 'Context'
description: 'Additional context.'
value: |
Add any other context or screenshots about the feature request here.
validations:
required: false

View file

@ -4,7 +4,7 @@ Here you will find instructions on how to install the necessary tools for buildi
## Prerequisites
- The only OS supported for building is Ubuntu 20.04. Advanced users may also be able to build on other Debian-based distributions like Fedora 37.
- The only OS supported for building is Ubuntu 20.04. Advanced users may also be able to build on other Debian-based distributions like Linux Mint.
- Android setup ([Android Studio](https://developer.android.com/studio) and subsequent dependencies)
- 100 GB of storage
@ -54,6 +54,7 @@ sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2-
Install [Rust](https://www.rust-lang.org/tools/install) with command:
```
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.bashrc
rustup install 1.67.1
rustup default 1.67.1
```

View file

@ -458,7 +458,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 102;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 4DQKUWSG6C;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@ -512,7 +512,7 @@
"$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**",
"$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs",
);
MARKETING_VERSION = 1.5.28;
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -645,7 +645,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 102;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 4DQKUWSG6C;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@ -699,7 +699,7 @@
"$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**",
"$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs",
);
MARKETING_VERSION = 1.5.28;
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -724,7 +724,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 102;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 4DQKUWSG6C;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@ -778,7 +778,7 @@
"$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**",
"$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs",
);
MARKETING_VERSION = 1.5.28;
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet;
PRODUCT_NAME = "$(TARGET_NAME)";

View file

@ -2,6 +2,7 @@ import 'package:decimal/decimal.dart';
import 'package:flutter_native_splash/cli_commands.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/exceptions/main_db/main_db_exception.dart';
import 'package:stackwallet/models/isar/models/block_explorer.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/models/isar/stack_theme.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
@ -34,6 +35,7 @@ class MainDB {
AddressSchema,
AddressLabelSchema,
EthContractSchema,
TransactionBlockExplorerSchema,
StackThemeSchema,
],
directory: (await StackFileSystem.applicationIsarDirectory()).path,
@ -45,6 +47,25 @@ class MainDB {
return true;
}
// tx block explorers
TransactionBlockExplorer? getTransactionBlockExplorer({required Coin coin}) {
return isar.transactionBlockExplorers
.where()
.tickerEqualTo(coin.ticker)
.findFirstSync();
}
Future<int> putTransactionBlockExplorer(
TransactionBlockExplorer explorer) async {
try {
return await isar.writeTxn(() async {
return await isar.transactionBlockExplorers.put(explorer);
});
} catch (e) {
throw MainDBException("failed putTransactionBlockExplorer: $explorer", e);
}
}
// addresses
QueryBuilder<Address, Address, QAfterWhereClause> getAddresses(
String walletId) =>

View file

@ -0,0 +1,35 @@
import 'package:isar/isar.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
part 'block_explorer.g.dart';
@collection
class TransactionBlockExplorer {
TransactionBlockExplorer({
required this.ticker,
required this.url,
});
Id id = Isar.autoIncrement;
@Index(unique: true, replace: true)
late final String ticker;
late final String url;
@ignore
Coin? get coin {
try {
return coinFromTickerCaseInsensitive(ticker);
} catch (_) {
return null;
}
}
Uri? getUrlFor({required String txid}) => Uri.tryParse(
url.replaceFirst(
"%5BTXID%5D",
txid,
),
);
}

View file

@ -0,0 +1,764 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'block_explorer.dart';
// **************************************************************************
// IsarCollectionGenerator
// **************************************************************************
// coverage:ignore-file
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters
extension GetTransactionBlockExplorerCollection on Isar {
IsarCollection<TransactionBlockExplorer> get transactionBlockExplorers =>
this.collection();
}
const TransactionBlockExplorerSchema = CollectionSchema(
name: r'TransactionBlockExplorer',
id: 4209077296238413906,
properties: {
r'ticker': PropertySchema(
id: 0,
name: r'ticker',
type: IsarType.string,
),
r'url': PropertySchema(
id: 1,
name: r'url',
type: IsarType.string,
)
},
estimateSize: _transactionBlockExplorerEstimateSize,
serialize: _transactionBlockExplorerSerialize,
deserialize: _transactionBlockExplorerDeserialize,
deserializeProp: _transactionBlockExplorerDeserializeProp,
idName: r'id',
indexes: {
r'ticker': IndexSchema(
id: -8264639257510259247,
name: r'ticker',
unique: true,
replace: true,
properties: [
IndexPropertySchema(
name: r'ticker',
type: IndexType.hash,
caseSensitive: true,
)
],
)
},
links: {},
embeddedSchemas: {},
getId: _transactionBlockExplorerGetId,
getLinks: _transactionBlockExplorerGetLinks,
attach: _transactionBlockExplorerAttach,
version: '3.0.5',
);
int _transactionBlockExplorerEstimateSize(
TransactionBlockExplorer object,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
var bytesCount = offsets.last;
bytesCount += 3 + object.ticker.length * 3;
bytesCount += 3 + object.url.length * 3;
return bytesCount;
}
void _transactionBlockExplorerSerialize(
TransactionBlockExplorer object,
IsarWriter writer,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
writer.writeString(offsets[0], object.ticker);
writer.writeString(offsets[1], object.url);
}
TransactionBlockExplorer _transactionBlockExplorerDeserialize(
Id id,
IsarReader reader,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
final object = TransactionBlockExplorer(
ticker: reader.readString(offsets[0]),
url: reader.readString(offsets[1]),
);
object.id = id;
return object;
}
P _transactionBlockExplorerDeserializeProp<P>(
IsarReader reader,
int propertyId,
int offset,
Map<Type, List<int>> 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<IsarLinkBase<dynamic>> _transactionBlockExplorerGetLinks(
TransactionBlockExplorer object) {
return [];
}
void _transactionBlockExplorerAttach(
IsarCollection<dynamic> col, Id id, TransactionBlockExplorer object) {
object.id = id;
}
extension TransactionBlockExplorerByIndex
on IsarCollection<TransactionBlockExplorer> {
Future<TransactionBlockExplorer?> getByTicker(String ticker) {
return getByIndex(r'ticker', [ticker]);
}
TransactionBlockExplorer? getByTickerSync(String ticker) {
return getByIndexSync(r'ticker', [ticker]);
}
Future<bool> deleteByTicker(String ticker) {
return deleteByIndex(r'ticker', [ticker]);
}
bool deleteByTickerSync(String ticker) {
return deleteByIndexSync(r'ticker', [ticker]);
}
Future<List<TransactionBlockExplorer?>> getAllByTicker(
List<String> tickerValues) {
final values = tickerValues.map((e) => [e]).toList();
return getAllByIndex(r'ticker', values);
}
List<TransactionBlockExplorer?> getAllByTickerSync(
List<String> tickerValues) {
final values = tickerValues.map((e) => [e]).toList();
return getAllByIndexSync(r'ticker', values);
}
Future<int> deleteAllByTicker(List<String> tickerValues) {
final values = tickerValues.map((e) => [e]).toList();
return deleteAllByIndex(r'ticker', values);
}
int deleteAllByTickerSync(List<String> tickerValues) {
final values = tickerValues.map((e) => [e]).toList();
return deleteAllByIndexSync(r'ticker', values);
}
Future<Id> putByTicker(TransactionBlockExplorer object) {
return putByIndex(r'ticker', object);
}
Id putByTickerSync(TransactionBlockExplorer object, {bool saveLinks = true}) {
return putByIndexSync(r'ticker', object, saveLinks: saveLinks);
}
Future<List<Id>> putAllByTicker(List<TransactionBlockExplorer> objects) {
return putAllByIndex(r'ticker', objects);
}
List<Id> putAllByTickerSync(List<TransactionBlockExplorer> objects,
{bool saveLinks = true}) {
return putAllByIndexSync(r'ticker', objects, saveLinks: saveLinks);
}
}
extension TransactionBlockExplorerQueryWhereSort on QueryBuilder<
TransactionBlockExplorer, TransactionBlockExplorer, QWhere> {
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QAfterWhere>
anyId() {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(const IdWhereClause.any());
});
}
}
extension TransactionBlockExplorerQueryWhere on QueryBuilder<
TransactionBlockExplorer, TransactionBlockExplorer, QWhereClause> {
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterWhereClause> idEqualTo(Id id) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IdWhereClause.between(
lower: id,
upper: id,
));
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterWhereClause> 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<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterWhereClause> idGreaterThan(Id id, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: include),
);
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterWhereClause> idLessThan(Id id, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: include),
);
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterWhereClause> 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<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterWhereClause> tickerEqualTo(String ticker) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.equalTo(
indexName: r'ticker',
value: [ticker],
));
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterWhereClause> 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<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition> idEqualTo(Id value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'id',
value: value,
));
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition> idGreaterThan(
Id value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'id',
value: value,
));
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition> idLessThan(
Id value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'id',
value: value,
));
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition> 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<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition> tickerEqualTo(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'ticker',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition> 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<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition> 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<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition> 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<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition> tickerStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'ticker',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition> tickerEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'ticker',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition>
tickerContains(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'ticker',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition>
tickerMatches(String pattern, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'ticker',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition> tickerIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'ticker',
value: '',
));
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition> tickerIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
property: r'ticker',
value: '',
));
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition> urlEqualTo(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'url',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition> 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<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition> 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<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition> 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<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition> urlStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'url',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition> urlEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'url',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition>
urlContains(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'url',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition>
urlMatches(String pattern, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'url',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition> urlIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'url',
value: '',
));
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer,
QAfterFilterCondition> 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<TransactionBlockExplorer, TransactionBlockExplorer, QAfterSortBy>
sortByTicker() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'ticker', Sort.asc);
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QAfterSortBy>
sortByTickerDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'ticker', Sort.desc);
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QAfterSortBy>
sortByUrl() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'url', Sort.asc);
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QAfterSortBy>
sortByUrlDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'url', Sort.desc);
});
}
}
extension TransactionBlockExplorerQuerySortThenBy on QueryBuilder<
TransactionBlockExplorer, TransactionBlockExplorer, QSortThenBy> {
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QAfterSortBy>
thenById() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.asc);
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QAfterSortBy>
thenByIdDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.desc);
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QAfterSortBy>
thenByTicker() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'ticker', Sort.asc);
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QAfterSortBy>
thenByTickerDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'ticker', Sort.desc);
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QAfterSortBy>
thenByUrl() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'url', Sort.asc);
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QAfterSortBy>
thenByUrlDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'url', Sort.desc);
});
}
}
extension TransactionBlockExplorerQueryWhereDistinct on QueryBuilder<
TransactionBlockExplorer, TransactionBlockExplorer, QDistinct> {
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QDistinct>
distinctByTicker({bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'ticker', caseSensitive: caseSensitive);
});
}
QueryBuilder<TransactionBlockExplorer, TransactionBlockExplorer, QDistinct>
distinctByUrl({bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'url', caseSensitive: caseSensitive);
});
}
}
extension TransactionBlockExplorerQueryProperty on QueryBuilder<
TransactionBlockExplorer, TransactionBlockExplorer, QQueryProperty> {
QueryBuilder<TransactionBlockExplorer, int, QQueryOperations> idProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'id');
});
}
QueryBuilder<TransactionBlockExplorer, String, QQueryOperations>
tickerProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'ticker');
});
}
QueryBuilder<TransactionBlockExplorer, String, QQueryOperations>
urlProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'url');
});
}
}

View file

@ -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<StackColors>()!.highlight,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () {
Navigator.of(context).pushNamed(ChooseCoinView.routeName,
arguments: const Tuple3<String, String, String>(
"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,
),
],
),
),
),
),
],
),
),

View file

@ -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<ManageExplorerView> createState() => _ManageExplorerViewState();
}
class _ManageExplorerViewState extends ConsumerState<ManageExplorerView> {
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<StackColors>()!.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<StackColors>()!
.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),
),
),
),
)
],
),
),
),
);
}
}

View file

@ -94,7 +94,10 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
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<TransactionsList> {
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,

View file

@ -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

View file

@ -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<String, String, String>) {
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,

View file

@ -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)
) {

View file

@ -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<int> 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);
}
}

View file

@ -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<ChooseCoinView> createState() => _ChooseCoinViewState();
}
class _ChooseCoinViewState extends ConsumerState<ChooseCoinView> {
List<Coin> _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<Coin> coins = showTestNet
? _coins
: _coins.sublist(0, _coins.length - kTestNetCoinCount);
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.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<StackColors>()!.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),
),
],
)
],
),
),
),
),
);
},
),
],
),
),
),
),
);
}
}

View file

@ -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"