diff --git a/meson.build b/meson.build index d396a23..f167490 100644 --- a/meson.build +++ b/meson.build @@ -46,6 +46,7 @@ executable( 'src/Views/Developer.vala', 'src/Views/Form.vala', 'src/Views/Success.vala', + 'src/Widgets/InvalidLabel.vala', 'src/Widgets/Stepper.vala', dependencies: deps, install: true diff --git a/po/POTFILES b/po/POTFILES index 0d0c593..e0123bf 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -3,4 +3,5 @@ src/MainWindow.vala src/Views/Developer.vala src/Views/Form.vala src/Views/Success.vala +src/Widgets/InvalidLabel.vala src/Widgets/Stepper.vala \ No newline at end of file diff --git a/src/MainWindow.vala b/src/MainWindow.vala index c3ae825..d263a04 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -85,9 +85,15 @@ public class MainWindow : Gtk.ApplicationWindow { }); main_box.append (form_box); + var scrolled_window = new Gtk.ScrolledWindow () { + child = main_box, + vscrollbar_policy = NEVER, + hscrollbar_policy = NEVER + }; + var toolbar_view = new Adw.ToolbarView (); toolbar_view.add_top_bar (headerbar); - toolbar_view.content = main_box; + toolbar_view.content = scrolled_window; child = toolbar_view; @@ -126,6 +132,7 @@ public class MainWindow : Gtk.ApplicationWindow { form_view.developer_name = name; form_view.developer_email = email; + form_view.focus_name (); }); success_view.back.connect (() => { diff --git a/src/Views/Developer.vala b/src/Views/Developer.vala index 2a833cf..c1be128 100644 --- a/src/Views/Developer.vala +++ b/src/Views/Developer.vala @@ -10,6 +10,12 @@ public class Views.Developer : Adw.Bin { public signal void next (string name, string email); + public bool is_valid { + get { + return name_entry.is_valid && email_entry.is_valid; + } + } + construct { Regex? email_regex = null; try { @@ -23,11 +29,19 @@ public class Views.Developer : Adw.Bin { text = GLib.Environment.get_real_name () }; + var name_invalid = new Widgets.InvalidLabel () { + text = _("This field is required") + }; + email_entry = new Granite.ValidatedEntry () { regex = email_regex, margin_top = 6 }; + var email_invalid = new Widgets.InvalidLabel () { + text = _("The email is invalid") + }; + next_button = new Gtk.Button.with_label (_("Next")) { margin_bottom = 32, sensitive = false, @@ -43,8 +57,10 @@ public class Views.Developer : Adw.Bin { }); form_box.append (new Granite.HeaderLabel (_("Name:"))); form_box.append (name_entry); + form_box.append (name_invalid); form_box.append (new Granite.HeaderLabel (_("Email:"))); form_box.append (email_entry); + form_box.append (email_invalid); form_box.append (next_button); var content_box = new Adw.Bin () { @@ -56,16 +72,29 @@ public class Views.Developer : Adw.Bin { child = content_box; - name_entry.changed.connect (check_valid); - email_entry.changed.connect (check_valid); + name_entry.changed.connect (() => { + check_valid (); + name_invalid.reveal_child = !name_entry.is_valid; + }); - next_button.clicked.connect (() => { - next (name_entry.text, email_entry.text); + email_entry.changed.connect (() => { + check_valid (); + email_invalid.reveal_child = !email_entry.is_valid; }); + + name_entry.activate.connect (go_next); + email_entry.activate.connect (go_next); + next_button.clicked.connect (go_next); + } + + private void go_next () { + if (is_valid) { + next (name_entry.text, email_entry.text); + } } private void check_valid () { - next_button.sensitive = name_entry.is_valid && email_entry.is_valid; + next_button.sensitive = is_valid; } public void reset_form () { diff --git a/src/Views/Form.vala b/src/Views/Form.vala index 6b825f9..fc35812 100644 --- a/src/Views/Form.vala +++ b/src/Views/Form.vala @@ -16,6 +16,12 @@ public class Views.Form : Adw.Bin { public string developer_name { get; set; } public string developer_email { get; set; } + public bool is_valid { + get { + return project_name_entry.is_valid && identifier_entry.is_valid && location_entry.text.length > 0; + } + } + construct { Regex? project_name_regex = null; Regex? identifier_regex = null; @@ -26,31 +32,63 @@ public class Views.Form : Adw.Bin { critical (e.message); } + var project_name_header = new Granite.HeaderLabel (_("Project Name:")) { + valign = END + }; + + var project_name_info = new Gtk.MenuButton () { + can_focus = false, + hexpand = true, + halign = END, + icon_name = "dialog-information-symbolic", + popover = build_info_popover (_("A unique name that is used for the project folder and other resources. The name should be in lower case without spaces and should not start with a number")) + }; + project_name_info.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); + project_name_info.add_css_class (Granite.STYLE_CLASS_FLAT); + + var project_name_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0) { + margin_top = 12 + }; + project_name_box.append (project_name_header); + project_name_box.append (project_name_info); + project_name_entry = new Granite.ValidatedEntry () { regex = project_name_regex, margin_top = 6 }; - var project_name_description = new Gtk.Label (_("A unique name that is used for the project folder and other resources. The name should be in lower case without spaces and should not start with a number.")) { - wrap = true, - xalign = 0, - margin_top = 3 + var project_name_invalid = new Widgets.InvalidLabel () { + text = _("Project name must start with a lowercase letter and contain only letters and numbers") }; - project_name_description.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); - project_name_description.add_css_class (Granite.STYLE_CLASS_SMALL_LABEL); + + var identifier_header = new Granite.HeaderLabel (_("Organization Identifier:")) { + valign = END + }; + + var identifier_info = new Gtk.MenuButton () { + can_focus = false, + hexpand = true, + halign = END, + icon_name = "dialog-information-symbolic", + popover = build_info_popover (_("A reverse domain-name identifier used to identify the application, such as 'io.github.username'. It may not contain dashes")) + }; + identifier_info.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); + identifier_info.add_css_class (Granite.STYLE_CLASS_FLAT); + + var identifier_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0) { + margin_top = 12 + }; + identifier_box.append (identifier_header); + identifier_box.append (identifier_info); identifier_entry = new Granite.ValidatedEntry () { regex = identifier_regex, margin_top = 6 }; - var identifier_description = new Gtk.Label (_("A reverse domain-name identifier used to identify the application, such as 'io.github.username'. It may not contain dashes.")) { - wrap = true, - xalign = 0, - margin_top = 3 + var identifier_invalid = new Widgets.InvalidLabel () { + text = _("App ID must start with a lowercase letter, use dots to separate parts, contain only letters and numbers, and replace hyphens (-) with underscores (_)") }; - identifier_description.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); - identifier_description.add_css_class (Granite.STYLE_CLASS_SMALL_LABEL); application_id_entry = new Gtk.Entry () { margin_top = 6, @@ -101,12 +139,12 @@ public class Views.Form : Adw.Bin { halign = START, css_classes = { Granite.STYLE_CLASS_H1_LABEL } }); - form_box.append (new Granite.HeaderLabel (_("Project Name:"))); + form_box.append (project_name_box); form_box.append (project_name_entry); - // form_box.append (project_name_description); - form_box.append (new Granite.HeaderLabel (_("Organization Identifier:"))); + form_box.append (project_name_invalid); + form_box.append (identifier_box); form_box.append (identifier_entry); - // form_box.append (identifier_description); + form_box.append (identifier_invalid); form_box.append (new Granite.HeaderLabel (_("Application ID:"))); form_box.append (application_id_entry); form_box.append (new Granite.HeaderLabel (_("Location:"))); @@ -133,16 +171,18 @@ public class Views.Form : Adw.Bin { project_name_entry.changed.connect (() => { application_id_entry.text = identifier_entry.text + "." + project_name_entry.text; - create_button.sensitive = project_name_entry.is_valid && identifier_entry.is_valid && location_entry.text.length > 0; + create_button.sensitive = is_valid; + project_name_invalid.reveal_child = !project_name_entry.is_valid; }); identifier_entry.changed.connect (() => { application_id_entry.text = identifier_entry.text + "." + project_name_entry.text; - create_button.sensitive = project_name_entry.is_valid && identifier_entry.is_valid && location_entry.text.length > 0; + create_button.sensitive = is_valid; + identifier_invalid.reveal_child = !identifier_entry.is_valid; }); location_entry.changed.connect (() => { - create_button.sensitive = project_name_entry.is_valid && identifier_entry.is_valid && location_entry.text.length > 0; + create_button.sensitive = is_valid; }); location_entry.icon_release.connect ((icon_pos) => { @@ -281,7 +321,7 @@ public class Views.Form : Adw.Bin { } } - void rename_file (string old_name, string new_name) { + private void rename_file (string old_name, string new_name) { try { GLib.File old_file = GLib.File.new_for_path (old_name); GLib.File new_file = GLib.File.new_for_path (new_name); @@ -290,4 +330,26 @@ public class Views.Form : Adw.Bin { debug (e.message); } } + + private Gtk.Popover build_info_popover (string text) { + var label = new Gtk.Label (text) { + wrap = true, + margin_top = 6, + margin_bottom = 6, + margin_start = 6, + margin_end = 6, + max_width_chars = 24, + justify = CENTER + }; + + var popover = new Gtk.Popover () { + child = label + }; + + return popover; + } + + public void focus_name () { + project_name_entry.grab_focus (); + } } diff --git a/src/Widgets/InvalidLabel.vala b/src/Widgets/InvalidLabel.vala new file mode 100644 index 0000000..50951d6 --- /dev/null +++ b/src/Widgets/InvalidLabel.vala @@ -0,0 +1,45 @@ +/* +* SPDX-License-Identifier: GPL-3.0-or-later +* SPDX-FileCopyrightText: 2024 Alain +*/ + +public class Widgets.InvalidLabel : Gtk.Grid { + private Gtk.Label text_label; + private Gtk.Revealer label_revealer; + + public string text { + set { + text_label.label = value; + } + + get { + return text_label.label; + } + } + + public bool reveal_child { + set { + label_revealer.reveal_child = value; + } + + get { + return label_revealer.reveal_child; + } + } + + construct { + text_label = new Gtk.Label (null) { + xalign = 0, + margin_top = 6, + wrap = true + }; + text_label.add_css_class ("error"); + text_label.add_css_class (Granite.STYLE_CLASS_SMALL_LABEL); + + label_revealer = new Gtk.Revealer () { + child = text_label + }; + + attach (label_revealer, 0, 0, 1, 1); + } +}