mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-03 17:29:23 +00:00
commit
920206e1f1
62 changed files with 4412 additions and 38 deletions
12
assets/svg/ordinal.svg
Normal file
12
assets/svg/ordinal.svg
Normal file
|
@ -0,0 +1,12 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="ordinal">
|
||||
<path id="Rectangle 43 (Stroke)" fill-rule="evenodd" clip-rule="evenodd" d="M4.99935 3.33366C4.07887 3.33366 3.33268 4.07985 3.33268 5.00033V15.0003C3.33268 15.9208 4.07887 16.667 4.99935 16.667H14.9993C15.9198 16.667 16.666 15.9208 16.666 15.0003V5.00033C16.666 4.07985 15.9198 3.33366 14.9993 3.33366H4.99935ZM1.66602 5.00033C1.66602 3.15938 3.1584 1.66699 4.99935 1.66699H14.9993C16.8403 1.66699 18.3327 3.15938 18.3327 5.00033V15.0003C18.3327 16.8413 16.8403 18.3337 14.9993 18.3337H4.99935C3.1584 18.3337 1.66602 16.8413 1.66602 15.0003V5.00033Z" fill="#8E9192"/>
|
||||
<circle id="Ellipse 76" cx="6.25" cy="6.25" r="1.25" fill="#8E9192"/>
|
||||
<g id="Vector">
|
||||
<path d="M6.89025 11.0396L5.11402 13.704C4.74482 14.2578 5.14181 14.9996 5.80739 14.9996H9.35986C10.0254 14.9996 10.4224 14.2578 10.0532 13.704L8.277 11.0396C7.94715 10.5449 7.2201 10.5449 6.89025 11.0396Z" fill="#8E9192"/>
|
||||
<path d="M10.5883 8.15695L7.76997 13.7936C7.49292 14.3476 7.89584 14.9996 8.51532 14.9996H14.1519C14.7714 14.9996 15.1743 14.3476 14.8973 13.7936L12.079 8.15694C11.7719 7.54274 10.8954 7.54274 10.5883 8.15695Z" fill="#8E9192"/>
|
||||
<path d="M6.89025 11.0396L5.11402 13.704C4.74482 14.2578 5.14181 14.9996 5.80739 14.9996H9.35986C10.0254 14.9996 10.4224 14.2578 10.0532 13.704L8.277 11.0396C7.94715 10.5449 7.2201 10.5449 6.89025 11.0396Z" stroke="#8E9192"/>
|
||||
<path d="M10.5883 8.15695L7.76997 13.7936C7.49292 14.3476 7.89584 14.9996 8.51532 14.9996H14.1519C14.7714 14.9996 15.1743 14.3476 14.8973 13.7936L12.079 8.15694C11.7719 7.54274 10.8954 7.54274 10.5883 8.15695Z" stroke="#8E9192"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
5
assets/svg/send.svg
Normal file
5
assets/svg/send.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="send">
|
||||
<path id="Vector" d="M9.16537 4.9832C9.16537 5.1919 9.04104 5.3807 8.84889 5.46272L1.55925 8.60772C1.49302 8.63625 1.4232 8.65001 1.35397 8.65001C1.21334 8.65001 1.07532 8.59295 0.974398 8.48615C0.824086 8.32692 0.789503 8.09076 0.887266 7.89478L2.08827 5.49311L6.03562 4.99794L2.08801 4.50221L0.887005 2.10053C0.789292 1.90488 0.823936 1.66862 0.974235 1.50949C1.12505 1.35026 1.35837 1.30161 1.55908 1.38797L8.86373 4.51851C9.05612 4.60004 9.16537 4.78917 9.16537 4.9832Z" fill="white"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 612 B |
|
@ -1,17 +0,0 @@
|
|||
FROM ubuntu:20.04 as base
|
||||
COPY . /stack_wallet
|
||||
WORKDIR /stack_wallet/scripts/linux
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y git=1:2.25.1-1ubuntu3.6 make=4.2.1-1.2 curl=7.68.0-1ubuntu2.14 cargo=0.62.0ubuntu0libgit2-0ubuntu0.20.04.1 \
|
||||
file=1:5.38-4 ca-certificates=20211016ubuntu0.20.04.1 cmake=3.16.3-1ubuntu1.20.04.1 cmake-data=3.16.3-1ubuntu1.20.04.1 g++=4:9.3.0-1ubuntu2 libgmp-dev=2:6.2.0+dfsg-4ubuntu0.1 libssl-dev=1.1.1f-1ubuntu2.16 \
|
||||
libclang-dev=1:10.0-50~exp1 unzip=6.0-25ubuntu1.1 python3=3.8.2-0ubuntu2 pkg-config=0.29.1-0ubuntu4 libglib2.0-dev=2.64.6-1~ubuntu20.04.4 libgcrypt20-dev=1.8.5-5ubuntu1.1 gettext-base=0.19.8.1-10build1 \
|
||||
libgirepository1.0-dev=1.64.1-1~ubuntu20.04.1 valac=0.48.6-0ubuntu1 xsltproc=1.1.34-4ubuntu0.20.04.1 docbook-xsl=1.79.1+dfsg-2 python3-pip=20.0.2-5ubuntu1.6 ninja-build=1.10.0-1build1 clang=1:10.0-50~exp1 \
|
||||
libgtk-3-dev=3.24.20-0ubuntu1.1 libunbound-dev=1.9.4-2ubuntu1.4 libzmq3-dev=4.3.2-2ubuntu1 libtool=2.4.6-14 autoconf=2.69-11.1 automake=1:1.16.1-4ubuntu6 bison=2:3.5.1+dfsg-1 \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& pip3 install --upgrade meson==0.64.1 markdown==3.4.1 markupsafe==2.1.1 jinja2==3.1.2 pygments==2.13.0 toml==0.10.2 typogrify==2.0.7 tomli==2.0.1 && cd .. && ./prebuild.sh && cd linux && ./build_all.sh
|
||||
WORKDIR /
|
||||
RUN git clone https://github.com/flutter/flutter.git -b 3.3.4
|
||||
ENV PATH "$PATH:/flutter/bin"
|
||||
WORKDIR /stack_wallet
|
||||
RUN flutter pub get Linux && flutter build linux
|
||||
ENTRYPOINT ["/bin/bash"]
|
|
@ -274,8 +274,12 @@ class DB {
|
|||
{required dynamic key, required String boxName}) async =>
|
||||
await mutex.protect(() async => await Hive.box<T>(boxName).delete(key));
|
||||
|
||||
Future<void> deleteAll<T>({required String boxName}) async =>
|
||||
await mutex.protect(() async => await Hive.box<T>(boxName).clear());
|
||||
Future<void> deleteAll<T>({required String boxName}) async {
|
||||
await mutex.protect(() async {
|
||||
final box = await Hive.openBox<T>(boxName);
|
||||
await box.clear();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> deleteBoxFromDisk({required String boxName}) async =>
|
||||
await mutex.protect(() async => await Hive.deleteBoxFromDisk(boxName));
|
||||
|
|
|
@ -15,6 +15,7 @@ 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/contact_entry.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/models/isar/ordinal.dart';
|
||||
import 'package:stackwallet/models/isar/stack_theme.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
@ -54,6 +55,7 @@ class MainDB {
|
|||
TransactionBlockExplorerSchema,
|
||||
StackThemeSchema,
|
||||
ContactEntrySchema,
|
||||
OrdinalSchema,
|
||||
LelantusCoinSchema,
|
||||
],
|
||||
directory: (await StackFileSystem.applicationIsarDirectory()).path,
|
||||
|
@ -247,7 +249,8 @@ class MainDB {
|
|||
await isar.utxos.putAll(utxos);
|
||||
});
|
||||
|
||||
Future<void> updateUTXOs(String walletId, List<UTXO> utxos) async {
|
||||
Future<bool> updateUTXOs(String walletId, List<UTXO> utxos) async {
|
||||
bool newUTXO = false;
|
||||
await isar.writeTxn(() async {
|
||||
final set = utxos.toSet();
|
||||
for (final utxo in utxos) {
|
||||
|
@ -269,12 +272,16 @@ class MainDB {
|
|||
blockHash: utxo.blockHash,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
newUTXO = true;
|
||||
}
|
||||
}
|
||||
|
||||
await isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await isar.utxos.putAll(set.toList());
|
||||
});
|
||||
|
||||
return newUTXO;
|
||||
}
|
||||
|
||||
Stream<UTXO?> watchUTXO({
|
||||
|
|
39
lib/dto/ordinals/address_inscription_response.dart
Normal file
39
lib/dto/ordinals/address_inscription_response.dart
Normal file
|
@ -0,0 +1,39 @@
|
|||
import 'package:stackwallet/dto/ordinals/litescribe_response.dart';
|
||||
import 'package:stackwallet/dto/ordinals/inscription_data.dart';
|
||||
|
||||
class AddressInscriptionResponse extends LitescribeResponse<AddressInscriptionResponse> {
|
||||
final int status;
|
||||
final String message;
|
||||
final AddressInscriptionResult result;
|
||||
|
||||
AddressInscriptionResponse({
|
||||
required this.status,
|
||||
required this.message,
|
||||
required this.result,
|
||||
});
|
||||
|
||||
factory AddressInscriptionResponse.fromJson(Map<String, dynamic> json) {
|
||||
return AddressInscriptionResponse(
|
||||
status: json['status'] as int,
|
||||
message: json['message'] as String,
|
||||
result: AddressInscriptionResult.fromJson(json['result'] as Map<String, dynamic>),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AddressInscriptionResult {
|
||||
final List<InscriptionData> list;
|
||||
final int total;
|
||||
|
||||
AddressInscriptionResult({
|
||||
required this.list,
|
||||
required this.total,
|
||||
});
|
||||
|
||||
factory AddressInscriptionResult.fromJson(Map<String, dynamic> json) {
|
||||
return AddressInscriptionResult(
|
||||
list: (json['list'] as List).map((item) => InscriptionData.fromJson(item as Map<String, dynamic>)).toList(),
|
||||
total: json['total'] as int,
|
||||
);
|
||||
}
|
||||
}
|
73
lib/dto/ordinals/inscription_data.dart
Normal file
73
lib/dto/ordinals/inscription_data.dart
Normal file
|
@ -0,0 +1,73 @@
|
|||
// inscription data from litescribe /address/inscriptions endpoint
|
||||
class InscriptionData {
|
||||
final String inscriptionId;
|
||||
final int inscriptionNumber;
|
||||
final String address;
|
||||
final String preview;
|
||||
final String content;
|
||||
final int contentLength;
|
||||
final String contentType;
|
||||
final String contentBody;
|
||||
final int timestamp;
|
||||
final String genesisTransaction;
|
||||
final String location;
|
||||
final String output;
|
||||
final int outputValue;
|
||||
final int offset;
|
||||
|
||||
InscriptionData({
|
||||
required this.inscriptionId,
|
||||
required this.inscriptionNumber,
|
||||
required this.address,
|
||||
required this.preview,
|
||||
required this.content,
|
||||
required this.contentLength,
|
||||
required this.contentType,
|
||||
required this.contentBody,
|
||||
required this.timestamp,
|
||||
required this.genesisTransaction,
|
||||
required this.location,
|
||||
required this.output,
|
||||
required this.outputValue,
|
||||
required this.offset,
|
||||
});
|
||||
|
||||
factory InscriptionData.fromJson(Map<String, dynamic> json) {
|
||||
return InscriptionData(
|
||||
inscriptionId: json['inscriptionId'] as String,
|
||||
inscriptionNumber: json['inscriptionNumber'] as int,
|
||||
address: json['address'] as String,
|
||||
preview: json['preview'] as String,
|
||||
content: json['content'] as String,
|
||||
contentLength: json['contentLength'] as int,
|
||||
contentType: json['contentType'] as String,
|
||||
contentBody: json['contentBody'] as String,
|
||||
timestamp: json['timestamp'] as int,
|
||||
genesisTransaction: json['genesisTransaction'] as String,
|
||||
location: json['location'] as String,
|
||||
output: json['output'] as String,
|
||||
outputValue: json['outputValue'] as int,
|
||||
offset: json['offset'] as int,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'InscriptionData {'
|
||||
' inscriptionId: $inscriptionId,'
|
||||
' inscriptionNumber: $inscriptionNumber,'
|
||||
' address: $address,'
|
||||
' preview: $preview,'
|
||||
' content: $content,'
|
||||
' contentLength: $contentLength,'
|
||||
' contentType: $contentType,'
|
||||
' contentBody: $contentBody,'
|
||||
' timestamp: $timestamp,'
|
||||
' genesisTransaction: $genesisTransaction,'
|
||||
' location: $location,'
|
||||
' output: $output,'
|
||||
' outputValue: $outputValue,'
|
||||
' offset: $offset'
|
||||
' }';
|
||||
}
|
||||
}
|
6
lib/dto/ordinals/litescribe_response.dart
Normal file
6
lib/dto/ordinals/litescribe_response.dart
Normal file
|
@ -0,0 +1,6 @@
|
|||
class LitescribeResponse<T> {
|
||||
final T? data;
|
||||
final String? error;
|
||||
|
||||
LitescribeResponse({this.data, this.error});
|
||||
}
|
89
lib/models/isar/ordinal.dart
Normal file
89
lib/models/isar/ordinal.dart
Normal file
|
@ -0,0 +1,89 @@
|
|||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/db/isar/main_db.dart';
|
||||
import 'package:stackwallet/dto/ordinals/inscription_data.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
|
||||
part 'ordinal.g.dart';
|
||||
|
||||
@collection
|
||||
class Ordinal {
|
||||
Id id = Isar.autoIncrement;
|
||||
|
||||
final String walletId;
|
||||
|
||||
@Index(unique: true, replace: true, composite: [
|
||||
CompositeIndex("utxoTXID"),
|
||||
CompositeIndex("utxoVOUT"),
|
||||
])
|
||||
final String inscriptionId;
|
||||
|
||||
final int inscriptionNumber;
|
||||
|
||||
final String content;
|
||||
|
||||
// following two are used to look up the UTXO object in isar combined w/ walletId
|
||||
final String utxoTXID;
|
||||
final int utxoVOUT;
|
||||
|
||||
Ordinal({
|
||||
required this.walletId,
|
||||
required this.inscriptionId,
|
||||
required this.inscriptionNumber,
|
||||
required this.content,
|
||||
required this.utxoTXID,
|
||||
required this.utxoVOUT,
|
||||
});
|
||||
|
||||
factory Ordinal.fromInscriptionData(InscriptionData data, String walletId) {
|
||||
return Ordinal(
|
||||
walletId: walletId,
|
||||
inscriptionId: data.inscriptionId,
|
||||
inscriptionNumber: data.inscriptionNumber,
|
||||
content: data.content,
|
||||
utxoTXID: data.output.split(':')[
|
||||
0], // "output": "062f32e21aa04246b8873b5d9a929576addd0339881e1ea478b406795d6b6c47:0"
|
||||
utxoVOUT: int.parse(data.output.split(':')[1]),
|
||||
);
|
||||
}
|
||||
|
||||
Ordinal copyWith({
|
||||
String? walletId,
|
||||
String? inscriptionId,
|
||||
int? inscriptionNumber,
|
||||
String? content,
|
||||
String? utxoTXID,
|
||||
int? utxoVOUT,
|
||||
}) {
|
||||
return Ordinal(
|
||||
walletId: walletId ?? this.walletId,
|
||||
inscriptionId: inscriptionId ?? this.inscriptionId,
|
||||
inscriptionNumber: inscriptionNumber ?? this.inscriptionNumber,
|
||||
content: content ?? this.content,
|
||||
utxoTXID: utxoTXID ?? this.utxoTXID,
|
||||
utxoVOUT: utxoVOUT ?? this.utxoVOUT,
|
||||
);
|
||||
}
|
||||
|
||||
UTXO? getUTXO(MainDB db) {
|
||||
return db.isar.utxos
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.txidEqualTo(utxoTXID)
|
||||
.and()
|
||||
.voutEqualTo(utxoVOUT)
|
||||
.findFirstSync();
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Ordinal {'
|
||||
' walletId: $walletId,'
|
||||
' inscriptionId: $inscriptionId,'
|
||||
' inscriptionNumber: $inscriptionNumber,'
|
||||
' content: $content,'
|
||||
' utxoTXID: $utxoTXID,'
|
||||
' utxoVOUT: $utxoVOUT'
|
||||
' }';
|
||||
}
|
||||
}
|
1489
lib/models/isar/ordinal.g.dart
Normal file
1489
lib/models/isar/ordinal.g.dart
Normal file
File diff suppressed because it is too large
Load diff
267
lib/pages/ordinals/ordinal_details_view.dart
Normal file
267
lib/pages/ordinals/ordinal_details_view.dart
Normal file
|
@ -0,0 +1,267 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:stackwallet/models/isar/ordinal.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/ordinals/widgets/dialogs.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.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';
|
||||
|
||||
class OrdinalDetailsView extends StatefulWidget {
|
||||
const OrdinalDetailsView({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
required this.ordinal,
|
||||
}) : super(key: key);
|
||||
|
||||
final String walletId;
|
||||
final Ordinal ordinal;
|
||||
|
||||
static const routeName = "/ordinalDetailsView";
|
||||
|
||||
@override
|
||||
State<OrdinalDetailsView> createState() => _OrdinalDetailsViewState();
|
||||
}
|
||||
|
||||
class _OrdinalDetailsViewState extends State<OrdinalDetailsView> {
|
||||
static const _spacing = 12.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Background(
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
leading: const AppBarBackButton(),
|
||||
title: Text(
|
||||
"Ordinal details",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12,
|
||||
horizontal: 39,
|
||||
),
|
||||
child: _OrdinalImageGroup(
|
||||
ordinal: widget.ordinal,
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
),
|
||||
_DetailsItemWCopy(
|
||||
title: "Inscription number",
|
||||
data: widget.ordinal.inscriptionNumber.toString(),
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
_DetailsItemWCopy(
|
||||
title: "ID",
|
||||
data: widget.ordinal.inscriptionId,
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
// todo: add utxo status
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
const _DetailsItemWCopy(
|
||||
title: "Amount",
|
||||
data: "TODO", // TODO infer from utxo utxoTXID:utxoVOUT
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
const _DetailsItemWCopy(
|
||||
title: "Owner address",
|
||||
data: "TODO", // infer from address associated w utxoTXID
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
_DetailsItemWCopy(
|
||||
title: "Transaction ID",
|
||||
data: widget.ordinal.utxoTXID,
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DetailsItemWCopy extends StatelessWidget {
|
||||
const _DetailsItemWCopy({
|
||||
Key? key,
|
||||
required this.title,
|
||||
required this.data,
|
||||
}) : super(key: key);
|
||||
|
||||
final String title;
|
||||
final String data;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RoundedWhiteContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
await Clipboard.setData(ClipboardData(text: data));
|
||||
if (context.mounted) {
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.info,
|
||||
message: "Copied to clipboard",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.copy,
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.infoItemIcons,
|
||||
width: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
SelectableText(
|
||||
data,
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _OrdinalImageGroup extends StatelessWidget {
|
||||
const _OrdinalImageGroup({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
required this.ordinal,
|
||||
}) : super(key: key);
|
||||
|
||||
final String walletId;
|
||||
final Ordinal ordinal;
|
||||
|
||||
static const _spacing = 12.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// Text(
|
||||
// "${ordinal.inscriptionId}", // Use any other property you want
|
||||
// style: STextStyles.w600_16(context),
|
||||
// ),
|
||||
// const SizedBox(
|
||||
// height: _spacing,
|
||||
// ),
|
||||
AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Image.network(
|
||||
ordinal.content, // Use the preview URL as the image source
|
||||
fit: BoxFit.cover,
|
||||
filterQuality:
|
||||
FilterQuality.none, // Set the filter mode to nearest
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Download",
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.arrowDown,
|
||||
width: 10,
|
||||
height: 12,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextSecondary,
|
||||
),
|
||||
buttonHeight: ButtonHeight.l,
|
||||
iconSpacing: 4,
|
||||
onPressed: () {
|
||||
// TODO: save and download image to device
|
||||
},
|
||||
),
|
||||
),
|
||||
// const SizedBox(
|
||||
// width: _spacing,
|
||||
// ),
|
||||
// Expanded(
|
||||
// child: PrimaryButton(
|
||||
// label: "Send",
|
||||
// icon: SvgPicture.asset(
|
||||
// Assets.svg.send,
|
||||
// width: 10,
|
||||
// height: 10,
|
||||
// color: Theme.of(context)
|
||||
// .extension<StackColors>()!
|
||||
// .buttonTextPrimary,
|
||||
// ),
|
||||
// buttonHeight: ButtonHeight.l,
|
||||
// iconSpacing: 4,
|
||||
// onPressed: () async {
|
||||
// final response = await showDialog<String?>(
|
||||
// context: context,
|
||||
// builder: (_) => const SendOrdinalUnfreezeDialog(),
|
||||
// );
|
||||
// if (response == "unfreeze") {
|
||||
// // TODO: unfreeze and go to send ord screen
|
||||
// }
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
889
lib/pages/ordinals/ordinals_filter_view.dart
Normal file
889
lib/pages/ordinals/ordinals_filter_view.dart
Normal file
|
@ -0,0 +1,889 @@
|
|||
/*
|
||||
* This file is part of Stack Wallet.
|
||||
*
|
||||
* Copyright (c) 2023 Cypher Stack
|
||||
* All Rights Reserved.
|
||||
* The code is distributed under GPLv3 license, see LICENSE file for details.
|
||||
* Generated by Cypher Stack on 2023-05-26
|
||||
*
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_rounded_date_picker/flutter_rounded_date_picker.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/themes/theme_providers.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/format.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
||||
|
||||
class OrdinalFilter {
|
||||
// final bool isMoonbird;
|
||||
// final bool isPunk;
|
||||
final DateTime? from;
|
||||
final DateTime? to;
|
||||
final String? inscription;
|
||||
final String keyword;
|
||||
|
||||
OrdinalFilter({
|
||||
// required this.isMoonbird,
|
||||
// required this.isPunk,
|
||||
required this.from,
|
||||
required this.to,
|
||||
required this.inscription,
|
||||
required this.keyword,
|
||||
});
|
||||
|
||||
OrdinalFilter copyWith({
|
||||
// bool? isMoonbird,
|
||||
// bool? isPunk,
|
||||
DateTime? from,
|
||||
DateTime? to,
|
||||
String? inscription,
|
||||
String? keyword,
|
||||
}) {
|
||||
return OrdinalFilter(
|
||||
// isMoonbird: isMoonbird ?? this.isMoonbird,
|
||||
// isPunk: isPunk ?? this.isPunk,
|
||||
from: from ?? this.from,
|
||||
to: to ?? this.to,
|
||||
inscription: inscription ?? this.inscription,
|
||||
keyword: keyword ?? this.keyword,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final ordinalFilterProvider = StateProvider<OrdinalFilter?>((_) => null);
|
||||
|
||||
class OrdinalsFilterView extends ConsumerStatefulWidget {
|
||||
const OrdinalsFilterView({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
static const String routeName = "/ordinalsFilterView";
|
||||
|
||||
@override
|
||||
ConsumerState<OrdinalsFilterView> createState() => _OrdinalsFilterViewState();
|
||||
}
|
||||
|
||||
class _OrdinalsFilterViewState extends ConsumerState<OrdinalsFilterView> {
|
||||
final _inscriptionTextEditingController = TextEditingController();
|
||||
final _keywordTextEditingController = TextEditingController();
|
||||
|
||||
// bool _isPunk = false;
|
||||
// bool _isMoonbird = false;
|
||||
|
||||
String _fromDateString = "";
|
||||
String _toDateString = "";
|
||||
|
||||
final keywordTextFieldFocusNode = FocusNode();
|
||||
final inscriptionTextFieldFocusNode = FocusNode();
|
||||
|
||||
late Color baseColor;
|
||||
|
||||
@override
|
||||
initState() {
|
||||
baseColor = ref.read(themeProvider.state).state.textSubtitle2;
|
||||
final filterState = ref.read(ordinalFilterProvider.state).state;
|
||||
if (filterState != null) {
|
||||
// _isMoonbird = filterState.isMoonbird;
|
||||
// _isPunk = filterState.isPunk;
|
||||
_selectedToDate = filterState.to;
|
||||
_selectedFromDate = filterState.from;
|
||||
_keywordTextEditingController.text = filterState.keyword;
|
||||
_inscriptionTextEditingController.text = filterState.inscription ?? "";
|
||||
}
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
dispose() {
|
||||
_inscriptionTextEditingController.dispose();
|
||||
_keywordTextEditingController.dispose();
|
||||
keywordTextFieldFocusNode.dispose();
|
||||
inscriptionTextFieldFocusNode.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// The following two getters are not required if the
|
||||
// date fields are to remain unclearable.
|
||||
Widget get _dateFromText {
|
||||
final isDateSelected = _fromDateString.isEmpty;
|
||||
return Text(
|
||||
isDateSelected ? "From..." : _fromDateString,
|
||||
style: STextStyles.fieldLabel(context).copyWith(
|
||||
color: isDateSelected
|
||||
? Theme.of(context).extension<StackColors>()!.textSubtitle2
|
||||
: Theme.of(context).extension<StackColors>()!.accentColorDark),
|
||||
);
|
||||
}
|
||||
|
||||
Widget get _dateToText {
|
||||
final isDateSelected = _toDateString.isEmpty;
|
||||
return Text(
|
||||
isDateSelected ? "To..." : _toDateString,
|
||||
style: STextStyles.fieldLabel(context).copyWith(
|
||||
color: isDateSelected
|
||||
? Theme.of(context).extension<StackColors>()!.textSubtitle2
|
||||
: Theme.of(context).extension<StackColors>()!.accentColorDark),
|
||||
);
|
||||
}
|
||||
|
||||
DateTime? _selectedFromDate = DateTime(2007);
|
||||
DateTime? _selectedToDate = DateTime.now();
|
||||
|
||||
MaterialRoundedDatePickerStyle _buildDatePickerStyle() {
|
||||
return MaterialRoundedDatePickerStyle(
|
||||
backgroundPicker: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
// backgroundHeader: Theme.of(context).extension<StackColors>()!.textSubtitle2,
|
||||
paddingMonthHeader: const EdgeInsets.only(top: 11),
|
||||
colorArrowNext: Theme.of(context).extension<StackColors>()!.textSubtitle1,
|
||||
colorArrowPrevious:
|
||||
Theme.of(context).extension<StackColors>()!.textSubtitle1,
|
||||
textStyleButtonNegative: STextStyles.datePicker600(context).copyWith(
|
||||
color: baseColor,
|
||||
),
|
||||
textStyleButtonPositive: STextStyles.datePicker600(context).copyWith(
|
||||
color: baseColor,
|
||||
),
|
||||
textStyleCurrentDayOnCalendar: STextStyles.datePicker400(context),
|
||||
textStyleDayHeader: STextStyles.datePicker600(context),
|
||||
textStyleDayOnCalendar: STextStyles.datePicker400(context).copyWith(
|
||||
color: baseColor,
|
||||
),
|
||||
textStyleDayOnCalendarDisabled:
|
||||
STextStyles.datePicker400(context).copyWith(
|
||||
color: Theme.of(context).extension<StackColors>()!.textSubtitle3,
|
||||
),
|
||||
textStyleDayOnCalendarSelected:
|
||||
STextStyles.datePicker400(context).copyWith(
|
||||
color: Theme.of(context).extension<StackColors>()!.textWhite,
|
||||
),
|
||||
textStyleMonthYearHeader: STextStyles.datePicker600(context).copyWith(
|
||||
color: Theme.of(context).extension<StackColors>()!.textSubtitle1,
|
||||
),
|
||||
textStyleYearButton: STextStyles.datePicker600(context).copyWith(
|
||||
color: Theme.of(context).extension<StackColors>()!.textWhite,
|
||||
),
|
||||
// textStyleButtonAction: GoogleFonts.inter(),
|
||||
);
|
||||
}
|
||||
|
||||
MaterialRoundedYearPickerStyle _buildYearPickerStyle() {
|
||||
return MaterialRoundedYearPickerStyle(
|
||||
backgroundPicker: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
textStyleYear: STextStyles.datePicker600(context).copyWith(
|
||||
color: Theme.of(context).extension<StackColors>()!.textSubtitle2,
|
||||
fontSize: 16,
|
||||
),
|
||||
textStyleYearSelected: STextStyles.datePicker600(context).copyWith(
|
||||
fontSize: 18,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDateRangePicker() {
|
||||
const middleSeparatorPadding = 2.0;
|
||||
const middleSeparatorWidth = 12.0;
|
||||
final isDesktop = Util.isDesktop;
|
||||
|
||||
final width = isDesktop
|
||||
? null
|
||||
: (MediaQuery.of(context).size.width -
|
||||
(middleSeparatorWidth +
|
||||
(2 * middleSeparatorPadding) +
|
||||
(2 * Constants.size.standardPadding))) /
|
||||
2;
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
key: const Key("OrdinalsViewFromDatePickerKey"),
|
||||
onTap: () async {
|
||||
final color =
|
||||
Theme.of(context).extension<StackColors>()!.accentColorDark;
|
||||
final height = MediaQuery.of(context).size.height;
|
||||
// check and hide keyboard
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 125));
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
final date = await showRoundedDatePicker(
|
||||
// This doesn't change statusbar color...
|
||||
// background: CFColors.starryNight.withOpacity(0.8),
|
||||
context: context,
|
||||
initialDate: DateTime.now(),
|
||||
height: height * 0.5,
|
||||
theme: ThemeData(
|
||||
primarySwatch: Util.createMaterialColor(
|
||||
color,
|
||||
),
|
||||
),
|
||||
//TODO pick a better initial date
|
||||
// 2007 chosen as that is just before bitcoin launched
|
||||
firstDate: DateTime(2007),
|
||||
lastDate: DateTime.now(),
|
||||
borderRadius: Constants.size.circularBorderRadius * 2,
|
||||
|
||||
textPositiveButton: "SELECT",
|
||||
|
||||
styleDatePicker: _buildDatePickerStyle(),
|
||||
styleYearPicker: _buildYearPickerStyle(),
|
||||
);
|
||||
if (date != null) {
|
||||
_selectedFromDate = date;
|
||||
|
||||
// flag to adjust date so from date is always before to date
|
||||
final flag = _selectedToDate != null &&
|
||||
!_selectedFromDate!.isBefore(_selectedToDate!);
|
||||
if (flag) {
|
||||
_selectedToDate = DateTime.fromMillisecondsSinceEpoch(
|
||||
_selectedFromDate!.millisecondsSinceEpoch);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
if (flag) {
|
||||
_toDateString = _selectedToDate == null
|
||||
? ""
|
||||
: Format.formatDate(_selectedToDate!);
|
||||
}
|
||||
_fromDateString = _selectedFromDate == null
|
||||
? ""
|
||||
: Format.formatDate(_selectedFromDate!);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: width,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
borderRadius:
|
||||
BorderRadius.circular(Constants.size.circularBorderRadius),
|
||||
border: Border.all(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: isDesktop ? 17 : 12,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.calendar,
|
||||
height: 20,
|
||||
width: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle2,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FittedBox(
|
||||
child: _dateFromText,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: middleSeparatorPadding),
|
||||
child: Container(
|
||||
width: middleSeparatorWidth,
|
||||
// height: 1,
|
||||
// color: CFColors.smoke,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
key: const Key("OrdinalsViewToDatePickerKey"),
|
||||
onTap: () async {
|
||||
final color =
|
||||
Theme.of(context).extension<StackColors>()!.accentColorDark;
|
||||
final height = MediaQuery.of(context).size.height;
|
||||
// check and hide keyboard
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 125));
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
final date = await showRoundedDatePicker(
|
||||
// This doesn't change statusbar color...
|
||||
// background: CFColors.starryNight.withOpacity(0.8),
|
||||
context: context,
|
||||
height: height * 0.5,
|
||||
theme: ThemeData(
|
||||
primarySwatch: Util.createMaterialColor(
|
||||
color,
|
||||
),
|
||||
),
|
||||
//TODO pick a better initial date
|
||||
// 2007 chosen as that is just before bitcoin launched
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime(2007),
|
||||
lastDate: DateTime.now(),
|
||||
borderRadius: Constants.size.circularBorderRadius * 2,
|
||||
|
||||
textPositiveButton: "SELECT",
|
||||
|
||||
styleDatePicker: _buildDatePickerStyle(),
|
||||
styleYearPicker: _buildYearPickerStyle(),
|
||||
);
|
||||
if (date != null) {
|
||||
_selectedToDate = date;
|
||||
|
||||
// flag to adjust date so from date is always before to date
|
||||
final flag = _selectedFromDate != null &&
|
||||
!_selectedToDate!.isAfter(_selectedFromDate!);
|
||||
if (flag) {
|
||||
_selectedFromDate = DateTime.fromMillisecondsSinceEpoch(
|
||||
_selectedToDate!.millisecondsSinceEpoch);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
if (flag) {
|
||||
_fromDateString = _selectedFromDate == null
|
||||
? ""
|
||||
: Format.formatDate(_selectedFromDate!);
|
||||
}
|
||||
_toDateString = _selectedToDate == null
|
||||
? ""
|
||||
: Format.formatDate(_selectedToDate!);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: width,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
borderRadius:
|
||||
BorderRadius.circular(Constants.size.circularBorderRadius),
|
||||
border: Border.all(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: isDesktop ? 17 : 12,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.calendar,
|
||||
height: 20,
|
||||
width: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle2,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FittedBox(
|
||||
child: _dateToText,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isDesktop)
|
||||
const SizedBox(
|
||||
width: 24,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (Util.isDesktop) {
|
||||
return DesktopDialog(
|
||||
maxWidth: 576,
|
||||
maxHeight: double.infinity,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
bottom: 32,
|
||||
),
|
||||
child: _buildContent(context),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () async {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 75));
|
||||
}
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
"Ordinals filter",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
),
|
||||
body: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: Constants.size.standardPadding,
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
BoxConstraints(minHeight: constraints.maxHeight),
|
||||
child: IntrinsicHeight(
|
||||
child: _buildContent(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildContent(BuildContext context) {
|
||||
final isDesktop = Util.isDesktop;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
if (isDesktop)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Ordinals filter",
|
||||
style: STextStyles.desktopH3(context),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const DesktopDialogCloseButton(),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 14 : 10,
|
||||
),
|
||||
// if (!isDesktop)
|
||||
// Align(
|
||||
// alignment: Alignment.centerLeft,
|
||||
// child: FittedBox(
|
||||
// child: Text(
|
||||
// "Collection",
|
||||
// style: STextStyles.smallMed12(context),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// if (!isDesktop)
|
||||
// const SizedBox(
|
||||
// height: 12,
|
||||
// ),
|
||||
// RoundedWhiteContainer(
|
||||
// padding: EdgeInsets.all(isDesktop ? 0 : 12),
|
||||
// child: Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// Row(
|
||||
// children: [
|
||||
// GestureDetector(
|
||||
// onTap: () {
|
||||
// setState(() {
|
||||
// _isPunk = !_isPunk;
|
||||
// });
|
||||
// },
|
||||
// child: Container(
|
||||
// color: Colors.transparent,
|
||||
// child: Row(
|
||||
// children: [
|
||||
// SizedBox(
|
||||
// height: 20,
|
||||
// width: 20,
|
||||
// child: Checkbox(
|
||||
// key: const Key("OrdinalsPunkCheckboxKey"),
|
||||
// materialTapTargetSize:
|
||||
// MaterialTapTargetSize.shrinkWrap,
|
||||
// value: _isPunk,
|
||||
// onChanged: (newValue) {
|
||||
// setState(() {
|
||||
// _isPunk = newValue!;
|
||||
// });
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(
|
||||
// width: 14,
|
||||
// ),
|
||||
// Align(
|
||||
// alignment: Alignment.centerLeft,
|
||||
// child: FittedBox(
|
||||
// child: Column(
|
||||
// children: [
|
||||
// Text(
|
||||
// "Punks",
|
||||
// style: isDesktop
|
||||
// ? STextStyles.desktopTextSmall(context)
|
||||
// : STextStyles.itemSubtitle12(context),
|
||||
// ),
|
||||
// if (isDesktop)
|
||||
// const SizedBox(
|
||||
// height: 4,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// SizedBox(
|
||||
// height: isDesktop ? 4 : 10,
|
||||
// ),
|
||||
// Row(
|
||||
// children: [
|
||||
// GestureDetector(
|
||||
// onTap: () {
|
||||
// setState(() {
|
||||
// _isMoonbird = !_isMoonbird;
|
||||
// });
|
||||
// },
|
||||
// child: Container(
|
||||
// color: Colors.transparent,
|
||||
// child: Row(
|
||||
// children: [
|
||||
// SizedBox(
|
||||
// height: 20,
|
||||
// width: 20,
|
||||
// child: Checkbox(
|
||||
// key: const Key(
|
||||
// "OrdinalsFilterMoonbirdCheckboxKey",
|
||||
// ),
|
||||
// materialTapTargetSize:
|
||||
// MaterialTapTargetSize.shrinkWrap,
|
||||
// value: _isMoonbird,
|
||||
// onChanged: (newValue) {
|
||||
// setState(() {
|
||||
// _isMoonbird = newValue!;
|
||||
// });
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(
|
||||
// width: 14,
|
||||
// ),
|
||||
// Align(
|
||||
// alignment: Alignment.centerLeft,
|
||||
// child: FittedBox(
|
||||
// child: Column(
|
||||
// children: [
|
||||
// Text(
|
||||
// "Moonbirds",
|
||||
// style: isDesktop
|
||||
// ? STextStyles.desktopTextSmall(context)
|
||||
// : STextStyles.itemSubtitle12(context),
|
||||
// ),
|
||||
// if (isDesktop)
|
||||
// const SizedBox(
|
||||
// height: 4,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// SizedBox(
|
||||
// height: isDesktop ? 32 : 24,
|
||||
// ),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FittedBox(
|
||||
child: Text(
|
||||
"Date",
|
||||
style: isDesktop
|
||||
? STextStyles.labelExtraExtraSmall(context)
|
||||
: STextStyles.smallMed12(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 10 : 8,
|
||||
),
|
||||
_buildDateRangePicker(),
|
||||
SizedBox(
|
||||
height: isDesktop ? 32 : 24,
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FittedBox(
|
||||
child: Text(
|
||||
"Inscription",
|
||||
style: isDesktop
|
||||
? STextStyles.labelExtraExtraSmall(context)
|
||||
: STextStyles.smallMed12(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 10 : 8,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: isDesktop ? 32 : 0),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
autocorrect: Util.isDesktop ? false : true,
|
||||
enableSuggestions: Util.isDesktop ? false : true,
|
||||
key: const Key("OrdinalsInscriptionFieldKey"),
|
||||
controller: _inscriptionTextEditingController,
|
||||
focusNode: inscriptionTextFieldFocusNode,
|
||||
onChanged: (_) => setState(() {}),
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.textDark,
|
||||
height: 1.8,
|
||||
)
|
||||
: STextStyles.field(context),
|
||||
decoration: standardInputDecoration(
|
||||
"Enter inscription number...",
|
||||
keywordTextFieldFocusNode,
|
||||
context,
|
||||
desktopMed: isDesktop,
|
||||
).copyWith(
|
||||
contentPadding: isDesktop
|
||||
? const EdgeInsets.symmetric(
|
||||
vertical: 10,
|
||||
horizontal: 16,
|
||||
)
|
||||
: null,
|
||||
suffixIcon: _inscriptionTextEditingController.text.isNotEmpty
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(right: 0),
|
||||
child: UnconstrainedBox(
|
||||
child: Row(
|
||||
children: [
|
||||
TextFieldIconButton(
|
||||
child: const XIcon(),
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
_inscriptionTextEditingController.text = "";
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 32 : 24,
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FittedBox(
|
||||
child: Text(
|
||||
"Keyword",
|
||||
style: isDesktop
|
||||
? STextStyles.labelExtraExtraSmall(context)
|
||||
: STextStyles.smallMed12(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 10 : 8,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: isDesktop ? 32 : 0),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
autocorrect: Util.isDesktop ? false : true,
|
||||
enableSuggestions: Util.isDesktop ? false : true,
|
||||
key: const Key("OrdinalsViewKeywordFieldKey"),
|
||||
controller: _keywordTextEditingController,
|
||||
focusNode: keywordTextFieldFocusNode,
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.textDark,
|
||||
height: 1.8,
|
||||
)
|
||||
: STextStyles.field(context),
|
||||
onChanged: (_) => setState(() {}),
|
||||
decoration: standardInputDecoration(
|
||||
"Type keyword...",
|
||||
keywordTextFieldFocusNode,
|
||||
context,
|
||||
desktopMed: isDesktop,
|
||||
).copyWith(
|
||||
contentPadding: isDesktop
|
||||
? const EdgeInsets.symmetric(
|
||||
vertical: 10,
|
||||
horizontal: 16,
|
||||
)
|
||||
: null,
|
||||
suffixIcon: _keywordTextEditingController.text.isNotEmpty
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(right: 0),
|
||||
child: UnconstrainedBox(
|
||||
child: Row(
|
||||
children: [
|
||||
TextFieldIconButton(
|
||||
child: const XIcon(),
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
_keywordTextEditingController.text = "";
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!isDesktop) const Spacer(),
|
||||
SizedBox(
|
||||
height: isDesktop ? 32 : 20,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Cancel",
|
||||
buttonHeight: isDesktop ? ButtonHeight.l : null,
|
||||
onPressed: () async {
|
||||
if (!isDesktop) {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(
|
||||
milliseconds: 75,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
buttonHeight: isDesktop ? ButtonHeight.l : null,
|
||||
onPressed: () async {
|
||||
await _onApplyPressed();
|
||||
},
|
||||
label: "Save",
|
||||
),
|
||||
),
|
||||
if (isDesktop)
|
||||
const SizedBox(
|
||||
width: 32,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (!isDesktop)
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onApplyPressed() async {
|
||||
final filter = OrdinalFilter(
|
||||
// isPunk: _isPunk,
|
||||
// isMoonbird: _isMoonbird,
|
||||
from: _selectedFromDate,
|
||||
to: _selectedToDate,
|
||||
inscription: _inscriptionTextEditingController.text,
|
||||
keyword: _keywordTextEditingController.text,
|
||||
);
|
||||
|
||||
ref.read(ordinalFilterProvider.state).state = filter;
|
||||
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
202
lib/pages/ordinals/ordinals_view.dart
Normal file
202
lib/pages/ordinals/ordinals_view.dart
Normal file
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* This file is part of Stack Wallet.
|
||||
*
|
||||
* Copyright (c) 2023 Cypher Stack
|
||||
* All Rights Reserved.
|
||||
* The code is distributed under GPLv3 license, see LICENSE file for details.
|
||||
* Generated by Cypher Stack on 2023-05-26
|
||||
*
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/pages/ordinals/widgets/ordinals_list.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/services/mixins/ordinals_interface.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/show_loading.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
|
||||
class OrdinalsView extends ConsumerStatefulWidget {
|
||||
const OrdinalsView({
|
||||
super.key,
|
||||
required this.walletId,
|
||||
});
|
||||
|
||||
static const routeName = "/ordinalsView";
|
||||
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
ConsumerState<OrdinalsView> createState() => _OrdinalsViewState();
|
||||
}
|
||||
|
||||
class _OrdinalsViewState extends ConsumerState<OrdinalsView> {
|
||||
late final TextEditingController searchController;
|
||||
late final FocusNode searchFocus;
|
||||
|
||||
String _searchTerm = "";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
searchController = TextEditingController();
|
||||
searchFocus = FocusNode();
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
searchController.dispose();
|
||||
searchFocus.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Background(
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
leading: const AppBarBackButton(),
|
||||
title: Text(
|
||||
"Ordinals",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
titleSpacing: 0,
|
||||
actions: [
|
||||
AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: AppBarIconButton(
|
||||
size: 36,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.arrowRotate,
|
||||
width: 20,
|
||||
height: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.topNavIconPrimary,
|
||||
),
|
||||
onPressed: () async {
|
||||
// show loading for a minimum of 2 seconds on refreshing
|
||||
await showLoading(
|
||||
whileFuture: Future.wait<void>([
|
||||
Future.delayed(const Duration(seconds: 2)),
|
||||
(ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId)
|
||||
.wallet as OrdinalsInterface)
|
||||
.refreshInscriptions()
|
||||
]),
|
||||
context: context,
|
||||
message: "Refreshing...",
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
// AspectRatio(
|
||||
// aspectRatio: 1,
|
||||
// child: AppBarIconButton(
|
||||
// size: 36,
|
||||
// icon: SvgPicture.asset(
|
||||
// Assets.svg.filter,
|
||||
// width: 20,
|
||||
// height: 20,
|
||||
// color: Theme.of(context)
|
||||
// .extension<StackColors>()!
|
||||
// .topNavIconPrimary,
|
||||
// ),
|
||||
// onPressed: () {
|
||||
// Navigator.of(context).pushNamed(
|
||||
// OrdinalsFilterView.routeName,
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
top: 8,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// ClipRRect(
|
||||
// borderRadius: BorderRadius.circular(
|
||||
// Constants.size.circularBorderRadius,
|
||||
// ),
|
||||
// child: TextField(
|
||||
// autocorrect: Util.isDesktop ? false : true,
|
||||
// enableSuggestions: Util.isDesktop ? false : true,
|
||||
// controller: searchController,
|
||||
// focusNode: searchFocus,
|
||||
// onChanged: (value) {
|
||||
// setState(() {
|
||||
// _searchTerm = value;
|
||||
// });
|
||||
// },
|
||||
// style: STextStyles.field(context),
|
||||
// decoration: standardInputDecoration(
|
||||
// "Search",
|
||||
// searchFocus,
|
||||
// context,
|
||||
// ).copyWith(
|
||||
// prefixIcon: Padding(
|
||||
// padding: const EdgeInsets.symmetric(
|
||||
// horizontal: 10,
|
||||
// vertical: 16,
|
||||
// ),
|
||||
// child: SvgPicture.asset(
|
||||
// Assets.svg.search,
|
||||
// width: 16,
|
||||
// height: 16,
|
||||
// ),
|
||||
// ),
|
||||
// suffixIcon: searchController.text.isNotEmpty
|
||||
// ? Padding(
|
||||
// padding: const EdgeInsets.only(right: 0),
|
||||
// child: UnconstrainedBox(
|
||||
// child: Row(
|
||||
// children: [
|
||||
// TextFieldIconButton(
|
||||
// child: const XIcon(),
|
||||
// onTap: () async {
|
||||
// setState(() {
|
||||
// searchController.text = "";
|
||||
// _searchTerm = "";
|
||||
// });
|
||||
// },
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// : null,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(
|
||||
// height: 16,
|
||||
// ),
|
||||
Expanded(
|
||||
child: OrdinalsList(
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
62
lib/pages/ordinals/widgets/dialogs.dart
Normal file
62
lib/pages/ordinals/widgets/dialogs.dart
Normal file
|
@ -0,0 +1,62 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||
|
||||
class SendOrdinalUnfreezeDialog extends StatelessWidget {
|
||||
const SendOrdinalUnfreezeDialog({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StackDialog(
|
||||
title: "This ordinal is frozen",
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.coinControl.blocked,
|
||||
width: 24,
|
||||
height: 24,
|
||||
color: Theme.of(context).extension<StackColors>()!.textDark,
|
||||
),
|
||||
message: "To send this ordinal, you must unfreeze it first.",
|
||||
leftButton: SecondaryButton(
|
||||
label: "Cancel",
|
||||
onPressed: Navigator.of(context).pop,
|
||||
),
|
||||
rightButton: PrimaryButton(
|
||||
label: "Unfreeze",
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop("unfreeze");
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UnfreezeOrdinalDialog extends StatelessWidget {
|
||||
const UnfreezeOrdinalDialog({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StackDialog(
|
||||
title: "Are you sure you want to unfreeze this ordinal?",
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.coinControl.blocked,
|
||||
width: 24,
|
||||
height: 24,
|
||||
color: Theme.of(context).extension<StackColors>()!.textDark,
|
||||
),
|
||||
leftButton: SecondaryButton(
|
||||
label: "Cancel",
|
||||
onPressed: Navigator.of(context).pop,
|
||||
),
|
||||
rightButton: PrimaryButton(
|
||||
label: "Unfreeze",
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop("unfreeze");
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
63
lib/pages/ordinals/widgets/ordinal_card.dart
Normal file
63
lib/pages/ordinals/widgets/ordinal_card.dart
Normal file
|
@ -0,0 +1,63 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:stackwallet/models/isar/ordinal.dart';
|
||||
import 'package:stackwallet/pages/ordinals/ordinal_details_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/ordinals/desktop_ordinal_details_view.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
|
||||
class OrdinalCard extends StatelessWidget {
|
||||
const OrdinalCard({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
required this.ordinal,
|
||||
}) : super(key: key);
|
||||
|
||||
final String walletId;
|
||||
final Ordinal ordinal;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RoundedWhiteContainer(
|
||||
radiusMultiplier: 2,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
Util.isDesktop
|
||||
? DesktopOrdinalDetailsView.routeName
|
||||
: OrdinalDetailsView.routeName,
|
||||
arguments: (walletId: walletId, ordinal: ordinal),
|
||||
);
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: Image.network(
|
||||
ordinal.content, // Use the preview URL as the image source
|
||||
fit: BoxFit.cover,
|
||||
filterQuality:
|
||||
FilterQuality.none, // Set the filter mode to nearest
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'INSC. ${ordinal.inscriptionNumber}', // infer from address associated with utxoTXID
|
||||
style: STextStyles.w500_12(context),
|
||||
),
|
||||
// const Spacer(),
|
||||
// Text(
|
||||
// "ID ${ordinal.inscriptionId}",
|
||||
// style: STextStyles.w500_8(context),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
119
lib/pages/ordinals/widgets/ordinals_list.dart
Normal file
119
lib/pages/ordinals/widgets/ordinals_list.dart
Normal file
|
@ -0,0 +1,119 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/models/isar/ordinal.dart';
|
||||
import 'package:stackwallet/pages/ordinals/widgets/ordinal_card.dart';
|
||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
|
||||
class OrdinalsList extends ConsumerStatefulWidget {
|
||||
const OrdinalsList({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
}) : super(key: key);
|
||||
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
ConsumerState<OrdinalsList> createState() => _OrdinalsListState();
|
||||
}
|
||||
|
||||
class _OrdinalsListState extends ConsumerState<OrdinalsList> {
|
||||
final double _spacing = Util.isDesktop ? 16 : 10;
|
||||
|
||||
late List<Ordinal> _data;
|
||||
|
||||
late final Stream<List<Ordinal>?> _stream;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_stream = ref
|
||||
.read(mainDBProvider)
|
||||
.isar
|
||||
.ordinals
|
||||
.where()
|
||||
.filter()
|
||||
.walletIdEqualTo(widget.walletId)
|
||||
.watch();
|
||||
|
||||
_data = ref
|
||||
.read(mainDBProvider)
|
||||
.isar
|
||||
.ordinals
|
||||
.where()
|
||||
.filter()
|
||||
.walletIdEqualTo(widget.walletId)
|
||||
.findAllSync();
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder<List<Ordinal>?>(
|
||||
stream: _stream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
_data = snapshot.data!;
|
||||
}
|
||||
|
||||
if (_data.isEmpty) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
RoundedWhiteContainer(
|
||||
child: Center(
|
||||
child: Text(
|
||||
"Your ordinals will appear here",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1)
|
||||
: STextStyles.label(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
if (Util.isDesktop) {
|
||||
return Wrap(
|
||||
spacing: _spacing,
|
||||
runSpacing: _spacing,
|
||||
children: _data
|
||||
.map((e) => SizedBox(
|
||||
width: 220,
|
||||
height: 270,
|
||||
child: OrdinalCard(
|
||||
walletId: widget.walletId,
|
||||
ordinal: e,
|
||||
)))
|
||||
.toList(),
|
||||
);
|
||||
} else {
|
||||
return GridView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: _data.length,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisSpacing: _spacing,
|
||||
mainAxisSpacing: _spacing,
|
||||
crossAxisCount: Util.isDesktop ? 4 : 2,
|
||||
childAspectRatio: 6 / 7, // was 3/4, less data displayed now
|
||||
),
|
||||
itemBuilder: (_, i) => OrdinalCard(
|
||||
walletId: widget.walletId,
|
||||
ordinal: _data[i],
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.d
|
|||
import 'package:stackwallet/pages/home_view/home_view.dart';
|
||||
import 'package:stackwallet/pages/monkey/monkey_view.dart';
|
||||
import 'package:stackwallet/pages/notification_views/notifications_view.dart';
|
||||
import 'package:stackwallet/pages/ordinals/ordinals_view.dart';
|
||||
import 'package:stackwallet/pages/paynym/paynym_claim_view.dart';
|
||||
import 'package:stackwallet/pages/paynym/paynym_home_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/receive_view.dart';
|
||||
|
@ -73,6 +74,7 @@ import 'package:stackwallet/widgets/stack_dialog.dart';
|
|||
import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/buy_nav_icon.dart';
|
||||
import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/coin_control_nav_icon.dart';
|
||||
import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/exchange_nav_icon.dart';
|
||||
import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/ordinals_nav_icon.dart';
|
||||
import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/paynym_nav_icon.dart';
|
||||
import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/receive_nav_icon.dart';
|
||||
import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/send_nav_icon.dart';
|
||||
|
@ -1024,6 +1026,22 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
}
|
||||
},
|
||||
),
|
||||
if (ref.watch(
|
||||
walletsChangeNotifierProvider.select(
|
||||
(value) =>
|
||||
value.getManager(widget.walletId).hasOrdinalsSupport,
|
||||
),
|
||||
))
|
||||
WalletNavigationBarItemData(
|
||||
label: "Ordinals",
|
||||
icon: const OrdinalsNavIcon(),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
OrdinalsView.routeName,
|
||||
arguments: widget.walletId,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
|
@ -23,6 +23,7 @@ import 'package:stackwallet/pages_desktop_specific/coin_control/desktop_coin_con
|
|||
import 'package:stackwallet/pages_desktop_specific/desktop_menu.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/ordinals/desktop_ordinals_view.dart';
|
||||
import 'package:stackwallet/providers/desktop/current_desktop_menu_item.dart';
|
||||
import 'package:stackwallet/providers/global/paynym_api_provider.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
|
@ -81,6 +82,7 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> {
|
|||
onCoinControlPressed: _onCoinControlPressed,
|
||||
onAnonymizeAllPressed: _onAnonymizeAllPressed,
|
||||
onWhirlpoolPressed: _onWhirlpoolPressed,
|
||||
onOrdinalsPressed: _onOrdinalsPressed,
|
||||
onMonkeyPressed: _onMonkeyPressed,
|
||||
),
|
||||
);
|
||||
|
@ -324,6 +326,15 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> {
|
|||
));
|
||||
}
|
||||
|
||||
void _onOrdinalsPressed() {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
|
||||
Navigator.of(context).pushNamed(
|
||||
DesktopOrdinalsView.routeName,
|
||||
arguments: widget.walletId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final manager = ref.watch(
|
||||
|
@ -341,6 +352,7 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> {
|
|||
)) ||
|
||||
manager.coin == Coin.firo ||
|
||||
manager.coin == Coin.firoTestNet ||
|
||||
manager.hasWhirlpoolSupport ||
|
||||
manager.coin == Coin.banano ||
|
||||
manager.hasWhirlpoolSupport;
|
||||
return Row(
|
||||
|
|
|
@ -29,6 +29,7 @@ class MoreFeaturesDialog extends ConsumerStatefulWidget {
|
|||
required this.onCoinControlPressed,
|
||||
required this.onAnonymizeAllPressed,
|
||||
required this.onWhirlpoolPressed,
|
||||
required this.onOrdinalsPressed,
|
||||
required this.onMonkeyPressed,
|
||||
}) : super(key: key);
|
||||
|
||||
|
@ -37,6 +38,7 @@ class MoreFeaturesDialog extends ConsumerStatefulWidget {
|
|||
final VoidCallback? onCoinControlPressed;
|
||||
final VoidCallback? onAnonymizeAllPressed;
|
||||
final VoidCallback? onWhirlpoolPressed;
|
||||
final VoidCallback? onOrdinalsPressed;
|
||||
final VoidCallback? onMonkeyPressed;
|
||||
|
||||
@override
|
||||
|
@ -105,6 +107,13 @@ class _MoreFeaturesDialogState extends ConsumerState<MoreFeaturesDialog> {
|
|||
iconAsset: Assets.svg.robotHead,
|
||||
onPressed: () => widget.onPaynymPressed?.call(),
|
||||
),
|
||||
if (manager.hasOrdinalsSupport)
|
||||
_MoreFeaturesItem(
|
||||
label: "Ordinals",
|
||||
detail: "View and control your ordinals in Stack",
|
||||
iconAsset: Assets.svg.ordinal,
|
||||
onPressed: () => widget.onOrdinalsPressed?.call(),
|
||||
),
|
||||
if (manager.coin == Coin.banano)
|
||||
_MoreFeaturesItem(
|
||||
label: "MonKey",
|
||||
|
|
|
@ -0,0 +1,314 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart';
|
||||
import 'package:stackwallet/models/isar/ordinal.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount_formatter.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/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
|
||||
class DesktopOrdinalDetailsView extends ConsumerStatefulWidget {
|
||||
const DesktopOrdinalDetailsView({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
required this.ordinal,
|
||||
}) : super(key: key);
|
||||
|
||||
final String walletId;
|
||||
final Ordinal ordinal;
|
||||
|
||||
static const routeName = "/desktopOrdinalDetailsView";
|
||||
|
||||
@override
|
||||
ConsumerState<DesktopOrdinalDetailsView> createState() =>
|
||||
_DesktopOrdinalDetailsViewState();
|
||||
}
|
||||
|
||||
class _DesktopOrdinalDetailsViewState
|
||||
extends ConsumerState<DesktopOrdinalDetailsView> {
|
||||
static const _spacing = 12.0;
|
||||
|
||||
late final UTXO? utxo;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
utxo = widget.ordinal.getUTXO(ref.read(mainDBProvider));
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final coin = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(widget.walletId).coin));
|
||||
|
||||
return DesktopScaffold(
|
||||
appBar: DesktopAppBar(
|
||||
background: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
leading: Expanded(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 32,
|
||||
),
|
||||
AppBarIconButton(
|
||||
size: 32,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
shadows: const [],
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.arrowLeft,
|
||||
width: 18,
|
||||
height: 18,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.topNavIconPrimary,
|
||||
),
|
||||
onPressed: Navigator.of(context).pop,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 18,
|
||||
),
|
||||
Text(
|
||||
"Ordinal details",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
useSpacers: false,
|
||||
isCompactHeight: true,
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24,
|
||||
top: 24,
|
||||
right: 24,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 300,
|
||||
height: 300,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: Image.network(
|
||||
widget.ordinal
|
||||
.content, // Use the preview URL as the image source
|
||||
fit: BoxFit.cover,
|
||||
filterQuality:
|
||||
FilterQuality.none, // Set the filter mode to nearest
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"INSC. ${widget.ordinal.inscriptionNumber}",
|
||||
style: STextStyles.w600_20(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
// PrimaryButton(
|
||||
// width: 150,
|
||||
// label: "Send",
|
||||
// icon: SvgPicture.asset(
|
||||
// Assets.svg.send,
|
||||
// width: 18,
|
||||
// height: 18,
|
||||
// color: Theme.of(context)
|
||||
// .extension<StackColors>()!
|
||||
// .buttonTextPrimary,
|
||||
// ),
|
||||
// buttonHeight: ButtonHeight.l,
|
||||
// iconSpacing: 8,
|
||||
// onPressed: () async {
|
||||
// final response = await showDialog<String?>(
|
||||
// context: context,
|
||||
// builder: (_) =>
|
||||
// const SendOrdinalUnfreezeDialog(),
|
||||
// );
|
||||
// if (response == "unfreeze") {
|
||||
// // TODO: unfreeze and go to send ord screen
|
||||
// }
|
||||
// },
|
||||
// ),
|
||||
// const SizedBox(
|
||||
// width: 16,
|
||||
// ),
|
||||
// SecondaryButton(
|
||||
// width: 150,
|
||||
// label: "Download",
|
||||
// icon: SvgPicture.asset(
|
||||
// Assets.svg.arrowDown,
|
||||
// width: 13,
|
||||
// height: 18,
|
||||
// color: Theme.of(context)
|
||||
// .extension<StackColors>()!
|
||||
// .buttonTextSecondary,
|
||||
// ),
|
||||
// buttonHeight: ButtonHeight.l,
|
||||
// iconSpacing: 8,
|
||||
// onPressed: () {
|
||||
// // TODO: save and download image to device
|
||||
// },
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
_DetailsItemWCopy(
|
||||
title: "Inscription number",
|
||||
data: widget.ordinal.inscriptionNumber.toString(),
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
_DetailsItemWCopy(
|
||||
title: "Inscription ID",
|
||||
data: widget.ordinal.inscriptionId,
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
// todo: add utxo status
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
_DetailsItemWCopy(
|
||||
title: "Amount",
|
||||
data: utxo == null
|
||||
? "ERROR"
|
||||
: ref.watch(pAmountFormatter(coin)).format(
|
||||
Amount(
|
||||
rawValue: BigInt.from(utxo!.value),
|
||||
fractionDigits: coin.decimals,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
_DetailsItemWCopy(
|
||||
title: "Owner address",
|
||||
data: utxo?.address ?? "ERROR",
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
_DetailsItemWCopy(
|
||||
title: "Transaction ID",
|
||||
data: widget.ordinal.utxoTXID,
|
||||
),
|
||||
const SizedBox(
|
||||
height: _spacing,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DetailsItemWCopy extends StatelessWidget {
|
||||
const _DetailsItemWCopy({
|
||||
Key? key,
|
||||
required this.title,
|
||||
required this.data,
|
||||
}) : super(key: key);
|
||||
|
||||
final String title;
|
||||
final String data;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RoundedWhiteContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
await Clipboard.setData(ClipboardData(text: data));
|
||||
if (context.mounted) {
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.info,
|
||||
message: "Copied to clipboard",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.copy,
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.infoItemIcons,
|
||||
width: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
SelectableText(
|
||||
data,
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
246
lib/pages_desktop_specific/ordinals/desktop_ordinals_view.dart
Normal file
246
lib/pages_desktop_specific/ordinals/desktop_ordinals_view.dart
Normal file
|
@ -0,0 +1,246 @@
|
|||
/*
|
||||
* This file is part of Stack Wallet.
|
||||
*
|
||||
* Copyright (c) 2023 Cypher Stack
|
||||
* All Rights Reserved.
|
||||
* The code is distributed under GPLv3 license, see LICENSE file for details.
|
||||
* Generated by Cypher Stack on 2023-05-26
|
||||
*
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/pages/ordinals/widgets/ordinals_list.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/services/mixins/ordinals_interface.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/show_loading.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
||||
|
||||
class DesktopOrdinalsView extends ConsumerStatefulWidget {
|
||||
const DesktopOrdinalsView({
|
||||
super.key,
|
||||
required this.walletId,
|
||||
});
|
||||
|
||||
static const String routeName = "/desktopOrdinalsView";
|
||||
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
ConsumerState<DesktopOrdinalsView> createState() => _DesktopOrdinals();
|
||||
}
|
||||
|
||||
class _DesktopOrdinals extends ConsumerState<DesktopOrdinalsView> {
|
||||
late final TextEditingController searchController;
|
||||
late final FocusNode searchFocusNode;
|
||||
|
||||
String _searchTerm = "";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
searchController = TextEditingController();
|
||||
searchFocusNode = FocusNode();
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
searchController.dispose();
|
||||
searchFocusNode.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
|
||||
return DesktopScaffold(
|
||||
appBar: DesktopAppBar(
|
||||
background: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
isCompactHeight: true,
|
||||
useSpacers: false,
|
||||
leading: Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 32,
|
||||
),
|
||||
AppBarIconButton(
|
||||
size: 32,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
shadows: const [],
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.arrowLeft,
|
||||
width: 18,
|
||||
height: 18,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.topNavIconPrimary,
|
||||
),
|
||||
onPressed: Navigator.of(context).pop,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 15,
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.ordinal,
|
||||
width: 32,
|
||||
height: 32,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Text(
|
||||
"Ordinals",
|
||||
style: STextStyles.desktopH3(context),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
// Expanded(
|
||||
// child: ClipRRect(
|
||||
// borderRadius: BorderRadius.circular(
|
||||
// Constants.size.circularBorderRadius,
|
||||
// ),
|
||||
// child: TextField(
|
||||
// autocorrect: Util.isDesktop ? false : true,
|
||||
// enableSuggestions: Util.isDesktop ? false : true,
|
||||
// controller: searchController,
|
||||
// focusNode: searchFocusNode,
|
||||
// onChanged: (value) {
|
||||
// setState(() {
|
||||
// _searchTerm = value;
|
||||
// });
|
||||
// },
|
||||
// style: STextStyles.field(context),
|
||||
// decoration: standardInputDecoration(
|
||||
// "Search",
|
||||
// searchFocusNode,
|
||||
// context,
|
||||
// ).copyWith(
|
||||
// prefixIcon: Padding(
|
||||
// padding: const EdgeInsets.symmetric(
|
||||
// horizontal: 10,
|
||||
// vertical: 20,
|
||||
// ),
|
||||
// child: SvgPicture.asset(
|
||||
// Assets.svg.search,
|
||||
// width: 16,
|
||||
// height: 16,
|
||||
// ),
|
||||
// ),
|
||||
// suffixIcon: searchController.text.isNotEmpty
|
||||
// ? Padding(
|
||||
// padding: const EdgeInsets.only(right: 0),
|
||||
// child: UnconstrainedBox(
|
||||
// child: Row(
|
||||
// children: [
|
||||
// TextFieldIconButton(
|
||||
// child: const XIcon(),
|
||||
// onTap: () async {
|
||||
// setState(() {
|
||||
// searchController.text = "";
|
||||
// _searchTerm = "";
|
||||
// });
|
||||
// },
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// : null,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(
|
||||
// width: 16,
|
||||
// ),
|
||||
// SecondaryButton(
|
||||
// width: 184,
|
||||
// label: "Filter",
|
||||
// buttonHeight: ButtonHeight.l,
|
||||
// icon: SvgPicture.asset(
|
||||
// Assets.svg.filter,
|
||||
// color: Theme.of(context)
|
||||
// .extension<StackColors>()!
|
||||
// .buttonTextSecondary,
|
||||
// ),
|
||||
// onPressed: () {
|
||||
// Navigator.of(context).pushNamed(
|
||||
// OrdinalsFilterView.routeName,
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
SecondaryButton(
|
||||
width: 184,
|
||||
label: "Update",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.arrowRotate,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextSecondary,
|
||||
),
|
||||
onPressed: () async {
|
||||
// show loading for a minimum of 2 seconds on refreshing
|
||||
await showLoading(
|
||||
isDesktop: true,
|
||||
whileFuture: Future.wait<void>([
|
||||
Future.delayed(const Duration(seconds: 2)),
|
||||
(ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId)
|
||||
.wallet as OrdinalsInterface)
|
||||
.refreshInscriptions()
|
||||
]),
|
||||
context: context,
|
||||
message: "Refreshing...");
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: OrdinalsList(
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -17,7 +17,9 @@ import 'package:stackwallet/models/add_wallet_list_entity/sub_classes/eth_token_
|
|||
import 'package:stackwallet/models/buy/response_objects/quote.dart';
|
||||
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
|
||||
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
|
||||
import 'package:stackwallet/models/isar/models/contact_entry.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/models/isar/ordinal.dart';
|
||||
import 'package:stackwallet/models/paynym/paynym_account_lite.dart';
|
||||
import 'package:stackwallet/models/send_view_auto_fill_data.dart';
|
||||
import 'package:stackwallet/pages/add_wallet_views/add_token_view/add_custom_token_view.dart';
|
||||
|
@ -58,6 +60,9 @@ import 'package:stackwallet/pages/intro_view.dart';
|
|||
import 'package:stackwallet/pages/manage_favorites_view/manage_favorites_view.dart';
|
||||
import 'package:stackwallet/pages/monkey/monkey_view.dart';
|
||||
import 'package:stackwallet/pages/notification_views/notifications_view.dart';
|
||||
import 'package:stackwallet/pages/ordinals/ordinal_details_view.dart';
|
||||
import 'package:stackwallet/pages/ordinals/ordinals_filter_view.dart';
|
||||
import 'package:stackwallet/pages/ordinals/ordinals_view.dart';
|
||||
import 'package:stackwallet/pages/paynym/add_new_paynym_follow_view.dart';
|
||||
import 'package:stackwallet/pages/paynym/paynym_claim_view.dart';
|
||||
import 'package:stackwallet/pages/paynym/paynym_home_view.dart';
|
||||
|
@ -142,6 +147,8 @@ import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub
|
|||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/notifications/desktop_notifications_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/ordinals/desktop_ordinal_details_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/ordinals/desktop_ordinals_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/password/create_password_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/password/delete_password_warning_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/password/forgot_password_desktop_view.dart';
|
||||
|
@ -166,8 +173,6 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
|||
import 'package:stackwallet/widgets/choose_coin_view.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import 'models/isar/models/contact_entry.dart';
|
||||
|
||||
/*
|
||||
* This file contains all the routes for the app.
|
||||
* To add a new route, add it to the switch statement in the generateRoute method.
|
||||
|
@ -435,6 +440,70 @@ class RouteGenerator {
|
|||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case OrdinalsView.routeName:
|
||||
if (args is String) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => OrdinalsView(
|
||||
walletId: args,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case DesktopOrdinalsView.routeName:
|
||||
if (args is String) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => DesktopOrdinalsView(
|
||||
walletId: args,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case OrdinalDetailsView.routeName:
|
||||
if (args is ({Ordinal ordinal, String walletId})) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => OrdinalDetailsView(
|
||||
walletId: args.walletId,
|
||||
ordinal: args.ordinal,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case DesktopOrdinalDetailsView.routeName:
|
||||
if (args is ({Ordinal ordinal, String walletId})) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => DesktopOrdinalDetailsView(
|
||||
walletId: args.walletId,
|
||||
ordinal: args.ordinal,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case OrdinalsFilterView.routeName:
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => const OrdinalsFilterView(),
|
||||
settings: RouteSettings(name: settings.name));
|
||||
|
||||
case UtxoDetailsView.routeName:
|
||||
if (args is Tuple2<Id, String>) {
|
||||
return getRoute(
|
||||
|
|
|
@ -36,6 +36,7 @@ import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_
|
|||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||
import 'package:stackwallet/services/mixins/coin_control_interface.dart';
|
||||
import 'package:stackwallet/services/mixins/electrum_x_parsing.dart';
|
||||
import 'package:stackwallet/services/mixins/ordinals_interface.dart';
|
||||
import 'package:stackwallet/services/mixins/wallet_cache.dart';
|
||||
import 'package:stackwallet/services/mixins/wallet_db.dart';
|
||||
import 'package:stackwallet/services/mixins/xpubable.dart';
|
||||
|
@ -109,7 +110,12 @@ String constructDerivePath({
|
|||
}
|
||||
|
||||
class LitecoinWallet extends CoinServiceAPI
|
||||
with WalletCache, WalletDB, ElectrumXParsing, CoinControlInterface
|
||||
with
|
||||
WalletCache,
|
||||
WalletDB,
|
||||
ElectrumXParsing,
|
||||
CoinControlInterface,
|
||||
OrdinalsInterface
|
||||
implements XPubAble {
|
||||
LitecoinWallet({
|
||||
required String walletId,
|
||||
|
@ -130,6 +136,7 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
_secureStore = secureStore;
|
||||
initCache(walletId, coin);
|
||||
initWalletDB(mockableOverride: mockableOverride);
|
||||
initOrdinalsInterface(walletId: walletId, coin: coin, db: db);
|
||||
initCoinControlInterface(
|
||||
walletId: walletId,
|
||||
walletName: walletName,
|
||||
|
@ -1864,14 +1871,6 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
String? blockReason;
|
||||
String? label;
|
||||
|
||||
final utxoAmount = jsonUTXO["value"] as int;
|
||||
|
||||
if (utxoAmount <= 10000) {
|
||||
shouldBlock = true;
|
||||
blockReason = "May contain ordinal";
|
||||
label = "Possible ordinal";
|
||||
}
|
||||
|
||||
final vout = jsonUTXO["tx_pos"] as int;
|
||||
|
||||
final outputs = txn["vout"] as List;
|
||||
|
@ -1886,6 +1885,25 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
}
|
||||
}
|
||||
|
||||
final utxoAmount = jsonUTXO["value"] as int;
|
||||
|
||||
// TODO check the specific output, not just the address in general
|
||||
// TODO optimize by freezing output in OrdinalsInterface, so one ordinal API calls is made (or at least many less)
|
||||
if (utxoOwnerAddress != null) {
|
||||
if (await inscriptionInAddress(utxoOwnerAddress!)) {
|
||||
shouldBlock = true;
|
||||
blockReason = "Ordinal";
|
||||
label = "Ordinal detected at address";
|
||||
}
|
||||
} else {
|
||||
// TODO implement inscriptionInOutput
|
||||
if (utxoAmount <= 10000) {
|
||||
shouldBlock = true;
|
||||
blockReason = "May contain ordinal";
|
||||
label = "Possible ordinal";
|
||||
}
|
||||
}
|
||||
|
||||
final utxo = isar_models.UTXO(
|
||||
walletId: walletId,
|
||||
txid: txn["txid"] as String,
|
||||
|
@ -1910,7 +1928,12 @@ class LitecoinWallet extends CoinServiceAPI
|
|||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
await db.updateUTXOs(walletId, outputArray);
|
||||
bool inscriptionsRefreshNeeded =
|
||||
await db.updateUTXOs(walletId, outputArray);
|
||||
|
||||
if (inscriptionsRefreshNeeded) {
|
||||
await refreshInscriptions();
|
||||
}
|
||||
|
||||
// finally update balance
|
||||
await _updateBalance();
|
||||
|
|
|
@ -21,6 +21,7 @@ import 'package:stackwallet/services/event_bus/events/global/node_connection_sta
|
|||
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||
import 'package:stackwallet/services/mixins/coin_control_interface.dart';
|
||||
import 'package:stackwallet/services/mixins/ordinals_interface.dart';
|
||||
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
|
||||
import 'package:stackwallet/services/mixins/xpubable.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
|
@ -244,6 +245,8 @@ class Manager with ChangeNotifier {
|
|||
|
||||
bool get hasCoinControlSupport => _currentWallet is CoinControlInterface;
|
||||
|
||||
bool get hasOrdinalsSupport => _currentWallet is OrdinalsInterface;
|
||||
|
||||
bool get hasTokenSupport => _currentWallet.coin == Coin.ethereum;
|
||||
|
||||
bool get hasWhirlpoolSupport => false;
|
||||
|
|
68
lib/services/litescribe_api.dart
Normal file
68
lib/services/litescribe_api.dart
Normal file
|
@ -0,0 +1,68 @@
|
|||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'package:stackwallet/dto/ordinals/inscription_data.dart';
|
||||
import 'package:stackwallet/dto/ordinals/litescribe_response.dart';
|
||||
|
||||
class LitescribeAPI {
|
||||
static final LitescribeAPI _instance = LitescribeAPI._internal();
|
||||
|
||||
factory LitescribeAPI({required String baseUrl}) {
|
||||
_instance.baseUrl = baseUrl;
|
||||
return _instance;
|
||||
}
|
||||
|
||||
LitescribeAPI._internal();
|
||||
|
||||
late String baseUrl;
|
||||
|
||||
Future<LitescribeResponse> _getResponse(String endpoint) async {
|
||||
final response = await http.get(Uri.parse('$baseUrl$endpoint'));
|
||||
if (response.statusCode == 200) {
|
||||
return LitescribeResponse(data: _validateJson(response.body));
|
||||
} else {
|
||||
throw Exception('LitescribeAPI _getResponse exception: Failed to load data');
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> _validateJson(String responseBody) {
|
||||
final parsed = jsonDecode(responseBody);
|
||||
if (parsed is Map<String, dynamic>) {
|
||||
return parsed;
|
||||
} else {
|
||||
throw const FormatException('LitescribeAPI _validateJson exception: Invalid JSON format');
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<InscriptionData>> getInscriptionsByAddress(String address, {int cursor = 0, int size = 1000}) async {
|
||||
// size param determines how many inscriptions are returned per response
|
||||
// default of 1000 is used to cover most addresses (I assume)
|
||||
// if the total number of inscriptions at the address exceeds the length of the list of inscriptions returned, another call with a higher size is made
|
||||
final int defaultLimit = 1000;
|
||||
final response = await _getResponse('/address/inscriptions?address=$address&cursor=$cursor&size=$size');
|
||||
|
||||
// Check if the number of returned inscriptions equals the limit
|
||||
final list = response.data['result']['list'];
|
||||
final int total = response.data['result']['total'] as int;
|
||||
final int currentSize = list.length as int;
|
||||
|
||||
if (currentSize == size && currentSize < total) {
|
||||
// If the number of returned inscriptions equals the limit and there are more inscriptions available,
|
||||
// increment the cursor and make the next API call to fetch the remaining inscriptions.
|
||||
final int newCursor = cursor + size;
|
||||
return getInscriptionsByAddress(address, cursor: newCursor, size: size);
|
||||
|
||||
} else {
|
||||
try {
|
||||
// Iterate through the list and create InscriptionData objects from each element
|
||||
final List<InscriptionData> inscriptions = (list as List<dynamic>)
|
||||
.map((json) => InscriptionData.fromJson(json as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
return inscriptions;
|
||||
} catch (e) {
|
||||
throw const FormatException('LitescribeAPI getInscriptionsByAddress exception: AddressInscriptionResponse.fromJson failure');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
94
lib/services/mixins/ordinals_interface.dart
Normal file
94
lib/services/mixins/ordinals_interface.dart
Normal file
|
@ -0,0 +1,94 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/db/isar/main_db.dart';
|
||||
import 'package:stackwallet/dto/ordinals/inscription_data.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart';
|
||||
import 'package:stackwallet/models/isar/ordinal.dart';
|
||||
import 'package:stackwallet/services/litescribe_api.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
||||
mixin OrdinalsInterface {
|
||||
late final String _walletId;
|
||||
late final Coin _coin;
|
||||
late final MainDB _db;
|
||||
|
||||
void initOrdinalsInterface({
|
||||
required String walletId,
|
||||
required Coin coin,
|
||||
required MainDB db,
|
||||
}) {
|
||||
_walletId = walletId;
|
||||
_coin = coin;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
final LitescribeAPI litescribeAPI =
|
||||
LitescribeAPI(baseUrl: 'https://litescribe.io/api');
|
||||
|
||||
Future<void> refreshInscriptions() async {
|
||||
final uniqueAddresses = await _db
|
||||
.getUTXOs(_walletId)
|
||||
.filter()
|
||||
.addressIsNotNull()
|
||||
.distinctByAddress()
|
||||
.addressProperty()
|
||||
.findAll();
|
||||
final inscriptions =
|
||||
await _getInscriptionDataFromAddresses(uniqueAddresses.cast<String>());
|
||||
|
||||
final ords = inscriptions
|
||||
.map((e) => Ordinal.fromInscriptionData(e, _walletId))
|
||||
.toList();
|
||||
|
||||
await _db.isar.writeTxn(() async {
|
||||
await _db.isar.ordinals
|
||||
.where()
|
||||
.filter()
|
||||
.walletIdEqualTo(_walletId)
|
||||
.deleteAll();
|
||||
await _db.isar.ordinals.putAll(ords);
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<InscriptionData>> _getInscriptionDataFromAddresses(
|
||||
List<String> addresses) async {
|
||||
List<InscriptionData> allInscriptions = [];
|
||||
for (String address in addresses) {
|
||||
try {
|
||||
var inscriptions =
|
||||
await litescribeAPI.getInscriptionsByAddress(address);
|
||||
allInscriptions.addAll(inscriptions);
|
||||
} catch (e) {
|
||||
throw Exception("Error fetching inscriptions for address $address: $e");
|
||||
}
|
||||
}
|
||||
return allInscriptions;
|
||||
}
|
||||
|
||||
// check if an inscription is in a given <UTXO> output
|
||||
Future<bool> inscriptionInOutput(UTXO output) async {
|
||||
if (output.address != null) {
|
||||
var inscriptions =
|
||||
await litescribeAPI.getInscriptionsByAddress("${output.address}");
|
||||
if (inscriptions.isNotEmpty) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
throw UnimplementedError(
|
||||
'TODO look up utxo without address. utxo->txid:output->address');
|
||||
}
|
||||
}
|
||||
|
||||
// check if an inscription is in a given <UTXO> output
|
||||
Future<bool> inscriptionInAddress(String address) async {
|
||||
var inscriptions = await litescribeAPI.getInscriptionsByAddress(address);
|
||||
if (inscriptions.isNotEmpty) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -206,6 +206,8 @@ class _SVG {
|
|||
String get messageQuestion => "assets/svg/message-question-1.svg";
|
||||
String get list => "assets/svg/list-ul.svg";
|
||||
String get unclaimedPaynym => "assets/svg/unclaimed.svg";
|
||||
String get send => "assets/svg/send.svg";
|
||||
String get ordinal => "assets/svg/ordinal.svg";
|
||||
|
||||
String get trocadorRatingA => "assets/svg/trocador_rating_a.svg";
|
||||
String get trocadorRatingB => "assets/svg/trocador_rating_b.svg";
|
||||
|
|
|
@ -306,6 +306,17 @@ class STextStyles {
|
|||
}
|
||||
}
|
||||
|
||||
static TextStyle w600_16(BuildContext context) {
|
||||
switch (_theme(context).themeId) {
|
||||
default:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static TextStyle w500_14(BuildContext context) {
|
||||
switch (_theme(context).themeId) {
|
||||
default:
|
||||
|
@ -339,6 +350,17 @@ class STextStyles {
|
|||
}
|
||||
}
|
||||
|
||||
static TextStyle w500_8(BuildContext context) {
|
||||
switch (_theme(context).themeId) {
|
||||
default:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textSubtitle1,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 8,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static TextStyle w600_20(BuildContext context) {
|
||||
switch (_theme(context).themeId) {
|
||||
default:
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* This file is part of Stack Wallet.
|
||||
*
|
||||
* Copyright (c) 2023 Cypher Stack
|
||||
* All Rights Reserved.
|
||||
* The code is distributed under GPLv3 license, see LICENSE file for details.
|
||||
* Generated by Cypher Stack on 2023-05-26
|
||||
*
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
|
||||
class OrdinalsNavIcon extends StatelessWidget {
|
||||
const OrdinalsNavIcon({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SvgPicture.asset(
|
||||
Assets.svg.ordinal,
|
||||
height: 20,
|
||||
width: 20,
|
||||
color: Theme.of(context).extension<StackColors>()!.bottomNavIconIcon,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -335,6 +335,8 @@ flutter:
|
|||
- assets/svg/trocador_rating_b.svg
|
||||
- assets/svg/trocador_rating_c.svg
|
||||
- assets/svg/trocador_rating_d.svg
|
||||
- assets/svg/send.svg
|
||||
- assets/svg/ordinal.svg
|
||||
- assets/svg/monkey.svg
|
||||
|
||||
# coin control icons
|
||||
|
|
|
@ -2958,6 +2958,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -388,6 +388,11 @@ class MockManager extends _i1.Mock implements _i12.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -349,6 +349,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -347,6 +347,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -667,6 +667,11 @@ class MockManager extends _i1.Mock implements _i13.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -454,6 +454,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -454,6 +454,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -454,6 +454,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -221,6 +221,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -452,6 +452,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -667,6 +667,11 @@ class MockManager extends _i1.Mock implements _i13.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -508,6 +508,11 @@ class MockManager extends _i1.Mock implements _i13.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -221,6 +221,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -221,6 +221,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -436,6 +436,11 @@ class MockManager extends _i1.Mock implements _i12.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -436,6 +436,11 @@ class MockManager extends _i1.Mock implements _i12.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -221,6 +221,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -221,6 +221,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -452,6 +452,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -688,6 +688,11 @@ class MockManager extends _i1.Mock implements _i15.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -452,6 +452,11 @@ class MockManager extends _i1.Mock implements _i10.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -223,6 +223,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -222,6 +222,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -221,6 +221,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -263,6 +263,11 @@ class MockManager extends _i1.Mock implements _i9.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -223,6 +223,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -856,7 +856,7 @@ class MockMainDB extends _i1.Mock implements _i9.MainDB {
|
|||
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||
) as _i5.Future<void>);
|
||||
@override
|
||||
_i5.Future<void> updateUTXOs(
|
||||
_i5.Future<bool> updateUTXOs(
|
||||
String? walletId,
|
||||
List<_i12.UTXO>? utxos,
|
||||
) =>
|
||||
|
@ -868,9 +868,9 @@ class MockMainDB extends _i1.Mock implements _i9.MainDB {
|
|||
utxos,
|
||||
],
|
||||
),
|
||||
returnValue: _i5.Future<void>.value(),
|
||||
returnValueForMissingStub: _i5.Future<void>.value(),
|
||||
) as _i5.Future<void>);
|
||||
returnValue: _i5.Future<bool>.value(),
|
||||
returnValueForMissingStub: _i5.Future<bool>.value(),
|
||||
) as _i5.Future<bool>);
|
||||
@override
|
||||
_i5.Stream<_i12.UTXO?> watchUTXO({
|
||||
required int? id,
|
||||
|
|
|
@ -2952,6 +2952,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -2198,6 +2198,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -585,6 +585,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
@ -3198,7 +3203,7 @@ class MockMainDB extends _i1.Mock implements _i14.MainDB {
|
|||
returnValueForMissingStub: _i19.Future<void>.value(),
|
||||
) as _i19.Future<void>);
|
||||
@override
|
||||
_i19.Future<void> updateUTXOs(
|
||||
_i19.Future<bool> updateUTXOs(
|
||||
String? walletId,
|
||||
List<_i22.UTXO>? utxos,
|
||||
) =>
|
||||
|
@ -3212,7 +3217,7 @@ class MockMainDB extends _i1.Mock implements _i14.MainDB {
|
|||
),
|
||||
returnValue: _i19.Future<void>.value(),
|
||||
returnValueForMissingStub: _i19.Future<void>.value(),
|
||||
) as _i19.Future<void>);
|
||||
) as _i19.Future<bool>);
|
||||
@override
|
||||
_i19.Stream<_i22.UTXO?> watchUTXO({
|
||||
required int? id,
|
||||
|
|
|
@ -2307,6 +2307,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
|
@ -2410,6 +2410,11 @@ class MockManager extends _i1.Mock implements _i6.Manager {
|
|||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasOrdinalsSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasOrdinalsSupport),
|
||||
returnValue: false,
|
||||
) as bool);
|
||||
@override
|
||||
bool get hasTokenSupport => (super.noSuchMethod(
|
||||
Invocation.getter(#hasTokenSupport),
|
||||
returnValue: false,
|
||||
|
|
Loading…
Reference in a new issue