11 Commits

Author SHA1 Message Date
ardocrat 875bd11bdb ci: macos universal release name 2026-03-10 02:02:15 +03:00
ardocrat 19e4cb664d ci: fix pre-release check 2026-03-10 01:58:28 +03:00
ardocrat 18bc327a99 build: update msi and android version 2026-03-10 01:43:15 +03:00
ardocrat 88e2fb0715 node: update 5.4.0 release 2026-03-10 01:09:53 +03:00
ardocrat feb38dc7cf ui: show scan and wallet actions before getting data from node 2026-03-10 00:49:18 +03:00
ardocrat 28ecb5b1f4 fix: camera image square crop and animate qr code scanning progress 2026-03-10 00:39:42 +03:00
ardocrat 024a9d0098 ui: make qr codes background lighter for better scanning 2026-03-10 00:06:57 +03:00
ardocrat 59cf46e1cb feat: open modal to send amount on address scan 2026-03-09 21:33:11 +03:00
ardocrat 22255e0f2a fix: close wallet panels when settings are open 2026-03-09 20:55:11 +03:00
ardocrat 7fdb8d272b fix: hide account list panel on wallet change, do not store account list at content 2026-03-09 20:48:00 +03:00
ardocrat d043562058 build: bump version 2026-03-09 20:10:01 +03:00
20 changed files with 248 additions and 251 deletions
+6 -6
View File
@@ -27,7 +27,7 @@ jobs:
[[ ${{ forgejo.ref_type }} == 'tag' ]] && app_ver=${{ forgejo.ref_name }} || app_ver=v${ver}
echo "v=${app_ver}" >> "$FORGEJO_OUTPUT"
echo $app_ver
[[ ${{ forgejo.ref_type }} == 'tag' ]] && pre=false || pre=true
[[ ${{ forgejo.ref_type }} == 'tag' ]] && pre='false' || pre='true'
echo "pre=${pre}" >> "$FORGEJO_OUTPUT"
echo "pre-release: ${pre}"
- name: Check existing release
@@ -331,11 +331,11 @@ jobs:
- name: Archive Universal
working-directory: macos
run: |
zip -r grim-${{ needs.version.outputs.v }}-macos.zip Grim.app
mv grim-${{ needs.version.outputs.v }}-macos.zip ../release
zip -r grim-${{ needs.version.outputs.v }}-macos-universal.zip Grim.app
mv grim-${{ needs.version.outputs.v }}-macos-universal.zip ../release
- name: Checksum Release Universal
working-directory: release
run: sha256sum grim-${{ needs.version.outputs.v }}-macos.zip > grim-${{ needs.version.outputs.v }}-macos-sha256sum.txt
run: sha256sum grim-${{ needs.version.outputs.v }}-macos-universal.zip > grim-${{ needs.version.outputs.v }}-macos-universal-sha256sum.txt
- name: Upload artifacts
run: |
tar -czf macos.tar.gz release
@@ -396,7 +396,7 @@ jobs:
token: ${{ secrets.RELEASE_TOKEN }}
tag: ${{ needs.version.outputs.v }}
override: true
prerelease: ${{ needs.version.outputs.pre }}
prerelease: ${{ needs.version.outputs.pre == 'true' }}
release-dir: release
release-notes: "Full Changelog: [${{ needs.version.outputs.last_tag }}...${{ needs.version.outputs.v }}](https://code.gri.mw/${{ forgejo.repository }}/compare/${{ needs.version.outputs.last_tag }}...${{ needs.version.outputs.v }})"
- name: Telegram Notify Channel
@@ -432,6 +432,6 @@ jobs:
release/grim-${{ needs.version.outputs.v }}-android-x86_64.apk
release/grim-${{ needs.version.outputs.v }}-linux-arm.AppImage
release/grim-${{ needs.version.outputs.v }}-linux-x86_64.AppImage
release/grim-${{ needs.version.outputs.v }}-macos.zip
release/grim-${{ needs.version.outputs.v }}-macos-universal.zip
release/grim-${{ needs.version.outputs.v }}-win-x86_64.msi
release/grim-${{ needs.version.outputs.v }}-win-x86_64.zip
+1 -1
View File
@@ -5,7 +5,7 @@ REPO_NAME=$1
TAG=$2
DOWNLOAD_URL=${HOST}/${REPO_NAME}/releases/download/${TAG}
FILES=( "grim-${TAG}-android.apk" "grim-${TAG}-android-x86_64.apk" "grim-${TAG}-linux-arm.AppImage" "grim-${TAG}-linux-x86_64.AppImage" "grim-${TAG}-macos.zip" "grim-${TAG}-win-x86_64.msi" "grim-${TAG}-win-x86_64.zip" )
FILES=( "grim-${TAG}-android.apk" "grim-${TAG}-android-x86_64.apk" "grim-${TAG}-linux-arm.AppImage" "grim-${TAG}-linux-x86_64.AppImage" "grim-${TAG}-macos-universal.zip" "grim-${TAG}-win-x86_64.msi" "grim-${TAG}-win-x86_64.zip" )
# Download release files
for f in "${FILES[@]}"; do
+1 -1
View File
@@ -26,6 +26,6 @@ jobs:
grim-${{ github.ref_name }}-android-x86_64.apk
grim-${{ github.ref_name }}-linux-arm.AppImage
grim-${{ github.ref_name }}-linux-x86_64.AppImage
grim-${{ github.ref_name }}-macos.zip
grim-${{ github.ref_name }}-macos-universal.zip
grim-${{ github.ref_name }}-win-x86_64.msi
grim-${{ github.ref_name }}-win-x86_64.zip
Generated
+13 -13
View File
@@ -4000,7 +4000,7 @@ dependencies = [
[[package]]
name = "grim"
version = "0.3.0"
version = "0.3.1"
dependencies = [
"android-activity",
"android_logger",
@@ -4091,7 +4091,7 @@ dependencies = [
[[package]]
name = "grin_api"
version = "5.4.0-alpha.0"
version = "5.4.0"
dependencies = [
"async-stream",
"bytes 1.11.1",
@@ -4124,7 +4124,7 @@ dependencies = [
[[package]]
name = "grin_chain"
version = "5.4.0-alpha.0"
version = "5.4.0"
dependencies = [
"bit-vec 0.6.3",
"bitflags 1.3.2",
@@ -4146,7 +4146,7 @@ dependencies = [
[[package]]
name = "grin_config"
version = "5.4.0-alpha.0"
version = "5.4.0"
dependencies = [
"dirs 2.0.2",
"grin_core",
@@ -4161,7 +4161,7 @@ dependencies = [
[[package]]
name = "grin_core"
version = "5.4.0-alpha.0"
version = "5.4.0"
dependencies = [
"blake2-rfc",
"byteorder",
@@ -4186,7 +4186,7 @@ dependencies = [
[[package]]
name = "grin_keychain"
version = "5.4.0-alpha.0"
version = "5.4.0"
dependencies = [
"blake2-rfc",
"byteorder",
@@ -4207,7 +4207,7 @@ dependencies = [
[[package]]
name = "grin_p2p"
version = "5.4.0-alpha.0"
version = "5.4.0"
dependencies = [
"bitflags 1.3.2",
"bytes 0.5.6",
@@ -4228,7 +4228,7 @@ dependencies = [
[[package]]
name = "grin_pool"
version = "5.4.0-alpha.0"
version = "5.4.0"
dependencies = [
"blake2-rfc",
"chrono",
@@ -4244,9 +4244,9 @@ dependencies = [
[[package]]
name = "grin_secp256k1zkp"
version = "0.7.14"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06832645c3d28079245827908045946db48e5d62b4c54b40701c7e21327e2571"
checksum = "bf7bb95f155b1eede2648a1b9afbba82bc3d9f2af0518b478767559a572bd973"
dependencies = [
"arrayvec 0.7.6",
"cc",
@@ -4259,7 +4259,7 @@ dependencies = [
[[package]]
name = "grin_servers"
version = "5.4.0-alpha.0"
version = "5.4.0"
dependencies = [
"async-stream",
"chrono",
@@ -4290,7 +4290,7 @@ dependencies = [
[[package]]
name = "grin_store"
version = "5.4.0-alpha.0"
version = "5.4.0"
dependencies = [
"byteorder",
"croaring",
@@ -4308,7 +4308,7 @@ dependencies = [
[[package]]
name = "grin_util"
version = "5.4.0-alpha.0"
version = "5.4.0"
dependencies = [
"anyhow",
"backtrace",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "grim"
version = "0.3.0"
version = "0.3.1"
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"
+1 -1
View File
@@ -12,7 +12,7 @@ android {
minSdk 24
targetSdk 36
versionCode 5
versionName "0.3.0"
versionName "0.3.1"
}
lint {
+1 -1
Submodule node updated: 376c85bab5...2ec7b4d5cd
+2 -2
View File
@@ -44,7 +44,6 @@ const BLUE_DARK: Color32 =
const FILL: Color32 = Color32::from_gray(244);
const FILL_DARK: Color32 = Color32::from_gray(26);
const FILL_DEEP: Color32 = Color32::from_gray(238);
const FILL_DEEP_DARK: Color32 = Color32::from_gray(32);
const FILL_LITE: Color32 = Color32::from_gray(249);
@@ -85,6 +84,7 @@ fn use_dark() -> bool {
}
impl Colors {
pub const FILL_DEEP: Color32 = Color32::from_gray(238);
pub const TRANSPARENT: Color32 = Color32::from_rgba_premultiplied(0, 0, 0, 0);
pub const STROKE: Color32 = Color32::from_gray(200);
@@ -172,7 +172,7 @@ impl Colors {
if use_dark() {
FILL_DEEP_DARK
} else {
FILL_DEEP
Self::FILL_DEEP
}
}
+24 -18
View File
@@ -12,21 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::sync::Arc;
use parking_lot::RwLock;
use std::thread;
use egui::load::SizedTexture;
use egui::{Pos2, Rect, RichText, TextureOptions, UiBuilder, Widget};
use image::{DynamicImage, EncodableLayout};
use grin_keychain::mnemonic::WORDS;
use grin_util::ZeroingString;
use grin_wallet_libwallet::SlatepackAddress;
use grin_keychain::mnemonic::WORDS;
use image::{DynamicImage, EncodableLayout};
use parking_lot::RwLock;
use std::sync::Arc;
use std::thread;
use crate::gui::Colors;
use crate::gui::icons::CAMERA_ROTATE;
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::types::{QrScanResult, QrScanState};
use crate::gui::views::View;
use crate::gui::Colors;
use crate::wallet::types::PhraseSize;
use crate::wallet::WalletUtils;
@@ -51,16 +51,13 @@ impl CameraContent {
/// Draw camera content.
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
let rect = if let Some(img_data) = cb.camera_image() {
if let Ok(img) =
image::load_from_memory(&*img_data.0) {
if let Ok(img) = image::load_from_memory(&*img_data.0) {
// Process image to find QR code.
self.scan_qr(&img);
// Draw image.
let img_rect = self.image_ui(ui, img, img_data.1);
// Show UR scan progress.
self.ur_progress_ui(ui);
img_rect
} else {
self.loading_ui(ui)
@@ -69,6 +66,9 @@ impl CameraContent {
self.loading_ui(ui)
};
// Show UR scan progress.
self.ur_progress_ui(ui, &rect);
// Show button to switch cameras.
if cb.can_switch_camera() {
let r = {
@@ -84,6 +84,7 @@ impl CameraContent {
});
});
}
ui.add_space(12.0);
ui.ctx().request_repaint();
}
@@ -125,7 +126,11 @@ impl CameraContent {
egui::Image::from_texture(sized_img)
// Setup to crop image at square.
.uv(Rect::from([
Pos2::new(1.0 - (img_size.y / img_size.x), 0.0),
if img_size.y > img_size.x {
Pos2::new(0.0, 1.0 - (img_size.x / img_size.y))
} else {
Pos2::new(1.0 - (img_size.y / img_size.x), 0.0)
},
Pos2::new(1.0, 1.0)
]))
.max_height(ui.available_width())
@@ -135,15 +140,17 @@ impl CameraContent {
}
/// Draw animated QR code scanning progress.
fn ur_progress_ui(&self, ui: &mut egui::Ui) {
fn ur_progress_ui(&self, ui: &mut egui::Ui, rect: &Rect) {
let show_ur_progress = {
self.ur_data.as_ref().read().is_some()
};
if show_ur_progress {
ui.centered_and_justified(|ui| {
ui.label(RichText::new(format!("{}%", self.ur_progress()))
.size(17.0)
.color(Colors::green()));
ui.scope_builder(UiBuilder::new().max_rect(rect.clone()), |ui| {
ui.centered_and_justified(|ui| {
ui.label(RichText::new(format!("{}%", self.ur_progress()))
.size(32.0)
.color(Colors::gold_dark()));
});
});
}
}
@@ -201,8 +208,7 @@ impl CameraContent {
let on_scan = async move {
// Prepare image data.
let img = image_data.to_luma8();
let mut img: rqrr::PreparedImage<image::GrayImage>
= rqrr::PreparedImage::prepare(img);
let mut img: rqrr::PreparedImage<image::GrayImage> = rqrr::PreparedImage::prepare(img);
// Scan and save results.
let grids = img.detect_grids();
if let Some(g) = grids.get(0) {
+11 -2
View File
@@ -14,7 +14,7 @@
use egui::epaint::{RectShape, Shadow};
use egui::os::OperatingSystem;
use egui::{Align2, CornerRadius, RichText, Stroke, StrokeKind, UiBuilder, Vec2};
use egui::{Align2, Color32, CornerRadius, RichText, Stroke, StrokeKind, UiBuilder, Vec2};
use lazy_static::lazy_static;
use parking_lot::RwLock;
use std::sync::atomic::{AtomicBool, Ordering};
@@ -43,6 +43,8 @@ pub struct Modal {
title: Option<String>,
/// Flag to check first content render.
first_draw: Arc<AtomicBool>,
/// Background color.
fill: Color32,
}
impl Modal {
@@ -61,6 +63,7 @@ impl Modal {
closeable: Arc::new(AtomicBool::new(true)),
title: None,
first_draw: Arc::new(AtomicBool::new(true)),
fill: Colors::fill(),
}
}
@@ -295,6 +298,12 @@ impl Modal {
(align, offset)
}
/// Set custom background color.
pub fn set_background_color(&self, color: Color32) {
let mut w_state = MODAL_STATE.write();
w_state.modal.as_mut().unwrap().fill = color;
}
/// Draw provided content.
fn content_ui(&self,
ui: &mut egui::Ui,
@@ -312,7 +321,7 @@ impl Modal {
sw: 8.0 as u8,
se: 8.0 as u8,
}
}, Colors::fill(), Stroke::NONE, StrokeKind::Outside);
}, self.fill, Stroke::NONE, StrokeKind::Outside);
let bg_idx = ui.painter().add(bg_shape.clone());
rect.min += egui::emath::vec2(6.0, 0.0);
+1 -1
View File
@@ -304,7 +304,7 @@ impl QrCodeContent {
let qr_state = self.qr_image_state.clone();
let text = self.text.clone();
thread::spawn(move || {
let mut encoder = ur::Encoder::bytes(text.as_bytes(), 100).unwrap();
let mut encoder = ur::Encoder::bytes(text.as_bytes(), 64).unwrap();
let mut data = Vec::with_capacity(encoder.fragment_count());
for _ in 0..encoder.fragment_count() {
let ur = encoder.next_part().unwrap();
+4 -3
View File
@@ -160,9 +160,9 @@ impl ContentContainer for WalletsContent {
}
fn container_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
let is_android = OperatingSystem::from_target_os() == OperatingSystem::Android;
let account_list_showing = self.wallet_content.account_content.list_content.is_some();
// Small repaint delay is needed for Android back navigation and account list opening.
let is_android = OperatingSystem::from_target_os() == OperatingSystem::Android;
let account_list_showing = self.wallet_content.account_content.show_list;
ui.ctx().request_repaint_after(Duration::from_millis(if account_list_showing {
10
} else if is_android {
@@ -516,6 +516,7 @@ impl WalletsContent {
}
}, ui);
if show_settings {
self.wallet_content.back(cb);
self.settings_content = Some(SettingsContent::default());
}
}
@@ -753,7 +754,7 @@ impl WalletsContent {
/// Select wallet to make some actions on it.
fn select_wallet(&mut self, w: &Wallet, data: Option<String>, cb: &dyn PlatformCallbacks) {
self.wallet_content.account_content.close_qr_scan(cb);
self.wallet_content.back(cb);
if let Some(data) = data {
w.task(WalletTask::OpenMessage(data));
}
+137 -48
View File
@@ -7,29 +7,30 @@
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distr1ibuted on an "AS IS" BASIS,
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use egui::{Align, Layout, RichText, StrokeKind};
use egui::{Align, Layout, RichText, ScrollArea, StrokeKind};
use egui::scroll_area::ScrollBarVisibility;
use grin_core::core::amount_to_hr_string;
use crate::gui::icons::{FOLDER_USER, PACKAGE, SCAN, SPINNER, USERS_THREE, USER_PLUS};
use crate::gui::icons::{CHECK, FOLDER_USER, PACKAGE, PATH, SCAN, SPINNER, USERS_THREE, USER_PLUS};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::types::{ModalPosition, QrScanResult};
use crate::gui::views::wallets::wallet::account::create::CreateAccountContent;
use crate::gui::views::wallets::wallet::account::list::WalletAccountsContent;
use crate::gui::views::wallets::wallet::types::{WalletContentContainer, GRIN};
use crate::gui::views::{CameraContent, CameraScanContent, Content, Modal, View};
use crate::gui::Colors;
use crate::gui::views::wallets::wallet::request::SendRequestContent;
use crate::wallet::{Wallet, WalletConfig};
use crate::wallet::types::WalletTask;
use crate::wallet::types::{WalletAccount, WalletTask};
/// Wallet account panel content.
pub struct AccountContent {
/// Account list content.
pub list_content: Option<WalletAccountsContent>,
pub struct WalletAccountContent {
/// Flag to show account list content.
pub show_list: bool,
/// Account creation [`Modal`] content.
create_account_content: CreateAccountContent,
@@ -37,15 +38,20 @@ pub struct AccountContent {
qr_scan_content: Option<CameraContent>,
/// QR code scan result
qr_scan_result: Option<QrScanResult>,
/// Send request creation [`Modal`] content.
send_content: Option<SendRequestContent>,
}
/// Account creation [`Modal`] identifier.
const CREATE_MODAL_ID: &'static str = "create_account_modal";
/// Identifier for sending request creation [`Modal`].
const SEND_MODAL_ID: &'static str = "account_send_request_modal";
impl WalletContentContainer for AccountContent {
impl WalletContentContainer for WalletAccountContent {
fn modal_ids(&self) -> Vec<&'static str> {
vec![
CREATE_MODAL_ID
CREATE_MODAL_ID,
SEND_MODAL_ID
]
}
@@ -56,6 +62,11 @@ impl WalletContentContainer for AccountContent {
cb: &dyn PlatformCallbacks) {
match modal.id {
CREATE_MODAL_ID => self.create_account_content.ui(ui, wallet, modal, cb),
SEND_MODAL_ID => {
if let Some(c) = self.send_content.as_mut() {
c.modal_ui(ui, wallet, modal, cb);
}
}
_ => {}
}
}
@@ -65,7 +76,7 @@ impl WalletContentContainer for AccountContent {
self.qr_scan_ui(ui, wallet, cb);
} else {
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
if self.list_content.is_some() {
if self.show_list {
self.list_ui(ui, wallet);
} else {
// Show account content.
@@ -76,18 +87,21 @@ impl WalletContentContainer for AccountContent {
}
}
impl Default for AccountContent {
impl Default for WalletAccountContent {
fn default() -> Self {
Self {
list_content: None,
show_list: false,
create_account_content: CreateAccountContent::default(),
qr_scan_content: None,
qr_scan_result: None,
send_content: None,
}
}
}
impl AccountContent {
const ACCOUNT_ITEM_HEIGHT: f32 = 75.0;
impl WalletAccountContent {
/// Check if QR code scanner was opened.
pub fn qr_scan_showing(&self) -> bool {
self.qr_scan_content.is_some() || self.qr_scan_result.is_some()
@@ -105,15 +119,15 @@ impl AccountContent {
/// Check if it's possible to go back at navigation stack.
pub fn can_back(&self) -> bool {
self.qr_scan_showing() || self.list_content.is_some()
self.qr_scan_showing() || self.show_list
}
/// Navigate back on navigation stack.
pub fn back(&mut self, cb: &dyn PlatformCallbacks) {
if self.qr_scan_showing() {
self.close_qr_scan(cb);
} else if self.list_content.is_some() {
self.list_content = None;
} else if self.show_list {
self.show_list = false;
}
}
@@ -142,13 +156,10 @@ impl AccountContent {
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
// Draw button to show QR code scanner.
let wallet_synced = wallet.synced_from_node();
if wallet_synced {
View::item_button(ui, View::item_rounding(0, 2, true), SCAN, None, || {
self.qr_scan_content = Some(CameraContent::default());
cb.start_camera();
});
}
View::item_button(ui, View::item_rounding(0, 2, true), SCAN, None, || {
self.qr_scan_content = Some(CameraContent::default());
cb.start_camera();
});
// Draw button to show list of accounts.
let accounts = wallet.accounts();
@@ -157,11 +168,7 @@ impl AccountContent {
} else {
USER_PLUS
};
let rounding = if wallet_synced {
View::item_rounding(1, 3, true)
} else {
View::item_rounding(0, 2, true)
};
let rounding = View::item_rounding(1, 3, true);
View::item_button(ui, rounding, accounts_icon, None, || {
if accounts.len() == 1 {
self.create_account_content = CreateAccountContent::default();
@@ -170,9 +177,7 @@ impl AccountContent {
.title(t!("wallets.accounts"))
.show();
} else {
self.list_content = Some(
WalletAccountsContent::new(accounts, wallet.get_config().account)
);
self.show_list = true;
}
});
@@ -240,19 +245,26 @@ impl AccountContent {
/// Draw account list content.
fn list_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet) {
if let Some(accounts) = self.list_content.as_mut() {
let mut selected = false;
accounts.ui(ui, |acc| {
let _ = wallet.set_active_account(&acc.label);
selected = true;
let accounts = wallet.accounts();
let size = accounts.len();
ScrollArea::vertical()
.id_salt("account_list_scroll")
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
.max_height(411.0)
.auto_shrink([true; 2])
.show_rows(ui, ACCOUNT_ITEM_HEIGHT, size, |ui, row_range| {
for index in row_range {
let acc = accounts.get(index).unwrap().clone();
let current = wallet.get_config().account == acc.label;
account_item_ui(ui, &acc, current, index, size, || {
let _ = wallet.set_active_account(&acc.label);
self.show_list = false;
});
if index == size - 1 {
ui.add_space(4.0);
}
}
});
if selected {
self.list_content = None;
return;
}
} else {
return;
}
ui.add_space(2.0);
View::horizontal_line(ui, Colors::item_stroke());
@@ -265,12 +277,12 @@ impl AccountContent {
ui.columns(2, |columns| {
columns[0].vertical_centered_justified(|ui| {
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
self.list_content = None;
self.show_list = false;
});
});
columns[1].vertical_centered_justified(|ui| {
View::button(ui, t!("modal.add"), Colors::white_or_black(false), || {
self.list_content = None;
self.show_list = false;
self.create_account_content = CreateAccountContent::default();
Modal::new(CREATE_MODAL_ID)
.position(ModalPosition::CenterTop)
@@ -290,8 +302,17 @@ impl AccountContent {
cb.stop_camera();
self.qr_scan_content = None;
match result {
QrScanResult::Address(_) => {
//TODO: send with address
QrScanResult::Address(a) => {
if let Some(data) = wallet.get_data() {
if data.info.amount_currently_spendable > 0 {
let address = Some(a.to_string());
self.send_content = Some(SendRequestContent::new(address));
Modal::new(SEND_MODAL_ID)
.position(ModalPosition::CenterTop)
.title(t!("wallets.send"))
.show();
}
}
}
QrScanResult::Slatepack(m) => {
wallet.task(WalletTask::OpenMessage(m));
@@ -321,4 +342,72 @@ impl AccountContent {
ui.add_space(6.0);
});
}
}
/// Draw account item.
fn account_item_ui(ui: &mut egui::Ui,
acc: &WalletAccount,
current: bool,
index: usize,
size: usize,
mut on_select: impl FnMut()) {
// Setup layout size.
let mut rect = ui.available_rect_before_wrap();
rect.set_height(ACCOUNT_ITEM_HEIGHT);
// Draw round background.
let bg_rect = rect.clone();
let item_rounding = View::item_rounding(index, size, false);
ui.painter().rect(bg_rect,
item_rounding,
Colors::fill(),
View::item_stroke(),
StrokeKind::Outside);
ui.vertical(|ui| {
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
// Draw button to select account.
if current {
View::selected_item_check(ui);
} else {
let button_rounding = View::item_rounding(index, size, true);
View::item_button(ui, button_rounding, CHECK, None, || {
on_select();
});
}
let layout_size = ui.available_size();
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
ui.add_space(8.0);
ui.vertical(|ui| {
ui.add_space(3.0);
// Show spendable amount.
let amount = amount_to_hr_string(acc.spendable_amount, true);
let amount_text = format!("{} {}", amount, GRIN);
ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
ui.add_space(1.0);
ui.label(RichText::new(amount_text)
.size(18.0)
.color(Colors::white_or_black(true)));
});
ui.add_space(-2.0);
// Show account name.
let default_acc_label = WalletConfig::DEFAULT_ACCOUNT_LABEL.to_string();
let acc_label = if acc.label == default_acc_label {
t!("wallets.default_account").into()
} else {
acc.label.to_owned()
};
let acc_name = format!("{} {}", FOLDER_USER, acc_label);
View::ellipsize_text(ui, acc_name, 15.0, Colors::text(false));
// Show account BIP32 derivation path.
let acc_path = format!("{} {}", PATH, acc.path);
ui.label(RichText::new(acc_path).size(15.0).color(Colors::gray()));
ui.add_space(3.0);
});
});
});
});
}
@@ -1,130 +0,0 @@
// Copyright 2025 The Grim Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use egui::scroll_area::ScrollBarVisibility;
use egui::{Align, Layout, RichText, ScrollArea, StrokeKind};
use grin_core::core::amount_to_hr_string;
use crate::gui::icons::{CHECK, FOLDER_USER, PATH};
use crate::gui::views::wallets::wallet::types::GRIN;
use crate::gui::views::View;
use crate::gui::Colors;
use crate::wallet::types::WalletAccount;
use crate::wallet::WalletConfig;
/// Wallet account list content.
pub struct WalletAccountsContent {
/// List of wallet accounts.
accounts: Vec<WalletAccount>,
/// Current wallet account label.
current_label: String,
}
const ACCOUNT_ITEM_HEIGHT: f32 = 75.0;
impl WalletAccountsContent {
/// Create new accounts content.
pub fn new(accounts: Vec<WalletAccount>, current: String) -> Self {
Self { accounts, current_label: current }
}
/// Draw account list content.
pub fn ui(&mut self, ui: &mut egui::Ui, mut on_select: impl FnMut(WalletAccount)) {
let size = self.accounts.len();
ScrollArea::vertical()
.id_salt("account_list_scroll")
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
.max_height(411.0)
.auto_shrink([true; 2])
.show_rows(ui, ACCOUNT_ITEM_HEIGHT, size, |ui, row_range| {
for index in row_range {
let acc = self.accounts.get(index).unwrap().clone();
self.account_item_ui(ui, &acc, index, size, || {
on_select(acc.clone());
});
if index == size - 1 {
ui.add_space(4.0);
}
}
});
}
/// Draw account item.
fn account_item_ui(&mut self,
ui: &mut egui::Ui,
acc: &WalletAccount,
index: usize,
size: usize,
mut on_select: impl FnMut()) {
// Setup layout size.
let mut rect = ui.available_rect_before_wrap();
rect.set_height(ACCOUNT_ITEM_HEIGHT);
// Draw round background.
let bg_rect = rect.clone();
let item_rounding = View::item_rounding(index, size, false);
ui.painter().rect(bg_rect,
item_rounding,
Colors::fill(),
View::item_stroke(),
StrokeKind::Outside);
ui.vertical(|ui| {
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
// Draw button to select account.
if self.current_label == acc.label {
View::selected_item_check(ui);
} else {
let button_rounding = View::item_rounding(index, size, true);
View::item_button(ui, button_rounding, CHECK, None, || {
on_select();
});
}
let layout_size = ui.available_size();
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
ui.add_space(8.0);
ui.vertical(|ui| {
ui.add_space(3.0);
// Show spendable amount.
let amount = amount_to_hr_string(acc.spendable_amount, true);
let amount_text = format!("{} {}", amount, GRIN);
ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
ui.add_space(1.0);
ui.label(RichText::new(amount_text)
.size(18.0)
.color(Colors::white_or_black(true)));
});
ui.add_space(-2.0);
// Show account name.
let default_acc_label = WalletConfig::DEFAULT_ACCOUNT_LABEL.to_string();
let acc_label = if acc.label == default_acc_label {
t!("wallets.default_account").into()
} else {
acc.label.to_owned()
};
let acc_name = format!("{} {}", FOLDER_USER, acc_label);
View::ellipsize_text(ui, acc_name, 15.0, Colors::text(false));
// Show account BIP32 derivation path.
let acc_path = format!("{} {}", PATH, acc.path);
ui.label(RichText::new(acc_path).size(15.0).color(Colors::gray()));
ui.add_space(3.0);
});
});
});
});
}
}
+2 -3
View File
@@ -13,7 +13,6 @@
// limitations under the License.
mod content;
mod list;
mod create;
pub use content::*;
pub use content::*;
mod create;
+17 -10
View File
@@ -19,7 +19,7 @@ use grin_chain::SyncStatus;
use crate::gui::icons::{ARROWS_CLOCKWISE, FILE_ARROW_DOWN, FILE_ARROW_UP, FILE_TEXT, GEAR_FINE, POWER, STACK};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::types::{LinePosition, ModalPosition};
use crate::gui::views::wallets::wallet::account::AccountContent;
use crate::gui::views::wallets::wallet::account::WalletAccountContent;
use crate::gui::views::wallets::wallet::message::MessageInputContent;
use crate::gui::views::wallets::wallet::request::{InvoiceRequestContent, SendRequestContent};
use crate::gui::views::wallets::wallet::transport::WalletTransportContent;
@@ -42,7 +42,7 @@ pub struct WalletContent {
pub settings_content: Option<WalletSettingsContent>,
/// Account panel content.
pub account_content: AccountContent,
pub account_content: WalletAccountContent,
/// Transport panel content.
pub transport_content: WalletTransportContent,
@@ -150,9 +150,13 @@ impl WalletContentContainer for WalletContent {
}
});
// Close scanner when account panel got hidden.
if !show_account && self.account_content.qr_scan_showing() {
self.account_content.close_qr_scan(cb);
// Close scanner or account list when account panel got hidden.
if !show_account {
if self.account_content.qr_scan_showing() {
self.account_content.close_qr_scan(cb);
} else {
self.account_content.show_list = false;
}
}
// Flag to check if account panel is opened.
@@ -204,7 +208,11 @@ impl WalletContentContainer for WalletContent {
bottom: 1.0 as i8,
},
fill: if top_panel_expanded {
Colors::fill_lite()
if self.transport_content.qr_address_content.is_some() {
Colors::FILL_DEEP
} else {
Colors::fill_lite()
}
} else {
Colors::TRANSPARENT
},
@@ -302,7 +310,7 @@ impl Default for WalletContent {
Self {
txs_content: Some(WalletTransactionsContent::new(None)),
settings_content: None,
account_content: AccountContent::default(),
account_content: WalletAccountContent::default(),
transport_content: WalletTransportContent::default(),
invoice_content: None,
send_content: None,
@@ -316,7 +324,7 @@ impl WalletContent {
pub fn title(&self) -> impl Into<String> {
if self.account_content.qr_scan_showing() {
t!("scan_qr")
} else if self.account_content.list_content.is_some() {
} else if self.account_content.show_list {
t!("wallets.accounts")
} else if self.transport_content.settings_content.is_some() {
t!("wallets.transport")
@@ -378,8 +386,7 @@ impl WalletContent {
self.settings_content = None;
});
});
let active = if wallet.synced_from_node() &&
has_wallet_data { Some(false) } else { None };
let active = if has_wallet_data { Some(false) } else { None };
columns[1].vertical_centered_justified(|ui| {
if wallet.invoice_creating() {
ui.add_space(4.0);
@@ -14,6 +14,7 @@
use egui::{Align, CornerRadius, Layout, RichText, StrokeKind};
use crate::AppConfig;
use crate::gui::icons::{CIRCLE_HALF, DOTS_THREE_CIRCLE, PLUGS, PLUGS_CONNECTED, POWER, QR_CODE, SHIELD_CHECKERED, SHIELD_SLASH, WARNING_CIRCLE, WRENCH};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::wallets::wallet::transport::settings::WalletTransportSettingsContent;
@@ -40,13 +41,10 @@ impl WalletContentContainer for WalletTransportContent {
fn container_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
if let Some(content) = self.qr_address_content.as_mut() {
// Close panel on wallet change.
if let Some(address) = wallet.slatepack_address() {
if address != content.text {
self.qr_address_content = None;
return;
}
}
let dark_theme = AppConfig::dark_theme().unwrap_or(false);
// Set light theme for better scanning.
AppConfig::set_dark_theme(false);
crate::setup_visuals(ui.ctx());
// Draw QR code content.
ui.add_space(6.0);
content.ui(ui, cb);
@@ -56,6 +54,9 @@ impl WalletContentContainer for WalletTransportContent {
});
});
ui.add_space(6.0);
// Set color theme back.
AppConfig::set_dark_theme(dark_theme);
crate::setup_visuals(ui.ctx());
} else if let Some(content) = self.settings_content.as_mut() {
let mut closed = false;
content.ui(ui, wallet, cb, || {
+1 -1
View File
@@ -52,7 +52,7 @@ impl WalletContentContainer for WalletTransactionsContent {
match m.id {
TX_INFO_MODAL => {
if let Some(content) = self.tx_info_content.as_mut() {
content.ui(ui, w, cb);
content.ui(ui, m, w, cb);
}
}
CANCEL_TX_CONFIRMATION_MODAL => {
+16 -1
View File
@@ -19,6 +19,7 @@ use grin_util::ToHex;
use grin_wallet_libwallet::TxLogEntryType;
use std::fs;
use crate::AppConfig;
use crate::gui::icons::{CIRCLE_HALF, COPY, CUBE, FILE_ARCHIVE, FILE_TEXT, HASH_STRAIGHT, PROHIBIT, QR_CODE, SEAL_CHECK};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::wallets::wallet::proof::PaymentProofContent;
@@ -54,7 +55,11 @@ impl WalletTransactionContent {
}
/// Draw [`Modal`] content.
pub fn ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
pub fn ui(&mut self,
ui: &mut egui::Ui,
modal: &Modal,
wallet: &Wallet,
cb: &dyn PlatformCallbacks) {
// Check values and setup transaction data.
let wallet_data = wallet.get_data();
if wallet_data.is_none() {
@@ -73,6 +78,12 @@ impl WalletTransactionContent {
let tx = txs.get(0).unwrap();
if let Some(content) = self.qr_code_content.as_mut() {
let dark_theme = AppConfig::dark_theme().unwrap_or(false);
// Set light theme for better scanning.
AppConfig::set_dark_theme(false);
crate::setup_visuals(ui.ctx());
modal.set_background_color(Colors::FILL_DEEP);
ui.add_space(6.0);
content.ui(ui, cb);
@@ -93,7 +104,11 @@ impl WalletTransactionContent {
});
});
});
// Set color theme back.
AppConfig::set_dark_theme(dark_theme);
crate::setup_visuals(ui.ctx());
} else {
modal.set_background_color(Colors::fill());
// Show transaction information.
self.info_ui(ui, tx, wallet, cb);
+1 -1
View File
@@ -7,7 +7,7 @@
<?endif ?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Version="0.3.0" UpgradeCode="C19F9B41-CD13-4F0E-B27D-E0EF8CF1CE91" Language="1033" Name="Grim" Manufacturer="Ardocrat">
<Product Id="*" Version="0.3.1" UpgradeCode="C19F9B41-CD13-4F0E-B27D-E0EF8CF1CE91" Language="1033" Name="Grim" Manufacturer="Ardocrat">
<Package Id="FA6823B7-7FB1-49A4-BF64-0442BCD2724B" InstallerVersion="300" Compressed="yes"/>
<Media Id="1" Cabinet="grim.cab" EmbedCab="yes" />