mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
feat(file-select): Queueing transfers
the logic of selecting files has been extended! From now on selecting file will put the files into a transfer queue, which is shown on the bottom panel. When a file is selected the file is added to the queue with a destination path, which is the **current other explorer path at the moment of selection. It is possible to navigate to the transfer queue by using `P` and pressing `ENTER` on a file will remove it from the transfer queue.Other commands will work as well on the transfer queue, like `COPY`, `MOVE`, `DELETE`, `RENAME`. closes #132
This commit is contained in:
@@ -44,6 +44,12 @@
|
||||
|
||||
Released on ??
|
||||
|
||||
- **Queuing transfers**:
|
||||
- the logic of selecting files has been extended!
|
||||
- From now on selecting file will put the files into a **transfer queue**, which is shown on the bottom panel.
|
||||
- When a file is selected the file is added to the queue with a destination path, which is the **current other explorer path at the moment of selection.**
|
||||
- It is possible to navigate to the transfer queue by using `P` and pressing `ENTER` on a file will remove it from the transfer queue.
|
||||
- Other commands will work as well on the transfer queue, like `COPY`, `MOVE`, `DELETE`, `RENAME`.
|
||||
- [issue 308](https://github.com/veeso/termscp/issues/308): added `--wno-keyring` flag to disable keyring
|
||||
- [issue 316](https://github.com/veeso/termscp/issues/316): Local directory path is not switching to what's specified in the bookmark. Now the local directory path is correctly set following this hierarchy:
|
||||
1. Local directory path specified for the host bridge
|
||||
|
||||
@@ -156,6 +156,14 @@ impl FileExplorer {
|
||||
.insert(PathBuf::from(src), PathBuf::from(dst));
|
||||
}
|
||||
|
||||
/// Enqueue all files for transfer
|
||||
pub fn enqueue_all(&mut self, dst: &Path) {
|
||||
let files: Vec<_> = self.iter_files().map(|f| f.path.clone()).collect();
|
||||
for file in files {
|
||||
self.enqueue(&file, dst);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get enqueued files
|
||||
pub fn enqueued(&self) -> &HashMap<PathBuf, PathBuf> {
|
||||
&self.transfer_queue
|
||||
|
||||
@@ -6,46 +6,14 @@ use super::FileTransferActivity;
|
||||
|
||||
impl FileTransferActivity {
|
||||
pub(crate) fn action_mark_file(&mut self, index: usize) {
|
||||
// get dest
|
||||
let dest_path = self.browser.other_explorer_no_found().wrkdir.clone();
|
||||
// get file
|
||||
let browser = self.browser.explorer_mut();
|
||||
let Some(file) = browser.get(index).map(|item| item.path().to_path_buf()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if browser.enqueued().contains_key(&file) {
|
||||
debug!("File already marked, unmarking {}", file.display());
|
||||
browser.dequeue(&file);
|
||||
} else {
|
||||
debug!("Marking file {}", file.display());
|
||||
browser.enqueue(&file, &dest_path);
|
||||
}
|
||||
|
||||
self.reload_browser_file_list();
|
||||
self.enqueue_file(index);
|
||||
}
|
||||
|
||||
pub(crate) fn action_mark_all(&mut self) {
|
||||
let dest_path = self.browser.other_explorer_no_found().wrkdir.clone();
|
||||
let browser = self.browser.explorer_mut();
|
||||
|
||||
let mut files = vec![];
|
||||
for file in browser.iter_files().map(|x| x.path()) {
|
||||
files.push(file.to_path_buf());
|
||||
}
|
||||
for file in files {
|
||||
debug!("Marking file {}", file.display());
|
||||
browser.enqueue(&file, &dest_path);
|
||||
}
|
||||
|
||||
self.reload_browser_file_list();
|
||||
self.enqueue_all();
|
||||
}
|
||||
|
||||
pub(crate) fn action_mark_clear(&mut self) {
|
||||
let browser = self.browser.explorer_mut();
|
||||
debug!("Clearing all marked files");
|
||||
browser.clear_queue();
|
||||
|
||||
self.reload_browser_file_list();
|
||||
self.clear_queue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,6 +166,12 @@ impl Component<Msg, NoUserEvent> for Log {
|
||||
self.perform(Cmd::Move(Direction::Up));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right, ..
|
||||
}) => Some(Msg::Ui(UiMsg::BottomPanelRight)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => Some(Msg::Ui(UiMsg::BottomPanelLeft)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::PageUp, ..
|
||||
}) => {
|
||||
|
||||
@@ -12,6 +12,7 @@ use super::{Msg, PendingActionMsg, TransferMsg, UiMsg};
|
||||
mod log;
|
||||
mod misc;
|
||||
mod popups;
|
||||
mod selected_files;
|
||||
mod transfer;
|
||||
|
||||
pub use misc::FooterBar;
|
||||
@@ -26,6 +27,7 @@ pub use popups::{
|
||||
pub use transfer::{ExplorerFind, ExplorerFuzzy, ExplorerLocal, ExplorerRemote};
|
||||
|
||||
pub use self::log::Log;
|
||||
pub use self::selected_files::SelectedFilesList;
|
||||
|
||||
#[derive(Default, MockComponent)]
|
||||
pub struct GlobalListener {
|
||||
|
||||
@@ -670,7 +670,7 @@ impl KeybindingsPopup {
|
||||
))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<P>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Toggle log panel"))
|
||||
.add_col(TextSpan::from(" Toggle bottom panel"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<Q|F10>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Quit termscp"))
|
||||
|
||||
133
src/ui/activities/filetransfer/components/selected_files.rs
Normal file
133
src/ui/activities/filetransfer/components/selected_files.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use tui_realm_stdlib::List;
|
||||
use tuirealm::command::{Cmd, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
|
||||
use crate::ui::activities::filetransfer::{MarkQueue, Msg, UiMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct SelectedFilesList {
|
||||
component: List,
|
||||
paths: Vec<PathBuf>,
|
||||
queue: MarkQueue,
|
||||
}
|
||||
|
||||
impl SelectedFilesList {
|
||||
pub fn new(
|
||||
paths: &[(PathBuf, PathBuf)],
|
||||
queue: MarkQueue,
|
||||
color: Color,
|
||||
title: &'static str,
|
||||
) -> Self {
|
||||
let enqueued_paths = paths
|
||||
.iter()
|
||||
.map(|(src, _)| src.clone())
|
||||
.collect::<Vec<PathBuf>>();
|
||||
|
||||
Self {
|
||||
queue,
|
||||
paths: enqueued_paths,
|
||||
component: List::default()
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.rewind(true)
|
||||
.scroll(true)
|
||||
.step(4)
|
||||
.highlighted_color(color)
|
||||
.highlighted_str("➤ ")
|
||||
.title(title, Alignment::Center)
|
||||
.rows(
|
||||
paths
|
||||
.iter()
|
||||
.map(|(src, dest)| {
|
||||
let name = src
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
let dest = dest
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
vec![
|
||||
TextSpan::from(name),
|
||||
TextSpan::from(" -> "),
|
||||
TextSpan::from(dest),
|
||||
]
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for SelectedFilesList {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(Direction::Down));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => {
|
||||
self.perform(Cmd::Move(Direction::Up));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::PageDown,
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Scroll(Direction::Down));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::PageUp, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Scroll(Direction::Up));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Home, ..
|
||||
}) => {
|
||||
self.perform(Cmd::GoTo(Position::Begin));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::End, .. }) => {
|
||||
self.perform(Cmd::GoTo(Position::End));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right, ..
|
||||
}) => Some(Msg::Ui(UiMsg::BottomPanelRight)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
}) => Some(Msg::Ui(UiMsg::BottomPanelLeft)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::BackTab | Key::Tab | Key::Char('p'),
|
||||
..
|
||||
}) => Some(Msg::Ui(UiMsg::LogBackTabbed)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => {
|
||||
// unmark the selected file
|
||||
let State::One(StateValue::Usize(idx)) = self.state() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let path = self.paths.get(idx)?;
|
||||
|
||||
Some(Msg::Ui(UiMsg::MarkRemove(self.queue, path.clone())))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -523,7 +523,7 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('p'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::ShowLogPanel)),
|
||||
}) => Some(Msg::Ui(UiMsg::GoToTransferQueue)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('r') | Key::Function(6),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
@@ -737,7 +737,7 @@ impl Component<Msg, NoUserEvent> for ExplorerRemote {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('p'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::ShowLogPanel)),
|
||||
}) => Some(Msg::Ui(UiMsg::GoToTransferQueue)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('r') | Key::Function(6),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
|
||||
@@ -40,6 +40,12 @@ use crate::system::watcher::FsWatcher;
|
||||
|
||||
// -- components
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub enum MarkQueue {
|
||||
Local,
|
||||
Remote,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
enum Id {
|
||||
ChmodPopup,
|
||||
@@ -74,6 +80,8 @@ enum Id {
|
||||
StatusBarRemote,
|
||||
SymlinkPopup,
|
||||
SyncBrowsingMkdirPopup,
|
||||
TransferQueueHostBridge,
|
||||
TransferQueueRemote,
|
||||
WaitPopup,
|
||||
WatchedPathsList,
|
||||
WatcherPopup,
|
||||
@@ -125,6 +133,8 @@ enum TransferMsg {
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum UiMsg {
|
||||
BottomPanelLeft,
|
||||
BottomPanelRight,
|
||||
ChangeFileSorting(FileSorting),
|
||||
ChangeTransferWindow,
|
||||
CloseChmodPopup,
|
||||
@@ -155,6 +165,7 @@ enum UiMsg {
|
||||
LogBackTabbed,
|
||||
/// Mark file on the list; usize is the index of the file
|
||||
MarkFile(usize),
|
||||
MarkRemove(MarkQueue, PathBuf),
|
||||
/// Mark all file at tab
|
||||
MarkAll,
|
||||
/// Clear all marks
|
||||
@@ -171,7 +182,7 @@ enum UiMsg {
|
||||
ShowFilterPopup,
|
||||
ShowGotoPopup,
|
||||
ShowKeybindingsPopup,
|
||||
ShowLogPanel,
|
||||
GoToTransferQueue,
|
||||
ShowMkdirPopup,
|
||||
ShowNewFilePopup,
|
||||
ShowOpenWithPopup,
|
||||
@@ -313,6 +324,45 @@ impl FileTransferActivity {
|
||||
self.browser.found_mut()
|
||||
}
|
||||
|
||||
/// Enqueue a file to be transferred
|
||||
fn enqueue_file(&mut self, index: usize) {
|
||||
let Some(src) = self
|
||||
.browser
|
||||
.explorer()
|
||||
.get(index)
|
||||
.map(|item| item.path().to_path_buf())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if self.browser.explorer().enqueued().contains_key(&src) {
|
||||
debug!("File already marked, unmarking {}", src.display());
|
||||
self.browser.explorer_mut().dequeue(&src);
|
||||
} else {
|
||||
debug!("Marking file {}", src.display());
|
||||
let dest = self.browser.other_explorer_no_found().wrkdir.clone();
|
||||
self.browser.explorer_mut().enqueue(&src, &dest);
|
||||
}
|
||||
self.reload_browser_file_list();
|
||||
self.refresh_host_bridge_transfer_queue();
|
||||
self.refresh_remote_transfer_queue();
|
||||
}
|
||||
|
||||
fn enqueue_all(&mut self) {
|
||||
let dest = self.browser.other_explorer_no_found().wrkdir.clone();
|
||||
self.browser.explorer_mut().enqueue_all(&dest);
|
||||
self.reload_browser_file_list();
|
||||
self.refresh_host_bridge_transfer_queue();
|
||||
self.refresh_remote_transfer_queue();
|
||||
}
|
||||
|
||||
fn clear_queue(&mut self) {
|
||||
self.browser.explorer_mut().clear_queue();
|
||||
self.reload_browser_file_list();
|
||||
self.refresh_host_bridge_transfer_queue();
|
||||
self.refresh_remote_transfer_queue();
|
||||
}
|
||||
|
||||
/// Get file name for a file in cache
|
||||
fn get_cache_tmp_name(&self, name: &str, file_type: Option<&str>) -> Option<String> {
|
||||
self.cache.as_ref().map(|_| {
|
||||
|
||||
@@ -11,7 +11,9 @@ use tuirealm::{State, StateValue, Update};
|
||||
use super::actions::SelectedFile;
|
||||
use super::actions::walkdir::WalkdirError;
|
||||
use super::browser::{FileExplorerTab, FoundExplorerTab};
|
||||
use super::{ExitReason, FileTransferActivity, Id, Msg, TransferMsg, TransferOpts, UiMsg};
|
||||
use super::{
|
||||
ExitReason, FileTransferActivity, Id, MarkQueue, Msg, TransferMsg, TransferOpts, UiMsg,
|
||||
};
|
||||
|
||||
impl Update<Msg> for FileTransferActivity {
|
||||
fn update(&mut self, msg: Option<Msg>) -> Option<Msg> {
|
||||
@@ -473,8 +475,8 @@ impl FileTransferActivity {
|
||||
self.browser.fuzzy_search(&needle);
|
||||
self.update_find_list();
|
||||
}
|
||||
UiMsg::ShowLogPanel => {
|
||||
assert!(self.app.active(&Id::Log).is_ok());
|
||||
UiMsg::GoToTransferQueue => {
|
||||
assert!(self.app.active(&Id::TransferQueueHostBridge).is_ok());
|
||||
}
|
||||
UiMsg::LogBackTabbed => {
|
||||
assert!(self.app.active(&Id::ExplorerHostBridge).is_ok());
|
||||
@@ -488,6 +490,18 @@ impl FileTransferActivity {
|
||||
UiMsg::MarkClear => {
|
||||
self.action_mark_clear();
|
||||
}
|
||||
UiMsg::MarkRemove(tab, path) => match tab {
|
||||
MarkQueue::Local => {
|
||||
self.host_bridge_mut().dequeue(&path);
|
||||
self.reload_host_bridge_filelist();
|
||||
self.refresh_host_bridge_transfer_queue();
|
||||
}
|
||||
MarkQueue::Remote => {
|
||||
self.remote_mut().dequeue(&path);
|
||||
self.reload_remote_filelist();
|
||||
self.refresh_remote_transfer_queue();
|
||||
}
|
||||
},
|
||||
UiMsg::Quit => {
|
||||
self.disconnect_and_quit();
|
||||
self.umount_quit();
|
||||
@@ -593,6 +607,31 @@ impl FileTransferActivity {
|
||||
UiMsg::WindowResized => {
|
||||
self.redraw = true;
|
||||
}
|
||||
|
||||
UiMsg::BottomPanelLeft => match self.app.focus() {
|
||||
Some(Id::TransferQueueHostBridge) => {
|
||||
assert!(self.app.active(&Id::Log).is_ok())
|
||||
}
|
||||
Some(Id::TransferQueueRemote) => {
|
||||
assert!(self.app.active(&Id::TransferQueueHostBridge).is_ok())
|
||||
}
|
||||
Some(Id::Log) => {
|
||||
assert!(self.app.active(&Id::TransferQueueRemote).is_ok())
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
UiMsg::BottomPanelRight => match self.app.focus() {
|
||||
Some(Id::TransferQueueHostBridge) => {
|
||||
assert!(self.app.active(&Id::TransferQueueRemote).is_ok())
|
||||
}
|
||||
Some(Id::TransferQueueRemote) => {
|
||||
assert!(self.app.active(&Id::Log).is_ok())
|
||||
}
|
||||
Some(Id::Log) => {
|
||||
assert!(self.app.active(&Id::TransferQueueHostBridge).is_ok())
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ use super::browser::{FileExplorerTab, FoundExplorerTab};
|
||||
use super::components::ATTR_FILES;
|
||||
use super::{Context, FileTransferActivity, Id, components};
|
||||
use crate::explorer::FileSorting;
|
||||
use crate::ui::activities::filetransfer::MarkQueue;
|
||||
use crate::utils::ui::{Popup, Size};
|
||||
|
||||
impl FileTransferActivity {
|
||||
@@ -81,6 +82,8 @@ impl FileTransferActivity {
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
self.refresh_host_bridge_transfer_queue();
|
||||
self.refresh_remote_transfer_queue();
|
||||
// Load status bar
|
||||
self.refresh_local_status_bar();
|
||||
self.refresh_remote_status_bar();
|
||||
@@ -138,6 +141,17 @@ impl FileTransferActivity {
|
||||
.direction(Direction::Horizontal)
|
||||
.horizontal_margin(1)
|
||||
.split(bottom_chunks[0]);
|
||||
let bottom_components = Layout::default()
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Percentage(25),
|
||||
Constraint::Percentage(25),
|
||||
Constraint::Percentage(50),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.direction(Direction::Horizontal)
|
||||
.split(bottom_chunks[1]);
|
||||
// Draw footer
|
||||
self.app.view(&Id::FooterBar, f, body[1]);
|
||||
// Draw explorers
|
||||
@@ -153,8 +167,13 @@ impl FileTransferActivity {
|
||||
} else {
|
||||
self.app.view(&Id::ExplorerRemote, f, tabs_chunks[1]);
|
||||
}
|
||||
// draw transfer queues
|
||||
self.app
|
||||
.view(&Id::TransferQueueHostBridge, f, bottom_components[0]);
|
||||
self.app
|
||||
.view(&Id::TransferQueueRemote, f, bottom_components[1]);
|
||||
// Draw log box
|
||||
self.app.view(&Id::Log, f, bottom_chunks[1]);
|
||||
self.app.view(&Id::Log, f, bottom_components[2]);
|
||||
// Draw status bar
|
||||
self.app
|
||||
.view(&Id::StatusBarHostBridge, f, status_bar_chunks[0]);
|
||||
@@ -928,6 +947,56 @@ impl FileTransferActivity {
|
||||
let _ = self.app.umount(&Id::FileInfoPopup);
|
||||
}
|
||||
|
||||
pub(super) fn refresh_host_bridge_transfer_queue(&mut self) {
|
||||
let enqueued = self
|
||||
.host_bridge()
|
||||
.enqueued()
|
||||
.iter()
|
||||
.map(|(src, dest)| (src.clone(), dest.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
let log_panel = self.theme().transfer_log_window;
|
||||
|
||||
assert!(
|
||||
self.app
|
||||
.remount(
|
||||
Id::TransferQueueHostBridge,
|
||||
Box::new(components::SelectedFilesList::new(
|
||||
&enqueued,
|
||||
MarkQueue::Local,
|
||||
log_panel,
|
||||
"Host Bridge transfer queue",
|
||||
)),
|
||||
vec![]
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn refresh_remote_transfer_queue(&mut self) {
|
||||
let enqueued = self
|
||||
.remote()
|
||||
.enqueued()
|
||||
.iter()
|
||||
.map(|(src, dest)| (src.clone(), dest.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
let log_panel = self.theme().transfer_log_window;
|
||||
|
||||
assert!(
|
||||
self.app
|
||||
.remount(
|
||||
Id::TransferQueueRemote,
|
||||
Box::new(components::SelectedFilesList::new(
|
||||
&enqueued,
|
||||
MarkQueue::Remote,
|
||||
log_panel,
|
||||
"Remote transfer queue",
|
||||
)),
|
||||
vec![]
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn refresh_local_status_bar(&mut self) {
|
||||
let sorting_color = self.theme().transfer_status_sorting;
|
||||
let hidden_color = self.theme().transfer_status_hidden;
|
||||
|
||||
Reference in New Issue
Block a user