diff --git a/packages/dart/test/breez_liquid_dart_test.dart b/packages/dart/test/breez_liquid_dart_test.dart index ffc00cb..d9834e4 100644 --- a/packages/dart/test/breez_liquid_dart_test.dart +++ b/packages/dart/test/breez_liquid_dart_test.dart @@ -9,7 +9,7 @@ void main() { group('main', () { setUpAll(() async { await initApi(); - ConnectRequest connectRequest = ConnectRequest(mnemonic: "", network: Network.liquidTestnet); + ConnectRequest connectRequest = ConnectRequest(mnemonic: "", config: defaultConfig(network: Network.testnet)); sdk = await connect(req: connectRequest); }); diff --git a/packages/flutter/example/lib/main.dart b/packages/flutter/example/lib/main.dart index 5598004..484ff5f 100644 --- a/packages/flutter/example/lib/main.dart +++ b/packages/flutter/example/lib/main.dart @@ -5,21 +5,23 @@ import 'package:flutter_breez_liquid_example/routes/home/home_page.dart'; import 'package:flutter_breez_liquid_example/services/credentials_manager.dart'; import 'package:flutter_breez_liquid_example/services/keychain.dart'; import 'package:flutter_breez_liquid_example/utils/config.dart'; -import 'package:path_provider/path_provider.dart'; + +import 'services/breez_liquid_sdk.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await initialize(); + final BreezLiquidSDK liquidSDK = BreezLiquidSDK(); final credentialsManager = CredentialsManager(keyChain: KeyChain()); final mnemonic = await credentialsManager.restoreMnemonic(); - BindingLiquidSdk? liquidSDK; if (mnemonic.isNotEmpty) { - liquidSDK = await reconnect(mnemonic: mnemonic); + reconnect(liquidSDK: liquidSDK, mnemonic: mnemonic); } runApp(App(credentialsManager: credentialsManager, liquidSDK: liquidSDK)); } Future reconnect({ + required BreezLiquidSDK liquidSDK, required String mnemonic, Network network = Network.mainnet, }) async { @@ -28,13 +30,13 @@ Future reconnect({ config: config, mnemonic: mnemonic, ); - return await connect(req: req); + return await liquidSDK.connect(req: req); } class App extends StatefulWidget { final CredentialsManager credentialsManager; - final BindingLiquidSdk? liquidSDK; - const App({super.key, required this.credentialsManager, this.liquidSDK}); + final BreezLiquidSDK liquidSDK; + const App({super.key, required this.credentialsManager, required this.liquidSDK}); static const title = 'Breez Liquid SDK Demo'; @@ -48,9 +50,15 @@ class _AppState extends State { return MaterialApp( title: App.title, theme: ThemeData.from(colorScheme: ColorScheme.fromSeed(seedColor: Colors.white), useMaterial3: true), - home: widget.liquidSDK == null - ? ConnectPage(credentialsManager: widget.credentialsManager) - : HomePage(credentialsManager: widget.credentialsManager, liquidSDK: widget.liquidSDK!), + home: widget.liquidSDK.wallet == null + ? ConnectPage( + liquidSDK: widget.liquidSDK, + credentialsManager: widget.credentialsManager, + ) + : HomePage( + credentialsManager: widget.credentialsManager, + liquidSDK: widget.liquidSDK, + ), ); } } diff --git a/packages/flutter/example/lib/routes/connect/connect_page.dart b/packages/flutter/example/lib/routes/connect/connect_page.dart index 018174c..ea8aa6a 100644 --- a/packages/flutter/example/lib/routes/connect/connect_page.dart +++ b/packages/flutter/example/lib/routes/connect/connect_page.dart @@ -3,13 +3,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_breez_liquid/flutter_breez_liquid.dart'; import 'package:flutter_breez_liquid_example/routes/connect/restore_page.dart'; import 'package:flutter_breez_liquid_example/routes/home/home_page.dart'; +import 'package:flutter_breez_liquid_example/services/breez_liquid_sdk.dart'; import 'package:flutter_breez_liquid_example/services/credentials_manager.dart'; import 'package:flutter_breez_liquid_example/utils/config.dart'; -import 'package:path_provider/path_provider.dart'; class ConnectPage extends StatefulWidget { + final BreezLiquidSDK liquidSDK; final CredentialsManager credentialsManager; - const ConnectPage({super.key, required this.credentialsManager}); + const ConnectPage({super.key, required this.liquidSDK, required this.credentialsManager}); @override State createState() => _ConnectPageState(); @@ -84,7 +85,7 @@ class _ConnectPageState extends State { context, MaterialPageRoute( builder: (BuildContext context) => HomePage( - liquidSDK: liquidSDK, + liquidSDK: widget.liquidSDK, credentialsManager: widget.credentialsManager, ), ), @@ -103,6 +104,6 @@ class _ConnectPageState extends State { config: config, mnemonic: mnemonic, ); - return await connect(req: req); + return await widget.liquidSDK.connect(req: req); } } diff --git a/packages/flutter/example/lib/routes/home/home_page.dart b/packages/flutter/example/lib/routes/home/home_page.dart index 8fc29d7..aad0acd 100644 --- a/packages/flutter/example/lib/routes/home/home_page.dart +++ b/packages/flutter/example/lib/routes/home/home_page.dart @@ -1,17 +1,17 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:flutter_breez_liquid/flutter_breez_liquid.dart'; import 'package:flutter_breez_liquid_example/routes/home/widgets/balance.dart'; import 'package:flutter_breez_liquid_example/routes/home/widgets/bottom_app_bar.dart'; import 'package:flutter_breez_liquid_example/routes/home/widgets/drawer.dart'; import 'package:flutter_breez_liquid_example/routes/home/widgets/payment_list/payment_list.dart'; import 'package:flutter_breez_liquid_example/routes/home/widgets/qr_scan_action_button.dart'; +import 'package:flutter_breez_liquid_example/services/breez_liquid_sdk.dart'; import 'package:flutter_breez_liquid_example/services/credentials_manager.dart'; class HomePage extends StatefulWidget { final CredentialsManager credentialsManager; - final BindingLiquidSdk liquidSDK; + final BreezLiquidSDK liquidSDK; const HomePage({super.key, required this.credentialsManager, required this.liquidSDK}); @@ -20,26 +20,6 @@ class HomePage extends StatefulWidget { } class _HomePageState extends State { - Stream walletInfoStream() async* { - debugPrint("Initialized walletInfoStream"); - yield await widget.liquidSDK.getInfo(); - while (true) { - await Future.delayed(const Duration(seconds: 10)); - yield await widget.liquidSDK.getInfo(); - debugPrint("Refreshed wallet info"); - } - } - - Stream> paymentsStream() async* { - debugPrint("Initialized paymentsStream"); - yield await widget.liquidSDK.listPayments(); - while (true) { - await Future.delayed(const Duration(seconds: 10)); - yield await widget.liquidSDK.listPayments(); - debugPrint("Refreshed payments"); - } - } - @override Widget build(BuildContext context) { return MaterialApp( @@ -70,13 +50,13 @@ class _HomePageState extends State { Container( height: constraints.maxHeight * 0.3, color: Colors.white, - child: Balance(walletInfoStream: walletInfoStream().asBroadcastStream()), + child: Balance(walletInfoStream: widget.liquidSDK.walletInfoStream), ), Container( height: constraints.maxHeight * 0.7, color: Colors.white, child: PaymentList( - paymentsStream: paymentsStream().asBroadcastStream(), + paymentsStream: widget.liquidSDK.paymentsStream, onRefresh: () async => await _sync(), ), ), @@ -84,12 +64,13 @@ class _HomePageState extends State { ); }, ), - drawer: HomePageDrawer(liquidSDK: widget.liquidSDK, credentialsManager: widget.credentialsManager), - floatingActionButton: QrActionButton(liquidSDK: widget.liquidSDK), + drawer: HomePageDrawer( + liquidSDK: widget.liquidSDK.wallet!, credentialsManager: widget.credentialsManager), + floatingActionButton: QrActionButton(liquidSDK: widget.liquidSDK.wallet!), floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, bottomNavigationBar: HomePageBottomAppBar( - liquidSDK: widget.liquidSDK, - paymentsStream: paymentsStream().asBroadcastStream(), + liquidSDK: widget.liquidSDK.wallet!, + paymentsStream: widget.liquidSDK.paymentsStream, ), ), ); @@ -98,7 +79,7 @@ class _HomePageState extends State { Future _sync() async { try { debugPrint("Syncing wallet."); - await widget.liquidSDK.sync(); + await widget.liquidSDK.wallet!.sync(); debugPrint("Wallet synced!"); } on Exception catch (e) { final errMsg = "Failed to sync wallet. $e"; diff --git a/packages/flutter/example/lib/routes/home/widgets/bottom_app_bar.dart b/packages/flutter/example/lib/routes/home/widgets/bottom_app_bar.dart index a0f7a98..17b2035 100644 --- a/packages/flutter/example/lib/routes/home/widgets/bottom_app_bar.dart +++ b/packages/flutter/example/lib/routes/home/widgets/bottom_app_bar.dart @@ -49,7 +49,7 @@ class HomePageBottomAppBar extends StatelessWidget { context: context, builder: (context) => ReceivePaymentDialog( liquidSDK: liquidSDK, - paymentsStream: paymentsStream.asBroadcastStream(), + paymentsStream: paymentsStream, ), ); }, diff --git a/packages/flutter/example/lib/routes/home/widgets/payment_list/widgets/payment_item.dart b/packages/flutter/example/lib/routes/home/widgets/payment_list/widgets/payment_item.dart index 0ffef8c..c86f474 100644 --- a/packages/flutter/example/lib/routes/home/widgets/payment_list/widgets/payment_item.dart +++ b/packages/flutter/example/lib/routes/home/widgets/payment_list/widgets/payment_item.dart @@ -62,7 +62,7 @@ class PaymentItem extends StatelessWidget { "${item.paymentType == PaymentType.send ? "-" : "+"}${item.amountSat} sats", style: Theme.of(context).textTheme.bodyLarge, ), - if (item.feesSat != null) ...[ + if (item.feesSat != BigInt.zero) ...[ Text("FEE: ${item.paymentType == PaymentType.receive ? "-" : ""}${item.feesSat} sats"), ] ], diff --git a/packages/flutter/example/lib/services/breez_liquid_sdk.dart b/packages/flutter/example/lib/services/breez_liquid_sdk.dart new file mode 100644 index 0000000..6467c7b --- /dev/null +++ b/packages/flutter/example/lib/services/breez_liquid_sdk.dart @@ -0,0 +1,149 @@ +import 'dart:async'; + +import 'package:flutter_breez_liquid/flutter_breez_liquid.dart' as liquid_sdk; +import 'package:rxdart/rxdart.dart'; + +class BreezLiquidSDK { + liquid_sdk.BindingLiquidSdk? wallet; + + Future connect({ + required liquid_sdk.ConnectRequest req, + }) async { + wallet = await liquid_sdk.connect(req: req); + _initializeEventsStream(wallet!); + _subscribeToSdkStreams(wallet!); + await _fetchWalletData(wallet!); + return wallet!; + } + + void disconnect(liquid_sdk.BindingLiquidSdk sdk) { + sdk.disconnect(); + _unsubscribeFromSdkStreams(); + } + + Future _fetchWalletData(liquid_sdk.BindingLiquidSdk sdk) async { + await _getInfo(sdk); + await _listPayments(sdk); + } + + Future _getInfo(liquid_sdk.BindingLiquidSdk sdk) async { + final walletInfo = await sdk.getInfo(req: const liquid_sdk.GetInfoRequest(withScan: false)); + _walletInfoController.add(walletInfo); + return walletInfo; + } + + Future> _listPayments(liquid_sdk.BindingLiquidSdk sdk) async { + final paymentsList = await sdk.listPayments(); + _paymentsController.add(paymentsList); + return paymentsList; + } + + StreamSubscription? _breezLogSubscription; + + Stream? _breezLogStream; + + /// Initializes SDK log stream. + /// + /// Call once on your Dart entrypoint file, e.g.; `lib/main.dart`. + void initializeLogStream() { + _breezLogStream ??= liquid_sdk.breezLogStream().asBroadcastStream(); + } + + StreamSubscription? _breezEventsSubscription; + + Stream? _breezEventsStream; + + void _initializeEventsStream(liquid_sdk.BindingLiquidSdk sdk) { + _breezEventsStream ??= sdk.addEventListener().asBroadcastStream(); + } + + /// Subscribes to SDK's event & log streams. + void _subscribeToSdkStreams(liquid_sdk.BindingLiquidSdk sdk) { + _subscribeToEventsStream(sdk); + _subscribeToLogStream(); + } + + final StreamController _walletInfoController = + BehaviorSubject(); + + Stream get walletInfoStream => _walletInfoController.stream; + + final StreamController _paymentResultStream = StreamController.broadcast(); + + final StreamController> _paymentsController = + BehaviorSubject>(); + + Stream> get paymentsStream => _paymentsController.stream; + + Stream get paymentResultStream => _paymentResultStream.stream; + + /* TODO: Liquid - Log statements are added for debugging purposes, should be removed after early development stage is complete & events are behaving as expected.*/ + /// Subscribes to LiquidSdkEvent's stream + void _subscribeToEventsStream(liquid_sdk.BindingLiquidSdk sdk) { + _breezEventsSubscription = _breezEventsStream?.listen( + (event) async { + if (event is liquid_sdk.LiquidSdkEvent_PaymentFailed) { + _logStreamController + .add(liquid_sdk.LogEntry(line: "Payment Failed. ${event.details.swapId}", level: "WARN")); + _paymentResultStream.addError(PaymentException(event.details)); + } + if (event is liquid_sdk.LiquidSdkEvent_PaymentPending) { + _logStreamController + .add(liquid_sdk.LogEntry(line: "Payment Pending. ${event.details.swapId}", level: "INFO")); + _paymentResultStream.add(event.details); + } + if (event is liquid_sdk.LiquidSdkEvent_PaymentRefunded) { + _logStreamController + .add(liquid_sdk.LogEntry(line: "Payment Refunded. ${event.details.swapId}", level: "INFO")); + _paymentResultStream.add(event.details); + } + if (event is liquid_sdk.LiquidSdkEvent_PaymentRefundPending) { + _logStreamController.add( + liquid_sdk.LogEntry(line: "Pending Payment Refund. ${event.details.swapId}", level: "INFO")); + _paymentResultStream.add(event.details); + } + if (event is liquid_sdk.LiquidSdkEvent_PaymentSucceeded) { + _logStreamController + .add(liquid_sdk.LogEntry(line: "Payment Succeeded. ${event.details.swapId}", level: "INFO")); + _paymentResultStream.add(event.details); + await _fetchWalletData(sdk); + } + if (event is liquid_sdk.LiquidSdkEvent_PaymentWaitingConfirmation) { + _logStreamController.add(liquid_sdk.LogEntry( + line: "Payment Waiting Confirmation. ${event.details.swapId}", level: "INFO")); + _paymentResultStream.add(event.details); + } + if (event is liquid_sdk.LiquidSdkEvent_Synced) { + _logStreamController.add(const liquid_sdk.LogEntry(line: "Received Synced event.", level: "INFO")); + await _fetchWalletData(sdk); + } + }, + ); + } + + final _logStreamController = StreamController.broadcast(); + + Stream get logStream => _logStreamController.stream; + + /// Subscribes to SDK's logs stream + void _subscribeToLogStream() { + _breezLogSubscription = _breezLogStream?.listen((logEntry) { + _logStreamController.add(logEntry); + }, onError: (e) { + _logStreamController.addError(e); + }); + } + + /// Unsubscribes from SDK's event & log streams. + void _unsubscribeFromSdkStreams() { + _breezEventsSubscription?.cancel(); + _breezLogSubscription?.cancel(); + } +} + +// TODO: Liquid - Return this exception from the SDK directly +class PaymentException { + final liquid_sdk.Payment details; + + const PaymentException(this.details); +} diff --git a/packages/flutter/example/pubspec.lock b/packages/flutter/example/pubspec.lock index 65165a4..b0ac61f 100644 --- a/packages/flutter/example/pubspec.lock +++ b/packages/flutter/example/pubspec.lock @@ -402,10 +402,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -446,6 +446,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.1" + rxdart: + dependency: "direct main" + description: + name: rxdart + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" + source: hosted + version: "0.27.7" sky_engine: dependency: transitive description: flutter diff --git a/packages/flutter/example/pubspec.yaml b/packages/flutter/example/pubspec.yaml index 10bf0e4..bcaffde 100644 --- a/packages/flutter/example/pubspec.yaml +++ b/packages/flutter/example/pubspec.yaml @@ -25,6 +25,7 @@ dependencies: path_provider: ^2.1.3 qr_flutter: ^4.1.0 intl: ^0.19.0 + rxdart: ^0.27.7 dev_dependencies: flutter_test: