7 Commits

Author SHA1 Message Date
ardocrat 0026fc3717 build: fix android no_mangle attributes for rust 2024 2026-04-11 23:12:56 +03:00
ardocrat 0fd04f14a4 wallet: save last scanned block info to save progress on scan interruption 2026-04-11 22:52:43 +03:00
ardocrat 3338f51de5 tor: update to arti 0.41 2026-04-11 00:33:41 +03:00
ardocrat 0fa8963bd2 fix: wallet txs selection, wait starting tor service on send 2026-04-10 15:50:58 +03:00
ardocrat 70bba5d7ce pull_to_refresh: refresh when dragged far enough without release 2026-04-10 15:38:43 +03:00
ardocrat 0bb43e1e5d ui: show loader when fee is calculating 2026-04-10 15:18:23 +03:00
ardocrat fd52757549 build: version 0.3.4 2026-04-10 15:09:41 +03:00
17 changed files with 378 additions and 409 deletions
Generated
+272 -325
View File
File diff suppressed because it is too large Load Diff
+13 -13
View File
@@ -1,12 +1,12 @@
[package]
name = "grim"
version = "0.3.3"
version = "0.3.4"
authors = ["Ardocrat <ardocrat@gri.mw>"]
description = "Cross-platform GUI for Grin with focus on usability and availability to be used by anyone, anywhere."
license = "Apache-2.0"
repository = "https://code.gri.mw/GUI/grim"
keywords = [ "crypto", "grin", "mimblewimble" ]
edition = "2021"
edition = "2024"
build = "build.rs"
[[bin]]
@@ -92,23 +92,23 @@ uuid = { version = "0.8.2", features = ["v4"] }
num-bigint = "0.4.6"
## tor
arti-client = { version = "0.38.0", features = ["pt-client", "static", "onion-service-service", "onion-service-client", "experimental-api", "bridge-client"] }
tor-rtcompat = { version = "0.38.0", features = ["static"] }
tor-config = "0.38.0"
fs-mistrust = "0.13.1"
tor-hsservice = "0.38.0"
tor-hsrproxy = "0.38.0"
tor-keymgr = "0.38.0"
tor-llcrypto = "0.38.0"
tor-hscrypto = "0.38.0"
tor-error = "0.38.0"
arti-client = { version = "0.41.0", features = ["pt-client", "static", "onion-service-service", "onion-service-client", "experimental-api", "bridge-client"] }
tor-rtcompat = { version = "0.41.0", features = ["static"] }
tor-config = "0.41.0"
fs-mistrust = "0.14.1"
tor-hsservice = "0.41.0"
tor-hsrproxy = "0.41.0"
tor-keymgr = "0.41.0"
tor-llcrypto = "0.41.0"
tor-hscrypto = "0.41.0"
tor-error = "0.41.0"
sha2 = "0.10.8"
ed25519-dalek = "2.1.1"
curve25519-dalek = "4.1.3"
hyper-tor = { version = "0.14.32", features = ["full"], package = "hyper" }
tls-api = "0.12.0"
tls-api-native-tls = "0.12.1"
safelog = "0.7.0"
safelog = "0.8.1"
## stratum server
tokio-old = { version = "0.2", features = ["full"], package = "tokio" }
+1 -1
View File
@@ -12,7 +12,7 @@ android {
minSdk 24
targetSdk 36
versionCode 5
versionName "0.3.3"
versionName "0.3.4"
}
lint {
+1 -1
View File
@@ -21,7 +21,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.3.3</string>
<string>0.3.4</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
+1 -1
View File
@@ -418,7 +418,7 @@ impl<Platform: PlatformCallbacks> eframe::App for App<Platform> {
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
#[unsafe(no_mangle)]
/// Handle Back key code event from Android.
pub extern "C" fn Java_mw_gri_android_MainActivity_onBack(
_env: jni::JNIEnv,
+2 -2
View File
@@ -203,7 +203,7 @@ lazy_static! {
/// Callback from Java code with last entered character from soft keyboard.
#[allow(non_snake_case)]
#[no_mangle]
#[unsafe(no_mangle)]
pub extern "C" fn Java_mw_gri_android_MainActivity_onCameraImage(
env: JNIEnv,
_class: JObject,
@@ -218,7 +218,7 @@ pub extern "C" fn Java_mw_gri_android_MainActivity_onCameraImage(
/// Callback from Java code with picked file path.
#[allow(non_snake_case)]
#[no_mangle]
#[unsafe(no_mangle)]
pub extern "C" fn Java_mw_gri_android_MainActivity_onFilePick(
_env: JNIEnv,
_class: JObject,
+3 -3
View File
@@ -404,7 +404,7 @@ lazy_static! {
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
#[unsafe(no_mangle)]
/// Callback from Java code with last entered character from soft keyboard.
pub extern "C" fn Java_mw_gri_android_MainActivity_onTextInput(
_env: jni::JNIEnv,
@@ -429,7 +429,7 @@ pub extern "C" fn Java_mw_gri_android_MainActivity_onTextInput(
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
#[unsafe(no_mangle)]
/// Callback from Java code when Clear key was pressed at soft keyboard.
pub extern "C" fn Java_mw_gri_android_MainActivity_onClearInput(
_env: jni::JNIEnv,
@@ -442,7 +442,7 @@ pub extern "C" fn Java_mw_gri_android_MainActivity_onClearInput(
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
#[unsafe(no_mangle)]
/// Callback from Java code when Enter key was pressed at soft keyboard.
pub extern "C" fn Java_mw_gri_android_MainActivity_onEnterInput(
_env: jni::JNIEnv,
+7 -1
View File
@@ -113,7 +113,7 @@ pub enum PullToRefreshState {
/// `far_enough` is true if the user dragged far enough to trigger a refresh.
far_enough: bool,
},
/// The user dragged far enough to trigger a refresh and released the pointer.
/// The user dragged far enough to trigger a refresh.
DoRefresh,
/// The refresh is currently happening.
Refreshing,
@@ -298,6 +298,12 @@ impl PullToRefresh {
} else {
state = PullToRefreshState::Idle;
}
} else if let PullToRefreshState::Dragging {
far_enough: enough, ..
} = state.clone() {
if enough {
state = PullToRefreshState::DoRefresh;
}
}
} else {
state = PullToRefreshState::Idle;
+1 -1
View File
@@ -709,7 +709,7 @@ lazy_static! {
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
#[unsafe(no_mangle)]
/// Callback from Java code to update display insets (cutouts).
pub extern "C" fn Java_mw_gri_android_MainActivity_onDisplayInsets(
_env: jni::JNIEnv,
+9 -4
View File
@@ -287,9 +287,14 @@ impl SendRequestContent {
});
columns[1].vertical_centered_justified(|ui| {
// Button to create Slatepack message request.
View::button(ui, t!("continue"), Colors::white_or_black(false), || {
self.on_continue(wallet);
});
if self.max_calculating || wallet.fee_calculating() {
ui.add_space(4.0);
View::small_loading_spinner(ui);
} else {
View::button(ui, t!("continue"), Colors::white_or_black(false), || {
self.on_continue(wallet);
});
}
});
});
ui.add_space(6.0);
@@ -297,7 +302,7 @@ impl SendRequestContent {
/// Callback when Continue button was pressed.
fn on_continue(&mut self, wallet: &Wallet) {
if self.amount_edit.is_empty() || self.max_calculating || wallet.fee_calculating() {
if self.amount_edit.is_empty() {
return;
}
// Check address to send over Tor if enabled.
+1 -1
View File
@@ -87,7 +87,7 @@ const DELETE_TX_CONFIRMATION_MODAL: &'static str = "delete_tx_conf_modal";
impl WalletTransactionsContent {
/// Height of transaction list item.
pub const TX_ITEM_HEIGHT: f32 = 73.0;
pub const TX_ITEM_HEIGHT: f32 = 75.0;
/// Create new content instance with opening tx info.
pub fn new(tx: Option<WalletTx>) -> Self {
+2 -2
View File
@@ -46,7 +46,7 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION");
/// Android platform entry point.
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[no_mangle]
#[unsafe(no_mangle)]
fn android_main(app: AndroidApp) {
// Setup logger.
logger::init_logger();
@@ -273,7 +273,7 @@ lazy_static! {
#[allow(dead_code)]
#[allow(non_snake_case)]
#[cfg(target_os = "android")]
#[no_mangle]
#[unsafe(no_mangle)]
pub extern "C" fn Java_mw_gri_android_MainActivity_onData(
_env: jni::JNIEnv,
_class: jni::objects::JObject,
+12 -12
View File
@@ -710,7 +710,7 @@ pub fn start_stratum_mining_server(server: &Server, config: StratumServerConfig)
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
#[unsafe(no_mangle)]
/// Get sync status text for Android notification from [`NODE_STATE`] in Java string format.
pub extern "C" fn Java_mw_gri_android_BackgroundService_getSyncStatusText(
_env: jni::JNIEnv,
@@ -725,7 +725,7 @@ pub extern "C" fn Java_mw_gri_android_BackgroundService_getSyncStatusText(
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
#[unsafe(no_mangle)]
/// Get sync title for Android notification in Java string format.
pub extern "C" fn Java_mw_gri_android_BackgroundService_getSyncTitle(
_env: jni::JNIEnv,
@@ -739,7 +739,7 @@ pub extern "C" fn Java_mw_gri_android_BackgroundService_getSyncTitle(
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
#[unsafe(no_mangle)]
/// Get start text for Android notification in Java string format.
pub extern "C" fn Java_mw_gri_android_BackgroundService_getStartText(
_env: jni::JNIEnv,
@@ -753,7 +753,7 @@ pub extern "C" fn Java_mw_gri_android_BackgroundService_getStartText(
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
#[unsafe(no_mangle)]
/// Get stop text for Android notification in Java string format.
pub extern "C" fn Java_mw_gri_android_BackgroundService_getStopText(
_env: jni::JNIEnv,
@@ -767,7 +767,7 @@ pub extern "C" fn Java_mw_gri_android_BackgroundService_getStopText(
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
#[unsafe(no_mangle)]
/// Get exit text for Android notification in Java string format.
pub extern "C" fn Java_mw_gri_android_BackgroundService_getExitText(
_env: jni::JNIEnv,
@@ -781,7 +781,7 @@ pub extern "C" fn Java_mw_gri_android_BackgroundService_getExitText(
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
#[unsafe(no_mangle)]
/// Check if node launch is possible.
pub extern "C" fn Java_mw_gri_android_BackgroundService_canStartNode(
_env: jni::JNIEnv,
@@ -795,7 +795,7 @@ pub extern "C" fn Java_mw_gri_android_BackgroundService_canStartNode(
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
#[unsafe(no_mangle)]
/// Check if node stop is possible.
pub extern "C" fn Java_mw_gri_android_BackgroundService_canStopNode(
_env: jni::JNIEnv,
@@ -809,7 +809,7 @@ pub extern "C" fn Java_mw_gri_android_BackgroundService_canStopNode(
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
#[unsafe(no_mangle)]
/// Start node from Android Java code.
pub extern "C" fn Java_mw_gri_android_NotificationActionsReceiver_startNode(
_env: jni::JNIEnv,
@@ -822,7 +822,7 @@ pub extern "C" fn Java_mw_gri_android_NotificationActionsReceiver_startNode(
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
#[unsafe(no_mangle)]
/// Stop node from Android Java code.
pub extern "C" fn Java_mw_gri_android_NotificationActionsReceiver_stopNode(
_env: jni::JNIEnv,
@@ -835,7 +835,7 @@ pub extern "C" fn Java_mw_gri_android_NotificationActionsReceiver_stopNode(
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
#[unsafe(no_mangle)]
/// Stop node from Android Java code.
pub extern "C" fn Java_mw_gri_android_NotificationActionsReceiver_stopNodeToExit(
_env: jni::JNIEnv,
@@ -852,7 +852,7 @@ pub extern "C" fn Java_mw_gri_android_NotificationActionsReceiver_stopNodeToExit
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
#[unsafe(no_mangle)]
/// Check if app exit is needed after node stop to finish Android app at background.
pub extern "C" fn Java_mw_gri_android_BackgroundService_exitAppAfterNodeStop(
_env: jni::JNIEnv,
@@ -866,7 +866,7 @@ pub extern "C" fn Java_mw_gri_android_BackgroundService_exitAppAfterNodeStop(
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[allow(non_snake_case)]
#[no_mangle]
#[unsafe(no_mangle)]
/// Handle unexpected application termination on Android (removal from recent apps).
pub extern "C" fn Java_mw_gri_android_MainActivity_onTermination(
_env: jni::JNIEnv,
+1 -1
View File
@@ -656,7 +656,7 @@ impl Tor {
}
};
// Wait to check service again.
thread::sleep(duration);
tokio::time::sleep(duration).await;
}
});
});
+49 -38
View File
@@ -52,6 +52,7 @@ use std::{fs, path, thread};
use chrono::Utc;
use log::error;
use num_bigint::BigInt;
use tor_config::deps::Itertools;
use uuid::Uuid;
/// Contains wallet instance, configuration and state, handles wallet commands.
@@ -63,7 +64,7 @@ pub struct Wallet {
instance: Arc<RwLock<Option<WalletInstance>>>,
/// Connection of current wallet instance.
connection: Arc<RwLock<ConnectionMethod>>,
/// Wallet secret key for transport service.
/// Keychain mask for API calls.
keychain_mask: Arc<RwLock<Option<SecretKey>>>,
/// Wallet Slatepack address to receive txs at transport.
@@ -532,13 +533,16 @@ impl Wallet {
if !self.is_open() || !has_instance {
return;
}
self.closing.store(true, Ordering::Relaxed);
// Stop repairing.
if self.is_repairing() {
self.repair_needed.store(false, Ordering::Relaxed);
}
// Close wallet at separate thread.
let wallet_close = self.clone();
let service_id = wallet_close.identifier();
let conn = wallet_close.connection.clone();
thread::spawn(move || {
wallet_close.closing.store(true, Ordering::Relaxed);
// Wait common operations to finish.
while wallet_close.message_opening() || wallet_close.send_creating() ||
wallet_close.invoice_creating() {
@@ -648,8 +652,12 @@ impl Wallet {
let w = lc.wallet_inst()?;
let parent_key_id = w.parent_key_id();
// Retrieve txs from database.
let txs_iter = w.tx_log_iter()
let txs: Vec<TxLogEntry> = w.tx_log_iter()
.filter(|tx_entry| tx_entry.parent_key_id == parent_key_id)
// Filter transactions to not show txs without slate (usually unspent outputs).
.filter(|tx| {
tx.tx_slate_id.is_some() || (tx.tx_slate_id.is_none() && tx.payment_proof.is_some())
})
.filter(|tx_entry| {
if tx_entry.tx_type == TxLogEntryType::TxSent
|| tx_entry.tx_type == TxLogEntryType::TxSentCancelled {
@@ -661,20 +669,26 @@ impl Wallet {
- BigInt::from(tx_entry.amount_debited)
>= BigInt::from(1)
}
});
let mut return_txs: Vec<TxLogEntry> = txs_iter.collect();
// Sort txs by creation date and confirmation status reversing an order.
return_txs.sort_by_key(|tx| if !tx.confirmed && (tx.tx_type == TxLogEntryType::TxSent ||
tx.tx_type == TxLogEntryType::TxReceived) {
i64::MAX
} else {
tx.creation_ts.timestamp()
});
// return_txs.sort_by_key(|tx| tx.confirmed);
return_txs.reverse();
// Apply limit.
return_txs = return_txs.into_iter().take(limit as usize).collect();
Ok(return_txs)
})
// Sort txs by creation date and confirmation status.
.sorted_by_key(|tx| if !tx.confirmed && (tx.tx_type == TxLogEntryType::TxSent ||
tx.tx_type == TxLogEntryType::TxReceived) {
-i64::MAX
} else {
-tx.creation_ts.timestamp()
})
// Sort to show unconfirmed at top.
.sorted_by_key(|tx| {
tx.confirmed || tx.tx_type == TxLogEntryType::TxReceivedCancelled ||
tx.tx_type == TxLogEntryType::TxSentCancelled ||
tx.tx_type == TxLogEntryType::TxReverted
})
// Apply limit.
.take(limit as usize)
.collect();
// Reverse an order.
// txs.reverse();
Ok(txs)
}
/// Send a task to the wallet.
@@ -799,7 +813,7 @@ impl Wallet {
let wallet = self.clone();
thread::spawn(move || {
// Wait when current sync will be finished.
if wallet.syncing() {
while wallet.syncing() {
thread::sleep(Duration::from_secs(1));
}
// Sync wallet data with new limit.
@@ -978,7 +992,10 @@ impl Wallet {
null
]
}).to_string();
// Wait Tor service to launch.
while Tor::is_service_starting(&self.identifier()) {
tokio::time::sleep(Duration::from_secs(1)).await;
}
// Send request to receiver.
let req_res = Tor::post(body, url).await;
if req_res.is_none() {
@@ -1126,13 +1143,17 @@ impl Wallet {
/// Update transaction action status.
fn on_tx_action(&self, id: u32, action: Option<WalletTxAction>) {
let mut w_data = self.data.write();
w_data.as_mut().unwrap().on_tx_action(id, action);
if let Some(data) = w_data.as_mut() {
data.on_tx_action(id, action);
}
}
/// Update transaction action error status.
fn on_tx_error(&self, id: u32, err: Option<Error>) {
let mut w_data = self.data.write();
w_data.as_mut().unwrap().on_tx_error(id, err);
if let Some(data) = w_data.as_mut() {
data.on_tx_error(id, err);
}
}
/// Save task result to consume later.
@@ -1735,7 +1756,8 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
let tx = w.retrieve_tx_by_id(None, Some(s.id));
if let Some(tx) = tx {
if let Some(addr) = r {
if Tor::is_service_running(&w.identifier()) {
let id = w.identifier();
if Tor::is_service_running(&id) || Tor::is_service_starting(&id) {
w.send_creating.store(false, Ordering::Relaxed);
send_tor(tx, &s, addr).await;
return;
@@ -1966,21 +1988,10 @@ fn update_txs(wallet: &Wallet, mut txs_limit: u32) -> Result<(), Error> {
return Err(Error::GenericError("Wallet is not open".to_string()));
}
// Filter transactions to not show txs without slate (usually unspent outputs).
let mut filter_txs = txs.iter().map(|v| v.clone()).filter(|tx| {
tx.tx_slate_id.is_some() || (tx.tx_slate_id.is_none() && tx.payment_proof.is_some())
}).collect::<Vec<TxLogEntry>>();
// Sort to show unconfirmed at top.
filter_txs.sort_by_key(|tx| {
tx.confirmed || tx.tx_type == TxLogEntryType::TxReceivedCancelled ||
tx.tx_type == TxLogEntryType::TxSentCancelled ||
tx.tx_type == TxLogEntryType::TxReverted
});
// Update limit with actual length.
let txs_size = txs.len() as u32;
let filter_size = filter_txs.len() as u32;
let filter_size = txs.len() as u32;
if txs_size > filter_size && txs_limit >= filter_size {
txs_limit = txs_limit - (txs_size - filter_size);
}
@@ -1990,7 +2001,7 @@ fn update_txs(wallet: &Wallet, mut txs_limit: u32) -> Result<(), Error> {
let data = wallet.get_data().unwrap();
let data_txs = data.txs.unwrap_or(vec![]);
let mut new_txs: Vec<WalletTx> = vec![];
for tx in &filter_txs {
for tx in &txs {
let mut height: Option<u64> = None;
let mut broadcasting_height: Option<u64> = None;
let mut action: Option<WalletTxAction> = None;
@@ -2183,10 +2194,10 @@ fn repair_wallet(wallet: &Wallet) {
}
});
// Start wallet scanning.
let r_inst = wallet.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let api = Owner::new(instance, Some(info_tx));
// Start wallet scanning.
match api.scan(wallet.keychain_mask().as_ref(), Some(1), false) {
Ok(()) => {
// Set sync error if scanning was not complete and wallet is open.
+1 -1
Submodule wallet updated: f54a8e7756...4f3d9aac25
+2 -2
View File
@@ -7,8 +7,8 @@
<?endif ?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Version="0.3.3" UpgradeCode="C19F9B41-CD13-4F0E-B27D-E0EF8CF1CE91" Language="1033" Name="Grim" Manufacturer="Ardocrat">
<Package Id="F0DD07CB-48B4-419C-9A20-CE41CD0D9252" InstallerVersion="300" Compressed="yes"/>
<Product Id="*" Version="0.3.4" UpgradeCode="C19F9B41-CD13-4F0E-B27D-E0EF8CF1CE91" Language="1033" Name="Grim" Manufacturer="Ardocrat">
<Package Id="ECF79CAD-4441-45A7-B80C-7CC5773FAD82" InstallerVersion="300" Compressed="yes"/>
<Media Id="1" Cabinet="grim.cab" EmbedCab="yes" />
<MajorUpgrade AllowDowngrades = "yes"/>