load ordinals into isar as a cache and backend view model

This commit is contained in:
julian 2023-07-21 18:43:59 -06:00
parent 48109f3c49
commit dffa33abd3
10 changed files with 218 additions and 270 deletions

View file

@ -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,
],
directory: (await StackFileSystem.applicationIsarDirectory()).path,
// inspector: kDebugMode,

View file

@ -3,7 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:stackwallet/models/ordinal.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';
@ -28,7 +28,7 @@ class OrdinalDetailsView extends StatefulWidget {
static const routeName = "/ordinalDetailsView";
@override
_OrdinalDetailsViewState createState() => _OrdinalDetailsViewState();
State<OrdinalDetailsView> createState() => _OrdinalDetailsViewState();
}
class _OrdinalDetailsViewState extends State<OrdinalDetailsView> {

View file

@ -11,7 +11,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/ordinal.dart';
import 'package:stackwallet/pages/ordinals/ordinals_filter_view.dart';
import 'package:stackwallet/pages/ordinals/widgets/ordinals_list.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
@ -19,6 +18,7 @@ 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/background.dart';
@ -46,7 +46,6 @@ class _OrdinalsViewState extends ConsumerState<OrdinalsView> {
late final FocusNode searchFocus;
String _searchTerm = "";
dynamic _manager;
@override
void initState() {
@ -56,14 +55,6 @@ class _OrdinalsViewState extends ConsumerState<OrdinalsView> {
super.initState();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
// Set _manager here when the widget's dependencies change
_manager = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(widget.walletId)));
}
@override
void dispose() {
searchController.dispose();
@ -73,6 +64,9 @@ class _OrdinalsViewState extends ConsumerState<OrdinalsView> {
@override
Widget build(BuildContext context) {
final manager = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(widget.walletId)));
return Background(
child: SafeArea(
child: Scaffold(
@ -100,7 +94,15 @@ class _OrdinalsViewState extends ConsumerState<OrdinalsView> {
.topNavIconPrimary,
),
onPressed: () async {
(_manager.wallet as OrdinalsInterface).refreshInscriptions();
// show loading for a minimum of 2 seconds on refreshing
await showLoading(
whileFuture: Future.wait<void>([
Future.delayed(const Duration(seconds: 2)),
(manager.wallet as OrdinalsInterface)
.refreshInscriptions()
]),
context: context,
message: "Refreshing...");
},
),
),
@ -193,7 +195,6 @@ class _OrdinalsViewState extends ConsumerState<OrdinalsView> {
Expanded(
child: OrdinalsList(
walletId: widget.walletId,
ordinalsFuture: (_manager.wallet as OrdinalsInterface).getOrdinals(),
),
),
],

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:stackwallet/models/ordinal.dart';
import 'package:stackwallet/models/isar/ordinal.dart';
import 'package:stackwallet/pages/ordinals/ordinal_details_view.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
@ -28,14 +28,17 @@ class OrdinalCard extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
AspectRatio(
aspectRatio: 1,
child: Container(
color: Colors.red,
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
filterQuality:
FilterQuality.none, // Set the filter mode to nearest
),
),
),

View file

@ -1,48 +1,96 @@
import 'package:flutter/material.dart';
import 'package:stackwallet/models/ordinal.dart';
import 'package:stackwallet/pages/ordinals/widgets/ordinal_card.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
import 'dart:async';
class OrdinalsList extends StatelessWidget {
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/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,
required this.ordinalsFuture,
}) : super(key: key);
final String walletId;
final Future<List<Ordinal>> ordinalsFuture;
double get spacing => 2.0;
@override
ConsumerState<OrdinalsList> createState() => _OrdinalsListState();
}
class _OrdinalsListState extends ConsumerState<OrdinalsList> {
static const double _spacing = 10.0;
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 FutureBuilder<List<Ordinal>>(
future: ordinalsFuture,
return StreamBuilder<List<Ordinal>?>(
stream: _stream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const LoadingIndicator();
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else if (snapshot.hasData) {
final List<Ordinal> ordinals = snapshot.data!;
return GridView.builder(
shrinkWrap: true,
itemCount: ordinals.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisSpacing: spacing,
mainAxisSpacing: spacing,
crossAxisCount: 2,
childAspectRatio: 6 / 7, // was 3/4, less data displayed now
),
itemBuilder: (_, i) => OrdinalCard(
walletId: walletId,
ordinal: ordinals[i],
),
);
} else {
return const Text('No data found.');
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: STextStyles.label(context),
),
),
),
],
);
}
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],
),
);
},
);
}

View file

@ -3,7 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:stackwallet/models/ordinal.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';
@ -29,7 +29,7 @@ class DesktopOrdinalDetailsView extends StatefulWidget {
static const routeName = "/desktopOrdinalDetailsView";
@override
_DesktopOrdinalDetailsViewState createState() =>
State<DesktopOrdinalDetailsView> createState() =>
_DesktopOrdinalDetailsViewState();
}

View file

@ -11,35 +11,18 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/isar/models/contact_entry.dart';
import 'package:stackwallet/pages/address_book_views/subviews/add_address_book_entry_view.dart';
import 'package:stackwallet/pages/address_book_views/subviews/address_book_filter_view.dart';
import 'package:stackwallet/pages_desktop_specific/ordinals/subwidgets/desktop_ordinals_list.dart';
import 'package:stackwallet/pages_desktop_specific/address_book_view/subwidgets/desktop_address_book_scaffold.dart';
import 'package:stackwallet/pages_desktop_specific/address_book_view/subwidgets/desktop_contact_details.dart';
import 'package:stackwallet/providers/global/address_book_service_provider.dart';
import 'package:stackwallet/pages/ordinals/widgets/ordinals_list.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/providers/ui/address_book_providers/address_book_filter_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/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/address_book_card.dart';
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_scaffold.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/rounded_container.dart';
import 'package:stackwallet/widgets/rounded_white_container.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,
@ -62,7 +45,7 @@ class _DesktopOrdinals extends ConsumerState<DesktopOrdinalsView> {
dynamic _manager;
@override
void initState() {
void initState() {
searchController = TextEditingController();
searchFocusNode = FocusNode();
@ -151,23 +134,23 @@ class _DesktopOrdinals extends ConsumerState<DesktopOrdinalsView> {
),
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 = "";
});
},
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
searchController.text = "";
_searchTerm = "";
});
},
),
],
),
),
],
),
),
)
)
: null,
),
),
@ -176,9 +159,8 @@ class _DesktopOrdinals extends ConsumerState<DesktopOrdinalsView> {
height: 16,
),
Expanded(
child: DesktopOrdinalsList(
child: OrdinalsList(
walletId: widget.walletId,
ordinalsFuture: (_manager.wallet as OrdinalsInterface).getOrdinals(),
),
),
],

View file

@ -1,56 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stackwallet/models/ordinal.dart';
import 'package:stackwallet/pages_desktop_specific/ordinals/desktop_ordinal_details_view.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class DesktopOrdinalCard extends StatelessWidget {
const DesktopOrdinalCard({
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(
DesktopOrdinalDetailsView.routeName,
arguments: (walletId: walletId, ordinal: ordinal),
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
AspectRatio(
aspectRatio: 1,
child: Container(
color: Colors.red,
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),
// ),
],
),
);
}
}

View file

@ -1,49 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stackwallet/models/ordinal.dart';
import 'package:stackwallet/pages_desktop_specific/ordinals/subwidgets/desktop_ordinal_card.dart';
class DesktopOrdinalsList extends StatelessWidget {
const DesktopOrdinalsList({
Key? key,
required this.walletId,
required this.ordinalsFuture,
}) : super(key: key);
final String walletId;
final Future<List<Ordinal>> ordinalsFuture;
get spacing => 2.0;
@override
Widget build(BuildContext context) {
return FutureBuilder<List<Ordinal>>(
future: ordinalsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else if (snapshot.hasData) {
final List<Ordinal> ordinals = snapshot.data!;
return GridView.builder(
shrinkWrap: true,
itemCount: ordinals.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisSpacing: spacing as double,
mainAxisSpacing: spacing as double,
crossAxisCount: 4,
childAspectRatio: 6 / 7, // was 3/4, less data displayed now
),
itemBuilder: (_, i) => DesktopOrdinalCard(
walletId: walletId,
ordinal: ordinals[i],
),
);
} else {
return const Text('No data found.');
}
},
);
}
}

View file

@ -26,58 +26,75 @@ mixin OrdinalsInterface {
final LitescribeAPI litescribeAPI =
LitescribeAPI(baseUrl: 'https://litescribe.io/api');
void refreshInscriptions() async {
List<dynamic> _inscriptions;
final utxos = await _db.getUTXOs(_walletId).findAll();
final uniqueAddresses = getUniqueAddressesFromUTXOs(utxos);
_inscriptions = await getInscriptionDataFromAddresses(uniqueAddresses);
// TODO save inscriptions to isar which gets watched by a FutureBuilder/StreamBuilder
}
Future<void> refreshInscriptions() async {
final uniqueAddresses = await _db
.getUTXOs(_walletId)
.filter()
.addressIsNotNull()
.distinctByAddress()
.addressProperty()
.findAll();
final inscriptions =
await _getInscriptionDataFromAddresses(uniqueAddresses.cast<String>());
Future<List<InscriptionData>> getInscriptionData() async {
try {
final utxos = await _db.getUTXOs(_walletId).findAll();
final uniqueAddresses = getUniqueAddressesFromUTXOs(utxos);
return await getInscriptionDataFromAddresses(uniqueAddresses);
} catch (e) {
throw Exception('Error in OrdinalsInterface getInscriptions: $e');
}
}
final ords = inscriptions
.map((e) => Ordinal.fromInscriptionData(e, _walletId))
.toList();
Future<List<Ordinal>> getOrdinals() async {
try {
final utxos = await _db.getUTXOs(_walletId).findAll();
final uniqueAddresses = getUniqueAddressesFromUTXOs(utxos);
return await getOrdinalsFromAddresses(uniqueAddresses);
} catch (e) {
throw Exception('Error in OrdinalsInterface getOrdinals: $e');
}
await _db.isar.writeTxn(() async {
await _db.isar.ordinals
.where()
.filter()
.walletIdEqualTo(_walletId)
.deleteAll();
await _db.isar.ordinals.putAll(ords);
});
}
//
// Future<List<InscriptionData>> getInscriptionData() async {
// try {
// final utxos = await _db.getUTXOs(_walletId).findAll();
// final uniqueAddresses = getUniqueAddressesFromUTXOs(utxos);
// return await _getInscriptionDataFromAddresses(uniqueAddresses);
// } catch (e) {
// throw Exception('Error in OrdinalsInterface getInscriptions: $e');
// }
// }
//
// Future<List<Ordinal>> getOrdinals() async {
// try {
// final utxos = await _db.getUTXOs(_walletId).findAll();
// final uniqueAddresses = getUniqueAddressesFromUTXOs(utxos);
// return await getOrdinalsFromAddresses(uniqueAddresses);
// } catch (e) {
// throw Exception('Error in OrdinalsInterface getOrdinals: $e');
// }
// }
//
// List<String> getUniqueAddressesFromUTXOs(List<UTXO> utxos) {
// final Set<String> uniqueAddresses = <String>{};
// for (var utxo in utxos) {
// if (utxo.address != null) {
// uniqueAddresses.add(utxo.address!);
// }
// }
// return uniqueAddresses.toList();
// }
//
// Future<List<InscriptionData>> getInscriptionDataFromAddress(
// String address) async {
// List<InscriptionData> allInscriptions = [];
// try {
// var inscriptions = await litescribeAPI.getInscriptionsByAddress(address);
// allInscriptions.addAll(inscriptions);
// } catch (e) {
// throw Exception(
// 'Error in OrdinalsInterface getInscriptionsByAddress: $e');
// }
// return allInscriptions;
// }
List<String> getUniqueAddressesFromUTXOs(List<UTXO> utxos) {
final Set<String> uniqueAddresses = <String>{};
for (var utxo in utxos) {
if (utxo.address != null) {
uniqueAddresses.add(utxo.address!);
}
}
return uniqueAddresses.toList();
}
Future<List<InscriptionData>> getInscriptionDataFromAddress(
String address) async {
List<InscriptionData> allInscriptions = [];
try {
var inscriptions = await litescribeAPI.getInscriptionsByAddress(address);
allInscriptions.addAll(inscriptions);
} catch (e) {
throw Exception(
'Error in OrdinalsInterface getInscriptionsByAddress: $e');
}
return allInscriptions;
}
Future<List<InscriptionData>> getInscriptionDataFromAddresses(
Future<List<InscriptionData>> _getInscriptionDataFromAddresses(
List<String> addresses) async {
List<InscriptionData> allInscriptions = [];
for (String address in addresses) {
@ -92,29 +109,29 @@ mixin OrdinalsInterface {
return allInscriptions;
}
Future<List<Ordinal>> getOrdinalsFromAddress(String address) async {
try {
var inscriptions = await litescribeAPI.getInscriptionsByAddress(address);
return inscriptions
.map((data) => Ordinal.fromInscriptionData(data, _walletId))
.toList();
} catch (e) {
throw Exception('Error in OrdinalsInterface getOrdinalsFromAddress: $e');
}
}
Future<List<Ordinal>> getOrdinalsFromAddresses(List<String> addresses) async {
List<Ordinal> allOrdinals = [];
for (String address in addresses) {
try {
var inscriptions =
await litescribeAPI.getInscriptionsByAddress(address);
allOrdinals.addAll(inscriptions
.map((data) => Ordinal.fromInscriptionData(data, _walletId)));
} catch (e) {
print("Error fetching inscriptions for address $address: $e");
}
}
return allOrdinals;
}
// Future<List<Ordinal>> getOrdinalsFromAddress(String address) async {
// try {
// var inscriptions = await litescribeAPI.getInscriptionsByAddress(address);
// return inscriptions
// .map((data) => Ordinal.fromInscriptionData(data, _walletId))
// .toList();
// } catch (e) {
// throw Exception('Error in OrdinalsInterface getOrdinalsFromAddress: $e');
// }
// }
//
// Future<List<Ordinal>> getOrdinalsFromAddresses(List<String> addresses) async {
// List<Ordinal> allOrdinals = [];
// for (String address in addresses) {
// try {
// var inscriptions =
// await litescribeAPI.getInscriptionsByAddress(address);
// allOrdinals.addAll(inscriptions
// .map((data) => Ordinal.fromInscriptionData(data, _walletId)));
// } catch (e) {
// print("Error fetching inscriptions for address $address: $e");
// }
// }
// return allOrdinals;
// }
}