// Haveno App extends the features of Haveno, supporting mobile devices and more.
// Copyright (C) 2024 Kewbit (https://kewbit.org)
// Source Code: https://git.haveno.com/haveno/haveno-app.git
//
// Author: Kewbit
// Website: https://kewbit.org
// Contact Email: me@kewbit.org
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
import 'package:flutter/material.dart';
import 'package:haveno/enums.dart';
import 'package:haveno/haveno_client.dart';
import 'package:haveno/haveno_service.dart';
import 'package:haveno_app/models/schema.dart';
import 'package:haveno_app/providers/haveno_client_providers/disputes_provider.dart';
import 'package:haveno_app/providers/haveno_client_providers/offers_provider.dart';
import 'package:haveno_app/providers/haveno_client_providers/price_provider.dart';
import 'package:haveno_app/providers/haveno_client_providers/trades_provider.dart';
import 'package:haveno_app/providers/haveno_client_providers/wallets_provider.dart';
import 'package:haveno_app/providers/haveno_client_providers/xmr_connections_provider.dart';
import 'package:haveno_app/services/desktop_manager_service.dart';
import 'package:haveno_app/services/local_notification_service.dart';
import 'package:haveno_app/services/platform_system_service/factory.dart';
import 'package:haveno_app/services/platform_system_service/schema.dart';
import 'package:haveno_app/services/secure_storage_service.dart';
import 'package:haveno_app/utils/payment_utils.dart';
import 'package:haveno_app/utils/salt.dart';
import 'package:haveno_app/views/screens/seednode_setup_screen.dart';
import 'package:provider/provider.dart';
import 'package:tray_manager/tray_manager.dart';
class DesktopLifecycleWidget extends PlatformLifecycleWidget {
const DesktopLifecycleWidget({
super.key,
required super.child,
required super.builder,
});
@override
_DesktopLifecycleWidgetState createState() => _DesktopLifecycleWidgetState();
}
class _DesktopLifecycleWidgetState extends PlatformLifecycleState with TrayListener {
late PlatformService platformService;
late SyncManager syncManager;
late NotificationsService notificationsService;
late String? _daemonPassword;
@override
Future initPlatform() async {
final desktopManagerService = DesktopManagerService();
print("Intializing tray mananger and adding lifecycle widget as listener");
//intializeSystemTray();
trayManager.addListener(this);
print("Initialized desktop platform");
HavenoChannel havenoChannel = HavenoChannel();
SecureStorageService secureStorageService = SecureStorageService();
platformService = await getPlatformService();
print("Setting up Tor daemon...");
await platformService.setupTorDaemon();
// Ensure seed node is configured
var seedNodeConfigured = await desktopManagerService.isSeednodeConfigured();
while (seedNodeConfigured == null || seedNodeConfigured == false) {
// Navigate to SeedNodeSetupScreen and wait for the result
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SeedNodeSetupScreen(),
),
);
// Recheck seed node configuration
seedNodeConfigured = await desktopManagerService.isSeednodeConfigured();
}
_daemonPassword = await secureStorageService.readHavenoDaemonPassword();
if (_daemonPassword == null) {
_daemonPassword = generateHexSalt(16);
await secureStorageService
.writeHavenoDaemonPassword(_daemonPassword as String);
print("Generated and saved new daemon password.");
await platformService.setupHavenoDaemon(_daemonPassword);
await havenoChannel.connect('127.0.0.1', 3201, _daemonPassword as String);
} else {
print("Loaded existing daemon password.");
await platformService.setupHavenoDaemon(_daemonPassword);
await havenoChannel.connect('127.0.0.1', 3201, _daemonPassword as String);
print("Haveno Daemon connected.");
setState(() {});
WidgetsBinding.instance.addPostFrameCallback((_) async {
print(
"Setting up and starting sync manager and notification listeners");
await havenoChannel.onConnected;
await _createSyncManagerWithTasks();
await _createNotificationListeners();
});
}
}
Future _createSyncManagerWithTasks() async {
syncManager = SyncManager(checkInterval: const Duration(seconds: 1));
var offersProvider = Provider.of(context, listen: false);
var pricesProvider = Provider.of(context, listen: false);
var walletsProvider = Provider.of(context, listen: false);
var tradesProvider = Provider.of(context, listen: false);
var xmrConnectionsProvider = Provider.of(context, listen: false);
var fetchOffersTask = SyncTask(taskFunction: offersProvider.getOffers, cooldown: const Duration(minutes: 1));
var fetchMyOffersTask = SyncTask(taskFunction: offersProvider.getMyOffers, cooldown: const Duration(minutes: 2));
var fetchPricesTask = SyncTask(taskFunction: pricesProvider.getXmrMarketPrices, cooldown: const Duration(seconds: 5));
var fetchBalancesTask = SyncTask(taskFunction: walletsProvider.getBalances, cooldown: const Duration(minutes: 2));
var fetchTransactionsTask = SyncTask(taskFunction: walletsProvider.getXmrTxs, cooldown: const Duration(minutes: 2));
var fetchTradesTask = SyncTask(taskFunction: tradesProvider.getTrades, cooldown: const Duration(minutes: 1));
var fetchXmrConnections = SyncTask(taskFunction: xmrConnectionsProvider.checkConnections, cooldown: const Duration(minutes: 1));
syncManager.addTask(fetchOffersTask);
syncManager.addTask(fetchMyOffersTask);
syncManager.addTask(fetchPricesTask);
syncManager.addTask(fetchBalancesTask);
syncManager.addTask(fetchTransactionsTask);
syncManager.addTask(fetchTradesTask);
syncManager.addTask(fetchXmrConnections);
syncManager.start();
}
Future _createNotificationListeners() async {
var tradesProvider = Provider.of(context, listen: false);
var disputesProvider =
Provider.of(context, listen: false);
notificationsService = NotificationsService();
var localNotificationsService = LocalNotificationsService();
notificationsService.addListener(
NotificationMessage_NotificationType.CHAT_MESSAGE,
(notification) {
if (notification.chatMessage.type == SupportType.TRADE) {
tradesProvider.addChatMessage(notification.chatMessage);
localNotificationsService.showNotification(
id: notification.chatMessage.hashCode,
title: 'New Message',
body: notification.chatMessage.message);
} else {
disputesProvider.addChatMessage(notification.chatMessage);
tradesProvider.addChatMessage(notification.chatMessage);
localNotificationsService.showNotification(
id: notification.chatMessage.hashCode,
title: 'New Support Message',
body: notification.chatMessage.message);
}
},
);
notificationsService.addListener(
NotificationMessage_NotificationType.TRADE_UPDATE,
(notification) {
tradesProvider.createOrUpdateTrade(notification.trade);
var direction =
notification.trade.role.contains('buyer') ? 'buying' : 'selling';
var total = formatXmr(notification.trade.amount);
var paymentMethod = notification.trade.offer.paymentMethodShortName;
localNotificationsService.showNotification(
id: notification.chatMessage.hashCode,
title: 'New Trade Opened',
body:
'You\'re $direction a total of $total XMR via $paymentMethod');
},
);
notificationsService.listen();
}
@override
void dispose() {
notificationsService.stop(); // Stop listening to notifications
syncManager.stop(); // Stop sync manager
trayManager.removeListener(this);
super.dispose();
}
}