Skip to content

Commit 4b7ba83

Browse files
authored
Form Validation Message (#16)
1 parent c442bc1 commit 4b7ba83

File tree

7 files changed

+192
-32
lines changed

7 files changed

+192
-32
lines changed

data/Application.css

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
@keyframes fancy-turn {
2-
0% { -gtk-icon-transform: rotate(0deg); }
3-
25% { -gtk-icon-transform: rotate(-30deg); }
4-
50% { -gtk-icon-transform: rotate(0deg); }
5-
75% { -gtk-icon-transform: rotate(30deg); }
6-
100% { -gtk-icon-transform: rotate(0deg); }
2+
0% {
3+
-gtk-icon-transform: rotate(0deg);
4+
}
5+
6+
25% {
7+
-gtk-icon-transform: rotate(-30deg);
8+
}
9+
10+
50% {
11+
-gtk-icon-transform: rotate(0deg);
12+
}
13+
14+
75% {
15+
-gtk-icon-transform: rotate(30deg);
16+
}
17+
18+
100% {
19+
-gtk-icon-transform: rotate(0deg);
20+
}
721
}
822

923
.fancy-turn.animation {
@@ -18,4 +32,4 @@
1832

1933
.fw-500 {
2034
font-weight: 500;
21-
}
35+
}

meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ executable(
4646
'src/Views/Developer.vala',
4747
'src/Views/Form.vala',
4848
'src/Views/Success.vala',
49+
'src/Widgets/InvalidLabel.vala',
4950
'src/Widgets/Stepper.vala',
5051
dependencies: deps,
5152
install: true

po/POTFILES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ src/MainWindow.vala
33
src/Views/Developer.vala
44
src/Views/Form.vala
55
src/Views/Success.vala
6+
src/Widgets/InvalidLabel.vala
67
src/Widgets/Stepper.vala

src/MainWindow.vala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,15 @@ public class MainWindow : Gtk.ApplicationWindow {
8585
});
8686
main_box.append (form_box);
8787

88+
var scrolled_window = new Gtk.ScrolledWindow () {
89+
child = main_box,
90+
vscrollbar_policy = NEVER,
91+
hscrollbar_policy = NEVER
92+
};
93+
8894
var toolbar_view = new Adw.ToolbarView ();
8995
toolbar_view.add_top_bar (headerbar);
90-
toolbar_view.content = main_box;
96+
toolbar_view.content = scrolled_window;
9197

9298
child = toolbar_view;
9399

@@ -126,6 +132,7 @@ public class MainWindow : Gtk.ApplicationWindow {
126132

127133
form_view.developer_name = name;
128134
form_view.developer_email = email;
135+
form_view.focus_name ();
129136
});
130137

131138
success_view.back.connect (() => {

src/Views/Developer.vala

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ public class Views.Developer : Adw.Bin {
1010

1111
public signal void next (string name, string email);
1212

13+
public bool is_valid {
14+
get {
15+
return name_entry.is_valid && email_entry.is_valid;
16+
}
17+
}
18+
1319
construct {
1420
Regex? email_regex = null;
1521
try {
@@ -23,11 +29,19 @@ public class Views.Developer : Adw.Bin {
2329
text = GLib.Environment.get_real_name ()
2430
};
2531

32+
var name_invalid = new Widgets.InvalidLabel () {
33+
text = _("This field is required")
34+
};
35+
2636
email_entry = new Granite.ValidatedEntry () {
2737
regex = email_regex,
2838
margin_top = 6
2939
};
3040

41+
var email_invalid = new Widgets.InvalidLabel () {
42+
text = _("The email is invalid")
43+
};
44+
3145
next_button = new Gtk.Button.with_label (_("Next")) {
3246
margin_bottom = 32,
3347
sensitive = false,
@@ -43,8 +57,10 @@ public class Views.Developer : Adw.Bin {
4357
});
4458
form_box.append (new Granite.HeaderLabel (_("Name:")));
4559
form_box.append (name_entry);
60+
form_box.append (name_invalid);
4661
form_box.append (new Granite.HeaderLabel (_("Email:")));
4762
form_box.append (email_entry);
63+
form_box.append (email_invalid);
4864
form_box.append (next_button);
4965

5066
var content_box = new Adw.Bin () {
@@ -56,16 +72,29 @@ public class Views.Developer : Adw.Bin {
5672

5773
child = content_box;
5874

59-
name_entry.changed.connect (check_valid);
60-
email_entry.changed.connect (check_valid);
75+
name_entry.changed.connect (() => {
76+
check_valid ();
77+
name_invalid.reveal_child = !name_entry.is_valid;
78+
});
6179

62-
next_button.clicked.connect (() => {
63-
next (name_entry.text, email_entry.text);
80+
email_entry.changed.connect (() => {
81+
check_valid ();
82+
email_invalid.reveal_child = !email_entry.is_valid;
6483
});
84+
85+
name_entry.activate.connect (go_next);
86+
email_entry.activate.connect (go_next);
87+
next_button.clicked.connect (go_next);
88+
}
89+
90+
private void go_next () {
91+
if (is_valid) {
92+
next (name_entry.text, email_entry.text);
93+
}
6594
}
6695

6796
private void check_valid () {
68-
next_button.sensitive = name_entry.is_valid && email_entry.is_valid;
97+
next_button.sensitive = is_valid;
6998
}
7099

71100
public void reset_form () {

src/Views/Form.vala

Lines changed: 83 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ public class Views.Form : Adw.Bin {
1616
public string developer_name { get; set; }
1717
public string developer_email { get; set; }
1818

19+
public bool is_valid {
20+
get {
21+
return project_name_entry.is_valid && identifier_entry.is_valid && location_entry.text.length > 0;
22+
}
23+
}
24+
1925
construct {
2026
Regex? project_name_regex = null;
2127
Regex? identifier_regex = null;
@@ -26,31 +32,63 @@ public class Views.Form : Adw.Bin {
2632
critical (e.message);
2733
}
2834

35+
var project_name_header = new Granite.HeaderLabel (_("Project Name:")) {
36+
valign = CENTER
37+
};
38+
39+
var project_name_info = new Gtk.MenuButton () {
40+
can_focus = false,
41+
hexpand = true,
42+
halign = END,
43+
icon_name = "dialog-information-symbolic",
44+
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"))
45+
};
46+
project_name_info.add_css_class (Granite.STYLE_CLASS_DIM_LABEL);
47+
project_name_info.add_css_class (Granite.STYLE_CLASS_FLAT);
48+
49+
var project_name_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0) {
50+
margin_top = 12
51+
};
52+
project_name_box.append (project_name_header);
53+
project_name_box.append (project_name_info);
54+
2955
project_name_entry = new Granite.ValidatedEntry () {
3056
regex = project_name_regex,
3157
margin_top = 6
3258
};
3359

34-
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.")) {
35-
wrap = true,
36-
xalign = 0,
37-
margin_top = 3
60+
var project_name_invalid = new Widgets.InvalidLabel () {
61+
text = _("Project name must start with a lowercase letter and contain only letters and numbers")
3862
};
39-
project_name_description.add_css_class (Granite.STYLE_CLASS_DIM_LABEL);
40-
project_name_description.add_css_class (Granite.STYLE_CLASS_SMALL_LABEL);
63+
64+
var identifier_header = new Granite.HeaderLabel (_("Organization Identifier:")) {
65+
valign = CENTER
66+
};
67+
68+
var identifier_info = new Gtk.MenuButton () {
69+
can_focus = false,
70+
hexpand = true,
71+
halign = END,
72+
icon_name = "dialog-information-symbolic",
73+
popover = build_info_popover (_("A reverse domain-name identifier used to identify the application, such as 'io.github.username'. It may not contain dashes"))
74+
};
75+
identifier_info.add_css_class (Granite.STYLE_CLASS_DIM_LABEL);
76+
identifier_info.add_css_class (Granite.STYLE_CLASS_FLAT);
77+
78+
var identifier_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0) {
79+
margin_top = 12
80+
};
81+
identifier_box.append (identifier_header);
82+
identifier_box.append (identifier_info);
4183

4284
identifier_entry = new Granite.ValidatedEntry () {
4385
regex = identifier_regex,
4486
margin_top = 6
4587
};
4688

47-
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.")) {
48-
wrap = true,
49-
xalign = 0,
50-
margin_top = 3
89+
var identifier_invalid = new Widgets.InvalidLabel () {
90+
text = _("App ID must start with a lowercase letter, use dots to separate parts, contain only letters and numbers, and replace hyphens (-) with underscores (_)")
5191
};
52-
identifier_description.add_css_class (Granite.STYLE_CLASS_DIM_LABEL);
53-
identifier_description.add_css_class (Granite.STYLE_CLASS_SMALL_LABEL);
5492

5593
application_id_entry = new Gtk.Entry () {
5694
margin_top = 6,
@@ -92,6 +130,7 @@ public class Views.Form : Adw.Bin {
92130
vexpand = true,
93131
valign = END,
94132
margin_bottom = 32,
133+
margin_top = 12
95134
};
96135
buttons_box.append (back_button);
97136
buttons_box.append (create_button);
@@ -101,12 +140,12 @@ public class Views.Form : Adw.Bin {
101140
halign = START,
102141
css_classes = { Granite.STYLE_CLASS_H1_LABEL }
103142
});
104-
form_box.append (new Granite.HeaderLabel (_("Project Name:")));
143+
form_box.append (project_name_box);
105144
form_box.append (project_name_entry);
106-
// form_box.append (project_name_description);
107-
form_box.append (new Granite.HeaderLabel (_("Organization Identifier:")));
145+
form_box.append (project_name_invalid);
146+
form_box.append (identifier_box);
108147
form_box.append (identifier_entry);
109-
// form_box.append (identifier_description);
148+
form_box.append (identifier_invalid);
110149
form_box.append (new Granite.HeaderLabel (_("Application ID:")));
111150
form_box.append (application_id_entry);
112151
form_box.append (new Granite.HeaderLabel (_("Location:")));
@@ -133,16 +172,18 @@ public class Views.Form : Adw.Bin {
133172

134173
project_name_entry.changed.connect (() => {
135174
application_id_entry.text = identifier_entry.text + "." + project_name_entry.text;
136-
create_button.sensitive = project_name_entry.is_valid && identifier_entry.is_valid && location_entry.text.length > 0;
175+
create_button.sensitive = is_valid;
176+
project_name_invalid.reveal_child = !project_name_entry.is_valid;
137177
});
138178

139179
identifier_entry.changed.connect (() => {
140180
application_id_entry.text = identifier_entry.text + "." + project_name_entry.text;
141-
create_button.sensitive = project_name_entry.is_valid && identifier_entry.is_valid && location_entry.text.length > 0;
181+
create_button.sensitive = is_valid;
182+
identifier_invalid.reveal_child = !identifier_entry.is_valid;
142183
});
143184

144185
location_entry.changed.connect (() => {
145-
create_button.sensitive = project_name_entry.is_valid && identifier_entry.is_valid && location_entry.text.length > 0;
186+
create_button.sensitive = is_valid;
146187
});
147188

148189
location_entry.icon_release.connect ((icon_pos) => {
@@ -285,7 +326,7 @@ public class Views.Form : Adw.Bin {
285326
}
286327
}
287328

288-
void rename_file (string old_name, string new_name) {
329+
private void rename_file (string old_name, string new_name) {
289330
try {
290331
GLib.File old_file = GLib.File.new_for_path (old_name);
291332
GLib.File new_file = GLib.File.new_for_path (new_name);
@@ -294,4 +335,26 @@ public class Views.Form : Adw.Bin {
294335
debug (e.message);
295336
}
296337
}
338+
339+
private Gtk.Popover build_info_popover (string text) {
340+
var label = new Gtk.Label (text) {
341+
wrap = true,
342+
margin_top = 6,
343+
margin_bottom = 6,
344+
margin_start = 6,
345+
margin_end = 6,
346+
max_width_chars = 24,
347+
justify = CENTER
348+
};
349+
350+
var popover = new Gtk.Popover () {
351+
child = label
352+
};
353+
354+
return popover;
355+
}
356+
357+
public void focus_name () {
358+
project_name_entry.grab_focus ();
359+
}
297360
}

src/Widgets/InvalidLabel.vala

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* SPDX-License-Identifier: GPL-3.0-or-later
3+
* SPDX-FileCopyrightText: 2024 Alain <[email protected]>
4+
*/
5+
6+
public class Widgets.InvalidLabel : Gtk.Grid {
7+
private Gtk.Label text_label;
8+
private Gtk.Revealer label_revealer;
9+
10+
public string text {
11+
set {
12+
text_label.label = value;
13+
}
14+
15+
get {
16+
return text_label.label;
17+
}
18+
}
19+
20+
public bool reveal_child {
21+
set {
22+
label_revealer.reveal_child = value;
23+
}
24+
25+
get {
26+
return label_revealer.reveal_child;
27+
}
28+
}
29+
30+
construct {
31+
text_label = new Gtk.Label (null) {
32+
xalign = 0,
33+
margin_top = 6,
34+
wrap = true
35+
};
36+
text_label.add_css_class ("error");
37+
text_label.add_css_class (Granite.STYLE_CLASS_SMALL_LABEL);
38+
39+
label_revealer = new Gtk.Revealer () {
40+
child = text_label
41+
};
42+
43+
attach (label_revealer, 0, 0, 1, 1);
44+
}
45+
}

0 commit comments

Comments
 (0)