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'; class Home extends StatefulWidget { final void Function() 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; Set jobTypeFilters = {}; String searchQuery = ''; Set selectedDataTypesJob = {}; Set selectedDataTypesBusiness = {}; Future _setFilters(Set filters) async { setState(() { jobTypeFilters = filters; }); _updateOverviewBusinesses(); } Future _updateOverviewBusinesses() async { var refreshedData = fetchBusinessDataOverview(typeFilters: jobTypeFilters.toList()); await refreshedData; setState(() { refreshBusinessDataOverviewFuture = refreshedData; }); } Map> _filterBySearch( Map> businesses) { Map> filteredBusinesses = businesses; for (JobType jobType in businesses.keys) { filteredBusinesses[jobType]!.removeWhere((tmpBusiness) => !tmpBusiness .name .replaceAll(RegExp(r'[^a-zA-Z]'), '') .toLowerCase() .contains(searchQuery .replaceAll(RegExp(r'[^a-zA-Z]'), '') .toLowerCase() .trim())); } filteredBusinesses.removeWhere((key, value) => value.isEmpty); return filteredBusinesses; } Future _setSearch(String search) async { setState(() { searchQuery = search; }); _updateOverviewBusinesses(); } @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( // backgroundColor: Theme.of(context).scaffoldBackgroundColor, floatingActionButton: _getFAB(), body: RefreshIndicator( edgeOffset: 120, onRefresh: () async { _updateOverviewBusinesses(); }, child: CustomScrollView( slivers: [ SliverAppBar( title: widescreen ? BusinessSearchBar( filters: jobTypeFilters, setFiltersCallback: _setFilters, setSearchCallback: _setSearch) : const Text('Job Link'), toolbarHeight: 70, pinned: true, scrolledUnderElevation: 0, centerTitle: true, expandedHeight: widescreen ? 70 : 120, 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.surface, 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 { selectedDataTypesBusiness = {}; 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.surface, 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: () { _updateOverviewBusinesses(); }, ), ), ]), )); } overviewBusinesses = snapshot.data; _isPreviousData = true; return BusinessDisplayPanel( groupedBusinesses: _filterBySearch(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: _filterBySearch(overviewBusinesses), widescreen: widescreen, selectable: false); } else { return SliverToBoxAdapter( child: Container( padding: const EdgeInsets.all(8.0), alignment: Alignment.center, child: const SizedBox( width: 75, height: 75, child: RiveAnimation.asset( '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; } 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: BusinessSearchBar( filters: jobTypeFilters, setFiltersCallback: _setFilters, setSearchCallback: _setSearch), ), ), ); } return null; } }