diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 7662ef1..d6366e9 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -1,75 +1,75 @@ - - CADisableMinimumFrameDurationOnPhone - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Realt Apps - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - realt_apps - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSApplicationQueriesSchemes - - https - - LSRequiresIPhoneOS - - NSCameraUsageDescription - This app needs access to the camera to scan QR codes for adding wallet addresses. - NSDocumentsFolderUsageDescription - Nous avons besoin d'accéder à vos documents pour sauvegarder et charger les fichiers. - NSPhotoLibraryUsageDescription - Cette application utilise la bibliothèque de photos pour permettre aux utilisateurs de sélectionner et d'envoyer des images. - UIApplicationSupportsIndirectInputEvents - - NSLocationWhenInUseUsageDescription - This app requires access to your location to provide location-based services. - UIBackgroundModes - - processing - remote-notification - fetch - - BGTaskSchedulerPermittedIdentifiers - - com.example.realtApps.backgroundtask - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIStatusBarHidden - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Realt Apps + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + realt_apps + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSApplicationQueriesSchemes + + https + + LSRequiresIPhoneOS + + NSCameraUsageDescription + This app needs access to the camera to scan QR codes for adding wallet addresses. + NSDocumentsFolderUsageDescription + Nous avons besoin d'accéder à vos documents pour sauvegarder et charger les fichiers. + NSPhotoLibraryUsageDescription + Cette application utilise la bibliothèque de photos pour permettre aux utilisateurs de sélectionner et d'envoyer des images. + UIApplicationSupportsIndirectInputEvents + + NSLocationWhenInUseUsageDescription + This app requires access to your location to provide location-based services. + UIBackgroundModes + + processing + remote-notification + fetch + + BGTaskSchedulerPermittedIdentifiers + + com.example.realtApps.backgroundtask + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + diff --git a/lib/api/api_service.dart b/lib/api/api_service.dart index 661d348..ca377e0 100644 --- a/lib/api/api_service.dart +++ b/lib/api/api_service.dart @@ -673,4 +673,83 @@ class ApiService { return []; } } + + static Future> fetchTokenVolumes({bool forceFetch = false}) async { + logger.i("apiService: fetchTokenVolumes -> Lancement de la requête"); + + var box = Hive.box('realTokens'); + final lastFetchTime = box.get('lastTokenVolumesFetchTime'); + final DateTime now = DateTime.now(); + + // Vérifiez si le cache est valide + if (!forceFetch && lastFetchTime != null) { + final DateTime lastFetch = DateTime.parse(lastFetchTime); + if (now.difference(lastFetch) < Parameters.apiCacheDuration) { + final cachedData = box.get('cachedTokenVolumesData'); + if (cachedData != null) { + logger.i("apiService: fetchTokenVolumes -> Requête annulée, cache valide"); + return json.decode(cachedData); + } + } + } + + // Définition des variables pour la requête GraphQL + const List stables = [ + "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d", + "0xddafbb505ad214d7b80b1f830fccc89b60fb7a83", + "0x7349c9eaa538e118725a6130e0f8341509b9f8a0" + ]; + final String limitDate = DateTime.now().subtract(Duration(days: 30)).toIso8601String().split('T').first; + + // Envoyer la requête GraphQL + final response = await http.post( + Uri.parse(Parameters.yamUrl), + headers: {'Content-Type': 'application/json'}, + body: json.encode({ + "query": ''' + query GetTokenVolumes(\$stables: [String!], \$limitDate: String!) { + tokens(first: 1000) { + id + decimals + volumes(where: { token_in: \$stables }) { + token { + decimals + } + volumeDays(orderBy: date, orderDirection: desc, where: { date_gte: \$limitDate }) { + date + quantity + volume + } + } + } + } + ''', + "variables": { + "stables": stables, + "limitDate": limitDate, + } + }), + ); + + if (response.statusCode == 200) { + logger.i("apiService: fetchTokenVolumes -> Requête lancée avec succès"); + + final decodedResponse = json.decode(response.body); + if (decodedResponse['data'] != null && decodedResponse['data']['tokens'] != null) { + final List tokens = decodedResponse['data']['tokens']; + + // Mettre les données dans le cache + box.put('cachedTokenVolumesData', json.encode(tokens)); + box.put('lastTokenVolumesFetchTime', now.toIso8601String()); + box.put('lastExecutionTime_TokenVolumes', now.toIso8601String()); + + return tokens; + } else { + logger.w("apiService: fetchTokenVolumes -> Aucune donnée disponible"); + return []; + } + } else { + throw Exception('apiService: fetchTokenVolumes -> Échec de la requête'); + } + } } diff --git a/lib/api/data_manager.dart b/lib/api/data_manager.dart index b4b0216..848b428 100644 --- a/lib/api/data_manager.dart +++ b/lib/api/data_manager.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; import 'dart:convert'; @@ -93,6 +95,7 @@ class DataManager extends ChangeNotifier { int rentedUnits = 0; int totalUnits = 0; double initialTotalValue = 0.0; + double yamTotalValue = 0.0; double totalTokens = 0.0; double walletTokensSums = 0.0; double rmmTokensSums = 0.0; @@ -151,8 +154,9 @@ class DataManager extends ChangeNotifier { List> propertiesForSaleFetched = []; List> yamMarketFetched = []; List> yamMarketData = []; - List> yamMarket = []; + List> yamHistory = []; + var customInitPricesBox = Hive.box('CustomInitPrices'); @@ -262,6 +266,20 @@ class DataManager extends ChangeNotifier { logger.w("Erreur lors de la récupération des données: $error"); } + // Mise à jour des Yam History + var yamHistoryData = await ApiService.fetchTokenVolumes(forceFetch: forceFetch); + if (yamHistoryData.isNotEmpty) { + logger.i("Mise à jour de l'historiques YAM avec de nouvelles valeurs."); + + // Sauvegarder les balances dans Hive + box.put('yamHistory', json.encode(yamHistoryData)); + rmmBalances = yamHistoryData.cast>(); + fetchYamHistory(); + notifyListeners(); // Notifier les listeners après la mise à jour + } else { + logger.d("Les RMM Balances sont vides, pas de mise à jour."); + } + loadWalletBalanceHistory(); loadRoiHistory(); loadApyHistory(); @@ -587,6 +605,8 @@ class DataManager extends ChangeNotifier { var box = Hive.box('realTokens'); initialTotalValue = 0.0; + yamTotalValue = 0.0; + // Charger les données en cache si disponibles final cachedGnosisTokens = box.get('cachedTokenData_gnosis'); if (cachedGnosisTokens != null) { @@ -738,6 +758,16 @@ class DataManager extends ChangeNotifier { List parts3 = fullName.split(','); String city = parts3.length >= 2 ? parts[1].trim() : 'Unknown City'; + +// Chercher la valeur Yam associée au token + final yamData = yamHistory.firstWhere( + (yam) => yam['id'].toLowerCase() == tokenContractAddress, + orElse: () => {}, + ); + + final double yamTotalVolume = yamData['totalVolume'] ?? 0.0; + final double yamAverageValue = yamData['averageValue'] ?? tokenPrice; + // Ajouter au Portfolio newPortfolio.add({ 'id': matchingRealToken['id'], @@ -794,10 +824,14 @@ class DataManager extends ChangeNotifier { 'gnosisContract': matchingRealToken['gnosisContract'], 'totalRentReceived': totalRentReceived, // Ajout du loyer total reçu 'initPrice': initPrice, - 'section8paid': matchingRealToken['section8paid'] ?? 0.0 + 'section8paid': matchingRealToken['section8paid'] ?? 0.0, + + 'yamTotalVolume': yamTotalVolume, // Ajout de la valeur Yam calculée + 'yamAverageValue': yamAverageValue // Ajout de la valeur moyenne Yam calculée }); initialTotalValue += double.parse(walletToken['amount']) * initPrice; + yamTotalValue += double.parse(walletToken['amount']) * yamAverageValue; if (tokenContractAddress.isNotEmpty) { // Récupérer les informations de loyer pour ce token @@ -880,6 +914,15 @@ class DataManager extends ChangeNotifier { List parts3 = fullName.split(','); String city = parts3.length >= 2 ? parts[1].trim() : 'Unknown'; +// Chercher la valeur Yam associée au token + final yamData = yamHistory.firstWhere( + (yam) => yam['id'].toLowerCase() == tokenContractAddress, + orElse: () => {}, + ); + + final double yamTotalVolume = yamData['totalVolume'] ?? 0.0; + final double yamAverageValue = yamData['averageValue'] ?? tokenPrice; + // Ajouter au Portfolio newPortfolio.add({ 'id': matchingRealToken['id'], @@ -937,10 +980,14 @@ class DataManager extends ChangeNotifier { 'gnosisContract': matchingRealToken['gnosisContract'], 'totalRentReceived': totalRentReceived, // Ajout du loyer total reçu 'initPrice': initPrice, - 'section8paid': matchingRealToken['section8paid'] ?? 0.0 + 'section8paid': matchingRealToken['section8paid'] ?? 0.0, + + 'yamTotalVolume': yamTotalVolume, // Ajout de la valeur Yam calculée + 'yamAverageValue': yamData['averageValue'], // Ajout de la valeur moyenne Yam calculée }); initialTotalValue += amount * initPrice; + yamTotalValue += amount * yamAverageValue; if (tokenContractAddress.isNotEmpty) { // Récupérer les informations de loyer pour ce token @@ -963,8 +1010,7 @@ class DataManager extends ChangeNotifier { } // Mise à jour des variables pour le Dashboard - totalWalletValue = - walletValueSum + rmmValueSum + rwaValue + totalUsdcDepositBalance + totalXdaiDepositBalance - totalUsdcBorrowBalance - totalXdaiBorrowBalance; + totalWalletValue = walletValueSum + rmmValueSum + rwaValue + totalUsdcDepositBalance + totalXdaiDepositBalance - totalUsdcBorrowBalance - totalXdaiBorrowBalance; archiveTotalWalletValue(totalWalletValue); walletValue = double.parse(walletValueSum.toStringAsFixed(3)); @@ -1775,4 +1821,63 @@ class DataManager extends ChangeNotifier { logger.w("Aucune donnée YamMarket disponible."); } } + +void fetchYamHistory() { + var box = Hive.box('realTokens'); + final yamHistoryJson = box.get('yamHistory'); + + if (yamHistoryJson == null) { + logger.w("fetchYamHistory -> Aucune donnée Yam History trouvée dans Hive."); + return; + } + + List yamHistoryData = json.decode(yamHistoryJson); + + List> tokenStatistics = yamHistoryData.map((tokenData) { + final tokenDecimals = int.tryParse(tokenData['decimals'].toString()) ?? 18; + final volumes = tokenData['volumes'] ?? []; + + double totalVolume = 0; + double totalQuantity = 0; + + for (var volume in volumes) { + final volumeTokenDecimals = int.tryParse(volume['token']['decimals'].toString()) ?? 6; + final volumeDays = volume['volumeDays'] ?? []; + + + for (var day in volumeDays) { + + final dayVolume = (day['volume'] != null) + ? (double.tryParse(day['volume'].toString()) ?? 0) / pow(10, volumeTokenDecimals) + : 0; + final dayQuantity = (day['quantity'] != null) + ? (double.tryParse(day['quantity'].toString()) ?? 0) / pow(10, tokenDecimals) + : 0; + + + totalVolume += dayVolume; + totalQuantity += dayQuantity; + } + } + + double averageValue = 0; + if (totalQuantity > 1e-10) { + averageValue = totalVolume / totalQuantity; + } else { + logger.w("Valeur aberrante détectée : totalQuantity=$totalQuantity pour token ${tokenData['id']}"); + } + + return { + 'id': tokenData['id'], + 'totalVolume': totalVolume, + 'averageValue': averageValue, + }; + }).toList(); + + logger.i("fetchyamHistory -> Mise à jour des statistiques des tokens Yam."); + yamHistory = tokenStatistics; + + notifyListeners(); +} + } diff --git a/lib/main.dart b/lib/main.dart index 31b973a..5261111 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -37,6 +37,7 @@ void main() async { Hive.openBox('apyValueArchive'), Hive.openBox('customInitPrices'), Hive.openBox('YamMarket'), + Hive.openBox('YamHistory'), ]); // Initialisation de SharedPreferences et DataManager diff --git a/lib/pages/dashboard/dashboard_page.dart b/lib/pages/dashboard/dashboard_page.dart index e09e7e8..95b524e 100644 --- a/lib/pages/dashboard/dashboard_page.dart +++ b/lib/pages/dashboard/dashboard_page.dart @@ -752,15 +752,24 @@ Widget _buildVerticalGauges(double factor, BuildContext context, DataManager dat _buildCard( S.of(context).wallet, Icons.dashboard, - _buildValueBeforeText( + _buildValueBeforeText( Utils.getFormattedAmount( - dataManager.convert(dataManager.totalWalletValue), + dataManager.convert(dataManager.yamTotalValue), dataManager.currencySymbol, _showAmounts), - S.of(context).totalPortfolio, + 'projection YAM', ), + [ + _buildValueBeforeText( + Utils.getFormattedAmount( + dataManager.convert(dataManager.totalWalletValue), + dataManager.currencySymbol, + _showAmounts), + S.of(context).totalPortfolio, + + ), _buildIndentedBalance( S.of(context).wallet, dataManager.convert(dataManager.walletValue), diff --git a/lib/pages/portfolio/portfolio_display_1.dart b/lib/pages/portfolio/portfolio_display_1.dart index 7ad23c3..4448010 100644 --- a/lib/pages/portfolio/portfolio_display_1.dart +++ b/lib/pages/portfolio/portfolio_display_1.dart @@ -314,10 +314,28 @@ class PortfolioDisplay1 extends StatelessWidget { ), ], ), - Text( - '${S.of(context).totalValue}: ${Utils.formatCurrency(dataManager.convert(token['totalValue']), dataManager.currencySymbol)}', - style: TextStyle(fontSize: 13 + appState.getTextSizeOffset()), - ), + RichText( + text: TextSpan( + style: TextStyle( + fontSize: 13 + appState.getTextSizeOffset(), + ), + children: [ + TextSpan( + text: '${S.of(context).totalValue}: ${Utils.formatCurrency(dataManager.convert(token['totalValue']), dataManager.currencySymbol)} yam: ', + ), + TextSpan( + text: '${Utils.formatCurrency(dataManager.convert((token['yamAverageValue'] * token['amount'])), dataManager.currencySymbol)} (${(token['yamAverageValue'] / token['tokenPrice'] * 100).toStringAsFixed(0)}%)', + style: TextStyle( + color: (token['yamAverageValue'] * token['amount']) > token['totalValue'] + ? Colors.green // Texte vert si la condition est vraie + : Colors.red, // Texte rouge si la condition est fausse + ), + ), + + ], + ), +), + Text( '${S.of(context).amount}: ${token['amount']?.toStringAsFixed(2) ?? '0.00'} / ${token['totalTokens'] ?? 'N/A'}', style: TextStyle(fontSize: 13 + appState.getTextSizeOffset()), diff --git a/lib/pages/token_bottom_sheet.dart b/lib/pages/token_bottom_sheet.dart index 2cce926..d16c1e7 100644 --- a/lib/pages/token_bottom_sheet.dart +++ b/lib/pages/token_bottom_sheet.dart @@ -908,6 +908,21 @@ Future showTokenDetails( Divider(), + _buildDetailRow( + context, + S.of(context).initialPrice, + Utils.formatCurrency( + dataManager.convert(token['tokenPrice']),dataManager.currencySymbol), + icon: Icons.price_change_sharp, // Icône pour rendement annuel en pourcentage + ), + _buildDetailRow( + context, + 'S.of(context).yamPrice', + '${Utils.formatCurrency( + dataManager.convert(token['yamAverageValue']), dataManager.currencySymbol)} (${(1 - (token['yamAverageValue'] / token['tokenPrice']) * 100 ).toStringAsFixed(0)}%)', + icon: Icons.price_change, // Icône pour rendement annuel en pourcentage + ), + _buildDetailRow( context, S.of(context).annualPercentageYield, diff --git a/lib/utils/parameters.dart b/lib/utils/parameters.dart index a1c7838..92e2809 100644 --- a/lib/utils/parameters.dart +++ b/lib/utils/parameters.dart @@ -9,6 +9,7 @@ class Parameters { static const String etherumUrl = 'https://gateway-arbitrum.network.thegraph.com/api/$theGraphApiKey/subgraphs/id/EVjGN4mMd9h9JfGR7yLC6T2xrJf9syhjQNboFb7GzxVW'; static const String rmmUrl = 'https://gateway-arbitrum.network.thegraph.com/api/$theGraphApiKey/subgraphs/id/2dMMk7DbQYPX6Gi5siJm6EZ2gDQBF8nJcgKtpiPnPBsK'; + static const String yamUrl = 'https://gateway-arbitrum.network.thegraph.com/api/$theGraphApiKey/subgraphs/id/4eJa4rKCR5f8fq48BKbYBPvf7DWHppGZRvfiVUSFXBGR'; static const String realTokensUrl = 'https://pitswap-api.herokuapp.com/api'; static const String rentTrackerUrl = 'https://ehpst.duckdns.org/realt_rent_tracker/api'; diff --git a/web/index.html b/web/index.html index ba60136..851cf03 100644 --- a/web/index.html +++ b/web/index.html @@ -1,6 +1,4 @@ - - - + - + realtoken_apps + + + - - - + + + + + + + + + \ No newline at end of file diff --git a/web/splash/img/dark-1x.png b/web/splash/img/dark-1x.png new file mode 100644 index 0000000..d5b6d22 Binary files /dev/null and b/web/splash/img/dark-1x.png differ diff --git a/web/splash/img/dark-2x.png b/web/splash/img/dark-2x.png new file mode 100644 index 0000000..602ce9a Binary files /dev/null and b/web/splash/img/dark-2x.png differ diff --git a/web/splash/img/dark-3x.png b/web/splash/img/dark-3x.png new file mode 100644 index 0000000..d7b193b Binary files /dev/null and b/web/splash/img/dark-3x.png differ diff --git a/web/splash/img/dark-4x.png b/web/splash/img/dark-4x.png new file mode 100644 index 0000000..6601d08 Binary files /dev/null and b/web/splash/img/dark-4x.png differ diff --git a/web/splash/img/light-1x.png b/web/splash/img/light-1x.png new file mode 100644 index 0000000..d5b6d22 Binary files /dev/null and b/web/splash/img/light-1x.png differ diff --git a/web/splash/img/light-2x.png b/web/splash/img/light-2x.png new file mode 100644 index 0000000..602ce9a Binary files /dev/null and b/web/splash/img/light-2x.png differ diff --git a/web/splash/img/light-3x.png b/web/splash/img/light-3x.png new file mode 100644 index 0000000..d7b193b Binary files /dev/null and b/web/splash/img/light-3x.png differ diff --git a/web/splash/img/light-4x.png b/web/splash/img/light-4x.png new file mode 100644 index 0000000..6601d08 Binary files /dev/null and b/web/splash/img/light-4x.png differ