Skip to content

Commit fbe6391

Browse files
committed
Add interactive settings and help menu to StatusBar
- Introduced new items in the StatusBar for settings, profile, and help, enhancing user accessibility. - Implemented hover effects for the help menu and added corresponding click handlers for help-related actions. - Updated color scheme for the status bar to match the sidebar for a cohesive look. - Adjusted font sizes in various views for consistency and improved readability.
1 parent 70c692f commit fbe6391

9 files changed

Lines changed: 151 additions & 130 deletions

File tree

tuttle/app/core/status_bar.py

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,15 @@
99
from dataclasses import dataclass
1010

1111
from flet import (
12+
Border,
13+
BorderSide,
1214
Container,
1315
Icon,
1416
Icons,
1517
Margin,
1618
Padding,
19+
PopupMenuButton,
20+
PopupMenuItem,
1721
Row,
1822
Text,
1923
MainAxisAlignment,
@@ -136,6 +140,10 @@ def __init__(
136140
on_click_expiring: Optional[Callable] = None,
137141
on_click_sync: Optional[Callable] = None,
138142
on_click_quick_add: Optional[Callable] = None,
143+
on_click_settings: Optional[Callable] = None,
144+
on_click_profile: Optional[Callable] = None,
145+
on_click_help_ask: Optional[Callable] = None,
146+
on_click_help_bug: Optional[Callable] = None,
139147
):
140148
self._on_click_overdue = on_click_overdue
141149
self._on_click_outstanding = on_click_outstanding
@@ -186,21 +194,107 @@ def __init__(
186194
self._right_divider = StatusBarDivider()
187195
self._right_divider.visible = False
188196

197+
# ── Chrome zone — settings, profile, help (left side) ──
198+
self._chrome_divider = StatusBarDivider()
199+
200+
self.settings_item = StatusBarItem(
201+
icon=Icons.SETTINGS_OUTLINED,
202+
tooltip="Preferences",
203+
on_click=on_click_settings,
204+
color=colors.text_muted,
205+
)
206+
self.profile_item = StatusBarItem(
207+
icon=Icons.PERSON_OUTLINE_OUTLINED,
208+
tooltip="Profile",
209+
on_click=on_click_profile,
210+
color=colors.text_muted,
211+
)
212+
self.help_menu = PopupMenuButton(
213+
content=Container(
214+
padding=Padding.symmetric(horizontal=dimens.STATUSBAR_ITEM_PADDING_H),
215+
border_radius=dimens.RADIUS_SM,
216+
content=Row(
217+
controls=[
218+
Icon(
219+
Icons.HELP_OUTLINE,
220+
size=dimens.SM_ICON_SIZE,
221+
color=colors.text_muted,
222+
),
223+
],
224+
spacing=0,
225+
vertical_alignment=CrossAxisAlignment.CENTER,
226+
),
227+
on_hover=lambda e: self._on_help_hover(e),
228+
),
229+
tooltip="Help",
230+
menu_padding=Padding.symmetric(vertical=4, horizontal=0),
231+
items=[
232+
PopupMenuItem(
233+
height=32,
234+
content=Row(
235+
controls=[
236+
Icon(
237+
Icons.CONTACT_SUPPORT,
238+
size=dimens.SM_ICON_SIZE,
239+
color=colors.text_secondary,
240+
),
241+
Text(
242+
"Ask a question",
243+
size=fonts.BODY_2_SIZE,
244+
color=colors.text_primary,
245+
),
246+
],
247+
spacing=dimens.SPACE_XS,
248+
vertical_alignment=CrossAxisAlignment.CENTER,
249+
),
250+
on_click=on_click_help_ask,
251+
),
252+
PopupMenuItem(
253+
height=32,
254+
content=Row(
255+
controls=[
256+
Icon(
257+
Icons.BUG_REPORT,
258+
size=dimens.SM_ICON_SIZE,
259+
color=colors.text_secondary,
260+
),
261+
Text(
262+
"Report a bug",
263+
size=fonts.BODY_2_SIZE,
264+
color=colors.text_primary,
265+
),
266+
],
267+
spacing=dimens.SPACE_XS,
268+
vertical_alignment=CrossAxisAlignment.CENTER,
269+
),
270+
on_click=on_click_help_bug,
271+
),
272+
],
273+
)
274+
189275
# Initialized to None; set by build()
190276
self.bar: Optional[Container] = None
191277

278+
def _on_help_hover(self, e):
279+
"""Hover effect for the help menu button."""
280+
e.control.bgcolor = "#20FFFFFF" if e.data == "true" else None
281+
e.control.update()
282+
192283
def build(self) -> Container:
193284
"""Build the status bar container."""
194285
self.bar = Container(
195286
height=dimens.FOOTER_HEIGHT,
196287
bgcolor=colors.bg_statusbar,
288+
border=Border(top=BorderSide(1, colors.border)),
197289
padding=Padding.symmetric(horizontal=dimens.SPACE_XS),
198290
content=Row(
199291
controls=[
200-
# Left zone
292+
# Left zone — chrome icons
201293
Row(
202294
controls=[
203-
self.entity_count_item,
295+
self.settings_item,
296+
self.profile_item,
297+
self.help_menu,
204298
],
205299
spacing=0,
206300
vertical_alignment=CrossAxisAlignment.CENTER,
@@ -237,12 +331,10 @@ def build(self) -> Container:
237331

238332
def update_for_view(
239333
self,
240-
entity_count_text: str,
334+
entity_count_text: str = "",
241335
summary_text: str = "",
242336
):
243-
"""Update status bar text for the currently active view."""
244-
self.entity_count_item.set_text(entity_count_text)
245-
337+
"""Update status bar for the currently active view."""
246338
if summary_text:
247339
self.entity_summary_item.set_text(summary_text)
248340
self.entity_summary_item.visible = True

tuttle/app/core/views.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,8 +1450,7 @@ def __init__(self, params: TViewParams):
14501450
controls=[
14511451
THeading(
14521452
f"My {self.entity_name_plural.title()}",
1453-
size=fonts.HEADLINE_2_SIZE,
1454-
color=colors.text_secondary,
1453+
size=fonts.HEADLINE_3_SIZE,
14551454
),
14561455
]
14571456
+ ([sort_control] if sort_control else []),

tuttle/app/dashboard/view.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -223,12 +223,7 @@ def build(self):
223223
content=Column(
224224
spacing=dimens.SPACE_XS,
225225
controls=[
226-
Text(
227-
"Dashboard",
228-
size=fonts.HEADLING_1_SIZE,
229-
color=colors.text_primary,
230-
weight=fonts.BOLDER_FONT,
231-
),
226+
views.THeading("Dashboard", size=fonts.HEADLINE_3_SIZE),
232227
Row(
233228
alignment=MainAxisAlignment.CENTER,
234229
controls=[self._spinner],

tuttle/app/home/view.py

Lines changed: 43 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,16 @@
44
from dataclasses import dataclass
55

66
from flet import (
7-
Alignment,
87
Border,
98
BorderSide,
109
Column,
1110
Container,
1211
ElevatedButton,
1312
Icon,
14-
IconButton,
1513
Icons,
1614
Margin,
1715
NavigationRailDestination,
1816
Padding,
19-
PopupMenuButton,
20-
PopupMenuItem,
2117
ResponsiveRow,
2218
Row,
2319
Text,
@@ -45,87 +41,6 @@
4541
from ..preferences.intent import PreferencesIntent
4642

4743

48-
def get_toolbar(
49-
on_click_new_btn: Callable,
50-
on_click_profile_btn: Callable,
51-
on_view_settings_clicked: Callable,
52-
):
53-
"""Slim toolbar — actions only, no redundant title."""
54-
new_btn = TextButton(
55-
content=Row(
56-
controls=[
57-
Icon(
58-
Icons.ADD,
59-
size=dimens.SM_ICON_SIZE,
60-
color=colors.accent,
61-
),
62-
Text(
63-
"New",
64-
size=fonts.BODY_1_SIZE,
65-
color=colors.accent,
66-
weight=fonts.BOLD_FONT,
67-
),
68-
],
69-
spacing=dimens.SPACE_XXS,
70-
),
71-
on_click=on_click_new_btn,
72-
)
73-
toolbar = Container(
74-
alignment=Alignment.CENTER,
75-
height=dimens.TOOLBAR_HEIGHT,
76-
bgcolor=colors.bg,
77-
padding=Padding.symmetric(horizontal=dimens.SPACE_LG),
78-
content=Row(
79-
alignment=MainAxisAlignment.END,
80-
vertical_alignment=CrossAxisAlignment.CENTER,
81-
controls=[
82-
Row(
83-
spacing=dimens.SPACE_XXS,
84-
controls=[
85-
new_btn,
86-
IconButton(
87-
icon=Icons.SETTINGS_OUTLINED,
88-
icon_size=dimens.ICON_SIZE,
89-
icon_color=colors.text_muted,
90-
on_click=on_view_settings_clicked,
91-
tooltip="Preferences",
92-
),
93-
IconButton(
94-
icon=Icons.PERSON_OUTLINE_OUTLINED,
95-
icon_size=dimens.ICON_SIZE,
96-
icon_color=colors.text_muted,
97-
tooltip="Profile",
98-
on_click=on_click_profile_btn,
99-
),
100-
PopupMenuButton(
101-
icon=Icons.HELP_OUTLINE,
102-
icon_size=dimens.ICON_SIZE,
103-
icon_color=colors.text_secondary,
104-
items=[
105-
PopupMenuItem(
106-
icon=Icons.CONTACT_SUPPORT,
107-
content="Ask a question",
108-
on_click=lambda _: webbrowser.open(
109-
"https://github.com/tuttle-dev/tuttle/discussions"
110-
),
111-
),
112-
PopupMenuItem(
113-
icon=Icons.BUG_REPORT,
114-
content="Report a bug",
115-
on_click=lambda _: webbrowser.open(
116-
"https://github.com/tuttle-dev/tuttle/issues"
117-
),
118-
),
119-
],
120-
),
121-
],
122-
),
123-
],
124-
),
125-
)
126-
return toolbar, new_btn
127-
128-
12944
class MainMenuItemsHandler:
13045
"""Manages home's main-menu items."""
13146

@@ -272,21 +187,39 @@ def __init__(self, params: TViewParams):
272187
self.status_bar_manager = StatusBarManager(
273188
on_click_overdue=lambda e: self._jump_to_invoicing(),
274189
on_click_outstanding=lambda e: self._jump_to_invoicing(),
190+
on_click_settings=lambda e: self.on_view_settings_clicked(e),
191+
on_click_profile=lambda e: self.on_click_profile(e),
192+
on_click_help_ask=lambda _: webbrowser.open(
193+
"https://github.com/tuttle-dev/tuttle/discussions"
194+
),
195+
on_click_help_bug=lambda _: webbrowser.open(
196+
"https://github.com/tuttle-dev/tuttle/issues"
197+
),
275198
)
276199

277-
# Toolbar (slim, no title — view heading is the title)
278-
self.toolbar, self._new_btn = get_toolbar(
279-
on_click_new_btn=self.on_click_add_new,
280-
on_click_profile_btn=self.on_click_profile,
281-
on_view_settings_clicked=self.on_view_settings_clicked,
200+
# "+ New" button (shown only for views that support creating entities)
201+
self._new_btn = TextButton(
202+
content=Row(
203+
controls=[
204+
Icon(Icons.ADD, size=dimens.SM_ICON_SIZE, color=colors.accent),
205+
Text(
206+
"New",
207+
size=fonts.BODY_1_SIZE,
208+
color=colors.accent,
209+
weight=fonts.BOLD_FONT,
210+
),
211+
],
212+
spacing=dimens.SPACE_XXS,
213+
),
214+
on_click=self.on_click_add_new,
282215
)
283216
self._update_new_btn_visibility()
284217

285218
def _on_sidebar_item_selected(self, item: views.NavigationMenuItem):
286219
"""Called when the user clicks a sidebar nav item."""
287220
self._selected_flat_index = self._all_items.index(item)
288221
self.destination_view = item.destination
289-
self.destination_content_container.content = self.destination_view
222+
self._destination_wrapper.content = self.destination_view
290223
self._update_new_btn_visibility()
291224
self._update_status_bar_for_view(item.label)
292225
self.update_self()
@@ -323,26 +256,33 @@ def on_click_profile(self, e):
323256

324257
# ── Build ─────────────────────────────────────────────────
325258
def build(self):
326-
self.destination_content_container = Container(
327-
padding=Padding.all(dimens.SPACE_MD),
259+
self._destination_wrapper = Container(
328260
content=self.destination_view,
329261
expand=True,
330262
)
331-
332-
# Status bar — VS Code style thin bar at bottom
333-
self.status_bar = Container(
334-
height=dimens.FOOTER_HEIGHT,
335-
bgcolor=colors.bg_statusbar,
336-
padding=Padding.symmetric(horizontal=dimens.SPACE_SM),
337-
content=Row(
263+
self.destination_content_container = Container(
264+
padding=Padding.only(
265+
left=dimens.SPACE_MD,
266+
right=dimens.SPACE_MD,
267+
bottom=dimens.SPACE_MD,
268+
top=dimens.SPACE_XS,
269+
),
270+
content=Column(
338271
controls=[
339-
Text("Tuttle", size=11, color=colors.text_inverse),
272+
Row(
273+
controls=[self._new_btn],
274+
alignment=MainAxisAlignment.END,
275+
),
276+
self._destination_wrapper,
340277
],
341-
alignment=MainAxisAlignment.START,
342-
vertical_alignment=CrossAxisAlignment.CENTER,
278+
spacing=0,
343279
),
280+
expand=True,
344281
)
345282

283+
# Status bar — interactive bar built by StatusBarManager
284+
self.status_bar = self.status_bar_manager.build()
285+
346286
# Sidebar
347287
self.side_bar = Container(
348288
width=dimens.SIDEBAR_WIDTH,
@@ -363,7 +303,6 @@ def build(self):
363303
horizontal_alignment=CrossAxisAlignment.START,
364304
spacing=0,
365305
controls=[
366-
self.toolbar,
367306
self.destination_content_container,
368307
],
369308
)

tuttle/app/invoicing/view.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -950,7 +950,7 @@ def build(self):
950950

951951
self.title_control = Row(
952952
controls=[
953-
views.THeading(title="Invoicing", size=fonts.HEADLINE_2_SIZE),
953+
views.THeading(title="Invoicing", size=fonts.HEADLINE_3_SIZE),
954954
],
955955
)
956956

0 commit comments

Comments
 (0)