feat(console): add app controller, main menu and statistics screen
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
195
console/core/app.py
Normal file
195
console/core/app.py
Normal file
@@ -0,0 +1,195 @@
|
||||
"""
|
||||
Main application controller for the AUTOPARTES console application.
|
||||
|
||||
The :class:`App` class owns the screen lifecycle loop: it renders the
|
||||
current screen, reads a keypress, dispatches it, and follows any
|
||||
navigation instruction the screen returns.
|
||||
"""
|
||||
|
||||
from console.core.navigation import Navigation
|
||||
from console.core.keybindings import Key
|
||||
|
||||
|
||||
class App:
|
||||
"""Top-level application controller.
|
||||
|
||||
Parameters:
|
||||
renderer: A :class:`BaseRenderer` implementation (e.g. CursesRenderer).
|
||||
db: A :class:`Database` instance for data access.
|
||||
"""
|
||||
|
||||
def __init__(self, renderer, db):
|
||||
self.renderer = renderer
|
||||
self.db = db
|
||||
self.nav = Navigation()
|
||||
self.screens = {}
|
||||
self.running = False
|
||||
self._register_screens()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Screen registration
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _register_screens(self):
|
||||
"""Import and register all screen instances.
|
||||
|
||||
Each screen is wrapped in a try/except so that screens not yet
|
||||
implemented do not prevent the application from starting.
|
||||
"""
|
||||
# --- Required screens (Task 6) --------------------------------
|
||||
try:
|
||||
from console.screens.menu_principal import MenuPrincipalScreen
|
||||
s = MenuPrincipalScreen()
|
||||
self.screens[s.name] = s
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from console.screens.estadisticas import EstadisticasScreen
|
||||
s = EstadisticasScreen()
|
||||
self.screens[s.name] = s
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# --- Optional screens (added by later tasks) -------------------
|
||||
try:
|
||||
from console.screens.vehiculo_nav import VehiculoNavScreen
|
||||
s = VehiculoNavScreen()
|
||||
self.screens[s.name] = s
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from console.screens.buscar_parte import BuscarParteScreen
|
||||
s = BuscarParteScreen()
|
||||
self.screens[s.name] = s
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from console.screens.buscar_texto import BuscarTextoScreen
|
||||
s = BuscarTextoScreen()
|
||||
self.screens[s.name] = s
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from console.screens.vin_decoder import VinDecoderScreen
|
||||
s = VinDecoderScreen()
|
||||
self.screens[s.name] = s
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from console.screens.catalogo import CatalogoScreen
|
||||
s = CatalogoScreen()
|
||||
self.screens[s.name] = s
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from console.screens.parte_detalle import ParteDetalleScreen
|
||||
s = ParteDetalleScreen()
|
||||
self.screens[s.name] = s
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from console.screens.comparador import ComparadorScreen
|
||||
s = ComparadorScreen()
|
||||
self.screens[s.name] = s
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from console.screens.admin_partes import AdminPartesScreen
|
||||
s = AdminPartesScreen()
|
||||
self.screens[s.name] = s
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from console.screens.admin_fabricantes import AdminFabricantesScreen
|
||||
s = AdminFabricantesScreen()
|
||||
self.screens[s.name] = s
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from console.screens.admin_crossref import AdminCrossrefScreen
|
||||
s = AdminCrossrefScreen()
|
||||
self.screens[s.name] = s
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from console.screens.admin_import import AdminImportScreen
|
||||
s = AdminImportScreen()
|
||||
self.screens[s.name] = s
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Main loop
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def run(self):
|
||||
"""Enter the main event loop.
|
||||
|
||||
Initialises the renderer, pushes the main menu onto the
|
||||
navigation stack, and loops until the user quits or the stack
|
||||
empties.
|
||||
"""
|
||||
self.renderer.init_screen()
|
||||
self.running = True
|
||||
self.nav.push('menu', {}, label='Menu')
|
||||
|
||||
try:
|
||||
while self.running:
|
||||
current = self.nav.current()
|
||||
if current is None:
|
||||
break
|
||||
|
||||
screen_name, context = current
|
||||
screen = self.screens.get(screen_name)
|
||||
|
||||
if screen is None:
|
||||
self.renderer.show_message(
|
||||
f'Pantalla "{screen_name}" no disponible', 'error'
|
||||
)
|
||||
self.nav.pop()
|
||||
continue
|
||||
|
||||
# Render
|
||||
self.renderer.clear()
|
||||
screen.render(context, self.db, self.renderer)
|
||||
self.renderer.refresh()
|
||||
|
||||
# Input
|
||||
key = self.renderer.get_key()
|
||||
|
||||
# Global key: F10 = back to main menu
|
||||
if key == Key.F10:
|
||||
self.nav.clear()
|
||||
self.nav.push('menu', {}, label='Menu')
|
||||
continue
|
||||
|
||||
# Screen-specific key handling
|
||||
result = screen.on_key(
|
||||
key, context, self.db, self.renderer, self.nav
|
||||
)
|
||||
|
||||
if result == 'quit':
|
||||
self.running = False
|
||||
elif result == 'back':
|
||||
self.nav.pop()
|
||||
elif isinstance(result, tuple) and len(result) == 3:
|
||||
name, ctx, label = result
|
||||
self.nav.push(name, ctx, label=label)
|
||||
elif isinstance(result, str):
|
||||
self.nav.push(result, {}, label=result)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
self.renderer.cleanup()
|
||||
Reference in New Issue
Block a user