diff --git a/lib/common_blocs/common_blocs.dart b/lib/common_blocs/common_blocs.dart new file mode 100644 index 0000000..05f68bb --- /dev/null +++ b/lib/common_blocs/common_blocs.dart @@ -0,0 +1,31 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:spoonshare/screens/main_bottom_nav/bloc/main_bottom_nav_bloc.dart'; + +class CommonBloc { + /// Bloc + static final bottomNavBloc = MainBottomNavBloc(); + + static final List blocProviders = [ + BlocProvider( + create: (context) => bottomNavBloc, + ), + ]; + + /// Dispose + static void dispose() { + bottomNavBloc.close(); + } + + static void resetBlocs() { + // bottomNavBloc.add(ResetAuthentication()); + } + + /// Singleton factory + static final CommonBloc _instance = CommonBloc._internal(); + + factory CommonBloc() { + return _instance; + } + + CommonBloc._internal(); +} diff --git a/lib/constants/app_colors.dart b/lib/constants/app_colors.dart new file mode 100644 index 0000000..c04a2d6 --- /dev/null +++ b/lib/constants/app_colors.dart @@ -0,0 +1,8 @@ +import 'package:flutter/material.dart'; + +class AppColors { + static Color basePrimaryColor = const Color(0xffFF9F1C); + static Color kWhiteColor = const Color(0xffFFFFFF); + static Color kBlackColor = const Color(0xff000000); + static Color kGreenColor = const Color(0xff32B046); +} diff --git a/lib/constants/app_constants.dart b/lib/constants/app_constants.dart new file mode 100644 index 0000000..ddb6b3f --- /dev/null +++ b/lib/constants/app_constants.dart @@ -0,0 +1,2 @@ +export 'app_colors.dart'; +export 'app_images.dart'; \ No newline at end of file diff --git a/lib/constants/app_images.dart b/lib/constants/app_images.dart new file mode 100644 index 0000000..c5d0046 --- /dev/null +++ b/lib/constants/app_images.dart @@ -0,0 +1,3 @@ +class AppImages { + static String spoonShareLogo = "assets/images/spoonshare.png"; +} diff --git a/lib/main.dart b/lib/main.dart index 5419877..ba20f99 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,48 +1,67 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:spoonshare/firebase_options.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:spoonshare/services/notifications_services.dart'; import 'package:spoonshare/splash_screen.dart'; import 'package:device_preview/device_preview.dart'; +import 'common_blocs/common_blocs.dart'; + +final navigatorKey = GlobalKey(); void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); + NotificationServices().firebaseInit(); await SharedPreferences.getInstance(); - runApp( DevicePreview( - enabled: !kReleaseMode, + enabled: !kReleaseMode, builder: (context) => const MyApp(), ), ); } -class MyApp extends StatelessWidget { +class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); + @override + State createState() => _MyAppState(); +} + +NavigatorState? get _navigator => navigatorKey.currentState; + + onNavigateSplash() { + _navigator!.pushAndRemoveUntil(MaterialPageRoute(builder: (context) { + return const SplashScreen(); + }), (route) => false); +} + +class _MyAppState extends State { @override Widget build(BuildContext context) { - return ScreenUtilInit( - designSize: const Size(360, 700), - minTextAdapt: true, - splitScreenMode: true, - builder: (_, child) { - return MaterialApp( + return MultiBlocProvider( + providers: CommonBloc.blocProviders, + child: ScreenUtilInit( + designSize: const Size(360, 800), + minTextAdapt: true, + splitScreenMode: true, + child: MaterialApp( + navigatorKey: navigatorKey, debugShowCheckedModeBanner: false, title: 'Spoon Share', theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.orange), + colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xffFF9F1C)), useMaterial3: true, ), - home: child, - ); - }, - child: const SplashScreen(), + home: const SplashScreen(), + ), + ), ); } } diff --git a/lib/models/users/user.dart b/lib/models/users/user.dart index 584e98f..9452da1 100644 --- a/lib/models/users/user.dart +++ b/lib/models/users/user.dart @@ -92,4 +92,6 @@ class UserProfile { String getOrganisation() { return organisation; } + + void cancelStreams() {} } diff --git a/lib/onboarding.dart b/lib/onboarding.dart index 339f22b..38f24c1 100644 --- a/lib/onboarding.dart +++ b/lib/onboarding.dart @@ -14,6 +14,9 @@ class Onboarding extends StatelessWidget { child: Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, + padding: EdgeInsets.only( + top: MediaQuery.of(context).padding.top, + ), color: Colors.white, child: Column( mainAxisAlignment: MainAxisAlignment.start, diff --git a/lib/screens/admin/admin_home.dart b/lib/screens/admin/admin_home.dart index 0604787..485bcb4 100644 --- a/lib/screens/admin/admin_home.dart +++ b/lib/screens/admin/admin_home.dart @@ -1,6 +1,5 @@ -// ignore_for_file: use_build_context_synchronously - import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:spoonshare/models/users/user.dart'; import 'package:spoonshare/screens/admin/ngo_management.dart'; import 'package:spoonshare/screens/admin/verify_donated_food.dart'; @@ -24,9 +23,6 @@ class _AdminHomeScreenState extends State { String? organization; bool isLoading = true; - // UserProfile instance - final UserProfile _userProfile = UserProfile(); - @override void initState() { super.initState(); @@ -35,31 +31,40 @@ class _AdminHomeScreenState extends State { Future fetchUserProfile() async { try { - await _userProfile.loadUserProfile(); - role = _userProfile.getRole(); - fullName = _userProfile.getFullName(); - email = _userProfile.getEmail(); - contactNumber = _userProfile.getContactNumber(); - organization = _userProfile.getOrganisation(); + final userProfile = UserProfile(); // Initialize here + await userProfile.loadUserProfile(); + role = userProfile.getRole(); + fullName = userProfile.getFullName(); + email = userProfile.getEmail(); + contactNumber = userProfile.getContactNumber(); + organization = userProfile.getOrganisation(); } catch (e) { - showErrorSnackbar(context, 'Error fetching user profile: $e'); + if (mounted) { + showErrorSnackbar(context, 'Error fetching user profile: $e'); + } } finally { - setState(() { - isLoading = false; - }); + if (mounted) { + setState(() { + isLoading = false; + }); + } } } + @override + void dispose() { + super.dispose(); + } + @override Widget build(BuildContext context) { return Scaffold( body: _buildVerifiedContent(), - ); } Widget _buildVerifiedContent() { - return Center( + return SingleChildScrollView( child: Container( padding: const EdgeInsets.all(16.0), child: Column( @@ -96,20 +101,6 @@ class _AdminHomeScreenState extends State { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Container( - width: 42, - height: 42, - decoration: ShapeDecoration( - color: Colors.black.withOpacity(0.08), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(50), - ), - ), - child: IconButton( - icon: const Icon(Icons.search), - onPressed: () {}, - ), - ), const SizedBox(width: 8), Container( width: 42, @@ -221,8 +212,8 @@ Widget _buildCard({ children: [ Text( title, - style: const TextStyle( - fontSize: 18, + style: TextStyle( + fontSize: 16.sp, fontFamily: 'DM Sans', fontWeight: FontWeight.bold, ), @@ -233,8 +224,8 @@ Widget _buildCard({ const SizedBox(height: 8), Text( description, - style: const TextStyle( - fontSize: 16, + style: TextStyle( + fontSize: 14.sp, fontFamily: 'DM Sans', ), ), diff --git a/lib/screens/admin/all_ngos.dart b/lib/screens/admin/all_ngos.dart index bcdaf96..169fa28 100644 --- a/lib/screens/admin/all_ngos.dart +++ b/lib/screens/admin/all_ngos.dart @@ -32,7 +32,7 @@ class _AllNGOScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('All NGO\s'), + title: const Text('All NGOs'), backgroundColor: const Color(0xFFFF9F1C), titleTextStyle: const TextStyle( color: Colors.white, diff --git a/lib/screens/auth/signin.dart b/lib/screens/auth/signin.dart index 5a72a66..9ddf74d 100644 --- a/lib/screens/auth/signin.dart +++ b/lib/screens/auth/signin.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:spoonshare/controllers/auth/signin_controller.dart'; import 'package:spoonshare/screens/auth/forgot_password.dart'; import 'package:spoonshare/screens/auth/signup.dart'; @@ -18,7 +19,6 @@ class _SignInScreenState extends State { final TextEditingController _emailController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); bool _isPasswordVisible = false; - @override void dispose() { @@ -34,7 +34,7 @@ class _SignInScreenState extends State { child: Center( child: Container( width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height, + height: MediaQuery.of(context).size.height + 10.h, clipBehavior: Clip.antiAlias, decoration: const BoxDecoration(color: Colors.white), child: Column( @@ -54,7 +54,8 @@ class _SignInScreenState extends State { padding: const EdgeInsets.only( top: 4, ), - decoration: const BoxDecoration(color: Color(0xFFFF9F1C)), + decoration: + const BoxDecoration(color: Color(0xFFFF9F1C)), child: const Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -118,7 +119,8 @@ class _SignInScreenState extends State { 'Explore nearby food or join us to make a difference!', textAlign: TextAlign.center, style: TextStyle( - color: Colors.black.withOpacity(0.800000011920929), + color: Colors.black + .withOpacity(0.800000011920929), fontSize: 14, fontFamily: 'DM Sans', fontWeight: FontWeight.w500, @@ -154,7 +156,8 @@ class _SignInScreenState extends State { height: 18, decoration: const BoxDecoration( image: DecorationImage( - image: AssetImage("assets/images/google.png"), + image: AssetImage( + "assets/images/google.png"), fit: BoxFit.fill, ), ), @@ -185,7 +188,8 @@ class _SignInScreenState extends State { ), ), Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), + padding: + const EdgeInsets.symmetric(horizontal: 8), child: Text( 'OR', style: TextStyle( @@ -209,7 +213,8 @@ class _SignInScreenState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox(height: 16), - InputField(label: 'Email', controller: _emailController), + InputField( + label: 'Email', controller: _emailController), const SizedBox(height: 16), InputField( label: 'Password', @@ -229,13 +234,15 @@ class _SignInScreenState extends State { onTap: () { Navigator.of(context).pushReplacement( MaterialPageRoute( - builder: (context) => ForgotPasswordScreen()), + builder: (context) => + ForgotPasswordScreen()), ); }, child: const Align( alignment: Alignment.bottomRight, child: Padding( - padding: EdgeInsets.only(top: 8.0, right: 36.0), + padding: + EdgeInsets.only(top: 8.0, right: 36.0), child: Text( 'forgot password?', style: TextStyle( @@ -324,8 +331,8 @@ class _SignInScreenState extends State { TextSpan( text: 'Don\'t have an account?', style: TextStyle( - color: - Colors.black.withOpacity(0.699999988079071), + color: Colors.black + .withOpacity(0.699999988079071), fontSize: 16, fontFamily: 'Roboto', fontWeight: FontWeight.w500, @@ -372,10 +379,11 @@ class _SignInScreenState extends State { context: context, ); // Hide loading indicator - Navigator.push( + Navigator.pushAndRemoveUntil( context, MaterialPageRoute( builder: (context) => const HomeScreen()), + ModalRoute.withName('/'), ); } catch (e) { // Handle any exceptions during signup @@ -393,7 +401,7 @@ class _SignInScreenState extends State { ), ), child: const SizedBox( - width: 312, + width: 250, height: 45, child: Center( child: Text( diff --git a/lib/screens/auth/signup.dart b/lib/screens/auth/signup.dart index a4af37d..d8b64f7 100644 --- a/lib/screens/auth/signup.dart +++ b/lib/screens/auth/signup.dart @@ -133,9 +133,8 @@ class _SignUpScreenState extends State { Container( width: ScreenUtil().setWidth(218), height: ScreenUtil().setHeight(38), - padding: EdgeInsets.symmetric( - horizontal: 18.w, - vertical: 10.h), + padding: + EdgeInsets.symmetric(horizontal: 18.w, vertical: 10.h), decoration: ShapeDecoration( shape: RoundedRectangleBorder( side: const BorderSide(width: 1), @@ -366,10 +365,11 @@ class _SignUpScreenState extends State { Navigator.of(context).pop(); - Navigator.push( + Navigator.pushAndRemoveUntil( context, MaterialPageRoute( builder: (context) => const HomeScreen()), + ModalRoute.withName('/'), ); } catch (e) { print("Signup failed: $e"); @@ -388,7 +388,7 @@ class _SignUpScreenState extends State { ), ), child: SizedBox( - width: 300.h, + width: 250.h, height: 45.h, child: Center( child: Text( diff --git a/lib/screens/chat/chat_page.dart b/lib/screens/chat/chat_page.dart index 5e243c4..ca690fd 100644 --- a/lib/screens/chat/chat_page.dart +++ b/lib/screens/chat/chat_page.dart @@ -1,25 +1,25 @@ -import 'package:flutter/material.dart'; -import 'package:spoonshare/widgets/bottom_navbar.dart'; - -class ChatPage extends StatelessWidget { - const ChatPage({Key? key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text("Chat Page"), - leading: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () { - Navigator.pushReplacementNamed(context, '/'); - }, - ), - ), - body: const Center( - child: Text("Chat Page Content"), - ), - bottomNavigationBar: const BottomNavBar(), - ); - } -} +// import 'package:flutter/material.dart'; +// import 'package:spoonshare/widgets/bottom_navbar.dart'; +// +// class ChatPage extends StatelessWidget { +// const ChatPage({Key? key}); +// +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// appBar: AppBar( +// title: const Text("Chat Page"), +// leading: IconButton( +// icon: const Icon(Icons.arrow_back), +// onPressed: () { +// Navigator.pushReplacementNamed(context, '/'); +// }, +// ), +// ), +// body: const Center( +// child: Text("Chat Page Content"), +// ), +// // bottomNavigationBar: const BottomNavBar(), +// ); +// } +// } diff --git a/lib/screens/dashboard/dashboard_page.dart b/lib/screens/dashboard/dashboard_page.dart index 2907cfc..5da58b2 100644 --- a/lib/screens/dashboard/dashboard_page.dart +++ b/lib/screens/dashboard/dashboard_page.dart @@ -15,28 +15,30 @@ class _DashboardPageState extends State { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Join Us'), - backgroundColor: const Color(0xFFFF9F1C), - titleTextStyle: const TextStyle( - color: Colors.white, - fontFamily: 'DM Sans', - fontSize: 18, - fontWeight: FontWeight.w700, - ), - leading: IconButton( - icon: const Icon(Icons.arrow_back), - color: Colors.white, - onPressed: () { - Navigator.pop(context); - }, - ), - ), - body: SingleChildScrollView( + return + // Scaffold( + // appBar: AppBar( + // title: const Text('Join Us'), + // backgroundColor: const Color(0xFFFF9F1C), + // titleTextStyle: const TextStyle( + // color: Colors.white, + // fontFamily: 'DM Sans', + // fontSize: 18, + // fontWeight: FontWeight.w700, + // ), + // leading: IconButton( + // icon: const Icon(Icons.arrow_back), + // color: Colors.white, + // onPressed: () { + // Navigator.pop(context); + // }, + // ), + // ), + // body: + SingleChildScrollView( child: Container( width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height - 80, + height: MediaQuery.of(context).size.height, padding: EdgeInsets.only( top: MediaQuery.of(context).padding.top, ), @@ -191,7 +193,6 @@ class _DashboardPageState extends State { ], ), ), - ), - ); + ); } } diff --git a/lib/screens/donate/donate_page.dart b/lib/screens/donate/donate_page.dart index 0314df1..8c41ce8 100644 --- a/lib/screens/donate/donate_page.dart +++ b/lib/screens/donate/donate_page.dart @@ -20,7 +20,10 @@ class _DonatePageState extends State { body: SingleChildScrollView( child: Container( width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height, + height: MediaQuery.of(context).size.height + 10.h, + padding: EdgeInsets.only( + top: MediaQuery.of(context).padding.top, + ), clipBehavior: Clip.antiAlias, decoration: const BoxDecoration(color: Colors.white), child: Column( @@ -62,8 +65,8 @@ class _DonatePageState extends State { ), const SizedBox(height: 16), SizedBox( - width: 275, - height: 69, + width: 275.w, + height: 80.h, child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, @@ -190,10 +193,9 @@ class _DonatePageState extends State { ], ), ), - SizedBox(height: 250, - child: Container( + Container( width: 320.w, - height: 300.h, + height: 290.h, decoration: const BoxDecoration( image: DecorationImage( image: AssetImage("assets/images/girlthinking.png"), @@ -201,7 +203,7 @@ class _DonatePageState extends State { ), ), ) - ), + ], ), ), diff --git a/lib/screens/donate/share_food.dart b/lib/screens/donate/share_food.dart index 7aaaa56..3c64943 100644 --- a/lib/screens/donate/share_food.dart +++ b/lib/screens/donate/share_food.dart @@ -5,7 +5,6 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:image_picker/image_picker.dart'; import 'package:spoonshare/models/users/user.dart'; @@ -101,7 +100,6 @@ class _ShareFoodScreenContentState extends State { return SingleChildScrollView( child: SizedBox( - height: 810.h, child: Container( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -120,7 +118,7 @@ class _ShareFoodScreenContentState extends State { ), if (showExpandedList) Container( - height: 200, // Set the height of the suggestions container + height: 200, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), @@ -488,7 +486,6 @@ class _ShareFoodScreenContentState extends State { return Container( width: screenWidth * 0.8667, height: screenHeight * 0.05625, - margin: const EdgeInsets.only(top: 20), decoration: BoxDecoration( color: const Color(0xFFFF9F1C), borderRadius: BorderRadius.circular(50), diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index 687ef40..7106094 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:spoonshare/screens/home/MainPage.dart'; import 'package:spoonshare/models/users/user.dart'; import 'package:spoonshare/onboarding.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:spoonshare/screens/main_bottom_nav/main_bottom_nav.dart'; class HomeScreen extends StatelessWidget { const HomeScreen({Key? key}) : super(key: key); @@ -29,14 +29,16 @@ class HomeScreen extends StatelessWidget { return userProfile.isAuthenticated() ? Center( - child: MainPage(name: name, role: role), + child: MainBottomNav(name: name, role: role), ) : Container(); } else { _requestLocationPermissions(context); return const Scaffold( body: Center( - child: CircularProgressIndicator(), + child: CircularProgressIndicator( + color: Color(0xffFF9F1C), + ), ), ); } diff --git a/lib/screens/home/home_page.dart b/lib/screens/home/home_page.dart index 69360fe..1c15db7 100644 --- a/lib/screens/home/home_page.dart +++ b/lib/screens/home/home_page.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:spoonshare/widgets/maps_widget.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:spoonshare/widgets/foodcards/nearby_daily_cards.dart'; import 'package:spoonshare/widgets/foodcards/nearby_food_cards.dart'; import 'package:spoonshare/widgets/foodcards/past_food_cards.dart'; @@ -16,16 +16,19 @@ class HomePage extends StatefulWidget { class _HomePageState extends State { int selectedIndex = 0; + @override Widget build(BuildContext context) { - return Scaffold( - body: SingleChildScrollView( + return SafeArea( + child: SingleChildScrollView( child: Padding( - padding: const EdgeInsets.only(left: 20, right: 20,), + padding: EdgeInsets.symmetric( + horizontal: 20.w, + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox(height: 30), + SizedBox(height: 10.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -57,21 +60,7 @@ class _HomePageState extends State { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Container( - width: 42, - height: 42, - decoration: ShapeDecoration( - color: Colors.black.withOpacity(0.08), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(50), - ), - ), - child: IconButton( - icon: const Icon(Icons.search), - onPressed: () {}, - ), - ), - const SizedBox(width: 8), + const SizedBox(width: 8), Container( width: 42, height: 42, @@ -134,7 +123,7 @@ class _HomePageState extends State { GestureDetector( onTap: () { setState(() { - selectedIndex = 1; // Update selected index + selectedIndex = 1; }); }, child: Text( @@ -159,7 +148,7 @@ class _HomePageState extends State { Expanded( flex: 1, child: Container( - height: 1, // Adjust the height of the line as needed + height: 1, color: selectedIndex == 0 ? const Color(0xFFFF9F1C) : const Color(0x28FF9F1C), @@ -168,7 +157,7 @@ class _HomePageState extends State { Expanded( flex: 1, child: Container( - height: 1, // Adjust the height of the line as needed + height: 1, color: selectedIndex == 1 ? const Color.fromARGB(255, 201, 141, 58) : const Color(0x28FF9F1C), @@ -204,7 +193,7 @@ class _HomePageState extends State { ], ), ), - Visibility( + Visibility( visible: selectedIndex == 0, child: Container( margin: const EdgeInsets.only(top: 30), @@ -232,7 +221,6 @@ class _HomePageState extends State { ], ), ), - Visibility( visible: selectedIndex != 0, child: Column( @@ -253,17 +241,6 @@ class _HomePageState extends State { ), ), ), - - floatingActionButton: FloatingActionButton( - onPressed: () { - Navigator.push( - context, MaterialPageRoute(builder: (context) => MapsWidget())); - }, - backgroundColor: const Color(0xFFFF9F1C), - foregroundColor: Colors.white, - child: const Icon(Icons.location_on), - ), - floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, ); } } diff --git a/lib/screens/main_bottom_nav/bloc/main_bottom_nav_bloc.dart b/lib/screens/main_bottom_nav/bloc/main_bottom_nav_bloc.dart new file mode 100644 index 0000000..9147939 --- /dev/null +++ b/lib/screens/main_bottom_nav/bloc/main_bottom_nav_bloc.dart @@ -0,0 +1,17 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; + +part 'main_bottom_nav_event.dart'; + +part 'main_bottom_nav_state.dart'; + +class MainBottomNavBloc extends Bloc { + MainBottomNavBloc() : super(MainBottomNavInitial(index: 0)) { + on((event, emit) { + emit(MainBottomNavInitial(index: 0)); + }); + on((event, emit) { + emit(ChangedTabState(index: 0)); + }); + } +} diff --git a/lib/screens/main_bottom_nav/bloc/main_bottom_nav_event.dart b/lib/screens/main_bottom_nav/bloc/main_bottom_nav_event.dart new file mode 100644 index 0000000..7314b1d --- /dev/null +++ b/lib/screens/main_bottom_nav/bloc/main_bottom_nav_event.dart @@ -0,0 +1,18 @@ +part of 'main_bottom_nav_bloc.dart'; + +abstract class MainBottomNav extends Equatable { + @override + List get props => []; +} + +class MainBottomNavInitialEvent extends MainBottomNav { + MainBottomNavInitialEvent(); +} + +class ChangeTabEvent extends MainBottomNav { + final int index; + + ChangeTabEvent({ + required this.index, + }); +} diff --git a/lib/screens/main_bottom_nav/bloc/main_bottom_nav_state.dart b/lib/screens/main_bottom_nav/bloc/main_bottom_nav_state.dart new file mode 100644 index 0000000..41b9704 --- /dev/null +++ b/lib/screens/main_bottom_nav/bloc/main_bottom_nav_state.dart @@ -0,0 +1,23 @@ +part of 'main_bottom_nav_bloc.dart'; + +abstract class MainBottomNavState {} + +class MainBottomNavInitial extends MainBottomNavState { + final int index; + + MainBottomNavInitial({required this.index}); + + MainBottomNavInitial copyWith({ + int? index, + }) { + return MainBottomNavInitial( + index: index ?? this.index, + ); + } +} + +class ChangedTabState extends MainBottomNavState { + final int index; + + ChangedTabState({required this.index}); +} diff --git a/lib/screens/main_bottom_nav/main_bottom_nav.dart b/lib/screens/main_bottom_nav/main_bottom_nav.dart new file mode 100644 index 0000000..ea4954e --- /dev/null +++ b/lib/screens/main_bottom_nav/main_bottom_nav.dart @@ -0,0 +1,160 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:spoonshare/constants/app_constants.dart'; +import 'package:spoonshare/screens/admin/admin_home.dart'; +import 'package:spoonshare/screens/dashboard/dashboard_page.dart'; +import 'package:spoonshare/screens/donate/donate_page.dart'; +import 'package:spoonshare/screens/home/home_page.dart'; +import 'package:spoonshare/screens/ngo/ngo_home.dart'; +import 'package:spoonshare/screens/recycle/recycle.dart'; +import 'package:spoonshare/screens/profile/user_profile.dart'; +import 'package:spoonshare/screens/volunteer/volunteer_home.dart'; +import 'package:spoonshare/widgets/app_exit_dialog.dart'; +import 'package:spoonshare/widgets/maps_widget.dart'; + +import 'bloc/main_bottom_nav_bloc.dart'; + +class MainBottomNav extends StatefulWidget { + final String name; + final String role; + + const MainBottomNav({Key? key, required this.name, required this.role}) + : super(key: key); + + @override + _MainBottomNavState createState() => _MainBottomNavState(); +} + +class _MainBottomNavState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 5, vsync: this); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + List _buildScreens() { + return [ + HomePage(name: widget.name, role: widget.role), + navigateToRoleScreen(context), + const DonatePage(), + const RecycleScreen(), + UserProfileScreen(name: widget.name, role: widget.role), + ]; + } + + Widget navigateToRoleScreen(BuildContext context) { + switch (widget.role) { + case 'Volunteer': + return const VolunteerHomeScreen(); + case 'NGO': + return const NGOHomeScreen(); + case 'Admin': + return const AdminHomeScreen(); + case 'Individual': + return const DashboardPage(); + default: + return const DashboardPage(); + } + } + + List _navBarsItems() { + return [ + const BottomNavigationBarItem( + activeIcon: Icon(Icons.home), + icon: Icon(Icons.home), + label: "Home", + ), + const BottomNavigationBarItem( + activeIcon: Icon(Icons.dashboard), + icon: Icon(Icons.dashboard), + label: "Dashboard", + ), + const BottomNavigationBarItem( + activeIcon: Icon(Icons.add_circle), + icon: Icon(Icons.add_circle), + label: "Donate", + ), + const BottomNavigationBarItem( + activeIcon: Icon(Icons.recycling), + icon: Icon(Icons.recycling), + label: "Recycle", + ), + const BottomNavigationBarItem( + activeIcon: Icon(Icons.person), + icon: Icon(Icons.person), + label: "Profile", + ), + ]; + } + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) { + // TODO: implement listener + }, + builder: (context, state) { + return PopScope( + canPop: false, + onPopInvoked: (willPop) async { + if (willPop) return; + if (_tabController.index == 0) { + return await showDialog( + context: context, + builder: (context) { + return ExitConfirmationDialog(); + }); + } else { + context.read().add(ChangeTabEvent(index: 0)); + _tabController.index = 0; + } + return; + }, + child: Scaffold( + body: IndexedStack( + index: _tabController.index, + children: _buildScreens(), + ), + bottomNavigationBar: BottomNavigationBar( + type: BottomNavigationBarType.fixed, + onTap: (index) { + context + .read() + .add(ChangeTabEvent(index: index)); + _tabController.index = index; + }, + showSelectedLabels: true, + showUnselectedLabels: true, + items: _navBarsItems(), + selectedItemColor: AppColors.basePrimaryColor, + currentIndex: _tabController.index, + ), + floatingActionButton: _tabController.index == 0 + ? FloatingActionButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => MapsWidget())); + }, + backgroundColor: AppColors.basePrimaryColor, + foregroundColor: AppColors.kWhiteColor, + child: const Icon(Icons.location_on), + ) + : null, + floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, + ), + ); + }, + ); + } +} diff --git a/lib/screens/ngo/ngo_form.dart b/lib/screens/ngo/ngo_form.dart index b47a8a8..4746f44 100644 --- a/lib/screens/ngo/ngo_form.dart +++ b/lib/screens/ngo/ngo_form.dart @@ -11,7 +11,7 @@ import 'package:image_picker/image_picker.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:spoonshare/models/users/user.dart'; -import 'package:spoonshare/screens/ngo/ngo_home.dart'; +import 'package:spoonshare/screens/home/home.dart'; import 'package:spoonshare/widgets/custom_text_field.dart'; import 'package:spoonshare/widgets/auto_complete.dart'; import 'package:spoonshare/widgets/snackbar.dart'; @@ -487,77 +487,103 @@ class NGOFormScreenState extends State { } } - Future _submitForm() async { - if (_validateFields()) { - try { - // Upload image to Firebase Storage - String imageUrl = await uploadImageToFirebaseStorage( - _imageFile, - _ngoNameController.text, - ); - - GeoPoint location = GeoPoint(lat, lng); - - await FirebaseFirestore.instance.collection('ngos').add({ - 'ngoName': _ngoNameController.text, - 'ngoNo': _ngoNoController.text, - 'mobileNo': _mobileNoController.text, - 'email': _emailController.text, - 'ngoImage': imageUrl, - 'type': _selectedType, - 'incorporationDay': _selectedIncorporationDay, - 'description': _decripationController.text, - 'address': _addressController.text, - 'location': location, - 'linkedin': _linkedinController.text, - 'verified': false, - }); - - SharedPreferences prefs = await SharedPreferences.getInstance(); - prefs.setString('organisation', _ngoNameController.text); - prefs.setString('role', 'NGO'); - - CollectionReference users = - FirebaseFirestore.instance.collection('users'); - - String userId = FirebaseAuth.instance.currentUser!.uid; - - await users.doc(userId).update({ - 'organisation': _ngoNameController.text, - 'role': 'NGO', - }); - - _ngoNameController.clear(); - _mobileNoController.clear(); - _emailController.clear(); - _decripationController.clear(); - _addressController.clear(); - _linkedinController.clear(); - _selectedType = null; - _selectedIncorporationDay = null; - - // Show success message if submission is successful - showSuccessSnackbar( - context, - 'Form submitted successfully. We will get back to you soon.', - ); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const NGOHomeScreen(), - ), - ); - } catch (error) { - showErrorSnackbar( - context, - 'Error submitting form. Please try again later.', - ); - } - } else { +Future _submitForm() async { + if (_validateFields()) { + try { + // Show loader + showDialog( + context: context, + barrierDismissible: false, // Prevent dialog from being dismissed + builder: (BuildContext context) { + return AlertDialog( + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CircularProgressIndicator(), + SizedBox(height: 16), + Text('Submitting Form...'), + ], + ), + ); + }, + ); + + // Upload image to Firebase Storage + String imageUrl = await uploadImageToFirebaseStorage( + _imageFile, + _ngoNameController.text, + ); + + GeoPoint location = GeoPoint(lat, lng); + + DocumentReference docRef = await FirebaseFirestore.instance.collection('ngos').add({ + 'ngoName': _ngoNameController.text, + 'ngoNo': _ngoNoController.text, + 'mobileNo': _mobileNoController.text, + 'email': _emailController.text, + 'ngoImage': imageUrl, + 'type': _selectedType, + 'incorporationDay': _selectedIncorporationDay, + 'description': _decripationController.text, + 'address': _addressController.text, + 'location': location, + 'linkedin': _linkedinController.text, + 'verified': false, + }); + + SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.setString('organisation', _ngoNameController.text); + prefs.setString('role', 'NGO'); + + CollectionReference users = FirebaseFirestore.instance.collection('users'); + + String userId = FirebaseAuth.instance.currentUser!.uid; + + await users.doc(userId).update({ + 'organisation': _ngoNameController.text, + 'role': 'NGO', + }); + print(_ngoNameController.text); + + _ngoNameController.clear(); + _mobileNoController.clear(); + _emailController.clear(); + _decripationController.clear(); + _addressController.clear(); + _linkedinController.clear(); + _selectedType = null; + _selectedIncorporationDay = null; + + // Close the dialog + Navigator.pop(context); + + showSuccessSnackbar( + context, + 'Form submitted successfully. We will get back to you soon.', + ); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const HomeScreen(), + ), + ); + } catch (error) { + // Close the dialog + Navigator.pop(context); + showErrorSnackbar( context, - 'Please fill all the required fields', + 'Error submitting form. Please try again later.', ); } + } else { + showErrorSnackbar( + context, + 'Please fill all the required fields', + ); } } + +} + + diff --git a/lib/screens/ngo/ngo_home.dart b/lib/screens/ngo/ngo_home.dart index 879db7c..6c85b18 100644 --- a/lib/screens/ngo/ngo_home.dart +++ b/lib/screens/ngo/ngo_home.dart @@ -85,7 +85,9 @@ class _NGOHomeScreenState extends State { Widget build(BuildContext context) { return Scaffold( body: isLoading - ? const Center(child: CircularProgressIndicator()) + ? const Center(child: CircularProgressIndicator( + color: const Color(0xFFFF9F1C), + )) : isVerified ? SingleChildScrollView( child: _buildVerifiedContent(), @@ -118,7 +120,7 @@ class _NGOHomeScreenState extends State { ), ), Text( - role ?? '', + role ?? 'NGO', style: TextStyle( color: Colors.black.withOpacity(0.5), fontSize: 16, @@ -242,7 +244,7 @@ class _NGOHomeScreenState extends State { ), ), Text( - role ?? '', + role ?? 'NGO', style: TextStyle( color: Colors.black.withOpacity(0.5), fontSize: 16, diff --git a/lib/screens/profile/settings_page.dart b/lib/screens/profile/settings_page.dart index cca4e7f..4c258b2 100644 --- a/lib/screens/profile/settings_page.dart +++ b/lib/screens/profile/settings_page.dart @@ -1,11 +1,34 @@ import 'package:flutter/material.dart'; +import 'package:spoonshare/screens/auth/forgot_password.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:spoonshare/constants/app_colors.dart'; +import 'package:spoonshare/services/auth.dart'; class SettingPage extends StatelessWidget { + final AuthService authService = AuthService(); + + SettingPage({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Settings'), + title: const Text( + 'Settings', + ), + backgroundColor: const Color(0xFFFF9F1C), + titleTextStyle: const TextStyle( + color: Colors.white, + fontFamily: 'DM Sans', + fontSize: 18, + fontWeight: FontWeight.w700), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + color: Colors.white, + onPressed: () { + Navigator.pop(context); + }, + ), ), body: Container( width: double.infinity, @@ -18,8 +41,9 @@ class SettingPage extends StatelessWidget { ListTile( leading: const Icon(Icons.help), title: const Text('Help'), - onTap: () { - // Add onTap functionality + onTap: () async { + final url = Uri.parse('mailto:spoonshare7@gmail.com'); + await launchUrl(url); }, ), const Divider(), @@ -27,55 +51,70 @@ class SettingPage extends StatelessWidget { leading: const Icon(Icons.lock), title: const Text('Change Password'), onTap: () { - // Add onTap functionality + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ForgotPasswordScreen()), + ); }, ), const Divider(), ListTile( - leading: const Icon(Icons.library_books), - title: const Text('Terms & Conditions'), - onTap: () { - // Add onTap functionality - }, - ), + leading: const Icon(Icons.library_books), + title: const Text('Privacy Policy'), + onTap: () async { + final url = Uri.parse( + 'https://www.termsfeed.com/live/6c1ed152-889a-47e8-bd6b-0d6012626d40'); + await launchUrl(url); + }), const Divider(), ListTile( - leading: const Icon(Icons.report), - title: const Text('Report Problem'), - onTap: () { - // Add onTap functionality - }, - ), + leading: const Icon(Icons.report), + title: const Text('Report Problem'), + onTap: () async { + final url = Uri.parse('https://forms.gle/RegtZGpSot3w4GxA9'); + await launchUrl(url); + }), const Divider(), ListTile( leading: const Icon(Icons.exit_to_app), title: const Text('Log out'), onTap: () { - // Add onTap functionality + authService.signOut(context); }, ), - const Expanded( - child: SizedBox(), - ), + const Divider(), Container( - width: double.infinity, - height: 69, - decoration: const BoxDecoration( - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Color(0x19000000), - blurRadius: 8, - offset: Offset(0, -4), - spreadRadius: 0, - ), - ], + padding: const EdgeInsets.all(16.0), + child: Text( + 'Version 1.3.0', + style: TextStyle( + color: AppColors.kBlackColor, + fontSize: 16, + fontFamily: 'DM Sans', + fontWeight: FontWeight.w700, + height: 0, + letterSpacing: 1.68, + ), ), + ), + Container( + padding: const EdgeInsets.all(16.0), + child: Text( + '© 2024 Spoonshare', + style: TextStyle( + color: AppColors.kBlackColor, + fontSize: 16, + fontFamily: 'DM Sans', + fontWeight: FontWeight.w700, + height: 0, + letterSpacing: 1.68, + ), ), + ), ], ), ), - ); } -} \ No newline at end of file +} diff --git a/lib/screens/profile/user_profile.dart b/lib/screens/profile/user_profile.dart index 7831328..b35b708 100644 --- a/lib/screens/profile/user_profile.dart +++ b/lib/screens/profile/user_profile.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:spoonshare/models/users/user.dart'; +import 'package:spoonshare/screens/profile/settings_page.dart'; import 'package:spoonshare/screens/volunteer/volunteer_form.dart'; import 'package:spoonshare/services/auth.dart'; import 'package:image_picker/image_picker.dart'; @@ -65,16 +66,27 @@ class _UserProfileScreenState extends State { }); } + @override + void dispose() { + nameController.dispose(); + emailController.dispose(); + contactController.dispose(); + orgController.dispose(); + roleController.dispose(); + profileImageController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Scaffold( body: SingleChildScrollView( + padding: MediaQuery.of(context).padding, child: Padding( padding: const EdgeInsets.all(18.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox(height: 30), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -105,6 +117,29 @@ class _UserProfileScreenState extends State { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ + const SizedBox(width: 8), + Container( + width: 42, + height: 42, + decoration: ShapeDecoration( + color: const Color(0xFFFF9F1C), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + ), + ), + child: IconButton( + icon: const Icon(Icons.settings), + color: Colors.white, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SettingPage(), + ), + ); + }, + ), + ), const SizedBox(width: 8), Container( width: 42, @@ -419,7 +454,6 @@ class _UserProfileScreenState extends State { ), ), ), - ); } diff --git a/lib/screens/recycle/pickup_food.dart b/lib/screens/recycle/pickup_food.dart index 3688214..190e027 100644 --- a/lib/screens/recycle/pickup_food.dart +++ b/lib/screens/recycle/pickup_food.dart @@ -10,7 +10,6 @@ import 'package:image_picker/image_picker.dart'; import 'package:spoonshare/models/users/user.dart'; import 'package:spoonshare/screens/donate/thank_you.dart'; import 'package:spoonshare/widgets/auto_complete.dart'; -import 'package:spoonshare/widgets/bottom_navbar.dart'; import 'package:spoonshare/widgets/custom_text_field.dart'; import 'package:spoonshare/widgets/loader.dart'; import 'package:spoonshare/widgets/snackbar.dart'; @@ -593,34 +592,35 @@ class RecycleFoodScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('Recycle Food'), - backgroundColor: const Color(0xFF06D801), - titleTextStyle: const TextStyle( - color: Colors.white, - fontFamily: 'Lora', - fontSize: 18, - fontWeight: FontWeight.w700), - leading: IconButton( - icon: const Icon(Icons.arrow_back), + appBar: AppBar( + title: const Text('Recycle Food'), + backgroundColor: const Color(0xFF06D801), + titleTextStyle: const TextStyle( color: Colors.white, - onPressed: () { - Navigator.pop(context); - }, - ), + fontFamily: 'Lora', + fontSize: 18, + fontWeight: FontWeight.w700), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + color: Colors.white, + onPressed: () { + Navigator.pop(context); + }, ), - body: Container( - padding: const EdgeInsets.only(right: 20, left: 20, bottom: 20), - child: const SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 20), - RecycleFoodScreenContent(), - ], - ), + ), + body: Container( + padding: const EdgeInsets.only(right: 20, left: 20, bottom: 20), + child: const SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 20), + RecycleFoodScreenContent(), + ], ), ), - bottomNavigationBar: const BottomNavBar()); + ), + // bottomNavigationBar: const BottomNavBar() + ); } } diff --git a/lib/screens/recycle/recycle.dart b/lib/screens/recycle/recycle.dart index a7e296c..0330699 100644 --- a/lib/screens/recycle/recycle.dart +++ b/lib/screens/recycle/recycle.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:spoonshare/screens/recycle/pickup_food.dart'; class RecycleScreen extends StatelessWidget { @@ -7,13 +8,14 @@ class RecycleScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - body: Center( + body: Container( + padding: MediaQuery.of(context).padding, child: Container( - padding: const EdgeInsets.all(16.0), + padding: const EdgeInsets.symmetric(horizontal: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, + children: [ - const SizedBox(height: 32), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -33,21 +35,7 @@ class RecycleScreen extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Container( - width: 42, - height: 42, - decoration: ShapeDecoration( - color: Colors.black.withOpacity(0.08), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(50), - ), - ), - child: IconButton( - icon: const Icon(Icons.search), - onPressed: () {}, - ), - ), - const SizedBox(width: 8), + const SizedBox(width: 8), Container( width: 42, height: 42, @@ -80,13 +68,13 @@ class RecycleScreen extends StatelessWidget { ), ), ), - const SizedBox(height: 40), + const SizedBox(height: 20), Center( child: Column( children: [ Container( - width: 298, - height: 179, + width: 298.w, + height: 179.h, decoration: const BoxDecoration( image: DecorationImage( image: AssetImage("assets/images/recycle.png"), @@ -151,10 +139,10 @@ class RecycleScreen extends StatelessWidget { ), ), ), - const SizedBox(height: 50), + const SizedBox(height: 40), GestureDetector( onTap: () { - Navigator.pushReplacement( + Navigator.push( context, MaterialPageRoute( builder: (BuildContext context) => const RecycleFoodScreen(), diff --git a/lib/screens/volunteer/volunteer_form.dart b/lib/screens/volunteer/volunteer_form.dart index 85f6304..79da08f 100644 --- a/lib/screens/volunteer/volunteer_form.dart +++ b/lib/screens/volunteer/volunteer_form.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:spoonshare/models/users/user.dart'; +import 'package:spoonshare/widgets/bottom_navbar.dart'; import 'package:spoonshare/widgets/custom_text_field.dart'; import 'package:spoonshare/widgets/snackbar.dart'; diff --git a/lib/services/notifications_services.dart b/lib/services/notifications_services.dart new file mode 100644 index 0000000..365d828 --- /dev/null +++ b/lib/services/notifications_services.dart @@ -0,0 +1,196 @@ +import 'dart:io'; + +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:spoonshare/main.dart'; + +class NotificationServices { + /// Initialising firebase message plugin + FirebaseMessaging messaging = FirebaseMessaging.instance; + + /// Initialising firebase message plugin + final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + + /// Function to initialise flutter local notification plugin to show notifications for android when app is active + void initLocalNotifications( + BuildContext context, RemoteMessage message) async { + await messaging.setAutoInitEnabled(true); + var androidInitializationSettings = + const AndroidInitializationSettings('@mipmap/ic_launcher'); + var iosInitializationSettings = const DarwinInitializationSettings(); + + var initializationSetting = InitializationSettings( + android: androidInitializationSettings, iOS: iosInitializationSettings); + + await _flutterLocalNotificationsPlugin.initialize(initializationSetting, + onDidReceiveNotificationResponse: (payload) { + // handle interaction when app is active for android + handleMessage(context, message); + }); + } + + void firebaseInit()async { + requestNotificationPermission(); + print("Token: ${await getDeviceToken()}"); + + FirebaseMessaging.onMessage.listen((message) { + RemoteNotification? notification = message.notification; + AndroidNotification? android = message.notification!.android; + if (kDebugMode) { + print("notifications title:${notification!.title}"); + print("notifications body:${notification.body}"); + print("notifications image:${notification.android!.imageUrl}"); + print('count:${android!.count}'); + print('data:${message.data.toString()}'); + } + + if (Platform.isIOS) { + foregroundMessage(); + } + + if (Platform.isAndroid) { + initLocalNotifications(navigatorKey.currentState!.context, message); + showNotification(message); + } + }); + } + + Future requestNotificationPermission() async { + NotificationSettings settings = await messaging.requestPermission( + alert: true, + announcement: true, + badge: true, + carPlay: true, + criticalAlert: true, + provisional: true, + sound: true, + ); + + if (settings.authorizationStatus == AuthorizationStatus.authorized) { + if (kDebugMode) { + print('user granted permission'); + } + } else if (settings.authorizationStatus == + AuthorizationStatus.provisional) { + if (kDebugMode) { + print('user granted provisional permission'); + } + } else { + // appsetting.AppSettings.openNotificationSettings(); + if (kDebugMode) { + print('user denied permission'); + } + } + } + + /// Function to show visible notification when app is active + Future showNotification(RemoteMessage message) async { + // const AndroidNotificationChannel channel = AndroidNotificationChannel( + // 'high_importance_channel', + // 'High Importance Notifications', + // description: 'This channel is used for important notifications.', + // importance: Importance.max, + // ); + AndroidNotificationChannel channel = AndroidNotificationChannel( + message.notification!.android!.channelId.toString(), + message.notification!.android!.channelId.toString(), + importance: Importance.max, + ); + await _flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.createNotificationChannel(channel); + AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails( + channel.id.toString(), channel.name.toString(), + channelDescription: 'your channel description', + importance: Importance.high, + priority: Priority.high, + playSound: true, + ticker: 'ticker', + sound: channel.sound); + + const DarwinNotificationDetails darwinNotificationDetails = + DarwinNotificationDetails( + presentAlert: true, presentBadge: true, presentSound: true); + + NotificationDetails notificationDetails = NotificationDetails( + android: androidNotificationDetails, iOS: darwinNotificationDetails); + + Future.delayed(Duration.zero, () { + _flutterLocalNotificationsPlugin.show( + 0, + message.notification!.title.toString(), + message.notification!.body.toString(), + notificationDetails, + ); + }); + } + + /// Function to get device token on which we will send the notifications + Future getDeviceToken() async { + if (Platform.isIOS) { + String? apnsToken = await messaging.getAPNSToken(); + String? token = await messaging.getToken(); + if (apnsToken == null) { + await Future.delayed( + const Duration( + seconds: 3, + ), + ); + apnsToken = await messaging.getAPNSToken(); + } + print("Token: $token"); + } + String? token = await messaging.getToken(); + return token!; + } + + void isTokenRefresh() async { + messaging.onTokenRefresh.listen((event) { + event.toString(); + if (kDebugMode) { + print('refresh'); + } + }); + } + + /// Handle tap on notification when app is in background or terminated + Future setupInteractMessage(BuildContext context) async { + // when app is terminated + RemoteMessage? initialMessage = + await FirebaseMessaging.instance.getInitialMessage(); + + if (initialMessage != null) { + handleMessage(context, initialMessage); + } + + /// When app is in background + FirebaseMessaging.onMessageOpenedApp.listen((event) { + handleMessage(context, event); + }); + } + + void handleMessage(BuildContext context, RemoteMessage message) { + if (message.data['type'] == 'message') { + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => MessageScreen( + // id: message.data['id'], + // ))); + } + } + + Future foregroundMessage() async { + await FirebaseMessaging.instance + .setForegroundNotificationPresentationOptions( + alert: true, + badge: true, + sound: true, + ); + } +} diff --git a/lib/splash_screen.dart b/lib/splash_screen.dart index be3f241..4477120 100644 --- a/lib/splash_screen.dart +++ b/lib/splash_screen.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:spoonshare/constants/app_images.dart'; import 'package:spoonshare/screens/home/home.dart'; class SplashScreen extends StatefulWidget { @@ -28,8 +29,7 @@ class _SplashScreenState extends State { child: Container( width: ScreenUtil().screenWidth, height: ScreenUtil().screenHeight, - padding: EdgeInsets.symmetric( - horizontal: ScreenUtil().setWidth(77)), + padding: EdgeInsets.symmetric(horizontal: ScreenUtil().setWidth(77)), clipBehavior: Clip.antiAlias, decoration: const BoxDecoration(color: Color(0xFFFF9F1C)), child: Column( @@ -38,24 +38,22 @@ class _SplashScreenState extends State { Container( width: ScreenUtil().setWidth(130), height: ScreenUtil().setWidth(130), - decoration: const BoxDecoration( + decoration: BoxDecoration( image: DecorationImage( - image: AssetImage("assets/images/spoonshare.png"), + image: AssetImage(AppImages.spoonShareLogo), fit: BoxFit.cover, ), ), ), - SizedBox( - height: ScreenUtil().setHeight(10)), + SizedBox(height: ScreenUtil().setHeight(10)), Text( 'SpoonShare', style: TextStyle( color: Colors.white, - fontSize:24.sp, + fontSize: 24.sp, fontFamily: 'Lora', fontWeight: FontWeight.w700, - letterSpacing: - ScreenUtil().setWidth(0.96), + letterSpacing: ScreenUtil().setWidth(0.96), ), ), SizedBox(height: ScreenUtil().setHeight(5)), diff --git a/lib/utils/app_text_style.dart b/lib/utils/app_text_style.dart new file mode 100644 index 0000000..28cc051 --- /dev/null +++ b/lib/utils/app_text_style.dart @@ -0,0 +1,188 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:spoonshare/constants/app_constants.dart'; + +abstract class AppTextStyle { + static final TextStyle _baseTextStyle = TextStyle( + fontFamily: 'DM Sans', + color: AppColors.kBlackColor, + fontWeight: FontWeight.w600, + ); + + static TextStyle get mediumXXSmall { + return _baseTextStyle.copyWith( + fontSize: 16.sp, + fontWeight: AppFontWeight.outfitMedium, + ); + } + + static TextStyle get mediumLarge { + return _baseTextStyle.copyWith( + fontSize: 48.sp, + fontWeight: AppFontWeight.outfitMedium, + ); + } + + static TextStyle get mediumMedium { + return _baseTextStyle.copyWith( + fontSize: 36.sp, + fontWeight: AppFontWeight.outfitMedium, + ); + } + + static TextStyle get mediumXSmall { + return _baseTextStyle.copyWith( + fontSize: 20.sp, + fontWeight: AppFontWeight.outfitMedium, + ); + } + + static TextStyle get mediumSmall { + return _baseTextStyle.copyWith( + fontSize: 24.sp, + fontWeight: AppFontWeight.outfitMedium, + ); + } + + static TextStyle get mediumBase { + return _baseTextStyle.copyWith( + fontSize: 32.sp, + fontWeight: AppFontWeight.outfitMedium, + ); + } + + static TextStyle get regularXLarge { + return _baseTextStyle.copyWith( + fontSize: 20.sp, + fontWeight: AppFontWeight.outfitRegular, + ); + } + + static TextStyle get regularLarge { + return _baseTextStyle.copyWith( + fontSize: 18.sp, + fontWeight: AppFontWeight.outfitRegular, + ); + } + + static TextStyle get regularMedium { + return _baseTextStyle.copyWith( + fontSize: 16.sp, + fontWeight: AppFontWeight.outfitRegular, + ); + } + + static TextStyle get regularSmall { + return _baseTextStyle.copyWith( + fontSize: 12.sp, + fontWeight: AppFontWeight.outfitRegular, + ); + } + + static TextStyle get regularXSmall { + return _baseTextStyle.copyWith( + fontSize: 10.sp, + fontWeight: AppFontWeight.outfitRegular, + ); + } + + static TextStyle get regularBase { + return _baseTextStyle.copyWith( + fontSize: 14.sp, + fontWeight: AppFontWeight.outfitRegular, + ); + } +} + +abstract class AppFontWeight { + static const FontWeight outfitRegular = FontWeight.w400; + static const FontWeight outfitMedium = FontWeight.w500; + static const FontWeight outfitSemiBold = FontWeight.w600; + static const FontWeight outfitBold = FontWeight.w700; +} + +extension TextStyleExtension on TextStyle { + TextStyle withOpacity(double opacity) { + return copyWith(color: color!.withOpacity(opacity)); + } + + TextStyle withColor(Color newColor) { + return copyWith(color: newColor); + } +} + +extension DarkTextThemeExtension on TextStyle { + TextStyle convertToDark([Color? darkThemeColor]) { + return copyWith( + color: darkThemeColor ?? Colors.white, + ); + } +} + +extension FontFamily on TextStyle { + TextStyle get outfit { + return copyWith(fontFamily: 'Outfit'); + } +} + +extension FontWeightExtension on TextStyle { + TextStyle get bold { + return copyWith(fontWeight: AppFontWeight.outfitBold); + } + + TextStyle get regular { + return copyWith(fontWeight: AppFontWeight.outfitRegular); + } + + TextStyle get semiBold { + return copyWith(fontWeight: AppFontWeight.outfitSemiBold); + } + + TextStyle get medium { + return copyWith(fontWeight: AppFontWeight.outfitMedium); + } +} + +extension FixedColorExtension on TextStyle { + TextStyle get black { + return withColor(Colors.black); + } + + TextStyle get white { + return withColor(Colors.white); + } +} + +extension FontSizeExtension on TextStyle { + TextStyle get size16 { + return copyWith(fontSize: 16.sp); + } + + TextStyle get size12 { + return copyWith(fontSize: 12.sp); + } + + TextStyle get size13 { + return copyWith(fontSize: 13.sp); + } + + TextStyle get size14 { + return copyWith(fontSize: 14.sp); + } + + TextStyle get size18 { + return copyWith(fontSize: 18.sp); + } + + TextStyle get size20 { + return copyWith(fontSize: 20.sp); + } + + TextStyle get size22 { + return copyWith(fontSize: 22.sp); + } + +// TextStyle responsiveFont(BuildContext context) { +// return copyWith(fontSize: fontSize?.toResponsiveHeight(context)); +// } +} diff --git a/lib/utils/app_theme.dart b/lib/utils/app_theme.dart new file mode 100644 index 0000000..20ca33c --- /dev/null +++ b/lib/utils/app_theme.dart @@ -0,0 +1,142 @@ +// import 'package:bataiyo_mobile_app/constants/app_constants.dart'; +// import 'package:bataiyo_mobile_app/theme/app_text_style.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_screenutil/flutter_screenutil.dart'; +// +// abstract class AppTheme { +// AppTheme._(); +// +// static ThemeData appTheme = ThemeData.light().copyWith( +// scaffoldBackgroundColor: AppColors.kWhiteColor, +// appBarTheme: const AppBarTheme( +// elevation: 0, +// centerTitle: true, +// backgroundColor: AppColors.kWhiteColor, +// ), +// inputDecorationTheme: _inputDecorationTheme, +// outlinedButtonTheme: _outlinedButtonTheme, +// elevatedButtonTheme: _elevatedButtonTheme, +// colorScheme: _colorScheme, +// textTheme: _textTheme, +// navigationBarTheme: _navigationBarTheme, +// bottomNavigationBarTheme: _bottomNavigationBarTheme, +// textSelectionTheme: _textSelectionTheme); +// +// static TextSelectionThemeData get _textSelectionTheme => +// const TextSelectionThemeData( +// cursorColor: AppColors.kBlackColor, +// selectionColor: AppColors.kBaseBrandColor, +// selectionHandleColor: AppColors.kBaseBrandColor); +// +// static ColorScheme get _colorScheme => const ColorScheme( +// brightness: Brightness.light, +// primary: AppColors.kBaseBrandColor, +// onPrimary: AppColors.kWhiteColor, +// secondary: AppColors.kBasePurpleColor, +// onSecondary: AppColors.kWhiteColor, +// error: AppColors.kErrorColor, +// onError: AppColors.kWhiteColor, +// background: AppColors.kWhiteColor, +// onBackground: AppColors.kGrey500Color, +// surface: AppColors.kGrey50Color, +// onSurface: AppColors.kGrey500Color, +// ); +// +// static OutlinedButtonThemeData get _outlinedButtonTheme => +// OutlinedButtonThemeData( +// style: OutlinedButton.styleFrom( +// foregroundColor: AppColors.kBlackColor, +// fixedSize: const Size(335, 48), +// padding: EdgeInsets.zero, +// textStyle: AppTextStyle.mediumXXSmall, +// disabledForegroundColor: AppColors.kGrey200Color, +// side: const BorderSide(color: AppColors.kBlackColor), +// shape: RoundedRectangleBorder( +// borderRadius: BorderRadius.circular(100.r), +// ), +// ), +// ); +// +// static NavigationBarThemeData get _navigationBarTheme => +// NavigationBarThemeData( +// labelTextStyle: MaterialStateProperty.all(AppTextStyle.regularBase), +// backgroundColor: AppColors.kWhiteColor, +// ); +// +// static BottomNavigationBarThemeData get _bottomNavigationBarTheme => +// BottomNavigationBarThemeData( +// selectedLabelStyle: AppTextStyle.regularXSmall, +// backgroundColor: AppColors.kWhiteColor, +// ); +// +// static ElevatedButtonThemeData get _elevatedButtonTheme => +// ElevatedButtonThemeData( +// style: ElevatedButton.styleFrom( +// backgroundColor: AppColors.kBlackColor, +// disabledForegroundColor: AppColors.kGrey200Color, +// fixedSize: const Size(335, 48), +// padding: EdgeInsets.zero, +// textStyle: AppTextStyle.mediumXXSmall, +// foregroundColor: AppColors.kWhiteColor, +// shape: RoundedRectangleBorder( +// borderRadius: BorderRadius.circular(100.r), +// ), +// ), +// ); +// +// static InputDecorationTheme get _inputDecorationTheme => InputDecorationTheme( +// filled: true, +// fillColor: AppColors.kWhiteColor, +// suffixIconColor: AppColors.kGrey300Color, +// contentPadding: EdgeInsets.symmetric( +// vertical: 16.h, +// horizontal: 16.w, +// ), +// hintStyle: +// AppTextStyle.regularBase.copyWith(color: AppColors.kGrey200Color), +// errorStyle: +// AppTextStyle.regularSmall.copyWith(color: AppColors.kErrorColor), +// border: OutlineInputBorder( +// borderSide: BorderSide( +// width: 1.w, +// color: AppColors.kGrey200Color, +// ), +// borderRadius: BorderRadius.circular(8.r), +// ), +// enabledBorder: OutlineInputBorder( +// borderSide: BorderSide( +// width: 1.w, +// color: AppColors.kGrey200Color, +// ), +// borderRadius: BorderRadius.circular(8.r), +// ), +// focusedBorder: OutlineInputBorder( +// borderSide: BorderSide( +// width: 1.w, +// color: AppColors.kBlackColor, +// ), +// borderRadius: BorderRadius.circular(8.r), +// ), +// errorBorder: OutlineInputBorder( +// borderSide: BorderSide( +// width: 1.w, +// color: AppColors.kErrorColor, +// ), +// borderRadius: BorderRadius.circular(8.r), +// ), +// ); +// +// static TextTheme get _textTheme => TextTheme( +// displayLarge: AppTextStyle.mediumXXSmall, +// displayMedium: AppTextStyle.mediumLarge, +// displaySmall: AppTextStyle.mediumMedium, +// headlineMedium: AppTextStyle.mediumXSmall, +// headlineSmall: AppTextStyle.mediumSmall, +// titleLarge: AppTextStyle.mediumBase, +// titleMedium: AppTextStyle.regularXLarge, +// titleSmall: AppTextStyle.regularLarge, +// bodyLarge: AppTextStyle.regularMedium, +// bodyMedium: AppTextStyle.regularSmall, +// labelLarge: AppTextStyle.regularBase, +// ); +// } diff --git a/lib/widgets/app_exit_dialog.dart b/lib/widgets/app_exit_dialog.dart new file mode 100644 index 0000000..e1416f3 --- /dev/null +++ b/lib/widgets/app_exit_dialog.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class ExitConfirmationDialog extends StatelessWidget { + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text('Exit App?'), + content: Text('Do you want to exit the app?'), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(false); // No + }, + child: Text('No'), + ), + TextButton( + onPressed: () { + Navigator.pop(context); + SystemNavigator.pop(); + }, + child: Text('Yes'), + ), + ], + ); + } +} diff --git a/lib/widgets/bottom_navbar.dart b/lib/widgets/bottom_navbar.dart index 7dc3c43..452d4e7 100644 --- a/lib/widgets/bottom_navbar.dart +++ b/lib/widgets/bottom_navbar.dart @@ -1,120 +1,120 @@ -import 'package:flutter/material.dart'; -import 'package:spoonshare/models/users/user.dart'; -import 'package:spoonshare/screens/dashboard/dashboard_home.dart'; -import 'package:spoonshare/screens/donate/donate_page.dart'; -import 'package:spoonshare/screens/home/home_page.dart'; -import 'package:spoonshare/screens/recycle/recycle.dart'; -import 'package:spoonshare/screens/profile/user_profile.dart'; - -class BottomNavBar extends StatelessWidget { - const BottomNavBar({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final UserProfile userProfile = UserProfile(); - final String name = userProfile.getFullName(); - final String role = userProfile.getRole(); - - return Container( - width: MediaQuery.of(context).size.width, - height: 67, - padding: const EdgeInsets.symmetric(vertical: 8), - decoration: BoxDecoration( - color: const Color(0xFFF0F2F3).withOpacity(0.5), - border: Border.all(color: const Color(0xFFF0F2F3)), - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _buildNavItem(context, Icons.home, 'Home', const Color(0xFFFF9F1C), - Colors.black54, HomePage(name: name, role: role)), - _buildNavItem( - context, - Icons.dashboard, - 'Dashboard', - const Color(0xFFFF9F1C), - Colors.black54, - DashboardHomePage( - role: role, - )), - _buildNavItem(context, Icons.add_circle, 'Donate Food', - const Color(0xFFFF9F1C), Colors.black54, const DonatePage()), - _buildNavItem(context, Icons.recycling, 'Recycle', - const Color(0xFF06D801), Colors.black54, const RecycleScreen()), - _buildNavItem( - context, - Icons.person, - 'Profile', - const Color(0xFFFF9F1C), - Colors.black54, - UserProfileScreen( - name: name, - role: role, - )), - ], - ), - ); - } - - Widget _buildNavItem(BuildContext context, IconData icon, String label, - Color iconColor, Color textColor, Widget destination) { - bool isSelected = ModalRoute.of(context)?.settings.name == label; - - return Expanded( - child: InkWell( - onTap: () { - if (isSelected) { - Navigator.popUntil(context, (route) => route.isFirst); - } else { - Navigator.push( - context, - PageRouteBuilder( - pageBuilder: (context, animation, secondaryAnimation) => - destination, - transitionsBuilder: - (context, animation, secondaryAnimation, child) { - const begin = Offset(-1.0, 0.0); - const end = Offset.zero; - const curve = Curves.easeInOutQuart; - var tween = Tween(begin: begin, end: end) - .chain(CurveTween(curve: curve)); - var offsetAnimation = animation.drive(tween); - return SlideTransition( - position: offsetAnimation, child: child); - }, - ), - ); - } - }, - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Icon( - icon, - size: 26, - color: isSelected ? Colors.black54 : iconColor, - ), - const SizedBox(height: 2), - Text( - label, - style: TextStyle( - color: isSelected - ? Colors.orange - : textColor, - fontSize: 12, - fontFamily: 'Roboto', - fontWeight: FontWeight.w700, - ), - ), - ], - ), - ), - ); - } -} +// import 'package:flutter/material.dart'; +// import 'package:spoonshare/models/users/user.dart'; +// import 'package:spoonshare/screens/dashboard/dashboard_home.dart'; +// import 'package:spoonshare/screens/donate/donate_page.dart'; +// import 'package:spoonshare/screens/home/home_page.dart'; +// import 'package:spoonshare/screens/recycle/recycle.dart'; +// import 'package:spoonshare/screens/profile/user_profile.dart'; +// +// class BottomNavBar extends StatelessWidget { +// const BottomNavBar({Key? key}) : super(key: key); +// +// @override +// Widget build(BuildContext context) { +// final UserProfile userProfile = UserProfile(); +// final String name = userProfile.getFullName(); +// final String role = userProfile.getRole(); +// +// return Container( +// width: MediaQuery.of(context).size.width, +// height: 67, +// padding: const EdgeInsets.symmetric(vertical: 8), +// decoration: BoxDecoration( +// color: const Color(0xFFF0F2F3).withOpacity(0.5), +// border: Border.all(color: const Color(0xFFF0F2F3)), +// borderRadius: const BorderRadius.only( +// topLeft: Radius.circular(20), +// topRight: Radius.circular(20), +// ), +// ), +// child: Row( +// mainAxisAlignment: MainAxisAlignment.spaceAround, +// children: [ +// _buildNavItem(context, Icons.home, 'Home', const Color(0xFFFF9F1C), +// Colors.black54, HomePage(name: name, role: role)), +// _buildNavItem( +// context, +// Icons.dashboard, +// 'Dashboard', +// const Color(0xFFFF9F1C), +// Colors.black54, +// DashboardHomePage( +// role: role, +// )), +// _buildNavItem(context, Icons.add_circle, 'Donate Food', +// const Color(0xFFFF9F1C), Colors.black54, const DonatePage()), +// _buildNavItem(context, Icons.recycling, 'Recycle', +// const Color(0xFF06D801), Colors.black54, const RecycleScreen()), +// _buildNavItem( +// context, +// Icons.person, +// 'Profile', +// const Color(0xFFFF9F1C), +// Colors.black54, +// UserProfileScreen( +// name: name, +// role: role, +// )), +// ], +// ), +// ); +// } +// +// Widget _buildNavItem(BuildContext context, IconData icon, String label, +// Color iconColor, Color textColor, Widget destination) { +// bool isSelected = ModalRoute.of(context)?.settings.name == label; +// +// return Expanded( +// child: InkWell( +// onTap: () { +// if (isSelected) { +// Navigator.popUntil(context, (route) => route.isFirst); +// } else { +// Navigator.push( +// context, +// PageRouteBuilder( +// pageBuilder: (context, animation, secondaryAnimation) => +// destination, +// transitionsBuilder: +// (context, animation, secondaryAnimation, child) { +// const begin = Offset(-1.0, 0.0); +// const end = Offset.zero; +// const curve = Curves.easeInOutQuart; +// var tween = Tween(begin: begin, end: end) +// .chain(CurveTween(curve: curve)); +// var offsetAnimation = animation.drive(tween); +// return SlideTransition( +// position: offsetAnimation, child: child); +// }, +// ), +// ); +// } +// }, +// child: Column( +// mainAxisSize: MainAxisSize.min, +// mainAxisAlignment: MainAxisAlignment.start, +// crossAxisAlignment: CrossAxisAlignment.center, +// children: [ +// Icon( +// icon, +// size: 26, +// color: isSelected ? Colors.black54 : iconColor, +// ), +// const SizedBox(height: 2), +// Text( +// label, +// style: TextStyle( +// color: isSelected +// ? Colors.orange +// : textColor, +// fontSize: 12, +// fontFamily: 'Roboto', +// fontWeight: FontWeight.w700, +// ), +// ), +// ], +// ), +// ), +// ); +// } +// } diff --git a/lib/widgets/foodcards/nearby_daily_cards.dart b/lib/widgets/foodcards/nearby_daily_cards.dart index b0f154a..995406a 100644 --- a/lib/widgets/foodcards/nearby_daily_cards.dart +++ b/lib/widgets/foodcards/nearby_daily_cards.dart @@ -1,7 +1,14 @@ import 'package:flutter/material.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:geolocator/geolocator.dart'; +import 'package:spoonshare/constants/app_constants.dart'; import 'package:spoonshare/screens/fooddetails/food_details.dart'; +import 'package:spoonshare/utils/app_text_style.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:http/http.dart' as http; +import 'dart:io'; +import 'package:path_provider/path_provider.dart'; class NearbyDailyFoodCard extends StatelessWidget { const NearbyDailyFoodCard({Key? key, this.dailyActive = false}) @@ -52,116 +59,129 @@ class NearbyDailyFoodCard extends StatelessWidget { return Text('Error: ${snapshot.error}'); } - return Card( - margin: const EdgeInsets.all(8), - elevation: 8, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - height: 200, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - image: DecorationImage( - image: NetworkImage(data['imageUrl'] ?? ''), - fit: BoxFit.cover, + return Padding( + padding: EdgeInsets.only(bottom: 24.h), + child: GestureDetector( + onTap: () => _navigateToFoodDetails(context, data), + child: Container( + width: 312.w, + height: 220.h, + decoration: BoxDecoration( + color: AppColors.basePrimaryColor, + borderRadius: BorderRadius.circular(10.r), + boxShadow: [ + BoxShadow( + blurRadius: 5, + color: AppColors.kBlackColor.withOpacity(0.4), ), + ], + image: DecorationImage( + image: NetworkImage(data['imageUrl'] ?? ''), + fit: BoxFit.cover, ), ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - const Icon(Icons.location_on, size: 16), - const SizedBox(width: 4), - Flexible( - child: Text( - data['venue'], - style: const TextStyle( - fontSize: 16, - fontFamily: 'DM Sans', - fontWeight: FontWeight.w700, + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + vertical: 10.0, + horizontal: 10.0, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 219.0, + decoration: BoxDecoration( + color: AppColors.kWhiteColor.withOpacity(0.6), + borderRadius: BorderRadius.circular(32.0), + border: + Border.all(color: AppColors.kWhiteColor), + ), + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 7.0, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const Icon(Icons.location_on_outlined), + const SizedBox(width: 4.0), + Expanded( + child: Text( + _truncateText(data['venue'], 20), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), ), - textAlign: TextAlign.center, - ), + const Spacer(), + Column( + children: [ + _circularButton( + icon: Icons.favorite_border_outlined, + onTap: () {}, + ), + const SizedBox(height: 12.0), + _circularButton( + icon: Icons.share_rounded, + onTap: () => _shareFoodCard(data), + ), + ], + ), + ], ), - const SizedBox(width: 8), - if (isNGOVerified) - const Icon(Icons.verified, - color: Colors.green, size: 16), - if (!isNGOVerified) - const Icon(Icons.cancel, color: Colors.red, size: 16), - ], - ), - const SizedBox(height: 6), - Text( - data['address'], - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w700, - fontFamily: "DM Sans", - ), - ), - ], - ), - ), - const Divider(height: 0), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Text( - 'Uploaded By: ${data['fullName']}', - style: const TextStyle( - fontSize: 14, - fontFamily: 'DM Sans', - fontWeight: FontWeight.w700, - ), - ), - Text( - 'Food Type: ${data['foodType']}', - style: const TextStyle( - fontSize: 14, - fontFamily: 'DM Sans', - fontWeight: FontWeight.w700, - ), - ), - ], - ), - ), - const Divider(height: 0), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Text( - 'Uploaded: $uploadTime', - style: const TextStyle( - fontSize: 14, - fontFamily: 'DM Sans', - fontWeight: FontWeight.w700, ), - ), - Text( - 'Daily: ${data['dailyActive']}', - style: const TextStyle( - fontSize: 14, - fontFamily: 'DM Sans', - fontWeight: FontWeight.w700, + const Spacer(), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 8.0, + ), + child: Container( + padding: + const EdgeInsets.fromLTRB(16.0, 18.0, 16.0, 11.0), + decoration: BoxDecoration( + color: AppColors.kWhiteColor, + borderRadius: BorderRadius.circular(10.0), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _customText( + headerText: 'UPLOADED BY:', + text: '${data['fullName']}', + ), + _customVerticalDivider(), + _customText( + headerText: 'FOOD TYPE:', + text: '${data['foodType']}', + ), + _customVerticalDivider(), + _customText( + headerText: 'UPLOAD TIME:', + text: uploadTime, + ), + ], + ), + ), ), + ], + ), + if (isNGOVerified) + Positioned( + left: 100.w, + bottom: 62.h, + child: ngoVerifiedWidget(), ), - ], - ), + ], ), - ], + ), ), ); }, @@ -176,8 +196,7 @@ class NearbyDailyFoodCard extends StatelessWidget { .doc('sharedfood') .collection('foodData') .where('dailyActive', isEqualTo: dailyActive) - .where('verified', - isEqualTo: true) + .where('verified', isEqualTo: true) .orderBy('timestamp', descending: true) .snapshots(), builder: (context, snapshot) { @@ -194,7 +213,8 @@ class NearbyDailyFoodCard extends StatelessWidget { if (positionSnapshot.connectionState == ConnectionState.waiting) { return Container( alignment: Alignment.center, - child: const CircularProgressIndicator(), + child: + const CircularProgressIndicator(color: Color(0xffFF9F1C)), ); } else if (positionSnapshot.hasError) { return Text('Error: ${positionSnapshot.error}'); @@ -245,3 +265,152 @@ class NearbyDailyFoodCard extends StatelessWidget { ); } } + +// Helper function to truncate text +String _truncateText(String text, int maxLength) { + return (text.length > maxLength) + ? '${text.substring(0, maxLength)}...' + : text; +} + +Widget ngoVerifiedWidget() { + return Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 3.h), + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50.r), + ), + color: AppColors.kGreenColor), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.check_circle_outline, + color: AppColors.kWhiteColor, + ), + SizedBox( + width: 4.w, + ), + Text( + "NGO VERIFIED", + style: + AppTextStyle.regularSmall.copyWith(color: AppColors.kWhiteColor), + ), + ], + ), + ); +} + +// Helper function to build circular buttons +Widget _circularButton({required IconData icon, required Function() onTap}) { + return GestureDetector( + onTap: onTap, + child: CircleAvatar( + radius: 20, + backgroundColor: AppColors.kWhiteColor.withOpacity(0.6), + child: Icon( + icon, + color: AppColors.kBlackColor, + ), + ), + ); +} + +// Helper function to build custom text widgets +Widget _customText({required String headerText, required String text}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + headerText, + style: TextStyle( + fontSize: 10.sp, + color: Colors.grey, + ), + ), + const SizedBox(height: 4.0), + Text( + text, + style: TextStyle( + fontSize: 12.sp, + color: AppColors.kBlackColor, + ), + ), + ], + ); +} + +// Helper function to build custom vertical divider +Widget _customVerticalDivider() { + return Container( + height: 20.0, + width: 1.0, + color: Colors.grey, + margin: const EdgeInsets.symmetric(horizontal: 8.0), + ); +} + +Future _shareImageFromUrl( + String imageUrl, + String venue, + String foodType, + String address, + String community, + String fromdate, + String fromtime, + String todate, + String totime, + bool dailyactive, + GeoPoint location) async { + try { + http.Response response = await http.get(Uri.parse(imageUrl)); + + Directory tempDir = await getTemporaryDirectory(); + File imageFile = File('${tempDir.path}/food_image.jpg'); + + await imageFile.writeAsBytes(response.bodyBytes); + + double latitude = location.latitude; + double longitude = location.longitude; + + String formattedVenue = venue.replaceAll(' ', '%20'); + + String googleMapsUrl = + 'https://www.google.com/maps/dir/?api=1&destination=$formattedVenue&destination_place_id=$latitude,$longitude'; + + await Share.shareXFiles( + [XFile(imageFile.path)], + text: ''' +Check out this free food at $venue. +They are offering $foodType, at $address, for the community $community. +The food will be available from $fromdate $fromtime to $todate $totime. +Daily Active: $dailyactive + +You can directly go and have food : $googleMapsUrl, + + +Download the SpoonShare app to find more such free food near you: +https://spoonshare.vercel.app/ +''', + ); + } catch (e) { + print('Error sharing image: $e'); + } +} + +void _shareFoodCard(Map data) { + String venue = data['venue']; + String imageUrl = data['imageUrl'] ?? ''; + String foodType = data['foodType']; + String address = data['address']; + String community = data['community'] ?? 'N/A'; + bool dailyActive = data['dailyActive']; + String fromdate = data['date'] ?? ''; + String fromtime = data['time'] ?? ''; + String todate = data['toDate'] ?? ''; + String totime = data['toTime'] ?? ''; + GeoPoint location = data['location']; + + _shareImageFromUrl(imageUrl, venue, foodType, address, community, fromdate, + fromtime, todate, totime, dailyActive, location); +} diff --git a/lib/widgets/foodcards/nearby_food_cards.dart b/lib/widgets/foodcards/nearby_food_cards.dart index 0750542..08085c1 100644 --- a/lib/widgets/foodcards/nearby_food_cards.dart +++ b/lib/widgets/foodcards/nearby_food_cards.dart @@ -1,8 +1,15 @@ import 'package:flutter/material.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:geolocator/geolocator.dart'; import 'package:intl/intl.dart'; +import 'package:spoonshare/constants/app_constants.dart'; import 'package:spoonshare/screens/fooddetails/food_details.dart'; +import 'package:spoonshare/utils/app_text_style.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:http/http.dart' as http; +import 'dart:io'; +import 'package:path_provider/path_provider.dart'; class NearbyFoodCard extends StatelessWidget { const NearbyFoodCard({super.key}); @@ -51,104 +58,129 @@ class NearbyFoodCard extends StatelessWidget { return Text('Error: ${snapshot.error}'); } - return Card( - margin: const EdgeInsets.all(8), - elevation: 8, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - height: 200, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - image: DecorationImage( - image: NetworkImage(data['imageUrl'] ?? ''), - fit: BoxFit.cover, + return Padding( + padding: EdgeInsets.only(bottom: 24.h), + child: GestureDetector( + onTap: () => _navigateToFoodDetails(context, data), + child: Container( + width: 312.w, + height: 220.h, + decoration: BoxDecoration( + color: AppColors.basePrimaryColor, + borderRadius: BorderRadius.circular(10.r), + boxShadow: [ + BoxShadow( + blurRadius: 5, + color: AppColors.kBlackColor.withOpacity(0.4), ), + ], + image: DecorationImage( + image: NetworkImage(data['imageUrl'] ?? ''), + fit: BoxFit.cover, ), ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - const Icon(Icons.location_on, size: 16), - const SizedBox(height: 6), - Flexible( - child: Text( - data['venue'], - style: const TextStyle( - fontSize: 16, - fontFamily: 'DM Sans', - fontWeight: FontWeight.w700, + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + vertical: 10.0, + horizontal: 10.0, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 219.0, + decoration: BoxDecoration( + color: AppColors.kWhiteColor.withOpacity(0.6), + borderRadius: BorderRadius.circular(32.0), + border: + Border.all(color: AppColors.kWhiteColor), + ), + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 7.0, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const Icon(Icons.location_on_outlined), + const SizedBox(width: 4.0), + Expanded( + child: Text( + _truncateText(data['venue'], 20), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), ), - textAlign: TextAlign.center, - ), + const Spacer(), + Column( + children: [ + _circularButton( + icon: Icons.favorite_border_outlined, + onTap: () {}, + ), + const SizedBox(height: 12.0), + _circularButton( + icon: Icons.share_rounded, + onTap: () => _shareFoodCard(data), + ), + ], + ), + ], ), - const SizedBox(width: 8), // Add some spacing here - if (isNGOVerified) - const Icon(Icons.verified, - color: Colors.green, size: 16), - if (!isNGOVerified) - const Icon(Icons.cancel, color: Colors.red, size: 16), - ], - ), - const SizedBox(height: 6), - Text( - data['address'], - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w700, - fontFamily: "DM Sans", - ), - ), - ], - ), - ), - const Divider(height: 0), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Text( - 'Uploaded By: ${data['fullName']}', - style: const TextStyle( - fontSize: 14, - fontFamily: 'DM Sans', - fontWeight: FontWeight.w700, ), - ), - Text( - 'Food Type: ${data['foodType']}', - style: const TextStyle( - fontSize: 14, - fontFamily: 'DM Sans', - fontWeight: FontWeight.w700, + const Spacer(), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 8.0, + ), + child: Container( + padding: + const EdgeInsets.fromLTRB(16.0, 18.0, 16.0, 11.0), + decoration: BoxDecoration( + color: AppColors.kWhiteColor, + borderRadius: BorderRadius.circular(10.0), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _customText( + headerText: 'UPLOADED BY:', + text: '${data['fullName']}', + ), + _customVerticalDivider(), + _customText( + headerText: 'FOOD TYPE:', + text: '${data['foodType']}', + ), + _customVerticalDivider(), + _customText( + headerText: 'UPLOAD TIME:', + text: uploadTime, + ), + ], + ), + ), ), - ), - ], - ), - ), - Padding( - padding: - const EdgeInsets.only(top: 4, left: 8, right: 8, bottom: 8), - child: Text( - 'Uploaded: $uploadTime', - style: const TextStyle( - fontSize: 14, - fontFamily: 'DM Sans', - fontWeight: FontWeight.w700, + ], ), - textAlign: TextAlign.center, - ), + if (isNGOVerified) + Positioned( + left: 100.w, + bottom: 62.h, + child: ngoVerifiedWidget(), + ), + ], ), - ], + ), ), ); }, @@ -178,7 +210,8 @@ class NearbyFoodCard extends StatelessWidget { if (positionSnapshot.connectionState == ConnectionState.waiting) { return Container( alignment: Alignment.center, - child: const CircularProgressIndicator(), + child: + const CircularProgressIndicator(color: Color(0xffFF9F1C)), ); } else if (positionSnapshot.hasError) { return Text('Error: ${positionSnapshot.error}'); @@ -265,3 +298,152 @@ class NearbyFoodCard extends StatelessWidget { return combinedDateTime.isBefore(currentDateTime); } } + +Widget ngoVerifiedWidget() { + return Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 3.h), + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50.r), + ), + color: AppColors.kGreenColor), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.check_circle_outline, + color: AppColors.kWhiteColor, + ), + SizedBox( + width: 4.w, + ), + Text( + "NGO VERIFIED", + style: + AppTextStyle.regularSmall.copyWith(color: AppColors.kWhiteColor), + ), + ], + ), + ); +} + +// Helper function to truncate text +String _truncateText(String text, int maxLength) { + return (text.length > maxLength) + ? '${text.substring(0, maxLength)}...' + : text; +} + +// Helper function to build circular buttons +Widget _circularButton({required IconData icon, required Function() onTap}) { + return GestureDetector( + onTap: onTap, + child: CircleAvatar( + radius: 20, + backgroundColor: AppColors.kWhiteColor.withOpacity(0.6), + child: Icon( + icon, + color: AppColors.kBlackColor, + ), + ), + ); +} + +// Helper function to build custom text widgets +Widget _customText({required String headerText, required String text}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + headerText, + style: TextStyle( + fontSize: 10.sp, + color: Colors.grey, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 4.0), + Text( + text, + style: TextStyle( + fontSize: 12.sp, + color: AppColors.kBlackColor, + ), + ), + ], + ); +} + +Widget _customVerticalDivider() { + return Container( + height: 20.0, + width: 1.0, + color: Colors.grey, + margin: const EdgeInsets.symmetric(horizontal: 8.0), + ); +} + +Future _shareImageFromUrl( + String imageUrl, + String venue, + String foodType, + String address, + String community, + String fromdate, + String fromtime, + String todate, + String totime, + bool dailyactive, + GeoPoint location) async { + try { + http.Response response = await http.get(Uri.parse(imageUrl)); + + Directory tempDir = await getTemporaryDirectory(); + File imageFile = File('${tempDir.path}/food_image.jpg'); + + await imageFile.writeAsBytes(response.bodyBytes); + + double latitude = location.latitude; + double longitude = location.longitude; + + String formattedVenue = venue.replaceAll(' ', '%20'); + + String googleMapsUrl = + 'https://www.google.com/maps/dir/?api=1&destination=$formattedVenue&destination_place_id=$latitude,$longitude'; + + await Share.shareXFiles( + [XFile(imageFile.path)], + text: ''' +Check out this free food at $venue. +They are offering $foodType, at $address, for the community $community. +The food will be available from $fromdate $fromtime to $todate $totime. +Daily Active: $dailyactive + +You can directly go and have food : $googleMapsUrl, + + +Download the SpoonShare app to find more such free food near you: +https://spoonshare.vercel.app/ +''', + ); + } catch (e) { + print('Error sharing image: $e'); + } +} + +void _shareFoodCard(Map data) { + String venue = data['venue']; + String imageUrl = data['imageUrl'] ?? ''; + String foodType = data['foodType']; + String address = data['address']; + String community = data['community'] ?? 'N/A'; + bool dailyActive = data['dailyActive']; + String fromdate = data['date'] ?? ''; + String fromtime = data['time'] ?? ''; + String todate = data['toDate'] ?? ''; + String totime = data['toTime'] ?? ''; + GeoPoint location = data['location']; + + _shareImageFromUrl(imageUrl, venue, foodType, address, community, fromdate, + fromtime, todate, totime, dailyActive, location); +} diff --git a/lib/widgets/foodcards/past_food_cards.dart b/lib/widgets/foodcards/past_food_cards.dart index 33e796a..d958883 100644 --- a/lib/widgets/foodcards/past_food_cards.dart +++ b/lib/widgets/foodcards/past_food_cards.dart @@ -1,8 +1,15 @@ import 'package:flutter/material.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:geolocator/geolocator.dart'; import 'package:intl/intl.dart'; +import 'package:spoonshare/constants/app_constants.dart'; import 'package:spoonshare/screens/fooddetails/food_details.dart'; +import 'package:spoonshare/utils/app_text_style.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:http/http.dart' as http; +import 'dart:io'; +import 'package:path_provider/path_provider.dart'; class PastFoodCard extends StatelessWidget { const PastFoodCard({super.key}); @@ -41,124 +48,143 @@ class PastFoodCard extends StatelessWidget { ); } - Widget buildCard(Map data, String uploadTime, - bool isNGOVerified, Position userLocation) { - GeoPoint foodLocation = data['location']; - return FutureBuilder( - future: _calculateDistance(foodLocation, userLocation), - builder: (context, snapshot) { - if (snapshot.hasError) { - return Text('Error: ${snapshot.error}'); - } +Widget buildCard(Map data, String uploadTime, + bool isNGOVerified, Position userLocation) { + GeoPoint foodLocation = data['location']; + return FutureBuilder( + future: _calculateDistance(foodLocation, userLocation), + builder: (context, snapshot) { + if (snapshot.hasError) { + return Text('Error: ${snapshot.error}'); + } - return Card( - margin: const EdgeInsets.all(8), - elevation: 8, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - color: Colors.black.withOpacity(0.2), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - height: 200, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - image: DecorationImage( - image: NetworkImage(data['imageUrl'] ?? ''), - fit: BoxFit.cover, - ), + return Padding( + padding: EdgeInsets.only(bottom: 24.h), + child: GestureDetector( + onTap: () => _navigateToFoodDetails(context, data), + child: Container( + width: 312.w, + height: 220.h, + decoration: BoxDecoration( + color: AppColors.basePrimaryColor, + borderRadius: BorderRadius.circular(10.r), + boxShadow: [ + BoxShadow( + blurRadius: 5, + color: AppColors.kBlackColor.withOpacity(0.4), ), + ], + image: DecorationImage( + image: NetworkImage(data['imageUrl'] ?? ''), + fit: BoxFit.cover, ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Column( + ), + child: Stack( + children: [ + Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - const Icon(Icons.location_on, size: 16), - const SizedBox(width: 4), - Flexible( - child: Text( - data['venue'], - style: const TextStyle( - fontSize: 16, - fontFamily: 'DM Sans', - fontWeight: FontWeight.w700, + Padding( + padding: const EdgeInsets.symmetric( + vertical: 10.0, + horizontal: 10.0, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 219.0, + decoration: BoxDecoration( + color: AppColors.kWhiteColor.withOpacity(0.6), + borderRadius: BorderRadius.circular(32.0), + border: Border.all(color: AppColors.kWhiteColor), + ), + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 7.0, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const Icon(Icons.location_on_outlined), + const SizedBox(width: 4.0), + Expanded( + child: Text( + _truncateText(data['venue'], 20), + overflow: TextOverflow.ellipsis, + ), + ), + ], ), - textAlign: TextAlign.center, ), - ), - const SizedBox(width: 8), // Add some spacing here - if (isNGOVerified) - const Icon(Icons.verified, - color: Colors.green, size: 16), - if (!isNGOVerified) - const Icon(Icons.cancel, color: Colors.red, size: 16), - ], - ), - const SizedBox(height: 6), - Text( - data['address'], - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w700, - color: Colors.white, - fontFamily: "DM Sans", + const Spacer(), + Column( + children: [ + _circularButton( + icon: Icons.favorite_border_outlined, + onTap: () {}, + ), + const SizedBox(height: 12.0), + _circularButton( + icon: Icons.share_rounded, + onTap: () => _shareFoodCard(data), + ), + ], + ), + ], ), ), - ], - ), - ), - const Divider(height: 0), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Text( - 'Uploaded By: ${data['fullName']}', - style: const TextStyle( - fontSize: 14, - fontFamily: 'DM Sans', - color: Colors.white, - fontWeight: FontWeight.w700, + const Spacer(), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 8.0, ), - ), - Text( - 'Food Type: ${data['foodType']}', - style: const TextStyle( - fontSize: 14, - color: Colors.white, - fontFamily: 'DM Sans', - fontWeight: FontWeight.w700, + child: Container( + padding: + const EdgeInsets.fromLTRB(16.0, 18.0, 16.0, 11.0), + decoration: BoxDecoration( + color: AppColors.kWhiteColor, + borderRadius: BorderRadius.circular(10.0), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _customText( + headerText: 'UPLOADED BY:', + text: '${data['fullName']}', + ), + _customVerticalDivider(), + _customText( + headerText: 'FOOD TYPE:', + text: '${data['foodType']}', + ), + _customVerticalDivider(), + _customText( + headerText: 'UPLOAD TIME:', + text: uploadTime, + ), + ], + ), ), ), ], ), - ), - Padding( - padding: - const EdgeInsets.only(top: 4, left: 8, right: 8, bottom: 8), - child: Text( - 'Uploaded: $uploadTime', - style: const TextStyle( - fontSize: 14, - color: Colors.white, - fontFamily: 'DM Sans', - fontWeight: FontWeight.w700, + if (isNGOVerified) + Positioned( + left: 100.w, + bottom: 62.h, + child: ngoVerifiedWidget(), ), - textAlign: TextAlign.center, - ), - ), - ], + ], + ), ), - ); - }, - ); - } + ), + ); + }, + ); +} @override Widget build(BuildContext context) { @@ -183,7 +209,8 @@ class PastFoodCard extends StatelessWidget { if (positionSnapshot.connectionState == ConnectionState.waiting) { return Container( alignment: Alignment.center, - child: const CircularProgressIndicator(), + child: + const CircularProgressIndicator(color: Color(0xffFF9F1C)), ); } else if (positionSnapshot.hasError) { return Text('Error: ${positionSnapshot.error}'); @@ -191,7 +218,6 @@ class PastFoodCard extends StatelessWidget { Position userLocation = positionSnapshot.data!; - // Filter and sort food docs by location and verification status // Filter and sort food docs by location and verification status foodDocs = foodDocs.where((doc) { bool isVerified = doc.data()['verified'] ?? false; @@ -268,3 +294,153 @@ class PastFoodCard extends StatelessWidget { return combinedDateTime.isBefore(currentDateTime); } } + +// Helper function to truncate text +String _truncateText(String text, int maxLength) { + return (text.length > maxLength) + ? '${text.substring(0, maxLength)}...' + : text; +} + +Widget ngoVerifiedWidget() { + return Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 3.h), + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50.r), + ), + color: AppColors.kGreenColor), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.check_circle_outline, + color: AppColors.kWhiteColor, + ), + SizedBox( + width: 4.w, + ), + Text( + "NGO VERIFIED", + style: + AppTextStyle.regularSmall.copyWith(color: AppColors.kWhiteColor), + ), + ], + ), + ); +} + +Future _shareImageFromUrl( + String imageUrl, + String venue, + String foodType, + String address, + String community, + String fromdate, + String fromtime, + String todate, + String totime, + bool dailyactive, + GeoPoint location) async { + try { + http.Response response = await http.get(Uri.parse(imageUrl)); + + Directory tempDir = await getTemporaryDirectory(); + File imageFile = File('${tempDir.path}/food_image.jpg'); + + await imageFile.writeAsBytes(response.bodyBytes); + + double latitude = location.latitude; + double longitude = location.longitude; + + String formattedVenue = venue.replaceAll(' ', '%20'); + + String googleMapsUrl = + 'https://www.google.com/maps/dir/?api=1&destination=$formattedVenue&destination_place_id=$latitude,$longitude'; + + await Share.shareXFiles( + [XFile(imageFile.path)], + text: ''' +Check out this free food at $venue. +They are offering $foodType, at $address, for the community $community. +The food will be available from $fromdate $fromtime to $todate $totime. +Daily Active: $dailyactive + +You can directly go and have food : $googleMapsUrl, + + +Download the SpoonShare app to find more such free food near you: +https://spoonshare.vercel.app/ +''', + ); + } catch (e) { + print('Error sharing image: $e'); + } +} + +void _shareFoodCard(Map data) { + String venue = data['venue']; + String imageUrl = data['imageUrl'] ?? ''; + String foodType = data['foodType']; + String address = data['address']; + String community = data['community'] ?? 'N/A'; + bool dailyActive = data['dailyActive']; + String fromdate = data['date'] ?? ''; + String fromtime = data['time'] ?? ''; + String todate = data['toDate'] ?? ''; + String totime = data['toTime'] ?? ''; + GeoPoint location = data['location']; + + _shareImageFromUrl(imageUrl, venue, foodType, address, community, fromdate, + fromtime, todate, totime, dailyActive, location); +} + + +// Helper function to build circular buttons +Widget _circularButton({required IconData icon, required Function() onTap}) { + return GestureDetector( + onTap: onTap, + child: CircleAvatar( + radius: 20, + backgroundColor: AppColors.kWhiteColor.withOpacity(0.6), + child: Icon( + icon, + color: AppColors.kBlackColor, + ), + ), + ); +} + +// Helper function to build custom text widgets +Widget _customText({required String headerText, required String text}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + headerText, + style: TextStyle( + fontSize: 10.sp, + color: Colors.grey, + ), + ), + const SizedBox(height: 4.0), + Text( + text, + style: TextStyle( + fontSize: 12.sp, + color: AppColors.kBlackColor, + ), + ), + ], + ); +} + +// Helper function to build custom vertical divider +Widget _customVerticalDivider() { + return Container( + height: 20.0, + width: 1.0, + color: Colors.grey, + margin: const EdgeInsets.symmetric(horizontal: 8.0), + ); +} diff --git a/lib/widgets/maps_widget.dart b/lib/widgets/maps_widget.dart index e1245da..b2ae14f 100644 --- a/lib/widgets/maps_widget.dart +++ b/lib/widgets/maps_widget.dart @@ -109,7 +109,6 @@ class _MapsWidgetState extends State { position: LatLng(lat, lng), infoWindow: infoWindow, icon: customMarkerIcon, - ); markers.add(marker); diff --git a/lib/widgets/snackbar.dart b/lib/widgets/snackbar.dart index be12bc6..bc5ae82 100644 --- a/lib/widgets/snackbar.dart +++ b/lib/widgets/snackbar.dart @@ -3,7 +3,7 @@ import "package:flutter/material.dart"; void showSuccessSnackbar(BuildContext context, String message) { final snackBar = SnackBar( content: Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), + padding: const EdgeInsets.symmetric(vertical: 6.0), child: Text( message, style: const TextStyle( diff --git a/pubspec.lock b/pubspec.lock index 46aab24..8593bc4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: "737321f9be522620ed3794937298fb0027a48a402624fa2500f7532f94aea810" + sha256: "3dee3db3468c5f4640a4e8aa9c1e22561c298976d8c39ed2fdd456a9a3db26e1" url: "https://pub.dev" source: hosted - version: "1.3.22" + version: "1.3.32" archive: dependency: transitive description: @@ -33,6 +33,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + bloc: + dependency: "direct main" + description: + name: bloc + sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" + url: "https://pub.dev" + source: hosted + version: "8.1.4" boolean_selector: dependency: transitive description: @@ -125,10 +133,10 @@ packages: dependency: transitive description: name: cross_file - sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e + sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" url: "https://pub.dev" source: hosted - version: "0.3.3+8" + version: "0.3.4+1" crypto: dependency: transitive description: @@ -161,6 +169,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + dbus: + dependency: transitive + description: + name: dbus + sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" + url: "https://pub.dev" + source: hosted + version: "0.7.10" device_frame: dependency: transitive description: @@ -193,6 +209,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -261,26 +285,26 @@ packages: dependency: transitive description: name: firebase_auth_platform_interface - sha256: dba259dee045b706112c3d7619b01c2ad338d86cc492df52dd2073584a64de66 + sha256: "4e204f9ef43d83ac9e7a324a9317e4dd2a1ddda2aa72b67bc6cc364f0b8492dc" url: "https://pub.dev" source: hosted - version: "7.1.5" + version: "7.2.5" firebase_auth_web: dependency: transitive description: name: firebase_auth_web - sha256: d2266452698dd5f6e522408dacfa06bb7f9703b5bdd11498fce2812ded50805b + sha256: "809a2eb444d1a07c0a680b205b86d713bc7171a4b2627fd6c01cf05f2b6f93cd" url: "https://pub.dev" source: hosted - version: "5.9.4" + version: "5.11.4" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "7e049e32a9d347616edb39542cf92cd53fdb4a99fb6af0a0bff327c14cd76445" + sha256: "4aef2a23d0f3265545807d68fbc2f76a6b994ca3c778d88453b99325abd63284" url: "https://pub.dev" source: hosted - version: "2.25.4" + version: "2.30.1" firebase_core_platform_interface: dependency: transitive description: @@ -293,10 +317,34 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: "57e61d6010e253b36d38191cefd6199d7849152cdcd234b61ca290cdb278a0ba" + sha256: "67f2fcc600fc78c2f731c370a3a5e6c87ee862e3a2fba6f951eca6d5dafe5c29" + url: "https://pub.dev" + source: hosted + version: "2.16.0" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + sha256: "199fe8186a5370d1cf5ce0819191079afc305914e8f38715f5e23943940dfe2d" url: "https://pub.dev" source: hosted - version: "2.11.4" + version: "14.7.9" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: "789a3b26097e2bdad15a407dc552d6af74287b477ceb4aa6ec0d8aa967caf9e5" + url: "https://pub.dev" + source: hosted + version: "4.5.24" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: "90dc7ed885e90a24bb0e56d661d4d2b5f84429697fd2cbb9e5890a0ca370e6f4" + url: "https://pub.dev" + source: hosted + version: "3.5.18" firebase_storage: dependency: "direct main" description: @@ -309,18 +357,18 @@ packages: dependency: transitive description: name: firebase_storage_platform_interface - sha256: af99d9fcf058d6f3a591cea899cad054ded7adb21fb1788dfe65c3e223196b15 + sha256: "3d18f72b6059b20438266d5efed466a6a78dfe4366b619945d54963b61ea72bd" url: "https://pub.dev" source: hosted - version: "5.1.9" + version: "5.1.19" firebase_storage_web: dependency: transitive description: name: firebase_storage_web - sha256: "06a684016de9609928865798dece0f7d34782a15a76f5ac08bd3a8780035b0b2" + sha256: "53fcd9d5bbe5539f9d69b337e632e67658882857736aa1dcc996b34daaafb113" url: "https://pub.dev" source: hosted - version: "3.7.0" + version: "3.9.4" fixnum: dependency: transitive description: @@ -334,6 +382,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2 + url: "https://pub.dev" + source: hosted + version: "8.1.5" flutter_dotenv: dependency: "direct main" description: @@ -358,6 +414,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: f9a05409385b77b06c18f200a41c7c2711ebf7415669350bb0f8474c07bd40d1 + url: "https://pub.dev" + source: hosted + version: "17.0.0" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: "33f741ef47b5f63cc7f78fe75eeeac7e19f171ff3c3df054d84c1e38bedb6a03" + url: "https://pub.dev" + source: hosted + version: "4.0.0+1" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "340abf67df238f7f0ef58f4a26d2a83e1ab74c77ab03cd2b2d5018ac64db30b7" + url: "https://pub.dev" + source: hosted + version: "7.1.0" flutter_localizations: dependency: transitive description: flutter @@ -481,10 +561,10 @@ packages: dependency: transitive description: name: google_identity_services_web - sha256: "0c56c2c5d60d6dfaf9725f5ad4699f04749fb196ee5a70487a46ef184837ccf6" + sha256: "9482364c9f8b7bd36902572ebc3a7c2b5c8ee57a9c93e6eb5099c1a9ec5265d8" url: "https://pub.dev" source: hosted - version: "0.3.0+2" + version: "0.3.1+1" google_maps: dependency: transitive description: @@ -577,10 +657,10 @@ packages: dependency: transitive description: name: google_sign_in_web - sha256: a278ea2d01013faf341cbb093da880d0f2a552bbd1cb6ee90b5bebac9ba69d77 + sha256: fc0f14ed45ea616a6cfb4d1c7534c2221b7092cc4f29a709f0c3053cc3e821bd url: "https://pub.dev" source: hosted - version: "0.12.3+2" + version: "0.12.4" html: dependency: transitive description: @@ -813,6 +893,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d + url: "https://pub.dev" + source: hosted + version: "2.2.4" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + url: "https://pub.dev" + source: hosted + version: "2.3.2" path_provider_linux: dependency: transitive description: @@ -941,6 +1045,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: ef3489a969683c4f3d0239010cc8b7a2a46543a8d139e111c06c558875083544 + url: "https://pub.dev" + source: hosted + version: "9.0.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "0f9e4418835d1b2c3ae78fdb918251959106cefdbc4dd43526e182f80e82f6d4" + url: "https://pub.dev" + source: hosted + version: "4.0.0" shared_preferences: dependency: "direct main" description: @@ -985,10 +1105,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.3.0" shared_preferences_windows: dependency: transitive description: @@ -1066,6 +1186,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.1" + timezone: + dependency: transitive + description: + name: timezone + sha256: a6ccda4a69a442098b602c44e61a1e2b4bf6f5516e875bbf0f427d5df14745d5 + url: "https://pub.dev" + source: hosted + version: "0.9.3" typed_data: dependency: transitive description: @@ -1126,10 +1254,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b + sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.3.1" url_launcher_windows: dependency: transitive description: @@ -1166,10 +1294,42 @@ packages: dependency: transitive description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + webview_flutter: + dependency: "direct main" + description: + name: webview_flutter + sha256: "25e1b6e839e8cbfbd708abc6f85ed09d1727e24e08e08c6b8590d7c65c9a8932" + url: "https://pub.dev" + source: hosted + version: "4.7.0" + webview_flutter_android: + dependency: transitive + description: + name: webview_flutter_android + sha256: f038ee2fae73b509dde1bc9d2c5a50ca92054282de17631a9a3d515883740934 + url: "https://pub.dev" + source: hosted + version: "3.16.0" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d + url: "https://pub.dev" + source: hosted + version: "2.10.0" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: f12f8d8a99784b863e8b85e4a9a5e3cf1839d6803d2c0c3e0533a8f3c5a992a7 url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "3.13.0" win32: dependency: transitive description: @@ -1211,5 +1371,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.4 <4.0.0" - flutter: ">=3.16.6" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" diff --git a/pubspec.yaml b/pubspec.yaml index 3224c6c..69aaca5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,10 @@ name: spoonshare description: "Meals of Grace." publish_to: 'none' -version: 1.2.0 +version: 1.3.0 environment: - sdk: '>=3.2.4 <4.0.0' + sdk: '>=3.2.1 <4.0.0' dependencies: flutter: @@ -35,7 +35,14 @@ dependencies: change_app_package_name: ^1.1.0 flutter_screenutil: ^5.9.0 device_preview: 1.1.0 - + bloc: ^8.1.4 + flutter_bloc: ^8.1.5 + equatable: ^2.0.5 + firebase_messaging: 14.7.9 + flutter_local_notifications: 17.0.0 + webview_flutter: ^4.7.0 + share_plus: ^9.0.0 + path_provider: ^2.1.3 dev_dependencies: flutter_test: