import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; import 'package:fbla_ui/api_logic.dart'; import 'package:fbla_ui/main.dart'; import 'package:fbla_ui/pages/create_edit_business.dart'; import 'package:fbla_ui/pages/export_data.dart'; import 'package:fbla_ui/pages/signin_page.dart'; import 'package:fbla_ui/shared.dart'; import 'package:flutter/material.dart'; import 'package:rive/rive.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:url_launcher/url_launcher.dart'; typedef Callback = void Function(); class Home extends StatefulWidget { final Callback themeCallback; const Home({super.key, required this.themeCallback}); @override State createState() => _HomeState(); } class _HomeState extends State { late Future refreshBusinessDataOverviewFuture; bool _isPreviousData = false; late Map> overviewBusinesses; @override void initState() { super.initState(); refreshBusinessDataOverviewFuture = fetchBusinessDataOverview(); initialLogin(); } Future initialLogin() async { final prefs = await SharedPreferences.getInstance(); bool? rememberMe = prefs.getBool('rememberMe'); if (rememberMe != null && rememberMe) { String? username = prefs.getString('username'); String? password = prefs.getString('password'); jwt = await signIn(username!, password!); if (!jwt.contains('Error:')) { setState(() { loggedIn = true; }); } } } void setStateCallback() { setState(() { loggedIn = loggedIn; }); } @override Widget build(BuildContext context) { bool widescreen = MediaQuery.sizeOf(context).width >= 1000; return Scaffold( floatingActionButton: _getFAB(), body: RefreshIndicator( edgeOffset: 120, onRefresh: () async { var refreshedData = fetchBusinessDataOverview(); await refreshedData; setState(() { refreshBusinessDataOverviewFuture = refreshedData; }); }, child: CustomScrollView( slivers: [ SliverAppBar( title: widescreen ? _searchBar() : const Text('Job Link'), toolbarHeight: 70, pinned: true, scrolledUnderElevation: 0, centerTitle: true, expandedHeight: widescreen ? 70 : 120, backgroundColor: Theme.of(context).colorScheme.surface, bottom: _getBottom(), leading: IconButton( icon: getIconFromThemeMode(themeMode), onPressed: () { setState(() { widget.themeCallback(); }); }, ), actions: [ IconButton( icon: const Icon(Icons.help), onPressed: () { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('About'), backgroundColor: Theme.of(context).colorScheme.background, content: SizedBox( width: 500, child: IntrinsicHeight( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Welcome to my FBLA 2024 Coding and Programming submission!\n\n' 'MarinoDev Job Link aims to provide comprehensive details of businesses and community partners' ' for Waukesha West High School\'s Career and Technical Education Department.\n\n'), MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( child: const Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Git Repo:'), Text( 'https://git.marinodev.com/MarinoDev/FBLA24\n', style: TextStyle( color: Colors.blue)), ], ), onTap: () { launchUrl(Uri.https( 'git.marinodev.com', '/MarinoDev/FBLA24')); }, ), ), MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( child: const Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Please direct any questions to'), Text('drake@marinodev.com', style: TextStyle( color: Colors.blue)), ], ), onTap: () { launchUrl(Uri.parse( 'mailto:drake@marinodev.com')); }, ), ) ], ), ), ), actions: [ TextButton( child: const Text('OK'), onPressed: () { Navigator.of(context).pop(); }), ], ); }); }, ), IconButton( icon: const Icon(Icons.picture_as_pdf), onPressed: () async { if (!_isPreviousData) { ScaffoldMessenger.of(context).clearSnackBars(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( width: 300, behavior: SnackBarBehavior.floating, content: Text('There is no data!'), duration: Duration(seconds: 2), ), ); } else { selectedDataTypes = {}; Navigator.push( context, MaterialPageRoute( builder: (context) => ExportData( groupedBusinesses: overviewBusinesses))); } }, ), Padding( padding: const EdgeInsets.only(right: 8.0), child: IconButton( icon: loggedIn ? const Icon(Icons.account_circle) : const Icon(Icons.login), onPressed: () { if (loggedIn) { var payload = JWT.decode(jwt).payload; showDialog( context: context, builder: (BuildContext context) { return AlertDialog( backgroundColor: Theme.of(context).colorScheme.background, title: Text('Hi, ${payload['username']}!'), content: Text( 'You are logged in as an admin with username ${payload['username']}.'), actions: [ TextButton( child: const Text('Cancel'), onPressed: () { Navigator.of(context).pop(); }), TextButton( child: const Text('Logout'), onPressed: () async { final prefs = await SharedPreferences .getInstance(); prefs.setBool('rememberMe', false); prefs.setString('username', ''); prefs.setString('password', ''); setState(() { loggedIn = false; }); Navigator.of(context).pop(); }), ], ); }); } else { Navigator.push( context, MaterialPageRoute( builder: (context) => SignInPage( refreshAccount: setStateCallback))); } }, ), ), ], ), FutureBuilder( future: refreshBusinessDataOverviewFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { if (snapshot.hasData) { if (snapshot.data.runtimeType == String) { _isPreviousData = false; return SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.all(16.0), child: Column(children: [ Center( child: Text(snapshot.data, textAlign: TextAlign.center)), Padding( padding: const EdgeInsets.all(8.0), child: FilledButton( child: const Text('Retry'), onPressed: () { var refreshedData = fetchBusinessDataOverview(); setState(() { refreshBusinessDataOverviewFuture = refreshedData; }); }, ), ), ]), )); } overviewBusinesses = snapshot.data; _isPreviousData = true; return BusinessDisplayPanel( groupedBusinesses: overviewBusinesses, widescreen: widescreen, selectable: false); } else if (snapshot.hasError) { return SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.only(left: 16.0, right: 16.0), child: Text( 'Error when loading data! Error: ${snapshot.error}'), )); } } else if (snapshot.connectionState == ConnectionState.waiting) { if (_isPreviousData) { return BusinessDisplayPanel( groupedBusinesses: overviewBusinesses, widescreen: widescreen, selectable: false); } else { return SliverToBoxAdapter( child: Container( padding: const EdgeInsets.all(8.0), alignment: Alignment.center, // child: const CircularProgressIndicator(), child: const SizedBox( width: 75, height: 75, child: RiveAnimation.asset( 'assets/mdev_triangle_loading.riv'), // child: RiveAnimation.file( // 'assets/mdev_triangle_loading.riv', // ), ), )); } } return SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.all(8.0), child: Text( '\nError: ${snapshot.error}', style: const TextStyle(fontSize: 18), textAlign: TextAlign.center, ), ), ); }), const SliverToBoxAdapter( child: SizedBox( height: 80, ), ) ], ), ), ); } Widget? _getFAB() { if (loggedIn) { return FloatingActionButton( child: const Icon(Icons.add_business), onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => const CreateEditBusiness())); }, ); } return null; } Widget _searchBar() { return SizedBox( width: 800, height: 50, child: TextField( onChanged: (query) { setState(() { searchFilter = query; }); }, decoration: InputDecoration( labelText: 'Search', hintText: 'Search', prefixIcon: const Padding( padding: EdgeInsets.only(left: 8.0), child: Icon(Icons.search), ), border: const OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(25.0)), ), suffixIcon: Padding( padding: const EdgeInsets.only(right: 8.0), child: IconButton( tooltip: 'Filters', icon: Icon(Icons.filter_list, color: isFiltered ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.onBackground), onPressed: () { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( // DO NOT MOVE TO SEPARATE WIDGET, setState is needed in main tree backgroundColor: Theme.of(context).colorScheme.background, title: const Text('Filter Options'), content: const FilterChips(), actions: [ TextButton( child: const Text('Reset'), onPressed: () { setState(() { filters = {}; selectedChips = {}; isFiltered = false; }); Navigator.of(context).pop(); }), TextButton( child: const Text('Cancel'), onPressed: () { selectedChips = Set.from(filters); Navigator.of(context).pop(); }), TextButton( child: const Text('Apply'), onPressed: () { setState(() { filters = Set.from(selectedChips); if (filters.isNotEmpty) { isFiltered = true; } else { isFiltered = false; } }); Navigator.of(context).pop(); }), ], ); }); }, ), ), ), ), ); } PreferredSizeWidget? _getBottom() { if (MediaQuery.sizeOf(context).width <= 1000) { return PreferredSize( preferredSize: const Size.fromHeight(0), child: SizedBox( // color: Theme.of(context).colorScheme.background, height: 70, child: Padding( padding: const EdgeInsets.all(10), child: _searchBar(), ), ), ); } return null; } }