Skip to content

Commit 64ddd06

Browse files
committed
Merge branch 'dev' into dev-feat-timesheets
2 parents 19191a8 + 746d4fe commit 64ddd06

File tree

11 files changed

+141
-26
lines changed

11 files changed

+141
-26
lines changed

app/app.py

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,21 @@
5252
class TuttleApp:
5353
"""The main application class"""
5454

55-
def __init__(self, page: Page) -> None:
55+
def __init__(
56+
self,
57+
page: Page,
58+
debug_mode: bool = False,
59+
):
60+
""" """
61+
self.debug_mode = debug_mode
5662
self.page = page
5763
self.page.title = "Tuttle"
5864
self.page.fonts = APP_FONTS
5965
self.page.theme = APP_THEME
6066
self.client_storage = ClientStorageImpl(page=self.page)
61-
preferences = PreferencesIntent(self.client_storage)
67+
preferences = PreferencesIntent(
68+
client_storage=self.client_storage,
69+
)
6270
preferences_result = preferences.get_preference_by_key(
6371
PreferencesStorageKeys.theme_mode_key
6472
)
@@ -241,21 +249,21 @@ def on_route_change(self, route):
241249

242250
def create_model(self):
243251
logger.info("Creating database model")
244-
sqlmodel.SQLModel.metadata.create_all(self.db_engine, checkfirst=True)
252+
sqlmodel.SQLModel.metadata.create_all(
253+
self.db_engine,
254+
checkfirst=True,
255+
)
245256

246257
def ensure_database(self):
247258
"""
248259
Ensure that the database exists and is up to date.
249260
"""
250261
if not self.db_path.exists():
251-
self.db_engine = sqlmodel.create_engine(
252-
f"sqlite:///{self.db_path}", echo=True
253-
)
254-
self.create_model()
262+
self.reset_database()
255263
else:
256264
logger.info("Database exists, skipping creation")
257265

258-
def clear_database(self):
266+
def reset_database(self):
259267
"""
260268
Delete the database and rebuild database model.
261269
"""
@@ -264,7 +272,10 @@ def clear_database(self):
264272
self.db_path.unlink()
265273
except FileNotFoundError:
266274
logger.info("Database file not found, skipping delete")
267-
self.db_engine = sqlmodel.create_engine(f"sqlite:///{self.db_path}", echo=True)
275+
self.db_engine = sqlmodel.create_engine(
276+
f"sqlite:///{self.db_path}",
277+
echo=self.debug_mode,
278+
)
268279
self.create_model()
269280

270281
def store_demo_timetracking_dataframe(self, time_tracking_data: DataFrame):
@@ -276,7 +287,7 @@ def store_demo_timetracking_dataframe(self, time_tracking_data: DataFrame):
276287

277288
def install_demo_data(self):
278289
"""Install demo data into the database."""
279-
self.clear_database()
290+
self.reset_database()
280291
try:
281292
demo.install_demo_data(
282293
n_projects=4,
@@ -300,9 +311,18 @@ def ensure_uploads_dir(self) -> Path:
300311
uploads_dir.mkdir(parents=True)
301312
return uploads_dir
302313

314+
def close(self):
315+
"""Closes the application."""
316+
self.page.window_close()
317+
303318
def build(self):
304319
self.page.go(self.page.route)
305320

321+
def reset_and_quit(self):
322+
"""Resets the application and quits."""
323+
self.reset_database()
324+
self.close()
325+
306326

307327
class TuttleRoutes:
308328
"""Utility class for parsing of routes to destination views"""
@@ -373,7 +393,9 @@ def parse_route(self, pageRoute: str):
373393
)
374394
elif routePath.match(PREFERENCES_SCREEN_ROUTE):
375395
screen = PreferencesScreen(
376-
params=self.tuttle_view_params, on_theme_changed=self.on_theme_changed
396+
params=self.tuttle_view_params,
397+
on_theme_changed_callback=self.on_theme_changed,
398+
on_reset_app_callback=self.app.reset_and_quit,
377399
)
378400
elif routePath.match(PROJECT_EDITOR_SCREEN_ROUTE):
379401
screen = ProjectEditorScreen(params=self.tuttle_view_params)

app/auth/intent.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
from typing import Type, Union, Optional
22

33
from core.intent_result import IntentResult
4+
from core.abstractions import Intent
45

56
from tuttle.model import User, Address
67

78
from .data_source import UserDataSource
89

910

10-
class AuthIntent:
11-
"""Handles User C_R_U_D intents"""
11+
class AuthIntent(Intent):
12+
"""Handles User intents"""
1213

1314
def __init__(self):
1415
self._data_source = UserDataSource()

app/clients/intent.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22

33
from contacts.intent import ContactsIntent
44
from core.intent_result import IntentResult
5+
from core.abstractions import Intent
56

67
from tuttle.model import Client, Contact
78

89
from .data_source import ClientDataSource
910

1011

11-
class ClientsIntent:
12+
class ClientsIntent(Intent):
1213
"""Provides methods to retrieve, store and delete clients from datasources"""
1314

1415
def __init__(self):

app/contacts/intent.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Mapping, Union
22

33
from core.intent_result import IntentResult
4+
from core.abstractions import Intent
45

56
from tuttle.model import Contact
67

app/contracts/intent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from clients.intent import ClientsIntent
66
from contacts.intent import ContactsIntent
7-
from core.abstractions import ClientStorage
7+
from core.abstractions import ClientStorage, Intent
88
from core.intent_result import IntentResult
99
from preferences.intent import PreferencesIntent
1010
from preferences.model import PreferencesStorageKeys

app/core/views.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,24 @@ def get_secondary_btn(
229229
)
230230

231231

232+
def get_danger_button(
233+
on_click,
234+
label: str,
235+
width: int = 200,
236+
icon: Optional[str] = None,
237+
tooltip: Optional[str] = None,
238+
):
239+
"""An elevated button with danger styling"""
240+
return ElevatedButton(
241+
label,
242+
width=width,
243+
on_click=on_click,
244+
icon=icon,
245+
color=colors.DANGER_COLOR,
246+
tooltip=tooltip,
247+
)
248+
249+
232250
def get_profile_photo_img(pic_src: str = image_paths.default_avatar):
233251

234252
return Image(

app/invoicing/intent.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from pathlib import Path
77

88
from auth.data_source import UserDataSource
9-
from core.abstractions import ClientStorage
9+
from core.abstractions import ClientStorage, Intent
1010
from core.intent_result import IntentResult
1111
from loguru import logger
1212
from pandas import DataFrame
@@ -22,7 +22,7 @@
2222
from auth.intent import AuthIntent
2323

2424

25-
class InvoicingIntent:
25+
class InvoicingIntent(Intent):
2626
"""Handles Invoicing C_R_U_D intents"""
2727

2828
def __init__(self, client_storage: ClientStorage):

app/preferences/intent.py

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
from core.abstractions import ClientStorage
1+
from loguru import logger
2+
3+
from flet import Page
4+
5+
from core.abstractions import ClientStorage, Intent
26
from core.intent_result import IntentResult
37

48
from .model import Preferences, PreferencesStorageKeys
59
from typing import Optional
610

711

8-
class PreferencesIntent:
9-
"""Handles Preferences C_R_U_D intents
12+
class PreferencesIntent(Intent):
13+
"""Handles Preferences intents
1014
1115
Intents handled (Methods)
1216
---------------
@@ -24,8 +28,10 @@ class PreferencesIntent:
2428
storing a preference item given it's key and value
2529
"""
2630

27-
def __init__(self, client_storage: ClientStorage):
28-
31+
def __init__(
32+
self,
33+
client_storage: ClientStorage,
34+
):
2935
self._client_storage = client_storage
3036

3137
def get_preferences(self) -> IntentResult:
@@ -130,3 +136,40 @@ def get_preferred_theme(self) -> IntentResult[Optional[str]]:
130136
result.error_msg = "Failed to load your preferred theme"
131137
result.log_message_if_any()
132138
return result
139+
140+
def clear_preferences(self) -> IntentResult[None]:
141+
"""Clears all preferences"""
142+
try:
143+
self._client_storage.clear_preferences()
144+
return IntentResult(was_intent_successful=True)
145+
except Exception as ex:
146+
logger.error(f"Failed to clear preferences: {ex.__class__.__name__}")
147+
logger.exception(ex)
148+
result = IntentResult(
149+
was_intent_successful=False,
150+
exception=ex,
151+
error_msg=f"Failed to clear preferences: {ex.__class__.__name__}",
152+
)
153+
result.log_message_if_any()
154+
return result
155+
156+
def reset_app(self) -> IntentResult:
157+
"""Resets the app to it's default state"""
158+
try:
159+
logger.info("Resetting the app to default state")
160+
logger.info("Clearing all preferences")
161+
self._client_storage.clear_preferences()
162+
logger.info("Clearing all data")
163+
self._page.window_close()
164+
165+
return IntentResult(
166+
was_intent_successful=True,
167+
)
168+
except Exception as ex:
169+
result = IntentResult(
170+
was_intent_successful=False,
171+
exception=ex,
172+
error_msg="Failed to reset app",
173+
)
174+
result.log_message_if_any()
175+
return result

app/preferences/view.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
from typing import Optional
1+
from typing import Optional, Callable
2+
3+
import sys
4+
5+
from loguru import logger
26

37
from flet import (
48
Column,
@@ -23,6 +27,7 @@
2327
get_dropdown,
2428
get_std_txt_field,
2529
horizontal_progress,
30+
lgSpace,
2631
mdSpace,
2732
smSpace,
2833
update_dropdown_items,
@@ -51,11 +56,13 @@ class PreferencesScreen(TuttleView, UserControl):
5156
def __init__(
5257
self,
5358
params: TuttleViewParams,
54-
on_theme_changed,
59+
on_theme_changed_callback: Callable,
60+
on_reset_app_callback: Callable,
5561
):
5662
super().__init__(params=params)
5763
self.intent = PreferencesIntent(client_storage=params.client_storage)
58-
self.on_theme_changed_callback = on_theme_changed
64+
self.on_theme_changed_callback = on_theme_changed_callback
65+
self.on_reset_app_callback = on_reset_app_callback
5966
self.preferences: Optional[Preferences] = None
6067
self.currencies = []
6168

@@ -111,6 +118,15 @@ def on_language_selected(self, e):
111118
return
112119
self.preferences.language = e.control.value
113120

121+
def on_reset_app(self, e):
122+
logger.warning("Resetting the app to default state")
123+
logger.warning("Clearning preferences")
124+
result: IntentResult[None] = self.intent.clear_preferences()
125+
assert result.was_intent_successful
126+
logger.warning("Clearning database")
127+
logger.warning("Quitting app after reset. Please restart.")
128+
self.on_reset_app_callback()
129+
114130
def get_tab_item(self, label, icon, content_controls):
115131
return Tab(
116132
tab_content=Column(
@@ -179,6 +195,15 @@ def build(self):
179195
"English",
180196
],
181197
)
198+
199+
# a reset button for the app with a warning sign, warning color and a confirmation dialog
200+
self.reset_button = views.get_danger_button(
201+
label="Reset App and Quit",
202+
icon=icons.RESTART_ALT_OUTLINED,
203+
on_click=self.on_reset_app,
204+
tooltip="Warning: This will reset the app to default state and delete all data. You will have to restart the app.",
205+
)
206+
182207
self.tabs = Tabs(
183208
selected_index=0,
184209
animation_duration=300,
@@ -190,6 +215,8 @@ def build(self):
190215
icons.SETTINGS_OUTLINED,
191216
[
192217
self.theme_control,
218+
lgSpace,
219+
self.reset_button,
193220
],
194221
),
195222
self.get_tab_item(

app/projects/intent.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
from clients.intent import ClientsIntent
66
from contracts.intent import ContractsIntent
77
from core.intent_result import IntentResult
8+
from core.abstractions import Intent
89

910
from tuttle.model import Client, Contract, Project
1011

1112
from .data_source import ProjectDataSource
1213

1314

14-
class ProjectsIntent:
15+
class ProjectsIntent(Intent):
1516
"""Handles intents related to the projects data Ui"""
1617

1718
def __init__(

0 commit comments

Comments
 (0)