import 'package:flutter/material.dart'; import 'package:flutter_fullscreen/flutter_fullscreen.dart'; import 'package:logging/logging.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:sheetless/core/models/config.dart'; import 'package:sheetless/core/models/sheet.dart'; import 'package:sheetless/core/services/api_client.dart'; import 'package:sheetless/core/services/storage_service.dart'; import '../../app.dart'; import '../auth/login_page.dart'; import '../sheet_viewer/sheet_viewer_page.dart'; import 'widgets/app_drawer.dart'; import 'widgets/sheets_list.dart'; /// Main home page displaying the list of sheet music. /// /// Features: /// - Pull-to-refresh sheet list /// - Shuffle mode for random practice /// - Navigation to sheet viewer /// - Logout functionality class HomePage extends StatefulWidget { final Config config; const HomePage({super.key, required this.config}); @override State createState() => _HomePageState(); } class _HomePageState extends State with RouteAware { final _log = Logger('HomePage'); final _storageService = StorageService(); ApiClient? _apiClient; late Future> _sheetsFuture; bool _isShuffling = false; String? _appName; String? _appVersion; @override void initState() { super.initState(); // Exit fullscreen when entering home page FullScreen.setFullScreen(false); // Subscribe to route changes WidgetsBinding.instance.addPostFrameCallback((_) { routeObserver.subscribe(this, ModalRoute.of(context)!); }); _loadAppInfo(); _sheetsFuture = _loadSheets(); } @override void dispose() { routeObserver.unsubscribe(this); super.dispose(); } // --------------------------------------------------------------------------- // Route Aware (Fullscreen Management) // --------------------------------------------------------------------------- @override void didPush() { FullScreen.setFullScreen(false); super.didPush(); } @override void didPopNext() { // Exit fullscreen when returning to home page FullScreen.setFullScreen(false); super.didPopNext(); } // --------------------------------------------------------------------------- // Data Loading // --------------------------------------------------------------------------- Future _loadAppInfo() async { final info = await PackageInfo.fromPlatform(); setState(() { _appName = info.appName; _appVersion = info.version; }); } Future> _loadSheets() async { final url = await _storageService.readSecure(SecureStorageKey.url); final jwt = await _storageService.readSecure(SecureStorageKey.jwt); _apiClient = ApiClient(baseUrl: url!, token: jwt); final sheets = await _apiClient!.fetchSheets(); _log.info('${sheets.length} sheets fetched'); final sortedSheets = await _sortSheetsByRecency(sheets); _log.info('${sortedSheets.length} sheets sorted'); return sortedSheets; } Future> _sortSheetsByRecency(List sheets) async { final accessTimes = await _storageService.readSheetAccessTimes(); sheets.sort((a, b) { // Use local access time if available and more recent than server update var dateA = accessTimes[a.uuid]; var dateB = accessTimes[b.uuid]; if (dateA == null || a.updatedAt.isAfter(dateA)) { dateA = a.updatedAt; } if (dateB == null || b.updatedAt.isAfter(dateB)) { dateB = b.updatedAt; } return dateB.compareTo(dateA); // Most recent first }); return sheets; } Future _refreshSheets() async { setState(() { _sheetsFuture = _loadSheets(); }); } // --------------------------------------------------------------------------- // Actions // --------------------------------------------------------------------------- void _handleShuffleChanged(bool enabled) async { final sheets = await _sheetsFuture; if (enabled) { sheets.shuffle(); } else { await _sortSheetsByRecency(sheets); } setState(() => _isShuffling = enabled); } Future _handleLogout() async { await _storageService.clearToken(); if (!mounted) return; Navigator.of( context, ).pushReplacement(MaterialPageRoute(builder: (_) => const LoginPage())); } void _openSheet(Sheet sheet) { // Record access time for recency sorting _storageService.writeSheetAccessTime(sheet.uuid, DateTime.now()); Navigator.push( context, MaterialPageRoute( builder: (_) => SheetViewerPage( sheet: sheet, apiClient: _apiClient!, config: widget.config, ), ), ); } // --------------------------------------------------------------------------- // UI // --------------------------------------------------------------------------- @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Sheetless')), endDrawer: AppDrawer( isShuffling: _isShuffling, onShuffleChanged: _handleShuffleChanged, onLogout: _handleLogout, appName: _appName, appVersion: _appVersion, ), body: RefreshIndicator(onRefresh: _refreshSheets, child: _buildBody()), ); } Widget _buildBody() { return FutureBuilder>( future: _sheetsFuture, builder: (context, snapshot) { if (snapshot.connectionState != ConnectionState.done) { return const Center(child: CircularProgressIndicator()); } if (snapshot.hasError) { _log.warning('Error loading sheets', snapshot.error); return _buildError(snapshot.error.toString()); } if (snapshot.hasData) { return SheetsList( sheets: snapshot.data!, onSheetSelected: _openSheet, ); } return const Center(child: CircularProgressIndicator()); }, ); } Widget _buildError(String message) { return Center( child: Padding( padding: const EdgeInsets.all(16.0), child: Text( message, style: Theme.of( context, ).textTheme.titleMedium?.copyWith(color: Colors.red), textAlign: TextAlign.center, ), ), ); } }