This commit is contained in:
Marcos Rodriguez Velez 2024-10-26 11:59:52 -04:00
parent 498e45b413
commit 50614b5e9e
24 changed files with 808 additions and 2394 deletions

View File

@ -311,4 +311,4 @@ RUBY VERSION
ruby 3.1.6p260
BUNDLED WITH
2.5.13
2.3.27

View File

@ -232,7 +232,7 @@ platform :ios do
type: "development",
platform: "catalyst",
app_identifier: app_identifiers,
readonly: true,
readonly: false,
clone_branch_directly: true
)
end
@ -243,7 +243,7 @@ platform :ios do
type: "appstore",
platform: "catalyst",
app_identifier: app_identifiers,
readonly: true,
readonly: false,
clone_branch_directly: true
)
end

157
fastlane/README.md Normal file
View File

@ -0,0 +1,157 @@
fastlane documentation
----
# Installation
Make sure you have the latest version of the Xcode command line tools installed:
```sh
xcode-select --install
```
For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
# Available Actions
## Android
### android prepare_keystore
```sh
[bundle exec] fastlane android prepare_keystore
```
Prepare the keystore file
### android update_version_build_and_sign_apk
```sh
[bundle exec] fastlane android update_version_build_and_sign_apk
```
Update version, build number, and sign APK
### android upload_to_browserstack_and_comment
```sh
[bundle exec] fastlane android upload_to_browserstack_and_comment
```
Upload APK to BrowserStack and post result as PR comment
----
## iOS
### ios register_devices_from_txt
```sh
[bundle exec] fastlane ios register_devices_from_txt
```
Register new devices from a file
### ios create_temp_keychain
```sh
[bundle exec] fastlane ios create_temp_keychain
```
Create a temporary keychain
### ios setup_provisioning_profiles
```sh
[bundle exec] fastlane ios setup_provisioning_profiles
```
Synchronize certificates and provisioning profiles
### ios fetch_dev_profiles_catalyst
```sh
[bundle exec] fastlane ios fetch_dev_profiles_catalyst
```
Fetch development certificates and provisioning profiles for Mac Catalyst
### ios fetch_appstore_profiles_catalyst
```sh
[bundle exec] fastlane ios fetch_appstore_profiles_catalyst
```
Fetch App Store certificates and provisioning profiles for Mac Catalyst
### ios setup_catalyst_provisioning_profiles
```sh
[bundle exec] fastlane ios setup_catalyst_provisioning_profiles
```
Setup provisioning profiles for Mac Catalyst
### ios clear_derived_data_lane
```sh
[bundle exec] fastlane ios clear_derived_data_lane
```
Clear derived data
### ios increment_build_number_lane
```sh
[bundle exec] fastlane ios increment_build_number_lane
```
Increment build number
### ios install_pods
```sh
[bundle exec] fastlane ios install_pods
```
Install CocoaPods dependencies
### ios upload_to_testflight_lane
```sh
[bundle exec] fastlane ios upload_to_testflight_lane
```
Upload IPA to TestFlight
### ios build_app_lane
```sh
[bundle exec] fastlane ios build_app_lane
```
Build the iOS app
### ios deploy
```sh
[bundle exec] fastlane ios deploy
```
Deploy to TestFlight
### ios update_release_notes
```sh
[bundle exec] fastlane ios update_release_notes
```
Update 'What's New' section in App Store Connect for the 'Prepare for Submission' version
----
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).

18
fastlane/report.xml Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
<testsuite name="fastlane.lanes">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000181">
</testcase>
<testcase classname="fastlane.lanes" name="1: match" time="46.393765">
</testcase>
</testsuite>
</testsuites>

View File

@ -2,7 +2,7 @@
import { NativeEventEmitter, NativeModules } from 'react-native';
const { TransactionsMonitorEventEmitter, TransactionsMonitor: TransactionsMonitorModule } = NativeModules;
const { TransactionsMonitorEventEmitter, TransactionsMonitor, LiveActivityManager } = NativeModules;
interface TransactionConfirmedEvent {
txid: string;
@ -25,7 +25,6 @@ export const subscribeToTransactionConfirmed = (callback: (txid: string) => void
callback(event.txid);
});
// Return the unsubscribe function
return () => {
subscription.remove();
};
@ -36,12 +35,12 @@ export const subscribeToTransactionConfirmed = (callback: (txid: string) => void
* @param txid - The transaction ID to monitor.
*/
export const addExternalTxId = async (txid: string): Promise<void> => {
if (!TransactionsMonitorModule || !TransactionsMonitorModule.addExternalTxId) {
throw new Error('TransactionsMonitorModule or addExternalTxId method is not available.');
if (!TransactionsMonitor || !TransactionsMonitor.addExternalTxId) {
throw new Error('TransactionsMonitor or addExternalTxId method is not available.');
}
try {
await TransactionsMonitorModule.addExternalTxId(txid);
await TransactionsMonitor.addExternalTxId(txid);
console.debug(`External txid ${txid} added for monitoring.`);
} catch (error) {
console.error(`Failed to add external txid ${txid}:`, error);
@ -54,12 +53,12 @@ export const addExternalTxId = async (txid: string): Promise<void> => {
* @param txid - The transaction ID to remove.
*/
export const removeExternalTxId = async (txid: string): Promise<void> => {
if (!TransactionsMonitorModule || !TransactionsMonitorModule.removeExternalTxId) {
throw new Error('TransactionsMonitorModule or removeExternalTxId method is not available.');
if (!TransactionsMonitor || !TransactionsMonitor.removeExternalTxId) {
throw new Error('TransactionsMonitor or removeExternalTxId method is not available.');
}
try {
await TransactionsMonitorModule.removeExternalTxId(txid);
await TransactionsMonitor.removeExternalTxId(txid);
console.debug(`External txid ${txid} removed from monitoring.`);
} catch (error) {
console.error(`Failed to remove external txid ${txid}:`, error);
@ -72,12 +71,12 @@ export const removeExternalTxId = async (txid: string): Promise<void> => {
* @returns Promise that resolves to an array of transaction IDs.
*/
export const getAllTxIds = async (): Promise<string[]> => {
if (!TransactionsMonitorModule || !TransactionsMonitorModule.getAllTxIds) {
throw new Error('TransactionsMonitorModule or getAllTxIds method is not available.');
if (!TransactionsMonitor || !TransactionsMonitor.getAllTxIds) {
throw new Error('TransactionsMonitor or getAllTxIds method is not available.');
}
return new Promise<string[]>((resolve, reject) => {
TransactionsMonitorModule.getAllTxIds((error: string | null, txids: string[] | null) => {
TransactionsMonitor.getAllTxIds((error: string | null, txids: string[] | null) => {
if (error) {
reject(error);
} else if (txids) {
@ -95,19 +94,53 @@ export const getAllTxIds = async (): Promise<string[]> => {
* @returns Promise that resolves to the number of confirmations.
*/
export const fetchTransactionConfirmations = async (txid: string): Promise<number> => {
if (!TransactionsMonitorModule || !TransactionsMonitorModule.fetchConfirmations) {
throw new Error('TransactionsMonitorModule or fetchConfirmations method is not available.');
if (!TransactionsMonitor || !TransactionsMonitor.fetchConfirmations) {
throw new Error('TransactionsMonitor or fetchConfirmations method is not available.');
}
return new Promise<number>((resolve, reject) => {
TransactionsMonitorModule.fetchConfirmations(txid, (error: string | null, confirmations: number | null) => {
TransactionsMonitor.fetchConfirmations(txid, (error: string | null, confirmations: number | null) => {
if (error) {
reject(error);
} else if (confirmations !== null) {
resolve(confirmations);
} else {
reject(new Error('No confirmations data received.'));
reject('No confirmations data received.');
}
});
});
};
/**
* Starts the persistent Live Activity for Dynamic Island.
*/
export const startPersistentLiveActivity = async (): Promise<void> => {
if (!LiveActivityManager || !LiveActivityManager.startPersistentLiveActivity) {
throw new Error('LiveActivityManager or startPersistentLiveActivity method is not available.');
}
try {
await LiveActivityManager.startPersistentLiveActivity();
console.debug('Persistent Live Activity started.');
} catch (error) {
console.error('Failed to start Persistent Live Activity:', error);
throw error;
}
};
/**
* Ends the persistent Live Activity for Dynamic Island.
*/
export const endPersistentLiveActivity = async (): Promise<void> => {
if (!LiveActivityManager || !LiveActivityManager.endPersistentLiveActivity) {
throw new Error('LiveActivityManager or endPersistentLiveActivity method is not available.');
}
try {
await LiveActivityManager.endPersistentLiveActivity();
console.debug('Persistent Live Activity ended.');
} catch (error) {
console.error('Failed to end Persistent Live Activity:', error);
throw error;
}
};

View File

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 70;
objectVersion = 73;
objects = {
/* Begin PBXBuildFile section */
@ -127,8 +127,6 @@
B4D0B2642C1DEA99006B6B1B /* ReceiveType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2632C1DEA99006B6B1B /* ReceiveType.swift */; };
B4D0B2662C1DEB7F006B6B1B /* ReceiveInterfaceMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2652C1DEB7F006B6B1B /* ReceiveInterfaceMode.swift */; };
B4D0B2682C1DED67006B6B1B /* ReceiveMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2672C1DED67006B6B1B /* ReceiveMethod.swift */; };
B4E2CA7B2CCC61F1009540B0 /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4E2CA7A2CCC61F1009540B0 /* KeychainManager.swift */; };
B4E2CA7C2CCC61F1009540B0 /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4E2CA7A2CCC61F1009540B0 /* KeychainManager.swift */; };
B4E2CA7D2CCC61F1009540B0 /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4E2CA7A2CCC61F1009540B0 /* KeychainManager.swift */; };
B4E2CA852CCC675E009540B0 /* MarketAPI+Electrum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5142558EBA3009312A5 /* MarketAPI+Electrum.swift */; };
B4E2CA862CCC6778009540B0 /* SwiftTCPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40FC3F829CCD1AC0007EBAC /* SwiftTCPClient.swift */; };
@ -137,6 +135,9 @@
B4E2CAD82CCCA5F5009540B0 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3A252FE1A3004D72DF /* WidgetKit.framework */; };
B4E2CAD92CCCA5F5009540B0 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3C252FE1A3004D72DF /* SwiftUI.framework */; };
B4E2CAE62CCCA5F6009540B0 /* TransactionsMonitorExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = B4E2CAD72CCCA5F5009540B0 /* TransactionsMonitorExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
B4E2CAFD2CCD3FF4009540B0 /* ElectrumFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4E2CAFB2CCD3FF4009540B0 /* ElectrumFetcher.swift */; };
B4E2CAFF2CCD4004009540B0 /* ElectrumFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4E2CAFB2CCD3FF4009540B0 /* ElectrumFetcher.swift */; };
B4E2CB002CCD4012009540B0 /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4E2CA7A2CCC61F1009540B0 /* KeychainManager.swift */; };
B4EE583C226703320003363C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; };
B4EFF73B2C3F6C5E0095D655 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4EFF73A2C3F6C5E0095D655 /* MockData.swift */; };
C978A716948AB7DEC5B6F677 /* (null) in Frameworks */ = {isa = PBXBuildFile; };
@ -377,6 +378,7 @@
B4E2CAC02CCCA57E009540B0 /* LiveActivityExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LiveActivityExtension.entitlements; sourceTree = "<group>"; };
B4E2CAD72CCCA5F5009540B0 /* TransactionsMonitorExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = TransactionsMonitorExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
B4E2CAE72CCCA5F6009540B0 /* TransactionsMonitorExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TransactionsMonitorExtension.entitlements; sourceTree = "<group>"; };
B4E2CAFB2CCD3FF4009540B0 /* ElectrumFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElectrumFetcher.swift; sourceTree = "<group>"; };
B4EFF73A2C3F6C5E0095D655 /* MockData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockData.swift; sourceTree = "<group>"; };
B642AFB13483418CAB6FF25E /* libRCTQRCodeLocalImage.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTQRCodeLocalImage.a; sourceTree = "<group>"; };
B9D9B3A7B2CB4255876B67AF /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
@ -399,13 +401,27 @@
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
LiveActivityManagerModule.m,
TransactionsMonitorEventEmitterModule.m,
TransactionsMonitorModule.m,
);
target = B4E2CAD62CCCA5F5009540B0 /* TransactionsMonitorExtension */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
B4E2CB072CCD406C009540B0 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */ = {
isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;
buildPhase = B4E2CAD52CCCA5F5009540B0 /* Resources */;
membershipExceptions = (
TransactionsMonitorEventEmitterModule.m,
TransactionsMonitorModule.m,
);
};
/* End PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
B4E2CADA2CCCA5F5009540B0 /* TransactionsMonitor */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (B4E2CAE82CCCA5F7009540B0 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = TransactionsMonitor; sourceTree = "<group>"; };
B4E2CADA2CCCA5F5009540B0 /* TransactionsMonitor */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (B4E2CAE82CCCA5F7009540B0 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, B4E2CB072CCD406C009540B0 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = TransactionsMonitor; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
@ -753,6 +769,7 @@
B44033C82BCC34AC00162242 /* Shared */ = {
isa = PBXGroup;
children = (
B4E2CAFB2CCD3FF4009540B0 /* ElectrumFetcher.swift */,
B4E2CA7A2CCC61F1009540B0 /* KeychainManager.swift */,
B450109A2C0FCD7E00619044 /* Utilities */,
6D2AA8062568B8E50090B089 /* Fiat */,
@ -997,11 +1014,11 @@
};
B4E2CAD62CCCA5F5009540B0 = {
CreatedOnToolsVersion = 16.0;
LastSwiftMigration = 1600;
};
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "BlueWallet" */;
compatibilityVersion = "Xcode 15.0";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
@ -1038,6 +1055,7 @@
B41B76832B66B2FF002C48D5 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */,
B48A6A272C1DF01000030AB9 /* XCRemoteSwiftPackageReference "keychain-swift" */,
);
preferredProjectObjectVersion = 60;
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
projectRoot = "";
@ -1247,6 +1265,7 @@
buildActionMask = 2147483647;
files = (
B44033E92BCC371A00162242 /* MarketData.swift in Sources */,
B4E2CAFD2CCD3FF4009540B0 /* ElectrumFetcher.swift in Sources */,
B44033CA2BCC350A00162242 /* Currency.swift in Sources */,
B4E2CA862CCC6778009540B0 /* SwiftTCPClient.swift in Sources */,
B44033EE2BCC374500162242 /* Numeric+abbreviated.swift in Sources */,
@ -1309,7 +1328,6 @@
B44033C62BCC332400162242 /* Balance.swift in Sources */,
B44033E62BCC36FF00162242 /* WalletData.swift in Sources */,
6DD410BF266CB13D0087DE03 /* Placeholders.swift in Sources */,
B4E2CA7C2CCC61F1009540B0 /* KeychainManager.swift in Sources */,
6DD410B0266CAF5C0087DE03 /* WalletInformationWidget.swift in Sources */,
6DD410B1266CAF5C0087DE03 /* MarketAPI+Electrum.swift in Sources */,
6DD410B8266CAF5C0087DE03 /* UserDefaultsExtension.swift in Sources */,
@ -1355,7 +1373,6 @@
B44033E52BCC36FF00162242 /* WalletData.swift in Sources */,
B44033EF2BCC374500162242 /* Numeric+abbreviated.swift in Sources */,
B4D0B2622C1DEA11006B6B1B /* ReceivePageInterfaceController.swift in Sources */,
B4E2CA7B2CCC61F1009540B0 /* KeychainManager.swift in Sources */,
B44033C52BCC332400162242 /* Balance.swift in Sources */,
6D4AF18425D215D1009DD853 /* UserDefaultsExtension.swift in Sources */,
B4AB225D2B02AD12001F4328 /* XMLParserDelegate.swift in Sources */,
@ -1368,6 +1385,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B4E2CAFF2CCD4004009540B0 /* ElectrumFetcher.swift in Sources */,
B4E2CB002CCD4012009540B0 /* KeychainManager.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1578,7 +1597,7 @@
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = Stickers/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LIBRARY_SEARCH_PATHS = (
"$(SDKROOT)/usr/lib/swift",
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
@ -1621,7 +1640,7 @@
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = Stickers/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LIBRARY_SEARCH_PATHS = (
"$(SDKROOT)/usr/lib/swift",
"$(SDKROOT)/System/iOSSupport/usr/lib/swift",
@ -1666,7 +1685,7 @@
"DEVELOPMENT_TEAM[sdk=macosx*]" = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = Widgets/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -1722,7 +1741,7 @@
"DEVELOPMENT_TEAM[sdk=macosx*]" = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = Widgets/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -1930,7 +1949,7 @@
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 7.0;
WATCHOS_DEPLOYMENT_TARGET = 8.7;
};
name = Debug;
};
@ -1979,7 +1998,7 @@
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 7.0;
WATCHOS_DEPLOYMENT_TARGET = 8.7;
};
name = Release;
};
@ -2026,7 +2045,7 @@
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 7.0;
WATCHOS_DEPLOYMENT_TARGET = 8.7;
};
name = Debug;
};
@ -2073,7 +2092,7 @@
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 7.0;
WATCHOS_DEPLOYMENT_TARGET = 8.7;
};
name = Release;
};
@ -2086,18 +2105,21 @@
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = TransactionsMonitorExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU;
"DEVELOPMENT_TEAM[sdk=macosx*]" = A7W54YZ4WU;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_PREPROCESSOR_DEFINITIONS = (
@ -2108,7 +2130,7 @@
INFOPLIST_FILE = TransactionsMonitor/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = TransactionsMonitor;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 BlueWallet. All rights reserved.";
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -2121,10 +2143,13 @@
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TransactionsMonitor;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development io.bluewallet.bluewallet.TransactionsMonitor";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development io.bluewallet.bluewallet.TransactionsMonitor catalyst";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "TransactionsMonitor/TransactionsMonitorExtension-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@ -2140,6 +2165,7 @@
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
@ -2148,18 +2174,20 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Distribution";
CODE_SIGN_STYLE = Automatic;
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = A7W54YZ4WU;
"DEVELOPMENT_TEAM[sdk=macosx*]" = A7W54YZ4WU;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = TransactionsMonitor/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = TransactionsMonitor;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 BlueWallet. All rights reserved.";
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -2170,9 +2198,13 @@
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TransactionsMonitor;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore io.bluewallet.bluewallet.TransactionsMonitor";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match AppStore io.bluewallet.bluewallet.TransactionsMonitor catalyst";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "TransactionsMonitor/TransactionsMonitorExtension-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};

View File

@ -67,7 +67,7 @@
<key>TransactionsMonitorExtension.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>130</integer>
<integer>8</integer>
</dict>
<key>WalletInformationAndMarketWidget.xcscheme_^#shared#^_</key>
<dict>

View File

@ -4,6 +4,8 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.usernotifications.time-sensitive</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>

View File

@ -4,6 +4,8 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.usernotifications.time-sensitive</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>

View File

@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.usernotifications.time-sensitive</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.io.bluewallet.bluewallet</string>

View File

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.io.bluewallet.bluewallet</string>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,185 @@
//
// ElectrumFetcherProtocol.swift
// BlueWallet
//
// Created by Marcos Rodriguez on 10/26/24.
// Copyright © 2024 BlueWallet. All rights reserved.
//
// ElectrumFetcher.swift
import Foundation
import Network
protocol ElectrumFetcherProtocol {
func fetchTransactionConfirmations(txid: String) async throws -> Int
}
class ElectrumFetcher: ElectrumFetcherProtocol {
private var connection: NWConnection?
private let host: NWEndpoint.Host
private let port: NWEndpoint.Port
private let useSSL: Bool
private let queue = DispatchQueue(label: "ElectrumFetcherQueue")
private let requestTimeout: TimeInterval = 10
init(host: String, port: UInt16, useSSL: Bool) {
self.host = NWEndpoint.Host(host)
self.port = NWEndpoint.Port(rawValue: port) ?? .init(integerLiteral: 50001) // Default port if invalid
self.useSSL = useSSL
}
deinit {
disconnect()
}
func connect() async throws {
let parameters = NWParameters.tcp
if useSSL {
let tlsOptions = NWProtocolTLS.Options()
sec_protocol_options_set_min_tls_protocol_version(tlsOptions.securityProtocolOptions, .TLSv12)
parameters.defaultProtocolStack.applicationProtocols.insert(tlsOptions, at: 0)
}
connection = NWConnection(host: host, port: port, using: parameters)
connection?.stateUpdateHandler = { [weak self] newState in
switch newState {
case .ready:
print("ElectrumFetcher: Connected to \(self?.host.description ?? "Unknown Host"):\(self?.port.rawValue ?? 0)")
case .failed(let error):
print("ElectrumFetcher: Connection failed with error: \(error.localizedDescription)")
case .cancelled:
print("ElectrumFetcher: Connection cancelled.")
default:
break
}
}
connection?.start(queue: queue)
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
connection?.stateUpdateHandler = { newState in
switch newState {
case .ready:
continuation.resume()
case .failed(let error):
continuation.resume(throwing: error)
case .cancelled:
continuation.resume(throwing: NSError(domain: "ElectrumFetcher", code: -1, userInfo: [NSLocalizedDescriptionKey: "Connection cancelled"]))
default:
break
}
}
}
}
func disconnect() {
connection?.cancel()
connection = nil
print("ElectrumFetcher: Disconnected from \(host.description):\(port.rawValue)")
}
private func sendRequest(request: [String: Any]) async throws -> [String: Any] {
let requestData = try JSONSerialization.data(withJSONObject: request, options: [])
var requestWithNewline = requestData
requestWithNewline.append(contentsOf: "\n".utf8)
if connection?.state != .ready {
try await connectWithRetry()
}
try await send(data: requestWithNewline)
print("ElectrumFetcher: Sent request \(request["method"] ?? "unknown method")")
let responseData = try await receive(timeout: requestTimeout)
print("ElectrumFetcher: Received response for request \(request["method"] ?? "unknown method")")
guard let responseJSON = try? JSONSerialization.jsonObject(with: responseData, options: []) as? [String: Any] else {
throw NSError(domain: "ElectrumFetcher", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid JSON response"])
}
return responseJSON
}
private func send(data: Data) async throws {
guard let connection = connection else {
throw NSError(domain: "ElectrumFetcher", code: -1, userInfo: [NSLocalizedDescriptionKey: "No active connection"])
}
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
connection.send(content: data, completion: .contentProcessed { error in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume()
}
})
}
}
private func receive(timeout: TimeInterval) async throws -> Data {
guard let connection = connection else {
throw NSError(domain: "ElectrumFetcher", code: -1, userInfo: [NSLocalizedDescriptionKey: "No active connection"])
}
return try await withTaskCancellationHandler(operation: {
return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Data, Error>) in
connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { data, context, isComplete, error in
if let error = error {
continuation.resume(throwing: error)
} else if let data = data, !data.isEmpty {
continuation.resume(returning: data)
} else {
continuation.resume(throwing: NSError(domain: "ElectrumFetcher", code: -1, userInfo: [NSLocalizedDescriptionKey: "No data received"]))
}
}
}
}, onCancel: {
connection.cancel()
})
}
private func connectWithRetry(maxRetries: Int = 3) async throws {
var attempt = 0
var delayTime: TimeInterval = 1
while attempt < maxRetries {
do {
try await connect()
return
} catch {
attempt += 1
if attempt >= maxRetries {
throw error
}
print("ElectrumFetcher: Connection attempt \(attempt) failed with error: \(error.localizedDescription). Retrying in \(delayTime) seconds...")
try await Task.sleep(nanoseconds: UInt64(delayTime * 1_000_000_000))
delayTime *= 2
}
}
}
func fetchTransactionConfirmations(txid: String) async throws -> Int {
let requestId = UUID().uuidString
let requestDict: [String: Any] = [
"id": requestId,
"method": "blockchain.transaction.get_confirmations",
"params": [txid]
]
let responseJSON = try await sendRequest(request: requestDict)
if let error = responseJSON["error"] as? [String: Any], let message = error["message"] as? String {
throw NSError(domain: "ElectrumFetcher", code: -1, userInfo: [NSLocalizedDescriptionKey: message])
}
if let result = responseJSON["result"] as? Int {
return result
} else {
throw NSError(domain: "ElectrumFetcher", code: -1, userInfo: [NSLocalizedDescriptionKey: "Unexpected response format"])
}
}
}

View File

@ -0,0 +1,34 @@
// LiveActivityManager.swift
import ActivityKit
import WidgetKit
import SwiftUI
@objc(LiveActivityManager)
class LiveActivityManager: NSObject {
func startPersistentLiveActivity() {
let attributes = TransactionsMonitorAttributes(name: "BlueWallet")
let contentState = TransactionsMonitorAttributes.ContentState(emoji: "😀")
do {
let activity = try Activity<TransactionsMonitorAttributes>.request(
attributes: attributes,
contentState: contentState,
pushType: nil
)
print("Live Activity started with ID: \(activity.id)")
} catch (let error) {
print("Error starting Live Activity: \(error.localizedDescription)")
}
}
func endPersistentLiveActivity() {
for activity in Activity<TransactionsMonitorAttributes>.activities {
Task {
await activity.end(dismissalPolicy: .immediate)
print("Live Activity ended with ID: \(activity.id)")
}
}
}
}

View File

@ -0,0 +1,10 @@
// LiveActivityManagerModule.m
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(LiveActivityManager, NSObject)
RCT_EXTERN_METHOD(startPersistentLiveActivity)
RCT_EXTERN_METHOD(endPersistentLiveActivity)
@end

View File

@ -1,85 +1,135 @@
//
// TransactionsMonitor.swift
// TransactionsMonitor
// BlueWallet
//
// Created by Marcos Rodriguez on 10/26/24.
// Copyright © 2024 BlueWallet. All rights reserved.
//
import WidgetKit
import SwiftUI
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), emoji: "😀")
// TransactionsMonitor.swift
import Foundation
import React
@objc(TransactionsMonitor)
class TransactionsMonitor: NSObject {
private let electrumFetcher: ElectrumFetcherProtocol
private let keychainManager: KeychainManager
private let service = "transactionData"
private let account = "transactions"
private let externalTxidsKey = "external_txids"
override init() {
let settings = UserDefaultsGroup.getElectrumSettings()
let host = settings.host ?? "electrum.blockstream.info"
let port = settings.port ?? 50002
let useSSL = settings.sslPort != nil
self.electrumFetcher = ElectrumFetcher(host: host, port: port, useSSL: useSSL)
self.keychainManager = KeychainManager.shared
super.init()
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), emoji: "😀")
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate, emoji: "😀")
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
// func relevances() async -> WidgetRelevances<Void> {
// // Generate a list containing the contexts this widget is relevant in.
// }
}
struct SimpleEntry: TimelineEntry {
let date: Date
let emoji: String
}
struct TransactionsMonitorEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack {
Text("Time:")
Text(entry.date, style: .time)
Text("Emoji:")
Text(entry.emoji)
}
}
}
struct TransactionsMonitor: Widget {
let kind: String = "TransactionsMonitor"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
if #available(iOS 17.0, *) {
TransactionsMonitorEntryView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
} else {
TransactionsMonitorEntryView(entry: entry)
.padding()
.background()
@objc
func startMonitoringTransactions() {
Task {
do {
let allTxids = try await fetchAllTxids()
for txid in allTxids {
await monitorTransaction(txid: txid)
}
print("TransactionsMonitor: Started monitoring \(allTxids.count) transactions.")
} catch {
print("TransactionsMonitor: Error fetching txids: \(error.localizedDescription)")
}
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
#Preview(as: .systemSmall) {
TransactionsMonitor()
} timeline: {
SimpleEntry(date: .now, emoji: "😀")
SimpleEntry(date: .now, emoji: "🤩")
}
@objc
func getAllTxIds(_ callback: @escaping RCTResponseSenderBlock) {
Task {
do {
let allTxids = try fetchAllTxidsSync()
callback([NSNull(), allTxids])
} catch {
callback([error.localizedDescription, NSNull()])
}
}
}
@objc
func addExternalTxId(_ txid: String) {
Task {
do {
try await addExternalTxIdAsync(txid: txid)
print("TransactionsMonitor: External txid \(txid) added for monitoring.")
} catch {
print("TransactionsMonitor: Error adding external txid \(txid): \(error.localizedDescription)")
}
}
}
@objc
func removeExternalTxId(_ txid: String) {
Task {
do {
try await removeExternalTxIdAsync(txid: txid)
print("TransactionsMonitor: External txid \(txid) removed from monitoring.")
} catch {
print("TransactionsMonitor: Error removing external txid \(txid): \(error.localizedDescription)")
}
}
}
private func addExternalTxIdAsync(txid: String) async throws {
var externalTxids = try keychainManager.retrieveCodable(service: externalTxidsKey, account: account, type: [String].self) ?? []
externalTxids.append(txid)
try keychainManager.saveCodable(object: externalTxids, service: externalTxidsKey, account: account)
await monitorTransaction(txid: txid)
}
private func removeExternalTxIdAsync(txid: String) async throws {
var externalTxids = try keychainManager.retrieveCodable(service: externalTxidsKey, account: account, type: [String].self) ?? []
if let index = externalTxids.firstIndex(of: txid) {
externalTxids.remove(at: index)
try keychainManager.saveCodable(object: externalTxids, service: externalTxidsKey, account: account)
} else {
throw NSError(domain: "TransactionsMonitor", code: -1, userInfo: [NSLocalizedDescriptionKey: "Txid not found in external monitoring"])
}
}
private func fetchAllTxids() async throws -> [String] {
var txids: [String] = []
if let walletTxs = try keychainManager.retrieveCodable(service: service, account: account, type: [Transaction].self) {
txids += walletTxs.map { $0.txid }
}
if let externalTxids = try keychainManager.retrieveCodable(service: externalTxidsKey, account: account, type: [String].self) {
txids += externalTxids
}
return txids
}
private func fetchAllTxidsSync() throws -> [String] {
var txids: [String] = []
if let walletTxs = try keychainManager.retrieveCodable(service: service, account: account, type: [Transaction].self) {
txids += walletTxs.map { $0.txid }
}
if let externalTxids = try keychainManager.retrieveCodable(service: externalTxidsKey, account: account, type: [String].self) {
txids += externalTxids
}
return txids
}
private func monitorTransaction(txid: String) async {
do {
let confirmations = try await electrumFetcher.fetchTransactionConfirmations(txid: txid)
if confirmations > 0 {
TransactionsMonitorEventEmitter.sendTransactionConfirmedEvent(txid: txid)
try await removeExternalTxIdAsync(txid: txid)
}
} catch {
print("TransactionsMonitor: Error monitoring txid \(txid): \(error.localizedDescription)")
}
}
}

View File

@ -1,10 +1,4 @@
//
// TransactionsMonitorBundle.swift
// TransactionsMonitor
//
// Created by Marcos Rodriguez on 10/26/24.
// Copyright © 2024 BlueWallet. All rights reserved.
//
// TransactionsMonitorBundle.swift
import WidgetKit
import SwiftUI
@ -12,7 +6,7 @@ import SwiftUI
@main
struct TransactionsMonitorBundle: WidgetBundle {
var body: some Widget {
TransactionsMonitor()
TransactionsMonitorWidget()
TransactionsMonitorLiveActivity()
}
}

View File

@ -0,0 +1,47 @@
//
// TransactionsMonitorEventEmitter.swift
// BlueWallet
//
// Created by Marcos Rodriguez on 10/26/24.
// Copyright © 2024 BlueWallet. All rights reserved.
//
// TransactionsMonitorEventEmitter.swift
import Foundation
import React
@objc(TransactionsMonitorEventEmitter)
class TransactionsMonitorEventEmitter: RCTEventEmitter {
static var shared: TransactionsMonitorEventEmitter?
static var hasListeners = false
override init() {
super.init()
TransactionsMonitorEventEmitter.shared = self
}
override static func requiresMainQueueSetup() -> Bool {
return true
}
override func startObserving() {
TransactionsMonitorEventEmitter.hasListeners = true
}
override func stopObserving() {
TransactionsMonitorEventEmitter.hasListeners = false
}
override func supportedEvents() -> [String] {
return ["TransactionConfirmed"]
}
static func sendTransactionConfirmedEvent(txid: String) {
if let emitter = TransactionsMonitorEventEmitter.shared, hasListeners {
emitter.sendEvent(withName: "TransactionConfirmed", body: ["txid": txid])
}
}
}

View File

@ -0,0 +1,7 @@
// TransactionsMonitorEventEmitterModule.m
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(TransactionsMonitorEventEmitter, RCTEventEmitter)
@end

View File

@ -0,0 +1,4 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//

View File

@ -1,10 +1,4 @@
//
// TransactionsMonitorLiveActivity.swift
// TransactionsMonitor
//
// Created by Marcos Rodriguez on 10/26/24.
// Copyright © 2024 BlueWallet. All rights reserved.
//
// TransactionsMonitorLiveActivity.swift
import ActivityKit
import WidgetKit
@ -25,33 +19,34 @@ struct TransactionsMonitorLiveActivity: Widget {
ActivityConfiguration(for: TransactionsMonitorAttributes.self) { context in
// Lock screen/banner UI goes here
VStack {
Text("Hello \(context.state.emoji)")
Text("Monitoring Transactions")
Text(context.state.emoji)
.font(.largeTitle)
}
.activityBackgroundTint(Color.cyan)
.activitySystemActionForegroundColor(Color.black)
} dynamicIsland: { context in
DynamicIsland {
// Expanded UI goes here. Compose the expanded UI through
// various regions, like leading/trailing/center/bottom
// Expanded UI
DynamicIslandExpandedRegion(.leading) {
Text("Leading")
Text("🔍")
}
DynamicIslandExpandedRegion(.trailing) {
Text("Trailing")
Text("📈")
}
DynamicIslandExpandedRegion(.bottom) {
Text("Bottom \(context.state.emoji)")
// more content
Text("Status: \(context.state.emoji)")
// more content if needed
}
} compactLeading: {
Text("L")
Text("🔍")
} compactTrailing: {
Text("T \(context.state.emoji)")
Text("📈")
} minimal: {
Text(context.state.emoji)
}
.widgetURL(URL(string: "http://www.apple.com"))
.widgetURL(URL(string: "http://www.bluewallet.com"))
.keylineTint(Color.red)
}
}
@ -59,18 +54,18 @@ struct TransactionsMonitorLiveActivity: Widget {
extension TransactionsMonitorAttributes {
fileprivate static var preview: TransactionsMonitorAttributes {
TransactionsMonitorAttributes(name: "World")
TransactionsMonitorAttributes(name: "BlueWallet")
}
}
extension TransactionsMonitorAttributes.ContentState {
fileprivate static var smiley: TransactionsMonitorAttributes.ContentState {
TransactionsMonitorAttributes.ContentState(emoji: "😀")
}
}
fileprivate static var starEyes: TransactionsMonitorAttributes.ContentState {
TransactionsMonitorAttributes.ContentState(emoji: "🤩")
}
fileprivate static var starEyes: TransactionsMonitorAttributes.ContentState {
TransactionsMonitorAttributes.ContentState(emoji: "🤩")
}
}
#Preview("Notification", as: .content, using: TransactionsMonitorAttributes.preview) {

View File

@ -0,0 +1,12 @@
// TransactionsMonitorModule.m
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(TransactionsMonitor, NSObject)
RCT_EXTERN_METHOD(startMonitoringTransactions)
RCT_EXTERN_METHOD(getAllTxIds:(RCTResponseSenderBlock)callback)
RCT_EXTERN_METHOD(addExternalTxId:(NSString *)txid)
RCT_EXTERN_METHOD(removeExternalTxId:(NSString *)txid)
@end

View File

@ -0,0 +1,75 @@
// TransactionsMonitorWidget.swift
import WidgetKit
import SwiftUI
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), status: "Idle")
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), status: "Active")
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let status = hourOffset % 2 == 0 ? "Active" : "Idle"
let entry = SimpleEntry(date: entryDate, status: status)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let status: String
}
struct TransactionsMonitorEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack {
Text("Status:")
Text(entry.status)
.font(.headline)
.foregroundColor(entry.status == "Active" ? .green : .red)
}
}
}
struct TransactionsMonitorWidget: Widget {
let kind: String = "TransactionsMonitorWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
if #available(iOS 17.0, *) {
TransactionsMonitorEntryView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
} else {
TransactionsMonitorEntryView(entry: entry)
.padding()
.background()
}
}
.configurationDisplayName("Transactions Monitor")
.description("Monitors your transactions and updates the Dynamic Island accordingly.")
}
}
#Preview(as: .systemSmall) {
TransactionsMonitorWidget()
} timeline: {
SimpleEntry(date: .now, status: "Active")
SimpleEntry(date: .now.addingTimeInterval(3600), status: "Idle")
}

View File

@ -6,5 +6,9 @@
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)group.io.bluewallet.bluewallet</string>
</array>
</dict>
</plist>