import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/database/tables/local_user_account.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart'; import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart'; import 'package:paperless_mobile/features/landing/view/widgets/expansion_card.dart'; import 'package:paperless_mobile/features/landing/view/widgets/mime_types_pie_chart.dart'; import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart'; import 'package:paperless_mobile/features/saved_view_details/view/saved_view_preview.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/routes/typed/branches/documents_route.dart'; import 'package:paperless_mobile/routes/typed/branches/inbox_route.dart'; import 'package:paperless_mobile/routes/typed/shells/authenticated_route.dart'; class LandingPage extends StatefulWidget { const LandingPage({super.key}); @override State createState() => _LandingPageState(); } class _LandingPageState extends State { final _searchBarHandle = SliverOverlapAbsorberHandle(); @override Widget build(BuildContext context) { final currentUser = context.watch().paperlessUser; return SafeArea( child: Scaffold( drawer: const AppDrawer(), body: NestedScrollView( headerSliverBuilder: (context, innerBoxIsScrolled) => [ SliverOverlapAbsorber( handle: _searchBarHandle, sliver: SliverSearchBar( titleText: S.of(context)!.documents, ), ), ], body: CustomScrollView( slivers: [ SliverToBoxAdapter( child: Text( S.of(context)!.welcomeUser( currentUser.fullName ?? currentUser.username, ), textAlign: TextAlign.center, style: Theme.of(context) .textTheme .displaySmall ?.copyWith(fontSize: 28), ).padded(24), ), SliverToBoxAdapter(child: _buildStatisticsCard(context)), if (currentUser.canViewSavedViews) ...[ SliverPadding( padding: const EdgeInsets.fromLTRB(16, 16, 0, 8), sliver: SliverToBoxAdapter( child: Row( children: [ Icon( Icons.saved_search, color: Theme.of(context).colorScheme.primary, ).paddedOnly(right: 8), Text( S.of(context)!.views, style: Theme.of(context).textTheme.titleMedium, ), ], ), ), ), BlocBuilder( builder: (context, state) { return state.maybeWhen( loaded: (savedViews) { final dashboardViews = savedViews.values .where((element) => element.showOnDashboard) .toList(); if (dashboardViews.isEmpty) { return SliverToBoxAdapter( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(S.of(context)!.noSavedViewOnHomepageHint) .padded(), TextButton.icon( onPressed: () {}, icon: const Icon(Icons.add), label: Text(S.of(context)!.newView), ) ], ).paddedOnly(left: 16), ); } return SliverList.builder( itemBuilder: (context, index) { return SavedViewPreview( savedView: dashboardViews.elementAt(index), expanded: index == 0, ); }, itemCount: dashboardViews.length, ); }, orElse: () => const SliverToBoxAdapter( child: Center( child: CircularProgressIndicator(), ), ), ); }, ), ], ], ), ), ), ); } Widget _buildStatisticsCard(BuildContext context) { final currentUser = context.read().paperlessUser; return ExpansionCard( initiallyExpanded: false, title: Text( S.of(context)!.statistics, style: Theme.of(context).textTheme.titleLarge, ), content: FutureBuilder( future: context.read().getServerStatistics(), builder: (context, snapshot) { if (!snapshot.hasData) { return const Center( child: CircularProgressIndicator(), ).paddedOnly(top: 8, bottom: 24); } final stats = snapshot.data!; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Card( color: Theme.of(context).colorScheme.surfaceVariant, child: ListTile( shape: Theme.of(context).cardTheme.shape, titleTextStyle: Theme.of(context).textTheme.labelLarge, title: Text(S.of(context)!.documentsInInbox), onTap: currentUser.canViewInbox ? () => InboxRoute().go(context) : null, trailing: Text( stats.documentsInInbox.toString(), style: Theme.of(context).textTheme.labelLarge, ), ), ), Card( color: Theme.of(context).colorScheme.surfaceVariant, child: ListTile( shape: Theme.of(context).cardTheme.shape, titleTextStyle: Theme.of(context).textTheme.labelLarge, title: Text(S.of(context)!.totalDocuments), onTap: currentUser.canViewDocuments ? () { DocumentsRoute().go(context); } : null, trailing: Text( stats.documentsTotal.toString(), style: Theme.of(context).textTheme.labelLarge, ), ), ), Card( color: Theme.of(context).colorScheme.surfaceVariant, child: ListTile( shape: Theme.of(context).cardTheme.shape, titleTextStyle: Theme.of(context).textTheme.labelLarge, title: Text(S.of(context)!.totalCharacters), trailing: Text( stats.totalChars.toString(), style: Theme.of(context).textTheme.labelLarge, ), ), ), AspectRatio( aspectRatio: 1.3, child: SizedBox( width: 300, child: MimeTypesPieChart(statistics: stats), ), ), ], ).padded(16); }, ), ); } }