mirror of
https://github.com/narrrl/proton-stream.git
synced 2026-11-03 16:41:26 +01:00
feat(core,app,android,ios): add a first share without a detour or a name to invent
The empty library said to add a link "under Shares, then refresh" with nothing to press on the phones, and on the desktop only switched page. All three now open the add form from there, the desktop with the caret in the link field; and adding already crawls, so there is nothing to refresh. A share's name was required before the viewer had seen anything to name. It is optional now: without one, ShareStore::add_verified takes the shared folder's own name from the link it has just opened. The link comes first in each form. The desktop form submits on Enter, and the Android link field has an eye to check what was pasted.
This commit is contained in:
1 parent
5807493a4a
commit
603cc0c1a9
14 files changed
+153
-50
No files matched your search
@@ -126,6 +126,8 @@ pub enum Action {
|
||||
Screenshot,
|
||||
/// A share link was pasted outside the form: open the form with it.
|
||||
PasteShare(String),
|
||||
/// Go to the add form, caret in the link field.
|
||||
OpenShareForm,
|
||||
AddShare {
|
||||
name: String,
|
||||
url: String,
|
||||
@@ -524,6 +526,8 @@ pub struct ShareForm {
|
||||
pub sending: bool,
|
||||
/// Why the last one was refused.
|
||||
pub error: Option<String>,
|
||||
/// Put the caret in the link field on the next frame the form is drawn.
|
||||
pub focus: bool,
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
@@ -1512,7 +1516,11 @@ impl App {
|
||||
self.page = Page::Shares;
|
||||
self.form.url = url;
|
||||
self.form.error = None;
|
||||
self.note(ctx, "share link pasted — name it and add it", false);
|
||||
self.note(ctx, "share link pasted — add it, or name it first", false);
|
||||
}
|
||||
Action::OpenShareForm => {
|
||||
self.page = Page::Shares;
|
||||
self.form.focus = true;
|
||||
}
|
||||
Action::AddShare {
|
||||
name,
|
||||
|
||||
@@ -649,7 +649,7 @@ fn empty_state(ui: &mut egui::Ui, actions: &mut Vec<Action>) {
|
||||
));
|
||||
ui.add_space(theme::space::XL);
|
||||
if ui::accent_button(ui, "Add a share").clicked() {
|
||||
actions.push(Action::Goto(Page::Shares));
|
||||
actions.push(Action::OpenShareForm);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -113,6 +113,15 @@ fn share_row(
|
||||
ui.add_space(theme::space::S);
|
||||
}
|
||||
|
||||
/// The add form's link field, which the library's empty state focuses.
|
||||
pub fn link_field_id() -> egui::Id {
|
||||
egui::Id::new("share-link")
|
||||
}
|
||||
|
||||
fn ui_enter_pressed(ctx: &egui::Context) -> bool {
|
||||
ctx.input(|input| input.key_pressed(egui::Key::Enter))
|
||||
}
|
||||
|
||||
fn add_form(ui: &mut egui::Ui, shares: &[Share], form: &mut ShareForm, actions: &mut Vec<Action>) {
|
||||
ui::section(ui, "Add a share");
|
||||
ui.label(ui::muted(
|
||||
@@ -128,24 +137,28 @@ fn add_form(ui: &mut egui::Ui, shares: &[Share], form: &mut ShareForm, actions:
|
||||
.show(ui, |ui| {
|
||||
ui.set_width(ui.available_width().min(640.0));
|
||||
|
||||
ui.label(ui::muted("Name"));
|
||||
ui.add(
|
||||
egui::TextEdit::singleline(&mut form.name)
|
||||
.hint_text("Anime")
|
||||
.desired_width(f32::INFINITY),
|
||||
);
|
||||
ui.add_space(theme::space::M);
|
||||
// Enter in any field of the form submits it, as the sign-in
|
||||
// form's do.
|
||||
let mut entered = false;
|
||||
let mut submitted_from = |response: egui::Response| {
|
||||
entered |= response.lost_focus() && ui_enter_pressed(&response.ctx);
|
||||
};
|
||||
|
||||
ui.label(ui::muted("Link"));
|
||||
ui.horizontal(|ui| {
|
||||
let paste = 60.0;
|
||||
let field = egui::Id::new("share-link");
|
||||
ui.add(
|
||||
egui::TextEdit::singleline(&mut form.url)
|
||||
.id(field)
|
||||
.password(true)
|
||||
.hint_text("https://drive.proton.me/urls/…#…")
|
||||
.desired_width(ui.available_width() - paste),
|
||||
let field = link_field_id();
|
||||
if std::mem::take(&mut form.focus) {
|
||||
ui.memory_mut(|memory| memory.request_focus(field));
|
||||
}
|
||||
submitted_from(
|
||||
ui.add(
|
||||
egui::TextEdit::singleline(&mut form.url)
|
||||
.id(field)
|
||||
.password(true)
|
||||
.hint_text("https://drive.proton.me/urls/…#…")
|
||||
.desired_width(ui.available_width() - paste),
|
||||
),
|
||||
);
|
||||
// The link is masked, so pasting is the only sensible way in —
|
||||
// and a button says that more plainly than the field does.
|
||||
@@ -170,20 +183,31 @@ fn add_form(ui: &mut egui::Ui, shares: &[Share], form: &mut ShareForm, actions:
|
||||
}
|
||||
ui.add_space(theme::space::M);
|
||||
|
||||
ui.label(ui::muted("Name (optional)"));
|
||||
submitted_from(
|
||||
ui.add(
|
||||
egui::TextEdit::singleline(&mut form.name)
|
||||
.hint_text("The shared folder's own name")
|
||||
.desired_width(f32::INFINITY),
|
||||
),
|
||||
);
|
||||
ui.add_space(theme::space::M);
|
||||
|
||||
ui::widgets::toggle(ui, &mut form.has_password, "The link asks for a password");
|
||||
if form.has_password {
|
||||
ui.add_space(theme::space::S);
|
||||
ui.add(
|
||||
egui::TextEdit::singleline(&mut form.password)
|
||||
.password(true)
|
||||
.hint_text("Link password")
|
||||
.desired_width(f32::INFINITY),
|
||||
submitted_from(
|
||||
ui.add(
|
||||
egui::TextEdit::singleline(&mut form.password)
|
||||
.password(true)
|
||||
.hint_text("Link password")
|
||||
.desired_width(f32::INFINITY),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ui.add_space(theme::space::L);
|
||||
let ready = !form.name.trim().is_empty()
|
||||
&& !form.url.trim().is_empty()
|
||||
let ready = !form.url.trim().is_empty()
|
||||
&& problem.is_none()
|
||||
&& (!form.has_password || !form.password.is_empty())
|
||||
&& !form.sending;
|
||||
@@ -193,11 +217,11 @@ fn add_form(ui: &mut egui::Ui, shares: &[Share], form: &mut ShareForm, actions:
|
||||
ui.add_space(theme::space::S);
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
if ui
|
||||
.add_enabled_ui(ready, |ui| ui::accent_button(ui, "Add and crawl"))
|
||||
let clicked = ui
|
||||
.add_enabled_ui(ready, |ui| ui::accent_button(ui, "Add share"))
|
||||
.inner
|
||||
.clicked()
|
||||
{
|
||||
.clicked();
|
||||
if clicked || (ready && entered) {
|
||||
actions.push(Action::AddShare {
|
||||
name: form.name.trim().to_string(),
|
||||
url: form.url.trim().to_string(),
|
||||
@@ -210,8 +234,8 @@ fn add_form(ui: &mut egui::Ui, shares: &[Share], form: &mut ShareForm, actions:
|
||||
if form.sending {
|
||||
ui.add(egui::Spinner::new().size(14.0));
|
||||
ui.label(ui::muted("adding…"));
|
||||
} else if form.name.trim().is_empty() || form.url.trim().is_empty() {
|
||||
ui.label(ui::muted("A name and a link are needed."));
|
||||
} else if form.url.trim().is_empty() {
|
||||
ui.label(ui::muted("Paste a share link to add it."));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -422,7 +422,7 @@ impl ShareStore {
|
||||
}
|
||||
|
||||
/// [`Self::add`], but only once the link has been opened with the
|
||||
/// password given.
|
||||
/// password given. An empty `name` takes the shared folder's own.
|
||||
///
|
||||
/// A link that is mistyped, expired or behind the wrong password used to
|
||||
/// be stored anyway, so the first try failed on the crawl and every retry
|
||||
@@ -444,7 +444,19 @@ impl ShareStore {
|
||||
}
|
||||
let custom_password = custom_password.filter(|password| !password.is_empty());
|
||||
let client = open_link(url, custom_password).await?;
|
||||
let share = self.add(name, url, custom_password)?;
|
||||
// A name is the viewer's to give, not theirs to invent before they
|
||||
// can see anything: without one, the shared folder's own name.
|
||||
let name = match name.trim() {
|
||||
"" => client
|
||||
.root_node()
|
||||
.await
|
||||
.ok()
|
||||
.map(|root| root.name)
|
||||
.filter(|name| !name.trim().is_empty())
|
||||
.unwrap_or_else(|| "Shared folder".to_owned()),
|
||||
given => given.to_owned(),
|
||||
};
|
||||
let share = self.add(&name, url, custom_password)?;
|
||||
Ok((share, client))
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user